fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding - #15157

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck
Sep 4, 2026
Merged

fix(service-storage): put the test layer in front of tsc, and repair what it was hiding#15157
os-warren merged 3 commits into
mainfrom
claude/issue-15050-service-storage-typecheck

Conversation

@os-sales

@os-salesos-sales commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes#15050

packages/services/service-storage had no typecheck script at all (its scripts were build and test), so no tsc program anywhere read this package's test layer. Turbo/CI typecheck lanes skipped it silently, because a zero-matching filter run exits 0. tsup transpiles with esbuild and vitest runs through esbuild type-stripping; neither type-checks.

Clause-②: no — this is the checked-test-zone mechanism (#14062/#14181) applied one package over; it touches no spec/API contract surface, only this package's own test files, tsc configs and the two shared ledger scripts the ratchet already routes graduations through.

The route, and which exemplar it was copied from

Copied from plugin-webhooks / service-cluster (#14181, PR #15032 — read before starting), not from plugin-auth / plugin-sharing / core, because it is the structural match. The deciding property is what the BUILD config does with tests:

packagetsconfig.json excludes tests?fits here
plugin-auth, plugin-sharing, coreyesno
plugin-webhooks, service-cluster, service-storagenoyes

service-storage's tsconfig.json includes the tests and always did, so the program that would have read them already existed and was simply never invoked. tsconfig.json is therefore untouched; the sibling tsconfig.test.json is the family's uniform instrument over the same files (module semantics only — esnext/bundler/lib: ES2022; strictness inherited, no paths).

A ninth file also needed the same treatment: scripts/i18n-extract.config.ts is the ninth instance #11351 onboarded for the other 8 packages that carry one — deliberately left out of that ledger before now, because SOURCES_COVERED only asks its question of a package that declares typecheck, and this one didn't. Giving it a typecheck script makes that question start firing here, so tsconfig.scripts.json (copied from packages/objectql's minimal shape) completes the family in the same PR rather than leaving a new gap the moment typecheck exists.

Measured error count, both ways, before and after

Dependency closure built first (pnpm --filter '@objectstack/service-storage^...' build; @objectstack/objectql's DTS worker had been truncated by an unrelated 10-minute foreground kill on the first attempt — see Findings — and was rebuilt before measuring):

programbeforeafter
BUILD semantics (tsc --noEmit -p tsconfig.json, already includes tests)51 (matches the DEBT ledger's recorded 51 exactly)0
test layer (tsc --noEmit -p tsconfig.test.json)100

1102 files in the BUILD program / 1068 in the test program, covering all 35 of the package's src/**/*.test.ts. (Re-measured after the merge below with --listFiles: still 1102 / 1068 / 35, both programs still exit 0.)

Both readings agree at 0/0 — the same headline result service-cluster reported, reached by a longer road. Unlike that package, this one's BUILD reading was not already clean, so the 51→0 needed real repair on both legs, not just the test-only split:

  • code-tier 8 (TS2339 × 4, TS2347 × 4) — genuine test-file defects, fixed in the tests (below).
  • config-tier 26 (TS2835 × 23 relative-import extensions, TS2550 × 3 Array.prototype.at) — repaired, not routed around.
  • noise 17 (TS7006 × 15, TS6196 × 1, TS6133 × 1).

The composition matches the DEBT entry's own note (code-tier 8 (TS2339 x4, TS2347 x4); config-tier 26 (TS2835 x23, TS2550 x3); noise 17 (TS7006 x15, TS6196, TS6133)) exactly — a positive control that the 51-measurement is reading the same thing the ledger already recorded.

The config-tier repair, and why it's a repair rather than a workaround

The 23 TS2835 are relative imports missing their .js extension, required under BUILD's NodeNext resolution but optional under the split's bundler resolution. The fix is to add the extension (./local-storage-adapter./local-storage-adapter.js), across 12 test files — this resolves correctly under both module modes, so it isn't a config-only accommodation, it's a real fix. Doing so also cleared all 15 TS7006 as a downstream cascade — the same shape @objectstack/core reported at 98 → 4 (#14916): an unresolved relative import makes every symbol it names any, and once the import resolves, the "implicitly any" errors disappear with it. Measured directly: 41 BUILD errors before the extension fix, 3 after (all TS2550).

The remaining 3 TS2550 (Array.prototype.at needing lib es2022) are rewritten to indexed access (arr[arr.length - 1]) in storage-adapter-list.conformance.test.ts rather than widening the shared BUILD tsconfig.json's lib — the narrower fix, and it leaves the BUILD config untouched.

The 8 code-tier errors, and why each fix is correct rather than convenient

  • TS2339 × 4, file-reference-lifecycle.test.ts — the driveInsert test helper builds row by spreading a value typed Record of string keys to unknown values, plus id. TypeScript's object-spread inference drops the source's index signature when it has no known properties, so an un-annotated row infers as bare { id: string } and every per-key read off row is an error — even though the runtime shape genuinely carries the caller's fields. Reproduced in isolation (a 5-line repro) before touching the file. Fixed with an explicit annotation on row — the same Record shape intersected with { id: string } — which states the true type rather than widening anything.
  • TS2347 × 4, storage-service-plugin.test.ts — a fake ctx: any's getService method (declared generic, one type parameter) becomes any-typed through the outer annotation, so calling it WITH an explicit type argument — ctx.getService('storage') spelled with the type argument inline, IStorageService — is invalid (TS can't verify a generic call through any). One call site in this same file had already hit this and left a comment documenting the fix: "Plain calls with casts, not the type-argument form — the fake ctx's getService is untyped, and each type-argument call adds a frozen-debt TS2347..." — the other three call sites just hadn't been converted yet. Converted all four to the cast form, ctx.getService('storage') as the target type, the pattern the file itself had already established.
  • TS6196 × 1 — a genuinely dead StorageFileInfo import, invisible until a tsc program finally read the file. Removed.
  • TS6133 × 1 — an unused destructured param in a .map() callback. Renamed to _r (the underscore-ignore convention already used elsewhere in this package's tests).

Ledger consequences

  • DEBT['@objectstack/service-storage'] deleted, not lowerederrors: 51 is gone; the graduation note is recorded in check-type-check-coverage.mjs's own prose, per its convention. Post-merge figures: the gate now reads 53 raw errors across 4 packages, where main at 460134af8 reads 104 across 5. (The pre-merge body said 66/6 from 117/7; both siblings of this family have since landed and taken their own entries out, so those two pairs are stale and are replaced here.)
  • No test-typecheck-debt.json created — residue is 0, so none is owed; opening one is maintainer-only (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286).
  • UNCHECKED_SOURCE_DEBT's "ninth config" paragraph updated to record that scripts/i18n-extract.config.ts joined the other eight, measuring 0 errors under its new tsconfig.scripts.json, same as all eight siblings.

⚠️check:type-source-resolution — an onboarding re-baseline, please review it as one

This gate went red on my diff. Onboarding tsconfig.test.json + tsconfig.scripts.json moves the package's tsc PROGRAM SET, which the gate judges per program — its own doc-block opens an explicit onboarding limb for exactly this, on the same three terms #14181/PR #15032 used for service-cluster.

Term 1 — provenance, measured four ways on one checkout by varying only what typecheck names (--list). Re-measured on the post-merge tree (the two landed siblings moved every absolute here and none of the deltas), restoring package.json from HEAD between rows and proving the restore byte-identical:

typecheck namesentrytotals
nothing / tsconfig.json onlyabsent121 programs / 302 pairs
tsconfig.test.json onlyPRESENT122 programs / 309 pairs
all three (this PR)PRESENT123 programs / 309 pairs

Rows 1–2 match origin/main @ 460134af8 measured on its own in a detached worktree (60 of 78 packages, 121 programs, 302 pairs, 18 clean).

Row 2 is load-bearing: BUILD carries zero dist-resolved workspace type imports, so the exposure is only reachable through the onboarded programs, not merely first seen there.

Term 2 — numbers: +1 package (60→61 of 78), +2 programs, +7 pairs — this entry and nothing else. The deltas are unchanged from the reviewed reading; only the absolutes moved (the pre-merge body said 58→59).

Term 3 — why the registry entry and not paths, measured both ways on the pre-merge checkout (temporary paths block added to tsconfig.test.json, tsc --noEmit -p run, then removed — never committed): redirecting all 7 deps to source takes this package's test layer from 0 errors to 306 (305 × TS6059 "not under rootDir" + 1 × TS6133), every TS6059 in another package's source (packages/types/src/**, packages/spec/src/**, packages/objectql/src/**, packages/observability/src/**, packages/drivers/driver-sql/src/**) — billed to a package that cannot pay it down. Same shape as #12570 (+5 for rest) and #8021 (247 TS6059), reproduced at larger scale on a package whose whole point here is reaching zero. This 306 was NOT re-measured after the merge — and it is not being claimed as unchangeable: main moved packages/spec/src/** and other trees the TS6059 land in, so the exact number could differ today. What the merge cannot flip is the direction the term rests on (a paths redirect bills hundreds of other packages' TS6059 to this one), and the committed tree contains no paths either way.

Gates

All figures in this section are the post-merge re-run, on head 2e8cbeed5 over merge base 460134af8 — not carried over from the pre-merge round. origin/main was merged into this branch (merge commit a58676160), never rebased; the conflict was in check-type-check-coverage.mjs only, and it resolved as "keep every entry" — all three graduation paragraphs kept, all three DEBT entries gone.

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands against the post-merge diff (20 paths vs merge base 460134af8).

66 command(s) — 36 pnpm, 30 direct node. All run, all 66 green.

The three that read exit 3 (PREREQUISITE NOT MET) in the pre-merge round — check:dual-build-cjs-loads, check:i18n, check:type-check-debt — plus the artifact-roster gate check:published-readme-exports that read exit 1 for the same missing-dist/ reason, were all re-run after building the whole workspace (turbo run build --filter './packages/*' --filter './packages/*/*', 71/71 successful) and all four are exit 0. check:type-check-debt re-measured 14 ledger entries in 147.9s, 153 raw tsc errors total, none above its recorded number.

check:type-check-coverage verdict on the merged tree: OK — 75/79 workspace packages type-checked (plus the root), 4 in the DEBT ledger (53 frozen raw errors), 1 exempt.check:type-source-resolution verdict: OK — 123 tsc program(s) across 78 packages scanned; 61 registered as still resolving a workspace dep's types through dist/.

Package-level, on the merged head: pnpm --filter @objectstack/service-storage typecheck — exit 0, all three legs (tsc --noEmit, tsc --noEmit -p tsconfig.scripts.json, check:test-typecheck) clean, 0 file(s) / 0 error(s) / 0 pinned signature(s). pnpm --filter @objectstack/service-storage exec vitest run — exit 0, 35 files / 536 tests passed, the same counts the pre-merge round recorded.

Both edited gate scripts (check-type-check-coverage.mjs, check-type-source-resolution.mjs) ran their own --self-test green on the merged tree.

pnpm-lock.yaml was not hand-merged: pnpm install over the merged manifest set leaves the auto-merged file byte-identical (git hash-object = 000bb7dfbcebaca6c6b92a26d9d970d464c3f6fd before and after), and pnpm install --frozen-lockfile exits 0.

Figures NOT re-measured after the merge, stated as such rather than implied: the before columns of the error table (51 BUILD / 10 test layer), their tier decomposition (8 / 26 / 17), the intermediate 41 → 3 extension-fix reading, and the 306-error paths measurement in Term 3. All four are readings of the pre-fix tree; the after column (0 / 0) is the live claim and it is re-measured above.

Findings (out of scope, reported)

  • @objectstack/objectql's DTS worker was truncated by the ~10-minute foreground kill on the first pnpm --filter '@objectstack/service-storage^...' build attempt in a fresh worktree: dist/ had .js/.mjs but zero .d.ts files, check-dts-emitted.mjs never ran (the whole pnpm build recursive run was SIGTERM'd mid-flight), yet the dist/*.js files were already on disk. This produced 16 spurious TS7016 ("Could not find a declaration file for module '@objectstack/objectql'") errors in the first undivided measurement (73, not 51) — a false read that would have been invisible without the ledger's 51 as a positive control to notice the mismatch against. Not filed as a separate issue: it's a restatement of the platform-fact AGENTS.md already documents (front-of-process ~10-minute SIGTERM cap can land mid-build), not a new defect — rebuilding the one truncated package (pnpm --filter '@objectstack/objectql' build, ~17s for its DTS step alone) fixed it.

Declined, with reasons

  • No paths rules added — measured at +306 TS6059 in other packages' source (above).
  • No test skipped, disabled or quarantined; no program narrowed; no @ts-expect-error/@ts-ignore added; strict untouched; BUILD tsconfig.json's lib untouched (the 3 TS2550 sites were rewritten instead).
  • packages/spec not touched (single-owner lane) — only read, to confirm PubSubHandler/IStorageService contracts were not implicated.
  • No content/docs/releases edit.
  • Nothing outside this card touched in the merge round — no edit to service-automation or service-knowledge, whose PRs have landed; the diff against main is the same 20 files it was when reviewed.

Changeset

.changeset/service-storage-test-tsc-program.md, patch on @objectstack/service-storage. No runtime code changed — src/** excluding tests is byte-identical — so no shipped behaviour moves; the level reflects the published package.json gaining typecheck/check:test-typecheck scripts and a tsx devDependency.

The merge-conflict round was carried out by a second Claude Code session, https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y; the implementation round's session is named in the footer below.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8


Generated by Claude Code

…what it was hiding (#15050)
`packages/services/service-storage` had no `typecheck` script at all, so no
tsc program anywhere read its test layer; its 51 errors were carried as a
DEBT entry. Gives it the #14062/#14181 checked-test-zone shape: a sibling
`tsconfig.test.json` plus `tsconfig.scripts.json` (the ninth `i18n-extract`
instance of #11351), both named by a new `typecheck` script. Fixes the
config-tier pile (missing `.js` extensions, `Array.prototype.at` lib gap)
and the genuine code-tier defects it was hiding (a test helper's spread
losing its index signature; a fake `ctx: any`'s generic calls). Both
readings now agree at 0/0. DEBT entry deleted, not lowered; no
test-typecheck-debt.json needed. `check:type-source-resolution` repaired via
the documented onboarding-limb registry entry, not `paths` (measured both
ways: `paths` -> 306 errors, all in other packages' source).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actionsBot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/services/service-storage/tsconfig.scripts.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899ddpackageMentionDocs.

Resolves the third and last conflict of the #14062 family. Both siblings
have landed on main (#15147 service-knowledge, #15152 service-automation)
and all three touch the same two gate scripts.
Resolution is "keep every entry" -- the three cards do not disagree:
* scripts/check-type-check-coverage.mjs: all three graduation paragraphs
kept above `DEBT` (service-storage first, so the service-knowledge
paragraph's "the paragraph above ... for `metadata` and `service-storage`"
back-reference still resolves), and all three DEBT entries deleted. The
ledger summary line is computed at runtime from DEBT/TEST_DEBT, so no
hand-edited total exists there to reconcile.
* scripts/check-type-source-resolution.mjs: auto-merged; all three new
registry entries present, service-cluster / service-i18n untouched.
* pnpm-lock.yaml: not hand-merged. `pnpm install` over the merged manifest
set leaves the auto-merged file byte-identical (hash 000bb7d...) and
`pnpm install --frozen-lockfile` exits 0.
The diff against main is unchanged from the reviewed one: same 20 files,
same +307/-133.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
…he two landed siblings
The merge of origin/main @ 460134a brought in the service-knowledge
(#15049) and service-automation (#15048) onboardings, which moved every
ABSOLUTE this entry's doc-block states and none of its DELTAS. Re-measured
all four provenance rows on the merged tree by varying only what the
`typecheck` script NAMES, restoring package.json from HEAD between rows
(restore proven byte-identical: hash-object == HEAD blob 40773ae,
`git diff HEAD` empty):
no `typecheck` script (origin/main) absent 121 programs / 302 pairs
names `tsconfig.json` only absent 121 programs / 302 pairs
names `tsconfig.test.json` only PRESENT 122 programs / 309 pairs
names all three (this card) PRESENT 123 programs / 309 pairs
origin/main itself measures 60 of 78 / 121 / 302 / 18 clean in a detached
worktree at 460134a, matching rows 1-2. Deltas unchanged: +1 package,
+2 programs, +7 pairs.
`check-type-check-coverage.mjs` needed no equivalent edit: its ledger
summary is computed at runtime from DEBT/TEST_DEBT and now prints
75/79 covered, 4 in DEBT, 53 frozen raw errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
@os-warren
os-warren added this pull request to the merge queueSep 4, 2026
Merged via the queue into main with commit ebb5550Sep 4, 2026
38 checks passed
@os-warren
os-warren deleted the claude/issue-15050-service-storage-typecheck branch September 4, 2026 09:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tooling(services): service-storage has no typecheck script — its test layer is compiled by no tsc program (#14062 family, sibling of #14181)

3 participants

@os-sales@os-warren@claude