diff --git a/hindsight-api-slim/hindsight_api/api/http.py b/hindsight-api-slim/hindsight_api/api/http.py index 006988d5..f6171e8d 100644 --- a/hindsight-api-slim/hindsight_api/api/http.py +++ b/hindsight-api-slim/hindsight_api/api/http.py @@ -1661,7 +1661,9 @@ class BankTemplateConfig(BaseModel): disposition_skepticism: int | None = Field(default=None, ge=1, le=5, description="Skepticism trait (1-5)") disposition_literalism: int | None = Field(default=None, ge=1, le=5, description="Literalism trait (1-5)") disposition_empathy: int | None = Field(default=None, ge=1, le=5, description="Empathy trait (1-5)") - entity_labels: list[str] | None = Field(default=None, description="Controlled vocabulary for entity labels") + entity_labels: list[dict[str, Any]] | None = Field( + default=None, description="Controlled vocabulary for entity labels" + ) entities_allow_free_form: bool | None = Field( default=None, description="Allow entities outside the label vocabulary" ) diff --git a/hindsight-api-slim/hindsight_api/config_resolver.py b/hindsight-api-slim/hindsight_api/config_resolver.py index 770952fa..79cc83ad 100644 --- a/hindsight-api-slim/hindsight_api/config_resolver.py +++ b/hindsight-api-slim/hindsight_api/config_resolver.py @@ -239,6 +239,15 @@ class ConfigResolver: logger.warning(f"Failed to check permissions for bank {bank_id}: {e}") # Continue without permission check (fail open for backward compatibility) + # Validate entity_labels structure + if "entity_labels" in normalized_updates and normalized_updates["entity_labels"] is not None: + from .engine.retain.entity_labels import parse_entity_labels + + try: + parse_entity_labels(normalized_updates["entity_labels"]) + except Exception as e: + raise ValueError(f"Invalid entity_labels format: {e}") + # Validate retain_strategies: reject empty string keys if "retain_strategies" in normalized_updates and normalized_updates["retain_strategies"]: empty_keys = [k for k in normalized_updates["retain_strategies"] if not str(k).strip()] diff --git a/hindsight-clients/go/api/openapi.yaml b/hindsight-clients/go/api/openapi.yaml index bc7db3aa..75133d84 100644 --- a/hindsight-clients/go/api/openapi.yaml +++ b/hindsight-clients/go/api/openapi.yaml @@ -3570,7 +3570,7 @@ components: type: integer entity_labels: items: - type: string + additionalProperties: {} nullable: true type: array entities_allow_free_form: diff --git a/hindsight-clients/go/model_bank_template_config.go b/hindsight-clients/go/model_bank_template_config.go index 080c548f..40d4a438 100644 --- a/hindsight-clients/go/model_bank_template_config.go +++ b/hindsight-clients/go/model_bank_template_config.go @@ -29,7 +29,7 @@ type BankTemplateConfig struct { DispositionSkepticism NullableInt32 `json:"disposition_skepticism,omitempty"` DispositionLiteralism NullableInt32 `json:"disposition_literalism,omitempty"` DispositionEmpathy NullableInt32 `json:"disposition_empathy,omitempty"` - EntityLabels []string `json:"entity_labels,omitempty"` + EntityLabels []map[string]interface{} `json:"entity_labels,omitempty"` EntitiesAllowFreeForm NullableBool `json:"entities_allow_free_form,omitempty"` } @@ -471,9 +471,9 @@ func (o *BankTemplateConfig) UnsetDispositionEmpathy() { } // GetEntityLabels returns the EntityLabels field value if set, zero value otherwise (both if not set or set to explicit null). -func (o *BankTemplateConfig) GetEntityLabels() []string { +func (o *BankTemplateConfig) GetEntityLabels() []map[string]interface{} { if o == nil { - var ret []string + var ret []map[string]interface{} return ret } return o.EntityLabels @@ -482,7 +482,7 @@ func (o *BankTemplateConfig) GetEntityLabels() []string { // GetEntityLabelsOk returns a tuple with the EntityLabels field value if set, nil otherwise // and a boolean to check if the value has been set. // NOTE: If the value is an explicit nil, `nil, true` will be returned -func (o *BankTemplateConfig) GetEntityLabelsOk() ([]string, bool) { +func (o *BankTemplateConfig) GetEntityLabelsOk() ([]map[string]interface{}, bool) { if o == nil || IsNil(o.EntityLabels) { return nil, false } @@ -498,8 +498,8 @@ func (o *BankTemplateConfig) HasEntityLabels() bool { return false } -// SetEntityLabels gets a reference to the given []string and assigns it to the EntityLabels field. -func (o *BankTemplateConfig) SetEntityLabels(v []string) { +// SetEntityLabels gets a reference to the given []map[string]interface{} and assigns it to the EntityLabels field. +func (o *BankTemplateConfig) SetEntityLabels(v []map[string]interface{}) { o.EntityLabels = v } diff --git a/hindsight-clients/python/hindsight_client_api/models/bank_template_config.py b/hindsight-clients/python/hindsight_client_api/models/bank_template_config.py index c5ebfd9e..324c7a14 100644 --- a/hindsight-clients/python/hindsight_client_api/models/bank_template_config.py +++ b/hindsight-clients/python/hindsight_client_api/models/bank_template_config.py @@ -37,7 +37,7 @@ class BankTemplateConfig(BaseModel): disposition_skepticism: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None disposition_literalism: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None disposition_empathy: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None - entity_labels: Optional[List[StrictStr]] = None + entity_labels: Optional[List[Dict[str, Any]]] = None entities_allow_free_form: Optional[StrictBool] = None __properties: ClassVar[List[str]] = ["reflect_mission", "retain_mission", "retain_extraction_mode", "retain_custom_instructions", "retain_chunk_size", "enable_observations", "observations_mission", "disposition_skepticism", "disposition_literalism", "disposition_empathy", "entity_labels", "entities_allow_free_form"] diff --git a/hindsight-clients/typescript/generated/types.gen.ts b/hindsight-clients/typescript/generated/types.gen.ts index 2d9ab0fa..3220af60 100644 --- a/hindsight-clients/typescript/generated/types.gen.ts +++ b/hindsight-clients/typescript/generated/types.gen.ts @@ -459,7 +459,9 @@ export type BankTemplateConfig = { * * Controlled vocabulary for entity labels */ - entity_labels?: Array | null; + entity_labels?: Array<{ + [key: string]: unknown; + }> | null; /** * Entities Allow Free Form * diff --git a/hindsight-docs/static/openapi.json b/hindsight-docs/static/openapi.json index 40eca055..740988d9 100644 --- a/hindsight-docs/static/openapi.json +++ b/hindsight-docs/static/openapi.json @@ -5275,7 +5275,8 @@ "anyOf": [ { "items": { - "type": "string" + "additionalProperties": true, + "type": "object" }, "type": "array" }, diff --git a/scripts/generate-clients.sh b/scripts/generate-clients.sh index 88be4687..06e22b44 100755 --- a/scripts/generate-clients.sh +++ b/scripts/generate-clients.sh @@ -108,6 +108,9 @@ cd "$PYTHON_CLIENT_DIR" # Run openapi-generator via Docker (pinned version for reproducibility) # Use --platform linux/amd64 to ensure identical output on both macOS (arm64) and Linux CI (amd64) # Use --user to match current user's UID/GID so generated files are writable +# Note: the generator may exit non-zero due to a known bug writing +# README_onlypackage.mustache, but all API/model files are generated +# before that step, so we allow the failure and verify files below. docker run --rm \ --platform linux/amd64 \ --user "$(id -u):$(id -g)" \ @@ -118,7 +121,13 @@ docker run --rm \ -i /local/openapi.json \ -g python \ -o /local/out \ - -c /local/config.yaml + -c /local/config.yaml || true + +# Verify critical generated files exist +if [ ! -f "$PYTHON_CLIENT_DIR/hindsight_client_api/api_client.py" ]; then + echo "❌ Error: Python client generation failed - api_client.py not found" + exit 1 +fi echo "Organizing generated files..." diff --git a/skills/hindsight-docs/references/openapi.json b/skills/hindsight-docs/references/openapi.json index 40eca055..740988d9 100644 --- a/skills/hindsight-docs/references/openapi.json +++ b/skills/hindsight-docs/references/openapi.json @@ -5275,7 +5275,8 @@ "anyOf": [ { "items": { - "type": "string" + "additionalProperties": true, + "type": "object" }, "type": "array" },