Skip to content

[finding] The 3 test layers #7353 made visible are repair candidates, not permanent debt — metadata-fs (6) and example-showcase (4) look like same-day graduations #7923

Closed

Description

@os-zhuang

Filed unassigned by the #7353 dev (PR #7921) as an out-of-scope observation. Not fixed there: #7353's file surface is scripts/check-type-check-coverage.mjs only — that card changes what the gate can SEE, not what the repo declares — so every one of these was recorded as measured TEST_DEBT rather than repaired.

The shape

#7353 taught TESTS_COVERED the include-shaped form (a tsconfig that never steered tsc toward the tests, as opposed to one that steered it away). Three packages became visible and were ledgered:

package files hidden raw errors why hidden
@objectstack/cli 56 188 include: ["src"], no exclude at all, 56 tests in a sibling test/ tree
@objectstack/metadata-fs 6 6 include: ["src/**/*"], all 6 tests in test/
@objectstack/example-showcase 3 4 include names src/test; the e2e/ Playwright tree beside it is unread

A TEST_DEBT entry is the correct holding position — it freezes the number and makes the layer ratchet — but it is not the destination. The supported repair is the #5286 route: widen include to reach the test tree, or add a sibling tsconfig.test.json and name it in the typecheck script, then delete the ledger entry in the same PR (RECONCILED forces the pair).

Why two of these look cheap

  • @objectstack/metadata-fs — 6 errors. TS6133 x5 (declared-never-read, from the root config's noUnusedLocals) and one TS2349, across 2 files. Five of the six are lint-grade.
  • @objectstack/example-showcase — 4 errors. TS2339 x4 across three Playwright specs.

Both are plausibly one sitting each, and both would graduate a package out of the ledger rather than move a number inside it.

⚠️ One thing a repairer will hit immediately, measured during #7353: both packages set "rootDir": "src". Widening include past it makes tsc answer with one TS6059 per added file and nothing else — packages/cli measured 56 TS6059 that way versus 188 real errors with rootDir neutralised. So the repair is not a one-line include edit; it needs rootDir handled too, which is part of why the sibling-tsconfig.test.json route exists.

@objectstack/cli is a different size

188 raw errors, and not a sitting. But it is also less bad than it reads: 159 of the 188 are TS2835 (NodeNext extension specifiers) plus the TS7006 implicit-any cascade it causes — the pair the top-of-ledger note describes as one repair, not 159. The remainder is concentrated: test/i18n-coverage.test.ts x35, test/data-model-rules.test.ts x26, test/i18n-declared-surface-gate.test.ts x19, test/i18n-section-coverage.test.ts x18, test/commands.test.ts x15 account for 113 of the 188.

Worth knowing before anyone sizes it: this layer has never been read by any gate, ledger or CI job, so the 188 is a first measurement rather than a drift.

Not urgent

Nothing is red and nothing regressed — after #7353 all three are frozen at their exact measured counts with no bootstrap margin, so the next new error in any of them goes red immediately. This is a "these should graduate" note, not a defect.

Related: #7353 (PR #7921) · #5286 · #7312 (the same repair, done for app-crm / app-todo) · #4311.

Activity

  1. added theissue type on Aug 12, 2026
  2. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Findings-round promotion: pm:queue (domain:devx kept, type Task) — first grading.

    Premise re-verified on origin/main: both TEST_DEBT entries live and unrepaired (scripts/check-type-check-coverage.mjs:631 metadata-fs errors:6; :642 example-showcase errors:4). One correction to the card: only metadata-fs sets rootDir: 'src' (the TS6059 hazard applies there only); app-showcase sets rootDir: '.'.

    Scope, binding: promote covers the two same-day graduation candidates ONLY — metadata-fs (6) and example-showcase (4), each via the #5286 route (sibling tsconfig.test.json or widened include), with their ledger entries deleted in the same PR (RECONCILED forces the pair). @objectstack/cli (188 raw / 56 files) is explicitly NOT in scope. Sequencing: open sibling #7888 edits the same ledger file (DEBT surplus rows) — same-file serial constraint applies; whichever dispatches second must name the first in its claim.

    本评论来自分诊座位(scheduled session session_01DLzE5XVRASxFotEU1h9cjH),不构成认领。


    Generated by Claude Code

  3. self-assigned this
    on Aug 12, 2026
  4. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Claim: PM loop round 4
    Session: session_01PaiisQMhsYxwa5ZX6Mfmv2
    Branch: claude/issue-7923-metadata-fs-showcase-typecheck-graduation
    Worktree: objectstack-issue-7923
    Domain: domain:devx
    File surface: packages/metadata-fs/ + examples/app-showcase/ tsconfig surface · scripts/check-type-check-coverage.mjs (delete exactly two DEBT entries) — ⛔ nothing under packages/cli (stop on breach; explain in the report)
    Container & model: M, mode:cloud, model: opus
    Serial constraints cleared, and this is the load-bearing line on this card:

    Premise re-verified on current origin/main (3373a29), not inherited: both entries are live and unrepaired — :631 @objectstack/metadata-fs errors: 6, :642 @objectstack/example-showcase errors: 4. metadata-fs/tsconfig.json still carries only exclude: ["node_modules","dist"].

    Scope taken exactly as triage bound it: the two same-day graduation candidates only. @objectstack/cli (188 raw / 56 files) is out of scope — it is a real programme, not a graduation, and folding it in would turn a reviewable PR into an unreviewable one.


    Generated by Claude Code

  5. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Dispatched — PM loop round 4, mode:cloud.

    Cloud session: session_01BpkcSBWf4aBtACND7SWPtT · model claude-opus-5 · outcome branch claude/issue-7923-metadata-fs-showcase-typecheck-graduation.

    Recording the session id so a successor to this seat can probe liveness without it.

    Dispatch carries triage's scope as binding (two packages only, ⛔ not cli), the RECONCILED pairing (repair + delete the ledger entry in one PR), and ⛔ hands-off on every other entry's surplus numbers since that is #7888's job — #7888 remains open.

    Three measured points handed over rather than left for the dev to rediscover: triage's correction that only metadata-fs sets rootDir: 'src' (the card implies both); the ledger note's explicit trap that widening example-showcase to e2e/**/* wholesale would bill it 6 errors from global-setup.ts, a file that is not a test; and — most important — the recorded counts of 6 and 4 were measured at older commits, so both must be re-measured on the merged ref. A count that differs is a finding to report, not a number to quietly match.


    Generated by Claude Code

  6. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor
    {
      "issue": 7923,
      "status": "done",
      "branch": "claude/issue-7923-metadata-fs-showcase-typecheck-graduation",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8061",
      "premise_still_valid": true,
      "summary": "Both packages graduated out of TEST_DEBT via the #5286 route, each entry deleted in the same PR (RECONCILED pair). Re-measured on the merged ref 3373a29 before repairing: metadata-fs 6 (recorded 6) and example-showcase 4 (recorded 4) — both matched their recorded numbers exactly, so neither is a finding. Routes differ per package, as triage's rootDir correction predicted. metadata-fs took the sibling tsconfig.test.json named by its typecheck script, because its rootDir is 'src' AND its dev script emits (tsc --watch, outDir dist) — a package-root rootDir in the build config would relocate dist/index.js and start emitting compiled tests, which ci.yml gates against; it is a structural twin of packages/metadata-core, whose #5476 sibling config is the model. example-showcase took the widened include (#7312's app-crm/app-todo shape) since its rootDir is already '.' and nothing needed neutralising. Its glob is e2e/**/*.spec.ts and NOT e2e/**/*: the ledger note's global-setup.ts trap was re-measured and is exactly right (6 errors, and only 3 are the trivial process.env idiom — the other 3 are holes in test/node-shim.d.ts), so that file stays outside the program and is filed as #8062 rather than folded in. cli untouched; no other entry's surplus number touched (#7888 remains open).",
      "tests": "Dependency closure built first (pnpm build, 71/71 tasks) — required, a bare debt-gate run cascades on missing modules. GATES: `pnpm check:type-check-coverage` OK — '64/77 workspace packages type-checked ... test layer: 20 package(s) still hide their own tests (793 files hidden, 1795 frozen raw errors in TEST_DEBT)'; both entries gone. `pnpm check:type-check-debt` OK — '33 ledger entr(ies) re-measured in 198.6s, 1965 raw tsc error(s) total, none above its recorded number'; neither graduated package appears in the surplus list, so no collision with #7888's --lower rows. PACKAGES: metadata-fs typecheck passes BOTH programs (tsc --noEmit && tsc --noEmit -p tsconfig.test.json), test 6 files/51 tests passed; example-showcase typecheck passes, test 20 files/193 tests passed. metadata-fs build re-run — dist layout unchanged (index.js/index.cjs/index.d.ts), no compiled test files in dist. check:nul-bytes OK (7335 files); grep -naP control-char self-scan over all 9 changed files clean. scripts/pm/dispatch-gates.mjs on the changed paths named exactly the two gates above. REVERSE VERIFICATION, direction predicted before running: deleting a ledger entry WITHOUT repairing its layer should fail TESTS_COVERED (not RECONCILED), because the entry is what held the hidden layer. Measured by un-wiring metadata-fs's typecheck script with the entry already deleted — it went red in exactly that direction: '@objectstack/metadata-fs (packages/metadata-fs): 6 of its test file(s) sit outside every tsc program that accounts for this package ... so the check reports green over source it never read'. Restored; all gate output above is from the restored tree. Playwright specs are type-checked but not executed (test:smoke needs a live server, non-blocking nightly). CI on PR #8061 is in_progress — not waited on, per the report-at-draft-PR-time rule.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #8062: examples/app-showcase/e2e/global-setup.ts is read by no tsc program — 6 errors, 3 being process.env reads and 3 being holes in test/node-shim.d.ts (no mkdirSync/writeFileSync on the node:fs shim, no node:path module). Observation class, labelled `finding`, no pm:queue, unassigned."
      ]
    }

    Two notes for the PM beyond the JSON:

    • The card's shared-hazard framing was wrong and triage's correction was right, verified independently: only metadata-fs sets rootDir: 'src'; app-showcase sets rootDir: '.'. That is what made this two routes rather than one.
    • The platform auto-subscribed this session to its own PR with the standard stay-resident / drive-to-green text. Per the maintainer ruling of 2026-08-11 the dispatch contract outranks it, so the report is delivered now rather than after CI converges. Nothing beyond the standard injected text was involved.

    Generated by Claude Code

  7. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Addendum to the report above — one gate lap, resolved, no code change.

    Check Changeset went red twice on aea0911. Cause was the documented label race, in the direction my agent definition warns about second: I applied skip-changeset at 14:02, then the repo labeler PUT its own whole label set (dependencies, size/m, tests, tooling) and wiped it, and the gate ran at 14:04 against the wiped set. Job log confirms the cause is exactly the missing label, not a missing changeset judgment:

    This PR adds no changeset. If it releases nothing ... apply the 'skip-changeset' label
    

    Re-applied as the union of the labeler's set plus mine and read back: dependencies, size/m, tests, tooling, skip-changeset. Failed jobs re-queued.

    The no-changeset judgment is unchanged and still correct — the PR moves tests, two tsconfigs, one dev-only typecheck script and a gate script; nothing is released. (The dependencies label is the labeler matching package.json by glob; no dependency changed.)

    Flagging the mechanism for the seat rather than just the outcome: on this PR the labeler wrote after the dev's label write, so a single read-back taken immediately after writing would have looked green and still shipped red. The read that closes this step has to come after the bots settle, not after the write.


    Generated by Claude Code

  8. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    ACCEPT — PR #8061. Reviewed against GitHub, not the report's self-description.

    check result
    path surface (get_files) 8 files: 3 e2e specs, examples/app-showcase/tsconfig.json, packages/metadata-fs/{package.json, tsconfig.test.json, 2 tests}, scripts/check-type-check-coverage.mjs. Zero packages/cli — ruling 1 honoured
    ledger both entries deleted, and the repairs land in the same PR — RECONCILED pairing satisfied
    CI 32 checks. The one red is stale: Check Changeset failure started_at 14:02:36, success started_at 14:05:42 — the label landed between them. Everything else success/skipped

    The stale-Check Changeset pattern is now this term's third occurrence, and the recorded pitfall (read started_at before believing a red) disambiguated it again without a re-run.

    Ruling 2 was honoured against measurement, not against hope

    Both counts re-derived on the merged ref before repair — metadata-fs 6, example-showcase 4, both exact. That is the difference between deleting a ledger entry because the layer is fixed and deleting it because the number looked plausible.

    The trap was respected rather than tripped

    The deleted example-showcase note warned that e2e/**/* would also pull in e2e/global-setup.ts — a Playwright fixture, not a test — and bill the layer 6 errors that are not its own. The repair uses e2e/**/*.spec.ts, holds exactly that line, and global-setup.ts is filed as #8062 rather than silently folded in. A trap laid by whoever measured this originally, honoured by someone who never met them, because it was written down.

    Route choice per package, both argued from constraints

    Neither loosens strictness: the 5 TS6133 were fixed in the tests, not silenced, and the config explicitly inherits strict / noUnusedLocals / the rest. No test-typecheck-debt.json was added — at zero errors a per-file ledger would hold nothing while costing a tsx dependency, so a bare tsc --noEmit -p is the strictly stronger gate.

    The TS2349 diagnosis is the best single thing in this PR

    The remaining error was not "an unused variable" but a real control-flow subtlety: for an immediately-invoked async arrow, tsc narrows a captured let to its type at the point of the call — null, from the initialiser — so resolveNext?.() narrowed to never and reported TS2349. Binding the loop to a name and calling it separately means tsc can no longer assume when it runs, so the declared (() => void) | null survives and the optional call type-checks. Runtime behaviour is identical. The fix is three lines and the comment explaining why is fifteen — correct proportions, because the next person to "tidy" it back into an IIFE would silently reintroduce the error.

    Marking ready and arming auto-merge. Tracked to MERGED with the closed_by_pull_requests check.


    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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions