Skip to content

Commit b572289

Browse files
test(spec): scoped-context and connector-author-shape compile their probes in one program, reading only the probe files' diagnostics (#23547)
Part of #23180 Clause-②: no Two of the three `src/` rows this round claimed come off the card's list. The third is left as it was, at a measured floor. ## Rows this PR closes, and the row it leaves All readings are local, three runs per file, each through `bash scripts/pm/os-verify-lock.sh -c`. They are shared-box seconds, because the lock excludes only other locked runs. The command was `env -u AI_AGENT -u CLAUDECODE -u CLAUDE_CODE -u CURSOR_AGENT NODE_OPTIONS=--max-old-space-size=4096 pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 FILE --reporter=default --reporter=json --outputFile.json=OUT`. Two figures are given per run: - the vitest module line, which is what the battery's per-file capture reads; - the run's whole `Duration`. One file compiled at collection time, which falls outside the module line. Before = `origin/main` at `5fd2c76d6c` (a detached worktree). After = this branch at `2da2cbe14a`. The merge of `origin/main` that follows touches no `packages/spec` file. | row (the card's reading) | before: module line · Duration | after: module line · Duration | verdict | |:--|:--|:--|:--| | `src/contracts/scoped-context.test.ts` (91.1 s) | 37327 / 25789 / 24079 ms · 38.19 / 26.63 / 24.85 s · 8/8 pass | 2927 / 2765 / 2689 ms · 3.76 / 3.52 / 3.47 s · 8/8 pass | **closed**: under 4 s locally on all three runs | | `src/integration/connector-author-shape.test.ts` (49.5 s) | 12176 / 11723 / 11716 ms · 18.02 / 17.06 / 16.73 s, of which 5.64 / 5.17 / 4.85 s was at collection · 11/11 pass | 2947 / 3070 / 3020 ms · 3.96 / 4.11 / 4.05 s, of which 0.83 / 0.87 / 0.85 s was at collection · 11/11 pass | **closed**: module line under 4 s on all three runs | | `src/shared/alias-integrity.test.ts` (40.8 s) | 17717 / 16472 / 17538 ms · 20.03 / 18.99 / 19.80 s · 20/20 pass | untouched | **left open** at its floor (section below) | The card's other rows are not addressed here, and #23180 remains open for them. The two dominant rows wait on decision card #23534. ## What changed Both files drive `ts.createProgram` over this package's source to compile probe snippets. Each compile then asked `ts.getPreEmitDiagnostics(program)` for the whole program and kept only the probes' diagnostics. The change is in each file's harness only, and it has two parts. 1. **Diagnostics are requested per probe file**, as `ts.getPreEmitDiagnostics(program, probeFile)`, which is the way `shared/retired-key-tsc-diagnostic.test.ts` already asks. - The whole-program request type-checked the body of every source file the probes reach, and the filter then threw all of that away. - A probe's own diagnostics still reflect every type it reaches. - A probe that never entered the program now throws instead of reading as empty. 2. **One program per file.** Each describe block registers its probes at collection (`registerProbes`). The first case that reads a result compiles every registered probe together, in one program. - Before, scoped-context built four programs. connector-author-shape built three, two of them at collection time. - Every probe is its own module, so sharing a program changes no probe's answer (the equivalence reading below measures this). - A name registered twice must carry the same text. A read of a probe the program never saw throws. Every `expect` line is unchanged. The probe texts are the same strings: only where they are built moved, from the case body to the describe body. No test is deleted, no timeout is raised, and no file moves project. The connector case's own `180_000` timeout stays where it was. Its comment now says that the CI readings it cites were taken on the old harness. ## Evidence **Equivalence.** One-shot instrumented copies, never committed, ran the old harness beside the new one: one program per call with whole-program diagnostics, against the shared program with per-probe diagnostics. They compared every probe's diagnostics as `category:start:length TScode: full message chain`. - scoped-context: 23 probe readings, all IDENTICAL (13 with zero diagnostics, 10 with one). - connector-author-shape: 9, all IDENTICAL. **Ablations.** Both were predicted red and both were observed red. Each ran through `node scripts/ablation-replace.mjs` in wrap mode, on `2da2cbe14a`. No `dist/` is in the path, because the harness maps `@objectstack/spec/*` to `src/` and tsc reads the source. - **S1**: deleted `count` from `IScopedObjectRepository` in `scoped-context.ts`. - Landed: anchor count 1 to 0, blob `429e0ad0d846` to `370bfd11bd4c`. - Result: scoped-context failed 1, passed 7. The failing case was `legal-count must compile clean`, which received `TS2339: Property 'count' does not exist on type 'IScopedObjectRepository'.` - Restore: blob equals HEAD's `429e0ad0d846`, and `git diff HEAD` is empty. - **C1**: changed the bare `Connector` alias in `connector.zod.ts` back to the parsed shape (`z.input` to `z.infer`). - Landed: anchor count 1 to 0, replacement count 0 to 1, blob `84b04fa30ed2` to `ee0e5f7efbd0`. - Result: connector-author-shape failed 3, passed 8: - L3 example #1: TS2739, missing `authentication`, `requestTimeoutMs` and `enabled`; - the live-key control: TS2740 on `retryConfig`; - `accepts the omitted defaults under the bare Connector`: TS2739. - Restore: blob equals HEAD's `84b04fa30ed2`, `git diff HEAD` is empty, and porcelain is 0. ## The row left open: `alias-integrity.test.ts` This file's subject is every module in `packages/spec/src`. Its `beforeAll` imports all 1,393 non-test modules and forces every schema, so the `strictObject` registry is complete. An AST scan then proves that every alias-table call site was reached. Profiled locally: - **The import walk alone**, timed per import in a scratch copy: 15,078 ms of a 17.7 s module line. vitest's own counter puts `transform` at 12.4 to 13.2 s on the three readings above. - **Inside the worker**, 14.7 s of a 17.3 s CPU profile is idle, waiting on the main process's per-module transform. Evaluating the modules takes about 2.6 s. So the floor is vitest's transform of the whole package: about 15 s locally, nearly 4x the 4 s margin. Two routes would reach the line, and neither is a fix to the test: - walk fewer modules, which removes the "every module" coverage the file exists for; - load the walk through a different loader, which is a rewrite in which every helper the assertions use has to come from that loader's module graph. Under the dispatch's stop rule the file is untouched, and the choice goes back to the card. ## Publish reading: nothing publishes The two edited files are `*.test.ts` under `src/`. `@objectstack/spec`'s `files[]` ships `src/**/*.zod.ts` only. Measured on the packed tarball (`pnpm pack` after `pnpm --filter @objectstack/spec build`, `18.0.0-next.1`): - 2,091 files, of which `*.test.ts`: 0. - Files containing `registerProbes`: 0. `__scoped_context_probes__`: 0. `__connector_author_shape_probes__`: 0. - Positive controls: `IScopedObjectRepository` is in 8 files and `ConnectorParsed` in 7. So `skip-changeset`. ## Gates, on `39d4bbdd5d` (this head) - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 76 commands. All 76 were run, each exit captured before any pipe: - 74 exited 0. `check:doc-formula-expressions` first exited 3 (`@objectstack/formula` and `@objectstack/lint` not built). After `turbo run build` of those two, it exited 0. - 2 are NOT MEASURED, exit 3 PREREQUISITE NOT MET: `pnpm check:dual-build-cjs-loads` and `pnpm check:lean-entry-closure`. Both load other packages' built `dist/` (objectql and the rest), and neither can read a test file. CI runs both. - The `--ran` verdict: `76 derived famil(ies) accounted for — 74 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)`, exit 0. - `pnpm --filter @objectstack/spec build` exited 0. - `pnpm --filter @objectstack/spec typecheck` exited 0. That includes `check:test-typecheck`, and `tsc -p tsconfig.test.json --listFilesOnly` lists both edited files. Neither file is in `test-typecheck-debt.json`. - `eslint --no-inline-config` on the two files exited 0. - Declared narrowing: the package's full `test` and `test:repo` were not run locally. The two edited files are leaves that nothing imports, and CI runs both projects. ## Acceptance notes - The `alias-integrity.test.ts` floor above is reported to the card with options, not filed. - `check:dual-build-cjs-loads` and `check:lean-entry-closure` are NOT MEASURED locally, as stated above. --- _Generated by [Claude Code](https://claude.ai/code/session_0121ooBYRmS8ziFXm9ai5YHm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 086fa46 commit b572289

2 files changed

Lines changed: 165 additions & 69 deletions

File tree

‎packages/spec/src/contracts/scoped-context.test.ts‎

Lines changed: 95 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ const HOOK_ZOD = resolve(SPEC_DIR, 'src/data/hook.zod.ts');
7171
* `src/`, so a probe can be written the way a hook author would actually type
7272
* it — import line included — instead of through relative paths no reader of
7373
* the docs would ever use.
74+
*
75+
* Diagnostics are asked of each PROBE file only, never of the whole program:
76+
* every assertion below reads a probe's own diagnostics, and a whole-program
77+
* request type-checks the body of every `packages/spec` source file the probes
78+
* reach, for diagnostics a filter then discarded. A probe's diagnostics still
79+
* reflect every type it reaches (the contract members, `HookContext`); only
80+
* the bodies nobody reads are skipped. `shared/retired-key-tsc-diagnostic.test.ts`
81+
* asks the same way.
7482
*/
7583
function compileProbes(probes: Readonly<Record<string, string>>): Map<string, ts.Diagnostic[]> {
7684
const dir = resolve(SPEC_DIR, 'src/__scoped_context_probes__');
@@ -107,16 +115,49 @@ function compileProbes(probes: Readonly<Record<string, string>>): Map<string, ts
107115

108116
const program = ts.createProgram([...paths.keys()], options, host);
109117
const out = new Map<string, ts.Diagnostic[]>();
110-
for (const name of Object.keys(probes)) out.set(name, []);
111-
for (const d of ts.getPreEmitDiagnostics(program)) {
112-
const file = d.file?.fileName ? resolve(d.file.fileName) : undefined;
113-
for (const name of Object.keys(probes)) {
114-
if (file === resolve(dir, `${name}.ts`)) out.get(name)!.push(d);
115-
}
118+
for (const name of Object.keys(probes)) {
119+
const path = resolve(dir, `${name}.ts`);
120+
const file = program.getSourceFile(path);
121+
if (file === undefined) throw new Error(`probe ${name} did not enter the program`);
122+
out.set(name, ts.getPreEmitDiagnostics(program, file).filter((d) => d.file !== undefined && resolve(d.file.fileName) === path));
116123
}
117124
return out;
118125
}
119126

127+
/**
128+
* Every probe this file compiles, by name — registered while the describe
129+
* blocks below are COLLECTED, and compiled together in ONE program the first
130+
* time any case reads a result.
131+
*
132+
* Each probe is its own module (an `import` line opens every one), so a probe
133+
* cannot see another's declarations and its diagnostics do not depend on which
134+
* probes share the program; what four programs did here — each re-parsing and
135+
* re-checking the same `HookContext` and contract types — one program does
136+
* once. A name registered twice must carry the same text (the
137+
* `harness-self-test` control is shared verbatim), and a case reading a probe
138+
* the program never saw fails rather than reading an empty answer.
139+
*/
140+
const REGISTERED = new Map<string, string>();
141+
let compiled: Map<string, ts.Diagnostic[]> | undefined;
142+
143+
function registerProbes(probes: Readonly<Record<string, string>>): () => Map<string, ts.Diagnostic[]> {
144+
for (const [name, text] of Object.entries(probes)) {
145+
const prior = REGISTERED.get(name);
146+
if (prior !== undefined && prior !== text) throw new Error(`probe ${name} is registered twice with different text`);
147+
REGISTERED.set(name, text);
148+
}
149+
return () => {
150+
compiled ??= compileProbes(Object.fromEntries(REGISTERED));
151+
const out = new Map<string, ts.Diagnostic[]>();
152+
for (const name of Object.keys(probes)) {
153+
const diagnostics = compiled.get(name);
154+
if (diagnostics === undefined) throw new Error(`probe ${name} was registered after the program was built`);
155+
out.set(name, diagnostics);
156+
}
157+
return out;
158+
};
159+
}
160+
120161
/** One diagnostic per line, `TS<code>: <message>`, for readable assertions. */
121162
function render(diagnostics: readonly ts.Diagnostic[]): string {
122163
return diagnostics
@@ -168,13 +209,15 @@ describe('the `HookContext.api` JSDoc example compiles', () => {
168209
expect(example.split('\n').length).toBeGreaterThanOrEqual(3);
169210
});
170211

212+
const compile = registerProbes({
213+
'jsdoc-usage': hook(example),
214+
// The harness's own control: a probe that MUST fail. A resolution failure
215+
// reports zero diagnostics for every probe and reads as green.
216+
'harness-self-test': hook("const x: number = ctx.api?.object('user');"),
217+
});
218+
171219
it('compiles verbatim, with zero diagnostics', () => {
172-
const results = compileProbes({
173-
'jsdoc-usage': hook(example),
174-
// The harness's own control: a probe that MUST fail. A resolution failure
175-
// reports zero diagnostics for every probe and reads as green.
176-
'harness-self-test': hook("const x: number = ctx.api?.object('user');"),
177-
});
220+
const results = compile();
178221
expect(render(results.get('harness-self-test')!), 'the harness must be able to report an error')
179222
.toContain('TS2322');
180223
expect(render(results.get('jsdoc-usage')!), 'the JSDoc example must compile').toBe('');
@@ -215,12 +258,13 @@ describe('every corpus-measured call compiles', () => {
215258
'legal-transaction-info': 'await ctx.api?.transaction(async (_tx, info) => info.owned);',
216259
};
217260

218-
it('compiles all of them clean', () => {
219-
const probes: Record<string, string> = {};
220-
for (const [name, body] of Object.entries(legal)) probes[name] = hook(body);
221-
probes['harness-self-test'] = hook("const x: number = ctx.api?.object('user');");
261+
const probes: Record<string, string> = {};
262+
for (const [name, body] of Object.entries(legal)) probes[name] = hook(body);
263+
probes['harness-self-test'] = hook("const x: number = ctx.api?.object('user');");
264+
const compile = registerProbes(probes);
222265

223-
const results = compileProbes(probes);
266+
it('compiles all of them clean', () => {
267+
const results = compile();
224268
expect(render(results.get('harness-self-test')!), 'the harness must be able to report an error')
225269
.toContain('TS2322');
226270

@@ -259,20 +303,22 @@ describe('the evidence bar is enforced, not just documented', () => {
259303
'rejects-top-level-insert': ["await ctx.api?.insert('task', { title: 't' });", 'IScopedContext'],
260304
};
261305

306+
const probes: Record<string, string> = {};
307+
for (const [name, [body]] of Object.entries(rejected)) {
308+
probes[name] = name === 'rejects-invented-member'
309+
? [
310+
"import type { HookContext } from '@objectstack/spec/data';",
311+
"import type { IScopedContext } from '@objectstack/spec/contracts';",
312+
'export const handler = async (ctx: HookContext) => {',
313+
body,
314+
'};',
315+
].join('\n')
316+
: hook(body);
317+
}
318+
const compile = registerProbes(probes);
319+
262320
it('reports TS2339 naming the contract type — not merely "some error"', () => {
263-
const probes: Record<string, string> = {};
264-
for (const [name, [body]] of Object.entries(rejected)) {
265-
probes[name] = name === 'rejects-invented-member'
266-
? [
267-
"import type { HookContext } from '@objectstack/spec/data';",
268-
"import type { IScopedContext } from '@objectstack/spec/contracts';",
269-
'export const handler = async (ctx: HookContext) => {',
270-
body,
271-
'};',
272-
].join('\n')
273-
: hook(body);
274-
}
275-
const results = compileProbes(probes);
321+
const results = compile();
276322

277323
for (const [name, diagnostics] of results) {
278324
const text = render(diagnostics);
@@ -323,24 +369,26 @@ describe('`updateById` DECLARES its answer, and the declaration is enforced', ()
323369
* `update` sibling's wider `Record<string, any> | number | null`, so a
324370
* copy-paste of the wrong sibling's shape is caught too.
325371
*/
372+
const compile = registerProbes({
373+
// The whole point of the declaration: a by-id update that matched no row
374+
// answers `null`, and before this the call site was handed `any` and
375+
// never asked.
376+
'rejects-unnarrowed-updateById':
377+
hook("const row: Record<string, any> = await ctx.api!.object('task').updateById('t1', { status: 'done' });"),
378+
// Anti-vacuity twin: the CORRECT spelling must still compile clean, so
379+
// the narrowing is a real answer shape and not a blanket refusal.
380+
'legal-updateById-null-checked': hook([
381+
"const row = await ctx.api!.object('task').updateById('t1', { status: 'done' });",
382+
'if (row !== null) {',
383+
' const seen: unknown = row.status;',
384+
' void seen;',
385+
'}',
386+
].join('\n')),
387+
'harness-self-test': hook("const x: number = ctx.api?.object('user');"),
388+
});
389+
326390
it('the answer cannot enter a record slot unnarrowed, and the null-checked read compiles', () => {
327-
const results = compileProbes({
328-
// The whole point of the declaration: a by-id update that matched no row
329-
// answers `null`, and before this the call site was handed `any` and
330-
// never asked.
331-
'rejects-unnarrowed-updateById':
332-
hook("const row: Record<string, any> = await ctx.api!.object('task').updateById('t1', { status: 'done' });"),
333-
// Anti-vacuity twin: the CORRECT spelling must still compile clean, so
334-
// the narrowing is a real answer shape and not a blanket refusal.
335-
'legal-updateById-null-checked': hook([
336-
"const row = await ctx.api!.object('task').updateById('t1', { status: 'done' });",
337-
'if (row !== null) {',
338-
' const seen: unknown = row.status;',
339-
' void seen;',
340-
'}',
341-
].join('\n')),
342-
'harness-self-test': hook("const x: number = ctx.api?.object('user');"),
343-
});
391+
const results = compile();
344392

345393
expect(render(results.get('harness-self-test')!), 'the harness must be able to report an error')
346394
.toContain('TS2322');

‎packages/spec/src/integration/connector-author-shape.test.ts‎

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ const SYNC_ARCHITECTURE = resolve(SPEC_DIR, 'docs/SYNC_ARCHITECTURE.md');
8686
* gate's reason: a documentation snippet declares a `const` and stops, and
8787
* TS6133 is an opinion about the snippet's framing. Everything else runs at the
8888
* repo's real strictness (`strict: true`).
89+
*
90+
* Diagnostics are asked of each PROBE file only, never of the whole program:
91+
* every assertion below reads a probe's own diagnostics, and a whole-program
92+
* request type-checks the body of every `packages/spec` source file the probes
93+
* reach, for diagnostics a filter then discarded. A probe's diagnostics still
94+
* reflect every type it reaches (`Connector`, its tombstones, `ConnectorParsed`);
95+
* only the bodies nobody reads are skipped.
96+
* `shared/retired-key-tsc-diagnostic.test.ts` asks the same way.
8997
*/
9098
function compileProbes(probes: Readonly<Record<string, string>>): Map<string, ts.Diagnostic[]> {
9199
const dir = resolve(SPEC_DIR, 'src/__connector_author_shape_probes__');
@@ -121,16 +129,48 @@ function compileProbes(probes: Readonly<Record<string, string>>): Map<string, ts
121129

122130
const program = ts.createProgram([...paths.keys()], options, host);
123131
const out = new Map<string, ts.Diagnostic[]>();
124-
for (const name of Object.keys(probes)) out.set(name, []);
125-
for (const d of ts.getPreEmitDiagnostics(program)) {
126-
const file = d.file?.fileName ? resolve(d.file.fileName) : undefined;
127-
for (const [name] of Object.entries(probes)) {
128-
if (file === resolve(dir, `${name}.ts`)) out.get(name)!.push(d);
129-
}
132+
for (const name of Object.keys(probes)) {
133+
const path = resolve(dir, `${name}.ts`);
134+
const file = program.getSourceFile(path);
135+
if (file === undefined) throw new Error(`probe ${name} did not enter the program`);
136+
out.set(name, ts.getPreEmitDiagnostics(program, file).filter((d) => d.file !== undefined && resolve(d.file.fileName) === path));
130137
}
131138
return out;
132139
}
133140

141+
/**
142+
* Every probe this file compiles, by name — registered while the describe
143+
* blocks below are COLLECTED, and compiled together in ONE program the first
144+
* time any case reads a result.
145+
*
146+
* Each probe is its own module (an `import` line opens every one), so a probe
147+
* cannot see another's declarations and its diagnostics do not depend on which
148+
* probes share the program; what three programs did here — each re-parsing and
149+
* re-checking the same `Connector` types — one program does once. A name
150+
* registered twice must carry the same text, and a case reading a probe the
151+
* program never saw fails rather than reading an empty answer.
152+
*/
153+
const REGISTERED = new Map<string, string>();
154+
let compiled: Map<string, ts.Diagnostic[]> | undefined;
155+
156+
function registerProbes(probes: Readonly<Record<string, string>>): () => Map<string, ts.Diagnostic[]> {
157+
for (const [name, text] of Object.entries(probes)) {
158+
const prior = REGISTERED.get(name);
159+
if (prior !== undefined && prior !== text) throw new Error(`probe ${name} is registered twice with different text`);
160+
REGISTERED.set(name, text);
161+
}
162+
return () => {
163+
compiled ??= compileProbes(Object.fromEntries(REGISTERED));
164+
const out = new Map<string, ts.Diagnostic[]>();
165+
for (const name of Object.keys(probes)) {
166+
const diagnostics = compiled.get(name);
167+
if (diagnostics === undefined) throw new Error(`probe ${name} was registered after the program was built`);
168+
out.set(name, diagnostics);
169+
}
170+
return out;
171+
};
172+
}
173+
134174
/** One diagnostic per line, `TS<code>: <message>`, for readable assertions. */
135175
function render(diagnostics: readonly ts.Diagnostic[]): string {
136176
return diagnostics
@@ -191,10 +231,24 @@ describe('SYNC_ARCHITECTURE.md L3 connector examples compile', () => {
191231
expect(markdown).not.toContain("```typescript\nimport type { ETLPipeline }");
192232
});
193233

234+
const probes: Record<string, string> = {};
235+
compilable.forEach((block, i) => { probes[`doc-l3-connector-${i}`] = block; });
236+
// The harness's own control: a probe that MUST fail. Without it a
237+
// resolution failure (paths mapping wrong, host overlay not applied) would
238+
// report zero diagnostics and read as a green example.
239+
probes['harness-self-test'] = [
240+
"import type { Connector } from '@objectstack/spec/integration';",
241+
"const broken: Connector = { label: 'no name, no type' };",
242+
].join('\n');
243+
const compile = registerProbes(probes);
244+
194245
// This `it` carries its OWN timeout instead of the package's 60 s default
195-
// (`vitest.config.ts`): it builds one `ts.createProgram` over the spec's types
196-
// (both document examples plus the harness self-test probe, at `strict`), and
197-
// that is more work than the default is sized for. CI readings, `Test Core
246+
// (`vitest.config.ts`): it is the first case to read a compile result, so it
247+
// builds the file's one `ts.createProgram` over the spec's types (every
248+
// probe registered above and below, at `strict`), and that was more work
249+
// than the default is sized for. The readings that follow are of the harness
250+
// before it shared one program and asked only the probe files for
251+
// diagnostics; the timeout is left where they put it. CI readings, `Test Core
198252
// (1/6)`: 55,680 ms on `main` (commit 85b4ac634e; the same figure appears at
199253
// 018b077fbf and eab7236ff7 because turbo replayed one cached run, so it is
200254
// one reading, not three) and 61,767 ms on a cold spec re-run (PR head
@@ -205,17 +259,7 @@ describe('SYNC_ARCHITECTURE.md L3 connector examples compile', () => {
205259
// import-everything `beforeAll`. Only this `it` is widened: raising the
206260
// global `testTimeout` would hide the next slow test as well.
207261
it('compiles every full connector example verbatim, import line included', () => {
208-
const probes: Record<string, string> = {};
209-
compilable.forEach((block, i) => { probes[`doc-l3-connector-${i}`] = block; });
210-
// The harness's own control: a probe that MUST fail. Without it a
211-
// resolution failure (paths mapping wrong, host overlay not applied) would
212-
// report zero diagnostics and read as a green example.
213-
probes['harness-self-test'] = [
214-
"import type { Connector } from '@objectstack/spec/integration';",
215-
"const broken: Connector = { label: 'no name, no type' };",
216-
].join('\n');
217-
218-
const results = compileProbes(probes);
262+
const results = compile();
219263
expect(render(results.get('harness-self-test')!), 'the harness must be able to report an error')
220264
.toContain('TS2739');
221265
compilable.forEach((_, i) => {
@@ -279,7 +323,7 @@ describe('the spellings the example used to carry are rejected', () => {
279323
`,
280324
} as const;
281325

282-
const results = compileProbes(probes);
326+
const compile = registerProbes(probes);
283327

284328
// ⚠ Measured, not assumed — and it is the one place the two tombstone
285329
// channels are NOT equally good. `retiredKey()` types the key as its
@@ -291,6 +335,7 @@ describe('the spellings the example used to carry are rejected', () => {
291335
// channel (the runtime block below) carries the key's own prescription.
292336
for (const probe of ['field-mappings-retired', 'sync-config-retired', 'webhooks-retired'] as const) {
293337
it(`\`${probe}\`: the key no longer type-checks, whatever it holds`, () => {
338+
const results = compile();
294339
const message = render(results.get(probe)!);
295340
expect(message).toMatch(/^TS(2741|2353): /);
296341
expect(message).toContain('[REMOVED]');
@@ -299,6 +344,7 @@ describe('the spellings the example used to carry are rejected', () => {
299344
}
300345

301346
it('…while a live sibling key in the same literal compiles', () => {
347+
const results = compile();
302348
expect(render(results.get('live-key-control')!)).toBe('');
303349
});
304350
});
@@ -382,13 +428,15 @@ describe('the bare `Connector` is the author shape; `ConnectorParsed` is the par
382428
`,
383429
} as const;
384430

385-
const results = compileProbes(probes);
431+
const compile = registerProbes(probes);
386432

387433
it('accepts the omitted defaults under the bare `Connector`', () => {
434+
const results = compile();
388435
expect(render(results.get('author-connector')!)).toBe('');
389436
});
390437

391438
it('rejects the same literal under `ConnectorParsed`, on the defaults it left out', () => {
439+
const results = compile();
392440
const message = render(results.get('parsed-connector')!);
393441
expect(message).toMatch(/TS2739: .* is missing the following properties/);
394442
expect(message).toContain('enabled');

0 commit comments

Comments
 (0)