The `control-flow.zod.ts` row said `validateControlFlow` and the schema
"do not fight" and "meet at exactly one seam". #16134 (landed `21aabbc7b`)
made both false: `FlowSchema`'s `superRefine` refuses a duplicate node id
over one node-id space spanning the top-level `nodes[]` and every region
body, which is a structural fact decided by the schema, so the clean
key-vs-structure division the row drew is exactly what that ruling removed.
The row now follows the canonical docblock as #16835 left it (`c3ce76c210`)
rather than restating the boundary independently: two seams, the #4001
region-slot `safeParse` and the #16134 node-id space, handing off at
`MAX_REGION_DEPTH` (32) where `analyzeRegion` becomes the only refusal of a
within-region duplicate. Named artefacts only, no schedule.
Prose inside one existing row. No row added or removed and no `Class` cell
touched, so no site count moves: `check:strictness-ledger` stays green and
still reports 444 sites measured, 1 authorable strip site left.
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #16948
Clause-②: no — the diff is one row of prose in
docs/audits/2026-07-unknown-key-strictness-ledger.md. No schema, no accept set, no published surface, noClasscell. The seat declared this at dispatch time before the work existed; this line is the dev's own reading of the diff that actually exists, and it agrees.Authored in Claude Code session
session_01MkQhmuuJAVDjmeWNixwDDH, dispatched by thedomain:specexecution seat.What changed
One line, one table row: the
control-flow.zod.tsrow of theautomation/file-level triage table. It restated — as paraphrase, not quotation — the two sentences #16835 retired from the canonical docblock:validateControlFlowis a sibling guard, not a key gate, and the two do not fight", glossed as "the schema answers key membership, which no structural check can decide";Both are false since #16134 (landed
21aabbc7b, PR #16825).FlowSchema'ssuperRefinerefuses a duplicate node id — a structural fact decided by the schema — over one node-id space spanning the top-levelnodes[]and every region body. The clean key-vs-structure division the row drew is precisely what that ruling removed.The row now says: two seams, the #4001 region-slot
safeParseand the #16134 node-id space, handing off atMAX_REGION_DEPTH(32), past whichanalyzeRegionis the only refusal of a within-region duplicate and a cross-region collision is not judged at all.The wording is copied, not invented
Deliberately, on the triage seat's instruction and for the reason the card gives — the ledger and the source drifting into two descriptions of one boundary is the defect being repaired. The row follows the docblock as PR #16947 left it (
c3ce76c210,packages/spec/src/automation/control-flow.zod.ts), which was itself checked againstFlowSchema'ssuperRefineheader andanalyzeRegion's header.Artefacts named, no schedule promised (
#16948's second red line): the landed commit21aabbc7b, the docblock commitc3ce76c210, and the pin that holds the boundary —packages/spec/src/automation/flow.test.ts's case "the seam at MAX_REGION_DEPTH: a within-region duplicate at nesting 32 is refused at parse; at nesting 33 the parse accepts and validateControlFlow refuses it in analyzeRegion's own line".Premises falsified first, at this head
Every item of the card was treated as a premise. All of them held at
59db8a02cb. Probes are case-insensitive and count OCCURRENCES (grep -oi ... | wc -l), not lines:A near-miss the probes caught:
"do not overlap"reads 3 in the same file, and none of the three is this claim — all three are the ledger's own prose about git merging non-overlapping rows. A count is not a reading until you look at what it matched.Source side, verified rather than inherited:
FlowSchema's node-idsuperRefineatpackages/spec/src/automation/flow.zod.ts;MAX_REGION_DEPTH = 32atpackages/spec/src/automation/control-flow.zod.ts;analyzeRegion'sduplicate node idline in the same file. PR #16947 has landed (c3ce76c210), so the docblock this row follows is the version onmain, not a version still under review.The annotation PR #17331 landed on the coarse
shared/row about an hour before this branch was cut is untouched: the diff contains no line mentioning it, and the file is still 1893 lines.No counts move, and that is measured
check:strictness-ledgernames the file and reports its own verdict:Identical numbers before and after. That green is a measurement, not a vacuous pass — proven by a one-shot ablation from the committed fix, both legs, with the mutation shown to reach disk:
authorable->probably fine, the exact stringstrictness-ledger-doc.test.tspins as refused) and the gate still exited 0 with unchanged numbers. That is not a gate defect:buildCountsresolves aClasscell only foropenFiles, i.e. files withstrip > 0, andcontrol-flow.zod.tshas none — it went strict at #4001. So a Class-cell mutation on a fully-strict row is invisible to the counts path by design. The anchor was moved to the File cell, which is read, and the ablation above is that run. Reported rather than quietly re-run.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackderives 18 families for this change set. All 18 run, every one recording its exit code before any pipe:All 18 exit 0. One needed a prerequisite first:
check:doc-formula-expressionsexited 3 — its ownPREREQUISITE NOT METbanner, i.e. NOT MEASURED, not red — until@objectstack/formulaand@objectstack/lintwere built; it exits 0 after. The build ran underscripts/pm/os-verify-lock.sh, which printedVERDICT command-exit 0.Left to CI, declared rather than skipped: the 48 artifact-roster families, the 11 declared-wide-population families, the 1 path-scheduled CI job (
Test Core) and the always-runs tail — each outside the derived total, and none of them narrowable by these paths. The 14 changeset-conditional families do not apply (see below).pnpm lintis not owed and cannot judge this diff. Measured from eslint's own configuration rather than assumed:eslint --no-inline-config --format jsonon the one changed file reports 1 file entry, 0 errors, and the warning "File ignored because no matching configuration was supplied" — there is no config matching.mdin this repo. No.ts/.jsfile is touched, so no untouched file's verdict can move either.No changeset —
skip-changesetNothing published moves. Measured, with a positive control: this diff contains 0 paths under
packages/, and no package'sfiles[]mentionsaudits;docs/audits/sits at the repo root, outside every package tarball. The two most recent precedents on this same ledger, PR #16766 and PR #16792, both merged carryingskip-changesetand no changeset file.skip-changesetis correct exactly when nothing published moves, and an empty-frontmatter changeset is refused for new files here (#5471). The measurement above decides it; the conflict is not resolved silently.Acceptance notes — out of scope, reported not folded
Filed as #17384 —
packages/spec/src/automation/control-flow.test.tsrestates the same two retired sentences, once in a block comment and once in adescribetitle (the half that reaches a test report and agrep). After this PR it is the last hand-written restatement in the tree. It was not folded in because the bounded in-place-fix exemption requires no new verification surface, and renaming adescribepullspackages/spec's whole test and typecheck face into a diff that otherwise has none. Carrier named on the card. Its dup check closed on 77 opendocumentationcards with lit and dark controls.Noted, not filed —
packages/spec/CHANGELOG.mdalso carries the old wording. It is correct and must stay: RELEASE-OWNED, past tense, recording what the docblock said before #16947 changed it. Carrier: none needed, nothing to do.Noted, not filed — the
buildCountsreading above, that aClasscell on a zero-strip row is never resolved, meanscheck:strictness-ledger's "every Class cell resolves" line speaks only for rows with open strip sites. This is the gate working as documented in its own comment, not a defect, so no card. Carrier if it ever matters: thedomain:specseat's #4001 strictness campaign, which owns both the gate and the ledger.Generated by Claude Code