From b195676a77ad0fda2eb3dab242ac28c46c185638 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 14:30:22 +0000 Subject: [PATCH 1/2] fix(metadata): repair the 89 hidden test-type errors and wire a `typecheck` script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `packages/metadata/tsconfig.json` declared a program over `src/**/*` — every test file included — while `package.json` declared no `typecheck` script, so `turbo run typecheck` never selected the package and nothing ever ran that program. Run it and it did not compile: 89 errors across 12 test files, all pre-existing, checked by nothing. Route ruled by precedent, not fresh judgement: #13978 took exactly this for the sibling `@objectstack/metadata-protocol` and it landed. Excluding `**/*.test.ts` is refused — it buys an honest config by giving up the coverage, against this repo's declared = enforced direction. Three mechanical families, no assertion moved and no non-test source touched: * 25 TS2835 — relative imports wanting an explicit `.js` extension under node16 resolution, across 12 files. Every rewritten specifier is relative; no package import (`@objectstack/*`, `vitest`, `node:*`) was touched. * 30 TS2345 — one shared mock `PluginContext` literal in metadata.test.ts, missing `registerServiceFactory` and `getServiceScoped`. Closed ONCE, in the single `createMockPluginContext` factory, with inert stubs: neither member is called anywhere in this package. * 20 TS2322 + 1 TS18048 + 1 TS6133 — the residue the config repair EXPOSED. `(evt) => arr.push(evt)` in a watcher slot typed `void | Promise` returns `number`, and the void-return assignability rule does not forgive it because the target is a union; brace-wrapping the concise body is behaviour-identical. Plus one possibly-undefined read and one unused type import. Repairing the config tier first took 89 -> 52: all 33 TS7006 dissolved (they were the cascade off the unresolved imports, not noise this package owed) while 21 errors the 89 had never contained appeared. So the ledger's recorded split (code-tier 30 / config 25 / noise 34) understated the code tier by 21 — the ledger's own "fix the config first, then read the residue" lesson, measured again. `@objectstack/metadata` graduates out of the DEBT ledger by that ledger's own prescribed path, with the measurement recorded in its preamble. `tsc --noEmit -p packages/metadata/tsconfig.json` now reports 0 errors over a program that holds all 43 of the package's test files. Fixes #14342 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 --- packages/metadata/package.json | 1 + .../src/loaders/database-loader.test.ts | 6 ++-- .../overlay-index-single-producer.test.ts | 2 +- .../metadata/src/metadata-history.test.ts | 6 ++-- .../src/metadata-manager-cluster.test.ts | 14 ++++---- .../src/metadata-realtime-events.test.ts | 4 +-- .../metadata/src/metadata-service.test.ts | 4 +-- packages/metadata/src/metadata.test.ts | 11 ++++-- .../metadata/src/plugin-hmr-reload.test.ts | 6 ++-- packages/metadata/src/plugin.test.ts | 4 +-- .../src/register-notifies-watchers.test.ts | 32 ++++++++--------- .../src/serializers/serializers.test.ts | 6 ++-- packages/metadata/src/utils/lru-cache.test.ts | 2 +- scripts/check-type-check-coverage.mjs | 35 +++++++++++-------- 14 files changed, 72 insertions(+), 61 deletions(-) diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 4ac2213264..5b591ba4ab 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -57,6 +57,7 @@ "build": "tsup && node ../../scripts/check-dts-emitted.mjs", "dev": "tsc --watch", "clean": "rm -rf dist", + "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage" diff --git a/packages/metadata/src/loaders/database-loader.test.ts b/packages/metadata/src/loaders/database-loader.test.ts index 832042b655..e6263ea0eb 100644 --- a/packages/metadata/src/loaders/database-loader.test.ts +++ b/packages/metadata/src/loaders/database-loader.test.ts @@ -1,10 +1,10 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { DatabaseLoader, type DatabaseLoaderOptions } from './database-loader'; +import { DatabaseLoader } from './database-loader.js'; import type { IDataDriver } from '@objectstack/spec/contracts'; -import { MetadataManager } from '../metadata-manager'; -import { MemoryLoader } from './memory-loader'; +import { MetadataManager } from '../metadata-manager.js'; +import { MemoryLoader } from './memory-loader.js'; // Suppress logger output during tests. Stable object (not a fresh one per // `createLogger()` call) so the #5108 block can assert on what `list()` says diff --git a/packages/metadata/src/loaders/overlay-index-single-producer.test.ts b/packages/metadata/src/loaders/overlay-index-single-producer.test.ts index 8f6f2597cc..0efa059293 100644 --- a/packages/metadata/src/loaders/overlay-index-single-producer.test.ts +++ b/packages/metadata/src/loaders/overlay-index-single-producer.test.ts @@ -43,7 +43,7 @@ */ import { describe, it, expect } from 'vitest'; -import { DatabaseLoader } from './database-loader'; +import { DatabaseLoader } from './database-loader.js'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; const INDEX = 'idx_sys_metadata_overlay_active'; diff --git a/packages/metadata/src/metadata-history.test.ts b/packages/metadata/src/metadata-history.test.ts index 193ef648d8..404a3a499a 100644 --- a/packages/metadata/src/metadata-history.test.ts +++ b/packages/metadata/src/metadata-history.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, beforeEach } from 'vitest'; -import { MetadataManager } from './metadata-manager'; -import { DatabaseLoader } from './loaders/database-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { DatabaseLoader } from './loaders/database-loader.js'; import { SqliteWasmDriver } from '@objectstack/driver-sqlite-wasm'; describe('Metadata History', () => { @@ -147,7 +147,7 @@ describe('Metadata History', () => { const diffResult = await manager.diff('object', 'test_object', 1, 2); expect(diffResult.identical).toBe(false); - expect(diffResult.patch.length).toBeGreaterThan(0); + expect(diffResult.patch!.length).toBeGreaterThan(0); expect(diffResult.summary).toContain('modified'); } }); diff --git a/packages/metadata/src/metadata-manager-cluster.test.ts b/packages/metadata/src/metadata-manager-cluster.test.ts index d4812e5f55..1b702e6c0c 100644 --- a/packages/metadata/src/metadata-manager-cluster.test.ts +++ b/packages/metadata/src/metadata-manager-cluster.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi } from 'vitest'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; import type { MetadataLoader } from './loaders/loader-interface.js'; import type { MetadataLoaderContract, MetadataLoadResult, MetadataSaveResult, MetadataStats } from '@objectstack/spec/system'; import type { IPubSub, PubSubMessage } from '@objectstack/spec/contracts'; @@ -172,8 +172,8 @@ describe('MetadataManager — cluster pub/sub bridge', () => { const a: unknown[] = []; const b: unknown[] = []; - mgrA.subscribe('object', (e) => a.push(e)); - mgrB.subscribe('object', (e) => b.push(e)); + mgrA.subscribe('object', (e) => { a.push(e); }); + mgrB.subscribe('object', (e) => { b.push(e); }); // Simulate manager A emitting a watch event by going through the // public publish surface directly (we don't have a repository @@ -197,7 +197,7 @@ describe('MetadataManager — cluster pub/sub bridge', () => { mgr.attachClusterPubSub(bus, 'node-B'); const received: unknown[] = []; - mgr.subscribe('object', (e) => received.push(e)); + mgr.subscribe('object', (e) => { received.push(e); }); mgr.detachClusterPubSub(); mgr.detachClusterPubSub(); // idempotent @@ -220,7 +220,7 @@ describe('MetadataManager — cluster pub/sub bridge', () => { const off2 = mgr.attachClusterPubSub(bus, 'node-B'); // should not double-subscribe const received: unknown[] = []; - mgr.subscribe('object', (e) => received.push(e)); + mgr.subscribe('object', (e) => { received.push(e); }); await bus.publish('metadata.changed', { originNode: 'node-A', @@ -239,7 +239,7 @@ describe('MetadataManager — cluster pub/sub bridge', () => { mgr.attachClusterPubSub(bus, 'node-B'); const received: unknown[] = []; - mgr.subscribe('object', (e) => received.push(e)); + mgr.subscribe('object', (e) => { received.push(e); }); await bus.publish('metadata.changed', { originNode: 'node-A' }); // missing type/event await bus.publish('metadata.changed', null); diff --git a/packages/metadata/src/metadata-realtime-events.test.ts b/packages/metadata/src/metadata-realtime-events.test.ts index c61e4cc9ca..17380e100e 100644 --- a/packages/metadata/src/metadata-realtime-events.test.ts +++ b/packages/metadata/src/metadata-realtime-events.test.ts @@ -24,8 +24,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MetadataEventSchema } from '@objectstack/spec/api'; import type { IRealtimeService, RealtimeEventPayload } from '@objectstack/spec/contracts'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; vi.mock('@objectstack/core', async (orig) => ({ diff --git a/packages/metadata/src/metadata-service.test.ts b/packages/metadata/src/metadata-service.test.ts index d0f441cf56..e1116c6ac4 100644 --- a/packages/metadata/src/metadata-service.test.ts +++ b/packages/metadata/src/metadata-service.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; // Suppress logger output during tests diff --git a/packages/metadata/src/metadata.test.ts b/packages/metadata/src/metadata.test.ts index 30cc3e85ed..14d48f664a 100644 --- a/packages/metadata/src/metadata.test.ts +++ b/packages/metadata/src/metadata.test.ts @@ -4,9 +4,9 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; -import type { MetadataLoader } from './loaders/loader-interface'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; +import type { MetadataLoader } from './loaders/loader-interface.js'; // Suppress logger output during tests vi.mock('@objectstack/core', async (orig) => ({ @@ -953,6 +953,11 @@ function createMockLoaderMany(name: string, items: any[], shouldFail = false): M function createMockPluginContext() { return { registerService: vi.fn(), + // Required members of `PluginContext` (@objectstack/core) that this suite + // never drives — inert stubs so the literal satisfies the parameter type + // rather than 30 call sites each asserting past it. + registerServiceFactory: vi.fn(), + getServiceScoped: vi.fn(), replaceService: vi.fn(), getService: vi.fn().mockReturnValue(null), getServices: vi.fn().mockReturnValue(new Map()), diff --git a/packages/metadata/src/plugin-hmr-reload.test.ts b/packages/metadata/src/plugin-hmr-reload.test.ts index 0927d96916..be07f88bec 100644 --- a/packages/metadata/src/plugin-hmr-reload.test.ts +++ b/packages/metadata/src/plugin-hmr-reload.test.ts @@ -12,8 +12,8 @@ import { describe, it, expect, vi } from 'vitest'; import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { MetadataPlugin } from './plugin'; -import type { NodeMetadataManager } from './node-metadata-manager'; +import { MetadataPlugin } from './plugin.js'; +import type { NodeMetadataManager } from './node-metadata-manager.js'; function fakeCtx() { return { @@ -111,7 +111,7 @@ describe('MetadataPlugin._reloadAndAnnounce — fires metadata:reloaded after re const file = writeArtifact('sweep3'); const perItemEvents: unknown[] = []; - mgr.subscribe('flow', (evt) => perItemEvents.push(evt)); + mgr.subscribe('flow', (evt) => { perItemEvents.push(evt); }); await (plugin as any)._reloadAndAnnounce(ctx, { path: file, fetchTimeoutMs: undefined }, [file]); diff --git a/packages/metadata/src/plugin.test.ts b/packages/metadata/src/plugin.test.ts index c0268b8a6d..8b795b4f83 100644 --- a/packages/metadata/src/plugin.test.ts +++ b/packages/metadata/src/plugin.test.ts @@ -1,8 +1,8 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi } from 'vitest'; -import { MetadataPlugin } from './plugin'; -import { NodeMetadataManager } from './node-metadata-manager'; +import { MetadataPlugin } from './plugin.js'; +import { NodeMetadataManager } from './node-metadata-manager.js'; vi.mock('@objectstack/core', async (orig) => { const real = (await orig()) as any; diff --git a/packages/metadata/src/register-notifies-watchers.test.ts b/packages/metadata/src/register-notifies-watchers.test.ts index b549b979a9..d1e3a185ae 100644 --- a/packages/metadata/src/register-notifies-watchers.test.ts +++ b/packages/metadata/src/register-notifies-watchers.test.ts @@ -50,8 +50,8 @@ import type { MetadataStats, MetadataWatchEvent, } from '@objectstack/spec/system'; -import { MetadataManager } from './metadata-manager'; -import { MemoryLoader } from './loaders/memory-loader'; +import { MetadataManager } from './metadata-manager.js'; +import { MemoryLoader } from './loaders/memory-loader.js'; // `.js` deliberately, unlike the three extensionless imports above it: under // `moduleResolution: nodenext` an extensionless relative import does not // resolve, and every symbol it names silently becomes `any` (AGENTS.md, the @@ -205,7 +205,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => describe('register()', () => { it('announces a first registration as "added"', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'account', { name: 'account', label: 'Account' }); @@ -222,7 +222,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'account', { name: 'account', label: 'V1' }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'account', { name: 'account', label: 'V2' }); @@ -290,7 +290,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('is silent when the caller opts out with { notify: false }', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'account', { name: 'account' }, { notify: false }); @@ -302,8 +302,8 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('only notifies watchers of the written type', async () => { const objects: any[] = []; const views: any[] = []; - manager.subscribe('object', (evt) => objects.push(evt)); - manager.subscribe('view', (evt) => views.push(evt)); + manager.subscribe('object', (evt) => { objects.push(evt); }); + manager.subscribe('view', (evt) => { views.push(evt); }); await manager.register('object', 'account', { name: 'account' }); @@ -320,7 +320,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => readOnly.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY); const seen: any[] = []; - readOnly.subscribe('object', (evt) => seen.push(evt)); + readOnly.subscribe('object', (evt) => { seen.push(evt); }); await readOnly.register('object', 'account', { name: 'account' }); @@ -340,7 +340,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('stops notifying after unsubscribe', async () => { const seen: any[] = []; - const off = manager.subscribe('object', (evt) => seen.push(evt)); + const off = manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.register('object', 'a', { name: 'a' }); off(); @@ -356,7 +356,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'account', { name: 'account' }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.unregister('object', 'account'); @@ -372,7 +372,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'account', { name: 'account' }, { notify: false }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.unregister('object', 'account', { notify: false }); @@ -384,7 +384,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => describe('bulk forms', () => { it('bulkRegister announces one event per item by default', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.bulkRegister([ { type: 'object', name: 'a', data: { name: 'a' } }, @@ -396,7 +396,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => it('bulkRegister forwards { notify: false } to every item', async () => { const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.bulkRegister( [ @@ -420,7 +420,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => ); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.bulkUnregister([ { type: 'object', name: 'a' }, @@ -442,7 +442,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => await manager.register('object', 'other', { name: 'other', packageId: 'com.other' }, { notify: false }); const seen: any[] = []; - manager.subscribe('object', (evt) => seen.push(evt)); + manager.subscribe('object', (evt) => { seen.push(evt); }); await manager.unregisterPackage('com.acme.crm'); @@ -456,7 +456,7 @@ describe('#3112 — register()/unregister() notify subscribe() watchers', () => describe('registerInMemory()', () => { it('stays silent by design (GitOps-owned artefacts, documented on the method)', async () => { const seen: any[] = []; - manager.subscribe('datasource', (evt) => seen.push(evt)); + manager.subscribe('datasource', (evt) => { seen.push(evt); }); manager.registerInMemory('datasource', 'crm_db', { name: 'crm_db', origin: 'code' }); diff --git a/packages/metadata/src/serializers/serializers.test.ts b/packages/metadata/src/serializers/serializers.test.ts index 9533a416a1..3943092f54 100644 --- a/packages/metadata/src/serializers/serializers.test.ts +++ b/packages/metadata/src/serializers/serializers.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; -import { JSONSerializer } from '../serializers/json-serializer'; -import { YAMLSerializer } from '../serializers/yaml-serializer'; -import { TypeScriptSerializer } from '../serializers/typescript-serializer'; +import { JSONSerializer } from '../serializers/json-serializer.js'; +import { YAMLSerializer } from '../serializers/yaml-serializer.js'; +import { TypeScriptSerializer } from '../serializers/typescript-serializer.js'; describe('Serializers', () => { describe('JSONSerializer', () => { diff --git a/packages/metadata/src/utils/lru-cache.test.ts b/packages/metadata/src/utils/lru-cache.test.ts index cef0663218..53c521a78e 100644 --- a/packages/metadata/src/utils/lru-cache.test.ts +++ b/packages/metadata/src/utils/lru-cache.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { LRUCache } from './lru-cache'; +import { LRUCache } from './lru-cache.js'; describe('LRUCache', () => { describe('basic set / get', () => { diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 7a070e6fdd..0484ca5f21 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -642,6 +642,26 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts'; // at all, and simultaneously EXPOSED 12 real defects in `service-settings` // that the unresolved imports had been masking. A config-tier count is an // upper bound on nothing: fix the config first, then read the residue. +// +// `@objectstack/metadata` GRADUATED from this ledger (#14342; entry: 89 raw, +// repaired to 0, the route #13978 took for `metadata-protocol` and ruled by +// precedent for this one). It is worth a line because it is the sharpest +// measurement of the paragraph directly above, and because the composition +// this entry recorded was wrong in a way no re-measure could show: the recorded +// split was code-tier 30 / config-tier 25 / noise 34, and repairing the 25 +// TS2835 alone took the pile to 52 -- ALL 33 TS7006 dissolved (they were the +// cascade, not noise the package owed), while 20 TS2322 and 1 TS18048 appeared +// that the 89 had never contained. So the true code tier was 51, not 30: 30 +// TS2345 (the one shared mock `PluginContext` literal, closed by adding the two +// members it lacked to the single factory), 20 TS2322 (`(evt) => arr.push(evt)` +// in a watcher slot typed `void | Promise` -- a concise arrow body whose +// `number` only becomes visible once the callback parameter has a real type, +// and which the void-return assignability rule does NOT forgive because the +// target is a union), and 1 TS18048. This is the `rest` lesson (TS18048 x13 +// exposed the same way) measured a second time, on a package whose note had +// already itemised its own tiers with confidence: a tier split read off an +// unrepaired config is a guess about what is UNDER it, and the only honest way +// to size the code tier is to fix the config and look. const DEBT = { '@objectstack/cloud-connection': { errors: 13, @@ -658,21 +678,6 @@ const DEBT = { errors: 3, note: 'all code-tier (TS2769/TS18046).', }, - '@objectstack/metadata': { - errors: 89, - note: 'code-tier 30 (TS2345 x30); config-tier 25 (TS2835 x25); noise 34 (TS7006 x33, TS6133). ' - + 'Re-measured 89 at 4b84834a32, DOWN from 92 at 5ab08428 -- itself up from 87, so this entry has ' - + 'now drifted both ways. Against the composition recorded here at 92 the delta is attributable ' - + 'tier by tier: code-tier lost the 4 TS2322 (-4), config-tier gained one TS2835 (+1), noise did ' - + 'not move. TS2353 then TS2322 have each passed through the code tier and left; TS2345 x30 is its ' - + 'only lasting resident. Read the 89 as three mechanical repairs, not 89 problems: all 30 TS2345 ' - + 'are one defect thirty times over, in metadata.test.ts between 608 and 945, every one the same ' - + 'mock PluginContext literal missing registerServiceFactory and getServiceScoped, so one shared ' - + 'fixture closes the code tier outright; the 25 TS2835 are the widest spread (12 files) and are ' - + 'one codemod, a relative import wanting an explicit .js extension under node16 resolution. ' - + 'metadata.test.ts (34) and register-notifies-watchers.test.ts (16) do still hold 50 of the 89, ' - + 'but that is over HALF -- the "two thirds" claimed here was true at neither 92 nor 89.', - }, '@objectstack/observability': { errors: 11, note: 'all code-tier (TS2554 wrong arity x10, TS2552).', From ef901e559bb6786c1c5f6dfb8e0502759edeeb36 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 15:53:50 +0000 Subject: [PATCH 2/2] docs(metadata): stop the codemod from falsifying the comment beside it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `register-notifies-watchers.test.ts` carried a block explaining why its `loader-interface` import spells `.js`. Two of its sentences were true when written and are false at this branch's head, and it is this branch that falsified them: * "unlike the three extensionless imports above it" — after the codemod the relative imports above it carry `.js`, so the contrast has nothing left to contrast with. (It was already loose: only two relative imports sat above it, the third being a package specifier the sentence's own reasoning is not about.) * "this package's pre-existing type-check debt (#4311) ... left for whoever pays that ledger down" — this branch IS that payment. The comment told the next reader to leave alone the thing the same change repaired. The reason the block exists is kept, because it is exactly what this change proves: under `moduleResolution: nodenext` an extensionless relative import does not resolve and every symbol it names silently becomes `any`, which is what makes `implements MetadataLoader` on the fixture below a check rather than decoration. What replaces the two false sentences is the fact that now holds — the package declares a `typecheck` script, so `turbo run typecheck` compiles this file and a regression here is a red gate rather than a silent `any`. Also makes the DEBT-preamble graduation note quote the row it replaces verbatim ("code-tier 30 (TS2345 x30); config-tier 25 (TS2835 x25); noise 34 (TS7006 x33, TS6133)") instead of paraphrasing the split. Both edits are comments. Re-verified on this head: `pnpm --filter @objectstack/metadata typecheck` 0 errors, 43 test files / 677 tests green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 --- .../metadata/src/register-notifies-watchers.test.ts | 11 ++++++----- scripts/check-type-check-coverage.mjs | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/metadata/src/register-notifies-watchers.test.ts b/packages/metadata/src/register-notifies-watchers.test.ts index d1e3a185ae..1a07faff82 100644 --- a/packages/metadata/src/register-notifies-watchers.test.ts +++ b/packages/metadata/src/register-notifies-watchers.test.ts @@ -52,13 +52,14 @@ import type { } from '@objectstack/spec/system'; import { MetadataManager } from './metadata-manager.js'; import { MemoryLoader } from './loaders/memory-loader.js'; -// `.js` deliberately, unlike the three extensionless imports above it: under -// `moduleResolution: nodenext` an extensionless relative import does not +// `.js` on this relative import, as on every relative import in this file: +// under `moduleResolution: nodenext` an extensionless relative import does not // resolve, and every symbol it names silently becomes `any` (AGENTS.md, the -// TS7006 cascade). Spelling this one correctly is what makes `implements +// TS7006 cascade). Spelling it correctly is what makes `implements // MetadataLoader` on the fixture below an actual check rather than decoration. -// The three above are this package's pre-existing type-check debt (#4311) and -// are left for whoever pays that ledger down. +// That is no longer a convention this file keeps on its own: the package +// declares a `typecheck` script, so `turbo run typecheck` compiles this file +// and an extensionless relative import here is a red gate, not a silent `any`. import type { MetadataLoader } from './loaders/loader-interface.js'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 0484ca5f21..b13ceef2e1 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -647,9 +647,10 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts'; // repaired to 0, the route #13978 took for `metadata-protocol` and ruled by // precedent for this one). It is worth a line because it is the sharpest // measurement of the paragraph directly above, and because the composition -// this entry recorded was wrong in a way no re-measure could show: the recorded -// split was code-tier 30 / config-tier 25 / noise 34, and repairing the 25 -// TS2835 alone took the pile to 52 -- ALL 33 TS7006 dissolved (they were the +// this entry recorded was wrong in a way no re-measure could show. Its opening +// itemisation read, verbatim, 'code-tier 30 (TS2345 x30); config-tier 25 +// (TS2835 x25); noise 34 (TS7006 x33, TS6133)'. Repairing the 25 TS2835 alone +// took the pile to 52 -- ALL 33 TS7006 dissolved (they were the // cascade, not noise the package owed), while 20 TS2322 and 1 TS18048 appeared // that the 89 had never contained. So the true code tier was 51, not 30: 30 // TS2345 (the one shared mock `PluginContext` literal, closed by adding the two