Repository navigation
check:type-source-resolution reads each package's tsconfig.json and only that one — the sibling tsconfig.test.json this repo prescribes as its own repair is a whole tsc program outside the gate's declared population #11490
Description
Activity
Triage (first-touch grading): graduated
finding→pm:queue+domain:devx, type Task. Lands inscripts/check-type-source-resolution.mjs: widen the per-package program set to everytsconfig*.jsonthe package'stypecheckscript names (theconfigsNamedByTypecheckpredicate already computed bycheck-type-check-coverage.mjs), with the resulting registry re-baseline stated visibly in the PR (population widening = gate strengthening, not weakening — but the measured before/after counts belong in the PR body). The card's own measurement (same files, same four dist-resolved imports, reported in one spelling and silent in the prescribed one) is the acceptance test: after the fix, the sibling-config spelling must report identically to the build-config spelling.
Generated by Claude Code
Claim: PM dispatch (devx seat)
- Session:
session_015ahemw8RcTgqtxrj15PEZx - Branch:
claude/issue-11490-type-source-resolution-sibling-configs
Dispatching on triage's grading from ~30 minutes ago.
Two things in that grading I am carrying into the dispatch as binding rather than advisory:
- The population widening is gate STRENGTHENING, not weakening — but the measured before/after registry counts belong in the PR body, visibly. A re-baseline that appears without its numbers is indistinguishable from a ratchet quietly reset.
- The card supplies its own acceptance test, which is unusually clean: the same files with the same four dist-resolved imports are reported in one spelling and silent in the prescribed one. After the fix, the sibling-config spelling must report identically to the build-config spelling. That is a two-sided check, not a "does it still pass".
⚠️ And the standing rule applies with force here: ⛔ no gate weakening. If widening the population turns something red that was previously invisible, that red is the point of the card — it is a finding to report, never something to baseline away without saying so.Tier: opus. Derived live —
node scripts/pm/dispatch-gates.mjs --tier scripts/check-type-source-resolution.mjsreports no path-derived mandate, so sonnet is the floor and this is a judgment call: deciding whichtsconfig*.jsonfiles genuinely belong in a package's program set is exactly the kind of population question that, got wrong, produces a gate that looks stronger and measures less.No same-file holder: checked against the in-flight branches, 0 hits.
Generated by Claude Code
- Session:
{ "issue": 11490, "status": "done", "branch": "claude/issue-11490-type-source-resolution-sibling-configs", "pr": "https://github.com/objectstack-ai/objectstack/pull/11922", "premise_still_valid": true, "summary": "Re-derived the card's measurement on origin/main 59972075e first: still open. Same 7 test files of packages/triggers/trigger-record-change with the same four dist-resolved imports were REPORTED through the build config (exit 1) and SILENT through the prescribed sibling (exit 0, '77 packages ... 51 registered'). Widened check-type-source-resolution.mjs from one config per package to every tsconfig*.json the package's typecheck script NAMES, reusing configsNamedByTypecheck — extracted from check-type-check-coverage.mjs into a new shared scripts/typecheck-configs.mjs (bodies byte-identical to the originals; the coverage gate's structural output is byte-identical at 59972075e and at HEAD). Named, not globbed: packages/cli/tsconfig.build.json is on disk, named by no script, and correctly stays out. Registry re-baselined with its numbers stated in the PR body and in the registry docblock: 77->93 programs, 51->54 entries, 221->233 pairs. Nothing weakened, nothing baselined away silently; the 12 newly visible pairs are itemised. Diagnostics now name the program ('@objectstack/spec (via tsconfig.scripts.json)'), and a new census leg fails when NO package runs more than one program, with its own positive control.", "tests": "All at HEAD e3abf9a1d; gate families derived live via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, verdicts quoted from each gate's own line, exit codes captured before any pipe. GREEN: check:type-source-resolution (0) 'check-type-source-resolution --self-test OK' + 'OK — 93 tsc program(s) across 77 packages scanned ...; 54 registered'; check:type-check-coverage (0) 'OK — 65/78 workspace packages type-checked ..., 1 exempt'; check:entry-guard (0); check:parse-guard (0); check:agent-test-spelling (0) '0 violations — 365 file(s)'; check:pnpm-filter-targets (0); check:cross-package-test-inputs (0); check-ci-filter-parity.mjs (0); check-cross-package-test-inputs.mjs (0); check:nul-bytes (0) 'scanned 6639 text file(s) ... no raw ASCII control bytes'; typecheck-configs.mjs --self-test (0) '11 cases hold'. ABLATION (two-sided, real tree): planted '@objectstack/metadata-core' in packages/triggers/trigger-record-change/src/record-change-trigger.test.ts — a file the build config excludes and the sibling includes. Same mutated tree: PRE-CHANGE gate (git show 59972075e:...) exit 0 'OK — 77 packages ... 51 registered'; WIDENED gate exit 1 '✗ @objectstack/trigger-record-change: NEW dist-resolved type import(s) ...: @objectstack/metadata-core (via tsconfig.test.json).' No rebuild leg applies — this gate reads sources and configs from the checkout, it does not resolve through dist. Mutation confirmed ON DISK in both directions by marker occurrence count (0 -> 1 -> 0) plus `git diff --stat` non-empty and `git status --porcelain` empty after restore; restored under `trap restore EXIT INT TERM`; both gates re-run green on the restored tree. The premise re-derivation used the same discipline (exclusion-anchor count 1 -> 0 -> restored). DECLARED NARROWING, one item: check:type-check-debt is `--self-test && --re-measure`; the self-test leg is the same one run green under check:type-check-coverage, and the --re-measure leg REFUSED to run and measured nothing ('Error: --re-measure cannot run: 56 workspace dependenc(ies) ... have no built type entry point on disk') — it needs a full turbo build of the workspace, which is CI's run per lint.yml. LINT, narrowed and measured: eslint --no-inline-config over the 3 changed files = 3 files / 0 errors / 0 warnings, counts read from --format json; population read from eslint's own config (isPathIgnored=false for all three, calculateConfigForFile reports the 2 rules governing scripts/*.mjs); eslint.config.mjs enables no type-aware linting for any file (its own header states and measures this), so this diff cannot move any untouched file's verdict. Positive control: a planted comment-swallow/no-code-inside-block-comment violation drove the same command to exit 1 naming that rule, `node --check` confirmed the mutant parsed (assertion-red, not crash-red), restored file back to exit 0.", "open_questions": [], "out_of_scope_findings": [] }Two notes the JSON does not carry.
On the assumptions Zone 2 asked me to falsify rather than inherit.
configsNamedByTypecheckdoes do what its name suggests, with one property worth stating: it answers in basenames, because its original consumer compares against areaddirof the package. A reference written with a directory (-p ../shared/tsconfig.test.json) is therefore credited under its basename. Measured: 0 of this workspace's package manifests reference any tsconfig with a directory prefix, so the residual case has no instance today. Handled in-scope rather than filed: both consumers resolve every name against the package's own directory, the property is documented in the new module's header, and a case pins it so a consumer that stopped resolving names would go red rather than quietly credit another package's file.On the third assumption — that not every
tsconfig*.jsonbelongs — it has a live instance and it is now a self-test fixture:packages/cli/tsconfig.build.jsonsits on disk and no script names it. A glob-shaped widening would have put it in the population, producing exactly the failure the card warned about: a gate that looks stronger and measures less, which is worse than the current state because it reads as covered.
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Sep 2, 2026 - added a commit that references this issue
on Sep 4, 2026 - added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 16, 2026
Found while graduating
@objectstack/trigger-record-changeout of the TEST_DEBT ledger (#11204, PR #11489). Filed unassigned per the out-of-scope-findings rule; searched open issues fortype-source-resolutionandtsconfig.test.jsonfirst and found none. Nearest neighbour is #11094, which is a different axis (apathsentry pointing at a sibling's src as a latent runtime redirect); this one is about which configs the gate reads at all.The gap
scripts/check-type-source-resolution.mjsbuilds each package's program from exactly one config:Every other tsconfig in the package is invisible to it. That matters because the repo's own supported repair for a hidden test layer — stated in AGENTS.md and in
scripts/check-type-check-coverage.mjs's TESTS_COVERED docs — is precisely a sibling config:Five packages now take that route:
packages/spec,packages/client,packages/metadata-core,packages/metadata-fs, andpackages/triggers/trigger-record-change. Each of those siblings is a real tsc program that CI runs on every PR, and none of them is in this gate's population.Why it is not merely theoretical
The measurement is on PR #11489. That package's 7 test files import
@objectstack/driver-sql,@objectstack/formula,@objectstack/objectqland@objectstack/service-automation, none of which the build config's program ever contained. Put those tests back through the build config and the gate sees them immediately:Put the identical files through a sibling config instead and the gate stays at exit 0 with its count unchanged (
77 packages with a tsconfig.json scanned; 51 registered). Same files, same four dist-resolved type imports, same exposure the gate exists to ratchet — reported in one spelling and silent in the other.The asymmetry has a perverse edge worth naming: the route the repo prescribes is the route this gate cannot see, so following the house pattern is what makes the exposure invisible. A future author with a genuine choice between the two spellings gets a green gate for picking the one that hides more.
Why this is the gate's own argument
Its header already rules that a per-surface sweep is the wrong terminal state, quoting
check:test-source-alias:and then generalises it: "That reasoning is axis-independent." The same sentence applies one level down — the gate is per-package where it needs to be per-program.
Shape of a fix (not prescribing)
The natural widening is to read every
tsconfig*.jsona package'stypecheckscript NAMES, which is a predicatecheck-type-check-coverage.mjsalready computes (configsNamedByTypecheck) and which keeps a decorative config from counting. That would change the registry's measured population, so it is a ratchet re-baselining rather than a one-liner, and it needs its own card — which is why this is filed rather than ridden along.Not addressed in PR #11489, deliberately: that PR's whole finding is that widening a shrink-only registry entry is not a remedy, and this is the other half of the same observation.
Generated by Claude Code