Repository navigation
[finding] The TEST_DEBT re-measure bills @objectstack/objectql for a TS6059 about its own generated config — the case remeasureProject's docstring calls "measuring the tape measure" #10779
Description
Measured while repairing PR #10755 (the Type Check · debt ledger failure on #10269).
Not repaired there: that card's surface is one entry guard, and this is a property of
the generated re-measure project.
The measurement
remeasureProject (scripts/check-type-check-coverage.mjs) neutralises the package's
rootDir only when it has to widen include:
if (include) {
project.include = (unreachable.length > 0 ? [...include, ...unreachable] : include).map(absolutise);
if (unreachable.length > 0) compilerOptions.rootDir = pkgAbs;
}@objectstack/objectql has all 225 of its test files under src/, so unreachable is
empty, no widening happens, and the generated project inherits rootDir: "src" from
packages/objectql/tsconfig.json. But dropping the test globs from exclude puts
src/dry-run-hash-compat.test.ts into the program, and its line 5 imports
../scripts/dry-run-hash-compat — a file tsc then pulls in transitively, from outside
rootDir. Re-measure output, verbatim, with the dependency closure built:
packages/objectql/src/dry-run-hash-compat.test.ts(5,51): error TS6059: File
'.../packages/objectql/scripts/dry-run-hash-compat.ts' is not under 'rootDir'
'.../packages/objectql/src'. 'rootDir' is expected to contain all source files.
That line is counted, and it is inside the frozen TEST_DEBT['@objectstack/objectql']
of 355.
Why this is a finding rather than a shrug
The function's own docstring already rules on this exact diagnostic, in the branch it
does neutralise:
rootDirgoes with them. It issrcin most of these packages, so widening
includepast it makes tsc answer with one TS6059 per added file and nothing
else [...] Under--noEmitthere is no output layout for it to protect, so a
TS6059 here is a diagnostic about this generated config rather than about the
package, and counting it would be measuring the tape measure.
The reasoning does not depend on how the file entered the program. A file reached
transitively from a test is outside rootDir for exactly the same reason a file
added to include is, produces exactly the same TS6059, and is just as much a
statement about the generated config rather than about the package — but it is billed,
because the neutralisation is gated on unreachable.length > 0 rather than on the
diagnostic.
Consequences are small and entirely in the "number means what it says" direction:
- the ledgered number is one higher than the debt it names, and the entry's
note
cannot account for the extra error; - it is frozen that way, so the fix is a
--loweropportunity (355 to 354 for
objectql), never a ratchet break; - an author paying down that package's test-layer debt cannot make this one go away by
fixing code, which is the property the ratchet is supposed to have.
Not proposing the remedy here
At least three shapes exist — always set compilerOptions.rootDir = pkgAbs in the
generated project (it protects no output layout under --noEmit); drop TS6059 in
countTscErrors for the generated project only; or leave it and say so in the
docstring. Which is right depends on how many ledgered packages are in this state, and
nobody has counted: it needs a transitively-imported file outside rootDir, so it is
invisible to any scan of include globs alone. Filed so the count gets made rather
than guessed.
Dedupe
search_issues was unavailable to this seat, so all 301 open issues (PRs excluded)
were fetched through the REST list endpoint and their titles and bodies grepped for
TS6059, rootDir, remeasureProject, tape measure, re-measure, TEST_DEBT,
debt ledger and type-check-coverage. TS6059 matches only #8249 (dist-resolved
declarations) and #4311 (the framework typecheck-debt card); rootDir matches only
those and #10756. Nothing names this billing.
Related, not duplicated: #10756 (the same package's scripts/ is invisible to its own
tsc -p), #10722 (nothing reads a ledger note), #10615 and #10723 (individual
entries disagreeing with their measurement).
Generated by Claude Code
Generated by Claude Code
Activity
huangyiirene commentedon Aug 21, 2026
Triage (finding round): promoted →
pm:queue, type Task. First the count (how many ledgered packages carry a transitively-imported outside-rootDirfile — invisible to include-glob scans, so it needs the re-measure itself), then the remedy sized to it; on the evidence here, always settingcompilerOptions.rootDir = pkgAbsin the generated project looks principled (the docstring's own "measuring the tape measure" reasoning does not depend on how the file entered the program, and--noEmitprotects no output layout) — but the dev should verify no ledger count rises under that change before choosing it. Each affected frozen entry becomes a--loweropportunity. Fold-or-serial pair with #10756 (same instrument file) — see the note there.Generated by Claude Code
fix(devx): count a package's unread source directories, and stop bill…
claude commentedon Aug 21, 2026
Folded dispatch #10756 + #10779 — draft PR #10876
{ "issue": 10756, "status": "done", "branch": "claude/issue-10756-type-check-coverage-blind-spot", "pr": "https://github.com/objectstack-ai/objectstack/pull/10876", "premise_still_valid": true, "summary": "Folded #10756 + #10779, one draft PR, per-card commits' worth of reasoning in one commit. #10756: censused all 64 covered packages and found 65 non-test .ts files outside every accounting program — 54 package-root tool configs vs 11 in a real source directory. The count chose the RATCHET LIMB (new SOURCES_COVERED invariant + shrink-only UNCHECKED_SOURCE_DEBT seeded at 10), because 11 directories across 11 packages is too many for the sibling-config route to close and repeating it would still leave the gate blind to the 12th; the limb needs no `tsc --listFiles` since the gate already walks every package tree. The 11th — packages/objectql/scripts, the card's own instance — is REPAIRED with a sibling tsconfig.scripts.json named in the typecheck script (0 errors). #10779: the remedy triage favoured is REFUTED by measurement — setting compilerOptions.rootDir = pkgAbs unconditionally RAISES @objectstack/rest 155 -> 175, because rest deliberately sets rootDir '..' for a paths rule pulling a sibling package's source in (#9960), and pkgAbs is NARROWER than that. Implemented option 2 instead: TS6059 is dropped where it is counted, for the generated re-measure project only. Two ledger entries moved, both DOWN and both exactly attributable, both notes re-tallied so neither carries a compositionAt: objectql TEST_DEBT 355 -> 354, lint 19 -> 16 (lint's 3 were a discovery of this change; its note already named them). No entry rose, and the surplus is unchanged at 12 on plugin-auth — the same pre-existing figure as the base tree — so this opens no new permission allowance.", "tests": "All final numbers at sha 52d0c746d2. (1) Gate union derived with `node scripts/pm/dispatch-gates.mjs` with NO hand-written path list (14 matched families), plus check:entry-guard and check:parse-guard by hand (both invisible to the derivation) and check:nul-bytes: 18/18 EXIT=0, each captured as `cmd > log 2>&1; ec=$?` with no pipe. (2) check:type-check-coverage verdict line: 'check-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root), 13 in the DEBT ledger (436 frozen raw errors...), 1 exempt.' + 'test layer: 20 package(s) ... 1484 frozen raw errors in TEST_DEBT' + NEW 'source layer: 10 directory(ies) of non-test source in 10 ledgered entr(y/ies) sit outside every tsc program their package's own `typecheck` runs (10 files as counted by this run).' (3) check:type-check-debt verdict line at 52d0c746d2: 'check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 274.4s, 1908 raw tsc error(s) total, none above its recorded number.' + 'surplus: 12 raw error(s) across 1 entr(ies)'. Base tree measured 1912 with surplus 12 on plugin-auth; delta -4 = objectql -1 + lint -3 exactly. (4) Self-test: '✓ check:type-check-coverage --self-test — 38 semantic case(s) + 38 observation case(s) + 29 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) hold.' (grew 34->38, 24->38, 25->29). (5) `pnpm --filter @objectstack/objectql typecheck` EXIT=0 and the log echoes 'tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json', proving the filter matched (#10853 trap). (6) THE REFUTATION was a real measured run, not an argument: with rootDir=pkgAbs the re-measure exited 1 with '• @objectstack/rest: TEST_DEBT records 155 raw tsc error(s), `tsc --noEmit` now reports 175 (+20)'. ABLATIONS — three, each mutated and RESTORED, every leg's mutation confirmed on disk by marker count before any result was read, never by an editor's exit code (one python anchor assertion did fail and correctly refused to write, so that ablation was re-anchored rather than silently skipped): (a) drop the plugin-auth/examples ledger key, count 1->0, gate goes red naming that directory; restored 0->1, green. (b) revert objectql's typecheck to `tsc --noEmit`, marker 1->0, gate goes red on packages/objectql/scripts — proving the sibling config is coverage only because the script NAMES it; restored 0->1, green. (c) remove `dropRootDirDiagnostics: true`, call-site count 1->0, self-test goes red with both #10779 wiring assertions firing; restored 0->1, green. No dist/ or build leg is involved — the subject is a CI gate script, so ablation-dist-preflight does not apply and no rebuild was needed or claimed.", "open_questions": [ { "question": "SOURCES_COVERED governs source files in SUBDIRECTORIES (depth > 0), so the 54 package-root tool configs (vitest.config.ts x32, tsup.config.ts x16, objectstack.config.ts x5, vitest.integration.config.ts x1) across 42 packages are unchecked TypeScript that no invariant names. Should they be governed too? The depth line can also be walked around by moving a file up into the package root. Filed as #10870 rather than decided in this PR; the gate's header argues it both ways.", "options": [ "A — leave as-is: they are one repo-wide convention, not 42 decisions; each is loaded by a tool that transpiles, and ledgering them seeds 42 identical entries, which is the 'ledger nobody trusts' failure #4311's own header warns about", "B — bring them into a program via a shared tsconfig.tools.json named in each typecheck script: closes the class and the evasion, costs 42 manifests plus an unmeasured number of surfaced errors", "C — a separate, deliberately coarse invariant over root tool configs with its own ledger for stragglers (B with a seed)" ], "recommendation": "A for now, which is what shipped — but as a recorded decision (#10870) rather than an oversight. Real business need is the weak axis: nothing is pulling on type-checked vitest configs, and no defect has been traced to one. Startup scope discipline cuts the same way — B expands gate surface across 42 packages with no measured pull. The AI-correctness axis is the one that argues for B eventually (agents add config files constantly), so this is worth revisiting the first time a broken tool config actually costs something, not before." } ], "out_of_scope_findings": [ "filed as #10868: 8 packages' scripts/i18n-extract.config.ts fail to type-check, 1-3 x TS2883 each — the inferred type of `default` names hash-suffixed internal chunks of @objectstack/spec's dist, so it is non-portable by construction; two candidate repairs, one of which lives in spec rather than in the 8 consumers", "filed as #10869: packages/plugins/plugin-auth/examples/basic-usage.ts imports @objectstack/plugin-hono-server, which the package declares in NO dependency block (1 x TS2307) — the census's only file in no tsc program at all, and a genuinely broken example the blind spot hid", "filed as #10870: [finding] the 54 package-root tool configs and the depth > 0 scoping line, for a maintainer to rule on (see open_questions)" ] }Generated by Claude Code
fix(devx): count a package's unread source directories, and stop bill…