Merge main for freeze capture validation

This commit is contained in:
Brennan Benson
2026-11-12 15:48:38 -08:00
4336 changed files with 49150 additions and 108198 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
/skill-stubs/*.md text eol=lf
/skill-stubs/_shared/*.md text eol=lf
/skills/*/SKILL.md text eol=lf
/src/cli/bundled-skill-guides.ts text eol=lf
/src/cli/skills/bundled-skill-guides.ts text eol=lf
# SVG bytes feed embedded data URLs and asset hashes on every build host.
*.svg text eol=lf
# Compiler input line breaks must not depend on the checkout OS.
+16 -3
View File
@@ -62,11 +62,24 @@ jobs:
current.closed_at === pr.closed_at
}
if (!Number.isSafeInteger(pr.number) || pr.number <= 0 ||
(superseded ? !/^[a-f0-9]{40}$/.test(pr.head?.sha ?? '') :
!Number.isFinite(Date.parse(pr.closed_at)))) {
(superseded && !/^[a-f0-9]{40}$/.test(pr.head?.sha ?? ''))) {
throw new Error('Missing PR identity')
}
if (!await stillCurrent()) return
if (!superseded && !Number.isFinite(Date.parse(pr.closed_at))) {
// A close quickly followed by a reopen can deliver a payload rendered after the reopen.
const { data: current } = await github.rest.pulls.get({
...context.repo, pull_number: pr.number
})
if (current.state !== 'closed' || current.merged_at !== null) {
core.info(`PR #${pr.number} changed after this close event; skipping cancellation`)
return
}
throw new Error('Missing PR identity')
}
if (!await stillCurrent()) {
core.info(`PR #${pr.number} changed after this event; skipping cancellation`)
return
}
for (const [prefix, workflow, suffix = ''] of targets) {
const group = prefix === 'cloud-verify-refs/pull'
? `${prefix}/${pr.number}${suffix}` : `${prefix}-${pr.number}${suffix}`
+6 -5
View File
@@ -48,12 +48,14 @@ on:
- 'src/cli/**'
- 'src/main/computer/**'
- 'src/main/runtime/rpc/dispatcher.ts'
- 'src/main/runtime/rpc/errors.ts'
- 'src/main/runtime/rpc/responses/rpc-response.ts'
- 'src/main/runtime/rpc/methods/computer/computer*.ts'
- 'src/shared/computer-use-*.ts'
- 'tests/e2e/computer-linux.e2e.ts'
- 'tests/e2e/computer-windows.e2e.ts'
- 'tests/e2e/computer-windows-store.e2e.ts'
- 'tests/e2e/computer-windows-background.e2e.ts'
- 'tests/e2e/helpers/windows-semantic-controls-fixture.*'
- 'tests/e2e/helpers/computer-cli-driver.ts'
- 'tests/e2e/helpers/computer-driver.ts'
- 'tests/e2e/vitest.config.ts'
@@ -97,9 +99,8 @@ jobs:
src/main/computer/macos-native/macos-native-provider-client.test.ts
src/main/providers/windows-shell-preflight-runtime.windows.test.ts
src/main/computer/desktop-script/actions/desktop-script-provider-actions.test.ts
src/cli/format.test.ts
src/cli/handlers/computer-action-validation.test.ts
src/cli/handlers/computer-state-formatting.test.ts
src/cli/output/command-result-output.test.ts
src/cli/computer/computer-state-formatting.test.ts
src/cli/index.test.ts
src/cli/runtime/envelope-schema.test.ts
src/shared/remote-runtime/remote-runtime-client.test.ts
@@ -152,4 +153,4 @@ jobs:
- run: pnpm verify:computer-native
- run: pnpm build:cli
- run: pnpm run build:electron-vite:parallel
- run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-store.e2e.ts
- run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-store.e2e.ts tests/e2e/computer-windows-background.e2e.ts
+4 -4
View File
@@ -11,8 +11,8 @@ on:
- 'native/macos-shipit-scheduling/**'
- 'config/scripts/macos-shipit-scheduling*'
- 'config/electron-builder.config.cjs'
- 'src/main/startup/main-process-quit*'
- 'src/main/window/main-window-state-lifecycle*'
- 'src/main/startup/quit/main-process-quit*'
- 'src/main/window/main-window/main-window-state-lifecycle*'
- 'src/main/window/dashboard-popout-window*'
- 'src/packages/process-host/**'
- 'src/main/runtime/agent-session/lifecycle/structured-agent-cli-writers*'
@@ -55,8 +55,8 @@ jobs:
src/main/updater/mac-install/updater.mac-install.test.ts
src/main/updater/updater.headless-serve-install.test.ts
src/main/updater/mac-install/updater-mac-quit-guard.test.ts
src/main/startup/main-process-quit-update-veto.test.ts
src/main/window/main-window-state-lifecycle.test.ts
src/main/startup/quit/main-process-quit-update-veto.test.ts
src/main/window/main-window/main-window-state-lifecycle.test.ts
- name: Prove login-backed terminal writer census on the disposable runner
env:
ORCA_MACOS_LOGIN_CENSUS_CI: '1'
+112 -4
View File
@@ -34,13 +34,19 @@ on:
- 'src/main/runtime/agent-session/agent-session-unsaved-*.ts'
# The phone/host contract program's unmocked desktop sources and its declaration owner's inputs.
- 'src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts'
- 'src/main/github/issue-field-options.ts'
- 'src/main/github/issues/issue-field-options.ts'
- 'config/tsconfig.test.mobile-host-declarations.json'
- 'config/tsconfig.node.json'
- 'src/types/build-constants.d.ts'
- 'src/main/native-chat/agent-session-wire/**'
# Recorded phone transcripts exercise these host decoders and their runtime dependencies.
- 'src/main/native-chat/*transcript*.ts'
- 'src/main/native-chat/transcript-discovery/**'
- 'src/main/native-chat/transcript-filesystem/**'
- 'src/main/native-chat/transcript-decoding/**'
- 'src/main/native-chat/transcript-streaming/**'
- 'src/main/native-chat/transcript-watching/**'
- 'src/main/native-chat/transcript-history/**'
- 'src/main/native-chat/subagent-entry-id-bounds.ts'
- 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-payload-bounds.ts'
- 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-reasoning-row.ts'
@@ -54,7 +60,55 @@ on:
- 'src/main/codex/structured/journal/codex-goal-journal-rows.ts'
- 'src/main/codex/__fixtures__/codex-hook-outcomes.jsonl'
- 'src/main/ai-vault/session-scanner-*.ts'
- 'src/main/ai-vault/ai-vault-scan-cancellation.ts'
- 'src/main/ai-vault/discovery/agent-session-directories.ts'
- 'src/main/ai-vault/discovery/session-file-discovery.ts'
- 'src/main/ai-vault/discovery/session-json-file.ts'
- 'src/main/ai-vault/discovery/session-path-absence.ts'
- 'src/main/ai-vault/discovery/session-scanner-agent-sources.ts'
- 'src/main/ai-vault/discovery/session-scanner-candidates.ts'
- 'src/main/ai-vault/discovery/session-scanner-directory-reader.test.ts'
- 'src/main/ai-vault/discovery/session-scanner-discovery-pipeline.test.ts'
- 'src/main/ai-vault/discovery/session-scanner-discovery.ts'
- 'src/main/ai-vault/discovery/session-scanner-roots.ts'
- 'src/main/ai-vault/discovery/session-scanner-source-discovery.ts'
- 'src/main/ai-vault/parsing/session-json-object.ts'
- 'src/main/ai-vault/parsing/session-record-fields.ts'
- 'src/main/ai-vault/parsing/session-record-time.ts'
- 'src/main/ai-vault/parsing/session-scanner-accumulator.ts'
- 'src/main/ai-vault/parsing/session-scanner-agent-parser.ts'
- 'src/main/ai-vault/parsing/session-scanner-candidate-parse.ts'
- 'src/main/ai-vault/parsing/session-scanner-core-parser-wsl-stall.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-injected-title.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-jsonl-reader.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-jsonl-reader.ts'
- 'src/main/ai-vault/parsing/session-scanner-messages.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-messages.ts'
- 'src/main/ai-vault/parsing/session-scanner-parse-wsl-stall.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-record-budget.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-recoverable-empty.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-text-normalization.ts'
- 'src/main/ai-vault/parsing/session-scanner-token-values.ts'
- 'src/main/ai-vault/parsing/session-scanner-unlimited-dedup.test.ts'
- 'src/main/ai-vault/prompts/session-scanner-first-user-prompt-capture.ts'
- 'src/main/ai-vault/prompts/session-scanner-first-user-prompt.test.ts'
- 'src/main/ai-vault/prompts/session-scanner-first-user-prompt.ts'
- 'src/main/ai-vault/scanner-fixtures/session-scanner-incremental-fixtures.ts'
- 'src/main/ai-vault/scanner-fixtures/session-scanner-prime-agent-fixtures.ts'
- 'src/main/ai-vault/scanner-fixtures/session-scanner-test-fixtures.ts'
- 'src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.ts'
- 'src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-discovery.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-inventory.test.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-inventory.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-only.test.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-walk.test.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-walk.ts'
- 'src/main/ai-vault/scope/session-scanner-scope.test.ts'
- 'src/main/ai-vault/session-scanner.ts'
- 'src/shared/error-message.ts'
- 'src/shared/surrogate-safe-text-slice.ts'
- 'src/shared/trimmed-string.ts'
- 'src/main/ai-vault/scan-lifecycle/ai-vault-scan-cancellation.ts'
- 'src/main/ai-vault/scan-service/session-scanner-service-env.ts'
- 'src/main/ai-vault/scanners/grok/session-scanner-grok-user-text.ts'
- 'src/main/provider-process/provider-json-record.ts'
@@ -122,13 +176,19 @@ on:
- 'src/main/runtime/agent-session/agent-session-unsaved-*.ts'
# The phone/host contract program's unmocked desktop sources and its declaration owner's inputs.
- 'src/main/runtime/integrations/github/runtime-github-repository-query-commands.ts'
- 'src/main/github/issue-field-options.ts'
- 'src/main/github/issues/issue-field-options.ts'
- 'config/tsconfig.test.mobile-host-declarations.json'
- 'config/tsconfig.node.json'
- 'src/types/build-constants.d.ts'
- 'src/main/native-chat/agent-session-wire/**'
# Recorded phone transcripts exercise these host decoders and their runtime dependencies.
- 'src/main/native-chat/*transcript*.ts'
- 'src/main/native-chat/transcript-discovery/**'
- 'src/main/native-chat/transcript-filesystem/**'
- 'src/main/native-chat/transcript-decoding/**'
- 'src/main/native-chat/transcript-streaming/**'
- 'src/main/native-chat/transcript-watching/**'
- 'src/main/native-chat/transcript-history/**'
- 'src/main/native-chat/subagent-entry-id-bounds.ts'
- 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-payload-bounds.ts'
- 'src/main/native-chat/agent-session-journal/item-lifecycle/journal-reasoning-row.ts'
@@ -142,7 +202,55 @@ on:
- 'src/main/codex/structured/journal/codex-goal-journal-rows.ts'
- 'src/main/codex/__fixtures__/codex-hook-outcomes.jsonl'
- 'src/main/ai-vault/session-scanner-*.ts'
- 'src/main/ai-vault/ai-vault-scan-cancellation.ts'
- 'src/main/ai-vault/discovery/agent-session-directories.ts'
- 'src/main/ai-vault/discovery/session-file-discovery.ts'
- 'src/main/ai-vault/discovery/session-json-file.ts'
- 'src/main/ai-vault/discovery/session-path-absence.ts'
- 'src/main/ai-vault/discovery/session-scanner-agent-sources.ts'
- 'src/main/ai-vault/discovery/session-scanner-candidates.ts'
- 'src/main/ai-vault/discovery/session-scanner-directory-reader.test.ts'
- 'src/main/ai-vault/discovery/session-scanner-discovery-pipeline.test.ts'
- 'src/main/ai-vault/discovery/session-scanner-discovery.ts'
- 'src/main/ai-vault/discovery/session-scanner-roots.ts'
- 'src/main/ai-vault/discovery/session-scanner-source-discovery.ts'
- 'src/main/ai-vault/parsing/session-json-object.ts'
- 'src/main/ai-vault/parsing/session-record-fields.ts'
- 'src/main/ai-vault/parsing/session-record-time.ts'
- 'src/main/ai-vault/parsing/session-scanner-accumulator.ts'
- 'src/main/ai-vault/parsing/session-scanner-agent-parser.ts'
- 'src/main/ai-vault/parsing/session-scanner-candidate-parse.ts'
- 'src/main/ai-vault/parsing/session-scanner-core-parser-wsl-stall.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-injected-title.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-jsonl-reader.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-jsonl-reader.ts'
- 'src/main/ai-vault/parsing/session-scanner-messages.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-messages.ts'
- 'src/main/ai-vault/parsing/session-scanner-parse-wsl-stall.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-record-budget.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-recoverable-empty.test.ts'
- 'src/main/ai-vault/parsing/session-scanner-text-normalization.ts'
- 'src/main/ai-vault/parsing/session-scanner-token-values.ts'
- 'src/main/ai-vault/parsing/session-scanner-unlimited-dedup.test.ts'
- 'src/main/ai-vault/prompts/session-scanner-first-user-prompt-capture.ts'
- 'src/main/ai-vault/prompts/session-scanner-first-user-prompt.test.ts'
- 'src/main/ai-vault/prompts/session-scanner-first-user-prompt.ts'
- 'src/main/ai-vault/scanner-fixtures/session-scanner-incremental-fixtures.ts'
- 'src/main/ai-vault/scanner-fixtures/session-scanner-prime-agent-fixtures.ts'
- 'src/main/ai-vault/scanner-fixtures/session-scanner-test-fixtures.ts'
- 'src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.ts'
- 'src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-discovery.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-inventory.test.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-inventory.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-only.test.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-walk.test.ts'
- 'src/main/ai-vault/scope/session-scanner-scope-walk.ts'
- 'src/main/ai-vault/scope/session-scanner-scope.test.ts'
- 'src/main/ai-vault/session-scanner.ts'
- 'src/shared/error-message.ts'
- 'src/shared/surrogate-safe-text-slice.ts'
- 'src/shared/trimmed-string.ts'
- 'src/main/ai-vault/scan-lifecycle/ai-vault-scan-cancellation.ts'
- 'src/main/ai-vault/scan-service/session-scanner-service-env.ts'
- 'src/main/ai-vault/scanners/grok/session-scanner-grok-user-text.ts'
- 'src/main/provider-process/provider-json-record.ts'
+1 -1
View File
@@ -247,7 +247,7 @@ jobs:
run: node "$(if [ -f config/scripts/orcad-distribution/template/build-orcad-template.mjs ]; then printf %s config/scripts/orcad-distribution/template/build-orcad-template.mjs; else printf %s config/scripts/build-orcad-template.mjs; fi)" --targets linux-${{ runner.arch == 'ARM64' && 'arm64' || 'x64' }}-glibc
- name: Verify a structured chat from the desktop server template
if: runner.os == 'Linux'
run: pnpm test:node-server --artifact src/main/orcad/orcad-packaged-structured-chat.integration.test.ts --maxWorkers=2
run: pnpm test:node-server --artifact src/main/orcad/testing/structured-chat/orcad-packaged-structured-chat.integration.test.ts --maxWorkers=2
# Only a Windows runner compiles it; arm64 cross-compiles here.
# Before the Node 18 check below: the build script imports TypeScript, which Node 18 cannot load.
# orcad's instance lock and stop proof read a PID's creation time through it; the x64 binary this
+2 -3
View File
@@ -853,8 +853,8 @@ jobs:
src/main/pty/shell-startup/zsh-startup-hook-user-config-equivalence.live-shell.test.ts \
src/main/runtime/terminal/wait/tui-idle-zsh-preexec-hook-order.live-shell.test.ts \
src/main/runtime/agent-session/environment/structured-session-cli-login-shell.live-shell.test.ts \
src/main/startup/shell-probe-terminal-detach.live-shell.test.ts \
src/main/startup/shell-probe-lifetime.live-shell.test.ts \
src/main/startup/shell-environment/shell-probe-terminal-detach.live-shell.test.ts \
src/main/startup/shell-environment/shell-probe-lifetime.live-shell.test.ts \
src/renderer/src/components/terminal-pane/pty-connection/emulation/fish-color-scheme-child-stdin.node-pty.test.ts \
src/shared/fish-wsl-cwd-report.node-pty.test.ts \
src/main/wsl/filesystem/wsl-sh-cwd-report.node-pty.test.ts \
@@ -1117,7 +1117,6 @@ jobs:
src/main/browser/cookies/browser-google-cookie-clear.electron.test.ts
src/main/browser/guest-lifecycle/popups/presentation/popup-origin-chrome.electron.test.ts
src/main/browser/guest-lifecycle/navigation/browser-link-materialization.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts
src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts
src/main/browser/extensions/api-runtime/browser-extension-api.electron.test.ts
@@ -47,8 +47,8 @@ jobs:
src/main/ipc/preflight-runnable-local-cli.test.ts
src/main/ipc/preflight-wsl-agent-detection.test.ts
src/main/ipc/preflight-windows-path-refresh.repro.test.ts
src/main/startup/hydrate-shell-path.windows.test.ts
src/main/startup/login-shell-environment.test.ts
src/main/startup/shell-environment/hydrate-shell-path.windows.test.ts
src/main/startup/shell-environment/login-shell-environment.test.ts
src/packages/process-host/src/process-tree-termination.test.ts
src/packages/process-host/src/run-process.test.ts
src/packages/process-host/src/run-process-termination-failure.test.ts
+2 -2
View File
@@ -15,8 +15,8 @@ on:
- 'src/main/managed-server/**'
- 'src/main/ssh/migration-source/**'
- 'src/main/persistence/migrating-orcad-catalog/**'
- 'src/main/orcad/orcad-artifact-materializer.ts'
- 'src/main/orcad/orcad-host-unavailable.ts'
- 'src/main/orcad/artifact-slots/orcad-artifact-materializer.ts'
- 'src/main/orcad/diagnostics/orcad-host-unavailable.ts'
- 'src/main/node-runtime/**'
- 'src/main/ssh/remote-exec/**'
- 'src/main/ssh/remote-host/**'
+3 -3
View File
@@ -12,8 +12,8 @@ on:
- 'src/main/managed-server/**'
- 'src/main/ssh/migration-source/**'
- 'src/main/persistence/migrating-orcad-catalog/**'
- 'src/main/orcad/orcad-artifact-materializer.ts'
- 'src/main/orcad/orcad-host-unavailable.ts'
- 'src/main/orcad/artifact-slots/orcad-artifact-materializer.ts'
- 'src/main/orcad/diagnostics/orcad-host-unavailable.ts'
- 'src/main/node-runtime/**'
- 'src/main/ssh/remote-exec/**'
- 'src/main/ssh/remote-host/**'
@@ -38,7 +38,7 @@ on:
- 'config/scripts/native-build/windows/build-windows-cli-launcher.mjs'
- 'config/scripts/native-build/windows/windows-rust-executable-build.mjs'
- 'native/windows-cli-launcher/**'
- 'src/main/orcad/orcad-cli-launcher.ts'
- 'src/main/orcad/serve-startup/orcad-cli-launcher.ts'
- '.github/actions/prepare-windows-cli-launcher/**'
- 'config/patches/node-pty*'
- 'config/patches/@vscode__windows-process-tree*'
+2 -3
View File
@@ -221,8 +221,8 @@ jobs:
pnpm-lock.yaml
cloud/pnpm-lock.yaml
# These two tests import the cloud relay workspace directly. Keeping them in one job
# avoids installing and building the same workspace once per unit-test shard.
# This test imports the cloud relay workspace directly. Its dedicated job avoids
# installing and building the same workspace once per unit-test shard.
- name: Install relay integration dependencies
id: relay_dependencies
if: ${{ !cancelled() && steps.root_dependencies.outcome == 'success' }}
@@ -237,5 +237,4 @@ jobs:
ORCA_BACKGROUND_LAUNCH: '1'
run: >-
pnpm exec vitest run --config config/vitest.config.ts
tests/e2e/relay-region-compatibility.unit.test.ts
tests/e2e/relay-region-correction.unit.test.ts
@@ -7,8 +7,7 @@ on:
- 'src/main/ai-vault/scanners/opencode/**'
- 'src/main/ai-vault/scanners/opencode2/**'
- 'src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime*'
- 'src/main/native-chat/transcript-opencode*'
- 'src/main/native-chat/transcript-zcode*'
- 'src/main/native-chat/transcript-history/opencode/**'
- 'src/shared/native-chat/native-chat-types.ts'
- 'src/shared/native-chat/transcript/native-chat-transcript-projection*'
- '.github/workflows/zcode-transcript-contract.yml'
@@ -41,4 +40,4 @@ jobs:
bun-version-file: config/.bun-version
no-cache: true
- name: Verify SQLite ordering, legacy readers, and host routing
run: pnpm test src/main/native-chat/transcript-zcode.test.ts src/main/native-chat/transcript-opencode-sqlite-query.test.ts src/main/ai-vault/scanners/zcode/session-scanner-zcode.test.ts src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts src/shared/native-chat/transcript/native-chat-transcript-projection.test.ts src/renderer/src/components/native-chat/conversation/projection/structured-agent-session-transcript-order.test.ts
run: pnpm test src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts src/main/ai-vault/scanners/zcode/session-scanner-zcode.test.ts src/main/ai-vault/scanners/opencode/reader-transport/opencode-wsl-runtime-preparation.test.ts src/shared/native-chat/transcript/native-chat-transcript-projection.test.ts src/renderer/src/components/native-chat/conversation/projection/structured-agent-session-transcript-order.test.ts
+1 -1
View File
@@ -308,7 +308,7 @@
"ignorePatterns": [
"src/main/acp/generated/acp-protocol.generated.ts",
"src/shared/rpc-contract/rpc-params-catalog.generated.ts",
"src/cli/bundled-skill-guides.ts",
"src/cli/skills/bundled-skill-guides.ts",
"resources/plugins/declarative/*.mjs",
"**/node_modules",
"**/dist",
+2 -2
View File
@@ -5,8 +5,8 @@ import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs
import { SignJWT, exportJWK, generateKeyPair, importJWK, type JWK } from 'jose'
import nacl from 'tweetnacl'
import WebSocket from 'ws'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/relay-transport'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/transports/relay-transport'
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
@@ -2,8 +2,8 @@
// client wrapper, the echo service, phone sockets and workerd memory sampling.
import { execSync } from 'node:child_process'
import WebSocket from 'ws'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/relay-transport'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/transports/relay-transport'
export const PORT = 8787
export const ORIGIN = `http://127.0.0.1:${PORT}`
@@ -39,7 +39,7 @@ export async function runLifecycleChecks(ctx: Ctx, main: Control, mainAck: Ack):
mute.on('close', (c) => (muteClose = c))
await new Promise((res) => mute.on('open', res))
// Complete the handshake by hand with the shipped proof answerer, then never pong.
const { answerRelayHostChallenge } = await import('../../../../src/main/runtime/mobile-cloud-relay/relay-host-proof')
const { answerRelayHostChallenge } = await import('../../../../src/main/runtime/mobile-cloud-relay/control-channel/relay-host-proof')
mute.send(JSON.stringify({ type: 'host-hello', v: 1, relayHostId: ctx.relayHostId, assignmentEpoch: 7,
hostPublicKeyB64: ctx.keypair.publicKeyB64, appVersion: 'spike' }))
mute.on('message', (raw) => {
@@ -5,8 +5,8 @@ import { readFileSync } from 'node:fs'
import { SignJWT, type KeyLike } from 'jose'
import nacl from 'tweetnacl'
import WebSocket from 'ws'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/relay-transport'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/transports/relay-transport'
export const PORT = 8789
export const REMOTE = process.env.MVP_URL?.replace(/\/$/, '')
@@ -8,8 +8,8 @@ import { join } from 'node:path'
import { SignJWT, type KeyLike } from 'jose'
import nacl from 'tweetnacl'
import WebSocket from 'ws'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/relay-transport'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/transports/relay-transport'
import { RelayPhoneHelloSchema } from '../../../../src/shared/remote-runtime/relay/mobile-relay-phone-protocol'
export const DIRECTOR_APP = process.env.DIRECTOR_APP ?? ''
+2 -2
View File
@@ -15,8 +15,8 @@ import { SignJWT, exportJWK, generateKeyPair, type KeyLike } from 'jose'
import nacl from 'tweetnacl'
import pg from 'pg'
import WebSocket from 'ws'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/relay-transport'
import { RelayControlClient } from '../../../../src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client'
import { CloudRelayTransport } from '../../../../src/main/runtime/rpc/transports/relay-transport'
import { RelayPhoneHelloSchema } from '../../../../src/shared/remote-runtime/relay/mobile-relay-phone-protocol'
const PORT = 8788
@@ -6,7 +6,6 @@ import {
type RelayAssignmentMigration
} from '../assignment-store.js'
import { openRelayDatabase, type RelayDatabase } from '../../database/database.js'
import { readRegisteredMigrationInventory } from './registered-migration-inventory.js'
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
const describePostgres = databaseUrl ? describe : describe.skip
@@ -245,39 +244,6 @@ describePostgres('PostgreSQL migration recovery', () => {
)
}
it('reaps an abandoned registered migration onto its healthy target', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advance(
ASSIGNMENT_LIMITS.migrationLeaseMs + STRANDED_MIGRATION_ABANDON_MS + 1
)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
expect(
await database.query(
`SELECT assignment.cell_id, assignment.assignment_epoch,
migration.completed_at, migration.aborted_at
FROM relay_assignments assignment
JOIN relay_assignment_migrations migration
ON migration.user_id = assignment.user_id
AND migration.relay_host_id = assignment.relay_host_id
AND migration.assignment_epoch = assignment.assignment_epoch
WHERE assignment.user_id = ? AND assignment.relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
).toEqual([
{
cell_id: input.cells.failed.id,
assignment_epoch: '2',
completed_at: String(
100 + ASSIGNMENT_LIMITS.migrationLeaseMs + STRANDED_MIGRATION_ABANDON_MS + 1
),
aborted_at: null
}
])
})
it('reaps immediately after the retired source has a durable completed fence', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
@@ -309,171 +275,6 @@ describePostgres('PostgreSQL migration recovery', () => {
])
})
it('reaps immediately after the retired source has an adopted legacy fence', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await input.store.adoptLegacyCellFence(
input.cells.source.id,
input.cellIncarnation
)
await input.store.commitLegacyCellFenceAdoption(
input.cells.source.id,
input.cellIncarnation
)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
expect(
(
await readRegisteredMigrationInventory(
database,
100 + 45_001 + ASSIGNMENT_LIMITS.migrationLeaseMs + 1
)
).abandoned
).toBe(1)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
expect(
await database.query(
`SELECT assignment.cell_id, assignment.assignment_epoch,
migration.completed_at, migration.aborted_at
FROM relay_assignments assignment
JOIN relay_assignment_migrations migration
ON migration.user_id = assignment.user_id
AND migration.relay_host_id = assignment.relay_host_id
AND migration.assignment_epoch = assignment.assignment_epoch
WHERE assignment.user_id = ? AND assignment.relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
).toEqual([
{
cell_id: input.cells.failed.id,
assignment_epoch: '2',
completed_at: String(100 + 45_001 + ASSIGNMENT_LIMITS.migrationLeaseMs + 1),
aborted_at: null
}
])
})
it('does not reap after temporary legacy adoption without durable commit', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await input.store.adoptLegacyCellFence(
input.cells.source.id,
input.cellIncarnation
)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
expect(
(
await readRegisteredMigrationInventory(
database,
100 + 45_001 + ASSIGNMENT_LIMITS.migrationLeaseMs + 1
)
).abandoned
).toBe(0)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(0)
input.advance(STRANDED_MIGRATION_ABANDON_MS)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('invalidates an adopted legacy fence when the source heartbeats', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await input.store.adoptLegacyCellFence(
input.cells.source.id,
input.cellIncarnation
)
await input.store.commitLegacyCellFenceAdoption(
input.cells.source.id,
input.cellIncarnation
)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
await input.heartbeat(input.cells.source)
expect(
await database.query(
`SELECT cell_id FROM relay_cell_legacy_fence_adoptions WHERE cell_id = ?`,
[input.cells.source.id]
)
).toEqual([])
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(0)
input.advance(STRANDED_MIGRATION_ABANDON_MS)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('invalidates an adopted legacy fence when the source restarts', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await input.store.adoptLegacyCellFence(
input.cells.source.id,
input.cellIncarnation
)
await input.store.commitLegacyCellFenceAdoption(
input.cells.source.id,
input.cellIncarnation
)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
await input.store.recordCellHeartbeat({
cellId: input.cells.source.id,
cellUrl: input.cells.source.url,
cellIncarnation: '99999999-9999-4999-8999-999999999999',
startedAt: 51,
ready: true,
observedRequests: 0
})
expect(
await database.query(
`SELECT cell_id FROM relay_cell_legacy_fence_adoptions WHERE cell_id = ?`,
[input.cells.source.id]
)
).toEqual([])
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(0)
input.advance(STRANDED_MIGRATION_ABANDON_MS)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('serializes legacy adoption with a returning source heartbeat', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await input.store.adoptLegacyCellFence(
input.cells.source.id,
input.cellIncarnation
)
const [commit, heartbeatResult] = await Promise.allSettled([
input.store.commitLegacyCellFenceAdoption(
input.cells.source.id,
input.cellIncarnation
),
input.heartbeat(input.cells.source)
])
expect(heartbeatResult.status).toBe('fulfilled')
if (commit.status === 'rejected') {
expect(commit.reason).toMatchObject({ message: 'legacy_cell_fence_not_active' })
}
expect(
await database.query(
`SELECT cell_id FROM relay_cell_legacy_fence_adoptions WHERE cell_id = ?`,
[input.cells.source.id]
)
).toEqual([])
input.advance(
ASSIGNMENT_LIMITS.migrationLeaseMs + STRANDED_MIGRATION_ABANDON_MS + 1
)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('restores the 24-hour guard when the fenced source heartbeats again', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
@@ -495,83 +296,6 @@ describePostgres('PostgreSQL migration recovery', () => {
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('restores the 24-hour guard when the fenced source restarts', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await completeSourceFence(input)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
await input.store.recordCellHeartbeat({
cellId: input.cells.source.id,
cellUrl: input.cells.source.url,
cellIncarnation: '99999999-9999-4999-8999-999999999999',
startedAt: 51,
ready: true,
observedRequests: 0
})
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(0)
expect(
await database.query(
`SELECT completed_at, aborted_at FROM relay_assignment_migrations
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
).toEqual([{ completed_at: null, aborted_at: null }])
input.advance(STRANDED_MIGRATION_ABANDON_MS)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('serializes fenced cleanup with a returning source heartbeat', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advancePastHeartbeat()
await completeSourceFence(input)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
const [cleanup, heartbeatResult] = await Promise.allSettled([
input.store.abortExpiredEvacuations(),
input.heartbeat(input.cells.source)
])
expect(cleanup.status).toBe('fulfilled')
expect(heartbeatResult.status).toBe('fulfilled')
if (cleanup.status !== 'fulfilled') throw cleanup.reason
if (heartbeatResult.status !== 'fulfilled') throw heartbeatResult.reason
expect([0, 1]).toContain(cleanup.value)
const migrations = await database.query(
`SELECT completed_at, aborted_at FROM relay_assignment_migrations
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
if (cleanup.value === 1) {
expect(migrations[0]?.completed_at).not.toBeNull()
} else {
expect(migrations).toEqual([{ completed_at: null, aborted_at: null }])
input.advance(STRANDED_MIGRATION_ABANDON_MS)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
}
})
it('keeps a freshly retired source protected without a durable fence', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
input.advance(ASSIGNMENT_LIMITS.migrationLeaseMs + 1)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(0)
expect(
await database.query(
`SELECT completed_at, aborted_at FROM relay_assignment_migrations
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
).toEqual([{ completed_at: null, aborted_at: null }])
input.advance(STRANDED_MIGRATION_ABANDON_MS)
await expect(input.store.abortExpiredEvacuations()).resolves.toBe(1)
})
it('rolls an abandoned disabled target back to its source', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
@@ -606,96 +330,6 @@ describePostgres('PostgreSQL migration recovery', () => {
])
})
it('serializes cleanup against supersession without reporting a false target', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await input.store.releaseActivity(input.identity, input.targetControlId)
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advance(
ASSIGNMENT_LIMITS.migrationLeaseMs + STRANDED_MIGRATION_ABANDON_MS + 1
)
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.store.attestCellFence(input.cells.failed.id, input.cellIncarnation)
const [cleanup, supersession] = await Promise.allSettled([
input.store.abortExpiredEvacuations(),
input.store.supersedeRegisteredCellEvacuations(
input.cells.source.id,
input.cells.failed.id,
input.cells.replacement.id,
100
)
])
expect(cleanup.status).toBe('fulfilled')
if (cleanup.status !== 'fulfilled') throw cleanup.reason
if (supersession.status === 'fulfilled') {
expect([0, 1]).toContain(supersession.value)
const assignment = await database.query(
`SELECT cell_id FROM relay_assignments WHERE user_id = ?`,
[input.identity.userId]
)
expect(assignment).toEqual([{
cell_id:
supersession.value === 1
? input.cells.replacement.id
: input.cells.source.id
}])
if (supersession.value === 0) expect(cleanup.value).toBeGreaterThanOrEqual(1)
} else {
expect(cleanup.value).toBeGreaterThanOrEqual(1)
expect(supersession.reason).toMatchObject({ message: 'migration_already_superseded' })
expect(
await database.query(
`SELECT cell_id FROM relay_assignments WHERE user_id = ?`,
[input.identity.userId]
)
).toEqual([{ cell_id: input.cells.source.id }])
}
})
it('completes a registered migration from a dead source idempotently', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.failed)
await input.store.attestCellFence(
input.cells.source.id,
input.cellIncarnation
)
const completion = {
assignmentEpoch: input.migration.assignmentEpoch,
sourceCellId: input.cells.source.id,
targetCellId: input.cells.failed.id
}
await expect(
input.store.cellEvacuationStatus(
input.cells.source.id,
input.cells.failed.id,
true
)
).resolves.toMatchObject({ inProgress: 0, completed: 1 })
await expect(
input.store.completeEvacuationFromDeadSource(input.identity, completion)
).resolves.toMatchObject({ changed: false })
expect(await reservations(input.cells)).toEqual({ source: 0, failed: 1, replacement: 0 })
})
it('requires fence evidence for dead-source completion', async () => {
const input = await fixture()
await input.store.releaseActivity(input.identity, input.sourceControlId)
await expect(
input.store.completeEvacuationFromDeadSource(input.identity, {
assignmentEpoch: input.migration.assignmentEpoch,
sourceCellId: input.cells.source.id,
targetCellId: input.cells.failed.id
})
).rejects.toThrow('cell_fence_attestation_missing')
expect(await reservations(input.cells)).toEqual({ source: 0, failed: 2, replacement: 0 })
})
it('supersedes a registered migration with exact accounting and idempotency', async () => {
const input = await fixture()
await database.query(
@@ -760,359 +394,6 @@ describePostgres('PostgreSQL migration recovery', () => {
])
})
it('reconciles durable cell accounting before aggregate supersession', async () => {
const input = await fixture()
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.store.attestCellFence(
input.cells.failed.id,
input.cellIncarnation
)
await database.query(
`UPDATE relay_cells
SET reserved_requests = CASE WHEN cell_id = ? THEN 1 ELSE 0 END
WHERE cell_id IN (?, ?)`,
[input.cells.replacement.id, input.cells.source.id, input.cells.replacement.id]
)
await expect(
input.store.supersedeRegisteredCellEvacuations(
input.cells.source.id,
input.cells.failed.id,
input.cells.replacement.id,
100
)
).resolves.toBe(1)
expect(await reservations(input.cells)).toEqual({
source: 1,
failed: 0,
replacement: 2
})
})
it('rebuilds an expired registered migration lease before supersession', async () => {
const input = await fixture()
await pinMigration(input, input.migration.assignmentEpoch)
await clearActivities(input)
await database.query(
`UPDATE relay_assignment_migrations SET expires_at = 0
WHERE user_id = ? AND relay_host_id = ? AND assignment_epoch = ?`,
[input.identity.userId, input.identity.relayHostId, input.migration.assignmentEpoch]
)
await fenceFailedCell(input)
await expect(supersedeAggregate(input)).resolves.toBe(1)
expect(await reservations(input.cells)).toEqual({
source: 0,
failed: 0,
replacement: 1
})
expect(
await database.query(
`SELECT assignment_epoch, source_request_units, target_reserved_units, aborted_at
FROM relay_assignment_migrations WHERE user_id = ? ORDER BY assignment_epoch`,
[input.identity.userId]
)
).toEqual([
{
assignment_epoch: '2',
source_request_units: '1',
target_reserved_units: '2',
aborted_at: '45101'
},
{
assignment_epoch: '3',
source_request_units: '0',
target_reserved_units: '1',
aborted_at: null
}
])
})
it('retires an obsolete row without discarding replacement activity', async () => {
const input = await fixture()
await pinMigration(input, input.migration.assignmentEpoch)
await fenceFailedCell(input)
await input.store.supersedeRegisteredEvacuation(input.identity, {
assignmentEpoch: input.migration.assignmentEpoch,
sourceCellId: input.cells.source.id,
currentTargetCellId: input.cells.failed.id,
replacementTargetCellId: input.cells.replacement.id
})
await database.query(
`UPDATE relay_assignment_migrations SET aborted_at = NULL
WHERE user_id = ? AND relay_host_id = ? AND assignment_epoch = ?`,
[input.identity.userId, input.identity.relayHostId, input.migration.assignmentEpoch]
)
const activityBefore = await assignmentActivities(input)
await expect(supersedeAggregate(input)).resolves.toBe(1)
expect(await assignmentActivities(input)).toEqual(activityBefore)
expect(await reservations(input.cells)).toEqual({
source: 1,
failed: 0,
replacement: 2
})
expect(
await database.query(
`SELECT assignment_epoch, aborted_at FROM relay_assignment_migrations
WHERE user_id = ? ORDER BY assignment_epoch`,
[input.identity.userId]
)
).toEqual([
{ assignment_epoch: '2', aborted_at: '45101' },
{ assignment_epoch: '3', aborted_at: null }
])
})
it('anchors a newer dormant failed-cell epoch and preserves drain evidence', async () => {
const input = await fixture()
const drainAttemptId = await pinMigration(input, input.migration.assignmentEpoch)
await clearActivities(input)
await database.query(
`UPDATE relay_assignments SET assignment_epoch = 4
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
await database.query(
`UPDATE relay_assignment_migrations SET expires_at = 0
WHERE user_id = ? AND relay_host_id = ? AND assignment_epoch = ?`,
[input.identity.userId, input.identity.relayHostId, input.migration.assignmentEpoch]
)
await fenceFailedCell(input)
await expect(supersedeAggregate(input)).resolves.toBe(1)
expect(await reservations(input.cells)).toEqual({
source: 0,
failed: 0,
replacement: 1
})
expect(
await database.query(
`SELECT assignment_epoch, source_request_units, target_reserved_units, aborted_at
FROM relay_assignment_migrations WHERE user_id = ? ORDER BY assignment_epoch`,
[input.identity.userId]
)
).toEqual([
{
assignment_epoch: '2',
source_request_units: '1',
target_reserved_units: '2',
aborted_at: '45101'
},
{
assignment_epoch: '4',
source_request_units: '0',
target_reserved_units: '1',
aborted_at: '45101'
},
{
assignment_epoch: '5',
source_request_units: '0',
target_reserved_units: '1',
aborted_at: null
}
])
expect(
await database.query(
`SELECT assignment_epoch, drain_attempt_id, source_request_units,
target_reserved_units
FROM relay_post_drain_migration_pins
WHERE user_id = ? ORDER BY assignment_epoch`,
[input.identity.userId]
)
).toEqual([
{
assignment_epoch: '2',
drain_attempt_id: drainAttemptId,
source_request_units: '1',
target_reserved_units: '2'
},
{
assignment_epoch: '4',
drain_attempt_id: drainAttemptId,
source_request_units: '0',
target_reserved_units: '1'
},
{
assignment_epoch: '5',
drain_attempt_id: drainAttemptId,
source_request_units: '0',
target_reserved_units: '1'
}
])
})
it('uses an existing current-epoch migration once when retiring an older row', async () => {
const input = await fixture()
await pinMigration(input, input.migration.assignmentEpoch)
await clearActivities(input)
await database.query(
`UPDATE relay_assignments SET assignment_epoch = 4
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
await database.query(
`INSERT INTO relay_assignment_migrations
(user_id, relay_host_id, source_cell_id, target_cell_id,
previous_epoch, assignment_epoch, source_request_units,
target_reserved_units, expires_at, target_registered_at,
completed_at, aborted_at, created_at, updated_at)
VALUES (?, ?, ?, ?, 2, 4, 0, 1, 0, 100, NULL, NULL, 100, 100)`,
[
input.identity.userId,
input.identity.relayHostId,
input.cells.source.id,
input.cells.failed.id
]
)
await database.query(
`INSERT INTO relay_assignment_migration_incarnations
(user_id, relay_host_id, assignment_epoch, source_cell_incarnation,
target_cell_incarnation)
VALUES (?, ?, 4, ?, ?)`,
[
input.identity.userId,
input.identity.relayHostId,
input.cellIncarnation,
input.cellIncarnation
]
)
await pinMigration(input, 4)
await fenceFailedCell(input)
await expect(supersedeAggregate(input)).resolves.toBe(1)
expect(
await database.query(
`SELECT assignment_epoch, aborted_at FROM relay_assignment_migrations
WHERE user_id = ? ORDER BY assignment_epoch`,
[input.identity.userId]
)
).toEqual([
{ assignment_epoch: '2', aborted_at: '45101' },
{ assignment_epoch: '4', aborted_at: '45101' },
{ assignment_epoch: '5', aborted_at: null }
])
expect(await reservations(input.cells)).toEqual({
source: 0,
failed: 0,
replacement: 1
})
})
it('rejects a newer failed-cell epoch with ambiguous activity', async () => {
const input = await fixture()
await database.query(
`UPDATE relay_assignments SET assignment_epoch = 4
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
await fenceFailedCell(input)
await expect(supersedeAggregate(input)).rejects.toThrow(
'migration_activity_topology_mismatch'
)
expect(
await database.query(
`SELECT assignment_epoch, aborted_at FROM relay_assignment_migrations
WHERE user_id = ?`,
[input.identity.userId]
)
).toEqual([{ assignment_epoch: '2', aborted_at: null }])
expect(await reservations(input.cells)).toEqual({
source: 1,
failed: 2,
replacement: 0
})
})
it('leaves lease repair retryable when supersession later fails', async () => {
const input = await fixture(1)
await database.query(
`DELETE FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ? AND activity_kind = 'migration'`,
[input.identity.userId, input.identity.relayHostId]
)
await database.query(
`UPDATE relay_assignments SET migration_leases = 0
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
await database.query(
`UPDATE relay_cells SET reserved_requests = reserved_requests - 1
WHERE cell_id = ?`,
[input.cells.failed.id]
)
await database.query(
`UPDATE relay_assignment_migrations SET expires_at = 0
WHERE user_id = ? AND relay_host_id = ? AND assignment_epoch = ?`,
[input.identity.userId, input.identity.relayHostId, input.migration.assignmentEpoch]
)
await fenceFailedCell(input)
await expect(supersedeAggregate(input)).rejects.toThrow(
'relay_capacity_exhausted'
)
expect(
await database.query(
`SELECT activity_id FROM relay_assignment_activity_leases
WHERE user_id = ? AND activity_kind = 'migration'`,
[input.identity.userId]
)
).toEqual([{ activity_id: 'migration:2' }])
expect(
await database.query(
`SELECT aborted_at FROM relay_assignment_migrations
WHERE user_id = ? AND assignment_epoch = 2`,
[input.identity.userId]
)
).toEqual([{ aborted_at: null }])
await database.query(
`UPDATE relay_cells SET capacity_requests = 2 WHERE cell_id = ?`,
[input.cells.replacement.id]
)
await expect(supersedeAggregate(input)).resolves.toBe(1)
expect(await reservations(input.cells)).toEqual({
source: 1,
failed: 0,
replacement: 2
})
})
it('serializes concurrent supersession retries without duplicating capacity', async () => {
const input = await fixture()
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.store.attestCellFence(
input.cells.failed.id,
input.cellIncarnation
)
const supersession = {
assignmentEpoch: input.migration.assignmentEpoch,
sourceCellId: input.cells.source.id,
currentTargetCellId: input.cells.failed.id,
replacementTargetCellId: input.cells.replacement.id
}
const results = await Promise.all([
input.store.supersedeRegisteredEvacuation(input.identity, supersession),
input.store.supersedeRegisteredEvacuation(input.identity, supersession)
])
expect(results[0]).toEqual(results[1])
expect(await reservations(input.cells)).toEqual({ source: 1, failed: 0, replacement: 2 })
expect(
await database.query(
`SELECT COUNT(*) AS count FROM relay_assignment_migrations WHERE user_id = ?`,
[input.identity.userId]
)
).toEqual([{ count: '2' }])
})
it('fails a competing aggregate supersession instead of reporting the wrong target', async () => {
const input = await fixture()
const alternate = {
@@ -1205,153 +486,6 @@ describePostgres('PostgreSQL migration recovery', () => {
).toEqual([{ assignment_epoch: '2', aborted_at: null }])
})
it('rolls back supersession when migration request-unit shape drifted', async () => {
const input = await fixture()
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.store.attestCellFence(input.cells.failed.id, input.cellIncarnation)
await database.query(
`UPDATE relay_assignment_activity_leases
SET request_units = request_units + 1
WHERE user_id = ? AND activity_kind = 'migration'`,
[input.identity.userId]
)
await database.query(
`UPDATE relay_cells SET reserved_requests = reserved_requests + 1 WHERE cell_id = ?`,
[input.cells.failed.id]
)
await expect(
input.store.supersedeRegisteredEvacuation(input.identity, {
assignmentEpoch: input.migration.assignmentEpoch,
sourceCellId: input.cells.source.id,
currentTargetCellId: input.cells.failed.id,
replacementTargetCellId: input.cells.replacement.id
})
).rejects.toThrow('migration_activity_lease_shape_mismatch')
expect(await reservations(input.cells)).toEqual({ source: 1, failed: 3, replacement: 0 })
expect(
await database.query(
`SELECT cell_id, assignment_epoch FROM relay_assignments WHERE user_id = ?`,
[input.identity.userId]
)
).toEqual([{ cell_id: input.cells.failed.id, assignment_epoch: '2' }])
})
it('rolls back supersession when locked cell reservation accounting drifted', async () => {
const input = await fixture()
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.store.attestCellFence(input.cells.failed.id, input.cellIncarnation)
await database.query(
`UPDATE relay_cells SET reserved_requests = 1 WHERE cell_id = ?`,
[input.cells.replacement.id]
)
await expect(
input.store.supersedeRegisteredEvacuation(input.identity, {
assignmentEpoch: input.migration.assignmentEpoch,
sourceCellId: input.cells.source.id,
currentTargetCellId: input.cells.failed.id,
replacementTargetCellId: input.cells.replacement.id
})
).rejects.toThrow('migration_cell_reservation_accounting_mismatch')
expect(await reservations(input.cells)).toEqual({ source: 1, failed: 2, replacement: 1 })
expect(
await database.query(
`SELECT assignment_epoch, aborted_at FROM relay_assignment_migrations
WHERE user_id = ?`,
[input.identity.userId]
)
).toEqual([{ assignment_epoch: '2', aborted_at: null }])
})
async function fenceFailedCell(input: RecoveryFixture): Promise<void> {
await input.store.setCellEnabled(input.cells.failed.id, false)
input.advancePastHeartbeat()
await input.heartbeat(input.cells.source)
await input.heartbeat(input.cells.replacement)
await input.store.attestCellFence(
input.cells.failed.id,
input.cellIncarnation
)
}
async function supersedeAggregate(input: RecoveryFixture): Promise<number> {
return await input.store.supersedeRegisteredCellEvacuations(
input.cells.source.id,
input.cells.failed.id,
input.cells.replacement.id,
100
)
}
async function clearActivities(input: RecoveryFixture): Promise<void> {
await database.query(
`DELETE FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
await database.query(
`UPDATE relay_assignments SET reserved_controls = 0, reserved_splices = 0,
reserved_invites = 0, pending_installs = 0, pending_confirmations = 0,
migration_leases = 0
WHERE user_id = ? AND relay_host_id = ?`,
[input.identity.userId, input.identity.relayHostId]
)
await database.query(
`UPDATE relay_cells SET reserved_requests = 0
WHERE cell_id IN (?, ?, ?)`,
[input.cells.source.id, input.cells.failed.id, input.cells.replacement.id]
)
}
async function pinMigration(
input: RecoveryFixture,
assignmentEpoch: number
): Promise<string> {
const drainAttemptId = `${input.identity.userId}-drain-${assignmentEpoch}`
await database.query(
`INSERT INTO relay_post_drain_migration_pins
(user_id, relay_host_id, assignment_epoch, drain_attempt_id,
source_cell_id, source_cell_incarnation, target_cell_id,
target_cell_incarnation, source_request_units, target_reserved_units,
pinned_at)
SELECT migration.user_id, migration.relay_host_id,
migration.assignment_epoch, ?, migration.source_cell_id,
incarnation.source_cell_incarnation, migration.target_cell_id,
incarnation.target_cell_incarnation, migration.source_request_units,
migration.target_reserved_units, 100
FROM relay_assignment_migrations migration
JOIN relay_assignment_migration_incarnations incarnation
ON incarnation.user_id = migration.user_id
AND incarnation.relay_host_id = migration.relay_host_id
AND incarnation.assignment_epoch = migration.assignment_epoch
WHERE migration.user_id = ? AND migration.relay_host_id = ?
AND migration.assignment_epoch = ?`,
[
drainAttemptId,
input.identity.userId,
input.identity.relayHostId,
assignmentEpoch
]
)
return drainAttemptId
}
async function assignmentActivities(input: RecoveryFixture): Promise<unknown[]> {
return await database.query(
`SELECT activity_id, activity_kind, cell_id, request_units
FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ? ORDER BY activity_id`,
[input.identity.userId, input.identity.relayHostId]
)
}
async function reservations(cells: RecoveryFixture['cells']): Promise<Record<string, number>> {
const rows = await database.query(
`SELECT cell_id, reserved_requests FROM relay_cells
File diff suppressed because it is too large Load Diff
@@ -111,25 +111,6 @@ function seated(epoch: number, joinedAt = 0) {
describe('reserve assignment on a director', () => {
it('reads what each cell acts on: a tripped dead-man is db, a flip back still leasing is reserve', async () => {
const tripped = setup({
feeds: [feed('c1', { admitModeEffective: 'db' }), feed('c2', { admitModeEffective: 'db' })]
})
expect(await tripped.assignment.plan(HOST, { reconnect: false, region: US })).toEqual({ kind: 'database' })
const leasing = setup({ feeds: [feed('c1', { admitMode: 'db', admitModeEffective: 'reserve' }), feed('c2')] })
expect(leasing.directory.admitModeOf('c1')).toBe('reserve')
// Raw db must immediately stop booking/pacing even while effective mode stays reserve during re-registration.
const reregistering = setup({
feeds: [
feed('c1', { admitModeEffective: 'reserve', admitModeRaw: 'db' }),
feed('c2', { admitModeEffective: 'reserve', admitModeRaw: 'db' })
]
})
expect(await reregistering.assignment.plan(HOST, { reconnect: false, region: US })).toEqual({ kind: 'database' })
expect(reregistering.reserved).toEqual([])
expect(reregistering.directory.admitModeOf('c1')).toBe('reserve')
})
it('before every live cell has answered, holds back only hosts that may be on an unheard cell', async () => {
const cells = cellList(['c1', 'c2'])
const onUnheard = { ...HOST, relayHostId: 'qrstuvwxyzabcdef' }
@@ -153,18 +134,6 @@ describe('reserve assignment on a director', () => {
expect((await gated.assignment.plan(onUnheard, { reconnect: true, region: US })).kind).not.toBe('retry')
})
it('answers a reconnect from memory at the same epoch with no booking and no database read', async () => {
const row = vi.fn(async () => null)
const { assignment, reserved } = setup({
feeds: [feed('c1', { full: [seated(7)] }), feed('c2')],
row
})
const plan = await assignment.plan(HOST, { reconnect: true, region: US })
expect(plan).toMatchObject({ kind: 'answer', lane: 'sticky', assignment: { cellId: 'c1', assignmentEpoch: 7 } })
expect(reserved).toEqual([])
expect(row).not.toHaveBeenCalled()
})
it('re-books a host on a cell that restarted since its seat, and only then answers it', async () => {
const { assignment, directory, reserved, now } = setup({
feeds: [feed('c1', { full: [seated(7)] }), feed('c2')]
@@ -198,57 +167,6 @@ describe('reserve assignment on a director', () => {
})
})
it('never sends a drained or demoted host back, and books it elsewhere above every known epoch', async () => {
const { assignment, directory, reserved, now } = setup()
directory.apply(
'c1',
feed('c1', {
full: undefined,
seq: 3,
changes: [
{ seq: 2, kind: 'join', ...HOST, epoch: 7, generation: 1, at: 1 },
{ seq: 3, kind: 'leave', ...HOST, epoch: 7, generation: 1, closeCode: 4503, at: 2 }
]
}),
now.value,
now.value
)
const plan = await assignment.plan(HOST, { reconnect: true, region: US })
expect(plan).toMatchObject({
kind: 'answer',
lane: 'placement',
assignment: { assignmentEpoch: 8 }
})
expect(reserved.length).toBeGreaterThan(0)
expect(reserved.every((call) => !call.sticky && call.epoch === 8)).toBe(true)
})
it('leaves a cell without a feed (c17/c18) on today’s path without starving the reserve pool', async () => {
const cells = cellList(['c1', 'c2', 'c17'])
let rowOnOld = false
const { assignment, directory, reserved, now } = setup({
cells,
feeds: [feed('c1'), feed('c2', { admitMode: 'db' })],
row: async () => (rowOnOld ? { cellId: 'c17', assignmentEpoch: 5 } : null)
})
directory.markNoFeed('c17', now.value)
expect(directory.isComplete()).toBe(true)
// An old image reports no ceiling; counting it as a database cell would zero the share.
expect(await assignment.plan(HOST, { reconnect: false, region: US })).toMatchObject({
kind: 'answer',
lane: 'placement',
assignment: { cellId: 'c1' }
})
expect(reserved.map((call) => call.cellId)).not.toContain('c17')
rowOnOld = true
expect(
await assignment.plan({ ...HOST, relayHostId: 'qrstuvwxyzabcdef' }, { reconnect: true, region: US })
).toMatchObject({ kind: 'database', epochFloor: 5 })
directory.markNoFeed('c1', now.value)
expect(directory.cellState('c1')?.ceiling).toBeUndefined()
expect(assignment.placementCells().find((cell) => cell.cellId === 'c1')?.general).toBe(false)
})
it('reads the row once for a host the map never saw, and refuses it while the database is down', async () => {
const down = setup({ row: async () => Promise.reject(new Error('timeout')) })
expect(await down.assignment.plan(HOST, { reconnect: false, region: US })).toMatchObject({
@@ -318,95 +236,6 @@ describe('reserve assignment on a director', () => {
}
})
describe('for a share-draw loser', () => {
const ASIA = 'asia-east2' as const
const OTHER = { ...HOST, relayHostId: 'qrstuvwxyzabcdef' }
const setupAsia = (reserveCell = feed('c1'), reserve?: (cellId: string) => ReserveAttempt) =>
setup({
...(reserve ? { reserve } : {}),
cells: [...cellList(['c1', 'c2'], ASIA), ...cellList(['c3'])],
feeds: [
reserveCell,
// Database room far above the reserve cell's, so the 0.25 draw loses.
feed('c2', {
admitMode: 'db',
counts: { seats: 0, controls: 0, bookings: 0, units: 0, ceiling: 5_000 }
}),
feed('c3')
]
})
const regionReserveOf = async (assignment: ReserveAssignment, identity = HOST) => {
const plan = await assignment.plan(identity, { reconnect: false, region: ASIA })
expect(plan).toMatchObject({ kind: 'database', epochFloor: 0 })
return plan.kind === 'database' ? plan.regionReserve : undefined
}
it('offers the reserve cell in its region only, only while it has a seat', async () => {
const { assignment, reserved, directory, now } = setupAsia()
const regionReserve = await regionReserveOf(assignment)
expect(reserved).toEqual([])
await expect(regionReserve?.()).resolves.toMatchObject({ cellId: 'c1', region: ASIA })
expect(reserved.map((call) => call.cellId)).toEqual(['c1'])
const full = feed('c1', { counts: { seats: 500, controls: 500, bookings: 0, units: 500, ceiling: 500 } })
directory.apply('c1', full, now.value, now.value)
expect(await regionReserveOf(assignment, OTHER)).toBeUndefined()
expect(reserved.map((call) => call.cellId)).toEqual(['c1'])
})
it('paces when the reserve cell has seats but no intake budget, rather than give up the region', async () => {
const { assignment, reserved } = setupAsia(feed('c1', { intake: { perSec: 1, burst: 20, tokens: 0 } }))
const regionReserve = await regionReserveOf(assignment)
await expect(regionReserve?.()).rejects.toBeInstanceOf(ReservePaceError)
expect(reserved).toEqual([])
})
it('gives the region up when the reserve cell refuses with a seat still on the map', async () => {
for (const outcome of ['full', 'unreachable']) {
const { assignment, reserved } = setupAsia(feed('c1'), (cellId) =>
cellId === 'c1' ? { outcome } : { outcome: 'ok' }
)
const regionReserve = await regionReserveOf(assignment)
// Null sends the host to the database lane's cross-region fallback, not a 1 s retry.
await expect(regionReserve?.()).resolves.toBeNull()
expect(reserved.map((call) => call.cellId)).toEqual(['c1'])
}
})
})
describe('for a region-correction request', () => {
const request = { reconnect: true, region: US, regionCorrection: true }
it('leaves the answer to the database lane, offering only the in-region reserve fallback and the spill', async () => {
const row = vi.fn(async () => null)
// A memory answer would skip the window exchange; the share draw is not offered either.
const { assignment, reserved } = setup({ feeds: [feed('c1', { full: [seated(7)] }), feed('c2')], row })
const plan = await assignment.plan(HOST, request)
expect(plan).toEqual({
kind: 'database',
regionReserve: expect.any(Function),
reserveSpill: expect.any(Function)
})
expect(reserved).toEqual([])
expect(row).not.toHaveBeenCalled()
await expect(plan.kind === 'database' ? plan.regionReserve?.() : null).resolves.toMatchObject({
assignmentEpoch: 8
})
})
it('keeps today’s path for a host the map never saw, without reading its row, but may spill', async () => {
const row = vi.fn(async () => ({ cellId: 'c1', assignmentEpoch: 3 }))
const { assignment, reserved } = setup({ row })
const plan = await assignment.plan(HOST, request)
expect(plan).toEqual({ kind: 'database', reserveSpill: expect.any(Function) })
expect(row).not.toHaveBeenCalled()
expect(reserved).toEqual([])
await expect(
plan.kind === 'database' ? plan.reserveSpill?.({ skipOwnRegion: false, rowEpoch: 3 }) : null
).resolves.toMatchObject({ assignmentEpoch: 4 })
})
})
describe('after the database lane found no cell anywhere', () => {
const ASIA = 'asia-east2' as const
const paced = { intake: { perSec: 1, burst: 20, tokens: 0 } }
@@ -428,31 +257,6 @@ describe('reserve assignment on a director', () => {
return plan.kind === 'database' ? plan.reserveSpill : undefined
}
it('books its own region first, minting above the row the database lane read', async () => {
const harness = setupSpill({})
const spill = await spillOf(harness)
await expect(spill?.({ skipOwnRegion: false, rowEpoch: 9 })).resolves.toMatchObject({
cellId: 'asia-r',
region: ASIA,
assignmentEpoch: 10
})
expect(harness.reserved).toEqual([{ cellId: 'asia-r', epoch: 10, sticky: false, dryRun: false }])
})
it('books the other region when its own is full, or already tried', async () => {
const full = setupSpill(fullCounts)
await expect((await spillOf(full))?.({ skipOwnRegion: false, rowEpoch: 0 })).resolves.toMatchObject({
cellId: 'us-r',
region: US,
assignmentEpoch: 5
})
const tried = setupSpill({})
await expect((await spillOf(tried))?.({ skipOwnRegion: true, rowEpoch: 0 })).resolves.toMatchObject({
cellId: 'us-r'
})
expect(tried.reserved.map((call) => call.cellId)).toEqual(['us-r'])
})
it('tries the other region after a pace, and answers the pace only if nothing places', async () => {
const abroad = setupSpill(paced)
await expect((await spillOf(abroad))?.({ skipOwnRegion: false, rowEpoch: 0 })).resolves.toMatchObject({
@@ -464,39 +268,10 @@ describe('reserve assignment on a director', () => {
)
})
it('returns null with no reserve seat anywhere, so the refusal stands', async () => {
const harness = setupSpill(fullCounts, fullCounts)
await expect((await spillOf(harness))?.({ skipOwnRegion: false, rowEpoch: 0 })).resolves.toBeNull()
expect(harness.reserved).toEqual([])
})
it('is not offered while the map is incomplete', async () => {
const gated = setup({
cells: [...cellList(['asia-r', 'asia-db'], ASIA), ...cellList(['us-r'])],
feeds: [feed('asia-r'), feed('asia-db', { admitMode: 'db', full: [seated(4)] })],
startedAt: 100_500
})
const plan = await gated.assignment.plan(HOST, { reconnect: true, region: ASIA })
expect(plan).toMatchObject({ kind: 'database' })
expect(plan.kind === 'database' && plan.reserveSpill).toBeUndefined()
const correction = await gated.assignment.plan(HOST, { reconnect: true, region: ASIA, regionCorrection: true })
expect(correction).toEqual({ kind: 'database' })
})
it('answers the shortest pace when every region paces', async () => {
const harness = setupSpill(paced, { intake: { perSec: 2, burst: 20, tokens: 0 } })
const spill = await spillOf(harness)
// Asia refills its token in 4 s, US in 2 s.
await expect(spill?.({ skipOwnRegion: false, rowEpoch: 0 })).rejects.toSatisfy(
(error) => error instanceof ReservePaceError && error.retryAfterSeconds === 2
)
})
})
describe('rehome on reconnect', () => {
const ASIA = 'asia-east2' as const
const OTHER = { ...HOST, relayHostId: 'qrstuvwxyzabcdef' }
const ask = { reconnect: true, region: ASIA, preferred: true }
// 2,000 controls on the US cell refill its move budget once a minute.
const usCounts = { counts: { seats: 1, controls: 2_000, bookings: 0, units: 1, ceiling: 4_000 } }
@@ -546,107 +321,6 @@ describe('reserve assignment on a director', () => {
expect(demoted).toEqual([{ cellId: 'us-r', epoch: 5, joinedAt: 1_000 }])
})
it('answers a region-correction request on the rehome lane', async () => {
const { assignment, warm } = setupRehome()
await warm()
expect(await assignment.plan(HOST, { ...ask, regionCorrection: true })).toMatchObject({
kind: 'answer',
lane: 'rehome'
})
})
it('moves no one into Asia at 98% full, or anywhere under 10% free', async () => {
for (const share of [0.98, 0.91]) {
const { assignment, reserved, warm } = setupRehome({ asia: asiaFull(share) })
await warm()
expect(await assignment.plan(HOST, ask), String(share)).toMatchObject({
kind: 'answer',
lane: 'sticky',
assignment: { cellId: 'us-r' }
})
expect(reserved.filter((call) => call.cellId === 'asia-r')).toEqual([])
}
const { assignment, warm } = setupRehome({ asia: asiaFull(0.9) })
await warm()
expect(await assignment.plan(HOST, ask)).toMatchObject({ lane: 'rehome' })
})
// 10% rehome headroom above the 0% spill line prevents spilled hosts bouncing back.
it('keeps a gap above the spill line: 5% free takes placements, not rehomes', async () => {
const { assignment, warm } = setupRehome({ asia: asiaFull(0.95) })
await warm()
expect(await assignment.plan(HOST, ask)).toMatchObject({ lane: 'sticky', assignment: { cellId: 'us-r' } })
expect(await assignment.plan(OTHER, { reconnect: false, region: ASIA })).toMatchObject({
lane: 'placement',
assignment: { cellId: 'asia-r' }
})
})
it('keeps today’s answer when any rule fails', async () => {
const sticky = { kind: 'answer', lane: 'sticky', assignment: { cellId: 'us-r' } }
const unhinted = setupRehome()
await unhinted.warm()
expect(await unhinted.assignment.plan(HOST, { ...ask, preferred: false })).toMatchObject(sticky)
const off = setupRehome({ percent: 0 })
await off.warm()
expect(await off.assignment.plan(HOST, ask)).toMatchObject(sticky)
const closed = setupRehome({ switchOpen: false })
await closed.warm()
expect(await closed.assignment.plan(HOST, ask)).toMatchObject(sticky)
const cold = setupRehome()
expect(await cold.assignment.plan(HOST, ask)).toMatchObject(sticky)
const sameRegion = setupRehome()
await sameRegion.warm()
expect(await sameRegion.assignment.plan(HOST, { ...ask, region: US })).toMatchObject(sticky)
})
it('never runs for a fresh ask, an unknown host, a database-mode source, or an incomplete map', async () => {
const fresh = setupRehome()
await fresh.warm()
expect(await fresh.assignment.plan(HOST, { ...ask, reconnect: false })).toMatchObject({ lane: 'placement' })
expect(await fresh.assignment.plan(OTHER, ask)).toMatchObject({ kind: 'answer', lane: 'placement' })
const onDatabase = setup({
cells: [...cellList(['us-db']), ...cellList(['asia-r'], ASIA)],
feeds: [feed('us-db', { admitMode: 'db', ...usCounts, full: [seated(5, 1_000)] }), feed('asia-r', asiaFull(0.5))],
rehome: new ReserveRehome({ percent: 100, switchOpen: () => true })
})
expect(await onDatabase.assignment.plan(HOST, ask)).toMatchObject({ kind: 'database' })
const gated = setup({
cells: [...cellList(['us-r']), ...cellList(['asia-r', 'asia-unheard'], ASIA)],
feeds: [feed('us-r', { ...usCounts, full: [seated(5, 1_000)] }), feed('asia-r', asiaFull(0.5))],
startedAt: 100_500,
rehome: new ReserveRehome({ percent: 100, switchOpen: () => true })
})
expect(await gated.assignment.plan(HOST, ask)).toMatchObject({ lane: 'sticky' })
expect(gated.reserved).toEqual([])
})
it('falls through to the sticky answer when the rehome booking is paced', async () => {
const { assignment, reserved, warm } = setupRehome({
reserve: (cellId) => (cellId === 'asia-r' ? { outcome: 'intake' } : { outcome: 'ok' })
})
await warm()
expect(await assignment.plan(HOST, ask)).toMatchObject({ kind: 'answer', lane: 'sticky' })
expect(reserved.map((call) => call.cellId)).toEqual(['asia-r'])
})
it('leaves a host another install keeps dropping', async () => {
const { assignment, directory, now, warm } = setupRehome()
await warm()
directory.apply(
'us-r',
feed('us-r', {
...usCounts,
full: undefined,
seq: 3,
changes: [{ seq: 3, kind: 'leave', ...HOST, epoch: 4, generation: 1, closeCode: 4408, at: now.value - 1 }]
}),
now.value,
now.value
)
expect(await assignment.plan(HOST, ask)).toMatchObject({ lane: 'sticky' })
})
it('falls through to the sticky answer when the booking is refused, and gives the move back', async () => {
let refuse = true
const { assignment, warm } = setupRehome({
@@ -657,52 +331,6 @@ describe('reserve assignment on a director', () => {
refuse = false
expect(await assignment.plan(HOST, ask)).toMatchObject({ lane: 'rehome' })
})
it('moves one host per source budget a minute, and logs moves and skips per region pair', async () => {
const lines: Record<string, unknown>[] = []
const { assignment, now, warm } = setupRehome({
usSeats: [seated(5, 1_000), { ...OTHER, epoch: 5, generation: 1, state: 'active', joinedAt: 1_000 }],
log: (line) => lines.push(JSON.parse(line))
})
await warm()
expect(await assignment.plan(HOST, ask)).toMatchObject({ lane: 'rehome' })
expect(await assignment.plan(OTHER, ask)).toMatchObject({ lane: 'sticky' })
now.value += 60_000
await assignment.plan({ ...HOST, relayHostId: 'flushflushflush1' }, ask)
expect(lines.filter((line) => line.event === 'orca_relay_reserve_rehome')).toEqual([
{ event: 'orca_relay_reserve_rehome', fromRegion: US, toRegion: ASIA, count: 0, skippedByRule: { 'source-budget': 1 } },
{ event: 'orca_relay_reserve_rehome', fromRegion: US, toRegion: ASIA, count: 1, skippedByRule: { 'source-budget': 1 } }
])
})
})
it('paces with Retry-After when every reserve cell is out of budget and no database cell exists', async () => {
const { assignment } = setup({ reserve: () => ({ outcome: 'intake' }) })
await expect(assignment.plan(HOST, { reconnect: false, region: US })).rejects.toMatchObject({
message: 'reserve_cells_paced',
retryAfterSeconds: expect.any(Number)
})
})
it('tells the old reserve seat to go when it books the host elsewhere, naming that seat exactly', async () => {
const { assignment, directory, demoted, now } = setup({
feeds: [feed('c1', { full: [seated(5, 1_000)] }), feed('c2')],
reserve: (cellId) => (cellId === 'c1' ? { outcome: 'full' } : { outcome: 'ok' })
})
// The host left c1 for a reason that moves it: its next placement is fresh, above 5.
directory.apply(
'c1',
feed('c1', {
full: undefined,
seq: 2,
changes: [{ seq: 2, kind: 'drain-only', ...HOST, epoch: 5, generation: 1, at: 2 }]
}),
now.value,
now.value
)
const plan = await assignment.plan(HOST, { reconnect: true, region: US })
expect(plan).toMatchObject({ kind: 'answer', lane: 'placement', assignment: { assignmentEpoch: 6 } })
expect(demoted).toEqual([{ cellId: 'c1', epoch: 5, joinedAt: 1_000 }])
})
// A half-open seat missed by the booking map must be demoted by the feed, before its 75s silence close.
@@ -714,91 +342,4 @@ describe('reserve assignment on a director', () => {
directory.apply('c1', feed('c1', { full: [seated(5, 1_000)] }), now.value, now.value)
expect(demoted).toEqual([{ cellId: 'c1', epoch: 5, joinedAt: 1_000 }])
})
it('answers a reconnect at the newest epoch even when an older epoch joined later', async () => {
const { assignment, reserved } = setup({
feeds: [feed('c1', { full: [seated(6, 1_000)] }), feed('c2', { full: [seated(5, 3_000)] })]
})
expect(await assignment.plan(HOST, { reconnect: true, region: US })).toMatchObject({
kind: 'answer',
lane: 'sticky',
assignment: { cellId: 'c1', assignmentEpoch: 6 }
})
expect(reserved).toEqual([])
})
it('in dry run only asks a cell to check a booking, and never changes the answer', async () => {
const { assignment, reserved } = setup({ mode: 'dry-run' })
const plan = await assignment.plan(HOST, { reconnect: false, region: US })
expect(plan.kind).toBe('database')
expect(plan.kind === 'database' && (await plan.placeFresh?.())).toBeNull()
expect(reserved).toMatchObject([{ dryRun: true }])
})
describe('when a cell answers a demotion', () => {
const outcomes = (lines: string[]) =>
lines
.map((line) => JSON.parse(line))
.filter((entry) => entry.event === 'orca_relay_reserve_demote_outcome')
it('logs the answer by host digest and leaves the seat to the feed', async () => {
const lines: string[] = []
const logged = Promise.withResolvers<void>()
const { assignment, directory } = setup({
feeds: [feed('c1', { full: [seated(10, 1_000)] }), feed('c2')],
demote: () => 'not-seated',
log: (line) => {
lines.push(line)
if (JSON.parse(line).event === 'orca_relay_reserve_demote_outcome') {
logged.resolve()
}
}
})
assignment.demoteSeat({ ...HOST, cellId: 'c1', epoch: 10, joinedAt: 1_000 }, 'reconcile')
await logged.promise
expect(outcomes(lines)).toMatchObject([
{ via: 'reconcile', cellId: 'c1', epoch: 10, outcome: 'not-seated' }
])
expect(outcomes(lines)[0].relayHostIdDigest).toMatch(/^[0-9a-f]{12}$/)
expect(lines.join('\n')).not.toContain(HOST.relayHostId)
expect(directory.seatsOnCell('c1')).toHaveLength(1)
})
})
describe('for a host re-asking off a draining cell', () => {
const events = (lines: string[], event: string) =>
lines.map((line) => JSON.parse(line)).filter((entry) => entry.event === event)
it('marks its pace as the drain’s, on the reserve lane, in the region fallback and in the spill', async () => {
const { assignment } = setup({
feeds: [feed('c1', { draining: true, full: [seated(7)] }), feed('c2')],
reserve: () => ({ outcome: 'intake' })
})
await expect(assignment.plan(HOST, { reconnect: true, region: US })).rejects.toMatchObject({ drained: true })
const plan = await assignment.plan(HOST, { reconnect: true, region: US, regionCorrection: true })
await expect(plan.kind === 'database' ? plan.regionReserve?.() : null).rejects.toMatchObject({ drained: true })
await expect(
plan.kind === 'database' ? plan.reserveSpill?.({ skipOwnRegion: false, rowEpoch: 0 }) : null
).rejects.toMatchObject({ drained: true })
})
it('leaves every other host as today: no drain count, and a plain pace', async () => {
const lines: string[] = []
const { assignment } = setup({ feeds: [feed('c1', { full: [seated(7)] }), feed('c2')], log: (line) => lines.push(line) })
const paced = setup({
feeds: [feed('c1', { full: [seated(7)] }), feed('c2', { draining: true, full: [seated(6)] })],
reserve: () => ({ outcome: 'intake' })
})
expect(await assignment.plan({ ...HOST, relayHostId: 'qrstuvwxyzabcdef' }, { reconnect: false, region: US })).toMatchObject({
kind: 'answer',
lane: 'placement'
})
await expect(
paced.assignment.plan(HOST, { reconnect: false, region: US })
).rejects.toMatchObject({ drained: false })
expect(events(lines, 'orca_relay_reserve_drain_replaced')).toEqual([])
})
})
})
File diff suppressed because it is too large Load Diff
@@ -345,305 +345,6 @@ describePostgres('PostgreSQL transaction recovery', () => {
warn.mockRestore()
}, 10_000)
it('defers assignment around a released-cell activity transaction', async () => {
const now = 1_100_000_000_000
const identity = { userId: 'released-order-user', relayHostId: 'releasedorder001' }
const cellId = 'released-order-cell'
const activityId = 'splice:released-order'
await database.query(
`DELETE FROM relay_assignment_activity_leases WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
await database.query(`DELETE FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`, [
identity.userId,
identity.relayHostId
])
const seedStore = new RelayAssignmentStore(database, () => now)
await seedStore.reconcileCells([
{ id: cellId, url: 'https://released-order.example.com', capacityRequests: 100 }
])
const assignment = await seedStore.assign(identity)
await seedStore.acquireActivity(identity, { activityId, kind: 'splice', cellId })
const assignmentLocked = signal()
const legacyCellLocked = signal()
let gateFirstAssignmentAttempt = true
const directorDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (
gateFirstAssignmentAttempt &&
phase === 'after' &&
sql.includes('FROM relay_assignments')
) {
gateFirstAssignmentAttempt = false
assignmentLocked.resolve()
await legacyCellLocked.promise
}
})
const directorStore = new RelayAssignmentStore(directorDatabase, () => now)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const directorAssign = directorStore.assign(identity)
await assignmentLocked.promise
let legacyAttempts = 0
const legacyRelease = database.transaction(async (transaction) => {
legacyAttempts++
const lease = (
await transaction.queryLocked(
`SELECT * FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ? AND activity_id = ?`,
[identity.userId, identity.relayHostId, activityId]
)
)[0]
if (!lease) return
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cellId])
legacyCellLocked.resolve()
await transaction.query(
`UPDATE relay_cells SET reserved_requests = reserved_requests - 2 WHERE cell_id = ?`,
[cellId]
)
await transaction.query(
`DELETE FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ? AND activity_id = ?`,
[identity.userId, identity.relayHostId, activityId]
)
await transaction.query(
`UPDATE relay_assignments SET reserved_splices = reserved_splices - 1
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
})
await Promise.all([directorAssign, legacyRelease])
expect(directorDatabase.attempts).toBe(2)
expect(legacyAttempts).toBe(1)
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_retry')
)
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_exhausted')
)
await expect(seedStore.resolve(identity)).resolves.toMatchObject({
cellId,
assignmentEpoch: assignment.assignmentEpoch
})
const state = await database.query(
`SELECT assignment.reserved_controls, assignment.reserved_splices,
cell.reserved_requests
FROM relay_assignments assignment
JOIN relay_cells cell ON cell.cell_id = assignment.cell_id
WHERE assignment.user_id = ? AND assignment.relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
expect(state).toEqual([
{ reserved_controls: '1', reserved_splices: '0', reserved_requests: '1' }
])
warn.mockRestore()
}, 15_000)
it('renews an existing control without waiting on a legacy cell lock', async () => {
const now = 1_150_000_000_000
const identity = { userId: 'sustained-lock-user', relayHostId: 'sustainedlock01' }
const cell = {
id: 'sustained-lock-cell',
url: 'https://sustained-lock.example.com',
capacityRequests: 100
}
const seedStore = new RelayAssignmentStore(database, () => now)
await seedStore.reconcileCells([cell])
await seedStore.assign(identity)
const legacyCellLocked = signal()
const releaseLegacyCell = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cell.id])
legacyCellLocked.resolve()
await releaseLegacyCell.promise
})
await legacyCellLocked.promise
let inventoryAttempts = 0
const assignmentDatabase = new TransactionProbeDatabase(
database,
async (phase, sql) => {
if (phase !== 'before' || !sql.includes('FROM relay_cells ORDER BY')) return
inventoryAttempts++
}
)
const assignmentStore = new RelayAssignmentStore(assignmentDatabase, () => now)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await expect(assignmentStore.assign(identity)).resolves.toMatchObject({
cellId: cell.id
})
releaseLegacyCell.resolve()
await expect(legacyTransaction).resolves.toBeUndefined()
expect(inventoryAttempts).toBe(0)
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_retry')
)
warn.mockRestore()
}, 15_000)
it('retries a new sticky control without forming a legacy cell-first deadlock', async () => {
const now = 1_175_000_000_000
const identity = {
userId: 'sticky-cell-contention-user',
relayHostId: 'stickycellwait1'
}
const cell = {
id: 'sticky-cell-contention-cell',
url: 'https://sticky-cell-contention.example.com',
capacityRequests: 100
}
const seedStore = new RelayAssignmentStore(database, () => now)
await seedStore.reconcileCells([cell])
await seedStore.assign(identity)
await seedStore.changeActivity(identity, 'migration', 1)
// Drops the grant's pending lease too: a sticky control the rows do not
// show is what sends the retry through the NOWAIT cell lock.
await seedStore.releaseActivity(identity, 'control-pending:1')
const legacyCellLocked = signal()
const directorAssignmentLocked = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cell.id])
legacyCellLocked.resolve()
await directorAssignmentLocked.promise
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
})
await legacyCellLocked.promise
let signalFirstAttempt = true
const directorLockOrder: string[] = []
const assignmentDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (phase === 'before') {
// Only locked statements reach this hook, so classifying the pin read
// is what proves it stays unlocked: if it ever grows a FOR UPDATE it
// shows up in the order below instead of silently joining the queue.
if (sql.includes('SELECT cell_id FROM relay_assignments')) {
directorLockOrder.push('pin-read')
} else if (sql.includes('FROM relay_assignments WHERE user_id = ?')) {
directorLockOrder.push('assignment')
} else if (sql.includes('FROM relay_assignment_activity_leases')) {
directorLockOrder.push('activity')
} else if (sql.includes('FROM relay_cells ORDER BY')) {
directorLockOrder.push('cell-inventory')
} else if (sql.includes('FROM relay_cells WHERE cell_id IN')) {
directorLockOrder.push('cell-rows')
} else if (sql.includes('FROM relay_cells WHERE cell_id = ?')) {
directorLockOrder.push('cell')
}
}
if (
signalFirstAttempt &&
phase === 'after' &&
sql.includes('FROM relay_assignments WHERE user_id = ?')
) {
signalFirstAttempt = false
directorAssignmentLocked.resolve()
}
})
const store = new RelayAssignmentStore(assignmentDatabase, () => now)
await expect(store.assign(identity)).resolves.toMatchObject({
cellId: cell.id,
assignmentEpoch: 1
})
await expect(legacyTransaction).resolves.toBeUndefined()
expect(assignmentDatabase.attempts).toBe(2)
// The retry still takes a cell row before the assignment row — the order
// that avoids the legacy cycle — but only the pinned row, never the
// inventory.
expect(directorLockOrder).toEqual([
'assignment',
'activity',
'cell',
'cell-rows',
'assignment',
'activity'
])
const state = await database.query(
`SELECT assignment.reserved_controls, assignment.migration_leases,
cell.reserved_requests
FROM relay_assignments assignment
JOIN relay_cells cell ON cell.cell_id = assignment.cell_id
WHERE assignment.user_id = ? AND assignment.relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
expect(state).toEqual([
{ reserved_controls: '1', migration_leases: '1', reserved_requests: '2' }
])
}, 15_000)
it('serializes normalized assignment and activity release without a retry', async () => {
const now = 1_200_000_000_000
const identity = { userId: 'normalized-order-user', relayHostId: 'normalizedorder1' }
const cellId = 'normalized-order-cell'
const activityId = 'splice:normalized-order'
await database.query(
`DELETE FROM relay_assignment_activity_leases WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
await database.query(`DELETE FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`, [
identity.userId,
identity.relayHostId
])
const seedStore = new RelayAssignmentStore(database, () => now)
await seedStore.reconcileCells([
{ id: cellId, url: 'https://normalized-order.example.com', capacityRequests: 100 }
])
await seedStore.assign(identity)
await seedStore.acquireActivity(identity, { activityId, kind: 'splice', cellId })
const assignmentLocked = signal()
const releaseReachedAssignment = signal()
const continueAssignment = signal()
let gateFirstAssignmentAttempt = true
const assignDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (
gateFirstAssignmentAttempt &&
phase === 'after' &&
sql.includes('FROM relay_assignments')
) {
gateFirstAssignmentAttempt = false
assignmentLocked.resolve()
await continueAssignment.promise
}
})
const releaseDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (phase === 'before' && sql.includes('FROM relay_assignments')) {
releaseReachedAssignment.resolve()
}
})
const assignStore = new RelayAssignmentStore(assignDatabase, () => now)
const releaseStore = new RelayAssignmentStore(releaseDatabase, () => now)
const assign = assignStore.assign(identity)
await assignmentLocked.promise
const release = releaseStore.releaseActivity(identity, activityId)
await releaseReachedAssignment.promise
continueAssignment.resolve()
await Promise.all([assign, release])
expect(assignDatabase.attempts).toBe(1)
expect(releaseDatabase.attempts).toBe(1)
const state = await database.query(
`SELECT assignment.reserved_controls, assignment.reserved_splices,
cell.reserved_requests
FROM relay_assignments assignment
JOIN relay_cells cell ON cell.cell_id = assignment.cell_id
WHERE assignment.user_id = ? AND assignment.relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
expect(state).toEqual([
{ reserved_controls: '1', reserved_splices: '0', reserved_requests: '1' }
])
}, 15_000)
it('takes the shared cell lock only for the final atomic activity write', async () => {
const now = 1_250_000_000_000
const cell = {
@@ -734,84 +435,6 @@ describePostgres('PostgreSQL transaction recovery', () => {
])
}, 15_000)
it('reconciles drift under the assignment-first lock order while activity waits', async () => {
const now = 1_300_000_000_000
const cells = [
{ id: 'reconcile-cell-a', url: 'https://reconcile-a.example.com', capacityRequests: 100 },
{ id: 'reconcile-cell-b', url: 'https://reconcile-b.example.com', capacityRequests: 100 }
]
const identities = Array.from({ length: 12 }, (_, index) => ({
userId: `reconcile-user-${index}`,
relayHostId: `reconcilehost${String(index).padStart(4, '0')}`
}))
const seedStore = new RelayAssignmentStore(database, () => now)
await seedStore.reconcileCells(cells)
for (const identity of identities) await seedStore.assign(identity)
await database.query(
`UPDATE relay_assignments SET reserved_controls = 7, reserved_splices = 5
WHERE user_id LIKE 'reconcile-user-%'`
)
await database.query(
`UPDATE relay_cells SET reserved_requests = 42
WHERE cell_id IN (?, ?)`,
['reconcile-cell-a', 'reconcile-cell-b']
)
const assignmentsLocked = signal()
const activityReachedAssignment = signal()
const continueReconciliation = signal()
let gateReconciliation = true
const reconcileDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (
gateReconciliation &&
phase === 'after' &&
sql.includes('SELECT assignment.* FROM relay_assignments assignment')
) {
gateReconciliation = false
assignmentsLocked.resolve()
await continueReconciliation.promise
}
})
const activityDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (phase === 'before' && sql.includes('FROM relay_assignments WHERE user_id')) {
activityReachedAssignment.resolve()
}
})
const reconcileStore = new RelayAssignmentStore(reconcileDatabase, () => now)
const activityStore = new RelayAssignmentStore(activityDatabase, () => now)
const reconciliation = reconcileStore.cellEvacuationStatus(
'reconcile-cell-a',
'reconcile-cell-b',
true
)
await assignmentsLocked.promise
const activity = activityStore.acquireActivity(identities[0]!, {
activityId: 'splice:reconcile',
kind: 'splice',
cellId: 'reconcile-cell-a'
})
await activityReachedAssignment.promise
continueReconciliation.resolve()
await Promise.all([reconciliation, activity])
expect(reconcileDatabase.attempts).toBe(1)
expect(activityDatabase.attempts).toBe(1)
const reservations = await database.query(
`SELECT cell.cell_id, cell.reserved_requests,
COALESCE(SUM(lease.request_units), 0) AS lease_units
FROM relay_cells cell
LEFT JOIN relay_assignment_activity_leases lease ON lease.cell_id = cell.cell_id
WHERE cell.cell_id IN (?, ?)
GROUP BY cell.cell_id, cell.reserved_requests ORDER BY cell.cell_id`,
['reconcile-cell-a', 'reconcile-cell-b']
)
expect(reservations).toEqual([
{ cell_id: 'reconcile-cell-a', reserved_requests: '8', lease_units: '8' },
{ cell_id: 'reconcile-cell-b', reserved_requests: '6', lease_units: '6' }
])
}, 15_000)
it('keeps a unit placed between reconciliation reading leases and locking cells', async () => {
const now = 1_350_000_000_000
const cells = [
@@ -945,529 +568,4 @@ describePostgres('PostgreSQL transaction recovery', () => {
).resolves.toMatchObject({ inProgress: 0 })
}, 15_000)
it('defers completion instead of deadlocking with a legacy cell-first lock', async () => {
const now = 1_500_000_000_000
const identity = {
userId: 'completion-contention-user',
relayHostId: 'completionwait01'
}
const sourceCell = {
id: 'completion-contention-source',
url: 'https://completion-contention-source.example.com',
capacityRequests: 100
}
const targetCell = {
id: 'completion-contention-target',
url: 'https://completion-contention-target.example.com',
capacityRequests: 100
}
const store = new RelayAssignmentStore(database, () => now, {
requireLiveCells: true
})
await store.reconcileCells([sourceCell, targetCell])
await store.recordCellHeartbeat({
cellId: sourceCell.id,
cellUrl: sourceCell.url,
cellIncarnation: '11111111-1111-4111-8111-111111111111',
startedAt: now - 100,
ready: true,
observedRequests: 0
})
await store.recordCellHeartbeat({
cellId: targetCell.id,
cellUrl: targetCell.url,
cellIncarnation: '22222222-2222-4222-8222-222222222222',
startedAt: now - 100,
ready: true,
observedRequests: 0
})
const assignment = await store.assign(identity)
const sourceControl = await store.activateControl(identity, {
cellId: sourceCell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
const migration = await store.startEvacuation(identity, targetCell.id)
await store.activateControl(identity, {
cellId: targetCell.id,
assignmentEpoch: migration.assignmentEpoch,
generation: 1
})
await store.markMigrationTargetRegistered(identity, {
cellId: targetCell.id,
assignmentEpoch: migration.assignmentEpoch
})
await store.releaseActivity(identity, sourceControl)
await store.setCellEnabled(sourceCell.id, false)
const legacyCellLocked = signal()
const completionAssignmentLocked = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [
sourceCell.id
])
legacyCellLocked.resolve()
await completionAssignmentLocked.promise
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
})
await legacyCellLocked.promise
let signalCompletion = true
const completionDatabase = new TransactionProbeDatabase(
database,
async (phase, sql) => {
if (
signalCompletion &&
phase === 'after' &&
sql.includes('FROM relay_assignments WHERE user_id')
) {
signalCompletion = false
completionAssignmentLocked.resolve()
}
}
)
const completionStore = new RelayAssignmentStore(completionDatabase, () => now, {
requireLiveCells: true
})
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await expect(completionStore.completeReadyEvacuations()).resolves.toBe(0)
await expect(legacyTransaction).resolves.toBeUndefined()
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_retry')
)
await expect(store.completeReadyEvacuations()).resolves.toBe(1)
}, 15_000)
it('normalizes persistent dead-source inventory contention', async () => {
let now = 1_550_000_000_000
const identity = {
userId: 'dead-source-contention-user',
relayHostId: 'deadsourcewait01'
}
const secondIdentity = {
userId: identity.userId,
relayHostId: 'deadsourcewait02'
}
const sourceCell = {
id: 'dead-source-contention-source',
url: 'https://dead-source-contention-source.example.com',
capacityRequests: 100
}
const targetCell = {
id: 'dead-source-contention-target',
url: 'https://dead-source-contention-target.example.com',
capacityRequests: 100
}
const sourceIncarnation = '11111111-1111-4111-8111-111111111111'
const targetIncarnation = '22222222-2222-4222-8222-222222222222'
const store = new RelayAssignmentStore(database, () => now, {
requireLiveCells: true
})
await store.reconcileCells([sourceCell, targetCell])
await store.setCellEnabled(targetCell.id, false)
await store.recordCellHeartbeat({
cellId: sourceCell.id,
cellUrl: sourceCell.url,
cellIncarnation: sourceIncarnation,
startedAt: now - 100,
ready: true,
observedRequests: 0
})
await store.recordCellHeartbeat({
cellId: targetCell.id,
cellUrl: targetCell.url,
cellIncarnation: targetIncarnation,
startedAt: now - 100,
ready: true,
observedRequests: 0
})
const assignment = await store.assign(identity)
const sourceControl = await store.activateControl(identity, {
cellId: sourceCell.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
await store.setCellEnabled(targetCell.id, true)
const migration = await store.startEvacuation(identity, targetCell.id)
await store.activateControl(identity, {
cellId: targetCell.id,
assignmentEpoch: migration.assignmentEpoch,
generation: 1
})
await store.markMigrationTargetRegistered(identity, {
cellId: targetCell.id,
assignmentEpoch: migration.assignmentEpoch
})
await store.releaseActivity(identity, sourceControl)
const secondAssignment = await store.assign(secondIdentity)
expect(secondAssignment.cellId).toBe(sourceCell.id)
const secondSourceControl = await store.activateControl(secondIdentity, {
cellId: sourceCell.id,
assignmentEpoch: secondAssignment.assignmentEpoch,
generation: 1
})
const secondMigration = await store.startEvacuation(secondIdentity, targetCell.id)
await store.activateControl(secondIdentity, {
cellId: targetCell.id,
assignmentEpoch: secondMigration.assignmentEpoch,
generation: 1
})
await store.markMigrationTargetRegistered(secondIdentity, {
cellId: targetCell.id,
assignmentEpoch: secondMigration.assignmentEpoch
})
await store.releaseActivity(secondIdentity, secondSourceControl)
await store.setCellEnabled(sourceCell.id, false)
now += 45_001
await store.recordCellHeartbeat({
cellId: targetCell.id,
cellUrl: targetCell.url,
cellIncarnation: targetIncarnation,
startedAt: now - 45_101,
ready: true,
observedRequests: 0
})
await store.attestCellFence(sourceCell.id, sourceIncarnation)
const legacyCellLocked = signal()
const completionAssignmentLocked = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [
sourceCell.id
])
legacyCellLocked.resolve()
await completionAssignmentLocked.promise
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
})
await legacyCellLocked.promise
let signalCompletion = true
const fallbackInventoryLocked = signal()
const freshAssignmentLocked = signal()
const persistentInventoryLocked = signal()
let releasePersistentInventory = false
const completionDatabase = new TransactionProbeDatabase(
database,
async (phase, sql) => {
if (
signalCompletion &&
phase === 'after' &&
sql.includes('FROM relay_assignments WHERE user_id')
) {
signalCompletion = false
completionAssignmentLocked.resolve()
} else if (
phase === 'after' &&
sql.trim() === 'SELECT * FROM relay_cells ORDER BY cell_id ASC'
) {
fallbackInventoryLocked.resolve()
await freshAssignmentLocked.promise
}
}
)
const completionStore = new RelayAssignmentStore(completionDatabase, () => now, {
requireLiveCells: true
})
const freshAssignmentTransaction = database.transaction(async (transaction) => {
await fallbackInventoryLocked.promise
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
freshAssignmentLocked.resolve()
await transaction.queryLocked(`SELECT * FROM relay_cells ORDER BY cell_id ASC`)
persistentInventoryLocked.resolve()
while (!releasePersistentInventory) {
await new Promise((resolve) => setTimeout(resolve, 250))
await transaction.query(`SELECT 1`)
}
})
const completion = completionStore.cellEvacuationStatus(
sourceCell.id,
targetCell.id,
true
)
await persistentInventoryLocked.promise
await expect(
completion
).resolves.toMatchObject({ inProgress: 2, completed: 0, blocked: 2 })
await expect(legacyTransaction).resolves.toBeUndefined()
releasePersistentInventory = true
await expect(freshAssignmentTransaction).resolves.toBeUndefined()
await expect(
store.cellEvacuationStatus(sourceCell.id, targetCell.id, true)
).resolves.toMatchObject({ inProgress: 0, completed: 2, blocked: 0 })
}, 25_000)
it('defers expired lease cleanup around a legacy cell-first lock', async () => {
const now = 1_600_000_000_000
const identity = {
userId: 'lease-contention-user',
relayHostId: 'leasecontention1'
}
const cell = {
id: 'lease-contention-cell',
url: 'https://lease-contention-cell.example.com',
capacityRequests: 100
}
const store = new RelayAssignmentStore(database, () => now)
await store.reconcileCells([cell])
await store.assign(identity)
await database.query(
`UPDATE relay_assignment_activity_leases SET expires_at = ?
WHERE user_id = ? AND relay_host_id = ?`,
[now - 1, identity.userId, identity.relayHostId]
)
const legacyCellLocked = signal()
const cleanupAssignmentLocked = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cell.id])
legacyCellLocked.resolve()
await cleanupAssignmentLocked.promise
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
})
await legacyCellLocked.promise
let signalCleanup = true
const cleanupDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (
signalCleanup &&
phase === 'after' &&
sql.includes('FROM relay_assignments WHERE user_id')
) {
signalCleanup = false
cleanupAssignmentLocked.resolve()
}
})
const cleanupStore = new RelayAssignmentStore(cleanupDatabase, () => now)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await expect(cleanupStore.releaseExpiredActivityLeases()).resolves.toBe(0)
await expect(legacyTransaction).resolves.toBeUndefined()
expect(cleanupDatabase.attempts).toBe(1)
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_retry')
)
await expect(store.releaseExpiredActivityLeases()).resolves.toBe(1)
}, 15_000)
it('skips an expired lease when another director owns its assignment lock', async () => {
const now = 1_650_000_000_000
const identity = {
userId: 'lease-assignment-contention-user',
relayHostId: 'leaseassignment1'
}
const cell = {
id: 'lease-assignment-contention-cell',
url: 'https://lease-assignment-contention-cell.example.com',
capacityRequests: 100
}
const store = new RelayAssignmentStore(database, () => now)
await store.reconcileCells([cell])
await store.assign(identity)
await database.query(
`UPDATE relay_assignment_activity_leases SET expires_at = ?
WHERE user_id = ? AND relay_host_id = ?`,
[now - 1, identity.userId, identity.relayHostId]
)
const assignmentLocked = signal()
const releaseAssignment = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
assignmentLocked.resolve()
await releaseAssignment.promise
})
await assignmentLocked.promise
const cleanupDatabase = new TransactionProbeDatabase(database, async () => undefined)
const cleanupStore = new RelayAssignmentStore(cleanupDatabase, () => now)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await expect(cleanupStore.releaseExpiredActivityLeases()).resolves.toBe(0)
expect(cleanupDatabase.attempts).toBe(1)
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_retry')
)
warn.mockRestore()
releaseAssignment.resolve()
await expect(legacyTransaction).resolves.toBeUndefined()
await expect(store.releaseExpiredActivityLeases()).resolves.toBe(1)
}, 15_000)
it('defers aggregate expiry cleanup around a legacy cell-first lock', async () => {
const now = 1_700_000_000_000
const identity = {
userId: 'aggregate-contention-user',
relayHostId: 'aggregatewait01'
}
const cell = {
id: 'aggregate-contention-cell',
url: 'https://aggregate-contention-cell.example.com',
capacityRequests: 100
}
const store = new RelayAssignmentStore(database, () => now)
await store.reconcileCells([cell])
await store.assign(identity)
await database.query(
`DELETE FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
await database.query(
`UPDATE relay_assignments SET lease_expires_at = ?
WHERE user_id = ? AND relay_host_id = ?`,
[now - 1, identity.userId, identity.relayHostId]
)
const legacyCellLocked = signal()
const cleanupAssignmentsLocked = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(`SELECT * FROM relay_cells WHERE cell_id = ?`, [cell.id])
legacyCellLocked.resolve()
await cleanupAssignmentsLocked.promise
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
})
await legacyCellLocked.promise
let signalCleanup = true
const cleanupDatabase = new TransactionProbeDatabase(database, async (phase, sql) => {
if (
signalCleanup &&
phase === 'after' &&
sql.includes('FROM relay_assignments WHERE lease_expires_at')
) {
signalCleanup = false
cleanupAssignmentsLocked.resolve()
}
})
const cleanupStore = new RelayAssignmentStore(cleanupDatabase, () => now)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
await expect(cleanupStore.releaseExpiredActivity()).resolves.toBe(0)
await expect(legacyTransaction).resolves.toBeUndefined()
expect(cleanupDatabase.attempts).toBe(1)
expect(warn).not.toHaveBeenCalledWith(
expect.stringContaining('orca_relay_postgres_transaction_retry')
)
await expect(store.releaseExpiredActivity()).resolves.toBe(1)
}, 15_000)
it('defers aggregate expiry before waiting on a legacy assignment row', async () => {
const now = 1_700_000_000_000
const identity = {
userId: 'aggregate-contention-user',
relayHostId: 'aggregatewait01'
}
const cell = {
id: 'aggregate-contention-cell',
url: 'https://aggregate-contention-cell.example.com',
capacityRequests: 100
}
const store = new RelayAssignmentStore(database, () => now)
await store.reconcileCells([cell])
await store.assign(identity)
await database.query(
`DELETE FROM relay_assignment_activity_leases
WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
await database.query(
`UPDATE relay_assignments SET lease_expires_at = ?
WHERE user_id = ? AND relay_host_id = ?`,
[now - 1, identity.userId, identity.relayHostId]
)
const legacyAssignmentLocked = signal()
const releaseLegacyAssignment = signal()
const legacyTransaction = database.transaction(async (transaction) => {
await transaction.queryLocked(
`SELECT * FROM relay_assignments WHERE user_id = ? AND relay_host_id = ?`,
[identity.userId, identity.relayHostId]
)
legacyAssignmentLocked.resolve()
await releaseLegacyAssignment.promise
})
await legacyAssignmentLocked.promise
const timedOut = Symbol('timed-out')
const cleanup = store.releaseExpiredActivity()
const result = await Promise.race([
cleanup,
new Promise<typeof timedOut>((resolve) => {
setTimeout(() => resolve(timedOut), 750)
})
])
releaseLegacyAssignment.resolve()
await legacyTransaction
if (result === timedOut) await cleanup
expect(result).toBe(0)
await expect(store.releaseExpiredActivity()).resolves.toBe(1)
}, 15_000)
it('keeps concurrent dormant reassignments capacity-consistent', async () => {
const now = 1_000_000_000_000
const serializedDatabase = new TransactionProbeDatabase(database, async () => undefined)
const store = new RelayAssignmentStore(serializedDatabase, () => now)
await database.query(
`DELETE FROM relay_assignment_activity_leases WHERE user_id LIKE 'postgres-user-%'`
)
await database.query(`DELETE FROM relay_assignments WHERE user_id LIKE 'postgres-user-%'`)
await database.query(`DELETE FROM relay_cells WHERE cell_id IN (?, ?, ?)`, [
'cell-a',
'cell-b',
'cell-c'
])
await store.reconcileCells([
{ id: 'cell-a', url: 'https://cell-a.example.com', capacityRequests: 100 },
{ id: 'cell-b', url: 'https://cell-b.example.com', capacityRequests: 100 },
{ id: 'cell-c', url: 'https://cell-c.example.com', capacityRequests: 100 }
])
const identities = Array.from({ length: 30 }, (_, index) => ({
userId: `postgres-user-${index}`,
relayHostId: `postgreshost${String(index).padStart(5, '0')}`
}))
await Promise.all(identities.map(async (identity) => await store.assign(identity)))
await database.query(`DELETE FROM relay_assignment_activity_leases`)
await database.query(
`UPDATE relay_assignments SET lease_expires_at = ?, last_activity_at = ?,
reserved_controls = 0, reserved_splices = 0, reserved_invites = 0,
pending_installs = 0, pending_confirmations = 0, migration_leases = 0`,
[0, 0]
)
await database.query(`UPDATE relay_cells SET reserved_requests = 0`)
await Promise.all(identities.map(async (identity) => await store.assign(identity)))
const reservations = await database.query(
`SELECT cell_id, reserved_requests FROM relay_cells ORDER BY cell_id`
)
expect(reservations).toEqual([
{ cell_id: 'cell-a', reserved_requests: '10' },
{ cell_id: 'cell-b', reserved_requests: '10' },
{ cell_id: 'cell-c', reserved_requests: '10' }
])
}, 10_000)
})
@@ -1,8 +0,0 @@
Exact relay contract snapshots from `027acb4efa2e6b226d40df266b86367423946d62`, `cloud/packages/relay-contract/src/`. Used to exercise the pre-correction strict wire parsers. Do not format or edit these baseline sources.
```text
aba94e108a5cd0f1af8b38875429ad8636d24c43a728273e3df60d9a1a1d1b6d director-messages.ts
bd13b5a694a5d683a5b680c14e46ab33f4ef4a5bfedf040d046b09d540cb4c17 wire-scalars.ts
bc89116f884a2f20a6588f9b91219aa596bc2410d28b499a93a78350def109d5 relay-regions.ts
8fcae470a5fc72f2fcdde9d2f09cd20289c256356dd490484ac1cfa53839fbe4 control-messages.ts
```
@@ -1,146 +0,0 @@
import { z } from 'zod'
import {
Base6432ByteSchema,
Base64Raw24ByteSchema,
Base64Url32ByteSchema,
EpochMsSchema,
GenerationSchema,
OpaqueIdSchema,
PositiveDurationMsSchema,
RelayHostIdSchema
} from './wire-scalars.js'
const AppVersionSchema = z.string().min(1).max(128)
const BoundedCiphertextSchema = z
.string()
.min(1)
.max(16 * 1024)
.regex(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/)
const ConnectionKindSchema = z.enum(['invite', 'resume'])
export const HostHelloSchema = z
.object({
v: z.literal(1),
relayHostId: RelayHostIdSchema,
assignmentEpoch: GenerationSchema,
hostPublicKeyB64: Base6432ByteSchema,
appVersion: AppVersionSchema,
previousGeneration: GenerationSchema.optional(),
controlResumeSecret: Base64Url32ByteSchema.optional()
})
.strict()
export const HostChallengeSchema = z
.object({
challengeId: OpaqueIdSchema,
relayEphemeralPublicKeyB64: Base6432ByteSchema,
nonceB64: Base64Raw24ByteSchema,
ciphertextB64: BoundedCiphertextSchema,
expiresAt: EpochMsSchema
})
.strict()
export const HostChallengeAckSchema = z
.object({ challengeId: OpaqueIdSchema, proofB64: Base6432ByteSchema })
.strict()
// Advertised on the control upgrade rather than in host-hello: HostHelloSchema
// is strict, so a new hello key is refused by every already-deployed cell.
export const RELAY_HOST_CAPABILITIES_HEADER = 'x-orca-host-capabilities'
// The host accepts kind/relayDeviceId on a pendingConns entry. A host that does
// not advertise this parses those entries strictly and would drop the whole ack.
export const RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS = 'pending-conn-details'
export function parseRelayHostCapabilities(
header: string | string[] | undefined
): ReadonlySet<string> {
const raw = Array.isArray(header) ? header.join(',') : (header ?? '')
return new Set(
raw
.split(',')
.map((token) => token.trim())
.filter((token) => token.length > 0 && token.length <= 64)
.slice(0, 16)
)
}
// kind/relayDeviceId are optional so an entry stays readable by a host that
// predates them; the cell only emits them to a host that advertised support.
const PendingConnectionSchema = z
.object({
connId: OpaqueIdSchema,
connTicket: Base64Url32ByteSchema,
kind: ConnectionKindSchema.optional(),
relayDeviceId: OpaqueIdSchema.optional()
})
.strict()
export const HostHelloAckSchema = z
.object({
v: z.literal(1),
generation: GenerationSchema,
controlResumeSecret: Base64Url32ByteSchema,
leaseExpiresAt: EpochMsSchema,
activeConnIds: z.array(OpaqueIdSchema).max(8),
pendingConns: z.array(PendingConnectionSchema).max(8)
})
.strict()
export const ConnectionOpenSchema = z
.object({
connId: OpaqueIdSchema,
connTicket: Base64Url32ByteSchema,
kind: ConnectionKindSchema,
relayDeviceId: OpaqueIdSchema,
attachDeadlineMs: PositiveDurationMsSchema
})
.strict()
export const HostDataAuthSchema = z
.object({
v: z.literal(1),
connTicket: Base64Url32ByteSchema,
generation: GenerationSchema
})
.strict()
export const InviteCreateSchema = z
.object({ reqId: OpaqueIdSchema, relayDeviceId: OpaqueIdSchema })
.strict()
export const InviteCreatedSchema = z
.object({
reqId: OpaqueIdSchema,
inviteToken: Base64Url32ByteSchema,
expiresAt: EpochMsSchema,
maxAttempts: z.number().int().positive().max(16)
})
.strict()
export const DeviceRevokeSchema = z
.object({ reqId: OpaqueIdSchema, relayDeviceId: OpaqueIdSchema })
.strict()
export const AuthRefreshSchema = z.object({ relayJwt: z.string().min(1).max(8 * 1024) }).strict()
export const DrainSchema = z
.object({
graceMs: z.number().int().nonnegative().max(60 * 60 * 1000),
recovery: z.literal('resolve-director')
})
.strict()
export const HeartbeatSchema = z.object({ t: EpochMsSchema }).strict()
export type HostHello = z.infer<typeof HostHelloSchema>
export type HostChallenge = z.infer<typeof HostChallengeSchema>
export type HostChallengeAck = z.infer<typeof HostChallengeAckSchema>
export type HostHelloAck = z.infer<typeof HostHelloAckSchema>
export type ConnectionOpen = z.infer<typeof ConnectionOpenSchema>
export type HostDataAuth = z.infer<typeof HostDataAuthSchema>
export type InviteCreate = z.infer<typeof InviteCreateSchema>
export type InviteCreated = z.infer<typeof InviteCreatedSchema>
export type DeviceRevoke = z.infer<typeof DeviceRevokeSchema>
export type AuthRefresh = z.infer<typeof AuthRefreshSchema>
export type Drain = z.infer<typeof DrainSchema>
export type Heartbeat = z.infer<typeof HeartbeatSchema>
@@ -1,75 +0,0 @@
import { z } from 'zod'
import {
Base64Url32ByteSchema,
CanonicalHttpsOriginSchema,
EpochMsSchema,
GenerationSchema,
RelayHostIdSchema
} from './wire-scalars.js'
import { RelayRegionSchema } from './relay-regions.js'
const SignedAssignmentLeaseSchema = z.string().min(1).max(8 * 1024)
export const AssignmentRequestSchema = z
.object({
v: z.literal(1),
relayHostId: RelayHostIdSchema,
// Client-declared reconnection; the director verifies it against the
// durable assignment before granting fast-lane admission.
reconnect: z.boolean().optional(),
preferredRegion: RelayRegionSchema.optional()
})
.strict()
export const AssignmentResponseSchema = z
.object({
v: z.literal(1),
cellUrl: CanonicalHttpsOriginSchema,
assignmentEpoch: GenerationSchema,
lease: SignedAssignmentLeaseSchema
})
.strict()
export const ResolveRequestSchema = z
.object({
v: z.literal(1),
relayHostId: RelayHostIdSchema,
resumeToken: Base64Url32ByteSchema
})
.strict()
export const ResolveResponseSchema = z
.object({
v: z.literal(1),
cellUrl: CanonicalHttpsOriginSchema,
assignmentEpoch: GenerationSchema,
leaseExpiresAt: EpochMsSchema
})
.strict()
export const RelayMovedSchema = z
.object({
v: z.literal(1),
cellUrl: CanonicalHttpsOriginSchema,
assignmentEpoch: GenerationSchema
})
.strict()
export function isTrustedNewerMove(input: {
sourceOrigin: string
configuredDirectorOrigin: string
currentAssignmentEpoch: number
move: z.infer<typeof RelayMovedSchema>
}): boolean {
// Why: cells and stale director responses must never redirect a credential-bearing client.
return (
input.sourceOrigin === input.configuredDirectorOrigin &&
input.move.assignmentEpoch > input.currentAssignmentEpoch
)
}
export type AssignmentRequest = z.infer<typeof AssignmentRequestSchema>
export type AssignmentResponse = z.infer<typeof AssignmentResponseSchema>
export type ResolveRequest = z.infer<typeof ResolveRequestSchema>
export type ResolveResponse = z.infer<typeof ResolveResponseSchema>
export type RelayMoved = z.infer<typeof RelayMovedSchema>
@@ -1,71 +0,0 @@
import { z } from 'zod'
export const RELAY_REGIONS = ['us-central1', 'asia-east2'] as const
export const RelayRegionSchema = z.enum(RELAY_REGIONS)
export type RelayRegion = z.infer<typeof RelayRegionSchema>
export const RELAY_DEFAULT_REGION: RelayRegion = 'us-central1'
// Field-name segment for the flat per-region runtime counters, spelled out rather than derived so
// the Terraform side can hold the same literal and a test can compare the two. `satisfies` makes a
// new region a compile error here, which is the point: a region with no segment would silently
// drop out of the region-skew alert's denominators.
export const RELAY_REGION_METRIC_SEGMENTS = {
'us-central1': 'UsCentral1',
'asia-east2': 'AsiaEast2'
} as const satisfies Record<RelayRegion, string>
const RelayProbeOriginSchema = z.string().url().max(2_048).refine(isCanonicalHttpsOrigin)
export const RelayRegionCatalogResponseSchema = z
.object({
v: z.literal(1),
regions: z
.array(
z
.object({
region: RelayRegionSchema,
probeOrigins: z.array(RelayProbeOriginSchema).min(1).max(2)
})
.strict()
)
.max(RELAY_REGIONS.length)
})
.strict()
.superRefine((catalog, context) => {
const regions = new Set<RelayRegion>()
const origins = new Set<string>()
for (const [regionIndex, entry] of catalog.regions.entries()) {
if (regions.has(entry.region)) {
context.addIssue({
code: 'custom',
message: 'duplicate relay region',
path: ['regions', regionIndex, 'region']
})
}
regions.add(entry.region)
for (const [originIndex, origin] of entry.probeOrigins.entries()) {
if (origins.has(origin)) {
context.addIssue({
code: 'custom',
message: 'duplicate relay probe origin',
path: ['regions', regionIndex, 'probeOrigins', originIndex]
})
}
origins.add(origin)
}
}
})
export type RelayRegionCatalogResponse = z.infer<typeof RelayRegionCatalogResponseSchema>
function isCanonicalHttpsOrigin(value: string): boolean {
try {
const url = new URL(value)
return url.protocol === 'https:' && url.origin === value
} catch {
return false
}
}
@@ -1,20 +0,0 @@
import { z } from 'zod'
export const Base64Url32ByteSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/)
export const Base64Url24ByteSchema = z.string().regex(/^[A-Za-z0-9_-]{32}$/)
export const Base6432ByteSchema = z.string().regex(/^(?:[A-Za-z0-9+/]{4}){10}[A-Za-z0-9+/]{3}=$/)
export const Base64Raw24ByteSchema = z.string().regex(/^(?:[A-Za-z0-9+/]{4}){8}$/)
export const RelayHostIdSchema = z.string().regex(/^[A-Za-z0-9_-]{16}$/)
export const OpaqueIdSchema = z.string().min(1).max(128)
export const EpochMsSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
export const GenerationSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
export const PositiveDurationMsSchema = z.number().int().positive().max(24 * 60 * 60 * 1000)
export const CanonicalHttpsOriginSchema = z.string().max(2048).refine((value) => {
try {
const url = new URL(value)
return url.protocol === 'https:' && url.origin === value && url.pathname === '/'
} catch {
return false
}
}, 'must be a canonical HTTPS origin')
@@ -50,35 +50,4 @@ describe('hello and replies', () => {
expect(hello.features).toEqual(['answer', 'future-feature'])
expect(hello.sources).toEqual(['slack', 'future-source'])
})
it('caps a hello at maxHelloProjects projects with short display strings', () => {
const project = (name: string) => ({
placeId: 'pl_1',
name,
kind: 'folder',
where: { kind: 'local' }
})
const hello = (projects: unknown[]) =>
ConnectorHelloRequestSchema.safeParse({
v: 1,
contract: 1,
orcaVersion: '1.4.230',
platform: 'linux',
runtime: 'desktop',
hostName: 'build-box',
agents: ['claude'],
promptKinds: [],
features: [],
sources: ['slack'],
projects
}).success
const longest = 'x'.repeat(CONNECTOR_LIMITS.maxDisplayTextLength)
expect(
hello(Array.from({ length: CONNECTOR_LIMITS.maxHelloProjects }, () => project(longest)))
).toBe(true)
expect(hello([project(`${longest}x`)])).toBe(false)
expect(
hello(Array.from({ length: CONNECTOR_LIMITS.maxHelloProjects + 1 }, () => project('orca')))
).toBe(false)
})
})
@@ -1,6 +1,4 @@
import { describe, expect, it } from 'vitest'
import { HostHelloAckSchema } from './control-messages.js'
import { isTrustedNewerMove, RelayMovedSchema } from './director-messages.js'
import {
buildHostChallengePlaintext,
buildHostProofMacInput,
@@ -8,29 +6,6 @@ import {
} from './host-proof-transcript.js'
describe('relay protocol contract', () => {
it('accepts moves only from the configured director at a strictly newer epoch', () => {
const move = RelayMovedSchema.parse({
v: 1,
cellUrl: 'https://relay-c2.onorca.dev',
assignmentEpoch: 4
})
const base = {
configuredDirectorOrigin: 'https://relay.onorca.dev',
currentAssignmentEpoch: 3,
move
}
expect(isTrustedNewerMove({ ...base, sourceOrigin: 'https://relay.onorca.dev' })).toBe(true)
expect(isTrustedNewerMove({ ...base, sourceOrigin: move.cellUrl })).toBe(false)
expect(
isTrustedNewerMove({
...base,
sourceOrigin: 'https://relay.onorca.dev',
currentAssignmentEpoch: 4
})
).toBe(false)
})
it('locks the complete host key-possession transcript', () => {
const transcript = buildHostProofTranscript({
relayOrigin: 'https://relay.onorca.dev',
@@ -89,29 +64,3 @@ describe('relay protocol contract', () => {
).toThrow('challengeNonce must be 24 bytes')
})
})
describe('pending connection details capability', () => {
it('reads a pending entry with or without the stated kind and device', () => {
const ack = {
v: 1 as const,
generation: 3,
controlResumeSecret: 'R'.repeat(43),
leaseExpiresAt: 1_800_000_000_000,
activeConnIds: []
}
const identifiers = { connId: 'conn-1', connTicket: 'T'.repeat(43) }
expect(HostHelloAckSchema.safeParse({ ...ack, pendingConns: [identifiers] }).success).toBe(true)
expect(
HostHelloAckSchema.safeParse({
...ack,
pendingConns: [{ ...identifiers, kind: 'resume', relayDeviceId: 'device-1' }]
}).success
).toBe(true)
expect(
HostHelloAckSchema.safeParse({
...ack,
pendingConns: [{ ...identifiers, reservationId: 'injected' }]
}).success
).toBe(false)
})
})
@@ -2,22 +2,9 @@ import { describe, expect, it } from 'vitest'
import { IdleRegionalRehomeResponseSchema } from './idle-regional-rehome.js'
describe('idle regional rehome response', () => {
it('accepts a source cell that has never heard of the reason field', () => {
expect(IdleRegionalRehomeResponseSchema.parse({ v: 1, outcome: 'deferred' })).toEqual({
v: 1,
outcome: 'deferred'
})
})
it('reads a reason it does not know as absent instead of failing the response', () => {
expect(
IdleRegionalRehomeResponseSchema.parse({ v: 1, outcome: 'deferred', reason: 'from-a-newer-cell' })
).toEqual({ v: 1, outcome: 'deferred' })
})
it('drops a field added after this decoder shipped', () => {
expect(
IdleRegionalRehomeResponseSchema.parse({ v: 1, outcome: 'committed', movedAt: 17 })
).toEqual({ v: 1, outcome: 'committed' })
})
})
@@ -1,53 +1,9 @@
import { describe, expect, it } from 'vitest'
import { hasAdmissionCapacity } from './admission-budgets.js'
import { decideResumeCommit, TokenFamilySchema } from './persistence-invariants.js'
const HASH = 'abcdefghijklmnopqrstuvwxyzABCDEFGH012345678'
describe('relay persistence invariants', () => {
it('admits pre-auth sockets only outside every reserved budget', () => {
expect(
hasAdmissionCapacity({
totalRequests: 649,
preAuthConnections: 44,
sourcePreAuthConnections: 3
})
).toBe(true)
expect(
hasAdmissionCapacity({
totalRequests: 650,
preAuthConnections: 0,
sourcePreAuthConnections: 0
})
).toBe(false)
expect(
hasAdmissionCapacity({
totalRequests: 0,
preAuthConnections: 45,
sourcePreAuthConnections: 0
})
).toBe(false)
expect(
hasAdmissionCapacity({ totalRequests: 0, preAuthConnections: 0, sourcePreAuthConnections: 4 })
).toBe(false)
expect(
hasAdmissionCapacity({
totalRequests: 2_899,
preAuthConnections: 0,
sourcePreAuthConnections: 0,
totalRequestCeiling: 2_900
})
).toBe(true)
expect(
hasAdmissionCapacity({
totalRequests: 2_900,
preAuthConnections: 0,
sourcePreAuthConnections: 0,
totalRequestCeiling: 2_900
})
).toBe(false)
})
it('makes commit-time current, grace, retired, expired, and revoked outcomes exact', () => {
const family = TokenFamilySchema.parse({
currentVersion: 3,
@@ -1,4 +1,4 @@
import { BOOTSTRAP_FATAL_EXIT_GUARD_KEY } from '../../src/main/startup/bootstrap-fatal-exit-guard'
import { BOOTSTRAP_FATAL_EXIT_GUARD_KEY } from '../../src/main/startup/process-failure/bootstrap-fatal-exit-guard'
export const BOOTSTRAP_FATAL_LOG_ENV_VAR = 'ORCA_BOOTSTRAP_FATAL_LOG'
export const BOOTSTRAP_FATAL_LOG_FILE_NAME = 'bootstrap-fatal.log'
const BOOTSTRAP_FATAL_LOG_MAX_BYTES = 262_144
+6 -4
View File
@@ -9,9 +9,11 @@ export const CLI_MAIN_ENTRY_NAMES = [
'skills/bundled-skill-install/bundled-skill-local-host',
'skills/bundled-skill-install/bundled-skill-installation',
'filesystem/macos-tree-clone',
'orcad/orcad-stale-lock-reclaim',
'orcad/orcad-host-identity-reset',
'plugins/plugin-authoring-checks',
'orcad/instance-lock/orcad-stale-lock-reclaim',
'orcad/profile-state/orcad-host-identity-reset',
'plugins/plugin-artifact-validation',
'plugins/plugin-content-hash',
'plugins/plugin-catalog-release-entry',
// `orca vm env doctor` runs its checks in the CLI process.
'environment-doctor/environment-doctor',
'environment-doctor/environment-doctor-host',
@@ -25,5 +27,5 @@ export const CLI_MAIN_ENTRY_NAMES = [
'persistence/profile-state/recovery/profile-state-recovery-command',
'persistence/profile-state/legacy-json/profile-state-export-path',
'persistence/profile-state/legacy-json/profile-state-recovery',
'startup/http1-compatibility-marker'
'startup/network-compatibility/http1-compatibility-marker'
] as const
@@ -30,7 +30,7 @@ export const MAIN_BUILD_ENTRY_POINTS: Record<string, string> = {
'src/main/ai-vault/scan-service/session-scanner-service-entry.ts'
),
'wsl-transcript-fs-process-entry': resolve(
'src/main/native-chat/wsl-transcript-fs-process-entry.ts'
'src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts'
),
// libuv spawns inline; run port probes on a worker to keep the UI loop free.
'port-scan-command-worker-entry': resolve('src/main/ports/port-scan-command-worker-entry.ts'),
+1 -1
View File
@@ -8,7 +8,7 @@
2 src/main/daemon/transport/stream/daemon-stream-backlog-probe.ts
1 src/main/daemon/process-scopes/daemon-user-manager-lifetime.ts
1 src/main/daemon/process-scopes/terminal-scopes.ts
2 src/main/orcad/node-pty-precondition.ts
2 src/main/orcad/native-runtime/node-pty-precondition.ts
1 src/main/pty/process-retirement/posix-pty-process-table.ts
1 src/main/pty/shell-startup/pwsh.ts
2 src/main/pty/shell-startup/zsh-startup-hook-pty-harness.ts
@@ -32,18 +32,11 @@ fi
case "$case_name" in
# The bundled launcher must stay in Electron's node mode.
nofuse-userns-bundled-help) command=("$launcher" --help) ;;
nofuse-userns-bundled-version) command=("$launcher" --version) ;;
nofuse-userns-bundled-status) command=("$launcher" status) ;;
nofuse-userns-bundled-skills) command=("$launcher" skills --help) ;;
nofuse-userns-bundled-worktree) command=("$launcher" worktree list) ;;
# Direct binaries must hand off before Ozone initializes.
nofuse-nosandbox-direct-binary-skills)
command=("$extracted_root/orca-ide" --no-sandbox skills --help)
;;
nofuse-nosandbox-direct-binary-gui)
command=("$extracted_root/orca-ide" --no-sandbox)
;;
stale-display-nosandbox-direct-binary-gui)
command=("$extracted_root/orca-ide" --no-sandbox)
;;
@@ -62,15 +55,6 @@ if ((status == 124)); then
exit 94
fi
if [[ "$case_name" == nofuse-userns-bundled-version ]]; then
version_file="$extracted_root/resources/app.asar.unpacked/out/package.json"
expected_version=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$version_file")
if [[ -z "$expected_version" || "$output" != "$expected_version" ]]; then
output="VERSION_MISMATCH expected=${expected_version:-missing} got=$output"
status=93
fi
fi
# Shell signal exits are reported as 128 plus the signal number.
if ((status >= 128)); then
echo "CRASHED status=$status case=$case_name"
+6 -4
View File
@@ -348,14 +348,16 @@ module.exports = {
'out/main/filesystem/macos-tree-clone.js',
'out/main/orca-profiles/profile-index-store.js',
'out/main/persistence/profile-state/**',
'out/main/plugins/plugin-authoring-checks.js',
'out/main/plugins/plugin-artifact-validation.js',
'out/main/plugins/plugin-content-hash.js',
'out/main/plugins/plugin-catalog-release-entry.js',
'out/main/environment-doctor/environment-doctor.js',
'out/main/environment-doctor/environment-doctor-host.js',
// `orca plugin test` and `orca plugin check` load the runtime, kit and hooks in Node.
'resources/plugins/declarative/*.mjs',
'out/main/skills/bundled-skill-install/bundled-skill-local-host.js',
'out/main/skills/bundled-skill-install/bundled-skill-installation.js',
'out/main/startup/http1-compatibility-marker.js',
'out/main/startup/network-compatibility/http1-compatibility-marker.js',
'out/main/daemon-entry.js',
'out/main/session-scanner-service-entry.js',
'out/main/wsl-transcript-fs-process-entry.js',
@@ -366,8 +368,8 @@ module.exports = {
'out/main/computer-sidecar.js',
'out/main/parcel-watcher-process-entry.js',
'out/main/orcad/orcad-local-serve-selection-entry.js',
'out/main/orcad/orcad-stale-lock-reclaim.js',
'out/main/orcad/orcad-host-identity-reset.js',
'out/main/orcad/instance-lock/orcad-stale-lock-reclaim.js',
'out/main/orcad/profile-state/orcad-host-identity-reset.js',
'out/main/chunks/**',
'out/renderer/**',
'out/mobile-web/**',
+35 -62
View File
@@ -13,14 +13,13 @@
1 test-no-in-test-import-actual:src/main/linear/issues/linear-description-images.test.ts
1 test-no-in-test-import-actual:src/main/managed-server/host-state/orcad-snapshot-room.test.ts
1 test-no-in-test-import-actual:src/main/omp/omp-session-cwd-lookup.test.ts
1 test-no-in-test-import-actual:src/main/orcad/electron-serve-provider-selection.test.ts
1 test-no-in-test-import-actual:src/main/pty/scrollback/overlay-mirror.test.ts
1 test-no-in-test-import-actual:src/main/runtime/rpc/server/pairing/mobile-pairing-userdata-path.test.ts
1 test-no-in-test-import-actual:src/main/runtime/terminal/artifacts/orca-runtime-files-terminal-artifact-swap-detection.test.ts
1 test-no-in-test-import-actual:src/main/speech/openai-api-key-store.test.ts
5 test-no-in-test-import-actual:src/main/ssh/config/ssh-config-loader-regression.test.ts
2 test-no-in-test-import-actual:src/main/ssh/connection/ssh-connection-auth-fallback.test.ts
1 test-no-in-test-import-actual:src/main/window/dropped-path-preparation.test.ts
1 test-no-in-test-import-actual:src/main/window/file-drop/dropped-path-preparation.test.ts
2 test-no-in-test-import-actual:src/main/workspace-hooks/hooks-runner.test.ts
1 test-no-in-test-import-actual:src/main/workspaces/removal/local-worktree-removal-recovery.test.ts
1 test-no-in-test-import-actual:src/renderer/src/components/terminal-pane/pty-connection/notifications/pty-connection-hook-idle-arbitration.test.ts
@@ -46,14 +45,13 @@
1 test-no-polling-loop:src/main/pty/process-retirement/posix-pty-process-groups.integration.test.ts
1 test-no-polling-loop:src/main/pty/process-retirement/windows-live-tree-kill.win32.test.ts
1 test-no-polling-loop:src/main/runtime/orchestration/mailbox/terminal-pointer/orchestration-codex-real-pty.integration.test.ts
1 test-no-polling-loop:src/main/runtime/rpc/methods/agent-launch/agent-launch-restart-replay.test.ts
1 test-no-polling-loop:src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts
3 test-no-polling-loop:src/main/skills/install/skill-bundle-process-termination.integration.test.ts
2 test-no-polling-loop:src/main/skills/runtime-upload/skill-upload-process-restart.integration.test.ts
1 test-no-polling-loop:src/main/ssh/connection/ssh-connection-host-key-store-wiring.test.ts
1 test-no-polling-loop:src/main/ssh/legacy-relay/endpoints/ssh-relay-endpoint-incumbent-shell.integration.test.ts
2 test-no-polling-loop:src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts
1 test-no-polling-loop:src/main/startup/run-electron-vite-dev.test.ts
2 test-no-polling-loop:src/main/startup/gpu-recovery/gpu-lifecycle-install-dir-acl-guard.test.ts
1 test-no-polling-loop:config/scripts/development/launch/run-electron-vite-dev.test.ts
2 test-no-polling-loop:src/main/windows/windows-host-job.win32.test.ts
1 test-no-polling-loop:src/main/windows/windows-pty-job.win32.test.ts
1 test-no-polling-loop:src/main/worker-thread-child-process/worker-thread-child-process.test.ts
@@ -73,7 +71,6 @@
1 test-no-polling-loop:tests/e2e/browser-tab.spec.ts
1 test-no-polling-loop:tests/e2e/chinese-ime-chat-input-repro.spec.ts
3 test-no-polling-loop:tests/e2e/combined-diff-scroll-restore.spec.ts
1 test-no-polling-loop:tests/e2e/computer-windows.e2e.ts
1 test-no-polling-loop:tests/e2e/cross-version-wire/released-relay-daemon.ts
1 test-no-polling-loop:tests/e2e/cross-version-wire/released-relay-transport.ts
1 test-no-polling-loop:tests/e2e/daemon-lifecycle-retirement.spec.ts
@@ -186,18 +183,12 @@
5 test-no-real-time-wait:src/main/pty/process-retirement/windows-live-tree-kill.win32.test.ts
1 test-no-real-time-wait:src/main/runtime/agent-session/structured-agent-session-legacy-send-behind-command.test.ts
3 test-no-real-time-wait:src/main/runtime/orchestration/mailbox/terminal-pointer/orchestration-codex-real-pty.integration.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/methods/agent-launch/agent-launch-restart-replay.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/server/runtime-rpc-long-poll-transport.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/terminal-subscribe-admission.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/terminal-subscribe-mounted-pane.test.ts
3 test-no-real-time-wait:src/main/runtime/rpc/terminal-subscribe-reconnect-rebind.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/ws-transport.test.ts
1 test-no-real-time-wait:src/main/runtime/rpc/terminal/terminal-subscribe-mounted-pane.test.ts
1 test-no-real-time-wait:src/main/runtime/terminal/recovery/layout-command-pane-starts-layout-owner.test.ts
1 test-no-real-time-wait:src/main/runtime/terminal/recovery/restored-pane-start-cli-handles.test.ts
1 test-no-real-time-wait:src/main/runtime/terminal/wait/launched-agent-crash-guard.live-shell.test.ts
1 test-no-real-time-wait:src/main/runtime/terminal/wait/launched-agent-shell-handoff-readiness.test.ts
2 test-no-real-time-wait:src/main/runtime/terminal/wait/tui-idle-name-only-real-pty.integration.test.ts
1 test-no-real-time-wait:src/main/runtime/workspaces/removal/owned-session-teardown-policy.test.ts
3 test-no-real-time-wait:src/main/runtime/workspaces/removal/worktree-teardown-unstopped-pty.test.ts
1 test-no-real-time-wait:src/main/skills/cloud/skill-cloud-direct-upload.test.ts
@@ -205,12 +196,10 @@
2 test-no-real-time-wait:src/main/skills/runtime-upload/skill-upload-process-restart.integration.test.ts
1 test-no-real-time-wait:src/main/ssh/connection/ssh-connection-host-key-store-wiring.test.ts
1 test-no-real-time-wait:src/main/ssh/legacy-relay/endpoints/ssh-relay-endpoint-incumbent-shell.integration.test.ts
2 test-no-real-time-wait:src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts
3 test-no-real-time-wait:src/main/startup/run-electron-vite-dev.test.ts
2 test-no-real-time-wait:src/main/startup/windows-install-dir-acl-recovery.test.ts
2 test-no-real-time-wait:src/main/startup/gpu-recovery/gpu-lifecycle-install-dir-acl-guard.test.ts
3 test-no-real-time-wait:config/scripts/development/launch/run-electron-vite-dev.test.ts
1 test-no-real-time-wait:src/main/updater/lifecycle/serve-supervisor-install-park.test.ts
1 test-no-real-time-wait:src/main/updater/updater.quit-and-install.test.ts
1 test-no-real-time-wait:src/main/window/main-window-updater-deferred-setup.test.ts
1 test-no-real-time-wait:src/main/updater/lifecycle/main-window-updater-deferred-setup.test.ts
3 test-no-real-time-wait:src/main/windows/windows-host-job.win32.test.ts
1 test-no-real-time-wait:src/main/windows/windows-process-tree-command-line-patch.test.ts
2 test-no-real-time-wait:src/main/windows/windows-pty-job.win32.test.ts
@@ -251,7 +240,6 @@
2 test-no-real-time-wait:tests/e2e/cli-floating-file-open-position.spec.ts
1 test-no-real-time-wait:tests/e2e/codex-ctrl-c-status.spec.ts
4 test-no-real-time-wait:tests/e2e/combined-diff-scroll-restore.spec.ts
1 test-no-real-time-wait:tests/e2e/computer-windows.e2e.ts
1 test-no-real-time-wait:tests/e2e/cross-version-wire/legacy-relay-bridge.unit.test.ts
1 test-no-real-time-wait:tests/e2e/cross-version-wire/released-relay-daemon.ts
2 test-no-real-time-wait:tests/e2e/cross-version-wire/released-relay-transport.ts
@@ -303,7 +291,7 @@
1 test-no-real-time-wait:tests/e2e/markdown-table-row-backspace.spec.ts
1 test-no-real-time-wait:tests/e2e/native-chat-history-prepend-anchor.spec.ts
3 test-no-real-time-wait:tests/e2e/notification-sound-replay.spec.ts
5 test-no-real-time-wait:tests/e2e/orchestration-idle-mail-delivery.spec.ts
4 test-no-real-time-wait:tests/e2e/orchestration-idle-mail-delivery.spec.ts
1 test-no-real-time-wait:tests/e2e/orchestration-idle-mail-restore.spec.ts
4 test-no-real-time-wait:tests/e2e/paced-terminal-typing.ts
1 test-no-real-time-wait:tests/e2e/paired-client-hosted-browser-double-restart.spec.ts
@@ -414,7 +402,6 @@
2 test-no-wall-clock:src/main/ai-vault/scanners/cursor/session-scanner-cursor-chat-meta.test.ts
1 test-no-wall-clock:src/main/ai-vault/scanners/devin/session-scanner-devin-db.test.ts
2 test-no-wall-clock:src/main/ai-vault/scanners/grok/session-scanner-grok-parser.test.ts
1 test-no-wall-clock:src/main/ai-vault/scanners/kimi/session-scanner-kimi-parser.test.ts
1 test-no-wall-clock:src/main/ai-vault/scanners/opencode/reader-transport/session-scanner-opencode-sqlite-process.test.ts
2 test-no-wall-clock:src/main/ai-vault/scanners/opencode/session-scanner-opencode-parser.test.ts
2 test-no-wall-clock:src/main/antigravity/posix-hook-invocation.test.ts
@@ -437,19 +424,15 @@
2 test-no-wall-clock:src/main/external-conversations/external-conversation-cancel-settlement.test.ts
3 test-no-wall-clock:src/main/external-conversations/external-conversation-cancel.test.ts
2 test-no-wall-clock:src/main/git/command-runner/host-routing/git-command-resolution-windows-checkout.test.ts
2 test-no-wall-clock:src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts
1 test-no-wall-clock:src/main/git/command-runner/hosted-cli/runner-gh-account-binding.test.ts
1 test-no-wall-clock:src/main/git/refs/maintenance/pack-refs-lock-ownership.test.ts
2 test-no-wall-clock:src/main/git/refs/maintenance/repo-ref-maintenance-real-git.test.ts
1 test-no-wall-clock:src/main/git/source-control/blob-absence-real-git.test.ts
2 test-no-wall-clock:src/main/git/worktrees/catalog/worktree-created-description-real-git.test.ts
3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-active-visible-priority.test.ts
8 test-no-wall-clock:src/main/github/pr-refresh-coordinator-alias-coalescing.test.ts
5 test-no-wall-clock:src/main/github/pr-refresh-coordinator-completion-order.test.ts
3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-foreground-pacing.test.ts
3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-refresh-events.test.ts
4 test-no-wall-clock:src/main/github/pr-refresh-coordinator-visibility-policy.test.ts
3 test-no-wall-clock:src/main/github/pr-refresh-coordinator-visible-follow-up.test.ts
4 test-no-wall-clock:src/main/github/pr-refresh-queue-growth-bound.test.ts
3 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-active-visible-priority.test.ts
8 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-alias-coalescing.test.ts
3 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts
2 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-queue-growth-bound.test.ts
2 test-no-wall-clock:src/main/grok/grok-hook-stdin-no-eof.test.ts
3 test-no-wall-clock:src/main/ipc/runtime-environments/runtime-environment-request-connections.test.ts
1 test-no-wall-clock:src/main/ipc/terminal-render-desync-evidence.test.ts
@@ -468,11 +451,11 @@
1 test-no-wall-clock:src/main/opencode/status-hooks/overlay-dir-gc.test.ts
2 test-no-wall-clock:src/main/orca-profiles/cloud-auth/profile-cloud-service-connect-overlap.test.ts
2 test-no-wall-clock:src/main/orca-profiles/cloud-auth/profile-cloud-service-sign-out-connect-race.test.ts
2 test-no-wall-clock:src/main/orcad/orcad-artifact-cache-retention.test.ts
1 test-no-wall-clock:src/main/orcad/orcad-artifact-materializer.test.ts
6 test-no-wall-clock:src/main/orcad/orcad-serve-parity.integration.test.ts
1 test-no-wall-clock:src/main/orcad/orcad-stale-lock-reclaim.test.ts
3 test-no-wall-clock:src/main/orcad/orcad-stop-request-shutdown.integration.test.ts
2 test-no-wall-clock:src/main/orcad/artifact-slots/orcad-artifact-cache-retention.test.ts
1 test-no-wall-clock:src/main/orcad/artifact-slots/orcad-artifact-materializer.test.ts
6 test-no-wall-clock:src/main/orcad/testing/serve-lifecycle/orcad-serve-parity.integration.test.ts
1 test-no-wall-clock:src/main/orcad/instance-lock/orcad-stale-lock-reclaim.test.ts
3 test-no-wall-clock:src/main/orcad/host-lifecycle/managed-stop/orcad-stop-request-shutdown.integration.test.ts
1 test-no-wall-clock:src/main/persistence/closed-terminal-tab-write-fence.test.ts
1 test-no-wall-clock:src/main/persistence/profile-state/backup/profile-state-backup-rotation.test.ts
1 test-no-wall-clock:src/main/persistence/profile-state/recovery/profile-state-section-repair.test.ts
@@ -492,21 +475,20 @@
1 test-no-wall-clock:src/main/runtime/agent-session/lifecycle/structured-codex-child-work-runtime.test.ts
2 test-no-wall-clock:src/main/runtime/agent-session/structured-agent-session-held-signal.test.ts
1 test-no-wall-clock:src/main/runtime/agent-session/structured-child-records-switch.test.ts
1 test-no-wall-clock:src/main/runtime/mobile-cloud-relay/mobile-relay-e2ee.integration.test.ts
1 test-no-wall-clock:src/main/runtime/mobile-cloud-relay/testing/mobile-relay-e2ee.integration.test.ts
2 test-no-wall-clock:src/main/runtime/orchestration/mailbox/terminal-pointer/orchestration-codex-real-pty.integration.test.ts
1 test-no-wall-clock:src/main/runtime/push/push-device-registration-persistence.test.ts
1 test-no-wall-clock:src/main/runtime/push/push-registration-rpc.test.ts
1 test-no-wall-clock:src/main/runtime/push/push-unpair-persistence.test.ts
1 test-no-wall-clock:src/main/runtime/rpc/methods/agent-launch/agent-launch-prestart-failure.test.ts
1 test-no-wall-clock:src/main/runtime/rpc/methods/agent-launch/agent-launch-replay.test.ts
2 test-no-wall-clock:src/main/runtime/rpc/methods/agent-launch/agent-launch-restart-replay.test.ts
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/federation/federation-output.test.ts
2 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/fleet-status-observed-identity.test.ts
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/worker-interactive-wait.test.ts
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/worker-output.test.ts
2 test-no-wall-clock:src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts
2 test-no-wall-clock:src/main/runtime/rpc/server/runtime-rpc-pairing-mode-persistence.test.ts
7 test-no-wall-clock:src/main/runtime/rpc/server/runtime-rpc-relay-pairing.test.ts
6 test-no-wall-clock:src/main/runtime/rpc/server/runtime-rpc-relay-pairing.test.ts
1 test-no-wall-clock:src/main/runtime/session-construction/agent-session/orca-runtime-agent-session-operation.test.ts
1 test-no-wall-clock:src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts
1 test-no-wall-clock:src/main/runtime/terminal/adoption/terminal-subscriber-driven-daemon-attach.test.ts
@@ -515,7 +497,6 @@
3 test-no-wall-clock:src/main/runtime/terminal/query/orca-runtime-codex-idle-title-chat-address.test.ts
1 test-no-wall-clock:src/main/runtime/terminal/recovery/terminal-pane-recovery-liveness-gate.test.ts
16 test-no-wall-clock:src/main/runtime/terminal/wait/tui-idle-hook-lane-runtime.test.ts
4 test-no-wall-clock:src/main/runtime/terminal/wait/tui-idle-name-only-real-pty.integration.test.ts
1 test-no-wall-clock:src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts
1 test-no-wall-clock:src/main/runtime/workspaces/removal/owned-session-teardown-policy.test.ts
6 test-no-wall-clock:src/main/skills/install/skill-bundle-process-termination.integration.test.ts
@@ -528,12 +509,9 @@
2 test-no-wall-clock:src/main/skills/transaction-state/skill-transaction-startup-recovery.test.ts
4 test-no-wall-clock:src/main/sqlite/sync-database.test.ts
4 test-no-wall-clock:src/main/ssh/remote-exec/ssh-remote-commands.test.ts
2 test-no-wall-clock:src/main/star-nag/service-direct-star.test.ts
3 test-no-wall-clock:src/main/star-nag/service-threshold-prompt.test.ts
1 test-no-wall-clock:src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts
1 test-no-wall-clock:src/main/startup/login-shell-environment.test.ts
2 test-no-wall-clock:src/main/startup/run-electron-vite-dev.test.ts
13 test-no-wall-clock:src/main/startup/windows-install-dir-acl-recovery.test.ts
2 test-no-wall-clock:config/scripts/development/launch/run-electron-vite-dev.test.ts
1 test-no-wall-clock:src/main/startup/shell-environment/login-shell-environment.test.ts
2 test-no-wall-clock:src/main/startup/windows-install-permissions/windows-install-dir-acl-recovery.test.ts
4 test-no-wall-clock:src/main/updater/lifecycle/serve-supervisor-install-park.test.ts
1 test-no-wall-clock:src/main/window/desktop-integration/macos-press-and-hold-default.defaults-domain.test.ts
2 test-no-wall-clock:src/main/windows/windows-host-job.win32.test.ts
@@ -547,7 +525,6 @@
3 test-no-wall-clock:src/renderer/src/components/activity/activity-auto-mark-read-loop.react185.test.tsx
2 test-no-wall-clock:src/renderer/src/components/artifacts/ArtifactCollection.test.tsx
1 test-no-wall-clock:src/renderer/src/components/browser-pane/assemble-chrome/browser-workspace-pane.adoption-chrome.test.tsx
3 test-no-wall-clock:src/renderer/src/components/native-chat/conversation/turns/NativeChatResolvedView.turn-status.test.tsx
2 test-no-wall-clock:src/renderer/src/components/native-chat/tools/tasks/NativeChatStructuredSessionStatus.test.tsx
1 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/review/use-workspace-reference-details.test.tsx
1 test-no-wall-clock:src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.lineage-agent-expansion-coupling.test.tsx
@@ -578,9 +555,9 @@
4 test-no-wall-clock:tests/e2e/cross-version-wire/release-checkout.unit.test.ts
2 test-no-wall-clock:tests/e2e/cross-version-wire/release-source-retention.unit.test.ts
1 test-no-wall-clock:tests/e2e/host-cold-park-remote-subscriber.unit.test.ts
1 test-timeout-needs-reason:src/cli/agent-launch-process.test.ts
1 test-timeout-needs-reason:src/cli/agents/agent-launch-process.test.ts
1 test-timeout-needs-reason:src/cli/runtime/serve-pid1-init-reaper.test.ts
1 test-timeout-needs-reason:src/cli/skills-install.test.ts
1 test-timeout-needs-reason:src/cli/skills/skills-install.test.ts
2 test-timeout-needs-reason:src/main/acp/journal/acp-structured-broken-stream.test.ts
4 test-timeout-needs-reason:src/main/agent-hooks/server/testing/http/server-transport-interference.test.ts
1 test-timeout-needs-reason:src/main/agent-hooks/wsl-relay/testing/wsl-hook-relay-live.integration.test.ts
@@ -598,7 +575,6 @@
1 test-timeout-needs-reason:src/main/browser/guest-lifecycle/find/browser-find-frame-traversal.electron.test.ts
1 test-timeout-needs-reason:src/main/browser/identity/wire-probes/browser-session-ua-wire-identity-cross-context.electron.test.ts
1 test-timeout-needs-reason:src/main/browser/identity/wire-probes/browser-session-ua-wire-identity.electron.test.ts
1 test-timeout-needs-reason:src/main/claude/child-process/claude-agent-sdk-exit-proof.test.ts
2 test-timeout-needs-reason:src/main/codex/app-server/codex-app-server-teardown.integration.test.ts
1 test-timeout-needs-reason:src/main/codex/hooks/installation/hook-service-legacy-cleanup.test.ts
1 test-timeout-needs-reason:src/main/codex/structured/catalog/codex-model-catalog-probe.test.ts
@@ -627,34 +603,31 @@
4 test-timeout-needs-reason:src/main/managed-server/lifecycle/orcad-managed-tunnel-transport-real-openssh.test.ts
3 test-timeout-needs-reason:src/main/native-chat/agent-model-catalog/agent-model-catalog-probe-runner.test.ts
1 test-timeout-needs-reason:src/main/notebook/notebook-kernel.test.ts
1 test-timeout-needs-reason:src/main/orcad/orcad-bundle-native-load-order.test.ts
1 test-timeout-needs-reason:src/main/orcad/orcad-packaged-node-pty.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/orcad-packaged-structured-chat.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/orcad-serve-parity.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/orcad-stop-request-shutdown.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/orcad-windows-conpty-breakaway.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/native-runtime/orcad-bundle-native-load-order.test.ts
1 test-timeout-needs-reason:src/main/orcad/native-runtime/orcad-packaged-node-pty.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/testing/structured-chat/orcad-packaged-structured-chat.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/testing/serve-lifecycle/orcad-serve-parity.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/host-lifecycle/managed-stop/orcad-stop-request-shutdown.integration.test.ts
1 test-timeout-needs-reason:src/main/orcad/native-runtime/orcad-windows-conpty-breakaway.integration.test.ts
1 test-timeout-needs-reason:src/main/persistence/applying-settings/persistence-source-control-ai-migration.test.ts
2 test-timeout-needs-reason:src/main/providers/agent-foreground-process-git-bash.win32.test.ts
1 test-timeout-needs-reason:src/main/providers/local-pty/local-pty-child-process-verdict.test.ts
2 test-timeout-needs-reason:src/main/providers/startup-line-typed-length.live-shell.test.ts
5 test-timeout-needs-reason:src/main/pty/process-retirement/node-pty-master-fd-retirement.test.ts
1 test-timeout-needs-reason:src/main/runtime/mobile-cloud-relay/mobile-relay-e2ee.integration.test.ts
1 test-timeout-needs-reason:src/main/runtime/mobile-cloud-relay/testing/mobile-relay-e2ee.integration.test.ts
1 test-timeout-needs-reason:src/main/runtime/rpc/server/orchestration-cli-subprocess.test.ts
2 test-timeout-needs-reason:src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts
1 test-timeout-needs-reason:src/main/runtime/rpc/ws-transport.test.ts
1 test-timeout-needs-reason:src/main/runtime/session-construction/agent-session/remote-agent-session-host-authority.integration.test.ts
1 test-timeout-needs-reason:src/main/runtime/terminal/launch/qoder-mobile-resume-adoption.test.ts
2 test-timeout-needs-reason:src/main/runtime/terminal/wait/launched-agent-crash-guard.live-shell.test.ts
3 test-timeout-needs-reason:src/main/runtime/terminal/wait/terminal-interactive-wait-visibility.test.ts
2 test-timeout-needs-reason:src/main/runtime/terminal/wait/terminal-interactive-wait-visibility.test.ts
1 test-timeout-needs-reason:src/main/runtime/terminal/wait/terminal-wait-name-only-idle.test.ts
4 test-timeout-needs-reason:src/main/runtime/terminal/wait/tui-idle-hook-lane-runtime.test.ts
3 test-timeout-needs-reason:src/main/runtime/terminal/wait/tui-idle-name-only-real-pty.integration.test.ts
1 test-timeout-needs-reason:src/main/skills/install/skill-bundle-process-termination.integration.test.ts
1 test-timeout-needs-reason:src/main/sqlite/sync-database-concurrent-backup.test.ts
2 test-timeout-needs-reason:src/main/ssh/connection/ssh-connection-sftp-wire.test.ts
2 test-timeout-needs-reason:src/main/ssh/system-transport/forwarding/system-ssh-forward-process-real-openssh.test.ts
2 test-timeout-needs-reason:src/main/startup/single-instance-lock-exit.electron.test.ts
1 test-timeout-needs-reason:src/main/window/renderer-publication-throttle.electron.test.ts
1 test-timeout-needs-reason:src/main/window/visibility/renderer-publication-throttle.electron.test.ts
1 test-timeout-needs-reason:src/main/windows/windows-administrator-restart.win32.test.ts
1 test-timeout-needs-reason:src/main/windows/windows-host-job.win32.test.ts
1 test-timeout-needs-reason:src/main/windows/windows-msys-job.win32.test.ts
@@ -725,7 +698,7 @@
1 test-timeout-needs-reason:tests/e2e/markdown-review-selection-performance.spec.ts
1 test-timeout-needs-reason:tests/e2e/mobile-subscribe-idle-hidden-pane-fit.spec.ts
1 test-timeout-needs-reason:tests/e2e/orcad-serve-open-url-on-desktop.spec.ts
10 test-timeout-needs-reason:tests/e2e/orchestration-idle-mail-delivery.spec.ts
9 test-timeout-needs-reason:tests/e2e/orchestration-idle-mail-delivery.spec.ts
1 test-timeout-needs-reason:tests/e2e/orchestration-idle-mail-restore.spec.ts
1 test-timeout-needs-reason:tests/e2e/orchestration-legacy-worker-missing-terminal-recovery.spec.ts
1 test-timeout-needs-reason:tests/e2e/orchestration-worker-settlement-release-cli.spec.ts
+2 -2
View File
@@ -41,7 +41,7 @@
"src/main/plugins/testing/plugin-test-hooks.ts",
"src/main/plugins/testing/plugin-test-register.ts",
"src/main/ai-vault/scan-service/session-scanner-service-entry.ts",
"src/main/native-chat/wsl-transcript-fs-process-entry.ts",
"src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts",
"src/main/usage/scan-worker/usage-scan-worker-entry.ts",
"src/main/workspaces/space-analysis/workspace-space-scan-worker-entry.ts",
"src/main/claude-accounts/profiles/setup/claude-profile-setup-worker-entry.ts",
@@ -57,7 +57,7 @@
"src/**/*-electron-fixture.ts",
"src/**/*-test-fixture.ts",
"src/**/*.test-fixture.{ts,tsx}",
"src/main/runtime/rpc/rpc-params-type-parity.ts",
"src/main/runtime/rpc/dispatch/rpc-params-type-parity.ts",
"src/main/runtime/rpc/methods/client-state/ui-state-schema-parity-checks.ts",
"src/renderer/src/components/editor/monaco-internal-test-module-types.ts",
"mobile/**/*.{ts,tsx}",
+1 -1
View File
@@ -1,6 +1,6 @@
# Legacy synchronous atomics uses per file; counts may only go DOWN.
# Only explicit worker/utility entry files are exempt; their imports remain guarded.
1 src/main/runtime/rpc/server/pairing/device-registry-disk-merge.ts
1 src/main/startup/ensure-virtual-display.ts
1 src/main/startup/linux-display/ensure-virtual-display.ts
1 src/shared/windows-retry-file-operations.ts
1 src/shared/windows-transient-lock-removal.ts
@@ -1,7 +1,7 @@
# Legacy synchronous compression uses per file; counts may only go DOWN.
# Only explicit worker/utility entry files are exempt; their imports remain guarded.
1 src/main/browser/extensions/actions/browser-extension-action-icon.ts
1 src/main/runtime/rpc/mobile-e2ee-v2-compressed-frame.ts
1 src/main/runtime/rpc/mobile/e2ee/mobile-e2ee-v2-compressed-frame.ts
1 src/main/ssh/remote-exec/ssh-posix-command-wrapper.ts
2 src/main/ssh/remote-exec/ssh-remote-powershell.ts
1 src/shared/remote-runtime/e2ee/node/paired-e2ee-node-compressed-frame.ts
+3 -3
View File
@@ -50,9 +50,9 @@
1 src/main/native-chat/agent-session-journal/writing/command-receipt-schema.ts
8 src/main/native-chat/agent-session-journal/writing/command-receipt-table.ts
2 src/main/native-chat/agent-session-journal/writing/journal-goal-attachment-inverse.ts
10 src/main/native-chat/transcript-opencode-sqlite-query.ts
14 src/main/native-chat/transcript-opencode2-fork-coverage.ts
10 src/main/native-chat/transcript-opencode2-sqlite.ts
10 src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.ts
14 src/main/native-chat/transcript-history/opencode/transcript-opencode2-fork-coverage.ts
10 src/main/native-chat/transcript-history/opencode/transcript-opencode2-sqlite.ts
10 src/main/opencode-usage/opencode-usage-row-queries.ts
1 src/main/opencode-usage/scanner.ts
12 src/main/persistence/moving-wsl-projects/desktop-state/wsl-move-orchestration-history-rewrite.ts
+1 -1
View File
@@ -18,7 +18,7 @@
"src/main/plugins/workers/plugin-host-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
"src/main/computer/computer-sidecar.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
"src/main/ai-vault/scan-service/session-scanner-service-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
"src/main/native-chat/wsl-transcript-fs-process-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
"src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
"src/main/ipc/filesystem/watcher-process/parcel-watcher-process-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
"src/main/orcad/orcad-local-serve-selection-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main."
}
+1 -1
View File
@@ -47,7 +47,7 @@
5 src/main/persistence/restoring-sessions/session-owner-fields.ts
12 src/main/persistence/restoring-sessions/session-owner-removal.ts
2 src/main/persistence/restoring-sessions/workspace-pane-normalization.ts
4 src/main/persistence/terminal-topology/terminal-topology-membership.ts
4 src/main/persistence/terminal-topology/pane-ownership/terminal-topology-membership.ts
1 src/main/persistence/tracking-repos/project-group-operations.ts
4 src/main/persistence/tracking-repos/repo-worktree-pruning.ts
3 src/main/persistence/tracking-repos/worktree-identity-migration.ts
+56 -57
View File
@@ -119,7 +119,6 @@
1 src/main/codex-accounts/managed-homes/guest-codex-account-homes.ts
6 src/main/codex-accounts/managed-homes/host-codex-managed-home-ownership.ts
2 src/main/codex-accounts/runtime-home/legacy-host/legacy-shared-auth-migration.ts
21 src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
36 src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-script-harness.ts
6 src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-script-interference-shims.ts
1 src/main/codex-accounts/credentials/managed-codex-auth-readiness.ts
@@ -317,8 +316,8 @@
4 src/main/muse/hook-service.ts
2 src/main/native-chat/agent-session-attachments/agent-session-attachment-claims.ts
1 src/main/native-chat/agent-session-journal/database/journal-host-database.ts
1 src/main/native-chat/host-readable-transcript-path.ts
2 src/main/native-chat/wsl-transcript-fs-process-spawn.ts
1 src/main/native-chat/transcript-discovery/host-readable-transcript-path.ts
2 src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts
1 src/main/node-runtime/appimage-bundled-node-runtime.ts
2 src/main/node-runtime/managed-runtime-release-install.ts
7 src/main/node-runtime/managed-runtime-release.ts
@@ -343,39 +342,39 @@
2 src/main/orca-profiles/storage/new-profile-state.ts
5 src/main/orca-profiles/storage/profile-legacy-state-import.ts
2 src/main/orcad/launcher.ts
7 src/main/orcad/node-pty-prebuilt-slot.ts
2 src/main/orcad/node-pty-precondition.ts
2 src/main/orcad/orcad-agent-browser-binary.ts
1 src/main/orcad/orcad-app-paths.ts
1 src/main/orcad/orcad-artifact-identity.ts
2 src/main/orcad/orcad-artifact-materializer.ts
12 src/main/orcad/orcad-bundled-runtime.ts
2 src/main/orcad/orcad-cli-launcher.ts
1 src/main/orcad/orcad-command-arguments.ts
1 src/main/orcad/orcad-completed-stop-receipt.ts
2 src/main/orcad/orcad-daemon-slot-pins.ts
2 src/main/orcad/orcad-data-root-locality.ts
3 src/main/orcad/orcad-data-root-privacy.ts
4 src/main/orcad/orcad-host-account-launch-env-store.ts
1 src/main/orcad/orcad-host-adapters.ts
1 src/main/orcad/orcad-host-identity-reset.ts
2 src/main/orcad/orcad-idle-stop-record.ts
3 src/main/orcad/orcad-instance-lock-hold.ts
16 src/main/orcad/orcad-instance-lock-reclaim.ts
4 src/main/orcad/orcad-instance-lock.ts
7 src/main/orcad/orcad-local-serve-selection.ts
1 src/main/orcad/orcad-lock-clone-machine.ts
1 src/main/orcad/orcad-managed-stop-cancellation.ts
3 src/main/orcad/orcad-managed-stop-decision.ts
2 src/main/orcad/orcad-managed-stop-request.ts
4 src/main/orcad/orcad-plaintext-credential-migration.ts
5 src/main/orcad/orcad-process-start-time.ts
11 src/main/orcad/orcad-secret-key-record.ts
3 src/main/orcad/orcad-stale-lock-reclaim.ts
1 src/main/orcad/orcad-stop-request-listener.ts
1 src/main/orcad/orcad-wsl-holder-release.ts
2 src/main/orcad/orcad-wsl-host-claude-profile-refresh.ts
1 src/main/orcad/serve-profile-ssh-targets.ts
7 src/main/orcad/native-runtime/node-pty-prebuilt-slot.ts
2 src/main/orcad/native-runtime/node-pty-precondition.ts
2 src/main/orcad/browser-providers/orcad-agent-browser-binary.ts
1 src/main/orcad/host-runtime/orcad-app-paths.ts
1 src/main/orcad/artifact-slots/orcad-artifact-identity.ts
2 src/main/orcad/artifact-slots/orcad-artifact-materializer.ts
12 src/main/orcad/runtime-launch/orcad-bundled-runtime.ts
2 src/main/orcad/serve-startup/orcad-cli-launcher.ts
1 src/main/orcad/serve-startup/orcad-command-arguments.ts
1 src/main/orcad/host-lifecycle/managed-stop/orcad-completed-stop-receipt.ts
2 src/main/orcad/artifact-slots/orcad-daemon-slot-pins.ts
2 src/main/orcad/instance-lock/orcad-data-root-locality.ts
3 src/main/orcad/instance-lock/orcad-data-root-privacy.ts
4 src/main/orcad/account-launch/orcad-host-account-launch-env-store.ts
1 src/main/orcad/host-runtime/orcad-host-adapters.ts
1 src/main/orcad/profile-state/orcad-host-identity-reset.ts
2 src/main/orcad/host-lifecycle/idle-exit/orcad-idle-stop-record.ts
3 src/main/orcad/instance-lock/orcad-instance-lock-hold.ts
16 src/main/orcad/instance-lock/orcad-instance-lock-reclaim.ts
4 src/main/orcad/instance-lock/orcad-instance-lock.ts
7 src/main/orcad/serve-startup/orcad-local-serve-selection.ts
1 src/main/orcad/instance-lock/orcad-lock-clone-machine.ts
1 src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-cancellation.ts
3 src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-decision.ts
2 src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-request.ts
4 src/main/orcad/credentials/orcad-plaintext-credential-migration.ts
5 src/main/orcad/instance-lock/orcad-process-start-time.ts
11 src/main/orcad/credentials/orcad-secret-key-record.ts
3 src/main/orcad/instance-lock/orcad-stale-lock-reclaim.ts
1 src/main/orcad/host-lifecycle/managed-stop/orcad-stop-request-listener.ts
1 src/main/orcad/instance-lock/orcad-wsl-holder-release.ts
2 src/main/orcad/account-launch/orcad-wsl-host-claude-profile-refresh.ts
1 src/main/orcad/profile-state/serve-profile-ssh-targets.ts
6 src/main/persistence/atomic-snapshot-writer.ts
5 src/main/persistence/loading-store/user-data-path.ts
4 src/main/persistence/migrating-orcad-catalog/blob-transfer/orcad-migration-blob-installers.ts
@@ -477,8 +476,8 @@
1 src/main/runtime/client/navigation/desktop-chat-creation-history.ts
9 src/main/runtime/host-instance-identity.ts
4 src/main/runtime/hosts/platform-probe/host-descriptor.ts
5 src/main/runtime/mobile-cloud-relay/relay-region-preference.ts
2 src/main/runtime/mobile-cloud-relay/relay-revoke-outbox.ts
5 src/main/runtime/mobile-cloud-relay/region-selection/relay-region-preference.ts
2 src/main/runtime/mobile-cloud-relay/pairing/relay-revoke-outbox.ts
1 src/main/runtime/orchestration/db/runtime-orchestration-database-access.ts
2 src/main/runtime/push/push-unregister-outbox.ts
1 src/main/runtime/rpc/methods/mobile-delivery/bundled-mobile-web-bundle.ts
@@ -489,8 +488,8 @@
3 src/main/runtime/rpc/server/pairing/e2ee-keypair.ts
3 src/main/runtime/rpc/server/runtime-metadata.ts
2 src/main/runtime/rpc/server/runtime-rpc-socket-metadata.ts
5 src/main/runtime/rpc/unix-socket-transport.ts
3 src/main/runtime/rpc/ws-fallback-port-store.ts
5 src/main/runtime/rpc/transports/unix-socket-transport.ts
3 src/main/runtime/rpc/transports/websocket/ws-fallback-port-store.ts
2 src/main/runtime/terminal/launch/claude-agent-teams-shim-env.ts
1 src/main/runtime/workspaces/persisted-surfaces/crew-operations.ts
1 src/main/server/serve-pairing-output.ts
@@ -516,24 +515,24 @@
3 src/main/ssh/system-transport/file-transfer/system-ssh-sftp-transfer.ts
3 src/main/ssh/system-transport/ssh-control-socket.ts
3 src/main/ssh/system-transport/system-ssh-binary.ts
3 src/main/startup/cli-launch-redirect.ts
3 src/main/startup/cli-launch/cli-launch-redirect.ts
2 src/main/startup/configure-process.ts
4 src/main/startup/gpu-fallback-marker.ts
2 src/main/startup/http1-compatibility-marker.ts
5 src/main/startup/http1-compatibility-profile-state.ts
1 src/main/startup/linux-dev-shm-policy.ts
4 src/main/startup/linux-realtime-scheduling-guard.ts
1 src/main/startup/linux-rttime-limit.ts
2 src/main/startup/profile-state-recovery-preflight.ts
1 src/main/startup/startup-diagnostics.ts
1 src/main/startup/windows-app-data-path.ts
5 src/main/startup/windows-install-dir-acl-poison-marker.ts
1 src/main/startup/windows-install-dir-acl-probe.ts
1 src/main/startup/windows-install-dir-package-acl-grant.ts
4 src/main/startup/windows-install-dir-package-acl-repair.ts
3 src/main/startup/windows-renderer-runtime-files.ts
3 src/main/startup/windows-user-data-acl.ts
6 src/main/startup/x-display-artifacts.ts
4 src/main/startup/gpu-recovery/gpu-fallback-marker.ts
2 src/main/startup/network-compatibility/http1-compatibility-marker.ts
5 src/main/startup/network-compatibility/http1-compatibility-profile-state.ts
1 src/main/startup/linux-runtime/linux-dev-shm-policy.ts
4 src/main/startup/linux-runtime/linux-realtime-scheduling-guard.ts
1 src/main/startup/linux-runtime/linux-rttime-limit.ts
2 src/main/startup/profile-startup/profile-state-recovery-preflight.ts
1 src/main/startup/diagnostics/startup-diagnostics.ts
1 src/main/startup/windows-startup/windows-app-data-path.ts
5 src/main/startup/windows-install-permissions/windows-install-dir-acl-poison-marker.ts
1 src/main/startup/windows-install-permissions/windows-install-dir-acl-probe.ts
1 src/main/startup/windows-install-permissions/windows-install-dir-package-acl-grant.ts
4 src/main/startup/windows-install-permissions/windows-install-dir-package-acl-repair.ts
3 src/main/startup/windows-startup/windows-renderer-runtime-files.ts
3 src/main/startup/windows-startup/windows-user-data-acl.ts
6 src/main/startup/linux-display/x-display-artifacts.ts
2 src/main/stats/stats-file-loader.ts
2 src/main/text-generation/source-control-prompt-files.ts
1 src/main/updater/lifecycle/windows-update-install-directory.ts
+4 -5
View File
@@ -9,7 +9,6 @@
1 src/main/browser/network/egress/browser-route-egress-electron-launch.ts
1 src/main/browser/routing/partitions/browser-route-partition-binding-store.ts
1 src/main/claude-accounts/runtime-auth-service.ts
2 src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
2 src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-script-harness.ts
1 src/main/codex-accounts/runtime-home/routing/runtime-home-service-home-routing.ts
1 src/main/codex-accounts/runtime-home/wsl/runtime-home-service-wsl.ts
@@ -28,8 +27,8 @@
1 src/main/ipc/worktree-logic.ts
1 src/main/network/macos-tailscale-dns-diagnostic.ts
1 src/main/opencode/status-hooks/legacy-plugin-acl-retry.ts
2 src/main/orcad/node-pty-precondition.ts
1 src/main/orcad/orcad-data-root-privacy.ts
2 src/main/orcad/native-runtime/node-pty-precondition.ts
1 src/main/orcad/instance-lock/orcad-data-root-privacy.ts
2 src/main/persistence/profile-state/access/profile-state-access-identity.ts
1 src/main/persistence/profile-state/recovery/profile-state-recovery-copy.ts
1 src/main/plugins/catalog/plugin-private-marketplace-ssh-shim.cjs
@@ -37,8 +36,8 @@
1 src/main/pty/shell-startup/pwsh.ts
2 src/main/pty/shell-startup/zsh-startup-hook-pty-harness.ts
1 src/main/runtime/integrations/skill/runtime-skill-install-authority.ts
2 src/main/startup/cli-launch-redirect.ts
1 src/main/startup/linux-secret-service-probe.ts
2 src/main/startup/cli-launch/cli-launch-redirect.ts
1 src/main/startup/linux-runtime/linux-secret-service-probe.ts
2 src/main/window/desktop-integration/macos-press-and-hold-default.ts
1 src/main/workspace-hooks/worktree-runner-script.ts
3 src/main/wsl/discovery/distro-catalog.ts
+2 -2
View File
@@ -69,7 +69,7 @@
; WSL host sees nothing here. These are the folders Electron's userData (app.getName()) can be, as the
; deleteAppData step lists them; a custom --user-data-dir can't be known here.
; Sync the flags with WSL_HOLDER_UNINSTALL_FLAG and WSL_SERVER_UNINSTALL_FLAG in
; src/main/startup/wsl-holder-uninstall-preflight.ts.
; src/main/startup/uninstall/wsl-holder-uninstall-preflight.ts.
${if} $installMode == "all"
SetShellVarContext current
${endIf}
@@ -114,7 +114,7 @@
; app, but once that folder is removed every agent fails a hook per event, so the last Orca for
; this user takes the entries out. Another install, or an unreadable registry, keeps them.
; Best effort: the exit code never blocks uninstall.
; Sync the flag with AGENT_HOOKS_UNINSTALL_FLAG in src/main/startup/agent-hooks-uninstall-preflight.ts.
; Sync the flag with AGENT_HOOKS_UNINSTALL_FLAG in src/main/startup/uninstall/agent-hooks-uninstall-preflight.ts.
!insertmacro ORCA_OTHER_INSTALL_STATE $1
${if} $1 == "none"
nsExec::Exec '"$INSTDIR\${APP_EXECUTABLE_FILENAME}" --orca-uninstall-remove-agent-hooks'
@@ -1,8 +1,8 @@
# Existing production source files importing child_process at runtime outside @orca/process-host.
# Migration debt: this list may only shrink; new callers use the package public API.
src/cli/handlers/core.ts
src/cli/handlers/interactive-login-interruption.ts
src/cli/runtime/cli-lifecycle-handlers.ts
src/cli/accounts/interactive-login-interruption.ts
src/cli/runtime/launch.ts
src/main/agent-hooks/installation/locking/managed-hook-owner-identity.ts
src/main/ai-vault/scan-service/session-scanner-service-spawn.ts
@@ -11,7 +11,6 @@ src/main/automations/precheck-runner.ts
src/main/browser/agent-browser/helper-process/agent-browser-bridge-execution.ts
src/main/browser/agent-browser/helper-process/agent-browser-bridge-raw-process.ts
src/main/browser/network/egress/browser-route-egress-electron-launch.ts
src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-script-harness.ts
src/main/codex-accounts/service.ts
src/main/computer/desktop-script/desktop-script-provider-bridge.ts
@@ -36,7 +35,7 @@ src/main/git/command-runner/process-capture/spawn-command-capture.ts
src/main/git/command-runner/process-capture/spawned-command-tree-kill.ts
src/main/git/command-runner/host-routing/wsl-aware-spawn.ts
src/main/git/command-runner/host-routing/wsl-git-read-environment.ts
src/main/github/gh-utils.ts
src/main/github/execution/github-legacy-command.ts
src/main/workspace-hooks/hooks.ts
src/main/ipc/developer-permissions.ts
src/main/ipc/filesystem/watcher-process/child-lifecycle/parcel-watcher-child-launch.ts
@@ -51,13 +50,12 @@ src/main/macos-keychain/generic-password.ts
src/main/power/macos-system-sleep-assertion.ts
src/main/macos-permissions/macos-tcc-prompt-watch.ts
src/main/memory/collector.ts
src/main/native-chat/wsl-transcript-fs-process-spawn.ts
src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-spawn.ts
src/main/network/macos-system-resolver-health.ts
src/main/network/macos-tailscale-dns-diagnostic.ts
src/main/plugins/workers/parent-process/plugin-host-process.ts
src/main/plugins/catalog/plugin-private-marketplace-ssh-shim.cjs
src/main/ports/port-scan-command-execution.ts
src/main/providers/macos-login-session-pty-probe.ts
src/main/providers/process-cwd.ts
src/main/pty/process-retirement/pty-descendant-fresh-table.ts
src/main/rate-limits/providers/gemini/gemini-cli-oauth-extractor.ts
@@ -67,7 +65,7 @@ src/main/ssh/system-transport/ssh-proxy-command.ts
src/main/ssh/system-transport/system-ssh-command.ts
src/main/ssh/system-transport/forwarding/system-ssh-dynamic-forward-process.ts
src/main/ssh/system-transport/file-transfer/system-ssh-file-transfer.ts
src/main/startup/ensure-virtual-display.ts
src/main/startup/linux-display/ensure-virtual-display.ts
src/main/pty/process-retirement/windows-process-tree-kill.ts
src/main/pty/shell-startup/zsh-startup-hook-pty-harness.ts
src/shared/ephemeral-vm-recipe-process.ts
+11 -11
View File
@@ -3,7 +3,7 @@
# collector (file-wide). Not counted: destructuring off a global object, a self-rescheduling setTimeout (it must back off or pause while hidden).
# This is a RATCHET: counts may only go DOWN.
# Prune after removing intervals: pnpm check:recurring-timer-ratchet --prune
1 src/cli/handlers/orchestration/check-keepalive.ts
1 src/cli/orchestration/messages/check-keepalive.ts
1 src/cli/runtime/mac-app-update-bundle.ts
1 src/cli/runtime/serve-supervised-service.ts
1 src/main/agent-hooks/server/server-opencode-binder.ts
@@ -38,10 +38,10 @@
1 src/main/native-chat/agent-session-wire/recovery/structured-agent-session-usage-limit-continue.ts
1 src/main/native-chat/agent-session-wire/termination/structured-agent-session-idle-sweep.ts
1 src/main/notifications/desktop-presence-reporter.ts
1 src/main/orcad/orcad-browser-install-park.ts
1 src/main/orcad/orcad-event-loop-watchdog.ts
1 src/main/orcad/orcad-lock-clone-machine.ts
1 src/main/orcad/orcad-stop-request-listener.ts
1 src/main/orcad/browser-providers/orcad-browser-install-park.ts
1 src/main/orcad/diagnostics/orcad-event-loop-watchdog.ts
1 src/main/orcad/instance-lock/orcad-lock-clone-machine.ts
1 src/main/orcad/host-lifecycle/managed-stop/orcad-stop-request-listener.ts
1 src/main/plugins/catalog/plugin-auto-updater.ts
1 src/main/plugins/catalog/plugin-kill-list-service.ts
1 src/main/plugins/installation/plugin-git-repository.ts
@@ -49,12 +49,12 @@
1 src/main/rate-limits/service/service-polling.ts
1 src/main/runtime/agent-state-rules/agent-state-rules-live-update.ts
1 src/main/runtime/mobile-cloud-relay/desktop-relay-service.ts
3 src/main/runtime/mobile-cloud-relay/relay-control-liveness.ts
3 src/main/runtime/mobile-cloud-relay/control-channel/relay-control-liveness.ts
1 src/main/runtime/rpc/methods/orchestration/messaging/check-run.ts
1 src/main/runtime/rpc/mobile-socket-wiring.ts
1 src/main/runtime/rpc/remote-runtime-server-heartbeat.ts
1 src/main/runtime/rpc/mobile/mobile-socket-wiring.ts
1 src/main/runtime/rpc/transports/websocket/remote-runtime-server-heartbeat.ts
1 src/main/runtime/rpc/server/runtime-metadata-ownership-watch.ts
1 src/main/runtime/rpc/unix-socket-transport.ts
1 src/main/runtime/rpc/transports/unix-socket-transport.ts
1 src/main/runtime/sidebar-inputs/paired-server-rows-producer.ts
1 src/main/runtime/terminal/input/agent-prompt-permission-hold.ts
1 src/main/runtime/terminal/output/terminal-derived-agent-facts.ts
@@ -64,8 +64,8 @@
1 src/main/ssh/port-forwarding/ssh-tcp-forwarding-probe.ts
1 src/main/ssh/protocol/ssh-channel-multiplexer.ts
1 src/main/startup/configure-process.ts
1 src/main/startup/event-loop-stall-probe.ts
1 src/main/startup/synthetic-title-runtime.ts
1 src/main/startup/diagnostics/event-loop-stall-probe.ts
1 src/main/startup/main-window/synthetic-title-runtime.ts
1 src/main/telemetry/wsl-usage-reporter.ts
1 src/main/wsl/host-lifecycle/wsl-hosts-policy-live-update.ts
1 src/main/wsl/orcad/wsl-orcad-connect-stop-fence.ts
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -2,7 +2,7 @@
# This is a RATCHET: counts may only go DOWN. Resolve the host instead (see fix text).
# A lower bound: destructured and differently named rows are not counted.
# Prune after a fix: pnpm check:repo-host-read-ratchet --prune
1 src/main/github/account-binding-required-host.ts
1 src/main/github/accounts/account-binding-required-host.ts
3 src/main/ipc/ephemeral-vm-recipe-context.ts
2 src/main/ipc/filesystem/filesystem-source-control-ai-targets.ts
1 src/main/ipc/folder-repo-git-upgrade.ts
@@ -46,7 +46,7 @@
1 src/main/workspaces/space-analysis/workspace-space-worktree-row.ts
1 src/shared/endpoint-catalog-adoption.ts
1 src/shared/folder-workspaces/folder-workspace-execution-host.ts
1 src/shared/github/github-cache-key.ts
1 src/shared/github/cache/github-cache-key.ts
1 src/shared/new-workspace-dialog-repo.ts
1 src/shared/sidebar-list/workspace-review-facts.ts
1 src/shared/workspace-session/workspace-session-terminal-buffers.ts
+95 -147
View File
@@ -180,7 +180,6 @@
"config/scripts/desktop-distribution/linux/package-linux-formats-appimage.test.mjs": 624,
"config/scripts/desktop-distribution/linux/package-linux-formats.test.mjs": 282,
"config/scripts/desktop-distribution/runtime-verification/packaged-node-pty-prebuild-prune.test.mjs": 74,
"config/scripts/orcad-distribution/template/packaged-orcad-browser.test.mjs": 407,
"config/scripts/desktop-distribution/runtime-verification/packaged-process-host.test.mjs": 537,
"config/scripts/desktop-distribution/runtime-verification/packaged-repository-input-exclusion.test.mjs": 222,
"config/scripts/desktop-distribution/runtime-verification/packaged-source-map-prune.test.mjs": 73,
@@ -224,7 +223,6 @@
"config/scripts/release/versioning/release-rc-history.test.mjs": 230,
"config/scripts/release/workflow-contracts/release-windows-signing-gate.test.mjs": 102,
"config/scripts/static-analysis/oxlint-contracts/renderer-scrollbar-style-plugin.test.mjs": 234,
"config/scripts/static-analysis/renderer-styles/renderer-tailwind-sources.test.mjs": 289,
"config/scripts/desktop-distribution/nsis/replace-cached-nsis-elevate.test.mjs": 1258,
"config/scripts/ci/workflow-contracts/required-pr-check-name.test.mjs": 1266,
"config/scripts/archives/seven-zip/resolve-7za-path.test.mjs": 1391,
@@ -1270,7 +1268,6 @@
"src/main/codex/hooks/definitions/codex-real-home-hook-key-spellings.test.ts": 408,
"src/main/codex/hooks/reconciliation/codex-real-home-hook-removal.test.ts": 388,
"src/main/codex/configuration/homes/codex-real-home-path.test.ts": 76,
"src/main/codex/hooks/reconciliation/codex-real-home-shared-entry.test.ts": 641,
"src/main/codex/history/backfill/codex-session-backfill.test.ts": 310,
"src/main/codex/history/bridges/codex-session-bridge.test.ts": 118,
"src/main/codex/history/index/codex-session-index-heal-state.test.ts": 363,
@@ -1360,7 +1357,6 @@
"src/main/codex/hooks/installation/hook-service-managed-codex-hashes.test.ts": 1048,
"src/main/codex/hooks/installation/hook-service-managed-install.test.ts": 492,
"src/main/codex/hooks/installation/hook-service-runtime-trust-repair.test.ts": 475,
"src/main/codex/hooks/installation/hook-service-trust-grant.test.ts": 318,
"src/main/codex/hooks/installation/hook-service-user-hook-mirroring.test.ts": 596,
"src/main/codex/hooks/installation/hook-service-user-hook-trust-spellings.test.ts": 657,
"src/main/codex/hooks/installation/hook-service-windows-key-spellings.test.ts": 461,
@@ -1556,7 +1552,6 @@
"src/main/daemon/pty-subprocess/testing/pty-subprocess-foreground-identity.test.ts": 818,
"src/main/daemon/pty-subprocess/testing/pty-subprocess-handle-lifecycle.test.ts": 449,
"src/main/daemon/pty-subprocess/testing/pty-subprocess-io-failure-cleanup.test.ts": 1127,
"src/main/daemon/pty-subprocess/testing/pty-subprocess-io-failure-native.test.ts": 3285,
"src/main/daemon/pty-subprocess/testing/pty-subprocess-spawn-file-foreground.test.ts": 528,
"src/main/daemon/pty-subprocess/testing/pty-subprocess-windows-shell-launch.test.ts": 459,
"src/main/daemon/pty-subprocess/testing/pty-subprocess.test.ts": 431,
@@ -1932,7 +1927,7 @@
"src/main/host/secret-protection-report.test.ts": 59,
"src/main/i18n/main-i18n-lazy-locale.test.ts": 518,
"src/main/i18n/system-language.test.ts": 358,
"src/main/media/image-file-signature.test.ts": 47,
"src/shared/image-file-signature.test.ts": 47,
"src/main/credentials/integration-state-file-cache.test.ts": 47,
"src/main/credentials/integration-token-file-cache.test.ts": 152,
"src/main/ipc/account-sign-in-link-opener.test.ts": 128,
@@ -2285,7 +2280,7 @@
"src/main/native-chat/agent-session-attachments/agent-session-attachment-claims.test.ts": 971,
"src/main/native-chat/agent-session-attachments/agent-session-attachment-store.test.ts": 340,
"src/main/native-chat/agent-session-attachments/agent-session-attachment-sweep.test.ts": 948,
"src/main/native-chat/agent-session-context-snapshots.test.ts": 65,
"src/main/native-chat/conversation-context/agent-session-context-snapshots.test.ts": 65,
"src/main/native-chat/agent-session-journal/history-import/adopted-directory-probe.test.ts": 153,
"src/main/native-chat/agent-session-journal/database/journal-checkpoint-attachment-sweep.test.ts": 8790,
"src/main/native-chat/agent-session-journal/history-import/journal-codex-history.test.ts": 786,
@@ -2399,37 +2394,37 @@
"src/main/native-chat/agent-session-wire/host/testing/structured-conversation-command-child-work.test.ts": 1810,
"src/main/native-chat/agent-session-wire/host/testing/structured-conversation-command.test.ts": 2989,
"src/main/native-chat/agent-session-wire/rewind/testing/structured-rewind-cut.test.ts": 672,
"src/main/native-chat/host-readable-transcript-path.test.ts": 150,
"src/main/native-chat/transcript-discovery/host-readable-transcript-path.test.ts": 150,
"src/main/native-chat/native-chat-inline-visuals-setting.test.ts": 751,
"src/main/native-chat/native-chat-scratch-folder-migration.test.ts": 109,
"src/main/native-chat/native-chat-visual-file-read.test.ts": 98,
"src/main/native-chat/native-chat-visuals-sweep.test.ts": 123,
"src/main/native-chat/conversation-files/native-chat-scratch-folder-migration.test.ts": 109,
"src/main/native-chat/conversation-files/native-chat-visual-file-read.test.ts": 98,
"src/main/native-chat/conversation-files/native-chat-visuals-sweep.test.ts": 123,
"src/main/native-chat/session-file-resolver-wsl-scan-gate.test.ts": 156,
"src/main/native-chat/session-file-resolver.test.ts": 245,
"src/main/native-chat/structured-agent-command-resolution.test.ts": 223,
"src/main/native-chat/structured-agent-configured-args.test.ts": 191,
"src/main/native-chat/transcript-claude-read-image.test.ts": 389,
"src/main/native-chat/transcript-file-version.test.ts": 121,
"src/main/native-chat/agent-launch/structured-agent-command-resolution.test.ts": 223,
"src/main/native-chat/agent-launch/structured-agent-configured-args.test.ts": 191,
"src/shared/native-chat/tools/native-chat-tool-viewed-images-shell-read.test.ts": 389,
"src/main/native-chat/transcript-watching/transcript-file-version.test.ts": 121,
"src/main/native-chat/transcript-interruption-message.test.ts": 263,
"src/main/native-chat/transcript-line-decoders-claude-command-output.test.ts": 248,
"src/main/native-chat/transcript-line-decoders-claude-control-bytes.test.ts": 237,
"src/main/native-chat/transcript-line-decoders-claude-image-companion.test.ts": 238,
"src/main/native-chat/transcript-line-decoders-claude-queued-prompt.test.ts": 255,
"src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-control-bytes.test.ts": 237,
"src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-image-companion.test.ts": 238,
"src/main/native-chat/transcript-decoding/transcript-line-decoders-claude-queued-prompt.test.ts": 255,
"src/main/native-chat/transcript-line-decoders-claude-stop-hook.test.ts": 211,
"src/main/native-chat/transcript-line-decoders.grok.test.ts": 283,
"src/main/native-chat/transcript-line-decoders.omp.test.ts": 297,
"src/main/native-chat/transcript-opencode-sqlite-query.test.ts": 886,
"src/main/native-chat/transcript-decoding/transcript-line-decoders.grok.test.ts": 283,
"src/main/native-chat/transcript-decoding/transcript-line-decoders.omp.test.ts": 297,
"src/main/native-chat/transcript-history/opencode/transcript-opencode-sqlite-query.test.ts": 886,
"src/main/native-chat/transcript-read-cache.test.ts": 513,
"src/main/native-chat/transcript-reader.test.ts": 565,
"src/main/native-chat/transcript-stream-lines.test.ts": 79,
"src/main/native-chat/transcript-turn-lifecycle.test.ts": 258,
"src/main/native-chat/transcript-streaming/transcript-stream-lines.test.ts": 79,
"src/main/native-chat/transcript-decoding/transcript-turn-lifecycle.test.ts": 258,
"src/main/native-chat/transcript-watch-engine-wsl-lifecycle.test.ts": 508,
"src/main/native-chat/transcript-watch-error.test.ts": 2306,
"src/main/native-chat/transcript-watch-unflushed-settle.test.ts": 554,
"src/main/native-chat/transcript-window-tool-attribution.test.ts": 523,
"src/main/native-chat/transcript-zcode.test.ts": 814,
"src/main/native-chat/wsl-transcript-fs-process-operations.test.ts": 43,
"src/main/native-chat/wsl-transcript-running-admission.test.ts": 1178,
"src/main/native-chat/transcript-history/opencode/transcript-zcode.test.ts": 814,
"src/main/native-chat/transcript-filesystem/process/wsl-transcript-fs-process-operations.test.ts": 43,
"src/main/native-chat/transcript-filesystem/wsl-transcript-running-admission.test.ts": 1178,
"src/main/native-chat/wsl-transcript-running-observer.test.ts": 103,
"src/main/workspaces/removal/nested-worktree-removal-plan.test.ts": 242,
"src/main/node-runtime/managed-runtime-installation.test.ts": 427,
@@ -2463,46 +2458,40 @@
"src/main/orca-profiles/project-transfer/profile-project-transfer-migration.test.ts": 1642,
"src/main/orca-profiles/project-transfer/profile-project-transfer.test.ts": 1456,
"src/main/orca-profiles/profile-telemetry-consent-seed.test.ts": 536,
"src/main/orcad/electron-serve-provider-selection.test.ts": 554,
"src/main/orcad/electron-sidecar-tab-registry.test.ts": 105,
"src/main/orcad/external-chromium-browser-session.test.ts": 152,
"src/main/orcad/node-pty-prebuilt-slot.test.ts": 185,
"src/main/orcad/node-pty-precondition.test.ts": 477,
"src/main/orcad/orcad-agent-session-claim-signer.test.ts": 64,
"src/main/orcad/orcad-app-paths.test.ts": 45,
"src/main/orcad/orcad-artifact-cache-retention.test.ts": 301,
"src/main/orcad/orcad-artifact-materializer.test.ts": 1129,
"src/main/orcad/browser-providers/electron/electron-sidecar-tab-registry.test.ts": 105,
"src/main/orcad/browser-providers/chromium/external-chromium-browser-session.test.ts": 152,
"src/main/orcad/native-runtime/node-pty-prebuilt-slot.test.ts": 185,
"src/main/orcad/native-runtime/node-pty-precondition.test.ts": 477,
"src/main/orcad/host-runtime/orcad-app-paths.test.ts": 45,
"src/main/orcad/artifact-slots/orcad-artifact-cache-retention.test.ts": 301,
"src/main/orcad/artifact-slots/orcad-artifact-materializer.test.ts": 1129,
"src/main/orcad/orcad-bind-address.test.ts": 41,
"src/main/orcad/orcad-browser-install-park.test.ts": 131,
"src/main/orcad/orcad-browser-provider.test.ts": 479,
"src/main/orcad/orcad-bundle-native-load-order.test.ts": 3555,
"src/main/orcad/orcad-chat-restart-offer.test.ts": 3691,
"src/main/orcad/orcad-cli-launcher.integration.test.ts": 390,
"src/main/orcad/orcad-cli-launcher.test.ts": 232,
"src/main/orcad/browser-providers/orcad-browser-provider.test.ts": 479,
"src/main/orcad/native-runtime/orcad-bundle-native-load-order.test.ts": 3555,
"src/main/orcad/serve-startup/orcad-cli-launcher.integration.test.ts": 390,
"src/main/orcad/serve-startup/orcad-cli-launcher.test.ts": 232,
"src/main/orcad/orcad-cli-orchestration.integration.test.ts": 214,
"src/main/orcad/orcad-forked-child-paths.test.ts": 337,
"src/main/orcad/orcad-help.integration.test.ts": 5016,
"src/main/orcad/orcad-idle-exit-monitor.test.ts": 106,
"src/main/orcad/orcad-instance-lock.test.ts": 315,
"src/main/orcad/orcad-launcher-isolation.integration.test.ts": 617,
"src/main/orcad/orcad-lifecycle-host.test.ts": 407,
"src/main/orcad/orcad-managed-idle-exit-chat-provider.test.ts": 2062,
"src/main/orcad/orcad-managed-idle-exit.test.ts": 180,
"src/main/orcad/orcad-managed-stop-cancel-race.test.ts": 218,
"src/main/orcad/orcad-managed-stop-cancellation.test.ts": 285,
"src/main/orcad/orcad-managed-stop-identity.test.ts": 270,
"src/main/orcad/orcad-managed-stop.test.ts": 1726,
"src/main/orcad/orcad-observability.test.ts": 187,
"src/main/orcad/orcad-packaged-node-pty.integration.test.ts": 78,
"src/main/orcad/orcad-packaged-structured-chat.integration.test.ts": 448,
"src/main/orcad/orcad-plaintext-credential-migration.test.ts": 223,
"src/main/orcad/orcad-process-start-time.test.ts": 77,
"src/main/orcad/orcad-secret-store.test.ts": 2281,
"src/main/orcad/orcad-serve-parity.integration.test.ts": 280,
"src/main/orcad/orcad-stale-lock-reclaim.test.ts": 258,
"src/main/orcad/orcad-startup-qualification.test.ts": 102,
"src/main/orcad/orcad-stop-request-shutdown.integration.test.ts": 293,
"src/main/orcad/orcad-windows-conpty-breakaway.integration.test.ts": 79,
"src/main/orcad/serve-startup/orcad-help.integration.test.ts": 5016,
"src/main/orcad/host-lifecycle/idle-exit/orcad-idle-exit-monitor.test.ts": 106,
"src/main/orcad/instance-lock/orcad-instance-lock.test.ts": 315,
"src/main/orcad/runtime-launch/orcad-launcher-isolation.integration.test.ts": 617,
"src/main/orcad/host-lifecycle/orcad-lifecycle-host.test.ts": 407,
"src/main/orcad/host-lifecycle/idle-exit/orcad-managed-idle-exit.test.ts": 180,
"src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-cancel-race.test.ts": 218,
"src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-cancellation.test.ts": 285,
"src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-identity.test.ts": 270,
"src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop.test.ts": 1726,
"src/main/orcad/diagnostics/orcad-observability.test.ts": 187,
"src/main/orcad/native-runtime/orcad-packaged-node-pty.integration.test.ts": 78,
"src/main/orcad/testing/structured-chat/orcad-packaged-structured-chat.integration.test.ts": 448,
"src/main/orcad/credentials/orcad-plaintext-credential-migration.test.ts": 223,
"src/main/orcad/instance-lock/orcad-process-start-time.test.ts": 77,
"src/main/orcad/credentials/orcad-secret-store.test.ts": 2281,
"src/main/orcad/testing/serve-lifecycle/orcad-serve-parity.integration.test.ts": 280,
"src/main/orcad/instance-lock/orcad-stale-lock-reclaim.test.ts": 258,
"src/main/orcad/runtime-launch/orcad-startup-qualification.test.ts": 102,
"src/main/orcad/host-lifecycle/managed-stop/orcad-stop-request-shutdown.integration.test.ts": 293,
"src/main/orcad/native-runtime/orcad-windows-conpty-breakaway.integration.test.ts": 79,
"src/main/persistence/preferences/persistence-onboarding-language-cohort.test.ts": 1760,
"src/main/persistence/applying-settings/persistence-automation-extra-agent-args.test.ts": 2268,
"src/main/persistence/applying-settings/persistence-automation-mode-precondition.test.ts": 1595,
@@ -2529,7 +2518,6 @@
"src/main/persistence/loading-store/terminal-bindings/reused-terminal-id-unbind.test.ts": 312,
"src/main/persistence/loading-store/profile-writing/state-write-round-trip.test.ts": 1983,
"src/main/persistence/loading-store/workspace-sessions/store-host-partition-before-unload.test.ts": 1670,
"src/main/persistence/loading-store/terminal-bindings/terminal-close-async-durability.test.ts": 5719,
"src/main/persistence/loading-store/workspace-sessions/workspace-session-terminal-binding-replay.test.ts": 95,
"src/main/persistence/loading-store/worktree-metadata/worktree-meta-write-normalization.test.ts": 127,
"src/main/persistence/migrating-orcad-catalog/blob-transfer/orcad-migration-blob-staging.test.ts": 466,
@@ -2598,43 +2586,39 @@
"src/main/persistence/restoring-sessions/workspace-pane-normalization-index.test.ts": 466,
"src/main/persistence/runtime-authored-workspace-session-fields.test.ts": 64,
"src/main/persistence/scheduling-automations/automation-run-operations.test.ts": 151,
"src/main/persistence/terminal-topology/closed-pane-binding-fence.test.ts": 289,
"src/main/persistence/terminal-topology/duplicate-pty-pane-repair.test.ts": 2087,
"src/main/persistence/terminal-topology/editor-renames-across-partitions.test.ts": 526,
"src/main/persistence/terminal-topology/open-in-new-group-layout-apply.test.ts": 1943,
"src/main/persistence/terminal-topology/pane-last-grid.test.ts": 5642,
"src/main/persistence/terminal-topology/persistence-closed-pane-binding-fence.test.ts": 1926,
"src/main/persistence/terminal-topology/persistence-cross-host-pane-identity.test.ts": 1744,
"src/main/persistence/terminal-topology/persistence-empty-root-layout-load.test.ts": 1841,
"src/main/persistence/terminal-topology/persistence-layout-binding-recovery.test.ts": 1835,
"src/main/persistence/terminal-topology/persistence-pane-alias-selective-saves.test.ts": 1961,
"src/main/persistence/terminal-topology/persistence-pane-identity-migration.test.ts": 5772,
"src/main/persistence/terminal-topology/persistence-pty-binding-leaf-tab-resolution.test.ts": 1996,
"src/main/persistence/terminal-topology/persistence-pty-binding-reconciliation.test.ts": 1898,
"src/main/persistence/terminal-topology/persistence-runtime-admitted-terminal-membership.test.ts": 2143,
"src/main/persistence/terminal-topology/persistence-split-pane-incarnation.test.ts": 1790,
"src/main/persistence/terminal-topology/persistence-ssh-pending-pty-kill.test.ts": 1776,
"src/main/persistence/terminal-topology/persistence-ssh-remote-cli-control-migration.test.ts": 1738,
"src/main/persistence/terminal-topology/persistence-ssh-remote-pty-binding-replay.test.ts": 1853,
"src/main/persistence/terminal-topology/persistence-ssh-remote-pty-leases.test.ts": 2079,
"src/main/persistence/terminal-topology/persistence-tab-terminal-pane-binding-load.test.ts": 1795,
"src/main/persistence/terminal-topology/terminal-close-write-failure-store.test.ts": 2870,
"src/main/persistence/terminal-topology/terminal-close-write-failure-switch-off.test.ts": 2027,
"src/main/persistence/terminal-topology/pane-ownership/duplicate-pty-pane-repair.test.ts": 2087,
"src/main/persistence/terminal-topology/command-application/editor-renames-across-partitions.test.ts": 526,
"src/main/persistence/terminal-topology/command-application/open-in-new-group-layout-apply.test.ts": 1943,
"src/main/persistence/terminal-topology/command-application/pane-last-grid.test.ts": 5642,
"src/main/persistence/terminal-topology/pane-ownership/persistence-closed-pane-binding-fence.test.ts": 1926,
"src/main/persistence/restoring-sessions/persistence-cross-host-pane-identity.test.ts": 1744,
"src/main/persistence/restoring-sessions/persistence-empty-root-layout-load.test.ts": 1841,
"src/main/persistence/loading-store/workspace-sessions/persistence-layout-binding-recovery.test.ts": 1835,
"src/main/persistence/restoring-sessions/persistence-pane-identity-migration.test.ts": 5772,
"src/main/persistence/terminal-topology/pane-ownership/persistence-pty-binding-leaf-tab-resolution.test.ts": 1996,
"src/main/persistence/loading-store/terminal-bindings/persistence-pty-binding-reconciliation.test.ts": 1898,
"src/main/persistence/terminal-topology/pane-admission/persistence-runtime-admitted-terminal-membership.test.ts": 2143,
"src/main/persistence/loading-store/terminal-bindings/persistence-split-pane-incarnation.test.ts": 1790,
"src/main/persistence/leasing-ssh-ptys/persistence-ssh-pending-pty-kill.test.ts": 1776,
"src/main/persistence/loading-store/workspace-sessions/persistence-ssh-remote-pty-binding-replay.test.ts": 1853,
"src/main/persistence/leasing-ssh-ptys/persistence-ssh-remote-pty-leases.test.ts": 2079,
"src/main/persistence/restoring-sessions/persistence-tab-terminal-pane-binding-load.test.ts": 1795,
"src/main/persistence/terminal-topology/command-application/terminal-close-write-failure-store.test.ts": 2870,
"src/main/persistence/terminal-topology/command-application/terminal-close-write-failure-switch-off.test.ts": 2027,
"src/main/persistence/terminal-topology/terminal-layout-apply.test.ts": 2005,
"src/main/persistence/terminal-topology/terminal-layout-create-resend.test.ts": 1773,
"src/main/persistence/terminal-topology/terminal-layout-create.test.ts": 2107,
"src/main/persistence/terminal-topology/terminal-layout-pane-ops.test.ts": 1753,
"src/main/persistence/terminal-topology/terminal-layout-rename-tabs.test.ts": 1977,
"src/main/persistence/terminal-topology/terminal-layout-sleep-wake.test.ts": 2198,
"src/main/persistence/terminal-topology/terminal-leaf-move-concurrent-close.test.ts": 1847,
"src/main/persistence/terminal-topology/terminal-leaf-move.test.ts": 2197,
"src/main/persistence/terminal-topology/terminal-owner-invariants.test.ts": 285,
"src/main/persistence/terminal-topology/terminal-pane-admission-layout-owner.test.ts": 1796,
"src/main/persistence/terminal-topology/terminal-sleep-quit-resume.test.ts": 1766,
"src/main/persistence/terminal-topology/terminal-window-pane-detach.test.ts": 1941,
"src/main/persistence/terminal-topology/unshown-live-terminal-placement-restart.test.ts": 2114,
"src/main/persistence/terminal-topology/windowless-host-reload-keeps-terminals.test.ts": 1872,
"src/main/persistence/terminal-topology/windowless-restart-lost-binding.test.ts": 1626,
"src/main/persistence/terminal-topology/pane-admission/terminal-layout-create-resend.test.ts": 1773,
"src/main/persistence/terminal-topology/pane-admission/terminal-layout-create.test.ts": 2107,
"src/main/persistence/terminal-topology/command-application/terminal-layout-pane-ops.test.ts": 1753,
"src/main/persistence/terminal-topology/command-application/terminal-layout-rename-tabs.test.ts": 1977,
"src/main/persistence/terminal-topology/command-application/terminal-layout-sleep-wake.test.ts": 2198,
"src/main/persistence/terminal-topology/pane-moves/terminal-leaf-move-concurrent-close.test.ts": 1847,
"src/main/persistence/terminal-topology/pane-moves/terminal-leaf-move.test.ts": 2197,
"src/shared/workspace-layout/terminal-topology/terminal-owner-invariants.test.ts": 285,
"src/main/persistence/terminal-topology/pane-admission/terminal-pane-admission-layout-owner.test.ts": 1796,
"src/main/persistence/loading-store/workspace-sessions/terminal-sleep-quit-resume.test.ts": 1766,
"src/main/persistence/terminal-topology/pane-moves/terminal-window-pane-detach.test.ts": 1941,
"src/main/runtime/terminal/adoption/unshown-live-terminal-placement-restart.test.ts": 2114,
"src/main/persistence/restoring-sessions/windowless-restart-lost-binding.test.ts": 1626,
"src/main/persistence/tracking-repos/local-worktree-metadata-scan-expectation.test.ts": 544,
"src/main/persistence/tracking-repos/missing-local-worktree-metadata-pruning.test.ts": 619,
"src/main/persistence/tracking-repos/persistence-deregistered-repo-residue.test.ts": 2374,
@@ -2995,7 +2979,6 @@
"src/main/runtime/terminal/output/headless-terminal-dispose-write-ordering.test.ts": 4309,
"src/main/runtime/terminal/recovery/held-pane-reopen-layout-owner.test.ts": 5093,
"src/main/runtime/terminal/wait/hermes-slash-readiness-transcript.test.ts": 4931,
"src/main/runtime/terminal/stop/hook-status-resume-sleep-ordering.test.ts": 9614,
"src/main/runtime/agent-session/environment/host-account-sign-in-flows.test.ts": 68,
"src/main/runtime/hosts/platform-probe/host-descriptor.test.ts": 207,
"src/main/runtime/terminal/surface/host-pane-remount-terminal-handle.test.ts": 5293,
@@ -3011,19 +2994,17 @@
"src/main/runtime/workspaces/creation/managed-worktree-create-registry.test.ts": 4143,
"src/main/runtime/workspaces/creation/managed-worktree-create-telemetry.test.ts": 4152,
"src/main/runtime/workspaces/removal/missing-worktree-terminal-reconciliation.test.ts": 432,
"src/main/runtime/mobile-cloud-relay/desktop-relay-service-installer.test.ts": 48,
"src/main/runtime/mobile-cloud-relay/mobile-relay-e2ee.integration.test.ts": 408,
"src/main/runtime/mobile-cloud-relay/relay-auth-coordinator-readiness.test.ts": 580,
"src/main/runtime/mobile-cloud-relay/relay-auth-coordinator-recovery.test.ts": 161,
"src/main/runtime/mobile-cloud-relay/relay-auth-coordinator.test.ts": 1013,
"src/main/runtime/mobile-cloud-relay/relay-control-client.test.ts": 701,
"src/main/runtime/mobile-cloud-relay/testing/desktop-relay-service-installer.test.ts": 48,
"src/main/runtime/mobile-cloud-relay/testing/mobile-relay-e2ee.integration.test.ts": 408,
"src/main/runtime/mobile-cloud-relay/authentication/relay-auth-coordinator-readiness.test.ts": 580,
"src/main/runtime/mobile-cloud-relay/authentication/relay-auth-coordinator.test.ts": 1013,
"src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client.test.ts": 701,
"src/main/runtime/mobile-cloud-relay/relay-control-origin.test.ts": 147,
"src/main/runtime/mobile-cloud-relay/relay-host-proof.test.ts": 122,
"src/main/runtime/mobile-cloud-relay/relay-http-client.test.ts": 175,
"src/main/runtime/mobile-cloud-relay/relay-readiness-retention.test.ts": 196,
"src/main/runtime/mobile-cloud-relay/relay-region-preference.test.ts": 253,
"src/main/runtime/mobile-cloud-relay/relay-region-refresh.test.ts": 127,
"src/main/runtime/mobile-cloud-relay/relay-revoke-outbox.test.ts": 73,
"src/main/runtime/mobile-cloud-relay/control-channel/relay-host-proof.test.ts": 122,
"src/main/runtime/mobile-cloud-relay/director/relay-http-client.test.ts": 175,
"src/main/runtime/mobile-cloud-relay/authentication/relay-readiness-retention.test.ts": 196,
"src/main/runtime/mobile-cloud-relay/region-selection/relay-region-preference.test.ts": 253,
"src/main/runtime/mobile-cloud-relay/pairing/relay-revoke-outbox.test.ts": 73,
"src/main/runtime/mobile-cloud-relay/relay-session-broker.test.ts": 804,
"src/main/runtime/mobile-desktop-relay/mobile-desktop-relay.integration.test.ts": 11377,
"src/main/runtime/mobile-desktop-relay/mobile-desktop-relay.test.ts": 1185,
@@ -3051,11 +3032,8 @@
"src/main/runtime/browser/client-pages/orca-runtime-browser-ghost-session-row-close.test.ts": 640,
"src/main/runtime/browser/screencast/orca-runtime-browser-screencast-e2ee-backlog.test.ts": 659,
"src/main/runtime/browser/screencast/orca-runtime-browser-screencast-fanout.test.ts": 607,
"src/main/runtime/browser/screencast/orca-runtime-browser-screencast-pending-frame-retry.test.ts": 595,
"src/main/runtime/browser/client-pages/orca-runtime-browser.test.ts": 682,
"src/main/runtime/terminal/query/orca-runtime-codex-idle-title-chat-address.test.ts": 5781,
"src/main/runtime/terminal/surface/orca-runtime-cold-restore-terminal-membership-layout-owner.test.ts": 4130,
"src/main/runtime/terminal/surface/orca-runtime-cold-restore-terminal-membership.test.ts": 3716,
"src/main/runtime/terminal/wait/orca-runtime-exact-worker-provider-session.test.ts": 3617,
"src/main/runtime/terminal/lifecycle/orca-runtime-exited-split-pane-close.test.ts": 4235,
"src/main/runtime/files/orca-runtime-files-preview-budget.test.ts": 672,
@@ -3237,7 +3215,6 @@
"src/main/runtime/rpc/e2ee-channel-text-backpressure.test.ts": 204,
"src/main/runtime/rpc/e2ee-channel-v2.test.ts": 287,
"src/main/runtime/rpc/e2ee-channel.test.ts": 539,
"src/main/runtime/rpc/e2ee-crypto.test.ts": 121,
"src/main/runtime/rpc/methods/accounts/accounts.test.ts": 161,
"src/main/runtime/rpc/methods/agent-launch/agent-launch-at-rest.test.ts": 4936,
"src/main/runtime/rpc/methods/agent-launch/agent-launch-caller-selection.test.ts": 1680,
@@ -3395,7 +3372,6 @@
"src/main/runtime/rpc/mobile-e2ee-v2-compressed-frame.test.ts": 75,
"src/main/runtime/rpc/mobile-e2ee-v2-desktop-session.test.ts": 98,
"src/main/runtime/rpc/mobile-socket-wiring.test.ts": 312,
"src/main/runtime/rpc/orcad-feature-readiness.test.ts": 40,
"src/main/runtime/rpc/orchestration-11745-regression-verification.test.ts": 22874,
"src/main/runtime/rpc/orchestration-check-wait-session.test.ts": 5096,
"src/main/runtime/rpc/orchestration-commit-notify-characterization.test.ts": 5702,
@@ -3425,7 +3401,6 @@
"src/main/runtime/rpc/remote-runtime-server-heartbeat-missed-probe-tolerance.test.ts": 59,
"src/main/runtime/rpc/remote-runtime-server-heartbeat.test.ts": 60,
"src/main/runtime/rpc/repo-add-cli.integration.test.ts": 1603,
"src/main/runtime/rpc/runtime-client-capabilities.test.ts": 43,
"src/main/runtime/rpc/runtime-source-admission.test.ts": 909,
"src/main/runtime/rpc/shadowed-port-probe.test.ts": 99,
"src/main/runtime/rpc/static-web-client-handler-stream.test.ts": 1823,
@@ -3470,7 +3445,6 @@
"src/main/runtime/rpc/unix-socket-transport.test.ts": 79,
"src/main/runtime/rpc/unpaired-device-auth-throttle.test.ts": 42,
"src/main/runtime/rpc/web-session-pairing.test.ts": 402,
"src/main/runtime/rpc/workspace-creator-context.test.ts": 40,
"src/main/runtime/rpc/worktree-removal-marker-projection.test.ts": 497,
"src/main/runtime/rpc/worktree-rm-cli.integration.test.ts": 6876,
"src/main/runtime/rpc/ws-empty-payload-frame-write.test.ts": 117,
@@ -3525,7 +3499,6 @@
"src/main/runtime/rpc/server/runtime-rpc-browser-host-admission.test.ts": 4402,
"src/main/runtime/rpc/server/runtime-rpc-delegated-device-sync.test.ts": 4986,
"src/main/runtime/rpc/server/runtime-rpc-device-key-dispatch.test.ts": 3597,
"src/main/runtime/rpc/server/runtime-rpc-device-revocation.test.ts": 5350,
"src/main/runtime/rpc/server/runtime-rpc-local-disconnect-forwarding.test.ts": 5138,
"src/main/runtime/rpc/server/runtime-rpc-long-poll-transport.test.ts": 30838,
"src/main/runtime/rpc/server/runtime-rpc-metadata-lifecycle.test.ts": 4652,
@@ -3558,8 +3531,6 @@
"src/main/runtime/workspaces/lineage/runtime-worktree-lineage-controller.test.ts": 1027,
"src/main/runtime/workspaces/startup/runtime-worktree-startup-readiness.test.ts": 289,
"src/main/runtime/workspaces/process-listing/runtime-worktree-structured-agent-rows-liveness.test.ts": 1412,
"src/main/runtime/browser/headless/serve-browser-tabs-layout-owner.test.ts": 321,
"src/main/runtime/session-lifecycle/close/close-by-split-handle-layout-owner.test.ts": 5010,
"src/main/runtime/session-lifecycle/close/close-mobile-row-in-main-layout.test.ts": 4514,
"src/main/runtime/session-lifecycle/projection/pane-sleeping-record.test.ts": 76,
"src/main/runtime/session-lifecycle/projection/sleep-record-pane-claim.test.ts": 48,
@@ -3648,7 +3619,6 @@
"src/main/runtime/terminal/output/tail/terminal-tail-redraw-spans.test.ts": 166,
"src/main/runtime/terminal-tail-redraw-wall-time.test.ts": 43418,
"src/main/runtime/terminal/output/tail/terminal-tail-sentinel-index.test.ts": 3649,
"src/main/runtime/terminal/stop/terminal-update-admission-census.test.ts": 4182,
"src/main/runtime/terminal/output/terminal-vertical-control-scan.test.ts": 168,
"src/main/runtime/terminal/wait/terminal-wait-detection.test.ts": 220,
"src/main/runtime/terminal/wait/terminal-wait-name-only-idle.test.ts": 5018,
@@ -3669,7 +3639,6 @@
"src/main/runtime/terminal/stop/exact-terminal-stop-liveness.test.ts": 4321,
"src/main/runtime/terminal/stop/terminal-update-admission.test.ts": 48,
"src/main/runtime/terminal/surface/closed-pane-exit-cleanup-main.test.ts": 4281,
"src/main/runtime/terminal/surface/main-terminal-acquisition.test.ts": 4330,
"src/main/runtime/terminal/surface/terminal-surface-replies-main.test.ts": 4324,
"src/main/runtime/terminal/wait/tui-idle-codex-plan-menu.test.ts": 4441,
"src/main/runtime/terminal/wait/tui-idle-command-paint.test.ts": 4148,
@@ -3684,8 +3653,6 @@
"src/main/runtime/terminal/surface/workspace-layout-operation-frames.test.ts": 2136,
"src/main/runtime/terminal/surface/workspace-layout-stream.test.ts": 427,
"src/main/runtime/terminal/surface/workspace-session-membership-scaling.test.ts": 72,
"src/main/runtime/workspaces/persisted-surfaces/live-terminal-placement-before-initial-tab.test.ts": 6111,
"src/main/runtime/workspaces/persisted-surfaces/sweep-terminal-placement.test.ts": 12352,
"src/main/runtime/workspaces/lifecycle/workspace-lifecycle-queue.test.ts": 116,
"src/main/runtime/workspaces/sleep/workspace-sleep-execution.test.ts": 110,
"src/main/runtime/workspaces/startup/worktree-agent-first-surface.test.ts": 5816,
@@ -3804,7 +3771,6 @@
"src/main/managed-server/install/orcad-launch-ssh-session.test.ts": 689,
"src/main/managed-server/migration/transfers/orcad-migration-blob-coordinator.test.ts": 1464,
"src/main/managed-server/migration/catalog/orcad-migration-catalog-client-frame-budget.test.ts": 405,
"src/main/managed-server/migration/catalog/orcad-migration-catalog-client-source-kind.test.ts": 400,
"src/main/managed-server/migration/catalog/orcad-migration-catalog-client.test.ts": 400,
"src/main/managed-server/migration/cutover/orcad-migration-cutover-coordinator.test.ts": 2408,
"src/main/managed-server/migration/cutover/orcad-migration-delta-move.test.ts": 3812,
@@ -3907,7 +3873,6 @@
"src/main/startup/configure-process.test.ts": 255,
"src/main/startup/e2e-external-open-guard.test.ts": 44,
"src/main/startup/e2e-spawn-log-env.test.ts": 89,
"src/main/startup/ensure-virtual-display.test.ts": 135,
"src/main/startup/first-window-startup-services.test.ts": 54,
"src/main/startup/gpu-fallback-marker.test.ts": 61,
"src/main/startup/gpu-lifecycle-install-dir-acl-guard.test.ts": 641,
@@ -4066,7 +4031,6 @@
"src/main/window/renderer-recovery-reload-watchdog.test.ts": 69,
"src/main/window/runtime-renderer-notification-sender.test.ts": 54,
"src/main/window/runtime-window-lifecycle-terminal-reveal.test.ts": 100,
"src/main/window/runtime-window-pane-title-notification.test.ts": 97,
"src/main/window/session-tab-close-request-relay.test.ts": 74,
"src/main/window/terminal-tab-close-request-relay.test.ts": 64,
"src/main/window/updater-package-recovery-ipc.test.ts": 169,
@@ -4233,7 +4197,6 @@
"src/preload/renderer-heap-statistics-reader.test.ts": 50,
"src/preload/renderer-process-memory-reader.test.ts": 66,
"src/preload/renderer-restart-wiring.test.ts": 96,
"src/preload/runtime-environment-subscriptions.test.ts": 71,
"src/preload/ssh-authority-forwarding.test.ts": 1111,
"src/preload/updater-package-recovery.test.ts": 1102,
"src/preload/usage-provider-api.test.ts": 46,
@@ -6325,7 +6288,6 @@
"src/shared/execution-host-registry-shared-control-connect.test.ts": 160,
"src/shared/execution-host-registry.test.ts": 169,
"src/shared/execution-host.test.ts": 56,
"src/shared/external-automation-jobs-file.test.ts": 72,
"src/shared/external-worktree-inbox.test.ts": 69,
"src/shared/feature-education-telemetry.test.ts": 36,
"src/shared/feature-tips.test.ts": 54,
@@ -6379,7 +6341,6 @@
"src/shared/git/worktrees/git-worktree-admin.test.ts": 161,
"src/shared/github/account-binding.test.ts": 48,
"src/shared/github/api-availability.test.ts": 47,
"src/shared/source-control/review-comments/pr-comments-resolution-prompt.test.ts": 49,
"src/shared/github/project-board-columns.test.ts": 64,
"src/shared/github/project-identity.test.ts": 33,
"src/shared/github/project-ref-input.test.ts": 40,
@@ -6607,17 +6568,12 @@
"src/shared/plugins/authoring/plugin-host-typings.test.ts": 2140,
"src/shared/plugins/catalog/plugin-catalog-release.test.ts": 229,
"src/shared/plugins/catalog/plugin-catalog.test.ts": 630,
"src/shared/plugins/catalog/plugin-detail-fields.test.ts": 171,
"src/shared/plugins/catalog/plugin-kill-list.test.ts": 195,
"src/shared/plugins/catalog/plugin-marketplace.test.ts": 246,
"src/shared/plugins/catalog/plugin-repository-link.test.ts": 205,
"src/shared/plugins/contributions/plugin-command-replaces.test.ts": 230,
"src/shared/plugins/contributions/plugin-content-pack-contributions.test.ts": 227,
"src/shared/plugins/contributions/plugin-custom-editor-contribution.test.ts": 238,
"src/shared/plugins/contributions/plugin-execution-profile.test.ts": 164,
"src/shared/plugins/contributions/plugin-panel-contribution.test.ts": 240,
"src/shared/plugins/contributions/plugin-settings-contribution.test.ts": 202,
"src/shared/plugins/contributions/plugin-status-bar-item-contribution.test.ts": 232,
"src/shared/plugins/declarative/plugin-declarative-catalog-rich.test.ts": 147,
"src/shared/plugins/declarative/plugin-declarative-example.test.ts": 198,
"src/shared/plugins/declarative/plugin-declarative-tree.test.ts": 168,
@@ -6629,21 +6585,14 @@
"src/shared/plugins/declarative/runtime/declarative-storage-sync.test.ts": 190,
"src/shared/plugins/declarative/testing/orca-testing-hooks.test.ts": 506,
"src/shared/plugins/declarative/testing/orca-testing-worker.test.ts": 480,
"src/shared/plugins/declarative/testing/orca-testing.test.ts": 489,
"src/shared/plugins/declarative/testing/testing-editor-host.test.ts": 197,
"src/shared/plugins/frames/plugin-frame-document-session.test.ts": 404,
"src/shared/plugins/frames/plugin-frame-document.test.ts": 426,
"src/shared/plugins/frames/plugin-frame-facades.test.ts": 383,
"src/shared/plugins/frames/plugin-frame-guest.test.ts": 50,
"src/shared/plugins/frames/plugin-panel-call-admission.test.ts": 89,
"src/shared/plugins/frames/plugin-panel-message-budget.test.ts": 99,
"src/shared/plugins/frames/plugin-panel-shell.test.ts": 376,
"src/shared/plugins/images/plugin-image-format.test.ts": 104,
"src/shared/plugins/images/plugin-image-url.test.ts": 42,
"src/shared/plugins/installation/plugin-file-validation.test.ts": 363,
"src/shared/plugins/installation/plugin-path-safety.test.ts": 221,
"src/shared/plugins/manifest/plugin-display-text.test.ts": 285,
"src/shared/plugins/manifest/plugin-manifest-source-module-floor.test.ts": 212,
"src/shared/plugins/manifest/plugin-manifest-v2-rules.test.ts": 239,
"src/shared/plugins/manifest/plugin-manifest.test.ts": 234,
"src/shared/plugins/view/plugin-editor-shot-acts.test.ts": 112,
@@ -7019,7 +6968,6 @@
"src/wsl-guest/wsl-install-plugins-handler.test.ts": 283,
"tests/e2e/SmartWorkspaceSourceResultList.linear-auth.unit.test.ts": 1976,
"tests/e2e/SmartWorkspaceSourceResultList.linear-read-error.unit.test.ts": 3757,
"tests/e2e/agent-launch-cli.unit.test.ts": 2379,
"tests/e2e/agent-launch-setup-choice.unit.test.ts": 2657,
"tests/e2e/ai-vault-manual-name-clear.unit.test.ts": 21206,
"tests/e2e/automation-chat-reopen.unit.test.ts": 6262,
@@ -84,7 +84,7 @@ const NON_UNIT_TEST_RUNNERS = [
// `orca plugin test` runs every example's own tests in Node; the unit test drives it.
name: 'example plugin tests (orca plugin test)',
include: ['examples/plugins/*/src/**/*.test.ts', 'examples/plugins/*/src/**/*.test.tsx'],
sources: ['src/cli/plugin-authoring/plugin-test-runner.test.ts'],
sources: ['src/cli/plugins/authoring/plugin-test-runner.test.ts'],
mirrors: ['readdirSync(EXAMPLES)']
},
{
@@ -37,22 +37,20 @@ export const UNIT_EXCLUDE = [
'src/main/pty/shell-startup/zsh-startup-hook-user-config-equivalence.live-shell.test.ts',
'src/main/runtime/terminal/wait/tui-idle-zsh-preexec-hook-order.live-shell.test.ts',
'src/main/runtime/agent-session/environment/structured-session-cli-login-shell.live-shell.test.ts',
'src/main/startup/shell-probe-terminal-detach.live-shell.test.ts',
'src/main/startup/shell-probe-lifetime.live-shell.test.ts',
'src/main/startup/shell-environment/shell-probe-terminal-detach.live-shell.test.ts',
'src/main/startup/shell-environment/shell-probe-lifetime.live-shell.test.ts',
'src/renderer/src/components/terminal-pane/pty-connection/emulation/fish-color-scheme-child-stdin.node-pty.test.ts',
'src/shared/fish-wsl-cwd-report.node-pty.test.ts',
'src/main/wsl/filesystem/wsl-sh-cwd-report.node-pty.test.ts',
'src/shared/terminal/pty/ingress/pty-reply-echo-shapes.node-pty.test.ts',
'src/shared/startup-shell-portability.live-shell.test.ts',
'src/shared/posix-command-path-lookup.test.ts',
'tests/e2e/relay-region-compatibility.unit.test.ts',
'tests/e2e/relay-region-correction.unit.test.ts',
'src/main/browser/cookies/browser-google-cookie-clear.electron.test.ts',
// Electron probes with in-process deadlines; the package job runs them serially.
'src/main/browser/guest-lifecycle/popups/presentation/popup-origin-chrome.electron.test.ts',
'src/main/browser/guest-lifecycle/navigation/browser-link-materialization.electron.test.ts',
'src/main/browser/client-pages/renderer/browser-client-page-renderer-lifecycle.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts',
'src/main/browser/extensions/api-runtime/browser-extension-api.electron.test.ts',
@@ -69,6 +69,33 @@ it('rechecks closure before cancelling when a PR reopens during lookup', async (
expect(cancel).not.toHaveBeenCalled()
})
it('skips a close event whose payload was rendered after a quick reopen', async () => {
const reopened = execute({
closed: { number: 123, closed_at: null },
current: { number: 123, state: 'open', closed_at: null, merged_at: null }
})
await expect(reopened.result).resolves.toBeUndefined()
expect(reopened.cancel).not.toHaveBeenCalled()
const stillClosed = execute()
await stillClosed.result
expect(stillClosed.cancel).toHaveBeenCalledWith(expect.objectContaining({ run_id: 42 }))
})
it('skips a close event when the PR was reopened and merged before cleanup', async () => {
const { result, cancel } = execute({
closed: { number: 123, closed_at: null },
current: {
number: 123,
state: 'closed',
closed_at: '2026-10-06T00:00:05Z',
merged_at: '2026-10-06T00:00:05Z'
}
})
await expect(result).resolves.toBeUndefined()
expect(cancel).not.toHaveBeenCalled()
})
const head = 'a'.repeat(40)
const pushed = { number: 123, head: { sha: head } }
const open = { ...pushed, state: 'open' }
@@ -13,7 +13,7 @@ import {
/** Files that decide which runtime executable launches orcad or the terminal daemon. */
export const RUNTIME_LAUNCHER_PATHS = [
// orcad handoff to its bundled runtime, and the pinned runtimes it can hand off to.
'src/main/orcad/orcad-bundled-runtime.ts',
'src/main/orcad/runtime-launch/orcad-bundled-runtime.ts',
'src/main/orcad/launcher.ts',
'config/scripts/orcad-distribution/server-bundle/orcad-entry-build.mjs',
'src/shared/node-runtime-pin.ts',
@@ -183,7 +183,9 @@ describe('run-electron-vite-dev', () => {
const tempDir = mkdtempSync(join(tmpdir(), 'orca-dev-wrapper-'))
const pidFile = join(tempDir, 'grandchild.pid')
const wrapperPath = resolve('config/scripts/development/launch/run-electron-vite-dev.mjs')
const fakeCliPath = resolve('src/main/startup/__fixtures__/fake-electron-vite-dev-cli.mjs')
const fakeCliPath = resolve(
'config/scripts/development/launch/fixtures/fake-electron-vite-dev-cli.mjs'
)
const wrapper = spawn(process.execPath, [wrapperPath], {
cwd: resolve('.'),
@@ -228,7 +230,9 @@ describe('run-electron-vite-dev', () => {
async () => {
const tempDir = mkdtempSync(join(tmpdir(), 'orca-dev-wrapper-'))
const wrapperPath = resolve('config/scripts/development/launch/run-electron-vite-dev.mjs')
const fakeCliPath = resolve('src/main/startup/__fixtures__/fake-electron-vite-dev-cli.mjs')
const fakeCliPath = resolve(
'config/scripts/development/launch/fixtures/fake-electron-vite-dev-cli.mjs'
)
const baseEnv = devWrapperTestEnv({
ORCA_ELECTRON_VITE_CLI: fakeCliPath,
ORCA_SKIP_DEV_CLI_PREPARE: '1',
@@ -302,7 +306,9 @@ describe('run-electron-vite-dev', () => {
const pidFile = join(tempDir, 'grandchild.pid')
const envFile = join(tempDir, 'env.json')
const wrapperPath = resolve('config/scripts/development/launch/run-electron-vite-dev.mjs')
const fakeCliPath = resolve('src/main/startup/__fixtures__/fake-electron-vite-dev-cli.mjs')
const fakeCliPath = resolve(
'config/scripts/development/launch/fixtures/fake-electron-vite-dev-cli.mjs'
)
const { wrapper, readOutput } = spawnDevWrapper(
[wrapperPath, '--remote-debugging-port=9448'],
@@ -4715,21 +4715,9 @@
"auto.components.settings.ZcodePlanAccountsSection.detailsUnavailable": {
"ko": "요금제 자격 증명 세부 정보는 Orca를 실행 중인 컴퓨터에서만 볼 수 있습니다."
},
"accounts.antigravity.signIn": {
"ko": "이 호스트에서 agy를 실행하고 브라우저 로그인을 완료한 후 현재 계정을 저장하세요. 다른 계정을 추가하려면 agy에서 /logout 명령을 실행하고 다시 로그인하세요."
},
"accounts.antigravity.signedOut": {
"ko": "로그인된 agy 자체 계정이 없습니다."
},
"accounts.antigravity.changed": {
"ko": "agy 자체 계정이 변경되었습니다. agy를 시작하기 전에 저장된 계정을 다시 선택하세요."
},
"accounts.antigravity.save": {
"ko": "현재 계정 저장"
},
"accounts.antigravity.sessions": {
"ko": "선택한 계정은 이 호스트의 새 agy 세션에 적용됩니다. 기존 세션은 이전 계정을 계속 사용할 수 있습니다."
},
"accounts.antigravity.keyword.antigravity": {
"ko": "antigravity"
},
@@ -30,10 +30,6 @@ describe('native dialog localization coverage', () => {
])
})
it('does not change the existing renderer scan by default', () => {
expect(collectLocalizationCandidates(path, source, '/repo')).toEqual([])
})
it('fails the opt-in check for a new native dialog literal', async () => {
const root = await mkdtemp(join(tmpdir(), 'orca-native-localization-'))
const report = vi.spyOn(console, 'error').mockImplementation(() => {})
@@ -236,20 +236,6 @@ for (const engine of ['chromium', 'webkit']) {
expect(sealed.reported.join(' ')).not.toContain('script-src')
}, 180_000)
it('loads the artifact https images the directive admits, and still refuses its font', async (ctx) => {
// Await both images explicitly; bounded settle once observed background before element on loaded Chrome.
const read = await open(browser(), {
assets: assetServer.origin,
frameReady: 'images',
signal: ctx.signal
})
expect(read.pixel).toBe(ARTIFACT_RGB)
// img-src governs CSS background and img element; require both requests.
expect([...read.secureHits].sort()).toEqual(['/css-bg.png', '/img.png'])
// Same HTTPS origin still cannot load font; img-src admission must not widen font-src.
expect(read.secureHits).not.toContain('/probe.woff2')
}, 120_000)
it('sends no referrer with an admitted https image, which is the shell header doing it', async (ctx) => {
const sealed = await open(browser(), {
assets: assetServer.origin,
@@ -384,109 +370,6 @@ for (const engine of ['chromium', 'webkit']) {
expect(read.popups).toBe(0)
}, 120_000)
it("cannot reach the shell through a meta refresh at the embedder's own URL", async (ctx) => {
// Same-origin meta-refresh targets shell document; foreign-origin arm cannot prove shell-origin counter works.
const own = await open(browser(), {
signal: ctx.signal,
extra: { head: '<meta http-equiv="refresh" content="0;url=/">' }
})
// Require painted artifact before interpreting navigation refusal.
expect(own.pixelBefore).toBe(ARTIFACT_RGB)
// Link-to-/ control reads one on same counter, proving refresh zero is observable.
expect(own.ownOriginTopNavigations).toBe(0)
expect(own.topNavigations).toBe(0)
// Also reject self-frame shell-document fetch, which would embed session page inside preview.
expect(own.ownOriginFrameNavigations).toBe(0)
// No-policy/allow-same-origin control must navigate frame to shell / to prove observer is not blind.
const loose = await open(browser(), {
signal: ctx.signal,
csp: null,
sandbox: 'allow-scripts allow-same-origin allow-top-navigation',
extra: { head: '<meta http-equiv="refresh" content="0;url=/">' },
// Navigating arm no longer contains marker; await navigation instead of artifact readiness.
frameReady: 'load',
expectNavigation: 'frame'
})
expect(loose.ownOriginFrameNavigations).toBe(1)
// Test sandbox token alone with no CSP; navigation must never begin or report.
const tokenOnly = await open(browser(), {
signal: ctx.signal,
csp: null,
extra: { head: '<meta http-equiv="refresh" content="0;url=/">' }
})
expect(tokenOnly.pixelBefore).toBe(ARTIFACT_RGB)
expect(tokenOnly.ownOriginFrameNavigations).toBe(0)
expect(tokenOnly.ownOriginTopNavigations).toBe(0)
expect(tokenOnly.violations).toEqual([])
// Test CSP alone with allow-same-origin: frame-src none blocks request at embedder; engines leave different content, so assert no server request.
const policyOnly = await open(browser(), {
signal: ctx.signal,
sandbox: 'allow-scripts allow-same-origin allow-top-navigation',
extra: { head: '<meta http-equiv="refresh" content="0;url=/">' },
frameReady: 'load'
})
expect(policyOnly.ownOriginFrameNavigations).toBe(0)
expect(policyOnly.ownOriginTopNavigations).toBe(0)
expect(policyOnly.violations.join(' ')).toContain('frame-src')
}, 180_000)
it('hands up nothing without a tap, and nothing for a form or a new window', async (ctx) => {
const meta = await open(browser(), {
signal: ctx.signal,
extra: { head: `<meta http-equiv="refresh" content="0;url=${foreignOrigin}/meta.html">` }
})
expect(meta.topNavigations).toBe(0)
expect(meta.ownOriginTopNavigations).toBe(0)
const form = await open(browser(), {
signal: ctx.signal,
act: async ({ frame }) => {
await frame.click('#submit', { timeout: 2000 })
}
})
expect(form.actError).toBeNull()
expect(form.pixelBefore).toBe(ARTIFACT_RGB)
expect(form.topNavigations).toBe(0)
const blank = await open(browser(), {
signal: ctx.signal,
act: async ({ frame }) => {
await frame.click('#blanklink', { timeout: 2000 })
}
})
expect(blank.actError).toBeNull()
expect(blank.pixelBefore).toBe(ARTIFACT_RGB)
expect(blank.topNavigations).toBe(0)
expect(blank.popups).toBe(0)
const allowedForm = await open(browser(), {
signal: ctx.signal,
csp: null,
sandbox: 'allow-forms allow-top-navigation-by-user-activation',
expectNavigation: 'main-frame',
act: async ({ frame }) => {
await frame.click('#submit', { timeout: 2000 })
}
})
expect(allowedForm.pixelBefore).toBe(ARTIFACT_RGB)
expect(allowedForm.actError).toBeNull()
expect(allowedForm.topNavigations).toBe(1)
const allowedBlank = await open(browser(), {
signal: ctx.signal,
csp: null,
sandbox: 'allow-popups allow-popups-to-escape-sandbox',
act: async ({ page, frame }) => {
await Promise.all([
page.waitForEvent('popup', { timeout: 10_000 }),
frame.click('#blanklink', { timeout: 2000 })
])
}
})
expect(allowedBlank.pixelBefore).toBe(ARTIFACT_RGB)
expect(allowedBlank.actError).toBeNull()
expect(allowedBlank.popups).toBe(1)
}, 180_000)
// Force slow-arm diagnostic sampling branch; formerly unexecuted branch had out-of-scope name and no test/lint coverage.
// Force bounded navigation timeout after missed click; require useful diagnostic instead of exhausting whole case budget.
@@ -0,0 +1,141 @@
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import * as esbuild from 'esbuild'
import { chromium } from 'playwright-core'
import {
MOBILE_WEB_APP_ROOT_RESET,
mobileWebAppBuildOptions
} from '../../build/mobile-web-app/build-mobile-web-app-bundle.mjs'
import { mobileWebAppDependenciesPresent } from '../mobile-web-app-bundle-dependencies.mjs'
import { createBundleServer, readShellCsp } from '../preview/mobile-web-app-render-harness.mjs'
// The phone's web pages must pan the transcript by touch and give a pinch to its text-size detector, not page zoom.
const mobileDir = fileURLToPath(new URL('../../../../mobile', import.meta.url))
const entry = `
import { createRoot } from 'react-dom/client'
import { Text, View } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { MobileNativeChatPageScroll } from './src/session/native-chat/transcript/MobileNativeChatPageScroll'
import {
MobileNativeChatPinchZoom,
useMobileNativeChatPinchGesture
} from './src/session/native-chat/transcript/mobile-native-chat-pinch-zoom'
function Transcript() {
const { fontScale, pinchGesture } = useMobileNativeChatPinchGesture('chat')
return <View style={{ height: 600, width: '100%' }}>
<Text testID="font-scale">{fontScale}</Text>
<GestureHandlerRootView style={{ flex: 1 }}>
<MobileNativeChatPinchZoom gesture={pinchGesture}>
<MobileNativeChatPageScroll testID="transcript">
{Array.from({ length: 60 }, (_, i) => <Text key={i}>message {i}</Text>)}
</MobileNativeChatPageScroll>
</MobileNativeChatPinchZoom>
</GestureHandlerRootView>
</View>
}
createRoot(document.getElementById('root')).render(<Transcript />)
`
const bundles = mobileWebAppDependenciesPresent()
const describeRender = bundles ? describe : describe.skip
let scratch
let server
let origin
let browser
beforeAll(async () => {
if (!bundles) {
return
}
await mkdir(join(mobileDir, '.tmp'), { recursive: true })
scratch = await mkdtemp(join(mobileDir, '.tmp', 'chat-touch-scroll-'))
const outDir = join(scratch, 'bundle')
await esbuild.build({
...mobileWebAppBuildOptions([]),
entryPoints: undefined,
stdin: { contents: entry, resolveDir: mobileDir, loader: 'tsx', sourcefile: 'transcript.tsx' },
splitting: false,
write: true,
outdir: outDir,
entryNames: 'transcript',
metafile: false
})
await writeFile(
join(outDir, 'index.html'),
'<!doctype html><html><head><meta charset="utf-8">' +
'<meta name="viewport" content="width=device-width, initial-scale=1">' +
`${MOBILE_WEB_APP_ROOT_RESET}</head>` +
'<body><div id="root"></div><script type="module" src="/transcript.js"></script></body></html>'
)
const served = await createBundleServer({ outDir, cspHeader: await readShellCsp() })
server = served.server
origin = served.origin
const executablePath = process.env.ORCA_MOBILE_WEB_RENDER_BROWSER
browser = await chromium.launch({ headless: true, ...(executablePath ? { executablePath } : {}) })
}, 180_000)
afterAll(async () => {
await browser?.close()
server?.close()
if (scratch) {
await rm(scratch, { recursive: true, force: true })
}
})
async function touch(page, steps) {
const cdp = await page.context().newCDPSession(page)
const [first, ...rest] = steps
await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: first })
for (const touchPoints of rest) {
await cdp.send('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints })
await page.evaluate(() => new Promise(requestAnimationFrame))
}
await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] })
await cdp.detach()
}
const frames = (count, at) => Array.from({ length: count + 1 }, (_, step) => at(step / count))
async function openTranscript() {
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
hasTouch: true,
isMobile: true
})
const page = await context.newPage()
await page.goto(`${origin}/`)
await page.getByTestId('transcript').waitFor()
return page
}
describeRender('native chat transcript in the phone web pages', () => {
it('pans the transcript under one finger', async () => {
const page = await openTranscript()
await touch(
page,
frames(10, (t) => [{ x: 195, y: 200 + 300 * t }])
)
await expect
.poll(() => page.getByTestId('transcript').evaluate((element) => element.scrollTop))
.toBeLessThan(-100)
await page.context().close()
})
it('gives a pinch to the text size, not to page zoom', async () => {
const page = await openTranscript()
await touch(
page,
frames(10, (t) => [
{ x: 195, y: 330 - 120 * t, id: 0 },
{ x: 195, y: 370 + 120 * t, id: 1 }
])
)
await expect.poll(() => page.getByTestId('font-scale').textContent()).not.toBe('1')
expect(await page.evaluate(() => window.visualViewport.scale)).toBe(1)
await page.context().close()
})
})
@@ -16,31 +16,12 @@ import { mobileWebAppDependenciesPresent } from '../mobile-web-app-bundle-depend
import {
createBundleServer,
installCspViolationRecorder,
installListenerRecorder,
readShellCsp
} from '../preview/mobile-web-app-render-harness.mjs'
const mobileDir = fileURLToPath(new URL('../../../../mobile', import.meta.url))
const componentDir = join(mobileDir, 'src/components')
const COMMANDS = [
{ label: 'H1', command: 'heading1', select: 'all', expect: 'h1' },
{ label: 'H2', command: 'heading2', select: 'all', expect: 'h2' },
{ label: 'H3', command: 'heading3', select: 'all', expect: 'h3' },
{ label: 'Bold', command: 'bold', select: 'word', expect: 'b,strong' },
{ label: 'Italic', command: 'italic', select: 'word', expect: 'i,em' },
{ label: 'Strike', command: 'strike', select: 'word', expect: 'strike,s,del' },
{ label: 'Bullet list', command: 'bulletList', select: 'all', expect: 'ul' },
{ label: 'Numbered list', command: 'orderedList', select: 'all', expect: 'ol' },
{ label: 'Checklist', command: 'taskList', select: 'all', expect: 'ul[data-type="taskList"]' },
{ label: 'Quote', command: 'quote', select: 'all', expect: 'blockquote' },
{ label: 'Inline code', command: 'inlineCode', select: 'word', expect: 'code' },
{ label: 'Code block', command: 'codeBlock', select: 'all', expect: 'pre' }
]
// Paragraph undoes another command; starting from p would prove nothing.
const PARAGRAPH = { label: 'Body', command: 'paragraph' }
// Serve inserted image locally: nonexistent URL logs WebKit load failure; successful paint proves shipped CSP admits it.
const IMAGE_PATH = '/inserted.png'
const URL_COMMANDS = [
@@ -151,21 +132,6 @@ function Harness() {
createRoot(document.getElementById('root')).render(createElement(Harness))
`
// Install ReactNativeWebView recorder before bundle: absence alone cannot prove editor JSON avoids shell bridge if bridge object never existed.
function installBridgeObjectRecorder() {
globalThis.__orcaBridgeReads = []
const bridge = {
postMessage: (message) => globalThis.__orcaBridgeReads.push(`post ${String(message)}`)
}
Object.defineProperty(globalThis, 'ReactNativeWebView', {
configurable: true,
get: () => {
globalThis.__orcaBridgeReads.push('read')
return bridge
}
})
}
const bundles = mobileWebAppDependenciesPresent()
const describeEditor = bundles ? describe : describe.skip
@@ -256,9 +222,7 @@ async function openPage(browser) {
}
})
page.on('pageerror', (error) => consoleErrors.push(`pageerror: ${error.message}`))
await page.addInitScript(installBridgeObjectRecorder)
await page.addInitScript(installCspViolationRecorder)
await page.addInitScript(installListenerRecorder, { selector: '#editor' })
await page.goto(`${origin}/`, { waitUntil: 'domcontentloaded' })
await page.waitForFunction(() => document.body.dataset.ready === 'yes')
await page.waitForSelector('#first-surface #editor')
@@ -326,89 +290,7 @@ describeEditor(
afterAll(() => browser?.close())
it('mounts the document, reports ready through the seam and never touches the bridge', async () => {
const { page, consoleErrors } = await openPage(browser)
try {
await setContent(page, '# Title', '<h1>Title</h1>')
expect(await readSurface(page)).toBe('<h1>Title</h1>')
// Initial markup proves document ready reached controller and pushed content through seam.
expect(await page.evaluate(() => globalThis.__orcaBridgeReads)).toEqual([])
expect(await page.evaluate(() => globalThis.__orcaCspViolations)).toEqual([])
expect(consoleErrors).toEqual([])
// No editor occlusion report: screen’s web module already measures viewport, so reporting twice would lift bar twice.
expect(await page.evaluate(() => globalThis.__orcaEditor.insets)).toEqual([])
} finally {
await page.close()
}
}, 600_000)
it('runs every one of the fifteen commands against the document', async () => {
const { page, consoleErrors } = await openPage(browser)
try {
for (const [index, entry] of COMMANDS.entries()) {
const body = bodyFor(index)
await setContent(page, body, `<p>${body}</p>`)
// Require command effect absent beforehand or undelivered command could pass on previous document.
expect(
await page.evaluate(
(selector) =>
document.querySelector('#first-surface #editor')?.querySelector(selector),
entry.expect
),
`${entry.label} was already applied before it ran`
).toBeNull()
await select(page, entry.select)
await press(page, entry.label)
await page.waitForFunction(
(selector) =>
document.querySelector('#first-surface #editor')?.querySelector(selector) !==
null,
entry.expect,
{ timeout: 15_000 }
)
}
// Paragraph must undo another block type; it is a no-op on p.
await setContent(page, '# Title', '<h1>Title</h1>')
await select(page, 'all')
await press(page, PARAGRAPH.label)
await page.waitForFunction(
() =>
document.querySelector('#first-surface #editor')?.querySelector('h1') === null &&
document.querySelector('#first-surface #editor')?.querySelector('p') !== null,
null,
{ timeout: 15_000 }
)
expect(await page.evaluate(() => globalThis.__orcaCspViolations)).toEqual([])
expect(consoleErrors).toEqual([])
} finally {
await page.close()
}
}, 600_000)
// Both engines nest unordered list inside existing p; inline paragraph serialization once dropped bullet when saved.
it('edits and selects under the shipped document style, which makes other text unselectable', async () => {
const { page, consoleErrors } = await openPage(browser)
try {
await setContent(page, 'alpha', '<p>alpha</p>')
const word = page.locator('#first-surface #editor p')
await word.click()
await page.keyboard.press('End')
await page.keyboard.type(' bravo')
await page.waitForFunction(
() =>
document.querySelector('#first-surface #editor p')?.textContent === 'alpha bravo'
)
// Use browser gesture: a scripted Range bypasses body user-select:none.
await page.evaluate(() => window.getSelection()?.removeAllRanges())
await word.dblclick()
expect(await page.evaluate(() => window.getSelection()?.toString().trim())).not.toBe('')
expect(consoleErrors).toEqual([])
} finally {
await page.close()
}
}, 600_000)
it('reports a typed bullet list as a list, and renders that markdown back as one', async () => {
const { page, consoleErrors } = await openPage(browser)
@@ -74,23 +74,6 @@ it('tracks workspace source and manifest closures without trusting dist or widen
).toBe(false)
})
it('checks ordinary source unit files against actual imports before skipping the matrix', async () => {
const reachable = 'src/main/persistence/example/used.test.ts'
const unrelated = 'src/main/persistence/example/unrelated.test.tsx'
const removed = 'src/main/persistence/example/removed.test.ts'
const root = moduleTree({
'entry.ts': `import './${reachable}'; throw Error('never execute')`,
[reachable]: 'export const value = 1',
[unrelated]: 'throw Error("never execute unrelated test")'
})
const inputs = await collectNodeServerInputs({ root, entryPoints: ['entry.ts'] })
expect(inputs.has(reachable)).toBe(true)
expect(inputs.has(unrelated)).toBe(false)
expect((await classifyNodeServerChanges([reachable], async () => inputs)).shouldRun).toBe(true)
expect((await classifyNodeServerChanges([unrelated], async () => inputs)).shouldRun).toBe(false)
expect((await classifyNodeServerChanges([removed], async () => inputs)).shouldRun).toBe(false)
})
it('retains qualification when a removed ordinary unit file breaks an actual import', async () => {
const removed = 'src/main/persistence/example/removed.test.ts'
const root = moduleTree({ 'entry.ts': `import './${removed}'` })
@@ -21,8 +21,8 @@ const CROSS_HOST_PREFIXES = [
'src/main/ssh/',
'src/main/managed-server/',
'src/main/node-runtime/',
'src/main/orcad/orcad-artifact-materializer.ts',
'src/main/orcad/orcad-host-unavailable.ts',
'src/main/orcad/artifact-slots/orcad-artifact-materializer.ts',
'src/main/orcad/diagnostics/orcad-host-unavailable.ts',
'src/main/providers/',
'src/wsl-guest/'
]
@@ -1,11 +1,11 @@
/** Mirrors ORCA_REQUIRED_TEST_INPUTS_ENV in src/main/orcad/orcad-node-slot-fixture.ts. */
/** Mirrors ORCA_REQUIRED_TEST_INPUTS_ENV in src/main/orcad/testing/native-slots/orcad-node-slot-fixture.ts. */
export const REQUIRED_TEST_INPUTS_ENV = 'ORCA_REQUIRED_TEST_INPUTS'
export const CROSS_RUNTIME_TEST_PATHS = []
// Need the built desktop server template; CI runs them by path after build-orcad-template.mjs.
export const TEMPLATE_TEST_PATHS = [
'src/main/orcad/orcad-packaged-structured-chat.integration.test.ts'
'src/main/orcad/testing/structured-chat/orcad-packaged-structured-chat.integration.test.ts'
]
export function nodeServerTestPaths({
@@ -18,10 +18,10 @@ export function nodeServerTestPaths({
'src/main/persistence/loading-store/profile-writing/profile-state',
'src/main/sqlite',
// The orcad server's identity and stop path, which Windows SSH hosts rely on (W2).
'src/main/orcad/orcad-instance-lock.test.ts',
'src/main/orcad/orcad-process-start-time.test.ts',
'src/main/orcad/orcad-managed-stop.test.ts',
'src/main/orcad/orcad-managed-stop-cancellation.test.ts',
'src/main/orcad/instance-lock/orcad-instance-lock.test.ts',
'src/main/orcad/instance-lock/orcad-process-start-time.test.ts',
'src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop.test.ts',
'src/main/orcad/host-lifecycle/managed-stop/orcad-managed-stop-cancellation.test.ts',
// The directory, not a prefix: its siblings are POSIX-host unit tests pr.yml already runs.
'src/main/daemon/pty-subprocess/conpty-startup-input-reader.ts',
'src/main/daemon/pty-subprocess/daemon-pty-physical-owner.ts',
@@ -39,18 +39,17 @@ export function nodeServerTestPaths({
'src/main/daemon/pty-subprocess/spawn-preflight.ts',
'src/main/daemon/pty-subprocess/subprocess-handle.ts',
'src/main/daemon/pty-subprocess/testing/pty-subprocess-spawn-file-foreground.test.ts',
'src/main/daemon/pty-subprocess/testing/pty-subprocess-io-failure-native.test.ts',
...(artifact
? [
'tests/e2e/daemon-running-work-probe.unit.test.ts',
'src/shared/terminal/pty/lifecycle/pty-running-work-probe.test.ts',
'src/main/orcad/orcad-packaged-node-pty.integration.test.ts',
'src/main/orcad/native-runtime/orcad-packaged-node-pty.integration.test.ts',
'src/main/providers/agent-foreground-process-git-bash.win32.test.ts',
'src/main/orcad/orcad-cli-launcher.integration.test.ts',
'src/main/orcad/orcad-launcher-isolation.integration.test.ts',
'src/main/orcad/orcad-stop-request-shutdown.integration.test.ts',
'src/main/orcad/orcad-windows-conpty-breakaway.integration.test.ts',
'src/main/orcad/orcad-serve-parity.integration.test.ts',
'src/main/orcad/serve-startup/orcad-cli-launcher.integration.test.ts',
'src/main/orcad/runtime-launch/orcad-launcher-isolation.integration.test.ts',
'src/main/orcad/host-lifecycle/managed-stop/orcad-stop-request-shutdown.integration.test.ts',
'src/main/orcad/native-runtime/orcad-windows-conpty-breakaway.integration.test.ts',
'src/main/orcad/testing/serve-lifecycle/orcad-serve-parity.integration.test.ts',
'config/scripts/archives/platform-command/zip-extractor-command.test.mjs'
]
: []),
@@ -1,12 +1,12 @@
import { createHash } from 'node:crypto'
import { existsSync } from 'node:fs'
import { copyFile, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises'
import 'node:fs'
import { copyFile, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { basename, dirname, join } from 'node:path'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { assembleOrcadArtifact } from '../../../../src/main/orcad/orcad-artifact-materializer.ts'
import { readOrcadArtifactIdentity } from '../../../../src/main/orcad/orcad-artifact-identity.ts'
import '../../../../src/main/orcad/artifact-slots/orcad-artifact-materializer.ts'
import '../../../../src/main/orcad/artifact-slots/orcad-artifact-identity.ts'
import {
ORCAD_TEMPLATE_MANIFEST_FILENAME,
orcadRipgrepArtifact
@@ -46,63 +46,6 @@ async function fixture() {
}
describe('packaged ripgrep storage', () => {
it('preserves every deployed slot byte and version, including old schema 3 templates', async () => {
const f = await fixture()
const original = new Map()
const cacheRoot = join(f.appDir, 'cache')
for (const target of Object.keys(f.manifest.targets)) {
const slot = await assembleOrcadArtifact({ templateDir: f.templateDir, cacheRoot, target })
original.set(target, basename(slot))
}
expect(compactPackagedOrcadRipgrep(f.resourcesDir)).toBeGreaterThan(0)
expect(compactPackagedOrcadRipgrep(f.resourcesDir)).toBe(0)
for (const target of Object.keys(f.manifest.targets)) {
const slot = await assembleOrcadArtifact({
templateDir: f.templateDir,
cacheRoot: join(f.appDir, 'fresh-cache'),
target
})
expect(basename(slot)).toBe(original.get(target))
expect(await readOrcadArtifactIdentity(slot)).toBe(original.get(target))
const filename = orcadRipgrepArtifact(target)
expect(await readFile(join(slot, filename))).toEqual(
await readFile(join(f.resourcesDir, filename))
)
if (process.platform !== 'win32' && !target.startsWith('win32-')) {
expect((await stat(join(slot, filename))).mode & 0o777).toBe(0o755)
}
expect(existsSync(join(f.templateDir, 'targets', target, filename))).toBe(
target.startsWith('darwin-')
)
}
// No template storage schema reaches the deployment slot.
expect(
existsSync(
join(
cacheRoot,
'linux-x64-glibc',
original.get('linux-x64-glibc'),
ORCAD_TEMPLATE_MANIFEST_FILENAME
)
)
).toBe(false)
})
it('keeps binaries that differ and never removes Darwin signing inputs', async () => {
const f = await fixture()
const desktop = join(f.resourcesDir, 'ripgrep/linux-arm64/rg')
await writeFile(desktop, 'different desktop bytes')
compactPackagedOrcadRipgrep(f.resourcesDir)
const manifest = JSON.parse(await readFile(f.manifestPath, 'utf8'))
expect(manifest.schemaVersion).toBe(4)
for (const [target, entry] of Object.entries(manifest.targets)) {
expect(entry.packagedRipgrep).toBe(
target.startsWith('darwin-') || target.startsWith('linux-arm64') ? undefined : true
)
}
expect(await readFile(desktop, 'utf8')).toBe('different desktop bytes')
})
it('does not launder corrupted target bytes while removing duplicates', async () => {
const f = await fixture()
await writeFile(join(f.templateDir, 'targets/linux-x64-glibc/ripgrep/linux-x64/rg'), 'tampered')
@@ -1,69 +0,0 @@
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { assembleOrcadArtifact } from '../../../../src/main/orcad/orcad-artifact-materializer.ts'
import { ORCAD_TEMPLATE_MANIFEST_FILENAME } from '../../../../src/shared/orcad-artifacts.ts'
import { writeOrcadTemplateTestFixture } from './orcad-template-test-fixture.mjs'
const require = createRequire(import.meta.url)
const {
finalizePackagedOrcadTemplate,
resealSignedWindowsApp
} = require('./packaged-orcad-template.cjs')
const roots = []
afterEach(async () => {
vi.restoreAllMocks()
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
})
async function packagedApp() {
vi.spyOn(console, 'log').mockImplementation(() => {})
const appDir = await mkdtemp(join(tmpdir(), 'orca-packaged-browser-'))
roots.push(appDir)
const resourcesDir = join(appDir, 'resources')
const templateDir = await writeOrcadTemplateTestFixture(resourcesDir)
const driver = join(resourcesDir, 'agent-browser-win32-x64.exe')
await writeFile(driver, 'desktop driver')
await finalizePackagedOrcadTemplate(resourcesDir, { platform: 'win32', signMacBinary: vi.fn() })
return {
appDir,
templateDir,
driver,
manifestPath: join(templateDir, ORCAD_TEMPLATE_MANIFEST_FILENAME)
}
}
describe("packaged orcad uses the app's agent-browser driver (#33846)", () => {
it("materializes the host target's slot with the driver the app ships", async () => {
const app = await packagedApp()
const slot = await assembleOrcadArtifact({
templateDir: app.templateDir,
cacheRoot: join(app.appDir, 'cache'),
target: 'win32-x64'
})
expect(await readFile(join(slot, 'agent-browser-win32-x64.exe'), 'utf8')).toBe('desktop driver')
// A target that carries its own copy keeps it.
const manifest = JSON.parse(await readFile(app.manifestPath, 'utf8'))
expect(manifest.targets['linux-x64-glibc'].packagedBrowser).toBeUndefined()
expect(manifest.targets['win32-arm64'].browserName).toBeUndefined()
})
it('reseals the driver after SignPath rewrites it', async () => {
const app = await packagedApp()
await writeFile(app.driver, 'authenticode-signed driver')
const list = join(app.appDir, 'inner-signing-list.txt')
await writeFile(list, 'Orca.exe\r\nresources\\agent-browser-win32-x64.exe\r\n')
resealSignedWindowsApp(app.appDir, list)
const slot = await assembleOrcadArtifact({
templateDir: app.templateDir,
cacheRoot: join(app.appDir, 'cache'),
target: 'win32-x64'
})
expect(await readFile(join(slot, 'agent-browser-win32-x64.exe'), 'utf8')).toBe(
'authenticode-signed driver'
)
})
})
@@ -5,7 +5,7 @@ import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-ben
const bundled = await build({
stdin: {
contents: "export * from './src/main/ai-vault/session-root-dedup.ts'",
contents: "export * from './src/main/ai-vault/parsing/session-root-dedup.ts'",
resolveDir: process.cwd(),
loader: 'ts'
},
@@ -32,10 +32,10 @@ async function load(contents) {
return import(`data:text/javascript;base64,${encoded}`)
}
const [baselineModule, currentModule] = await Promise.all([
load(`import { sessionSortTime } from './session-scanner-accumulator';
load(`import { sessionSortTime } from './parsing/session-scanner-accumulator';
export ${baselineFunction.getText(baselineSource)}`),
load(`export { canStopParsingSessions } from './session-scan-cutoff';
export { ScannedSessionCollection } from './session-root-dedup';`)
load(`export { canStopParsingSessions } from './parsing/session-scan-cutoff';
export { ScannedSessionCollection } from './parsing/session-root-dedup';`)
])
const baseline = baselineModule.canStopParsingSessions
const current = currentModule.canStopParsingSessions
@@ -1,28 +1,81 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { performance } from 'node:perf_hooks'
import { build } from 'esbuild'
import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-benchmark-schedule.mjs'
import { summarizeBenchmarkSamples } from '../measurement/benchmark-sample-summary.mjs'
import ts from 'typescript-api'
const baselineSourcePathArgument = process.argv.find((arg) =>
arg.startsWith('--baseline-source-path=')
)
const baselineSourcePath = resolve(
baselineSourcePathArgument?.slice('--baseline-source-path='.length) ??
'src/main/ai-vault/session-scanner-accumulator.ts'
)
function retargetLegacyAccumulatorValueImports(source) {
const program = ts.createSourceFile(baselineSourcePath, source, ts.ScriptTarget.Latest, true)
assert.equal(program.parseDiagnostics.length, 0, 'Baseline source must parse')
const replacements = []
for (const statement of program.statements) {
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) {
continue
}
if (
resolve(dirname(baselineSourcePath), statement.moduleSpecifier.text) !==
resolve('src/main/ai-vault/session-scanner-values')
) {
continue
}
const clause = statement.importClause
assert(
clause && !clause.name && clause.namedBindings && ts.isNamedImports(clause.namedBindings)
)
const imports = clause.namedBindings.elements.map((binding) => {
const name = (binding.propertyName ?? binding.name).text
const owner =
name === 'extractString'
? 'src/shared/trimmed-string.ts'
: name === 'timestampMs'
? 'src/main/ai-vault/parsing/session-record-time.ts'
: null
assert(owner, `Unsupported historical accumulator value import: ${name}`)
return `import ${clause.isTypeOnly ? 'type ' : ''}{ ${binding.getText(program)} } from ${JSON.stringify(resolve(owner))}`
})
replacements.push({
start: statement.getStart(program),
end: statement.end,
text: imports.join('\n')
})
}
for (const replacement of replacements.toReversed()) {
source = source.slice(0, replacement.start) + replacement.text + source.slice(replacement.end)
}
return source
}
// git show <ref>:src/main/ai-vault/session-scanner-accumulator.ts | node config/scripts/session-timeline-benchmark.mjs
const target = resolve('src/main/ai-vault/session-scanner-accumulator.ts')
async function load(source) {
const target = resolve('src/main/ai-vault/parsing/session-scanner-accumulator.ts')
async function load(source, sourcePath = target) {
const result = await build({
stdin: {
contents: `
export {createAccumulator, cloneSessionAccumulator, updateTimeline, finalizeSession}
from './session-scanner-accumulator';
export {createClaudeSessionParseState, consumeClaudeSessionLine}
from './session-scanner-primary-parsers';`,
from '../scanners/claude/session-scanner-primary-parsers';`,
loader: 'ts',
resolveDir: dirname(target)
},
bundle: true,
write: false,
platform: 'node',
format: 'esm',
format: 'cjs',
plugins: [
{
name: 'timeline-baseline',
@@ -30,18 +83,26 @@ async function load(source) {
plugin.onLoad({ filter: /session-scanner-accumulator\.ts$/ }, () => ({
contents: source,
loader: 'ts',
resolveDir: dirname(target)
resolveDir: dirname(sourcePath)
}))
}
}
]
})
return import(
`data:text/javascript;base64,${Buffer.from(result.outputFiles[0].text).toString('base64')}`
)
const directory = await mkdtemp(join(tmpdir(), 'orca-session-timeline-'))
try {
const file = join(directory, 'timeline.cjs')
await writeFile(file, result.outputFiles[0].text)
return createRequire(import.meta.url)(file)
} finally {
await rm(directory, { recursive: true, force: true })
}
}
const implementations = {
before: await load(readFileSync(0, 'utf8')),
before: await load(
retargetLegacyAccumulatorValueImports(readFileSync(0, 'utf8')),
baselineSourcePath
),
after: await load(readFileSync(target, 'utf8'))
}
const file = { path: 'timeline.jsonl', mtimeMs: 0, modifiedAt: '2026-01-01T00:00:00.000Z' }
@@ -6,7 +6,7 @@ import { performance } from 'node:perf_hooks'
import { build } from 'esbuild'
import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-benchmark-schedule.mjs'
const modulePath = 'src/main/native-chat/transcript-stream-lines.ts'
const modulePath = 'src/main/native-chat/transcript-streaming/transcript-stream-lines.ts'
const baselineSource = readFileSync(0, 'utf8')
assert.ok(baselineSource.includes('decodeTranscriptStream'), 'Pipe the baseline module on stdin')
const arms = {}
@@ -50,7 +50,10 @@ if (ROUNDS % 2 !== 0) {
}
const CHUNK_SOURCE = readFileSync(
new URL('../../../../src/main/runtime/rpc/terminal-output-frame-chunks.ts', import.meta.url),
new URL(
'../../../../src/main/runtime/rpc/terminal/terminal-output-frame-chunks.ts',
import.meta.url
),
'utf8'
)
@@ -101,7 +104,10 @@ const { TerminalStreamOpcode, encodeTerminalStreamJson, encodeTerminalStreamText
.href
)
const { exceedsTerminalStreamChunkBytes, iterateTerminalOutputFrameChunks } = await import(
new URL('../../../../src/main/runtime/rpc/terminal-output-frame-chunks.ts', import.meta.url).href
new URL(
'../../../../src/main/runtime/rpc/terminal/terminal-output-frame-chunks.ts',
import.meta.url
).href
)
function previousGate(data) {
@@ -68,7 +68,10 @@ const {
measureTerminalStreamByteLength,
terminalStreamByteLengthExceeds
} = await import(
new URL('../../../../src/main/runtime/rpc/terminal-stream-byte-length.ts', import.meta.url).href
new URL(
'../../../../src/main/runtime/rpc/terminal/terminal-stream-byte-length.ts',
import.meta.url
).href
)
const REQUESTED_SNAPSHOT_BYTE_BUDGET = (() => {
@@ -1,258 +0,0 @@
#!/usr/bin/env node
// Compare real CLI emits before/after deferring RuntimeClient’s Zod/ws/tweetnacl/store graph until after flag validation.
// Restore seven baseline files; use fresh processes to measure cold graphs, alternate arm order, and verify output/exit equality before timing.
import { execFileSync, spawnSync } from 'node:child_process'
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { performance } from 'node:perf_hooks'
import { fileURLToPath } from 'node:url'
const REPO = fileURLToPath(new URL('../../../..', import.meta.url))
const ROUNDS = Number(process.env.ORCA_CLI_DEFER_BENCH_ROUNDS ?? '30')
const WARMUP = Number(process.env.ORCA_CLI_DEFER_BENCH_WARMUP ?? '3')
for (const [name, value] of [
['ORCA_CLI_DEFER_BENCH_ROUNDS', ROUNDS],
['ORCA_CLI_DEFER_BENCH_WARMUP', WARMUP]
]) {
if (!Number.isSafeInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer, received ${value}`)
}
}
if (ROUNDS % 2 !== 0) {
// Why: arms alternate which one leads; an odd count biases one arm.
throw new Error(`ORCA_CLI_DEFER_BENCH_ROUNDS must be even, received ${ROUNDS}`)
}
const TOUCHED = [
'src/cli/args.ts',
'src/cli/dispatch.ts',
'src/cli/flags.ts',
'src/cli/format.ts',
'src/cli/index.ts',
'src/cli/runtime/client.ts',
'src/cli/selectors.ts'
]
// Re-read production call forms so reverting deferral cannot make both arms identical; prose mentions must not satisfy the guard.
function assertMarkersFresh() {
const checks = [
['src/cli/index.ts', "await import('./runtime-client.js')"],
['src/cli/index.ts', 'await loadRuntimeClientClass()'],
['src/cli/index.ts', "import type { RuntimeClient } from './runtime-client'"],
['src/cli/runtime/client.ts', "await import('./websocket-transport.js')"],
['src/cli/runtime/client.ts', 'await loadSendWebSocketRequest()'],
['src/cli/args.ts', "import { RuntimeClientError } from './runtime/types'"],
['src/cli/flags.ts', "import { RuntimeClientError } from './runtime/types'"],
['src/cli/dispatch.ts', "import { RuntimeClientError } from './runtime/types'"],
['src/cli/selectors.ts', "import { RuntimeClientError } from './runtime/types'"],
['src/cli/format.ts', "} from './runtime/types'"]
]
for (const [file, marker] of checks) {
if (!readFileSync(join(REPO, file), 'utf8').includes(marker)) {
throw new Error(
`${file} no longer contains \`${marker}\` — cli-runtime-client-deferral-benchmark.mjs is stale`
)
}
}
}
function buildArm(label, baselineRev) {
// Build inside repo so emitted modules resolve its node_modules.
const outDir = join(REPO, `.bench-out-${label}`)
rmSync(outDir, { recursive: true, force: true })
const restore = []
try {
if (baselineRev) {
for (const file of TOUCHED) {
const path = join(REPO, file)
restore.push([path, readFileSync(path)])
writeFileSync(
path,
execFileSync('git', ['show', `${baselineRev}:${file}`], {
cwd: REPO,
maxBuffer: 64 * 1024 * 1024
})
)
}
}
execFileSync(
'npx',
[
'tsc',
'-p',
'config/tsconfig.cli.json',
'--outDir',
outDir,
'--composite',
'false',
'--incremental',
'false'
],
{ cwd: REPO, stdio: 'inherit' }
)
} finally {
for (const [path, contents] of restore) {
writeFileSync(path, contents)
}
}
return join(outDir, 'cli/index.js')
}
function run(entry, argv, env) {
const result = spawnSync(process.execPath, [entry, ...argv], {
cwd: REPO,
env: { ...process.env, ...env },
encoding: 'buffer'
})
if (result.error) {
throw result.error
}
return {
status: result.status,
stdout: result.stdout.toString('utf8'),
stderr: result.stderr.toString('utf8')
}
}
// Count eager CJS graph in a child via Module._load, measuring the graph deferral actually moves.
function countEagerModules(entry) {
const probe = `
const Module = require('module')
const original = Module._load
const seen = new Set()
Module._load = function (request, parent, isMain) {
try { seen.add(Module._resolveFilename(request, parent, isMain)) } catch { seen.add(request) }
return original.apply(this, arguments)
}
require(${JSON.stringify(entry)})
const all = [...seen]
process.stdout.write(JSON.stringify({
total: all.length,
nodeModules: all.filter((p) => p.includes('node_modules')).length
}))
`
const result = spawnSync(process.execPath, ['-e', probe], { cwd: REPO, encoding: 'utf8' })
if (result.status !== 0) {
throw new Error(`module probe failed: ${result.stderr}`)
}
return JSON.parse(result.stdout)
}
const median = (values) => {
const sorted = [...values].sort((a, b) => a - b)
return sorted[Math.floor(sorted.length / 2)]
}
const baselineIndex = process.argv.indexOf('--baseline')
const baselineRev = baselineIndex === -1 ? 'HEAD' : process.argv[baselineIndex + 1]
assertMarkersFresh()
const userDataPath = mkdtempSync(join(REPO, '.bench-userdata-'))
try {
console.log(`Building eager baseline (${baselineRev}) …`)
const eagerEntry = buildArm('eager', baselineRev)
console.log('Building deferred (working tree) …')
const deferredEntry = buildArm('deferred', null)
const eagerGraph = countEagerModules(eagerEntry)
const deferredGraph = countEagerModules(deferredEntry)
console.log(
`\nEager modules at process load: ${eagerGraph.total} -> ${deferredGraph.total} ` +
`(node_modules ${eagerGraph.nodeModules} -> ${deferredGraph.nodeModules})`
)
if (deferredGraph.total >= eagerGraph.total) {
throw new Error(
'deferred arm loads no fewer modules — the fixture does not exercise the change'
)
}
// Use empty user-data dirs for deterministic runtime-dependent cases.
const isolated = { ORCA_USER_DATA_PATH: userDataPath }
/** @type {Array<[string, string[], Record<string, string>]>} */
const cases = [
['orca --help', ['--help'], {}],
['orca help worktree', ['help', 'worktree'], {}],
['orca (no args)', [], {}],
['unknown command', ['no-such-command'], {}],
['unknown flag', ['worktree', 'list', '--nope'], {}],
['orca agent-context --json', ['agent-context', '--json'], {}],
['orca status --json', ['status', '--json'], isolated],
['orca worktree list --json', ['worktree', 'list', '--json'], isolated]
]
// Compare output/exit before timing or a broken arm printing nothing appears fastest.
for (const [label, argv, env] of cases) {
const before = run(eagerEntry, argv, env)
const after = run(deferredEntry, argv, env)
if (
before.status !== after.status ||
before.stdout !== after.stdout ||
before.stderr !== after.stderr
) {
throw new Error(`arms disagree for "${label}" — refusing to report a timing`)
}
if (before.stdout.length + before.stderr.length === 0) {
throw new Error(`"${label}" produced no output on either arm; it proves nothing`)
}
}
const pad = (value, width) => String(value).padStart(width)
console.log('\nFresh process per sample, wall clock. Lower is better.')
console.log(`rounds=${ROUNDS} warmup=${WARMUP} (per-arm median, arms alternate lead)`)
console.log(`${pad('case', 26)} ${pad('eager', 10)} ${pad('deferred', 10)} ${pad('speedup', 9)}`)
// Accumulated so V8 cannot treat the spawn loop as dead code.
let consumed = 0
for (const [label, argv, env] of cases) {
for (let index = 0; index < WARMUP; index += 1) {
consumed += run(eagerEntry, argv, env).stdout.length
consumed += run(deferredEntry, argv, env).stdout.length
}
const samples = { eager: [], deferred: [] }
for (let round = 0; round < ROUNDS; round += 1) {
// Alternate which arm leads so a drifting machine load cannot be
// attributed to one arm.
const order =
round % 2 === 0
? [
['eager', eagerEntry],
['deferred', deferredEntry]
]
: [
['deferred', deferredEntry],
['eager', eagerEntry]
]
for (const [arm, entry] of order) {
const started = performance.now()
const result = run(entry, argv, env)
samples[arm].push(performance.now() - started)
consumed += result.stdout.length
}
}
const eagerMs = median(samples.eager)
const deferredMs = median(samples.deferred)
console.log(
`${pad(label, 26)} ${pad(`${eagerMs.toFixed(1)} ms`, 10)} ${pad(`${deferredMs.toFixed(1)} ms`, 10)} ${pad(`${(eagerMs / deferredMs).toFixed(2)}x`, 9)}`
)
}
if (consumed === 0) {
throw new Error('no output consumed — the timing loop was optimised away')
}
console.log(
'\nThe help and error rows are the ones the change targets: they return\n' +
'before any client construction, so they drop the whole graph. `status` and\n' +
'`worktree list` still construct a client, so they only save the eager parse\n' +
'of the parts the local path never uses (ws/tweetnacl via websocket-transport).'
)
} finally {
rmSync(userDataPath, { recursive: true, force: true })
for (const label of ['eager', 'deferred']) {
rmSync(join(REPO, `.bench-out-${label}`), { recursive: true, force: true })
}
}
@@ -1,446 +0,0 @@
#!/usr/bin/env node
// Compare stdout/stderr/exit byte-for-byte across two real CLI emits: current tree and selected files restored from baseline (default HEAD~).
// Usage: node config/scripts/cli-runtime-client-deferral-equivalence.mjs [--baseline <rev>]
import { execFileSync, spawnSync } from 'node:child_process'
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const REPO = fileURLToPath(new URL('../../../..', import.meta.url))
// Restore only these baseline files; remove baseline-absent files (e.g. cli-error.ts), then restore the working tree.
const TOUCHED = [
'src/cli/args.ts',
'src/cli/cli-error.ts',
'src/cli/dispatch.ts',
'src/cli/flags.ts',
'src/cli/format.ts',
'src/cli/index.ts',
'src/cli/runtime/client.ts',
'src/cli/selectors.ts'
]
// Fail if real deferral call shapes change or baseline/current could become identical and pass vacuously.
function assertMarkersFresh() {
const index = readFileSync(join(REPO, 'src/cli/index.ts'), 'utf8')
const client = readFileSync(join(REPO, 'src/cli/runtime/client.ts'), 'utf8')
const checks = [
['src/cli/index.ts', index, 'await loadRuntimeClientClass()'],
['src/cli/index.ts', index, "await import('./runtime-client.js')"],
['src/cli/index.ts', index, "import type { RuntimeClient } from './runtime-client'"],
['src/cli/runtime/client.ts', client, 'await loadSendWebSocketRequest()'],
['src/cli/runtime/client.ts', client, "await import('./websocket-transport.js')"]
]
for (const [file, source, marker] of checks) {
// Match calls so prose mentions cannot satisfy the guard.
if (!source.includes(marker)) {
throw new Error(
`${file} no longer contains \`${marker}\` — cli-runtime-client-deferral-equivalence.mjs is stale`
)
}
}
const repointed = ['src/cli/args.ts', 'src/cli/flags.ts', 'src/cli/dispatch.ts']
for (const file of repointed) {
const source = readFileSync(join(REPO, file), 'utf8')
if (!source.includes("import { RuntimeClientError } from './runtime/types'")) {
throw new Error(`${file} no longer repoints RuntimeClientError at ./runtime/types — stale`)
}
}
}
function buildTree(label, baselineRev) {
// Build inside repo so emitted modules resolve its node_modules.
const outDir = join(REPO, `.equiv-out-${label}`)
rmSync(outDir, { recursive: true, force: true })
const restored = []
try {
if (baselineRev) {
for (const file of TOUCHED) {
const path = join(REPO, file)
restored.push([path, existsSync(path) ? readFileSync(path) : null])
const old = spawnSync('git', ['show', `${baselineRev}:${file}`], {
cwd: REPO,
maxBuffer: 64 * 1024 * 1024
})
if (old.status === 0) {
writeFileSync(path, old.stdout)
} else {
rmSync(path, { force: true })
}
}
}
execFileSync(
'npx',
[
'tsc',
'-p',
'config/tsconfig.cli.json',
'--outDir',
outDir,
'--composite',
'false',
'--incremental',
'false'
],
{ cwd: REPO, stdio: 'inherit' }
)
} finally {
for (const [path, contents] of restored) {
if (contents === null) {
rmSync(path, { force: true })
} else {
writeFileSync(path, contents)
}
}
}
return join(outDir, 'cli/index.js')
}
// Every case must exit itself; bound spawnSync so reaching a blocking command fails instead of stalling forever.
const RUN_TIMEOUT_MS = 30_000
function run(entry, argv, env) {
const result = spawnSync(process.execPath, [entry, ...argv], {
cwd: REPO,
env: { ...process.env, ...env },
encoding: 'buffer',
timeout: RUN_TIMEOUT_MS,
killSignal: 'SIGKILL'
})
if (result.error) {
if (result.error.code === 'ETIMEDOUT') {
throw new Error(
`orca ${argv.join(' ')} did not exit within ${RUN_TIMEOUT_MS} ms — it reached a blocking command`
)
}
throw result.error
}
return {
status: result.status,
stdout: result.stdout.toString('utf8'),
stderr: result.stderr.toString('utf8')
}
}
// Require identical bytes; empty user-data dirs make runtime-dependent cases deterministically report not running.
function buildCases(isolatedUserData) {
const isolated = { ORCA_USER_DATA_PATH: isolatedUserData }
const cases = [
// Paths that must never load the runtime client at all.
[[], {}],
[['--help'], {}],
[['help'], {}],
[['help', 'worktree'], {}],
[['help', 'browser'], {}],
[['worktree', '--help'], {}],
[['help', 'no-such-command'], {}],
[['no-such-command'], {}],
[['no-such-command'], { ORCA_PAIRING_CODE: 'garbage' }],
[['wrktree', 'list'], {}],
[['agent-context'], {}],
[['agent-context', '--json'], {}],
// Flag validation must still fire before any runtime lookup.
[['worktree', 'list', '--nonexistent-flag'], {}],
[['worktree', 'list', '--nonexistent-flag', '--json'], {}],
[['browser', 'snapshot', '--nonexistent-flag'], {}],
// resolveRemotePairing throws from the RuntimeClient CONSTRUCTOR.
[['status', '--pairing-code', 'x', '--environment', 'y'], isolated],
[['status', '--pairing-code', 'x', '--environment', 'y', '--json'], isolated],
[['status', '--pairing-code', 'not-a-pairing-code'], isolated],
[['status', '--pairing-code', 'not-a-pairing-code', '--json'], isolated],
[['status', '--pairing-code', 'orca://pair?code=zzzz'], isolated],
[['status', '--environment', 'no-such-environment'], isolated],
[['status', '--environment', 'no-such-environment', '--json'], isolated],
[['worktree', 'list', '--environment', 'no-such-environment', '--json'], isolated],
// The env-var fallback must stay live for non-suppressed commands...
[['status', '--json'], { ...isolated, ORCA_PAIRING_CODE: 'not-a-pairing-code' }],
[['status', '--json'], { ...isolated, ORCA_REMOTE_PAIRING: 'not-a-pairing-code' }],
[['status', '--json'], { ...isolated, ORCA_ENVIRONMENT: 'no-such-environment' }],
// Keep suppression for local-only groups. Do not invoke agent hooks on/off: they modify real ~/.claude configuration outside ORCA_USER_DATA_PATH.
[['environment', 'list', '--json'], { ...isolated, ORCA_ENVIRONMENT: 'no-such-environment' }],
[['environment', 'list', '--json'], { ...isolated, ORCA_PAIRING_CODE: 'not-a-pairing-code' }],
[['agent-context', '--json'], { ...isolated, ORCA_PAIRING_CODE: 'not-a-pairing-code' }],
// Runtime-unavailable reporting (RuntimeClientError formatting).
[['status'], isolated],
[['status', '--json'], isolated],
[['worktree', 'list', '--json'], isolated],
[['terminal', 'list', '--json'], isolated]
]
// Seed fuzzing for reproducibility; all pool tokens must be safe to execute, checked against UNSAFE_TOKENS before running any case.
const tokens = [
'worktree',
'list',
'status',
'browser',
'snapshot',
'terminal',
'environment',
'agent',
'agent-context',
'vm',
'--json',
'--help',
'--pairing-code',
'--environment',
'--worktree',
'orca://pair?code=!!!',
'',
'-',
'--',
'--=',
'a'.repeat(300),
'näme-ünicode',
'💥',
'../..',
'x\ty'
]
let seed = 0x9e3779b9
const next = () => {
seed ^= seed << 13
seed ^= seed >>> 17
seed ^= seed << 5
return (seed >>> 0) / 0x100000000
}
// Check entire pool, not just sampled cases: serve once survived review here and escaped the per-case scan by chance.
assertTokensSafe(tokens, 'fuzz token pool')
assertFuzzPoolDeclaredReadOnly(tokens)
for (let index = 0; index < 400; index += 1) {
const length = 1 + Math.floor(next() * 4)
const argv = []
for (let part = 0; part < length; part += 1) {
argv.push(tokens[Math.floor(next() * tokens.length)])
}
cases.push([argv, isolated])
}
for (const [argv] of cases) {
assertTokensSafe(argv, `argv ${JSON.stringify(argv)}`)
}
return cases
}
// Cases run real CLI with developer HOME/PATH: deny foreground commands, surviving children, config/registry writes and real UI input.
// Deny mixed read/write groups (capture/intercept/label/relation) wholesale; fuzzing cannot distinguish their subcommands.
const FOREGROUND_TOKENS = ['serve', 'open', 'claude-teams', 'exec', 'eval', 'launch', 'attach']
const MUTATING_TOKENS = [
// persistent config and registry state
'on',
'off',
'hooks',
'create',
'remove',
'rm',
'delete',
'add',
'edit',
'set',
'set-value',
'set-base-ref',
'setup-clone',
'setup-create',
'setup-update',
'setup-delete',
'setup-existing-folder',
'install',
'uninstall',
'reinstall',
'update',
'clone',
'apply',
'write',
'reset',
'clear',
'enable',
'disable',
'use-default',
'permissions',
// process and pane lifecycle
'run',
'run-stop',
'start',
'stop',
'kill',
'shutdown',
'close',
'split',
'switch',
'rename',
'focus',
// orchestration writes
'send',
'reply',
'dispatch',
'task-create',
'task-update',
'gate-create',
'gate-resolve',
// issue-tracker writes
'save-issue',
'comment',
'label',
'relation',
'assignee',
'priority',
'estimate',
'due-date',
// browser / computer / emulator input and navigation
'goto',
'back',
'forward',
'reload',
'click',
'dblclick',
'hover',
'fill',
'type',
'type-text',
'select',
'select-all',
'uncheck',
'keypress',
'press-key',
'inserttext',
'hotkey',
'paste-text',
'perform-secondary-action',
'drag',
'scroll',
'scrollintoview',
'wheel',
'move',
'up',
'down',
'tap',
'gesture',
'button',
'rotate',
'upload',
'download',
'dismiss',
'accept',
'highlight',
'viewport',
'geolocation',
'headers',
'credentials',
'offline',
'media',
'device',
'capture',
'intercept'
]
const UNSAFE_TOKENS = new Map([
...FOREGROUND_TOKENS.map((token) => [token, 'runs in the foreground or spawns a process']),
...MUTATING_TOKENS.map((token) => [token, 'can write outside ORCA_USER_DATA_PATH'])
])
// Deny lists miss new verbs (serve once escaped); require explicit read-only allowlisting for every pool token too.
const READ_ONLY_FUZZ_TOKENS = new Set([
// command tokens: every path they can form is a list/show or a parse error
'agent',
'agent-context',
'browser',
'environment',
'list',
'snapshot',
'status',
'terminal',
'vm',
'worktree',
// global flags and hostile strings, which reach no handler at all
'--json',
'--help',
'--pairing-code',
'--environment',
'--worktree',
'orca://pair?code=!!!',
'',
'-',
'--',
'--=',
'a'.repeat(300),
'näme-ünicode',
'💥',
'../..',
'x\ty'
])
function assertTokensSafe(tokens, context) {
for (const token of tokens) {
const reason = UNSAFE_TOKENS.get(token)
if (reason) {
throw new Error(`Refusing to run ${context}: "${token}" ${reason}`)
}
}
}
// Reject neither/both-list membership before any case runs; sampling cannot prove pool safety.
function assertFuzzPoolDeclaredReadOnly(tokens) {
for (const token of tokens) {
if (!READ_ONLY_FUZZ_TOKENS.has(token)) {
throw new Error(
`Fuzz token ${JSON.stringify(token)} is not declared in READ_ONLY_FUZZ_TOKENS — declare it read-only or drop it`
)
}
}
for (const token of READ_ONLY_FUZZ_TOKENS) {
if (UNSAFE_TOKENS.has(token)) {
throw new Error(
`Token ${JSON.stringify(token)} is declared both read-only and unsafe — the two lists disagree`
)
}
}
}
const baselineIndex = process.argv.indexOf('--baseline')
const baselineRev = baselineIndex === -1 ? 'HEAD' : process.argv[baselineIndex + 1]
assertMarkersFresh()
const isolatedUserData = mkdtempSync(join(REPO, '.equiv-userdata-'))
mkdirSync(join(isolatedUserData, 'empty'), { recursive: true })
let oldEntry
let newEntry
try {
// Build cases and check safety before expensive tsc compiles so unsafe tokens fail immediately.
const cases = buildCases(isolatedUserData)
console.log(`Building baseline (${baselineRev}) …`)
oldEntry = buildTree('old', baselineRev)
console.log('Building working tree …')
newEntry = buildTree('new', null)
console.log(`Comparing ${cases.length} invocations byte for byte …`)
let mismatches = 0
for (const [argv, env] of cases) {
const before = run(oldEntry, argv, env)
const after = run(newEntry, argv, env)
if (
before.status !== after.status ||
before.stdout !== after.stdout ||
before.stderr !== after.stderr
) {
mismatches += 1
console.error(`\nMISMATCH argv=${JSON.stringify(argv)} env=${JSON.stringify(env)}`)
console.error(` exit before=${before.status} after=${after.status}`)
if (before.stdout !== after.stdout) {
console.error(` stdout before=${JSON.stringify(before.stdout.slice(0, 400))}`)
console.error(` after =${JSON.stringify(after.stdout.slice(0, 400))}`)
}
if (before.stderr !== after.stderr) {
console.error(` stderr before=${JSON.stringify(before.stderr.slice(0, 400))}`)
console.error(` after =${JSON.stringify(after.stderr.slice(0, 400))}`)
}
}
}
if (mismatches > 0) {
throw new Error(`${mismatches} of ${cases.length} invocations differ`)
}
console.log(`\nAll ${cases.length} invocations byte-identical (stdout, stderr, exit code).`)
} finally {
rmSync(isolatedUserData, { recursive: true, force: true })
for (const label of ['old', 'new']) {
rmSync(resolve(REPO, `.equiv-out-${label}`), { recursive: true, force: true })
}
}
@@ -17,11 +17,12 @@ const { validatePluginFile, pluginManifestWarnings } = loadPluginSchemaModule(
const { parsePluginManifest } = loadPluginSchemaModule('manifest/plugin-manifest.ts')
// Why the CLI's main entry: `orca plugin validate` and `release-entry` run these same checks, and
// the printed contentHash must be what Orca computes at install.
const {
pluginCatalogReleaseForHead,
validateDeclaredPluginArtifacts,
validatePluginInstallContent
} = loadPluginSchemaModule('../../main/plugins/plugin-authoring-checks.ts')
const { pluginCatalogReleaseForHead } = loadPluginSchemaModule(
'../../main/plugins/plugin-catalog-release-entry.ts'
)
const { validateDeclaredPluginArtifacts, validatePluginInstallContent } = loadPluginSchemaModule(
'../../main/plugins/plugin-artifact-validation.ts'
)
// Why shared: `orca plugin validate` runs these same strict artifact checks.
const { strictPluginArtifactErrors, strictPluginArtifactWarnings } = loadPluginSchemaModule(
'authoring/plugin-strict-artifact-checks.ts'
@@ -4,7 +4,7 @@ import path from 'node:path'
import process from 'node:process'
import { runProcess } from '@orca/process-host'
import { afterEach, describe, expect, it } from 'vitest'
import { createPluginFromTemplate } from '../../../../src/cli/plugin-authoring/plugin-create'
import { createPluginFromTemplate } from '../../../../src/cli/plugins/authoring/plugin-create'
const SCRIPT = path.join(import.meta.dirname, './validate-plugin-files.mjs')
const roots = []
@@ -4,10 +4,8 @@ import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
const STARTUP_TIMEOUT_MS = 20_000
const APPIMAGE_EXTRACTION_TIMEOUT_MS = 60_000
const commandArgs = process.argv.slice(2)
const appImageArg = valueAfter('--appimage')
const pairingOnly = commandArgs.includes('--pairing-only')
if (!appImageArg) {
fail('Usage: run-headless-linux-pairing-docker.mjs --appimage /path/to/orca.AppImage')
}
@@ -32,28 +30,17 @@ const images = [
tag: 'orca-headless-pairing:debian-13',
base: 'debian@sha256:020c0d20b9880058cbe785a9db107156c3c75c2ac944a6aa7ab59f2add76a7bd',
libasound: 'libasound2t64'
},
{
name: 'ubuntu-22.04-baseline',
tag: 'orca-headless-pairing:ubuntu-22.04',
base: 'ubuntu@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982',
libasound: 'libasound2'
}
]
try {
docker(['volume', 'create', artifactVolume])
docker(['network', 'create', network])
for (const image of pairingOnly ? images.slice(0, 2) : images) {
for (const image of images) {
buildImage(image)
}
extractAppImage(images[0].tag)
if (!pairingOnly) {
await validateStartupMatrix()
await validateUnavailableContracts()
}
await validateAuthenticatedPairing()
await validateUnreachableOffer()
console.log('Headless Linux pairing Docker validation passed.')
} finally {
for (const container of containers) {
@@ -111,71 +98,6 @@ function extractAppImage(image) {
])
}
async function validateStartupMatrix() {
const required = images.slice(0, 2)
for (const image of required) {
for (const launch of ['direct', 'xvfb', 'dbus-xvfb', 'journal']) {
for (const mode of ['human', 'json']) {
const result = await startAndWait({ image, launch, mode, address: 'pair-host.test' })
validateReady(result.stdout, mode, 'pair-host.test', {
allowStdoutNoise: launch !== 'direct'
})
stopContainer(result.name)
console.log(`PASS ${image.name} ${launch} ${mode}`)
}
}
}
const baseline = await startAndWait({
image: images[2],
launch: 'xvfb',
mode: 'json',
address: '127.0.0.1'
})
validateReady(baseline.stdout, 'json', '127.0.0.1', { allowStdoutNoise: true })
stopContainer(baseline.name)
console.log(`PASS ${images[2].name} xvfb json`)
const publicEntry = await startAndWait({
image: images[0],
launch: 'direct',
mode: 'json',
address: '127.0.0.1',
appPath: '/artifacts/orca.AppImage',
startupTimeoutMs: APPIMAGE_EXTRACTION_TIMEOUT_MS
})
validateReady(publicEntry.stdout, 'json', '127.0.0.1', { allowStdoutNoise: true })
stopContainer(publicEntry.name)
console.log('PASS AppImage --appimage-extract-and-run --no-sandbox serve')
}
async function validateUnavailableContracts() {
const invalid = await startAndWait({
image: images[0],
launch: 'direct',
mode: 'json',
address: '0.0.0.0'
})
const invalidPayload = readyJson(invalid.stdout)
assert(invalidPayload.pairing?.available === false, 'wildcard pairing must be unavailable')
assert(
invalidPayload.pairing.reason === 'invalid_advertised_endpoint',
'wildcard pairing must expose invalid_advertised_endpoint'
)
stopContainer(invalid.name)
const disabled = await startAndWait({
image: images[1],
launch: 'journal',
mode: 'json',
address: '127.0.0.1',
noPairing: true
})
const disabledPayload = readyJson(disabled.stdout)
assert(disabledPayload.pairing?.reason === 'disabled_by_operator', 'disabled reason is missing')
stopContainer(disabled.name)
console.log('PASS explicit unavailable pairing contracts')
}
async function validateAuthenticatedPairing() {
const server = await startAndWait({
image: images[0],
@@ -220,22 +142,6 @@ async function validateAuthenticatedPairing() {
console.log('PASS paired E2EE updater inventory and manual-service fallback')
}
async function validateUnreachableOffer() {
const server = await startAndWait({
image: images[0],
launch: 'direct',
mode: 'json',
address: 'orca-pairing-server:6769',
port: '6768',
networkAlias: 'orca-pairing-server'
})
const payload = readyJson(server.stdout)
const client = runPairingClient(payload.pairing.url)
assert(client.status !== 0, 'a deliberately mismatched advertised port unexpectedly connected')
stopContainer(server.name)
console.log('PASS unreachable advertised endpoint fails the real client probe')
}
async function startAndWait({
image,
launch,
@@ -318,39 +224,6 @@ function hasCompleteReadyContract(stdout) {
return readyJsonObjects(stdout).length > 0
}
function validateReady(logs, mode, expectedHost, options = {}) {
if (mode === 'human') {
assert(
(logs.match(/^Orca server ready$/gm) ?? []).length === 1,
'human ready marker is not exact-once'
)
assert(logs.includes('Bound endpoint: ws://0.0.0.0:'), 'human bound endpoint is missing')
assert(
logs.includes(`Advertised endpoint: ws://${expectedHost}:`),
'human advertised endpoint is missing'
)
assert(logs.includes('Pairing URL: orca://pair?code='), 'human pairing URL is missing')
return
}
if (!options.allowStdoutNoise) {
const stdoutLines = logs.split(/\r?\n/).filter(Boolean)
assert(
stdoutLines.length === 1,
`JSON stdout must contain exactly one line, found ${stdoutLines.length}:\n${logs}`
)
}
const payload = readyJson(logs)
assert(payload.schemaVersion === 1, 'ready JSON schemaVersion is missing')
assert(payload.endpoint === payload.boundEndpoint, 'legacy JSON endpoint alias is inconsistent')
assert(payload.boundEndpoint?.startsWith('ws://0.0.0.0:'), 'JSON bound endpoint is invalid')
assert(payload.advertisedEndpoint?.includes(expectedHost), 'JSON advertised endpoint is invalid')
assert(payload.pairing?.available === true, 'JSON pairing offer is unavailable')
assert(
new URL(payload.boundEndpoint).port === new URL(payload.advertisedEndpoint).port,
'an advertised host without a port must use the actual bound port'
)
}
function readyJson(logs) {
const matches = readyJsonObjects(logs)
assert(matches.length === 1, `expected one ready JSON object, found ${matches.length}`)
@@ -24,18 +24,6 @@ const CLEANUP_TIMEOUT_MS = 30_000
// Exact statuses reject silent no-op launches as well as crashes.
const CASES = [
{
name: 'nofuse-userns-bundled-help',
expectStatus: 0,
expectOutput: 'Usage: orca <command>',
why: 'The bundled launcher must run with no FUSE, no display, and userns restricted (#11609, #12530).'
},
{
name: 'nofuse-userns-bundled-version',
expectStatus: 0,
expectOutput: /^\d+\.\d+\.\d+/m,
why: 'A deployment must be able to read the installed version without a display (#13719).'
},
{
name: 'nofuse-userns-bundled-status',
// No runtime is running; the CLI must report that itself.
@@ -43,32 +31,12 @@ const CASES = [
expectOutput: 'appRunning',
why: 'A command that needs the runtime must report its absence, not abort.'
},
{
name: 'nofuse-userns-bundled-skills',
expectStatus: 0,
// Why: the rendered help header, not a bare 'skills' — the case name contains that word.
expectOutput: 'Usage: orca skills',
why: 'skills is a pure-text command that must never need Chromium (#14229).'
},
{
name: 'nofuse-userns-bundled-worktree',
expectStatus: 1,
expectOutput: "Orca is not running. Run 'orca open' first.",
why: 'A runtime-dependent command must report the missing runtime, not abort.'
},
{
name: 'nofuse-nosandbox-direct-binary-skills',
expectStatus: 0,
expectOutput: 'Usage: orca skills',
why: 'A direct binary launch that reaches JavaScript must run the command, not boot a GUI (#14229).'
},
{
name: 'nofuse-nosandbox-direct-binary-gui',
// A missing display is an expected diagnosis, not a crash.
expectStatus: 1,
expectOutput: 'needs a usable display server',
why: 'A desktop launch with no display must diagnose it instead of dying in uv_close (#13719).'
},
{
name: 'stale-display-nosandbox-direct-binary-gui',
expectStatus: 1,
@@ -179,7 +179,7 @@ function serializeEmbeddedModule(guides) {
})
.join(',\n')
return `// Generated by config/scripts/generate-bundled-skill-guides.mjs. Do not edit.\n\nexport type BundledSkillGuideReference = {\n readonly name: string\n readonly markdown: string\n}\n\nexport type BundledSkillGuide = {\n readonly name: string\n readonly description: string\n readonly markdown: string\n readonly aliases: readonly string[]\n readonly references: readonly BundledSkillGuideReference[]\n}\n\n${markdownConstants}\n\n// oxfmt-ignore\nexport const BUNDLED_SKILL_GUIDES = [\n${guideEntries}\n] as const satisfies readonly BundledSkillGuide[]\n`
return `// Generated by config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs. Do not edit.\n\nexport type BundledSkillGuideReference = {\n readonly name: string\n readonly markdown: string\n}\n\nexport type BundledSkillGuide = {\n readonly name: string\n readonly description: string\n readonly markdown: string\n readonly aliases: readonly string[]\n readonly references: readonly BundledSkillGuideReference[]\n}\n\n${markdownConstants}\n\n// oxfmt-ignore\nexport const BUNDLED_SKILL_GUIDES = [\n${guideEntries}\n] as const satisfies readonly BundledSkillGuide[]\n`
}
async function readGuideReferences(repoRoot, guideName) {
@@ -317,7 +317,7 @@ async function buildArtifacts(repoRoot = REPO_ROOT) {
return [
{
path: path.join(repoRoot, 'src', 'cli', 'bundled-skill-guides.ts'),
path: path.join(repoRoot, 'src', 'cli', 'skills', 'bundled-skill-guides.ts'),
content: serializeEmbeddedModule(guides)
},
...projections
@@ -208,12 +208,15 @@ function hookLines({ hooks, hookContribution, typeText }) {
function renderHostApi(sources, { frame, worker }) {
const { hostApi, hostDoc, capabilities } = sources
const names = hostApi.PLUGIN_HOST_METHOD_NAMES
const names = hostApi.PLUGIN_STABLE_HOST_METHOD_NAMES
const methodsFor = (surface) => hostApi.pluginHostMethodsFor(surface).map(code).join(', ')
const methods = names.map((name) => {
const spec = hostApi.PLUGIN_HOST_API[name]
const facts = [
spec.capability ? `capability ${code(spec.capability)}` : 'no capability',
...Object.entries(spec.paramCapabilities ?? {}).map(
([param, kind]) => `${code(param)} also needs ${code(kind)}`
),
spec.surfaces.join(', '),
`since ${hostVersion(sources, spec.since)}`,
...(spec.deprecatedSince === undefined
@@ -1,7 +1,6 @@
import { createHash } from 'node:crypto'
import { execFileSync } from 'node:child_process'
import {
chmod,
copyFile,
mkdir,
mkdtemp,
@@ -114,29 +113,6 @@ describe('skill bundle manifest generator', () => {
expect(digests).toHaveLength(2)
})
it.runIf(process.platform !== 'win32')(
'rejects executable files in shipped skill packages',
async () => {
const packageRoot = await createPackage()
await writeFile(path.join(packageRoot, 'SKILL.md'), 'skill\n')
await writeFile(path.join(packageRoot, 'run.sh'), '#!/bin/sh\necho ok\n')
await chmod(path.join(packageRoot, 'run.sh'), 0o755)
await expect(collectPackageFiles(packageRoot)).rejects.toThrow(
'Executable file is not allowed in a shipped skill: run.sh'
)
}
)
it.runIf(process.platform === 'linux')('rejects case-colliding paths', async () => {
const packageRoot = await createPackage()
await writeFile(path.join(packageRoot, 'SKILL.md'), 'skill')
await writeFile(path.join(packageRoot, 'Readme.md'), 'one')
await writeFile(path.join(packageRoot, 'README.md'), 'two')
await expect(collectPackageFiles(packageRoot)).rejects.toThrow('Case-colliding skill paths')
})
it('keeps guarding a directory or link that only wears an OS metadata name', async () => {
const packageRoot = await createPackage()
await writeFile(path.join(packageRoot, 'SKILL.md'), 'demo skill\n')
@@ -126,13 +126,6 @@ describe('main sync-spawn ratchet', () => {
expect(uses(source)).toEqual({ count: 0, symbols: [] })
})
it.each(['import {', 'const broken = ;'])(
'fails closed on source it cannot parse: %s',
(source) => {
expect(() => uses(source)).toThrow('Cannot parse src/main/consumer.ts')
}
)
it('fails a new file and a rising count, and passes at the baseline count', () => {
const one = "import { spawnSync } from 'node:child_process'\nspawnSync('a')"
const root = tree({
@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os'
import path from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { main as fsMain } from './check-main-sync-fs-ratchet.mjs'
import { collectSyncLaunchUses } from '../../process-boundaries/check-main-sync-spawn-ratchet.mjs'
import '../../process-boundaries/check-main-sync-spawn-ratchet.mjs'
import { countBlockingApis, main } from './check-main-blocking-api-ratchet.mjs'
const file = 'src/main/consumer.ts'
@@ -24,34 +24,6 @@ afterEach(() => {
})
describe('main blocking API ratchets', () => {
it('counts import aliases, namespace APIs, CommonJS and Atomics aliases by category', () => {
expect(
counts(`import { scryptSync as hash } from 'node:crypto'
import * as z from 'node:zlib'
const { pbkdf2Sync } = require('crypto')
hash('pw'); pbkdf2Sync('pw'); z.gzipSync('bytes'); z.futureSync('bytes')
const { wait: sleep } = Atomics; sleep(buffer, 0, 0)`)
).toEqual({ compression: 2, crypto: 2, atomics: 1, sqlite: 0, ipc: 0 })
})
it.each([
["import { DatabaseSync as DB } from 'node:sqlite'", 'DB'],
["import DB from 'better-sqlite3'", 'DB'],
["import DB from '../sqlite/sync-database'", 'DB'],
["const { DatabaseSync: DB } = require('node:sqlite')", 'DB'],
["const { DatabaseSync: DB } = process.getBuiltinModule('node:sqlite')", 'DB'],
["import * as sqlite from 'node:sqlite'", 'sqlite.DatabaseSync']
])('guards SQLite constructors and statement work: %s', (load, ctor) => {
expect(
counts(`${load}
const db = new ${ctor}('db')
db.exec('schema')
const statement = db.prepare('select')
statement.run(); statement.get(); statement.all(); for (const row of statement.iterate()) {}
db.prepare('select').get()`)
).toEqual({ compression: 0, crypto: 0, atomics: 0, sqlite: 9, ipc: 0 })
})
it('guards typed SQL receivers without charging Map.get or async backup', () => {
expect(
counts(`import type Database from '../sqlite/sync-database'
@@ -77,31 +49,6 @@ describe('main blocking API ratchets', () => {
).toBe(5)
})
it('guards callable better-sqlite3 construction and modern builtin subprocess loads', () => {
expect(
counts(
`import Database from 'better-sqlite3'; const db = Database('test'); db.exec('sql'); db.prepare('sql').get()`
).sqlite
).toBe(4)
expect(
collectSyncLaunchUses(file, `process.getBuiltinModule('node:child_process').execSync('test')`)
.count
).toBe(1)
})
it('guards synchronous preload requests and main replies', () => {
expect(
counts(
"import { ipcRenderer as ipc } from 'electron'; ipc.sendSync('get')",
'src/preload/index.ts'
).ipc
).toBe(1)
expect(counts("ipcMain.on('get', (event) => { event.returnValue = value })").ipc).toBe(1)
expect(counts("ipc.sendSync('get'); event.returnValue = value", 'src/shared/ipc.ts').ipc).toBe(
2
)
})
it('ignores strings, comments, unrelated SQL-shaped methods and nonblocking APIs', () => {
expect(
counts(`import * as c from 'node:crypto'; import * as z from 'node:zlib'
@@ -1,55 +0,0 @@
import {
cpSync,
mkdtempSync,
mkdirSync,
readFileSync,
rmSync,
symlinkSync,
writeFileSync
} from 'node:fs'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { expect, it } from 'vitest'
const require = createRequire(import.meta.resolve('@tailwindcss/vite'))
const { compile } = require('@tailwindcss/node')
const { Scanner } = require('@tailwindcss/oxide')
it('generates renderer CSS from production files while excluding test and spec files', async () => {
const root = mkdtempSync(join(tmpdir(), 'orca-tailwind-sources-'))
try {
const renderer = join(root, 'src', 'renderer', 'src')
const assets = join(renderer, 'assets')
cpSync(resolve('src/renderer/src/assets'), assets, { recursive: true })
cpSync(resolve('src/shared/styles'), join(root, 'src', 'shared', 'styles'), { recursive: true })
symlinkSync(resolve('node_modules'), join(root, 'node_modules'), 'junction')
const components = join(renderer, 'components')
mkdirSync(components)
writeFileSync(join(components, 'example.tsx'), 'export const style = "z-[31415]"')
writeFileSync(join(components, 'example.test.tsx'), 'export const style = "z-[27182]"')
writeFileSync(join(components, 'example.spec.ts'), 'export const style = "z-[16180]"')
const compiler = await compile(readFileSync(join(assets, 'main.css'), 'utf8'), {
base: assets,
onDependency() {}
})
const automaticSources =
compiler.root === 'none' ? [] : [compiler.root ?? { base: root, pattern: '**/*' }]
const scanner = new Scanner({
sources: [
...automaticSources.map((source) => ({ ...source, negated: false })),
...compiler.sources
]
})
const css = compiler.build(scanner.scan())
expect(css).toContain('z-index: 31415')
expect(css).not.toContain('z-index: 27182')
expect(css).not.toContain('z-index: 16180')
writeFileSync(join(components, 'added.tsx'), 'export const style = "z-[14142]"')
expect(compiler.build(scanner.scan())).toContain('z-index: 14142')
} finally {
rmSync(root, { recursive: true, force: true })
}
})
@@ -1,6 +1,6 @@
// A startOrcad test that fails partway must not leave latches closed: later tests in the worker would wait 10 s.
import { afterEach } from 'vitest'
import { _resetOrcadFeatureReadinessForTests } from '../../../../src/main/runtime/rpc/orcad-feature-readiness'
import { _resetOrcadFeatureReadinessForTests } from '../../../../src/main/runtime/rpc/startup/orcad-readiness'
afterEach(() => {
_resetOrcadFeatureReadinessForTests()
@@ -12,14 +12,13 @@ const NODE_RUNTIME_INCLUDE = [
'tests/e2e/cursor-quota-transport.unit.test.ts',
'tests/e2e/helpers/electron-process-shutdown.unit.test.ts',
'src/main/ai-vault-search/storage/session-search-index-writer.test.ts',
'src/main/ai-vault/session-scanner-unlimited-dedup.test.ts',
'src/main/ai-vault/parsing/session-scanner-unlimited-dedup.test.ts',
// The parse cache persists through node:sqlite.
'src/main/ai-vault/parse-cache/session-parse-cache-antigravity-upgrade.test.ts',
'src/main/ai-vault/parse-cache/session-parse-cache-persistence.test.ts',
'src/main/ai-vault/session-scanner-scope-cwd-facts.test.ts',
'src/main/ai-vault/session-scanner-scope-walk.test.ts',
'src/main/ai-vault/scope/session-scanner-scope-walk.test.ts',
// The forced-probe handoff race opens only under V8's promise scheduling, which Electron main uses.
'src/main/github/rate-limit.test.ts',
'src/main/github/api/rate-limit.test.ts',
// Electron main bridges streams with Node's implementation.
'src/main/host/electron-http-client.test.ts',
'src/main/browser/extensions/native-messaging/browser-extension-native-host-process-backpressure.test.ts',
@@ -108,28 +107,26 @@ const NODE_RUNTIME_INCLUDE = [
// Playwright's CDP transport needs Node's WebSocket implementation.
'src/main/browser/guest-lifecycle/popups/presentation/popup-origin-chrome.electron.test.ts',
'src/main/browser/guest-lifecycle/navigation/browser-link-materialization.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts',
'src/main/ai-vault/scanners/devin/session-scanner-devin-db.test.ts',
'src/shared/workspace-session/workspace-session-salvage.test.ts',
'src/main/git/repositories/discovery/repo-detection.test.ts',
'src/renderer/src/components/browser-pane/annotate/browser-grab-toast-owner-cleanup.test.tsx',
'src/main/window/native-chat-paste-files.test.ts',
'src/main/native-chat/paste-files/native-chat-paste-files.test.ts',
'src/main/computer/desktop-script/runtime-host/desktop-script-serve-channel.test.ts',
'src/main/providers/local-pty/local-pty-child-process-verdict.test.ts',
'src/main/providers/working-directory-validation.test.ts',
'src/main/pty/process-retirement/posix-pty-process-groups.integration.test.ts',
'src/shared/ephemeral-vm-recipe-output-capture.test.ts',
'src/main/window/main-window-updater-deferred-setup.test.ts',
'src/main/updater/lifecycle/main-window-updater-deferred-setup.test.ts',
'src/renderer/src/components/terminal-pane/host-state/terminal-pane-host-state-memo.test.ts',
'src/main/native-chat/agent-session-journal/**/*.test.ts',
'src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-terminal-writer-acquire.test.ts',
'src/main/runtime/agent-session/records/agent-session-conversation-name-store.test.ts',
'src/main/runtime/rpc/ws-transport.test.ts',
'src/main/runtime/rpc/methods/structured-sessions/attachments/structured-agent-session-visual.test.ts',
// Bun resolves 'ws' to its built-in client, which bypasses the patched package.
'src/main/runtime/rpc/ws-empty-payload-frame-write.test.ts',
'src/main/runtime/rpc/transports/websocket/ws-empty-payload-frame-write.test.ts',
'src/shared/remote-runtime/remote-runtime-client.test.ts',
'src/shared/remote-runtime/remote-runtime-connect-bound.test.ts',
// Bun's built-in ws client ignores maxPayload; the product opener only runs on Node.
@@ -144,7 +141,7 @@ const NODE_RUNTIME_INCLUDE = [
'src/main/ipc/renderer-shutdown-checkpoint-sleep-records.test.ts',
'src/main/runtime/agent-session/records/structured-agent-session-startup-tab-restore.test.ts',
'src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts',
'src/main/runtime/mobile-cloud-relay/relay-control-client.test.ts',
'src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client.test.ts',
'src/main/ipc/filesystem/watchers/filesystem-watcher-ignore-real.test.ts',
'src/main/ipc/runtime-environments/runtime-environment-preference.test.ts',
'src/renderer/src/components/emulator-pane/emulator-device-frame-visibility.test.tsx',
@@ -130,10 +130,10 @@ const CODEX_INDEX_HEAL_CONTRACT_PREFIXES = [
]
const ORCAD_BROWSER_PREFIXES = [
'src/main/orcad/external-chromium-',
'src/main/orcad/orcad-browser-provider',
'src/main/orcad/orcad-agent-browser-binary',
'src/main/orcad/electron-serve-browser-process'
'src/main/orcad/browser-providers/chromium/external-chromium-',
'src/main/orcad/browser-providers/orcad-browser-provider',
'src/main/orcad/browser-providers/orcad-agent-browser-binary',
'src/main/orcad/browser-providers/electron/electron-serve-browser-process'
]
// Include page build/verification/manifest/packaging, entry/routes/imported mobile sources and render-check shell policy.
@@ -211,7 +211,7 @@ const CROSS_VERSION_WIRE_PREFIXES = [
'src/shared/browser/client-hosting/browser-client-host-protocol',
'src/shared/browser/network/tunnels/browser-network-tunnel-protocol',
// Both released/current WebSocket transports must accept web upgrade marker (cross-version-web-upgrade-marker).
'src/main/runtime/rpc/ws-transport.ts',
'src/main/runtime/rpc/transports/websocket/ws-transport.ts',
'src/shared/web-origin-security.ts',
'src/shared/browser/client-hosting/browser-client-host-placement',
'src/shared/agent-launch/contracts/agent-launch-intent',
@@ -241,7 +241,7 @@ const CROSS_VERSION_WIRE_PREFIXES = [
'src/shared/agent-session-record',
'src/shared/agent-session-provider-handle',
'src/shared/agent-session/journal/',
'src/main/ai-vault/structured-session-ownership.ts',
'src/main/ai-vault/structured-sessions/structured-session-ownership.ts',
'src/main/native-chat/agent-session-journal/',
'src/main/native-chat/agent-session-wire/',
// The released phone contract runs the current discovery owner and Claude producer.
@@ -258,11 +258,11 @@ const CROSS_VERSION_WIRE_PREFIXES = [
'src/main/runtime/rpc/dispatcher',
// Run on every request the suites dispatch, whatever its method.
'src/main/runtime/rpc/core.ts',
'src/main/runtime/rpc/errors.ts',
'src/main/runtime/rpc/orchestration-contract-fence.ts',
'src/main/runtime/rpc/orchestration-session-caller.ts',
'src/main/runtime/rpc/orchestration-legacy-compatibility.ts',
'src/main/runtime/rpc/orchestration-mutation-executor.ts',
'src/main/runtime/rpc/responses/rpc-response.ts',
'src/main/runtime/rpc/orchestration/compatibility/orchestration-contract-fence.ts',
'src/main/runtime/rpc/orchestration/callers/orchestration-session-caller.ts',
'src/main/runtime/rpc/orchestration/compatibility/orchestration-legacy-compatibility.ts',
'src/main/runtime/rpc/orchestration/mutations/orchestration-mutation-executor.ts',
'src/shared/orchestration/orchestration-rpc-contract.ts',
'src/main/runtime/rpc/methods/agent-launch/agent-launch',
'src/main/runtime/rpc/methods/history/ai-vault.ts',
@@ -132,7 +132,7 @@ export const PR_E2E_SOURCE_ROUTES = [
file
) ||
(isProductSource(file) &&
/^src\/(?:cli\/runtime\/(?:launch|serve-)|main\/orcad\/(?:main|orcad-entry|orcad-command-startup|orcad-instance-lock|orcad-command-arguments|orcad-lifecycle)\.ts$|main\/startup\/desktop-profile-instance-lock\.ts$|main\/daemon\/(?:[^/]+\/)*daemon-(?:spawner|endpoint-adoption|init)|main\/server\/serve-)/.test(
/^src\/(?:cli\/runtime\/(?:launch|serve-)|main\/orcad\/(?:main|orcad-entry|orcad-command-startup|orcad-instance-lock|orcad-command-arguments|orcad-lifecycle)\.ts$|main\/startup\/profile-startup\/desktop-profile-instance-lock\.ts$|main\/daemon\/(?:[^/]+\/)*daemon-(?:spawner|endpoint-adoption|init)|main\/server\/serve-)/.test(
file
))
},
@@ -142,14 +142,16 @@ export const PR_E2E_SOURCE_ROUTES = [
matches: (file) =>
file === 'tests/e2e/helpers/orca-serve-cli-host.ts' ||
(isProductSource(file) &&
/^src\/main\/startup\/(?:windows-app-data-path|main-process-preflight)\.ts$/.test(file))
/^src\/main\/startup\/(?:windows-startup\/windows-app-data-path|main-process-preflight)\.ts$/.test(
file
))
},
{
id: 'clipboard.windows-copy-file',
specs: ['tests/e2e/file-explorer-copy-file-windows.spec.ts'],
matches: (file) =>
isProductSource(file) &&
/^src\/(?:main\/window\/clipboard-(?:file-copy|ipc-handlers)|renderer\/src\/components\/right-sidebar\/file-explorer\/panel\/file-explorer-row-file-transfer)\.ts$/.test(
/^src\/(?:main\/window\/clipboard\/clipboard-(?:file-copy|ipc-handlers)|renderer\/src\/components\/right-sidebar\/file-explorer\/panel\/file-explorer-row-file-transfer)\.ts$/.test(
file
)
},
@@ -492,7 +494,7 @@ export const PR_E2E_SOURCE_ROUTES = [
matches: (file) =>
file === 'tests/e2e/helpers/connector-fake-claude.ts' ||
(isProductSource(file) &&
/^src\/(?:main\/external-conversations\/|main\/runtime\/rpc\/methods\/connector\.ts$|cli\/(?:handlers|specs)\/connector\.ts$|shared\/rpc-contract\/connector-params\.ts$)/.test(
/^src\/(?:main\/external-conversations\/|main\/runtime\/rpc\/methods\/connector\.ts$|cli\/(?:connectors|specs)\/connector\.ts$|shared\/rpc-contract\/connector-params\.ts$)/.test(
file
))
}
@@ -51,7 +51,7 @@ export const PR_EDITOR_PREVIEW_E2E_SOURCE_ROUTES = [
) ||
[
'src/main/ipc/filesystem/filesystem-read-handlers.ts',
'src/main/startup/privileged-custom-schemes.ts',
'src/main/startup/chromium-policy/privileged-custom-schemes.ts',
'src/preload/api/fs-bridge.ts',
'src/renderer/src/runtime/files/reads/runtime-file-range-preview.ts',
'src/renderer/src/runtime/files/reads/runtime-file-read-client.ts',
@@ -15,16 +15,7 @@ function featureRoute(id, specs, prefixes) {
// Feature areas whose specs the nightly suite alone used to select (gap audit, Oct 2026).
export const PR_FEATURE_AREA_E2E_SOURCE_ROUTES = [
featureRoute(
'feature.automations-ui',
['automation-prompt-disclosure'],
[`${COMPONENTS}automations/`]
),
featureRoute(
'feature.sparse-checkout',
['sparse-explorer-proof', 'sparse-preset-proof'],
[`${COMPONENTS}sparse/`]
),
featureRoute('feature.sparse-checkout', ['sparse-explorer-proof'], [`${COMPONENTS}sparse/`]),
featureRoute(
'feature.onboarding',
['onboarding', 'onboarding-language-catalog-retry'],
@@ -129,7 +129,6 @@ export const LINUX_PACKAGE_TESTS = [
'src/main/browser/guest-lifecycle/popups/presentation/popup-origin-chrome.electron.test.ts',
'src/main/browser/guest-lifecycle/navigation/browser-link-materialization.electron.test.ts',
'src/main/browser/client-pages/renderer/browser-client-page-renderer-lifecycle.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-native-compatibility.electron.test.ts',
'src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-compatibility.electron.test.ts',
'src/main/browser/extensions/api-runtime/browser-extension-api.electron.test.ts',
@@ -195,10 +194,10 @@ export const WINDOWS_BOUNDARY_TESTS = [
'src/main/cli/wsl/wsl-cli-powershell-boundary.test.ts',
'src/main/cursor/hook-service.test.ts',
'src/main/orca-profiles/profile-index-store.test.ts',
'src/main/startup/windows-install-dir-acl-repair.win32.test.ts',
'src/main/startup/windows-app-data-path.test.ts',
'src/main/startup/windows-std-handle-inheritance.win32.test.ts',
'src/main/startup/windows-isolated-spawner.win32.test.ts',
'src/main/startup/windows-install-permissions/windows-install-dir-acl-repair.win32.test.ts',
'src/main/startup/windows-startup/windows-app-data-path.test.ts',
'src/main/startup/windows-startup/windows-std-handle-inheritance.win32.test.ts',
'src/main/startup/windows-startup/windows-isolated-spawner.win32.test.ts',
'src/main/runtime/workspaces/catalog/worktree-scan-admin-fingerprint-gate.test.ts',
'src/shared/secure-storage/secure-path-windows-acl.win32.test.ts',
'src/main/runtime/rpc/server/pairing/unreadable-secret-store-preservation.win32.test.ts',
@@ -123,9 +123,9 @@ const SPECIFIC_PLUGIN_ROUTES = [
'src\\/main\\/plugins\\/panels\\/(?:frame-serving\\/|guest-isolation\\/|desktop-actions\\/)?plugin-frame-',
'src\\/shared\\/plugins\\/frames\\/plugin-(?:frame-guest|panel-shell)',
'src\\/preload\\/plugin-frame-guest',
'src\\/main\\/window\\/(?:main-window-webview-security|attach-main-window-services|host-frame-permission-policy)',
'src\\/main\\/window\\/(?:security/(?:main-window-webview-security|host-frame-permission-policy)|attach-main-window-services)',
'src\\/main\\/ipc\\/(?:plugins|plugin-frame-entry-ipc)\\.ts$',
'src\\/main\\/startup\\/main-process-plugins',
'src\\/main\\/startup\\/plugin-startup\\/main-process-plugins',
'config\\/build-plugins\\/main-build-entry-points',
'src\\/main\\/browser\\/guest-lifecycle\\/input\\/browser-guest-shortcut-(?:forwarding|dispatch)',
'src\\/renderer\\/src\\/components\\/(?:plugins\\/frames\\/|right-sidebar\\/plugin-panels\\/PluginPanel\\.tsx$)',
@@ -165,7 +165,7 @@ const SPECIFIC_PLUGIN_ROUTES = [
'src\\/main\\/plugins\\/panels\\/(?:frame-serving\\/)?plugin-frame-(?:module-|source-serving|protocol\\.ts$)',
'src\\/main\\/plugins\\/plugin-content-hash',
'src\\/shared\\/plugins\\/frames\\/plugin-frame-source-modules',
'src\\/cli\\/plugin-authoring\\/plugin-(?:type-files|components-template)',
'src\\/cli\\/plugins\\/authoring\\/plugin-(?:type-files|components-template)',
'examples\\/plugins\\/hello-tsx\\/'
].join('|'),
'plugin-frame-guest|plugin-smoke'
@@ -218,7 +218,7 @@ const SPECIFIC_PLUGIN_ROUTES = [
'src\\/(?:main\\/plugins\\/view\\/(?!plugin-view-replay)|shared\\/plugins\\/view\\/)',
'src\\/main\\/runtime\\/rpc\\/methods\\/plugin-view',
// Every `plugin shot` checks for a chrome region and resolves the default shots folder.
'src\\/cli\\/(?:handlers\\/plugin-(?:view|shot-chrome|shot-default-dir)|specs\\/plugin-view)',
'src\\/cli\\/(?:plugins\\/view\\/plugin-(?:view|shot-chrome|shot-default-dir)|specs\\/plugin-view)',
'src\\/renderer\\/src\\/hooks\\/(?:ipc-events\\/plugin-view-ipc-bridge|use-document-dark-theme)',
'src\\/renderer\\/src\\/components\\/right-sidebar\\/(?:plugin-panels\\/health\\/(?:plugin-panel-draw-status|use-plugin-panel-draw-report)|right-sidebar-panel-content)'
].join('|'),
@@ -233,14 +233,14 @@ const SPECIFIC_PLUGIN_ROUTES = [
`${STORE}shot-replay`,
'src\\/main\\/plugins\\/view\\/plugin-view-replay',
'src\\/main\\/plugins\\/testing\\/plugin-test-(?:hooks|register)',
'src\\/cli\\/(?:handlers|specs)\\/plugin-shot-scenario',
'src\\/cli\\/plugin-authoring\\/plugin-(?:panel-check|test-runner|test-node)'
'src\\/cli\\/(?:plugins\\/view|specs)\\/plugin-shot-scenario',
'src\\/cli\\/plugins\\/authoring\\/plugin-(?:panel-check|test-runner|test-node)'
].join('|')
),
pluginRoute(
'plugins.view-shot-editor',
['plugin-shot-editor'],
'src\\/cli\\/handlers\\/plugin-shot-editor'
'src\\/cli\\/plugins\\/view\\/plugin-shot-editor'
),
pluginRoute(
'plugins.startup-and-manifest',
@@ -18,11 +18,11 @@ export const SHELL_PREFIXES = [
'src/main/runtime/terminal/wait/tui-idle-evidence-source.ts',
'src/main/runtime/terminal/wait/tui-idle-evidence.ts',
'src/main/runtime/terminal/wait/tui-idle-zsh-preexec-hook-order.live-shell.test.ts',
'src/main/startup/shell-probe-terminal-detach',
'src/main/startup/shell-probe-lifetime',
'src/main/startup/hydrate-shell-path',
'src/main/startup/login-shell-environment',
'src/main/startup/shell-path-probe',
'src/main/startup/shell-environment/shell-probe-terminal-detach',
'src/main/startup/shell-environment/shell-probe-lifetime',
'src/main/startup/shell-environment/hydrate-shell-path',
'src/main/startup/shell-environment/login-shell-environment',
'src/main/startup/shell-environment/shell-path-probe',
'src/renderer/src/components/terminal-pane/pty-connection/emulation/fish-color-scheme',
'src/shared/fish-',
'src/shared/terminal/pty/ingress/pty-reply-echo-shapes',
@@ -1,6 +1,6 @@
[
{
"filePath": "src/main/startup/main-process-quit.ts",
"filePath": "src/main/startup/quit/main-process-quit.ts",
"kind": "object-property:message",
"text": "before-quit allowed for update install",
"dynamic": false,
@@ -8,7 +8,7 @@
"reason": "Updater lifecycle diagnostics are not displayed as UI copy."
},
{
"filePath": "src/main/startup/main-process-quit.ts",
"filePath": "src/main/startup/quit/main-process-quit.ts",
"kind": "object-property:message",
"text": "will-quit cleanup for update install; daemonTeardown=disconnect",
"dynamic": false,
@@ -16,7 +16,7 @@
"reason": "Updater lifecycle diagnostics are not displayed as UI copy."
},
{
"filePath": "src/main/startup/profile-state-recovery-preflight.ts",
"filePath": "src/main/startup/profile-startup/profile-state-recovery-preflight.ts",
"kind": "object-property:message",
"text": "Invalid profile-state recovery launch.",
"dynamic": false,
+8 -7
View File
@@ -64,6 +64,7 @@
"../src/main/ipc/worktree-linked-work-item-metadata.ts",
"../src/main/ipc/worktree-metadata-merge.ts",
"../src/main/wsl/discovery/wsl-availability.ts",
"../src/main/wsl/discovery/wsl-discovery-change.ts",
"../src/main/wsl/filesystem/wsl-directory-probe-command.ts",
"../src/main/wsl/discovery/wsl-distro-list-output.ts",
"../src/main/wsl/discovery/wsl-distro-retry.ts",
@@ -87,13 +88,13 @@
"../src/main/persistence/applying-settings/ui-selection-normalization.ts",
"../src/main/persistence/applying-settings/ui-interaction-merge.ts",
"../src/main/credentials/protected-secret-persistence.ts",
"../src/main/startup/serve-desktop-activation.ts",
"../src/main/startup/serve-mode-argv.ts",
"../src/main/startup/single-instance-lock.ts",
"../src/main/startup/startup-diagnostics.ts",
"../src/main/window/focus-existing-window.ts",
"../src/main/window/foreground-activation-policy.ts",
"../src/main/window/macos-app-activation.ts"
"../src/main/startup/serve-launch/serve-desktop-activation.ts",
"../src/main/startup/serve-launch/serve-mode-argv.ts",
"../src/main/startup/profile-startup/single-instance-lock.ts",
"../src/main/startup/diagnostics/startup-diagnostics.ts",
"../src/main/window/activation/focus-existing-window.ts",
"../src/main/window/activation/foreground-activation-policy.ts",
"../src/main/window/activation/macos-app-activation.ts"
],
"compilerOptions": {
"paths": {

Some files were not shown because too many files have changed in this diff Show More