mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
Merge current main for activity index review
Preserve the original implementation and authorship while preparing the review head. Co-authored-by: Neil <neil@stably.ai>
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.
|
||||
|
||||
@@ -12,24 +12,57 @@ body:
|
||||
|
||||
Please write your issue in English.
|
||||
|
||||
- type: dropdown
|
||||
id: where
|
||||
attributes:
|
||||
label: Where were you using Orca?
|
||||
description: Where you saw the bug.
|
||||
options:
|
||||
- Desktop only
|
||||
- Desktop + SSH host
|
||||
- Desktop + Orca server
|
||||
- Desktop + WSL
|
||||
- Mobile app
|
||||
- Web browser
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: 'The device you were using. WSL: pick Windows. Phone: pick iOS or Android.'
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
- iOS
|
||||
- Android
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: remote_os
|
||||
attributes:
|
||||
label: Remote host operating system
|
||||
description: Only for SSH, Orca server or web.
|
||||
options:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- Other
|
||||
- Not sure
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: orca_version
|
||||
attributes:
|
||||
label: Orca version
|
||||
description: Include the exact Orca version if you can. You can find it in `Orca > About Orca`.
|
||||
placeholder: v0.14.2
|
||||
description: 'macOS: Orca > About Orca. Windows/Linux: Help > About Orca. Mobile: Settings > About.'
|
||||
placeholder: v1.4.2
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { test } from 'node:test'
|
||||
import { createAccessTokenSource } from './gcloud-access-token.mjs'
|
||||
import {
|
||||
CloudSqlRolloutLease,
|
||||
LEASE_TTL_MS,
|
||||
@@ -102,26 +101,6 @@ const HOLDER = {
|
||||
runAttempt: '1'
|
||||
}
|
||||
|
||||
test('acquires a lease on an empty object with ifGenerationMatch=0', async () => {
|
||||
const storage = fakeStorage()
|
||||
const claim = await leaseFor(storage).acquire(HOLDER)
|
||||
|
||||
assert.equal(claim.state, 'created')
|
||||
const upload = storage.state.requests.find((request) => request.path.startsWith('/upload/'))
|
||||
assert.equal(upload.search.get('ifGenerationMatch'), '0')
|
||||
assert.equal(upload.search.get('name'), OBJECT)
|
||||
assert.deepEqual(storage.state.object.body, {
|
||||
repository: 'stablyai/orca-cloud',
|
||||
workflow: 'Deploy Relay Production Same-Cap',
|
||||
run_id: '42',
|
||||
run_url: 'https://github.com/stablyai/orca-cloud/actions/runs/42',
|
||||
run_attempt: '1',
|
||||
acquired_at: NOW,
|
||||
expires_at: NOW + LEASE_TTL_MS,
|
||||
holder_key: 'stablyai/orca-cloud/42'
|
||||
})
|
||||
})
|
||||
|
||||
test('refuses a live lease held by another run and never writes', async () => {
|
||||
const storage = fakeStorage({
|
||||
object: {
|
||||
@@ -221,19 +200,6 @@ test('refuses to release a lease another run now holds', async () => {
|
||||
assert.equal(storage.state.object.generation, '1500')
|
||||
})
|
||||
|
||||
test('fails closed when the bucket answers 5xx', async () => {
|
||||
const storage = fakeStorage({
|
||||
faults: [{ when: (request) => request.method === 'GET', status: 503 }]
|
||||
})
|
||||
|
||||
const error = await leaseFor(storage)
|
||||
.acquire(HOLDER)
|
||||
.catch((thrown) => thrown)
|
||||
|
||||
assert.match(error.message, /lease inspection failed: 503/)
|
||||
assert.equal(storage.state.requests.filter((request) => request.method !== 'GET').length, 0)
|
||||
})
|
||||
|
||||
test('treats an unreadable record as held, not free', async () => {
|
||||
const storage = fakeStorage({
|
||||
object: { generation: '1500', body: { holder_key: 'stablyai/orca/9001' } }
|
||||
@@ -247,19 +213,6 @@ test('treats an unreadable record as held, not free', async () => {
|
||||
assert.equal(storage.state.object.generation, '1500')
|
||||
})
|
||||
|
||||
test('reports a 412 during acquisition as a conflict', async () => {
|
||||
const storage = fakeStorage({
|
||||
faults: [{ when: (request) => request.path.startsWith('/upload/'), status: 412 }]
|
||||
})
|
||||
|
||||
const error = await leaseFor(storage)
|
||||
.acquire(HOLDER)
|
||||
.catch((thrown) => thrown)
|
||||
|
||||
assert.ok(error instanceof LeaseConflict)
|
||||
assert.match(error.message, /changed concurrently/)
|
||||
})
|
||||
|
||||
test('renewal rewrites only expires_at on the observed generation', async () => {
|
||||
const storage = fakeStorage()
|
||||
const lease = leaseFor(storage)
|
||||
@@ -273,37 +226,3 @@ test('renewal rewrites only expires_at on the observed generation', async () =>
|
||||
assert.equal(storage.state.object.body.acquired_at, NOW)
|
||||
assert.equal(storage.state.object.body.holder_key, HOLDER.holderKey)
|
||||
})
|
||||
|
||||
test('renewal stops once the object belongs to someone else', async () => {
|
||||
const storage = fakeStorage({
|
||||
object: {
|
||||
generation: '1500',
|
||||
body: storedRecord({ holderKey: 'stablyai/orca/9001', expiresAt: NOW + 60_000 })
|
||||
}
|
||||
})
|
||||
|
||||
assert.deepEqual(await leaseFor(storage).renew(HOLDER.holderKey), {
|
||||
renewed: false,
|
||||
reason: 'foreign'
|
||||
})
|
||||
})
|
||||
|
||||
test('the access token source re-mints only after the reuse window', () => {
|
||||
let clock = 0
|
||||
let mints = 0
|
||||
const source = createAccessTokenSource({
|
||||
run: () => `token-${++mints}`,
|
||||
now: () => clock
|
||||
})
|
||||
|
||||
assert.equal(source(), 'token-1')
|
||||
clock = 39 * 60 * 1_000
|
||||
assert.equal(source(), 'token-1')
|
||||
clock = 41 * 60 * 1_000
|
||||
assert.equal(source(), 'token-2')
|
||||
})
|
||||
|
||||
test('the access token source rejects an empty gcloud response', () => {
|
||||
const source = createAccessTokenSource({ run: () => '' })
|
||||
assert.throws(() => source(), /empty token/)
|
||||
})
|
||||
|
||||
@@ -385,7 +385,7 @@ jobs:
|
||||
ORCA_RELEASE_JAVASCRIPT_BUILD_IDENTITY: dev
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
# Leave build identity unset: telemetry accepts only official stable/rc, so branch artifacts stay silent.
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
|
||||
# After the app build so nothing that cleans out/ can drop it; electron-builder ships it.
|
||||
- name: Download the orcad deployment template
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -38,8 +38,8 @@ on:
|
||||
- '!src/main/daemon/executable-host/windows/testing/daemon-host-relocation.test-fixture.ts'
|
||||
- '!src/main/daemon/endpoint/publication/testing/daemon-endpoint-reachability-test-harness.ts'
|
||||
- 'src/main/providers/local-pty/local-pty-provider.ts'
|
||||
- 'src/main/providers/windows-shell-args.ts'
|
||||
- 'src/main/providers/windows-shell-preflight-runtime.windows.test.ts'
|
||||
- 'src/main/providers/shell-launch/windows/shell-args.ts'
|
||||
- 'src/main/providers/shell-launch/windows/shell-preflight-runtime.windows.test.ts'
|
||||
- 'native/computer-use-macos/**'
|
||||
- 'native/computer-use-linux/**'
|
||||
- 'native/computer-use-windows/**'
|
||||
@@ -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'
|
||||
@@ -95,11 +97,10 @@ jobs:
|
||||
config/scripts/performance/macos-computer-owner/macos-computer-helper-owner-loss-processes.test.mjs
|
||||
src/main/computer/sidecar-transport/sidecar-client.test.ts
|
||||
src/main/computer/macos-native/macos-native-provider-client.test.ts
|
||||
src/main/providers/windows-shell-preflight-runtime.windows.test.ts
|
||||
src/main/providers/shell-launch/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
|
||||
|
||||
@@ -235,7 +235,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
# Leave dev identity unset so official-only stable/rc telemetry stays silent.
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
|
||||
# After the app build so nothing that cleans out/ can drop it; electron-builder ships it.
|
||||
- name: Download the orcad deployment template
|
||||
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ inputs.javascript_source_sha }}
|
||||
ORCA_RELEASE_JAVASCRIPT_BUILD_IDENTITY: dev
|
||||
# Unset dev build identity so official stable/rc telemetry stays silent.
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
|
||||
# Inline so refs predating the wait can still use the trusted dispatch workflow.
|
||||
- name: Wait for the qualified orcad template
|
||||
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
environment:
|
||||
name: docs-production
|
||||
url: https://www.onorca.dev/docs
|
||||
url: https://orca.dev/docs
|
||||
env:
|
||||
VERCEL_TELEMETRY_DISABLED: '1'
|
||||
steps:
|
||||
|
||||
@@ -3,7 +3,7 @@ on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
|
||||
paths:
|
||||
- 'src/main/git/command-runner/spawned-command-tree-kill*'
|
||||
- 'src/main/git/command-runner/process-capture/spawned-command-tree-kill*'
|
||||
- '.github/workflows/git-command-termination-runtime.yml'
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
@@ -24,4 +24,4 @@ jobs:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/install-node-dependencies
|
||||
- name: Verify exited native child does not trigger taskkill
|
||||
run: node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/git/command-runner/spawned-command-tree-kill.test.ts
|
||||
run: node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/git/command-runner/process-capture/spawned-command-tree-kill.test.ts
|
||||
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
# Unset dev identity so official-only stable/rc telemetry stays silent.
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
|
||||
# After the app build so nothing that cleans out/ can drop it; electron-builder ships it.
|
||||
- name: Download the orcad deployment template
|
||||
|
||||
@@ -14,48 +14,248 @@ jobs:
|
||||
apply-os-label:
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 5
|
||||
# Serialize runs per issue; each run reconciles from current state, so order does not matter.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-issue-${{ github.event.issue.number }}
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: Apply OS label from issue form
|
||||
- name: Reconcile OS and mobile labels from issue form
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const body = issue.body || "";
|
||||
const osMatch = body.match(/^### Operating system\s+(.+)$/m);
|
||||
// The only labels this workflow may add or remove.
|
||||
const MANAGED_LABELS = ["os:macos", "os:Windows", "os:linux", "os:mobile", "mobile"];
|
||||
const MAX_PASSES = 3;
|
||||
|
||||
if (!osMatch) {
|
||||
core.info("No operating system field found. Skipping.");
|
||||
return;
|
||||
const NOTE_PREFIX =
|
||||
"> \u{1F310} **Translation note (machine-translated by an assistant on ";
|
||||
const NOTE_SUFFIX =
|
||||
"):** This issue's title was originally written in Chinese and has been translated to English (the body is unchanged). The original, untranslated text is preserved intact in the collapsible section below.";
|
||||
const NOTE_DATE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
||||
|
||||
// Metadata headings in accepted order; equal rank = current/legacy names of one field.
|
||||
const HEADING_RANKS = new Map([
|
||||
["Summary", 1],
|
||||
["Where were you using Orca?", 2],
|
||||
["Client", 2],
|
||||
["Operating system", 3],
|
||||
["Host operating system", 3],
|
||||
["Remote host operating system", 4],
|
||||
["Orca version", 5],
|
||||
["Coding harness and version", 6],
|
||||
["Details", 7],
|
||||
["Reproduction steps", 7],
|
||||
]);
|
||||
const FREE_TEXT_RANK = 7;
|
||||
|
||||
const OS_LABELS = new Map([
|
||||
["macos", "os:macos"],
|
||||
["windows", "os:Windows"],
|
||||
["linux", "os:linux"],
|
||||
["ios", "os:mobile"],
|
||||
["android", "os:mobile"],
|
||||
["other", null],
|
||||
]);
|
||||
// Value: whether the choice means the mobile app. Retired option strings stay for open issues.
|
||||
const WHERE_IS_MOBILE = new Map([
|
||||
["desktop only", false],
|
||||
["desktop + ssh host", false],
|
||||
["desktop + orca server", false],
|
||||
["desktop + wsl", false],
|
||||
["mobile app", true],
|
||||
["web browser", false],
|
||||
["other", false],
|
||||
["desktop (this computer)", false],
|
||||
["desktop + ssh", false],
|
||||
["desktop + orca remote server", false],
|
||||
["mobile (ios)", true],
|
||||
["mobile (android)", true],
|
||||
["web / browser", false],
|
||||
]);
|
||||
const CLIENT_IS_MOBILE = new Map([
|
||||
...WHERE_IS_MOBILE,
|
||||
["desktop", false],
|
||||
["mobile", true],
|
||||
["web", false],
|
||||
]);
|
||||
const REMOTE_OS_VALUES = new Set(["linux", "macos", "windows", "other", "not sure"]);
|
||||
|
||||
const isBlank = (line) => /^[ \t]*$/.test(line);
|
||||
const isHeading = (line) => /^###(?:[ \t]|$)/.test(line);
|
||||
const isUnselected = (value) => value === "" || value === "_no response_";
|
||||
const isTranslationNote = (line) =>
|
||||
line.length === NOTE_PREFIX.length + 10 + NOTE_SUFFIX.length &&
|
||||
line.startsWith(NOTE_PREFIX) &&
|
||||
line.endsWith(NOTE_SUFFIX) &&
|
||||
NOTE_DATE.test(line.slice(NOTE_PREFIX.length, NOTE_PREFIX.length + 10));
|
||||
|
||||
// Reads only the leading metadata prefix; null means "not a recognizable bug form".
|
||||
function readFormFields(body) {
|
||||
if (typeof body !== "string") {
|
||||
return null;
|
||||
}
|
||||
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
||||
let index = 0;
|
||||
const skipBlankLines = () => {
|
||||
while (index < lines.length && isBlank(lines[index])) {
|
||||
index += 1;
|
||||
}
|
||||
};
|
||||
|
||||
skipBlankLines();
|
||||
if (index < lines.length && isTranslationNote(lines[index])) {
|
||||
index += 1;
|
||||
if (index >= lines.length || !isBlank(lines[index])) {
|
||||
return null;
|
||||
}
|
||||
skipBlankLines();
|
||||
}
|
||||
|
||||
const fields = new Map();
|
||||
let lastRank = 0;
|
||||
while (index < lines.length) {
|
||||
const line = lines[index];
|
||||
if (!isHeading(line)) {
|
||||
return null;
|
||||
}
|
||||
// Exact "### <heading>" only; any other heading shape is unknown.
|
||||
const name = line.startsWith("### ") ? line.slice(4) : null;
|
||||
const rank = HEADING_RANKS.get(name);
|
||||
if (rank === undefined || rank < lastRank || fields.has(name)) {
|
||||
return null;
|
||||
}
|
||||
if (rank === FREE_TEXT_RANK) {
|
||||
// Free text starts here and is never inspected.
|
||||
return fields;
|
||||
}
|
||||
lastRank = rank;
|
||||
index += 1;
|
||||
|
||||
let value = "";
|
||||
let valueLines = 0;
|
||||
while (index < lines.length && !isHeading(lines[index])) {
|
||||
if (!isBlank(lines[index])) {
|
||||
value = lines[index];
|
||||
valueLines += 1;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
if (valueLines > 1) {
|
||||
return null;
|
||||
}
|
||||
fields.set(name, value.trim().toLowerCase());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const selectedOs = osMatch[1].trim().toLowerCase();
|
||||
const osLabelMap = {
|
||||
macos: "os:macos",
|
||||
windows: "os:Windows",
|
||||
linux: "os:linux",
|
||||
};
|
||||
// Returns the managed labels the body asks for, or null to leave the issue untouched.
|
||||
function desiredLabelsFor(body) {
|
||||
const fields = readFormFields(body);
|
||||
if (!fields || !fields.has("Orca version")) {
|
||||
return null;
|
||||
}
|
||||
const osHeading = ["Operating system", "Host operating system"].find((name) =>
|
||||
fields.has(name),
|
||||
);
|
||||
if (!osHeading) {
|
||||
return null;
|
||||
}
|
||||
const desired = new Set();
|
||||
|
||||
const desiredLabel = osLabelMap[selectedOs];
|
||||
if (!desiredLabel) {
|
||||
core.info(`No OS label configured for "${selectedOs}". Skipping.`);
|
||||
return;
|
||||
const os = fields.get(osHeading);
|
||||
if (!isUnselected(os)) {
|
||||
if (!OS_LABELS.has(os)) {
|
||||
return null;
|
||||
}
|
||||
const osLabel = OS_LABELS.get(os);
|
||||
if (osLabel) {
|
||||
desired.add(osLabel);
|
||||
}
|
||||
}
|
||||
|
||||
// The current heading wins by presence, even when blank.
|
||||
const whereChoices = fields.has("Where were you using Orca?")
|
||||
? [fields.get("Where were you using Orca?"), WHERE_IS_MOBILE]
|
||||
: fields.has("Client")
|
||||
? [fields.get("Client"), CLIENT_IS_MOBILE]
|
||||
: null;
|
||||
if (whereChoices && !isUnselected(whereChoices[0])) {
|
||||
const [where, isMobileByChoice] = whereChoices;
|
||||
if (!isMobileByChoice.has(where)) {
|
||||
return null;
|
||||
}
|
||||
if (isMobileByChoice.get(where)) {
|
||||
desired.add("mobile");
|
||||
}
|
||||
}
|
||||
|
||||
// Validated only; the remote host never contributes a label.
|
||||
const remoteOs = fields.get("Remote host operating system") ?? "";
|
||||
if (!isUnselected(remoteOs) && !REMOTE_OS_VALUES.has(remoteOs)) {
|
||||
return null;
|
||||
}
|
||||
return desired;
|
||||
}
|
||||
|
||||
const managedLabels = Object.values(osLabelMap);
|
||||
const existingLabels = issue.labels.map((label) =>
|
||||
typeof label === "string" ? label : label.name,
|
||||
);
|
||||
const labelsToKeep = existingLabels.filter(
|
||||
(label) => !managedLabels.includes(label),
|
||||
);
|
||||
const nextLabels = [...new Set([...labelsToKeep, desiredLabel])];
|
||||
|
||||
await github.rest.issues.setLabels({
|
||||
const issueRef = {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: nextLabels,
|
||||
});
|
||||
issue_number: context.payload.issue.number,
|
||||
};
|
||||
|
||||
core.info(`Applied label ${desiredLabel} to issue #${issue.number}.`);
|
||||
// The event is only a signal: decisions come from the issue as it is now.
|
||||
async function readCurrentIssue() {
|
||||
const { data } = await github.rest.issues.get(issueRef);
|
||||
const labelNames = (data.labels ?? []).map((label) =>
|
||||
typeof label === "string" ? label : label.name,
|
||||
);
|
||||
const desired = desiredLabelsFor(data.body);
|
||||
const present = MANAGED_LABELS.filter((label) => labelNames.includes(label));
|
||||
return {
|
||||
desired,
|
||||
toAdd: desired ? MANAGED_LABELS.filter((l) => desired.has(l) && !present.includes(l)) : [],
|
||||
toRemove: desired ? present.filter((l) => !desired.has(l)) : [],
|
||||
};
|
||||
}
|
||||
|
||||
let state = await readCurrentIssue();
|
||||
let hasWritten = false;
|
||||
for (let pass = 1; ; pass += 1) {
|
||||
if (!state.desired) {
|
||||
if (hasWritten) {
|
||||
throw new Error(
|
||||
`Issue #${issueRef.issue_number} stopped matching the bug form while labels were being updated. Managed labels may be incomplete; rerun this job once the issue body is settled.`,
|
||||
);
|
||||
}
|
||||
core.info("No recognizable bug form at the start of the issue body. Labels left untouched.");
|
||||
return;
|
||||
}
|
||||
if (state.toAdd.length === 0 && state.toRemove.length === 0) {
|
||||
core.info(
|
||||
`Issue #${issueRef.issue_number} managed labels match the form: [${[...state.desired].join(", ")}].`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (pass > MAX_PASSES) {
|
||||
throw new Error(
|
||||
`Issue #${issueRef.issue_number} labels still differ from the form after ${MAX_PASSES} passes (to add: [${state.toAdd.join(", ")}], to remove: [${state.toRemove.join(", ")}]). The issue is probably being edited; rerun this job.`,
|
||||
);
|
||||
}
|
||||
|
||||
hasWritten = true;
|
||||
if (state.toAdd.length > 0) {
|
||||
await github.rest.issues.addLabels({ ...issueRef, labels: state.toAdd });
|
||||
}
|
||||
for (const name of state.toRemove) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({ ...issueRef, name });
|
||||
} catch (error) {
|
||||
// Already gone is the desired outcome; the re-read below verifies real state.
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
state = await readCurrentIssue();
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
test_files: '["tests/e2e/workspace-layout-oracle-headless.spec.ts","tests/e2e/workspace-layout-oracle.spec.ts","tests/e2e/cli-file-open-position.spec.ts","tests/e2e/relaunch-first-terminal.spec.ts","tests/e2e/relaunch-sleeping-agents-resume-on-view.spec.ts","tests/e2e/cli-first-worktree-single-terminal.spec.ts","tests/e2e/unopened-workspace-agent-single-resume.spec.ts","tests/e2e/terminal-pane-close-layout-consistency.spec.ts","tests/e2e/golden-file-open-edit-save.spec.ts","tests/e2e/golden-source-control-open-diff.spec.ts","tests/e2e/paired-cli-terminal-graph-sync-tab-retention.spec.ts","tests/e2e/floating-workspace-pane-height.spec.ts","tests/e2e/editor-model-host-isolation.spec.ts","tests/e2e/editor-tab-selection-restore.spec.ts","tests/e2e/finished-agent-ghost-resume.spec.ts","tests/e2e/live-background-terminal-mount-authority.spec.ts","tests/e2e/workspace-back-forward-navigation.spec.ts","tests/e2e/worktree-lineage-toggle-shortcut.spec.ts","tests/e2e/local-cli-terminal-retention.spec.ts","tests/e2e/default-tabs-mark-crash-relaunch.spec.ts","tests/e2e/terminal-cli-split-closed-before-mount.spec.ts","tests/e2e/layout-stream-loss-reopen.spec.ts","tests/e2e/terminal-tab-close-restart-persistence.spec.ts","tests/e2e/artificial-opencode-terminal-load.spec.ts","tests/e2e/headless-paired-remote-terminal-retention-memory.spec.ts","tests/e2e/headless-paired-remote-terminal-reveal-grid.spec.ts","tests/e2e/headless-paired-remote-terminal-stall-recovery.spec.ts","tests/e2e/headless-paired-remote-terminal-viewer-size.spec.ts","tests/e2e/headless-serve-browser-tab-restart.spec.ts","tests/e2e/headless-serve-cli-terminal-retention-parity.spec.ts","tests/e2e/headless-serve-desktop-activation.spec.ts","tests/e2e/headless-serve-chat-tab-placement.spec.ts","tests/e2e/headless-serve-mobile-native-chat-identity.spec.ts","tests/e2e/layout-compat-old-phone-on-new-server.spec.ts","tests/e2e/layout-compat-serve-restart-keeps-at-at-terminals.spec.ts","tests/e2e/paired-mobile-serve-restart-terminal-tabs.spec.ts","tests/e2e/paired-mobile-desktop-tab-composition.spec.ts","tests/e2e/paired-remote-terminal-serve-restart-binding.spec.ts","tests/e2e/paired-desktop-server-layout-frames.spec.ts","tests/e2e/paired-desktop-server-held-pane-reopen.spec.ts","tests/e2e/paired-desktop-server-layout-intents.spec.ts","tests/e2e/paired-desktop-server-layout-writes.spec.ts","tests/e2e/ssh-orcad-"]'
|
||||
test_files: '["tests/e2e/workspace-layout-oracle-headless.spec.ts","tests/e2e/workspace-layout-oracle.spec.ts","tests/e2e/cli-file-open-position.spec.ts","tests/e2e/relaunch-first-terminal.spec.ts","tests/e2e/relaunch-sleeping-agents-resume-on-view.spec.ts","tests/e2e/cli-first-worktree-single-terminal.spec.ts","tests/e2e/unopened-workspace-agent-single-resume.spec.ts","tests/e2e/terminal-pane-close-layout-consistency.spec.ts","tests/e2e/golden-file-open-edit-save.spec.ts","tests/e2e/golden-source-control-open-diff.spec.ts","tests/e2e/paired-cli-terminal-graph-sync-tab-retention.spec.ts","tests/e2e/floating-workspace-pane-height.spec.ts","tests/e2e/editor-model-host-isolation.spec.ts","tests/e2e/editor-tab-selection-restore.spec.ts","tests/e2e/finished-agent-ghost-resume.spec.ts","tests/e2e/live-background-terminal-mount-authority.spec.ts","tests/e2e/workspace-back-forward-navigation.spec.ts","tests/e2e/worktree-lineage-toggle-shortcut.spec.ts","tests/e2e/local-cli-terminal-retention.spec.ts","tests/e2e/default-tabs-mark-crash-relaunch.spec.ts","tests/e2e/terminal-cli-split-closed-before-mount.spec.ts","tests/e2e/layout-stream-loss-reopen.spec.ts","tests/e2e/terminal-tab-close-restart-persistence.spec.ts","tests/e2e/artificial-opencode-terminal-load.spec.ts","tests/e2e/headless-paired-remote-terminal-retention-memory.spec.ts","tests/e2e/headless-paired-remote-terminal-reveal-grid.spec.ts","tests/e2e/headless-paired-remote-terminal-stall-recovery.spec.ts","tests/e2e/headless-paired-remote-terminal-viewer-size.spec.ts","tests/e2e/headless-serve-browser-tab-restart.spec.ts","tests/e2e/headless-serve-cli-terminal-retention-parity.spec.ts","tests/e2e/headless-serve-desktop-activation.spec.ts","tests/e2e/headless-serve-chat-tab-placement.spec.ts","tests/e2e/headless-serve-mobile-native-chat-identity.spec.ts","tests/e2e/layout-compat-old-phone-on-new-server.spec.ts","tests/e2e/layout-compat-serve-restart-keeps-at-at-terminals.spec.ts","tests/e2e/paired-mobile-serve-restart-terminal-tabs.spec.ts","tests/e2e/paired-mobile-desktop-tab-composition.spec.ts","tests/e2e/paired-remote-terminal-serve-restart-binding.spec.ts","tests/e2e/ssh-orcad-"]'
|
||||
run_changed_e2e: true
|
||||
needs_build: true
|
||||
artifact_suffix: -layout-owner-switch-on
|
||||
|
||||
@@ -11,12 +11,12 @@ 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*'
|
||||
- 'src/main/providers/macos-tcc-login-shell*'
|
||||
- 'src/main/providers/shell-launch/macos/tcc-login-shell*'
|
||||
- 'src/shared/process-table-snapshot*'
|
||||
- 'src/shared/darwin-process-table-capture*'
|
||||
- 'src/shared/update-status-types.ts'
|
||||
@@ -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'
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Main blocking work guards
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: main-blocking-work-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
jobs:
|
||||
ratchets:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/install-node-dependencies
|
||||
# Run the blocking-work ratchets over the full tree after every main merge.
|
||||
- name: Enforce ratchets
|
||||
run: |
|
||||
node config/scripts/static-analysis/ratchets/runtime-lifecycle/check-main-sync-fs-ratchet.mjs
|
||||
node config/scripts/static-analysis/ratchets/runtime-lifecycle/check-main-blocking-api-ratchet.mjs
|
||||
# The PR preflight runs spawn separately, so the discovery runner intentionally excludes it.
|
||||
- name: Enforce synchronous launches
|
||||
if: '!cancelled()'
|
||||
run: node config/scripts/static-analysis/process-boundaries/check-main-sync-spawn-ratchet.mjs
|
||||
@@ -284,7 +284,7 @@ jobs:
|
||||
"${{ steps.apk.outputs.checksum }}"
|
||||
fi
|
||||
|
||||
# Advisory like the site bump: a failed mirror only leaves onorca.dev's "mirror link" on the prior APK.
|
||||
# Advisory like the site bump: a failed mirror only leaves orca.dev's "mirror link" on the prior APK.
|
||||
release-mirror-r2:
|
||||
needs: android-build
|
||||
if: needs.android-build.outputs.publish_release == 'true'
|
||||
|
||||
@@ -34,18 +34,24 @@ 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'
|
||||
- 'src/main/claude/claude-internal-message-content.ts'
|
||||
- 'src/main/claude/claude-stop-hook-feedback.ts'
|
||||
- 'src/main/claude/structured/journal/claude-internal-message-content.ts'
|
||||
- 'src/main/claude/structured/journal/claude-stop-hook-feedback.ts'
|
||||
- 'src/main/claude/structured/journal/**'
|
||||
- 'src/main/claude/__fixtures__/claude-adapter-capture-fold.jsonl'
|
||||
- 'src/main/codex/app-server/codex-item-field-readers.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,18 +176,24 @@ 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'
|
||||
- 'src/main/claude/claude-internal-message-content.ts'
|
||||
- 'src/main/claude/claude-stop-hook-feedback.ts'
|
||||
- 'src/main/claude/structured/journal/claude-internal-message-content.ts'
|
||||
- 'src/main/claude/structured/journal/claude-stop-hook-feedback.ts'
|
||||
- 'src/main/claude/structured/journal/**'
|
||||
- 'src/main/claude/__fixtures__/claude-adapter-capture-fold.jsonl'
|
||||
- 'src/main/codex/app-server/codex-item-field-readers.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
|
||||
|
||||
@@ -851,9 +851,10 @@ jobs:
|
||||
src/main/pty/history/terminal-history-fish-session.node-pty.test.ts \
|
||||
src/main/pty/shell-startup/zsh-scoped-histfile.live-shell.test.ts \
|
||||
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 \
|
||||
@@ -1116,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
|
||||
|
||||
@@ -1321,7 +1321,7 @@ jobs:
|
||||
ORCA_BUILD_IDENTITY: ${{ steps.tag-classify.outputs.identity }}
|
||||
SHARED_JAVASCRIPT: ${{ needs.release-javascript.outputs.supported }}
|
||||
ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ needs.release-javascript.outputs.source_sha }}
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }}
|
||||
|
||||
# After the app build so nothing that cleans out/ can drop it; electron-builder ships it.
|
||||
@@ -2357,7 +2357,7 @@ jobs:
|
||||
needs:
|
||||
- cut
|
||||
- publish-release
|
||||
# Why stable-only: onorca.dev's mirror link tracks GitHub's latest release, which skips RCs.
|
||||
# Why stable-only: orca.dev's mirror link tracks GitHub's latest release, which skips RCs.
|
||||
if: ${{ !cancelled() && needs.publish-release.result == 'success' && needs.cut.outputs.tag != '' && !contains(needs.cut.outputs.tag, '-rc.') && startsWith(needs.cut.outputs.tag, 'v') }}
|
||||
uses: ./.github/workflows/release-mirror-r2.yml
|
||||
with:
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
ORCA_CLI_BUILD_COMPILER: ${{ inputs.dev_build && 'tsrs' || 'bun' }}
|
||||
ORCA_BUILD_IDENTITY: ${{ steps.identity.outputs.value }}
|
||||
ORCA_RELEASE_JAVASCRIPT_BUILD_IDENTITY: ${{ inputs.dev_build && 'dev' || '' }}
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
ORCA_POSTHOG_WRITE_KEY: ${{ !inputs.dev_build && secrets.ORCA_POSTHOG_WRITE_KEY || '' }}
|
||||
run: |
|
||||
pnpm run "$JAVASCRIPT_BUILD_SCRIPT"
|
||||
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
ORCA_BUILD_IDENTITY: ${{ steps.tag-classify.outputs.identity }}
|
||||
ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ inputs.javascript_source_sha }}
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
|
||||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||||
ORCA_POSTHOG_WRITE_KEY: ${{ secrets.ORCA_POSTHOG_WRITE_KEY }}
|
||||
|
||||
# Design D2: the parent release-cut run merged it from every node-server lane at this tag.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Release Mirror (R2)
|
||||
|
||||
# Copy published release downloads to the public R2 bucket behind https://downloads.onorca.dev.
|
||||
# onorca.dev keeps GitHub as the default download and offers this as an opt-in "mirror link"
|
||||
# Copy published release downloads to the public R2 bucket behind https://downloads.orca.dev.
|
||||
# orca.dev keeps GitHub as the default download and offers this as an opt-in "mirror link"
|
||||
# for when GitHub release downloads return 5xx. Called by release-cut (stable desktop tags) and
|
||||
# mobile-android-release after publishing; dispatch manually to backfill a tag.
|
||||
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
assets:
|
||||
description: Space-separated asset names (blank = the onorca.dev download assets)
|
||||
description: Space-separated asset names (blank = the orca.dev download assets)
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
@@ -24,7 +24,7 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
assets:
|
||||
description: Space-separated asset names (blank = the onorca.dev download assets)
|
||||
description: Space-separated asset names (blank = the orca.dev download assets)
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
@@ -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
|
||||
|
||||
+58
-1
@@ -114,6 +114,22 @@
|
||||
"unicorn/throw-new-error": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["src/main/backend/**/*.ts"],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"group": ["electron", "electron/*"],
|
||||
"message": "Backend services use the typed desktop capability bridge; only the shell imports Electron."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,cjs,mjs,jsx,ts,tsx,mts,cts}"],
|
||||
"excludeFiles": [
|
||||
@@ -151,6 +167,47 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/main/backend/**/*.ts"],
|
||||
"excludeFiles": [
|
||||
"**/*.{test,spec}.{js,cjs,mjs,jsx,ts,tsx,mts,cts}",
|
||||
"**/__tests__/**",
|
||||
"src/renderer/src/runtime/web-runtime-browser-creation-placement-test-rig.ts"
|
||||
],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "@linear/sdk",
|
||||
"allowTypeImports": true,
|
||||
"message": "Value-importing @linear/sdk hoists its ~2.6MB CJS bundle into the eager top-level require block and defeats the lazy loader. Use `import type` plus loadLinearSdk() from src/main/linear/linear-sdk.ts."
|
||||
},
|
||||
{
|
||||
"name": "ssh2",
|
||||
"allowTypeImports": true,
|
||||
"message": "Value-importing ssh2 hoists it into the eager main-process require block and blocks startup (#35731). Use `import type` plus loadSsh2() from src/main/ssh/ssh2-module-load.ts."
|
||||
}
|
||||
],
|
||||
"patterns": [
|
||||
{
|
||||
"regex": "(?i)(?:^|[/\\\\])(?:web-session-tabs-sync-test-harness|web-runtime-browser-creation-placement-test-rig)(?:\\.[cm]?[jt]sx?)?(?:[?#].*)?$",
|
||||
"message": "This module is unit-test support. Import the production web-session owner instead."
|
||||
},
|
||||
{
|
||||
"regex": "\\.fixture(?:\\.tsx?)?(?:[?#].*)?$",
|
||||
"message": "Component fixtures are dev-only and load through the fixture explorer's import.meta.glob; importing one would ship it in a production bundle."
|
||||
},
|
||||
{
|
||||
"group": ["electron", "electron/*"],
|
||||
"message": "Backend services use the typed desktop capability bridge; only the shell imports Electron."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/renderer/src/**/*.{ts,tsx}"],
|
||||
"rules": {
|
||||
@@ -251,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",
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ cask "orca" do
|
||||
url "https://github.com/stablyai/orca/releases/download/v#{version}/orca-macos-#{arch}.dmg"
|
||||
name "Orca"
|
||||
desc "IDE for orchestrating AI coding agents across terminals and worktrees"
|
||||
homepage "https://onorca.dev/"
|
||||
homepage "https://orca.dev/"
|
||||
|
||||
livecheck do
|
||||
url :url
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ cask "orca@rc" do
|
||||
url "https://github.com/stablyai/orca/releases/download/v#{version}/orca-macos-#{arch}.dmg"
|
||||
name "Orca RC"
|
||||
desc "IDE for orchestrating AI coding agents across terminals and worktrees"
|
||||
homepage "https://onorca.dev/"
|
||||
homepage "https://orca.dev/"
|
||||
|
||||
livecheck do
|
||||
url "https://github.com/stablyai/orca"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<h1 align="center">
|
||||
<a href="https://onOrca.dev"><img src="resources/build/icon.png" alt="Orca" width="64" valign="middle" /></a> Orca
|
||||
<a href="https://orca.dev"><img src="resources/build/icon.png" alt="Orca" width="64" valign="middle" /></a> Orca
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
@@ -20,7 +20,7 @@
|
||||
Run Claude Code, Codex, and any other coding agent in parallel, each in its own worktree. Chat or terminal, on your laptop or in the cloud, with diffs, a browser, and a CLI in one app.
|
||||
</p>
|
||||
|
||||
<h3 align="center"><a href="https://onorca.dev/download"><ins>Download Orca</ins></a></h3>
|
||||
<h3 align="center"><a href="https://orca.dev/download"><ins>Download Orca</ins></a></h3>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/readme-hero.jpg" alt="Orca desktop app with a coordinator agent running worker agents in parallel workspaces, and the Orca mobile app in the corner" width="960" />
|
||||
@@ -36,11 +36,11 @@
|
||||
|
||||
Run any agent in Chat UI or as the raw CLI, and move a conversation between the two without losing context.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/agents/native-chat)
|
||||
[Docs →](https://orca.dev/docs/agents/native-chat)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/agents/native-chat"><picture><source srcset="docs/assets/readme-features/chat-or-terminal.webp" type="image/webp"><img src="docs/assets/readme-features/chat-or-terminal.poster.jpg" alt="The same agent conversation in Chat UI and in the terminal, side by side" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/agents/native-chat"><picture><source srcset="docs/assets/readme-features/chat-or-terminal.webp" type="image/webp"><img src="docs/assets/readme-features/chat-or-terminal.poster.jpg" alt="The same agent conversation in Chat UI and in the terminal, side by side" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -50,11 +50,11 @@ Run any agent in Chat UI or as the raw CLI, and move a conversation between the
|
||||
|
||||
Hand a big task to one agent. It splits the work, starts worker agents in their own workspaces, and brings their results back to you.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/cli/orchestration)
|
||||
[Docs →](https://orca.dev/docs/cli/orchestration)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/cli/orchestration"><picture><source srcset="docs/assets/readme-features/orchestration.webp" type="image/webp"><img src="docs/assets/readme-features/orchestration.poster.jpg" alt="A coordinator agent splitting a task between two worker agents and reporting their results" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/cli/orchestration"><picture><source srcset="docs/assets/readme-features/orchestration.webp" type="image/webp"><img src="docs/assets/readme-features/orchestration.poster.jpg" alt="A coordinator agent splitting a task between two worker agents and reporting their results" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -64,11 +64,11 @@ Hand a big task to one agent. It splits the work, starts worker agents in their
|
||||
|
||||
Pair a Remote Orca Server, work on any SSH host, or give a worktree its own cloud VM. Your agents keep running when your laptop sleeps.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/ways-to-run)
|
||||
[Docs →](https://orca.dev/docs/ways-to-run)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/ways-to-run"><picture><source srcset="docs/assets/readme-features/remote.webp" type="image/webp"><img src="docs/assets/readme-features/remote.poster.jpg" alt="Choosing which machine a new worktree runs on, from this computer to an SSH host or a per-workspace environment" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/ways-to-run"><picture><source srcset="docs/assets/readme-features/remote.webp" type="image/webp"><img src="docs/assets/readme-features/remote.poster.jpg" alt="Choosing which machine a new worktree runs on, from this computer to an SSH host or a per-workspace environment" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -78,11 +78,11 @@ Pair a Remote Orca Server, work on any SSH host, or give a worktree its own clou
|
||||
|
||||
Pair Orca with the mobile app to get pinged when an agent needs you, reply in chat, and keep work moving on any of your machines when you are away from your desk.
|
||||
|
||||
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://www.onorca.dev/download/android) · [Docs →](https://www.onorca.dev/docs/mobile)
|
||||
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://orca.dev/download/android) · [Docs →](https://orca.dev/docs/mobile)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/mobile"><picture><source srcset="docs/assets/readme-features/mobile-companion.webp" type="image/webp"><img src="docs/assets/readme-features/mobile-companion.poster.jpg" alt="The Orca mobile app getting a notification that Claude needs input, opening the session, and replying so the agent opens the PR" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/mobile"><picture><source srcset="docs/assets/readme-features/mobile-companion.webp" type="image/webp"><img src="docs/assets/readme-features/mobile-companion.poster.jpg" alt="The Orca mobile app getting a notification that Claude needs input, opening the session, and replying so the agent opens the PR" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -92,11 +92,11 @@ Pair Orca with the mobile app to get pinged when an agent needs you, reply in ch
|
||||
|
||||
Review diffs, leave notes on exact changed lines, and send focused feedback back to an agent.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/review/annotate-ai-diff)
|
||||
[Docs →](https://orca.dev/docs/review/annotate-ai-diff)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/review/annotate-ai-diff"><picture><source srcset="docs/assets/readme-features/annotate-ai-diffs.webp" type="image/webp"><img src="docs/assets/readme-features/annotate-ai-diffs.poster.jpg" alt="A note on a changed diff line sent back to the agent" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/review/annotate-ai-diff"><picture><source srcset="docs/assets/readme-features/annotate-ai-diffs.webp" type="image/webp"><img src="docs/assets/readme-features/annotate-ai-diffs.poster.jpg" alt="A note on a changed diff line sent back to the agent" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -106,11 +106,11 @@ Review diffs, leave notes on exact changed lines, and send focused feedback back
|
||||
|
||||
Draft commit messages and pull requests from the diff, then send failing checks or PR review comments back to an agent to fix.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/review/commit-push)
|
||||
[Docs →](https://orca.dev/docs/review/commit-push)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/review/commit-push"><picture><source srcset="docs/assets/readme-features/git-integration.webp" type="image/webp"><img src="docs/assets/readme-features/git-integration.poster.jpg" alt="Drafting a commit and pull request, then sending a review comment back to the agent" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/review/commit-push"><picture><source srcset="docs/assets/readme-features/git-integration.webp" type="image/webp"><img src="docs/assets/readme-features/git-integration.poster.jpg" alt="Drafting a commit and pull request, then sending a review comment back to the agent" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -120,11 +120,11 @@ Draft commit messages and pull requests from the diff, then send failing checks
|
||||
|
||||
Quickly split tasks into isolated environments, so multiple agents can work side by side without interfering with each other.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/model/worktrees)
|
||||
[Docs →](https://orca.dev/docs/model/worktrees)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/model/worktrees"><picture><source srcset="docs/assets/readme-features/parallel-worktrees.webp" type="image/webp"><img src="docs/assets/readme-features/parallel-worktrees.poster.jpg" alt="Creating a new worktree with its own agent next to other running worktrees" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/model/worktrees"><picture><source srcset="docs/assets/readme-features/parallel-worktrees.webp" type="image/webp"><img src="docs/assets/readme-features/parallel-worktrees.poster.jpg" alt="Creating a new worktree with its own agent next to other running worktrees" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -134,11 +134,11 @@ Quickly split tasks into isolated environments, so multiple agents can work side
|
||||
|
||||
Click any element in Orca's browser to copy its HTML, styles, and selector, then paste it into your agent's chat.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/browser/design-mode)
|
||||
[Docs →](https://orca.dev/docs/browser/design-mode)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/browser/design-mode"><picture><source srcset="docs/assets/readme-features/design-mode.webp" type="image/webp"><img src="docs/assets/readme-features/design-mode.poster.jpg" alt="Picking a button in Orca's browser and sending it to the agent's chat" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/browser/design-mode"><picture><source srcset="docs/assets/readme-features/design-mode.webp" type="image/webp"><img src="docs/assets/readme-features/design-mode.poster.jpg" alt="Picking a button in Orca's browser and sending it to the agent's chat" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -148,11 +148,11 @@ Click any element in Orca's browser to copy its HTML, styles, and selector, then
|
||||
|
||||
See your GitHub and Linear tasks in one place, and start a workspace for each when you're ready to build.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/review/linear)
|
||||
[Docs →](https://orca.dev/docs/review/linear)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/review/linear"><picture><source srcset="docs/assets/readme-features/github-linear-tasks.webp" type="image/webp"><img src="docs/assets/readme-features/github-linear-tasks.poster.jpg" alt="GitHub and Linear issues in Orca, with a workspace started from a Linear issue" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/review/linear"><picture><source srcset="docs/assets/readme-features/github-linear-tasks.webp" type="image/webp"><img src="docs/assets/readme-features/github-linear-tasks.poster.jpg" alt="GitHub and Linear issues in Orca, with a workspace started from a Linear issue" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -162,11 +162,11 @@ See your GitHub and Linear tasks in one place, and start a workspace for each wh
|
||||
|
||||
Anything you can do in Orca, your agents can do too: create worktrees, start workers, drive the browser and desktop apps, and schedule automations, all from the command line.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/cli/overview)
|
||||
[Docs →](https://orca.dev/docs/cli/overview)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/cli/overview"><picture><source srcset="docs/assets/readme-features/orca-cli.webp" type="image/webp"><img src="docs/assets/readme-features/orca-cli.poster.jpg" alt="An agent running orca worktree create to start a worker in a new worktree" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/cli/overview"><picture><source srcset="docs/assets/readme-features/orca-cli.webp" type="image/webp"><img src="docs/assets/readme-features/orca-cli.poster.jpg" alt="An agent running orca worktree create to start a worker in a new worktree" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -176,11 +176,11 @@ Anything you can do in Orca, your agents can do too: create worktrees, start wor
|
||||
|
||||
Themes, editors, languages, and panels from the community, installed in a click.
|
||||
|
||||
[Browse plugins →](https://www.onorca.dev/plugins)
|
||||
[Browse plugins →](https://orca.dev/plugins)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/plugins"><picture><source srcset="docs/assets/readme-features/plugins.webp" type="image/webp"><img src="docs/assets/readme-features/plugins.poster.jpg" alt="Installing the Orca Midnight theme from the plugin catalog" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/plugins"><picture><source srcset="docs/assets/readme-features/plugins.webp" type="image/webp"><img src="docs/assets/readme-features/plugins.poster.jpg" alt="Installing the Orca Midnight theme from the plugin catalog" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -190,25 +190,25 @@ Themes, editors, languages, and panels from the community, installed in a click.
|
||||
|
||||
Put agents, terminals, browsers, and diffs side by side in split panes shaped to the task.
|
||||
|
||||
[Docs →](https://www.onorca.dev/docs/model/tabs-panes-splits)
|
||||
[Docs →](https://orca.dev/docs/model/tabs-panes-splits)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
<a href="https://www.onorca.dev/docs/model/tabs-panes-splits"><picture><source srcset="docs/assets/readme-features/split-anything.webp" type="image/webp"><img src="docs/assets/readme-features/split-anything.poster.jpg" alt="Dragging tabs into split panes with an agent, browser, terminal, and diff" width="100%" /></picture></a>
|
||||
<a href="https://orca.dev/docs/model/tabs-panes-splits"><picture><source srcset="docs/assets/readme-features/split-anything.webp" type="image/webp"><img src="docs/assets/readme-features/split-anything.poster.jpg" alt="Dragging tabs into split panes with an agent, browser, terminal, and diff" width="100%" /></picture></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
**Also in the box:**
|
||||
|
||||
- **[Drag Files to Agents](https://www.onorca.dev/docs/editing/file-explorer)**: drag files from the explorer, or images from your desktop, straight into an agent's prompt.
|
||||
- **[Jump Anywhere](https://www.onorca.dev/docs/model/quick-open)**: one shortcut jumps to any workspace or tab across every repo and host.
|
||||
- **[Rich Previews](https://www.onorca.dev/docs/editing/viewers)**: open images, PDFs, and Markdown right in the workspace, next to the agent that made them.
|
||||
- **[Mobile Emulator](https://www.onorca.dev/docs/cli/overview#mobile-emulator)**: run an iOS Simulator or Android emulator beside your agent, and let it tap, type, and check its own work.
|
||||
- **[Dictation](https://www.onorca.dev/docs/settings#voice)**: press a shortcut and talk. Orca types what you say into the agent's prompt or terminal.
|
||||
- **[Usage & Accounts](https://www.onorca.dev/docs/agents/usage-tracking)**: see how much of your agent limits you've used and when they reset, and switch accounts without logging out.
|
||||
- **[Automations](https://www.onorca.dev/docs/cli/automations)**: schedule prompts to run on any repo: nightly dependency bumps, weekly triage, flaky-test sweeps.
|
||||
- **[Computer Use](https://www.onorca.dev/docs/cli/computer-use)**: when there's no CLI or API, agents can see and drive desktop apps and external browsers.
|
||||
- **[Drag Files to Agents](https://orca.dev/docs/editing/file-explorer)**: drag files from the explorer, or images from your desktop, straight into an agent's prompt.
|
||||
- **[Jump Anywhere](https://orca.dev/docs/model/quick-open)**: one shortcut jumps to any workspace or tab across every repo and host.
|
||||
- **[Rich Previews](https://orca.dev/docs/editing/viewers)**: open images, PDFs, and Markdown right in the workspace, next to the agent that made them.
|
||||
- **[Mobile Emulator](https://orca.dev/docs/cli/overview#mobile-emulator)**: run an iOS Simulator or Android emulator beside your agent, and let it tap, type, and check its own work.
|
||||
- **[Dictation](https://orca.dev/docs/settings#voice)**: press a shortcut and talk. Orca types what you say into the agent's prompt or terminal.
|
||||
- **[Usage & Accounts](https://orca.dev/docs/agents/usage-tracking)**: see how much of your agent limits you've used and when they reset, and switch accounts without logging out.
|
||||
- **[Automations](https://orca.dev/docs/cli/automations)**: schedule prompts to run on any repo: nightly dependency bumps, weekly triage, flaky-test sweeps.
|
||||
- **[Computer Use](https://orca.dev/docs/cli/computer-use)**: when there's no CLI or API, agents can see and drive desktop apps and external browsers.
|
||||
- **And many, many more**: we ship daily, so this list is perpetually behind. The [changelog](https://github.com/stablyai/orca/releases) is the real feature list.
|
||||
|
||||
---
|
||||
@@ -261,7 +261,7 @@ Works with **any CLI agent** — if it runs in a terminal, it runs in Orca.
|
||||
|
||||
### Desktop — macOS, Windows, Linux
|
||||
|
||||
- **[Download from onOrca.dev](https://onorca.dev/download)**
|
||||
- **[Download from orca.dev](https://orca.dev/download)**
|
||||
- Or grab a build directly: [macOS Apple Silicon](https://github.com/stablyai/orca/releases/latest/download/orca-macos-arm64.dmg) · [macOS Intel](https://github.com/stablyai/orca/releases/latest/download/orca-macos-x64.dmg) · [Windows (.exe)](https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe) · [Linux AppImage](https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage) · [All builds](https://github.com/stablyai/orca/releases/latest)
|
||||
|
||||
_Or via a package manager:_
|
||||
@@ -284,7 +284,7 @@ yay -S stably-orca-bin
|
||||
Pair with your desktop app to monitor and steer your agents from your phone.
|
||||
|
||||
- **iOS:** [Download on the App Store](https://apps.apple.com/us/app/orca-ide/id6766130217)
|
||||
- **Android:** [Download APK](https://www.onorca.dev/download/android) · [Install guide](https://www.onorca.dev/docs/android-apk)
|
||||
- **Android:** [Download APK](https://orca.dev/download/android) · [Install guide](https://orca.dev/docs/android-apk)
|
||||
|
||||
---
|
||||
|
||||
@@ -297,7 +297,7 @@ Pair with your desktop app to monitor and steer your agents from your phone.
|
||||
<img src="docs/assets/wechat-qr.jpg" alt="WeChat QR code for the Orca community" width="160" />
|
||||
|
||||
- **Feedback & Ideas:** We ship fast. Missing something? [Request a new feature](https://github.com/stablyai/orca/issues).
|
||||
- **Privacy:** See the [privacy & telemetry docs](https://www.onorca.dev/docs/telemetry) for what anonymous usage data Orca collects and how to opt out.
|
||||
- **Privacy:** See the [privacy & telemetry docs](https://orca.dev/docs/telemetry) for what anonymous usage data Orca collects and how to opt out.
|
||||
- **Show Support:** [Star](https://github.com/stablyai/orca) this repo to follow along with our daily ships.
|
||||
|
||||
---
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^2.1.5",
|
||||
"@hono/node-server": "^2.1.6",
|
||||
"@orca-cloud/cloud-database": "workspace:*",
|
||||
"@orca-cloud/push-contract": "workspace:*",
|
||||
"google-auth-library": "^11.3.1",
|
||||
"hono": "^4.13.13",
|
||||
"hono": "^4.13.17",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"zod": "^4.6.7"
|
||||
},
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { PushDeviceRegistryStore, type PushDeviceUpsert } from './device-registry-store.js'
|
||||
import { openInMemoryPushDatabase } from '../database/push-database.js'
|
||||
import type { CloudDatabase as PushDatabase } from '@orca-cloud/cloud-database/cloud-database'
|
||||
|
||||
const OWNER = 'abcdefghijklmnop'
|
||||
const OTHER = 'ponmlkjihgfedcba'
|
||||
|
||||
describe('push device registry store', () => {
|
||||
let database: PushDatabase
|
||||
let clock = 1_700_000_000_000
|
||||
let devices: PushDeviceRegistryStore
|
||||
|
||||
beforeEach(async () => {
|
||||
database = await openInMemoryPushDatabase()
|
||||
clock = 1_700_000_000_000
|
||||
devices = new PushDeviceRegistryStore(database, () => clock)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await database.close()
|
||||
})
|
||||
|
||||
async function upsertOk(input: PushDeviceUpsert): Promise<string> {
|
||||
const result = await devices.upsert(input)
|
||||
if (!result.ok) throw new Error(`unexpected upsert refusal: ${result.reason}`)
|
||||
return result.registrationId
|
||||
}
|
||||
|
||||
function androidDevice(deviceId: string): PushDeviceUpsert {
|
||||
return {
|
||||
hostFingerprint: OWNER,
|
||||
deviceId,
|
||||
platform: 'android',
|
||||
token: `token-${deviceId}`
|
||||
}
|
||||
}
|
||||
|
||||
it('keeps one registration per host and device while replacing the token', async () => {
|
||||
const first = await upsertOk({
|
||||
hostFingerprint: OWNER,
|
||||
deviceId: 'device-1',
|
||||
platform: 'ios',
|
||||
token: 'a'.repeat(64),
|
||||
apnsEnvironment: 'sandbox'
|
||||
})
|
||||
clock += 1_000
|
||||
const second = await upsertOk({
|
||||
hostFingerprint: OWNER,
|
||||
deviceId: 'device-1',
|
||||
platform: 'ios',
|
||||
token: 'b'.repeat(64),
|
||||
apnsEnvironment: 'production'
|
||||
})
|
||||
expect(second).toBe(first)
|
||||
const registration = await devices.findById(first)
|
||||
expect(registration).toMatchObject({
|
||||
token: 'b'.repeat(64),
|
||||
apnsEnvironment: 'production',
|
||||
dead: false
|
||||
})
|
||||
expect(await devices.list(OWNER)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('revives a registration that a re-registered token replaces', async () => {
|
||||
const registrationId = await upsertOk({
|
||||
hostFingerprint: OWNER,
|
||||
deviceId: 'device-1',
|
||||
platform: 'android',
|
||||
token: 'token-one'
|
||||
})
|
||||
await devices.markDead((await devices.findById(registrationId))!)
|
||||
expect((await devices.findById(registrationId))?.dead).toBe(true)
|
||||
await upsertOk({
|
||||
hostFingerprint: OWNER,
|
||||
deviceId: 'device-1',
|
||||
platform: 'android',
|
||||
token: 'token-two'
|
||||
})
|
||||
expect(await devices.findById(registrationId)).toMatchObject({
|
||||
token: 'token-two',
|
||||
dead: false
|
||||
})
|
||||
})
|
||||
|
||||
it('lets only the owning host delete a registration', async () => {
|
||||
const registrationId = await upsertOk({
|
||||
hostFingerprint: OWNER,
|
||||
deviceId: 'device-1',
|
||||
platform: 'android',
|
||||
token: 'token-one'
|
||||
})
|
||||
expect(await devices.deleteOwned(OTHER, registrationId)).toBe(false)
|
||||
expect(await devices.findById(registrationId)).not.toBeNull()
|
||||
expect(await devices.deleteOwned(OWNER, registrationId)).toBe(true)
|
||||
expect(await devices.findById(registrationId)).toBeNull()
|
||||
})
|
||||
|
||||
it('scopes lookups and listings to the owning host', async () => {
|
||||
const owned = await upsertOk({
|
||||
hostFingerprint: OWNER,
|
||||
deviceId: 'device-1',
|
||||
platform: 'android',
|
||||
token: 'token-one'
|
||||
})
|
||||
const foreign = await upsertOk({
|
||||
hostFingerprint: OTHER,
|
||||
deviceId: 'device-2',
|
||||
platform: 'android',
|
||||
token: 'token-two'
|
||||
})
|
||||
const found = await devices.findOwned(OWNER, [owned, foreign])
|
||||
expect([...found.keys()]).toEqual([owned])
|
||||
expect(await devices.list(OTHER)).toEqual([
|
||||
{ registrationId: foreign, deviceId: 'device-2', platform: 'android', dead: false }
|
||||
])
|
||||
expect(await devices.findOwned(OWNER, [])).toEqual(new Map())
|
||||
})
|
||||
|
||||
it('counts the cap per host, not across the whole table', async () => {
|
||||
for (let index = 0; index < PUSH_LIMITS.maxDevicesPerHost; index++) {
|
||||
await upsertOk(androidDevice(`device-${index}`))
|
||||
}
|
||||
expect((await devices.upsert(androidDevice('extra'))).ok).toBe(false)
|
||||
expect(
|
||||
(await devices.upsert({ ...androidDevice('device-0'), hostFingerprint: OTHER })).ok
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
answerPushHostChallenge,
|
||||
@@ -27,21 +26,6 @@ describe('push host challenge store', () => {
|
||||
await database.close()
|
||||
})
|
||||
|
||||
it('rejects a challenge the moment its own ttl elapses', async () => {
|
||||
const host = createPushHostKeypair(4)
|
||||
const challenge = await store.issue(hostPublicKeyB64(host))
|
||||
const proof = answerPushHostChallenge(challenge!, {
|
||||
gatewayOrigin: GATEWAY_ORIGIN,
|
||||
keypair: host,
|
||||
now: () => clock
|
||||
})!
|
||||
clock += PUSH_LIMITS.challengeTtlMs + 1
|
||||
await expect(store.verify(challenge!.challengeId, proof)).resolves.toEqual({
|
||||
ok: false,
|
||||
reason: 'expired'
|
||||
})
|
||||
})
|
||||
|
||||
it('refuses a wrong host: the box will not open and a foreign proof will not match', async () => {
|
||||
const owner = createPushHostKeypair(6)
|
||||
const intruder = createPushHostKeypair(7)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { PushHostSessionStore } from './host-session-store.js'
|
||||
import { openInMemoryPushDatabase } from '../database/push-database.js'
|
||||
import type { CloudDatabase as PushDatabase } from '@orca-cloud/cloud-database/cloud-database'
|
||||
|
||||
const HOST = 'abcdefghijklmnop'
|
||||
|
||||
describe('push host session store', () => {
|
||||
let database: PushDatabase
|
||||
let clock = 1_700_000_000_000
|
||||
let sessions: PushHostSessionStore
|
||||
|
||||
beforeEach(async () => {
|
||||
database = await openInMemoryPushDatabase()
|
||||
clock = 1_700_000_000_000
|
||||
sessions = new PushHostSessionStore(database, () => clock)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await database.close()
|
||||
})
|
||||
|
||||
it('reports expiry separately from an unknown token', async () => {
|
||||
const session = await sessions.create(HOST)
|
||||
clock += PUSH_LIMITS.sessionTtlMs + 1
|
||||
await expect(sessions.resolve(session.sessionToken)).resolves.toEqual({
|
||||
ok: false,
|
||||
reason: 'session_expired'
|
||||
})
|
||||
await expect(sessions.resolve('not-a-session')).resolves.toEqual({
|
||||
ok: false,
|
||||
reason: 'unknown_session'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps one live session per host and prunes it once expired', async () => {
|
||||
const first = await sessions.create(HOST)
|
||||
const second = await sessions.create(HOST)
|
||||
// The earlier session is gone the moment its host proves again, so a flood
|
||||
// of proofs leaves one row per host rather than one per proof.
|
||||
await expect(sessions.resolve(first.sessionToken)).resolves.toEqual({
|
||||
ok: false,
|
||||
reason: 'unknown_session'
|
||||
})
|
||||
await expect(sessions.resolve(second.sessionToken)).resolves.toMatchObject({ ok: true })
|
||||
const other = await sessions.create('ponmlkjihgfedcba')
|
||||
await expect(sessions.resolve(second.sessionToken)).resolves.toMatchObject({ ok: true })
|
||||
clock += PUSH_LIMITS.sessionTtlMs + 1
|
||||
expect(await sessions.pruneExpired()).toBe(2)
|
||||
await expect(sessions.resolve(other.sessionToken)).resolves.toMatchObject({ ok: false })
|
||||
})
|
||||
})
|
||||
@@ -1,81 +0,0 @@
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { Hono } from 'hono'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ClientIpRateLimiter, clientIpRateLimit } from './client-ip-rate-limit.js'
|
||||
|
||||
const CAPACITY = PUSH_LIMITS.unauthenticatedRequestsPerMinutePerIp
|
||||
|
||||
function limiterApp(limiter: ClientIpRateLimiter, trustedProxyHops = 0): Hono {
|
||||
const app = new Hono()
|
||||
app.post('/probe', clientIpRateLimit(limiter, { trustedProxyHops }), (context) =>
|
||||
context.json({ ok: true })
|
||||
)
|
||||
return app
|
||||
}
|
||||
|
||||
describe('client ip rate limiter', () => {
|
||||
it('buckets on the last forwarded hop, the only one the platform appended', async () => {
|
||||
const app = limiterApp(new ClientIpRateLimiter({ now: () => 1_000 }))
|
||||
for (let index = 0; index < CAPACITY; index++) {
|
||||
await app.request('/probe', {
|
||||
method: 'POST',
|
||||
headers: { 'x-forwarded-for': `10.0.0.${index}, 203.0.113.7` }
|
||||
})
|
||||
}
|
||||
const sameClient = await app.request('/probe', {
|
||||
method: 'POST',
|
||||
headers: { 'x-forwarded-for': '10.9.9.9, 203.0.113.7' }
|
||||
})
|
||||
expect(sameClient.status).toBe(429)
|
||||
const otherClient = await app.request('/probe', {
|
||||
method: 'POST',
|
||||
headers: { 'x-forwarded-for': '10.0.0.1, 198.51.100.9' }
|
||||
})
|
||||
expect(otherClient.status).toBe(200)
|
||||
})
|
||||
|
||||
it('skips the configured trusted proxies when counting from the right', async () => {
|
||||
const app = limiterApp(new ClientIpRateLimiter({ now: () => 1_000, capacity: 1 }), 1)
|
||||
// <client>, <cloud run>, <load balancer>: one trusted hop after the client.
|
||||
const headers = { 'x-forwarded-for': '203.0.113.7, 10.0.0.1' }
|
||||
expect((await app.request('/probe', { method: 'POST', headers })).status).toBe(200)
|
||||
expect((await app.request('/probe', { method: 'POST', headers })).status).toBe(429)
|
||||
expect(
|
||||
(
|
||||
await app.request('/probe', {
|
||||
method: 'POST',
|
||||
headers: { 'x-forwarded-for': '198.51.100.9, 10.0.0.1' }
|
||||
})
|
||||
).status
|
||||
).toBe(200)
|
||||
})
|
||||
|
||||
it('trusts nothing when the header is shorter than the configured depth', async () => {
|
||||
const app = limiterApp(new ClientIpRateLimiter({ now: () => 1_000, capacity: 1 }), 1)
|
||||
// Only one hop, so the client value the depth points at does not exist.
|
||||
const headers = { 'x-forwarded-for': '203.0.113.7' }
|
||||
expect((await app.request('/probe', { method: 'POST', headers })).status).toBe(200)
|
||||
expect(
|
||||
(
|
||||
await app.request('/probe', {
|
||||
method: 'POST',
|
||||
headers: { 'x-forwarded-for': '198.51.100.9' }
|
||||
})
|
||||
).status
|
||||
).toBe(429)
|
||||
})
|
||||
|
||||
it('ignores spoofable x-real-ip and uses a single shared bucket', async () => {
|
||||
const app = limiterApp(new ClientIpRateLimiter({ now: () => 1_000, capacity: 1 }))
|
||||
expect(
|
||||
(await app.request('/probe', { method: 'POST', headers: { 'x-real-ip': '198.51.100.9' } }))
|
||||
.status
|
||||
).toBe(200)
|
||||
expect(
|
||||
(await app.request('/probe', { method: 'POST', headers: { 'x-real-ip': '203.0.113.7' } }))
|
||||
.status
|
||||
).toBe(429)
|
||||
expect((await app.request('/probe', { method: 'POST' })).status).toBe(429)
|
||||
expect((await app.request('/probe', { method: 'POST' })).status).toBe(429)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,6 @@
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createPushHostKeypair } from '../host-auth/host-challenge-answering.test-fixture.js'
|
||||
import type { CloudDatabase as PushDatabase } from '@orca-cloud/cloud-database/cloud-database'
|
||||
import { createPushServer } from '../push-server.js'
|
||||
import { createPushServerHarness, testPushConfig } from './push-server-harness.test-fixture.js'
|
||||
import { createPushServerHarness } from './push-server-harness.test-fixture.js'
|
||||
|
||||
describe('push gateway authentication and device routes', () => {
|
||||
let harness: Awaited<ReturnType<typeof createPushServerHarness>>
|
||||
@@ -16,63 +13,6 @@ describe('push gateway authentication and device routes', () => {
|
||||
await harness.close()
|
||||
})
|
||||
|
||||
it('reports not ready when the database is unreachable', async () => {
|
||||
const unreachable: PushDatabase = {
|
||||
dialect: 'sqlite',
|
||||
query: async () => {
|
||||
throw new Error('no connection')
|
||||
},
|
||||
transaction: async (operation) => await operation(unreachable),
|
||||
lockQuotaScope: async () => undefined,
|
||||
tryLockScope: async () => true,
|
||||
tryLockSharedScope: async () => true,
|
||||
close: async () => undefined
|
||||
}
|
||||
const broken = createPushServer(testPushConfig(), unreachable, {
|
||||
fcmAccessToken: async () => 'token',
|
||||
fcmTransport: async () => ({ status: 200, body: '{}' })
|
||||
})
|
||||
expect((await broken.app.request('/health')).status).toBe(200)
|
||||
expect((await broken.app.request('/ready')).status).toBe(503)
|
||||
await broken.worker.stop()
|
||||
})
|
||||
|
||||
it('completes challenge, session, register, list, delete', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(11))
|
||||
const registrationId = await harness.registerAndroid(sessionToken)
|
||||
|
||||
const list = await harness.authorized('/v1/devices', {}, sessionToken)
|
||||
expect(await list.json()).toEqual({
|
||||
devices: [{ registrationId, deviceId: 'device-1', platform: 'android', dead: false }]
|
||||
})
|
||||
|
||||
const deleted = await harness.authorized(
|
||||
`/v1/devices/${registrationId}`,
|
||||
{ method: 'DELETE' },
|
||||
sessionToken
|
||||
)
|
||||
expect(deleted.status).toBe(204)
|
||||
expect(await harness.server.devices.findById(registrationId)).toBeNull()
|
||||
})
|
||||
|
||||
it('refuses a request with no bearer, a bogus bearer, and an expired session', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(12))
|
||||
expect((await harness.server.app.request('/v1/devices')).status).toBe(401)
|
||||
const bogus = await harness.authorized('/v1/devices', {}, 'nonsense')
|
||||
expect(bogus.status).toBe(401)
|
||||
expect(await bogus.json()).toEqual({ error: 'invalid_token' })
|
||||
|
||||
harness.advanceClock(PUSH_LIMITS.sessionTtlMs + 1)
|
||||
const expired = await harness.authorized('/v1/devices', {}, sessionToken)
|
||||
expect(expired.status).toBe(401)
|
||||
expect(await expired.json()).toEqual({ error: 'session_expired' })
|
||||
})
|
||||
|
||||
it('never returns the host fingerprint on the challenge itself', async () => {
|
||||
const challenge = await harness.issueChallenge(createPushHostKeypair(22))
|
||||
expect(challenge).not.toHaveProperty('hostFingerprint')
|
||||
})
|
||||
|
||||
it('lets only the owning host delete a registration', async () => {
|
||||
const ownerToken = await harness.signIn(createPushHostKeypair(14))
|
||||
const intruderToken = await harness.signIn(createPushHostKeypair(15))
|
||||
@@ -87,34 +27,4 @@ describe('push gateway authentication and device routes', () => {
|
||||
expect(await forbidden.json()).toEqual({ error: 'not_found' })
|
||||
expect(await harness.server.devices.findById(registrationId)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('replaces the token on a re-registration and keeps one registration id', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(23))
|
||||
const first = await harness.registerAndroid(sessionToken)
|
||||
const again = await harness.post(
|
||||
'/v1/devices',
|
||||
{
|
||||
v: 1,
|
||||
deviceId: 'device-1',
|
||||
platform: 'android',
|
||||
token: 'rotated_token:APA91b-newnewnewnewnewnewnewnewnewnew'
|
||||
},
|
||||
sessionToken
|
||||
)
|
||||
expect(await again.json()).toEqual({ registrationId: first })
|
||||
expect(await harness.server.devices.findById(first)).toMatchObject({
|
||||
token: 'rotated_token:APA91b-newnewnewnewnewnewnewnewnewnew'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects a malformed registration body', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(16))
|
||||
const bad = await harness.post(
|
||||
'/v1/devices',
|
||||
{ v: 1, deviceId: 'device-1', platform: 'ios', token: 'not-hex' },
|
||||
sessionToken
|
||||
)
|
||||
expect(bad.status).toBe(400)
|
||||
expect(await bad.json()).toEqual({ error: 'invalid_request' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
createPushHostKeypair,
|
||||
hostPublicKeyB64
|
||||
} from '../host-auth/host-challenge-answering.test-fixture.js'
|
||||
import { createPushServerHarness, notification } from './push-server-harness.test-fixture.js'
|
||||
import { createPushServerHarness } from './push-server-harness.test-fixture.js'
|
||||
|
||||
const CLIENT_IP = '203.0.113.7'
|
||||
const OTHER_CLIENT_IP = '198.51.100.9'
|
||||
@@ -48,42 +48,6 @@ describe('push gateway request limits', () => {
|
||||
expect(await response.json()).toEqual({ error: 'request_too_large' })
|
||||
})
|
||||
|
||||
it('still refuses an oversized body that declares a content length', async () => {
|
||||
const body = oversizedChallengeBody()
|
||||
const response = await harness.server.app.request('/v1/host/challenge', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'content-length': String(Buffer.byteLength(body))
|
||||
},
|
||||
body
|
||||
})
|
||||
expect(response.status).toBe(413)
|
||||
expect(await response.json()).toEqual({ error: 'request_too_large' })
|
||||
})
|
||||
|
||||
it('caps an authenticated oversized send as well', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(61))
|
||||
const response = await harness.server.app.request(
|
||||
new Request('http://push.test/v1/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: `Bearer ${sessionToken}`
|
||||
},
|
||||
body: new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(oversizedChallengeBody()))
|
||||
controller.close()
|
||||
}
|
||||
}),
|
||||
duplex: 'half'
|
||||
} as RequestInit)
|
||||
)
|
||||
expect(response.status).toBe(413)
|
||||
expect(await response.json()).toEqual({ error: 'request_too_large' })
|
||||
})
|
||||
|
||||
it('rate limits one client ip across both unauthenticated routes', async () => {
|
||||
const body = JSON.stringify({
|
||||
v: 1,
|
||||
@@ -135,27 +99,6 @@ describe('push gateway request limits', () => {
|
||||
expect(spoofed.status).toBe(429)
|
||||
})
|
||||
|
||||
it('lets a throttled client back in once the window refills', async () => {
|
||||
const body = JSON.stringify({
|
||||
v: 1,
|
||||
hostPublicKeyB64: hostPublicKeyB64(createPushHostKeypair(63))
|
||||
})
|
||||
const headers = { 'content-type': 'application/json', 'x-forwarded-for': CLIENT_IP }
|
||||
for (let index = 0; index < PUSH_LIMITS.unauthenticatedRequestsPerMinutePerIp; index++) {
|
||||
await harness.server.app.request('/v1/host/challenge', { method: 'POST', headers, body })
|
||||
}
|
||||
expect(
|
||||
(await harness.server.app.request('/v1/host/challenge', { method: 'POST', headers, body }))
|
||||
.status
|
||||
).toBe(429)
|
||||
|
||||
harness.advanceClock(60_000)
|
||||
expect(
|
||||
(await harness.server.app.request('/v1/host/challenge', { method: 'POST', headers, body }))
|
||||
.status
|
||||
).toBe(200)
|
||||
})
|
||||
|
||||
it('limits authenticated hosts independently behind the same IP', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(64))
|
||||
const headers = { 'x-forwarded-for': CLIENT_IP }
|
||||
@@ -175,43 +118,4 @@ describe('push gateway request limits', () => {
|
||||
})
|
||||
expect(challenge.status).toBe(200)
|
||||
})
|
||||
|
||||
// Why: a database error carries the failing row in its message. The response
|
||||
// and the log must both stop at the error's name.
|
||||
it('answers an unexpected route failure with a bare 500 and logs only the name', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(66))
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
try {
|
||||
await harness.database.close()
|
||||
const response = await harness.authorized('/v1/devices', {}, sessionToken)
|
||||
expect(response.status).toBe(500)
|
||||
expect(await response.json()).toEqual({ error: 'internal' })
|
||||
const logged = warn.mock.calls.map((call) => String(call[0])).join('\n')
|
||||
expect(logged).toContain('"event":"orca_push_request_failed"')
|
||||
expect(logged).not.toContain('SELECT')
|
||||
expect(logged).not.toContain('push_devices')
|
||||
expect(harness.server.observability.consume().request_error).toBe(1)
|
||||
} finally {
|
||||
warn.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('charges a repeated registration id once and returns one result', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(65))
|
||||
const registrationId = await harness.registerAndroid(sessionToken)
|
||||
|
||||
const response = await harness.post(
|
||||
'/v1/send',
|
||||
{
|
||||
v: 1,
|
||||
registrationIds: [registrationId, registrationId, registrationId],
|
||||
notification: notification()
|
||||
},
|
||||
sessionToken
|
||||
)
|
||||
expect(await response.json()).toEqual({ results: [{ registrationId, status: 'queued' }] })
|
||||
expect(await harness.server.deliveryStore.pendingCount(registrationId)).toBe(1)
|
||||
const [row] = await harness.database.query('SELECT COUNT(*) AS sends FROM push_events')
|
||||
expect(Number(row?.sends)).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { PushNotificationSchema } from '@orca-cloud/push-contract/send-messages'
|
||||
import { PUSH_LIMITS } from '@orca-cloud/push-contract/push-limits'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createPushHostKeypair } from '../host-auth/host-challenge-answering.test-fixture.js'
|
||||
import {
|
||||
APNS_TOKEN,
|
||||
createPushServerHarness,
|
||||
FCM_TOKEN,
|
||||
notification
|
||||
@@ -20,24 +17,6 @@ describe('push gateway send route', () => {
|
||||
await harness.close()
|
||||
})
|
||||
|
||||
it('rejects a batch over the registration cap', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(16))
|
||||
const oversized = await harness.post(
|
||||
'/v1/send',
|
||||
{
|
||||
v: 1,
|
||||
registrationIds: Array.from(
|
||||
{ length: PUSH_LIMITS.maxRegistrationIdsPerSend + 1 },
|
||||
(_, index) => `reg-${index}`
|
||||
),
|
||||
notification: notification()
|
||||
},
|
||||
sessionToken
|
||||
)
|
||||
expect(oversized.status).toBe(400)
|
||||
expect(await oversized.json()).toEqual({ error: 'invalid_request' })
|
||||
})
|
||||
|
||||
it('queues a send, delivers it to fcm, and reports a dead token on the next send', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(17))
|
||||
const registrationId = await harness.registerAndroid(sessionToken)
|
||||
@@ -75,101 +54,6 @@ describe('push gateway send route', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('leaves a live registration alone when the provider reports a transient failure', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(24))
|
||||
const registrationId = await harness.registerAndroid(sessionToken)
|
||||
await harness.post(
|
||||
'/v1/send',
|
||||
{ v: 1, registrationIds: [registrationId], notification: notification() },
|
||||
sessionToken
|
||||
)
|
||||
harness.setFcmResponse({
|
||||
status: 503,
|
||||
body: JSON.stringify({ error: { status: 'UNAVAILABLE', message: 'backend busy' } })
|
||||
})
|
||||
await harness.flushDeliveries()
|
||||
expect(await harness.server.devices.findById(registrationId)).toMatchObject({ dead: false })
|
||||
})
|
||||
|
||||
it('reports retries from the durable worker after the provider delay', async () => {
|
||||
const token = await harness.signIn(createPushHostKeypair(26))
|
||||
const registrationId = await harness.registerAndroid(token)
|
||||
await harness.post(
|
||||
'/v1/send',
|
||||
{
|
||||
v: 1,
|
||||
registrationIds: [registrationId],
|
||||
notification: notification()
|
||||
},
|
||||
token
|
||||
)
|
||||
harness.setFcmResponse({ status: 503, body: '{}' })
|
||||
await harness.flushDeliveries()
|
||||
expect(harness.server.observability.consume()).toMatchObject({
|
||||
delivery_error: 1,
|
||||
delivery_retry: 0
|
||||
})
|
||||
harness.advanceClock(10_000)
|
||||
harness.setFcmResponse({ status: 200, body: '{}' })
|
||||
await harness.server.worker.runDue()
|
||||
expect(harness.server.observability.consume()).toMatchObject({
|
||||
delivery_sent: 1,
|
||||
delivery_retry: 1
|
||||
})
|
||||
})
|
||||
|
||||
it('sends a burst as individual APNs alerts grouped by the host thread', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(18))
|
||||
const registration = await harness.post(
|
||||
'/v1/devices',
|
||||
{
|
||||
v: 1,
|
||||
deviceId: 'iphone-1',
|
||||
platform: 'ios',
|
||||
token: APNS_TOKEN,
|
||||
apnsEnvironment: 'sandbox'
|
||||
},
|
||||
sessionToken
|
||||
)
|
||||
const { registrationId } = (await registration.json()) as { registrationId: string }
|
||||
for (const seq of [1, 2, 3]) {
|
||||
await harness.post(
|
||||
'/v1/send',
|
||||
{
|
||||
v: 1,
|
||||
registrationIds: [registrationId],
|
||||
notification: notification({ notificationId: `note-${seq}`, notificationSeq: seq })
|
||||
},
|
||||
sessionToken
|
||||
)
|
||||
}
|
||||
await harness.flushDeliveries()
|
||||
expect(harness.apnsRequests).toHaveLength(3)
|
||||
const bodies = harness.apnsRequests.map(
|
||||
(request) =>
|
||||
JSON.parse(request.body) as {
|
||||
aps: { alert: { title: string; body: string }; 'thread-id': string }
|
||||
orca: Record<string, unknown> & { notificationSeq: number }
|
||||
}
|
||||
)
|
||||
expect(
|
||||
harness.apnsRequests.every((request) => request.host === 'api.sandbox.push.apple.com')
|
||||
).toBe(true)
|
||||
expect(bodies.map((body) => body.aps.alert)).toEqual(
|
||||
Array.from({ length: 3 }, () => ({
|
||||
title: 'Agent needs input',
|
||||
body: 'Waiting on your answer'
|
||||
}))
|
||||
)
|
||||
expect(new Set(bodies.map((body) => body.aps['thread-id'])).size).toBe(1)
|
||||
expect(bodies.map((body) => body.orca.notificationSeq).sort((a, b) => a - b)).toEqual([1, 2, 3])
|
||||
expect(bodies.every((body) => !('coalescedCount' in body.orca))).toBe(true)
|
||||
expect(bodies.every((body) => !('summaryMembers' in body.orca))).toBe(true)
|
||||
expect(
|
||||
new Set(harness.apnsRequests.map((request) => request.headers['apns-collapse-id'])).size
|
||||
).toBe(3)
|
||||
})
|
||||
|
||||
it('reports an error for a registration the host does not own', async () => {
|
||||
const ownerToken = await harness.signIn(createPushHostKeypair(19))
|
||||
const intruderToken = await harness.signIn(createPushHostKeypair(20))
|
||||
@@ -188,29 +72,4 @@ describe('push gateway send route', () => {
|
||||
})
|
||||
expect(await harness.server.deliveryStore.pendingCount(registrationId)).toBe(0)
|
||||
})
|
||||
|
||||
it('rate limits a host that exhausted its 15-minute allowance', async () => {
|
||||
const sessionToken = await harness.signIn(createPushHostKeypair(21))
|
||||
const registrationId = await harness.registerAndroid(sessionToken)
|
||||
const hostFingerprint = (await harness.server.devices.findById(registrationId))!.hostFingerprint
|
||||
for (let index = 0; index < PUSH_LIMITS.hostEventsPerWindow; index++) {
|
||||
expect(
|
||||
await harness.server.deliveryStore.accept(
|
||||
hostFingerprint,
|
||||
registrationId,
|
||||
PushNotificationSchema.parse(
|
||||
notification({ notificationId: `note-${index + 1000}`, notificationSeq: index + 1000 })
|
||||
)
|
||||
)
|
||||
).toBe('queued')
|
||||
}
|
||||
const limited = await harness.post(
|
||||
'/v1/send',
|
||||
{ v: 1, registrationIds: [registrationId], notification: notification() },
|
||||
sessionToken
|
||||
)
|
||||
expect(limited.status).toBe(200)
|
||||
expect(await limited.json()).toEqual({ results: [{ registrationId, status: 'rate_limited' }] })
|
||||
expect(await harness.server.deliveryStore.pendingCount(registrationId)).toBe(300)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { startPushBackground } from '../push-background.js'
|
||||
import { createPushHostKeypair } from '../host-auth/host-challenge-answering.test-fixture.js'
|
||||
import { createPushServerHarness, notification } from './push-server-harness.test-fixture.js'
|
||||
|
||||
const cleanups: (() => Promise<void>)[] = []
|
||||
afterEach(async () => {
|
||||
for (const cleanup of cleanups.splice(0)) {
|
||||
await cleanup()
|
||||
}
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
async function fixture() {
|
||||
const h = await createPushServerHarness()
|
||||
const token = await h.signIn(createPushHostKeypair(31))
|
||||
const registrationId = await h.registerAndroid(token)
|
||||
let stop = async () => {}
|
||||
cleanups.push(async () => {
|
||||
await stop()
|
||||
await h.close()
|
||||
})
|
||||
vi.useFakeTimers()
|
||||
return {
|
||||
...h,
|
||||
registrationId,
|
||||
start: () => {
|
||||
stop = startPushBackground({ mode: 'active' }, h.server)
|
||||
},
|
||||
send: (event = notification(), ids = [registrationId]) =>
|
||||
h.post('/v1/send', { v: 1, registrationIds: ids, notification: event }, token),
|
||||
invalid: () => h.post('/v1/send', { v: 1 }, token)
|
||||
}
|
||||
}
|
||||
|
||||
it('keeps concurrent accepted retries idempotent while automatic delivery runs', async () => {
|
||||
const h = await fixture()
|
||||
h.start()
|
||||
await Promise.all(Array.from({ length: 10 }, () => h.send()))
|
||||
await vi.waitFor(() => expect(h.fcmRequests).toHaveLength(1))
|
||||
await h.send()
|
||||
await h.server.worker.runDue()
|
||||
expect(h.fcmRequests).toHaveLength(1)
|
||||
expect(await h.server.deliveryStore.pendingCount(h.registrationId)).toBe(0)
|
||||
})
|
||||
@@ -1,71 +0,0 @@
|
||||
import { generateKeyPairSync } from 'node:crypto'
|
||||
import { expect, it } from 'vitest'
|
||||
|
||||
import { ApnsClient } from './apns-client.js'
|
||||
import type { ApnsRequest } from './apns-http2-transport.js'
|
||||
import type { ApnsResponse } from './apns-stream-response.js'
|
||||
import type { ApnsCredentials } from '../../config.js'
|
||||
import { buildPushDelivery } from '../../delivery/push-delivery-message.js'
|
||||
|
||||
const HOST = 'abcdefghijklmnop'
|
||||
|
||||
function credentials(): ApnsCredentials {
|
||||
const { privateKey } = generateKeyPairSync('ec', {
|
||||
namedCurve: 'P-256',
|
||||
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
||||
publicKeyEncoding: { type: 'spki', format: 'pem' }
|
||||
})
|
||||
return { keyPem: privateKey, keyId: 'ABCDE12345', teamId: 'TEAM123456' }
|
||||
}
|
||||
|
||||
function delivery(now = Date.now()) {
|
||||
return buildPushDelivery({
|
||||
expiresAt: now + 300_000,
|
||||
registrationId: 'reg-1',
|
||||
hostFingerprint: HOST,
|
||||
notification: {
|
||||
notificationId: 'note-1',
|
||||
notificationSeq: 7,
|
||||
notificationEpoch: 'epoch-1',
|
||||
source: 'agent-task-complete',
|
||||
agentState: 'needs-input',
|
||||
title: 'Agent needs input',
|
||||
body: 'Waiting on your answer',
|
||||
worktreeId: 'wt-1'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function fakeTransport(response: ApnsResponse) {
|
||||
const requests: ApnsRequest[] = []
|
||||
return {
|
||||
requests,
|
||||
transport: async (request: ApnsRequest): Promise<ApnsResponse> => {
|
||||
requests.push(request)
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('does not collapse background dismissals with visible alerts', async () => {
|
||||
const fake = fakeTransport({ status: 200, body: '' })
|
||||
const apns = new ApnsClient({
|
||||
topic: 'test',
|
||||
credentials: credentials(),
|
||||
transport: fake.transport
|
||||
})
|
||||
const alert = delivery()
|
||||
await apns.send(
|
||||
{ ...alert, orca: { ...alert.orca, kind: 'dismiss' } },
|
||||
{
|
||||
token: 'test',
|
||||
apnsEnvironment: 'sandbox'
|
||||
}
|
||||
)
|
||||
expect(fake.requests[0]?.headers).not.toHaveProperty('apns-collapse-id')
|
||||
expect(fake.requests[0]?.headers).toMatchObject({
|
||||
'apns-push-type': 'background',
|
||||
'apns-priority': '5'
|
||||
})
|
||||
expect(JSON.parse(fake.requests[0]!.body).aps).toEqual({ 'content-available': 1 })
|
||||
})
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/pg": "^8.23.1",
|
||||
"jose": "^6.2.13",
|
||||
"jose": "^6.2.16",
|
||||
"pg": "^8.23.1",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"zod": "^4.6.7"
|
||||
@@ -26,7 +26,7 @@
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^5.0.5",
|
||||
"wrangler": "^4.157.0",
|
||||
"ws": "^8.22.0"
|
||||
"ws": "^8.22.2"
|
||||
},
|
||||
"allowScripts": {
|
||||
"workerd@1.20261103.1": true,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^2.1.5",
|
||||
"hono": "^4.13.13",
|
||||
"@hono/node-server": "^2.1.6",
|
||||
"hono": "^4.13.17",
|
||||
"zod": "^4.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
"typecheck": "pnpm --dir ../.. build:process-host && tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^2.1.5",
|
||||
"@hono/node-server": "^2.1.6",
|
||||
"@orca-cloud/postgres-schema": "workspace:*",
|
||||
"@orca-cloud/relay-contract": "workspace:*",
|
||||
"hono": "^4.13.13",
|
||||
"jose": "^6.2.13",
|
||||
"hono": "^4.13.17",
|
||||
"jose": "^6.2.16",
|
||||
"pg": "^8.23.1",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"ws": "^8.22.0",
|
||||
"ws": "^8.22.2",
|
||||
"zod": "^4.6.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import pg from 'pg'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { ASSIGNMENT_CONNECTION_HEADROOM_QUERY } from './assignment-connection-headroom-query.js'
|
||||
import { RelayAssignmentStore } from './assignment-store.js'
|
||||
import {
|
||||
openRelayDatabase,
|
||||
@@ -9,7 +7,6 @@ import {
|
||||
|
||||
const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
|
||||
const describePostgres = databaseUrl ? describe : describe.skip
|
||||
const headroomIndexName = 'relay_control_connection_reservation_headroom'
|
||||
const cell = {
|
||||
id: 'connection-headroom-postgres',
|
||||
url: 'https://connection-headroom-postgres.example.com',
|
||||
@@ -160,39 +157,6 @@ describePostgres('PostgreSQL assignment connection headroom', () => {
|
||||
expect(Number(reservations[0]!.count)).toBe(1)
|
||||
}, 15_000)
|
||||
|
||||
it('uses the composite headroom index when released history dominates', async () => {
|
||||
await databases[0]!.query(
|
||||
`INSERT INTO relay_control_connection_reservations
|
||||
(reservation_id, idempotency_key, user_id, relay_host_id,
|
||||
assignment_epoch, cell_id, state, created_at, timeout_at, updated_at)
|
||||
SELECT 'headroom-plan-' || value, 'headroom-plan-' || value,
|
||||
'connection-headroom-postgres-plan', 'plan-host-' || value,
|
||||
1, ?, 'released', 1, 2, 1
|
||||
FROM generate_series(1, 50000) AS value`,
|
||||
[cell.id]
|
||||
)
|
||||
await databases[0]!.query(`ANALYZE relay_control_connection_reservations`)
|
||||
|
||||
const client = new pg.Client({ connectionString: databaseUrl })
|
||||
await client.connect()
|
||||
let reservationIndexPlan: Record<string, unknown> | undefined
|
||||
try {
|
||||
const plan = await client.query(
|
||||
`EXPLAIN (FORMAT JSON) ${ASSIGNMENT_CONNECTION_HEADROOM_QUERY}`
|
||||
)
|
||||
reservationIndexPlan = findReservationHeadroomIndexPlan(
|
||||
plan.rows[0]?.['QUERY PLAN']
|
||||
)
|
||||
} finally {
|
||||
await client.end()
|
||||
}
|
||||
|
||||
expect(reservationIndexPlan).toBeDefined()
|
||||
expect(String(reservationIndexPlan?.['Index Cond'])).toContain('cell_id')
|
||||
expect(String(reservationIndexPlan?.['Index Cond'])).toContain('state = ANY')
|
||||
expect(reservationIndexPlan?.['Filter']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('deduplicates expired debt after a fresh PostgreSQL snapshot', async () => {
|
||||
const now = 200
|
||||
const store = new RelayAssignmentStore(databases[0]!, () => now, {
|
||||
@@ -337,16 +301,3 @@ describePostgres('PostgreSQL assignment connection headroom', () => {
|
||||
).toEqual([{ state: 'released' }])
|
||||
})
|
||||
})
|
||||
|
||||
function findReservationHeadroomIndexPlan(
|
||||
value: unknown
|
||||
): Record<string, unknown> | undefined {
|
||||
if (value === null || typeof value !== 'object') return undefined
|
||||
const record = value as Record<string, unknown>
|
||||
if (!Array.isArray(value) && record['Index Name'] === headroomIndexName) return record
|
||||
for (const child of Object.values(value)) {
|
||||
const match = findReservationHeadroomIndexPlan(child)
|
||||
if (match) return match
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -157,14 +157,6 @@ describePostgres('a host leaving a dead reserve cell, on PostgreSQL', () => {
|
||||
expect(await reservedOn(dead)).toEqual(reservedBefore)
|
||||
})
|
||||
|
||||
it('hard crash with no dead-man trip: a ledger-only row moves too', async () => {
|
||||
const host = nextHost()
|
||||
await ledgerRow(host, 7)
|
||||
await store.setCellAdmitMode(dead.id, 'reserve')
|
||||
await killDeadCell()
|
||||
expect(await hostAssign(host)).toMatchObject({ cellId: live.id, assignmentEpoch: 8 })
|
||||
})
|
||||
|
||||
it('a seat ahead of the row: the move mints above it, and the revived cell\'s seat never wins the row back', async () => {
|
||||
const host = nextHost()
|
||||
await ledgerRow(host, 4)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Generated
+33
-33
@@ -188,8 +188,8 @@ importers:
|
||||
apps/push:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^2.1.5
|
||||
version: 2.1.5(hono@4.13.13)
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(hono@4.13.17)
|
||||
'@orca-cloud/cloud-database':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/cloud-database
|
||||
@@ -200,8 +200,8 @@ importers:
|
||||
specifier: ^11.3.1
|
||||
version: 11.3.1(supports-color@10.2.2)
|
||||
hono:
|
||||
specifier: ^4.13.13
|
||||
version: 4.13.13
|
||||
specifier: ^4.13.17
|
||||
version: 4.13.17
|
||||
tweetnacl:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
@@ -231,8 +231,8 @@ importers:
|
||||
apps/relay:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^2.1.5
|
||||
version: 2.1.5(hono@4.13.13)
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(hono@4.13.17)
|
||||
'@orca-cloud/postgres-schema':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/postgres-schema
|
||||
@@ -240,11 +240,11 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/relay-contract
|
||||
hono:
|
||||
specifier: ^4.13.13
|
||||
version: 4.13.13
|
||||
specifier: ^4.13.17
|
||||
version: 4.13.17
|
||||
jose:
|
||||
specifier: ^6.2.13
|
||||
version: 6.2.13
|
||||
specifier: ^6.2.16
|
||||
version: 6.2.16
|
||||
pg:
|
||||
specifier: ^8.23.1
|
||||
version: 8.23.1
|
||||
@@ -252,8 +252,8 @@ importers:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
ws:
|
||||
specifier: ^8.22.0
|
||||
version: 8.22.0
|
||||
specifier: ^8.22.2
|
||||
version: 8.22.2
|
||||
zod:
|
||||
specifier: ^4.6.7
|
||||
version: 4.6.7
|
||||
@@ -286,8 +286,8 @@ importers:
|
||||
specifier: ^8.23.1
|
||||
version: 8.23.1
|
||||
jose:
|
||||
specifier: ^6.2.13
|
||||
version: 6.2.13
|
||||
specifier: ^6.2.16
|
||||
version: 6.2.16
|
||||
pg:
|
||||
specifier: ^8.23.1
|
||||
version: 8.23.1
|
||||
@@ -320,17 +320,17 @@ importers:
|
||||
specifier: ^4.157.0
|
||||
version: 4.157.0(@cloudflare/workers-types@5.20261104.1)(@types/node@24.19.2)
|
||||
ws:
|
||||
specifier: ^8.22.0
|
||||
version: 8.22.0
|
||||
specifier: ^8.22.2
|
||||
version: 8.22.2
|
||||
|
||||
apps/relay-fence-broker:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^2.1.5
|
||||
version: 2.1.5(hono@4.13.13)
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(hono@4.13.17)
|
||||
hono:
|
||||
specifier: ^4.13.13
|
||||
version: 4.13.13
|
||||
specifier: ^4.13.17
|
||||
version: 4.13.17
|
||||
zod:
|
||||
specifier: ^4.6.7
|
||||
version: 4.6.7
|
||||
@@ -647,8 +647,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@hono/node-server@2.1.5':
|
||||
resolution: {integrity: sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==}
|
||||
'@hono/node-server@2.1.6':
|
||||
resolution: {integrity: sha512-p/KH9MnfNcsm3sR6x4lNgm5j+QN4Et8srKredl9DZfXiyZ1UQ3aQ2YVpFLbqktYN5JI+DB+VZFKM2PqE5hLKjQ==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
hono: ^4 || ^5.0.0-0
|
||||
@@ -1208,16 +1208,16 @@ packages:
|
||||
resolution: {integrity: sha512-1R6uiq6XhXW2ZrcGtZpGgVDsYGzSmveVOn8Pp37qe1fbThHZ34e3t3eXj5mVu6PFSVx9fvB1KNVnYgVtq/BRcA==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
hono@4.13.13:
|
||||
resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==}
|
||||
hono@4.13.17:
|
||||
resolution: {integrity: sha512-vZIcbkvjOR5hHRWERbGkr8fGfF0GGM1ooYkuH448iO3b2AdVEr3AXZSw4Z3Kn+1L5z0CVoVeyV29VGYmFh05kQ==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
jose@6.2.13:
|
||||
resolution: {integrity: sha512-7TIg10ZE1x82YediN+lcrPNvGMKge9yFHQD+Qho/b8y2adfugU3AsvvHfxV0fzyFllT6l9xCjf4RWY9g/Z9PJQ==}
|
||||
jose@6.2.16:
|
||||
resolution: {integrity: sha512-4LSJpbuBuPdGrONCdZUy0xnxtLf8eEz+EY0SMXdHQEBU7bgUHDVblU/JAjryv3wID9/LK7/YuwFwq+AQNfMscQ==}
|
||||
|
||||
json-bigint@1.0.0:
|
||||
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
|
||||
@@ -1609,8 +1609,8 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
ws@8.22.0:
|
||||
resolution: {integrity: sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==}
|
||||
ws@8.22.2:
|
||||
resolution: {integrity: sha512-/rEZ2ejown2N5gxVTHUu9bVVC7ttu0/Tgzf5hbTOixR3Fs7mbbXgn2DbdbcKgJOmpQMtrIUWivtYuVMnp/W/yA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -1748,9 +1748,9 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.28.2':
|
||||
optional: true
|
||||
|
||||
'@hono/node-server@2.1.5(hono@4.13.13)':
|
||||
'@hono/node-server@2.1.6(hono@4.13.17)':
|
||||
dependencies:
|
||||
hono: 4.13.13
|
||||
hono: 4.13.17
|
||||
|
||||
'@img/colour@1.1.0': {}
|
||||
|
||||
@@ -2149,7 +2149,7 @@ snapshots:
|
||||
|
||||
google-logging-utils@2.1.0: {}
|
||||
|
||||
hono@4.13.13: {}
|
||||
hono@4.13.17: {}
|
||||
|
||||
https-proxy-agent@7.0.6(supports-color@10.2.2):
|
||||
dependencies:
|
||||
@@ -2158,7 +2158,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
jose@6.2.13: {}
|
||||
jose@6.2.16: {}
|
||||
|
||||
json-bigint@1.0.0:
|
||||
dependencies:
|
||||
@@ -2506,7 +2506,7 @@ snapshots:
|
||||
|
||||
ws@8.21.0: {}
|
||||
|
||||
ws@8.22.0: {}
|
||||
ws@8.22.2: {}
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ ignores:
|
||||
- '**/__fixtures__/**'
|
||||
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
|
||||
- 'src/main/claude/claude-stream-json-connection.ts'
|
||||
- 'src/main/claude/claude-agent-sdk-process-spawn.ts'
|
||||
- 'src/main/claude/child-process/claude-agent-sdk-process-spawn.ts'
|
||||
- 'src/main/claude/structured/launch/claude-structured-child-env.ts'
|
||||
- 'src/main/claude/claude-model-catalog-probe.ts'
|
||||
- 'src/main/claude/model-catalog/claude-model-catalog-probe.ts'
|
||||
rule:
|
||||
all:
|
||||
- any:
|
||||
@@ -67,9 +67,9 @@ ignores:
|
||||
- '**/__fixtures__/**'
|
||||
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
|
||||
- 'src/main/claude/claude-stream-json-connection.ts'
|
||||
- 'src/main/claude/claude-agent-sdk-process-spawn.ts'
|
||||
- 'src/main/claude/child-process/claude-agent-sdk-process-spawn.ts'
|
||||
- 'src/main/claude/structured/launch/claude-structured-child-env.ts'
|
||||
- 'src/main/claude/claude-model-catalog-probe.ts'
|
||||
- 'src/main/claude/model-catalog/claude-model-catalog-probe.ts'
|
||||
rule:
|
||||
all:
|
||||
- any:
|
||||
|
||||
@@ -62,11 +62,11 @@ Tasks cover explicit text targeting with another field logically focused, Unicod
|
||||
entry plus submit, select-all replacement plus submit, and color choice plus one
|
||||
click. Interference probes remain separate from task latency comparisons.
|
||||
|
||||
Explicit-target typing is a native capability probe. Orca's public CLI/RPC currently
|
||||
accepts typing at the focused field and does not expose an element target for text.
|
||||
Failure of this probe identifies a capability to consider adding; it does not prove
|
||||
a violation of the current public typing contract. Click-then-type tasks exercise
|
||||
the existing sequence, although this benchmark still bypasses public transports.
|
||||
Explicit-target typing is a native capability probe. Orca's public CLI/RPC accepts
|
||||
an element target for text (`type-text --element-index`) on providers that report
|
||||
`actions.typeTextElement` (macOS and Linux); Windows types only at the focused
|
||||
field. Click-then-type tasks send no element with the text, like `type-text`
|
||||
without the index, although this benchmark still bypasses public transports.
|
||||
|
||||
The Linux Electron job also records restoration stages in `focus-probe/results.json`.
|
||||
It records the AT-SPI focus acknowledgment, selected ACTIVE state, manual legacy PID
|
||||
|
||||
@@ -137,7 +137,7 @@ def main():
|
||||
raise AssertionError("cancellation fixture could not focus and select the field")
|
||||
before = keymap()
|
||||
operation = trial.directory / "cancel.json"
|
||||
bench.write_json(operation, engine._req("type_text", field, True, text="éΩAa" * 150))
|
||||
bench.write_json(operation, engine._req("type_text", None, True, text="éΩAa" * 150))
|
||||
held_before = held_keys()
|
||||
env = dict(os.environ)
|
||||
ready = trial.directory / "held-ready"
|
||||
|
||||
@@ -194,7 +194,7 @@ class CuaEngine:
|
||||
raise RuntimeError("selected cua element has no token")
|
||||
if action == "click":
|
||||
tool, args = "click", {**base, "element_token": tok}
|
||||
elif action == "type":
|
||||
elif action in ("type", "type_into"):
|
||||
tool, args = "type_text", {**base, "element_token": tok, "text": text}
|
||||
elif action == "return":
|
||||
tool, args = "press_key", {**base, "element_token": tok, "key": "return"}
|
||||
@@ -254,6 +254,9 @@ class OrcaEngine:
|
||||
if action == "click":
|
||||
req = self._req("click", el, escalate)
|
||||
elif action == "type":
|
||||
# Why: click-then-type mirrors public `type-text` without --element-index, which types at focus.
|
||||
req = self._req("type_text", None, escalate, text=text)
|
||||
elif action == "type_into":
|
||||
req = self._req("type_text", el, escalate, text=text)
|
||||
elif action == "return":
|
||||
req = self._req("press_key", el, escalate, key="return")
|
||||
|
||||
@@ -12,7 +12,7 @@ SELECTORS = {
|
||||
"scroll": (r"list|scroll|document|web", r"\blist\b|\bbenchelectron\b"),
|
||||
}
|
||||
TASKS = {
|
||||
"targeted_type": [("type", "field")],
|
||||
"targeted_type": [("type_into", "field")],
|
||||
"type_submit": [("click", "field"), ("type", "field"), ("return", "field")],
|
||||
"replace_submit": [("click", "field"), ("ctrl_a", "field"), ("type", "field"), ("return", "field")],
|
||||
"choose_and_click": [("choose", "color"), ("click", "button")],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CLI_MAIN_ENTRY_NAMES } from './cli-main-entry-names'
|
||||
/** Main-build Rollup inputs: output file name (without .js) to its source entry. */
|
||||
export const MAIN_BUILD_ENTRY_POINTS: Record<string, string> = {
|
||||
index: resolve('src/main/index.ts'),
|
||||
'backend-entry': resolve('src/main/backend/backend-entry.ts'),
|
||||
'browser-extension-native-host-supervisor-entry': resolve(
|
||||
'src/main/browser/extensions/native-messaging/browser-extension-native-host-supervisor-entry.ts'
|
||||
),
|
||||
@@ -15,7 +16,7 @@ export const MAIN_BUILD_ENTRY_POINTS: Record<string, string> = {
|
||||
),
|
||||
'computer-sidecar': resolve('src/main/computer/computer-sidecar.ts'),
|
||||
'claude-conversation-fork-worker-entry': resolve(
|
||||
'src/main/claude/claude-conversation-fork-worker-entry.ts'
|
||||
'src/main/claude/transcripts/claude-conversation-fork-worker-entry.ts'
|
||||
),
|
||||
'stt-worker': resolve('src/main/speech/stt-worker.ts'),
|
||||
'warp-theme-parser-worker': resolve('src/main/warp-themes/warp-theme-parser-worker.ts'),
|
||||
@@ -25,11 +26,14 @@ export const MAIN_BUILD_ENTRY_POINTS: Record<string, string> = {
|
||||
'foreign-sqlite-reader-entry': resolve(
|
||||
'src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts'
|
||||
),
|
||||
'agent-identity-worker-entry': resolve(
|
||||
'src/main/agent-hooks/identity/agent-identity-worker-entry.ts'
|
||||
),
|
||||
'session-scanner-service-entry': resolve(
|
||||
'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'),
|
||||
|
||||
@@ -15,6 +15,7 @@ type OutputChunk = Rollup.OutputChunk
|
||||
|
||||
// Plain-Node processes and CLI modules cannot load Electron's API.
|
||||
const PLAIN_NODE_ENTRY_NAMES = [
|
||||
'backend-entry',
|
||||
'browser-extension-native-host-supervisor-entry',
|
||||
'daemon-entry',
|
||||
'parcel-watcher-process-entry',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
export const WORKER_THREAD_ENTRY_NAMES = [
|
||||
'warp-theme-parser-worker',
|
||||
'foreign-sqlite-reader-entry',
|
||||
'agent-identity-worker-entry',
|
||||
'session-search-worker-entry',
|
||||
'main-thread-hang-watchdog-entry',
|
||||
'port-scan-command-worker-entry',
|
||||
|
||||
@@ -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,26 +348,29 @@ 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',
|
||||
'out/main/foreign-sqlite-reader-entry.js',
|
||||
'out/main/agent-identity-worker-entry.js',
|
||||
'out/main/session-search-worker-entry.js',
|
||||
'out/main/plugin-host-entry.js',
|
||||
'out/main/browser-extension-native-host-supervisor-entry.js',
|
||||
'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/**',
|
||||
|
||||
@@ -5,37 +5,27 @@
|
||||
4 test-no-in-test-import-actual:src/main/bitbucket/credential-store.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/bitbucket/status-no-decrypt.test.ts
|
||||
3 test-no-in-test-import-actual:src/main/claude-usage/scanner-scan.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/claude/claude-agent-sdk-process-spawn.test.ts
|
||||
5 test-no-in-test-import-actual:src/main/daemon/endpoint/publication/testing/daemon-endpoint-publish.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/daemon/history/storage/testing/terminal-history-permissions.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/git/command-runner/spawned-command-tree-kill.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/git/command-runner/process-capture/spawned-command-tree-kill.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/ipc/filesystem/filesystem-content-search-cancellation.test.ts
|
||||
1 test-no-in-test-import-actual:src/main/linear/accounts/linear-account-client.test.ts
|
||||
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/managed-server/host-state/orcad-snapshot-room.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
|
||||
3 test-no-in-test-import-actual:src/renderer/src/components/terminal-pane/snapshots/remote-snapshot-alt-screen-host-chain.test.ts
|
||||
1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts
|
||||
1 test-no-polling-loop:src/main/browser/cookies/browser-cookie-import-concurrency.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/claude-stop-ack-race.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/claude-stream-json-connection.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/claude-supervised-stop-real-cli.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/claude-supervised-stop.integration.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/structured/claude-structured-real-cli-fold.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/structured/claude-structured-real-cli-stop-task.test.ts
|
||||
5 test-no-polling-loop:src/main/claude/structured/claude-structured-real-cli.test.ts
|
||||
1 test-no-polling-loop:src/main/claude/structured/launch/claude-structured-real-cli-options.test.ts
|
||||
1 test-no-polling-loop:src/main/codex/hooks/reconciliation/codex-hook-toggle-off-wins.test.ts
|
||||
1 test-no-polling-loop:src/main/codex/structured/launch/codex-structured-real-cli-command-env.test.ts
|
||||
1 test-no-polling-loop:src/main/daemon/providers/adapter/testing/daemon-self-retirement-respawn.test.ts
|
||||
@@ -50,19 +40,17 @@
|
||||
2 test-no-polling-loop:src/main/ipc/worktrees/git/worktree-git-status-attribution-real-git.test.ts
|
||||
1 test-no-polling-loop:src/main/provider-process/provider-group-orphan-guard.integration.test.ts
|
||||
1 test-no-polling-loop:src/main/provider-process/provider-process-supervisor.integration.test.ts
|
||||
1 test-no-polling-loop:src/main/providers/startup-line-typed-length.live-shell.test.ts
|
||||
1 test-no-polling-loop:src/main/pty/history/terminal-history-fish-session.node-pty.test.ts
|
||||
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
|
||||
@@ -82,7 +70,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
|
||||
@@ -151,16 +138,6 @@
|
||||
1 test-no-real-time-wait:src/main/browser/identity/wire-probes/browser-session-ua-wire-identity.electron.test.ts
|
||||
1 test-no-real-time-wait:src/main/browser/network/tunnels/execution-route-socket-duplex.test.ts
|
||||
1 test-no-real-time-wait:src/main/browser/network/tunnels/remote-browser-socks-server.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/claude-agent-sdk-exit-proof.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/claude-agent-sdk-user-message-queue.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/claude-stop-ack-race.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/claude-stream-json-connection.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/claude-supervised-stop-real-cli.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/claude-supervised-stop.integration.test.ts
|
||||
2 test-no-real-time-wait:src/main/claude/structured/claude-structured-real-cli-fold.test.ts
|
||||
2 test-no-real-time-wait:src/main/claude/structured/claude-structured-real-cli-stop-task.test.ts
|
||||
5 test-no-real-time-wait:src/main/claude/structured/claude-structured-real-cli.test.ts
|
||||
1 test-no-real-time-wait:src/main/claude/structured/launch/claude-structured-real-cli-options.test.ts
|
||||
1 test-no-real-time-wait:src/main/codex/hooks/reconciliation/codex-hook-reconcile.test.ts
|
||||
1 test-no-real-time-wait:src/main/codex/hooks/reconciliation/codex-hook-toggle-off-wins.test.ts
|
||||
1 test-no-real-time-wait:src/main/codex/structured/launch/codex-structured-real-cli-command-env.test.ts
|
||||
@@ -185,10 +162,8 @@
|
||||
1 test-no-real-time-wait:src/main/daemon/shell-readiness/testing/terminal-shell-recovery-barrier.test.ts
|
||||
1 test-no-real-time-wait:src/main/daemon/terminal-host/testing/terminal-host.test.ts
|
||||
3 test-no-real-time-wait:src/main/daemon/transport/client/testing/client.test.ts
|
||||
1 test-no-real-time-wait:src/main/git/refs/maintenance/local-repo-ref-maintenance.test.ts
|
||||
4 test-no-real-time-wait:src/main/git/refs/maintenance/repo-ref-maintenance-real-git.test.ts
|
||||
1 test-no-real-time-wait:src/main/git/worktrees/removal/remove-worktree-branch-cleanup.test.ts
|
||||
1 test-no-real-time-wait:src/main/git/worktrees/removal/worktree-prune-guard.test.ts
|
||||
1 test-no-real-time-wait:src/main/grok/grok-hook-stdin-no-eof.test.ts
|
||||
4 test-no-real-time-wait:src/main/ipc/runtime-environments/runtime-environment-request-connections.test.ts
|
||||
1 test-no-real-time-wait:src/main/ipc/worktrees/creation/worktree-base-directory-poller-marker-fanout.test.ts
|
||||
@@ -199,39 +174,29 @@
|
||||
1 test-no-real-time-wait:src/main/native-chat/agent-session-wire/recovery/testing/structured-agent-session-target-reconcile.test.ts
|
||||
1 test-no-real-time-wait:src/main/provider-process/provider-group-orphan-guard.integration.test.ts
|
||||
2 test-no-real-time-wait:src/main/provider-process/provider-process-supervisor.integration.test.ts
|
||||
1 test-no-real-time-wait:src/main/providers/agent-foreground-process-git-bash.win32.test.ts
|
||||
2 test-no-real-time-wait:src/main/providers/startup-line-typed-length.live-shell.test.ts
|
||||
2 test-no-real-time-wait:src/main/pty/history/terminal-history-fish-session.node-pty.test.ts
|
||||
1 test-no-real-time-wait:src/main/pty/process-retirement/node-pty-master-fd-retirement.test.ts
|
||||
1 test-no-real-time-wait:src/main/pty/process-retirement/posix-pty-process-groups.integration.test.ts
|
||||
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
|
||||
3 test-no-real-time-wait:src/main/skills/install/skill-bundle-process-termination.integration.test.ts
|
||||
1 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
|
||||
3 test-no-real-time-wait:src/main/skills/install/skill-bundle-process-termination.integration.test.ts
|
||||
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
|
||||
@@ -258,8 +223,8 @@
|
||||
2 test-no-real-time-wait:src/shared/remote-runtime/control/remote-runtime-shared-control-auth-refusal.test.ts
|
||||
2 test-no-real-time-wait:src/shared/startup-command-staging.node-pty.test.ts
|
||||
4 test-no-real-time-wait:src/shared/terminal/pty/ingress/pty-reply-echo-shapes.node-pty.test.ts
|
||||
1 test-no-real-time-wait:src/wsl-guest/agent-hook-canonical-status.test.ts
|
||||
5 test-no-real-time-wait:src/wsl-guest/agent-hook-server.test.ts
|
||||
1 test-no-real-time-wait:src/wsl-guest/agent-hooks/status/agent-hook-canonical-status.test.ts
|
||||
5 test-no-real-time-wait:src/wsl-guest/agent-hooks/server/agent-hook-server.test.ts
|
||||
1 test-no-real-time-wait:tests/e2e/accumulated-workspace-status-fixture.ts
|
||||
1 test-no-real-time-wait:tests/e2e/agent-session-live-force-exit-resume.spec.ts
|
||||
3 test-no-real-time-wait:tests/e2e/agent-session-log-tail-stability.spec.ts
|
||||
@@ -272,8 +237,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
|
||||
3 test-no-real-time-wait:tests/e2e/cross-version-wire/host-model-catalog-released-phone.unit.test.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
|
||||
@@ -325,7 +288,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
|
||||
@@ -425,34 +388,23 @@
|
||||
4 test-no-wall-clock:src/cli/runtime/serve-bundle-park.test.ts
|
||||
5 test-no-wall-clock:src/cli/runtime/serve-pid1-init-reaper.test.ts
|
||||
1 test-no-wall-clock:src/cli/runtime/status-caller.test.ts
|
||||
2 test-no-wall-clock:src/main/agent-hooks/installation/commands/windows-native-hook-exe-hosts.test.ts
|
||||
2 test-no-wall-clock:src/main/agent-hooks/installation/scripts/managed-hook-timeout.test.ts
|
||||
1 test-no-wall-clock:src/main/agent-hooks/server/testing/http/server-endpoint-file-lifecycle.test.ts
|
||||
3 test-no-wall-clock:src/main/agent-hooks/server/testing/structured-ingress/server-ingest-structured-status.test.ts
|
||||
4 test-no-wall-clock:src/main/agent-hooks/server/testing/identity/server-launch-resume-identity.test.ts
|
||||
5 test-no-wall-clock:src/main/agent-hooks/server/testing/replay/spool.test.ts
|
||||
2 test-no-wall-clock:src/main/agent-hooks/installation/commands/windows-native-hook-exe-hosts.test.ts
|
||||
3 test-no-wall-clock:src/main/agent-hooks/server/testing/replay/spool.test.ts
|
||||
3 test-no-wall-clock:src/main/agent-hooks/server/testing/structured-ingress/server-ingest-structured-status.test.ts
|
||||
1 test-no-wall-clock:src/main/ai-vault/parse-cache/session-parse-cache-preview-upgrade.test.ts
|
||||
2 test-no-wall-clock:src/main/ai-vault/scanners/copilot/session-scanner-copilot-parser.test.ts
|
||||
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
|
||||
2 test-no-wall-clock:src/main/browser/cookies/browser-cookie-import-partition-reland.electron.test.ts
|
||||
2 test-no-wall-clock:src/main/browser/network/execution-routes/wsl-browser-network-execution-route.test.ts
|
||||
1 test-no-wall-clock:src/main/claude/claude-background-wake-retention.test.ts
|
||||
1 test-no-wall-clock:src/main/claude/claude-descendant-escalation-boundary.test.ts
|
||||
1 test-no-wall-clock:src/main/claude/claude-folder-trust-file.test.ts
|
||||
5 test-no-wall-clock:src/main/claude/claude-supervised-stop-real-cli.test.ts
|
||||
6 test-no-wall-clock:src/main/claude/claude-supervised-stop.integration.test.ts
|
||||
4 test-no-wall-clock:src/main/claude/structured/claude-structured-real-cli-fold.test.ts
|
||||
1 test-no-wall-clock:src/main/claude/structured/claude-structured-real-cli-rewind.test.ts
|
||||
3 test-no-wall-clock:src/main/claude/structured/claude-structured-real-cli-stop-task.test.ts
|
||||
10 test-no-wall-clock:src/main/claude/structured/claude-structured-real-cli.test.ts
|
||||
3 test-no-wall-clock:src/main/claude/structured/launch/claude-structured-real-cli-options.test.ts
|
||||
1 test-no-wall-clock:src/main/codex-accounts/legacy-wsl-runtime-auth-drain-rollback-script.test.ts
|
||||
1 test-no-wall-clock:src/main/codex-accounts/legacy-wsl-auth-drain/legacy-wsl-runtime-auth-drain-rollback-script.test.ts
|
||||
6 test-no-wall-clock:src/main/codex/app-server/codex-app-server-client.test.ts
|
||||
2 test-no-wall-clock:src/main/codex/structured/launch/codex-structured-real-cli-command-env.test.ts
|
||||
2 test-no-wall-clock:src/main/codex/trust/hooks/codex-hook-trust-derivation.test.ts
|
||||
@@ -468,28 +420,26 @@
|
||||
2 test-no-wall-clock:src/main/daemon/transport/framing/testing/daemon-stream-binary-negotiation.test.ts
|
||||
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/git-command-resolution-windows-checkout.test.ts
|
||||
1 test-no-wall-clock:src/main/git/gh-rate-limit-breaker.test.ts
|
||||
2 test-no-wall-clock:src/main/git/command-runner/host-routing/git-command-resolution-windows-checkout.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
|
||||
2 test-no-wall-clock:src/main/git/runner-gh-account-binding.test.ts
|
||||
1 test-no-wall-clock:src/main/git/source-control/blob-absence-real-git.test.ts
|
||||
1 test-no-wall-clock:src/main/git/source-control/diffs/blob-absence-real-git.test.ts
|
||||
2 test-no-wall-clock:src/main/git/worktrees/catalog/worktree-created-description-real-git.test.ts
|
||||
1 test-no-wall-clock:src/main/git/wsl-windows-checkout-git-probe.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
|
||||
5 test-no-wall-clock:src/main/ipc/worktrees-orphan-directory-cleanup.test.ts
|
||||
2 test-no-wall-clock:src/main/ipc/worktrees-orphan-directory-cleanup.test.ts
|
||||
2 test-no-wall-clock:src/main/ipc/worktrees/git/worktree-git-status-attribution-real-git.test.ts
|
||||
2 test-no-wall-clock:src/main/macos-permissions/macos-tcc-log-stream-orphans.test.ts
|
||||
4 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-fence-mutation-race.test.ts
|
||||
6 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-fence.test.ts
|
||||
4 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-install-lock.test.ts
|
||||
2 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-lock-windows.test.ts
|
||||
1 test-no-wall-clock:src/main/managed-server/host-state/orcad-windows-host-fence-ops.test.ts
|
||||
1 test-no-wall-clock:src/main/managed-server/install/remote-install-lock-exited-owner.test.ts
|
||||
3 test-no-wall-clock:src/main/managed-server/install/remote-node-runtime-store-gc.test.ts
|
||||
@@ -498,11 +448,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
|
||||
@@ -510,7 +460,6 @@
|
||||
1 test-no-wall-clock:src/main/persistence/tracking-repos/worktree-metadata-normalization.test.ts
|
||||
2 test-no-wall-clock:src/main/provider-process/provider-group-orphan-guard.integration.test.ts
|
||||
4 test-no-wall-clock:src/main/provider-process/provider-process-supervisor.integration.test.ts
|
||||
2 test-no-wall-clock:src/main/providers/startup-line-typed-length.live-shell.test.ts
|
||||
2 test-no-wall-clock:src/main/pty/history/terminal-history-fish-session.node-pty.test.ts
|
||||
1 test-no-wall-clock:src/main/pty/history/terminal-history-gc-fs-call-count.test.ts
|
||||
2 test-no-wall-clock:src/main/pty/process-retirement/posix-pty-process-groups.integration.test.ts
|
||||
@@ -518,30 +467,23 @@
|
||||
5 test-no-wall-clock:src/main/rate-limits/service/service-account-target-selection.test.ts
|
||||
1 test-no-wall-clock:src/main/rate-limits/service/service-cursor-usage.test.ts
|
||||
3 test-no-wall-clock:src/main/rate-limits/service/service-minimax-usage.test.ts
|
||||
3 test-no-wall-clock:src/main/rate-limits/service/service-refresh-orchestration.test.ts
|
||||
3 test-no-wall-clock:src/main/rate-limits/service/service-zcode-usage.test.ts
|
||||
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
|
||||
2 test-no-wall-clock:src/main/runtime/orchestration/workers/archives/worker-output-archive.test.ts
|
||||
3 test-no-wall-clock:src/main/runtime/orchestration/workers/archives/worker-terminal-archive-release.test.ts
|
||||
4 test-no-wall-clock:src/main/runtime/orchestration/workers/archives/worker-terminal-archive-screen.test.ts
|
||||
5 test-no-wall-clock:src/main/runtime/orchestration/workers/archives/worker-terminal-wrap-provenance.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/methods/orchestration/worker/observation/fleet-status-observed-identity.test.ts
|
||||
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/output/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
|
||||
@@ -550,29 +492,21 @@
|
||||
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
|
||||
6 test-no-wall-clock:src/main/skills/transaction-state/skill-install-lock.test.ts
|
||||
1 test-no-wall-clock:src/main/skills/install/skill-install-recovery.test.ts
|
||||
1 test-no-wall-clock:src/main/skills/install/skill-install-request.test.ts
|
||||
1 test-no-wall-clock:src/main/skills/provider-placement/skill-placement-reconciliation.test.ts
|
||||
1 test-no-wall-clock:src/main/skills/provider-placement/skill-placement-transaction.test.ts
|
||||
2 test-no-wall-clock:src/main/skills/transaction-state/skill-transaction-startup-recovery.test.ts
|
||||
4 test-no-wall-clock:src/main/skills/runtime-upload/skill-upload-process-restart.integration.test.ts
|
||||
6 test-no-wall-clock:src/main/skills/transaction-state/skill-install-lock.test.ts
|
||||
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/managed-server/activation/orcad-exited-own-fence-mutation-race.test.ts
|
||||
6 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-fence.test.ts
|
||||
4 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-install-lock.test.ts
|
||||
2 test-no-wall-clock:src/main/managed-server/activation/orcad-exited-own-lock-windows.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
|
||||
@@ -586,7 +520,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
|
||||
@@ -596,8 +529,8 @@
|
||||
2 test-no-wall-clock:src/renderer/src/components/workspace-cleanup/use-workspace-cleanup-removal.agent-stop.test.tsx
|
||||
1 test-no-wall-clock:src/renderer/src/components/worktree-jump/WorktreeJumpPalette.recent-tabs.behavior.test.tsx
|
||||
2 test-no-wall-clock:src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts
|
||||
1 test-no-wall-clock:src/renderer/src/lib/workspace/ownership/workspace-terminal-host-authority.test.ts
|
||||
1 test-no-wall-clock:src/renderer/src/lib/workspace/activation/worktree-activation-emptied-workspace-reseed.test.ts
|
||||
1 test-no-wall-clock:src/renderer/src/lib/workspace/ownership/workspace-terminal-host-authority.test.ts
|
||||
2 test-no-wall-clock:src/renderer/src/store/github/pull-requests/github-pr-branch-linked-pr-divergence.test.ts
|
||||
5 test-no-wall-clock:src/renderer/src/store/slices/worktrees/metadata/worktree-meta-hosted-review.test.ts
|
||||
1 test-no-wall-clock:src/shared/__tests__/process-host/windows-cmd-shim-resolution.test.ts
|
||||
@@ -608,18 +541,17 @@
|
||||
2 test-no-wall-clock:src/shared/startup-command-staging.node-pty.test.ts
|
||||
1 test-no-wall-clock:src/shared/startup-command-staging.test.ts
|
||||
2 test-no-wall-clock:src/shared/terminal/pty/ingress/pty-reply-echo-shapes.node-pty.test.ts
|
||||
10 test-no-wall-clock:src/wsl-guest/agent-hook-integration.test.ts
|
||||
1 test-no-wall-clock:src/wsl-guest/agent-hook-opencode-tui-identity.test.ts
|
||||
1 test-no-wall-clock:src/wsl-guest/agent-hook-server.test.ts
|
||||
1 test-no-wall-clock:src/wsl-guest/relay-idle-process-owner.test.ts
|
||||
10 test-no-wall-clock:src/wsl-guest/agent-hooks/publication/agent-hook-integration.test.ts
|
||||
1 test-no-wall-clock:src/wsl-guest/agent-hooks/server/agent-hook-server.test.ts
|
||||
1 test-no-wall-clock:src/wsl-guest/agent-hooks/presence/relay-idle-process-owner.test.ts
|
||||
1 test-no-wall-clock:tests/e2e/cross-version-wire/cross-version-web-device-key.unit.test.ts
|
||||
2 test-no-wall-clock:tests/e2e/cross-version-wire/legacy-relay-bridge.unit.test.ts
|
||||
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
|
||||
@@ -637,12 +569,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/claude-agent-sdk-exit-proof.test.ts
|
||||
1 test-timeout-needs-reason:src/main/claude/claude-background-run-isolation-real-cli.test.ts
|
||||
2 test-timeout-needs-reason:src/main/claude/claude-status-line-windows.test.ts
|
||||
1 test-timeout-needs-reason:src/main/claude/claude-stop-ack-race.test.ts
|
||||
1 test-timeout-needs-reason:src/main/claude/structured/claude-structured-real-cli.test.ts
|
||||
1 test-timeout-needs-reason:src/main/claude/windows-native-hook-registration.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
|
||||
@@ -671,34 +597,28 @@
|
||||
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
|
||||
@@ -738,7 +658,6 @@
|
||||
1 test-timeout-needs-reason:tests/e2e/daemon-generation-legacy-close-safety.spec.ts
|
||||
2 test-timeout-needs-reason:tests/e2e/daemon-generation-reconnect-safety.spec.ts
|
||||
2 test-timeout-needs-reason:tests/e2e/default-tabs-mark-crash-relaunch.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/desktop-published-split-orientation-legacy-leaf.spec.ts
|
||||
2 test-timeout-needs-reason:tests/e2e/desktop-window-mobile-markdown.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/floating-tab-rename.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/golden-quit-relaunch-session.spec.ts
|
||||
@@ -770,7 +689,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
|
||||
@@ -794,11 +713,6 @@
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-client-hosted-profile-cookies.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-client-project-removal-stops-host-terminals.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-cmd-j-host-qualified-tabs.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-desktop-server-held-pane-reopen.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-desktop-server-layout-frames.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-desktop-server-layout-intents.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-desktop-server-layout-writes.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-external-worktree-discovery.spec.ts
|
||||
2 test-timeout-needs-reason:tests/e2e/paired-host-usage-credentials.spec.ts
|
||||
7 test-timeout-needs-reason:tests/e2e/paired-instant-browser-tab.spec.ts
|
||||
1 test-timeout-needs-reason:tests/e2e/paired-mobile-desktop-mirror-files-git-review.spec.ts
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
2 src/renderer/src/App.tsx
|
||||
3 src/renderer/src/app-shell/ActiveWorkspaceSyncGate.tsx # existing watch selector now visible through useCallback; includes its import-path occurrence
|
||||
2 src/renderer/src/app-shell/use-app-shell-services.ts
|
||||
3 src/renderer/src/attention/unread/unread-badge-count-selector.ts
|
||||
1 src/renderer/src/component-fixtures/component-fixture-explorer.tsx # dev-only fixture page seeds default settings
|
||||
1 src/renderer/src/components/JiraIssueWorkspace.tsx
|
||||
1 src/renderer/src/components/Landing.tsx
|
||||
3 src/renderer/src/components/LinearIssueTextEditor.tsx
|
||||
3 src/renderer/src/components/LinearIssueWorkspace.tsx
|
||||
5 src/renderer/src/components/LinearItemDrawer.tsx
|
||||
1 src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.tsx
|
||||
1 src/renderer/src/components/activity/ActivityPrototypePage.tsx
|
||||
1 src/renderer/src/components/activity/activity-scope-filter-controls.tsx
|
||||
2 src/renderer/src/components/activity/activity-thread-actions.ts
|
||||
1 src/renderer/src/components/activity/use-agent-pane-thread-store-data.ts
|
||||
1 src/renderer/src/components/activity/use-agent-pane-threads.ts
|
||||
4 src/renderer/src/components/agent/AgentSettingsDialog.tsx
|
||||
1 src/renderer/src/components/automations/hosts/automation-host-client.ts
|
||||
1 src/renderer/src/components/automations/automations-page-fixtures.ts
|
||||
1 src/renderer/src/components/automations/use-automations-page-controller.ts
|
||||
1 src/renderer/src/components/automations/authoring/use-automations-page-destination-form.ts
|
||||
1 src/renderer/src/components/automations/automations-page-fixtures.ts
|
||||
1 src/renderer/src/components/automations/hosts/automation-host-client.ts
|
||||
1 src/renderer/src/components/automations/use-automations-page-controller.ts
|
||||
1 src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx
|
||||
1 src/renderer/src/components/browser-pane/browser-reopen-on-server.ts
|
||||
1 src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-page-stream.ts
|
||||
@@ -33,18 +33,16 @@
|
||||
1 src/renderer/src/components/editor/EditorEditFileSurface.tsx
|
||||
1 src/renderer/src/components/editor/EditorMarkdownFileSurface.tsx
|
||||
1 src/renderer/src/components/editor/csv/csv-link-routing.ts
|
||||
1 src/renderer/src/components/editor/markdown-preview/links/markdown-preview-link-actions.ts
|
||||
1 src/renderer/src/components/editor/markdown-preview/links/markdown-preview-system-link-action.ts
|
||||
2 src/renderer/src/components/editor/markdown/documents/useMarkdownDocuments.ts
|
||||
1 src/renderer/src/components/editor/restored-editor-workspace-runtime-owner.ts
|
||||
2 src/renderer/src/components/editor/useEditorPanelContentState.ts
|
||||
1 src/renderer/src/components/editor/useEditorPanelFileContentLoader.ts
|
||||
2 src/renderer/src/components/feature-wall/ConnectIntegrationsList.tsx
|
||||
1 src/renderer/src/components/feature-wall/FeatureWallBrowserAction.tsx
|
||||
1 src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx
|
||||
2 src/renderer/src/components/feature-wall/setup/integrations/ConnectIntegrationsList.tsx
|
||||
1 src/renderer/src/components/feature-wall/setup/browser/FeatureWallBrowserAction.tsx
|
||||
1 src/renderer/src/components/feature-wall/setup/FeatureWallSetupChecklist.tsx
|
||||
1 src/renderer/src/components/feature-wall/FeatureWallTourSurface.tsx
|
||||
2 src/renderer/src/components/feature-wall/use-feature-wall-task-source-presentation.ts
|
||||
2 src/renderer/src/components/feature-wall/use-integration-connection-status.ts
|
||||
2 src/renderer/src/components/feature-wall/setup/integrations/use-feature-wall-task-source-presentation.ts
|
||||
2 src/renderer/src/components/feature-wall/setup/integrations/use-integration-connection-status.ts
|
||||
1 src/renderer/src/components/floating-terminal/use-floating-terminal-panel-controller.ts
|
||||
1 src/renderer/src/components/floating-terminal/use-floating-terminal-panel-store-state.ts
|
||||
1 src/renderer/src/components/integrations/source-control/bitbucket-credentials-dialog.tsx
|
||||
@@ -64,6 +62,7 @@
|
||||
2 src/renderer/src/components/linear-item-drawer-comment-footer.tsx
|
||||
15 src/renderer/src/components/linear-item-drawer-edit-controller.tsx
|
||||
1 src/renderer/src/components/linear-item-drawer-edit-section.tsx
|
||||
1 src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.tsx
|
||||
1 src/renderer/src/components/native-chat/media/visuals/NativeChatVisualFrame.tsx
|
||||
1 src/renderer/src/components/new-workspace/use-jira-source-connection.ts
|
||||
7 src/renderer/src/components/new-workspace/use-linear-source-connection.ts
|
||||
@@ -175,7 +174,6 @@
|
||||
2 src/renderer/src/components/task-page/use-task-page-source-availability.ts
|
||||
2 src/renderer/src/components/task-page/use-task-page-store-bindings.ts
|
||||
1 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-controller.ts
|
||||
1 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-lifecycle.ts
|
||||
1 src/renderer/src/components/terminal-pane/layout/close/retire-unbound-runtime-terminal-pane.ts
|
||||
2 src/renderer/src/components/terminal-pane/layout/close/terminal-pane-close-admission.ts
|
||||
2 src/renderer/src/components/terminal-pane/layout/close/use-terminal-pane-close-actions.ts
|
||||
@@ -205,10 +203,10 @@
|
||||
1 src/renderer/src/hooks/ipc-events/settings-sidebar-ipc-bridge.ts
|
||||
1 src/renderer/src/hooks/ipc-events/terminal-request-ipc-bridge.ts
|
||||
1 src/renderer/src/hooks/skills/discovery/use-active-skill-discovery-runtime-target.ts
|
||||
2 src/renderer/src/hooks/use-integration-provider-status-refresh.ts
|
||||
1 src/renderer/src/hooks/skills/runtime/useActiveProjectSkillRuntime.ts
|
||||
1 src/renderer/src/hooks/useEditorExternalWatch.ts
|
||||
1 src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.ts
|
||||
1 src/renderer/src/hooks/skills/runtime/useActiveProjectSkillRuntime.ts
|
||||
2 src/renderer/src/hooks/use-integration-provider-status-refresh.ts
|
||||
1 src/renderer/src/hooks/useEditorExternalWatch.ts
|
||||
1 src/renderer/src/hooks/useIpcEvents.ts
|
||||
4 src/renderer/src/hooks/useIssueMetadata.ts
|
||||
2 src/renderer/src/hooks/useLinearIssuePublication.ts
|
||||
@@ -216,51 +214,53 @@
|
||||
7 src/renderer/src/hooks/useSettingsNavigationMetadata.ts
|
||||
1 src/renderer/src/hooks/useUnreadDockBadge.ts
|
||||
2 src/renderer/src/hooks/useWindowsTerminalCapabilityOwnerKey.ts
|
||||
1 src/renderer/src/lib/agent-session/resume/history/launch-ai-vault-session.ts
|
||||
6 src/renderer/src/lib/browser/navigation/http-link-destinations.ts
|
||||
1 src/renderer/src/lib/browser/navigation/http-link-localhost-route.ts # extracted from http-link-routing.ts
|
||||
1 src/renderer/src/lib/browser/navigation/http-link-routing.ts
|
||||
2 src/renderer/src/lib/browser/workspace/workspace-browser-tab-open.ts
|
||||
1 src/renderer/src/lib/workspace/creation/surface-policy/client-creation-action-error.ts
|
||||
2 src/renderer/src/lib/workspace/creation/surface-policy/client-creation-action-policy.ts
|
||||
1 src/renderer/src/runtime/hosts/selection/default-creation-host.ts # the sanctioned reader of the default host
|
||||
1 src/renderer/src/lib/agent-session/resume/history/launch-ai-vault-session.ts
|
||||
1 src/renderer/src/runtime/hosts/preflight/local-preflight-context.ts
|
||||
2 src/renderer/src/runtime/hosts/observations/provider-runtime-context.ts
|
||||
2 src/renderer/src/lib/workspace/ownership/repo-runtime-owner.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/runtime-session-mirror-owners.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/subscriptions/runtime-session-mirror-targets.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/single-runtime-legacy-owner.ts
|
||||
1 src/renderer/src/lib/skills/runtime/skill-runtime-command.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/terminal-close-route.ts
|
||||
4 src/renderer/src/lib/workspace/ownership/terminal-worktree-route.ts # resolveTerminalPtyOwner: teardown, close and PTY identity read spawn's attribution (round 3 F8), which retired the deprecated resolver's readers
|
||||
3 src/renderer/src/attention/unread/unread-badge-count-selector.ts
|
||||
1 src/renderer/src/runtime/hosted-terminals/restoration/web-runtime-worktree-terminal-after-wake.ts
|
||||
1 src/renderer/src/lib/work-items/linear/linear-issue-url-lookup.ts
|
||||
1 src/renderer/src/lib/work-items/linear/linear-issue-write.ts
|
||||
1 src/renderer/src/lib/workspace/activation/worktree-activation.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/worktree-runtime-owner.ts # a setup terminal with no row spawns on the focused runtime
|
||||
1 src/renderer/src/lib/workspace/activation/worktree-jump-navigation.ts
|
||||
1 src/renderer/src/lib/workspace/creation/surface-policy/client-creation-action-error.ts
|
||||
2 src/renderer/src/lib/workspace/creation/surface-policy/client-creation-action-policy.ts
|
||||
2 src/renderer/src/lib/workspace/ownership/repo-runtime-owner.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/runtime-session-mirror-owners.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/single-runtime-legacy-owner.ts
|
||||
1 src/renderer/src/lib/workspace/ownership/terminal-close-route.ts
|
||||
4 src/renderer/src/lib/workspace/ownership/terminal-worktree-route.ts # resolveTerminalPtyOwner: teardown, close and PTY identity read spawn's attribution (round 3 F8), which retired the deprecated resolver's readers
|
||||
1 src/renderer/src/lib/workspace/ownership/worktree-runtime-owner.ts # a setup terminal with no row spawns on the focused runtime
|
||||
2 src/renderer/src/runtime/browser/creation/web-runtime-browser-creation.ts
|
||||
1 src/renderer/src/runtime/hosted-terminals/creation/web-runtime-terminal-creation.ts
|
||||
1 src/renderer/src/runtime/hosted-terminals/restoration/web-runtime-worktree-terminal-after-wake.ts
|
||||
2 src/renderer/src/runtime/hosts/observations/provider-runtime-context.ts
|
||||
1 src/renderer/src/runtime/hosts/preflight/local-preflight-context.ts
|
||||
1 src/renderer/src/runtime/hosts/selection/default-creation-host.ts # the sanctioned reader of the default host
|
||||
17 src/renderer/src/runtime/jira/runtime-jira-client.ts
|
||||
2 src/renderer/src/runtime/jira/runtime-jira-summary-client.ts
|
||||
5 src/renderer/src/runtime/jira/runtime-jira-user-fields-client.ts
|
||||
8 src/renderer/src/runtime/linear/runtime-linear-client.ts
|
||||
6 src/renderer/src/runtime/linear/runtime-linear-issue-client.ts
|
||||
12 src/renderer/src/runtime/linear/runtime-linear-project-client.ts
|
||||
5 src/renderer/src/runtime/web-session-tabs-sync/subscriptions/use-runtime-session-mirror-environment-key.ts
|
||||
2 src/renderer/src/runtime/browser/creation/web-runtime-browser-creation.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/initial-terminal/web-runtime-initial-terminal-bootstrap-dispatch.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/actions/web-runtime-session-tab-lifecycle.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/actions/web-runtime-session-tab-move.ts
|
||||
1 src/renderer/src/runtime/hosted-terminals/creation/web-runtime-terminal-creation.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/actions/web-runtime-worktree-activation.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/active-session-subscription.ts
|
||||
2 src/renderer/src/runtime/web-session-tabs-sync/global-session-subscription.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/initial-terminal/web-runtime-initial-terminal-bootstrap-dispatch.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/subscriptions/runtime-session-mirror-targets.ts
|
||||
5 src/renderer/src/runtime/web-session-tabs-sync/subscriptions/use-runtime-session-mirror-environment-key.ts
|
||||
1 src/renderer/src/runtime/web-session-tabs-sync/tracking-decisions.ts
|
||||
3 src/renderer/src/runtime/web-session-tabs-sync/use-web-session-tabs-sync.ts
|
||||
9 src/renderer/src/store/index.ts
|
||||
2 src/renderer/src/store/project-groups/project-group-mutations.ts
|
||||
1 src/renderer/src/store/repos/all-host-repo-catalog-actions.ts
|
||||
3 src/renderer/src/store/repos/create-repo-slice.ts
|
||||
2 src/renderer/src/store/repos/repo-add-actions.ts
|
||||
1 src/renderer/src/store/repos/repo-host-identity.ts
|
||||
1 src/renderer/src/store/repos/repo-reorder-host-split.ts
|
||||
1 src/renderer/src/store/repos/repo-update.ts
|
||||
1 src/renderer/src/store/repos/runtime-repo-catalog-actions.ts
|
||||
1 src/renderer/src/store/slices/browser/create-browser-slice.ts
|
||||
@@ -272,7 +272,6 @@
|
||||
4 src/renderer/src/store/slices/browser/tabs/browser-tab-actions.ts
|
||||
1 src/renderer/src/store/slices/browser/tabs/paired-browser-tab-creator.ts
|
||||
1 src/renderer/src/store/slices/editor/create-editor-slice.ts
|
||||
1 src/renderer/src/store/slices/editor/markdown/markdown-link-action.ts
|
||||
1 src/renderer/src/store/slices/editor/markdown/markdown-preview-actions.ts
|
||||
1 src/renderer/src/store/slices/editor/opening/open-file-action.ts
|
||||
1 src/renderer/src/store/slices/editor/opening/open-file-apply.ts
|
||||
@@ -303,16 +302,12 @@
|
||||
6 src/renderer/src/store/slices/linear/linear-team-actions.ts
|
||||
11 src/renderer/src/store/slices/linear/linear.ts
|
||||
2 src/renderer/src/store/slices/preflight/preflight.ts
|
||||
1 src/renderer/src/store/repos/repo-host-identity.ts
|
||||
1 src/renderer/src/store/repos/repo-reorder-host-split.ts
|
||||
3 src/renderer/src/store/repos/create-repo-slice.ts
|
||||
1 src/renderer/src/store/slices/runtime-status/create-runtime-status-slice.ts
|
||||
2 src/renderer/src/store/slices/settings/settings-owner-hydration-publication.ts
|
||||
5 src/renderer/src/store/slices/settings/create-settings-slice.ts
|
||||
1 src/renderer/src/store/terminals/terminal-tab-retirement.ts
|
||||
2 src/renderer/src/store/slices/workspace-attention/workspace-unread-characterization-fixture.ts
|
||||
2 src/renderer/src/store/slices/settings/settings-owner-hydration-publication.ts
|
||||
1 src/renderer/src/store/slices/settings/worktree-visibility-owner-settings.ts
|
||||
3 src/renderer/src/store/slices/settings/worktree-visibility-settings-write.ts
|
||||
2 src/renderer/src/store/slices/workspace-attention/workspace-unread-characterization-fixture.ts
|
||||
5 src/renderer/src/store/slices/worktrees/create-worktree-slice.ts
|
||||
1 src/renderer/src/store/slices/worktrees/create/create-worktree.ts
|
||||
1 src/renderer/src/store/slices/worktrees/create/created-worktree-state-merge.ts
|
||||
@@ -326,6 +321,7 @@
|
||||
1 src/renderer/src/store/terminals/terminal-active-workspace-creation.ts
|
||||
1 src/renderer/src/store/terminals/terminal-tab-appearance.ts
|
||||
1 src/renderer/src/store/terminals/terminal-tab-close-providers.ts
|
||||
1 src/renderer/src/store/terminals/terminal-tab-retirement.ts
|
||||
1 src/renderer/src/web/preload-api/web-app-api.ts
|
||||
3 src/renderer/src/web/preload-api/web-preference-normalization.ts
|
||||
20 src/renderer/src/web/preload-api/web-preferences-store.ts
|
||||
|
||||
+3
-2
@@ -11,6 +11,7 @@
|
||||
"src/main/speech/stt-worker.ts",
|
||||
"src/main/warp-themes/warp-theme-parser-worker.ts",
|
||||
"src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts",
|
||||
"src/main/agent-hooks/identity/agent-identity-worker-entry.ts",
|
||||
"src/main/ports/port-scan-command-worker-entry.ts",
|
||||
"src/main/plugins/panels/plugin-source-transpile-worker-entry.ts",
|
||||
"src/main/worker-thread-child-process/worker-thread-child-process-entry.ts",
|
||||
@@ -41,7 +42,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 +58,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}",
|
||||
|
||||
@@ -0,0 +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/linux-display/ensure-virtual-display.ts
|
||||
1 src/shared/windows-retry-file-operations.ts
|
||||
1 src/shared/windows-transient-lock-removal.ts
|
||||
@@ -0,0 +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/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
|
||||
@@ -0,0 +1,3 @@
|
||||
# Legacy synchronous crypto uses per file; counts may only go DOWN.
|
||||
# Only explicit worker/utility entry files are exempt; their imports remain guarded.
|
||||
3 src/main/browser/cookies/sources/browser-cookie-key.ts
|
||||
@@ -0,0 +1,9 @@
|
||||
# Legacy synchronous ipc uses per file; counts may only go DOWN.
|
||||
# Only explicit worker/utility entry files are exempt; their imports remain guarded.
|
||||
3 src/main/host/native-chat-visual-guest-theme.ts
|
||||
1 src/main/ipc/renderer-shutdown-checkpoint.ts
|
||||
1 src/main/ipc/settings.ts
|
||||
1 src/preload/api/app-bridge.ts
|
||||
1 src/preload/api/native-chat-bridge.ts
|
||||
1 src/preload/api/settings-bridge.ts
|
||||
1 src/preload/native-chat-visual-guest-theme.ts
|
||||
@@ -0,0 +1,101 @@
|
||||
# Legacy synchronous sqlite uses per file; counts may only go DOWN.
|
||||
# Only explicit worker/utility entry files are exempt; their imports remain guarded.
|
||||
3 src/main/ai-vault-search/query/session-search-engine.ts
|
||||
6 src/main/ai-vault-search/query/session-search-postings.ts
|
||||
8 src/main/ai-vault-search/query/session-search-retrieval.ts
|
||||
2 src/main/ai-vault-search/query/session-search-snippet.ts
|
||||
2 src/main/ai-vault-search/query/session-search-source-presence.ts
|
||||
2 src/main/ai-vault-search/query/session-search-typo-repair.ts
|
||||
10 src/main/ai-vault-search/storage/session-search-file-records.ts
|
||||
8 src/main/ai-vault-search/storage/session-search-index-generation.ts
|
||||
17 src/main/ai-vault-search/storage/session-search-index-writer.ts
|
||||
10 src/main/ai-vault-search/storage/session-search-message-rows.ts
|
||||
3 src/main/ai-vault-search/storage/session-search-query-schema.ts
|
||||
17 src/main/ai-vault-search/storage/session-search-retention-delete.ts
|
||||
13 src/main/ai-vault-search/storage/session-search-schema.ts
|
||||
16 src/main/ai-vault-search/storage/session-search-store.ts
|
||||
26 src/main/ai-vault-search/storage/session-search-tool-purge.ts
|
||||
9 src/main/ai-vault-search/storage/session-search-write-buffer.ts
|
||||
19 src/main/ai-vault/parse-cache/session-parse-cache-database.ts
|
||||
13 src/main/ai-vault/parse-cache/session-scope-cwds-table.ts
|
||||
2 src/main/ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite-capture.ts
|
||||
4 src/main/ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite-list.ts
|
||||
1 src/main/ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite-open.ts
|
||||
6 src/main/ai-vault/scanners/opencode/sqlite/session-scanner-opencode-sqlite.ts
|
||||
5 src/main/ai-vault/scanners/opencode2/session-scanner-opencode2-sqlite-list.ts
|
||||
9 src/main/ai-vault/scanners/opencode2/session-scanner-opencode2-sqlite.ts
|
||||
5 src/main/browser-passwords/browser-password-chromium-import.ts
|
||||
1 src/main/browser/cookies/chromium/prepare-import.ts
|
||||
5 src/main/browser/cookies/sources/browser-cookie-firefox-import.ts
|
||||
25 src/main/browser/cookies/staging/browser-cookie-staged-import.ts
|
||||
8 src/main/codex/history/index/codex-state-db.ts
|
||||
3 src/main/foreign-sqlite-readers/readers/cursor-profile.ts
|
||||
44 src/main/github/pr-watch/pr-check-event-tables.ts
|
||||
14 src/main/github/pr-watch/pr-pending-actions.ts
|
||||
18 src/main/github/pr-watch/pr-watch-tables.ts
|
||||
6 src/main/managed-data-accounts/credential-capture.ts
|
||||
19 src/main/native-chat/agent-session-attachments/agent-session-attachment-claims.ts
|
||||
2 src/main/native-chat/agent-session-journal/database/journal-checkpoint-owner.ts
|
||||
13 src/main/native-chat/agent-session-journal/database/journal-database.ts
|
||||
1 src/main/native-chat/agent-session-journal/database/journal-host-database.ts
|
||||
12 src/main/native-chat/agent-session-journal/durable-rows/journal-row-table.ts
|
||||
18 src/main/native-chat/agent-session-journal/history-import/journal-pi-active-branch.ts
|
||||
15 src/main/native-chat/agent-session-journal/outcomes/journal-outcome-format.ts
|
||||
6 src/main/native-chat/agent-session-journal/outcomes/journal-outcome-table.ts
|
||||
6 src/main/native-chat/agent-session-journal/queued-messages/journal-queue-import.ts
|
||||
5 src/main/native-chat/agent-session-journal/queued-messages/queued-message-schema.ts
|
||||
10 src/main/native-chat/agent-session-journal/replay/journal-historical-inputs.ts
|
||||
4 src/main/native-chat/agent-session-journal/replay/journal-read-row.ts
|
||||
8 src/main/native-chat/agent-session-journal/replay/journal-retained-inputs.ts
|
||||
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-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
|
||||
8 src/main/persistence/profile-state/automation-runs/profile-state-automation-runs-migration.ts
|
||||
4 src/main/persistence/profile-state/automation-runs/profile-state-automation-runs-reader.ts
|
||||
6 src/main/persistence/profile-state/automation-runs/profile-state-automation-runs-storage.ts
|
||||
2 src/main/persistence/profile-state/automation-runs/profile-state-automation-runs-validation.ts
|
||||
8 src/main/persistence/profile-state/automation-runs/profile-state-automation-runs-writer.ts
|
||||
2 src/main/persistence/profile-state/automation-runs/profile-state-automation-runs.ts
|
||||
3 src/main/persistence/profile-state/database/profile-state-database-snapshot.ts
|
||||
4 src/main/persistence/profile-state/database/profile-state-database-validation.ts
|
||||
6 src/main/persistence/profile-state/database/profile-state-database.ts
|
||||
3 src/main/persistence/profile-state/database/profile-state-read-snapshot.ts
|
||||
4 src/main/persistence/profile-state/database/profile-state-revision.ts
|
||||
3 src/main/persistence/profile-state/database/profile-state-write-transaction.ts
|
||||
2 src/main/persistence/profile-state/document-parts/profile-state-document-parts-reader.ts
|
||||
25 src/main/persistence/profile-state/document-parts/profile-state-document-parts-storage.ts
|
||||
3 src/main/persistence/profile-state/document-parts/profile-state-document-parts.ts
|
||||
4 src/main/persistence/profile-state/document-parts/profile-state-legacy-document-refresh.ts
|
||||
2 src/main/persistence/profile-state/documents/profile-state-document-reader.ts
|
||||
7 src/main/persistence/profile-state/documents/profile-state-documents.ts
|
||||
8 src/main/persistence/profile-state/documents/profile-state-domain-writes.ts
|
||||
4 src/main/persistence/profile-state/legacy-json/profile-state-json-acceptance.ts
|
||||
2 src/main/persistence/profile-state/recovery/profile-state-section-repair.ts
|
||||
3 src/main/rate-limits/providers/opencode-go/opencode-go-api-key-source.ts
|
||||
2 src/main/runtime/agent-session/records/agent-session-record-lineage.ts
|
||||
34 src/main/runtime/agent-session/records/agent-session-record-rows.ts
|
||||
23 src/main/runtime/orchestration/db/dispatch-context/presentation-completion-source.ts
|
||||
6 src/main/runtime/orchestration/db/dispatch-row-writer.ts
|
||||
1 src/main/runtime/orchestration/db/federation/federated-stub-home-run-backfill.ts
|
||||
9 src/main/runtime/orchestration/db/lifecycle-transition.ts
|
||||
7 src/main/runtime/orchestration/db/mutation-receipts/mutation-receipt-capacity.ts
|
||||
5 src/main/runtime/orchestration/db/orchestration-db.ts
|
||||
4 src/main/runtime/orchestration/db/peer-runs/peer-run-queue-sql.ts
|
||||
10 src/main/runtime/orchestration/db/reset/removed-orchestration-work-read.ts
|
||||
6 src/main/runtime/orchestration/db/reset/removed-orchestration-work-store.ts
|
||||
4 src/main/runtime/orchestration/db/runs/run-coordinator-mail-routing.ts
|
||||
12 src/main/runtime/orchestration/db/schema/orchestration-schema-version-skew.ts
|
||||
15 src/main/runtime/orchestration/db/schema/structured-worker-orca-session-backfill.ts
|
||||
5 src/main/runtime/orchestration/db/worker-dispatch/worker-dispatch-settlement.ts
|
||||
4 src/main/runtime/orchestration/workers/lifecycle/context-only-dispatch-release.ts
|
||||
4 src/main/sqlite/node-sqlite-statement.ts
|
||||
19 src/main/sqlite/row-cache/keyed-json-row-cache.ts
|
||||
5 src/main/sqlite/sync-database.ts
|
||||
8 src/main/sqlite/versioned-cache-database.ts
|
||||
12 src/main/wsl/filesystem/wsl-drive-unc-conversion-stores.ts
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"src/main/plugins/panels/plugin-source-transpile-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/claude/transcripts/claude-conversation-fork-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/speech/stt-worker.ts": "Dedicated utility/forked-process entry; speech work stays off Electron main.",
|
||||
"src/main/warp-themes/warp-theme-parser-worker.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/ai-vault-search/worker/session-search-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/agent-hooks/identity/agent-identity-worker-entry.ts": "Dedicated worker-thread entry; terminal agent identity SQL stays off Electron main and orcad's loop.",
|
||||
"src/main/ports/port-scan-command-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/ipc/command-path/command-path-directory-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/worker-thread-child-process/worker-thread-child-process-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/usage/scan-worker/usage-scan-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/workspaces/space-analysis/workspace-space-scan-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/claude-accounts/profiles/setup/claude-profile-setup-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/persistence/profile-state/backup/profile-state-backup-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/persistence/profile-state/writer/profile-state-writer-worker-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/hang-watchdog/main-thread-hang-watchdog-entry.ts": "Dedicated worker-thread entry; blocking work stays off Electron main.",
|
||||
"src/main/browser/extensions/native-messaging/browser-extension-native-host-supervisor-entry.ts": "Dedicated utility/child-process entry; blocking work stays off Electron main.",
|
||||
"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/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
|
||||
|
||||
+125
-125
@@ -30,7 +30,7 @@
|
||||
7 src/main/antigravity/native-file-credentials.ts
|
||||
12 src/main/artifacts/artifact-create-intent-store.ts
|
||||
2 src/main/artifacts/artifact-share-record-store.ts
|
||||
1 src/main/automations/external-manager-discovery.ts
|
||||
1 src/main/automations/external-managers/external-manager-discovery.ts
|
||||
7 src/main/bitbucket/credential-store.ts
|
||||
1 src/main/browser-passwords/browser-password-chromium-import.ts
|
||||
2 src/main/browser-passwords/browser-password-vault-file.ts
|
||||
@@ -54,6 +54,7 @@
|
||||
5 src/main/browser/cookies/sources/browser-cookie-source-profiles.ts
|
||||
11 src/main/browser/cookies/staging/browser-session-cookie-staging.ts
|
||||
1 src/main/browser/downloads/browser-download-destination.ts
|
||||
2 src/main/browser/electron-fixtures/popup-identity-electron-probe.ts
|
||||
1 src/main/browser/extensions/api-runtime/browser-extension-worker-shim-source.ts
|
||||
8 src/main/browser/extensions/installation/browser-extension-install-store.ts
|
||||
1 src/main/browser/extensions/installation/browser-extension-load-directory.ts
|
||||
@@ -62,7 +63,6 @@
|
||||
4 src/main/browser/extensions/profile-storage/browser-extension-profile-state-store.ts
|
||||
3 src/main/browser/extensions/profile-storage/browser-extension-retired-profile-storage.ts
|
||||
1 src/main/browser/guest-lifecycle/popups/compatibility/browser-popup-background-cdp-probe.ts
|
||||
2 src/main/browser/electron-fixtures/popup-identity-electron-probe.ts
|
||||
1 src/main/browser/identity/mode/browser-identity-mode-record.ts
|
||||
2 src/main/browser/identity/mode/browser-identity-mode-store.ts
|
||||
2 src/main/browser/network/egress/browser-route-egress-electron-launch.ts
|
||||
@@ -72,15 +72,14 @@
|
||||
4 src/main/browser/routing/partitions/browser-route-partition-pending-deletes.ts
|
||||
4 src/main/browser/sessions/profiles/browser-session-meta-store.ts
|
||||
2 src/main/browser/site-settings/browser-profile-site-store.ts
|
||||
7 src/main/claude-accounts/sign-in/claude-account-folder.ts
|
||||
1 src/main/claude-accounts/profiles/routing/claude-observed-config-dirs.ts
|
||||
14 src/main/claude-accounts/profiles/setup/claude-profile-history.ts
|
||||
2 src/main/claude-accounts/profiles/claude-profile-homes.ts
|
||||
1 src/main/claude-accounts/profiles/routing/claude-profile-launch-preparation.ts
|
||||
7 src/main/claude-accounts/profiles/claude-profile-paths.ts
|
||||
1 src/main/claude-accounts/profiles/routing/claude-observed-config-dirs.ts
|
||||
1 src/main/claude-accounts/profiles/routing/claude-profile-launch-preparation.ts
|
||||
5 src/main/claude-accounts/profiles/routing/claude-profile-router.ts
|
||||
14 src/main/claude-accounts/profiles/setup/claude-profile-history.ts
|
||||
26 src/main/claude-accounts/profiles/setup/claude-profile-prompt-history.ts
|
||||
5 src/main/claude-accounts/profiles/setup/claude-profile-provisioning.ts
|
||||
5 src/main/claude-accounts/profiles/routing/claude-profile-router.ts
|
||||
1 src/main/claude-accounts/profiles/setup/claude-profile-settings.ts
|
||||
1 src/main/claude-accounts/profiles/setup/claude-profile-setup-worker.ts
|
||||
1 src/main/claude-accounts/profiles/setup/claude-profile-setup.ts
|
||||
@@ -88,15 +87,16 @@
|
||||
1 src/main/claude-accounts/profiles/wsl/claude-profile-wsl-router.ts
|
||||
2 src/main/claude-accounts/runtime-auth/keychain.ts
|
||||
1 src/main/claude-accounts/runtime-auth/runtime-paths.ts
|
||||
7 src/main/claude-accounts/sign-in/claude-account-folder.ts
|
||||
3 src/main/claude/agent-view/agent-view-install.ts
|
||||
1 src/main/claude/claude-diff-panel-auto-open.ts
|
||||
14 src/main/claude/claude-folder-trust-file.ts
|
||||
2 src/main/claude/claude-profile-hook-target.ts
|
||||
1 src/main/claude/claude-rejected-api-key.ts
|
||||
1 src/main/claude/claude-rewind-transcript.ts
|
||||
1 src/main/claude/configuration/claude-diff-panel-auto-open.ts
|
||||
14 src/main/claude/configuration/claude-folder-trust-file.ts
|
||||
2 src/main/claude/hooks/claude-profile-hook-target.ts
|
||||
1 src/main/claude/configuration/claude-rejected-api-key.ts
|
||||
1 src/main/claude/transcripts/claude-rewind-transcript.ts
|
||||
1 src/main/claude/hook-service.ts
|
||||
3 src/main/claude/statusline-install.ts
|
||||
6 src/main/claude/windows-hook-files.ts
|
||||
3 src/main/claude/hooks/statusline-install.ts
|
||||
6 src/main/claude/hooks/windows-hook-files.ts
|
||||
1 src/main/cli/appimage/appimage-copy-pre-extraction.ts
|
||||
3 src/main/cli/appimage/appimage-extracted-root.ts
|
||||
1 src/main/cli/appimage/appimage-extraction-pruning.ts
|
||||
@@ -104,34 +104,32 @@
|
||||
4 src/main/cli/appimage/appimage-generation-residency.ts
|
||||
16 src/main/cli/appimage/appimage-stable-launcher.ts
|
||||
1 src/main/cli/installation/cli-command-inspection.ts
|
||||
1 src/main/cli/launchers/cli-dev-launcher.ts
|
||||
3 src/main/cli/installation/cli-install-location.ts
|
||||
1 src/main/cli/launchers/cli-dev-launcher.ts
|
||||
2 src/main/cli/launchers/linux-bare-orca-dispatcher.ts
|
||||
7 src/main/cli/launchers/linux-terminal-orca-cli-shim.ts
|
||||
2 src/main/cli/wsl/wsl-managed-cli.ts
|
||||
2 src/main/codex-accounts/codex-account-identity.ts
|
||||
3 src/main/codex-accounts/codex-account-registration.ts
|
||||
4 src/main/codex-accounts/codex-config-mirror.ts
|
||||
1 src/main/codex-accounts/codex-credential-absence-grace.ts
|
||||
1 src/main/codex-accounts/codex-login-auth-snapshot.ts
|
||||
5 src/main/codex-accounts/codex-managed-home-lifecycle.ts
|
||||
6 src/main/codex-accounts/codex-managed-home-path.ts
|
||||
17 src/main/filesystem/atomic-file-publication.ts
|
||||
1 src/main/codex-accounts/guest-codex-account-homes.ts
|
||||
6 src/main/codex-accounts/host-codex-managed-home-ownership.ts
|
||||
2 src/main/codex-accounts/legacy-shared-auth-migration.ts
|
||||
21 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
|
||||
36 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-script-harness.ts
|
||||
6 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-script-interference-shims.ts
|
||||
1 src/main/codex-accounts/managed-codex-auth-readiness.ts
|
||||
7 src/main/codex-accounts/runtime-home-service-auth-core.ts
|
||||
5 src/main/codex-accounts/runtime-home-service-auth-provenance.ts
|
||||
23 src/main/codex-accounts/runtime-home-service-auth-sync.ts
|
||||
19 src/main/codex-accounts/runtime-home-service-legacy-migration.ts
|
||||
1 src/main/codex-accounts/runtime-home-service-managed-home.ts
|
||||
14 src/main/codex-accounts/runtime-home-service-paths.ts
|
||||
1 src/main/codex-accounts/runtime-home-service-sync.ts
|
||||
2 src/main/codex-accounts/runtime-home-service-wsl-core.ts
|
||||
2 src/main/codex-accounts/credentials/codex-account-identity.ts
|
||||
3 src/main/codex-accounts/account-management/codex-account-registration.ts
|
||||
4 src/main/codex-accounts/managed-homes/codex-config-mirror.ts
|
||||
1 src/main/codex-accounts/credentials/codex-credential-absence-grace.ts
|
||||
1 src/main/codex-accounts/login/codex-login-auth-snapshot.ts
|
||||
5 src/main/codex-accounts/managed-homes/codex-managed-home-lifecycle.ts
|
||||
6 src/main/codex-accounts/managed-homes/codex-managed-home-path.ts
|
||||
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
|
||||
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
|
||||
7 src/main/codex-accounts/runtime-home/credentials/runtime-home-service-auth-core.ts
|
||||
5 src/main/codex-accounts/runtime-home/credentials/runtime-home-service-auth-provenance.ts
|
||||
23 src/main/codex-accounts/runtime-home/credentials/runtime-home-service-auth-sync.ts
|
||||
19 src/main/codex-accounts/runtime-home/legacy-host/runtime-home-service-legacy-migration.ts
|
||||
1 src/main/codex-accounts/runtime-home/routing/runtime-home-service-managed-home.ts
|
||||
14 src/main/codex-accounts/runtime-home/runtime-home-service-paths.ts
|
||||
1 src/main/codex-accounts/runtime-home/runtime-home-service-sync.ts
|
||||
2 src/main/codex-accounts/runtime-home/credentials/runtime-home-service-wsl-core.ts
|
||||
1 src/main/codex-usage/codex-session-file-discovery.ts
|
||||
1 src/main/codex/configuration/codex-path-observation.ts
|
||||
21 src/main/codex/configuration/homes/codex-home-paths.ts
|
||||
@@ -246,24 +244,27 @@
|
||||
7 src/main/emulator/serve-sim-execution.ts
|
||||
17 src/main/emulator/serve-sim-runtime-materializer.ts
|
||||
12 src/main/external-editors/external-editor-launch.ts
|
||||
17 src/main/filesystem/atomic-file-publication.ts
|
||||
9 src/main/filesystem/durable-file-write.ts
|
||||
2 src/main/filesystem/rolling-file-backup.ts
|
||||
2 src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry-path.ts
|
||||
1 src/main/foreign-sqlite-readers/foreign-sqlite-reader-spawn.ts
|
||||
1 src/main/foreign-sqlite-readers/readers/cursor-profile.ts
|
||||
1 src/main/foreign-sqlite-readers/readers/opencode-binder-sessions.ts
|
||||
1 src/main/git/huge-folder-ignore.ts
|
||||
5 src/main/git/repo-detection.ts
|
||||
13 src/main/git/repo-git-marker-scan.ts
|
||||
1 src/main/git/source-control/ignores/huge-folder-ignore.ts
|
||||
5 src/main/git/repositories/discovery/repo-detection.ts
|
||||
13 src/main/git/repositories/discovery/repo-git-marker-scan.ts
|
||||
3 src/main/git/worktrees/catalog/worktree-git-path.ts
|
||||
1 src/main/github/client/check/check-job-log-cache-retention-child.mjs
|
||||
3 src/main/grok/grok-hook-backup-cleanup.ts
|
||||
3 src/main/grok/grok-hook-owners.ts
|
||||
4 src/main/grok/grok-hook-symlink-cleanup-marker.ts
|
||||
4 src/main/grok/hook-service.ts
|
||||
4 src/main/hang-watchdog/hang-detection-marker.ts
|
||||
8 src/main/hang-watchdog/hang-detection-marker.ts
|
||||
4 src/main/hang-watchdog/hang-incident-snapshot.ts
|
||||
1 src/main/hang-watchdog/hang-watchdog-worker-path.ts
|
||||
1 src/main/hang-watchdog/previous-hang-detection.ts
|
||||
9 src/main/hang-watchdog/renderer-hang-records.ts
|
||||
1 src/main/hermes/hermes-default-home.ts
|
||||
10 src/main/hermes/hermes-home-filesystem.ts
|
||||
2 src/main/hermes/hook-service.ts
|
||||
@@ -276,12 +277,12 @@
|
||||
2 src/main/ipc/filesystem/watcher-process/child-lifecycle/parcel-watcher-canary-directory.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/child-lifecycle/parcel-watcher-child-launch.ts
|
||||
4 src/main/ipc/filesystem/watcher-process/event-delivery/parcel-watcher-delivery-canary.ts
|
||||
3 src/main/ipc/filesystem/watcher-process/parcel-watcher-entry-path.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/native-subscriptions/parcel-watcher-shallow-file-subscription.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/native-subscriptions/parcel-watcher-shallow-subscription.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/supervision/parcel-watcher-supervisor-subscribe.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/native-subscriptions/rebinding-file-watch.ts
|
||||
3 src/main/ipc/filesystem/watcher-process/parcel-watcher-entry-path.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/root-paths/watcher-event-root-path-rewrite.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/supervision/parcel-watcher-supervisor-subscribe.ts
|
||||
1 src/main/ipc/floating-workspace-directory.ts
|
||||
1 src/main/ipc/macos-keyboard-layout-snapshot.ts
|
||||
1 src/main/ipc/notification-authorization-status.ts
|
||||
@@ -296,6 +297,7 @@
|
||||
13 src/main/kimi/hook-service.ts
|
||||
3 src/main/kiro/hook-service.ts
|
||||
1 src/main/kiro/standalone-hooks.ts
|
||||
3 src/main/lib/hidden-probe-cwd.ts
|
||||
4 src/main/linear/accounts/linear-credential-paths.ts
|
||||
3 src/main/linear/accounts/linear-legacy-viewer-store.ts
|
||||
1 src/main/linear/accounts/linear-token-store.ts
|
||||
@@ -314,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
|
||||
@@ -323,62 +325,62 @@
|
||||
1 src/main/observability/bounded-log-file-writer.ts
|
||||
2 src/main/observability/bundle.ts
|
||||
6 src/main/observability/local-file-sink.ts
|
||||
1 src/main/opencode/opencode-private-server.ts
|
||||
14 src/main/opencode/status-hooks/hook-service.ts
|
||||
1 src/main/opencode/status-hooks/legacy-plugin-acl-retry.ts
|
||||
2 src/main/opencode/status-hooks/opencode-overlay-manifest.ts
|
||||
1 src/main/opencode/status-hooks/opencode-plugin-config-writer.ts
|
||||
1 src/main/opencode/opencode-private-server.ts
|
||||
1 src/main/opencode/text-tasks/wsl/opencode-wsl-text-task.ts
|
||||
2 src/main/orca-profiles/storage/new-profile-state.ts
|
||||
3 src/main/orca-profiles/catalog/other-profile-workspace-catalog.ts
|
||||
3 src/main/orca-profiles/cloud-auth/profile-artifact-cloud-cleanup.ts
|
||||
1 src/main/orca-profiles/cloud-auth/profile-cloud-index.ts
|
||||
4 src/main/orca-profiles/cloud-session/profile-cloud-session-store.ts
|
||||
12 src/main/orca-profiles/profile-index-store.ts
|
||||
5 src/main/orca-profiles/storage/profile-legacy-state-import.ts
|
||||
4 src/main/orca-profiles/project-transfer/profile-project-move-intent.ts
|
||||
3 src/main/orca-profiles/project-transfer/profile-project-move-record.ts
|
||||
4 src/main/orca-profiles/project-transfer/profile-project-state-file.ts
|
||||
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
|
||||
9 src/main/persistence/migrating-orcad-catalog/cutover-journals/migration-journal-directory.ts
|
||||
4 src/main/persistence/migrating-orcad-catalog/blob-transfer/orcad-migration-blob-installers.ts
|
||||
3 src/main/persistence/migrating-orcad-catalog/blob-transfer/orcad-migration-blob-staging.ts
|
||||
17 src/main/persistence/migrating-orcad-catalog/blob-transfer/orcad-migration-staged-file.ts
|
||||
9 src/main/persistence/migrating-orcad-catalog/cutover-journals/migration-journal-directory.ts
|
||||
1 src/main/persistence/migrating-orcad-catalog/terminal-history/orcad-scrollback-snapshot-transfer.ts
|
||||
11 src/main/persistence/migrating-orcad-catalog/terminal-history/orcad-shell-history-installer.ts
|
||||
7 src/main/persistence/migrating-orcad-catalog/terminal-history/orcad-terminal-history-installer.ts
|
||||
@@ -388,33 +390,33 @@
|
||||
6 src/main/persistence/moving-wsl-projects/terminal-state/wsl-move-slept-scrollback.ts
|
||||
2 src/main/persistence/moving-wsl-projects/terminal-state/wsl-move-slept-spool-sweep.ts
|
||||
5 src/main/persistence/preferences/active-view-preference.ts
|
||||
5 src/main/persistence/profile-state/access/profile-state-access-identity.ts
|
||||
12 src/main/persistence/profile-state/access/profile-state-access-owner.ts
|
||||
3 src/main/persistence/profile-state/access/profile-state-access.ts
|
||||
2 src/main/persistence/profile-state/backup/profile-state-backup-path.ts
|
||||
2 src/main/persistence/profile-state/backup/profile-state-backup-worker.ts
|
||||
1 src/main/persistence/profile-state/database/profile-state-authority-marker.ts
|
||||
2 src/main/persistence/profile-state/database/profile-state-database.ts
|
||||
2 src/main/persistence/profile-state/database/profile-state-storage-classification.ts
|
||||
4 src/main/persistence/profile-state/legacy-json/profile-state-authority-exports.ts
|
||||
2 src/main/persistence/profile-state/legacy-json/profile-state-export-path.ts
|
||||
1 src/main/persistence/profile-state/legacy-json/profile-state-legacy-backup-path.ts
|
||||
2 src/main/persistence/profile-state/legacy-json/profile-state-legacy-import.ts
|
||||
8 src/main/persistence/profile-state/legacy-json/profile-state-recovery.ts
|
||||
6 src/main/persistence/profile-state/legacy-json/profile-state-versioned-export.ts
|
||||
5 src/main/persistence/profile-state/access/profile-state-access-identity.ts
|
||||
12 src/main/persistence/profile-state/access/profile-state-access-owner.ts
|
||||
3 src/main/persistence/profile-state/access/profile-state-access.ts
|
||||
2 src/main/persistence/profile-state/offline-access/profile-state-active-location.ts
|
||||
5 src/main/persistence/profile-state/startup/profile-state-authority-bootstrap.ts
|
||||
1 src/main/persistence/profile-state/database/profile-state-authority-marker.ts
|
||||
2 src/main/persistence/profile-state/backup/profile-state-backup-path.ts
|
||||
2 src/main/persistence/profile-state/backup/profile-state-backup-worker.ts
|
||||
6 src/main/persistence/profile-state/offline-access/profile-state-offline-settings.ts
|
||||
2 src/main/persistence/profile-state/profile-state-sqlite-authority.ts
|
||||
5 src/main/persistence/profile-state/recovery/profile-state-database-quarantine.ts
|
||||
9 src/main/persistence/profile-state/recovery/profile-state-database-recovery.ts
|
||||
2 src/main/persistence/profile-state/database/profile-state-database.ts
|
||||
4 src/main/persistence/profile-state/startup/profile-state-migration.ts
|
||||
6 src/main/persistence/profile-state/offline-access/profile-state-offline-settings.ts
|
||||
6 src/main/persistence/profile-state/recovery/profile-state-recovery-command.ts
|
||||
8 src/main/persistence/profile-state/recovery/profile-state-recovery-copy.ts
|
||||
2 src/main/persistence/profile-state/recovery/profile-state-recovery-crash-process.ts
|
||||
1 src/main/persistence/profile-state/recovery/profile-state-recovery-required.ts
|
||||
2 src/main/persistence/profile-state/recovery/profile-state-section-repair.ts
|
||||
2 src/main/persistence/profile-state/profile-state-sqlite-authority.ts
|
||||
5 src/main/persistence/profile-state/startup/profile-state-authority-bootstrap.ts
|
||||
4 src/main/persistence/profile-state/startup/profile-state-migration.ts
|
||||
1 src/main/persistence/profile-state/startup/profile-state-startup-recovery-dialog.ts
|
||||
2 src/main/persistence/profile-state/database/profile-state-storage-classification.ts
|
||||
1 src/main/persistence/profile-state/writer/profile-state-writer-worker-path.ts
|
||||
2 src/main/persistence/restoring-sessions/floating-workspace-normalization.ts
|
||||
1 src/main/persistence/tracking-repos/worktree-metadata-normalization.ts
|
||||
@@ -430,8 +432,8 @@
|
||||
3 src/main/plugins/plugin-secrets-store.ts
|
||||
3 src/main/plugins/plugin-storage-store.ts
|
||||
1 src/main/plugins/testing/plugin-editor-static-check.ts
|
||||
1 src/main/plugins/workers/parent-process/plugin-host-process.ts
|
||||
3 src/main/plugins/workers/child-runtime/plugin-worker-source-hooks.ts
|
||||
1 src/main/plugins/workers/parent-process/plugin-host-process.ts
|
||||
8 src/main/ports/darwin-port-scan-lock.ts
|
||||
1 src/main/ports/port-scan-command-client.ts
|
||||
1 src/main/ports/port-scan-command-execution.ts
|
||||
@@ -439,18 +441,17 @@
|
||||
5 src/main/providers/local-pty/local-pty-executable-spawn.ts
|
||||
1 src/main/providers/local-pty/local-pty-session-operations.ts
|
||||
1 src/main/providers/local-pty/local-pty-shell-ready-wrapper-root.ts
|
||||
1 src/main/providers/macos-login-session-pty-probe.ts
|
||||
3 src/main/providers/macos-tcc-login-shell.ts
|
||||
3 src/main/providers/windows-powershell-executable.ts
|
||||
2 src/main/providers/working-directory-validation.ts
|
||||
1 src/main/providers/shell-launch/macos/login-session-pty-probe.ts
|
||||
3 src/main/providers/shell-launch/macos/tcc-login-shell.ts
|
||||
3 src/main/providers/shell-launch/windows/powershell-executable.ts
|
||||
2 src/main/providers/working-directory/validation.ts
|
||||
4 src/main/pty/history/fish-history-session.ts
|
||||
4 src/main/pty/history/terminal-history-deletion.ts
|
||||
2 src/main/pty/history/terminal-history-paths.ts
|
||||
5 src/main/pty/history/terminal-history.ts
|
||||
1 src/main/pty/process-retirement/posix-pty-process-table.ts
|
||||
12 src/main/pty/scrollback/overlay-mirror.ts
|
||||
1 src/main/pty/scrollback/terminal-scrollback-snapshot-nonblocking-reads.ts
|
||||
11 src/main/pty/scrollback/terminal-scrollback-snapshots.ts
|
||||
7 src/main/pty/scrollback/terminal-scrollback-snapshots.ts
|
||||
2 src/main/pty/shell-startup/codex-shell-launch-preflight.ts
|
||||
7 src/main/pty/shell-startup/git-bash.ts
|
||||
3 src/main/pty/shell-startup/legacy-terminal-posix-tombstone.ts
|
||||
@@ -464,7 +465,6 @@
|
||||
1 src/main/qoder/workspace-trust.ts
|
||||
4 src/main/rate-limits/providers/cursor/cursor-auth.ts
|
||||
2 src/main/rate-limits/providers/grok/grok-auth.ts
|
||||
3 src/main/lib/hidden-probe-cwd.ts
|
||||
2 src/main/rate-limits/providers/opencode-go/opencode-go-api-key-source.ts
|
||||
1 src/main/rate-limits/providers/zcode/zcode-usage-credentials.ts
|
||||
3 src/main/ripgrep/bundled-ripgrep-path.ts
|
||||
@@ -475,8 +475,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
|
||||
@@ -487,8 +487,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
|
||||
@@ -514,26 +514,26 @@
|
||||
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
|
||||
2 src/main/text-generation/prompts/source-control-prompt-files.ts
|
||||
1 src/main/updater/lifecycle/windows-update-install-directory.ts
|
||||
9 src/main/updater/linux-package/appimage-runtime-identity.ts
|
||||
1 src/main/updater/linux-package/linux-package-install-command.ts
|
||||
|
||||
@@ -9,40 +9,36 @@
|
||||
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
|
||||
3 src/main/filesystem/atomic-file-publication.ts
|
||||
2 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-recovery-script-harness.ts
|
||||
2 src/main/codex-accounts/legacy-wsl-runtime-auth-drain-script-harness.ts
|
||||
1 src/main/codex-accounts/runtime-home-service-home-routing.ts
|
||||
1 src/main/codex-accounts/runtime-home-service-wsl.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
|
||||
1 src/main/codex-accounts/service.ts
|
||||
1 src/main/computer/macos-permissions/macos-computer-use-permissions.ts
|
||||
2 src/main/daemon/process-scopes/daemon-cgroup-scope.ts
|
||||
1 src/main/daemon/endpoint/identity/daemon-process-identity-query.ts
|
||||
1 src/main/daemon/endpoint/identity/daemon-process-start-time.ts
|
||||
2 src/main/daemon/process-scopes/daemon-cgroup-scope.ts
|
||||
1 src/main/daemon/process-scopes/daemon-user-manager-lifetime.ts
|
||||
1 src/main/daemon/pty-subprocess/shell-launch-plan.ts
|
||||
1 src/main/daemon/process-scopes/terminal-scopes.ts
|
||||
1 src/main/daemon/pty-subprocess/shell-launch-plan.ts
|
||||
1 src/main/emulator/serve-sim-runtime-materializer.ts
|
||||
3 src/main/filesystem/atomic-file-publication.ts
|
||||
1 src/main/git/command-runner/git-exec-file-sync.ts
|
||||
1 src/main/git/repo-detection-command.ts
|
||||
1 src/main/git/runner.ts
|
||||
1 src/main/git/repositories/discovery/repo-detection-command.ts
|
||||
1 src/main/ipc/worktree-logic.ts
|
||||
2 src/main/window/desktop-integration/macos-press-and-hold-default.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
|
||||
1 src/main/pty/process-retirement/posix-pty-process-table.ts
|
||||
1 src/main/pty/shell-startup/pwsh.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
|
||||
1 src/main/workspace-hooks/worktree-runner-script.ts
|
||||
3 src/main/wsl/discovery/distro-catalog.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/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
|
||||
3 src/main/wsl/discovery/distro-catalog.ts
|
||||
1 src/shared/fish-binary-requirement.ts
|
||||
1 src/shared/openssh-default-shell.ts
|
||||
1 src/shared/secure-storage/secure-file.ts
|
||||
|
||||
@@ -7,35 +7,35 @@
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/browser/MobileBrowserKeyRow.tsx",
|
||||
"filePath": "mobile/src/browser/pane-view/MobileBrowserKeyRow.tsx",
|
||||
"kind": "jsx-expression",
|
||||
"text": "Esc",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/browser/MobileBrowserPointerModifiers.tsx",
|
||||
"filePath": "mobile/src/browser/pane-view/MobileBrowserPointerModifiers.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "Alt",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/browser/MobileBrowserPointerModifiers.tsx",
|
||||
"filePath": "mobile/src/browser/pane-view/MobileBrowserPointerModifiers.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "Cmd",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/browser/MobileBrowserPointerModifiers.tsx",
|
||||
"filePath": "mobile/src/browser/pane-view/MobileBrowserPointerModifiers.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "Ctrl",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/browser/MobileBrowserPointerModifiers.tsx",
|
||||
"filePath": "mobile/src/browser/pane-view/MobileBrowserPointerModifiers.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "Shift",
|
||||
"dynamic": false,
|
||||
@@ -289,7 +289,7 @@
|
||||
{
|
||||
"filePath": "mobile/src/settings/about-screen.tsx",
|
||||
"kind": "jsx-attribute:title",
|
||||
"text": "onOrca.dev",
|
||||
"text": "orca.dev",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
@@ -329,70 +329,70 @@
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-smart-source-modes.ts",
|
||||
"filePath": "mobile/src/new-workspace/source-selection/mobile-smart-source-modes.ts",
|
||||
"kind": "object-property:label",
|
||||
"text": "GitHub",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-smart-source-modes.ts",
|
||||
"filePath": "mobile/src/new-workspace/source-selection/mobile-smart-source-modes.ts",
|
||||
"kind": "object-property:label",
|
||||
"text": "GitLab",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-smart-source-modes.ts",
|
||||
"filePath": "mobile/src/new-workspace/source-selection/mobile-smart-source-modes.ts",
|
||||
"kind": "object-property:label",
|
||||
"text": "Jira",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-smart-source-modes.ts",
|
||||
"filePath": "mobile/src/new-workspace/source-selection/mobile-smart-source-modes.ts",
|
||||
"kind": "object-property:label",
|
||||
"text": "Linear",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-tasks-create-drawers.tsx",
|
||||
"filePath": "mobile/src/tasks/workspace-creation/mobile-tasks-create-drawers.tsx",
|
||||
"kind": "jsx-attribute:placeholder",
|
||||
"text": "lin_api_...",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-tasks-options.tsx",
|
||||
"filePath": "mobile/src/tasks/task-list/mobile-tasks-options.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "GitHub",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-tasks-options.tsx",
|
||||
"filePath": "mobile/src/tasks/task-list/mobile-tasks-options.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "GitLab",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-tasks-options.tsx",
|
||||
"filePath": "mobile/src/tasks/task-list/mobile-tasks-options.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "Jira",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-tasks-options.tsx",
|
||||
"filePath": "mobile/src/tasks/task-list/mobile-tasks-options.tsx",
|
||||
"kind": "object-property:label",
|
||||
"text": "Linear",
|
||||
"dynamic": false,
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"filePath": "mobile/src/tasks/mobile-tasks-workspace-trust-drawers.tsx",
|
||||
"filePath": "mobile/src/tasks/workspace-creation/mobile-tasks-workspace-trust-drawers.tsx",
|
||||
"kind": "jsx-attribute:placeholder",
|
||||
"text": "src/renderer packages/ui",
|
||||
"dynamic": false,
|
||||
|
||||
@@ -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,18 +1,17 @@
|
||||
|
||||
# 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
|
||||
src/main/window/desktop-integration/app-icon.ts
|
||||
src/main/automations/precheck-runner.ts
|
||||
src/main/automations/dispatch/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-runtime-auth-drain-recovery-script-harness.ts
|
||||
src/main/codex-accounts/legacy-wsl-runtime-auth-drain-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
|
||||
src/main/computer/macos-permissions/macos-computer-use-permission-status.ts
|
||||
@@ -28,15 +27,15 @@ src/main/emulator/serve-sim-runtime-materializer.ts
|
||||
src/main/emulator/simctl-simulator-devices.ts
|
||||
src/main/emulator/simulator-app-visibility.ts
|
||||
src/main/external-editors/external-editor-launch.ts
|
||||
src/main/git/command-runner/exec-file-capture.ts
|
||||
src/main/git/command-runner/process-capture/exec-file-capture.ts
|
||||
src/main/git/command-runner/git-exec-file-sync.ts
|
||||
src/main/git/command-runner/git-spawn.ts
|
||||
src/main/git/command-runner/git-stream-stdout.ts
|
||||
src/main/git/command-runner/spawn-command-capture.ts
|
||||
src/main/git/command-runner/spawned-command-tree-kill.ts
|
||||
src/main/git/command-runner/wsl-aware-spawn.ts
|
||||
src/main/git/wsl-git-read-environment.ts
|
||||
src/main/github/gh-utils.ts
|
||||
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/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,14 +50,13 @@ 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/providers/working-directory/process-cwd.ts
|
||||
src/main/pty/process-retirement/pty-descendant-fresh-table.ts
|
||||
src/main/rate-limits/providers/gemini/gemini-cli-oauth-extractor.ts
|
||||
src/main/runtime/rpc/server/pairing/network/windows-default-route-interfaces.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
|
||||
@@ -75,4 +73,4 @@ src/shared/fish-binary-requirement.ts
|
||||
src/shared/process-table-ps-capture.ts
|
||||
src/shared/terminal/pty/native-process/pty-slave-line-discipline-echo.ts
|
||||
src/shared/terminal/pty/native-process/shell-process-readiness.ts
|
||||
src/wsl-guest/preflight-handler.ts
|
||||
src/wsl-guest/commands/preflight-handler.ts
|
||||
|
||||
@@ -28,27 +28,27 @@
|
||||
1 src/renderer/src/components/artifacts/ArtifactPublishButton.tsx
|
||||
2 src/renderer/src/components/artifacts/ArtifactsPage.tsx
|
||||
2 src/renderer/src/components/artifacts/useArtifactPagination.ts
|
||||
1 src/renderer/src/components/automations/AutomationsPageSurface.tsx
|
||||
2 src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.tsx
|
||||
1 src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx
|
||||
1 src/renderer/src/components/automations/authoring/AutomationProjectCombobox.tsx
|
||||
1 src/renderer/src/components/automations/authoring/use-automation-agent-picker-entries.ts
|
||||
2 src/renderer/src/components/automations/authoring/use-automation-create-destination.ts
|
||||
2 src/renderer/src/components/automations/authoring/use-automation-migrate-action.ts
|
||||
1 src/renderer/src/components/automations/authoring/use-automations-page-setup-state.ts
|
||||
1 src/renderer/src/components/automations/external/use-external-automation-scope-retention.ts
|
||||
2 src/renderer/src/components/automations/external/use-scoped-external-automations.ts
|
||||
1 src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx
|
||||
4 src/renderer/src/components/automations/hosts/use-automation-host-catalog.ts
|
||||
4 src/renderer/src/components/automations/listing/use-automation-list-search.ts
|
||||
1 src/renderer/src/components/automations/listing/use-automations-page-list-state.ts
|
||||
1 src/renderer/src/components/automations/runs/AutomationRunHistory.tsx
|
||||
1 src/renderer/src/components/automations/runs/AutomationRunsDashboard.tsx
|
||||
1 src/renderer/src/components/automations/runs/AutomationRunsTable.tsx
|
||||
1 src/renderer/src/components/automations/AutomationsPageSurface.tsx
|
||||
1 src/renderer/src/components/automations/authoring/use-automation-agent-picker-entries.ts
|
||||
2 src/renderer/src/components/automations/authoring/use-automation-create-destination.ts
|
||||
4 src/renderer/src/components/automations/hosts/use-automation-host-catalog.ts
|
||||
4 src/renderer/src/components/automations/listing/use-automation-list-search.ts
|
||||
2 src/renderer/src/components/automations/authoring/use-automation-migrate-action.ts
|
||||
1 src/renderer/src/components/automations/runs/use-automation-runs-dashboard.ts
|
||||
1 src/renderer/src/components/automations/listing/use-automations-page-list-state.ts
|
||||
16 src/renderer/src/components/automations/runs/use-selected-automation-run-history.ts
|
||||
1 src/renderer/src/components/automations/use-automations-page-local-state.ts
|
||||
1 src/renderer/src/components/automations/use-automations-page-refresh.ts
|
||||
1 src/renderer/src/components/automations/authoring/use-automations-page-setup-state.ts
|
||||
1 src/renderer/src/components/automations/use-automations-page-store-state.ts
|
||||
1 src/renderer/src/components/automations/external/use-external-automation-scope-retention.ts
|
||||
2 src/renderer/src/components/automations/external/use-scoped-external-automations.ts
|
||||
16 src/renderer/src/components/automations/runs/use-selected-automation-run-history.ts
|
||||
7 src/renderer/src/components/browser-extension-install-dialog.tsx
|
||||
1 src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.tsx
|
||||
2 src/renderer/src/components/browser-pane/annotate/MarkupOverlay.tsx
|
||||
@@ -99,11 +99,11 @@
|
||||
1 src/renderer/src/components/editor/combined-diff/resolve-changes/use-combined-diff-section-row-keys.ts
|
||||
1 src/renderer/src/components/editor/combined-diff/review-controls/use-combined-diff-notes-actions.ts
|
||||
1 src/renderer/src/components/editor/combined-diff/scroll-viewport/use-combined-diff-virtualizer.ts
|
||||
1 src/renderer/src/components/editor/csv/grid/CsvGrid.tsx
|
||||
1 src/renderer/src/components/editor/csv/CsvViewer.tsx
|
||||
1 src/renderer/src/components/editor/csv/editing/clipboard/useCsvClipboard.ts
|
||||
1 src/renderer/src/components/editor/csv/preview/useCsvPagedPreview.ts
|
||||
3 src/renderer/src/components/editor/csv/editing/useCsvTableEditor.ts
|
||||
1 src/renderer/src/components/editor/csv/grid/CsvGrid.tsx
|
||||
1 src/renderer/src/components/editor/csv/preview/useCsvPagedPreview.ts
|
||||
1 src/renderer/src/components/editor/diff/large-files/useDiffViewerLargeDiffLifecycle.ts
|
||||
3 src/renderer/src/components/editor/diff/monaco-view/DiffViewer.tsx
|
||||
1 src/renderer/src/components/editor/diff/review/DiffNotesSendMenu.tsx
|
||||
@@ -150,19 +150,19 @@
|
||||
1 src/renderer/src/components/emulator-pane/use-emulator-video-stream.ts
|
||||
1 src/renderer/src/components/emulator-pane/use-mobile-emulator-agent-setup-state.ts
|
||||
1 src/renderer/src/components/feature-tips/FeatureTipsModal.tsx
|
||||
1 src/renderer/src/components/feature-wall/AgentCapabilitiesSetupAction.tsx
|
||||
1 src/renderer/src/components/feature-wall/BrowserPricingStoryboard.tsx
|
||||
1 src/renderer/src/components/feature-wall/BrowserStoryboardWindow.tsx
|
||||
2 src/renderer/src/components/feature-wall/EditorAnimatedVisual.tsx
|
||||
1 src/renderer/src/components/feature-wall/FeatureWallBrowserAction.tsx
|
||||
2 src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx
|
||||
1 src/renderer/src/components/feature-wall/setup/agent-capabilities/AgentCapabilitiesSetupAction.tsx
|
||||
1 src/renderer/src/components/feature-wall/browser-storyboard/BrowserPricingStoryboard.tsx
|
||||
1 src/renderer/src/components/feature-wall/browser-storyboard/BrowserStoryboardWindow.tsx
|
||||
2 src/renderer/src/components/feature-wall/editor-storyboard/EditorAnimatedVisual.tsx
|
||||
1 src/renderer/src/components/feature-wall/setup/browser/FeatureWallBrowserAction.tsx
|
||||
2 src/renderer/src/components/feature-wall/setup/FeatureWallSetupChecklist.tsx
|
||||
1 src/renderer/src/components/feature-wall/FeatureWallTourSurface.tsx
|
||||
1 src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.tsx
|
||||
1 src/renderer/src/components/feature-wall/WorkbenchTerminalSplitMenu.tsx
|
||||
1 src/renderer/src/components/feature-wall/setup/FullDiskAccessSetupPrompt.tsx
|
||||
1 src/renderer/src/components/feature-wall/workbench-storyboard/WorkbenchTerminalSplitMenu.tsx
|
||||
1 src/renderer/src/components/feature-wall/agents-orchestration/OrchestrationPage.tsx
|
||||
2 src/renderer/src/components/feature-wall/agents-orchestration/UsageAccountsCard.tsx
|
||||
2 src/renderer/src/components/feature-wall/use-feature-wall-tour-telemetry.ts
|
||||
3 src/renderer/src/components/feature-wall/useAiCommitPrSettings.ts
|
||||
2 src/renderer/src/components/feature-wall/tour/use-feature-wall-tour-telemetry.ts
|
||||
3 src/renderer/src/components/feature-wall/commit-pr-settings/useAiCommitPrSettings.ts
|
||||
1 src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx
|
||||
3 src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx
|
||||
1 src/renderer/src/components/floating-terminal/use-floating-terminal-panel-geometry.ts
|
||||
@@ -182,7 +182,7 @@
|
||||
1 src/renderer/src/components/github-project/useProjectPickerBrowse.ts
|
||||
3 src/renderer/src/components/github-project/useProjectViewTable.ts
|
||||
1 src/renderer/src/components/github/CommentReactions.tsx
|
||||
8 src/renderer/src/components/github/GitHubMarkdownComposer.tsx
|
||||
4 src/renderer/src/components/github/GitHubMarkdownComposer.tsx
|
||||
2 src/renderer/src/components/github/github-duplicate-issue-candidates.ts
|
||||
1 src/renderer/src/components/github/github-rate-limit-display.tsx
|
||||
2 src/renderer/src/components/github/use-github-image-uploads.ts
|
||||
@@ -206,7 +206,6 @@
|
||||
2 src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts
|
||||
1 src/renderer/src/components/mobile/use-mobile-pairing-generation.ts
|
||||
1 src/renderer/src/components/native-chat-recovery/offers/native-chat-restart-runtimes.ts
|
||||
1 src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
|
||||
8 src/renderer/src/components/native-chat/attachments/chips/use-native-chat-composer-attachments.ts
|
||||
2 src/renderer/src/components/native-chat/attachments/intake/use-native-chat-resolved-path-attachments.ts
|
||||
2 src/renderer/src/components/native-chat/composer/controls/NativeChatComposerToolbar.tsx
|
||||
@@ -227,7 +226,6 @@
|
||||
6 src/renderer/src/components/native-chat/messages/actions/use-native-chat-fork.ts
|
||||
4 src/renderer/src/components/native-chat/messages/agent-senders/native-chat-agent-message-sender-source.ts
|
||||
6 src/renderer/src/components/native-chat/messages/notices/use-structured-agent-session-start-failure-facts.ts
|
||||
1 src/renderer/src/components/native-chat/pane/actions/NativeChatCopyOrcaSessionIdMenuItem.tsx
|
||||
1 src/renderer/src/components/native-chat/pane/actions/use-native-chat-hand-off.tsx
|
||||
1 src/renderer/src/components/native-chat/pane/actions/use-native-chat-session-context-actions.tsx
|
||||
1 src/renderer/src/components/native-chat/paste/native-chat-paste-destination.ts
|
||||
@@ -252,6 +250,7 @@
|
||||
2 src/renderer/src/components/native-chat/session/terminal/read/use-native-chat-live-session.ts
|
||||
3 src/renderer/src/components/native-chat/session/terminal/read/use-native-chat-retained-session.ts
|
||||
1 src/renderer/src/components/native-chat/tools/lines/NativeChatToolOutput.tsx
|
||||
1 src/renderer/src/components/native-chat/tools/runs/use-native-chat-tool-run-window.ts
|
||||
1 src/renderer/src/components/native-chat/usage/NativeChatAccountUsageCard.tsx
|
||||
2 src/renderer/src/components/native-chat/usage/use-native-chat-account-usage.ts
|
||||
1 src/renderer/src/components/native-chat/viewport/find/use-native-chat-find.ts
|
||||
@@ -541,16 +540,16 @@
|
||||
4 src/renderer/src/components/sidebar/worktree-list/viewport/use-row-measurement.ts
|
||||
1 src/renderer/src/components/sidebar/worktree-list/viewport/use-scroll-to-top.ts
|
||||
1 src/renderer/src/components/sidebar/worktree-list/viewport/use-virtualizer.ts
|
||||
1 src/renderer/src/components/skills/SkillsPage.tsx
|
||||
1 src/renderer/src/components/skills/deletion/use-skill-delete-flow.ts
|
||||
1 src/renderer/src/components/skills/freshness/use-reviewed-skill-plan.ts
|
||||
1 src/renderer/src/components/skills/installation/SkillBundleInstallFlow.tsx
|
||||
2 src/renderer/src/components/skills/installation/SkillInstallDialog.tsx
|
||||
4 src/renderer/src/components/skills/installation/SkillInstallManagementDialog.tsx
|
||||
1 src/renderer/src/components/skills/setup/WslAgentSkillSetupPanel.tsx
|
||||
1 src/renderer/src/components/skills/sharing/SkillShareDialog.tsx
|
||||
1 src/renderer/src/components/skills/sharing/SkillSharedLinkRow.tsx
|
||||
1 src/renderer/src/components/skills/SkillsPage.tsx
|
||||
1 src/renderer/src/components/skills/setup/WslAgentSkillSetupPanel.tsx
|
||||
2 src/renderer/src/components/skills/sharing/use-owned-skill-shares.ts
|
||||
1 src/renderer/src/components/skills/freshness/use-reviewed-skill-plan.ts
|
||||
1 src/renderer/src/components/skills/deletion/use-skill-delete-flow.ts
|
||||
1 src/renderer/src/components/sparse/SparseCheckoutPresetSelect.tsx
|
||||
1 src/renderer/src/components/ssh-host-recovery/SshHostMoveAnywayAction.tsx
|
||||
2 src/renderer/src/components/star-nag/StarNagAgentValueMomentObserver.tsx
|
||||
@@ -634,7 +633,7 @@
|
||||
1 src/renderer/src/components/terminal-pane/controller/TerminalPaneCodexRestartPortals.tsx
|
||||
1 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-context-actions.ts
|
||||
3 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-foundation.ts
|
||||
10 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-projection.ts
|
||||
2 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-projection.ts
|
||||
3 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-startup-actions.ts
|
||||
1 src/renderer/src/components/terminal-pane/controller/use-terminal-pane-store-actions.ts
|
||||
1 src/renderer/src/components/terminal-pane/errors/TerminalErrorToast.tsx
|
||||
@@ -691,17 +690,17 @@
|
||||
1 src/renderer/src/hooks/composer-state/quick-submit-action.ts
|
||||
1 src/renderer/src/hooks/composer-state/workspace-identity-state.ts
|
||||
3 src/renderer/src/hooks/linear-issue-committed-state.ts
|
||||
1 src/renderer/src/hooks/use-audio-capture.ts
|
||||
6 src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.ts
|
||||
4 src/renderer/src/hooks/skills/freshness/useSkillFreshness.ts
|
||||
1 src/renderer/src/hooks/skills/installation/use-bundled-skill-install.ts
|
||||
1 src/renderer/src/hooks/use-audio-capture.ts
|
||||
2 src/renderer/src/hooks/useDetectedAgents.ts
|
||||
2 src/renderer/src/hooks/useGitHubTaskItems.ts
|
||||
2 src/renderer/src/hooks/useImmediateMutation.ts
|
||||
6 src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.ts
|
||||
1 src/renderer/src/hooks/useIssueMetadata.ts
|
||||
3 src/renderer/src/hooks/useLinearIssuePublication.ts
|
||||
1 src/renderer/src/hooks/useMacTccAttributionSeveredNotice.ts
|
||||
1 src/renderer/src/hooks/useSettingsNavigationMetadata.ts
|
||||
4 src/renderer/src/hooks/skills/freshness/useSkillFreshness.ts
|
||||
1 src/renderer/src/hooks/useTaskPageGitHubWorkItemMutation.ts
|
||||
6 src/renderer/src/hooks/useVirtualizedScrollAnchor.ts
|
||||
1 src/renderer/src/lib/agent-session/launch/inventory/host-chat-program-acquisition.ts
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# are skipped too. Remove a line once its file is safe to compile.
|
||||
|
||||
# Render-time reads of providerRuntimeSessionGeneration (lib/provider-runtime-context.ts; hazard live-read-in-memo-block).
|
||||
src/renderer/src/components/feature-wall/FeatureWallSetupChecklist.tsx
|
||||
src/renderer/src/components/feature-wall/use-integration-connection-status.ts
|
||||
src/renderer/src/components/feature-wall/setup/FeatureWallSetupChecklist.tsx
|
||||
src/renderer/src/components/feature-wall/setup/integrations/use-integration-connection-status.ts
|
||||
src/renderer/src/components/integrations/task-trackers/jira-integration-card.tsx
|
||||
src/renderer/src/components/integrations/task-trackers/linear-integration-card.tsx
|
||||
src/renderer/src/components/new-workspace/use-jira-source-connection.ts
|
||||
@@ -28,6 +28,8 @@ src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-s
|
||||
src/renderer/src/components/editor/markdown-preview/search/use-markdown-preview-search-reveal.ts
|
||||
src/renderer/src/components/editor/markdown-preview/viewport/use-markdown-preview-scroll-anchor.ts
|
||||
src/renderer/src/components/github-item-dialog/inspect-pull-request/pr-files-combined-diff-body.tsx
|
||||
src/renderer/src/components/native-chat/tools/runs/NativeChatToolRunMembers.tsx
|
||||
src/renderer/src/components/native-chat/tools/runs/use-native-chat-tool-run-window.ts
|
||||
src/renderer/src/components/right-sidebar/file-explorer/panel/FileExplorerVirtualRows.tsx
|
||||
src/renderer/src/components/right-sidebar/file-explorer/tree/FileExplorerFilesTreePane.tsx
|
||||
src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
# 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
|
||||
1 src/main/automations/retained-run-reconciliation.ts
|
||||
1 src/main/automations/service.ts
|
||||
1 src/main/automations/observation/retained-run-reconciliation.ts
|
||||
1 src/main/automations/automation-service.ts
|
||||
1 src/main/browser/extensions/native-messaging/browser-extension-native-ports.ts
|
||||
1 src/main/browser/extensions/web-store/browser-extension-webstore-updater.ts
|
||||
1 src/main/claude/agent-view/agent-view-bridge.ts
|
||||
1 src/main/cli/appimage/appimage-serve-preparation.ts
|
||||
1 src/main/codex-accounts/codex-login-session.ts
|
||||
1 src/main/codex-accounts/login/codex-login-session.ts
|
||||
1 src/main/codex/configuration/homes/codex-windows-sandbox-read-grant.ts
|
||||
1 src/main/crash-reporting/memory/pre-gone-host-memory.ts
|
||||
1 src/main/crash-reporting/process-exits/process-gone-diagnostics.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,13 +49,13 @@
|
||||
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/sidebar-inputs/paired-server-rows-producer.ts
|
||||
1 src/main/runtime/rpc/transports/unix-socket-transport.ts
|
||||
1 src/main/runtime/sidebar-inputs/workspace-rows/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
|
||||
1 src/main/runtime/terminal/wait/terminal-idle-polls.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
|
||||
|
||||
+491
-383
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user