Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, '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 > 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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .changeset/service-cluster-test-tsc-program.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
---
"@objectstack/service-cluster": patch
---

fix(service-cluster): put the test layer in front of tsc, and repair the TS2322 it was hiding (#14181)

`packages/services/service-cluster` had **no `typecheck` script at all** — its
scripts were `build` and `test` — so no tsc program anywhere read this package.
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. The package's own `tsconfig.json` does
include the tests and always did, so the program that would have read them
already existed and was simply never invoked.

What that hid was in the worst possible file. `src/memory/memory.contract.test.ts`
is the package's **contract witness** — type conformance to the `IPubSub` /
`ILock` / `IKV` / `ICounter` contracts is the entire point of its existence — and
it did not compile:

```
src/memory/memory.contract.test.ts(26,46): error TS2322:
Type 'number' is not assignable to type 'void | Promise<void>'.
```

`cluster.pubsub.subscribe('e', (m) => received.push(m.payload))` passes a concise
arrow body as a `PubSubHandler`, whose contract return type is
`void | Promise<void>`. The body returns `Array.prototype.push`'s `number`, and
TypeScript's void-return assignability relaxation does **not** forgive it,
because the target is a UNION rather than a bare `void`. It is repaired with a
block body — the handler is side-effect-only by contract, and the returned length
was an accident of arrow syntax, never intent. The identical shape is what
`@objectstack/metadata` graduated on (20 of them, `(evt) => arr.push(evt)` in a
watcher slot).

⛔ The spec contract is untouched: `PubSubHandler` returning `void | Promise<void>`
is correct and deliberate (the union is what lets a driver `await` an async
handler). The defect was in the test, so the test is where it is fixed — no
consumer-side widening, no source signature change.

Wired by the route the `packages/plugins/**` family settled on in #14062: a
sibling `tsconfig.test.json` that changes **module semantics only** (`esnext` /
`bundler` / `lib: ES2022`, matching how vitest actually executes these files)
with **strictness inherited and untouched**, named by a new `typecheck` script
through the shared `check:test-typecheck` gate. Measured before the repair: 1
error under build semantics, 1 under the new config — the two readings agree, so
this package carried no config-tier pile. After: 0 and 0, across a 410-file
program covering all 7 of its `src/**/*.test.ts`.

No `test-typecheck-debt.json` is added, and its **absence is the zero**: the gate
reads a missing ledger as `{ entries: {} }`, under which any error in any file
here is red immediately. The package's `DEBT` entry in
`scripts/check-type-check-coverage.mjs` (`errors: 1`) is deleted in this PR
rather than lowered — that is the graduation the ratchet's own invariant
requires, and it is why the error was fixed rather than ledgered.

No runtime code changes: `src/**` (excluding tests) is byte-identical, so no
shipped behaviour moves. The `patch` level reflects the published `package.json`
gaining `typecheck` / `check:test-typecheck` scripts and a `tsx` devDependency.
5 changes: 4 additions & 1 deletion packages/services/service-cluster/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,14 +24,17 @@
},
"scripts": {
"build": "rm -rf dist && tsup && node ../../../scripts/check-dts-emitted.mjs",
"test": "vitest run"
"test": "vitest run",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck",
"check:test-typecheck": "tsx ../../../scripts/check-test-typecheck.mts --self-test && tsx ../../../scripts/check-test-typecheck.mts --package packages/services/service-cluster --project tsconfig.test.json"
},
"dependencies": {
"@objectstack/core": "workspace:*",
"@objectstack/spec": "workspace:*"
},
"devDependencies": {
"@types/node": "^26.2.0",
"tsx": "^4.23.12",
"typescript": "^6.0.3",
"vitest": "^4.1.10"
},
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ describe('defineCluster(memory) smoke', () => {

// Round-trip through all four.
const received: unknown[] = [];
cluster.pubsub.subscribe('e', (m) => received.push(m.payload));
cluster.pubsub.subscribe('e', (m) => { received.push(m.payload); });
await cluster.pubsub.publish('e', 'hi');
expect(received).toEqual(['hi']);

Expand Down
78 changes: 78 additions & 0 deletions packages/services/service-cluster/tsconfig.test.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
// The TEST-layer type-check program (#14181 — the `packages/services/**`
// instance of the class #14062 settled for `packages/plugins/**`, itself
// adopting the mechanism #5286 set for `packages/spec`, #5449 generalised,
// #12542 carried to `packages/rest` and #13176 to `packages/plugins/
// plugin-security`). `tsconfig.json` beside this one stays exactly as it is: it
// is the BUILD config. This sibling puts the test layer in front of tsc under
// the module semantics vitest really executes it with, and `package.json`'s
// `typecheck` script NAMES it (via `check:test-typecheck --project`), because a
// config no script invokes is exactly the phantom this whole change is about.
//
// ⚠️ WHAT WAS DIFFERENT HERE, and why this package was the worst case in the
// family rather than one more of it: `service-cluster` had NO `typecheck`
// script at all — its scripts were `build` and `test`. The other members hid
// their tests behind an `exclude` in a config some script still ran; this one
// ran no tsc anywhere. Its build config does NOT exclude tests and never did,
// so the program that would have read them already existed and simply was
// never invoked, while turbo/CI typecheck lanes skipped the package silently (a
// zero-matching filter run exits 0). `tsup` type-strips, `vitest` type-strips.
//
// What differs from the build config, and what deliberately does NOT:
// - MODULE SEMANTICS ONLY, plus `lib`. The tests are written and executed as
// ESM by vitest (esbuild/vite). Matching that is FIDELITY, not laxity: it is
// the same subtraction `packages/spec`, `packages/rest` and the
// `packages/plugins/**` family each made.
// - ⛔ STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`,
// `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`,
// `rootDir`, `paths` and `types` are all INHERITED from `tsconfig.json`
// (and through it the root config), and none of them is re-declared here.
// ⚠️ A child that declared its own `paths` would REPLACE the parent map
// rather than merge into it, silently sending a source-resolved specifier
// back to `dist/` — a BUILD ARTIFACT — so this file declares none.
// Nothing here may loosen a type rule; if a test does not compile, that is
// the finding.
// - `lib: ["ES2022"]`, for the same reason `packages/rest` states: the root
// config's `lib` is ES2020 and vitest runs on a Node that has es2022
// builtins, so the gap is reported as TS2550 about the CHECK. No `DOM`:
// nothing in this layer touches a browser global.
//
// MEASURED at 44ffa2103, workspace closure built first (`tsc --noEmit --pretty
// false --listFiles -p tsconfig.test.json`, and the same command without
// `--listFiles`):
//
// files in this program 410
// own `src/**/*.test.ts` in it 7
// errors under BUILD semantics 1
// errors under THIS config 1
//
// The two readings agree, so this package carried no config-tier pile at all —
// the single error is code-tier, and it is REPAIRED in the same PR rather than
// ledgered. It was a TS2322 at `src/memory/memory.contract.test.ts:26`:
// `(m) => received.push(m.payload)` passed as a `PubSubHandler`, whose contract
// return type is `void | Promise<void>`. A concise arrow body returns
// `Array.prototype.push`'s `number`, and the void-return assignability
// relaxation does NOT forgive it because the target is a UNION rather than bare
// `void`. That is the same shape `@objectstack/metadata` graduated on (#14342),
// and the fix is a block body — the handler is side-effect-only by contract.
// Triage was explicit that this one is to be fixed, not ledgered.
//
// There is NO `test-typecheck-debt.json` beside this config, and its ABSENCE is
// the zero: `check:test-typecheck` reads a missing ledger as `{ entries: {} }`,
// under which ANY error in ANY file here is red immediately, with no entry to be
// added to. That is strictly stronger than a ledger holding nothing, and it is
// the same call `plugin-webhooks` and `plugin-security` (#13176) each recorded
// for themselves. If this package ever acquires residue that cannot be fixed in
// the PR that causes it, THAT is when a ledger and a `gen:test-typecheck-debt`
// script are owed — and adding one is maintainer-only (#5286), exactly as the
// gate says when it refuses.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["ES2022"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -663,6 +663,16 @@ const ROOT_PROGRAM_COUPLED_SCRIPT = 'scripts/check-test-typecheck.mts';
// 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.
//
// `@objectstack/service-cluster` GRADUATED from this ledger (#14181; entry: 1
// raw, repaired to 0). Its single TS2322 was the very shape the paragraph above
// itemises for `metadata` -- `(m) => received.push(m.payload)` in a slot typed
// `void | Promise<void>` -- caught here in the package's own CONTRACT witness.
// It is worth a line because this package reached the ledger by a different road
// than the rest: it had NO `typecheck` script at all, so its build config never
// ran even though that config DOES include the tests. 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.
const DEBT = {
'@objectstack/cloud-connection': {
errors: 13,
Expand DownExpand Up@@ -690,10 +700,6 @@ const DEBT = {
+ 'by acquiring a second file, then 5 -> 3 by graduating the first -- so re-read what the pile is '
+ 'made of before sizing it, never just the number.',
},
'@objectstack/service-cluster': {
errors: 1,
note: 'code-tier 1 (TS2322).',
},
'@objectstack/service-knowledge': {
errors: 10,
note: 'code-tier 3 (TS2339/TS2352/TS2493); config-tier 3 (TS2835); noise 4 (TS7006). Re-measured 10 at '
Expand Down
43 changes: 43 additions & 0 deletions scripts/check-type-source-resolution.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -586,6 +586,49 @@ const KNOWN_DIST_RESOLVED_TYPE_IMPORTS = {
'@objectstack/lint', '@objectstack/mcp', '@objectstack/platform-objects',
'@objectstack/plugin-auth', '@objectstack/spec', '@objectstack/types',
],
// #14181 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.test.json` -- a program this card ADDED. This is the
// limb's cleanest case rather than a borderline one: `service-cluster` had NO
// `typecheck` script AT ALL before (its scripts were `build` and `test`), so
// it ran ZERO counted programs and there is no pre-existing program for a dep
// to be laundered through. Both deps here are annotated `via
// tsconfig.test.json` by this gate's own failure text.
//
// Provenance measured four ways on one checkout, by varying only what the
// `typecheck` script NAMES (`--list`, totals as printed):
//
// no `typecheck` script (origin/main) absent 118 programs / 288 pairs
// names `tsconfig.json` only absent 118 programs / 288 pairs
// names `tsconfig.test.json` only PRESENT 119 programs / 290 pairs
// names both (this card) PRESENT 119 programs / 290 pairs
//
// Row 2 is the load-bearing one: the BUILD program carries no dist-resolved
// workspace type import at all, so the exposure is not merely first SEEN
// through the onboarded program, it is only REACHABLE through it. (The two
// programs put the same files in -- this package's `tsconfig.json` has never
// excluded tests -- so module semantics, NodeNext vs bundler, is the only
// axis that differs.)
//
// Numbers, `--list` before/after on the same checkout (before at 44ffa2103,
// after with this card applied):
//
// before 57 of 78 packages, 118 programs, 288 pairs, 21 clean
// after 58 of 78 packages, 119 programs, 290 pairs, 20 clean
//
// so +1 package, +1 program, +2 pairs -- this entry and nothing else.
//
// Why the entry and not `paths`, which is what this gate's failure text asks
// for: MEASURED both ways on the same checkout, and `paths` is decisively the
// wrong tool here. Redirecting these two deps to source takes this package's
// test layer from 0 errors to 435, ALL of them TS6059 (`not under rootDir`)
// and every one of them in ANOTHER package's source -- `packages/spec/src/**`
// and `packages/core/src/**` -- billed to a package that cannot pay them down.
// That is the PR #12570 finding (+5 TS6133 for `rest`) and the #8021 one (247
// TS6059) reproduced at a much larger scale, on a package whose entire point
// in this card was to reach ZERO test-layer errors. Note the direction: the
// #5286 route it took makes its OWN test files compile clean, and `paths`
// would immediately re-bury that result under other packages' diagnostics.
'@objectstack/service-cluster': ['@objectstack/core', '@objectstack/spec'],
// #14386 re-baseline (the onboarding limb above): a NEW entry, reached ONLY
// through `tsconfig.typecheck.json` -- a program that card ADDED (this
// package's `typecheck` was a bare `tsc --noEmit` before it, with no sibling
Expand Down
Loading