Repository navigation
docs(spec): the slot judges' door lists name the parse, and why it is not one of them (#23320) - #23383
Conversation
…rement) Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…t of route (a) is recorded, not landed Route (a) (ca82975) put flowNodeValueTemplateRefusals and the text-slot judge in FlowSchema's refinement. Measured against it: registerFlow's located slot refusal and objectstack validate's expression-invalid finding are both preempted by the raw schema issue list, and the stored flow canonicalization fails a row the v18 stored pass lists as a TODO today. Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…not one of them The value-slot and text-slot judges run at registerFlow, at the validateStackExpressions rule (objectstack validate, objectstack compile, the runtime publish gate of an active save) and at every run. FlowSchema.parse runs neither, but for an end node's message through EndConfigSchema. Both module docs now say so, with the two measured reasons: a parse refusal would replace registerFlow's and the validate rule's located refusal with the parse's issue list, and would fail the row in the stored pass, whose verdict a refusal after the parse leaves unmoved. Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…ot-judges-at-parse Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
… site under its D3 entry, a text-slot site under registerFlow Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
…own call, and the measured pins as those doors' located refusals Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 142 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin aed8c24ce3397dd3039f9cfe7efb3d73804705fd && git checkout aed8c24ce3397dd3039f9cfe7efb3d73804705fd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 713f231854cfbb115179096b2af1971afff14dbc c7a71df7ecce8d8b9c6ff83bca00bfacd7cf2f4a && git checkout -B drift-repro 713f231854cfbb115179096b2af1971afff14dbc && git merge --no-ff c7a71df7ecce8d8b9c6ff83bca00bfacd7cf2f4a
node scripts/docs-audit/affected-docs.mjs --json 713f231854cfbb115179096b2af1971afff14dbc
|
Contract reviewServed-tier: Inputs read: card #23320 (body, triage ① Derived judgmentsAccept set: unchanged — right. The diff edits three docblocks and no schema, refinement, export or test. Public surface: one comment sentence published — right, and declared. Factual claims in the new comments, each read at the head:
One wording imprecision, not a false sentence: ② Semver level
③ Boundary flagsOutcome inside the triage direction: yes. The triage's direction reads "If the seat finds the parse must not run the slot judges … it says why in that doc and closes the card with the measurement." The module docs say why, with the two measured reasons and the condition for revisiting; the PR body carries the measurement (route (a) applied at Dev deviations, each answered:
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #23320
Clause-②: no
What this does
The card allowed two outcomes (triage
6152852815, unlock6156025101). Either the value-slot and text-slot judges run inFlowSchema's refinement, or the seat measures why the parse must not run them and the module docs say so. This PR is the second outcome, and it changes comments only:packages/spec/src/automation/flow-value-slot-template.ts: a new module-doc section, "The doors that run it — and whyFlowSchema.parseis not one". It names every door that runs the judge and the doors that do not:FlowSchema.parse, and sodefineStack,ObjectStackDefinitionSchema, theflowmetadata type's schema and an artifact's parse. It gives the two measured reasons, says what the gap leaves, and states the condition under which the judges can join the parse.packages/spec/src/automation/flow-text-slot-template.ts: the door list is corrected.HttpConfigSchemacomposes the judge too, and the doors areregisterFlowand thevalidateStackExpressionsrule, whichobjectstack validate,objectstack compileand the publish gate of anactivemetadata save all run. A new section covers the one text slot the parse does judge: a top-levelendnode'smessage, whose contract has no other door. It points to the reasons above.packages/spec/src/automation/flow.zod.ts: one comment sentence at the contract arm, pointing to those docs.The premise, at base
6d96721faThe premise holds. A throwaway probe used
FlowSchema.safeParseanddefineStackwith a manifest and one object:FlowSchema.parsedefineStackcreate_recordfields.title: '{record.name}'fields.titlescreenrecordId: '{x}'recordIdnotifytitletemplate'{name}'titlehttpurl: '{id}'url{{ name }}holePM hypothesis H1 holds. At base,
flow-node-config-refusals.tscalls both judges only to buildslotJudgeRefusedPaths, the exemption PR #23319 added. Neither judge adds an issue at the parse. The one exception is already there: a top-levelendnode'smessage({x}) is refused at the parse, becauseparseEndNodeConfigappliesEndConfigSchema, which composes the text-slot judge.The measurement: route (a), applied and reverted
Commit
ca829754bputs both judges inFlowSchema's refinement, as the dispatch's suggested route. It sits beside theflowNodeConfigRefusalsarm and reports each slot token once at its own path, keeping #23319's exemption and skippingend, which its contract already judges. Commitd5b704543reverts it. Both stay in this branch's history as the record. Specdist/was rebuilt, and the route's code marker was confirmed indist/, before any consumer reading.defineStack(STACK_SCHEMA_INVALID, 422, atflows.0.nodes.1.config.SLOT), and both controls are accepted.@objectstack/spec:src/automation src/conversions src/migrations src/stack, 74 files, 2877 tests passed. No spec pin depends on the parse accepting a slot token.@objectstack/service-automation: the 13 test files that pin a slot refusal: 38 of 378 tests failed, across 11 files. 37 of the failures areregisterFlow's located refusal, for examplenode 'w' (create_record) create_record field value at config.fields.total: …followed bysource:, replaced by the parse's raw issue list ([{"code":"custom","path":["nodes",1,"config","fields","total"],…}]). That happens becauseregisterFlowparses first and lets the parse's error through. The remaining failure is fix(spec,service-automation,metadata-protocol): the v18 stored preview lists every row protocol 18 refuses and no conversion rewrites #23315's pin "a braced operand parses, is not rewritten, and is reported by node":canonicalizeStoredFlowthrows instead of listing the TODO.@objectstack/lint: 19 of 6699 tests failed, across 3 files. 18 failures areobjectstack validate'sexpression-invalidfinding, preempted because the harness, likeobjectstack validateandobjectstack compile, parses the stack before the rules run. The remaining failure is a stale fixture (see the Acceptance notes).@objectstack/metadata-protocol: 238 files, 28353 tests passed. Its stored-migration pins use a stand-in canonicalizer that does not parse.@objectstack/verify: 38 files, 306 tests passed.@objectstack/cli(unit): not run under the route. A grep of the CLI's tests finds no flow that carries a slot token. The two brace hits are a dashboard filter placeholder and a connector input.Re-measured after merging
mainatdc393266f(#23356). Route (a) was applied over this branch's head28da9bd3ewithscripts/ablation-replace.mjsand checked on disk against the head plus the route's patch. Spec was rebuilt, andablation-dist-preflightfound the route's marker in 20 built files. Incanonicalize-stored-flow.test.ts,builtin/text-slot-template.test.tsandbuiltin/http-slots.test.ts, 13 of 74 tests failed:registerFlowTODO pins for text slots, including "the listing and the door agree in both directions".registerFlow's located text-slot and http-slot refusals.The source was then restored from
HEAD(blob equal to HEAD,git diff HEADempty) and spec rebuilt. The marker is absent from all 232 built files, and the tree is clean.Two earlier attempts at this leg are recorded here:
'end'anddist/spells"end". Its trap restored the source, and the third run's restore leg rebuiltdist/.Why the parse does not run them
registerFlowandobjectstack validate/objectstack compileeach report a slot token with its node, its slot's label, its config path and its remedy. A refusal at the parse comes first at all three, and replaces that report with the parse's issue list. PR fix(spec)!: a builtin config value carrying a {token} is judged for its type at the build doors (#23260) #23319 named the same reason for its exemption.AutomationEngine.canonicalizeStoredFlow, whichos migrate meta --storedruns on each flow row. When the parse refuses a row, the row is reportedfailed, and that flipsstoredMigrationClean. The result is a non-zero exit that no run of the command can clear, because no conversion rewrites a slot token (ADR-0087 D2, "refused, not converted"). A refusal made after the parse is listed as a TODO instead, and the verdict does not move. A value-slot site is listed under its step-18 D3 entryflow-value-slot-template-dialect-refused(fix(spec,service-automation,metadata-protocol): the v18 stored preview lists every row protocol 18 refuses and no conversion rewrites #23315). A text-slot site is listed underregisterFlow(fix(spec,service-automation): the v18 stored preview lists page components on the retired API source and every refusal registerFlow makes after the parse #23356, which landed while this card was in flight).Every author-facing door already refuses the token:
registerFlowand every run,objectstack validateandobjectstack compile(expression-invalid,error, gating), and the publish gate of anactivemetadata save (INVALID_METADATA, 422, pinned inruntime-authoring-gate.flow-filter-operand.test.ts). What the gap leaves: a stack built bydefineStackthat goes through none of those doors.os devandos serveload it and boot it,registerFlowrefuses the flow at boot with its located line, and the flow is not registered. The module doc states when this can change: onceregisterFlowreports a parse refusal as precisely as its own, and once the stored pass lists a slot site the parse refuses instead of failing the row.ADR-0087 and stored rows
Nothing narrows, so no stored-row disposition and no D3 registration is owed.
node scripts/check-adr-0087-registration.mjs --base origin/mainreads: "this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen)".Changeset: none (
skip-changeset)The diff is comments only. Measured on a spec build at this head:
dist/carries none of the new text (the phrase "The doors that run it" is absent, while the control phrase from an exported docblock is present indist/automation/index.d.ts), and the route-(a) code marker is absent fromdist/.@objectstack/spec'sfiles[]shipssrc/**/*.zod.ts, so the tarball'ssrc/automation/flow.zod.tschanges by one comment sentence. Comment-only changes are on the no-publish fast track.Tests and gates
All at head
c7a71df7e.@objectstack/spec:vitest run --project local src/automation src/conversions src/migrationspassed 54 files, 2284 tests.pnpm run typecheckexited 0:tsc --noEmit,check:scripts-typecheck, andcheck:test-typecheck: OK.node scripts/pm/dispatch-gates.mjs --commands(no paths) derived 71 commands, the same set as the dispatch's list. Every command was run with its exit code captured before any pipe. 70 exited 0.pnpm check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET). It needs every package'sdist/, and 10 packages are unbuilt in this worktree. Two attempts to build them queued 9 minutes each on the shared verify lock without getting it. So this one is NOT MEASURED. The diff changes no built output (see Changeset above).✓ dispatch-gates --ran: 71 derived famil(ies) accounted for — 70 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).dist/first refused with exit 3 on a stale digest after the last comment commit. They were run again after a rebuild atc7a71df7e, and all exited 0.check:api-surfacereads "public API surface + factory signatures unchanged".--re-measurere-runs every DEBT and EXEMPT package'stsc. This diff is comments inpackages/spec, whose own typecheck exited 0. CI's lint job runs it.Acceptance notes
packages/lint/src/lint-flow-patterns.test.ts, "per-iteration containment … parses under FlowSchema — the docs example is authorable", copies the example fromcontent/docs/automation/flows.mdx. The copy writesrecipients: ['{currentCase.owner}'], while the page itself writes{ dialect: 'cel', source: 'currentCase.owner' }. The test passes only because the parse leaves value slots to their judge. Owner: none.failed. Examples today: a top-levelendmessagecarrying{x}, a typed-key token (spec(automation): the #21898 token carve-out (builtinValueJudged/carriesInterpolationToken) outlived every interpolation — a built-in config value carrying a{token}registers past its type, then fails every run #23260), an undeclared builtin config key.os migrate meta --storedthen prints that such rows "keep reading canonically through the chain, so nothing is broken". For a flow, the boot refuses that row. Owner: the stored-preview lane (domain:specseat 2).defineStackpins are not added. They belong to outcome (a). Under this outcome they would pin the gap itself. The pins that hold the reasons already exist:registerFlow's located refusal inservice-automation,objectstack validate's in@objectstack/lint, and the stored listing incanonicalize-stored-flow.test.ts. Each went red under route (a).Generated by Claude Code