Skip to content

[finding] plugin-rest's TEST_DEBT per-code tally lists 147 while declaring it was counted at 153 — 6 unaccounted, with no remainder clause #10821

Description

@claude

Found while implementing #10722 (the note-vs-field consistency check). Not fixed there: #10722's rule
deliberately reads only the tier itemisation and leaves per-code tallies alone, and this sits in the
half it does not read.

The entry

TEST_DEBT['@objectstack/plugin-rest'] in scripts/check-type-check-coverage.mjs opens:

TS2835 x67 (NodeNext extensions), TS7006 x57, TS2554 x13, TS2550 x10
  (composition as counted at 153; not re-tallied by class at the 155 below).

67 + 57 + 13 + 10 = 147. The note declares the tally was taken at 153. The gap is 6, and the
note carries no clause accounting for it.

Why that reads as a gap rather than an abridgement

Every other per-code tally in either ledger that lists only its leading classes says so explicitly, in
the same sentence:

  • plugin-approvals — "TS2339 x296, TS2550 x20, TS2345 x16, TS18048 x10, plus 6 singletons (TS2554 x2, TS1470, TS2352, TS2353, TS6133)" — 296+20+16+10+6 = 348 = its errors.
  • objectql — "code-tier 300 (… plus 8 singletons); config-tier 9; noise 46" — sums to its errors: 355.

plugin-rest has no such clause, so from the ledger alone it cannot be told whether 6 classes are
simply unlisted or the tally is 6 short. Both readings are plausible; neither is checkable without a
measurement.

Why it is worth recording rather than repairing on sight

It cannot be resolved from the ledger — it needs check:type-check-debt --re-measure against a built
workspace closure
, which also settles the second question the note raises: the tally says 153 while
the entry records errors: 155, so the composition describes a pile two smaller than the field even on
its own account.

⛔ Do NOT "repair" this by editing the digits to make them sum. That is the exact failure mode #10722
is about — prose adjusted to agree with itself while nobody re-measured. The repair is a re-tally from
tsc, with the remainder either listed or admitted as a gap ("an invented attribution is worse than an
admitted gap" is this ledger's own rule).

Scope note

#10722's check abstains here by design, with evidence: per-code tallies are partial by construction, and
this very entry is the worked example in its docblock for why summing them would red an accurate entry.
Closing this finding is a data repair on one entry, not a change to that rule.


Generated by Claude Code


Generated by Claude Code

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    domain:devxDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 21, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 21, 2026
  3. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    Triage first-touch: promoted to queue, type Task. The repair is a re-measure, not an edit: run check:type-check-debt --re-measure for @objectstack/plugin-rest against a built workspace closure, then write the tally the measurement produces — remainder listed as singletons or admitted as a gap, per the ledger's own rule. It also settles the 153-vs-errors: 155 discrepancy the note carries on its own account. ⛔ Binding constraint from the card: do NOT adjust digits to make them sum without re-measuring — that is the exact #10722 failure mode. Size/model suggestion: S mechanical but needs the build, so M container.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 24, 2026
  5. self-assigned this
    on Aug 24, 2026
  6. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator

    Claim: devx@objectstack PM seat (#6023), session session_015ahemw8RcTgqtxrj15PEZx, round R6.
    Branch: claude/issue-10821-plugin-rest-tally-retally
    Model tier: opus — derived live, not recalled: node scripts/pm/dispatch-gates.mjs --tier scripts/check-type-check-coverage.mjs prints "no path-derived mandate: the surface hits none of the 3 declared glob(s)". Clause ② judged from content and not reached — a ledger data repair changes no contract accept/reject behaviour and widens no public surface.

    Zone 1 — rulings (binding)

    1. ⛔ Do not make the digits sum. The card says this and it is the whole point: adjusting prose until it agrees with itself, with nothing re-measured, is the exact failure [finding] nothing reads a DEBT/TEST_DEBT note, and --lower rewrites only the digits — so the advertised way to lower an entry desynchronises its prose by construction #10722 exists to catch. The repair is a re-tally from tsc.
    2. Re-measure against a built workspace closure. pnpm check:type-check-debt --re-measure needs the dependency closure built first. This is proven feasible in this container — [finding] @objectstack/trigger-record-change TEST_DEBT records 9 while tsc reports 0 — graduation candidate the ratchet deliberately leaves alone #11204's dev did it today: turbo run build --filter=./packages/* --filter=./packages/*/* → 70/70, then the re-measure at ~275s. Budget for it; do not skip it and reason from the ledger instead.
    3. Answer BOTH questions the note raises, not just the first. (a) the per-code tally sums to 147 while declaring 153; (b) the note says 153 while the entry's own errors: field records 155. A repair that fixes the sum and leaves the 153-vs-155 discrepancy unexplained has done half the job.
    4. If the remainder cannot be attributed, admit it as a gap. This ledger's own rule: "an invented attribution is worse than an admitted gap." Follow the sibling form — plugin-approvals writes "plus 6 singletons (TS2554 x2, TS1470, …)", objectql writes tiers plus singletons. Match the house spelling.
    5. ⛔ Touch only the @objectstack/plugin-rest entry. [finding] @objectstack/plugin-auth's TEST_DEBT records 109 where tsc now reports 98 — an 11-error surplus opened by PR #10614 #10615 is a live card on @objectstack/plugin-auth's entry in the same object and is NOT yours — leave it alone, even if your re-measure shows it is also wrong. Note it in out_of_scope_findings instead.

    Zone 2 — mechanism assumptions to falsify before you rely on them

    • ⚠️ The card's arithmetic is the card's, not a measurement. 67+57+13+10 = 147 is checkable from the ledger text, but whether 147/153/155 is the current truth is not — the note is dated and the tree has moved. Re-derive all three numbers before deciding which one is wrong. It is entirely possible that none of them matches today's tsc and the repair is a full re-tally rather than a remainder clause.
    • errors: 155 is a ceiling this gate enforces. Before you touch it, establish whether your re-measured number is above or below it — the gate is shrink-only, and a number that moves the wrong way is a red main, not a data repair. If tsc now reports more than 155, STOP and report: that is a regression card, not this card.
    • "the tally was taken at 153" may describe a different measurement basis than errors: — e.g. one counted before a NodeNext pass, or one counting files rather than diagnostics. Read the surrounding ledger prose and the sibling entries before assuming the two numbers were ever meant to agree.
    • ⚠️ A re-measure that reports 0 for this package would be a graduation, not a tally repair — and a zero-hit reading needs a positive control before you believe it (a misconfigured invocation reports 0 too). [finding] @objectstack/trigger-record-change TEST_DEBT records 9 while tsc reports 0 — graduation candidate the ratchet deliberately leaves alone #11204's dev has a worked example of that control on the card.

    Zone 3 — suggested routes (not binding)


    Generated by Claude Code

  7. claude commented on Aug 24, 2026

    @claude
    ContributorAuthor
    {
      "issue": 10821,
      "status": "done",
      "branch": "claude/issue-10821-plugin-rest-tally-retally",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11496",
      "premise_still_valid": true,
      "summary": "The defect was real and confirmed verbatim at base 951b025e4: the entry's per-code tally read TS2835 x67, TS7006 x57, TS2554 x13, TS2550 x10 = 147, declared 'composition as counted at 153', and sat above an errors: 155 field, with no remainder clause. ONE NAMING CORRECTION: the entry is keyed '@objectstack/rest', not '@objectstack/plugin-rest' -- no such package exists (packages/rest, name @objectstack/rest). The card inherited that name from the COMPOSITION docblock, which also spells it 'plugin-rest'; the entry is identified unambiguously by its content, so the premise holds. Re-tallied from one tsc run against a built workspace closure: TS2835 x72, TS7006 x49, TS2550 x16, TS2554 x14, plus 4 singletons (TS2769 x2, TS2345, TS6133) = 155 EXACTLY, the recorded number. No digit was adjusted to make the old numbers agree: the old shape moved in BOTH directions against the new measurement (TS7006 fell 57 -> 49 while TS2835 rose 67 -> 72, TS2550 10 -> 16, TS2554 13 -> 14) and two classes it never named are in the pile, so no rescale of it could have been right. BOTH questions answered: (a) the 6 unaccounted at 153 are retired UNATTRIBUTED -- the per-class breakdown at e8db1a230 was never retained, and this ledger's rule is that an invented attribution is worse than an admitted gap; (b) 153-vs-155 was never one measurement disagreeing with itself -- 153 was measured at e8db1a230, RECORDED was later lowered onto a DIFFERENT measurement (155 at 55da611, #6939), and the composition was never re-taken across that move, which is exactly what the 'not re-tallied by class' clause was saying. tsc reports exactly 155 today, so errors: 155 is UNCHANGED -- not above the shrink-only ceiling (no regression card), not below it (no lowering, no graduation). This is a note-only change; no ratchet number moves in either direction. ONE DECLARED CHANGE BEYOND THE ENTRY, in the same file: the COMPOSITION docblock (#10722) used THIS entry as its worked example for why per-code tallies are not summed ('plugin-rest's tally sums to 147 and says so in the same breath'). Re-tallying the entry makes that sentence false about the tree, in a comment whose job is to stop someone 'fixing' the rule into summing tallies -- the docblock's own stated false-red hazard. The RULE IS UNTOUCHED; only its worked example is marked retired and the entry named correctly. Flagging the scope tension honestly: #10615 holds @objectstack/plugin-auth's entry in this same file, so the in-place-fix exemption's 'no other claim on this file' condition does not strictly hold -- I judged that leaving a statement MY OWN edit falsifies is worse, and the two regions are ~850 lines apart. PM may split it out if that call is wrong.",
      "tests": "All on final commit 9fef66af (worktree clean, git diff --quiet HEAD exit 0). [1] CLOSURE BUILD, under the shared verify lock: `turbo run build --filter=./packages/* --filter=./packages/*/* --concurrency=4` -> 'Tasks: 70 successful, 70 total ... Time: 6m58.413s', wrapper verdict 'os-verify-lock: VERDICT command-exit 0 - held the lock 421s (7m01s) - waited 0s'. [2] THE MEASUREMENT, under the lock on the final tree: `pnpm check:type-check-debt` -> 'check-type-check-coverage --re-measure: OK - 32 ledger entr(ies) re-measured in 282.1s, 1897 raw tsc error(s) total, none above its recorded number.' plus 'os-verify-lock: VERDICT command-exit 0 - held the lock 283s (4m43s)'. @objectstack/rest does NOT appear in that run's surplus list, so tsc reports exactly its recorded 155. [3] HISTOGRAM PROVENANCE -- it comes from the gate's OWN measurement world, not a re-derived one. The script was temporarily instrumented (6 lines) to dump raw tsc output per project, `--re-measure` was run, the dump for TESTDEBT__packages_rest was histogrammed with the gate's own counting rule (TSC_ERROR_LINE, TS6059 dropped -- 0 dropped here) giving 155 lines summing to 155 by code, and the script was then RESTORED BYTE-IDENTICALLY before the real edit was written: sha256 5a795e74fe162fbf03807c80f6cba1546208c1bded83b34277b8d75de5fee8ec before and after, `git diff --quiet` exit 0, `grep -c __DUMP_LABEL` -> 0. No instrumentation is in the diff. [4] POSITIVE CONTROL for the reading (the PM's zero-hit warning): the reading is 155, not 0; it is spread over 64 named packages/rest/src/*.test.ts files; and it independently equals a number recorded by a different party at a different commit. A misconfigured invocation could not produce that coincidence. tscErrorCount also refuses to return 0 when tsc exits non-zero with no parseable diagnostics, so the silent-zero path is closed by the gate itself. [5] EDIT LANDED ON DISK, proved before believing any downstream result: both edits were made by anchored python replacements that ASSERT exactly one anchor match, then verified by grepping for the injected text (1 hit each) AND the removed text (0 hits each) -- not by the editor's exit code. `node --check` clean. [6] GATE FLOOR, derived not recalled: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no path list passed; it derived 1 changed path three-dot against merge base 951b025e4, and confirmed the --repo assertion against this checkout's origin). All 8 derived families run green on 9fef66af, each quoted from its OWN printed verdict line with the exit code captured before any pipe: check:type-check-debt (above); check:type-check-coverage 'OK - 65/78 workspace packages type-checked...' with self-test '47 semantic + 59 observation + 29 re-measure + 28 built-closure + 19 auto-lowering case(s) hold'; check:cross-package-test-inputs 'OK: 14 package(s) read outside themselves, all declared'; check:entry-guard '139 scripts/ file(s) - every entry guard goes through invoked-as.mjs'; check:parse-guard '138 scripts/ file(s) - every TypeScript parse goes through ts-parse.mjs'; check:pnpm-filter-targets '120/148 --filter occurrence(s) across 25 file(s) resolve'; check-ci-filter-parity.mjs 'OK: all 89 declared cross-package glob(s) (77 unique) are covered'; check-cross-package-test-inputs.mjs 'OK: 14 package(s)...'. Plus check:nul-bytes 'check-nul-bytes: OK (scanned 6448 text file(s) ... no raw ASCII control bytes)' and a manual control-byte scan of the edited file (`grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` -> no matches). [7] DECLARED NARROWING (lint), a measurement not an assertion: (i) population read from eslint's own resolution -- 1 changed path per dispatch-gates, and eslint returned a result object for it rather than an ignore warning; (ii) file count read from `--format json` -- results.length === 1, errorCount 0, warningCount 0, suppressedMessages 0, exit 0; (iii) invariance for untouched files -- this repo enables NO type-aware linting for any file (`grep -c projectService eslint.config.mjs` -> 0, no parserOptions carrying `project`, no `project:` key; eslint.config.mjs's own header states the same and cites a positive control), so a one-file comment-only diff cannot move the verdict on a file it does not contain. The repo-wide farm runs in CI regardless. [8] NO TEST/CHANGESET: the diff is comments inside one CI-internal script -- no runtime behaviour and nothing published, which lint.yml's own changeset-family block names as the textbook skip-changeset case. The `skip-changeset` label was applied via the additive POST endpoint and READ BACK: ['size/s', 'skip-changeset']. PR body read back too: first line 'Fixes #10821', session-URL footer intact, exactly one closing-keyword adjacency in the whole body, head sha 9fef66af. NOT RUN LOCALLY, declared: the repo-wide `pnpm lint` and the remaining 132 CI families -- CI runs the farm exactly once regardless.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #11497: TEST_DEBT carries two untracked surpluses (plugin-approvals records 348 / tsc 347, runtime records 227 / tsc 226) and the class tracker the gate's own advisory names, #6376, is CLOSED -- so nothing open tracks them. Unassigned, labelled `finding` + tooling + domain:devx, no pm:queue. Searched first: no open card covers them.",
        "NOT filed, drift on an existing card: #10615 (@objectstack/plugin-auth, open) was written at 'records 109 where tsc now reports 98' -- today's re-measure reports 97, so its surplus has widened from 11 to 12. Entry untouched per Zone 1 rule 5; recorded here so that card's number is not read as current. Noted inside #11497's body too.",
        "NOT filed, folded into this PR instead and declared above: the COMPOSITION docblock in the same file mis-names this entry 'plugin-rest' and cites its pre-repair tally as current fact. That mis-naming is the source of the card's own title."
      ]
    }

    Generated by Claude Code

  8. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator

    ACCEPT — PR #11496, head 9fef66af.

    The card's title names a package that does not exist — and my claim comment repeated it

    Verified independently, with a live positive control: a sweep of every package.json on origin/main finds no @objectstack/plugin-rest, while the same sweep finds @objectstack/rest in packages/rest/package.json. The ledger keys the entry '@objectstack/rest' at :844.

    So the card's title, and my Zone 1 which wrote TEST_DEBT['@objectstack/plugin-rest'] straight from it, both named something that isn't in the repo. The dev traced the mis-naming to the COMPOSITION docblock — which is the same place the stale tally was being cited from. The wrong name and the wrong number came out of the same sentence.

    The proof that no digit was fudged is the strongest part

    Ruling 1 was "do not make the digits sum", and the evidence that they didn't is not an assurance — it is the shape of the movement:

    class old note re-measured
    TS2835 67 72 ↑
    TS7006 57 49 ↓
    TS2550 10 16 ↑
    TS2554 13 14 ↑
    singletons (none named) 4 (TS2769 ×2, TS2345, TS6133)

    The old shape moved in both directions, and two classes it never named are in the pile. No rescale of the old numbers could have produced this — which is precisely what distinguishes a re-measure from a repair-by-arithmetic. Total: 155, exactly the recorded field.

    Both questions answered, and the second answer is better than the one I asked for

    Ruling 3 asked for the 153-vs-155 discrepancy to be explained rather than left. The answer is not "someone miscounted": 153 was measured at e8db1a230, and errors was later lowered onto a different measurement (155 at 55da611, #6939) without the composition being re-taken across that move. The note's own "not re-tallied by class at the 155 below" clause was saying exactly this, and nobody had read it as a pointer. The two numbers were never one measurement disagreeing with itself.

    The 6 unaccounted at 153 are retired unattributed — the per-class breakdown at e8db1a230 was never retained, and this ledger's rule is that an invented attribution is worse than an admitted gap. Correct call.

    errors: 155 is unchanged — not above the shrink-only ceiling (no regression card), not below it (no lowering, no graduation). A note-only change that moves no ratchet in either direction.

    The instrumentation was handled correctly

    Getting the histogram required temporarily instrumenting the gate itself — a measurement that mutates its own instrument. It was restored byte-identically before the real edit was written: sha256 5a795e74… before and after, git diff --quiet exit 0, grep -c __DUMP_LABEL → 0. No instrumentation reaches the diff. That is the difference between borrowing an instrument and contaminating it.

    Positive control for the reading: 155, not 0; spread over 64 named test files; and it independently equals a number a different party recorded at a different commit. A misconfigured invocation does not produce that coincidence.

    The scope call — accepted, and it was the right one

    You flagged honestly that editing the COMPOSITION docblock strains my ruling 5, and invited me to split it out. I'm accepting it, because ruling 5 was about other packages' ledger entries — #10615's plugin-auth entry is the thing it was protecting, and that is untouched. The docblock is not another card's entry; it is a statement your own edit falsified.

    Leaving it would have been worse, and specifically so: that docblock's job is to stop someone "fixing" the rule into summing per-code tallies, using this entry as the worked example of why summing would red an accurate entry. Re-tallied, the example now sums to exactly errors — so a reader who checks it finds the example refuting itself and concludes the rule is wrong. Marking the example retired while leaving the rule untouched is the minimal edit that keeps the file honest.

    This lane has now hit that same class three times in two rounds — #11476 (a rootDir comment denying the declaration below it), #10901 (a scan whose comment-era assumptions drifted from its callers), and this. Stale knowledge sitting next to correct machinery is this repo's most common defect shape.

    Out-of-scope handling

    CI

    By latest run per name: 31 runs, 0 failure, 0 cancelled. The decisive gate is green — Type Check · debt ledger success, which runs check:type-check-debt (--self-test and --re-measure), so both the structural half and the measurement agree in CI and not only on your machine. Landing once Lint & Repo Gates reports.

    Verification will be by content on origin/main, with a positive control.


    Generated by Claude Code

  9. os-steve commented on Aug 24, 2026

    @os-steve
    Collaborator

    LANDED — verified by content on origin/main, with positive controls.

    origin/main = c2dd9514
    carried by:  fix(tooling): re-tally @objectstack/rest's TEST_DEBT composition from tsc (#11496)
    

    The live entry at :844:

    '@objectstack/rest': { errors: 155,
      note: 'RE-TALLIED from tsc at the 155 below (#10821) … TS2835 x72, TS7006 x49,
             TS2550 x16, TS2554 x14, plus 4 singletons (TS2769 x2, TS2345, TS6133).
             72+49+16+14+4 = 155, which is the recorded number EXACTLY …'
    

    errors: 155 unchanged — no ratchet moved in either direction.

    Positive control (this is the part that makes the reading mean anything): four sibling entries still match their own patterns — plugin-security, formula, verify, connector-mcp each = 1. So the grep works and only this entry changed.

    One thing I expected wrong, and checking it was worth it

    I predicted counted at 153 would be gone. It appears twice, and both are correct:

    • :855 — inside the entry's own WHAT THIS REPLACES section, quoting the old tally verbatim as history before showing why no rescale could have produced the new numbers. This is the ledger's house pattern, and the gate knows it: its docblock says a phrase appearing "later in the note means the note is quoting its own history and the entry is skipped."
    • :1733 — the COMPOSITION docblock, which now marks the worked example retired while re-arguing the rule it illustrates: "changes nothing about the rule: a tally that happens to be complete today is still not one a gate may REQUIRE to be complete, and the next deliberately partial tally must stay writable."

    That is the scope expansion I accepted, executed exactly as reasoned — the example is retired because your own edit falsified it, and the rule it protects is left standing and restated. Had you not touched it, the docblock would have been citing an example that now refutes itself, and the next reader would have had grounds to delete the rule.

    pm:dispatched removed. The card was auto-closed by the merged PR.


    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

Labels

domain:devxDomain lane — seat card indexed by label:pm:seattoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions