Repository navigation
[finding] a shrink-only ratchet whose self-test asserts its own entries are PRESENT can never reach zero — one instance found and fixed, the class unswept #11694
Description
Found while landing #11596 slice 2 (PR #11692). The instance is repaired there; the class is not
swept and that is what this card is for. Filed unassigned.
The shape
A shrink-only ratchet exists to be driven to zero. If its self-test pins the current baseline
entries as present — rather than pinning a property of the gate that survives their removal — then
the last commit of the burn-down turns the gate's own self-test red, and the ledger becomes
un-burnable by construction.
Measured instance, scripts/check-cli-test-child-env.mjs:
t(`the enumerated population contains ${named.split('/').pop()}`,
live.refusal === null && live.findings.some((f) => f.file === named));findings holds a file only while it still leaks. Two cases were written this way to pin
#11441's measurement that the derivation really covers those files — a good intention, and the
comment above them says exactly that in words ("they are members of the population"). But the
spelling asserts a transient fact about the tree, not a property of the gate. Repairing the two
files reported 2 of 54 case(s) failed, and the failure message read "the derivation lost this
file" when the file was merely clean.
The repair in #11692 re-spells it as membership of the scanned population — walked, and
classified as a spawner — which is the claim that was intended and the one that survives the repair.
The gate's rule is byte-identical; the case count is unchanged; and it still discriminates (narrowing
the population root reds it again).
Why this is worth a sweep rather than a note
The two failure modes are indistinguishable on the day the pin is written and diverge only at the
moment the ratchet finally succeeds — i.e. the pin is a booby trap that arms itself years before it
fires, at the exact moment someone is trying to do the right thing. A seat hitting it mid-burn-down
sees a red gate and the most likely reactions are both wrong: leave an entry in the ledger to keep
the self-test green, or weaken the gate.
What a sweep would check
For each shrink-only / ratchet gate under scripts/, does its selfTest() (or its test twin) assert
anything that is only true while the baseline is non-empty? Candidates worth reading, chosen
because they are the ratchets this lane has touched recently and is not an exhaustive list —
⛔ derive the population rather than working from it:
scripts/check-settings-bind-window.mjs—⚠️ most urgent: itsKNOWN_PRE_BIND_READSreaches
empty when fix(mcp): read the stdio transport's localization after the settings engine bind #11623 lands (AuthPlugin reads theauthsettings namespace in the pre-bind window — storedsys_settingrows are ignored at boot under the shippedos serveorder #11579 removed one entry, MCP stdio transport pins its localization to the manifest defaults — the settings read happens instart(), inside the pre-bind window under every composition order #11580 removes the other), so if it carries
this shape it fires imminently. That PR's dev has been asked to verify the empty-ledger path.scripts/check-cli-test-child-env.mjs— the instance, already repaired.scripts/check-ratchet-remedy-authority.mjs,check-type-check-coverage(TEST_DEBT),
check-query-options-erasure,check-slot-lookup,check-i18n-coverage.
⭐ The discriminating question for each, and it is answerable by reading the assertion alone:
would this case still pass if the baseline were empty? If not, it is pinned to the debt rather
than to the gate.
Related
- PR test(cli): route the last three serve-* e2e spawners through childEnv(), 3 -> 0 on the child-env ratchet #11692 — the instance and its repair, with the reverse-verification.
- finding: #11341's bare-
...process.envworklist is two files short, and one of them spawns a realos serve#11441 — the measurement the two mis-spelled cases were pinning. - burn down the
cli-test-child-envbaseline: 18 spawned children in 17packages/cli/testfiles still built from the whole ofprocess.env#11596 — the burn-down that surfaced it by actually reaching zero. - fix(mcp): read the stdio transport's localization after the settings engine bind #11623 — the next ledger due to hit empty.
Activity
os-zhuang commentedon Aug 24, 2026
The most urgent candidate is retired — measured, and it hands the sweep a better discriminator
scripts/check-settings-bind-window.mjsdoes not carry this shape. Its ledger reached emptyhalf an hour after this card was filed (#11623 merged the last of the two entries out;
KNOWN_PRE_BIND_READSis now[]), and the gate answered:⭐ And the reason is the useful part, from the seat that measured it:
That is a stronger property than the question this card originally posed. So the sweep gets a second,
sharper discriminator — and it is the one to check first, because it is answerable by reading the
self-test's fixture defaults rather than reasoning about every case:
the ledger to
[], the gate cannot acquire the anti-shrink shape — the empty case is covered onevery run, not just on the day the ratchet finally succeeds.
pinned to the debt rather than to the gate.
Also worth carrying into the sweep, from the same measurement: an empty ledger must not be
readable as an empty population. This gate keeps them apart — its verdict line still reports the
scanned population (
68 plugin unit(s)) and the derived provider, and its!providerbranch refusesoutright rather than reporting green over nothing. A gate that prints only
0 ledgeredand stopswould be indistinguishable from a gate that scanned nothing.
Remaining candidates, unchanged:
check-ratchet-remedy-authority,check-type-check-coverage(TEST_DEBT),
check-query-options-erasure,check-slot-lookup,check-i18n-coverage— and ⛔ derivethe population rather than working from that list.
Generated by Claude Code
claude commentedon Aug 25, 2026
Concentrated triage batch:
finding→pm:queue+domain:devx, Task, M — the class sweep the fixed instance calls for: enumerate every shrink-only ratchet's self-test and repair the ones that pin baseline ENTRIES as present (which makes the ledger un-burnable by construction) into pins on gate properties that survive entry removal. Sweep rules apply: enumerate the population first, per-item before/after list in the PR body, zero changes outside the N items.Generated by Claude Code
os-steve commentedon Aug 25, 2026
Claim: devx PM seat, session
session_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-11694-selftest-present-ratchet-class.Dispatching now. Label swapped
pm:queue→pm:dispatched.Generated by Claude Code
os-steve commentedon Aug 25, 2026
Claim released —
pm:dispatched→pm:queue, assignee cleared. This card is free to take.The dispatch at 05:05Z was killed by a container restart ~14 minutes in. Verified by content, not assumed: branch
claude/issue-11694-selftest-present-ratchet-classexists onoriginbut is 0 commits ahead ofmain, and its worktree is clean. Nothing was implemented and nothing is salvageable — start from the card, not from the branch.Nothing about the card's own analysis changed — the two-step discriminator in the first comment (fixture defaults the ledger to
[]? then: would each case pass on an empty baseline?) still stands and is still the cheap way in.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
Claim: PM loop round R1
Session:
session_01UjM2ia8Av1v5NqfqQEQmC6Branch:
claude/issue-11694-selftest-present-ratchet-classWorktree:
objectstack-issue-11694Domain:
domain:devxFile surface:
scripts/check-*.mjs— the self-test bodies of the ratchet gates the sweep derives, and nothing else (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus— derived live, no path-derived mandate, so sonnet is the floor. Opus is deliberate: the deliverable is a judgment applied per gate ("is this assertion pinned to the debt or to the gate?"), and a sweep that gets that judgment wrong edits working gates.Clause-②: no — gate self-tests; no contract accept/reject behaviour, no public surface.
Serial constraints cleared: no in-flight claim on any
scripts/check-*.mjsself-test. #11525 / PR #12030 is live inscripts/but confined tocheck-objectql-double-limit.mjs(a new file), its baseline,lint.ymland rootpackage.json— no overlap with existing gates' self-tests. #11952 is confined toscripts/objectui-range.mjs; #11620 to the turbo build graph; #11942 tocontent/docs/**.The sweep's own candidate list names gates that other queued cards also edit. Those cards are not dispatched this round and go into the seat's hot-file serial queue behind this one:
check-ratchet-remedy-authoritycheck-slot-lookupcheck-i18n-coveragefold-or-serial, answered: serial, do not fold. Gate ① fails — this card repairs self-tests pinned to baseline entries, while each sibling repairs a different defect in the gate's rule (#11935: declares no path; #11681: typed ≠ complete; #11671: untranslated leaf drift). Same files, different defects, different fixes.
⛔ Consequence for the dev: if the sweep's derived population includes those three gates, repair only the self-test shape this card is about. Do not touch their rules — that work is claimed by cards that have not been dispatched yet, and editing it here would land two defects' repairs in one PR with only one of them reviewed.
Re-claim after a container death, not a fresh dispatch
The previous session claimed this at 05:04Z and released it at 05:24Z — its dev died to a container restart ~14 min in. Release verified by content: the branch exists on
origin, 0 commits ahead ofmain, clean worktree. Nothing was implemented. Reusing the branch name, as that comment invited.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
os-dev-report
{ "issue": 11694, "status": "done", "branch": "claude/issue-11694-selftest-present-ratchet-class", "pr": "https://github.com/objectstack-ai/objectstack/pull/12050", "premise_still_valid": true, "derived_population_size": 33, "repaired_count": 1, "files_changed": ["scripts/check-driver-memory-census.mjs"], "final_commit": "650639399", "summary": "Derived the population mechanically rather than from the card's candidate list: 133 scripts/ files ship a selfTest(); 33 of those are shrink-only ratchets (roster, JSON baseline, or per-file numeric ceilings). Only 3 read the real ledger from INSIDE selfTest(), and exactly one carried the shape: check-driver-memory-census.mjs asserted `ruledConsumers.length === 2`, which contradicts the gate's own LIVE invariant (a migrated consumer must lose its entry) and made the #5499/#5704 retirement ledger un-burnable. Re-spelled as the ruling-authority claim it was actually protecting, on a witness pair so it is not vacuous at zero rows, plus a shrink-only RULED_CEILING for the anti-growth half. None of the card's six named candidates carries the shape, including its 'most urgent' one; the three gates locked by #11935/#11681/#11671 are all clean, so not one byte of them changed. GitHub body sanitizer: NO truncation found in #11694's body — it is complete from 'The shape' through 'Related', and the code block's arrow function and ${...} interpolation both survived (the '/> entities are API transport encoding, not mutation).", "tests": "All at 650639399. Gate families derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (stderr: \"derived from the tree of 'objectstack-ai/objectstack' at commit 650639399\", \"--repo ... checked against this checkout's 'origin' remote — it holds\", \"1 path(s) vs merge base 1e79aa4f8\"). All 8 named families run, exit captured before any pipe, all EXIT=0: check:agent-test-spelling, check:cross-package-test-inputs, check:driver-memory-census, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs. Plus check:nul-bytes EXIT=0 and — because --residue flags them as the two artifact-roster gates whose roster sits under scripts/, where a silent verdict 'is not evidence in EITHER direction' — check:pm-dispatch-gates EXIT=0 (`✓ dispatch-gates self-test: 579 cases pass.`) and check:where-matcher EXIT=0 (`✓ where-matcher conformance holds: 298 matcher(s) discovered`). Gate verdict lines quoted, not $?. REPO-WIDE lint run, NOT narrowed: `eslint . --no-inline-config --format json` EXIT=0, 5066 files, 0 errors, 0 warnings. REVERSE VERIFICATION, 3 legs, each a trap-guarded (EXIT INT TERM) mutation of the real ledger with the mutation confirmed on disk by a ruledConsumers-length/marker read before the reading was taken, and the tree re-read clean after each — no build step involved (the gate is plain node over source, nothing resolves through dist/): LEG A, ruledConsumers 2->1 (the shrink the programme exists to perform): SELFTEST_EXIT=1 BEFORE the repair (`x self-test: the ledger rules exactly the consumers #5704/#6664 named`), SELFTEST_EXIT=0 with 0 failure lines AFTER — this is the defect, measured. LEG B, 2->3 with the third arm pointing at a file the scan really binds so only the ceiling can fire: SELFTEST_EXIT=1 (`x self-test: the ruled set never grows past what those rulings named (2 arm(s); it may shrink to empty)`). LEG C, rulingMarkers -> ['#0']: SELFTEST_EXIT=1 (`x self-test: every ruled consumer is admitted by one of this programme's two rulings`). Legs B and C are the repaired pin still discriminating. All three re-measured after the second commit changed the witness bytes. Production gate green: `check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live...`. No changeset; skip-changeset applied and READ BACK (labels now [\"size/s\",\"skip-changeset\"]).", "deviations": [ "skip-changeset label: the additive POST /issues/12050/labels endpoint returned HTTP 403 for this session ('GitHub access is not enabled for this session') — the same 403 that blocked direct REST reads all task. Used the declared fallback: read labels (['size/s']) -> union -> whole-set write -> read back ([\"size/s\",\"skip-changeset\"]). Declaring the fallback and its read-back per protocol.", "Duplicate pre-check for the filed finding used MCP search_issues (2 calls) rather than the REST list endpoints, because REST is 403 for this session. Bounded and declared." ], "open_questions": [ { "question": "The shape's habitat is NOT confined to scripts/ — measured, not swept. Widen the sweep to packages/**?", "options": [ "A — file a follow-up card to sweep the out-of-scripts ratchets (packages/spec/scripts/check-dual-source-exports.ts, check-exported-any.ts, liveness/check-liveness.mts, packages/lint/scripts/check-doc-formula-expressions.mjs) AND the two test twins (packages/spec/scripts/check-generated-ledger.test.ts, check-react-blocks-declaration-parity.test.ts)", "B — widen this PR now", "C — record and stop" ], "recommendation": "A. Zone 2 said widening is your decision with my measurement, so B is out. C is out because there is a concrete lead, not just a possibility: check-generated-ledger.test.ts pins ledger bucket counts as exactly 1 (`expect(output).toContain('1 explicit manual-only generators')` and `'1 needing an external input'`) — the same species as the repaired instance, in a test twin rather than a --self-test. A separate card keeps this PR's 'zero changes outside the N items' intact and gives the twin population its own reviewed sweep." } ], "out_of_scope_findings": [ "filed as #12052: check:slot-lookup is a shrink-only ratchet with NO self-test and no --self-test in its wiring; check-self-test-wired.mjs enforces only the superset ('a script that SHIPS one must run it') so the absence is invisible to it. Carries Blocked-by: #11681, whose rule change would rewrite any self-test written today." ] }Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
ACCEPT — PR #12050 · the sweep did its job by shrinking, not by finding more
Reviewer of record: devx seat,
session_01UjM2ia8Av1v5NqfqQEQmC6(seat #6023). Dispatched and collected in the same round.Checklist result
main✓ ·Fixes #11694✓scripts/check-driver-memory-census.mjs. Read fromget_files.in_progress. Holding until they converge.skip-changesetcorrect, read back after the size labeler's write.⭐⭐ The population was derived, and the derivation is the deliverable
Zone 1 forbade working from the card's candidate list. It did not:
⭐ Not one of the card's six named candidates carries it — including the one the card called "most urgent". The card's own analysis was wrong about where the instances were, and the only way to discover that was to derive the population instead of checking the list. This is the second time this round a card's candidate list has been falsified by the dev that took it seriously.
⭐⭐ And the three gates I locked behind this card — #11935, #11681, #11671 — are all clean, so not one byte of them changed. That constraint was the riskiest part of the dispatch (a sweep with a derived population running into files claimed by other cards), and it held.
The one instance, and why the repair is better than the defect deserved
check-driver-memory-census.mjsassertedruledConsumers.length === 2under the label "the ledger rules exactly the consumers #5704/#6664 named". That reads correctly on the day it is written and means something else: it holds only while both arms are still ledgered, so the gate's own LIVE invariant — a migrated consumer must lose its entry in the same PR — cannot be obeyed without turning the self-test red, with a message claiming the ledger lost an entry it must have. The retirement programme it belongs to exists to shrink that set to zero, and an equality on its size made that unreachable by construction. Textbook instance of the class.The repair splits the pin into what it was actually protecting:
validateLedgerShaperequires some marker; it does not require the ruling to be real). This claim survives the set shrinking to empty.RULED_CEILING, lowerable by any PR that migrates an arm away, raisable only by a new maintainer ruling.⭐ The detail that shows the author understood the class rather than the instance:
[].every(...)is a pass that proves nothing, so the positive claim is carried on a witness pair — a synthetic ruled row keeps the positive half exercised once the real ledger reaches zero, and a synthetic unruled row gives the case teeth at that point. ⇒ The repair does not merely avoid the original defect; it avoids becoming vacuous at exactly the moment the burn-down succeeds, which is the second-order version of the same trap. The idiom is cited to an in-repo precedent rather than invented.The header's self-description was updated to match ("holds the ruled set to the rulings that admitted it — a claim that survives the set shrinking to empty"). A gate whose prose still described the old assertion would be the failure this lane rates worse than no gate at all.
Evidence
Three reverse-verification legs, each a trap-guarded mutation of the real ledger with the mutation confirmed on disk before the reading was taken, and the tree re-read clean after:
ruledConsumers2→1 (the shrink the programme exists to perform):SELFTEST_EXIT=1before the repair,SELFTEST_EXIT=0with zero failure lines after. ⇒ The defect, measured rather than argued.Legs B and C are the repaired pin still discriminating in both of its directions. All three re-measured after the second commit changed the witness bytes — ⛔ not inherited from the first run.
⭐ Repo-wide lint was actually run, not narrowed:
eslint . --no-inline-config --format json→ exit 0 over 5066 files. And the two artifact-roster gates whose roster sits underscripts/were run on the dev's own initiative, because--residueflags them as gates where a silent verdict "is not evidence in EITHER direction".Out-of-scope finding — verified filed
#12052:⚠️ Note #11681 was moved to
check:slot-lookupis a shrink-only ratchet with no self-test at all, andcheck-self-test-wired.mjsenforces only the superset ("a script that ships one must run it"), so the absence is invisible to it. Correctly filed rather than fixed, and correctly carriesBlocked-by: #11681— whose rule change would rewrite any self-test written today.pm:retriageby this seat an hour later on a routing dissent, so #12052's blocker is itself unsettled; recorded on both.Ruling on the open question — option A: file the follow-up, do not widen this PR
The dev measured that the shape's habitat is not confined to
scripts/and named six out-of-tree carriers: four ratchets (packages/spec/scripts/check-dual-source-exports.ts,check-exported-any.ts,liveness/check-liveness.mts,packages/lint/scripts/check-doc-formula-expressions.mjs) and two test twins (check-generated-ledger.test.ts,check-react-blocks-declaration-parity.test.ts).A, for the reason the dev gives and one it could not act on:
check-generated-ledger.test.tspins ledger bucket counts as exactly 1 — the same species as the repaired instance, in a test twin rather than a--self-test. ⇒ Option C (record and stop) would discard a measured lead.packages/spec/**, which has exactly one owner (domain:spec) regardless of who found them, and onlypackages/lint/scripts/check-doc-formula-expressions.mjsis this lane's. Filing one cross-lane card would hand a spec-owned sweep to whoever picked it up. ⇒ Filed as a lane-split follow-up next, with the measurement carried verbatim.Generated by Claude Code