diff --git a/packages/adapters/hono/package.json b/packages/adapters/hono/package.json index 4fd636ad50c..0eb469a48f3 100644 --- a/packages/adapters/hono/package.json +++ b/packages/adapters/hono/package.json @@ -14,7 +14,8 @@ "scripts": { "build": "tsup --config ../../../tsup.config.ts && node ../../../scripts/check-dts-emitted.mjs", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json" }, "dependencies": { "@objectstack/plugin-hono-server": "workspace:*", diff --git a/packages/adapters/hono/src/__mocks__/runtime.ts b/packages/adapters/hono/src/__mocks__/runtime.ts index b87987aa761..949587c1c53 100644 --- a/packages/adapters/hono/src/__mocks__/runtime.ts +++ b/packages/adapters/hono/src/__mocks__/runtime.ts @@ -9,13 +9,13 @@ // in `packages/**`. Declaring MORE than this adapter calls is fine (it calls // only `getDiscoveryInfo`, `handleAuth` and `dispatch`); declaring something the // dispatcher does not implement is not. -import { vi } from 'vitest'; +import { vi, type Mock } from 'vitest'; export class HttpDispatcher { - getDiscoveryInfo = vi.fn().mockReturnValue({ version: '1.0', routes: {} }); - handleAuth = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { ok: true } } }); - handleMetadata = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { objects: [] } } }); - handleData = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { records: [] } } }); + getDiscoveryInfo: Mock = vi.fn().mockReturnValue({ version: '1.0', routes: {} }); + handleAuth: Mock = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { ok: true } } }); + handleMetadata: Mock = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { objects: [] } } }); + handleData: Mock = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { records: [] } } }); constructor(_kernel: any) {} } diff --git a/packages/adapters/hono/src/hono.test.ts b/packages/adapters/hono/src/hono.test.ts index 063236de3fa..5f4c0204d19 100644 --- a/packages/adapters/hono/src/hono.test.ts +++ b/packages/adapters/hono/src/hono.test.ts @@ -22,13 +22,18 @@ import { objectStackMiddleware, createHonoApp } from './index'; const mockKernel = { name: 'test-kernel' } as any; +// What `objectStackMiddleware` puts on the context (`c.set('objectStack', kernel)`). +// An app a test reads it back from must declare that variable: a bare `new Hono()` +// has no `Variables`, so Hono types `c.get` as taking `never`. +type KernelEnv = { Variables: { objectStack: { name: string } } }; + describe('objectStackMiddleware', () => { beforeEach(() => { vi.clearAllMocks(); }); it('sets kernel on context via c.set', async () => { - const app = new Hono(); + const app = new Hono(); const middleware = objectStackMiddleware(mockKernel); app.use('*', middleware); @@ -61,7 +66,7 @@ describe('objectStackMiddleware', () => { }); it('provides the correct kernel instance', async () => { - const app = new Hono(); + const app = new Hono(); const middleware = objectStackMiddleware(mockKernel); app.use('*', middleware); diff --git a/packages/adapters/hono/tsconfig.test.json b/packages/adapters/hono/tsconfig.test.json new file mode 100644 index 00000000000..4e9fca04706 --- /dev/null +++ b/packages/adapters/hono/tsconfig.test.json @@ -0,0 +1,75 @@ +// The TEST-layer type-check program (#20800 -- the last three packages of the +// #4311 ledger, graduated by the #15048 / #15050 road: a sibling program named +// by the `typecheck` script). `tsconfig.json` beside this one stays exactly as +// it is: it is the BUILD config. `package.json`'s `typecheck` script NAMES this +// sibling (`tsc --noEmit -p tsconfig.test.json`), because a config no script +// invokes reads as coverage and delivers none. +// +// WHAT THE BUILD CONFIG ALREADY DID. `tsconfig.json` includes `src/**/*` with no +// test exclusion, so the program that reads these tests always existed; the +// package simply declared no `typecheck` script, so nothing ever ran it. That +// is the `service-automation` / `service-cluster` shape, not the +// exclude-and-compensate one, and AGENTS.md forbids ADDING such an exclusion. +// +// What differs from the build config, and what deliberately does NOT: +// - MODULE SEMANTICS and `lib`, nothing else. vitest runs these files as ESM +// through esbuild on a Node that has the ES2022 builtins. `lib` keeps the +// build config's `DOM` / `DOM.Iterable` (a child's `lib` REPLACES the +// parent's, it does not merge) and adds ES2022 for the Node the suite runs +// on. The build leg of the script (`tsc --noEmit`) keeps the NodeNext +// reading of the same files. +// - STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`, +// `noImplicitReturns`, `noFallthroughCasesInSwitch`, `rootDir`, `paths` and +// `types` are all INHERITED and none is re-declared. ⚠️ A child that declared +// its own `paths` would REPLACE the parent map, so this file declares none. +// Nothing here may loosen a type rule; if a test does not compile, that is +// the finding. +// +// MEASURED at f8178ffece (origin/main), the workspace closure built first +// (`turbo run build --filter='@objectstack/hono^...'`), BEFORE any fix, once per +// config (`tsc --noEmit --pretty false [--listFiles] -p `): +// +// program under this config 858 files (7 of them this package's `src`), +// all 5 of its `src/**/*.test.ts` in it +// BUILD config (tsconfig.json) 3 = TS2769 x2 + TS18046 x1 +// THIS config 3 = TS2769 x2 + TS18046 x1 +// +// The two readings AGREE, so no config-tier pile; the 3 were code tier. All in +// `hono.test.ts` (36, 69, 70): a bare `new Hono()` has Hono's default `Env`, which +// declares no `Variables`, so `c.get('objectStack')` is typed as taking `never` +// (TS2769 x2) and the `kernel` it returns is `unknown` (TS18046). The middleware +// under test does `c.set('objectStack', kernel)`, so the two tests that read the +// variable back now build their app as `new Hono()` with the variable +// declared (`{ objectStack: { name: string } }`, the one field the test reads). +// `objectStackMiddleware`'s `(c: any, next: any)` source signature was NOT touched. +// +// THESE 3 WERE NOT THE WHOLE PILE. With them fixed, THIS config reported 4 more: +// TS2883 ("The inferred type of 'getDiscoveryInfo' cannot be named without a +// reference to 'Procedure' from '.pnpm/@vitest+spy@4.1.11/...'") on the four +// `vi.fn()` class fields of `src/__mocks__/runtime.ts`. tsc withholds +// declaration-emit diagnostics while any semantic error stands, so the first +// reading could not show them, and the BUILD config never shows them at all +// (observed: exit 0 on the same file under NodeNext resolution; only this +// config's `bundler` resolution reports it). The `DEBT` ledger's "3" was a count +// of what the first config could see, not of what this layer holds. Repaired +// at the cause, the same way as the others: the four fields are annotated +// `Mock` (vitest's own mock type) instead of leaving the inferred type to name an +// unexported pnpm-store path. No cast, no `@ts-expect-error`, no `src` change: +// `src/__mocks__/runtime.ts` is a vitest alias target (`vitest.config.ts`), never +// built or published (`tsup` enters `src/index.ts` alone). +// +// AFTER: 0 and 0. There is NO `test-typecheck-debt.json` beside this config, and +// its ABSENCE is the zero: any error in any file here is red immediately, with no +// ledger to be added to -- the same call `metadata-fs` and +// `trigger-record-change` recorded. +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/cloud-connection/package.json b/packages/cloud-connection/package.json index 9e3532c3507..e67b43f6fb3 100644 --- a/packages/cloud-connection/package.json +++ b/packages/cloud-connection/package.json @@ -20,7 +20,8 @@ }, "scripts": { "build": "tsup --config ../../tsup.config.ts && node ../../scripts/check-dts-emitted.mjs", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/cloud-connection/src/cloud-connection-plugin.test.ts b/packages/cloud-connection/src/cloud-connection-plugin.test.ts index 65f53fc72d5..b925e881505 100644 --- a/packages/cloud-connection/src/cloud-connection-plugin.test.ts +++ b/packages/cloud-connection/src/cloud-connection-plugin.test.ts @@ -151,7 +151,7 @@ describe('multi-tenant mode (env-registry + per-env kernel auth)', () => { it('install forwards to the control plane install action for an authenticated session', async () => { const rawApp = makeRawApp(); const { ctx, fireKernelReady } = makeCtx({ rawApp, services: mtServices('user-1') }); - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true, data: { installed: true } }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true, data: { installed: true } }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ controlPlaneUrl: 'http://cloud.test', controlPlaneApiKey: 'svc-key' }).start(ctx as any); await fireKernelReady(); @@ -180,7 +180,7 @@ describe('multi-tenant mode (env-registry + per-env kernel auth)', () => { const rawApp = makeRawApp(); const { ctx, fireKernelReady } = makeCtx({ rawApp, services: mtServices('user-1') }); const items = [{ id: 'pkg_a', manifest_id: 'com.acme.crm' }]; - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true, data: { items } }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true, data: { items } }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ controlPlaneUrl: 'http://cloud.test', controlPlaneApiKey: 'svc-key' }).start(ctx as any); await fireKernelReady(); @@ -206,7 +206,7 @@ describe('single-environment mode (host auth, fixed env id)', () => { it('uses the configured environment id + the host kernel auth session', async () => { const rawApp = makeRawApp(); const { ctx, fireKernelReady } = makeCtx({ rawApp, services: { auth: sessionAuth('admin-1') } }); - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true, data: { packages: [{ id: 'p1' }] } }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true, data: { packages: [{ id: 'p1' }] } }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ singleEnvironment: true, diff --git a/packages/cloud-connection/src/connection-credential-store.test.ts b/packages/cloud-connection/src/connection-credential-store.test.ts index 0028789ecc1..d4eeeb5a52d 100644 --- a/packages/cloud-connection/src/connection-credential-store.test.ts +++ b/packages/cloud-connection/src/connection-credential-store.test.ts @@ -112,7 +112,7 @@ describe('CloudConnectionPlugin credential behavior', () => { new ConnectionCredentialStore(credPath).write({ runtimeToken: 'oscc_stored', environmentId: 'env_1' }); const rawApp = makeRawApp(); const { ctx, fire } = makeCtx(rawApp, { auth: sessionAuth('admin'), manifest: { register: vi.fn() } }); - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true, data: { items: [] } }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true, data: { items: [] } }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ singleEnvironment: true, controlPlaneUrl: 'http://cloud.test', controlPlaneApiKey: '', credentialPath: credPath, @@ -134,7 +134,7 @@ describe('CloudConnectionPlugin credential behavior', () => { store.write({ runtimeToken: 'oscc_stored', environmentId: 'env_1' }); const rawApp = makeRawApp(); const { ctx, fire } = makeCtx(rawApp, { auth: sessionAuth('admin'), manifest: { register: vi.fn() } }); - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ singleEnvironment: true, controlPlaneUrl: 'http://cloud.test', controlPlaneApiKey: '', credentialPath: credPath, @@ -221,7 +221,7 @@ describe('CloudConnectionPlugin credential behavior', () => { store.write({ runtimeToken: 'oscc_stored', runtimeId: 'rt-1' }); const rawApp = makeRawApp(); const { ctx, fire } = makeCtx(rawApp, { auth: sessionAuth('admin'), manifest: { register: vi.fn() } }); - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ singleEnvironment: true, controlPlaneUrl: 'http://cloud.test', controlPlaneApiKey: '', credentialPath: credPath, @@ -267,7 +267,7 @@ describe('CloudConnectionPlugin credential behavior', () => { new ConnectionCredentialStore(credPath).write({ runtimeToken: 'oscc_stored', runtimeId: 'rt-1' }); const rawApp = makeRawApp(); const { ctx, fire } = makeCtx(rawApp, { auth: sessionAuth('admin'), manifest: { register: vi.fn() } }); - const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ success: true, data: { items: [{ id: 'p1' }] } }), { status: 200 })); + const fetchSpy = vi.fn(async (_url: string, _init?: RequestInit) => new Response(JSON.stringify({ success: true, data: { items: [{ id: 'p1' }] } }), { status: 200 })); vi.stubGlobal('fetch', fetchSpy); await new CloudConnectionPlugin({ singleEnvironment: true, controlPlaneUrl: 'http://cloud.test', controlPlaneApiKey: '', credentialPath: credPath, diff --git a/packages/cloud-connection/src/marketplace-install-local-bundle.test.ts b/packages/cloud-connection/src/marketplace-install-local-bundle.test.ts index 0bca5d69712..ef9aeb1855f 100644 --- a/packages/cloud-connection/src/marketplace-install-local-bundle.test.ts +++ b/packages/cloud-connection/src/marketplace-install-local-bundle.test.ts @@ -78,7 +78,7 @@ describe('install-local compiled-bundle normalization', () => { // The UI bundle registers at kernel:ready; the LAST register call is // the installed package — flattened: top-level id + sections. - const installed = register.mock.calls.at(-1)![0]; + const installed = register.mock.lastCall![0]; expect(installed.id).toBe('app.acme.crm'); expect(installed.namespace).toBe('crm'); expect(installed.version).toBe('2.0.0'); @@ -103,6 +103,6 @@ describe('install-local compiled-bundle normalization', () => { makeC({ manifest: flat }), ); expect(res.payload?.success).toBe(true); - expect(register.mock.calls.at(-1)![0].id).toBe('com.acme.flat'); + expect(register.mock.lastCall![0].id).toBe('com.acme.flat'); }); }); diff --git a/packages/cloud-connection/tsconfig.test.json b/packages/cloud-connection/tsconfig.test.json new file mode 100644 index 00000000000..257ab6fe78a --- /dev/null +++ b/packages/cloud-connection/tsconfig.test.json @@ -0,0 +1,72 @@ +// The TEST-layer type-check program (#20800 -- the last three packages of the +// #4311 ledger, graduated by the #15048 / #15050 road: a sibling program named +// by the `typecheck` script). `tsconfig.json` beside this one stays exactly as +// it is: it is the BUILD config. `package.json`'s `typecheck` script NAMES this +// sibling (`tsc --noEmit -p tsconfig.test.json`), because a config no script +// invokes reads as coverage and delivers none. +// +// WHAT THE BUILD CONFIG ALREADY DID. `tsconfig.json` includes `src` with no +// test exclusion, so the program that reads these tests always existed; the +// package simply declared no `typecheck` script, so nothing ever ran it. That +// is the `service-automation` / `service-cluster` shape, not the +// exclude-and-compensate one, and AGENTS.md forbids ADDING such an exclusion. +// +// What differs from the build config, and what deliberately does NOT: +// - MODULE SEMANTICS and `lib`, nothing else. vitest runs these files as ESM +// through esbuild on a Node that has the ES2022 builtins, and the root +// config's `lib` is ES2020. `module: esnext` / `moduleResolution: bundler` +// is the reading vitest really performs; the build leg of the script +// (`tsc --noEmit`) keeps the stricter NodeNext reading of the same files, +// so the two programs complement each other rather than overlap. +// - STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`, +// `noImplicitReturns`, `noFallthroughCasesInSwitch`, `rootDir`, `paths` and +// `types` are all INHERITED and none is re-declared. ⚠️ A child that declared +// its own `paths` would REPLACE the parent map, so this file declares none. +// Nothing here may loosen a type rule; if a test does not compile, that is +// the finding. +// +// MEASURED at f8178ffece (origin/main), the workspace closure built first +// (`turbo run build --filter='@objectstack/cloud-connection^...'`), BEFORE any +// fix, once per config (`tsc --noEmit --pretty false [--listFiles] -p `): +// +// program under this config 923 files (44 of them this package's `src`), +// all 30 of its `src/**/*.test.ts` in it +// BUILD config (tsconfig.json) 13 = TS2493 x11 + TS2550 x2 +// THIS config 11 = TS2493 x11 +// +// The two readings DISAGREE by exactly two, and those two are the config tier: +// TS2550 ("Property 'at' does not exist ... change your target library"), both +// `Array#at` in `marketplace-install-local-bundle.test.ts` (81, 106) -- the CHECK +// reading ES2020 where the runtime is Node >= 22, which is what `lib` below +// reconciles. The other 11 are code tier and agree under both configs: TS2493 +// ("Tuple type '[]' of length '0' has no element at index N"), every one an +// index into `fetchSpy.mock.calls[0]` where `fetchSpy = vi.fn(async () => ...)` +// was declared with NO parameters, so vitest types `.mock.calls` as an array of +// EMPTY tuples. The call site it stubs is `fetch(url, init)`, which the plugin +// always calls with a string URL and an init object; the fix types each of the 7 +// declarations (`cloud-connection-plugin.test.ts` x3, +// `connection-credential-store.test.ts` x4) to that call -- +// `vi.fn(async (_url: string, _init?: RequestInit) => ...)`. No cast was added, +// no `@ts-expect-error`, no `src` signature touched. +// +// The build leg reads these tests under ES2020 too, so the two `Array#at` reads +// were not left to this file's `lib`: they now use vitest's own +// `mock.lastCall` (the LAST register call, which is what both comments say), +// which compiles under both programs. Without that the first leg of `typecheck` +// would stay red on files this leg accepts. +// +// AFTER: 0 and 0. There is NO `test-typecheck-debt.json` beside this config, and +// its ABSENCE is the zero: any error in any file here is red immediately, with no +// ledger to be added to -- the same call `metadata-fs` and +// `trigger-record-change` recorded. +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["ES2022"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/observability/package.json b/packages/observability/package.json index 2416adf31d7..cf967024ef6 100644 --- a/packages/observability/package.json +++ b/packages/observability/package.json @@ -20,7 +20,8 @@ }, "scripts": { "build": "tsup --config ../../tsup.config.ts && node ../../scripts/check-dts-emitted.mjs", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json" }, "dependencies": { "@objectstack/spec": "workspace:*" diff --git a/packages/observability/src/__tests__/error-exporters.test.ts b/packages/observability/src/__tests__/error-exporters.test.ts index 9d611130c3a..a106c6b892f 100644 --- a/packages/observability/src/__tests__/error-exporters.test.ts +++ b/packages/observability/src/__tests__/error-exporters.test.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect } from 'vitest'; +import type { ErrorReporter } from '../contracts.js'; import { NoopErrorReporter, InMemoryErrorReporter, @@ -9,7 +10,7 @@ import { describe('NoopErrorReporter', () => { it('captures without throwing or recording anything', () => { - const r = new NoopErrorReporter(); + const r: ErrorReporter = new NoopErrorReporter(); expect(() => r.captureException(new Error('boom'), { x: 1 })).not.toThrow(); }); }); diff --git a/packages/observability/src/__tests__/loggers.test.ts b/packages/observability/src/__tests__/loggers.test.ts index 3671206695b..2d326088b75 100644 --- a/packages/observability/src/__tests__/loggers.test.ts +++ b/packages/observability/src/__tests__/loggers.test.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect } from 'vitest'; +import type { Logger } from '../contracts.js'; import { NoopLogger, ConsoleLogger, JsonLogger } from '../loggers.js'; function makeSink() { @@ -15,7 +16,7 @@ function makeSink() { describe('NoopLogger', () => { it('discards every call', () => { - const l = new NoopLogger(); + const l: Logger = new NoopLogger(); expect(() => { l.debug('x'); l.info('x'); @@ -26,7 +27,7 @@ describe('NoopLogger', () => { }); it('child() returns a logger that also discards', () => { - const l = new NoopLogger(); + const l: Logger = new NoopLogger(); const c = l.child?.({ foo: 'bar' }); expect(c).toBeDefined(); expect(() => c?.info('hi')).not.toThrow(); diff --git a/packages/observability/src/__tests__/metrics-exporters.test.ts b/packages/observability/src/__tests__/metrics-exporters.test.ts index 5acb6cd22a2..7fde229d289 100644 --- a/packages/observability/src/__tests__/metrics-exporters.test.ts +++ b/packages/observability/src/__tests__/metrics-exporters.test.ts @@ -1,6 +1,7 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { describe, it, expect } from 'vitest'; +import type { MetricsRegistry } from '../contracts.js'; import { NoopMetricsRegistry, InMemoryMetricsRegistry, @@ -9,7 +10,7 @@ import { describe('NoopMetricsRegistry', () => { it('discards observations without throwing', () => { - const m = new NoopMetricsRegistry(); + const m: MetricsRegistry = new NoopMetricsRegistry(); expect(() => { m.counter('x'); m.histogram('y', 42); diff --git a/packages/observability/src/__tests__/otlp-http.test.ts b/packages/observability/src/__tests__/otlp-http.test.ts index c1509da3b4a..97e919c62eb 100644 --- a/packages/observability/src/__tests__/otlp-http.test.ts +++ b/packages/observability/src/__tests__/otlp-http.test.ts @@ -11,7 +11,7 @@ interface CapturedRequest { } function makeFakeFetch(captured: CapturedRequest[], opts: { ok?: boolean; status?: number } = {}) { - return (async (input: RequestInfo | URL, init?: RequestInit) => { + return (async (input: string | URL | Request, init?: RequestInit) => { const headers: Record = {}; if (init?.headers) { for (const [k, v] of Object.entries(init.headers as Record)) { diff --git a/packages/observability/tsconfig.test.json b/packages/observability/tsconfig.test.json new file mode 100644 index 00000000000..d7361d8b9ed --- /dev/null +++ b/packages/observability/tsconfig.test.json @@ -0,0 +1,66 @@ +// The TEST-layer type-check program (#20800 -- the last three packages of the +// #4311 ledger, graduated by the #15048 / #15050 road: a sibling program named +// by the `typecheck` script). `tsconfig.json` beside this one stays exactly as +// it is: it is the BUILD config. `package.json`'s `typecheck` script NAMES this +// sibling (`tsc --noEmit -p tsconfig.test.json`), because a config no script +// invokes reads as coverage and delivers none. +// +// WHAT THE BUILD CONFIG ALREADY DID. `tsconfig.json` includes `src` with no +// test exclusion, so the program that reads these tests always existed; the +// package simply declared no `typecheck` script, so nothing ever ran it. That +// is the `service-automation` / `service-cluster` shape, not the +// exclude-and-compensate one, and AGENTS.md forbids ADDING such an exclusion. +// +// What differs from the build config, and what deliberately does NOT: +// - MODULE SEMANTICS and `lib`, nothing else. vitest runs these files as ESM +// through esbuild on a Node that has the ES2022 builtins, and the root +// config's `lib` is ES2020. The build leg of the script (`tsc --noEmit`) +// keeps the stricter NodeNext reading of the same files. +// - STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`, +// `noImplicitReturns`, `noFallthroughCasesInSwitch`, `rootDir`, `paths` and +// `types` are all INHERITED and none is re-declared. ⚠️ A child that declared +// its own `paths` would REPLACE the parent map, so this file declares none. +// Nothing here may loosen a type rule; if a test does not compile, that is +// the finding. +// +// MEASURED at f8178ffece (origin/main), the workspace closure built first +// (`turbo run build --filter='@objectstack/observability^...'`), BEFORE any +// fix, once per config (`tsc --noEmit --pretty false [--listFiles] -p `): +// +// program under this config 401 files (16 of them this package's `src`), +// all 7 of its `src/**/*.test.ts` in it +// BUILD config (tsconfig.json) 11 = TS2554 x10 + TS2552 x1 +// THIS config 11 = TS2554 x10 + TS2552 x1 +// +// The two readings AGREE, so this package carried no config-tier pile at all and +// all 11 were code tier from the start. Two shapes, both in the test layer: +// - TS2554 x10 ("Expected 0 arguments, but got N"): `error-exporters.test.ts` +// x1, `loggers.test.ts` x6, `metrics-exporters.test.ts` x3. Each is a call on +// the concrete `NoopErrorReporter` / `NoopLogger` / `NoopMetricsRegistry` +// whose methods are declared with NO parameters (a legal narrowing of the +// interface), so the CLASS type has arity 0 while the contract every consumer +// calls through (`ErrorReporter`, `Logger`, `MetricsRegistry`) has the real +// one. The fix declares the instance as the contract it is used through +// (`const r: ErrorReporter = new NoopErrorReporter()`), so the test makes the +// call a consumer makes. The source signature was NOT widened. +// - TS2552 x1 (`otlp-http.test.ts`, 14): the fake `fetch` named `RequestInfo`, +// a DOM-lib name this program does not have (`RequestInit` comes from +// `@types/node`, `RequestInfo` does not). Typed to the call site it stubs: +// `string | URL | Request`. +// No cast was added, no `@ts-expect-error`. +// +// AFTER: 0 and 0. There is NO `test-typecheck-debt.json` beside this config, and +// its ABSENCE is the zero: any error in any file here is red immediately, with no +// ledger to be added to -- the same call `metadata-fs` and +// `trigger-record-change` recorded. +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "module": "esnext", + "moduleResolution": "bundler", + "lib": ["ES2022"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 4974c44ebc8..5f41a1857d9 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -805,19 +805,32 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts'; // they were not already. Repaired by the #5286 route -- a `tsconfig.test.json` // over the test layer, named by a new `typecheck` script -- so the entry is // deleted rather than lowered. +// +// `@objectstack/cloud-connection`, `@objectstack/observability` and +// `@objectstack/hono` GRADUATED from this ledger together (#20800, the last +// three packages of #4311; entries: 13 / 11 / 3 raw, each repaired to 0). Same +// road in as `service-automation` above: no `typecheck` script at all, and a +// BUILD `tsconfig.json` that does NOT exclude tests, so the program that would +// have read them existed and was never invoked. Repaired by the #5286 route -- a +// sibling `tsconfig.test.json`, named by a new `typecheck` script of the +// zero-residue shape (`tsc --noEmit && tsc --noEmit -p tsconfig.test.json`, no +// per-file ledger) -- so the entries are deleted rather than lowered. Measured +// BOTH ways before any fix (build config vs the new test config): cloud-connection +// 13 vs 11, observability 11 vs 11, hono 3 vs 3, so every entry's count was +// confirmed exactly under the build config. Only cloud-connection carried a +// config tier (TS2550 x2, `Array#at` under lib ES2020 -- rewritten to vitest's +// `mock.lastCall` rather than widening the shared BUILD config, the +// `service-storage` call above); the rest were test shapes, each fixed at its +// cause: a `vi.fn(async () => ...)` mock typed to the `fetch(url, init)` call it +// stubs (TS2493 x11), a Noop instance declared as the contract it is called +// through (TS2554 x10), a fake `fetch` parameter typed without a DOM-only name +// (TS2552), a Hono app declaring the context variable the middleware sets +// (TS2769 x2, TS18046). `hono`'s "3" was not its whole pile: tsc withholds +// declaration-emit diagnostics while a semantic error stands, so once the 3 were +// fixed the test config showed 4 more (TS2883 on the `vi.fn()` fields of +// `src/__mocks__/runtime.ts`, annotated `Mock`) that the build config never shows. +// No cast, no `@ts-expect-error`, no `src` signature widened. const DEBT = { - '@objectstack/cloud-connection': { - errors: 13, - note: 'code-tier 11 (TS2493 tuple indexing) + 2 config-tier.', - }, - '@objectstack/hono': { - errors: 3, - note: 'all code-tier (TS2769/TS18046).', - }, - '@objectstack/observability': { - errors: 11, - note: 'all code-tier (TS2554 wrong arity x10, TS2552).', - }, '@objectstack/spec-monorepo': { errors: 26, compositionAt: 80,