Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions config/scripts/check-reliability-gates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function validateProtection(gate, failures) {
if (gate.flakeHistory?.status !== 'stable') {
failures.push(`${gate.id}: protection active gates must have stable flakeHistory`)
}
if (!['complete', 'not-required'].includes(gate.redGreenEvidence?.status)) {
if (!hasCompleteRedGreenEvidence(gate)) {
failures.push(`${gate.id}: protection active gates must have complete red/green evidence`)
}
}
Expand All @@ -166,6 +166,17 @@ function isIsoDate(value) {
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value)
}

// Why: cross-field validators run even after an earlier type check flagged a
// malformed field, so coerce to an array before `.includes` to report the error
// instead of throwing on hand-edited manifests.
function asArray(value) {
return Array.isArray(value) ? value : []
}

function hasCompleteRedGreenEvidence(gate) {
return ['complete', 'not-required'].includes(gate.redGreenEvidence?.status)
}

function validateEvidenceRun(gate, run, index, failures) {
const owner = `${gate.id}: evidenceRuns[${index}]`
if (!isRecord(run)) {
Expand All @@ -186,7 +197,7 @@ function validateEvidenceRun(gate, run, index, failures) {
}
if (!isNonEmptyString(run.command)) {
failures.push(`${owner}.command must be a non-empty string`)
} else if (!gate.commands.includes(run.command)) {
} else if (!asArray(gate.commands).includes(run.command)) {
failures.push(`${owner}.command must match one of the gate commands`)
}
if (!Number.isFinite(run.durationSeconds) || run.durationSeconds < 0) {
Expand Down Expand Up @@ -222,7 +233,7 @@ function validateAssertionRef(gate, ref, index, failures) {
}
if (!isNonEmptyString(ref.file)) {
failures.push(`${owner}.file must be a non-empty string`)
} else if (!gate.testFiles.includes(ref.file)) {
} else if (!asArray(gate.testFiles).includes(ref.file)) {
failures.push(`${owner}.file must be one of the gate testFiles`)
}
if (!hasNonEmptyStringArray(ref.assertions)) {
Expand Down Expand Up @@ -253,12 +264,12 @@ function validateCoveredScope(gate, failures) {
return
}
for (const platform of gate.coveredPlatforms) {
if (!gate.platforms.includes(platform)) {
if (!asArray(gate.platforms).includes(platform)) {
failures.push(`${gate.id}: covered platform is outside risk scope: ${platform}`)
}
}
for (const provider of gate.coveredProviders) {
if (!gate.providers.includes(provider)) {
if (!asArray(gate.providers).includes(provider)) {
failures.push(`${gate.id}: covered provider is outside risk scope: ${provider}`)
}
}
Expand Down Expand Up @@ -343,7 +354,7 @@ async function validateGate(gate, maturities, root) {
if (!['soaking', 'stable'].includes(gate.flakeHistory?.status)) {
failures.push(`${gate.id}: ${gate.maturity} gates must have soaking or stable flakeHistory`)
}
if (!['complete', 'not-required'].includes(gate.redGreenEvidence?.status)) {
if (!hasCompleteRedGreenEvidence(gate)) {
failures.push(`${gate.id}: ${gate.maturity} gates must have complete red/green evidence`)
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/main/emulator/backends/ios-emulator-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,22 @@ export class IosEmulatorBackend implements EmulatorBackend {
logcat: false
}

private readonly serveSimExecutable: ServeSimExecutable
private cachedServeSimExecutable: ServeSimExecutable | undefined
private readonly waitForEndpointReady: (endpoint: string) => Promise<boolean>

constructor(options: EmulatorBridgeOptions = {}) {
this.serveSimExecutable = resolveServeSimExecutable()
this.waitForEndpointReady = options.waitForEndpointReady ?? waitForServeSimEndpointReady
}

// Why: resolving the executable can materialize the serve-sim runtime (a one-time
// recursive copy + xattr subprocess on macOS after each version bump). Defer it
// off the startup path — the bridge is constructed before the main window shows —
// so it only runs when an emulator command is actually issued.
private get serveSimExecutable(): ServeSimExecutable {
this.cachedServeSimExecutable ??= resolveServeSimExecutable()
return this.cachedServeSimExecutable
}
Comment on lines +60 to +63

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect test coverage for lazy/memoized serve-sim executable resolution
fd 'ios-emulator-backend.test.ts' | xargs -I{} sed -n '1,200p' {}

Repository: stablyai/orca

Length of output: 7613


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the backend implementation and the matching test file for coverage
# around resolveServeSimExecutable() being lazy and memoized.
printf '\n== ios-emulator-backend.ts ==\n'
sed -n '1,180p' src/main/emulator/backends/ios-emulator-backend.ts

printf '\n== ios-emulator-backend.test.ts (resolveServeSimExecutable references) ==\n'
rg -n "resolveServeSimExecutable|serveSimExecutable|cachedServeSimExecutable|constructor" src/main/emulator/backends/ios-emulator-backend.test.ts src/main/emulator/backends/ios-emulator-backend.ts

Repository: stablyai/orca

Length of output: 7606


Add coverage for lazy serve-sim resolution. src/main/emulator/backends/ios-emulator-backend.test.ts should assert that resolveServeSimExecutable() is not called on construction and is only invoked once across repeated serveSimExecutable-backed calls.


isSupportedOnHost(): boolean {
return platform() === 'darwin'
}
Expand Down
76 changes: 46 additions & 30 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,11 @@ let watcherShutdownPromise: Promise<void> | null = null
let watcherShutdownDone = false
let automations: AutomationService | null = null
let keybindings: KeybindingService | null = null
let expectedRendererReload: { webContentsId: number; until: number } | null = null
// Why: the crash/freeze-recovery reload re-fires did-finish-load; flag it so the
// local-PTY orphan sweep spares live sessions across that one reload (#5787).
let recoveryReloadInFlight: { webContentsId: number; until: number } | null = null
// Why: a reload/teardown intent set for one renderer must not leak to a later load.
// The recovery reload re-fires did-finish-load, so its flag lets the local-PTY orphan
// sweep spare live sessions across that one reload (#5787).
const expectedRendererReload = createWebContentsTimedFlag()
const recoveryReloadInFlight = createWebContentsTimedFlag()
let firstWindowStartupServicesReady: Promise<void> = Promise.resolve()
// Why: GPU child crashes clustered right after launch indicate a broken driver;
// track them so Orca can move this build onto software rendering.
Expand Down Expand Up @@ -435,51 +436,66 @@ function focusExistingWindow(): void {
})
}

// Why: a webContents-scoped flag that auto-expires so an intent set for one renderer
// can't leak to a later load. `consume` clears on a positive match for one-shot
// signals (the recovery reload fires exactly one did-finish-load).
function createWebContentsTimedFlag(defaultDurationMs = 10_000): {
mark: (webContentsId: number, durationMs?: number) => void
clear: (webContentsId?: number) => void
matches: (webContentsId: number, options?: { consume?: boolean }) => boolean
} {
let state: { webContentsId: number; until: number } | null = null
return {
mark(webContentsId, durationMs = defaultDurationMs) {
state = { webContentsId, until: Date.now() + durationMs }
},
clear(webContentsId) {
if (webContentsId === undefined || state?.webContentsId === webContentsId) {
state = null
}
},
matches(webContentsId, options) {
if (!state || Date.now() > state.until) {
state = null
return false
}
if (state.webContentsId !== webContentsId) {
return false
}
if (options?.consume) {
state = null
}
return true
}
}
}

function markExpectedRendererReload(webContentsId: number, durationMs = 10_000): void {
expectedRendererReload = { webContentsId, until: Date.now() + durationMs }
expectedRendererReload.mark(webContentsId, durationMs)
}

function clearExpectedRendererReload(webContentsId?: number): void {
if (webContentsId === undefined || expectedRendererReload?.webContentsId === webContentsId) {
expectedRendererReload = null
}
expectedRendererReload.clear(webContentsId)
}

function getExpectedTeardownScope(webContentsId?: number): ExpectedTeardownScope {
if (isQuitting || isQuittingForUpdate()) {
return 'app-shutdown'
}
if (!expectedRendererReload) {
if (webContentsId === undefined) {
return 'none'
}
if (Date.now() > expectedRendererReload.until) {
expectedRendererReload = null
return 'none'
}
return webContentsId !== undefined && expectedRendererReload.webContentsId === webContentsId
? 'renderer-reload'
: 'none'
return expectedRendererReload.matches(webContentsId) ? 'renderer-reload' : 'none'
}

function markRecoveryReloadInFlight(webContentsId: number, durationMs = 10_000): void {
recoveryReloadInFlight = { webContentsId, until: Date.now() + durationMs }
recoveryReloadInFlight.mark(webContentsId, durationMs)
}

function isRecoveryReloadInFlight(webContentsId: number): boolean {
if (!recoveryReloadInFlight) {
return false
}
if (Date.now() > recoveryReloadInFlight.until) {
recoveryReloadInFlight = null
return false
}
if (recoveryReloadInFlight.webContentsId !== webContentsId) {
return false
}
// Why: consume on read — the recovery reload fires exactly one did-finish-load,
// so clearing here keeps a later genuine reload sweeping orphaned local PTYs.
recoveryReloadInFlight = null
return true
// so a later genuine reload still sweeps orphaned local PTYs.
return recoveryReloadInFlight.matches(webContentsId, { consume: true })
}

function recordAgentStateCrashBreadcrumb(agentType: string, state: string): void {
Expand Down
34 changes: 8 additions & 26 deletions src/main/providers/macos-tcc-login-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,16 @@ function isDisabledByEnv(): boolean {
}

/**
* Wrap a macOS POSIX shell spawn in `/usr/bin/login` so terminal children carry
* their own TCC identity instead of collapsing into Orca's bundle identifier.
* Wrap a macOS shell spawn in `/usr/bin/login -flpq <user> …` so terminal children
* get their own TCC identity instead of collapsing into Orca's bundle id — signed
* CLIs like `op` otherwise re-prompt every launch because tccd attributes the grant
* to Orca and never persists it (#6996). This mirrors how Terminal.app spawns shells.
*
* Why: when Orca spawns a shell directly, macOS attributes a spawned CLI's
* "access other apps' data" request (kTCCServiceSystemPolicyAppData) to Orca's
* bundle id and never persists the grant, so signed CLIs like `op` re-prompt on
* every launch (#6996). Native terminals (Terminal.app and others) launch shells
* through login(1), which lets tccd resolve each child's own code identity and
* remember the decision. This matches that spawn shape without a native patch.
* Why the env(1) interposition: login(1) overwrites SHELL from the account DB even
* under -p, so `/usr/bin/env SHELL=<shell>` re-asserts the shell Orca actually runs
* without disturbing login's attribution (skipped when the shell path contains `=`).
*
* Flags: -f (skip auth; we are the logged-in user relaunching as ourselves),
* -l (do not chdir to home — node-pty already set cwd — and skip the login
* dash-argv0 marker), -p (preserve Orca's env, including its ZDOTDIR shell
* integration), -q (suppress the login banner so wrapped terminals look
* unchanged). The underlying shell keeps its own args (e.g. zsh's `-l`) so
* login-shell behavior is unchanged.
*
* SHELL: even under -p, login(1) overwrites SHELL with the account shell from
* the user database, while Orca terminals deliberately export the shell they
* actually run (fallback shells, custom shell settings). Interposing
* `/usr/bin/env SHELL=<shell>` between login and the shell re-asserts the
* intended value — and, as a same-process exec, does not disturb login's TCC
* attribution. Skipped only if the shell path itself contains `=`, which env(1)
* would misparse as an assignment.
*
* No-op off macOS, when already wrapped, when the login binary or username is
* unavailable, or when disabled via {@link DISABLE_ENV_VAR}, so terminal
* spawning never regresses.
* No-op off macOS, when already wrapped, or when disabled via {@link DISABLE_ENV_VAR}.
*/
export function wrapShellSpawnForMacosTccAttribution(
file: string,
Expand Down
8 changes: 6 additions & 2 deletions src/main/rate-limits/claude-pty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ const SESSION_RE = /current\s*session/i
const WEEKLY_RE = /(?:current\s*week|weekly\s*(?:limits?|usage|rate\s*limits?)|7\s*[- ]?\s*day)/i
const FABLE_WORD_RE = /\bfable\b/i
const FABLE_LABEL_RE = /^\s*fable\s*$/i
const FABLE_WEEKLY_LABEL_RE =
/(?:current\s*week|weekly\s*(?:limits?|usage|rate\s*limits?)|7\s*[- ]?\s*day)\s*(?:\([^)]*\bfable\b[^)]*\)|[-:]?\s*\bfable\b)/i
// Why: derive from WEEKLY_RE so a future weekly-wording change stays in one place
// instead of silently reopening the Fable-weekly parsing gap this fix closed.
const FABLE_WEEKLY_LABEL_RE = new RegExp(
`${WEEKLY_RE.source}\\s*(?:\\([^)]*\\bfable\\b[^)]*\\)|[-:]?\\s*\\bfable\\b)`,
'i'
)
const PERCENT_RE = /(\d{1,3})(?:\.\d+)?\s*%\s*(used|consumed|left|remaining|available)/i
const ESC = String.fromCharCode(27)
const BEL = String.fromCharCode(7)
Expand Down
Loading