From 1910ff3b185a309a970fee2dbdf403999718a824 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 20:15:26 +0000 Subject: [PATCH 1/2] test(objectql,runtime): widen the deleted-member absence pin from one file to the tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #14667 deleted the private `ObjectQLPlugin.actionObjectKey` and wrote a guard for it: `expect(plugin!.text).not.toContain('actionObjectKey')`. The kind of guard was right; its SCOPE was the defect. A pin written by the deleting PR can only look where its author thought to look, and the whole failure mode is references the author did not know about — five files in three other packages went on naming the dead member as a live reader, and one deletion produced two separate follow-up cards. Both `action-owner-key-single-source.test.ts` pins now assert the absence tree-wide instead: one `git grep` over `.ts` under `packages/` and `examples/`, tracked plus untracked, with three exclusion rules carried in the pin beside their reasons (published CHANGELOGs, `.changeset/`, and the two pins themselves, which name the member because naming it is how they hunt for it). Widening also covers the half no removal-time check can see. Three of the five references existed when the member died; the other two were written 1 h 41 min AFTER it, by a later PR, into a file that was clean at deletion time. A pin that runs on every PR reddens on that second kind at the moment it is written. Also in this change: - `packages/spec/src/stack.zod.ts` — two comments naming the dead member as the registration-key reader TODAY now name `standaloneActionOwnerKey`, the live helper. Only that one word rots; `collectBundleActions` beside it is alive. - `packages/objectql/src/action-governance.ts` — accurate history, reworded so it no longer carries the dead name. - `scripts/cross-package-test-inputs.mjs` and `turbo.json` — the declaration a tree-scoped test owes. Without it neither the affected-subset filter nor the turbo cache re-runs these suites for the files they now judge, which is the blind spot `check:cross-package-test-inputs` exists to close. ⛔ An assertion of absence is not a stale mention. The pins name the member on purpose; "repairing" those lines deletes the guard. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- packages/objectql/src/action-governance.ts | 4 +- .../action-owner-key-single-source.test.ts | 284 +++++++++++++++++- .../action-owner-key-single-source.test.ts | 232 +++++++++++++- packages/spec/src/stack.zod.ts | 8 +- scripts/cross-package-test-inputs.mjs | 55 ++++ turbo.json | 18 +- 6 files changed, 586 insertions(+), 15 deletions(-) diff --git a/packages/objectql/src/action-governance.ts b/packages/objectql/src/action-governance.ts index 0c2d30e8c3..a820af2852 100644 --- a/packages/objectql/src/action-governance.ts +++ b/packages/objectql/src/action-governance.ts @@ -74,8 +74,8 @@ export function isObjectLessActionKey(objectName: string | undefined | null): bo * Standalone `action` metadata declares `objectName` (spec `ActionSchema`); * bundle collectors attach `object`; an object-less action owns the canonical * `'global'` key. Three other writers spelled this same three-line ladder — - * the runtime's `standaloneActionObjectName`, the ObjectQL plugin's private - * `actionObjectKey`, and an inline copy inside + * the runtime's `standaloneActionObjectName`, a private owner-key method on the + * ObjectQL plugin, and an inline copy inside * {@link collectEngineActionDeclarations}. All of them resolve HERE now: the * plugin calls this function directly (same package) and * `@objectstack/runtime` re-exports it, keeping `standaloneActionObjectName` diff --git a/packages/objectql/src/action-owner-key-single-source.test.ts b/packages/objectql/src/action-owner-key-single-source.test.ts index 30fb7310c0..d2c6a3249d 100644 --- a/packages/objectql/src/action-owner-key-single-source.test.ts +++ b/packages/objectql/src/action-owner-key-single-source.test.ts @@ -12,12 +12,14 @@ * silently different the first time that constant moves. * * `@objectstack/runtime` carries the matching weld for its own copy - * (`action-owner-key-single-source.test.ts` there). This one is scoped to this - * package's source so it stays a package-local test input. + * (`action-owner-key-single-source.test.ts` there). The LADDER halves below are + * scoped to this package's source so they stay package-local test inputs; the + * absence half is not, and the section on it explains why. */ -import { readFileSync, readdirSync } from 'node:fs'; -import { dirname, join } from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; import { describe, it, expect } from 'vitest'; import { GLOBAL_ACTION_OBJECT_KEY, standaloneActionOwnerKey } from './action-governance.js'; @@ -66,12 +68,14 @@ describe('standalone-action owner key — one spelling in @objectstack/objectql } }); - it('leaves no private `actionObjectKey` behind on the plugin', () => { + it('derives the plugin owner key through the canonical helper', () => { const plugin = nonTestSources().find((s) => s.file === 'plugin.ts'); expect(plugin, 'plugin.ts is missing from the scan').toBeDefined(); - expect(plugin!.text).not.toContain('actionObjectKey'); - // Positive control for the negative above: the plugin does still derive - // owner keys — it just does it through the canonical helper now. + // The negative that used to live here — "plugin.ts does not name the + // deleted member" — moved to the TREE-scoped section at the bottom of + // this file (#14878). Its scope was the defect, not its subject. What + // stays here is the positive half: the plugin still derives owner keys, + // it just does it through the canonical helper now. expect(plugin!.text).toContain('standaloneActionOwnerKey('); }); @@ -89,3 +93,267 @@ describe('standalone-action owner key — one spelling in @objectstack/objectql expect(body[1]).not.toContain("'global'"); }); }); + +/** + * ── [#14878] The absence assertion is TREE-scoped, not FILE-scoped ────────── + * + * The negative that used to sit in the plugin test above read `plugin.ts` and + * nothing else, and THAT SCOPE was the defect. A pin written by the deleting PR + * can only look where its author thought to look, and the whole failure mode is + * references the author did not know about: the file-scoped pin stayed green + * while five other files in three other packages went on naming the deleted + * member as something that reads a key TODAY, and the one deletion produced two + * separate follow-up cards. + * + * Widening to the tree also covers the half that nothing keyed on the deleting + * diff can ever see. Three of those five references already existed when the + * member died. The other two were written 1 h 41 min AFTER it, by a later PR, + * into a file that was clean at deletion time — so a check that greps the + * deleting PR's own post-image is structurally blind to them. A pin that runs on + * every PR is not: it reddens on the second kind at the moment it is written, + * which is the only moment the person who can classify the mention is present. + * + * ⛔ AN ASSERTION OF ABSENCE IS NOT A STALE MENTION. This file and its twin in + * `@objectstack/runtime` name the dead member because naming it is how they hunt + * for it. "Repairing" those lines deletes the guard — a naive fixer turning a + * pin into its own removal. That is why the two pins exclude themselves below, + * with the reason written beside the rule; it is the first thing to get right + * about this shape, not a refinement of it. + * + * ── Scope, and where it stops ─────────────────────────────────────────────── + * + * `.ts` under `packages/` and `examples/`. That is the whole measured + * population: every mention this symbol has ever had outside the release record + * was a `.ts` file in one of those two roots, and so were all five surviving + * references. It is also the radius `@objectstack/core` and `@objectstack/types` + * already declare for pins of this shape, so it costs one table entry and one + * turbo task and no scheduler surgery. + * + * ⚠️ Widening to `docs/`, `content/`, `skills/` or `apps/` is TWO edits, never + * one: `SCANNED_ROOTS` here AND this package's globs in + * `scripts/cross-package-test-inputs.mjs` — and a NEW top-level root needs a + * matching ci.yml `crosspkg:` entry, which `check-ci-filter-parity.mjs` is the + * gate for. Widening the scanner alone reads as coverage while turbo never + * re-runs this suite for the files it now claims to judge, which is exactly the + * blind spot `check:cross-package-test-inputs` exists to close. + */ + +/** + * The member PR #14667 deleted from `ObjectQLPlugin`. Held as DATA: naming a + * symbol in a string cannot resurrect it, and this file is excluded from its own + * scan precisely so it may carry the name. + */ +const DELETED_PLUGIN_MEMBER = 'actionObjectKey'; + +/** + * The live spelling that replaced it. Used as the scan's reach control below — + * it is the one symbol guaranteed to sit in both scanned roots for as long as + * the convergence holds, and if it ever stops doing so this pin should say so + * loudly rather than quietly stop reaching. + */ +const CANONICAL_HELPER = 'standaloneActionOwnerKey'; + +/** The two pins that hunt the dead member, and therefore have to name it. */ +const PIN_FILES: readonly string[] = [ + 'packages/objectql/src/action-owner-key-single-source.test.ts', + 'packages/runtime/src/action-owner-key-single-source.test.ts', +]; + +/** + * Where a mention of the dead member is NOT a defect, each with its reason + * beside it. + * + * ⛔ This is a scan-SCOPE decision written where the scan lives, and it must + * stay that: an allowlist FILE — one more path pasted in whenever a report is + * inconvenient — is the permission slip this whole shape exists to avoid. A rule + * here has to be a statement about a CLASS of file that is true by construction, + * never "this one site is fine". + * + * The first two rules cannot fire while `SCANNED_EXTENSION` is `.ts`, and they + * are kept anyway: they are the ruled exclusions, and the day someone widens the + * extension set they are what stops the release record from being re-admitted as + * a pile of false reds. + */ +const NOT_A_STALE_MENTION: ReadonlyArray<{ readonly covers: (file: string) => boolean; readonly why: string }> = [ + { + // A published CHANGELOG entry is the record OF the removal. It is true in + // the past tense, it is what a consumer reads to find out the member is + // gone, and rewriting it would falsify shipped release history. + covers: (file) => file === 'CHANGELOG.md' || file.endsWith('/CHANGELOG.md'), + why: 'a published CHANGELOG is the record of the removal itself', + }, + { + // The same record before the release process compiles it into the above. + covers: (file) => file.startsWith('.changeset/'), + why: 'a changeset is that record before it is compiled into a CHANGELOG', + }, + { + // The pins carry the name as their own search string and as accurate + // history of what they pin. Excluding them is what lets the pin exist: + // a scan that flagged its own needle would have no green state at all. + covers: (file) => PIN_FILES.includes(file), + why: 'the pin carries the name as its own search string — repairing it deletes the guard', + }, +]; + +/** + * This package is CJS-typed (no `"type": "module"`), so `module: NodeNext` + * forbids `import.meta` here — the same constraint `srcDir()` above records. + * Walk up from the CWD to this package's own manifest instead, which works + * wherever vitest is invoked from. + */ +function findUp(marker: (dir: string) => boolean, what: string): string { + let dir = process.cwd(); + for (;;) { + if (marker(dir)) return dir; + const parent = dirname(dir); + if (parent === dir) throw new Error(`could not locate ${what} walking up from ${process.cwd()}`); + dir = parent; + } +} + +const PACKAGE_ROOT = findUp((dir) => { + const manifest = join(dir, 'package.json'); + if (!existsSync(manifest)) return false; + const { name } = JSON.parse(readFileSync(manifest, 'utf8')) as { name?: string }; + return name === '@objectstack/objectql'; +}, 'the @objectstack/objectql package root'); + +/** + * The repo root by ARITHMETIC from this package rather than by a second + * marker-file walk, deliberately: a walk keyed on a workspace-root marker would + * NAME that root file, and a declared root-level path is a new top-level root + * that ci.yml's `crosspkg:` filter would then have to carry. Anchoring off the + * manifest keeps this pin's declared radius inside roots that already exist. + * + * The arithmetic is not trusted on faith — the reach test below fails on any + * wrong root, because no wrong root can see both scanned trees. + */ +const REPO_ROOT = resolve(PACKAGE_ROOT, '../..'); + +/** The trees this pin binds. See the scope note above before changing it. */ +const SCANNED_ROOTS: readonly string[] = ['packages', 'examples']; + +/** Spelled once so the declared glob and the scan stay in correspondence. */ +const SCANNED_EXTENSION = '.ts'; + +/** + * Generous on purpose. The scan is one `git grep` and a handful of file reads — + * tens of milliseconds — so this is not a budget, it is headroom against a + * merge-queue runner doing a full monorepo build at the same time. A pin that + * times out before its assertion runs reports nothing, and reporting nothing is + * indistinguishable from finding nothing. + */ +const SCAN_TIMEOUT_MS = 60_000; + +function git(args: string[]): string[] { + let stdout: string; + try { + stdout = execFileSync('git', args, { cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 1 << 28 }); + } catch (error) { + const failure = error as { status?: number; stderr?: string }; + // `git grep` exits 1 for "found nothing", which is data. Anything else is + // a BROKEN scan and must never read as "no stale mentions" — throwing + // here, plus the reach test below, is what keeps a green result meaning + // "looked and found nothing" rather than "never looked". + if (failure.status === 1) return []; + throw new Error( + `git ${args.join(' ')} failed with status ${String(failure.status)}: ${failure.stderr ?? ''}`, + ); + } + return stdout.split('\0').filter((entry) => entry.length > 0); +} + +/** + * Every scanned file that so much as mentions `symbol`. + * + * Tracked files PLUS untracked ones with ignored paths excluded (`--untracked`) + * — i.e. exactly the files a human authored, never build output. A file written + * but not yet `git add`ed still reddens, which is what makes this a local-loop + * guard rather than something you find out about in the merge queue. + */ +function filesMentioning(symbol: string): string[] { + return git([ + 'grep', + '--files-with-matches', + '-z', + '--untracked', + '--text', + '--fixed-strings', + '-e', + symbol, + '--', + ...SCANNED_ROOTS, + ]).filter((file) => file.endsWith(SCANNED_EXTENSION)); +} + +/** `:` for every mention that no rule above excuses. */ +function staleMentionSites(symbol: string): string[] { + const sites: string[] = []; + for (const file of filesMentioning(symbol)) { + if (NOT_A_STALE_MENTION.some((rule) => rule.covers(file))) continue; + const lines = readFileSync(join(REPO_ROOT, file), 'utf8').split('\n'); + lines.forEach((text, index) => { + if (text.includes(symbol)) sites.push(`${file}:${index + 1}`); + }); + } + return sites; +} + +describe('standalone-action owner key — the deleted member is dead TREE-WIDE (#14878)', () => { + it( + 'is named nowhere outside the release record and the two pins', + () => { + const sites = staleMentionSites(DELETED_PLUGIN_MEMBER); + expect( + sites, + sites.length === 0 + ? '' + : [ + `These files name \`${DELETED_PLUGIN_MEMBER}\`, a private \`ObjectQLPlugin\``, + 'member that was DELETED when the standalone-action owner-key ladder was', + 'converged onto one spelling:', + '', + ...sites.map((site) => ` - ${site}`), + '', + `The live spelling is \`${CANONICAL_HELPER}\`, exported from`, + '`@objectstack/objectql` (packages/objectql/src/action-governance.ts). If the', + 'sentence is otherwise accurate, rename the one word rather than rewriting', + 'the clause — the neighbouring names in these sentences are usually alive.', + '', + '⛔ Before you touch a site, decide which of three it is:', + ' (a) a LIVE CLAIM that the member exists -> fix it', + ' (b) accurate HISTORY naming it in the past -> reword so it no longer', + ' carries the dead name, or add a rule to NOT_A_STALE_MENTION above', + ' with the reason beside it — never an allowlist file', + ' (c) an ASSERTION THAT IT IS GONE -> ⛔ leave it alone. It is the guard.', + ].join('\n'), + ).toEqual([]); + }, + SCAN_TIMEOUT_MS, + ); + + it( + 'the scan reaches both roots and can see the name it hunts', + () => { + // Anti-vacuity, at both stages a tree scan can go silently blind. + // + // A grep that matched nothing — wrong repo root, git missing, a + // pathspec that names no tree — yields the same empty violation set + // as a clean repo, and the assertion above cannot tell them apart. + // That is the exact property the file-scoped pin lost. + expect(filesMentioning(DELETED_PLUGIN_MEMBER)).toContain(PIN_FILES[0]); + + // ...and it must leave this package. The live helper is the reach + // control because it is the one symbol the convergence guarantees in + // both scanned roots: `examples/app-showcase/test/actions.test.ts` is + // literally one of the five files that carried the dead name until it + // was repaired, so a scan that cannot see it is a scan that would not + // have caught the defect this pin exists for. + const reached = filesMentioning(CANONICAL_HELPER); + expect(reached).toContain('examples/app-showcase/test/actions.test.ts'); + expect(reached).toContain('packages/runtime/src/action-execution.ts'); + }, + SCAN_TIMEOUT_MS, + ); +}); diff --git a/packages/runtime/src/action-owner-key-single-source.test.ts b/packages/runtime/src/action-owner-key-single-source.test.ts index 78ec06f5fb..99c990a3a8 100644 --- a/packages/runtime/src/action-owner-key-single-source.test.ts +++ b/packages/runtime/src/action-owner-key-single-source.test.ts @@ -34,10 +34,16 @@ * repo, which is the whole shape #14422 was filed to remove — so the same * convergence needed the same weld, or the next reader re-inlines one and * nothing says so. + * + * Half D (#14878) is the odd one out and says so at its own section below: it + * is not about this package's source at all. It is the TREE-scoped absence pin + * for the plugin member this convergence deleted, carried here as well as in + * `@objectstack/objectql` so that losing either copy still leaves a guard. */ +import { execFileSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; -import { dirname, join } from 'node:path'; +import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, it, expect } from 'vitest'; import { GLOBAL_ACTION_OBJECT_KEY, standaloneActionOwnerKey } from '@objectstack/objectql'; @@ -176,3 +182,227 @@ describe('standalone-action owner key — half C: no bare literal (#14678)', () } }); }); + +/** + * ── Half D [#14878]: the absence assertion is TREE-scoped, not FILE-scoped ─── + * + * #14667 deleted a private `actionObjectKey` member from `ObjectQLPlugin` and + * DID write a guard for it — `not.toContain(...)` against `plugin.ts`. The kind + * of guard was right; its SCOPE was the defect. A pin written by the deleting PR + * can only look where its author thought to look, and the whole failure mode is + * references the author did not know about: five files in three other packages + * went on naming the dead member as something that reads a key TODAY, and one + * deletion produced two separate follow-up cards. + * + * Widening to the tree also covers the half nothing keyed on the deleting diff + * can see. Three of those five references already existed when the member died. + * The other two were written 1 h 41 min AFTER it, by a later PR, into a file + * that was clean at deletion time — so a check that greps the deleting PR's own + * post-image is structurally blind to them. A pin that runs on every PR is not. + * + * ⛔ AN ASSERTION OF ABSENCE IS NOT A STALE MENTION. This file and its twin in + * `@objectstack/objectql` name the dead member because naming it is how they + * hunt for it; "repairing" those lines deletes the guard. Both pins therefore + * exclude themselves below, with the reason written beside the rule. + * + * ── Why this is carried twice, on purpose ─────────────────────────────────── + * + * Either copy alone catches everything — the scan is the same tree both times. + * The redundancy is against the ONE failure the file-scoped pin already + * demonstrated: a guard disappearing with the file that held it. Two packages + * hold it, so deleting one leaves the property still pinned. Sharing the scan + * through a helper module would undo exactly that, and it would put a new + * always-loaded module in `scripts/` for a pin — which is the machinery this + * shape was chosen to avoid. + * + * ── Scope, and where it stops ─────────────────────────────────────────────── + * + * `.ts` under `packages/` and `examples/` — the whole measured population of + * this symbol's mentions outside the release record, and the radius + * `@objectstack/core` and `@objectstack/types` already declare for pins of this + * shape. ⚠️ Widening to `docs/`, `content/`, `skills/` or `apps/` is TWO edits: + * `SCANNED_ROOTS` here AND this package's globs in + * `scripts/cross-package-test-inputs.mjs` (a NEW top-level root needs a matching + * ci.yml `crosspkg:` entry — `check-ci-filter-parity.mjs` is the gate). Widening + * the scanner alone reads as coverage while turbo never re-runs this suite for + * the files it now claims to judge. + */ + +/** + * The member #14667 deleted from `ObjectQLPlugin`. Held as DATA: naming a symbol + * in a string cannot resurrect it, and this file is excluded from its own scan + * precisely so it may carry the name. + */ +const DELETED_PLUGIN_MEMBER = 'actionObjectKey'; + +/** + * The live spelling that replaced it, used as the scan's reach control below. It + * is the one symbol the convergence guarantees in both scanned roots, so if it + * ever stops being there this pin says so loudly rather than quietly stopping. + */ +const CANONICAL_HELPER = 'standaloneActionOwnerKey'; + +/** The two pins that hunt the dead member, and therefore have to name it. */ +const PIN_FILES: readonly string[] = [ + 'packages/objectql/src/action-owner-key-single-source.test.ts', + 'packages/runtime/src/action-owner-key-single-source.test.ts', +]; + +/** + * Where a mention of the dead member is NOT a defect, each with its reason + * beside it. + * + * ⛔ A scan-SCOPE decision written where the scan lives, and it must stay that. + * An allowlist FILE — one more path pasted in whenever a report is inconvenient + * — is the permission slip this shape exists to avoid. A rule here has to be a + * statement about a CLASS of file that is true by construction, never "this one + * site is fine". + * + * The first two rules cannot fire while `SCANNED_EXTENSION` is `.ts`, and they + * are kept anyway: they are the ruled exclusions, and the day someone widens the + * extension set they are what stops the release record from being re-admitted as + * a pile of false reds. + */ +const NOT_A_STALE_MENTION: ReadonlyArray<{ readonly covers: (file: string) => boolean; readonly why: string }> = [ + { + // A published CHANGELOG entry is the record OF the removal: true in the + // past tense, and what a consumer reads to learn the member is gone. + covers: (file) => file === 'CHANGELOG.md' || file.endsWith('/CHANGELOG.md'), + why: 'a published CHANGELOG is the record of the removal itself', + }, + { + // The same record before the release process compiles it into the above. + covers: (file) => file.startsWith('.changeset/'), + why: 'a changeset is that record before it is compiled into a CHANGELOG', + }, + { + // The pins carry the name as their own search string. Excluding them is + // what lets the pin exist at all: a scan that flagged its own needle + // would have no green state. + covers: (file) => PIN_FILES.includes(file), + why: 'the pin carries the name as its own search string — repairing it deletes the guard', + }, +]; + +/** …/packages/runtime/src → repo root, by arithmetic from this file. */ +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..'); + +/** The trees this pin binds. See the scope note above before changing it. */ +const SCANNED_ROOTS: readonly string[] = ['packages', 'examples']; + +/** Spelled once so the declared glob and the scan stay in correspondence. */ +const SCANNED_EXTENSION = '.ts'; + +/** + * Generous on purpose. The scan is one `git grep` and a handful of file reads, + * so this is headroom against a merge-queue runner doing a full monorepo build + * at the same time, not a budget. A pin that times out before its assertion runs + * reports nothing, and reporting nothing is indistinguishable from finding + * nothing. + */ +const SCAN_TIMEOUT_MS = 60_000; + +function git(args: string[]): string[] { + let stdout: string; + try { + stdout = execFileSync('git', args, { cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 1 << 28 }); + } catch (error) { + const failure = error as { status?: number; stderr?: string }; + // `git grep` exits 1 for "found nothing", which is data. Anything else is + // a BROKEN scan and must never read as "no stale mentions". + if (failure.status === 1) return []; + throw new Error( + `git ${args.join(' ')} failed with status ${String(failure.status)}: ${failure.stderr ?? ''}`, + ); + } + return stdout.split('\0').filter((entry) => entry.length > 0); +} + +/** + * Every scanned file that so much as mentions `symbol`. Tracked files PLUS + * untracked ones with ignored paths excluded (`--untracked`) — exactly the files + * a human authored, never build output. A file written but not yet `git add`ed + * still reddens, which keeps this a local-loop guard. + */ +function filesMentioning(symbol: string): string[] { + return git([ + 'grep', + '--files-with-matches', + '-z', + '--untracked', + '--text', + '--fixed-strings', + '-e', + symbol, + '--', + ...SCANNED_ROOTS, + ]).filter((file) => file.endsWith(SCANNED_EXTENSION)); +} + +/** `:` for every mention that no rule above excuses. */ +function staleMentionSites(symbol: string): string[] { + const sites: string[] = []; + for (const file of filesMentioning(symbol)) { + if (NOT_A_STALE_MENTION.some((rule) => rule.covers(file))) continue; + const lines = readFileSync(join(REPO_ROOT, file), 'utf8').split('\n'); + lines.forEach((text, index) => { + if (text.includes(symbol)) sites.push(`${file}:${index + 1}`); + }); + } + return sites; +} + +describe('standalone-action owner key — half D: the deleted member is dead TREE-WIDE (#14878)', () => { + it( + 'is named nowhere outside the release record and the two pins', + () => { + const sites = staleMentionSites(DELETED_PLUGIN_MEMBER); + expect( + sites, + sites.length === 0 + ? '' + : [ + `These files name \`${DELETED_PLUGIN_MEMBER}\`, a private \`ObjectQLPlugin\``, + 'member deleted when the standalone-action owner-key ladder was converged', + 'onto one spelling:', + '', + ...sites.map((site) => ` - ${site}`), + '', + `The live spelling is \`${CANONICAL_HELPER}\`, exported from`, + '`@objectstack/objectql` (packages/objectql/src/action-governance.ts). If the', + 'sentence is otherwise accurate, rename the one word rather than rewriting', + 'the clause — the neighbouring names are usually alive.', + '', + '⛔ Before you touch a site, decide which of three it is:', + ' (a) a LIVE CLAIM that the member exists -> fix it', + ' (b) accurate HISTORY naming it in the past -> reword so it no longer', + ' carries the dead name, or add a rule to NOT_A_STALE_MENTION above', + ' with the reason beside it — never an allowlist file', + ' (c) an ASSERTION THAT IT IS GONE -> ⛔ leave it alone. It is the guard.', + ].join('\n'), + ).toEqual([]); + }, + SCAN_TIMEOUT_MS, + ); + + it( + 'the scan reaches both roots and can see the name it hunts', + () => { + // Anti-vacuity, at both stages a tree scan can go silently blind. A + // grep that matched nothing — wrong repo root, git missing, a + // pathspec naming no tree — yields the same empty violation set as a + // clean repo, and the assertion above cannot tell them apart. That is + // exactly the property the file-scoped pin lost. + expect(filesMentioning(DELETED_PLUGIN_MEMBER)).toContain(PIN_FILES[1]); + + // ...and it must leave this package. `examples/app-showcase/test/ + // actions.test.ts` is literally one of the five files that carried + // the dead name until it was repaired, so a scan that cannot see it + // is a scan that would not have caught the defect this pin is for. + const reached = filesMentioning(CANONICAL_HELPER); + expect(reached).toContain('examples/app-showcase/test/actions.test.ts'); + expect(reached).toContain('packages/objectql/src/action-governance.ts'); + }, + SCAN_TIMEOUT_MS, + ); +}); diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index 8c27145e45..4ece324632 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -378,7 +378,8 @@ const STACK_DEFINITION_COLLECTIONS_SHAPE = { * delete, not a rename). An embedded action is keyed by the object it is * written ON — the declaration-resolution key `collectActionDeclarations` / * `resolveRouteActionDeclaration` use — not by its own `objectName` (the - * registration key `collectBundleActions` / `actionObjectKey` read). One + * registration key `collectBundleActions` / `standaloneActionOwnerKey` read). + * One * global and one object-bound action MAY share a `name`: they occupy two * keys, and a by-name reader on the object's route resolves the object's own * `actions` (embedded, or merged in from here) before a standalone global @@ -1570,8 +1571,9 @@ function joinDeclarationOrigins(origins: readonly string[]): string { * `object` alias is already canonicalized) or global; an embedded action is * scoped by the object it is written on, whatever its own `objectName` says — * that is how `collectActionDeclarations` and `resolveRouteActionDeclaration` - * key it. (The runtime's REGISTRATION key — `collectBundleActions` / - * `actionObjectKey` — reads the action's own `objectName` instead; the walk + * key it. (The REGISTRATION key — `collectBundleActions` / + * `standaloneActionOwnerKey` — reads the action's own `objectName` instead; the + * walk * deliberately follows the resolution side, where the by-name collision the * card describes happens.) * diff --git a/scripts/cross-package-test-inputs.mjs b/scripts/cross-package-test-inputs.mjs index 110ecda2e3..ab51be6a4d 100644 --- a/scripts/cross-package-test-inputs.mjs +++ b/scripts/cross-package-test-inputs.mjs @@ -848,6 +848,35 @@ export const CROSS_PACKAGE_TEST_INPUTS = { 'packages/spec/src/**': ['packages/qa/downstream-contract/test/source-resolution.pin.test.ts'], }, }, + '@objectstack/objectql': { + // src/action-owner-key-single-source.test.ts carries the #14878 TREE-scoped + // absence pin: `git grep` over `packages/` and `examples/` for the private + // `ObjectQLPlugin` member #14667 deleted, so that the PR which writes a new + // stale mention of it reddens at the moment it is written rather than + // surfacing as a follow-up card weeks later. The two `.ts` globs ARE that + // scan surface -- the pin spells the same two roots and the same extension + // in `SCANNED_ROOTS` / `SCANNED_EXTENSION`, and its own header says the two + // widen together or not at all. + // + // The scan's paths come out of `git grep`, which this gate's collector + // cannot name, so the globs are held by the escaping test itself rather than + // by a path on the roster -- the `heldBy` shape below. + // + // `cross-package-test-inputs.mjs` is NAMED in that pin's header (it is where + // the widening instruction points) and never read, the same shape as the + // `check-nul-bytes.mjs` mentions elsewhere in this table: the literal + // collector takes quoted paths out of comments, and declaring the file is + // cheaper than rewording prose to dodge the scanner. + globs: [ + 'packages/**/*.ts', + 'examples/**/*.ts', + 'scripts/cross-package-test-inputs.mjs', + ], + heldBy: { + 'packages/**/*.ts': ['packages/objectql/src/action-owner-key-single-source.test.ts'], + 'examples/**/*.ts': ['packages/objectql/src/action-owner-key-single-source.test.ts'], + }, + }, '@objectstack/runtime': { // src/error-envelope.conformance.test.ts imports `stripComments` from // `js-comment-mask.mjs` to decide which text in the ten dispatcher modules @@ -857,10 +886,36 @@ export const CROSS_PACKAGE_TEST_INPUTS = { // it has to re-run this package's suite. The `.d.mts` sibling is declared // alongside it because it is what gives `stripComments` its type, so this // package's typecheck verdict is a function of it too. + // + // src/action-owner-key-single-source.test.ts also carries the #14878 TREE-scoped + // absence pin: `git grep` over `packages/` and `examples/` for the private + // `ObjectQLPlugin` member #14667 deleted, so that the PR which writes a new + // stale mention of it reddens at the moment it is written rather than + // surfacing as a follow-up card weeks later. The two `.ts` globs ARE that + // scan surface -- the pin spells the same two roots and the same extension + // in `SCANNED_ROOTS` / `SCANNED_EXTENSION`, and its own header says the two + // widen together or not at all. + // + // The scan's paths come out of `git grep`, which this gate's collector + // cannot name, so the globs are held by the escaping test itself rather than + // by a path on the roster -- the `heldBy` shape below. + // + // `cross-package-test-inputs.mjs` is NAMED in that pin's header (it is where + // the widening instruction points) and never read, the same shape as the + // `check-nul-bytes.mjs` mentions elsewhere in this table: the literal + // collector takes quoted paths out of comments, and declaring the file is + // cheaper than rewording prose to dodge the scanner. globs: [ 'scripts/js-comment-mask.mjs', 'scripts/js-comment-mask.d.mts', + 'packages/**/*.ts', + 'examples/**/*.ts', + 'scripts/cross-package-test-inputs.mjs', ], + heldBy: { + 'packages/**/*.ts': ['packages/runtime/src/action-owner-key-single-source.test.ts'], + 'examples/**/*.ts': ['packages/runtime/src/action-owner-key-single-source.test.ts'], + }, }, '@objectstack/driver-sql': { // src/live-dialect-matrix.isolation.test.ts imports `stripComments` from diff --git a/turbo.json b/turbo.json index ff4197c601..0a4764e973 100644 --- a/turbo.json +++ b/turbo.json @@ -222,7 +222,23 @@ "!coverage/**", "!.turbo/**", "$TURBO_ROOT$/scripts/js-comment-mask.mjs", - "$TURBO_ROOT$/scripts/js-comment-mask.d.mts" + "$TURBO_ROOT$/scripts/js-comment-mask.d.mts", + "$TURBO_ROOT$/packages/**/*.ts", + "$TURBO_ROOT$/examples/**/*.ts", + "$TURBO_ROOT$/scripts/cross-package-test-inputs.mjs" + ] + }, + "@objectstack/objectql#test": { + "dependsOn": ["^build"], + "outputs": [], + "inputs": [ + "$TURBO_DEFAULT$", + "!dist/**", + "!coverage/**", + "!.turbo/**", + "$TURBO_ROOT$/packages/**/*.ts", + "$TURBO_ROOT$/examples/**/*.ts", + "$TURBO_ROOT$/scripts/cross-package-test-inputs.mjs" ] }, "@objectstack/driver-sql#test": { From c754845e33b47492835e51905ad040f95d0a4a4f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 3 Sep 2026 21:37:57 +0000 Subject: [PATCH 2/2] fix(objectql,runtime): scope the absence pin to packages/, which the gate farm owns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pnpm check:pm-dispatch-gates` went red on the previous commit — reproduced locally, 1 of 1291 self-test cases failed, exit 1: ✗ but no hint of this gate reaches a test file outside packages/** Root cause, measured rather than guessed. `scripts/cross-package-test-inputs.mjs` is a declaration table that `check-cross-package-test-inputs.mjs` imports, and `dispatch-gates.mjs` appends a followed module's globs to every importer as watch hints. So the `examples/**/*.ts` glob the previous commit added became an inherited hint on that gate — and the self-test pins that no hint of it reaches a test file outside `packages/**`, which is the whole reason the gate is listed as a change-KIND rather than a path derivation. That case is not a count to bump. All 41 tracked test files outside `packages/` are under `examples/`, so one examples-wide glob does not shrink the residue class it guards, it EMPTIES it — and the case's own instruction ("re-point at another member of its class") cannot be followed because no other member exists. Editing it would be weakening a gate to fit a declaration, in a `scripts/pm/` file this change does not own. So the pin narrows instead: `SCANNED_ROOTS` is `packages/` alone, and the declared glob drops to `packages/**/*.ts`, which the table already carried for `@objectstack/core` and `@objectstack/types` — the hint population is now byte-identical to `origin/main`'s and the census does not move at all. ⚠️ What that costs is written into both pins rather than left to be rediscovered: of this symbol's five surviving references, four were under `packages/**` and one was a test under the showcase example, which this pin no longer sees. Widening needs the residue measurement behind that self-test case redone first; the headers say so, and say that editing the case is not the repair. The reach control moves with the scope — from the showcase test to `packages/cli/src/commands/lint.ts`, which is also one of the files that carried the dead name until it was repaired, so it still proves the scan leaves its home package. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --- .../action-owner-key-single-source.test.ts | 59 ++++++++++++------- .../action-owner-key-single-source.test.ts | 48 ++++++++++----- scripts/cross-package-test-inputs.mjs | 38 +++++++----- turbo.json | 2 - 4 files changed, 93 insertions(+), 54 deletions(-) diff --git a/packages/objectql/src/action-owner-key-single-source.test.ts b/packages/objectql/src/action-owner-key-single-source.test.ts index d2c6a3249d..476f8f4103 100644 --- a/packages/objectql/src/action-owner-key-single-source.test.ts +++ b/packages/objectql/src/action-owner-key-single-source.test.ts @@ -122,20 +122,35 @@ describe('standalone-action owner key — one spelling in @objectstack/objectql * * ── Scope, and where it stops ─────────────────────────────────────────────── * - * `.ts` under `packages/` and `examples/`. That is the whole measured - * population: every mention this symbol has ever had outside the release record - * was a `.ts` file in one of those two roots, and so were all five surviving - * references. It is also the radius `@objectstack/core` and `@objectstack/types` - * already declare for pins of this shape, so it costs one table entry and one - * turbo task and no scheduler surgery. + * `.ts` under `packages/`, and that boundary is a MEASURED TRADE rather than a + * default — read this before widening it. * - * ⚠️ Widening to `docs/`, `content/`, `skills/` or `apps/` is TWO edits, never - * one: `SCANNED_ROOTS` here AND this package's globs in - * `scripts/cross-package-test-inputs.mjs` — and a NEW top-level root needs a - * matching ci.yml `crosspkg:` entry, which `check-ci-filter-parity.mjs` is the - * gate for. Widening the scanner alone reads as coverage while turbo never - * re-runs this suite for the files it now claims to judge, which is exactly the - * blind spot `check:cross-package-test-inputs` exists to close. + * `examples/` was in the scan for one commit. It is the right radius on the + * evidence (one of the five surviving references lived there), and the repo's + * gate farm refused it: declaring an examples-wide `.ts` glob in + * `scripts/cross-package-test-inputs.mjs` makes that glob an inherited watch + * hint on every importer of that table, `check:cross-package-test-inputs` + * included — and `dispatch-gates.mjs`'s self-test pins that no hint of that gate + * reaches a test file outside `packages/**`, because the whole reason it is + * listed as a change-KIND rather than a path derivation is that the hint route + * cannot reach the population it judges. Measured on this tree: all 41 tracked + * test files outside `packages/` are under `examples/`, so that one glob does + * not shrink the residue class, it EMPTIES it, and the case cannot be + * re-pointed at another member because there is none. + * + * ⇒ Widening this pin to `examples/` is not a two-line change and ⛔ must not be + * done by editing that self-test case. It needs the residue measurement behind + * that case redone, which is a `scripts/pm/` decision owned by another lane. + * What it costs today, stated rather than discovered later: of this symbol's + * five surviving references, four were `packages/**` and one was a test under + * the showcase example — which this pin would not have caught. + * + * ⚠️ Any widening — `examples/`, `docs/`, `content/`, `skills/`, `apps/` — is + * TWO edits, never one: `SCANNED_ROOTS` here AND this package's globs in + * `scripts/cross-package-test-inputs.mjs` (a NEW top-level root needs a matching + * ci.yml `crosspkg:` entry too, which `check-ci-filter-parity.mjs` gates). + * Widening the scanner alone reads as coverage while turbo never re-runs this + * suite for the files it now claims to judge. */ /** @@ -231,8 +246,8 @@ const PACKAGE_ROOT = findUp((dir) => { */ const REPO_ROOT = resolve(PACKAGE_ROOT, '../..'); -/** The trees this pin binds. See the scope note above before changing it. */ -const SCANNED_ROOTS: readonly string[] = ['packages', 'examples']; +/** The tree this pin binds. See the scope note above before changing it. */ +const SCANNED_ROOTS: readonly string[] = ['packages']; /** Spelled once so the declared glob and the scan stay in correspondence. */ const SCANNED_EXTENSION = '.ts'; @@ -344,14 +359,14 @@ describe('standalone-action owner key — the deleted member is dead TREE-WIDE ( // That is the exact property the file-scoped pin lost. expect(filesMentioning(DELETED_PLUGIN_MEMBER)).toContain(PIN_FILES[0]); - // ...and it must leave this package. The live helper is the reach - // control because it is the one symbol the convergence guarantees in - // both scanned roots: `examples/app-showcase/test/actions.test.ts` is - // literally one of the five files that carried the dead name until it - // was repaired, so a scan that cannot see it is a scan that would not - // have caught the defect this pin exists for. + // ...and it must LEAVE this package, which is the half a file-scoped + // pin never had. The live helper is the reach control: it is the one + // symbol the convergence guarantees outside this package, and the CLI + // site below is one of the files that carried the DEAD name until it + // was repaired — so a scan that cannot see it is a scan that would + // not have caught the defect this pin exists for. const reached = filesMentioning(CANONICAL_HELPER); - expect(reached).toContain('examples/app-showcase/test/actions.test.ts'); + expect(reached).toContain('packages/cli/src/commands/lint.ts'); expect(reached).toContain('packages/runtime/src/action-execution.ts'); }, SCAN_TIMEOUT_MS, diff --git a/packages/runtime/src/action-owner-key-single-source.test.ts b/packages/runtime/src/action-owner-key-single-source.test.ts index 99c990a3a8..36fc57c89e 100644 --- a/packages/runtime/src/action-owner-key-single-source.test.ts +++ b/packages/runtime/src/action-owner-key-single-source.test.ts @@ -217,15 +217,35 @@ describe('standalone-action owner key — half C: no bare literal (#14678)', () * * ── Scope, and where it stops ─────────────────────────────────────────────── * - * `.ts` under `packages/` and `examples/` — the whole measured population of - * this symbol's mentions outside the release record, and the radius - * `@objectstack/core` and `@objectstack/types` already declare for pins of this - * shape. ⚠️ Widening to `docs/`, `content/`, `skills/` or `apps/` is TWO edits: - * `SCANNED_ROOTS` here AND this package's globs in + * `.ts` under `packages/`, and that boundary is a MEASURED TRADE rather than a + * default — read this before widening it. + * + * `examples/` was in the scan for one commit. It is the right radius on the + * evidence (one of the five surviving references lived there), and the repo's + * gate farm refused it: declaring an examples-wide `.ts` glob in + * `scripts/cross-package-test-inputs.mjs` makes that glob an inherited watch + * hint on every importer of that table, `check:cross-package-test-inputs` + * included — and `dispatch-gates.mjs`'s self-test pins that no hint of that gate + * reaches a test file outside `packages/**`, because the whole reason it is + * listed as a change-KIND rather than a path derivation is that the hint route + * cannot reach the population it judges. Measured on this tree: all 41 tracked + * test files outside `packages/` are under `examples/`, so that one glob does + * not shrink the residue class, it EMPTIES it, and the case cannot be + * re-pointed at another member because there is none. + * + * ⇒ Widening this pin to `examples/` is not a two-line change and ⛔ must not be + * done by editing that self-test case. It needs the residue measurement behind + * that case redone, which is a `scripts/pm/` decision owned by another lane. + * What it costs today, stated rather than discovered later: of this symbol's + * five surviving references, four were `packages/**` and one was a test under + * the showcase example — which this pin would not have caught. + * + * ⚠️ Any widening — `examples/`, `docs/`, `content/`, `skills/`, `apps/` — is + * TWO edits, never one: `SCANNED_ROOTS` here AND this package's globs in * `scripts/cross-package-test-inputs.mjs` (a NEW top-level root needs a matching - * ci.yml `crosspkg:` entry — `check-ci-filter-parity.mjs` is the gate). Widening - * the scanner alone reads as coverage while turbo never re-runs this suite for - * the files it now claims to judge. + * ci.yml `crosspkg:` entry too, which `check-ci-filter-parity.mjs` gates). + * Widening the scanner alone reads as coverage while turbo never re-runs this + * suite for the files it now claims to judge. */ /** @@ -287,8 +307,8 @@ const NOT_A_STALE_MENTION: ReadonlyArray<{ readonly covers: (file: string) => bo /** …/packages/runtime/src → repo root, by arithmetic from this file. */ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..'); -/** The trees this pin binds. See the scope note above before changing it. */ -const SCANNED_ROOTS: readonly string[] = ['packages', 'examples']; +/** The tree this pin binds. See the scope note above before changing it. */ +const SCANNED_ROOTS: readonly string[] = ['packages']; /** Spelled once so the declared glob and the scan stay in correspondence. */ const SCANNED_EXTENSION = '.ts'; @@ -395,12 +415,12 @@ describe('standalone-action owner key — half D: the deleted member is dead TRE // exactly the property the file-scoped pin lost. expect(filesMentioning(DELETED_PLUGIN_MEMBER)).toContain(PIN_FILES[1]); - // ...and it must leave this package. `examples/app-showcase/test/ - // actions.test.ts` is literally one of the five files that carried - // the dead name until it was repaired, so a scan that cannot see it + // ...and it must LEAVE this package, which is the half a file-scoped + // pin never had. The CLI site below is one of the files that carried + // the DEAD name until it was repaired, so a scan that cannot see it // is a scan that would not have caught the defect this pin is for. const reached = filesMentioning(CANONICAL_HELPER); - expect(reached).toContain('examples/app-showcase/test/actions.test.ts'); + expect(reached).toContain('packages/cli/src/commands/lint.ts'); expect(reached).toContain('packages/objectql/src/action-governance.ts'); }, SCAN_TIMEOUT_MS, diff --git a/scripts/cross-package-test-inputs.mjs b/scripts/cross-package-test-inputs.mjs index ab51be6a4d..0f137d7384 100644 --- a/scripts/cross-package-test-inputs.mjs +++ b/scripts/cross-package-test-inputs.mjs @@ -858,9 +858,17 @@ export const CROSS_PACKAGE_TEST_INPUTS = { // in `SCANNED_ROOTS` / `SCANNED_EXTENSION`, and its own header says the two // widen together or not at all. // - // The scan's paths come out of `git grep`, which this gate's collector - // cannot name, so the globs are held by the escaping test itself rather than - // by a path on the roster -- the `heldBy` shape below. + // ⛔ `packages/` ONLY, and the `examples/` half is REFUSED rather than + // forgotten. An examples-wide `.ts` glob here would be inherited as a watch + // hint by every importer of this table -- `check-cross-package-test-inputs` + // included -- and `scripts/pm/dispatch-gates.mjs`'s self-test pins that no + // hint of that gate reaches a test file outside `packages/**`, which is the + // whole reason it is listed there as a change-KIND instead of a path + // derivation. Measured: all 41 tracked test files outside `packages/` are + // under `examples/`, so that glob does not shrink the residue class, it + // EMPTIES it, and the case cannot be re-pointed at another member. Widening + // needs that residue measurement redone first; the pins' own headers carry + // what it costs meanwhile. // // `cross-package-test-inputs.mjs` is NAMED in that pin's header (it is where // the widening instruction points) and never read, the same shape as the @@ -869,13 +877,8 @@ export const CROSS_PACKAGE_TEST_INPUTS = { // cheaper than rewording prose to dodge the scanner. globs: [ 'packages/**/*.ts', - 'examples/**/*.ts', 'scripts/cross-package-test-inputs.mjs', ], - heldBy: { - 'packages/**/*.ts': ['packages/objectql/src/action-owner-key-single-source.test.ts'], - 'examples/**/*.ts': ['packages/objectql/src/action-owner-key-single-source.test.ts'], - }, }, '@objectstack/runtime': { // src/error-envelope.conformance.test.ts imports `stripComments` from @@ -896,9 +899,17 @@ export const CROSS_PACKAGE_TEST_INPUTS = { // in `SCANNED_ROOTS` / `SCANNED_EXTENSION`, and its own header says the two // widen together or not at all. // - // The scan's paths come out of `git grep`, which this gate's collector - // cannot name, so the globs are held by the escaping test itself rather than - // by a path on the roster -- the `heldBy` shape below. + // ⛔ `packages/` ONLY, and the `examples/` half is REFUSED rather than + // forgotten. An examples-wide `.ts` glob here would be inherited as a watch + // hint by every importer of this table -- `check-cross-package-test-inputs` + // included -- and `scripts/pm/dispatch-gates.mjs`'s self-test pins that no + // hint of that gate reaches a test file outside `packages/**`, which is the + // whole reason it is listed there as a change-KIND instead of a path + // derivation. Measured: all 41 tracked test files outside `packages/` are + // under `examples/`, so that glob does not shrink the residue class, it + // EMPTIES it, and the case cannot be re-pointed at another member. Widening + // needs that residue measurement redone first; the pins' own headers carry + // what it costs meanwhile. // // `cross-package-test-inputs.mjs` is NAMED in that pin's header (it is where // the widening instruction points) and never read, the same shape as the @@ -909,13 +920,8 @@ export const CROSS_PACKAGE_TEST_INPUTS = { 'scripts/js-comment-mask.mjs', 'scripts/js-comment-mask.d.mts', 'packages/**/*.ts', - 'examples/**/*.ts', 'scripts/cross-package-test-inputs.mjs', ], - heldBy: { - 'packages/**/*.ts': ['packages/runtime/src/action-owner-key-single-source.test.ts'], - 'examples/**/*.ts': ['packages/runtime/src/action-owner-key-single-source.test.ts'], - }, }, '@objectstack/driver-sql': { // src/live-dialect-matrix.isolation.test.ts imports `stripComments` from diff --git a/turbo.json b/turbo.json index 0a4764e973..9d2942fdd1 100644 --- a/turbo.json +++ b/turbo.json @@ -224,7 +224,6 @@ "$TURBO_ROOT$/scripts/js-comment-mask.mjs", "$TURBO_ROOT$/scripts/js-comment-mask.d.mts", "$TURBO_ROOT$/packages/**/*.ts", - "$TURBO_ROOT$/examples/**/*.ts", "$TURBO_ROOT$/scripts/cross-package-test-inputs.mjs" ] }, @@ -237,7 +236,6 @@ "!coverage/**", "!.turbo/**", "$TURBO_ROOT$/packages/**/*.ts", - "$TURBO_ROOT$/examples/**/*.ts", "$TURBO_ROOT$/scripts/cross-package-test-inputs.mjs" ] },