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
This commit is contained in:
Neil
2026-10-17 00:38:41 -07:00
committed by GitHub
parent 83baa08352
commit ee56703d51
6 changed files with 244 additions and 1 deletions
+4
View File
@@ -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
@@ -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
@@ -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]*?\*\/|(?<![:'"`\\])\/\/.*$/gm
export function isScannedPath(rel) {
return (
rel.startsWith(`${SCAN_ROOT}/`) &&
/\.(ts|tsx)$/.test(rel) &&
!/\.(test|spec)(?:-fixture|-support)?\.tsx?$/.test(rel) &&
rel !== DEFINING_FILE
)
}
/** True when uncommented code loads the defining module and names a deprecated resolver. */
export function importsDeprecatedRuntimeOwner(sourceText) {
const code = sourceText.replace(COMMENT, ' ')
return DEFINING_MODULE_SPECIFIER.test(code) && DEPRECATED_NAME.test(code)
}
export function collectDeprecatedRuntimeOwnerImporters(root = process.cwd()) {
const sources = readTrackedSources(root, [SCAN_ROOT], isScannedPath)
return [...sources]
.filter(([, source]) => 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]))
}
@@ -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)
})
})
@@ -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],
+2 -1
View File
@@ -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",