From a92d2f67259e8e0ce52ce137a9a7ff64cbe91259 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 03:37:06 +0000 Subject: [PATCH 1/4] test(infra): attribute and gate the ECONNREFUSED network-escape class repo-wide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit happy-dom's default document URL is http://localhost:3000, so a DOM test that renders a component reaching one of the `apiFetch ?? fetch` fallbacks resolves a relative /api/v1/... against a real socket. The resulting stack carries no `stderr | FILE > TEST` header and no user frame, so Vitest cannot attribute it. That anonymity — not the noise — is why the class was fixed four times and still reproduced in 12 of 16 green shards. Enforcement lives in `afterEach`, not in a throwing `fetch`: every one of these call sites catches by construction (best-effort reads), so a rejection from inside `fetch` is swallowed and the test stays green. A measured sweep of every project (dom 8 shards, dom-heavy, unit, apps/console) attributes the class to 21 files in 9 packages, none of which is a file the four closed cards fixed. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b --- vitest.setup.base.ts | 1 + vitest.setup.network-escape-guard.ts | 216 +++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 vitest.setup.network-escape-guard.ts diff --git a/vitest.setup.base.ts b/vitest.setup.base.ts index 2013d38326..782d6572f1 100644 --- a/vitest.setup.base.ts +++ b/vitest.setup.base.ts @@ -9,6 +9,7 @@ import { vi } from 'vitest'; import { installI18nGlobalReset } from './vitest.setup.i18n-global'; +import './vitest.setup.network-escape-guard'; // objectui#4514 — put react-i18next's GLOBAL default-instance pointer back // after every test, so a provider-less render resolves the same way whether it diff --git a/vitest.setup.network-escape-guard.ts b/vitest.setup.network-escape-guard.ts new file mode 100644 index 0000000000..406a3a167e --- /dev/null +++ b/vitest.setup.network-escape-guard.ts @@ -0,0 +1,216 @@ +/** + * ObjectUI — network-escape guard (objectui#6640) + * + * Makes a test that reaches a REAL socket a NAMED, RED test instead of an + * anonymous stack on stderr. + * + * ## The class this closes + * + * happy-dom's DEFAULT document URL is `http://localhost:3000` — nothing in this + * repo configures it. So any DOM-env test that renders a component reaching one + * of the ~18 `apiFetch ?? fetch` / `globalThis.fetch` fallbacks in product code + * resolves a relative `/api/v1/...` against a real TCP socket, and prints: + * + * Error: connect ECONNREFUSED 127.0.0.1:3000 + * at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1637:16) + * + * That stack has NO `stderr | FILE > TESTNAME` header and NO user stack frame — + * it is an unhandled socket error raised below the layer Vitest captures per + * test, so Vitest cannot tie it to a file. THE ANONYMITY IS THE DEFECT, not the + * noise: it is why this class was fixed four times (objectui#5225 / #3339 / + * #4106 / #4688, all still intact on main) and still reproduced in 12 of 16 + * green shards. Each fix closed the files someone had listed by hand, and the + * output never said who was left. A measured sweep of every project found 21 + * emitting files across 9 packages, none of them a file those four cards fixed. + * + * ## Why enforcement is in `afterEach` and NOT a throwing `fetch` + * + * The obvious instrument — make the escaping `fetch` reject loudly — DOES NOT + * WORK HERE, and would have shipped a guard that never fires. Every one of + * these call sites already tolerates failure by construction: + * + * const doFetch = apiFetch ?? fetch; + * try { ... } catch { /* best-effort: leaves the rows as the server sent *\/ } + * + * That tolerance is exactly why the suite is GREEN while escaping. A throw from + * inside `fetch` lands in that same `catch` and is swallowed, leaving the test + * green and the guard silent. So the escape is RECORDED at the call and + * ASSERTED in `afterEach`, where no product `catch` can reach it. + * + * ## What it does NOT do + * + * It does not silence anything: the real request still goes out and the real + * ECONNREFUSED still prints, because those stacks are the evidence that a test + * reached for a socket (objectui#6640 ruling). It adds attribution beside them. + * It skips and quarantines nothing — every test still runs and asserts exactly + * what it asserted before. + * + * ## The burn-down list + * + * `KNOWN_ESCAPES` is the 21 files measured on `67dadd6`. They are not excused: + * each still emits, and now prints an ATTRIBUTED line naming itself, so a + * reader who meets a bare stack in a truncated run can tell whose it is. The + * list may only SHRINK — a file removed from it can never come back green, and + * a NEW escape in any other file is red on its first run. Fix one by serving + * the probe from a double (see `DatasetReportRenderer.test.tsx` for the shape), + * then delete its line here. + */ +import { afterEach, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +const REPO_ROOT = path.resolve(import.meta.dirname); + +/** The origin happy-dom hands every relative URL when no test owns port 3000. */ +const ESCAPE_ORIGIN = /^https?:\/\/(?:127\.0\.0\.1|localhost):3000(?:\/|$)/; + +/** + * Files measured escaping on 67dadd6 (objectui#6640). ONLY SHRINKS. + * The comment on each line is the endpoint it reached. + */ +const KNOWN_ESCAPES: ReadonlySet = new Set([ + // /api/v1/security/explain + "examples/schema-catalog/test/catalog-gallery-render.test.tsx", + // /api/v1/meta/_drafts + "packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx", + // /api/v1/meta/_drafts + "packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx", + // /api/v1/meta/_drafts + "packages/app-shell/src/console/home/__tests__/HomePage.inboxLinksTarget.test.tsx", + // /api/v1/meta/_drafts + "packages/app-shell/src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx", + // /api/v1/meta/object + "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx", + // /api/v1/meta/object + "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx", + // /api/v1/automation/_status + "packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx", + // /api/v1/ai/conversations + "packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx", + // /api/v1/security/explain + "packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx", + // /api/v1/meta/object/task + "packages/plugin-charts/src/ObjectChart.heightChain.test.tsx", + // /api/v1/security/explain + "packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx", + // /api/task/42, /api/v1/security/explain + "packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx", + // /api/v1/security/explain + "packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx", + // /api/v1/security/explain + "packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx", + // /api/v1/security/explain + "packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx", + // /api/v1/security/explain + "packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx", + // /api/v1/security/explain + "packages/plugin-kanban/src/ObjectKanban.navWidthDefault.test.tsx", + // /api/v1/security/explain + "packages/plugin-kanban/src/ObjectKanban.overlayTitleI18n.test.tsx", + // /api/v1/security/explain + "packages/plugin-kanban/src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx", + // /api/v1/security/explain + "packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx",]); + +type Escape = { file: string; test: string; url: string }; + +/** Escapes seen since the current test started. */ +let pending: Escape[] = []; + +const LEDGER = process.env.OBJECTUI_NETWORK_ESCAPE_LEDGER; + +function relative(p: string | undefined): string { + if (!p) return ''; + return path.relative(REPO_ROOT, p).split(path.sep).join('/'); +} + +const realFetch = globalThis.fetch; + +globalThis.fetch = function guardedFetch(input: any, init?: any) { + let raw: string; + try { + raw = typeof input === 'string' ? input : (input?.url ?? String(input)); + } catch { + raw = ''; + } + let absolute = raw; + try { + absolute = new URL(raw, (globalThis as any).location?.href ?? undefined).href; + } catch { + /* a non-URL input cannot be an escape; leave it as-is */ + } + + if (ESCAPE_ORIGIN.test(absolute)) { + let state: any = {}; + try { + state = expect.getState() ?? {}; + } catch { + /* called outside a test */ + } + const escape: Escape = { + file: relative(state.testPath), + test: state.currentTestName ?? '', + url: absolute, + }; + pending.push(escape); + + if (LEDGER) { + try { + fs.appendFileSync(LEDGER, JSON.stringify(escape) + '\n'); + } catch { + /* the instrument must never break a run */ + } + } + + // Known escapes get an ATTRIBUTED line next to the anonymous stack the real + // request is about to print. This is the half that cures the reported harm: + // a bare ECONNREFUSED in a truncated log no longer reads as an unowned red. + // Written straight to process stderr, NOT via `console` — under happy-dom + // `globalThis.console` is the window's virtual console and never reaches the + // terminal (measured: the line vanished entirely). The real ECONNREFUSED + // stack is written to process stderr by Node, so this is also the only way + // to put the attribution in the SAME stream, next to the stack it explains. + if (KNOWN_ESCAPES.has(escape.file)) { + try { + process.stderr.write( + `[network-escape - known - objectui#6640] ${escape.file} -> ${escape.url}\n` + + ` The ECONNREFUSED stack near this line belongs to that file. Serve the\n` + + ` probe from a double, then delete its line from KNOWN_ESCAPES in\n` + + ` vitest.setup.network-escape-guard.ts.\n`, + ); + } catch { + /* the instrument must never break a run */ + } + } + } + + // Always pass through. The real connection attempt IS the evidence that a + // test reached for a socket; hiding it would keep the escape and remove the + // proof (objectui#6640 ruling). + return realFetch.call(globalThis, input, init); +} as typeof globalThis.fetch; + +afterEach(() => { + const seen = pending; + pending = []; + const unknown = seen.filter((e) => !KNOWN_ESCAPES.has(e.file)); + if (unknown.length === 0) return; + + const byUrl = [...new Set(unknown.map((e) => e.url))]; + const file = unknown[0].file; + throw new Error( + `Network escape: this test reached a REAL socket at ${byUrl.join(', ')}.\n` + + ` file: ${file}\n` + + ` test: ${unknown[0].test}\n` + + `\n` + + `happy-dom's default document URL is http://localhost:3000, so a relative\n` + + `fetch from a component under test resolves to a live TCP connection. The\n` + + `product call site catches the failure by design, so the test stayed green\n` + + `while printing an unattributable ECONNREFUSED stack — that is objectui#6640.\n` + + `\n` + + `Fix: serve the probe from a double rather than the network. See\n` + + `packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx for the\n` + + `shape (vi.stubGlobal('fetch', router) + vi.unstubAllGlobals()). Do NOT add\n` + + `this file to KNOWN_ESCAPES — that list only shrinks.`, + ); +}); From 84e89f789a1977d9a4f1e101bdefbe343eb411e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 05:01:16 +0000 Subject: [PATCH 2/4] test(infra): derive the guard's repo root without Vite's import.meta.url rewrite Two defects found by running the gates rather than assuming them. `tsconfig.vitest-setup.json` ships no `@types/node` by design, so the first draft's `node:fs` / `node:path` / `process` / `import.meta.dirname` uses were five type errors. The guard now declares the one structural type it needs and drops the file-ledger entirely. `new URL('.', import.meta.url)` is statically rewritten by Vite at transform time, so the value reaching the run was `/@fs/...` rather than a real path. Every path relative-isation missed, which failed all 21 known escapes at once. Derived by string surgery on `import.meta.url` instead. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b --- vitest.setup.network-escape-guard.ts | 80 ++++++++++++++++++---------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/vitest.setup.network-escape-guard.ts b/vitest.setup.network-escape-guard.ts index 406a3a167e..2489715969 100644 --- a/vitest.setup.network-escape-guard.ts +++ b/vitest.setup.network-escape-guard.ts @@ -56,10 +56,50 @@ * then delete its line here. */ import { afterEach, expect } from 'vitest'; -import fs from 'node:fs'; -import path from 'node:path'; -const REPO_ROOT = path.resolve(import.meta.dirname); +// No `node:*` imports, no `process` typings and no `import.meta.dirname` here, +// deliberately: `tsconfig.vitest-setup.json` — the gate that compiles this file +// — ships NO `@types/node` on purpose (it documents the measurement: adding it +// costs 12 errors inside third-party declarations). Every Node touch below goes +// through a locally-declared structural type instead, so the gate stays green +// without weakening it for the other root setup files. + +/** + * This file sits at the repo root, so its own directory IS the repo root. + * + * Derived by STRING SURGERY on `import.meta.url`, not with `new URL('.', + * import.meta.url)`: Vite statically rewrites that exact pattern at transform + * time, and the value that survives into the run is `/@fs/...`, not a real path. + * Measured — it silently turned every path relative-isation into a miss, which + * failed all 21 known escapes at once. + */ +const REPO_ROOT = (() => { + const withoutScheme = import.meta.url.replace(/^file:\/\//, ''); + const dir = withoutScheme.replace(/\/[^/]*$/, '/'); + try { + return decodeURIComponent(dir); + } catch { + return dir; + } +})(); + +/** The sliver of `process` this file uses, declared rather than imported. */ +type StderrHost = { process?: { stderr?: { write(chunk: string): void } } }; + +/** + * The attribution line goes to process stderr, NOT through `console`: under + * happy-dom `globalThis.console` is the window's virtual console and never + * reaches the terminal (measured — the line vanished entirely). Node writes the + * real ECONNREFUSED stack to process stderr, so this is also the only way to put + * the attribution in the SAME stream, beside the stack it explains. + */ +function writeStderr(message: string): void { + try { + (globalThis as StderrHost).process?.stderr?.write(message); + } catch { + /* the instrument must never break a run */ + } +} /** The origin happy-dom hands every relative URL when no test owns port 3000. */ const ESCAPE_ORIGIN = /^https?:\/\/(?:127\.0\.0\.1|localhost):3000(?:\/|$)/; @@ -117,11 +157,10 @@ type Escape = { file: string; test: string; url: string }; /** Escapes seen since the current test started. */ let pending: Escape[] = []; -const LEDGER = process.env.OBJECTUI_NETWORK_ESCAPE_LEDGER; - function relative(p: string | undefined): string { if (!p) return ''; - return path.relative(REPO_ROOT, p).split(path.sep).join('/'); + const normalised = p.replace(/\\/g, '/'); + return normalised.startsWith(REPO_ROOT) ? normalised.slice(REPO_ROOT.length) : normalised; } const realFetch = globalThis.fetch; @@ -154,33 +193,16 @@ globalThis.fetch = function guardedFetch(input: any, init?: any) { }; pending.push(escape); - if (LEDGER) { - try { - fs.appendFileSync(LEDGER, JSON.stringify(escape) + '\n'); - } catch { - /* the instrument must never break a run */ - } - } - // Known escapes get an ATTRIBUTED line next to the anonymous stack the real // request is about to print. This is the half that cures the reported harm: // a bare ECONNREFUSED in a truncated log no longer reads as an unowned red. - // Written straight to process stderr, NOT via `console` — under happy-dom - // `globalThis.console` is the window's virtual console and never reaches the - // terminal (measured: the line vanished entirely). The real ECONNREFUSED - // stack is written to process stderr by Node, so this is also the only way - // to put the attribution in the SAME stream, next to the stack it explains. if (KNOWN_ESCAPES.has(escape.file)) { - try { - process.stderr.write( - `[network-escape - known - objectui#6640] ${escape.file} -> ${escape.url}\n` + - ` The ECONNREFUSED stack near this line belongs to that file. Serve the\n` + - ` probe from a double, then delete its line from KNOWN_ESCAPES in\n` + - ` vitest.setup.network-escape-guard.ts.\n`, - ); - } catch { - /* the instrument must never break a run */ - } + writeStderr( + `[network-escape - known - objectui#6640] ${escape.file} -> ${escape.url}\n` + + ` The ECONNREFUSED stack near this line belongs to that file. Serve the\n` + + ` probe from a double, then delete its line from KNOWN_ESCAPES in\n` + + ` vitest.setup.network-escape-guard.ts.\n`, + ); } } From 1836f41f8c6c6e621b97802981d6a845d2b67219 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 05:24:33 +0000 Subject: [PATCH 3/4] test(infra): pin the network-escape ledger shrink-only, in both directions The guard's docstring said KNOWN_ESCAPES "may only shrink" and nothing made that true: an author meeting the guard's red could go green by adding a line, which is how a burn-down ledger decays into the permanent quarantine it is not. Exports the set for the pin and adds the reconcile. A name in the ledger but not the pin means it grew (red). A name in the pin but not the ledger means a fix landed and the pin is stale (red), so shrinking is a deliberate two-line change rather than a silent one. An anchored non-vacuity floor covers the case both reconciles pass on two empty collections, and checks each pinned path still resolves on disk. No behaviour change to the guard beyond the export. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b --- .../__tests__/network-escape-ledger.test.ts | 140 ++++++++++++++++++ vitest.setup.network-escape-guard.ts | 7 +- 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 scripts/__tests__/network-escape-ledger.test.ts diff --git a/scripts/__tests__/network-escape-ledger.test.ts b/scripts/__tests__/network-escape-ledger.test.ts new file mode 100644 index 0000000000..af2efb14d1 --- /dev/null +++ b/scripts/__tests__/network-escape-ledger.test.ts @@ -0,0 +1,140 @@ +/** + * The shrink-only pin for the network-escape ledger (objectui#6640). + * + * `KNOWN_ESCAPES` in `vitest.setup.network-escape-guard.ts` records the 21 test + * files measured reaching a real socket on `67dadd6`. The guard's own docstring + * says that list "may only shrink" — and until this file existed, nothing made + * that true. An author who hit the guard's red could make it green by adding a + * line, which is exactly how a burn-down ledger decays into the permanent + * quarantine it is not supposed to be. THAT is the failure this pin prevents; + * it does not re-measure escapes (that needs a real DOM run) and does not try. + * + * It reconciles the live set against the pinned literal in BOTH directions: + * + * - a name in the ledger but not in the pin -> the ledger GREW. Red. + * - a name in the pin but not in the ledger -> a real fix landed, and the + * pin is now stale. Red until the pin is updated too, which is the point: + * shrinking the ledger is a deliberate TWO-LINE change (delete from the + * ledger, delete from the pin), never a silent one. + * + * Plus an anchored non-vacuity floor, because both reconciles above pass + * vacuously if the imported set or the pin is empty — the classic way a pin + * keeps reporting green after the thing it pins stopped existing. + */ +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { KNOWN_ESCAPES } from '../../vitest.setup.network-escape-guard'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +/** + * The 21 files measured escaping on `67dadd6`, pinned verbatim. + * + * Provenance: a full sweep of every Vitest project (`dom` all 8 shards, + * `dom-heavy`, `unit`, `apps/console`) with an attribution ledger wrapping + * `fetch`. Do not add to this list. Deleting from it is the intended direction + * and must be done in lockstep with `KNOWN_ESCAPES`. + */ +const PINNED_LEDGER: readonly string[] = [ + "examples/schema-catalog/test/catalog-gallery-render.test.tsx", + "packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx", + "packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx", + "packages/app-shell/src/console/home/__tests__/HomePage.inboxLinksTarget.test.tsx", + "packages/app-shell/src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx", + "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx", + "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx", + "packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx", + "packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx", + "packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx", + "packages/plugin-charts/src/ObjectChart.heightChain.test.tsx", + "packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx", + "packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx", + "packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx", + "packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx", + "packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx", + "packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx", + "packages/plugin-kanban/src/ObjectKanban.navWidthDefault.test.tsx", + "packages/plugin-kanban/src/ObjectKanban.overlayTitleI18n.test.tsx", + "packages/plugin-kanban/src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx", + "packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx", +]; + +describe('network-escape ledger (objectui#6640) is shrink-only', () => { + it('has not GROWN: every name in KNOWN_ESCAPES is in the pin', () => { + const pinned = new Set(PINNED_LEDGER); + const added = [...KNOWN_ESCAPES].filter((file) => !pinned.has(file)).sort(); + + expect( + added, + [ + 'The network-escape ledger GREW, and it may only shrink.', + '', + 'A test that reaches a real socket is a defect to fix, not a line to add here.', + 'If the guard went red on your file, serve its probe from a double instead —', + 'packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx is the shape', + "(vi.stubGlobal('fetch', router) + vi.unstubAllGlobals()).", + '', + 'Names added to KNOWN_ESCAPES but absent from PINNED_LEDGER:', + ...added.map((file) => ` ${file}`), + ].join('\n'), + ).toEqual([]); + }); + + it('has not gone STALE: every pinned name is still in KNOWN_ESCAPES', () => { + const stale = PINNED_LEDGER.filter((file) => !KNOWN_ESCAPES.has(file)).sort(); + + expect( + stale, + [ + 'A pinned escape is gone from KNOWN_ESCAPES — which is good news, banked wrong.', + '', + 'Shrinking the ledger is deliberately a TWO-LINE change: delete the entry from', + 'KNOWN_ESCAPES in vitest.setup.network-escape-guard.ts AND delete it from', + 'PINNED_LEDGER in this file. This red is the second line asking to be written.', + '', + 'Pinned but no longer in the ledger:', + ...stale.map((file) => ` ${file}`), + ].join('\n'), + ).toEqual([]); + }); + + it('is not vacuous: the pin is non-empty and every pinned path exists on disk', () => { + // Both reconciles above are satisfied by two empty collections. Anchor the + // floor to a literal so that emptying either side is a red rather than a + // silent green — and check the paths resolve, so a rename cannot leave the + // pin agreeing with the ledger about files that no longer exist. + expect( + PINNED_LEDGER.length, + 'PINNED_LEDGER is empty, so both reconciles above pass vacuously. If the ledger ' + + 'genuinely reached zero, that is the win this whole instrument was built for — ' + + 'delete the guard\'s KNOWN_ESCAPES machinery and this pin together, rather than ' + + 'leaving a pin that asserts nothing.', + ).toBeGreaterThan(0); + + expect( + KNOWN_ESCAPES.size, + 'KNOWN_ESCAPES is empty while PINNED_LEDGER is not — see the staleness test above.', + ).toBeGreaterThan(0); + + const missing = PINNED_LEDGER.filter( + (file) => !fs.existsSync(path.join(repoRoot, file)), + ).sort(); + + expect( + missing, + [ + 'A pinned escape names a file that is not on disk.', + '', + 'The ledger keys off the test file path, so a renamed or deleted file leaves an', + 'entry that can never match and can never be burned down — it would sit here', + 'looking like outstanding work that no longer exists.', + '', + 'Missing paths:', + ...missing.map((file) => ` ${file}`), + ].join('\n'), + ).toEqual([]); + }); +}); diff --git a/vitest.setup.network-escape-guard.ts b/vitest.setup.network-escape-guard.ts index 2489715969..bcdba52803 100644 --- a/vitest.setup.network-escape-guard.ts +++ b/vitest.setup.network-escape-guard.ts @@ -50,7 +50,10 @@ * `KNOWN_ESCAPES` is the 21 files measured on `67dadd6`. They are not excused: * each still emits, and now prints an ATTRIBUTED line naming itself, so a * reader who meets a bare stack in a truncated run can tell whose it is. The - * list may only SHRINK — a file removed from it can never come back green, and + * list may only SHRINK — enforced mechanically by the reconcile pin in + * `scripts/__tests__/network-escape-ledger.test.ts`, which is the only reason + * the word "only" here is a fact rather than a hope: a file removed from it can + * never come back green, and * a NEW escape in any other file is red on its first run. Fix one by serving * the probe from a double (see `DatasetReportRenderer.test.tsx` for the shape), * then delete its line here. @@ -108,7 +111,7 @@ const ESCAPE_ORIGIN = /^https?:\/\/(?:127\.0\.0\.1|localhost):3000(?:\/|$)/; * Files measured escaping on 67dadd6 (objectui#6640). ONLY SHRINKS. * The comment on each line is the endpoint it reached. */ -const KNOWN_ESCAPES: ReadonlySet = new Set([ +export const KNOWN_ESCAPES: ReadonlySet = new Set([ // /api/v1/security/explain "examples/schema-catalog/test/catalog-gallery-render.test.tsx", // /api/v1/meta/_drafts From 0e1f5ae04e1bd7db9084b78b43966bc6e7dc6869 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 05:43:25 +0000 Subject: [PATCH 4/4] style(infra): keep the ledger and its pin edited one line at a time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The closing bracket sat on the same line as the last ledger entry, so the burn-down's intended edit — delete the line for a file you just fixed — turned into a syntax error on the last entry. Detached it, and matched the surrounding style (two-space indent, single quotes) in both lists. Also repairs a docstring wrap and states the two-line rule where a reader meets it: an entry leaves KNOWN_ESCAPES and PINNED_LEDGER together. No behaviour change; the ledger contents are byte-identical in membership. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b --- .../__tests__/network-escape-ledger.test.ts | 42 +++++++-------- vitest.setup.network-escape-guard.ts | 53 ++++++++++--------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/scripts/__tests__/network-escape-ledger.test.ts b/scripts/__tests__/network-escape-ledger.test.ts index af2efb14d1..1b01699e20 100644 --- a/scripts/__tests__/network-escape-ledger.test.ts +++ b/scripts/__tests__/network-escape-ledger.test.ts @@ -39,27 +39,27 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../ * and must be done in lockstep with `KNOWN_ESCAPES`. */ const PINNED_LEDGER: readonly string[] = [ - "examples/schema-catalog/test/catalog-gallery-render.test.tsx", - "packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx", - "packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx", - "packages/app-shell/src/console/home/__tests__/HomePage.inboxLinksTarget.test.tsx", - "packages/app-shell/src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx", - "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx", - "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx", - "packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx", - "packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx", - "packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx", - "packages/plugin-charts/src/ObjectChart.heightChain.test.tsx", - "packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx", - "packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx", - "packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx", - "packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx", - "packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx", - "packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx", - "packages/plugin-kanban/src/ObjectKanban.navWidthDefault.test.tsx", - "packages/plugin-kanban/src/ObjectKanban.overlayTitleI18n.test.tsx", - "packages/plugin-kanban/src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx", - "packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx", + 'examples/schema-catalog/test/catalog-gallery-render.test.tsx', + 'packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx', + 'packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx', + 'packages/app-shell/src/console/home/__tests__/HomePage.inboxLinksTarget.test.tsx', + 'packages/app-shell/src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx', + 'packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx', + 'packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx', + 'packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx', + 'packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx', + 'packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx', + 'packages/plugin-charts/src/ObjectChart.heightChain.test.tsx', + 'packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx', + 'packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx', + 'packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx', + 'packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx', + 'packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx', + 'packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx', + 'packages/plugin-kanban/src/ObjectKanban.navWidthDefault.test.tsx', + 'packages/plugin-kanban/src/ObjectKanban.overlayTitleI18n.test.tsx', + 'packages/plugin-kanban/src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx', + 'packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx', ]; describe('network-escape ledger (objectui#6640) is shrink-only', () => { diff --git a/vitest.setup.network-escape-guard.ts b/vitest.setup.network-escape-guard.ts index bcdba52803..41440118ca 100644 --- a/vitest.setup.network-escape-guard.ts +++ b/vitest.setup.network-escape-guard.ts @@ -52,11 +52,11 @@ * reader who meets a bare stack in a truncated run can tell whose it is. The * list may only SHRINK — enforced mechanically by the reconcile pin in * `scripts/__tests__/network-escape-ledger.test.ts`, which is the only reason - * the word "only" here is a fact rather than a hope: a file removed from it can - * never come back green, and - * a NEW escape in any other file is red on its first run. Fix one by serving - * the probe from a double (see `DatasetReportRenderer.test.tsx` for the shape), - * then delete its line here. + * the word "only" here is a fact rather than a hope. A file removed from it can + * never come back green, and a NEW escape in any other file is red on its first + * run. Fix one by serving the probe from a double (see + * `DatasetReportRenderer.test.tsx` for the shape), then delete its line here AND + * from `PINNED_LEDGER` in the pin — the two must move together. */ import { afterEach, expect } from 'vitest'; @@ -113,47 +113,48 @@ const ESCAPE_ORIGIN = /^https?:\/\/(?:127\.0\.0\.1|localhost):3000(?:\/|$)/; */ export const KNOWN_ESCAPES: ReadonlySet = new Set([ // /api/v1/security/explain - "examples/schema-catalog/test/catalog-gallery-render.test.tsx", + 'examples/schema-catalog/test/catalog-gallery-render.test.tsx', // /api/v1/meta/_drafts - "packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx", + 'packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx', // /api/v1/meta/_drafts - "packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx", + 'packages/app-shell/src/console/home/__tests__/HomePage.authoringCapabilityGate.test.tsx', // /api/v1/meta/_drafts - "packages/app-shell/src/console/home/__tests__/HomePage.inboxLinksTarget.test.tsx", + 'packages/app-shell/src/console/home/__tests__/HomePage.inboxLinksTarget.test.tsx', // /api/v1/meta/_drafts - "packages/app-shell/src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx", + 'packages/app-shell/src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx', // /api/v1/meta/object - "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx", + 'packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.inactiveRetained.test.tsx', // /api/v1/meta/object - "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx", + 'packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx', // /api/v1/automation/_status - "packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx", + 'packages/app-shell/src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx', // /api/v1/ai/conversations - "packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx", + 'packages/app-shell/src/views/studio-design/__tests__/studioSurfaceContext.test.tsx', // /api/v1/security/explain - "packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx", + 'packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx', // /api/v1/meta/object/task - "packages/plugin-charts/src/ObjectChart.heightChain.test.tsx", + 'packages/plugin-charts/src/ObjectChart.heightChain.test.tsx', // /api/v1/security/explain - "packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx", + 'packages/plugin-detail/src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx', // /api/task/42, /api/v1/security/explain - "packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx", + 'packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx', // /api/v1/security/explain - "packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx", + 'packages/plugin-detail/src/__tests__/recordDetailsBodySource.test.tsx', // /api/v1/security/explain - "packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx", + 'packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx', // /api/v1/security/explain - "packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx", + 'packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx', // /api/v1/security/explain - "packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx", + 'packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx', // /api/v1/security/explain - "packages/plugin-kanban/src/ObjectKanban.navWidthDefault.test.tsx", + 'packages/plugin-kanban/src/ObjectKanban.navWidthDefault.test.tsx', // /api/v1/security/explain - "packages/plugin-kanban/src/ObjectKanban.overlayTitleI18n.test.tsx", + 'packages/plugin-kanban/src/ObjectKanban.overlayTitleI18n.test.tsx', // /api/v1/security/explain - "packages/plugin-kanban/src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx", + 'packages/plugin-kanban/src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx', // /api/v1/security/explain - "packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx",]); + 'packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx', +]); type Escape = { file: string; test: string; url: string };