Skip to content

finding: check-engine-double-contract.mjs hardcodes its own ledger size in 5 prose sites that go stale on every --write, with nothing to catch them #9915

Description

@os-steve

Observation only — filed unassigned, not fixed. Found while resolving the PR #9712 / #9680 merge conflict, where regenerating the pinned ledger moved the row count and made these numbers wrong in the same commit.

What

scripts/check-engine-double-contract.mjs states its own census size as a literal in explanatory prose. scripts/engine-double-contract.pinned.json is a generated artifact whose size changes whenever anyone runs --write — which the gate's own failure messages instruct authors to do — so every one of these sentences silently goes stale on a routine, sanctioned action.

After the routine regeneration in PR #9712 (308 rows / 319 doubles becomes 310 rows / 321 doubles), five sites are wrong:

line text
1481 folding 308 generated rows in would
1501 doubles across 308 (file, verb) pairs, so 11 pairs carry more than one
1540 for one verb (measured: 10 of 308 rows do)
1581 a missing artifact reports 308 separate "not in the
2918 a fresh checkout reporting 308 problems for one missing file

(Line 1501 also carries 319 pinned doubles. Its derived claim — "11 pairs carry more than one" — happens to survive, because 321 − 310 is still 11. That it stayed true by coincidence rather than by construction is the point.)

A sixth site, 308 entries today, was corrected in PR #9712 only because it sat inside the paragraph that PR was already rewriting for a different reason. The other five were deliberately left alone as out of scope.

Why it is worth a card rather than a shrug

This is the same defect class the gate itself now guards against, one level up. #9680's finding was "the pinned count is a printed number, not a checked one". These are measured numbers in prose that nothing checks — and they sit in the comment block whose stated job is to justify a merge-blocking ratchet to the next reader.

There is direct evidence of the cost: the 269 commits figure in this same header shipped wrong by ~12x, was caught only by a human reading it, and needed its own correction (#9878). A reader who spots 308 disagreeing with a run that prints 310 has no way to know which other numbers in the header are still trustworthy, so a small drift discredits sound measurements sitting next to it.

Notes toward a fix (not a decision)

Options seen while working in the file, in rough order of cost:

  1. Rephrase to remove the magnitude — "the generated rows", "a fresh checkout reports one error, not one per row". Cheapest; loses the sense of scale that makes the DEBT-ledger comparison argument land.
  2. Round and mark the numbers as illustrative ("~300 rows"), so drift cannot make them false.
  3. Derive them: have --write regenerate a small marked block in the header, the way the ledger itself is generated. Most faithful, and the most machinery for a comment.

Whether these numbers are load-bearing enough to be worth any of that is a judgement call, which is why this is filed rather than fixed.

Scope

Prose only. No invariant, no criterion, no exit code, no output string is affected — the gate's behaviour is correct at every one of these sites. check-engine-double-contract is green at PR #9712's head with all five present.

Generated by Claude Code

Activity

  1. claude commented on Aug 19, 2026

    @claude
    Contributor

    Claim: PM loop round 14
    Session: session_01XqDQYVU5smx29ts9pAErja
    Branch: claude/issue-9915-ledger-size-prose
    Worktree: objectstack-issue-9915
    Domain: domain:devx
    File surface: scripts/check-engine-double-contract.mjs (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (node scripts/pm/dispatch-gates.mjs --tier scripts/check-engine-double-contract.mjs run this round: "no path-derived mandate … This line is a FLOOR, never a clearance"; content limb judged no — the card is prose-only and states "no invariant affected")
    Clause-②: no
    Serial constraints cleared: swept all open PRs for any touching engine-double-contract — none. The predecessor on this exact file, PR #9712 (#9680), is MERGED — it is what unblocked this card, and its conflict resolution already corrected a 6th prose site (the one that sat in the paragraph being rewritten), so the remaining count may be lower than the card's 5. Re-derive rather than trusting it.


    Generated by Claude Code

  2. claude commented on Aug 19, 2026

    @claude
    Contributor
    {
      "issue": 9915,
      "status": "done",
      "branch": "claude/issue-9915-ledger-size-prose",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9944",
      "premise_still_valid": true,
      "summary": "Re-derived the population rather than trusting the card's 5: the file states its own pinned-ledger size at 7 prose sites (13 figure occurrences), not 5. All five sites the card listed are still stale (308/319 against a live 310/321) — the count did not go down. Two further sites (L206, L214) are the same defect latent: correct today, wrong on the next --write; L214 is the very site PR #9712 refreshed, which is why refreshing is not a fix. One figure was never right at all: `so 11 pairs carry more than one` — 11 is the excess of doubles over rows, while the count of rows pinning more than one is 10, as the paragraph 40 lines below already said. Six sites lost the number entirely (replaced by claims the ratchets guarantee, or by a pointer to the ledger); one keeps a literal because it is genuinely underivable, anchored as a dated measurement. Clause-② stays `no` and this is proved mechanically, not asserted.",
      "tests": "All at final commit e7ebebd66 (git rev-parse --short HEAD), no commits after. COMMENT-ONLY PROOF: both revisions emitted through the TypeScript compiler with removeComments:true and compared byte-for-byte -> `emitted bytes: base=80651 head=80651` / `IDENTICAL - the change is comment-only`. (A raw ts.createScanner token diff was tried first and reported `DIFFERENT`; it desyncs on regex-literal ambiguity without parser context, so the full-parser emit is the instrument I trust and report.) GATES, re-derived from the actual changeset by `node scripts/pm/dispatch-gates.mjs` with NO paths passed (it takes the change set from the merge base itself; it placed 3 families and reported `Model tier - no path-derived mandate`): (1) `pnpm check:engine-double-contract` -> exit 0, verdict lines `check-engine-double-contract: OK - 321 pinned, 133 in the DEBT ledger, 2 exempt.` and `check-engine-double-contract: 310 (file, verb) row(s) held by the RETAINED ledger - a pin that leaves names itself.`, self-test line `OK  self-test: separates engine doubles from driver doubles ...`; (2) `pnpm check:cross-package-test-inputs` -> exit 0, `All 33 self-test cases passed.` / `OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.`; (3) `pnpm check:nul-bytes` -> exit 0, `check-nul-bytes: OK (scanned 6312 text file(s) -- 6312 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).` plus my own out-of-gate sweep `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` -> no hits. Exit codes were captured before any pipe (redirect-then-capture), and each verdict above is the gate's own printed line, not a bare $?. No ablation applies: the change compiles to identical bytes, so there is no behaviour to ablate.",
      "h1_prose_load_bearing": "CONFIRMED load-bearing, not incidental asides. Every one of the 7 sites sits inside prose whose stated job is to justify the merge-blocking ratchet to the next reader: the header sections are literally titled `## RETAINED, and why the pinned set is enumerated rather than counted (#9680)` and `## Why an identity ledger and not a count, priced rather than assumed`, and the paragraph carrying two of the figures opens `The maintenance objection to an enumeration is real but was measured and is small`. The L214 figures are the closing move of that cost argument (`same order of magnitude` as the DEBT ledger already maintained = the cost is one already being paid). The four function-doc sites answer `why counted per file rather than as membership`, i.e. why the ledger has the shape the ratchet enforces. The file supplies its own corroboration: the ⛔ block at L186-190 records that an earlier figure in this same header shipped describing a ~9% sample and had to be re-measured — proof that a reader did consult these numbers and act on them. So the card's framing holds: a gate that catches a lost pin was misstating how many pins it holds, inside the argument for keeping it merge-blocking.",
      "h2_figure_census": "107 figure occurrences across 84 comment lines (method: extract every numeric literal from comment lines, then hand-classify by whether it describes a population this file measures). 34 are self-describing. Of those, 13 describe the PINNED LEDGER and are invalidated by --write — that is the card's population, and all 13 were changed. The remaining ~21 describe populations --write does not touch and were left alone: the DISCOVERED/PINNED sizes at L642-643 (`250 doubles this gate discovers` reads 487 today, `82 PINNED` reads 321 — the worst drift in the file, ~1.9x and ~3.9x, filed as #9943), the CallExpression census at L474-489 (`310 of them` plus an 8-bucket histogram, `93`, `217`), L2992's `117` (accurate today), and the corpus measurements at L569-580/L617. The balance of the 107 are historical or dated (the #9680 319->318 incident record, the #8058 audit, the churn paragraph explicitly dated `in the month to 2026-08-18`, and the ⛔ block that quotes a WRONG figure on purpose — untouched) or non-measuring (exit codes, list ordinals, HTTP statuses, the copyright year, `turbo 2.10.7`). A second, mechanical cross-check ran every array length in the pinned ledger against the file's prose; it independently flagged L206 and L214, the two latent sites the card missed, and correctly flagged L474 as a false positive (see H3).",
      "h3_site_dispositions": "NONE became run-time-derived, and the reason is structural rather than a shortfall: these are COMMENTS, and nothing renders a comment. Deriving one at run time would require making --write rewrite this script's own source — machinery, and a behaviour change to --write that would flip Clause-② to yes. The available structural move for a comment is instead to rest the claim on something the ratchets already guarantee, which is what six sites do. DELETED (6 sites): L214 `310 entries today`/`135` -> the shape claim alone (same file format, same order of magnitude, same reconciliation shape), with an explicit note that the sizes are deliberately not copied here because --write moves one of them; L1479-1481 `135`/`308 generated rows` -> `a census that ALREADY OUTNUMBERS them and, by the opposite polarities below, can only outnumber them further` — monotone-true because the very next sentence states pinned is grow-only and the baseline shrink-only; L1500-1501 `319`/`308`/`11` -> points at the ledger rows reading `\"pinned\"` above 1 as the live list; L1540 `10 of 308` -> same pointer; L1581 `308` -> `one \"not in the ledger\" error per census row -- hundreds of them, and growing, since this ledger is grow-only` (a floor on a grow-only ledger cannot go stale); L2918 `308` -> `one problem per census row`. MUST STAY LITERAL (1 site, and this is a real finding as H3 predicted): L206's `309 of 310 (file, verb) rows agreeing` is NOT a ledger size — it is the agreement rate of an external proxy scan against the ledger at a past HEAD. The script cannot see it: it has the ledger, but not the independent scan the number compares against, and re-deriving it would be performing a new measurement. It stays literal and is anchored as a dated measurement (`calibrated on 2026-08-19 ... That calibration is a dated measurement, not a standing property`), matching the idiom the paragraph above it already uses. Its denominator was also dropped (`every (file, verb) row but ONE`) so --write cannot falsify what remains. BONUS CORRECTION: dropping `135` removed a second inaccuracy — the gate prints `133 in the DEBT ledger, 2 exempt`, so 135 was the baseline file's total entries, not what the DEBT ledger carries.",
      "h4_sibling_sweep": "NOT clean — one sibling has the identical shape, named but NOT fixed per your ruling. Method: enumerate scripts/ files carrying a --write/--update/--fix regenerator mode (11 found), resolve the artifact each one writes, compute every array length in that artifact, and grep the script's own comment lines for those values. Result: `scripts/check-role-word.mjs` hardcodes its regenerated baseline's size in two prose sites — L133 `check-role-word: OK (43 baselined file(s), no new occurrences).` and L144 `ledger untouched — `43 problem(s)`, exit 1` — and `scripts/role-word-baseline.json` holds exactly 43 entries today, so it is accurate-but-latent, the same state L206/L214 were in here. The other regenerators are clean on this test: check-error-status-conformance (baseline 34), check-i18n-coverage (12), check-query-options-erasure-ratchet (29/17), check-slot-lookup-ratchet (25) — none repeat their own size in prose. Caveat on the method's reach: it can only catch a figure that still EQUALS the live size, so it finds latent sites and misses already-stale ones (it would not have found this card's own 308s). A sweep for already-stale siblings needs the per-file hand classification used in H2.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #9943 (unassigned, labels finding/tooling/domain:devx): same file states its DISCOVERED and PINNED population sizes in present tense — `250 doubles this gate discovers` is 487 today, `82 PINNED doubles` is 321. Not --write-invalidated, so outside this card's fence; and correcting it needs a decision, not a prose edit, because re-running the measurement could change the criterion's conclusion. Also records the L474-489 CallExpression census (`310 of them`) as apparently drifted (an independent approximate re-scan counted 341, with two buckets reproducing exactly), and flags that its 310 coincides with the ledger's 310 rows while being a different population — a standing conflation hazard.",
        "NOT filed, named for you to card per your H4 ruling: scripts/check-role-word.mjs hardcodes its own regenerated baseline size (43) in two prose sites, currently accurate and therefore latent."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions