From ee56703d5136b9a5be499ed9f31445dd421fbf66 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 17 Oct 2026 00:38:41 -0700 Subject: [PATCH] chore(ci): ratchet importers of the deprecated runtime-owner resolvers (#30002) * chore(ci): ratchet renderer importers of the deprecated runtime-owner resolvers getRuntimeEnvironmentIdForWorktree and resolveRuntimeOwnerForWorktree read a worktree two hosts publish as not-a-runtime, which callers treat as local. The 62 renderer files importing them are grandfathered; the list may only shrink. * ci: register the deprecated runtime-owner ratchet step in the preflight phase map --- .github/workflows/pr.yml | 4 + ...cated-runtime-owner-importers-baseline.txt | 66 ++++++++++++ ...check-deprecated-runtime-owner-ratchet.mjs | 70 ++++++++++++ ...-deprecated-runtime-owner-ratchet.test.mjs | 101 ++++++++++++++++++ config/scripts/pr-preflight-gates.test.mjs | 1 + package.json | 3 +- 6 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 config/deprecated-runtime-owner-importers-baseline.txt create mode 100644 config/scripts/check-deprecated-runtime-owner-ratchet.mjs create mode 100644 config/scripts/check-deprecated-runtime-owner-ratchet.test.mjs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 98852288786..c1ea47f200e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -386,6 +386,10 @@ jobs: if: needs.code_paths.outputs.static_analysis == 'true' run: bun config/scripts/check-layout-writer-ratchets.mjs + - name: Enforce deprecated runtime-owner importer ratchet + if: needs.code_paths.outputs.static_analysis == 'true' + run: node config/scripts/check-deprecated-runtime-owner-ratchet.mjs + - name: Check Node runtime pin if: needs.code_paths.outputs.static_analysis == 'true' run: node config/scripts/check-node-runtime-pin.mjs diff --git a/config/deprecated-runtime-owner-importers-baseline.txt b/config/deprecated-runtime-owner-importers-baseline.txt new file mode 100644 index 00000000000..7230da894ee --- /dev/null +++ b/config/deprecated-runtime-owner-importers-baseline.txt @@ -0,0 +1,66 @@ +# Renderer files that import getRuntimeEnvironmentIdForWorktree / resolveRuntimeOwnerForWorktree. +# This is a RATCHET: the list may only SHRINK. Both resolvers read an ambiguous (two-host) +# worktree as "not a runtime", which callers treat as local. +# Prune after migrating a file: pnpm check:deprecated-runtime-owner-ratchet --prune +src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.tsx +src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-page-lifecycle.ts +src/renderer/src/components/browser-pane/workspace-doc/doc-preview-document-actions.ts +src/renderer/src/components/browser-pane/workspace-doc/workspace-doc-page-pane.tsx +src/renderer/src/components/native-chat/native-chat-file-link.ts +src/renderer/src/components/native-chat/native-chat-http-link-source-owner.ts +src/renderer/src/components/native-chat/native-chat-runtime-owner.ts +src/renderer/src/components/right-sidebar/FileExplorer.tsx +src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-controller-state.tsx +src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts +src/renderer/src/components/right-sidebar/file-explorer-row-context-menu.tsx +src/renderer/src/components/right-sidebar/git-status-file-watch-refresh.ts +src/renderer/src/components/right-sidebar/source-control/listing/entry-context-menu.tsx +src/renderer/src/components/right-sidebar/use-file-explorer-tree-pane-state.ts +src/renderer/src/components/right-sidebar/useFileSearchRunner.ts +src/renderer/src/components/right-sidebar/useFileSearchScope.ts +src/renderer/src/components/tab-bar/use-tab-bar-runtime-model.ts +src/renderer/src/components/tab-bar/web-runtime-tab-move-mirror.ts +src/renderer/src/components/tab-group/useTabGroupActivationCommands.ts +src/renderer/src/components/tab-group/useTabGroupCreationCommands.ts +src/renderer/src/components/terminal-pane/terminal-input-host-platform.ts +src/renderer/src/components/terminal-pane/terminal-pane-menu-paste.ts +src/renderer/src/components/terminal-pane/terminal-pane-paste-execution.ts +src/renderer/src/components/terminal-pane/terminal-pane-paste-listeners.ts +src/renderer/src/components/terminal-workspace-model.ts +src/renderer/src/hooks/editor-external-watch-targets.ts +src/renderer/src/hooks/ipc-events/browser-state-ipc-bridge.ts +src/renderer/src/hooks/ipc-events/tab-lifecycle-ipc-bridge.ts +src/renderer/src/lib/activate-ai-vault-structured-session.ts +src/renderer/src/lib/active-agent-note-send.ts +src/renderer/src/lib/active-agent-note-target.ts +src/renderer/src/lib/agent-hibernation-coordinator.ts +src/renderer/src/lib/client-creation-action-policy.ts +src/renderer/src/lib/doc-preview-grants.ts +src/renderer/src/lib/execute-open-editor-path-move.ts +src/renderer/src/lib/execution-host-facts.ts +src/renderer/src/lib/file-preview.ts +src/renderer/src/lib/host-mirrored-pane-liveness.ts +src/renderer/src/lib/launch-agent-background-session.ts +src/renderer/src/lib/launch-agent-in-new-tab.ts +src/renderer/src/lib/launch-ai-vault-session.ts +src/renderer/src/lib/structured-agent-session-tab-activation.ts +src/renderer/src/lib/tab-number-shortcuts.ts +src/renderer/src/lib/terminal-tab-owner-activation.ts +src/renderer/src/lib/terminal-worktree-route.ts +src/renderer/src/lib/web-runtime-worktree-terminal-after-wake.ts +src/renderer/src/lib/workspace-browser-tab-close.ts +src/renderer/src/lib/workspace-tab-palette-activation.ts +src/renderer/src/lib/worktree-activation-pty-inventory.ts +src/renderer/src/lib/worktree-activation.ts +src/renderer/src/lib/worktree-creation-agent-seeds.ts +src/renderer/src/lib/worktree-row-open-in-owner.ts +src/renderer/src/lib/worktree-sort-order-host-split.ts +src/renderer/src/runtime/close-mirrored-editor-tab.ts +src/renderer/src/runtime/web-runtime-terminal-actions.ts +src/renderer/src/store/slices/browser/browser-page-conversion-actions.ts +src/renderer/src/store/slices/browser/browser-tab-actions.ts +src/renderer/src/store/slices/tabs/tabs-host-mirroring.ts +src/renderer/src/store/slices/terminal-tab-retirement.ts +src/renderer/src/store/terminals/terminal-active-workspace-creation.ts +src/renderer/src/store/terminals/terminal-pane-hibernation.ts +src/renderer/src/store/terminals/terminal-shutdown.ts diff --git a/config/scripts/check-deprecated-runtime-owner-ratchet.mjs b/config/scripts/check-deprecated-runtime-owner-ratchet.mjs new file mode 100644 index 00000000000..a2854403737 --- /dev/null +++ b/config/scripts/check-deprecated-runtime-owner-ratchet.mjs @@ -0,0 +1,70 @@ +import process from 'node:process' +import { pathToFileURL } from 'node:url' +import { readTrackedSources, runEntryRatchet } from './ratchet-baseline.mjs' + +// Ratchet on renderer files that import the deprecated runtime-owner resolvers. Both read a +// worktree that two hosts publish as "not a runtime", which callers then treat as local, so a stop, +// sleep or kill can land on the wrong machine. New code resolves the owner with its host and +// ambiguity kept; the importer list may only shrink. + +const BASELINE_PATH = 'config/deprecated-runtime-owner-importers-baseline.txt' +const SCAN_ROOT = 'src/renderer/src' +const DEPRECATED_NAMES = ['getRuntimeEnvironmentIdForWorktree', 'resolveRuntimeOwnerForWorktree'] +// The defining module and its own tests are not importers. +const DEFINING_FILE = 'src/renderer/src/lib/worktree-runtime-owner.ts' + +// Any reference to a deprecated name in a file that loads the defining module, whatever the import +// form (named, aliased, namespace, dynamic, `.then` destructuring, re-export). Simpler than tracking +// bindings, and in production code a mention of these names is a use. +const DEPRECATED_NAME = new RegExp(`\\b(?:${DEPRECATED_NAMES.join('|')})\\b`) +const DEFINING_MODULE_SPECIFIER = /['"`][^'"`]*\/worktree-runtime-owner['"`]/ +// Block and line comments; `://` inside a string (a URL) is not a line comment. +const COMMENT = /\/\*[\s\S]*?\*\/|(? importsDeprecatedRuntimeOwner(source)) + .map(([rel]) => rel) + .sort() +} + +const RATCHET = { + name: 'deprecated-runtime-owner', + baselinePath: BASELINE_PATH, + header: [ + '# Renderer files that import getRuntimeEnvironmentIdForWorktree / resolveRuntimeOwnerForWorktree.', + '# This is a RATCHET: the list may only SHRINK. Both resolvers read an ambiguous (two-host)', + '# worktree as "not a runtime", which callers treat as local.', + '# Prune after migrating a file: pnpm check:deprecated-runtime-owner-ratchet --prune' + ], + collect: collectDeprecatedRuntimeOwnerImporters, + fix: [ + 'getRuntimeEnvironmentIdForWorktree / resolveRuntimeOwnerForWorktree collapse an owner that two', + 'hosts publish into "local". Resolve the owner with its host kept and refuse on ambiguity', + 'instead (see src/renderer/src/lib/resolve-owner.ts).' + ] +} + +export function main(root = process.cwd(), arg = undefined) { + return runEntryRatchet(RATCHET, root, arg) +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + process.exit(main(process.cwd(), process.argv[2])) +} diff --git a/config/scripts/check-deprecated-runtime-owner-ratchet.test.mjs b/config/scripts/check-deprecated-runtime-owner-ratchet.test.mjs new file mode 100644 index 00000000000..8de50f66890 --- /dev/null +++ b/config/scripts/check-deprecated-runtime-owner-ratchet.test.mjs @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest' + +import { + importsDeprecatedRuntimeOwner, + isScannedPath +} from './check-deprecated-runtime-owner-ratchet.mjs' + +describe('importsDeprecatedRuntimeOwner', () => { + it('finds a named import of either deprecated resolver', () => { + expect( + importsDeprecatedRuntimeOwner( + "import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'\n" + ) + ).toBe(true) + expect( + importsDeprecatedRuntimeOwner( + "import {\n foo,\n resolveRuntimeOwnerForWorktree\n} from './worktree-runtime-owner'\n" + ) + ).toBe(true) + }) + + it('finds an aliased import, a re-export and a dynamic import', () => { + expect( + importsDeprecatedRuntimeOwner( + "import { getRuntimeEnvironmentIdForWorktree as envFor } from '@/lib/worktree-runtime-owner'" + ) + ).toBe(true) + expect( + importsDeprecatedRuntimeOwner( + "export { resolveRuntimeOwnerForWorktree } from './worktree-runtime-owner'" + ) + ).toBe(true) + expect( + importsDeprecatedRuntimeOwner( + "const { getRuntimeEnvironmentIdForWorktree } = await import('@/lib/worktree-runtime-owner')" + ) + ).toBe(true) + }) + + it('finds a use through a namespace import or a module-valued dynamic import', () => { + expect( + importsDeprecatedRuntimeOwner( + "import * as owner from '@/lib/worktree-runtime-owner'\nowner.getRuntimeEnvironmentIdForWorktree(s, id)" + ) + ).toBe(true) + expect( + importsDeprecatedRuntimeOwner( + "const mod = await import('@/lib/worktree-runtime-owner')\nmod.resolveRuntimeOwnerForWorktree(s, id)" + ) + ).toBe(true) + }) + + it('finds destructuring in a promise-based dynamic import', () => { + expect( + importsDeprecatedRuntimeOwner( + "import('@/lib/worktree-runtime-owner').then(({ resolveRuntimeOwnerForWorktree }) => resolveRuntimeOwnerForWorktree(s, id))" + ) + ).toBe(true) + }) + + it('reads through comments inside an import list, and ignores a commented-out import', () => { + expect( + importsDeprecatedRuntimeOwner( + "import { /* legacy */ getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'" + ) + ).toBe(true) + expect( + importsDeprecatedRuntimeOwner( + "import {\n a, // keep\n resolveRuntimeOwnerForWorktree\n} from './worktree-runtime-owner'" + ) + ).toBe(true) + expect( + importsDeprecatedRuntimeOwner( + "// import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'\n/* owner.resolveRuntimeOwnerForWorktree() */" + ) + ).toBe(false) + }) + + it('ignores other names from the module, and same-named functions from other modules', () => { + expect( + importsDeprecatedRuntimeOwner( + "import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'" + ) + ).toBe(false) + expect( + importsDeprecatedRuntimeOwner( + "import { getRuntimeEnvironmentIdForWorktree } from '@/lib/other-owner'" + ) + ).toBe(false) + }) +}) + +describe('isScannedPath', () => { + it('scans renderer sources only, not tests or the defining module', () => { + expect(isScannedPath('src/renderer/src/store/terminals/terminal-shutdown.ts')).toBe(true) + expect(isScannedPath('src/renderer/src/store/terminals/terminal-shutdown.test.ts')).toBe(false) + expect(isScannedPath('src/renderer/src/store/host-twin.test-fixture.ts')).toBe(false) + expect(isScannedPath('src/renderer/src/lib/worktree-runtime-owner.ts')).toBe(false) + expect(isScannedPath('src/main/runtime/foo.ts')).toBe(false) + }) +}) diff --git a/config/scripts/pr-preflight-gates.test.mjs b/config/scripts/pr-preflight-gates.test.mjs index 248d4d6714a..2836d3ebb3e 100644 --- a/config/scripts/pr-preflight-gates.test.mjs +++ b/config/scripts/pr-preflight-gates.test.mjs @@ -242,6 +242,7 @@ it('pins every foreground and background step to its selected phase', () => { ['Enforce runtime Electron-import ratchet', staticPhase], ['Enforce focus-read ratchet', staticPhase], ['Enforce layout-writer ratchets', staticPhase], + ['Enforce deprecated runtime-owner importer ratchet', staticPhase], ['Check Node runtime pin', staticPhase], ['Decide whether the orcad terminal smoke runs', staticPhase], ['Verify the generated RPC params catalog', staticPhase], diff --git a/package.json b/package.json index 8fab9675480..20362bce974 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "audit:perf": "oxlint --config config/oxlint-performance-audit.json --format json src", "test:perf:contracts": "vitest run --config config/vitest.performance.config.ts", "format": "oxfmt --write .", - "lint": "oxlint && pnpm run audit:anti-slop && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run check:reliability-gates && pnpm run check:dead-classes && pnpm run check:max-lines-ratchet && pnpm run check:ts-nocheck-ratchet && pnpm run check:runtime-electron-ratchet && pnpm run check:focus-read-ratchet && pnpm run check:layout-writer-ratchets && pnpm run check:readme-local-links && pnpm run check:node-runtime-pin && pnpm run verify:rpc-params-catalog && pnpm run verify:acp-protocol && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalogs && pnpm run verify:localization-extraction && pnpm run verify:localization-coverage && pnpm run check:process-host-imports && pnpm run check:main-sync-spawn-ratchet && pnpm run check:compat-boundary-imports", + "lint": "oxlint && pnpm run audit:anti-slop && pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run check:reliability-gates && pnpm run check:dead-classes && pnpm run check:max-lines-ratchet && pnpm run check:ts-nocheck-ratchet && pnpm run check:runtime-electron-ratchet && pnpm run check:focus-read-ratchet && pnpm run check:layout-writer-ratchets && pnpm run check:deprecated-runtime-owner-ratchet && pnpm run check:readme-local-links && pnpm run check:node-runtime-pin && pnpm run verify:rpc-params-catalog && pnpm run verify:acp-protocol && pnpm run verify:bundled-skill-guides && pnpm run verify:skill-bundle-manifest && pnpm run verify:localization-catalogs && pnpm run verify:localization-extraction && pnpm run verify:localization-coverage && pnpm run check:process-host-imports && pnpm run check:main-sync-spawn-ratchet && pnpm run check:compat-boundary-imports", "audit:code-quality": "pnpm run audit:code-quality:native && pnpm run audit:code-quality:type-aware && pnpm run audit:react-doctor", "audit:code-quality:native": "oxlint --config config/oxlint-code-quality-native-plugins.json src config tests mobile --deny-warnings", "audit:code-quality:type-aware": "oxlint --type-aware --config config/oxlint-code-quality-type-aware.json src config tests --deny-warnings", @@ -38,6 +38,7 @@ "check:reliability-gates": "node config/scripts/check-reliability-gates.mjs", "check:max-lines-ratchet": "node config/scripts/check-max-lines-ratchet.mjs", "check:ts-nocheck-ratchet": "node config/scripts/check-ts-nocheck-ratchet.mjs", + "check:deprecated-runtime-owner-ratchet": "node config/scripts/check-deprecated-runtime-owner-ratchet.mjs", "check:runtime-electron-ratchet": "node config/scripts/check-runtime-electron-ratchet.mjs", "check:focus-read-ratchet": "node config/scripts/check-focus-read-ratchet.mjs", "check:layout-writer-ratchets": "node config/scripts/check-layout-writer-ratchets.mjs",