The corrected messages in #13858 now state a runtime behaviour that nothing pins, so they can go stale again with every gate green — which is the drift packages/runtime/src/sandbox/undeclared-field-write-driver-split.integration.test.ts exists to prevent for the two paths it does cover.
What is covered today
That file pins two things, on both driver families:
an L2 BODY-written undeclared key — a hook body mutating its own ctx.input;a CALLER-supplied undeclared key — a payload the caller hands engine.insert / engine.update directly.
What is not
Neither block covers the two shapes #13858 measured and rewrote prose for:
- a
ctx.api.object(x).update({...}) carrying an undeclared key, issued from inside a hook body and from inside an action body (validate-hook-body-writes.ts's ctx.api branch, validate-action-body-writes.ts); - a flow
create_record / update_record node's fields map (validate-flow-node-writes.ts).
The card itself said so: the claim was "derived from the call shape plus the existing caller-payload pin", and the triage made a reproduction a hard gate for exactly that reason. Both were then measured — real QuickJS sandbox, real bodies through hookBodyRunnerFactory / actionBodyRunnerFactory, real ObjectQL, the real AutomationEngine with the real builtin CRUD node executors, real driver-sql (better-sqlite3) and real driver-memory — and all six runs answered INVALID_FIELD / 400, Unknown field 'stagee' on object 'deal', with nothing stored on either family and no shadow column on the schemaless one.
That measurement ran as a scratch harness and was deleted: the dispatch fenced committed packages/runtime/** changes out of #13858 and told the seat to report a warranted pin rather than add one. This is that report.
Why it is worth a pin rather than a note
Three lint messages — one of them on a rule that gates (validate-flow-node-writes is severity: 'error') — now assert "INVALID_FIELD / 400, identically on every driver/datasource, before any statement is built". The corroborating pin is about a different call shape, so if the door ever moves for the ctx.api or flow-node paths specifically, every gate stays green and three author-facing sentences quietly become wrong again. That is the same failure mode #13858 was filed to fix, one layer down.
Suggested shape
Extend undeclared-field-write-driver-split.integration.test.ts with two it.each(FAMILIES) blocks in its existing idiom, or add a sibling file:
- a hook body and an action body whose
ctx.api write carries the typo, asserting the escaping SandboxError carries code: 'INVALID_FIELD' / status: 400 and that the target row is untouched with no shadow column; - a flow
create_record and update_record node, asserting the run fails, the node's error names the refusal, and nothing is stored.
The flow half needs service-automation plus both drivers in one package, which packages/cli already declares; the hook/action half fits in packages/runtime beside the existing file.
Assertions should follow the ADR-0112 envelope rule — codeandstatus, never a bare toThrow().
Found while implementing #13858 (PR pending). Not fixed there: out of that card's declared scope.
Generated by Claude Code
The corrected messages in #13858 now state a runtime behaviour that nothing pins, so they can go stale again with every gate green — which is the drift
packages/runtime/src/sandbox/undeclared-field-write-driver-split.integration.test.tsexists to prevent for the two paths it does cover.What is covered today
That file pins two things, on both driver families:
an L2 BODY-written undeclared key— a hook body mutating its ownctx.input;a CALLER-supplied undeclared key— a payload the caller handsengine.insert/engine.updatedirectly.What is not
Neither block covers the two shapes #13858 measured and rewrote prose for:
ctx.api.object(x).update({...})carrying an undeclared key, issued from inside a hook body and from inside an action body (validate-hook-body-writes.ts'sctx.apibranch,validate-action-body-writes.ts);create_record/update_recordnode'sfieldsmap (validate-flow-node-writes.ts).The card itself said so: the claim was "derived from the call shape plus the existing caller-payload pin", and the triage made a reproduction a hard gate for exactly that reason. Both were then measured — real QuickJS sandbox, real bodies through
hookBodyRunnerFactory/actionBodyRunnerFactory, realObjectQL, the realAutomationEnginewith the real builtin CRUD node executors, realdriver-sql(better-sqlite3) and realdriver-memory— and all six runs answeredINVALID_FIELD/ 400,Unknown field 'stagee' on object 'deal', with nothing stored on either family and no shadow column on the schemaless one.That measurement ran as a scratch harness and was deleted: the dispatch fenced committed
packages/runtime/**changes out of #13858 and told the seat to report a warranted pin rather than add one. This is that report.Why it is worth a pin rather than a note
Three lint messages — one of them on a rule that gates (
validate-flow-node-writesisseverity: 'error') — now assert "INVALID_FIELD / 400, identically on every driver/datasource, before any statement is built". The corroborating pin is about a different call shape, so if the door ever moves for thectx.apior flow-node paths specifically, every gate stays green and three author-facing sentences quietly become wrong again. That is the same failure mode #13858 was filed to fix, one layer down.Suggested shape
Extend
undeclared-field-write-driver-split.integration.test.tswith twoit.each(FAMILIES)blocks in its existing idiom, or add a sibling file:ctx.apiwrite carries the typo, asserting the escapingSandboxErrorcarriescode: 'INVALID_FIELD'/status: 400and that the target row is untouched with no shadow column;create_recordandupdate_recordnode, asserting the run fails, the node's error names the refusal, and nothing is stored.The flow half needs
service-automationplus both drivers in one package, whichpackages/clialready declares; the hook/action half fits inpackages/runtimebeside the existing file.Assertions should follow the ADR-0112 envelope rule —
codeandstatus, never a baretoThrow().Found while implementing #13858 (PR pending). Not fixed there: out of that card's declared scope.
Generated by Claude Code