fix(config): validate entity_labels structure on PATCH (#902)
* test: add regression tests for #874 and #894 Add tests for None event_date in fact extraction (AttributeError fix) and for _register_profile skipping .env overwrite with short config keys. * fix(config): validate entity_labels structure on PATCH (#891) Config PATCH accepted bare strings in entity_labels values without validation, causing silent failures at retain time. Now validates via parse_entity_labels() before writing to DB, and fixes the BankTemplateConfig type from list[str] to list[dict[str, Any]]. * fix(scripts): handle Python client generator README crash gracefully The openapi-generator sometimes crashes writing README_onlypackage.mustache. Allow the failure with || true since all API/model files are generated before that step, and add a verification check for api_client.py. * chore: regenerate docs skill openapi.json
This commit is contained in:
parent
f659bb17c4
commit
7e23f8e149
9 changed files with 37 additions and 13 deletions
|
|
@ -1661,7 +1661,9 @@ class BankTemplateConfig(BaseModel):
|
||||||
disposition_skepticism: int | None = Field(default=None, ge=1, le=5, description="Skepticism trait (1-5)")
|
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_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)")
|
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(
|
entities_allow_free_form: bool | None = Field(
|
||||||
default=None, description="Allow entities outside the label vocabulary"
|
default=None, description="Allow entities outside the label vocabulary"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,15 @@ class ConfigResolver:
|
||||||
logger.warning(f"Failed to check permissions for bank {bank_id}: {e}")
|
logger.warning(f"Failed to check permissions for bank {bank_id}: {e}")
|
||||||
# Continue without permission check (fail open for backward compatibility)
|
# 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
|
# Validate retain_strategies: reject empty string keys
|
||||||
if "retain_strategies" in normalized_updates and normalized_updates["retain_strategies"]:
|
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()]
|
empty_keys = [k for k in normalized_updates["retain_strategies"] if not str(k).strip()]
|
||||||
|
|
|
||||||
|
|
@ -3570,7 +3570,7 @@ components:
|
||||||
type: integer
|
type: integer
|
||||||
entity_labels:
|
entity_labels:
|
||||||
items:
|
items:
|
||||||
type: string
|
additionalProperties: {}
|
||||||
nullable: true
|
nullable: true
|
||||||
type: array
|
type: array
|
||||||
entities_allow_free_form:
|
entities_allow_free_form:
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ type BankTemplateConfig struct {
|
||||||
DispositionSkepticism NullableInt32 `json:"disposition_skepticism,omitempty"`
|
DispositionSkepticism NullableInt32 `json:"disposition_skepticism,omitempty"`
|
||||||
DispositionLiteralism NullableInt32 `json:"disposition_literalism,omitempty"`
|
DispositionLiteralism NullableInt32 `json:"disposition_literalism,omitempty"`
|
||||||
DispositionEmpathy NullableInt32 `json:"disposition_empathy,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"`
|
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).
|
// 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 {
|
if o == nil {
|
||||||
var ret []string
|
var ret []map[string]interface{}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
return o.EntityLabels
|
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
|
// GetEntityLabelsOk returns a tuple with the EntityLabels field value if set, nil otherwise
|
||||||
// and a boolean to check if the value has been set.
|
// and a boolean to check if the value has been set.
|
||||||
// NOTE: If the value is an explicit nil, `nil, true` will be returned
|
// 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) {
|
if o == nil || IsNil(o.EntityLabels) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
@ -498,8 +498,8 @@ func (o *BankTemplateConfig) HasEntityLabels() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEntityLabels gets a reference to the given []string and assigns it to the EntityLabels field.
|
// SetEntityLabels gets a reference to the given []map[string]interface{} and assigns it to the EntityLabels field.
|
||||||
func (o *BankTemplateConfig) SetEntityLabels(v []string) {
|
func (o *BankTemplateConfig) SetEntityLabels(v []map[string]interface{}) {
|
||||||
o.EntityLabels = v
|
o.EntityLabels = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class BankTemplateConfig(BaseModel):
|
||||||
disposition_skepticism: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None
|
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_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
|
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
|
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"]
|
__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"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,9 @@ export type BankTemplateConfig = {
|
||||||
*
|
*
|
||||||
* Controlled vocabulary for entity labels
|
* Controlled vocabulary for entity labels
|
||||||
*/
|
*/
|
||||||
entity_labels?: Array<string> | null;
|
entity_labels?: Array<{
|
||||||
|
[key: string]: unknown;
|
||||||
|
}> | null;
|
||||||
/**
|
/**
|
||||||
* Entities Allow Free Form
|
* Entities Allow Free Form
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -5275,7 +5275,8 @@
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ cd "$PYTHON_CLIENT_DIR"
|
||||||
# Run openapi-generator via Docker (pinned version for reproducibility)
|
# 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 --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
|
# 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 \
|
docker run --rm \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64 \
|
||||||
--user "$(id -u):$(id -g)" \
|
--user "$(id -u):$(id -g)" \
|
||||||
|
|
@ -118,7 +121,13 @@ docker run --rm \
|
||||||
-i /local/openapi.json \
|
-i /local/openapi.json \
|
||||||
-g python \
|
-g python \
|
||||||
-o /local/out \
|
-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..."
|
echo "Organizing generated files..."
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5275,7 +5275,8 @@
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue