fix(agent-bff): enforce the action input contract on execute - #1860
fix(agent-bff): enforce the action input contract on execute#1860Tonours wants to merge 18 commits into
Conversation
5 new issues
|
e44cf1c to
9d3486c
Compare
nbouliol
left a comment
There was a problem hiding this comment.
Solid fix: the three repro cases and the canExecute gate are each gated and tested before execute(). Main concern is that the same rule now lives in two places in a few spots — document vs runtime — which is the drift class the ticket is about.
|
Second push, from the review passes rather than from the thread comments. The validation path was invisible. An enum with no usable options silently stops being validated. The list message was ambiguous. Two things the OpenAPI text claimed more broadly than the code delivers, now stated:
Cross-SDK check on the record-picker branch, since it decides between "packed id string" and the PK's numeric type. Python emits Also: Adjacent bug found and deliberately left alone: |
nbouliol
left a comment
There was a problem hiding this comment.
Round 1 findings all addressed — the shared field-type module is the right seam. Remaining points below are minor; CI's only red is the unrelated ai-proxy LLM integration test.
| type: 'object', | ||
| // No property is required and no additional one is forbidden on purpose: a load or change hook | ||
| // rebuilds the form at call time, so the schema's static fields are an indication, not the | ||
| // contract — and the read-model cannot tell a hookless action from a legacy schema that simply |
There was a problem hiding this comment.
This dropped the closing line the comment had on main (// omitted its hooks, so "static" is never certain.), so the sentence now ends on "that simply".
nbouliol
left a comment
There was a problem hiding this comment.
All six threads from the earlier rounds are addressed in code: the primitive table, the enum-options predicate and the required-field predicate now share read-model/field-type.ts and action-form-mapper.ts, mapActionForm covers a legacy EnumList, the record-picker schema keeps its required description, and a malformed reference publishes as null. The three repros and the canExecute gate are each gated and tested before execute().
fb0f49c to
f90b2bb
Compare
f90b2bb to
2cd3169
Compare

What
/executevalidates the submittedvaluesagainst the live form before running the action.missing_required_fields.canExecutewas a form-time hint nobody re-read at execute; the gate and the hint now come from the samemissingRequiredFieldNameshelper, so they cannot disagree.invalid_action_value, list items included — the message names the failing index (index 1 to be a number).Validation runs after
setFields, on the form the change hooks rebuilt: what is checked is exactly whatexecute()would submit. A loaded default satisfies a required field.fixes PRD-1096
Why
The document promised required / enum / type and the BFF forwarded everything unvalidated, so a destructive action gated on
canExecuteran anyway.What is not checked
The OpenAPI text says so now, because the gap is invisible otherwise:
format(date-time,uuid) — aDatefield takes any string.Dropdown/RadioGroup/CheckboxGroupcarry their values inwidgetEdit.parameters.static.options, not inenums, and a searchable dropdown computes them per query — a static list is not an exhaustive one, so enforcing it would reject valid values.JsonandFilefields, by design. A File value is already a data-uri string by the time the validator runs.Observability
Every rejection logs a
Warnwith the field names — never the values, which carry customer data. An enum field the BFF had to accept without options logs too: that is a change hook returning an empty or malformedenums, and it silently disables the check.Cross-SDK check
The validator branches on
field.getReference(), so an agent that omitsreferenceon a record picker would see packed ids rejected against the PK's numeric type. Checked, not assumed: the Python agent emits it (agent-python/.../generator_action.py:76,output["reference"] = f"{collection.name}.{pk}") and so does Ruby (agent-ruby/.../generator_action.rb:110), both alongside the PK column type, exactly like Node (generator-actions.ts:145-149). No SDK gap found.Shared rules
The column-type algebra lives in one place,
read-model/field-type.ts:FieldType,normalizeFieldType,isEnumFieldType,primitiveShapeOf,enumOptionsOf. The OpenAPI document builder and the runtime validator both read it, so a primitive a newer agent introduces is unconstrained on both sides or constrained on both — never one.NumberListand the array form['Number']normalize to the same thing, so agents that differ on that encoding are judged identically.Known limitation
Breaking for JSON callers that sent coerced values: a string in a Number field is the ticket's own repro and now answers 422.
How to test
yarn workspace @forestadmin/agent-bff testandyarn workspace @forestadmin/agent-client test.test/action/action-execute-validation.test.ts:values: {}with a required field → 400,Tier: "NOPE"→ 422,Amount: "nan"→ 422, andcanExecute:false→ 400 instead of executing.Definition of Done
General
Security