docs(approvals): the resubmit discriminator states what main measured, not an invariant main falsified - #17686
Conversation
…, not an invariant main falsified `resolveRecordedContinuation` argued its `action: 'resubmit'` discriminator was "exact and structural" on three clauses. The first two hold. The third — "a resubmit opens the next round as a NEW row, so at most one such action row exists per request" — was measured false: a resubmit whose own resume strands opens no next round, the row stays `returned`, and after `restoreConsumedSuspension` re-arms the pause a second `resubmit` by the same submitter lands a SECOND `action: 'resubmit'` row. Prose only, zero behaviour change. No door narrowed, no guard touched: the comment now records the doubling, states why the read is correct anyway (it is a presence check with `limit: 1`, deciding identically on one row or two), and points at the pin that measured it. The pin's header gains one line recording that the ruling landed and chose prose, so the doubling is accepted residue; none of its assertions move. Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: Claude <noreply@anthropic.com>
…t discriminator prose Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 6 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 f28f8d1830b692e050bb09c6be7a3e681a2d0618 && git checkout f28f8d1830b692e050bb09c6be7a3e681a2d0618
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e743fb591c426fdff636220dca2054e89804b265 babc3d49a3165ff7a75b4be20ba598b5e90be91a && git checkout -B drift-repro e743fb591c426fdff636220dca2054e89804b265 && git merge --no-ff babc3d49a3165ff7a75b4be20ba598b5e90be91a
node scripts/docs-audit/affected-docs.mjs --json e743fb591c426fdff636220dca2054e89804b265 |
Fixes #17601
Clause-②: noExecutes the ruling of 2026-09-11T11:53Z on #17601 (comment
5634032720, directorseat summon #22, class-one self-adjudication) — option B, and nothing else.
Prose only. Zero behaviour change. No door narrowed, no guard touched, no
assertion in the landed pin moved.
What the comment claimed, and what
mainmeasuresresolveRecordedContinuationdiscriminates the two continuation issuers of areturnedapproval row by the presence of anaction: 'resubmit'audit row, andargued the discriminator was "exact and structural" on three clauses. PR #17613
measured the third one false:
action: 'resubmit'has exactly one writer in the file(
resubmit), and it is inserted before that resume;action row exists per request" — does not hold on the stranded-resubmit
strand. A
resubmitwhose own resume strands opens no next round, so the rowstays
returned; once an operator re-arms the pause withrestoreConsumedSuspension, a secondresubmitby the same submitter passesevery door guard and writes a second
action: 'resubmit'row.So the comment now states YES, more than one such row can exist for one
request, and names where that was measured
(
stranded-resubmit-second-door.test.ts) so the next reader does not re-derivethe chain from the same lines.
It also states why the read below it is still correct, for a reason clause ③
never needed: the read is a presence check (
limit: 1), so it decidesidentically on one row or two — the pin's MEASUREMENT C drives this resolver on
the doubled row and it still answers
resubmit. What the doubling costs is theaudit trail's one-row-per-advancement shape, not the edge this resolver picks,
and that cost is accepted residue under the ruling.
Why not the other two options
Recorded by the ruling, restated here only so a reviewer need not fetch it:
option A narrows the published
resubmitdoor (22 sites inpackages/rest) andwould leave the operator-repaired strand with no reachable exit, which is the
maintainer's floor; option C leaves a known-false invariant in the file that
states it, which is the shape that produced the card. Requiring the approval
ledger to hold one row per advancement is a separate card in A's shape
(
Clause-②: yes, with the exit route answered in the same round); #15970 is itssibling. That card is not opened by this PR.
The pin
One line added to the header docblock of
packages/plugins/plugin-approvals/src/stranded-resubmit-second-door.test.ts.Its docblock already said "When that ruling lands and the repair changes this
behaviour, THIS PIN is the thing to update" — the ruling landed and chose prose,
so the honest update records that the doubling is accepted residue. No
assertion in that file is weakened, re-worded or re-scoped; every measurement
stands exactly as it was.
Acceptance criterion, checked
The dispatched criterion was: a reader of
resolveRecordedContinuationcananswer "can more than one
action: 'resubmit'row exist for one request?" asYES from the comment alone and find the measurement without leaving the file;
and the guard counts in
approval-service.tsare unmoved.origin/main)grep -c 'assertLatestForRun'grep -c 'DUPLICATE_REQUEST'The third row is the strongest form of "no guard touched": every added and
removed line in both files is a comment line. Reproduce with
which prints nothing.
Tests
Measured at
babc3d49a.pnpm --filter @objectstack/plugin-approvals test— 44 files / 733 testspassed, 0 failed.
pnpm --filter @objectstack/plugin-approvals typecheck— green;check:test-typecheckholds at 8 files / 324 errors / 27 pinnedsignatures, i.e. unchanged, so the pin's edited docblock added no test-layer
type debt.
pnpm --filter '@objectstack/plugin-approvals...' build— green (thedependency closure, so every dist-reading gate below read a fresh build).
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackand reconciled with
--ran: 62 families derived → 59 exit 0, 3 NOT MEASURED, reconciled by the tool ratherthan by arithmetic over my own list (
--ranfedcommand :: exit code, each codecaptured before any pipe):
62 derived famil(ies) accounted for — 59 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3). The three arecheck:dual-build-cjs-loads,check:i18nandcheck:type-check-debt, eachexiting 3 = PREREQUISITE NOT MET, each because it needs the whole-repo build
closure (
turbo run build --filter='./packages/*' --filter='./packages/*/*') thatCI does before its step — ⛔ not read as passes. Also run, because the derivation
marks their roster as sitting in a directory one of my paths is in:
check-changeset-fixed(roster under.changeset),check:authz-resolver,check:error-code-casing,check:filter-alias-parity— all exit 0.eslint . --no-inline-config --format jsonover 6630 files (the population read from eslint's own JSONoutput, not estimated) — 0 errors, 0 warnings, 120s under the shared verify
lock, at
babc3d49a.No ablation or reverse-verification run, deliberately: this diff changes no
executable byte, so there is no behaviour for a mutation to flip. The measurement
this PR points at was taken in PR #17613 and is unchanged here.
Changeset
patchon@objectstack/plugin-approvals, as the ruling graded it — notskip-changeset.block inside a method body, and it does not reach the published surface. The
package publishes
files: ["dist"], and after a fresh build none ofat most one such action row,MORE THAN ONE such row,ACCEPTED RESIDUEorexact and structuralappears anywhere underdist/(0 hits each), against a positivecontrol
resolveRecordedContinuationthat is present in all four dist entries.The ruling graded the change
patchand the dispatch forbids switching routes,so the
patchchangeset ships as ordered; this reading is recorded for thegrading seat, not acted on.
Acceptance notes
noted, not filed:the sibling "at most one" assumptions elsewhere inapproval-service.tsremain a declared-unmeasured zero, exactly as theoriginal card declared it. No sweep was run here and none is claimed. Carrier
for that sweep: none — it stays declared until someone opens a card for it.
noted, not filed:the two surviving clauses are now labelled as clauses inthe comment, which makes the next falsification cheaper to record. Style, not
a defect.
🤖 Generated with Claude Code
Generated by Claude Code
Generated by Claude Code