Merge remote-tracking branch 'origin/main' into brennanb2025/c5-read-child-records

# Conflicts:
#	src/main/claude/claude-structured-session-state.ts
#	src/main/codex/codex-structured-child-work-producer.test.ts
#	src/main/codex/codex-structured-session-adapter.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-host-test-harness.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-host.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-rest-test-rig.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-restart-interruption-test-harness.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-status-feed.test.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-working-at-teardown.ts
#	src/main/native-chat/agent-session-wire/structured-conversation-command-admission.ts
#	src/main/native-chat/agent-session-wire/structured-conversation-command.ts
#	src/shared/agent-session-wire.ts
This commit is contained in:
Brennan Benson
2026-09-30 00:58:49 -07:00
3076 changed files with 85920 additions and 69151 deletions
+3
View File
@@ -47,6 +47,9 @@
# Generated method->params catalog: compared byte-for-byte by
# verify:rpc-params-catalog, so a CRLF checkout would fail the gate.
/src/shared/rpc-contract/rpc-params-catalog.generated.ts linguist-generated=true text eol=lf
# Mobile RPC recording goldens: replay requires the file text to equal what rpc:record writes
# (LF), so a CRLF checkout would fail every golden.
/mobile/rpc-foundation/goldens/*.json text eol=lf
# Mobile web page source. Every text byte here is hashed into an asset digest and from
# there into buildId, so a CRLF Windows checkout would ship a different bundle id for
# identical source (91af2897 vs 9d78435e, measured on the bundle this replaced).
+1 -1
View File
@@ -11,7 +11,7 @@
<!-- What problem does this solve, and why is this approach better than the alternatives you considered? -->
## Linked Issue
_If you do not have one and are an outside contributors, your PR **wiil** be ignored. Refs is not sufficient. Link an actual issue_
<!-- Link the issue this PR addresses, there should ALWAYS be one (for outside contributors) -->
<!-- SPECIAL CASE: If you are a maintainer (member of stablyai org) AVOID opening needless issues. Only attach pre-existing ones -->
+38 -60
View File
@@ -8,6 +8,11 @@ on:
- reopened
paths:
- 'mobile/**'
# Why all of src/shared: mobile imports hundreds of its modules, directly and through the RPC
# recordings and the host params check, so any shared edit can move a golden or break mobile's
# typecheck. Why the root lockfile: those modules resolve their packages from the root install.
- 'src/shared/**'
- 'pnpm-lock.yaml'
# Mobile launch contracts exercise the real host dispatcher and durable receipt store.
- 'src/main/agent-launch/**'
- 'src/main/runtime/rpc/**'
@@ -17,21 +22,6 @@ on:
- 'src/main/runtime/orca-runtime.ts'
- 'src/main/runtime/agent-session-*.ts'
- 'src/main/native-chat/agent-session-wire/**'
- 'src/shared/agent-launch-*.ts'
- 'src/shared/agent-session-*.ts'
- 'src/shared/new-workspace/worktree-create-collision.ts'
# Why: the mobile terminal link parsers are conformance-tested against
# these shared fixtures; desktop-side fixture edits must re-run this suite.
- 'src/shared/terminal-file-link-conformance.ts'
# Why: mobile imports the negotiated capability names directly and records
# the whole capability read verbatim in its goldens, so a capability added
# desktop-side rewrites a mobile fixture and must re-run this suite.
- 'src/shared/protocol-version.ts'
# Why: mobile's rpc-params-contract.ts is a type-only re-export of the
# generated params catalog, and mobile/tsconfig.json includes **/*.ts. A
# schema edit anywhere under here changes mobile's types, so a desktop-only
# change can break mobile's typecheck with no other mobile signal.
- 'src/shared/rpc-contract/**'
# Why: this job holds the only checks that load the Fastfile, so edits to
# it or to the release workflow it guards must re-run them.
- '.github/workflows/mobile.yml'
@@ -40,20 +30,30 @@ on:
- 'config/scripts/mobile-release-check-scope*'
- 'config/scripts/mobile-test-change-scope*'
- 'config/scripts/pr-code-change-scope.mjs'
- 'config/scripts/mobile-recording-pin-checkout.test.mjs'
# Why main too: a squash is where a spliced corpus lands, and where a behaviour-change branch's
# own pinned commit leaves main's history for its pull request's head ref, which the guard follows.
# Why main too: two pull requests can each pass against their own base and disagree once both
# land, and `verify` never runs on main. The same source paths as above, less the CI inputs only
# `verify` reads.
push:
branches:
- main
paths:
- 'mobile/**'
- 'src/shared/**'
- 'pnpm-lock.yaml'
- 'src/main/agent-launch/**'
- 'src/main/runtime/rpc/**'
- 'src/main/runtime/runtime-rpc/**'
- 'src/main/runtime/runtime-rpc.ts'
- 'src/main/runtime/device-registry.ts'
- 'src/main/runtime/orca-runtime.ts'
- 'src/main/runtime/agent-session-*.ts'
- 'src/main/native-chat/agent-session-wire/**'
- '.github/workflows/mobile.yml'
concurrency:
# Per commit on main, not per branch. GitHub cancels any PENDING run in a group when a new one
# queues, whatever `cancel-in-progress` says, so one shared main group drops the middle merge of
# three -- and a pin that breaks there is exactly what this workflow now checks for.
# three -- and a merge that breaks main there is exactly what `main-tests` checks for.
group: mobile-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
@@ -151,6 +151,14 @@ jobs:
ORCA_BACKGROUND_LAUNCH: '1'
run: pnpm test
# Reports, never gates: the goldens are content-addressed JSON, so their raw diff is hashes.
# This decodes which recorded behaviour the pull request moves into the job summary, and runs
# after a red Test too, when a reviewer most wants it.
- name: Summarize RPC recording changes
if: ${{ !cancelled() }}
continue-on-error: true
run: pnpm run rpc:diff HEAD^1 --summary "$GITHUB_STEP_SUMMARY"
- name: Test iOS release version resolution
if: steps.ruby-scope.outputs.should_run != 'false'
run: ruby fastlane/ios_release_version_test.rb
@@ -177,14 +185,11 @@ jobs:
- name: Check formatting
run: pnpm format:check
recording-pin:
name: RPC recording pin
# Why ARM: pure Node plus git; the golden comparison masks `platform`.
main-tests:
name: Mobile tests on main
if: github.event_name == 'push'
# Why ARM: the same pure-Node Vitest run `verify` makes on a pull request.
runs-on: ubuntu-24.04-arm
# Why pull-requests: the guard asks GitHub which pull requests hold a pin main's history lacks.
permissions:
contents: read
pull-requests: read
defaults:
run:
@@ -194,13 +199,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
# The reachability verdict is read straight off history. On a shallow checkout
# `git merge-base --is-ancestor` answers from grafted parents, so the guard refuses to
# answer at all rather than reporting a pass it has no evidence for -- and the pinned tree
# below has to be checkable out.
fetch-depth: 0
# Ancestry needs commits; the pinned worktree fetches its historical blobs on demand.
filter: blob:none
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
@@ -211,31 +210,10 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Seconds. No `--ref`, so the pin is judged against the same tree it was read out of. On a
# pull request that is the merge preview, which already carries main's repins; judging the
# branch head instead fails every branch cut before the day's repin, and its instruction would
# tell the author to pin their own head. A branch that pins its own commit still passes on the
# push after the squash: the guard asks GitHub which pull requests hold the pin and fetches
# their `refs/pull/<n>/head`, which GitHub keeps for good. The token lifts the API rate limit.
- name: Check the recording pin is reachable
shell: bash
# The whole suite, not only the recordings: the mutant and page-bridge suites read the goldens
# too. A red run here names each golden and the `rpc:record` command; the author of the merge
# that turned it red re-records in a follow-up.
- name: Test
env:
GITHUB_TOKEN: ${{ github.token }}
run: pnpm exec tsx scripts/rpc-recording-pin-guard.mts reachable
# ~2 min locally for the record itself, so it is gated rather than run twice over. A pull
# request that moves none of the corpus, the manifest or the recorder cannot move this
# verdict away from the one the base commit already published, and `verify` replays the
# corpus against the branch tree in the meantime. A push to main has no `verify` job and is
# where a squash lands a spliced corpus, so there it always runs.
- name: Reproduce the corpus from the pinned tree
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
PIN_GUARD_BASE: ${{ github.event.pull_request.base.sha }}
run: |
if [ -n "$PIN_GUARD_BASE" ]; then
pnpm exec tsx scripts/rpc-recording-pin-guard.mts reproduce --if-changed-since "$PIN_GUARD_BASE"
else
pnpm exec tsx scripts/rpc-recording-pin-guard.mts reproduce
fi
ORCA_BACKGROUND_LAUNCH: '1'
run: pnpm test
+33
View File
@@ -439,6 +439,8 @@ jobs:
runs-on: ubuntu-24.04-arm
env:
CODEX_CLI_VERSION: '0.150.1'
# Why a second pin: --no-daemon only exists from 0.156, and Orca's codex wrapper relies on it.
CODEX_NO_DAEMON_CLI_VERSION: '0.158.0'
steps:
- name: Checkout
@@ -467,6 +469,34 @@ jobs:
pnpm exec vitest run --config config/vitest.config.ts \
src/main/codex/codex-index-heal-binary-contract.test.ts
- name: Install pinned no-daemon Codex CLI
run: |
set -euo pipefail
npm install --no-audit --no-fund --prefix "$RUNNER_TEMP/codex-cli-no-daemon" \
"@openai/codex@$CODEX_NO_DAEMON_CLI_VERSION"
- name: Verify Codex --no-daemon contract
env:
ORCA_CODEX_NO_DAEMON_CONTRACT_REQUIRED: '1'
ORCA_CODEX_NO_DAEMON_CONTRACT_VERSION: ${{ env.CODEX_NO_DAEMON_CLI_VERSION }}
run: |
set -euo pipefail
ORCA_CODEX_NO_DAEMON_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-no-daemon/node_modules/.bin/codex" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/pty/codex-no-daemon-binary-contract.test.ts
# Why the no-daemon install: it is the newest pinned Codex, and its project
# lookup decides whether a worktree launch stops at the trust prompt.
- name: Verify Codex project-trust contract
env:
ORCA_CODEX_TRUST_CONTRACT_REQUIRED: '1'
ORCA_CODEX_TRUST_CONTRACT_VERSION: ${{ env.CODEX_NO_DAEMON_CLI_VERSION }}
run: |
set -euo pipefail
ORCA_CODEX_TRUST_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-no-daemon/node_modules/.bin/codex" \
pnpm exec vitest run --config config/vitest.config.ts \
src/main/agent-trust-presets.test.ts
xterm_patch_sync:
name: xterm patch sync
needs: [code_paths]
@@ -513,6 +543,8 @@ jobs:
# and its fish lane is the only end-to-end guard for #9993, so a skip would
# report green with nothing exercised. Turns those skips into failures.
ORCA_REQUIRE_FISH: '1'
# Why: the runner image ships pwsh, and the codex wrapper's PowerShell lane must not skip.
ORCA_REQUIRE_PWSH: '1'
steps:
- name: Checkout
@@ -607,6 +639,7 @@ jobs:
src/main/providers/local-pty-shell-ready-zsh-launch-environment.test.ts \
src/main/providers/__tests__/shell-ready-framework-example.test.ts \
src/main/pty/codex-shell-launch-preflight.test.ts \
src/main/pty/codex-shell-no-daemon.test.ts \
src/main/pty/omp-shell-wrapper-alias-safety.test.ts \
src/main/pty/omp-shell-wrapper.node-pty.test.ts \
src/main/shell-startup-feature-channel.test.ts \
+1
View File
@@ -47,6 +47,7 @@ Avoid type assertions except `as const`. Unavoidable casts need a line-specific
- **Test**: `pnpm test [path/to/file.test.ts]`
- **Lint**: `oxlint`, or `pnpm run check:code-quality:changed` for changed files (full `pnpm lint` is slow); format with `pnpm format`
- **Design system**: `pnpm run lint:design-system` for the full renderer report (not a gate); the changed-lines gate above is what CI enforces
- **Real Claude CLI**: when you change Claude structured-session code (`src/main/claude/claude-structured-*`), run `ORCA_REAL_CLAUDE_CLI_TEST=1 pnpm test src/main/claude/claude-structured-real-cli.test.ts src/main/claude/claude-structured-real-cli-fold.test.ts`; it uses your real Claude login
# Writing Pull Requests
+3 -2
View File
@@ -2,13 +2,14 @@ import { isDismissedAlert, reconcileQueuedDismissal } from './push-queued-dismis
import { parsePushDeliveryPayload } from './push-delivery-payload.js'
import { createHash, randomUUID } from 'node:crypto'
import { PUSH_LIMITS, type PushNotification } from '@orca-cloud/push-contract'
import { WORKER_DRAINS } from './push-worker-concurrency.js'
import type { PushDatabase, SqlRow } from './push-database.js'
const RETENTION_MS = 24 * 60 * 60_000
// accept() caps expires_at at due_at + TTL, so older due_at is expired: scans skip an unpruned backlog.
const TTL_MS = PUSH_LIMITS.notificationTtlSeconds * 1000
// Covers one claimer per worker drain skipping a device another drain holds.
const CLAIM_CANDIDATE_ATTEMPTS = 4
// One winner plus every other drain holding a device head, so a full set of peers cannot exhaust it.
const CLAIM_CANDIDATE_ATTEMPTS = WORKER_DRAINS
export const PRUNE_BATCH_ROWS = 2_000
export const PRUNE_MAX_BATCHES = 50
export const DELIVERY_LEASE_MS = 30_000
+3 -1
View File
@@ -1,6 +1,7 @@
import { buildPushDelivery } from './push-delivery-message.js'
import type { PushDispatcher } from './push-dispatcher.js'
import type { DurablePushStore } from './durable-push-store.js'
import { WORKER_DRAINS } from './push-worker-concurrency.js'
export class DurablePushWorker {
private timer?: NodeJS.Timeout
@@ -29,7 +30,8 @@ export class DurablePushWorker {
return
}
if (this.stopped) return
const pending = Promise.allSettled(Array.from({ length: 4 }, () => this.drain())).then(
const drains = Array.from({ length: WORKER_DRAINS }, () => this.drain())
const pending = Promise.allSettled(drains).then(
(results) => {
const failure = results.find((result) => result.status === 'rejected')
if (failure?.status === 'rejected') throw failure.reason
@@ -0,0 +1,3 @@
// Twelve drains lift the ~30/s ceiling four drains hit at ~120 ms per item; each drain holds one
// delivery in flight, so this is the worker's concurrency, not its database draw.
export const WORKER_DRAINS = 12
+1 -1
View File
@@ -8482,7 +8482,7 @@ function isDatabaseLockUnavailable(error: unknown): boolean {
return error instanceof Error && error.message === 'database_lock_unavailable'
}
export function cellInventoryLockOptions(mode: CellInventoryLockMode): RelayLockOptions {
function cellInventoryLockOptions(mode: CellInventoryLockMode): RelayLockOptions {
if (mode === 'nowait') return { failIfUnavailable: true, measureHoldMs: true }
if (mode === 'pool-default') return { measureHoldMs: true }
return { lockTimeoutMs: CELL_INVENTORY_LOCK_TIMEOUT_MS, measureHoldMs: true }
@@ -1,335 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { cellInventoryLockOptions, type CellInventoryLockMode } from './assignment-store.js'
// Which entry points can reach a call site. A site a sweep can enter must never
// take the bounded wait: its 55P03 becomes a terminal transaction failure, and
// the incident monitor freezes on a single one.
type Reachability = 'request' | 'sweep' | 'both' | 'orphan'
// 'caller' is not a CellInventoryLockMode: those sites take the mode threaded
// from `assign`, which is 'request' for a client and 'pool-default' for the
// evacuateDeadCells sweep.
type CensusMode = CellInventoryLockMode | 'caller'
type CensusEntry = { method: string; mode: CensusMode; reach: Reachability }
// Every lockCellInventory / lockGeneralCellInventory call site in
// assignment-store.ts, in source order. A new site fails this test until it is
// classified here, which is the point.
const CENSUS: CensusEntry[] = [
// assignStickyOnce is gone from this list: its retry now locks only the row
// the host is pinned to (lockCellRows), which is what a sticky refresh
// touches. Placement below is the one genuinely fleet-wide decision left.
{ method: 'assignOnce', mode: 'caller', reach: 'both' },
{ method: 'assignOnce', mode: 'caller', reach: 'both' },
{ method: 'assignOnce', mode: 'nowait', reach: 'both' },
{ method: 'assignOnce', mode: 'nowait', reach: 'both' },
{ method: 'assignOnce', mode: 'nowait', reach: 'both' },
{ method: 'refreshDrainMigrationLeasesOnce', mode: 'request', reach: 'request' },
// changeActivity, acquireActivity, activateControl and
// removeSupersededSameCellControls no longer take the inventory: they lock
// only the one or two cell rows they touch, in cell_id order (lockCellRows),
// so they cannot cycle with placement's ordered inventory lock, and the
// 23-row lock there had serialised every reconnect in the fleet behind every
// other one. The control accept path went one step further and takes no cell
// read lock at all: its single conditional write is the last statement before
// COMMIT.
{ method: 'startEvacuation', mode: 'request', reach: 'request' },
{ method: 'completeEvacuationFromDeadSourceOnce', mode: 'request', reach: 'request' },
{ method: 'completeEvacuationFromDeadSourceOnce', mode: 'nowait', reach: 'request' },
{ method: 'supersedeRegisteredEvacuationOnce', mode: 'request', reach: 'request' },
{ method: 'supersedeRegisteredEvacuationOnce', mode: 'nowait', reach: 'request' },
{ method: 'prepareRegisteredCellSupersession', mode: 'request', reach: 'request' },
{ method: 'prepareRegisteredCellSupersession', mode: 'request', reach: 'request' },
{ method: 'completeEvacuation', mode: 'nowait', reach: 'both' },
{ method: 'completeEvacuation', mode: 'pool-default', reach: 'both' },
{ method: 'rebalanceDormant', mode: 'request', reach: 'request' },
// startRegionalRehomeCandidate is gone: the rehome commit reads the inventory
// unlocked and locks only its target row, NOWAIT, as the statement before
// COMMIT (reserveRegionalRehomeTargetRow below).
{ method: 'completeRegionalRehomeCandidate', mode: 'nowait', reach: 'sweep' },
// Both regional-rehome abort sweeps share this rollback; only the 24-hour
// one also disables the durable switch.
{ method: 'rollBackStalledRegionalRehomes', mode: 'nowait', reach: 'sweep' },
{ method: 'abortExpiredEvacuations', mode: 'nowait', reach: 'sweep' },
{ method: 'abortExpiredEvacuations', mode: 'nowait', reach: 'sweep' },
{ method: 'releaseExpiredActivityLeases', mode: 'nowait', reach: 'sweep' },
{ method: 'releaseExpiredActivity', mode: 'nowait', reach: 'sweep' }
// reconcileReservationAccounting and leastLoadedCell are gone too: the first
// repairs exactly two cells' counters and now holds only those rows, and the
// second selects from the inventory its single caller has already locked.
]
// Every inline `FROM relay_cells ... FOR UPDATE` outside the named lock helpers,
// in source order: whole-table locks in reconciliation and sticky placement,
// and single-row locks for a cell the method is already scoped to (heartbeat,
// fence, drain generation, configuration, or a reservation adjust that runs
// under a lock its caller already holds). A new inline lock fails the census
// below until it is listed here; per-connection paths that touch more than one
// cell go through lockCellRows so the order is fixed.
const NAMED_LOCK_HELPERS = ['lockCellInventory', 'lockGeneralCellInventory', 'lockCellRows']
const INLINE_CELL_LOCK_SITES = [
'reconcileCellsWithOptions',
'assignStickyOnce',
'recordCellHeartbeat',
'attestCellFence',
'adoptLegacyCellFence',
'commitLegacyCellFenceAdoption',
'prepareCellFenceAttempt',
'attestCellFenceAttempt',
'attestCellFenceAttempt',
'configureCell',
'reserveRegionalRehomeTargetRow',
'assertDrainCellGeneration',
'adjustCellReservation'
]
// The background sweeps, and nothing else. A method reachable from one of these
// can be entered by a sweep tick, whatever else can also enter it. Both lists are
// read from source, so a new sweep step or a new route widens the derivation here
// instead of silently widening what a bounded wait can be entered from.
const SWEEP_ENTRY_FILES = ['./assignment-cleanup-steps.ts', './regional-rehome-worker.ts']
const REQUEST_ENTRY_FILES = [
'./app.ts',
'./relay-server.ts',
'./host-session-registry.ts',
'./cell-admission-startup.ts'
]
const DECLARATION = /^ {2}(?:private |public )?(?:static )?(?:async )?([A-Za-z_][\w]*)[(<]/
function storeSource(): string[] {
return readFileSync(new URL('./assignment-store.ts', import.meta.url), 'utf8').split('\n')
}
function entryPoints(files: string[]): string[] {
return files.flatMap((file) =>
[
...readFileSync(new URL(file, import.meta.url), 'utf8').matchAll(
/assignments\.([A-Za-z_][\w]*)\(/g
)
].map((call) => call[1]!)
)
}
// Same-class call graph: store methods only ever reach each other through `this.`.
function storeCallGraph(lines: string[]): Map<string, Set<string>> {
const bounds: { name: string; start: number }[] = []
lines.forEach((line, index) => {
const declaration = DECLARATION.exec(line)
if (declaration) bounds.push({ name: declaration[1]!, start: index })
})
const callees = new Map<string, Set<string>>()
bounds.forEach((method, index) => {
const end = bounds[index + 1]?.start ?? lines.length
const names = callees.get(method.name) ?? new Set<string>()
for (const call of lines
.slice(method.start, end)
.join('\n')
.matchAll(/this\.([A-Za-z_][\w]*)\s*\(/g)) {
names.add(call[1]!)
}
callees.set(method.name, names)
})
return callees
}
function closure(callees: Map<string, Set<string>>, roots: string[]): Set<string> {
const reached = new Set<string>()
const pending = [...roots]
while (pending.length > 0) {
const name = pending.pop()!
if (reached.has(name)) continue
reached.add(name)
for (const callee of callees.get(name) ?? []) if (!reached.has(callee)) pending.push(callee)
}
return reached
}
// Why: a hand-written reachability column is a claim, not a check. Derive both
// directions, so a new sweep edge into a bounded site fails here instead of in
// production, and so 'sweep' and 'both' stop being asserted by hand.
function derivedReachability(lines: string[]): (method: string) => Reachability {
const callees = storeCallGraph(lines)
const sweep = closure(callees, entryPoints(SWEEP_ENTRY_FILES))
const request = closure(callees, entryPoints(REQUEST_ENTRY_FILES))
return (method) =>
sweep.has(method)
? request.has(method)
? 'both'
: 'sweep'
: request.has(method)
? 'request'
: 'orphan'
}
function readCallSites(): { method: string; mode: CensusMode }[] {
const sites: { method: string; mode: CensusMode }[] = []
let method = '<module>'
for (const line of storeSource()) {
const declaration = DECLARATION.exec(line)
if (declaration) method = declaration[1]!
if (/private async lock(General)?CellInventory\(/.test(line)) continue
const call = /lock(?:General)?CellInventory\(\s*\w+\s*,\s*(?:'([a-z-]+)'|(\w+))\s*\)/.exec(line)
if (!call) continue
sites.push({ method, mode: (call[1] ?? 'caller') as CensusMode })
}
return sites
}
// Tier 3 and tier 4 of the row lock order documented in assignment-store.ts. A
// transaction that takes relay_cells before this host's reservation rows can
// cycle with one that takes them the other way round, and PostgreSQL resolves
// that as a 40P01 during exactly the drain and rehome waves these paths exist
// to run. The cell row is the one every host on a cell shares, so it is the
// lock that must be taken last, which fixes the direction for everyone else.
const CELL_LOCK_CALL =
/this\.(?:lockCellInventory|lockGeneralCellInventory|lockCellRows|adjustCellReservationAtomically|adjustCellReservation)\(|UPDATE relay_cells/
const RESERVATION_LOCK_CALL =
/this\.(?:lockControlConnectionReservations|insertControlConnectionReservation|claimControlConnectionReservation|releaseSupersededControlConnectionReservations)\(|(?:UPDATE|INTO|DELETE FROM)\s+relay_control_connection_reservations/
// The lock helpers themselves, plus the one reporting query that reads both
// tables without locking either.
const ROW_LOCK_ORDER_EXEMPT = [
'lockCellInventory',
'lockGeneralCellInventory',
'lockCellRows',
'lockControlConnectionReservations',
'adjustCellReservation',
'adjustCellReservationAtomically',
'insertControlConnectionReservation',
'claimControlConnectionReservation',
'releaseSupersededControlConnectionReservations',
'cellDeploymentStatus'
]
function methodSpans(lines: string[]): { name: string; start: number; end: number }[] {
const starts: { name: string; start: number }[] = []
lines.forEach((line, index) => {
const declaration = DECLARATION.exec(line)
if (declaration) starts.push({ name: declaration[1]!, start: index })
})
return starts.map((entry, index) => ({
...entry,
end: starts[index + 1]?.start ?? lines.length
}))
}
function pathsTakingCellsBeforeReservations(lines: string[]): string[] {
const offending: string[] = []
for (const span of methodSpans(lines)) {
if (ROW_LOCK_ORDER_EXEMPT.includes(span.name)) continue
let cell = Number.POSITIVE_INFINITY
let reservation = Number.POSITIVE_INFINITY
for (let index = span.start; index < span.end; index++) {
const line = lines[index]!
if (CELL_LOCK_CALL.test(line)) cell = Math.min(cell, index)
if (RESERVATION_LOCK_CALL.test(line)) reservation = Math.min(reservation, index)
}
if (cell < reservation && reservation !== Number.POSITIVE_INFINITY) {
offending.push(span.name)
}
}
return offending
}
describe('cell inventory lock call-site census', () => {
it('classifies every call site exactly as recorded', () => {
expect(readCallSites()).toEqual(CENSUS.map(({ method, mode }) => ({ method, mode })))
})
// Why: the census only sees lockCellInventory calls, so a hand-written
// `relay_cells ... FOR UPDATE` would escape classification entirely.
it('routes every relay_cells row lock through a named lock helper', () => {
const lines = storeSource()
const rawSites: string[] = []
// Whole statements, not a fixed window: a wide column list or a raw
// FOR UPDATE inside query() must not slip past.
const source = lines.join('\n')
const bounds: { name: string; start: number }[] = []
lines.forEach((line, index) => {
const declaration = DECLARATION.exec(line)
if (declaration) bounds.push({ name: declaration[1]!, start: index })
})
const methodAt = (offset: number): string => {
const lineIndex = source.slice(0, offset).split('\n').length - 1
let name = '<module>'
for (const bound of bounds) if (bound.start <= lineIndex) name = bound.name
return name
}
const tick = String.fromCharCode(96)
const statementCall = new RegExp(
'\\.(queryLocked|query)\\(\\s*' + tick + '([^' + tick + ']*)' + tick,
'g'
)
for (const call of source.matchAll(statementCall)) {
const statement = call[2]!
if (!/\bFROM\s+relay_cells\b/.test(statement)) continue
const locks = call[1] === 'queryLocked' || /\bFOR\s+UPDATE\b/.test(statement)
if (!locks) continue
const method = methodAt(call.index)
if (NAMED_LOCK_HELPERS.includes(method)) continue
rawSites.push(method)
}
expect(rawSites).toEqual(INLINE_CELL_LOCK_SITES)
})
it('takes the host reservation rows before the shared cell row everywhere', () => {
expect(pathsTakingCellsBeforeReservations(storeSource())).toEqual([])
})
it('leaves no call site taking the inventory without naming a mode', () => {
const source = readFileSync(new URL('./assignment-store.ts', import.meta.url), 'utf8')
const unclassified = source
.split('\n')
.filter((line) => /lock(?:General)?CellInventory\(\s*\w+\s*\)/.test(line))
.filter((line) => !line.includes('private async'))
expect(unclassified).toEqual([])
})
it('derives the same reachability the census claims', () => {
const reachOf = derivedReachability(storeSource())
expect(readCallSites().map(({ method }) => reachOf(method))).toEqual(
CENSUS.map((entry) => entry.reach)
)
})
// Why: this is the whole point of the classification. A shorter wait on a
// sweep-reachable site turns contention into a terminal transaction failure
// that counts against the incident gate's relayPostgresRetryExhausted bar.
// Why: the hold distribution is what the 500ms bound will be tuned against, so
// a mode that stops asking for it goes unmeasured in exactly the lane that
// matters. Nothing else in the suite reads the pool-default branch.
it('measures the hold in every lock mode', () => {
const modes: CellInventoryLockMode[] = ['request', 'nowait', 'pool-default']
expect(modes.map((mode) => cellInventoryLockOptions(mode).measureHoldMs)).toEqual([
true,
true,
true
])
})
it('never puts a sweep-reachable site on the bounded wait', () => {
const reachOf = derivedReachability(storeSource())
const bounded = readCallSites().filter(
(site) => site.mode === 'request' && ['sweep', 'both'].includes(reachOf(site.method))
)
expect(bounded).toEqual([])
})
it('routes every sweep-only site to NOWAIT so it can skip the tick', () => {
const reachOf = derivedReachability(storeSource())
const queueing = readCallSites().filter(
(site) => reachOf(site.method) === 'sweep' && site.mode !== 'nowait'
)
expect(queueing).toEqual([])
})
})
@@ -1,4 +1,3 @@
import { readFileSync } from 'node:fs'
import { afterEach, describe, expect, it, vi } from 'vitest'
const fakes = vi.hoisted(() => ({
@@ -78,7 +77,6 @@ describe('bounded cell-inventory lock wait', () => {
// Why: a bound at or above the pool default would fence nothing, and one far
// below the hold time would convert ordinary contention into terminal failures.
it('keeps the request bound strictly inside the pool default', () => {
expect(CELL_INVENTORY_LOCK_TIMEOUT_MS).toBe(500)
expect(CELL_INVENTORY_LOCK_TIMEOUT_MS).toBeLessThan(POSTGRES_LOCK_TIMEOUT_MS)
})
@@ -361,15 +359,6 @@ describe('bounded cell-inventory lock wait', () => {
await database.close()
})
// Why: index.ts boots a server on import, so its wiring can only be read. An
// unspread hold metric is invisible: the flush simply omits the fields.
it('spreads the hold counts into the runtime metrics flush', () => {
const source = readFileSync(new URL('./index.ts', import.meta.url), 'utf8')
const flush = /observability\.start\(\(\) => \(\{([^}]*)\}\)\)/.exec(source)
expect(flush?.[1]).toContain('...consumeRelayCellInventoryHold(database)')
})
// Why: 500ms is a first value, not a measurement. Tuning it needs the hold
// distribution, which no runtime metric carried.
it('reports how long the inventory lock was held to COMMIT', async () => {
@@ -492,25 +481,6 @@ describe('background sweeps skip a contended cell inventory', () => {
expect(warnings.entries).toEqual([])
await database.close()
})
it('still aborts the expired evacuation once the inventory is free', async () => {
const database = await openInMemoryRelayDatabase()
const probe = new InventoryLockProbe(database)
let now = 1_000
const store = new RelayAssignmentStore(probe, () => now)
await store.reconcileCells(CELLS)
const assignment = await store.assign(identity)
await store.activateControl(identity, {
cellId: assignment.cellId,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
await store.startEvacuation(identity, 'cell-b')
now += 24 * 60 * 60_000
expect(await store.abortExpiredEvacuations()).toBe(1)
await database.close()
})
})
// Returns each inventory lock the run took, as its bound or 'nowait'.
@@ -45,21 +45,6 @@ describe('sweep schedule jitter', () => {
expect(timers).toEqual([6_600])
})
// Why: index.ts boots a server on import, so its wiring can only be read.
it('jitters the director assignment cleanup tick', () => {
const source = readFileSync(new URL('./index.ts', import.meta.url), 'utf8')
const cleanup = /runAssignmentCleanup\(assignments\)\s*\},\s*([^\n]*?)\)\n/.exec(source)
expect(cleanup?.[1]).toBe('jitteredSweepIntervalMs(30_000)')
})
it('jitters the credential cleanup tick', () => {
const source = readFileSync(new URL('./index.ts', import.meta.url), 'utf8')
const cleanup = /'\[orca-relay\] credential cleanup failed'\s*\),\s*([^\n]*?)\n/.exec(source)
expect(cleanup?.[1]).toBe('jitteredSweepIntervalMs(30_000)')
})
// A census, not a list of the timers that happen to be gated today: an ungated sweep runs in
// every cell as well as the director, which multiplies one table scan by the fleet size.
it('gates every periodic sweep in index.ts on the maintenance role', () => {
@@ -2,8 +2,7 @@ import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
parseProductionCapacityCellArguments,
prepareProductionCapacityCell,
PRODUCTION_CAPACITY_CELL_IDS
prepareProductionCapacityCell
} from './prepare-relay-production-capacity-canary.mjs'
const config = {
@@ -63,24 +62,6 @@ function canaryFetch() {
describe('production Relay capacity cell admission', () => {
it('allows only the serving rollout cells', () => {
assert.deepEqual(PRODUCTION_CAPACITY_CELL_IDS, [
'production-gce-c7',
'production-gce-c8',
'production-gce-c9',
'production-gce-c10',
'production-gce-c13',
'production-gce-c14',
'production-gce-c15',
'production-gce-c16',
'production-gce-c19',
'production-gce-c20',
'production-gce-c21',
'production-gce-c22',
'production-gce-c23',
'production-gce-c24',
'production-gce-c25',
'production-gce-c26'
])
assert.deepEqual(parseProductionCapacityCellArguments([
'--director-origin', 'https://relay.onorca.dev',
'--cell-origin', 'https://c7.relay.onorca.dev',
@@ -1,5 +1,4 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { test } from 'node:test'
import {
LEASED_WORKFLOWS,
@@ -21,7 +20,6 @@ import {
revisionMintingScripts,
workflowFiles
} from './cloud-sql-rollout-lock-census.mjs'
import { relayWorkflowFile } from './relay-repository.mjs'
const expectedLease = { production: PRODUCTION_LEASE, staging: STAGING_LEASE, selectable: SELECTABLE_LEASE }
const leasedFiles = Object.keys(LEASED_WORKFLOWS)
@@ -182,29 +180,3 @@ test('census: no workflow rolls out against the shared instance outside the leas
assert.ok(!(file in NOT_A_CLOUD_SQL_CANDIDATE), `${file} cannot be both leased and a non-candidate`)
}
})
// The API and auth deploy scripts share this contract but stay in the private repository.
const serviceCapScripts = ['dev/scripts/deploy-relay-blue-green.mjs']
test('budgets tagged Cloud Run candidates outside the service-wide instance cap', () => {
for (const file of serviceCapScripts) {
const script = readFileSync(new URL(`../../${file}`, import.meta.url), 'utf8')
assert.match(script, /'--no-traffic'/, file)
assert.match(script, /'--max'/, file)
}
const budget = readFileSync(
new URL('../../dev/scripts/relay-cloud-sql-connection-budget.mjs', import.meta.url),
'utf8'
)
assert.match(budget, /directly addressable tagged revisions outside service-level caps/)
assert.match(
budget,
/apiCandidate: retainedDirectorRollback \+ inputs\.apiInstances \* inputs\.apiPoolMax/
)
const director = readWorkflow(relayWorkflowFile('deploy-relay-production-director.yml'))
const capacity = readWorkflow(relayWorkflowFile('deploy-relay-production-capacity-job.yml'))
const asia = readWorkflow(relayWorkflowFile('operate-relay-asia-admission.yml'))
assert.match(director, /--max-instances "\$\{DIRECTOR_MAX_INSTANCES\}"/)
assert.match(capacity, /--max-instances 5/)
assert.match(asia, /--max-instances "\$\{DIRECTOR_MAX_INSTANCES\}"/)
})
@@ -309,11 +309,6 @@ test('push credentials cannot assume the shared Relay deploy identity', () => {
assert.doesNotMatch(terraform('push-gateway.tf'), /member\s*=\s*local\.relay_github_deploy_service_account_member/)
})
// A latest revision needs a successor even when validation is inert.
test('dedicated database admits three simultaneous revision pools', () => {
assert.match(terraform('push-gateway.tf'), /var\.push_max_instances \* var\.push_database_pool_max \* 3 <= 64/)
})
test('push has only a dedicated database attachment and a narrowly scoped deployment lease', () => {
const service = terraform('push-gateway.tf')
const database = terraform('push-dedicated-database.tf')
@@ -1,48 +0,0 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { readRelayWorkflow } from './relay-repository.mjs'
const workflow = readRelayWorkflow('push-deploy.yml')
const position = (name) => {
const index = workflow.indexOf(`- name: ${name}`)
assert.notEqual(index, -1)
return index
}
const capability = position('Require image support for inert validation')
const deploy = position('Deploy the candidate revision with no traffic')
const activation = position('Retire inert validation and activate the verified image')
const shift = position('Shift all traffic to the verified candidate')
test('the exact build digest must support validation before production boot', () => {
assert.match(workflow, /docker buildx build --push --platform linux\/amd64 --provenance=false --metadata-file/)
assert.match(workflow, /containerimage\.digest/)
assert.doesNotMatch(workflow, /gcloud artifacts docker images describe/)
assert.ok(capability < deploy)
const preflight = workflow.slice(capability, deploy)
assert.match(preflight, /docker run --rm --network none --entrypoint node "\$\{IMAGE\}"/)
assert.match(preflight, /loadPushConfig\(env\)\.mode !== "validation"/)
assert.match(preflight, /validation_mode_not_fail_closed/)
})
test('inert validation and credential checks precede deliberate activation of the same digest', () => {
assert.match(workflow.slice(deploy, activation), /--update-env-vars ORCA_PUSH_MODE=validation/)
assert.match(workflow.slice(deploy, activation), /\.mode == "validation"/)
assert.ok(position('Prove the runtime identity can reach FCM') < activation)
const active = workflow.slice(activation, shift)
assert.ok(active.indexOf('gcloud run deploy') < active.indexOf('gcloud run revisions delete'))
assert.match(active, /--image "\$\{IMAGE\}"/)
assert.match(active, /--remove-env-vars ORCA_PUSH_MODE/)
assert.match(active, /\.spec\.containers\[0\]\.image == \$image/)
assert.match(active, /\.spec\.serviceAccountName == \$account/)
assert.match(active, /\.mode == "active"/)
assert.ok(active.indexOf('ACTIVATION_ATTEMPTED=true') < active.indexOf('gcloud run deploy'))
assert.match(workflow, /deletion below must stop its workers/)
})
test('production startup connects read-only and gates all background work in validation', () => {
const entry = readFileSync(new URL('../../apps/push/src/index.ts', import.meta.url), 'utf8')
assert.match(entry, /readOnly: config\.mode === 'validation'/)
assert.match(entry, /startPushBackground\(config,/)
assert.doesNotMatch(entry, /worker\.start\(/)
})
@@ -1,375 +0,0 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { test } from 'node:test'
import { relayWorkflowUrl } from './relay-repository.mjs'
const workflow = readFileSync(
relayWorkflowUrl('operate-relay-asia-admission.yml'),
'utf8'
)
const iam = readFileSync(new URL('../../infra/terraform/relay-github-actions.tf', import.meta.url), 'utf8')
const stagingProof = readFileSync(
relayWorkflowUrl('prove-relay-asia-staging.yml'),
'utf8'
)
const directorWorkflow = readFileSync(
relayWorkflowUrl('deploy-relay-production-director.yml'),
'utf8'
)
const terraformReadme = readFileSync(
new URL('../../infra/terraform/README.md', import.meta.url),
'utf8'
)
const proofIam = readFileSync(
new URL('../../infra/terraform/relay-asia-proof-iam.tf', import.meta.url),
'utf8'
)
const relayTerraform = readFileSync(
new URL('../../infra/terraform/relay.tf', import.meta.url),
'utf8'
)
const rolloutEvidence = readFileSync(
new URL('./relay-asia-rollout-evidence.mjs', import.meta.url),
'utf8'
)
const admissionBudgets = readFileSync(
new URL('../../packages/relay-contract/src/admission-budgets.ts', import.meta.url),
'utf8'
)
test('offers the exact audited admission modes under the shared deployment lock', () => {
for (const mode of [
'inspect', 'initialize', 'verify', 'register', 'configure', 'promote', 'rollback'
]) {
assert.match(workflow, new RegExp(`\\b${mode}\\b`))
}
assert.match(workflow, /production-cloud-sql-rollout/)
assert.match(workflow, /relay-staging-mutation/)
assert.match(workflow, /selector-generation/)
assert.match(workflow, /selector-attempt-id/)
})
test('requires exact confirmations and uses the existing admin identity', () => {
assert.match(workflow, /INITIALIZE_ADMISSION_SELECTOR/)
assert.match(workflow, /REGISTER_ASIA_MIGRATION_ONLY/)
assert.match(workflow, /PROMOTE_ASIA_GENERAL/)
assert.match(workflow, /ROLLBACK_ASIA_MIGRATION_ONLY/)
assert.match(workflow, /CONFIGURE_ASIA_DIRECTOR/)
assert.match(workflow, /GCP_RELAY_DEPLOY_SERVICE_ACCOUNT/)
assert.match(workflow, /id_token_audience: \$\{\{ env\.DIRECTOR_ORIGIN \}\}\/v1\/admin\/drain/)
assert.match(iam, /"operate-relay-asia-admission\.yml"/)
})
test('discovers generation read-only and explicitly initializes only generation zero', () => {
assert.match(workflow, /leave empty only for inspect/)
assert.match(workflow, /test -z "\$\{EXPECTED_SELECTOR_GENERATION\}"/)
assert.match(workflow, /test "\$\{EXPECTED_SELECTOR_GENERATION\}" = 0/)
assert.match(workflow, /\^\(0\|\[1-9\]\[0-9\]\*\)\$/)
assert.match(workflow, /selector-membership-sha256/)
assert.match(workflow, /\^\[a-f0-9\]\{64\}\$/)
assert.match(workflow, /director-image-digest/)
assert.match(workflow, /\.spec\.containers\[0\]\.image == \$image/)
})
test('uploads one sanitized machine-readable admission result', () => {
assert.match(workflow, /sanitize-relay-asia-admission-result\.mjs/)
const upload = /- name: Upload sanitized admission result\n([\s\S]*?)(?=\n - name:)/
.exec(workflow)?.[1]
assert.ok(upload)
assert.match(
upload,
/if: \$\{\{ inputs\.mode != 'configure' && steps\.admission-operation\.outcome == 'success' \}\}/
)
assert.match(upload, /uses: actions\/upload-artifact@v4/)
assert.match(
upload,
/relay-asia-admission-result-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}/
)
assert.match(upload, /path: \$\{\{ runner\.temp \}\}\/relay-asia-admission-result\/result\.json/)
assert.match(upload, /if-no-files-found: error/)
assert.match(upload, /retention-days: 7/)
assert.ok(
workflow.indexOf('Upload sanitized admission result') >
workflow.indexOf('Upload immutable canary evidence')
)
})
test('binds selector operations and director configuration to reviewed implementations', () => {
assert.match(workflow, /operate-relay-asia-admission\.mjs/)
assert.match(workflow, /prepare-relay-asia-director-cells\.mjs/)
assert.match(workflow, /deploy-relay-blue-green\.mjs/)
assert.match(workflow, /--prune-revisions false/)
assert.doesNotMatch(workflow, /gcloud secrets versions add/)
assert.match(workflow, /orca-cloud-relay-regional-placement-enabled/)
assert.match(workflow, /\.valueSource\.secretKeyRef/)
assert.match(workflow, /jq -er --arg secret "\$\{REGIONAL_PLACEMENT_SECRET\}"/)
assert.doesNotMatch(workflow, /jq -e --arg secret "\$\{REGIONAL_PLACEMENT_SECRET\}"/)
assert.doesNotMatch(workflow, /--regional-placement-enabled/)
assert.doesNotMatch(workflow, /"\$\{\{ inputs\./)
assert.doesNotMatch(workflow, /dns/i)
})
test('requires immutable staged evidence and a timed production canary before expansion', () => {
assert.match(workflow, /actions: read/)
assert.match(workflow, /actions\/download-artifact@v4/)
assert.match(workflow, /relay-asia-staging-\$\{EVIDENCE_RUN_ID\}-\$\{EVIDENCE_RUN_ATTEMPT\}/)
assert.match(workflow, /evidence_kind=staging/)
assert.match(workflow, /load-relay-controls\.mjs/)
assert.match(workflow, /--controls 1/)
assert.match(workflow, /--splices 1/)
assert.match(workflow, /--splice-hold-seconds 60/)
assert.match(workflow, /--relay-asia-load-principals 1/)
assert.match(workflow, /--duration-seconds 300/)
assert.match(workflow, /--required-lease-horizons 2/)
assert.match(workflow, /pnpm\/action-setup@v4/)
assert.match(workflow, /Install exact canary dependencies/)
assert.match(workflow, /pnpm install --frozen-lockfile/)
assert.match(workflow, /pnpm --filter @orca-cloud\/relay-contract build/)
assert.ok(
workflow.indexOf('Build the canary Relay contract') <
workflow.indexOf('Run a real five-minute canary control and splice')
)
assert.match(workflow, /--load-report "\$\{RUNNER_TEMP\}\/relay-asia-canary-load\.json"/)
assert.match(workflow, /"production-gce-c28":"migration-only","production-gce-c29":"migration-only"/)
// C28/C29 promotion downloads C27's canary under exactly this name.
assert.match(workflow, /relay-asia-\$\{\{ steps\.inputs\.outputs\.canary_hostname \}\}-canary-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}/)
assert.match(workflow, /echo "canary_hostname=\$\{canary_cell##\*-\}"/)
assert.match(workflow, /id: canary-evidence-upload/)
assert.match(workflow, /Return an unproven canary cell to migration-only/)
assert.match(workflow, /steps\.canary-evidence-upload\.outcome != 'success'/)
assert.match(workflow, /--mode recover-promotion[\s\S]*?--attempt-id "\$\{SELECTOR_ATTEMPT_ID\}"/)
assert.match(workflow, /--attempt-id "\$\{SELECTOR_ATTEMPT_ID\}-rollback"/)
assert.match(workflow, /evidence_kind=c27/)
assert.match(workflow, /orca_relay_runtime_metrics/)
assert.match(workflow, /relay-asia-rollout-evidence\.mjs create-canary \\\n\s+--cell-id "\$\{CANARY_CELL\}"/)
assert.match(workflow, /retention-days: 7/)
assert.match(workflow, /Require the exact director image before promotion/)
assert.match(workflow, /DIRECTOR_ORIGIN.*\/v1\/admin\/runtime-status/)
assert.match(workflow, /\.imageDigest.*IMAGE_DIGEST/)
const provenance = /- name: Verify evidence provenance and rollout binding before authentication\n([\s\S]*?)(?=\n - id: auth)/
.exec(workflow)?.[1]
assert.ok(provenance)
assert.match(provenance, /\.head_sha \| select\(type == "string" and test\("\^\[a-f0-9\]\{40\}\$"\)\)/)
assert.match(provenance, /--commit-sha "\$\{evidence_commit_sha\}"/)
assert.doesNotMatch(provenance, /--commit-sha "\$\{GITHUB_SHA\}"/)
})
test('binds each production promotion wave to its exact evidence and canary', () => {
const cases = /case "\$\{TARGET_CELL_IDS\}" in\n([\s\S]*?)\n\s*esac/.exec(workflow)?.[1]
assert.ok(cases)
const waves = Object.fromEntries(
[...cases.matchAll(/^ {14}([a-z0-9,-]+)\)\n([\s\S]*?);;/gm)].map((match) => [match[1], {
evidence: /evidence_kind=([a-z0-9]+)/.exec(match[2])?.[1] ?? 'none',
canary: /canary_cell=([a-z0-9-]+)/.exec(match[2])?.[1] ?? 'none'
}])
)
assert.deepEqual(waves, {
'production-gce-c27': { evidence: 'staging', canary: 'production-gce-c27' },
'production-gce-c28,production-gce-c29': { evidence: 'c27', canary: 'none' },
'production-gce-c30': { evidence: 'none', canary: 'production-gce-c30' }
})
assert.match(cases, /\*\) echo "production promotion wave is not reviewed" >&2; exit 1 ;;/)
assert.match(workflow, /if test "\$\{evidence_kind\}" = none; then\n\s+test -z "\$\{EVIDENCE_RUN_ID\}"/)
assert.doesNotMatch(workflow, /inputs\.cell-ids == /)
})
test('runs the timed canary and its automatic rollback for C27 and C30 alike', () => {
const steps = workflow.split(/\n(?= - )/)
const named = (name) => steps.find((step) => step.includes(`name: ${name}`))
for (const name of [
'Install exact canary dependencies',
'Build the canary Relay contract',
'Verify the canary cell state and start the timed canary',
'Run a real five-minute canary control and splice',
'Collect regional, Relay SQL, and Cloud SQL canary evidence',
'Upload immutable canary evidence'
]) {
assert.match(named(name), /if: \$\{\{ steps\.inputs\.outputs\.canary == 'true' \}\}/, name)
}
assert.match(
workflow,
/if: \$\{\{ inputs\.mode == 'configure' \|\| steps\.inputs\.outputs\.canary == 'true' \}\}/
)
const rollback = named('Return an unproven canary cell to migration-only')
assert.match(
rollback,
/if: \$\{\{ always\(\) && steps\.inputs\.outputs\.canary == 'true' && steps\.admission-operation\.outcome != 'skipped' && steps\.canary-evidence-upload\.outcome != 'success' \}\}/
)
assert.match(rollback, /CANARY_CELL: \$\{\{ steps\.inputs\.outputs\.canary_cell \}\}/)
assert.match(rollback, /--mode recover-promotion \\\n\s+--cell-ids "\$\{CANARY_CELL\}"/)
assert.match(rollback, /--mode rollback \\\n\s+--cell-ids "\$\{CANARY_CELL\}"/)
assert.match(rollback, /'\.states\[\$cell\]' <<< "\$\{result\}"\)" = migration-only/)
const start = named('Verify the canary cell state and start the timed canary')
assert.match(start, /production-gce-c30\)\n\s+verify_cells=production-gce-c30\n\s+expected_states='\{"production-gce-c30":"general"\}'/)
assert.match(start, /test "\$\(jq -cS '\.states' <<< "\$\{result\}"\)" = "\$\(jq -cS '\.' <<< "\$\{expected_states\}"\)"/)
assert.match(named('Run a real five-minute canary control and splice'), /--duration-seconds 300/)
})
test('creates staging evidence only after the bounded launch-path load and rollback', () => {
assert.match(stagingProof, /runs-on: \[self-hosted, linux, x64, relay-asia-east2-load\]/)
assert.doesNotMatch(stagingProof, /group: relay-asia-east2-load/)
assert.match(stagingProof, /pnpm\/action-setup@v4/)
assert.match(stagingProof, /pnpm install --frozen-lockfile/)
assert.match(stagingProof, /pnpm --filter @orca-cloud\/relay-contract build/)
assert.match(stagingProof, /run_phase launch 5 5/)
assert.doesNotMatch(stagingProof, /run_phase control|run_phase mixed/)
assert.match(stagingProof, /--aggregate-controls "\$\(\(controls \* 4\)\)"/)
assert.match(stagingProof, /--aggregate-splices "\$\(\(splices \* 4\)\)"/)
assert.match(stagingProof, /--required-lease-horizons 2/)
assert.match(stagingProof, /--splice-ramp-seconds 120/)
assert.match(stagingProof, /--max-generator-rss-growth-mib 512/)
assert.match(stagingProof, /--relay-asia-load-principals 32/)
assert.match(stagingProof, /ulimit -n/)
assert.match(stagingProof, /--region-behavior-probes 1/)
assert.match(stagingProof, /--capacity-cell-origin https:\/\/c4\.relay-staging\.onorca\.dev/)
assert.match(stagingProof, /--rebind-probes 2/)
assert.match(stagingProof, /--skip-rebind-overflow-check/)
assert.doesNotMatch(stagingProof, /--request-unit-invites|--regional-fallback-probes/)
assert.match(stagingProof, /--aggregate-reader-splices.*echo 5/)
assert.match(stagingProof, /--aggregate-reader-bytes.*echo 12582912/)
assert.match(stagingProof, /--phase-barrier-dir "\$\{proof_dir\}\/\$\{phase\}-barrier"/)
assert.match(stagingProof, /--duration-seconds 210/)
assert.match(stagingProof, /trap stop_shards EXIT/)
assert.match(stagingProof, /if ! wait "\$\{pid\}"; then failed=1; break; fi/)
assert.match(stagingProof, /connectionFailuresByReason/)
assert.match(stagingProof, /--launch-report "\$\{proof_dir\}\/launch\.json"/)
assert.match(stagingProof, /id-token: write/)
assert.match(stagingProof, /STAGING_GCP_RELAY_ASIA_PROOF_WORKLOAD_IDENTITY_PROVIDER/)
assert.match(stagingProof, /STAGING_GCP_RELAY_ASIA_PROOF_SERVICE_ACCOUNT/)
assert.doesNotMatch(stagingProof, /STAGING_GCP_DEPLOY_SERVICE_ACCOUNT/)
assert.doesNotMatch(stagingProof, /STAGING_RELAY_LOAD_ACCESS_TOKEN/)
assert.doesNotMatch(stagingProof, /secrets versions access|signing-key-file/)
assert.match(stagingProof, /relay-asia-rollout-evidence\.mjs create-staging/)
assert.match(stagingProof, /Require the exact staging director image before promotion/)
assert.match(stagingProof, /DIRECTOR_ORIGIN.*\/v1\/admin\/runtime-status/)
assert.match(stagingProof, /\.imageDigest.*IMAGE_DIGEST/)
assert.match(stagingProof, /Return staging C4 to migration-only/)
assert.match(stagingProof, /steps\.promote\.outcome != 'skipped'/)
assert.match(stagingProof, /--mode recover-promotion[\s\S]*?--attempt-id "\$\{PROMOTE_ATTEMPT_ID\}"/)
assert.match(stagingProof, /--mode rollback[\s\S]*?--expected-generation "\$\{promoted_generation\}"/)
assert.match(stagingProof, /if: \$\{\{ success\(\) \}\}/)
assert.match(
stagingProof,
/recover:\n if: \$\{\{ always\(\) && github\.ref == 'refs\/heads\/main' \}\}/
)
assert.match(stagingProof, /needs: prove/)
assert.match(stagingProof, /Recover staging C4 with a fresh identity/)
assert.equal((stagingProof.match(/google-github-actions\/auth@v2/g) ?? []).length, 2)
assert.equal((stagingProof.match(/--mode recover-promotion/g) ?? []).length, 2)
assert.equal((stagingProof.match(/--mode rollback/g) ?? []).length, 2)
})
test('keeps the private runner below its 64-port Cloud NAT allocation', () => {
const profile = /run_phase launch (\d+) (\d+)/.exec(stagingProof)
const controlsPerShard = Number(profile?.[1])
const splicesPerShard = Number(profile?.[2])
const rebindProbes = Number(/--rebind-probes (\d+)/.exec(stagingProof)?.[1])
const runtimeStatusSockets = 1
assert.ok(
controlsPerShard * 4 + splicesPerShard * 4 * 2 + rebindProbes + runtimeStatusSockets < 64
)
})
test('paces one-source staging upgrades below the Relay anti-abuse ceiling', () => {
const splicesPerShard = Number(/run_phase launch \d+ (\d+)/.exec(stagingProof)?.[1])
const rebindProbes = Number(/--rebind-probes (\d+)/.exec(stagingProof)?.[1])
const spliceRampMs = Number(/--splice-ramp-seconds (\d+)/.exec(stagingProof)?.[1]) * 1000
const ceiling = Number(
/maxPreAuthAttemptsPerSourcePerMinute: (\d+)/.exec(admissionBudgets)?.[1]
)
const totalSplices = splicesPerShard * 4
const attempts = Array.from({ length: totalSplices }, (_, ordinal) =>
Math.floor(ordinal * spliceRampMs / (totalSplices - 1))
).flatMap((startedAt) => [startedAt, startedAt])
attempts.push(...Array.from({ length: 4 + rebindProbes }, () => 0))
const busiestMinute = Math.max(...attempts.map((startedAt) =>
attempts.filter((attempt) => attempt >= startedAt && attempt < startedAt + 60_000).length
))
assert.ok(busiestMinute < ceiling)
})
test('reserves rollback time beyond the complete bounded staging proof envelope', () => {
const timeoutMinutes = Number(/timeout-minutes: (\d+)/.exec(stagingProof)?.[1])
assert.equal(timeoutMinutes, 75)
const spliceRampSeconds = Number(/--splice-ramp-seconds (\d+)/.exec(stagingProof)?.[1])
const launchSeconds = 180 + spliceRampSeconds + 210 + 60
const setupEvidenceAndRollbackSeconds = 10 * 60
const envelopeMinutes = Math.ceil((launchSeconds + setupEvidenceAndRollbackSeconds) / 60)
assert.ok(timeoutMinutes - envelopeMinutes >= 30)
assert.match(stagingProof, /--ramp-seconds 180/)
assert.match(stagingProof, /--duration-seconds 210/)
})
test('binds the staging proof to one least-privilege Google identity', () => {
assert.match(
proofIam,
/github_relay_asia_proof_workflow_file = "prove-relay-asia-staging\.yml"/
)
assert.match(
proofIam,
/assertion\.workflow_ref == '\$\{prefix\}\$\{local\.github_relay_asia_proof_workflow_file\}@refs\/heads\/main'/
)
assert.match(proofIam, /assertion\.environment == 'staging'/)
assert.match(proofIam, /assertion\.event_name == 'workflow_dispatch'/)
assert.match(proofIam, /roles\/logging\.viewer/)
assert.match(proofIam, /roles\/monitoring\.viewer/)
assert.match(rolloutEvidence, /readCloudSqlBackends/)
assert.match(rolloutEvidence, /cloudSql: await readCloudSqlBackends/)
assert.doesNotMatch(proofIam, /compute\.|cloudsql\.|secretmanager\.|roles\/editor|roles\/run\./)
})
test('keeps the production US-first switch in durable Secret Manager state', () => {
assert.match(directorWorkflow, /options: \[preserve, enable, disable\]/)
assert.match(directorWorkflow, /default: preserve/)
assert.match(directorWorkflow, /gcloud secrets versions add/)
assert.match(directorWorkflow, /preserve\) desired="\$\{current\}"/)
assert.match(directorWorkflow, /--regional-placement-secret-version "\$\{target_version\}"/)
assert.match(directorWorkflow, /test "\$\{CEILING\}" = "\$\{DIRECTOR_MAX_INSTANCES\}"/)
assert.match(directorWorkflow, /orca-cloud-relay-regional-placement-enabled/)
assert.match(directorWorkflow, /\.valueSource\.secretKeyRef \/\/ \.valueFrom\.secretKeyRef/)
assert.match(directorWorkflow, /\.version \/\/ \.key/)
assert.match(directorWorkflow, /\.secret \/\/ \.name/)
assert.match(workflow, /\.valueSource\.secretKeyRef \/\/ \.valueFrom\.secretKeyRef/)
assert.doesNotMatch(directorWorkflow, /--regional-placement-enabled/)
assert.doesNotMatch(workflow, /inputs\.regional-placement-enabled/)
})
test('prunes incompatible production revisions only when explicitly confirmed', () => {
assert.match(
directorWorkflow,
/prune-incompatible-revisions:[\s\S]*?default: false[\s\S]*?type: boolean/
)
assert.match(directorWorkflow, /PRUNE_INCOMPATIBLE_RELAY_DIRECTOR_REVISIONS/)
assert.match(
directorWorkflow,
/test "\$\{REGIONAL_PLACEMENT_MODE\}" = preserve[\s\S]*?test "\$\{CONFIRMATION\}" = PRUNE_INCOMPATIBLE_RELAY_DIRECTOR_REVISIONS/
)
assert.match(
directorWorkflow,
/--prune-revisions "\$\{PRUNE_INCOMPATIBLE_REVISIONS\}"/
)
})
test('documents the exact regional-placement secret bootstrap before director rollout', () => {
for (const address of [
'google_secret_manager_secret.relay_regional_placement_enabled',
'google_secret_manager_secret_version.relay_regional_placement_enabled',
'google_secret_manager_secret_iam_member.relay_regional_placement_runtime_accessor',
'google_secret_manager_secret_iam_member.relay_regional_placement_deploy_accessor[0]',
'google_secret_manager_secret_iam_member.relay_regional_placement_deploy_adder[0]',
'google_secret_manager_secret_iam_member.relay_regional_placement_deploy_viewer[0]'
]) {
assert.match(terraformReadme, new RegExp(address.replaceAll(/[.[\]]/g, '\\$&')))
}
assert.match(terraformReadme, /Before the first director deployment/)
assert.match(terraformReadme, /Pass the exact environment tfvars/)
// The Cloudflare records left with the apps root; a -var for a variable this root no longer
// declares is a hard error, so no relay procedure may still tell an operator to pass it.
assert.doesNotMatch(terraformReadme, /manage_artifact_dns/)
assert.match(terraformReadme, /exactly these six additions/)
assert.match(terraformReadme, /version metadata/)
assert.match(
relayTerraform,
/resource "google_secret_manager_secret_iam_member" "relay_regional_placement_deploy_viewer"[\s\S]*?role\s+= "roles\/secretmanager\.viewer"/
)
})
@@ -1,269 +0,0 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { test } from 'node:test'
import { fileURLToPath } from 'node:url'
import { relayWorkflowUrl } from './relay-repository.mjs'
function workflow(name) {
return readFileSync(
fileURLToPath(relayWorkflowUrl(name)),
'utf8'
)
}
test('same-cap wrapper is reusable, canary-bound, and sequential', () => {
const wrapper = workflow('deploy-relay-production-same-cap.yml')
const job = workflow('deploy-relay-production-same-cap-job.yml')
assert.match(wrapper, /options: \[verify, canary-apply, batch-apply, rollback\]/)
assert.match(wrapper, /relay-same-cap-canary-\$\{\{ inputs\.canary-run-id \}\}/)
assert.match(wrapper, /needs: \[gate, cell_1\]/)
assert.match(wrapper, /needs: \[gate, cell_2\]/)
assert.match(wrapper, /needs: \[gate, cell_3\]/)
assert.match(job, /on:\n workflow_call:/)
assert.match(job, /c27\|c28\|c29\|c30\)/)
assert.match(job, /EXPECTED_HARD_CAP=3000/)
assert.match(job, /EXPECTED_REGION=asia-east2/)
assert.match(job, /--hard-cap "\$\{EXPECTED_HARD_CAP\}"/)
assert.match(job, /--regional-rehome-protocol "\$\{DESIRED_REHOME_PROTOCOL\}"/)
assert.match(job, /--argjson protocol "\$\{PREDECESSOR_REHOME_PROTOCOL\}"/)
assert.match(job, /runtime predecessor mismatch fields=/)
// A rollback interrupted between apply and restore must be resumable.
assert.match(job, /ROLLBACK_RESUME=true/)
assert.match(job, /test "\$\{LIVE_IMAGE_DIGEST\}" = "\$\{DESIRED_IMAGE_DIGEST\}"/)
// Resume must skip BOTH the drain (no restart will clear the flag) and the
// apply (state already converged), and prove convergence instead.
assert.match(
job,
/Reversibly isolate and drain only the selected cell\n if: \$\{\{ inputs\.mode != 'verify' && env\.ROLLBACK_RESUME != 'true' \}\}/
)
assert.match(
job,
/Apply only the selected same-cap template and MIG\n if: \$\{\{ inputs\.mode != 'verify' && env\.ROLLBACK_RESUME != 'true' \}\}/
)
assert.match(
job,
/Require converged Terraform state and a stable MIG on resume\n if: \$\{\{ inputs\.mode != 'verify' && env\.ROLLBACK_RESUME == 'true' \}\}/
)
assert.match(job, /resume found unconverged resources/)
// A canary or batch cell that failed before its template apply also
// resumes here with template drift from repo changes since its last roll;
// only a plan the reviewed validator approves for the image the cell
// already serves may pass, and resume still applies nothing.
assert.match(job, /requiring reviewed rollback-image drift/)
assert.match(
job,
/--image "\$\{DESIRED_IMAGE\}" \\\n {16}--rollback-image "\$\{DESIRED_IMAGE\}"/
)
// The relaxation is only safe if the reviewed validator actually runs on
// the NON-converged branch, in same-cap-cell mode, with the trust config
// the validator requires, restricted to the template-and-MIG change pair.
assert.match(
job,
/if ! terraform -chdir=infra\/terraform show -json[\s\S]{0,220}\| length == 0' >\/dev\/null\n then\n/
)
assert.match(
job,
/requiring reviewed rollback-image drift'\n[\s\S]{0,400}?\n {16}--mode same-cap-cell --cell-id "\$\{TARGET_CELL_ID\}" \\\n/
)
assert.match(
job,
/Require converged Terraform state and a stable MIG on resume[\s\S]{0,300}CAPACITY_SERVICE_ACCOUNT: \$\{\{ vars\.PRODUCTION_GCP_RELAY_CAPACITY_SERVICE_ACCOUNT \}\}\n {10}DIRECTOR_RUNTIME_SERVICE_ACCOUNT: \$\{\{ vars\.PRODUCTION_GCP_RELAY_DIRECTOR_RUNTIME_SERVICE_ACCOUNT \}\}/
)
assert.match(
job,
/--rollback-image "\$\{DESIRED_IMAGE\}" \\\n {16}--capacity-service-account "\$\{CAPACITY_SERVICE_ACCOUNT\}" \\\n {16}--rehome-director-service-account "\$\{DIRECTOR_RUNTIME_SERVICE_ACCOUNT\}"/
)
assert.match(
job,
/host-drain \\\n {16}--regional-rehome-protocol "\$\{DESIRED_REHOME_PROTOCOL\}" \\\n {16}"\$\{POOL_ARGUMENTS\[@\]\}"\)"\n {12}echo "\$\{RESUME_REVIEW\}"\n {12}jq -e '\.changes == 2' <<< "\$\{RESUME_REVIEW\}" >\/dev\/null/
)
// A resume applies nothing at all, which is what a resume means: the only accepted
// unconverged plan is the template-and-MIG rollback-image drift, and it is left pending.
const resumeStep = job.slice(
job.indexOf('- name: Require converged Terraform state and a stable MIG on resume'),
job.indexOf('- name: Apply only the selected same-cap template and MIG')
)
assert.equal(resumeStep.split('terraform -chdir=infra/terraform apply').length, 1)
assert.match(job, /resume requires the isolated migration-only cell/)
assert.match(job, /test "\$\{TARGET_INCARNATION\}" = "\$\{SOURCE_INCARNATION\}"/)
assert.match(job, /\(.regionalRehomeProtocol \/\/ 0\) == \$protocol/)
assert.match(job, /\(\.draining == false or \$drainingOk\)/)
// Selector expectations must follow the mutations' returned generations,
// not fixed offsets: isolate is a no-op on a cell a failed canary already
// isolated, and the restore inspect must expect post-restore membership.
assert.match(job, /SELECTOR_GENERATION_AFTER_ISOLATE=\$\{EFFECTIVE_SELECTOR_GENERATION\}/)
assert.match(job, /SELECTOR_GENERATION_AFTER_ISOLATE=\$\{ISOLATE_GENERATION\}/)
assert.match(job, /--expected-selector-generation "\$\{SELECTOR_GENERATION_AFTER_ISOLATE\}"/)
assert.match(job, /--expected-selector-generation "\$\{SELECTOR_GENERATION_AFTER_RESTORE\}"/)
assert.match(job, /--expected-migration-only-cells "\$\{RESTORED_MIGRATION_CELLS\}"/)
assert.match(job, /--expected-general-cells "\$\{RESTORED_GENERAL_CELLS\}"/)
assert.match(job, /FAILSAFE_GENERATION/)
// Later batch waves start after ~16-min predecessor rolls, so BOTH evidence
// age checks must scale by wave or cell_2+ can never pass; the bound's
// per-wave step is the cell job timeout, so the two must move together.
assert.match(job, /--required-migration-policy strict \\\n --wave-index "\$\{WAVE_INDEX\}"/)
// Wave 0 must retry freshness-only failures too: one Cloud Monitoring publish
// lag at the sample instant is not health evidence, and single-shot wave 0
// failed a whole batch on a series that was fresh again a minute later.
assert.match(
job,
/dry-run\.state\.json" \\\n {14}--wave-index "\$\{WAVE_INDEX\}" \\\n {14}--selector-wave-delta "\$\{SELECTOR_WAVE_DELTA\}" --retry-freshness/
)
assert.doesNotMatch(job, /RETRY_ARGS/)
// Break-glass: the override skips the aggregate 15-minute monitor evidence and
// nothing else. The live per-wave recheck still runs on the override path, off
// the dispatch inputs the rehome inspect below verifies against the director.
assert.match(
job,
/if test -n "\$\{GATE_OVERRIDE_CONFIRMATION\}"; then[\s\S]{0,700}?--no-monitor-state \\\n {14}--expected-selector-generation "\$\{EXPECTED_SELECTOR_GENERATION\}" \\\n {14}--selector-membership-file[\s\S]{0,160}?--wave-index "\$\{WAVE_INDEX\}" \\\n {14}--selector-wave-delta "\$\{SELECTOR_WAVE_DELTA\}" --retry-freshness/
)
// The override is re-validated here, not trusted from the caller, and it is
// bound to the digest this wave installs.
assert.match(
job,
/test "\$\{GATE_OVERRIDE_CONFIRMATION\}" = \\\n {14}"SKIP_RELAY_MONITOR_GATE \$\{TARGET_IMAGE_DIGEST\}"/
)
assert.match(job, /\[\[ "\$\{GATE_OVERRIDE_REASON\}" =~ \^\[\[:print:\]\]\{12,500\}\$ \]\]/)
// Exactly the aggregate-evidence steps are skipped, and only them: every step
// that reads or spends the sealed monitor artifact carries the override guard.
const overrideSkipped = [
'Require fresh aggregate monitor evidence reference',
'Download private aggregate monitor evidence',
'Verify monitor evidence provenance',
"Download this wave's single-use safety authority",
'Require safety evidence consumed by this workflow'
]
for (const name of overrideSkipped) {
assert.match(
job,
new RegExp(`- name: ${name}\\n {8}if: \\$\\{\\{ inputs\\.mode != 'verify' && inputs\\.gate-override-confirmation == '' \\}\\}`)
)
}
assert.equal(
job.match(/inputs\.gate-override-confirmation == ''/g).length,
overrideSkipped.length
)
// The wrapper validates the override before anything runs, passes it to every
// cell, seals it into the canary artifact, and prints it in the run summary.
assert.match(wrapper, /--gate-override-reason "\$\{GATE_OVERRIDE_REASON\}" \\\n {12}--gate-override-confirmation "\$\{GATE_OVERRIDE_CONFIRMATION\}"\)/)
// One per cell job in the serial cell_1..cell_10 chain.
assert.equal(
wrapper.match(/gate-override-confirmation: \$\{\{ inputs\.gate-override-confirmation \}\}/g).length,
10
)
assert.match(wrapper, /Aggregate monitor gate overridden \(break-glass\)/)
assert.match(wrapper, /ACTOR: \$\{\{ github\.actor \}\}/)
for (const name of [
'Reject previously consumed aggregate safety evidence',
'Consume aggregate safety evidence for this exact wave'
]) {
assert.match(
wrapper,
new RegExp(`- name: ${name}\\n {8}if: \\$\\{\\{ inputs\\.mode != 'verify' && inputs\\.gate-override-confirmation == '' \\}\\}`)
)
}
assert.match(job, /timeout-minutes: 75/)
// Both age gates step by the cell job timeout above; the constant is
// duplicated across the two languages, so pin each copy to it.
for (const source of [
'../../dev/scripts/relay-monitor-evidence.mjs',
'../../apps/relay-ops/src/incident-live-preflight-cli.ts'
]) {
const body = readFileSync(fileURLToPath(new URL(source, import.meta.url)), 'utf8')
assert.match(body, /WAVE_PREDECESSOR_TIMEOUT_MS = 75 \* 60_000/)
assert.match(body, /\^\[0-9\]\$/)
}
// Aged-evidence replay via job re-runs is fenced: mutations are
// single-dispatch, so a failed cell needs a fresh gate and monitor run.
assert.match(job, /test "\$\{GITHUB_RUN_ATTEMPT\}" = 1/)
for (const index of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) {
assert.match(wrapper, new RegExp(`wave-index: '${index}'`))
}
assert.doesNotMatch(job, /EFFECTIVE_SELECTOR_GENERATION \+ 1\)/)
assert.doesNotMatch(job, /EFFECTIVE_SELECTOR_GENERATION \+ 2\)/)
assert.match(job, /\$region == "us-central1" and \$protocol == 0 and [.]region == null/)
assert.match(job, /[.]regionalRehomeProtocol \/\/ 0/)
assert.match(job, /runtime predecessor normalized legacy fields=/)
assert.match(job, /probe-relay-rehome-trust[.]mjs/)
assert.doesNotMatch(job, /service_account: \$\{\{ vars\.PRODUCTION_GCP_RELAY_(?:DIRECTOR_)?RUNTIME_SERVICE_ACCOUNT/)
assert.doesNotMatch(job, /roles\/iam\.serviceAccountTokenCreator/)
})
// Why: the same-cap caller defines release_lease itself, and a caller-defined job presents the
// caller as job_workflow_ref, so the pair must admit the caller alongside its reusable job.
test('shared deploy WIF admits the exact same-cap reusable workflow pair and the caller itself', () => {
const terraform = readFileSync(
fileURLToPath(new URL('../../infra/terraform/relay-github-actions.tf', import.meta.url)),
'utf8'
)
const providerStart = terraform.indexOf(
'resource "google_iam_workload_identity_pool_provider" "github"'
)
const providerEnd = terraform.indexOf('\nresource "', providerStart + 1)
const sharedProvider = terraform.slice(providerStart, providerEnd)
assert.ok(providerStart >= 0 && providerEnd > providerStart)
assert.match(sharedProvider, /local\.relay_github_workflow_conditions\["github"\]/)
// The pairing itself now lives in the clause the provider renders, once per accepted repository.
assert.match(
terraform,
/assertion\.workflow_ref == '\$\{prefix\}\$\{local\.github_production_relay_same_cap_workflow_file\}@refs\/heads\/main' && \(assertion\.job_workflow_ref == '\$\{prefix\}\$\{local\.github_production_relay_same_cap_job_workflow_file\}@refs\/heads\/main' \|\| assertion\.job_workflow_ref == '\$\{prefix\}\$\{local\.github_production_relay_same_cap_workflow_file\}@refs\/heads\/main'\)/
)
})
test('pause and disable precede optional installation and cloud diagnostics', () => {
const job = workflow('operate-relay-production-rehome-job.yml')
const emergency = job.indexOf('Apply emergency durable pause or disable before diagnostics')
const install = job.indexOf('pnpm install --frozen-lockfile')
const revision = job.indexOf('Verify exact serving and rollback director identities')
assert.ok(emergency > 0)
assert.ok(emergency < install)
assert.ok(emergency < revision)
assert.match(job, /inputs\.mode == 'pause' \|\| inputs\.mode == 'disable'/)
assert.match(job, /Seal 24-hour aggregate region observation evidence/)
assert.match(job, /--freshness=25h --limit=30000/)
assert.match(job, /relay-region-observation-\$\{\{ github\.run_id \}\}-\$\{\{ github\.run_attempt \}\}/)
assert.match(job, /test "\$\{RATE_PER_MINUTE\}" = 10/)
})
test('a failed enable independently restores and verifies durable disabled state', () => {
const job = workflow('operate-relay-production-rehome-job.yml')
const enable = job.indexOf('Apply exact durable regional rehome enable')
const evidence = job.indexOf('Read fresh aggregate completion and abort evidence')
const summary = job.indexOf('Publish aggregate control evidence')
const recovery = job.indexOf('Fail closed after an unsuccessful enable run')
assert.ok(enable > 0 && enable < evidence && evidence < summary && summary < recovery)
const recoveryStep = job.slice(recovery)
assert.match(
recoveryStep,
/failure\(\) && inputs\.mode == 'enable' && steps\.google-auth\.outcome == 'success'/
)
assert.match(recoveryStep, /--mode recover-enable/)
assert.match(recoveryStep, /--expected-control-generation "\$\{EXPECTED_CONTROL_GENERATION\}"/)
assert.match(recoveryStep, /RECOVER_FAILED_REGIONAL_REHOME_ENABLE/)
assert.match(recoveryStep, /\.control\.enabled == false/)
assert.doesNotMatch(recoveryStep, /gcloud|pnpm/)
})
test('director rollout has a strict one-time identity bootstrap', () => {
const workflowBody = workflow('deploy-relay-production-director.yml')
const script = readFileSync(
fileURLToPath(new URL('./deploy-relay-blue-green.mjs', import.meta.url)),
'utf8'
)
assert.match(workflowBody, /BOOTSTRAP_RELAY_DIRECTOR_REHOME_IDENTITY/)
assert.match(workflowBody, /--predecessor-runtime-service-account/)
assert.match(workflowBody, /--expected-rehome-generation/)
assert.match(script, /args\.push\('--service-account', config\['runtime-service-account'\]\)/)
assert.match(script, /director predecessor runtime service account does not match/)
const candidateProof = script.indexOf('await verifyRehomeDisabled(candidate.origin)')
const trafficMove = script.indexOf('operations.updateTraffic(config, [`--to-tags=')
assert.ok(candidateProof > 0 && candidateProof < trafficMove)
assert.equal(script.indexOf('verifyRehomeDisabled', trafficMove), -1)
})
test('rehome job pipes every control result through tee under pipefail', () => {
const job = workflow('operate-relay-production-rehome-job.yml')
// Without `shell: bash` the step exit code is tee's, so a thrown inspect/apply passes green.
assert.match(job, /defaults:\n run:\n(?: #.*\n)* shell: bash\n/)
assert.ok((job.match(/\| tee "\$\{RUNNER_TEMP\}/g) ?? []).length >= 5)
})
@@ -13,7 +13,6 @@ import { readRelayWorkflow } from './relay-repository.mjs'
import { validateCapacityPlan } from './validate-relay-capacity-plan.mjs'
const workflow = readRelayWorkflow('deploy-relay-production-same-cap-job.yml')
const capacityWorkflow = readRelayWorkflow('deploy-relay-production-capacity-job.yml')
const production = readFileSync(
new URL('../../infra/terraform/environments/production.tfvars', import.meta.url),
'utf8'
@@ -295,74 +294,10 @@ describe('same-cap roll scripts accept every same-cap cell', () => {
assert.equal(resolveCellShape('production-gce-c31').status, 1)
})
it('passes the same-cap allowlist on every canary invocation the job runs', () => {
const invocations = workflow.split('prepare-relay-production-capacity-canary.mjs').slice(1)
assert.equal(invocations.length, 4)
for (const invocation of invocations) {
const lines = invocation.split('\n')
const end = lines.findIndex((line) => !line.endsWith('\\'))
const call = lines.slice(0, end + 1).join(' ')
assert.match(call, /--approved-cells same-cap/)
// The restore call picks its mode from the cell's entry admission class.
assert.match(call, /--mode (isolate|drain|activate|"\$\{RESTORE_MODE\}")/)
}
})
it('paces the drain it sends to the selected cell', () => {
const drain = workflow.split('--mode drain')[1] ?? ''
assert.match(drain.split('\n').slice(0, 2).join(' '), /--pace-window-ms "\$\{DRAIN_PACE_WINDOW_MS\}"/)
// 5 min is the cell's DRAIN_PACE_WINDOW_MAX_MS; a 2,700-host cell at 2 min overruns the director's sticky lane.
assert.match(workflow, /DRAIN_PACE_WINDOW_MS: '300000'/)
// The transition wait has to outlast the pacing window on top of the leases it waits on.
assert.match(workflow, /--activity restart-safe[\s\S]*?--timeout-ms 1200000/)
})
it('passes this cell\'s rehome protocol and pool on every plan validation the job runs', () => {
const invocations = workflow.split('validate-relay-capacity-plan.mjs').slice(1)
assert.equal(invocations.length, 2)
for (const invocation of invocations) {
const lines = invocation.split('\n')
const end = lines.findIndex((line) => !line.trimEnd().endsWith('\\'))
const call = lines.slice(0, end + 1).join(' ')
assert.match(call, /--mode same-cap-cell/)
assert.match(call, /--regional-rehome-protocol "\$\{DESIRED_REHOME_PROTOCOL\}"/)
assert.match(call, /"\$\{POOL_ARGUMENTS\[@\]\}"/)
}
// Each of those steps must build the flag from the resolved pool, and only when there is one.
const builders = workflow.split(
'if test -n "${EXPECTED_DATABASE_POOL_MAX}"; then\n' +
' POOL_ARGUMENTS=(--database-pool-max "${EXPECTED_DATABASE_POOL_MAX}")'
)
assert.equal(builders.length, 3)
assert.equal(workflow.split('POOL_ARGUMENTS=()').length, 3)
})
// One cell's compute path and nothing else: the template and the MIG bound to it. The cell
// backend service stays out because the capacity role has no compute.backendServices.update,
// so naming it fails the apply after the MIG has already rolled.
it('targets exactly this cell template and MIG on every plan the job runs', () => {
const plans = workflow.split('terraform -chdir=infra/terraform plan').slice(1)
assert.equal(plans.length, 2)
for (const plan of plans) {
const lines = plan.split('\n')
const end = lines.findIndex((line) => !line.trimEnd().endsWith('\\'))
const call = lines.slice(0, end + 1).join('\n')
assert.deepEqual(
[...call.matchAll(/-target=([\w.]+)\[\\"\$\{TARGET_CELL_ID\}\\"\]/g)]
.map(([, resource]) => resource),
[
'google_compute_instance_template.relay_gce_cell',
'google_compute_instance_group_manager.relay_gce_cell'
]
)
// Any target that is not one of those two, or not scoped to this cell, fails here.
assert.equal(call.split('-target=').length, 3)
}
})
it('never names a backend service on any plan or apply in the job', () => {
assert.equal(workflow.includes('google_compute_backend_service'), false)
})
it('validates a correct plan for every wave cell at that cell\'s rehome protocol', () => {
const trusted = SAME_CAP_CELLS.filter((cell) => REHOME_SOURCE_CELLS.has(cell))
@@ -569,25 +504,6 @@ describe('same-cap roll scripts accept every same-cap cell', () => {
)
})
it('pins the capacity identity on every plan validation the job runs', () => {
const invocations = workflow.split('validate-relay-capacity-plan.mjs').slice(1)
assert.equal(invocations.length, 2)
for (const invocation of invocations) {
const lines = invocation.split('\n')
const end = lines.findIndex((line) => !line.trimEnd().endsWith('\\'))
assert.match(
lines.slice(0, end + 1).join(' '),
/--capacity-service-account "\$\{CAPACITY_SERVICE_ACCOUNT\}"/
)
}
// Both steps must read it from the same repository variable the job already requires.
assert.equal(
workflow.split(
'CAPACITY_SERVICE_ACCOUNT: ${{ vars.PRODUCTION_GCP_RELAY_CAPACITY_SERVICE_ACCOUNT }}'
).length,
4
)
})
it('decides the predecessor draining rule from the real block, for both classes', () => {
// A zero-host cell sheds nothing, and a failed canary's own drain leaves the flag set
@@ -777,29 +693,5 @@ describe('same-cap roll scripts accept every same-cap cell', () => {
assert.equal(rolls('resume', { changes: 0 }), 'no-replace')
assert.equal(rolls('none', { changes: 0 }), 'no-replace')
})
it('waits on the image a stranded cell actually serves', () => {
const isolate = workflow
.split('name: Reversibly isolate and drain only the selected cell')[1]
.split('\n - id:')[0]
assert.match(isolate, /--expected-image-digests "\$\{PREDECESSOR_IMAGE_DIGEST\}"/)
// A stranded cell has to come back on a new process, which is what clears the drain.
const after = workflow
.split('name: Verify new incarnation, exact image, protocol, and durable safety')[1]
.split('\n - name:')[0]
assert.match(after, /test "\$\{TARGET_INCARNATION\}" != "\$\{SOURCE_INCARNATION\}"/)
assert.match(after, /if test "\$\{ROLLBACK_RESUME\}" = true; then/)
})
it('leaves the US-only capacity job on the default allowlist', () => {
assert.doesNotMatch(capacityWorkflow, /--approved-cells/)
})
})
// Both trusted versions must prove the same authenticated drain boundary.
it('proves rehome trust for protocol 3 on forward and rollback rolls', () => {
const step = workflow.split('name: Prove exact per-host trust and idempotent no-neighbor behavior')[1].split('\n - name:')[0]
assert.match(step, /inputs\.rollback-rehome-protocol != '0'/)
assert.match(step, /inputs\.target-rehome-protocol != '0'/)
assert.match(step, /probe-relay-rehome-trust\.mjs/)
})
+5 -5
View File
@@ -23,7 +23,7 @@ edit plus a second set of Apple credentials.
| Cloud Run service | `orca-cloud-push` | `push_cloud_run_service_name` |
| Region | `us-central1` | `region` |
| Instances | min 1, max 2 | `push_min_instances`, `push_max_instances` |
| Database pool | 2 per instance | `push_database_pool_max` |
| Database pool | 6 per instance | `push_database_pool_max` |
| Concurrency | 80 | `push_concurrency` |
| Ingress | all | `INGRESS_TRAFFIC_ALL` |
| Invoker | IAM disabled | `invoker_iam_disabled = true` on the service |
@@ -36,8 +36,8 @@ cold start delays a notification past the point where it is worth showing, so th
keeps a notification prompt. The
ceiling is a different question, answered below.
Push uses its approved dedicated two-vCPU HA database. Two instances with a two-connection
pool draw four connections; three simultaneous revision resources draw twelve. Tagged
Push uses its approved dedicated two-vCPU HA database. Two instances with a six-connection
pool draw twelve connections; three simultaneous revision resources draw thirty-six. Tagged
candidates can run outside the service-wide cap, so Terraform bounds instances × pool × 3
at 64 connections, leaving dedicated capacity for maintenance and operators. Increase pool
sizes only after measuring contention. The shared Relay budget excludes push entirely.
@@ -57,7 +57,7 @@ Set on the container by Terraform:
| `ORCA_PUSH_PUBLIC_URL` | `push_base_url` |
| `ORCA_PUSH_FCM_PROJECT_ID` | `project_id` (required for standalone runtime) |
| `ORCA_PUSH_DATABASE_URL` | Secret `orca-cloud-push-dedicated-database-url`, pinned version |
| `ORCA_PUSH_DATABASE_POOL_MAX` | `push_database_pool_max`, 2 per instance |
| `ORCA_PUSH_DATABASE_POOL_MAX` | `push_database_pool_max`, 6 per instance |
| `ORCA_PUSH_APNS_KEY` | Secret `orca-cloud-push-apns-key`, version `latest` |
| `ORCA_PUSH_APNS_KEY_ID` | Secret `orca-cloud-push-apns-key-id`, version `latest` |
| `ORCA_PUSH_APPLE_TEAM_ID` | Secret `orca-cloud-push-apple-team-id`, version `latest` |
@@ -180,7 +180,7 @@ runtime identity with a validate-only FCM request. Cloud Run rejects deletion of
created revision even when it has no tag or traffic. Activation therefore creates a successor
before removing the validation tag and deleting validation. The dedicated 64-connection budget
reserves three simultaneous revision pools: serving, validation/rejected,
and active/recovery successor (12 configured pool connections at the current two-by-two shape).
and active/recovery successor (36 configured pool connections at the current two-by-six shape).
Revision deletion is not proof of physical SQL session drain; verify termination and SQL sessions
in controlled rollout acceptance. There is no shutdown sleep used as a drain gate.
@@ -444,4 +444,5 @@ push_gateway_enabled = true
push_base_url = "https://push.onorca.dev"
# Dedicated push pools allow three revision resources during validation and recovery.
push_max_instances = 2
push_database_pool_max = 6
manage_push_domain_mapping = true
+2 -2
View File
@@ -20,8 +20,8 @@
"load:relay:model": "node dev/scripts/run-relay-load-model.mjs",
"load:relay:recovery-gate": "node dev/scripts/run-relay-recovery-wave-gate.mjs",
"ops:relay": "pnpm --filter @orca-cloud/relay-ops dev",
"pretest": "node --test dev/scripts/capture-terraform-plan-baseline.test.mjs dev/scripts/operate-relay-asia-admission.test.mjs dev/scripts/prepare-relay-asia-director-cells.test.mjs dev/scripts/prepare-relay-asia-topology-input.test.mjs dev/scripts/production-cloud-sql-rollout-lock.test.mjs dev/scripts/read-relay-serving-regional-placement-version.test.mjs dev/scripts/relay-asia-admission-workflow.test.mjs dev/scripts/relay-asia-rollout-evidence.test.mjs dev/scripts/relay-asia-topology-workflow.test.mjs dev/scripts/relay-cloud-sql-connection-budget.test.mjs dev/scripts/relay-load-reader-evidence.test.mjs dev/scripts/relay-lock-contention-alerts.test.mjs dev/scripts/relay-region-hint-metrics.test.mjs dev/scripts/relay-staging-deploy-identity.test.mjs dev/scripts/sanitize-relay-asia-admission-result.test.mjs dev/scripts/terraform-root-partition.test.mjs dev/scripts/validate-relay-asia-topology-plan.test.mjs ../.github/actions/cloud-sql-rollout-lease/action-contract.test.mjs ../.github/actions/cloud-sql-rollout-lease/storage-lease.test.mjs",
"test": "pnpm -r test && node --test dev/scripts/classify-relay-production-capacity-director.test.mjs dev/scripts/classify-relay-staging-bootstrap.test.mjs dev/scripts/deploy-relay-blue-green.test.mjs dev/scripts/deploy-relay-gce-candidate.test.mjs dev/scripts/deploy-relay-gce-multi-target.test.mjs dev/scripts/github-smoke-token.test.mjs dev/scripts/infra.test.mjs dev/scripts/operate-relay-regional-rehome.test.mjs dev/scripts/power-staging-relay.test.mjs dev/scripts/prepare-relay-capacity-canary.test.mjs dev/scripts/prepare-relay-production-capacity-canary.test.mjs dev/scripts/probe-relay-legacy-admission.test.mjs dev/scripts/probe-relay-rehome-trust.test.mjs dev/scripts/production-cell-image-digest-consistency.test.mjs dev/scripts/push-gateway-workflow.test.mjs dev/scripts/push-gateway-recovery.test.mjs dev/scripts/push-validation-workflow.test.mjs dev/scripts/read-relay-production-capacity-identity.test.mjs dev/scripts/relay-admin-endpoint-retry-workflow.test.mjs dev/scripts/relay-admin-transient-retry.test.mjs dev/scripts/relay-admission-selector.test.mjs dev/scripts/relay-gce-terraform-fence.test.mjs dev/scripts/relay-load-connection-failure.test.mjs dev/scripts/relay-load-control-peer.test.mjs dev/scripts/relay-load-director-capacity-gate.test.mjs dev/scripts/relay-load-model.test.mjs dev/scripts/relay-load-phase-barrier.test.mjs dev/scripts/relay-load-placement-boundary.test.mjs dev/scripts/relay-load-profile.test.mjs dev/scripts/relay-load-rebind-boundary.test.mjs dev/scripts/relay-load-region-behavior.test.mjs dev/scripts/relay-load-request-unit-boundary.test.mjs dev/scripts/relay-load-run-lifecycle.test.mjs dev/scripts/relay-monitor-evidence.test.mjs dev/scripts/relay-production-capacity-wave.test.mjs dev/scripts/relay-production-capacity-workflow.test.mjs dev/scripts/relay-production-identity-boundaries.test.mjs dev/scripts/relay-production-same-cap-wave.test.mjs dev/scripts/relay-public-workflow-contract.test.mjs dev/scripts/relay-recovery-wave-gate.test.mjs dev/scripts/relay-region-observation-evidence.test.mjs dev/scripts/relay-regional-rehome-workflow.test.mjs dev/scripts/relay-rehome-aggregate-evidence.test.mjs dev/scripts/relay-repository.test.mjs dev/scripts/relay-same-cap-script-census.test.mjs dev/scripts/relay-same-cap-shadow-gate.test.mjs dev/scripts/relay-staging-c4-refresh-workflow.test.mjs dev/scripts/relay-staging-capacity-identity.test.mjs dev/scripts/staging-relay-apply-guard.test.mjs dev/scripts/validate-relay-capacity-plan.test.mjs dev/scripts/verify-relay-capacity-transition.test.mjs dev/scripts/verify-relay-legacy-bootstrap.test.mjs dev/scripts/workload-identity-attribute-conditions.test.mjs",
"pretest": "node --test dev/scripts/capture-terraform-plan-baseline.test.mjs dev/scripts/operate-relay-asia-admission.test.mjs dev/scripts/prepare-relay-asia-director-cells.test.mjs dev/scripts/prepare-relay-asia-topology-input.test.mjs dev/scripts/production-cloud-sql-rollout-lock.test.mjs dev/scripts/read-relay-serving-regional-placement-version.test.mjs dev/scripts/relay-asia-rollout-evidence.test.mjs dev/scripts/relay-asia-topology-workflow.test.mjs dev/scripts/relay-cloud-sql-connection-budget.test.mjs dev/scripts/relay-load-reader-evidence.test.mjs dev/scripts/relay-lock-contention-alerts.test.mjs dev/scripts/relay-region-hint-metrics.test.mjs dev/scripts/relay-staging-deploy-identity.test.mjs dev/scripts/sanitize-relay-asia-admission-result.test.mjs dev/scripts/terraform-root-partition.test.mjs dev/scripts/validate-relay-asia-topology-plan.test.mjs ../.github/actions/cloud-sql-rollout-lease/action-contract.test.mjs ../.github/actions/cloud-sql-rollout-lease/storage-lease.test.mjs",
"test": "pnpm -r test && node --test dev/scripts/classify-relay-production-capacity-director.test.mjs dev/scripts/classify-relay-staging-bootstrap.test.mjs dev/scripts/deploy-relay-blue-green.test.mjs dev/scripts/deploy-relay-gce-candidate.test.mjs dev/scripts/deploy-relay-gce-multi-target.test.mjs dev/scripts/github-smoke-token.test.mjs dev/scripts/infra.test.mjs dev/scripts/operate-relay-regional-rehome.test.mjs dev/scripts/power-staging-relay.test.mjs dev/scripts/prepare-relay-capacity-canary.test.mjs dev/scripts/prepare-relay-production-capacity-canary.test.mjs dev/scripts/probe-relay-legacy-admission.test.mjs dev/scripts/probe-relay-rehome-trust.test.mjs dev/scripts/production-cell-image-digest-consistency.test.mjs dev/scripts/push-gateway-workflow.test.mjs dev/scripts/push-gateway-recovery.test.mjs dev/scripts/read-relay-production-capacity-identity.test.mjs dev/scripts/relay-admin-endpoint-retry-workflow.test.mjs dev/scripts/relay-admin-transient-retry.test.mjs dev/scripts/relay-admission-selector.test.mjs dev/scripts/relay-gce-terraform-fence.test.mjs dev/scripts/relay-load-connection-failure.test.mjs dev/scripts/relay-load-control-peer.test.mjs dev/scripts/relay-load-director-capacity-gate.test.mjs dev/scripts/relay-load-model.test.mjs dev/scripts/relay-load-phase-barrier.test.mjs dev/scripts/relay-load-placement-boundary.test.mjs dev/scripts/relay-load-profile.test.mjs dev/scripts/relay-load-rebind-boundary.test.mjs dev/scripts/relay-load-region-behavior.test.mjs dev/scripts/relay-load-request-unit-boundary.test.mjs dev/scripts/relay-load-run-lifecycle.test.mjs dev/scripts/relay-monitor-evidence.test.mjs dev/scripts/relay-production-capacity-wave.test.mjs dev/scripts/relay-production-capacity-workflow.test.mjs dev/scripts/relay-production-identity-boundaries.test.mjs dev/scripts/relay-production-same-cap-wave.test.mjs dev/scripts/relay-public-workflow-contract.test.mjs dev/scripts/relay-recovery-wave-gate.test.mjs dev/scripts/relay-region-observation-evidence.test.mjs dev/scripts/relay-rehome-aggregate-evidence.test.mjs dev/scripts/relay-repository.test.mjs dev/scripts/relay-same-cap-script-census.test.mjs dev/scripts/relay-same-cap-shadow-gate.test.mjs dev/scripts/relay-staging-c4-refresh-workflow.test.mjs dev/scripts/relay-staging-capacity-identity.test.mjs dev/scripts/staging-relay-apply-guard.test.mjs dev/scripts/validate-relay-capacity-plan.test.mjs dev/scripts/verify-relay-capacity-transition.test.mjs dev/scripts/verify-relay-legacy-bootstrap.test.mjs dev/scripts/workload-identity-attribute-conditions.test.mjs",
"typecheck": "pnpm -r typecheck"
},
"devDependencies": {
+5
View File
@@ -227,6 +227,11 @@ module.exports = {
// Why: out/electron-dev caches `pnpm dev`'s per-branch Electron.app copies (~270MB each).
// CI never creates it, but packaging on a machine that has run dev would pack them all.
'!out/electron-dev{,/**/*}',
// Why: relayExtraResource already ships out/relay to resources/relay, which is
// the only path a packaged build resolves. Packing it again added 14MB and put
// relay.js inside app.asar, so a script-heuristic verdict on relay.js took the
// whole asar with it as a compound object and gutted the install (#20966, #20972).
'!out/relay{,/**/*}',
'!electron.vite.config.{js,ts,mjs,cjs}',
'!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,CHANGELOG.md,README.md}',
'!{.env,.env.*,.npmrc,pnpm-lock.yaml}',
+4 -5
View File
@@ -71,10 +71,9 @@
//
// In the screenshot annotator a "shape" is the drawn geometry -- pen, arrow, rect,
// ellipse, highlight. A domain noun, and it pervades every symbol in the module.
// mobile/src/test-support/rpc-recording is the golden recorder engine. recorder-digest.ts
// hashes these files' RAW BYTES into every golden's `recorderSha256` header, so any edit
// here -- a rename or even an added comment -- invalidates all 208 recordings. The exemption
// is config-scoped for that reason: an inline directive would change the bytes it protects.
// In mobile/src/test-support/rpc-recording a "shape" is the container a recorded value is
// interned as (list, map or whole) and the structure a recorder fixture must match -- a
// domain noun across the engine, as in the annotator.
{
"files": ["**/test-support/rpc-recording/**"],
"rules": {
@@ -113,7 +112,7 @@
// `shapedSidebar` is a persisted onboarding-checklist field and a telemetry enum member;
// renaming it would orphan saved state.
{
"files": ["**/src/shared/constants.ts", "**/src/shared/onboarding-state-types.ts"],
"files": ["**/src/shared/onboarding-defaults.ts", "**/src/shared/onboarding-state-types.ts"],
"rules": {
"anti-slop/no-shape-in-symbol-names": "off"
}
File diff suppressed because one or more lines are too long
@@ -1,8 +1,21 @@
diff --git a/src/IIPHandler.ts b/src/IIPHandler.ts
index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df64c5bd088 100644
index 559b907416eb38318f439d060d7f89311ed34c7e..73cedbe99f831bffd202697cc8ba80a46a39cb99 100644
--- a/src/IIPHandler.ts
+++ b/src/IIPHandler.ts
@@ -34,6 +34,7 @@ const DEFAULT_HEADER: IHeaderFields = {
@@ -13,8 +13,10 @@ import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
// Local const enum mirror - esbuild can't inline const enums from external packages
const enum DecoderConst {
- // Limit held memory in base64 decoder (encoded bytes).
- KEEP_DATA = 4194304,
+ // Held memory in base64/QOI decoders between images. Zero because each kept
+ // decoder pins a wasm memory, and V8 caps those per process (~124 in a
+ // sandboxed renderer), so idle terminals must not hold one.
+ KEEP_DATA = 0,
// Initial buffer allocation for the decoder.
INITIAL_DATA = 1048576,
// Local mirror of const enum (esbuild can't inline const enums from external packages)
@@ -34,6 +36,7 @@ const DEFAULT_HEADER: IHeaderFields = {
export class IIPHandler implements IOscHandler, IResetHandler {
@@ -10,7 +23,7 @@ index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df6
private _aborted = false;
private _hp = new HeaderParser();
private _header: IHeaderFields = DEFAULT_HEADER;
@@ -55,6 +56,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
@@ -55,6 +58,7 @@ export class IIPHandler implements IOscHandler, IResetHandler {
}
public reset(): void {
@@ -18,7 +31,47 @@ index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df6
this._hp.reset();
this._dec.release();
this._qoiDec.release();
@@ -198,8 +200,13 @@ export class IIPHandler implements IOscHandler, IResetHandler {
@@ -92,7 +96,10 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._aborted = true;
return;
}
- this._dec.init();
+ if (!this._initDecoder()) {
+ this._aborted = true;
+ return;
+ }
} else if (this._abortMulti) {
this._aborted = true;
return;
@@ -135,7 +142,9 @@ export class IIPHandler implements IOscHandler, IResetHandler {
this._isMultipart = true;
this._abortMulti = false;
this._dec.release();
- this._dec.init();
+ if (!this._initDecoder()) {
+ this._abortMulti = true;
+ }
return true;
}
@@ -179,7 +188,15 @@ export class IIPHandler implements IOscHandler, IResetHandler {
let blob: Blob | ImageData;
if (metrics.mime === 'image/qoi') {
- const data = this._qoiDec.decode(this._dec.data8);
+ let data: Uint8Array<ArrayBuffer>;
+ try {
+ data = this._qoiDec.decode(this._dec.data8);
+ } catch (e) {
+ console.warn('IIP: could not decode QOI image', e);
+ this._dec.release();
+ this._qoiDec.release();
+ return true;
+ }
blob = new ImageData(
new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength),
this._qoiDec.width,
@@ -198,8 +215,13 @@ export class IIPHandler implements IOscHandler, IResetHandler {
blob = new Blob([this._dec.data8], { type: metrics.mime });
}
this._dec.release();
@@ -32,6 +85,25 @@ index 559b907416eb38318f439d060d7f89311ed34c7e..8541b4b0ea0d6b451aaae49007d69df6
this._storage.addImage(bm);
return true;
})
@@ -209,6 +231,18 @@ export class IIPHandler implements IOscHandler, IResetHandler {
});
}
+ // Why: wasm memory exhaustion must drop this image, not throw out of the parser and wedge the write queue.
+ private _initDecoder(): boolean {
+ try {
+ this._dec.init();
+ return true;
+ } catch (e) {
+ console.warn('IIP: could not allocate decoder', e);
+ this._dec.release();
+ return false;
+ }
+ }
+
private _resize(w: number, h: number): [number, number] {
const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;
const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;
diff --git a/src/ImageAddon.ts b/src/ImageAddon.ts
index 8fd39543118cd420e36c1614c1af370b6c7bbfbb..0c44d2a81642113417bf8dc10a4faa76d7cc5864 100644
--- a/src/ImageAddon.ts
@@ -144,8 +216,156 @@ index 5854efaec1fdf9dfcb886023542998a563b6d2f2..3afaf9bd63ffd7a4cdf32bf0ac24cf33
}
public get document(): Document | undefined {
diff --git a/src/SixelHandler.ts b/src/SixelHandler.ts
index 1af2d85bcdd541ed60b1e707f6186a91f1bbf1b2..0711a122ea43d5212a2851add9e744b65550ec85 100644
--- a/src/SixelHandler.ts
+++ b/src/SixelHandler.ts
@@ -10,6 +10,7 @@ import { RGBA8888 } from 'sixel/lib/Types';
import { ImageRenderer } from './ImageRenderer';
import { DecoderAsync, Decoder } from 'sixel/lib/Decoder';
+import { LIMITS } from 'sixel/lib/wasm';
// always free decoder ressources after decoding if it exceeds this limit
const MEM_PERMA_LIMIT = 4194304; // 1024 pixels * 1024 pixels * 4 channels = 4MB
@@ -18,48 +19,88 @@ const MEM_PERMA_LIMIT = 4194304; // 1024 pixels * 1024 pixels * 4 channels = 4MB
const DEFAULT_PALETTE = PALETTE_ANSI_256;
DEFAULT_PALETTE.set(PALETTE_VT340_COLOR);
+// Why pooled: every decoder owns a wasm memory, and V8 caps live wasm memories
+// per process (~124 in a sandboxed renderer). Terminals borrow a decoder only
+// while a SIXEL sequence is open, so idle terminals hold none.
+const MAX_IDLE_DECODERS = 2;
+const idleDecoders = new Map<number, Decoder[]>();
+let poolPrimed = false;
+
+function primeDecoderPool(memoryLimit: number): void {
+ if (poolPrimed) return;
+ poolPrimed = true;
+ // Async compile once, off the parser's hot path; later decoders reuse the cached module.
+ DecoderAsync({ memoryLimit, palette: DEFAULT_PALETTE }).then(
+ d => releaseDecoder(d, memoryLimit),
+ () => { poolPrimed = false; }
+ );
+}
+
+function acquireDecoder(memoryLimit: number): Decoder {
+ return idleDecoders.get(memoryLimit)?.pop() ?? new Decoder({ memoryLimit, palette: DEFAULT_PALETTE });
+}
+
+function releaseDecoder(dec: Decoder, memoryLimit: number): void {
+ if (dec.memoryUsage > MEM_PERMA_LIMIT) {
+ dec.release();
+ }
+ const idle = idleDecoders.get(memoryLimit) ?? [];
+ if (idle.length < MAX_IDLE_DECODERS) {
+ idle.push(dec);
+ idleDecoders.set(memoryLimit, idle);
+ }
+}
+
export class SixelHandler implements IDcsHandler, IResetHandler {
private _size = 0;
private _aborted = false;
private _dec: Decoder | undefined;
+ private _decMemoryLimit = 0;
+ // Color registers outlive a single image, so they live here rather than in a pooled decoder.
+ private readonly _palette = new Uint32Array(LIMITS.PALETTE_SIZE);
constructor(
private readonly _opts: IImageAddonOptions,
private readonly _storage: SixelImageStorage,
private readonly _coreTerminal: ITerminalExt
) {
- DecoderAsync({
- memoryLimit: this._opts.pixelLimit * 4,
- palette: DEFAULT_PALETTE,
- paletteLimit: this._opts.sixelPaletteLimit
- }).then(d => this._dec = d);
+ this._palette.set(DEFAULT_PALETTE);
+ primeDecoderPool(this._opts.pixelLimit * 4);
}
public reset(): void {
- /**
- * reset sixel decoder to defaults:
- * - release all memory
- * - nullify palette (4096)
- * - apply default palette (256)
- */
- if (this._dec) {
- this._dec.release();
- // FIXME: missing interface on decoder to nullify full palette
- (this._dec as any)._palette.fill(0);
- this._dec.init(0, DEFAULT_PALETTE, this._opts.sixelPaletteLimit);
- }
+ this._returnDecoder();
+ this._palette.fill(0);
+ this._palette.set(DEFAULT_PALETTE);
}
public hook(params: IParams): void {
this._size = 0;
this._aborted = false;
- if (this._dec) {
- const fillColor = params.params[1] === 1 ? 0 : extractActiveBg(
- this._coreTerminal._core._inputHandler._curAttrData,
- this._coreTerminal._core._themeService?.colors);
- this._dec.init(fillColor, null, this._opts.sixelPaletteLimit);
+ this._returnDecoder();
+ const memoryLimit = this._opts.pixelLimit * 4;
+ try {
+ this._dec = acquireDecoder(memoryLimit);
+ } catch (e) {
+ // Why: exhausting wasm memory must drop this image, not throw out of the parser and wedge the write queue.
+ console.warn(`SIXEL: could not allocate decoder - ${e}`);
+ this._aborted = true;
+ return;
}
+ this._decMemoryLimit = memoryLimit;
+ const fillColor = params.params[1] === 1 ? 0 : extractActiveBg(
+ this._coreTerminal._core._inputHandler._curAttrData,
+ this._coreTerminal._core._themeService?.colors);
+ this._dec.init(fillColor, this._palette, this._opts.sixelPaletteLimit);
+ }
+
+ private _returnDecoder(): void {
+ const dec = this._dec;
+ if (!dec) return;
+ this._dec = undefined;
+ this._palette.set(dec.palette);
+ releaseDecoder(dec, this._decMemoryLimit);
}
public put(data: Uint32Array, start: number, end: number): void {
@@ -83,6 +124,14 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
}
public unhook(success: boolean): boolean | Promise<boolean> {
+ try {
+ return this._unhook(success);
+ } finally {
+ this._returnDecoder();
+ }
+ }
+
+ private _unhook(success: boolean): boolean {
if (this._aborted || !success || !this._dec) {
return true;
}
@@ -100,9 +149,6 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
const canvas = ImageRenderer.createCanvas(undefined, width, height);
canvas.getContext('2d')?.putImageData(new ImageData(this._dec.data8 as Uint8ClampedArray<ArrayBuffer>, width, height), 0, 0);
- if (this._dec.memoryUsage > MEM_PERMA_LIMIT) {
- this._dec.release();
- }
this._storage.addImage(canvas);
return true;
}
diff --git a/src/kitty/KittyGraphicsHandler.ts b/src/kitty/KittyGraphicsHandler.ts
index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1fa320c4f8 100644
index de889dfff75d9ecc8ab47a025e6989ffe75bb202..cf66e5b75c78645b1641e53d1425d88201134f78 100644
--- a/src/kitty/KittyGraphicsHandler.ts
+++ b/src/kitty/KittyGraphicsHandler.ts
@@ -7,6 +7,7 @@ import { IDisposable } from '@xterm/xterm';
@@ -172,10 +392,12 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
this._cleanupAllPending();
if (this._activeDecoder) {
this._activeDecoder.release();
@@ -200,6 +203,25 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -200,8 +203,38 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
this._activeDecoder = pending.decoder;
}
if (!this._activeDecoder) {
- this._activeDecoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);
- this._activeDecoder.init();
+ // Budget WASM capacity, including one page of decoder state and rounding.
+ const decoderCapacity = this._maxEncodedBytes + 131072;
+ if (decoderCapacity > this._opts.storageLimit * 1000000) {
@@ -195,10 +417,23 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
+ this._sendResponse(oldest[1].cmd.id, 'ENOMEM:pending image budget exceeded', oldest[1].cmd.quiet ?? 0);
+ }
+ }
this._activeDecoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);
this._activeDecoder.init();
+ const decoder = new Base64Decoder(Constants.DECODER_KEEP_DATA, this._maxEncodedBytes, this._initialEncodedBytes);
+ try {
+ decoder.init();
+ } catch (e) {
+ // Why: wasm memory exhaustion must drop this image, not throw out of the parser and wedge the write queue.
+ console.warn('KITTY: could not allocate decoder', e);
+ this._aborted = true;
+ if (this._parsedCommand?.id !== undefined) {
+ this._sendResponse(this._parsedCommand.id, 'ENOMEM:could not allocate decoder', this._parsedCommand.quiet ?? 0);
+ }
+ return;
+ }
+ this._activeDecoder = decoder;
}
@@ -550,9 +572,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
if (this._activeDecoder.put(data.subarray(start, end)) !== DECODER_OK) {
@@ -550,9 +583,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
}
private async _decodeAndDisplay(image: IKittyImageData, cmd: IKittyCommand): Promise<void> {
@@ -210,7 +445,7 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
const cropX = Math.max(0, cmd.x ?? 0);
const cropY = Math.max(0, cmd.y ?? 0);
const cropW = cmd.sourceWidth || (bitmap.width - cropX);
@@ -660,6 +684,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -660,6 +695,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
}
}
@@ -218,7 +453,7 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
const zIndex = cmd.zIndex ?? 0;
this._kittyStorage.addImage(image.id, bitmap, true, layer, zIndex);
bitmap = undefined; // ownership transferred to storage
@@ -693,6 +718,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -693,6 +729,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
}
if (image.format === KittyFormat.PNG) {
@@ -231,7 +466,7 @@ index de889dfff75d9ecc8ab47a025e6989ffe75bb202..54ebea9c061e5bb92b187cab7a53bc1f
const blob = new Blob([bytes as BlobPart], { type: 'image/png' });
if (!window.createImageBitmap) {
const url = URL.createObjectURL(blob);
@@ -775,27 +806,45 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
@@ -775,27 +817,45 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
private async _decompressZlib(compressed: Uint8Array): Promise<Uint8Array> {
try {
return await this._decompress(compressed, 'deflate');
+103 -116
View File
@@ -340,114 +340,145 @@
"demotionRule": "Keep experimental or demote if output differs, discarded bytes return, settled capture remains reachable, cancellation loses its tail, or the deterministic encoding budget regresses. Never weaken the oracle or increase retries to obtain a pass."
},
{
"id": "agent-session.trust-preflight-completion",
"title": "Agent launch preflight waits for its trust owner to settle",
"id": "agent-session.spawn-workspace-trust",
"title": "Agent PTY spawns wait for a bounded workspace trust write",
"maturity": "experimental",
"protection": "partial",
"owner": "agent-session-runtime",
"layer": "cross-layer-unit",
"surfaces": [
"agent trust IPC",
"remote (SSH) agent workspace trust writer",
"renderer agent preflight",
"Codex continuation launch admission",
"main-process worktree startup trust",
"Codex launch and resume preparation"
"renderer PTY spawn builder",
"runtime PTY spawn builder",
"structured Codex chat create intent",
"agent workspace trust dispatcher",
"execution-host workspace trust writer (main and SSH relay)",
"SSH relay agent workspace trust"
],
"platforms": ["macos", "linux", "windows"],
"providers": ["local", "daemon", "ssh", "wsl", "remote-runtime"],
"coveredPlatforms": ["macos"],
"coveredProviders": ["local", "ssh"],
"coverageNotes": "Actual main IPC handler, renderer preflight, session-continuation caller and config mutation queue with fake Electron bridge, config writer, launch and remote owner, plus the actual main-process worktree-startup, Codex quick-launch-prep and Codex resume-prep modules with mocked trust writer, runtime home and hook service. Counts prove admission ordering and the per-call trust-write bound only; no native PTY/CLI, actual config mutation or live provider evidence. Because codexHookService and ensureRealHomeCodexHookState are both mocked and isHostSystemDefaultRealHomeSelected defaults to false, no suite drives either config.toml lane the two launch-prep sites re-enter after the trust write, so nothing here speaks to whether a launch as a whole is bounded. The remote-preset suite calls markRemoteAgentWorkspaceTrusted directly and asserts its artifact shapes plus its unresolvable-home bail-out; nothing exercises markRemoteWorktreeTrusted, the unbounded caller that wraps it on the launch paths, so the SSH side of the bound is unmeasured here.",
"motivatingLinks": ["https://github.com/stablyai/orca/blob/main/src/main/ipc/agent-trust.ts"],
"invariant": "Every local Codex trust write a launch waits on - the five markCodexProjectTrusted call sites - settles before that launch proceeds, or is abandoned at a bounded deadline; rejected, abandoned and SSH-unreachable writes leave the workspace untrusted and never fall back to local trust. Two limits sit outside that claim and are not asserted. First, the deadline bounds the write, not the whole launch: a caller whose next step re-enters the same config.toml lane can still wait on that lane after its own write is abandoned. Second, one Codex trust write a launch waits on is not bounded at all - the remote write reached through markRemoteWorktreeTrusted in runtime/runtime-worktree-agent-startup.ts, which derives preset 'codex' from TUI_AGENT_CONFIG the same way the local path does and is awaited ahead of the agent spawn on every SSH launch path. Only the agentTrust:markTrusted handler's remote write is capped.",
"oracle": "Held local write or actual queue predecessor yields zero continuation launch calls until release, then exactly one; delayed local rejection settles successfully afterward. SSH awaits its remote writer and never invokes local trust. A never-settling local or SSH write resolves the handler at the deadline with a named warning, no leaked timer and no local fallback, and a rejection arriving after the deadline stays handled. An already-complete write admits the launch on microtasks with zero timers. Two concurrent launches for one workspace settle independently. In the main-process worktree-startup, Codex quick-launch-prep and Codex resume-prep modules a never-settling write resolves the caller at the deadline, still yields the launch home or resume outcome, leaks no timer, and writes no other preset artifact; the write stays ordered before the runtime-home and hook-repair steps the spawn waits on. Those three module cases mock codexHookService, so they establish that the write is abandoned at the deadline, not that the launch around it is bounded. No case drives markRemoteWorktreeTrusted, so nothing here bounds the remote Codex write that fronts an SSH launch; the remote-preset suite checks only what markRemoteAgentWorkspaceTrusted writes and when it bails out, never how long it may take.",
"coverageNotes": "The actual renderer and runtime spawn-option builders with a mocked dispatcher, the spawn hook with a mocked dispatcher, the structured Codex create intent with a mocked dispatcher, the dispatcher with mocked preset writers and the real Claude config writer on a temp file, the execution-host writer and the relay spawn function with every real preset writer under a throwaway home, and the relay pty.spawn handler with a mocked trust function. Proves the builders await trust before returning, the fresh-launch and setting gates, the per-preset deadlines, that SSH launches hand every preset to the relay, and, for the agents that inherit trust from a home (Claude, Copilot, Qoder), the refusal of any stored path that is a root, a home or a folder above one, while Codex, Cursor and Antigravity still trust a home. No native PTY, agent CLI, live SSH host or WSL guest.",
"motivatingLinks": [
"https://github.com/stablyai/orca/blob/main/src/main/agent-workspace-trust.ts"
],
"invariant": "A fresh PTY launch of an agent with a trust preset pre-trusts its workspace root (for Codex, whose lookup keys on its start folder, the folder it starts in, including a floating terminal's) before the provider spawn, in both spawn builders, on the host that runs the agent: main for local and WSL launches, the SSH relay on its own disk for SSH launches. It never waits past the preset's deadline: 20 s for local Codex, the short budget for every other write, local or on the relay. A failed or abandoned write lets the launch proceed untrusted, so the agent asks. Restored or reattached panes, spawns with no launch command and the setting turned off are never trusted; for an agent whose trust on a home covers the folders below it (Claude, Copilot, Qoder), no writer stores a path that is a root, a home or a folder above one, and an unknown home writes nothing; Codex, Cursor and Antigravity, whose trust on a home covers only the home, trust it as they did before; and a WSL launch never writes the Windows home. A structured Codex chat, which has no PTY, pre-trusts its folder the same way when it is created, under the same setting.",
"oracle": "A trust write held pending keeps each builder from returning until it settles. A never-settling local Codex write resolves the dispatcher only after the long deadline; a never-settling local Claude write resolves after the short budget with a named warning. Rejected and throwing writes, and a throwing guard, resolve with a warning. Restored panes, spawns without a command and the setting off make no dispatcher call; Codex in a floating terminal or a workspace subfolder is trusted at that folder, and every other preset at the workspace root. Creating a structured Codex chat trusts its folder before launch preparation, and nothing with the setting off. An SSH launch returns the relay field for every preset and calls no writer, and the relay's pty.spawn starts no process until that launch's trust call settles. A home, a folder above one, a root, a symlink to a home and a missing path that climbs back to the home through `..` write no Claude, Copilot or Qoder trust file, on this machine and on the relay, and the home writes Codex, Cursor and Antigravity trust.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts tests/e2e/agent-trust-completion.unit.test.ts src/renderer/src/lib/agent-trust-preflight.test.ts src/renderer/src/lib/launch-agent-session-continuation.test.ts src/main/remote-agent-trust-presets.test.ts src/main/startup/codex-launch-trust-write-deadline.test.ts src/main/runtime/runtime-worktree-agent-startup.test.ts"
"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts src/main/agent-workspace-trust-spawn.test.ts src/main/agent-workspace-trust.test.ts src/main/execution-host-workspace-trust.test.ts src/relay/agent-workspace-trust-spawn.test.ts src/relay/agent-workspace-trust-spawn-guard-failure.test.ts src/relay/pty-handler-agent-workspace-trust.test.ts src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts"
],
"testFiles": [
"tests/e2e/agent-trust-completion.unit.test.ts",
"src/renderer/src/lib/agent-trust-preflight.test.ts",
"src/renderer/src/lib/launch-agent-session-continuation.test.ts",
"src/main/remote-agent-trust-presets.test.ts",
"src/main/startup/codex-launch-trust-write-deadline.test.ts",
"src/main/runtime/runtime-worktree-agent-startup.test.ts"
"src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts",
"src/main/agent-workspace-trust-spawn.test.ts",
"src/main/agent-workspace-trust.test.ts",
"src/main/execution-host-workspace-trust.test.ts",
"src/relay/agent-workspace-trust-spawn.test.ts",
"src/relay/agent-workspace-trust-spawn-guard-failure.test.ts",
"src/relay/pty-handler-agent-workspace-trust.test.ts",
"src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts"
],
"assertionRefs": [
{
"file": "tests/e2e/agent-trust-completion.unit.test.ts",
"file": "src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts",
"assertions": [
"holds the actual session-continuation launch call until the local write settles",
"keeps renderer preflight pending behind the actual config mutation queue",
"settles best-effort only after a queued local trust failure",
"bounds a never-settling local write and continues untrusted",
"admits an already-complete write without waiting for any timer",
"absorbs a local rejection that arrives after the deadline",
"bounds a never-settling SSH trust write without a local fallback",
"settles two concurrent launches for the same workspace independently",
"keeps remote failures best-effort without a local fallback"
"trusts Codex in a floating terminal at the resolved folder it starts in",
"holds the spawn until the trust write settles",
"never re-runs trust for a restored pane or a spawn with no launch command"
]
},
{
"file": "src/main/startup/codex-launch-trust-write-deadline.test.ts",
"file": "src/main/agent-workspace-trust-spawn.test.ts",
"assertions": [
"bounds a never-settling write in quick-launch prep and still returns the launch home",
"keeps the quick-launch trust write ahead of the home the spawn waits on",
"bounds a never-settling write in resume prep and still resolves the resume home",
"keeps the resume trust write ahead of the hook repair that precedes the spawn",
"contains a rejected resume trust write without trusting the workspace elsewhere"
"does nothing with the setting off",
"does nothing for a restored or reattached pane",
"trusts Codex in a floating terminal at the folder it starts in",
"trusts Codex at a subfolder it starts in, which its lookup keys on"
]
},
{
"file": "src/main/runtime/runtime-worktree-agent-startup.test.ts",
"file": "src/main/agent-workspace-trust.test.ts",
"assertions": [
"waits for the Codex trust write before resolving",
"bounds a never-settling Codex trust write instead of holding the launch open"
"contains a rejected or throwing write so the launch proceeds",
"gives only Codex the long deadline its shared config lane needs",
"gives a local Claude write a short budget, after which Claude asks",
"never pre-trusts %s for an agent that inherits trust from it",
"trusts the home folder for Codex, Cursor and Antigravity, whose trust there stays there",
"hands an SSH %s launch to the relay instead of writing anything here"
]
},
{
"file": "src/main/execution-host-workspace-trust.test.ts",
"assertions": [
"writes no %s trust",
"guards exactly the agents that inherit trust from a home",
"trusts a home folder workspace for %s, whose trust there covers only the home",
"writes no %s trust when the host knows no home"
]
},
{
"file": "src/relay/agent-workspace-trust-spawn.test.ts",
"assertions": [
"writes %s trust on the relay host's own disk",
"trusts the relay home for %s, whose trust there covers only the home",
"leaves Antigravity to ask, since its writer is unverified on SSH hosts"
]
},
{
"file": "src/relay/agent-workspace-trust-spawn-guard-failure.test.ts",
"assertions": ["skips %s trust and never fails the spawn"]
},
{
"file": "src/relay/pty-handler-agent-workspace-trust.test.ts",
"assertions": [
"writes the launch's trust with its final env before the agent's process starts"
]
},
{
"file": "src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts",
"assertions": [
"pre-trusts the chat folder before launch preparation, as a Codex terminal launch does",
"writes nothing with the setting off, and still prepares the launch"
]
}
],
"evidenceRuns": [
{
"date": "2026-09-27",
"date": "2026-09-29",
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts tests/e2e/agent-trust-completion.unit.test.ts src/renderer/src/lib/agent-trust-preflight.test.ts src/renderer/src/lib/launch-agent-session-continuation.test.ts src/main/remote-agent-trust-presets.test.ts src/main/startup/codex-launch-trust-write-deadline.test.ts src/main/runtime/runtime-worktree-agent-startup.test.ts",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty/spawn-options-agent-workspace-trust.test.ts src/main/agent-workspace-trust-spawn.test.ts src/main/agent-workspace-trust.test.ts src/main/execution-host-workspace-trust.test.ts src/relay/agent-workspace-trust-spawn.test.ts src/relay/agent-workspace-trust-spawn-guard-failure.test.ts src/relay/pty-handler-agent-workspace-trust.test.ts src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts",
"result": "passed",
"durationSeconds": 0.413,
"summary": "53 tests across six suites pass: 15 trust-completion (four deadline cases), 8 renderer preflight, 7 continuation-launch, 6 remote-preset, 5 Codex launch/resume prep deadline and 12 worktree-startup tests. Six of those are the bound and ordering cases this candidate adds. No native launch, PTY or actual config write."
"durationSeconds": 4.16,
"summary": "201 tests across eight suites pass, run through node_modules/.bin/vitest with a throwaway HOME. No native launch, PTY or live SSH host."
}
],
"runtimeBudget": {
"p95Seconds": 10,
"scope": "Focused mocked preflight/continuation/remote trust suites; p95 not established."
"scope": "Focused spawn-builder, hook, dispatcher, execution-host writer and relay suites; p95 not established."
},
"flakeHistory": {
"status": "not-started",
"evidence": "Author and independent local mocked evidence; CI soak and native platform evidence pending."
"evidence": "Local mocked evidence only; CI soak pending."
},
"redGreenEvidence": {
"status": "complete",
"evidence": "Trust-completion suite, re-run at the earlier head. Pre-await implementation: 5 fail/10 pass - the three launch-admission orderings plus the never-settling local and SSH cases, the SSH one only by exhausting the test timeout. Unbounded-await implementation: 3 fail/12 pass - never-settling local write, post-deadline rejection and never-settling SSH write all hang to the test timeout. The already-complete-write case passes unbounded too, so it is a timer control, not a red. Launch-prep and worktree-startup suites, this head: with the three call sites reverted to a bare await, 3 fail/14 pass - the quick-launch, resume and worktree-startup never-settling cases each hang to the 30s vitest timeout; restored, 17 pass. Whole gate at this head: 53 pass across six suites."
"evidence": "Each named case failed with its fix removed and passed restored: the builder's await turned into a fire-and-forget call fails the hold case; guarding every preset fails each Codex, Cursor and Antigravity home case, and guarding none fails each Claude, Copilot and Qoder home, root, symlink and `..` case; dropping the unknown-home rule fails every no-home case; the relay deriving only Claude from launchAgent fails each non-Claude relay case; the dispatcher handing only Claude to the relay fails each SSH preset case; dropping the relay's Antigravity return fails its case; letting the host writer's catch rethrow fails every guard-failure case; dropping resolve() from the guard's forms fails the missing `..` path case for every guarded preset; marking Codex as trusted at the workspace root, or a builder dropping the start folder, fails the Codex floating-terminal and subfolder cases; removing the relay handler's trust call, or not awaiting it, fails the relay handler case; removing the structured Codex create intent's trust call fails its pre-trust case, and dropping its setting check fails its setting-off case."
},
"performanceBudget": {
"required": true,
"evidence": "No new subprocess, polling, retry or trust operation. Each of the five markCodexProjectTrusted call sites a launch awaits is capped by one per-call deadline timer, cleared on settlement, so none of those five can hold its caller indefinitely; the five share that one timer shape and no site gained a second write or a retry. The cap covers the local Codex writes only - the remote write through markRemoteWorktreeTrusted is still awaited with no timer, so an SSH launch can still be held open by a trust write. It also bounds the trust write, not the launch. At startup/codex-launch-preparation.ts the first awaited step after the write is ensureRealHomeHooksIfSelected, not codexHookService.prepareRuntimeHomeForLaunch: when the host system-default real home is selected it calls ensureRealHomeCodexHookState, which acquires runExclusivelyForCodexTrustConfig directly on ~/.codex/config.toml, the same lane the abandoned write still holds a queue slot for. When the real home is not selected that call returns without awaiting the lane and runtimeHome.prepareForCodexLaunchAsync is synchronous on this non-WSL path, so the first re-entry is prepareRuntimeHomeForLaunch through runExclusivelyForRuntimeAndSystemTrustConfig. At startup/codex-session-resume-launch.ts the first awaited step is the hook branch: ensureRealHomeCodexHookState when the resume home is ~/.codex, otherwise installForLaunchPrep or refreshRuntimeUserHooksForLaunchPrep through runExclusivelyForRuntimeAndSystemTrustConfig, whose inner acquire is that same ~/.codex/config.toml. Either way a wedged lane still stalls those two launches one step after the write is abandoned, and the earliest such step is the real-home ensure, not the hook-service call the gate used to name. Only markLocalWorktreeTrusted in runtime/runtime-worktree-agent-startup.ts has nothing after its Codex branch, so only it has an end-to-end bounded launch path. Reliability fix, no performance gain claim."
"evidence": "Plain shells and agents without a preset add no trust I/O; the hook returns after the preset check. A local Codex write is capped at 20 s and every other write, local or on the relay, at the short budget, each by one timer cleared on settlement. An SSH launch adds no round trip: the field rides the existing pty.spawn request. The deadline bounds the wait, not the write: an abandoned write still lands later. The local Claude writer reads and parses Claude's config synchronously on the main thread before its lock."
},
"knownGaps": [
"CI soak pending.",
"No live Codex CLI, PTY, trust prompt, native Electron app or actual trust-config mutation was exercised.",
"Windows/Linux/WSL/daemon/remote-runtime behavior was not executed; remote owner is mocked. No routing/wire/PTY ownership change.",
"Abandoning a write at the deadline does not cancel it; a legitimate slow write (WSL grant sessions run to 30s) can land after the launch already started, restoring the pre-fix ordering for that case only.",
"The deadline value is a reasoned cap over observed lane holders, not a measured p95.",
"All five awaited local Codex trust writes (markCodexProjectTrusted) are now bounded by the same helper: the agentTrust:markTrusted IPC handler, ipc/worktree-remote.ts, runtime/runtime-worktree-agent-startup.ts, startup/codex-launch-preparation.ts and startup/codex-session-resume-launch.ts. Four have a test in these suites; the ipc/worktree-remote.ts site is bounded but still has no test here. A sixth awaited Codex trust write - the remote one through markRemoteWorktreeTrusted - is not bounded at all; see the remote gap below.",
"Bounding the trust write does not bound the launch at two of the three new sites, and on both the first unbounded re-entry is earlier than the hook-service call. In startup/codex-launch-preparation.ts the next awaited call after the abandoned write is ensureRealHomeHooksIfSelected, which is conditional: only when the target is not WSL and runtimeHome.isHostSystemDefaultRealHomeSelected(launchEnv) is true does it call ensureRealHomeCodexHookState, which chains behind that module's own serial ensure promise and then acquires runExclusivelyForCodexTrustConfig on ~/.codex/config.toml (it skips the lane only when hooks are enabled, the lane is already unavailable and its retry backoff has not elapsed). When the real home is not selected that call returns without awaiting the lane, runtimeHome.prepareForCodexLaunchAsync is synchronous on the non-WSL path, and the first re-entry is codexHookService.prepareRuntimeHomeForLaunch. In startup/codex-session-resume-launch.ts the next awaited call is the hook branch: ensureRealHomeCodexHookState when the resume home is ~/.codex, otherwise installForLaunchPrep or refreshRuntimeUserHooksForLaunchPrep. All of those reach the same unbounded per-config.toml serial queue (runExclusivelyForCodexTrustConfig, FIFO with no depth cap and no timeout) on ~/.codex/config.toml; the runExclusivelyForRuntimeAndSystemTrustConfig wrapper takes the runtime home's config.toml first, and markCodexProjectTrusted itself takes both. A wedged lane therefore still stalls a quick-launch or a resume one step after the write is abandoned, and the abandoned write keeps its queue slot ahead of that step. The new launch-prep tests mock codexHookService and ensureRealHomeCodexHookState wholesale and default the real-home selection to false, so no test in these suites can catch this residual stall. Only markLocalWorktreeTrusted in runtime/runtime-worktree-agent-startup.ts, which has nothing after its Codex branch, is bounded end to end.",
"The cap remains per call site, not global. Two launches racing the same wedged lane each wait their own deadline, so the worst case a user can see is one deadline per launch, not one shared cap.",
"The remote (SSH) trust write reached through markRemoteWorktreeTrusted in runtime/runtime-worktree-agent-startup.ts is still awaited unbounded; only the agentTrust:markTrusted handler's remote write is capped. It is a Codex trust write a launch waits on, not merely an adjacent one: it reads TUI_AGENT_CONFIG[agent].preflightTrust, which is 'codex' for the codex agent, and markRemoteAgentWorkspaceTrusted then branches into markRemoteCodexProjectTrusted. Its only production caller is markRemoteWorkspaceTrustedForAgent in runtime/orca-runtime-activate-managed-worktree.ts, reached with a connectionId from seven launch entry points. On orca-runtime-create-agent-session.ts, orca-runtime-get-agent-session-execution-namespace.ts, orca-runtime-terminal-create-deduplication.ts, runtime-remote-managed-worktree-create.ts and runtime-folder-worktree-create.ts (wired in by orca-runtime-create-managed-worktree.ts) the await sits directly before the createTerminal that spawns the agent; on orca-runtime-resolve-mobile-session-terminal-command.ts and orca-runtime-resolve-worktree-removal-target.ts it instead gates the launch command those functions return for their caller to spawn, so the launch still waits on it. It chains a session.resolveHome multiplexer round trip, an SFTP realpath, a read, a createDir and a write, none with a timeout, over a link that may be half-open - so an SSH Codex launch can still hang with nothing happening, the same failure this branch bounds locally. Deliberately left out of scope here, and deliberately left out of the invariant."
"No live agent CLI, PTY, trust prompt, native Electron app, SSH host or WSL guest was exercised.",
"Abandoning a write at the deadline does not cancel it; a slow write can land after the agent already asked.",
"The deadline values are reasoned caps, not measured p95s.",
"Antigravity over SSH is not written on the relay (its writer is unverified there), so it still asks."
],
"promotionCriteria": [
"Retain pending-write/queue/launch-boundary red-green evidence and remote owner assertions; obtain actual platform/session evidence and CI soak before promotion."
"Retain the hold, deadline and home or root red/green evidence; obtain native platform and SSH evidence and a CI soak before promotion."
],
"demotionRule": "Remain experimental until repeated independent/CI evidence; investigate ordering or unbounded-wait regressions without weakening completion or deadline assertions."
"demotionRule": "Remain experimental until repeated independent or CI evidence; investigate ordering or unbounded-wait regressions without weakening the hold or deadline assertions."
},
{
"id": "settings.general-section-lifetime",
@@ -461,20 +492,9 @@
"app version and remote update display",
"autosave settings draft"
],
"platforms": [
"macos",
"linux",
"windows"
],
"providers": [
"local",
"remote-runtime",
"ssh",
"wsl"
],
"coveredPlatforms": [
"macos"
],
"platforms": ["macos", "linux", "windows"],
"providers": ["local", "remote-runtime", "ssh", "wsl"],
"coveredPlatforms": ["macos"],
"coveredProviders": [],
"coverageNotes": "Actual GeneralPane, update/version and remote-status components with mocked store/API; actual autosave form. Windows runtime on/off simulated. No physical updater, paired host, settings write or native input.",
"motivatingLinks": [
@@ -485,9 +505,7 @@
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/GeneralPane.section-lifetime.test.tsx"
],
"testFiles": [
"src/renderer/src/components/settings/GeneralPane.section-lifetime.test.tsx"
],
"testFiles": ["src/renderer/src/components/settings/GeneralPane.section-lifetime.test.tsx"],
"assertionRefs": [
{
"file": "src/renderer/src/components/settings/GeneralPane.section-lifetime.test.tsx",
@@ -551,20 +569,9 @@
"Grok and Cursor account status",
"Codex pending sign-in link"
],
"platforms": [
"macos",
"linux",
"windows"
],
"providers": [
"local",
"remote-runtime",
"ssh",
"wsl"
],
"coveredPlatforms": [
"macos"
],
"platforms": ["macos", "linux", "windows"],
"providers": ["local", "remote-runtime", "ssh", "wsl"],
"coveredPlatforms": ["macos"],
"coveredProviders": [],
"coverageNotes": "Actual AccountsPane, Grok, Cursor and Codex login components; synthetic APIs/store with Windows account support enabled/disabled. No real account, keychain or provider action.",
"motivatingLinks": [
@@ -575,9 +582,7 @@
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/settings/AccountsPane.section-lifetime.test.tsx"
],
"testFiles": [
"src/renderer/src/components/settings/AccountsPane.section-lifetime.test.tsx"
],
"testFiles": ["src/renderer/src/components/settings/AccountsPane.section-lifetime.test.tsx"],
"assertionRefs": [
{
"file": "src/renderer/src/components/settings/AccountsPane.section-lifetime.test.tsx",
@@ -943,7 +948,7 @@
"invariant": "One structured-send operation id causes at most one provider dispatch. A recorded or transport-ambiguous send reuses that id across retry, caller reconnect, client remount, and journal recovery; only a terminal rejection may rotate to a first delivery.",
"oracle": "Inject adapter acknowledgement loss, RPC response loss, caller replacement, logical-client close after response, auth recovery with a written request, missing journal submissions, legacy pending rows, stale fences, operation expiry, mobile remount, and durable-journal capacity. Assert one provider dispatch or one operation id for every ambiguous retry, fresh identity only after rejection, and no eviction of ambiguous mobile ids.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/mobile-structured-session-operation-retention.test.ts ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts"
],
"testFiles": [
@@ -957,7 +962,6 @@
"src/main/runtime/orchestration/structured-pointer-operation-id.test.ts",
"src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx",
"src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx",
"src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx",
"src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx",
"src/renderer/src/lib/launch-structured-agent-session.test.ts",
"mobile/src/session/mobile-native-chat-image-attachment.test.ts",
@@ -1033,7 +1037,7 @@
"date": "2026-09-12",
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSession.transport-probe.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"result": "passed",
"durationSeconds": 22.1,
"summary": "Thirteen focused host, shared, renderer, and orchestration files passed 148 tests."
@@ -12353,7 +12357,7 @@
"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty-pending-projection-admissions.test.ts src/main/ipc/ssh-pty-legacy-projection.test.ts src/main/ipc/ssh-pty-model-admission.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime-path-candidate-history.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/orca-runtime-tail-wait-memo.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-source-delivery-ledger.test.ts src/main/ssh/ssh-pty-retired-source-deliveries.test.ts src/main/ssh/ssh-relay-session.test.ts src/main/ssh/ssh-relay-session-data-delivery.test.ts src/main/ssh/ssh-relay-session-recovery-races.test.ts src/main/ssh/ssh-relay-session-incarnation.test.ts src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts src/main/ssh/ssh-relay-session-terminal-error.test.ts src/main/ssh/ssh-pty-recovery-retention-budget.test.ts src/main/ssh/relay-protocol-backpressure.test.ts src/relay/protocol-backpressure.test.ts src/relay/pty-source-credit-ledger.test.ts src/relay/pty-source-credit-scheduler.test.ts src/relay/relay-pty-source-publication.test.ts src/relay/ssh-pty-source-credit-adapter.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-source-delivery-ledger.test.ts src/main/ssh/ssh-pty-retired-source-deliveries.test.ts src/main/ssh/ssh-relay-session.test.ts src/main/ssh/ssh-relay-session-data-delivery.test.ts src/main/ssh/ssh-relay-session-recovery-races.test.ts src/main/ssh/ssh-relay-session-incarnation.test.ts src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts src/main/ssh/ssh-relay-session-terminal-error.test.ts src/main/ssh/ssh-pty-recovery-retention-budget.test.ts src/relay/protocol-backpressure.test.ts src/relay/pty-source-credit-ledger.test.ts src/relay/pty-source-credit-scheduler.test.ts src/relay/relay-pty-source-publication.test.ts src/relay/ssh-pty-source-credit-adapter.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/ssh-pty-model-admission.test.ts src/main/ipc/ssh-pty-output-model-migration.test.ts src/main/ssh/ssh-relay-session-model-migration.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/relay/git-response-stream-ownership.test.ts src/relay/pty-handler-output-streaming.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/main/providers/ssh-pty-notification-routing.test.ts src/main/providers/ssh-pty-provider-agent-session-create-operation.test.ts src/main/providers/ssh-pty-provider-exit-race.test.ts src/main/providers/ssh-pty-provider-reattach-incarnation.test.ts --reporter=dot",
@@ -12400,7 +12404,6 @@
"src/main/ssh/ssh-relay-session-terminal-error.test.ts",
"src/main/ssh/ssh-pty-recovery-retention-budget.test.ts",
"src/main/ssh/ssh-pty-retired-source-deliveries.test.ts",
"src/main/ssh/relay-protocol-backpressure.test.ts",
"src/relay/protocol-backpressure.test.ts",
"src/relay/git-response-stream-ownership.test.ts",
"src/relay/pty-handler-output-streaming.test.ts",
@@ -12617,13 +12620,6 @@
"invalid-checkpoint cancellation retains the exact delivery until restore response settlement, then retry mints a fresh activation and emits one live source frame"
]
},
{
"file": "src/main/ssh/relay-protocol-backpressure.test.ts",
"assertions": [
"main SSH decoder accepts one maximum frame plus 1 MiB partial input and rejects one extra byte",
"a throwing continuation clears retained input, releases one pause epoch, and publishes one typed ownership error"
]
},
{
"file": "src/relay/protocol-backpressure.test.ts",
"assertions": [
@@ -12812,7 +12808,7 @@
"date": "2026-07-29",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-source-delivery-ledger.test.ts src/main/ssh/ssh-pty-retired-source-deliveries.test.ts src/main/ssh/ssh-relay-session.test.ts src/main/ssh/ssh-relay-session-data-delivery.test.ts src/main/ssh/ssh-relay-session-recovery-races.test.ts src/main/ssh/ssh-relay-session-incarnation.test.ts src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts src/main/ssh/ssh-relay-session-terminal-error.test.ts src/main/ssh/ssh-pty-recovery-retention-budget.test.ts src/main/ssh/relay-protocol-backpressure.test.ts src/relay/protocol-backpressure.test.ts src/relay/pty-source-credit-ledger.test.ts src/relay/pty-source-credit-scheduler.test.ts src/relay/relay-pty-source-publication.test.ts src/relay/ssh-pty-source-credit-adapter.test.ts --reporter=dot",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-source-delivery-ledger.test.ts src/main/ssh/ssh-pty-retired-source-deliveries.test.ts src/main/ssh/ssh-relay-session.test.ts src/main/ssh/ssh-relay-session-data-delivery.test.ts src/main/ssh/ssh-relay-session-recovery-races.test.ts src/main/ssh/ssh-relay-session-incarnation.test.ts src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts src/main/ssh/ssh-relay-session-terminal-error.test.ts src/main/ssh/ssh-pty-recovery-retention-budget.test.ts src/relay/protocol-backpressure.test.ts src/relay/pty-source-credit-ledger.test.ts src/relay/pty-source-credit-scheduler.test.ts src/relay/relay-pty-source-publication.test.ts src/relay/ssh-pty-source-credit-adapter.test.ts --reporter=dot",
"result": "passed",
"durationSeconds": 2.24,
"summary": "Sixteen deterministic SSH relay/session/source/decoder files passed 192 tests, including exit-sealed private recovery, retained stale-transfer cancellation authority, exact private-frame proof watermarks, one retirement record across 10,000 same-PTY token rotations, stale-owner fallback, and scheduler rejection isolation; no live topology was exercised."
@@ -13158,7 +13154,7 @@
"invariant": "PTY snapshot capability discovery must never synchronously block renderer JavaScript. Restored daemon capability must be known before workspace readiness enables cold activation; unknown or legacy capability must remain eager. A healthy SSH provider must return definitive false without polling. One unresponsive capability batch must fail open within one second regardless of PTY count, and stale async responses must not update current bindings.",
"oracle": "The preload test rejects sendSync and requires ipcRenderer.invoke. Unit contracts assert 512-ID batching, one-second fail-open, unknown retry, definitive SSH false, and generation-fenced stale responses. During an injected 1.5-second Electron main-thread stall, a renderer-owned 50ms interval must keep a maximum gap below 500ms and each API call must return within 100ms. The production cold-activation journey must still mount at most three of eight daemon tabs after reload, while Docker SSH restoration remains eager.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/preload/pty-snapshot-capability-ipc.test.ts src/main/ipc/pty-startup-barrier-and-listing.test.ts src/main/providers/ssh-pty-provider.test.ts src/renderer/src/components/terminal/terminal-provider-snapshot-capability.test.ts src/renderer/src/components/terminal/use-terminal-provider-snapshot-capability.test.tsx src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts src/renderer/src/app-startup-routing.test.ts --reporter=dot",
"pnpm exec vitest run --config config/vitest.config.ts src/preload/pty-snapshot-capability-ipc.test.ts src/main/ipc/pty-startup-barrier-and-listing.test.ts src/main/providers/ssh-pty-provider.test.ts src/renderer/src/components/terminal/terminal-provider-snapshot-capability.test.ts src/renderer/src/components/terminal/use-terminal-provider-snapshot-capability.test.tsx src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts --reporter=dot",
"pnpm exec electron-vite build --mode e2e",
"SKIP_BUILD=1 pnpm exec playwright test tests/e2e/pty-snapshot-capability-main-stall.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --repeat-each=3",
"SKIP_BUILD=1 pnpm exec playwright test tests/e2e/terminal-cold-activation-deferral.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
@@ -13172,7 +13168,6 @@
"src/renderer/src/components/terminal/use-terminal-provider-snapshot-capability.test.tsx",
"src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts",
"src/renderer/src/components/terminal-pane/terminal-hidden-view-parking.test.ts",
"src/renderer/src/app-startup-routing.test.ts",
"tests/e2e/pty-snapshot-capability-main-stall.spec.ts",
"tests/e2e/terminal-cold-activation-deferral.spec.ts",
"tests/e2e/ssh-cold-activation-restore.spec.ts"
@@ -15501,7 +15496,7 @@
"oracle": "Seed status, dispatch, and worker_done rows across direct-handle and canonical Run recipients in an isolated DB. Compare pointer count, RPC and built-CLI check output, direct SQLite rows, unread/peek/all/type filters, concurrent pollers, fixed Delivery IDs, explicit acknowledgment, restart, filtered check --wait, and coordinator remint. Route a 125-row old-handle backlog, inject a commit without notification, and require startup repair. Exercise duplicate Run/Dispatch owners, stale panes, 50-row pages, cancellation, lifecycle fencing, and absent PTYs. Drop a federation ACK, reconnect/restart v1/v2 peers, and require stable import plus no duplicate read-row wake. Hold a healthy SSH write past five seconds but below the 60-second settlement deadline, then distinguish the three settlement outcomes end to end: only a proven refusal releases the reservation and drains a delivery parked behind the watermark; a dropped in-flight settlement must surface as unverifiable with bytes handed to the transport, preserve the durable write-attempted reservation, and emit no duplicate pointer after restart; a settled write that throws mid-pointer is unverifiable, not a refusal; and an Enter whose settlement is lost stays at enter-attempted so restart emits no second Enter. Install the production PTY controller and verify that it routes settled writes through the owning provider and refuses before any byte when the routed provider cannot settle. Census every production PTY provider class and reject a settlement synthesized from the fire-and-forget write.",
"commands": [
"pnpm run build:cli && pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-message-delivery-identity.test.ts --reporter=dot --testTimeout=5000",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/providers/settled-pty-writer-census.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal-send-stale-leaf-liveness.test.ts src/main/runtime/rpc/methods/orchestration/runs/runs.test.ts src/main/runtime/rpc/methods/orchestration/messaging/send.test.ts src/main/runtime/rpc/methods/orchestration/messaging/check.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration/federation-sync.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation.test.ts src/main/runtime/rpc/methods/orchestration/federation/federation-lifecycle-settlement.test.ts --reporter=dot"
],
@@ -15518,7 +15513,6 @@
"src/main/runtime/orchestration/formatter.test.ts",
"src/main/providers/ssh-pty-provider.test.ts",
"src/main/providers/ssh-pty-write.test.ts",
"src/main/providers/settled-pty-writer-census.test.ts",
"src/main/runtime/orchestration/mailbox-pointer-stage.test.ts",
"src/main/daemon/client.test.ts",
"src/main/daemon/daemon-pty-router.test.ts",
@@ -15620,13 +15614,6 @@
"file": "src/main/runtime/orchestration/mailbox-pointer-stage.test.ts",
"assertions": ["a refused pointer write drains a delivery parked behind its watermark"]
},
{
"file": "src/main/providers/settled-pty-writer-census.test.ts",
"assertions": [
"every production IPtyProvider class exposes a settled writer",
"no settled writer synthesizes its settlement from the fire-and-forget write"
]
},
{
"file": "src/main/ipc/pty-controller-ownership-routing.test.ts",
"assertions": [
@@ -15666,7 +15653,7 @@
"date": "2026-09-05",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/providers/settled-pty-writer-census.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
"result": "passed",
"durationSeconds": 4.73,
"summary": "267 tests passed after the pointer-write path moved to the three-valued WriteSettlement union. New coverage: a dropped in-flight SSH settlement reaches the stager as unverifiable with bytes handed to the transport, a settled write that throws mid-pointer preserves the write-attempted reservation, an Enter whose settlement is lost stays at enter-attempted with no second Enter after restart, a refusal releases the reservation and drains a delivery parked behind its watermark, the production controller refuses before any byte when the routed provider cannot settle, and a census pins the five production IPtyProvider classes and rejects a settlement synthesized from the fire-and-forget write. Each new assertion was verified red against the pre-fix shape."
@@ -15675,7 +15662,7 @@
"date": "2026-08-13",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/providers/settled-pty-writer-census.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orchestration-mailbox-routing-races.test.ts src/main/runtime/orchestration-mailbox-notification-consistency.test.ts src/main/runtime/orchestration-mailbox-detached-routing.test.ts src/main/runtime/orchestration-mailbox-transport-settlement.test.ts src/main/ipc/pty-controller-ownership-routing.test.ts src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts src/main/runtime/orchestration/orchestration-run-delivery-db.test.ts src/main/runtime/orchestration/orchestration-worker-dispatch-db.test.ts src/main/runtime/orchestration/formatter.test.ts src/main/providers/ssh-pty-provider.test.ts src/main/providers/ssh-pty-write.test.ts src/main/runtime/orchestration/mailbox-pointer-stage.test.ts src/main/daemon/client.test.ts src/main/daemon/daemon-pty-router.test.ts src/main/daemon/degraded-daemon-pty-provider.test.ts",
"result": "passed",
"durationSeconds": 8.22,
"summary": "245 tests passed across mailbox identity, durable coordinator-handle migration, insertion-time canonicalization, duplicate-free 51-row ownership branch caps, unrestricted reservation merging, direct and Dispatch pointer suppression, persisted reconciliation, 50-row paging and filtered waits, cross-PTY serialization, lifecycle fencing, bounded daemon and SSH transport settlement, outstanding Deliveries, reminted Dispatch ownership, acknowledgment, cancellation, and bounded pane lookup."
@@ -119,15 +119,13 @@ describe('the actual Bun build and profile-test dependency graph', () => {
expect((await classifyBunProfileChanges([file], async () => inputs)).shouldRun).toBe(true)
})
it('retains all selected tests and uses the same selectors as the Bun runner', () => {
it('retains all selected tests and the selectors the Bun runner uses', () => {
const tests = discoverBunProfileTests()
expect(tests.length).toBeGreaterThan(80)
expect(tests.every((file) => inputs.has(file))).toBe(true)
expect(
bunProfileTestPaths().every((selector) => tests.some((file) => file.includes(selector)))
).toBe(true)
const runner = readFileSync(new URL('./run-bun-profile-tests.mjs', import.meta.url), 'utf8')
expect(runner).toContain('testArgs.length > 0 ? testArgs : bunProfileTestPaths({ artifact })')
})
})
-4
View File
@@ -117,7 +117,6 @@
"config/scripts/mobile-launch-contract-workflow.test.mjs": 439,
"config/scripts/mobile-mirrored-storage-write-path.test.mjs": 136,
"config/scripts/mobile-pairing-qrcode-import-plugin.test.mjs": 421,
"config/scripts/mobile-recording-pin-checkout.test.mjs": 309,
"config/scripts/mobile-release-check-scope.test.mjs": 610,
"config/scripts/mobile-release-shell-switch-workflow.test.mjs": 227,
"config/scripts/mobile-set-native-props-web-siblings.test.mjs": 959,
@@ -193,7 +192,6 @@
"config/scripts/package-electron-runtime-contract.test.mjs": 447,
"config/scripts/package-linux-formats.test.mjs": 208,
"config/scripts/packaged-browser-lane-contract.test.mjs": 104,
"config/scripts/packaged-hang-watchdog-worker-contract.test.mjs": 161,
"config/scripts/packaged-node-pty-prebuild-prune.test.mjs": 93,
"config/scripts/packaged-source-map-prune.test.mjs": 117,
"config/scripts/patched-dependencies-frozen-install.test.mjs": 900,
@@ -1301,7 +1299,6 @@
"src/main/codex/codex-structured-session-shutdown.test.ts": 471,
"src/main/codex/codex-structured-thread-goal.test.ts": 73,
"src/main/codex/codex-structured-thread-open.test.ts": 300,
"src/main/codex/codex-structured-turn-processes.integration.test.ts": 194,
"src/main/codex/codex-subagent-execution-projection.test.ts": 202,
"src/main/codex/codex-subagent-executions.test.ts": 71,
"src/main/codex/codex-subagent-roster.test.ts": 118,
@@ -1610,7 +1607,6 @@
"src/main/daemon/terminal-host-teardown-recreate.test.ts": 484,
"src/main/daemon/terminal-host-wsl-context.test.ts": 249,
"src/main/daemon/terminal-host.test.ts": 691,
"src/main/daemon/terminal-mouse-tail-retention.test.ts": 171,
"src/main/daemon/terminal-session-teardown.test.ts": 95,
"src/main/daemon/terminal-shell-armed-input-mode-recovery.test.ts": 215,
"src/main/daemon/terminal-shell-lifecycle-scanner.test.ts": 56,
@@ -92,7 +92,6 @@ describe('conservative unit selection', () => {
count: FULL_SHARD_COUNT
}))
)
expect(FULL_SHARD_COUNT).toBe(5)
})
it('records failures that would have been missed while shadow runs remain full', () => {
@@ -1,91 +0,0 @@
import { readFileSync, readdirSync } from 'node:fs'
import { join, matchesGlob, resolve } from 'node:path'
import { createRequire } from 'node:module'
import { describe, expect, it } from 'vitest'
import { electronViteConfig } from '../../electron.vite.config'
import { GUARDED_ENTRY_NAMES } from '../build-plugins/plain-node-entry-guard'
const REPO_ROOT = resolve(__dirname, '..', '..')
const CLI_ROOT = join(REPO_ROOT, 'src', 'cli')
const packaging = createRequire(import.meta.url)('../electron-builder.config.cjs')
function listCliSourceFiles(dir: string): string[] {
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const path = join(dir, entry.name)
if (entry.isDirectory()) {
return listCliSourceFiles(path)
}
return entry.isFile() && entry.name.endsWith('.ts') && !entry.name.endsWith('.test.ts')
? [path]
: []
})
}
// Why: `import type` is erased by tsc, so it needs no emitted module at runtime.
const VALUE_IMPORT_FROM_MAIN = /(?<!\btype\s)from '(?:\.\.\/)+main\/([^']+)'/g
const DYNAMIC_IMPORT_FROM_MAIN = /import\(\s*['"](?:\.\.\/)+main\/([^'"]+)['"]\s*\)/g
function findMainImports(): { file: string; module: string }[] {
return listCliSourceFiles(CLI_ROOT).flatMap((file) => {
const source = readFileSync(file, 'utf-8')
return [
...source.matchAll(VALUE_IMPORT_FROM_MAIN),
...source.matchAll(DYNAMIC_IMPORT_FROM_MAIN)
].map((match) => ({
file: file.slice(REPO_ROOT.length + 1),
module: match[1].replace(/\.js$/, '')
}))
})
}
function findElectronViteMainEntries(): Record<string, string> {
const input = electronViteConfig.main?.build?.rollupOptions?.input
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new Error('Expected named main-process inputs')
}
return input
}
describe('CLI imports of main-process modules', () => {
// Why: electron-vite cleans out/main and emits only its declared entries, so a
// `src/main/*` module the CLI imports but the config omits is deleted by the
// build that runs after `build:cli` — keep source-level feedback ahead of the
// final-artifact runtime verifier.
it('has an electron-vite entry for every main module the CLI imports', () => {
const entries = findElectronViteMainEntries()
const missing = findMainImports().filter(
({ module }) => entries[module] !== join(REPO_ROOT, 'src', 'main', `${module}.ts`)
)
expect(missing).toEqual([])
})
it('guards every CLI main module against Electron imports', () => {
const guarded = new Set<string>(GUARDED_ENTRY_NAMES)
expect(findMainImports().filter(({ module }) => !guarded.has(module))).toEqual([])
})
it('unpacks every CLI main entry for plain Node outside Electron', () => {
const missing = findMainImports().filter(
({ module }) =>
!packaging.asarUnpack.some((pattern: string) =>
matchesGlob(`out/main/${module}.js`, pattern)
)
)
expect(missing).toEqual([])
})
it('finds the imports it is meant to guard', () => {
// Why: a broken matcher would make the guard above vacuously pass.
expect(findMainImports()).toContainEqual({
file: join('src', 'cli', 'profile-state-location.ts'),
module: 'persistence/profile-state/profile-state-active-location'
})
expect(findMainImports()).toContainEqual({
file: join('src', 'cli', 'handlers', 'agent-hooks.ts'),
module: 'orca-profiles/profile-index-store'
})
expect(findMainImports().length).toBeGreaterThanOrEqual(2)
expect(Object.keys(findElectronViteMainEntries()).length).toBeGreaterThanOrEqual(2)
})
})
@@ -19,13 +19,6 @@ describe('client-hosted browser package coverage', () => {
expect(steps[boundaries].run).not.toContain(file)
})
it('bundles the WSL browser-network relay with its version stamp', () => {
const relayBuild = readFileSync(join(projectDir, 'config/scripts/build-relay.mjs'), 'utf8')
expect(relayBuild).toContain("outfile: join(outDir, 'wsl-browser-network-relay.js')")
expect(relayBuild).toContain("join(outDir, '.browser-network-version')")
})
it('runs client-hosted Electron lifecycle coverage on native package hosts', () => {
const prWorkflow = readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8')
const parsedWorkflow = parse(prWorkflow)
@@ -34,4 +34,34 @@ describe('Codex index-heal contract PR gate', () => {
'set -euo pipefail'
)
})
it('pins the --no-daemon contract to one Codex version and fails when it is missing', () => {
const install = job.steps.find((step) => step.name === 'Install pinned no-daemon Codex CLI')
const verify = job.steps.find((step) => step.name === 'Verify Codex --no-daemon contract')
expect(job.env.CODEX_NO_DAEMON_CLI_VERSION).toMatch(/^\d+\.\d+\.\d+$/)
expect(install.run).toContain('"@openai/codex@$CODEX_NO_DAEMON_CLI_VERSION"')
expect(verify.env.ORCA_CODEX_NO_DAEMON_CONTRACT_VERSION).toBe(
'${{ env.CODEX_NO_DAEMON_CLI_VERSION }}'
)
expect(verify.env.ORCA_CODEX_NO_DAEMON_CONTRACT_REQUIRED).toBe('1')
expect(install.run).toContain('--prefix "$RUNNER_TEMP/codex-cli-no-daemon"')
expect(verify.run).toContain(
'ORCA_CODEX_NO_DAEMON_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-no-daemon/node_modules/.bin/codex"'
)
expect(verify.run).toContain('src/main/pty/codex-no-daemon-binary-contract.test.ts')
})
it('pins the project-trust contract to the no-daemon Codex and fails when it is missing', () => {
const verify = job.steps.find((step) => step.name === 'Verify Codex project-trust contract')
expect(verify.env.ORCA_CODEX_TRUST_CONTRACT_VERSION).toBe(
'${{ env.CODEX_NO_DAEMON_CLI_VERSION }}'
)
expect(verify.env.ORCA_CODEX_TRUST_CONTRACT_REQUIRED).toBe('1')
expect(verify.run).toContain(
'ORCA_CODEX_TRUST_CONTRACT_BINARY="$RUNNER_TEMP/codex-cli-no-daemon/node_modules/.bin/codex"'
)
expect(verify.run).toContain('src/main/agent-trust-presets.test.ts')
})
})
@@ -6,197 +6,6 @@ import { parse } from 'yaml'
const projectDir = resolve(import.meta.dirname, '../..')
describe('computer-use e2e workflow', () => {
it('cancels superseded pull request runs without cancelling scheduled runs', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
expect(workflow.concurrency).toEqual({
group: 'computer-e2e-${{ github.event.pull_request.number || github.ref }}',
'cancel-in-progress': "${{ github.event_name == 'pull_request' }}"
})
})
it('runs computer-use e2e files serially because they share desktop focus', () => {
const config = readFileSync(join(projectDir, 'tests/e2e/vitest.config.ts'), 'utf8')
expect(config).toContain('fileParallelism: false')
})
it('guards e2e source against fragile waits and Windows Calculator drift', () => {
const driver = readFileSync(join(projectDir, 'tests/e2e/helpers/computer-driver.ts'), 'utf8')
const cliDriver = readFileSync(
join(projectDir, 'tests/e2e/helpers/computer-cli-driver.ts'),
'utf8'
)
const windowsStoreE2e = readFileSync(
join(projectDir, 'tests/e2e/computer-windows-store.e2e.ts'),
'utf8'
)
expect(driver).not.toContain('await delay(3500)')
expect(driver).toContain("await waitForComputerWindowTitle('gedit', fileName, 15000)")
expect(cliDriver).toContain('ORCA_DEV_USER_DATA_PATH')
expect(cliDriver).toContain('orca-computer-runtime-')
expect(cliDriver).toContain('retryMissingRuntimeMetadata')
expect(cliDriver).toContain('Could not read Orca runtime metadata')
expect(cliDriver).toContain("'serve', '--no-pairing', '--json'")
expect(windowsStoreE2e).toContain("app.bundleId === 'ApplicationFrameHost'")
expect(windowsStoreE2e).toContain("app.bundleId === 'win32calc'")
expect(windowsStoreE2e).toContain('buttonIndex >= 0')
expect(windowsStoreE2e).toContain('pane(?:\\s|$)/m')
expect(windowsStoreE2e).toContain('String(clickIndex)')
expect(windowsStoreE2e).not.toContain(
"for (const buttonName of ['One', 'Plus', 'Two', 'Equals'])"
)
})
it('triggers on computer-use shared contracts, scripts, and agent skill changes', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const triggerPaths = workflow.on.pull_request.paths
expect(triggerPaths).toEqual(
expect.arrayContaining([
'config/scripts/computer-e2e-workflow.test.mjs',
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs',
'config/scripts/computer-use-modifier-safety.test.mjs',
'config/scripts/computer-use-skill-guidance.test.mjs',
'config/scripts/computer-use-smoke.mjs',
'config/scripts/computer-use-smoke.test.mjs',
'skills/computer-use/SKILL.md',
'src/main/computer/**',
'src/main/runtime/rpc/dispatcher.ts',
'src/main/runtime/rpc/errors.ts',
'src/main/runtime/rpc/methods/computer*.ts',
'src/shared/computer-use-*.ts',
'tests/e2e/vitest.config.ts'
])
)
expect(triggerPaths).not.toContain('src/shared/runtime-types.ts')
})
it('runs focused computer-use regression tests in the PR native-smoke job', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const nativeSmokeRuns = workflow.jobs['native-smoke'].steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
const checkout = workflow.jobs['native-smoke'].steps.find(
(step) => step.uses === 'actions/checkout@v6'
)
const regressionRun = nativeSmokeRuns.find((run) => run.includes('pnpm vitest run'))
const expectedRegressionFiles = [
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs',
'config/scripts/macos-computer-helper-owner-loss-processes.test.mjs',
'config/scripts/computer-use-modifier-safety.test.mjs',
'config/scripts/computer-use-skill-guidance.test.mjs',
'config/scripts/computer-use-smoke.test.mjs',
'src/main/computer/computer-provider-lifecycle.test.ts',
'src/main/computer/computer-provider-unavailable-message.test.ts',
'src/main/computer/sidecar-client.test.ts',
'src/main/computer/macos-native-provider-client.test.ts',
'src/main/computer/macos-native-provider-socket.test.ts',
'src/main/computer/macos-computer-use-permissions.test.ts',
'src/main/computer/macos-computer-use-permission-status.test.ts',
'src/main/computer/desktop-script-provider-client.test.ts',
'src/main/computer/desktop-script-provider-cache.test.ts',
'src/main/computer/desktop-script-provider-actions.test.ts',
'src/main/computer/desktop-script-provider-cache-lifecycle.test.ts',
'src/main/computer/desktop-script-provider-errors.test.ts',
'src/main/computer/desktop-script-provider-action-errors.test.ts',
'src/shared/computer-use-error-recovery.test.ts',
'src/shared/computer-use-key-spec.test.ts',
'src/cli/format.test.ts',
'src/cli/handlers/computer.test.ts',
'src/cli/handlers/computer-action-routing.test.ts',
'src/cli/handlers/computer-action-validation.test.ts',
'src/cli/handlers/computer-state-formatting.test.ts',
'src/cli/specs/computer.test.ts',
'src/cli/index.test.ts',
'src/main/runtime/rpc/dispatcher-computer-errors.test.ts',
'src/main/runtime/rpc/errors.test.ts',
'src/main/runtime/rpc/methods/computer.test.ts',
'src/main/runtime/rpc/methods/computer-actions.test.ts',
'src/cli/runtime/envelope-schema.test.ts',
'src/shared/remote-runtime-client.test.ts'
]
expect(checkout.with['persist-credentials']).toBe(false)
expect(regressionRun).toBeTruthy()
for (const file of expectedRegressionFiles) {
expect(regressionRun).toContain(file)
}
})
it('keeps Linux native imports available without installing the GUI-only stack in PR smoke', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const nativeSmokeInstall = workflow.jobs['native-smoke'].steps.find(
(step) => step.if === "runner.os == 'Linux'"
)
const scheduledLinuxInstall = workflow.jobs.linux.steps.find((step) =>
step.run?.includes('apt-get install')
)
expect(nativeSmokeInstall.run).toContain('python3')
expect(nativeSmokeInstall.run).toContain('python3-gi')
expect(nativeSmokeInstall.run).toContain('gir1.2-atspi-2.0')
expect(nativeSmokeInstall.run).toContain('at-spi2-core')
expect(nativeSmokeInstall.run).not.toContain('gedit')
expect(nativeSmokeInstall.run).not.toContain('xvfb')
expect(nativeSmokeInstall.run).not.toContain('xdotool')
expect(scheduledLinuxInstall.run).toContain('gedit')
expect(scheduledLinuxInstall.run).toContain('xvfb')
expect(scheduledLinuxInstall.run).toContain('xdotool')
})
it('builds and tests the macOS helper on every trigger without hosted TCC e2e', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const job = workflow.jobs['mac-native-owner-smoke']
const runs = job.steps.map((step) => step.run).filter((run) => typeof run === 'string')
const checkout = job.steps.find((step) => step.uses === 'actions/checkout@v6')
const install = job.steps.find(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
expect(job.if).toBeUndefined()
expect(job['runs-on']).toBe('macos-15')
expect(checkout.with['persist-credentials']).toBe(false)
expect(install.with['native-runtime']).toBe('electron')
expect(runs).toContain('pnpm bench:macos-computer-helper-owner-loss --expect reaped --trials 1')
const cleanupRun = runs.find((run) =>
run.includes('config/scripts/macos-computer-helper-owner-loss-processes.test.mjs')
)
expect(cleanupRun).toContain(
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs'
)
expect(runs).toContain('pnpm verify:computer-native')
expect(
runs.find((run) => run.includes('config/scripts/build-native-for-platform.test.mjs'))
).toContain('--config config/vitest.config.ts')
expect(runs.join('\n')).not.toContain('test:e2e:computer')
expect(workflow.jobs.mac).toBeUndefined()
expect(workflow.on.pull_request.paths).toEqual(
expect.arrayContaining([
'config/scripts/build-native-for-platform.mjs',
'config/scripts/build-native-for-platform.test.mjs',
'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs',
'config/scripts/macos-computer-helper-owner-loss-group-recovery.test.mjs',
'config/scripts/macos-computer-helper-owner-loss-metrics.mjs',
'config/scripts/macos-computer-helper-owner-loss-processes.mjs',
'config/scripts/macos-computer-helper-owner-loss-processes.test.mjs',
'config/scripts/macos-computer-helper-owner-loss-trial-cleanup.mjs'
])
)
})
it('uses the cached Electron dependency path for scheduled Linux and Windows e2e', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
@@ -220,29 +29,6 @@ describe('computer-use e2e workflow', () => {
}
})
it('runs deterministic macOS owner-loss benchmark cleanup coverage', () => {
const benchmark = readFileSync(
join(projectDir, 'config/scripts/macos-computer-helper-owner-loss-benchmark.mjs'),
'utf8'
)
const cleanup = readFileSync(
join(projectDir, 'config/scripts/macos-computer-helper-owner-loss-trial-cleanup.mjs'),
'utf8'
)
expect(benchmark).toContain('spawnBenchmarkProcess(executable, [launcherDir]')
expect(benchmark).toContain("stdio: ['ignore', stdoutDescriptor, stderrDescriptor]")
expect(benchmark).toContain('cleanupOwnerLossTrial({')
const parseIndex = benchmark.indexOf('parseBenchmarkTrialResult(serializedResult)')
const cleanupIndex = benchmark.indexOf('cleanupOwnerLossTrial({')
expect(parseIndex).toBeGreaterThanOrEqual(0)
expect(cleanupIndex).toBeGreaterThanOrEqual(0)
expect(parseIndex).toBeLessThan(cleanupIndex)
expect(benchmark).toContain('trialCleanupSha256: artifactSha256(trialCleanupPath)')
expect(cleanup).toContain('killRecordedAndMatchingProcesses(options.recordPath')
expect(cleanup).toContain("signalValidatedProcessGroup(options.pid, options.marker, 'SIGKILL'")
})
it('boots the built daemon under plain Node in the PR native-smoke job after the main build', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
@@ -279,34 +65,6 @@ describe('computer-use e2e workflow', () => {
)
})
it('re-runs the native-smoke job when the daemon bundle graph changes', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const triggerPaths = workflow.on.pull_request.paths
expect(triggerPaths).toEqual(
expect.arrayContaining([
'config/scripts/daemon-boot-smoke.mjs',
'config/scripts/windows-daemon-workspace-close-repro.mjs',
'electron.vite.config.ts',
'config/build-plugins/**',
'src/main/daemon/**'
])
)
})
it('does not run computer-use e2e in PR smoke jobs', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const nativeSmokeRuns = workflow.jobs['native-smoke'].steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
expect(nativeSmokeRuns.join('\n')).not.toContain('test:e2e:computer')
})
it('builds Electron main output before every computer-use e2e run', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
@@ -352,48 +110,4 @@ describe('computer-use e2e workflow', () => {
expect(allRuns.join('\n')).toContain('test:e2e:computer')
expect(allRuns.join('\n')).not.toContain('test:e2e:computer -- --reporter')
})
it('runs Linux e2e on schedule without advertising hosted macOS TCC coverage', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const triggerPaths = workflow.on.pull_request.paths
const linuxRuns = workflow.jobs.linux.steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
expect(triggerPaths).toEqual(
expect.arrayContaining([
'tests/e2e/computer-linux.e2e.ts',
'tests/e2e/helpers/computer-cli-driver.ts',
'tests/e2e/helpers/computer-driver.ts'
])
)
expect(triggerPaths).not.toContain('tests/e2e/computer-mac.e2e.ts')
expect(triggerPaths).not.toContain('tests/e2e/computer-mac-safari.e2e.ts')
expect(workflow.jobs.mac).toBeUndefined()
expect(linuxRuns).toContain(
'xvfb-run --auto-servernum dbus-run-session -- pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-linux.e2e.ts'
)
})
it('runs every Windows computer-use e2e file in the scheduled Windows job', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
const triggerPaths = workflow.on.pull_request.paths
const windowsRuns = workflow.jobs.windows.steps
.map((step) => step.run)
.filter((run) => typeof run === 'string')
expect(triggerPaths).toEqual(
expect.arrayContaining([
'tests/e2e/computer-windows.e2e.ts',
'tests/e2e/computer-windows-store.e2e.ts'
])
)
expect(windowsRuns).toContain(
'pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-store.e2e.ts'
)
})
})
@@ -162,6 +162,32 @@ describe('electron-builder config', () => {
expect(packs('out/renderer/index.html')).toBe(true)
})
// Why: an AV verdict on the bundled relay.js used to take app.asar with it as a
// compound object, gutting the install (#20966). resources/relay is the only copy
// a packaged build resolves, so the asar copy was 14MB of pure blast radius.
it('keeps the relay bundles out of app.asar and ships them only through extraResources', () => {
const matcher = new FileMatcher('/app', '/dest', (value) => value, electronBuilderConfig.files)
matcher.prependPattern('**/*')
const isPacked = matcher.createFilter()
const packs = (repoPath) => isPacked(join('/app', repoPath), { isDirectory: () => false })
for (const relayPath of [
'out/relay/linux-x64/relay.js',
'out/relay/win32-x64/relay.js',
'out/relay/darwin-arm64/relay-watcher.js',
'out/relay/wsl/wsl-agent-hook-relay.js'
]) {
expect(packs(relayPath)).toBe(false)
}
for (const platform of ['mac', 'linux', 'win']) {
expect(electronBuilderConfig[platform].extraResources).toContainEqual({
from: 'out/relay',
to: 'relay'
})
}
})
it('keeps runtime resources available through extraResources', () => {
const bundledPluginResources = expect.objectContaining({
from: 'resources/plugins/launch',
@@ -20,9 +20,6 @@ import { createRequire } from 'node:module'
import { electronViteConfig } from '../../electron.vite.config'
import { BOOTSTRAP_FATAL_EXIT_GUARD_KEY } from '../../src/main/startup/bootstrap-fatal-exit-guard'
const targetConfig = readFileSync('config/electron-vite-target.config.cts', 'utf8')
const devRunner = readFileSync('config/scripts/run-electron-vite-dev.mjs', 'utf8')
type BootstrapProcessMock = EventEmitter & {
env: Record<string, string>
pid: number
@@ -253,11 +250,6 @@ describe('Electron Vite output contract', () => {
)
})
it('rejects prototype properties as build targets', () => {
// Own-property check only: an inherited key like `constructor` must not select a build target.
expect(targetConfig).toContain('Object.hasOwn(configByTarget, target)')
})
it('gives the dev terminal daemon helper the TCC identity watched by Orca', () => {
// Asserted on the values rather than the source text: the ids moved into
// dev-electron-bundle-identity.mjs so every dev bundle signs to one cdhash.
@@ -265,7 +257,5 @@ describe('Electron Vite output contract', () => {
expect(getDevHelperPlistPatches()).toEqual([
{ key: 'CFBundleIdentifier', value: DEV_HELPER_BUNDLE_ID }
])
expect(devRunner).toContain("'Electron Helper.app',")
expect(devRunner).toContain('setPlistValue(helperPlistPath, key, value)')
})
})
@@ -4,53 +4,8 @@ import { parse } from 'yaml'
import { describe, expect, it } from 'vitest'
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
const headlessLinuxGuide = readFileSync('docs/reference/headless-linux-server.md', 'utf8')
const signalCase = readFileSync('config/docker/headless-serve-shutdown/run-signal-case.sh', 'utf8')
const shutdownDockerRunner = readFileSync(
'config/scripts/run-headless-serve-shutdown-docker.mjs',
'utf8'
)
const shutdownDockerfile = readFileSync('config/docker/headless-serve-shutdown/Dockerfile', 'utf8')
const desktopStartupOracle = readFileSync(
'config/docker/headless-serve-shutdown/run-appimage-desktop-startup-case.sh',
'utf8'
)
const headlessLinuxProse = headlessLinuxGuide.replace(/\s+/g, ' ')
function readSystemdUnitBlocks(doc, unitName) {
const escapedUnitName = unitName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return [...doc.matchAll(new RegExp(`^# /etc/systemd/system/${escapedUnitName}$`, 'gm'))].map(
(match) => {
const start = match.index + match[0].length
const end = doc.indexOf('```', start)
const nextUnitHeaderOffset = doc.slice(start).search(/^# \/etc\/systemd\/system\/.+$/m)
const nextUnitHeader = nextUnitHeaderOffset === -1 ? -1 : start + nextUnitHeaderOffset
if (end === -1 || (nextUnitHeader !== -1 && end > nextUnitHeader)) {
throw new Error(`Missing closing code fence for ${unitName}`)
}
return doc.slice(start, end)
}
)
}
describe('headless serve shutdown PR gate', () => {
it('reads only exact, closed systemd unit blocks', () => {
expect(
readSystemdUnitBlocks('# /etc/systemd/system/orca-serveXservice\n```', 'orca-serve.service')
).toEqual([])
expect(() =>
readSystemdUnitBlocks('# /etc/systemd/system/orca-serve.service\n', 'orca-serve.service')
).toThrow('Missing closing code fence for orca-serve.service')
expect(() =>
readSystemdUnitBlocks(
'# /etc/systemd/system/orca-serve.service\n' +
'KillMode=mixed\n' +
'# /etc/systemd/system/other.service\n```',
'orca-serve.service'
)
).toThrow('Missing closing code fence for orca-serve.service')
})
it('packages Linux artifacts before running the Docker signal oracle', () => {
const steps = workflow.jobs.package.steps
const packageStep = steps.find((step) => step.name === 'Package unpacked app')
@@ -72,130 +27,4 @@ describe('headless serve shutdown PR gate', () => {
steps.filter((step) => step.run?.includes('run-headless-serve-shutdown-docker.mjs'))
).toHaveLength(1)
})
it('keeps readiness polling finite and leak-free', () => {
expect(signalCase).toContain('read_ready_line()')
expect(signalCase).toContain("sed -u -n 's/^[^{]*//p'")
expect(signalCase).toContain('startup_timeout_seconds=${ORCA_STARTUP_TIMEOUT_SECONDS:-180}')
expect(signalCase).toContain('startup_deadline=$((SECONDS + startup_timeout_seconds))')
expect(signalCase).toContain('while (( SECONDS < startup_deadline )); do')
expect(signalCase).toContain('kill -0 "$app_pid" 2>/dev/null || break')
expect(signalCase).toContain(
"jq's `inputs` waits for EOF even when wrapped in `first`, so a tail -F"
)
expect(signalCase).not.toContain('tail --pid=')
})
it('gives owned shutdown state a bounded cleanup grace', () => {
expect(signalCase).toContain('for shutdown_poll in {0..50}; do')
expect(signalCase).toContain('[[ -z "$listener_after" && -z "$owned_residue" ]]')
expect(signalCase).toContain('((${#survivors[@]} == 0))')
expect(signalCase).toContain('((shutdown_poll < 50)) && sleep 0.1')
})
it('checks that a serving-electron signal target owns the ready socket', () => {
const ssRecord =
'LISTEN 0 128 127.0.0.1:41235 0.0.0.0:* users:(("orca-ide",pid=23,fd=7),("orca-ide",pid=25,fd=8))'
expect([...ssRecord.matchAll(/pid=([0-9]+)/g)].map((match) => match[1])).toEqual(['23', '25'])
expect(signalCase).toContain(
'listener_before_pids=$(grep -oE \'pid=[0-9]+\' <<<"$listener_before" | cut -d= -f2 || true)'
)
expect(signalCase).toContain('signal_target_pid=$(head -n1 <<<"$listener_before_pids")')
expect(signalCase).toContain('outside the entrypoint process tree')
})
it('runs the original AppImage desktop startup oracle before extraction and signals', () => {
expect(shutdownDockerfile).toContain(
'COPY run-appimage-desktop-startup-case.sh /usr/local/bin/run-appimage-desktop-startup-case'
)
const startupCall = shutdownDockerRunner.indexOf(
'runDesktopStartupOracle({ image, appImage, platform })'
)
const extractionCall = shutdownDockerRunner.indexOf(
"'timeout --kill-after=10s 120s /input/orca.AppImage --appimage-extract"
)
const signalLoop = shutdownDockerRunner.indexOf("for (const signal of ['INT', 'TERM'])")
expect(startupCall).toBeGreaterThan(-1)
expect(extractionCall).toBeGreaterThan(startupCall)
expect(signalLoop).toBeGreaterThan(startupCall)
expect(shutdownDockerRunner).toContain("'/usr/local/bin/run-appimage-desktop-startup-case'")
})
it('preserves startup logs when the launcher exits before its marker', () => {
expect(desktopStartupOracle).toContain('signal_process_group TERM || true')
expect(desktopStartupOracle).toContain('signal_process_group KILL || true')
expect(desktopStartupOracle).toContain('cat "$stdout_log" >&2 2>/dev/null || true')
expect(desktopStartupOracle).toContain('cat "$stderr_log" >&2 2>/dev/null || true')
expect(desktopStartupOracle).toContain(
'FAIL: desktop launcher exited before ${reason} (status=${observed_status})'
)
expect(desktopStartupOracle).toContain('ORCA_STARTUP_STATE_DIR_CLEANUP=1')
expect(desktopStartupOracle).toContain(
'[[ "$state_dir" =~ ^/tmp/orca-appimage-startup\\.[^/]+$ ]] || return 0'
)
})
it('requires the bound AppImage to be executable before launch and extraction', () => {
expect(desktopStartupOracle).toContain(
'[[ -x "$appimage" ]] || { echo "FAIL: AppImage is not executable: $appimage" >&2; exit 1; }'
)
expect(shutdownDockerRunner).toContain(
'\'test -r /input/orca.AppImage && test -x /input/orca.AppImage || { echo "FAIL: AppImage bind must be readable and executable" >&2; exit 1; }\''
)
})
it('gives the original AppImage enough bounded extraction space', () => {
expect(shutdownDockerRunner).toContain("'/tmp:rw,nosuid,nodev,exec,size=1g'")
})
it('keeps owned Xvfb alive during the documented systemd graceful stop', () => {
const serveUnits = readSystemdUnitBlocks(headlessLinuxGuide, 'orca-serve.service')
const ownedXvfbUnits = serveUnits.filter((unit) => !/^Environment=DISPLAY=/m.test(unit))
const managedXvfbUnits = serveUnits.filter((unit) => /^Environment=DISPLAY=/m.test(unit))
expect(ownedXvfbUnits).toHaveLength(1)
expect(ownedXvfbUnits[0]).toMatch(/^ExecStart=.*orca-linux\.AppImage serve.*$/m)
expect(ownedXvfbUnits[0]).toMatch(/^KillMode=mixed$/m)
expect(managedXvfbUnits).toHaveLength(1)
expect(managedXvfbUnits[0]).toMatch(/^KillMode=mixed$/m)
})
it('distinguishes persisted state from live work during a service restart', () => {
expect(headlessLinuxProse).toContain(
'The detached terminal daemon is preserved by a different mechanism: it is launched through `systemd-run --user --scope`'
)
expect(headlessLinuxProse).toContain(
'These guarantees preserve live processes only when the daemon is in its own'
)
expect(headlessLinuxProse).toContain(
'The unscoped fallback remains destructive: a service restart kills every terminal'
)
expect(headlessLinuxProse).toContain(
'Treat a stop as destructive unless `health.terminalDaemon.cgroupUnit` names an `orca-daemon-*.scope` on that host'
)
expect(headlessLinuxProse).toContain(
'A separately paired runtime is outside that boundary; local execution and SSH hosts reached through this runtime are not. An affected or unknown omission, missing scope, failed request or lost connection is `unverifiable`'
)
expect(headlessLinuxGuide).toContain(
'sudo -Hu orca /home/orca/.local/bin/orca-ide terminal list --json'
)
expect(headlessLinuxGuide).not.toContain('sudo -Hu orca orca-ide terminal list --json')
expect(headlessLinuxGuide).not.toContain('Two facts make this safe and predictable')
})
it('uses the registered CLI name from ordinary Linux shells', () => {
const commandRule =
'The registered Linux CLI command is `orca-ide`, not `orca`, to avoid shadowing the GNOME Orca screen reader.'
const substitutionRule =
"From an ordinary shell outside that service user's managed environment, substitute `orca-ide` for `orca` in commands below."
const censusCommand = '`sudo -Hu orca /home/orca/.local/bin/orca-ide terminal list --json`'
expect(headlessLinuxProse).toContain(commandRule)
expect(headlessLinuxProse).toContain(substitutionRule)
expect(headlessLinuxProse).toContain(censusCommand)
expect(headlessLinuxGuide).toContain('best-effort dispatcher at `$HOME/.local/bin/orca`')
expect(headlessLinuxProse.indexOf(substitutionRule)).toBeLessThan(
headlessLinuxProse.indexOf(censusCommand)
)
})
})
@@ -2,75 +2,86 @@
import assert from 'node:assert/strict'
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, dirname, join } from 'node:path'
import { basename, dirname, join, resolve } from 'node:path'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import { build } from 'esbuild'
// Pass a directory containing journal-open.ts and journal-row-table.ts from the base commit.
// Pass the root of a checkout (or `git archive <base> src` extract) of the base commit: each arm is
// bundled whole from its own tree and writes its own fixture, since the two store history differently.
const baselineDir = process.argv[2]
assert.ok(
baselineDir,
'Usage: node --expose-gc journal-replay-retention-benchmark.mjs BASELINE_DIR'
'Usage: node --expose-gc journal-replay-retention-benchmark.mjs BASELINE_CHECKOUT_ROOT'
)
assert.ok(global.gc, 'Run with --expose-gc to measure live backing memory during replay')
const root = fileURLToPath(new URL('../..', import.meta.url))
const journalSource = './src/main/native-chat/agent-session-journal'
const entries = {
baseline: `export {openAgentSessionJournal} from '${journalSource}/journal-store-factory'; export {replayJournal} from '${journalSource}/journal-open'; export {openJournalDatabase} from '${journalSource}/journal-database'; export {journalDatabaseFile} from '${journalSource}/journal-paths';`,
current: `export {openAgentSessionJournal} from '${journalSource}/journal-store-factory'; export {replayJournal} from '${journalSource}/journal-open'; export {JournalHostDatabase, journalDatabasePath} from '${journalSource}/journal-host-database';`
}
const sourceRoots = { baseline: resolve(baselineDir), current: root }
const identity = {
sessionId: 'benchmark',
workspaceId: 'fixture',
hostId: 'local',
agent: 'codex',
providerHandle: { kind: 'codex', threadId: 'thread' }
}
const fixture = await mkdtemp(join(tmpdir(), 'orca-journal-replay-bench-'))
try {
const implementations = {}
for (const arm of ['baseline', 'current']) {
const outfile = join(fixture, `${arm}.cjs`)
await build({
stdin: {
contents:
"export {openAgentSessionJournal} from './src/main/native-chat/agent-session-journal/journal-store-factory'; export {loadJournal} from './src/main/native-chat/agent-session-journal/journal-open'; export {journalDatabaseFile} from './src/main/native-chat/agent-session-journal/journal-paths';",
resolveDir: root
},
bundle: true,
platform: 'node',
format: 'cjs',
outfile,
plugins: [
{
name: 'replay-memory-probe',
setup(plugin) {
plugin.onLoad(
{ filter: /journal-(?:open|row-table|reducer)\.ts$/ },
async ({ path }) => {
const leaf = basename(path)
let source = await readFile(
arm === 'baseline' && leaf !== 'journal-reducer.ts'
? join(baselineDir, leaf)
: path,
'utf8'
)
if (leaf === 'journal-reducer.ts') {
const marker =
'export function applyJournalRow(state: JournalReducerState, row: JournalRow): void {'
assert.ok(source.includes(marker))
source = source.replace(
marker,
`${marker}\nglobalThis.__replayMemoryProbe?.(row.seq);`
)
}
return { contents: source, loader: 'ts', resolveDir: dirname(path) }
}
)
}
// Released in `finally`, newest first: an open SQLite handle blocks the fixture's removal on Windows.
const releases = []
async function bundle(arm) {
const outfile = join(fixture, `${arm}.cjs`)
await build({
stdin: { contents: entries[arm], resolveDir: sourceRoots[arm] },
bundle: true,
platform: 'node',
format: 'cjs',
outfile,
// A bare base checkout has no node_modules of its own.
nodePaths: [join(root, 'node_modules')],
plugins: [
{
name: 'replay-memory-probe',
setup(plugin) {
plugin.onLoad({ filter: /journal-reducer\.ts$/ }, async ({ path }) => {
const marker =
'export function applyJournalRow(state: JournalReducerState, row: JournalRow): void {'
const source = await readFile(path, 'utf8')
assert.ok(source.includes(marker), `${basename(path)} lost the probe marker`)
return {
contents: source.replace(
marker,
`${marker}\nglobalThis.__replayMemoryProbe?.(row.seq);`
),
loader: 'ts',
resolveDir: dirname(path)
}
})
}
]
})
implementations[arm] = createRequire(import.meta.url)(outfile)
}
]
})
return createRequire(import.meta.url)(outfile)
}
/** One long-revised item, written by the arm's own store; returns the arm's replay of it. */
async function openArm(arm) {
const implementation = await bundle(arm)
const stateDirectory = join(fixture, arm)
let journal
let database
if (arm === 'baseline') {
journal = await implementation.openAgentSessionJournal({ identity, journalDir: stateDirectory })
} else {
database = implementation.JournalHostDatabase.open(stateDirectory)
releases.push(() => database.close())
journal = await implementation.openAgentSessionJournal({ identity, database })
}
const identity = {
sessionId: 'benchmark',
workspaceId: 'fixture',
hostId: 'local',
agent: 'codex',
providerHandle: { kind: 'codex', threadId: 'thread' }
}
const journalDir = join(fixture, 'session')
const journal = await implementations.current.openAgentSessionJournal({ identity, journalDir })
releases.push(() => journal.close())
const item = { provider: 'codex', threadId: 'thread', turnId: 'turn', ordinal: 0 }
const text = 'x'.repeat(32768)
for (let revision = 0; revision < 2000; revision++) {
@@ -84,11 +95,29 @@ try {
{ fence: 1 }
)
}
await journal.close()
await releases.pop()()
if (arm === 'current') {
return {
path: implementation.journalDatabasePath(stateDirectory),
replay: () => implementation.replayJournal(database.db, identity.sessionId)
}
}
const path = implementation.journalDatabaseFile(stateDirectory)
const opened = implementation.openJournalDatabase(path)
releases.push(() => opened.db.close())
return {
path,
// The base replay takes the connection's read-only flag before the chat.
replay: () => implementation.replayJournal(opened.db, opened.readOnly, identity.sessionId)
}
}
try {
const arms = { baseline: await openArm('baseline'), current: await openArm('current') }
for (const arm of ['baseline', 'current', 'current', 'baseline']) {
global.gc()
const start = performance.now()
let loaded = implementations[arm].loadJournal(journalDir, identity.sessionId)
let loaded = arms[arm].replay()
const ms = performance.now() - start
assert.equal(loaded.state.items.size, 1)
assert.equal([...loaded.state.items.values()][0].revision, 2000)
@@ -103,7 +132,7 @@ try {
global.gc()
peakLiveHeap = Math.max(peakLiveHeap, process.memoryUsage().heapUsed)
}
loaded = implementations[arm].loadJournal(journalDir, identity.sessionId)
loaded = arms[arm].replay()
delete globalThis.__replayMemoryProbe
assert.equal(loaded.state.items.size, 1)
loaded = null
@@ -111,12 +140,19 @@ try {
JSON.stringify({
arm,
ms,
databaseBytes: (await stat(implementations[arm].journalDatabaseFile(journalDir))).size,
databaseBytes: (await stat(arms[arm].path)).size,
peakLiveHeapDelta: peakLiveHeap - initialHeap
})
)
}
} finally {
delete globalThis.__replayMemoryProbe
for (const release of releases.toReversed()) {
try {
await release()
} catch (error) {
console.error('[journal-replay-retention-benchmark] release failed', error)
}
}
await rm(fixture, { recursive: true, force: true })
}
@@ -1,96 +0,0 @@
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { expect, it } from 'vitest'
import { parse } from 'yaml'
import { runProcessSync } from './script-child-process.mjs'
const workflow = parse(
readFileSync(new URL('../../.github/workflows/mobile.yml', import.meta.url), 'utf8')
)
it('keeps full ancestry and credentials for lazy pinned-tree reads', () => {
const job = workflow.jobs['recording-pin']
const checkout = job.steps.find((step) => step.uses?.startsWith('actions/checkout@'))
expect(checkout.with['fetch-depth']).toBe(0)
expect(checkout.with.filter).toBe('blob:none')
expect(checkout.with['persist-credentials']).not.toBe(false)
expect(job.if).toBeUndefined()
expect(workflow.on.push.branches).toEqual(['main'])
expect(workflow.concurrency.group).toContain('github.sha')
const reachable = job.steps.find((step) => step.name === 'Check the recording pin is reachable')
expect(reachable.run).toBe('pnpm exec tsx scripts/rpc-recording-pin-guard.mts reachable')
const reproduce = job.steps.find(
(step) => step.name === 'Reproduce the corpus from the pinned tree'
)
// Both steps ask GitHub which pull requests hold a pin main's history lacks.
expect(job.permissions).toEqual({ contents: 'read', 'pull-requests': 'read' })
expect(reachable.env.GITHUB_TOKEN).toContain('github.token')
expect(reproduce.env.GITHUB_TOKEN).toContain('github.token')
expect(reproduce.run).toContain('reproduce --if-changed-since "$PIN_GUARD_BASE"')
expect(reproduce.run).toContain(
'else\n pnpm exec tsx scripts/rpc-recording-pin-guard.mts reproduce\nfi'
)
})
it('retains ancestry while fetching a missing pinned blob for a detached worktree', () => {
const directory = mkdtempSync(join(tmpdir(), 'mobile-pin-checkout-'))
const source = join(directory, 'source')
const checkout = join(directory, 'checkout')
const pinnedTree = join(directory, 'pinned-tree')
const git = (cwd, ...args) => {
const result = runProcessSync({ program: 'git', args, cwd })
expect(result.code, result.stderr).toBe(0)
return result.stdout.trim()
}
try {
git(directory, 'init', '--quiet', source)
git(source, 'symbolic-ref', 'HEAD', 'refs/heads/main')
git(source, 'config', 'user.name', 'Pin checkout fixture')
git(source, 'config', 'user.email', 'pin-checkout@example.invalid')
git(source, 'config', 'uploadpack.allowFilter', 'true')
git(source, 'config', 'uploadpack.allowAnySHA1InWant', 'true')
const corpus = 'mobile/rpc-foundation/goldens/fixture.json'
mkdirSync(join(source, 'mobile/rpc-foundation/goldens'), { recursive: true })
const original = '{"baseline":"original historical recording"}\n'
const commit = () => {
git(source, 'add', '-A')
git(source, '-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'recording')
return git(source, 'rev-parse', 'HEAD')
}
writeFileSync(join(source, corpus), original)
const baseline = commit()
const oldBlob = git(source, 'rev-parse', `${baseline}:${corpus}`)
writeFileSync(join(source, corpus), '{"baseline":"current recording"}\n')
commit()
git(
directory,
'clone',
'--filter=blob:none',
'--no-checkout',
'--single-branch',
'--no-tags',
pathToFileURL(source).href,
checkout
)
git(checkout, 'checkout', '--quiet', '--force', 'main')
expect(git(checkout, 'rev-parse', '--is-shallow-repository')).toBe('false')
expect(git(checkout, 'rev-list', '--count', 'HEAD')).toBe('2')
git(checkout, 'merge-base', '--is-ancestor', baseline, 'HEAD')
expect(git(checkout, 'rev-list', '--objects', '--missing=print', 'HEAD')).toContain(
`?${oldBlob}`
)
git(checkout, 'worktree', 'add', '--detach', pinnedTree, baseline)
expect(readFileSync(join(pinnedTree, corpus), 'utf8')).toBe(original)
expect(git(checkout, 'rev-list', '--objects', '--missing=print', 'HEAD')).not.toContain(
`?${oldBlob}`
)
git(checkout, 'worktree', 'remove', '--force', pinnedTree)
} finally {
rmSync(directory, { recursive: true, force: true })
}
})
@@ -3,6 +3,9 @@ import { pathToFileURL } from 'node:url'
import { isDocsOnlyPath } from './pr-code-change-scope.mjs'
const APPLICATION_PREFIXES = ['src/', 'mobile/app/', 'mobile/src/']
// The root lockfile triggers Mobile Checks for its tests and typechecks; the Ruby release checks
// read no root Node dependency.
const NON_RELEASE_FILES = new Set(['mobile/README.md', 'pnpm-lock.yaml'])
export function shouldRunMobileReleaseChecks(files) {
return (
@@ -10,7 +13,7 @@ export function shouldRunMobileReleaseChecks(files) {
files.some(
(file) =>
!isDocsOnlyPath(file) &&
file !== 'mobile/README.md' &&
!NON_RELEASE_FILES.has(file) &&
!file.startsWith('mobile/docs/') &&
!(
APPLICATION_PREFIXES.some((prefix) => file.startsWith(prefix)) &&
@@ -51,7 +51,6 @@ it.each([
'mobile/src/release.json',
'mobile/new-toolchain/input',
'package.json',
'pnpm-lock.yaml',
'.github/workflows/mobile.yml',
'.github/workflows/mobile-ios-release.yml',
'.github/actions/install-node-dependencies/action.yml',
@@ -62,6 +61,11 @@ it.each([
expect(shouldRunMobileReleaseChecks(['mobile/src/view.tsx', file])).toBe(true)
})
it('skips Ruby checks for a root lockfile change, which fastlane never reads', () => {
expect(shouldRunMobileReleaseChecks(['pnpm-lock.yaml'])).toBe(false)
expect(shouldRunMobileReleaseChecks(['pnpm-lock.yaml', 'mobile/fastlane/Fastfile'])).toBe(true)
})
it('runs Ruby checks when the changed-file evidence is empty', () => {
expect(shouldRunMobileReleaseChecks([])).toBe(true)
})
@@ -72,7 +76,12 @@ it('gates Ruby independently and retains mobile static validation', () => {
expect(steps.indexOf(detector)).toBeGreaterThan(
steps.findIndex((step) => step.uses === './.github/actions/install-node-dependencies')
)
const gated = steps.filter((step) => step.if !== undefined && step.name !== 'Test')
const gated = steps.filter(
(step) =>
step.if !== undefined &&
step.name !== 'Test' &&
step.name !== 'Summarize RPC recording changes'
)
expect(gated.map((step) => step.name)).toEqual([
'Setup Ruby and fastlane',
'Test iOS release version resolution',
@@ -1,4 +1,5 @@
import { readFileSync } from 'node:fs'
import { matchesGlob } from 'node:path'
import { parse } from 'yaml'
import { expect, it } from 'vitest'
import { shouldRunMobileTests } from './mobile-test-change-scope.mjs'
@@ -42,6 +43,17 @@ it('retains tests on missing evidence and a move out of the source tree', () =>
expect(shouldRunMobileTests(['mobile/src/deleted.ts', 'mobile/docs/moved.ts'])).toBe(true)
})
it('runs Mobile Checks on any shared-module or root lockfile change, on a pull request and on main', () => {
const workflow = parse(
readFileSync(new URL('../../.github/workflows/mobile.yml', import.meta.url), 'utf8')
)
for (const paths of [workflow.on.pull_request.paths, workflow.on.push.paths]) {
for (const file of ['src/shared/any/module.ts', 'pnpm-lock.yaml']) {
expect(paths.some((pattern) => matchesGlob(file, pattern))).toBe(true)
}
}
})
it('skips only tests, after successful detection, and retains all other mobile gates', () => {
const workflow = parse(
readFileSync(new URL('../../.github/workflows/mobile.yml', import.meta.url), 'utf8')
@@ -1,4 +1,4 @@
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
@@ -250,23 +250,3 @@ describe('the synthesized RequireContext', () => {
expect(() => context('constructor')).toThrow('no route module')
})
})
describe('the web entry', () => {
it('leaves the suspense boundary to expo-router', async () => {
const entry = await readFile(join(projectDir, 'mobile', 'web-entry', 'index.tsx'), 'utf8')
// A second boundary around the whole tree catches nothing the router has not already caught,
// and would only make the fallback ambiguous about which layer suspended.
expect(entry).not.toContain('Suspense')
})
itBundling('because the router already wraps every screen in one', async () => {
// The premise of the test above, read off the copy that is bundled: getQualifiedRouteComponent
// wraps each screen itself, which is what makes the lazy route manifest safe without a
// boundary of our own.
const useScreens = await readFile(
join(projectDir, 'mobile', 'node_modules', 'expo-router', 'build', 'useScreens.js'),
'utf8'
)
expect(useScreens).toContain('<react_1.default.Suspense fallback=')
})
})
@@ -52,15 +52,6 @@ const DICTATION_GRANTS = ['native.audio.start', 'native.audio.read', 'native.aud
* web builds are a denied microphone and a no-op screen lock. */
const NATIVE_AUDIO_MODULES = ['@orca/expo-two-way-audio', 'expo-keep-awake']
/**
* How many of their modules re-enter the session closure when the seam's web half is moved aside.
*
* Recorded rather than measured here, because measuring it means walking the closure a second time
* against a mutated tree. Five from `@orca/expo-two-way-audio` (its module, `core`, `events`,
* `hooks` and the index) and three from `expo-keep-awake`. The docstring above carries the run.
*/
const NATIVE_AUDIO_MODULES_BEHIND_THE_SEAM = 8
const SESSION_PATHNAME = '/h/[hostId]/session/[worktreeId]'
const SESSION = 'app/h/[hostId]/session/[worktreeId].tsx'
@@ -216,11 +207,6 @@ describe('the census rule itself', () => {
expect(dictationGrantsNeeded({ local: ['src/platform/media-picker.web.ts'] })).toEqual([])
})
it('records what the seam keeps out, in the number that was measured', () => {
expect(NATIVE_AUDIO_MODULES_BEHIND_THE_SEAM).toBe(8)
expect(NATIVE_AUDIO_MODULES).toHaveLength(2)
})
/**
* Gated on the mobile install, not merely deferred behind `import()`.
*
+2 -139
View File
@@ -1,20 +1,7 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { MobileWebBundleRouteSchema } from '../../src/shared/mobile-web-bundle/manifest-contract.ts'
import {
buildMobileWebAppBundle,
resolveMobileWebPageRoutes
} from './build-mobile-web-app-bundle.mjs'
import { computeMobileWebBundleBuildId } from './mobile-web-bundle-manifest.mjs'
import {
collectMobileWebAppRouteKeys,
routePathnameFromKey
} from './mobile-web-app-route-manifest.mjs'
import { mobileWebAppDependenciesPresent } from './mobile-web-app-bundle-dependencies.mjs'
import { spelledCountsAgainstTables } from './spelled-count-census.mjs'
import { resolveMobileWebPageRoutes } from './build-mobile-web-app-bundle.mjs'
import { routePathnameFromKey } from './mobile-web-app-route-manifest.mjs'
/**
* Which screens this desktop declares as page routes, and whether the bundle can render each.
@@ -25,98 +12,6 @@ import { spelledCountsAgainstTables } from './spelled-count-census.mjs'
* to register would have had to choose between a lint fence and a split it did not ask for.
*/
const projectDir = fileURLToPath(new URL('../..', import.meta.url))
const appDir = join(projectDir, 'mobile', 'app')
// The sharded `test` job does not install mobile dependencies, so anything that runs esbuild over
// the route tree is skipped there and run for real in pr.yml's mobile_web_app job.
const itBundling = mobileWebAppDependenciesPresent() ? it : it.skip
async function withScratch(run) {
const scratch = await mkdtemp(join(tmpdir(), 'orca-mobile-web-page-routes-test-'))
try {
return await run(scratch)
} finally {
await rm(scratch, { recursive: true, force: true })
}
}
/**
* Every page route this bundle declares, written out rather than read from the source that
* produces it: the point is to pin the list, and comparing the manifest to its own input would
* pass whatever that input became. Shared by the assertions below, which read it two ways: what
* the route tree resolves to, and what the built manifest actually carries.
*/
const EXPECTED_PAGE_ROUTES = [
{ pathname: '/h/[hostId]', grants: ['navigate', 'storage', 'externalLink', 'haptics'] },
{
pathname: '/h/[hostId]/agent-history/[worktreeId]',
grants: ['navigate', 'storage', 'externalLink', 'haptics']
},
{
pathname: '/h/[hostId]/tasks',
grants: ['navigate', 'storage', 'externalLink', 'haptics', 'native.clipboard.write']
},
{
pathname: '/h/[hostId]/files/[worktreeId]',
grants: ['navigate', 'storage', 'externalLink', 'haptics']
},
{
pathname: '/h/[hostId]/files/preview/[worktreeId]',
grants: ['navigate', 'storage', 'externalLink', 'haptics']
},
{
pathname: '/h/[hostId]/source-control/[worktreeId]',
grants: ['navigate', 'storage', 'externalLink', 'haptics', 'native.clipboard.write']
},
{
pathname: '/h/[hostId]/review/[worktreeId]',
grants: ['navigate', 'storage', 'externalLink', 'haptics', 'native.clipboard.write']
},
{
pathname: '/h/[hostId]/session/[worktreeId]',
grants: [
'navigate',
'storage',
'externalLink',
'haptics',
'screencastBinary',
'native.clipboard.write',
'native.clipboard.read',
'native.media.pick',
'native.media.read',
'native.media.release',
'native.audio.start',
'native.audio.read',
'native.audio.stop'
],
// The one optional grant in the list (C8.1): the HTML preview's links, hidden rather than dead
// against a shell that cannot open one.
optionalGrants: ['externalNavigation']
}
]
const sessionGrants = EXPECTED_PAGE_ROUTES.filter(
(route) => route.pathname === '/h/[hostId]/session/[worktreeId]'
).flatMap((route) => route.grants)
const sessionOptionalGrants = EXPECTED_PAGE_ROUTES.filter(
(route) => route.pathname === '/h/[hostId]/session/[worktreeId]'
).flatMap((route) => route.optionalGrants ?? [])
const withPrefix = (prefix) => sessionGrants.filter((grant) => grant.startsWith(prefix))
/** Every count this table's own comments spell out, beside the list each is a count of. */
const SPELLED_COUNTS = [
{ precedes: 'grants', counted: sessionGrants.length },
{ precedes: 'audio verbs', counted: withPrefix('native.audio.').length },
{ precedes: 'media verbs', counted: withPrefix('native.media.').length },
// "All three or none": the audio verbs again, as the rule that they are declared together.
{ precedes: 'or none', counted: withPrefix('native.audio.').length },
// C8.1's, and the count the optional lane will grow first.
{ precedes: 'optional grant', counted: sessionOptionalGrants.length }
]
describe('the page routes the manifest declares', () => {
it('turns a route key into the URL pattern expo-router gives it', () => {
expect(routePathnameFromKey('./h/[hostId]/index.tsx')).toBe('/h/[hostId]')
@@ -131,24 +26,6 @@ describe('the page routes the manifest declares', () => {
expect(routePathnameFromKey('./h/[hostId]/_layout.tsx')).toBeNull()
})
it("spells the session route's own counts off the table it comments", async () => {
const source = await readFile(
join(projectDir, 'config', 'scripts', 'mobile-web-page-routes.mjs'),
'utf8'
)
for (const { precedes, spelled, counts } of spelledCountsAgainstTables(
source,
SPELLED_COUNTS
)) {
expect(spelled, precedes).toEqual(counts)
}
})
it('declares only routes the bundle has a module for', async () => {
const keys = await collectMobileWebAppRouteKeys(appDir)
expect(resolveMobileWebPageRoutes(keys)).toEqual(EXPECTED_PAGE_ROUTES)
})
/**
* The optional lane through the builder, which drops what it does not name.
*
@@ -200,18 +77,4 @@ describe('the page routes the manifest declares', () => {
)
).toThrow('has no module in the bundle')
})
itBundling(
'reaches the built manifest, where the build id does not move for it',
async () => {
await withScratch(async (scratch) => {
const { manifest } = await buildMobileWebAppBundle({ outDir: join(scratch, 'bundle') })
expect(manifest.routes).toEqual(EXPECTED_PAGE_ROUTES)
// The routes are derived from the same tree the script is built from, so the assets
// already decide them and the id has no reason to carry them as well.
expect(manifest.buildId).toBe(computeMobileWebBundleBuildId(manifest.assets))
})
},
240_000
)
})
@@ -3,7 +3,6 @@ import { createRequire } from 'node:module'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import { relayArtifactFilenames } from '../../src/shared/relay-artifacts.ts'
const projectDir = resolve(import.meta.dirname, '../..')
const require = createRequire(import.meta.url)
@@ -25,18 +24,10 @@ describe('Electron runtime package contract', () => {
}
it('keeps the native Windows registry addon optional and platform-gated', () => {
const rebuildScript = readProject('config/scripts/rebuild-native-deps.mjs')
const ensureScript = readProject('config/scripts/ensure-native-runtime.mjs')
expect(packageJson.optionalDependencies['@orca/windows-registry']).toBe('workspace:*')
// Why: allowBuilds stops pnpm running node-gyp at install time -- the root
// Windows-only rebuild owns this addon so it is built against the right runtime ABI.
expect(pnpmWorkspace.allowBuilds['@orca/windows-registry']).toBe(false)
// Why assert the guard and the member separately: the list now carries more
// than one addon, so pinning the whole literal only tested its formatting.
expect(rebuildScript).toContain("rebuildPlatform === 'win32'")
expect(rebuildScript).toContain("'@orca/windows-registry'")
expect(ensureScript).toContain("process.platform === 'win32'")
expect(ensureScript).toContain("'@orca/windows-registry'")
if (windowsAddonsInstalled) {
expect(packageTargets.win32).toEqual(
expect.arrayContaining([
@@ -55,20 +46,10 @@ describe('Electron runtime package contract', () => {
})
it('keeps the native Windows process-table addon optional and platform-gated', () => {
const rebuildScript = readFileSync(
join(projectDir, 'config/scripts/rebuild-native-deps.mjs'),
'utf8'
)
const ensureScript = readFileSync(
join(projectDir, 'config/scripts/ensure-native-runtime.mjs'),
'utf8'
)
expect(packageJson.optionalDependencies['@vscode/windows-process-tree']).toBe('0.8.0')
// Why: same rule as the registry addon -- allowBuilds stops pnpm running node-gyp at
// install time so the Windows-only rebuild owns it with the right runtime ABI.
expect(pnpmWorkspace.allowBuilds['@vscode/windows-process-tree']).toBe(false)
expect(rebuildScript).toContain("'@vscode/windows-process-tree'")
expect(ensureScript).toContain("'@vscode/windows-process-tree'")
// Why pin the patch: the upstream binding.gyp requires Spectre-mitigated
// libraries our build agents do not carry, and the enumeration stops after
// 1024 processes -- on a busy host that silently hides the very descendants
@@ -92,195 +73,6 @@ describe('Electron runtime package contract', () => {
}
})
it('guards package scripts that launch Electron tooling', () => {
const scripts = packageJson.scripts
const guardedScripts = [
'start',
'dev',
'dev-stable-name',
'build:unpack',
'build:win',
'build:mac',
'build:mac:release',
'build:linux',
'test:e2e',
'test:e2e:terminal-rendering-golden',
'test:e2e:posix-profile-index-golden',
'test:e2e:terminal-rendering-release-evidence',
'test:e2e:headful'
]
for (const scriptName of guardedScripts) {
expect(scripts[scriptName], scriptName).toContain('pnpm run ensure:electron-runtime &&')
}
})
it('keeps Windows and Linux package builds off macOS native helper builds', () => {
const scripts = packageJson.scripts
expect(scripts['build:desktop']).not.toContain('build:computer-macos')
expect(scripts['build:desktop']).not.toContain('build:keyboard-layout-macos')
expect(scripts['build:win']).toContain('pnpm run build:desktop')
expect(scripts['build:win']).not.toContain('pnpm run build ')
expect(scripts['build:win']).not.toContain('build:computer-macos')
expect(scripts['build:win']).not.toContain('build:keyboard-layout-macos')
expect(scripts['build:linux']).toContain('pnpm run build:desktop')
expect(scripts['build:linux']).not.toContain('pnpm run build ')
expect(scripts['build:linux']).not.toContain('build:computer-macos')
expect(scripts['build:linux']).not.toContain('build:keyboard-layout-macos')
expect(scripts['build:mac']).toContain('pnpm run build:computer-macos')
expect(scripts['build:mac']).toContain('pnpm run build:keyboard-layout-macos')
expect(scripts['build:release']).toContain('pnpm run build:native')
expect(scripts['build:release']).not.toContain('build:computer-macos')
})
it('runs the web build through the heap-sized Vite wrapper', () => {
expect(packageJson.scripts['build:web']).toContain('node config/scripts/run-vite-web-build.mjs')
expect(packageJson.scripts['build:web']).toContain('node config/scripts/verify-web-build.mjs')
})
it('guards release publishing before electron-builder runs', () => {
const releaseWorkflow = readFileSync(
join(projectDir, '.github/workflows/release-cut.yml'),
'utf8'
)
const parsedWorkflow = parse(releaseWorkflow)
const macWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-mac-build.yml'), 'utf8')
)
const releaseCommands = new Map(
parsedWorkflow.jobs.build.strategy.matrix.include.map(({ platform, release_command }) => [
platform,
release_command
])
)
const macReleaseCommand = macWorkflow.jobs['build-mac'].steps.find(
(step) => step.name === 'Publish release artifacts (macOS)'
).with.command
expect([...releaseCommands.keys()].sort()).toEqual(['linux-arm64', 'linux-x64', 'win'])
for (const command of [...releaseCommands.values(), macReleaseCommand]) {
expect(command).toContain('node config/scripts/ensure-native-runtime.mjs --runtime=electron')
expect(command).toContain('electron-builder')
expect(command.indexOf('ensure-native-runtime')).toBeLessThan(
command.indexOf('electron-builder')
)
}
expect(macReleaseCommand).toContain(' && ORCA_MAC_RELEASE=1 ')
expect(releaseCommands.get('linux-x64')).toContain(' && pnpm exec electron-builder ')
expect(releaseCommands.get('linux-x64')).toContain('--linux AppImage deb rpm --x64')
expect(releaseCommands.get('linux-arm64')).toContain('ORCA_LINUX_ARM64_RELEASE=1')
expect(releaseCommands.get('linux-arm64')).toContain('--linux AppImage deb rpm --arm64')
expect(releaseCommands.get('win')).toContain(
'; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; pnpm exec electron-builder '
)
})
it('blocks Linux and macOS release packaging on watcher process fault recovery', () => {
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
)
const macWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-mac-build.yml'), 'utf8')
)
const assertFaultGate = (steps, publishStepName, expectedCondition) => {
const names = steps.map((step) => step.name)
const gate = steps.find((step) => step.name === 'Gate runtime file-watcher process isolation')
expect(gate.if).toBe(expectedCondition)
expect(gate['continue-on-error']).toBeUndefined()
expect(gate.run).toContain('node config/scripts/runtime-file-watcher-fault-harness.mjs')
expect(gate.run).toContain('ELECTRON_RUN_AS_NODE=1 pnpm exec electron')
expect(names.indexOf('Build app')).toBeLessThan(names.indexOf(gate.name))
expect(names.indexOf(gate.name)).toBeLessThan(names.indexOf(publishStepName))
}
assertFaultGate(
releaseWorkflow.jobs.build.steps,
'Publish release artifacts (Linux)',
"runner.os == 'Linux'"
)
assertFaultGate(
macWorkflow.jobs['build-mac'].steps,
'Publish release artifacts (macOS)',
undefined
)
})
it('packages and release-gates the SSH relay watcher child', () => {
const relayBuild = readFileSync(join(projectDir, 'config/scripts/build-relay.mjs'), 'utf8')
const builderConfig = readFileSync(
join(projectDir, 'config/electron-builder.config.cjs'),
'utf8'
)
const remoteCommands = readFileSync(
join(projectDir, 'src/main/ssh/ssh-remote-commands.ts'),
'utf8'
)
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
)
const macWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-mac-build.yml'), 'utf8')
)
expect(relayBuild).toContain("'parcel-watcher-process-entry.ts'")
expect(relayBuild).toContain("outfile: join(outDir, 'relay-watcher.js')")
expect(relayBuild).toContain("outfile: join(outDir, 'relay-ai-vault-service.js')")
expect(builderConfig).toContain("from: 'out/relay'")
// Hashing and remote install probing are manifest-driven, so the contract
// is that both companions are declared once and that both sites read it.
expect(relayArtifactFilenames(true)).toContain('relay-watcher.js')
expect(relayArtifactFilenames(true)).toContain('relay-ai-vault-service.js')
expect(relayBuild).toContain('relayArtifactFilenames(')
expect(remoteCommands).toContain('relayArtifactFilenames(')
const assertRelayGate = (steps, publishStepName) => {
const names = steps.map((step) => step.name)
const gate = steps.find((step) => step.name === 'Gate SSH relay watcher process isolation')
expect(gate['continue-on-error']).toBeUndefined()
expect(gate.run).toContain('node config/scripts/relay-watcher-fault-harness.mjs')
expect(names.indexOf('Build app')).toBeLessThan(names.indexOf(gate.name))
expect(names.indexOf(gate.name)).toBeLessThan(names.indexOf(publishStepName))
}
assertRelayGate(releaseWorkflow.jobs.build.steps, 'Publish release artifacts (Linux)')
assertRelayGate(macWorkflow.jobs['build-mac'].steps, 'Publish release artifacts (macOS)')
const releaseNames = releaseWorkflow.jobs.build.steps.map((step) => step.name)
expect(releaseNames.indexOf('Gate SSH relay watcher process isolation')).toBeLessThan(
releaseNames.indexOf('Build Windows release artifacts')
)
})
it('packages and verifies the Windows SSH node-pty console-list fallback', () => {
const relayBuild = readFileSync(join(projectDir, 'config/scripts/build-relay.mjs'), 'utf8')
const relayDeploy = readFileSync(join(projectDir, 'src/main/ssh/ssh-relay-deploy.ts'), 'utf8')
const patchAsset = readFileSync(
join(projectDir, 'config/relay-assets/node-pty-1.1.0-console-list-agent-patch.cjs'),
'utf8'
)
expect(relayBuild).toContain('copyFileSync(')
expect(relayBuild).toContain('hash.update(readFileSync')
expect(relayBuild).toContain('node-pty-1.1.0-console-list-agent-patch.cjs')
expect(relayDeploy).toContain('assertPatchedNodePtyConsoleListAgent')
expect(relayDeploy.match(/\$\{windowsNodePtyPatchCommand\(nodePath\)\}/g)).toHaveLength(2)
expect(patchAsset).toContain('consoleProcessList = [shellPid];')
expect(patchAsset).toContain('packageJson.version !== EXPECTED_NODE_PTY_VERSION')
})
it('pins the Windows release builder to the VS 2022 runner image', () => {
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
)
const windowsReleaseEntry = releaseWorkflow.jobs.build.strategy.matrix.include.find(
({ platform }) => platform === 'win'
)
expect(windowsReleaseEntry.os).toBe('windows-2022')
})
it('keeps release-cut signing provenance on GitHub-hosted runners', () => {
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
@@ -306,360 +98,4 @@ describe('Electron runtime package contract', () => {
expect(releaseWorkflow.jobs['publish-release'].needs).toContain('build')
expect(releaseWorkflow.jobs['publish-release'].needs).toContain('build-mac')
})
it('runs the macOS release build in an isolated Blacksmith workflow', () => {
const releaseMacWorkflowText = readFileSync(
join(projectDir, '.github/workflows/release-mac-build.yml'),
'utf8'
)
const releaseMacWorkflow = parse(releaseMacWorkflowText)
const buildMacJob = releaseMacWorkflow.jobs['build-mac']
const checkoutStep = buildMacJob.steps.find((step) => step.name === 'Checkout')
const publishStep = buildMacJob.steps.find(
(step) => step.name === 'Publish release artifacts (macOS)'
)
expect(releaseMacWorkflow['run-name']).toBe(
'Mac release build ${{ inputs.tag }} (${{ inputs.release_run_id }})'
)
expect(releaseMacWorkflow.on.workflow_dispatch.inputs.tag.required).toBe(true)
expect(releaseMacWorkflow.on.workflow_dispatch.inputs.release_run_id.required).toBe(true)
expect(buildMacJob['runs-on']).toBe('blacksmith-6vcpu-macos-15')
expect(checkoutStep.with.ref).toBe('refs/tags/${{ inputs.tag }}')
expect(publishStep.with.command).toContain('ORCA_MAC_RELEASE=1')
expect(publishStep.with.command).toContain('electron-builder')
expect(publishStep.with.command).toContain('--mac --publish always')
expect(releaseMacWorkflowText).not.toContain('signpath/')
expect(releaseMacWorkflowText).not.toContain('SIGNPATH_')
})
it('publishes both Linux release matrix entries', () => {
const releaseWorkflow = readFileSync(
join(projectDir, '.github/workflows/release-cut.yml'),
'utf8'
)
const parsedWorkflow = parse(releaseWorkflow)
const publishLinuxStep = parsedWorkflow.jobs.build.steps.find(
(step) => step.name === 'Publish release artifacts (Linux)'
)
expect(publishLinuxStep.if).toContain("matrix.platform == 'linux-x64'")
expect(publishLinuxStep.if).toContain("matrix.platform == 'linux-arm64'")
expect(publishLinuxStep.with.command).toBe('${{ matrix.release_command }}')
})
it('keeps Linux postinstall repairing Chromium sandbox permissions', () => {
const afterInstallScript = readFileSync(
join(projectDir, 'resources/linux/packaging/after-install.sh'),
'utf8'
)
expect(afterInstallScript).toContain('chrome-sandbox')
expect(afterInstallScript).toContain('chmod 4755 "$sandbox"')
expect(afterInstallScript).not.toContain('chmod 0755 "$sandbox"')
expect(afterInstallScript).toContain('is_owned_link()')
expect(afterInstallScript).toContain('readlink -f -- "$link"')
expect(afterInstallScript).toContain('[ ! -e "$link" ] && [ ! -L "$link" ]')
expect(afterInstallScript).not.toContain('[ ! -e "$link" ] || [ -L "$link" ]')
})
it('advances only the skill release ledger in a taggable release-cut commit', () => {
const releaseWorkflow = readFileSync(
join(projectDir, '.github/workflows/release-cut.yml'),
'utf8'
)
const parsedWorkflow = parse(releaseWorkflow)
const checkoutStep = parsedWorkflow.jobs.cut.steps.find((step) => step.name === 'Checkout ref')
const bumpStep = parsedWorkflow.jobs.cut.steps.find(
(step) => step.name === 'Bump package.json and tag'
)
const bumpIndex = bumpStep.run.indexOf(
'npm version "$VERSION" --no-git-tag-version --allow-same-version'
)
const generateIndex = bumpStep.run.indexOf(
'node config/scripts/generate-skill-bundle-manifest.mjs --release "$VERSION"'
)
const commands = bumpStep.run.replace(/^\s*#.*$/gm, '')
// Unanchored: a `git add` chained after `&&` stages just as effectively.
const stagedPaths = [...commands.matchAll(/\bgit add (.+)$/gm)].flatMap((match) =>
match[1].trim().split(/\s+/)
)
// Quotes trimmed and deduped: the index guard names the row a second time.
const mentioned = new Set(commands.match(/resources[/\\]skills[^\s'"]*/g))
expect(checkoutStep.with['fetch-depth']).toBe(0)
expect(bumpIndex).toBeGreaterThanOrEqual(0)
// Why: the cut is the only point that advances the release ledger, so this
// tag's revision is never rebuilt later — it appends that row, nothing else.
expect(generateIndex).toBeGreaterThan(bumpIndex)
expect(bumpStep.run.indexOf('git add package.json')).toBeGreaterThan(generateIndex)
expect(stagedPaths).toEqual(['package.json', 'resources/skills/release-mapping.json'])
// Every distinct mention must be staged, so a copy, a redirect, or a path
// held in a variable cannot reach the content-addressed artifacts. Matched
// without a trailing slash so `dir="resources/skills"` still counts.
expect([...mentioned]).toEqual(stagedPaths.slice(1))
// Regeneration is banned job-wide by the generator suite. Here: `-a`, `-am`,
// and `--all` sweep unstaged artifacts in; `--allow-empty` below must not.
expect(commands).not.toMatch(/\bcommit\b[^\n]*(?:\s-[a-z]*a[a-z]*\b|\s--all\b)/)
expect(bumpStep.run).toContain('git diff --cached --quiet')
expect(bumpStep.run).toContain('git commit --allow-empty -m "$commit_message"')
})
it('keeps release-cut RC retries monotonic across stale attempts', () => {
const releaseWorkflow = readFileSync(
join(projectDir, '.github/workflows/release-cut.yml'),
'utf8'
)
const parsedWorkflow = parse(releaseWorkflow)
const versionStep = parsedWorkflow.jobs.cut.steps.find(
(step) => step.name === 'Compute next version'
)
expect(versionStep.run).toContain('node config/scripts/release-rc-history.mjs "$1"')
expect(versionStep.run).toContain('tag_matches_current_ref')
expect(versionStep.run).toContain('cutting the next version instead of reusing stale artifacts')
expect(versionStep.run).toContain('git rev-parse "$existing_rc_tag"')
})
it('bumps separate Homebrew casks for stable and RC desktop tags', () => {
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
)
const homebrewWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/homebrew-bump.yml'), 'utf8')
)
expect(releaseWorkflow.jobs['homebrew-bump'].if).toContain(
"startsWith(needs.cut.outputs.tag, 'v')"
)
expect(releaseWorkflow.jobs['homebrew-bump'].if).not.toContain('-rc.')
expect(releaseWorkflow.jobs['homebrew-bump-published-rc-draft'].with.tag).toBe(
'${{ needs.cut.outputs.latest_published_rc_tag }}'
)
const resolveCaskStep = homebrewWorkflow.jobs['bump-cask'].steps.find(
(step) => step.name === 'Resolve cask target'
)
const renderStep = homebrewWorkflow.jobs['bump-cask'].steps.find(
(step) => step.name === 'Render updated cask file'
)
const copyStep = homebrewWorkflow.jobs['bump-cask'].steps.find(
(step) => step.name === 'Copy cask into tap and open PR'
)
expect(resolveCaskStep.run).toContain('token="orca@rc"')
expect(resolveCaskStep.run).toContain('token="orca"')
expect(renderStep.env.CASK_PATH).toBe('${{ steps.cask.outputs.path }}')
expect(copyStep.run).toContain('cp "$CASK_PATH" "tap/$CASK_PATH"')
expect(copyStep.run).toContain('git add "$CASK_PATH"')
})
it('installs the Electron package binary in the shared unit-test workflow', () => {
const unitTestWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/unit-tests.yml'), 'utf8')
)
const installStep = unitTestWorkflow.jobs.test.steps.find(
(step) => step.name === 'Install Electron package binary for tests'
)
expect(installStep.run).toBe('node config/scripts/install-electron-package-binary.mjs')
})
it('smokes the packaged CLI from outside the checkout in PR checks', () => {
const prWorkflow = readFileSync(join(projectDir, '.github/workflows/pr.yml'), 'utf8')
const parsedWorkflow = parse(prWorkflow)
const smokeStep = parsedWorkflow.jobs.package.steps.find(
(step) => step.name === 'Smoke packaged CLI'
)
expect(smokeStep.run).toBe(
'node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/linux-unpacked'
)
})
it('keeps terminal scale perf wired to the report budget gate', () => {
const packageScripts = packageJson.scripts
const terminalPerfWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/terminal-perf.yml'), 'utf8')
)
const steps = terminalPerfWorkflow.jobs['terminal-perf'].steps
const runStep = steps.find((step) => step.name === 'Run terminal scale perf report gate')
const uploadStep = steps.find((step) => step.name === 'Upload terminal perf report')
expect(packageScripts['test:e2e:terminal-perf:scale:report']).toContain(
'run-terminal-scale-perf-report-gate.mjs'
)
expect(runStep.run).toContain('pnpm run test:e2e:terminal-perf:scale:report')
expect(runStep.run).toContain('xvfb-run --auto-servernum')
const manualProfileKnobs = [
['ORCA_TERMINAL_PERF_FRAME_COUNT', 'frame_count', 'ORCA_E2E_OPENCODE_FRAME_COUNT'],
[
'ORCA_TERMINAL_PERF_FRAME_INTERVAL_MS',
'frame_interval_ms',
'ORCA_E2E_OPENCODE_FRAME_INTERVAL_MS'
],
[
'ORCA_TERMINAL_PERF_PRESSURE_OUTPUT_CHARS',
'pressure_output_chars',
'ORCA_E2E_OPENCODE_PRESSURE_OUTPUT_CHARS'
],
['ORCA_TERMINAL_PERF_SCALE_PANES', 'scale_panes', 'ORCA_E2E_OPENCODE_SCALE_PANES'],
[
'ORCA_TERMINAL_PERF_SCALE_CROSS_WORKSPACE_PANES',
'scale_cross_workspace_panes',
'ORCA_E2E_OPENCODE_SCALE_CROSS_WORKSPACE_PANES'
],
[
'ORCA_TERMINAL_PERF_SCALE_PRESSURE_PANES',
'scale_pressure_panes',
'ORCA_E2E_OPENCODE_SCALE_PRESSURE_PANES'
],
[
'ORCA_TERMINAL_PERF_SCALE_HIDDEN_PRESSURE_PANES',
'scale_hidden_pressure_panes',
'ORCA_E2E_OPENCODE_SCALE_HIDDEN_PRESSURE_PANES'
]
]
for (const [workflowEnv, inputName, runnerEnv] of manualProfileKnobs) {
expect(runStep.env[workflowEnv]).toBe(`\${{ inputs.${inputName} }}`)
expect(runStep.run).toContain(runnerEnv)
}
expect(uploadStep.uses).toBe('actions/upload-artifact@v7')
expect(uploadStep.with.path).toBe('${{ env.ORCA_E2E_TERMINAL_PERF_REPORT_PATH }}')
})
it('keeps platform golden regressions in the manual and release workflows', () => {
const packageScripts = packageJson.scripts
const goldenWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/golden-e2e-experiment.yml'), 'utf8')
)
const releaseWorkflow = parse(
readFileSync(join(projectDir, '.github/workflows/release-cut.yml'), 'utf8')
)
const steps = goldenWorkflow.jobs['golden-e2e'].steps
const goldenPlatformLabels = new Map([
['linux', 'Linux'],
['mac', 'macOS'],
['windows', 'Windows']
])
const goldenMatrix = goldenWorkflow.jobs['golden-e2e'].strategy.matrix.include
const goldenPlatforms = goldenMatrix.map(({ platform }) => platform).sort()
const goldenRunSteps = new Map(
goldenPlatforms.map((platform) => {
const label = goldenPlatformLabels.get(platform)
expect(label, platform).toBeDefined()
return [platform, steps.find((step) => step.name === `Run golden E2E tests on ${label}`)]
})
)
const releaseGoldenJob = releaseWorkflow.jobs['terminal-rendering-golden']
const releaseGoldenMatrix = releaseGoldenJob.strategy.matrix.include
const releaseEvidenceJob = releaseWorkflow.jobs['terminal-rendering-release-evidence']
const releaseBuildNeeds = releaseWorkflow.jobs.build.needs
const publishReleaseNeeds = releaseWorkflow.jobs['publish-release'].needs
// Why: Windows release evidence is temporarily paused for CI runner PTY readiness.
const releaseEvidencePlatforms = ['linux', 'mac']
expect(packageScripts['test:e2e:terminal-rendering-golden']).toContain(
'@terminal-rendering-golden'
)
expect(packageScripts['test:e2e:terminal-rendering-golden']).toContain(
'terminal-raw-emoji-table-scroll-restore.spec.ts'
)
expect(packageScripts['test:e2e:terminal-rendering-golden']).toContain(
'terminal-webgl-atlas-budget.spec.ts'
)
expect(packageScripts['test:e2e:terminal-rendering-golden']).not.toContain(
'terminal-long-table-scroll-restore.spec.ts'
)
const goldenCommand = packageScripts['test:e2e:terminal-rendering-golden']
expect(goldenCommand).toContain('--project electron-headless')
expect(goldenCommand).toContain('--project electron-headful')
expect(packageScripts['test:e2e:windows-fresh-startup-golden']).toContain(
'golden-windows-fresh-startup.spec.ts'
)
expect(packageScripts['test:e2e:windows-fresh-startup-golden']).toContain(
'@windows-fresh-startup-golden'
)
expect(packageScripts['test:e2e:posix-profile-index-golden']).toContain(
'golden-posix-profile-index-fsync.spec.ts'
)
expect(packageScripts['test:e2e:posix-profile-index-golden']).toContain(
'golden-posix-fresh-startup.spec.ts'
)
expect(packageScripts['test:e2e:posix-profile-index-golden']).toContain(
'@posix-profile-index-golden'
)
expect(packageScripts['test:e2e:terminal-rendering-release-evidence']).toContain(
'terminal-opencode-emoji-table-rendering.spec.ts'
)
expect(packageScripts['test:e2e:terminal-rendering-release-evidence']).toContain(
'terminal-long-table-scroll-restore.spec.ts'
)
expect(goldenMatrix).toEqual([
{ os: 'ubuntu-latest', platform: 'linux' },
{ os: 'macos-15', platform: 'mac' },
{ os: 'windows-2022', platform: 'windows' }
])
expect(goldenRunSteps.get('linux')?.run).toContain(
'pnpm run test:e2e:terminal-rendering-golden'
)
expect(goldenRunSteps.get('linux')?.run).toContain(
'pnpm run --if-present test:e2e:posix-profile-index-golden'
)
expect(goldenRunSteps.get('mac')?.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
expect(goldenRunSteps.get('mac')?.run).toContain(
'pnpm run --if-present test:e2e:posix-profile-index-golden'
)
expect(goldenRunSteps.get('windows')).toMatchObject({
if: "runner.os == 'Windows'",
shell: 'pwsh'
})
expect(goldenRunSteps.get('windows').run).toContain(
'pnpm run --if-present test:e2e:windows-fresh-startup-golden'
)
expect(goldenWorkflow.on.pull_request).toBeUndefined()
expect(goldenWorkflow.on.workflow_dispatch).toBeDefined()
expect(releaseBuildNeeds).not.toContain('terminal-rendering-golden')
expect(releaseBuildNeeds).not.toContain('terminal-rendering-release-evidence')
expect(publishReleaseNeeds).toContain('terminal-rendering-golden')
expect(publishReleaseNeeds).toContain('build')
expect(publishReleaseNeeds).not.toContain('terminal-rendering-release-evidence')
expect(releaseGoldenJob['continue-on-error']).toBeUndefined()
expect(releaseGoldenMatrix).toEqual(goldenMatrix)
const releaseLinuxRunStep = releaseGoldenJob.steps.find(
(step) => step.name === 'Run terminal rendering golden on Linux'
)
expect(releaseLinuxRunStep.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
expect(releaseLinuxRunStep.run).toContain(
'pnpm run --if-present test:e2e:posix-profile-index-golden'
)
const releaseMacRunStep = releaseGoldenJob.steps.find(
(step) => step.name === 'Run terminal rendering golden on macOS'
)
expect(releaseMacRunStep.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
expect(releaseMacRunStep.run).toContain(
'pnpm run --if-present test:e2e:posix-profile-index-golden'
)
const releaseWindowsRunStep = releaseGoldenJob.steps.find(
(step) => step.name === 'Run fresh-startup golden on Windows'
)
expect(releaseWindowsRunStep).toMatchObject({
if: "runner.os == 'Windows'",
shell: 'pwsh'
})
expect(releaseWindowsRunStep.run).toContain(
'pnpm run --if-present test:e2e:windows-fresh-startup-golden'
)
expect(releaseWindowsRunStep.run).not.toContain('test:e2e:workspace-session-golden')
expect(releaseWindowsRunStep.run).not.toContain('test:e2e:source-control-golden')
expect(releaseEvidenceJob['continue-on-error']).toBe(true)
expect(
releaseEvidenceJob.strategy.matrix.include.map(({ platform }) => platform).sort()
).toEqual(releaseEvidencePlatforms)
expect(releaseEvidenceJob.steps.map((step) => step.run ?? '')).toContain(
'xvfb-run --auto-servernum env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1 pnpm run test:e2e:terminal-rendering-release-evidence'
)
})
})
@@ -1,37 +0,0 @@
import { readFileSync } from 'node:fs'
import { parse } from 'yaml'
import { describe, expect, it } from 'vitest'
describe('packaged hang watchdog worker contract', () => {
it('boots the worker from app.asar in PR checks', () => {
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
const smokeSource = readFileSync(
'config/scripts/smoke-packaged-hang-watchdog-worker.mjs',
'utf8'
)
const smokeStep = workflow.jobs.package.steps.find(
(step) => step.name === 'Smoke packaged hang watchdog worker'
)
expect(smokeStep.run).toBe(
'xvfb-run --auto-servernum node config/scripts/smoke-packaged-hang-watchdog-worker.mjs --app-dir=dist/linux-unpacked'
)
expect(smokeSource).toContain(
"process.platform === 'linux' ? ['--no-sandbox', launcherDir] : [launcherDir]"
)
expect(smokeSource).toContain('const LAUNCH_TIMEOUT_MS = 30_000')
expect(smokeSource).toContain('timeout: LAUNCH_TIMEOUT_MS')
})
// Why: Electron ignores process.exitCode, so the gate needs app.exit plus a stdout assertion.
it('fails the smoke when the packaged worker never reports success', () => {
const smokeSource = readFileSync(
'config/scripts/smoke-packaged-hang-watchdog-worker.mjs',
'utf8'
)
expect(smokeSource).toContain('app.exit(1)')
expect(smokeSource).not.toContain('app.quit()')
expect(smokeSource).toContain('if (!result.stdout.includes(SUCCESS_LINE))')
})
})
+7 -1
View File
@@ -53,8 +53,14 @@ const GIT_COMPAT_PREFIXES = [
]
// Why narrow: the contract pins Codex's read-repair, so it runs when the heal that
// depends on it, its app-server transport, or the contract itself changes.
// depends on it, its app-server transport, or the contract itself changes. The same
// job pins --no-daemon for Orca's codex shell wrapper and the project-trust key.
const CODEX_INDEX_HEAL_CONTRACT_PREFIXES = [
'src/main/agent-trust-presets',
'src/main/codex/config-toml-trust',
'src/main/pty/codex-no-daemon-binary-contract',
'src/main/pty/codex-shell-launch-preflight',
'src/shared/codex-shell-function',
'src/main/codex/codex-index-heal-binary-contract',
'src/main/codex/codex-session-index-heal',
'src/main/codex/codex-app-server-session',
@@ -153,6 +153,13 @@ describe('per-job path classification', () => {
expectClassification(['src/main/codex/codex-index-heal-binary-contract.test.ts'], {
codex_index_heal_contract: true
})
for (const file of ['src/main/agent-trust-presets.ts', 'src/main/codex/config-toml-trust.ts']) {
expectClassification([file], {
codex_index_heal_contract: true,
package: true,
package_windows: true
})
}
// Keep the real-binary gate live when a transport or launch dependency changes.
for (const file of [
'src/main/codex/codex-app-server-capability-signal.ts',
@@ -1,86 +0,0 @@
import { readFileSync } from 'node:fs'
import { parse } from 'yaml'
import { describe, expect, it } from 'vitest'
// Why: pr.yml re-lists the `lint` chain as individual steps so a single failure
// does not mask the rest and oxlint keeps its `--format github` annotations.
// Hand-maintained mirrors drift (#10601: three verifiers never ran on PRs), so
// this gate fails the moment a `lint` step has no counterpart in pr.yml.
// Flags that only change reporting, so they must not split two otherwise identical commands.
const REPORTING_FLAGS_WITH_VALUE = new Set(['--format', '--reporter'])
const REPORTING_FLAGS = new Set(['--quiet'])
const PACKAGE_RUNNER_TOKENS = new Set(['pnpm', 'npm', 'yarn', 'npx', 'run', 'exec', 'node'])
function splitCommandChain(command) {
return command
.split(/\n|&&|;/)
.map((part) => part.trim())
.filter(Boolean)
}
function canonicalize(command) {
const tokens = command.split(/\s+/)
const canonical = []
for (let index = 0; index < tokens.length; index += 1) {
const token = tokens[index]
if (canonical.length === 0 && PACKAGE_RUNNER_TOKENS.has(token)) {
continue
}
if (REPORTING_FLAGS.has(token)) {
continue
}
if (REPORTING_FLAGS_WITH_VALUE.has(token)) {
index += 1
continue
}
canonical.push(token)
}
return canonical.join(' ')
}
/** Expands `pnpm run x` indirection until every entry is a real binary invocation. */
function resolveLeafCommands(command, scripts, seen = new Set()) {
const leaves = []
for (const part of splitCommandChain(command)) {
const scriptName = part.match(/^(?:pnpm|npm|yarn)(?:\s+run)?\s+([\w:-]+)$/)?.[1]
if (scriptName && scripts[scriptName] && !seen.has(scriptName)) {
leaves.push(
...resolveLeafCommands(scripts[scriptName], scripts, new Set([...seen, scriptName]))
)
continue
}
leaves.push(canonicalize(part))
}
return leaves
}
describe('PR workflow lint parity', () => {
it('runs every `pnpm lint` step on pull requests', () => {
const { scripts } = JSON.parse(readFileSync('package.json', 'utf8'))
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
// Scan every job: which one hosts the lint steps is an organizational
// detail that has already been renamed once (verify -> static_analysis).
const workflowCommands = new Set(
Object.values(workflow.jobs)
.flatMap((job) => job.steps ?? [])
.filter((step) => typeof step.run === 'string')
.flatMap((step) => resolveLeafCommands(step.run, scripts))
)
const missing = resolveLeafCommands(scripts.lint, scripts).filter(
(leaf) => !workflowCommands.has(leaf)
)
expect(
missing,
`.github/workflows/pr.yml is missing lint steps: ${missing.join(', ')}. ` +
'Add a step for each one so PR CI matches `pnpm lint`.'
).toEqual([])
})
})
@@ -2,7 +2,6 @@ import { existsSync, globSync, readFileSync } from 'node:fs'
import { parse } from 'yaml'
import { describe, expect, it } from 'vitest'
import { UNIT_EXCLUDE } from './ci-unit-files.mjs'
import { mobileWebCheckArgs } from './run-mobile-web-app-checks.mjs'
import { MOBILE_WEB_APP_DEPENDENCIES_REQUIRED_ENV } from './mobile-web-app-bundle-dependencies.mjs'
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
@@ -559,12 +558,5 @@ describe('PR workflow parallelism', () => {
expect(step.run).toContain('node config/scripts/run-mobile-web-app-checks.mjs')
expect(step.run).not.toContain('--prepare-route-snapshot')
expect(step.env[MOBILE_WEB_APP_DEPENDENCIES_REQUIRED_ENV]).toBe('1')
expect(mobileWebCheckArgs).toEqual([
'run',
'--config',
'config/vitest.config.ts',
'config/scripts/mobile-web-app-',
'config/scripts/build-mobile-web-app-bundle.test.mjs'
])
})
})
@@ -98,26 +98,6 @@ const PATCHED = {
}
describe('pnpm diff format', () => {
it('keeps the exact git flags pnpm uses, so patches survive `pnpm patch-commit`', () => {
expect(PNPM_DIFF_FLAGS).toEqual([
'-c',
'core.safecrlf=false',
'-c',
'core.quotePath=false',
'diff',
'--src-prefix=a/',
'--dst-prefix=b/',
'--ignore-cr-at-eol',
'--irreversible-delete',
'--full-index',
'--no-index',
'--text',
'--no-ext-diff',
'--no-color',
'--'
])
})
it('matches pnpm git config isolation', () => {
const environment = pnpmDiffEnvironment({ PATH: '/usr/bin', HOME: '/Users/someone' })
expect(environment).toMatchObject({
+1 -1
View File
@@ -9,7 +9,7 @@ import { PAGE_ROUTE_MODULES } from './mobile-web-app-page-route-modules.mjs'
import { spawnProcess } from './script-child-process.mjs'
const require = createRequire(import.meta.url)
export const mobileWebCheckArgs = [
const mobileWebCheckArgs = [
'run',
'--config',
'config/vitest.config.ts',
@@ -23,6 +23,11 @@ const knobByFlag = {
'--keys': 'ORCA_TYPING_BENCH_KEYS',
'--cadence-ms': 'ORCA_TYPING_BENCH_KEY_CADENCE_MS',
'--cpu-workers': 'ORCA_TYPING_BENCH_CPU_WORKERS',
'--git-churn-repos': 'ORCA_TYPING_BENCH_GIT_CHURN_REPOS',
'--git-churn-files': 'ORCA_TYPING_BENCH_GIT_CHURN_FILES',
'--git-churn-concurrency': 'ORCA_TYPING_BENCH_GIT_CHURN_CONCURRENCY',
'--codex-frame-rows': 'ORCA_TYPING_BENCH_CODEX_FRAME_ROWS',
'--codex-split-delay-ms': 'ORCA_TYPING_BENCH_CODEX_SPLIT_DELAY_MS',
'--worktrees': 'ORCA_TYPING_BENCH_METADATA_WORKTREES',
'--repositories': 'ORCA_TYPING_BENCH_METADATA_REPOSITORIES',
'--terminal-tabs': 'ORCA_TYPING_BENCH_METADATA_TERMINAL_TABS',
@@ -8,7 +8,6 @@ import {
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { copyScriptWithLocalModules } from './script-module-dependencies.mjs'
@@ -108,18 +107,4 @@ describe('copyScriptWithLocalModules', () => {
copyScriptWithLocalModules(join(sourceDir, 'entry.mjs'), destinationDir)
expect(existsSync(join(destinationDir, 'entry.mjs'))).toBe(true)
})
// The real tree this stages: the packaged-addon gate reaches its PE reader by
// require, so a walker that missed it would break every rebuild fixture.
it('stages the node-pty job-ownership gate with everything it requires', () => {
const destinationDir = join(mkdtempSync(join(fixtureDir, 'dest-')), 'scripts')
copyScriptWithLocalModules(
fileURLToPath(new URL('./node-pty-job-ownership.cjs', import.meta.url)),
destinationDir
)
expect(readdirSync(destinationDir).sort()).toEqual([
'node-pty-job-ownership.cjs',
'windows-pe-machine.cjs'
])
})
})
@@ -37,26 +37,6 @@ it('gives every removed SSH spec a dedicated owner even for test-only edits', ()
])
})
it('runs all four isolated shards and the special SSH suites exactly once', () => {
expect(job.strategy.matrix.shard).toEqual([1, 2, 3, 4])
expect(job.strategy['fail-fast']).toBe(false)
const remaining = job.steps.find((step) => step.name === 'Run remaining Docker SSH E2E')
expect(remaining.run).toContain('pnpm run test:e2e:ssh-docker --shard=${{ matrix.shard }}/4')
expect(remaining.if).toBe('always()')
expect(remaining['continue-on-error']).toBeUndefined()
expect(readRunner(runners[0])).toContain("'--workers=1'")
expect(job.steps.find((step) => step.name === 'Run Docker SSH watcher isolation E2E').if).toBe(
'matrix.shard == 1'
)
expect(
job.steps.find(
(step) => step.name === 'Run Docker SSH terminal parking + startup readiness E2E'
).if
).toBe('always() && matrix.shard == 1')
const upload = job.steps.find((step) => step.uses === 'actions/upload-artifact@v7')
expect(upload.with.name).toContain('${{ matrix.shard }}')
})
it('native Playwright shards preserve every SSH test and project exactly once', async () => {
const cli = join(dirname(require.resolve('playwright/package.json')), 'cli.js')
const env = {
@@ -45,29 +45,6 @@ describe('terminal IME e2e workflow', () => {
expect(nativeIndex).toBeGreaterThan(deterministicIndex)
})
it('keeps IBus lifecycle scoped to owned processes', () => {
const runner = readFileSync(
join(projectDir, 'config/scripts/run-terminal-ibus-hangul-e2e.mjs'),
'utf8'
)
expect(runner).toContain(
"['--xim', '--verbose', '--panel=disable', '--emoji-extension=disable']"
)
expect(runner).toContain("spawn('xfwm4', ['--compositor=off']")
expect(runner).toContain("['initial-input-mode', 'hangul']")
expect(runner).toContain("['hangul-keyboard', '2']")
expect(runner).toContain("process.kill(-processGroupId, 'SIGTERM')")
expect(runner).toContain("process.kill(-processGroupId, 'SIGKILL')")
expect(runner).toContain('const killDeadline = Date.now() + processKillTimeoutMs')
expect(runner).toMatch(
/'test:e2e:headful',\s*'--workers=1',\s*'tests\/e2e\/terminal-ibus-hangul-native\.spec\.ts'/
)
expect(runner).not.toContain("'--replace'")
expect(runner).not.toContain('killall')
expect(runner).not.toContain('pkill')
})
it('runs native Wayland independently with CJK fonts and retained evidence', () => {
const job = workflow.jobs['linux-wayland']
expect(job.needs).toBeUndefined()
@@ -82,13 +59,4 @@ describe('terminal IME e2e workflow', () => {
expect(upload.if).toBe('always()')
expect(upload.with.name).toBe('terminal-wayland-ime-evidence')
})
it('bounds blocking native input commands', () => {
const nativeSpec = readFileSync(
join(projectDir, 'tests/e2e/terminal-ibus-hangul-native.spec.ts'),
'utf8'
)
expect(nativeSpec.match(/timeout: NATIVE_COMMAND_TIMEOUT_MS/g)).toHaveLength(3)
})
})
@@ -5,7 +5,7 @@
// CSI strip against the shipped shape (256KB tail + shared CSI_SEQUENCE_PATTERN). Every
// variant is asserted to agree with the baseline before it is timed.
// 2. TerminalKittyKeyboardModeTracker.scanReplay — measured to justify leaving it alone, and
// to record that porting the daemon mouse mirror's includes() pre-filter makes it slower.
// to record that an includes() introducer pre-filter makes it slower.
//
// Payloads are generated deterministically (LCG, no Math.random) so runs compare.
//
@@ -214,7 +214,7 @@ const kittyScan = (data) => {
tracker.scanReplay(data)
return tracker.flags
}
// Mirrors src/main/daemon/terminal-mouse-mode-mirror.ts:41-47.
// The includes() introducer pre-filter a daemon-side mode scan would use.
const kittyGated = (data) => {
if (!data.includes('\x1b[?') && !data.includes('\x1bc') && !data.includes('\x9b')) {
return 0
@@ -53,19 +53,6 @@ describe('electron-builder dev-channel identity', () => {
expect(config.win.verifyUpdateCodeSignature).toBe(false)
})
// Why on every channel: the hook is the only handle electron-builder gives on
// the NSIS uninstaller, and it signs nothing — it relays the file to and from
// the CI SignPath request. Carrying it must not drag a publisherName onto a
// dev build, which is the failure the split above exists to prevent.
it('carries the uninstaller sign hook without changing publisherName semantics', () => {
for (const env of [{}, WIN_ADHOC_ENV]) {
const config = loadConfigWithEnv(env)
expect(typeof config.win.signtoolOptions.sign).toBe('function')
}
expect(loadConfigWithEnv({}).win.signtoolOptions.publisherName).toBe('SignPath Foundation')
expect(loadConfigWithEnv(WIN_ADHOC_ENV).win.signtoolOptions.publisherName).toBeUndefined()
})
it.each([
['hourly', { ORCA_WIN_HOURLY: '1' }, 'orca-hourly'],
['daily', { ORCA_WIN_DAILY: '1' }, 'orca-daily'],
+1 -35
View File
@@ -1,4 +1,3 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it, vi } from 'vitest'
import { parseArgs } from '../../tests/tools/win-crash-survival-e2e/cli-args.mjs'
import { buildCrashAssertions } from '../../tests/tools/win-crash-survival-e2e/crash-assertions.mjs'
@@ -13,26 +12,6 @@ import { closeApp, resolveElectronMainPid } from '../../tests/tools/win-update-e
import { isPidAlive } from '../../tests/tools/win-update-e2e/daemon-processes.mjs'
describe('win-crash-survival-e2e proof contracts', () => {
it('keeps the packaged proof manually dispatchable without a PR trigger', () => {
const workflow = readFileSync('.github/workflows/win-crash-survival-e2e.yml', 'utf8')
expect(workflow).not.toMatch(/^ pull_request:/m)
expect(workflow).toMatch(/^ workflow_dispatch:/m)
expect(workflow).not.toMatch(/^ push:/m)
expect(workflow).toContain('--expect "$env:EXPECT"')
expect(workflow).toContain('exit $LASTEXITCODE')
expect(workflow).toContain("'!config/**/*.test.*'")
expect(workflow).toContain("'!src/**/*.test.*'")
expect(workflow).toContain("'!src/**/*.bench.*'")
expect(workflow).toContain("'!config/reliability-gates.jsonc'")
expect(workflow).toContain("'resources/**'")
expect(workflow).toContain('cache: pnpm')
expect(workflow.indexOf('- name: Setup Node.js')).toBeGreaterThan(
workflow.indexOf('- name: Setup pnpm')
)
expect(workflow).toContain("if: steps.cache-installer.outputs.cache-hit != 'true'")
expect(workflow).toContain('crash-survival-electron-builder-')
})
it('requires the full survival oracle, including daemon identity and reattach', () => {
const base = {
profile: 'survival',
@@ -58,15 +37,6 @@ describe('win-crash-survival-e2e proof contracts', () => {
).toBe(false)
})
it('scans for FailFast only after the post-crash input probe', () => {
const harness = readFileSync('tests/tools/win-crash-survival-e2e/run.mjs', 'utf8')
const scanIndex = harness.indexOf('const { events: failFastEvents }')
const probeIndex = harness.indexOf('reattachProven = await proveReattachedShell')
expect(scanIndex).not.toBe(-1)
expect(probeIndex).not.toBe(-1)
expect(scanIndex).toBeGreaterThan(probeIndex)
})
it('fails closed when the Windows event log query fails', () => {
let command = ''
expect(() =>
@@ -162,11 +132,7 @@ describe('win-crash-survival-e2e proof contracts', () => {
expect(reattachSentinelMatches('1660|canary|extra', 'canary', 1660)).toBe(false)
})
it('requires the real packaged main for the crash proof but permits fallback cleanup', async () => {
const harness = readFileSync('tests/tools/win-crash-survival-e2e/run.mjs', 'utf8')
expect(harness).toContain(
'resolveElectronMainPid(session.app, { allowLauncherFallback: false })'
)
it('resolves the real packaged main but permits fallback cleanup', async () => {
expect(
await resolveElectronMainPid({
evaluate: async () => 222,
@@ -1,43 +1,13 @@
import { execFileSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { existsSync } from 'node:fs'
import { isAbsolute, join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const projectDir = resolve(import.meta.dirname, '../..')
const PATCH = readFileSync(
join(projectDir, 'config/patches/@vscode__windows-process-tree@0.8.0.patch'),
'utf8'
)
const PACKAGE_DIR = join(projectDir, 'node_modules', '@vscode', 'windows-process-tree')
const RESOLVED_GYP = "require.resolve('node-addon-api/node_addon_api.gyp')"
describe('windows-process-tree node-addon-api gyp path', () => {
it('stages headers without a pnpm-sensitive gyp dependency', () => {
expect(PATCH).not.toContain('+ "../../node-addon-api')
expect(PATCH).toContain('+ "include_dirs": ["deps/node-addon-api"],')
expect(PATCH).toContain('+ "defines": ["NAPI_CPP_EXCEPTIONS", "_HAS_EXCEPTIONS=1"],')
const buildScript = readFileSync(
join(projectDir, 'config/scripts/build-windows-process-tree-relay-addon.mjs'),
'utf8'
)
expect(buildScript).toContain('stageWindowsProcessTreeNodeAddonApiHeaders(PACKAGE_DIR)')
expect(buildScript).toContain('Repaired un-applied pnpm patch hunks before build.')
const rebuildHelper = readFileSync(
join(projectDir, 'config/scripts/windows-process-tree-gyp-rebuild.mjs'),
'utf8'
)
expect(rebuildHelper).toContain("createRequire(join(packageDir, 'package.json'))")
expect(rebuildHelper).toContain("resolve('node-addon-api/package.json')")
expect(rebuildHelper).toContain("'napi.h'")
expect(rebuildHelper).toContain("'napi-inl.h'")
expect(rebuildHelper).toContain("'napi-inl.deprecated.h'")
const rebuildScript = readFileSync(
join(projectDir, 'config/scripts/rebuild-native-deps.mjs'),
'utf8'
)
expect(rebuildScript).toContain('stageWindowsProcessTreeNodeAddonApiHeaders()')
})
// The installed Windows dependency is exercised by the Windows CI lane.
it.runIf(process.platform === 'win32')(
'resolves node_addon_api.gyp to a real file from the package directory',
@@ -1,87 +0,0 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'))
describe('packaged Windows PTY native capability routing', () => {
it('runs the packaged executable immediately after the unpacked app is built', () => {
const job = workflow.jobs.package_windows
const packageIndex = job.steps.findIndex((step) => step.name === 'Package unpacked app')
const smokeIndex = job.steps.findIndex(
(step) => step.name === 'Smoke packaged Windows PTY native capability'
)
const smoke = job.steps[smokeIndex]
expect(job['runs-on']).toBe('windows-2022')
expect(smokeIndex).toBe(packageIndex + 1)
expect(smoke.run).toBe(
'pnpm run smoke:windows-pty-native-capability -- --exe=dist/win-unpacked/Orca.exe'
)
expect(smoke.if).toBeUndefined()
expect(smoke['continue-on-error']).toBeUndefined()
expect(packageJson.scripts['smoke:windows-pty-native-capability']).toBe(
'node tests/tools/windows-pty-native-capability-smoke/run.mjs'
)
})
it('keeps patched source rebuild, release build, runtime reuse, and aggregate routing intact', () => {
const job = workflow.jobs.package_windows
const install = job.steps.find(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
const nodeCacheSave = job.steps.find(
(step) => step.name === 'Save compiled Node native modules'
)
const electronCache = job.steps.find(
(step) => step.name === 'Restore compiled Electron native modules'
)
const build = job.steps.find((step) => step.name === 'Build package inputs')
const prepare = job.steps.find((step) => step.name === 'Prepare Electron native runtime')
const packageStep = job.steps.find((step) => step.name === 'Package unpacked app')
const verify = workflow.jobs.verify.steps.find(
(step) => step.name === 'Require successful checks'
)
const ensureNativeRuntime = readFileSync('config/scripts/ensure-native-runtime.mjs', 'utf8')
expect(install.with['native-runtime']).toBe('node')
expect(install.with['persist-native-cache']).toBe('false')
expect(nodeCacheSave.uses).toBe('actions/cache/save@v5')
expect(nodeCacheSave.with.key).toContain('-node-node')
expect(electronCache.with.key).toContain('-electron-node')
for (const cache of [nodeCacheSave, electronCache]) {
expect(cache.with.key).toContain('.github/actions/install-node-dependencies/action.yml')
expect(cache.with.key).toContain('config/scripts/ensure-native-runtime.mjs')
expect(cache.with.key).toContain('config/scripts/rebuild-native-deps.mjs')
}
expect(ensureNativeRuntime).toContain("runPnpm(['exec', 'node-gyp', 'rebuild']")
expect(ensureNativeRuntime).toContain("resolve(moduleDir, 'scripts', 'post-install.js')")
expect(build.run).toBe('pnpm run build:release:parallel')
expect(prepare.run).toBe('node config/scripts/ensure-native-runtime.mjs --runtime=electron')
expect(packageStep.env.ORCA_REUSE_PREPARED_NATIVE_RUNTIME).toBe('1')
expect(workflow.jobs.verify.needs).toContain('package_windows')
expect(verify.env.PACKAGE_WINDOWS).toBe('${{ needs.package_windows.result }}')
expect(verify.run).toContain('"$PACKAGE_WINDOWS"')
})
it('keeps the native probe event-based, scoped, and runnable in packaged Node mode', () => {
const driver = readFileSync('tests/tools/windows-pty-native-capability-smoke/run.mjs', 'utf8')
const probe = readFileSync(
'tests/tools/windows-pty-native-capability-smoke/packaged-node-pty-capability-probe.cjs',
'utf8'
)
const source = `${driver}\n${probe}`
expect(driver).toContain("ELECTRON_RUN_AS_NODE: '1'")
expect(source).not.toMatch(/\b(?:sleep|tasklist|taskkill)\b/i)
expect(source).not.toContain('maxRetries')
expect(source).not.toContain('retryDelay')
expect(source).not.toContain("from 'node:child_process'")
expect(source).not.toContain("require('node:child_process')")
expect(probe).toContain("'System32', 'wscript.exe'")
expect(probe).toContain('real-orca-detached-launcher.vbs')
expect(probe).not.toMatch(/cmd\.exe|start "" \/b/i)
expect(probe).toContain('native.terminateJob(target._pty, target.pid)')
})
})
@@ -1,498 +0,0 @@
import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
const projectDir = resolve(import.meta.dirname, '../..')
const readWorkflow = (relativePath) => parse(readFileSync(join(projectDir, relativePath), 'utf8'))
describe('Windows signing workflow contract', () => {
it('preflights SignPath module install before Windows signing side effects', () => {
const parsedWorkflow = readWorkflow('.github/workflows/release-cut.yml')
const steps = parsedWorkflow.jobs.build.steps
const stepNames = steps.map((step) => step.name)
const installStepIndexes = stepNames.flatMap((name, index) =>
name === 'Install SignPath PowerShell module' ? [index] : []
)
const buildIndex = stepNames.indexOf('Build Windows release artifacts')
const verifyNodePtyIndex = stepNames.indexOf('Verify Windows node-pty ConPTY runtime')
const uploadIndex = stepNames.indexOf('Upload unsigned Windows installer for SignPath')
const downloadIndex = stepNames.indexOf('Download signed Windows installer from SignPath')
expect(verifyNodePtyIndex).toBe(buildIndex + 1)
expect(installStepIndexes).toEqual([verifyNodePtyIndex + 1])
expect(installStepIndexes[0]).toBeLessThan(uploadIndex)
expect(steps[verifyNodePtyIndex].run).toContain(
'dist/win-unpacked/resources/node_modules/node-pty/build/Release'
)
expect(steps[verifyNodePtyIndex].run).toContain('conpty/conpty.dll')
const uploadThroughDownloadScript = steps
.slice(uploadIndex, downloadIndex + 1)
.map((step) => step.run ?? '')
.join('\n')
expect(uploadThroughDownloadScript).not.toContain('Install-Module -Name SignPath')
const installStep = steps[installStepIndexes[0]]
expect(installStep.if).toBe("matrix.platform == 'win' && github.run_attempt == 1")
expect(installStep.uses).toBe('./.github/actions/install-signpath-module')
expect(installStep.run).toBeUndefined()
const installAction = readWorkflow('.github/actions/install-signpath-module/action.yml')
const actionStep = installAction.runs.steps[0]
const installRun = actionStep.run
const sleepSeconds = [...installRun.matchAll(/Start-Sleep -Seconds (\d+)/g)].map(
([, seconds]) => seconds
)
expect(installAction.runs.using).toBe('composite')
expect(actionStep.shell).toBe('pwsh')
expect(installRun).toContain(
'if ($null -eq (Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue))'
)
expect(installRun).toContain('Register-PSRepository -Default -InstallationPolicy Trusted')
expect(installRun).toContain('Set-PSRepository -Name PSGallery -InstallationPolicy Trusted')
expect(installRun).toMatch(/\$env:PSModulePath -split \[System\.IO\.Path\]::PathSeparator/)
expect(installRun).toContain(
"$signPathModulePath = Join-Path -Path $currentUserModuleRoot -ChildPath 'SignPath'"
)
expect(installRun).toMatch(/for \(\$attempt = 1; \$attempt -le 3; \$attempt\+\+\)/)
expect(sleepSeconds).toContain('15')
expect(sleepSeconds).toContain('30')
expect(installRun).toContain(
'Install-Module -Name SignPath -Repository PSGallery -MinimumVersion 4.0.0 -MaximumVersion 4.999.999 -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop'
)
expect(installRun).toContain('Import-Module SignPath -ErrorAction Stop')
expect(installRun).toContain(
'Get-Command -Name Get-SignedArtifact -Module SignPath -ErrorAction Stop'
)
expect(installRun).toContain('Remove-Item -LiteralPath $signPathModulePath -Recurse -Force')
expect(installRun).not.toContain('SignPath*')
expect(installRun).not.toMatch(/throw\s+\$_/)
})
it('falls back to a hash-pinned SignPath nupkg when the gallery API is down', () => {
const installAction = readWorkflow('.github/actions/install-signpath-module/action.yml')
const installRun = installAction.runs.steps[0].run
// Why: the gallery API 403s during Azure Front Door incidents while its CDN
// stays up, so a pinned nupkg is the fallback. The hash pin is the only
// integrity check on that route — losing it would let any payload install.
const { 'fallback-version': version, 'fallback-sha256': sha256 } = installAction.inputs
expect(version.default).toMatch(/^4\.\d+\.\d+$/)
expect(sha256.default).toMatch(/^[0-9a-f]{64}$/)
expect(installRun).toContain('Get-FileHash -LiteralPath $nupkg -Algorithm SHA256')
expect(installRun).toContain('$actualHash -ne $expectedHash.ToUpperInvariant()')
expect(installRun).toContain('throw "SHA-256 mismatch for $source')
expect(installRun).toContain(
'https://cdn.powershellgallery.com/packages/signpath.$version.nupkg'
)
// The module only resolves by name when the folder matches its ModuleVersion.
expect(installRun).toContain(
'$versionRoot = Join-Path -Path $signPathModulePath -ChildPath $version'
)
// The fallback only runs after the gallery route is exhausted, and still
// fails the job when neither route produced a usable module.
expect(installRun.indexOf('$installed = $true')).toBeLessThan(
installRun.indexOf('if (-not $installed)')
)
expect(installRun).toContain('throw "Unable to install the SignPath PowerShell module')
})
it('still installs SignPath when the cut ref predates the composite action', () => {
const parsedWorkflow = readWorkflow('.github/workflows/release-cut.yml')
const steps = parsedWorkflow.jobs.build.steps
const stepNames = steps.map((step) => step.name)
const checkoutIndex = stepNames.indexOf('Checkout')
const restoreIndex = stepNames.indexOf('Restore composite actions from the workflow ref')
const installIndex = stepNames.indexOf('Install SignPath PowerShell module')
// Why: the build job checks out the cut tag, which for a hotfix cut from an
// older ref can predate `.github/actions/install-signpath-module`; without
// this restore the `uses: ./…` step dies on a missing action.yml.
expect(restoreIndex).toBeGreaterThan(checkoutIndex)
expect(restoreIndex).toBeLessThan(installIndex)
const restoreStep = steps[restoreIndex]
const restoreRun = restoreStep.run
expect(restoreStep.env.WORKFLOW_SHA).toBe('${{ github.workflow_sha }}')
expect(restoreRun).toContain('.github/actions/install-signpath-module/action.yml')
expect(restoreRun).toContain('git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"')
expect(restoreRun).toContain('git checkout "$WORKFLOW_SHA" -- .github/actions')
// Why: restoring the action must not turn signing into a soft dependency —
// a missing module still has to fail the Windows job, and the CDN fallback
// still has to reject an unexpected payload.
expect(steps[installIndex]['continue-on-error']).toBeUndefined()
expect(restoreStep['continue-on-error']).toBeUndefined()
const installRun = readWorkflow('.github/actions/install-signpath-module/action.yml').runs
.steps[0].run
expect(installRun).toContain('$actualHash -ne $expectedHash.ToUpperInvariant()')
expect(installRun).toContain('throw "SHA-256 mismatch for $source')
})
it('never recreates Windows signing requests on a workflow rerun', () => {
const parsedWorkflow = readWorkflow('.github/workflows/release-cut.yml')
const steps = parsedWorkflow.jobs.build.steps
const stepNames = steps.map((step) => step.name)
const skipStep = steps.find((step) => step.name === 'Skip Windows artifact rebuild on rerun')
expect(skipStep?.if).toBe("matrix.platform == 'win' && github.run_attempt != 1")
expect(skipStep?.run).toContain('Existing signed release assets must be reused')
const signingStepNames = [
'Build Windows release artifacts',
'Stage unsigned inner PE files for signing',
'Upload unsigned inner binaries for SignPath',
'Submit inner binaries signing request',
'Download signed inner binaries from SignPath',
'Upload unsigned Windows installer for SignPath',
'Submit Windows installer signing request',
'Download signed Windows installer from SignPath',
'Stage signed Windows release assets',
'Publish signed Windows release artifacts'
]
for (const stepName of signingStepNames) {
const step = steps[stepNames.indexOf(stepName)]
expect(step?.if, stepName).toContain('github.run_attempt == 1')
}
})
it('shares one SignPath module install path between release and rehearsal', () => {
const rehearsalWorkflow = readWorkflow('.github/workflows/windows-signing-rehearsal.yml')
const stepNames = rehearsalWorkflow.jobs.rehearse.steps.map((step) => step.name)
const installIndex = stepNames.indexOf('Install SignPath PowerShell module')
// Why: the rehearsal exists to prove the real signing flow, so it must
// install the module exactly the way the release job does.
expect(rehearsalWorkflow.jobs.rehearse.steps[installIndex].uses).toBe(
'./.github/actions/install-signpath-module'
)
expect(rehearsalWorkflow.jobs.rehearse.steps[installIndex].run).toBeUndefined()
expect(installIndex).toBeLessThan(
stepNames.indexOf('Download signed inner binaries from SignPath')
)
})
it('requires Windows inner binary signatures before publishing', () => {
const parsedWorkflow = readWorkflow('.github/workflows/release-cut.yml')
const steps = parsedWorkflow.jobs.build.steps
const stepNames = steps.map((step) => step.name)
const outerVerifyIndex = stepNames.indexOf('Verify signed Windows installer')
const innerVerifyIndex = stepNames.indexOf('Verify Windows inner binary signatures')
const evidenceIndex = stepNames.indexOf('Upload Windows inner signing evidence')
const publishIndex = stepNames.indexOf('Publish signed Windows release artifacts')
expect(outerVerifyIndex).toBeGreaterThan(-1)
expect(innerVerifyIndex).toBe(outerVerifyIndex + 1)
expect(stepNames[innerVerifyIndex + 1]).toBe('Notify Slack when Windows signing fails')
expect(evidenceIndex).toBe(innerVerifyIndex + 2)
expect(publishIndex).toBe(evidenceIndex + 1)
expect(steps[innerVerifyIndex].env.ORCA_WINDOWS_INNER_SIGNATURE_REQUIRED).toBe('true')
expect(steps[innerVerifyIndex].run).toContain("@('Orca.exe', 'resources\\bin\\orca.exe')")
expect(stepNames).not.toContain('Roll back to original installer after failed rebuild')
const innerChainStepNames = [
'Stage unsigned inner PE files for signing',
'Upload unsigned inner binaries for SignPath',
'Submit inner binaries signing request',
'Download signed inner binaries from SignPath',
'Restore signed inner binaries into unpacked app',
'Restore signed uninstaller for the installer rebuild',
'Replace cached elevate.exe with the signed copy',
'Rebuild NSIS installer from signed unpacked app'
]
for (const stepName of innerChainStepNames) {
const step = steps[stepNames.indexOf(stepName)]
expect(step, stepName).toBeDefined()
expect(step['continue-on-error'], stepName).toBeUndefined()
}
})
})
// Why these exist: the NSIS uninstaller is generated inside electron-builder's
// uninstaller pass and deleted immediately after being embedded, so the only way
// CI can sign it is the export/import relay through win.signtoolOptions.sign.
// Every link is asserted here the way Orca.exe and conpty_console_list.node are.
describe('Windows NSIS uninstaller signing', () => {
const releaseSteps = () => readWorkflow('.github/workflows/release-cut.yml').jobs.build.steps
const stepNamed = (steps, name) => steps.find((step) => step.name === name)
const EXPORT_ENV = 'ORCA_WIN_UNINSTALLER_EXPORT_PATH'
const SIGNED_ENV = 'ORCA_WIN_UNINSTALLER_SIGNED_PATH'
it('exports the uninstaller from the first Windows build', () => {
const build = stepNamed(releaseSteps(), 'Build Windows release artifacts')
expect(build.env[EXPORT_ENV]).toContain('uninstaller-signing')
expect(build.env[EXPORT_ENV]).toContain('orca-uninstaller.exe')
})
// Why this is a test and not a comment: `files` in the electron-builder config
// is all-negation, so app-builder packs whatever is left in the checkout root.
// These steps retry, and a retried attempt would pack an unsigned .exe into
// app.asar — the very defect this chain removes. Every relay path must live
// outside the checkout.
it('keeps every relay path out of the packed checkout', () => {
const relayEnvValues = [
...releaseSteps(),
...readWorkflow('.github/workflows/windows-signing-rehearsal.yml').jobs.rehearse.steps
].flatMap((step) => [step.env?.[EXPORT_ENV], step.env?.[SIGNED_ENV]].filter(Boolean))
expect(relayEnvValues.length).toBe(4)
for (const value of relayEnvValues) {
expect(value).toContain('runner.temp')
expect(value).not.toContain('github.workspace')
}
const relayScripts = [
...releaseSteps(),
...readWorkflow('.github/workflows/windows-signing-rehearsal.yml').jobs.rehearse.steps
]
.map((step) => step.run ?? '')
.filter((run) => run.includes('uninstaller-signing'))
expect(relayScripts.length).toBeGreaterThan(0)
for (const run of relayScripts) {
// Why count occurrences rather than assert `toContain` once: a step
// carrying two relay paths could root the first in RUNNER_TEMP and leave
// the second bare-relative — which resolves against the checkout, and is
// exactly the shape of the defect this test exists to catch.
const mentions = run.match(/uninstaller-signing/g) ?? []
const rooted = run.match(/Join-Path \$env:RUNNER_TEMP 'uninstaller-signing/g) ?? []
expect(rooted.length, run).toBe(mentions.length)
expect(run).not.toContain('$env:GITHUB_WORKSPACE')
}
})
it('stages the uninstaller into the same request as the inner binaries', () => {
const stage = stepNamed(releaseSteps(), 'Stage unsigned inner PE files for signing')
expect(stage.run).toContain('uninstaller-signing\\unsigned\\orca-uninstaller.exe')
expect(stage.run).toContain('uninstaller\\orca-uninstaller.exe')
// No third SignPath request: exactly two submissions, as budgeted for the
// 1h + 4h approval waits inside the 360-minute job cap.
const submissions = releaseSteps().filter(
(step) => step.uses === 'signpath/github-action-submit-signing-request@v2'
)
expect(submissions).toHaveLength(2)
})
it('keeps the uninstaller out of the inner-binary copy-back list', () => {
const stage = stepNamed(releaseSteps(), 'Stage unsigned inner PE files for signing')
const restoreInner = stepNamed(
releaseSteps(),
'Restore signed inner binaries into unpacked app'
)
expect(stage.run).not.toMatch(/\$list\.Add\(['"]uninstaller/)
expect(restoreInner.run).not.toContain('orca-uninstaller.exe')
})
it('blocks publication when the exported uninstaller is missing', () => {
const stage = stepNamed(releaseSteps(), 'Stage unsigned inner PE files for signing')
const uninstallerBlock = stage.run.slice(stage.run.indexOf('$exportedUninstaller'))
expect(uninstallerBlock).toContain('throw "No exported NSIS uninstaller')
expect(uninstallerBlock).not.toContain('catch')
expect(uninstallerBlock).toMatch(/Copy-Item[^\r\n]*-ErrorAction Stop/)
})
it('restores exact artifact paths rather than suffix-matching another executable', () => {
for (const [name, path] of [
['Restore signed inner binaries into unpacked app', '$relative'],
[
'Restore signed uninstaller for the installer rebuild',
"'uninstaller\\orca-uninstaller.exe'"
]
]) {
const restore = stepNamed(releaseSteps(), name)
expect(restore.run).toContain(`(Join-Path 'signed-inner' ${path})`)
expect(restore.run).toContain(`(Join-Path 'signed-inner/signing-stage' ${path})`)
expect(restore.run).toContain('if (@($candidates).Count -ne 1)')
expect(restore.run).not.toContain('Select-Object -First 1')
expect(restore.run).not.toContain('-like "*$relative"')
const rehearsal = readWorkflow('.github/workflows/windows-signing-rehearsal.yml')
const rehearseRestore = stepNamed(rehearsal.jobs.rehearse.steps, name)
expect(rehearseRestore.run).toBe(restore.run)
expect(restore.env.SIGNING_POLICY).toBe('release-signing')
expect(rehearseRestore.env.SIGNING_POLICY).toBe(
"${{ inputs.signing-policy-slug || 'test-signing' }}"
)
expect(restore.run).toContain("$requireValid = $env:SIGNING_POLICY -ne 'test-signing'")
expect(restore.run).toContain(
"if ($null -eq $signature.SignerCertificate -or ($requireValid -and ($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Subject -notlike '*CN=SignPath Foundation*')))"
)
}
})
it('requires the CLI signature at its electron-builder extraResources destination', () => {
const require = createRequire(import.meta.url)
const config = require('../electron-builder.config.cjs')
const cli = config.win.extraResources.find((resource) => resource.to === 'bin/orca.exe')
expect(cli).toBeDefined()
const payloadPath = `resources/${cli.to}`.replaceAll('/', '\\')
const rehearsal = readWorkflow('.github/workflows/windows-signing-rehearsal.yml')
for (const gate of [
stepNamed(releaseSteps(), 'Verify Windows inner binary signatures'),
stepNamed(rehearsal.jobs.rehearse.steps, 'Verify signatures end to end')
]) {
expect(gate.run).toContain(`foreach ($requiredTarget in @('Orca.exe', '${payloadPath}'))`)
expect(gate.run).toContain(
'if ($targets -notcontains $requiredTarget) { $targets += $requiredTarget }'
)
expect(gate.run).toContain('foreach ($relative in $targets)')
}
})
it('waits for approval even when the notification fails', () => {
const steps = releaseSteps()
const notify = stepNamed(
steps,
'Notify Slack that inner-binary signing is waiting for approval'
)
const download = stepNamed(steps, 'Download signed inner binaries from SignPath')
expect(notify['continue-on-error']).toBe(true)
expect(download.if).not.toContain('notify-inner-signing')
expect(download.if).toContain("steps.submit-inner-signing.outcome == 'success'")
})
it('re-injects the signed uninstaller into the rebuilt installer', () => {
const steps = releaseSteps()
const restore = stepNamed(steps, 'Restore signed uninstaller for the installer rebuild')
const rebuild = stepNamed(steps, 'Rebuild NSIS installer from signed unpacked app')
const names = steps.map((step) => step.name)
expect(restore.if).toContain('github.run_attempt == 1')
expect(restore.if).toContain("steps.restore-signed-inner.outcome == 'success'")
expect(restore.run).toContain('orca-uninstaller.exe')
expect(names.indexOf(restore.name)).toBeLessThan(names.indexOf(rebuild.name))
expect(rebuild.env[SIGNED_ENV]).toContain('uninstaller-signing')
// Default success() gating blocks the rebuild after a failed restore.
expect(rebuild.if).not.toContain('restore-signed-uninstaller')
})
// NSIS hides the uninstaller in a compressed data section the bundled 7za
// cannot read, so the gate proves it from the sign hook's digest receipt
// instead of extracting it — and only when the relay actually ran.
it('reports the embedded uninstaller in the inner-binary evidence gate', () => {
const gate = stepNamed(releaseSteps(), 'Verify Windows inner binary signatures')
expect(gate.env.UNINSTALLER_SIGNING_COMPLETED).toBe(
"${{ steps.restore-signed-uninstaller.outcome == 'success' }}"
)
expect(gate.run).toContain('.embedded-sha256')
expect(gate.run).toContain("$failures.Add('The NSIS uninstaller signing did not complete.')")
expect(gate.run).toContain("$env:UNINSTALLER_SIGNING_COMPLETED -eq 'true'")
expect(gate.run).toContain('not signed by SignPath Foundation: Uninstall Orca.exe')
// The uninstaller must not join the 7z payload loop, which cannot see it.
expect(gate.run).not.toContain("$targets += 'Uninstall Orca.exe'")
})
it('rehearses the uninstaller leg end to end', () => {
const steps = readWorkflow('.github/workflows/windows-signing-rehearsal.yml').jobs.rehearse
.steps
const names = steps.map((step) => step.name)
const pack = stepNamed(steps, 'Package Windows app and export the NSIS uninstaller')
const rebuild = stepNamed(steps, 'Build NSIS installer from signed unpacked app')
const verify = stepNamed(steps, 'Verify signatures end to end')
// --dir never produces an uninstaller, so the rehearsal has to build the
// installer the way release-cut's first Windows pass does.
expect(pack.run).toContain('--win --publish never')
expect(pack.run).not.toContain('--dir')
expect(pack.env[EXPORT_ENV]).toContain('orca-uninstaller.exe')
expect(names).toContain('Restore signed uninstaller for the installer rebuild')
expect(rebuild.env[SIGNED_ENV]).toContain('orca-uninstaller.exe')
expect(verify.run).toContain('.embedded-sha256')
// The receipt only proves the import leg ran. The rehearsal is where the
// shipped uninstaller itself gets checked — the release job cannot install
// onto the runner it publishes from.
expect(verify.run).toContain('shipped: Uninstall Orca.exe')
expect(verify.run).toContain('-tnsis')
expect(verify.run).toContain("-ArgumentList '/S'")
})
// This workflow is the merge gate, so it must not be able to fail on its own
// artefact: 7-Zip's NSIS handler is unreliable enough that its output has to
// be corroborated before a signature verdict is drawn from it.
it('never lets an unreliable extract fail the rehearsal', () => {
const steps = readWorkflow('.github/workflows/windows-signing-rehearsal.yml').jobs.rehearse
.steps
const verify = stepNamed(steps, 'Verify signatures end to end')
// The 7-Zip route is only trusted when it reproduces the relayed bytes;
// otherwise it falls through to the install route rather than failing.
expect(verify.run).toContain(
'Write-Host "7-Zip\'s NSIS output did not match the relayed digest; falling back to a silent install."'
)
expect(verify.run).toMatch(/\$installedUninstaller = \$null\r?\n\s*\}/)
// The comparison that is not tautological: a file NSIS wrote out, against
// the digest the sign hook recorded.
expect(verify.run).toContain('$shippedDigest -ne $expectedDigest')
expect(verify.run).toContain('the uninstaller the installer ships is not the relayed one')
// An installer that prompts must not hang to the 360-minute job cap, and
// the app it launches must not outlive the step holding install-dir handles.
expect(verify.run).toContain('-PassThru')
expect(verify.run).toContain('$installerProcess.WaitForExit(300000)')
expect(verify.run).toContain('the silent install did not exit within 5 minutes')
expect(verify.run).toMatch(/for \(\$attempt = 0; \$attempt -lt 20; \$attempt\+\+\)/)
expect(verify.run).toContain("Get-Process -Name 'orca-terminal-daemon'")
})
// resources\elevate.exe is downgraded to advisory because app-builder-lib's
// CopyElevateHelper clobbers it on every nsis pack — a pre-existing defect
// that predates the uninstaller relay and is being tracked separately. The
// escape hatch it needed is the kind that quietly grows until the gate
// asserts nothing, so pin it to exactly that one file.
it('confines the advisory escape hatch to elevate.exe', () => {
const steps = readWorkflow('.github/workflows/windows-signing-rehearsal.yml').jobs.rehearse
.steps
const verify = stepNamed(steps, 'Verify signatures end to end')
const advisoryCalls = verify.run
.split('\n')
.filter((line) => line.includes('-Advisory') && line.includes('Test-Signature'))
expect(advisoryCalls).toHaveLength(1)
expect(advisoryCalls[0]).toContain('installed: $relative')
expect(verify.run).toContain("if ($relative -eq 'resources\\elevate.exe')")
// Both uninstaller verdicts stay fatal — the whole point of the gate.
for (const call of ['relayed: orca-uninstaller.exe', 'shipped: Uninstall Orca.exe']) {
const line = verify.run
.split('\n')
.find((it) => it.includes(`Test-Signature`) && it.includes(call))
expect(line, call).toBeDefined()
expect(line, call).not.toContain('-Advisory')
}
// An advisory must still reach the evidence artifact, or downgrading it
// becomes indistinguishable from deleting the check.
expect(verify.run).toContain('ADVISORY (known pre-existing')
expect(verify.run).toContain('$script:advisories.Add($problem)')
})
it('wires the electron-builder sign hook that the relay depends on', () => {
const require = createRequire(import.meta.url)
const configPath = resolve(projectDir, 'config/electron-builder.config.cjs')
delete require.cache[require.resolve(configPath)]
const config = require(configPath)
expect(typeof config.win.signtoolOptions.sign).toBe('function')
delete require.cache[require.resolve(configPath)]
})
})
@@ -0,0 +1,160 @@
import { createRequire } from 'node:module'
import { afterEach, describe, expect, it, vi } from 'vitest'
const require = createRequire(import.meta.url)
const { Terminal } = require('@xterm/xterm')
const { ImageAddon } = require('@xterm/addon-image')
// V8 caps live wasm memories per renderer (~124 under Electron's sandbox), so
// these contracts pin that idle terminals hold none and that exhaustion drops
// an image instead of wedging the terminal's write queue.
const SIXEL_RED_REGISTER_1 = '\x1bPq#1;2;100;0;0#1~\x1b\\'
const SIXEL_USE_REGISTER_1 = '\x1bPq#1~\x1b\\'
const PNG_1X1 =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
const IIP_PNG = `\x1b]1337;File=inline=1;size=${Buffer.from(PNG_1X1, 'base64').length}:${PNG_1X1}\x07`
class TrackedBitmap {
width = 1
height = 1
close = vi.fn()
}
function stubCanvasGlobals() {
const painted = []
vi.stubGlobal('ImageBitmap', TrackedBitmap)
vi.stubGlobal('window', { ImageBitmap: TrackedBitmap })
vi.stubGlobal('createImageBitmap', async () => new TrackedBitmap())
vi.stubGlobal(
'ImageData',
class {
constructor(data, width, height) {
Object.assign(this, { data: new Uint8ClampedArray(data), width, height })
}
}
)
vi.stubGlobal('document', {
createElement: () => ({
width: 0,
height: 0,
getContext: () => ({ putImageData: (image) => painted.push(image) })
})
})
return painted
}
function createTerminal(options = {}) {
const terminal = new Terminal({ allowProposedApi: true })
const addon = new ImageAddon({
enableSizeReports: false,
storageLimit: 32,
kittySizeLimit: 8 * 1024 * 1024,
...options
})
terminal.loadAddon(addon)
return {
terminal,
addon,
sixel: addon._handlers.get('sixel'),
iip: addon._handlers.get('iip')
}
}
function write(terminal, data) {
return new Promise((resolve) => terminal.write(data, resolve))
}
// A throw out of a handler leaves xterm's write queue waiting forever.
function writeOrWedge(terminal, data) {
return Promise.race([
write(terminal, data).then(() => 'parsed'),
new Promise((resolve) => setTimeout(() => resolve('wedged'), 2000))
])
}
function firstPixel(image) {
return Array.from(image.data.subarray(0, 4))
}
afterEach(() => vi.unstubAllGlobals())
describe('xterm image wasm budget', () => {
it('keeps idle terminals free of wasm decoders', async () => {
const panes = Array.from({ length: 300 }, () => createTerminal())
try {
// Let any asynchronous decoder instantiation settle.
await new Promise((resolve) => setTimeout(resolve, 100))
for (const { sixel, iip } of panes) {
expect(sixel._dec).toBeUndefined()
expect(iip._dec._inst).toBeNull()
expect(iip._qoiDec._inst).toBeFalsy()
}
} finally {
for (const { terminal } of panes) {
terminal.dispose()
}
}
})
it('returns decoders after each SIXEL and IIP image', async () => {
const painted = stubCanvasGlobals()
const { terminal, addon, sixel, iip } = createTerminal()
let added = 0
addon.onImageAdded(() => added++)
try {
await write(terminal, SIXEL_RED_REGISTER_1)
expect(painted).toHaveLength(1)
expect(sixel._dec).toBeUndefined()
await write(terminal, IIP_PNG)
await new Promise((resolve) => setTimeout(resolve, 0))
expect(iip._dec._inst).toBeNull()
expect(added).toBe(2)
} finally {
terminal.dispose()
}
})
it('keeps SIXEL color registers across images until a terminal reset', async () => {
const painted = stubCanvasGlobals()
const { terminal } = createTerminal()
try {
await write(terminal, SIXEL_RED_REGISTER_1)
await write(terminal, SIXEL_USE_REGISTER_1)
expect(firstPixel(painted[1])).toEqual([255, 0, 0, 255])
await write(terminal, '\x1bc')
await write(terminal, SIXEL_USE_REGISTER_1)
expect(firstPixel(painted[2])).not.toEqual([255, 0, 0, 255])
} finally {
terminal.dispose()
}
})
it('drops images without wedging the parser when wasm memory is exhausted', async () => {
stubCanvasGlobals()
// A pixelLimit no other test uses keeps the shared SIXEL pool empty for this key.
const { terminal, addon } = createTerminal({ pixelLimit: 1234567 })
const replies = []
terminal.onData((data) => replies.push(data))
function exhausted() {
throw new RangeError('WebAssembly.Memory(): could not allocate memory')
}
vi.stubGlobal('WebAssembly', { ...WebAssembly, Memory: exhausted, Instance: exhausted })
try {
expect(await writeOrWedge(terminal, '\x1b_Ga=T,f=32,s=1,v=1,i=7;AAAA/w==\x1b\\')).toBe(
'parsed'
)
expect(await writeOrWedge(terminal, IIP_PNG)).toBe('parsed')
expect(await writeOrWedge(terminal, SIXEL_USE_REGISTER_1)).toBe('parsed')
expect(await writeOrWedge(terminal, 'after')).toBe('parsed')
expect(addon._storage._images.size).toBe(0)
expect(replies.join('')).toContain('ENOMEM')
expect(terminal.buffer.active.getLine(0).translateToString(true)).toContain('after')
} finally {
terminal.dispose()
}
})
})
+4 -4
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="106" height="20" role="img" aria-label="downloads: 83m">
<title>downloads: 83m</title>
<svg xmlns="http://www.w3.org/2000/svg" width="106" height="20" role="img" aria-label="downloads: 84m">
<title>downloads: 84m</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
@@ -15,7 +15,7 @@
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">downloads</text>
<text x="37" y="14">downloads</text>
<text x="90" y="15" fill="#010101" fill-opacity=".3">83m</text>
<text x="90" y="14">83m</text>
<text x="90" y="15" fill="#010101" fill-opacity=".3">84m</text>
<text x="90" y="14">84m</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 935 B

After

Width:  |  Height:  |  Size: 935 B

+4 -3
View File
@@ -45,9 +45,10 @@ unverified because the available account has exhausted its credits. Hook event
mapping for these paths follows the official documentation. The China executable
`qoderclicn` is not registered; it was not available for verification.
New Tab follows the existing manual-launch trust behavior: an untrusted folder
can show Qoder's trust dialog. Automated workspace/draft launch paths run the
trust preflight before prompt delivery.
Every launch Orca starts (New Tab, workspace and draft launches, automations)
pre-trusts the workspace at spawn while the agent-wide "Trust the folder when
Orca starts an agent" setting is on. A hand-typed `qodercli` in a plain terminal
can still show Qoder's trust dialog.
## Sources
@@ -48,6 +48,15 @@ after reset, disable or disposal. `config/scripts/xterm-image-lifecycle-contract
exercises those boundaries against the installed addon. Font zoom scales visible
tiles without creating enlarged full-image canvases;
`config/scripts/xterm-image-resize-contract.test.mjs` checks allocation and tile mapping.
Every wasm memory reserves its guard region inside V8's sandbox, which caps a
renderer at about 124 live memories no matter how much RAM is free. Upstream
gave each terminal a SIXEL decoder at activation and kept IIP decoders after the
first image, so a window with ~120+ terminals ran out. The patch borrows SIXEL
decoders from a small shared pool only while a sequence is open, keeping color
registers on the terminal, and drops IIP decoders after each image. A decoder
that cannot be allocated drops that image; before the patch it threw out of the
parser and left the terminal's write queue stuck.
`config/scripts/xterm-image-wasm-budget-contract.test.mjs` covers both.
## Rules
+8 -1
View File
@@ -196,13 +196,20 @@ function createMainBootstrapPlugin() {
}
}
/**
* Diagnostic escape hatch: an unminified main bundle so a V8 CPU profile of the
* main process attributes self time to real function names. Release builds never
* set this, and `pnpm build` does not read it.
*/
const MAIN_MINIFY: 'oxc' | false = process.env.ORCA_UNMINIFIED_MAIN === '1' ? false : 'oxc'
export const electronViteConfig: UserConfig = {
main: {
build: {
// Why: 'esbuild' makes rolldown disable its own minifier and re-print every
// chunk through esbuild, which is undeclared here and only resolves via
// pnpm hoisting. 'oxc' is rolldown's in-process minifier.
minify: 'oxc',
minify: MAIN_MINIFY,
// Why: 'hidden' emits .js.map with no sourceMappingURL, so the shipped
// bundle never references maps that packaging strips out. Release CI
// uploads them so minified crash traces stay decodable.
+1 -1
View File
@@ -180,7 +180,7 @@ Do **not** bump for additive changes:
Set `MIN_COMPATIBLE_MOBILE_VERSION` (kill-switch) when desktop ships a change that requires a minimum mobile version to function safely. Same for `MIN_COMPATIBLE_DESKTOP_VERSION` from the mobile side.
When a verdict is `blocked`, `mobile/src/components/ProtocolBlockScreen.tsx` renders a screen pointing the user at either the App Store (mobile too old) or GitHub Releases (desktop too old).
When a verdict is `blocked`, `mobile/src/components/ProtocolBlockScreen.tsx` renders a screen pointing the user at the update that clears it. When mobile is too old it opens the newest release if the installed app's update check knows one, otherwise the App Store (iOS) or GitHub Releases (Android). When desktop is too old it opens GitHub Releases.
To exercise the block screen locally: set `MIN_COMPATIBLE_DESKTOP_VERSION = 999` in `mobile/src/transport/protocol-version.ts`, rebuild, pair to any desktop. Revert before merging.
+2
View File
@@ -12,6 +12,8 @@
"typecheck": "tsc --noEmit",
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
"check:tests-typecheck": "node scripts/check-tests-typecheck-ratchet.mjs",
"rpc:record": "tsx scripts/rpc-recording.mts",
"rpc:diff": "tsx scripts/rpc-diff.mts",
"lint": "oxlint",
"format": "oxfmt --write .",
"format:check": "oxfmt --check .",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.history-scan",
"family": "aiVault.history",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "ec16931c5431d3dbb05729d4670c381a434bff71648d334b7b5224db188fdccb",
"scenarioSha256": "0431ac82cbb8c60b16f4432fd0da7cff665485d668e512b20fc1168ec63db3fe",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"3719c54df702": {
"name": "aiVault.listSessions#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.history-scan",
"family": "aiVault.history",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "ec16931c5431d3dbb05729d4670c381a434bff71648d334b7b5224db188fdccb",
"scenarioSha256": "e97c6db772a70ee1912419ac67835b6074aaf9a341d4360389a11465f08092c5",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"053ddb72973f": {
"activeWorktreePath": "/repo/feature",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.history-scan",
"family": "aiVault.history",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "ec16931c5431d3dbb05729d4670c381a434bff71648d334b7b5224db188fdccb",
"scenarioSha256": "10011c7c75e74d9c2e880c5458c9156264ae07e91956a80946ede1d4e684952a",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"05215ca2ea35": {
"name": "aiVault.listSessions#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.history-screen",
"family": "aiVault.history-screen",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "c78ab47eea594b7e1988403321ff9ba135ca60c513bb9d5848bdde26c0ffe3c3",
"scenarioSha256": "d52d3c5858298a4a6a90bd9a8986b780004477de105fe93f6303d9c303ffea38",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"03106dceb986": {
"name": "status.get#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.history-screen",
"family": "aiVault.history-screen",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "c78ab47eea594b7e1988403321ff9ba135ca60c513bb9d5848bdde26c0ffe3c3",
"scenarioSha256": "f50f63c4e2a69793b3d322ed16089c4241ff6169d8f9549106480230fb8dd5e7",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"03106dceb986": {
"name": "status.get#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-launch",
"family": "aiVault.resume-launch",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "ebf1bbc01ad7704fe79eff0969fcc2d4af8ebfa7c2410d2ed9d3ae8c6976b434",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"3e6273f4fe55": {
"name": "session.tabs.createTerminal#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-launch",
"family": "aiVault.resume-launch",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "281ccf5a082f3788ae8bf19f742ea4baf35c20c78bc91d7d91873845583e1a91",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"0bce25f5646d": {
"name": "session.tabs.createTerminal#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-launch",
"family": "aiVault.resume-launch",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "941fcf202417c94ef546f5ee331983689d8b72397dac6f61dda944ca24abed9e",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"0bce25f5646d": {
"name": "session.tabs.createTerminal#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-launch",
"family": "aiVault.resume-launch",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "f0ac1c996e5b1b4043e0a081978476c6c2dd8a5d517d5752857721205a588fb0",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"0bce25f5646d": {
"name": "session.tabs.createTerminal#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-preparation",
"family": "aiVault.resume-preparation",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "dc9926f95475413627315e1f1c96740ececa697c6a0a5e3c42e18cfd4d58448a",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"1c21b98bedb1": {
"status": "rejected",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-preparation",
"family": "aiVault.resume-preparation",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "6719aea706086a68709894546f9595264407db2df94fa56180cb3c68b08aaa69",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"02733b10ba3d": {
"failure": {
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-preparation",
"family": "aiVault.resume-preparation",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "3afaf2807506f5bde2159b367f34c6b777739d6aad564796d54ac0da05b5bd02",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"5d9b438a0a47": {
"failure": {
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "aiVault.resume-preparation",
"family": "aiVault.resume-preparation",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "2f43211e4084c0493bd02ec98868acf53a4c748f89a70cd9657c9c3ee87b12fb",
"scenarioSha256": "a5eedea5f551e0ca0e143292f1d9aa8920dd7af62a02d8e8777666ab3779c019",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"88c8bb20eb6f": {
"name": "aiVault.prepareSessionResume#1",
+1 -10
View File
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "workspace.file-inventory",
"family": "legacy-inventory",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "262eaad263a45aa13ec5b27c12b59946b12c202474229fff7a5727dba6d702ca",
"scenarioSha256": "23ffc912a432dcd3ff70be1903a8d518cf85634f27a2be6d21585963e338e7e3",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"32c9018052ba": {
"name": "files.searchPaths#1",
+1 -10
View File
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "project.update-metadata",
"family": "project-explicit-false",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "d3b7f33d810e1fa420ac41a628cde9fe4a9e65fd57f89fbca0a40fc7d74951ab",
"scenarioSha256": "b31992be2f91bd61fbe1b8a5400da3b7a56753564b0b0b2b38bc5d549812d693",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"0c4dced3e005": {
"error": "",
+1 -10
View File
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "linear.issue-detail",
"family": "linear-detail-barrier",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "d3b7f33d810e1fa420ac41a628cde9fe4a9e65fd57f89fbca0a40fc7d74951ab",
"scenarioSha256": "130e493fcd7765e037405f59e6cc78a0cc1793b1ae092cad933ff9d5a9df8b7a",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"0056f47b204a": {
"name": "linear.getIssue#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "browser.page-commands",
"family": "browser.dialog",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "55971752f963202d30160851197a301089b0f3ebd0c46725af1a461d8310d658",
"scenarioSha256": "95f377bc4d8bf1248cafed26b2e9f425ad37e8d3c99e354bd6a8c67eb9bd9b1b",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"93f4e9f3ead2": {
"name": "browser.dialogAccept#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "browser.page-commands",
"family": "browser.dialog",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "55971752f963202d30160851197a301089b0f3ebd0c46725af1a461d8310d658",
"scenarioSha256": "5071188ee492a4ced5be793b2dab32baa9750ee6535128635f274fd79198e2f1",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"44cc0151aab3": {
"name": "browser.dialogDismiss#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "browser.page-commands",
"family": "browser.keyboard",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "55971752f963202d30160851197a301089b0f3ebd0c46725af1a461d8310d658",
"scenarioSha256": "eb2294c30af70ebc2bac092dc5105249ae8cf1941f750406622f7ff6dd70cc1b",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"6104536ec606": {
"name": "browser.keypress#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "browser.page-commands",
"family": "browser.pointer-click",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "55971752f963202d30160851197a301089b0f3ebd0c46725af1a461d8310d658",
"scenarioSha256": "73faa87b359a11959543542016295bb6b36a10934dd99ad5c1a77a4290a608cd",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"002a5bcbde42": {
"name": "browser.mouseClick#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "browser.page-commands",
"family": "browser.pointer-click",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "55971752f963202d30160851197a301089b0f3ebd0c46725af1a461d8310d658",
"scenarioSha256": "34efd4d8568ac15d4e67d475e28194522d6413ad27dce1d6850718af15b3f874",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"002a5bcbde42": {
"name": "browser.mouseClick#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "browser.page-commands",
"family": "browser.wheel",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "55971752f963202d30160851197a301089b0f3ebd0c46725af1a461d8310d658",
"scenarioSha256": "e1bc21248ccff45ff217e385a51618b6f5724c037bfbefa03bb76a48dd4d793b",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"0ea51271b822": {
"name": "browser.mouseWheel#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-terminal-attachment",
"family": "clipboard.image-attachment",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "5411e29436e9faf18a1b81f369082f3ae087a9a96a2e1ea0f97e65e9391bfd0c",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"08a3a6323626": {
"name": "before-terminal-send",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-terminal-attachment",
"family": "clipboard.image-attachment",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "ea04d03c15d3cb94be7fe111a8a6219c4e0304095679bbae62af623f5b36c9f4",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"08a3a6323626": {
"name": "before-terminal-send",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-terminal-attachment",
"family": "clipboard.image-attachment",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "e6be7d5dd6b4f5083627a3ba864b1b040d71bf72b60ad940b7d0d575964a7b80",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"7ed3d39f0607": {
"status": "fulfilled",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-terminal-attachment",
"family": "clipboard.image-attachment",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "d6db064741cd68d6ad8a3e490a774d8145adb4a5b7cca8218f5c972b1a31f93b",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"080f28c67e30": {
"name": "terminal.send#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-terminal-attachment",
"family": "clipboard.image-attachment",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "e7f492523421873f045726e15ec95eac26d43f7e971a33fd89ec1a9749492987",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"3c402c915494": {
"name": "clipboard.startImageUpload#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-upload",
"family": "clipboard.image-upload",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "195c2f15d81c70012ea88750ab3f84bfe512f7e422f7d2d09fb7919bd9cfd85a",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"8e6c4f65042a": {
"name": "clipboard.appendImageUploadChunk#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-upload",
"family": "clipboard.image-upload",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "160101326d39705c2036c12646ff6b13d997a1cf91bdc86e5e29279ba31867e4",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"261840506035": {
"name": "clipboard.appendImageUploadChunk#1",
@@ -1,17 +1,8 @@
{
"goldenFormatVersion": 6,
"operation": "clipboard.image-upload",
"family": "clipboard.image-upload",
"namedDeltas": [],
"runnerVersion": 1,
"baseline": "ccdb324b63e4942891942edde1216586a51774bd",
"lockfileSha256": "9317f3a98ab047f9f96fe26fd8531b8a632ac5954064b322f41a904148cbf6bb",
"recorderSha256": "0317fe2aebe4743ce5e7ae194531aa91b4fe0a84df457640851fe6dfdb9362af",
"adapterSha256": "16779b663f4faec8cbccdb42efd7b568a2912845b161f8a4827754687eb4235c",
"scenarioSha256": "89ffa843d08ee27dd44b7bba507ce84661b0df73c35f7a8c273e004604710dc9",
"platform": "darwin",
"scenarioVersion": 1,
"projectionVersion": 2,
"goldenFormatVersion": 5,
"values": {
"7bc2e4227914": {
"name": "clipboard.startImageUpload#1",

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