Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
007f35e
Revert "feat(orcad): source-side dormant export of a relay-hosted SSH…
Oct 2, 2026
f3eb614
Revert "feat(ssh): update, roll back, recover and stop a managed orca…
Oct 2, 2026
61bf434
Revert "feat(ssh): deploy and pair an empty managed orcad server over…
Oct 2, 2026
52bd063
Revert "fix(ssh): orcad GC honors the activation journal; readiness r…
Oct 2, 2026
3880b0a
Revert "feat(ssh): remote orcad stop by request file and journaled de…
Oct 2, 2026
428da57
Revert "feat(orcad): supervisable server: stop requests, managed stop…
Oct 2, 2026
dbbfc3d
Revert "feat(ssh): crash-safe orcad activation, rollback and recovery…
Oct 2, 2026
92602ac
Revert "feat(runtime): SSH access links for paired servers in a downg…
Oct 2, 2026
a0df1c0
Revert "feat(relay): capability-gated owner reset with a durable prep…
Oct 2, 2026
9a615dd
Revert "feat(ssh): track connection-manager drains, test probes and p…
Oct 2, 2026
840aeae
Revert "feat(daemon): idle retirement, session census and recovery-on…
Oct 2, 2026
2342808
Revert "feat(ssh): add pty.resumeClient and split SSH PTY process lis…
Oct 2, 2026
bf6399c
Revert "feat(relay): await owned watcher and agent children on shutdo…
Oct 2, 2026
982d7aa
Revert "feat(session): retry failed renderer session writes and verif…
Oct 2, 2026
7eae6aa
Revert "feat(ssh): remote orcad primitives on the pinned Node runtime…
Oct 2, 2026
0a407f1
Revert "fix(runtime): fence runtime-environment subscriptions and sta…
Oct 2, 2026
93517f0
Revert "feat(orcad): migration manifest and dormant-state contracts (…
Oct 2, 2026
2b13568
Revert "feat(ssh): wire SshConnection through the work and transport …
Oct 2, 2026
25d7195
Revert "feat(profiles): carry markdown frontmatter visibility in proj…
Oct 2, 2026
ba84e32
Revert "fix(runtime): project the PTY incarnation onto mobile session…
Oct 2, 2026
adf1f68
Revert "feat(daemon): tag daemon stream data with the PTY incarnation…
Oct 2, 2026
c5e717d
Revert "feat(ssh): port the SSH connection work ledger and transport …
Oct 2, 2026
93915c2
Revert "feat(relay): fence and drain file and git response streams on…
Oct 2, 2026
91620bf
Revert "refactor(runtime-rpc): extract the Node WebSocket lifecycle; …
Oct 2, 2026
db0e0eb
Revert "feat(relay): route relay handlers through work admission; pro…
Oct 2, 2026
cb60b07
Revert "feat(relay): land the #16741 T1 seam (work drain, publication…
Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
100 changes: 0 additions & 100 deletions config/scripts/orcad-daemon-protocol-crossing.test.mjs

This file was deleted.

41 changes: 0 additions & 41 deletions docs/reference/orcad-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,47 +150,6 @@ An external supervisor (systemd, launchd, a process manager). orcad conforms to
exits with code 1 if teardown stalls. The bundled runtime also stops gracefully if its
launcher's IPC channel closes. On POSIX, both the launcher and runtime ignore `SIGHUP`,
so terminal hangups do not stop a headless host. Use `SIGTERM` or `SIGINT` to stop it.
- **Stop requests.** A file stops orcad the same way `SIGTERM` does, without a PID that may
since have been reused by another process:
- `.orcad-stop-request` beside `orcad.js` in the running slot. orcad deletes it and stops.
- An instance-bound request in the data root, named
`.orcad-managed-stop-request.<sha256 of the instance lock nonce>`. orcad stops only when it
names this orcad's version, runtime ID, PID, start time and lock nonce, and while the
instance lock still holds that record. The file is kept as evidence.
- `orcad --complete-managed-stop '<request JSON>'` writes that request, waits for the
instance to exit, and prints one JSON line whose `verdict` is `live`, `unverifiable` or
`exited`. `exited` needs proof: no process with that PID, or a PID whose start time shows
it now belongs to another process. On `exited` it writes
`<data-root>/orcad-stop-receipts/<transactionId>.json`. It exits 0 whenever it printed a
verdict, 64 for a malformed invocation, and 1 for a failure before any verdict, which is
never evidence of exit.
- A request with `retireIdleDaemon: true` asks orcad to retire the terminal daemon too. This
is best effort and never blocks or fails the stop:
- The daemon is retired only when it proves it owns no live session across every
generation.
- A busy daemon (`live`) or one whose state cannot be proven (`unverifiable`) stays up with
its terminals, and orcad reopens new-terminal admission before exiting.
- The completed-stop receipt records `retirement` as `retired`, `live` or `unverifiable`.
If orcad exits without recording an outcome, the receipt says `unverifiable`.
- `orcad --cancel-managed-stop '<request JSON>'` withdraws a request orcad has not acted on.
orcad and the canceller each try to create `<transactionId>.decision.json` exclusively,
so exactly one wins. `canceled` means orcad keeps running and the request file is removed;
`dispatched` means orcad already began stopping, and only the completion can say how it
ended.
- A build advertises all of the above with `health.stopRequests: 1` in its readiness line.
Clients stop such a build through the slot request file and older builds with `SIGTERM`,
after corroborating the PID with readiness either way. A launch clears a slot request
that the previous process never consumed.
- **Decommissioning a managed slot.** An Orca client decommissions through the same activation
journal and fence as deploy and rollback. It refuses while the terminal census reports live
or uncounted terminals, stops the instance with a managed request that also asks to retire
the daemon, and records that no version is active only after `exited` is proven. A stop
that did not finish is cancelled; if orcad already acted on it, or the host cannot answer,
the fence stays for recovery.
- **Instance lock.** `<data-root>/orcad.lock` names the running orcad. A record that is
unreadable, malformed or over 64 KiB is never reclaimed: orcad exits 78 until an operator
removes it. A shutdown whose teardown failed keeps the lock until the process exits, so a
second orcad cannot start beside a writer that may still be running.
- **Exit codes.**

| Code | Meaning | Supervisor should |
Expand Down
13 changes: 0 additions & 13 deletions src/main/daemon/daemon-health-identity.ts

This file was deleted.

54 changes: 1 addition & 53 deletions src/main/daemon/daemon-health.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { getDaemonPidPath, serializeDaemonPidFile } from './daemon-spawner'
import type { SocketProbeOutcome } from './daemon-endpoint-probe'
import {
checkDaemonHealth,
checkDaemonHealthWithCoverage,
E2E_FORCE_DAEMON_HEALTH_UNREACHABLE_ENV,
healthCheckDaemon
} from './daemon-health'
Expand Down Expand Up @@ -106,63 +105,12 @@ describe('daemon health', () => {
try {
await expect(checkDaemonHealth(socketPath, tokenPath)).resolves.toBe('healthy')
await expect(healthCheckDaemon(socketPath, tokenPath)).resolves.toBe(true)
await expect(checkDaemonHealthWithCoverage(socketPath, tokenPath)).resolves.toMatchObject({
verdict: 'healthy',
coverage: process.platform === 'win32' ? 'handshake' : 'pty-spawn',
runtimeKind: 'node',
runtimeVersion: process.version
})
expect(ptySpawnHealthCheck).toHaveBeenCalledTimes(3)
expect(ptySpawnHealthCheck).toHaveBeenCalledTimes(2)
} finally {
await server.shutdown()
}
})

it('treats missing coverage from a legacy Windows daemon as handshake-only', async () => {
writeFileSync(tokenPath, 'legacy-token')
const server = createServer((socket) => {
let pending = ''
socket.on('data', (chunk) => {
pending += chunk.toString()
for (;;) {
const newline = pending.indexOf('\n')
if (newline === -1) {
return
}
const message: unknown = JSON.parse(pending.slice(0, newline))
const type =
typeof message === 'object' && message !== null && 'type' in message
? message.type
: undefined
pending = pending.slice(newline + 1)
if (type === 'hello') {
socket.write(`${JSON.stringify({ type: 'hello', ok: true })}\n`)
} else if (type === 'ptySpawnHealth') {
socket.write(
`${JSON.stringify({ id: 'health-1', ok: true, payload: { healthy: true } })}\n`
)
}
}
})
})
await new Promise<void>((resolve, reject) => {
server.once('error', reject)
server.listen(socketPath, resolve)
})

const platform = Object.getOwnPropertyDescriptor(process, 'platform')!
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
try {
await expect(checkDaemonHealthWithCoverage(socketPath, tokenPath)).resolves.toEqual({
verdict: 'healthy',
coverage: 'handshake'
})
} finally {
Object.defineProperty(process, 'platform', platform)
await closeServer(server)
}
})

it('fails when a protocol-healthy daemon cannot spawn PTYs', async () => {
const server = new DaemonServer({
socketPath,
Expand Down
77 changes: 10 additions & 67 deletions src/main/daemon/daemon-health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,71 +21,29 @@ export const E2E_FORCE_DAEMON_HEALTH_UNREACHABLE_ENV = 'ORCA_E2E_FORCE_DAEMON_HE
// also covers a live-but-wedged daemon that simply missed the RPC budget.
export type DaemonHealth = 'healthy' | 'unreachable' | 'rejected' | 'pty-spawn-unhealthy'

export type DaemonHealthCheck = {
verdict: DaemonHealth
coverage: 'pty-spawn' | 'handshake'
/** Optional runtime proof from newer daemons; absent on mixed-version peers. */
runtimeKind?: 'node'
runtimeVersion?: string
}

function readRuntimeIdentity(
payload: unknown
): Pick<DaemonHealthCheck, 'runtimeKind' | 'runtimeVersion'> {
if (typeof payload !== 'object' || payload === null) {
return {}
}
const runtimeKind = 'runtimeKind' in payload ? payload.runtimeKind : undefined
const runtimeVersion = 'runtimeVersion' in payload ? payload.runtimeVersion : undefined
// Why drop other kinds: only a Node daemon reports here; anything else is an unknown peer.
return {
...(runtimeKind === 'node' ? { runtimeKind } : {}),
...(typeof runtimeVersion === 'string' && runtimeVersion.length > 0 ? { runtimeVersion } : {})
}
}

function readPtySpawnHealthCoverage(
payload: unknown,
fallback: DaemonHealthCheck['coverage']
): DaemonHealthCheck['coverage'] {
if (typeof payload !== 'object' || payload === null) {
return fallback
}
const coverage = 'coverage' in payload ? payload.coverage : undefined
return coverage === 'pty-spawn' || coverage === 'handshake' ? coverage : fallback
}

export function checkDaemonHealthWithCoverage(
socketPath: string,
tokenPath: string
): Promise<DaemonHealthCheck> {
export function checkDaemonHealth(socketPath: string, tokenPath: string): Promise<DaemonHealth> {
return new Promise((resolve) => {
// Older Windows daemons answered this RPC without spawning; an absent optional coverage
// field must preserve that weaker meaning during adoption.
const fallbackCoverage = process.platform === 'win32' ? 'handshake' : 'pty-spawn'
const resolveVerdict = (verdict: DaemonHealth): void =>
resolve({ verdict, coverage: fallbackCoverage })
if (process.env[E2E_FORCE_DAEMON_HEALTH_UNREACHABLE_ENV] === '1') {
resolveVerdict('unreachable')
resolve('unreachable')
return
}

if (process.platform !== 'win32' && !existsSync(socketPath)) {
resolveVerdict('unreachable')
resolve('unreachable')
return
}

let token: string
try {
token = readFileSync(tokenPath, 'utf8').trim()
} catch {
resolveVerdict('unreachable')
resolve('unreachable')
return
}

let settled = false
let sock: Socket | null = null
const settle = (result: DaemonHealthCheck): void => {
const settle = (result: DaemonHealth): void => {
if (settled) {
return
}
Expand All @@ -100,7 +58,7 @@ export function checkDaemonHealthWithCoverage(
sock?.off('connect', onConnect)
sock?.off('data', onData)
}
const onError = (): void => settle({ verdict: 'unreachable', coverage: fallbackCoverage })
const onError = (): void => settle('unreachable')
const onConnect = (): void => {
const hello: HelloMessage = {
type: 'hello',
Expand Down Expand Up @@ -131,13 +89,13 @@ export function checkDaemonHealthWithCoverage(
try {
message = JSON.parse(line) as Record<string, unknown>
} catch {
settle({ verdict: 'rejected', coverage: fallbackCoverage })
settle('rejected')
return
}

if (message.type === 'hello') {
if (!(message as HelloResponse).ok) {
settle({ verdict: 'rejected', coverage: fallbackCoverage })
settle('rejected')
return
}
// Why: a protocol-live daemon with a stale cwd or node-pty helper
Expand All @@ -148,20 +106,12 @@ export function checkDaemonHealthWithCoverage(
}

if (message.id === 'health-1') {
const identity = readRuntimeIdentity(message.payload)
settle({
verdict: message.ok === true ? 'healthy' : 'pty-spawn-unhealthy',
coverage: readPtySpawnHealthCoverage(message.payload, fallbackCoverage),
...identity
})
settle(message.ok === true ? 'healthy' : 'pty-spawn-unhealthy')
return
}
}
}
const timer = setTimeout(
() => settle({ verdict: 'unreachable', coverage: fallbackCoverage }),
HEALTH_CHECK_TIMEOUT_MS
)
const timer = setTimeout(() => settle('unreachable'), HEALTH_CHECK_TIMEOUT_MS)

sock = connect({ path: socketPath })
sock.on('error', onError)
Expand All @@ -172,13 +122,6 @@ export function checkDaemonHealthWithCoverage(
})
}

export async function checkDaemonHealth(
socketPath: string,
tokenPath: string
): Promise<DaemonHealth> {
return (await checkDaemonHealthWithCoverage(socketPath, tokenPath)).verdict
}

export async function healthCheckDaemon(socketPath: string, tokenPath: string): Promise<boolean> {
return (await checkDaemonHealth(socketPath, tokenPath)) === 'healthy'
}
Expand Down
Loading
Loading