Skip to content

docs(approvals): the resubmit discriminator states what main measured, not an invariant main falsified - #17686

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-17601-scope-resubmit-invariant
Sep 11, 2026
Merged

docs(approvals): the resubmit discriminator states what main measured, not an invariant main falsified#17686
os-sales merged 2 commits into
mainfrom
claude/issue-17601-scope-resubmit-invariant

Conversation

@claude

@claude claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #17601

Clause-②: no

Executes the ruling of 2026-09-11T11:53Z on #17601 (comment 5634032720, director
seat 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 main measures

resolveRecordedContinuation discriminates the two continuation issuers of a
returned approval row by the presence of an action: 'resubmit' audit row, and
argued the discriminator was "exact and structural" on three clauses. PR #17613
measured the third one false:

  • clauses ① and ② hold — action: 'resubmit' has exactly one writer in the file
    (resubmit), and it is inserted before that resume;
  • clause ③ — "a resubmit opens the next round as a NEW row, so at most one such
    action row exists per request" — does not hold on the stranded-resubmit
    strand. A resubmit whose own resume strands opens no next round, so the row
    stays returned; once an operator re-arms the pause with
    restoreConsumedSuspension, a second resubmit by the same submitter passes
    every 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-derive
the 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 decides
identically 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 the
audit 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 resubmit door (22 sites in packages/rest) and
would 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 its
sibling. 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 resolveRecordedContinuation can
answer "can more than one action: 'resubmit' row exist for one request?" as
YES from the comment alone and find the measurement without leaving the file;
and the guard counts in approval-service.ts are unmoved.

check before (origin/main) after
grep -c 'assertLatestForRun' 5 5
grep -c 'DUPLICATE_REQUEST' 2 2
non-comment lines in the diff 0

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

git diff -U0 origin/main -- packages/plugins/plugin-approvals/src/approval-service.ts \
  packages/plugins/plugin-approvals/src/stranded-resubmit-second-door.test.ts \
  | grep -E '^[+-]' | grep -vE '^[+][+][+]|^---' | grep -vE '^[+-][[:space:]]*(//|[*])'

which prints nothing.

Tests

Measured at babc3d49a.

  • pnpm --filter @objectstack/plugin-approvals test44 files / 733 tests
    passed
    , 0 failed.
  • pnpm --filter @objectstack/plugin-approvals typecheck — green;
    check:test-typecheck holds at 8 files / 324 errors / 27 pinned
    signatures
    , i.e. unchanged, so the pin's edited docblock added no test-layer
    type debt.
  • pnpm --filter '@objectstack/plugin-approvals...' build — green (the
    dependency closure, so every dist-reading gate below read a fresh build).
  • Gate families derived on the final diff with
    node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
    and reconciled with --ran: 62 families derived → 59 exit 0, 3 NOT MEASURED, reconciled by the tool rather
    than by arithmetic over my own list (--ran fed command :: exit code, each code
    captured before any pipe): 62 derived famil(ies) accounted for — 59 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3). The three are
    check:dual-build-cjs-loads, check:i18n and check:type-check-debt, each
    exiting 3 = PREREQUISITE NOT MET, each because it needs the whole-repo build
    closure (turbo run build --filter='./packages/*' --filter='./packages/*/*') that
    CI 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.
  • The repo-wide lint union, not a narrowing: eslint . --no-inline-config --format json over 6630 files (the population read from eslint's own JSON
    output, 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

patch on @objectstack/plugin-approvals, as the ruling graded it — not
skip-changeset.

⚠️ One reading handed up rather than acted on: the changed text is a line-comment
block inside a method body, and it does not reach the published surface. The
package publishes files: ["dist"], and after a fresh build none of at most one such action row, MORE THAN ONE such row, ACCEPTED RESIDUE or exact and structural appears anywhere under dist/ (0 hits each), against a positive
control resolveRecordedContinuation that is present in all four dist entries.
The ruling graded the change patch and the dispatch forbids switching routes,
so the patch changeset ships as ordered; this reading is recorded for the
grading seat, not acted on.

Acceptance notes

  • noted, not filed: the sibling "at most one" assumptions elsewhere in
    approval-service.ts remain a declared-unmeasured zero, exactly as the
    original 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 in
    the 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

…, 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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e743fb591c426fdff636220dca2054e89804b265packageMentionDocs.

Which tree this was computed on

This run read content/docs from f28f8d1830b692e050bb09c6be7a3e681a2d0618 — the merge of head babc3d49a3165ff7a75b4be20ba598b5e90be91a into base e743fb591c426fdff636220dca2054e89804b265, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

2 participants