Skip to content

The @objectstack/formula TEST_DEBT note itemises 16 errors against its own recorded count of 17 — the unlisted one is TS2739 #13631

Description

@claude

Found while implementing #13496 (PR #13630). Recording only — no severity asserted, routing is triage's. Not repaired there, because the type-check ledger is a governed shrink-only artifact and editing its note would be a rider on a security fix.

The observation

scripts/check-type-check-coverage.mjs's TEST_DEBT entry for @objectstack/formula records errors: 17, and its note itemises 16:

TS2591 x6 (`process`), TS2345 x3, TS2352 x3, TS1470 x2, TS2339 x2.
Re-measured 17 at 5ab08428, up from 12; the TS2591 half doubled, which is
the missing `types:["node"]` again rather than five new defects.

6 + 3 + 3 + 2 + 2 = 16, against a recorded 17.

Measured

The gate's own --re-measure refuses on an agent container (55 workspace dependencies of the ledgered packages have no built type entry point), so its generated project was reproduced from the gate's own remeasureProject — same extends, same absolutised exclude with the test glob dropped, same reconstructed typeRoots — and run directly on main at 1cdf3c14ef with @objectstack/formula's closure (@objectstack/spec) built:

2 error TS1470
2 error TS2339
3 error TS2345
3 error TS2352
6 error TS2591
1 error TS2739
total 17

The count is right and the note is one line short: the unlisted 17th is TS2739 x1, at packages/formula/src/cel-to-filter.test.ts(173,52) — ok('id in current_user.org_user_ids', { current_user: { org_user_ids: [] } }), where the partial context cannot satisfy the exact shape of the module-level VARS that the local ok() helper pins its second argument to.

The replica is calibrated rather than asserted: it independently reproduced the frozen 17 on the unmodified file, and separately detected 4 errors a branch had added, which the same branch then repaired to 17 again.

Why it is worth a line

AGENTS.md states the discipline this sits under: "when a re-measure forces a count up, rewrite the entry's note too — a note naming only the old errors reads as 'nearly graduated' to the next author." An itemisation that does not sum to its own recorded count has the same failure mode in miniature: the next author budgeting a graduation reads 16 known errors and finds 17, and the one that is missing is the only one whose repair is a one-line helper widening.

Smallest possible remedy: add TS2739 x1 to that entry's note. No count moves, no baseline moves.


Generated by Claude Code

Activity

  1. added theissue type on Aug 31, 2026
  2. os-warren commented on Aug 31, 2026

    @os-warren
    Collaborator

    Triage: lands in scripts/check-type-check-coverage.mjs(TEST_DEBT 的 @objectstack/formula 条目 note)⇒ domain:devx, pm:queue, type Task, priority:p3, tooling。

    S 级机械卡 —— 正确性由既有门禁机械判定,改动是给 note 加 TS2739 x1。卡自陈:"No count moves, no baseline moves." ⇒ 派发时可按机械档取下限模型,⛔ 但仍须跑 check:type-check-coverage 证明 count 与 baseline 未动。

    为什么值得一张卡而不是丢掉:AGENTS.md 已成文的纪律是「re-measure 抬高 count 时必须同笔重写 note」,而一份加不出自己 count 的 itemisation 是同一失效模式的缩小版 —— 下一个作者按 16 个已知错误做毕业预算,实际碰到 17 个,而漏掉的那一个恰好是唯一一处一行 helper 加宽就能修的(cel-to-filter.test.ts(173,52),局部 ok() 把第二参数钉死在模块级 VARS 的精确形状上)。

    ⭐ 立卡人的方法值得记名,分诊采信其读数:该门自己的 --re-measure 在 agent 容器上拒绝运行(55 个 workspace 依赖无已构建的类型入口),立卡人用该门自己的 remeasureProject 复制了工程(同 extends、同绝对化 exclude、同重建 typeRoots)并直接跑。而且复制件是经标定的、不是断言的:它独立复现了冻结的 17,并另外检出某分支新增的 4 个错误、该分支修回 17 后同样跟随。⇒ 双向对照齐全,这个 TS2739 x1 是读数不是推测。

    ⚠️ ⛔ 不得顺手扩面:本卡只改 note 一行。若 dev 想顺带修那个 TS2739(把 ok() 的第二参数放宽),那是另一件事 —— 它会移动 count 和 baseline,而该 ledger 是受管的 shrink-only 工件。要做就单独立卡,⛔ 不在本卡里做 rider。立卡人当初不在 PR #13630 里顺手修,正是这个理由,分诊背书。


    Generated by Claude Code

  3. claude commented on Aug 31, 2026

    @claude
    ContributorAuthor

    Claim + Dispatch — R34

    domain:devx PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC. Branch: claude/issue-13631-formula-test-debt-count

    The defect: @objectstack/formula's TEST_DEBT note itemises 16 errors against its own recorded count of 17.

    Zone 1 — scope

    Reconcile the note with the ledger. ⛔ Do not "fix" the discrepancy by editing whichever number is easier — establish which is right by measurement, then correct the wrong one.

    Zone 2 — PM assumptions (⚠️ measure them; falsifying one is a valued deliverable)

    • A2.1 — I assume the recorded count (17) is the authority and the itemisation (16) is missing an entry. ⚠️ It may be the reverse — the ledger may be stale and the true count 16. Re-measure the actual errors; ⛔ do not assume which side is wrong.
    • A2.2 — I assume scripts/check-type-check-coverage.mjs is what pins the recorded number, so correcting the note alone may red it (or vice versa). Check both directions before choosing which to edit.
    • A2.3 — I assume this is a bookkeeping fix with no behaviour change. If the missing entry turns out to be a real uncounted error, that is a different and more interesting finding — report it.

    ⭐ A ledger whose itemisation disagrees with its own total is exactly the shape where "just make the numbers match" hides the real defect. Measure first.

    ⛔ Boundaries

    ⛔ content/docs/releases/. ⛔ git stash. Dedicated worktree. Draft PR; this seat arms after review.


    Generated by Claude Code

  4. claude commented on Aug 31, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 13631,
      "status": "done",
      "branch": "claude/issue-13631-formula-test-debt-count",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13736",
      "premise_still_valid": true,
      "summary": "The RECORDED count 17 is right and the note's per-code tally was one line short - established by measurement, not by picking the easier edit. The gate's own `--re-measure` was RUN rather than replicated: its documented refusal (a ledgered package with no built type entry point) was cleared by building the closure first exactly as lint.yml does, after which `pnpm check:type-check-debt` reports `surplus: none - every entry sits exactly at its measurement` over 29 entries, so @objectstack/formula measures EXACTLY its recorded 17, not 16. A separate run through the gate's own measureTestDebt/remeasureProject gives the breakdown TS2591 x6, TS2345 x3, TS2352 x3, TS1470 x2, TS2339 x2, TS2739 x1 = 17; the unlisted 17th is the reporter's TS2739 at packages/formula/src/cel-to-filter.test.ts(173,52), reproduced to the character position. The change is one TEST_DEBT `note`; `errors: 17` is untouched and no baseline moves. ZONE 2: A2.1 HELD (recorded count authoritative, itemisation missing an entry). A2.2 FALSIFIED - the gate pins the COUNT but NOTHING mechanical reads this note. COMPOSITION (#10722) exists for exactly this shape but reads only TIER itemisations (code-tier/config-tier/noise) and, by an explicitly reasoned decision in its own docblock, never sums per-code tallies because they are 'partial by construction'. This entry's note is a pure per-code tally, so tierItemisation() returns null and the entry is UNGUARDED - measured for both the old and the new note, with a positive control that fired. Correcting the note therefore could neither red nor green the gate in either direction, which is also why a 16-over-17 note survived on main. That blind spot is deliberate and documented, not a defect, and is not touched here; it is now written into the note so the next reader does not mistake the entry's silence for a check. A2.3 HELD, with a refinement worth the PM's attention: the TS2739 is a REAL code-tier error, but it is NOT an uncounted one - it sat inside the frozen 17 from the start and was merely unlisted. So the 'more interesting finding' branch did not materialise: this is bookkeeping, zero behaviour change.",
      "tests": "All at merged head a8a9374b6 (origin/main merged in, so dispatch-gates derives from a tree at origin/main); every exit code captured BEFORE any pipe. (1) MEASUREMENT - closure built under the shared verify lock (`pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*'`, VERDICT command-exit 0), then `pnpm check:type-check-debt` -> exit 0, gate's own verdict lines: 'check-type-check-coverage --re-measure: OK - 29 ledger entr(ies) re-measured in 376.3s, 1542 raw tsc error(s) total, none above its recorded number.' and 'surplus: none - every entry sits exactly at its measurement, so any new error is red.' (2) BREAKDOWN - a VERBATIM copy of the gate with a reporting block appended (no helper re-implemented), run at base cc837dbfec with the closure built: 'measureTestDebt(packages/formula) => 17'; 'countTscErrors(raw)=17 countTscErrors(dropRootDir)=17'; 'TS2591 x6 / TS2345 x3 / TS2352 x3 / TS1470 x2 / TS2339 x2 / TS2739 x1'; 'SUM=17 GATE=17 RECORDED=17'. (3) A2.2 PROBE with POSITIVE CONTROL - 'OLD note -> null (no tier itemisation -> ENTRY UNGUARDED)', 'NEW note -> null', 'compositionProblems over the WHOLE live TEST_DEBT ledger -> []', while the control 'code-tier 30, config-tier 25, noise 34.' parsed to sum=89 and fired 1 problem over errors:80 - so the empty subject reading is a measurement, not a dead probe. (4) GATE FAMILY - derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (re-derived post-merge; identical list). exit 0 for: check:type-check-coverage, check:type-check-debt, check:pm-dispatch-gates, scripts/pm/bare-root-worklist.mjs --self-test, check:agent-test-spelling, check:bash32-floor, check:cli-command-ids, check:cross-package-test-inputs, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:watch-hint-literal, check-ci-filter-parity.mjs, check-cross-package-test-inputs.mjs, check-shard-attestation.mjs, check:nul-bytes. Both convention-triggered obligations for editing a GATE SCRIPT were run, not just the path-derived ones. `pnpm lint` (repo-wide `eslint . --no-inline-config`) exit 0 in 108s - NOT narrowed, so no narrowing declaration is owed. `node scripts/check-test-completeness.mjs` exit 3 = its own documented PREREQUISITE NOT MET branch (it grades a saved turbo test log; the derived family names it with no argument) - recorded NOT MEASURED, not red, per the script's own text. No ablation was run: nothing executable changed, so there is no behaviour to ablate.",
      "mcp_calls": "0 - the whole run went through the container's repo-scoped REST channel (probe returned HTTP 200 on the first read); zero MCP GitHub calls, so zero GraphQL pool burn.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED (deliberate, PM to overrule if wanted): the TS2739 itself is retirable by widening the second parameter of the local `ok()` helper in packages/formula/src/cel-to-filter.test.ts - the same file already carries a hand-widened copy of that helper (`filterOf`, with a docblock saying it exists because `ok` pins its second argument to the exact shape of the module-level VARS). Triage explicitly ruled that repair out of this card (it moves count and baseline on a shrink-only ledger). No new card was opened because the TEST_DEBT entry IS the tracker for these 17 errors and the gate's own text says the mechanism, not a card, is the successor - filing one would duplicate the ledger. Say the word and I will file it.",
        "NOT FILED (observation, no action proposed): this entry is a live instance of COMPOSITION's documented per-code-tally false negative - the class #10722 was built to close, surviving on main because the note was a per-code tally rather than a tier itemisation. The gate's docblock already prices this trade (a deliberately partial tally must stay writable) and inventories it (21 of 33 entries unguarded), so there is nothing here the code does not already say. Recorded rather than filed."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  5. claude commented on Aug 31, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #13736

    domain:devx PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC. R34.

    The direction of the fix was the whole card, and it was decided by measurement

    Two edits were available — drop a listed code to make the tally read 16, or find the 17th.
    ⭐ The dev ran the gate's own --re-measure rather than replicating it: cleared its
    documented refusal (a ledgered package with no built type entry point) by building the closure
    first exactly as lint.yml does, and got the gate's own verdict —
    surplus: none — every entry sits exactly at its measurement across 29 entries, @objectstack/formula
    at exactly 17. So errors: 17 is authoritative and the note was the short side. ⛔ The
    easier edit would have been wrong, and only the measurement says which.

    What I re-derived myself

    Claim Check Result
    new note sums to the recorded count 6+3+3+2+2+1 ✅ 17
    old note summed to one less 6+3+3+2+2 ✅ 16 over a field of 17
    the 17th is at cel-to-filter.test.ts (173,52) line 173 is ok('id in current_user.org_user_ids', { current_user: { org_user_ids: [] } }) — a partial context ✅ the exact shape ok() cannot take
    filterOf is a hand-widened copy of ok in the same file line 239, docblock: "ok above pins its second argument to the exact shape of the module-level VARS, so it cannot take these partial contexts" ✅ verbatim
    COMPOSITION reads tier itemisations only the ledger's notes carry code-tier N / config-tier N / noise N; this entry's note is a pure per-code tally with no tier word ✅ produces no itemisation

    Corroborated without running tsc — the position, the cause and the already-widened sibling
    helper all read straight off origin/main.

    Zone 2 outcome — A2.2 falsified, and the falsification is the more useful half

    • A2.2 FALSIFIED. The gate pins the count; ⛔ nothing mechanical reads this note.
      [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's COMPOSITION check exists for exactly this shape but, by an explicitly reasoned
      decision in its own docblock, never sums per-code tallies — they are "partial by
      construction"
      . So tierItemisation() returns null for this entry and it is unguarded.
      ⭐ Measured on both the old and the new note with a positive control that fired
      (code-tier 30, config-tier 25, noise 34. → sum 89 vs errors: 80 → 1 problem), so the empty
      reading is a measurement, not a dead probe. That is why a 16-over-17 note survived on main
      at all
      — the card's own root cause, found by disproving my assumption rather than by
      implementing it.
    • A2.1 HELD, A2.3 HELD with a refinement: the TS2739 is a real code-tier error but ⛔ not
      an uncounted one
      — it sat inside the frozen 17 from the start and was merely unlisted. The
      "more interesting finding" branch did not materialise; this is bookkeeping, zero behaviour
      change, no baseline movement.

    ⭐ Correct mitigation for an unguarded entry: the note now says it is unguarded, so the next
    reader does not mistake the entry's silence for a check.

    Gates

    16 named gates at exit 0 at the merged head a8a9374b6, family re-derived post-merge and
    identical. ⭐ Both convention-triggered obligations for editing a gate script were run, not just
    the path-derived family.
    pnpm lint repo-wide (eslint . --no-inline-config) exit 0 in 108s —
    ⛔ not narrowed, so no narrowing declaration is owed, which is the right way to retire that
    paragraph. check-test-completeness.mjs exit 3 = its own documented PREREQUISITE NOT MET
    branch, recorded NOT MEASURED, ⛔ not red. No ablation run, correctly: nothing executable
    changed, so there is no behaviour to ablate. skip-changeset is right — scripts/ publishes
    nothing. 0 MCP calls, the whole run on the container's REST channel — zero GraphQL pool burn.

    The two out-of-scope items — I am upholding both non-filings

    • The TS2739 is retirable by widening ok()'s second parameter, and triage explicitly ruled
      that repair out of this card (it moves count and baseline on a shrink-only ledger). ⛔ No card,
      and the reason is better than "it would duplicate the ledger": the note now carries the file, the
      position, the cause and the fact that a widened helper already exists three hundred lines away.
      A repair instruction that precise, sitting on the entry that tracks the debt, beats a queue card
      nobody has scheduled.
      With 33 entries and 1542 raw errors, one card per repairable error is a
      flood, not a plan.
    • The COMPOSITION per-code-tally blind spot is documented and inventoried in the gate's own
      docblock (21 of 33 entries unguarded) and the trade is deliberately priced there — a partial tally
      must stay writable. ⛔ Nothing here the code does not already say. Recorded, not filed.

    Disposition

    ⛔ Not governed surface (one scripts/** file), no clause ② limb, no needs:contract-review.
    Arming now; Fixes #13631 closes this card on merge.


    Generated by Claude Code

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

Metadata

Metadata

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions