mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
Merge main for freeze capture validation
This commit is contained in:
+1
-1
@@ -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.
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/**'
|
||||
|
||||
@@ -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*'
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 ?? ''
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/**',
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+258
-282
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
|
||||
+1
-1
@@ -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',
|
||||
|
||||
+9
-3
@@ -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"
|
||||
},
|
||||
|
||||
-4
@@ -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.
|
||||
|
||||
+141
@@ -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' }
|
||||
|
||||
+1
-1
@@ -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 = {}
|
||||
|
||||
+8
-2
@@ -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) {
|
||||
|
||||
+4
-1
@@ -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 = []
|
||||
|
||||
+1
-128
@@ -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}`)
|
||||
|
||||
-32
@@ -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
|
||||
|
||||
+4
-1
@@ -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')
|
||||
|
||||
-7
@@ -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({
|
||||
|
||||
+1
-54
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user