mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* Fix Windows daemon host prune liveness contract * Scope host prune evidence per version * Drop redundant default cases from exhaustive liveness switches All three switches consume ProcessLivenessVerdict/ProcessSignalEvidence values constructed in-process by inspectProcessSignal/inspectProcessLiveness; the union is never deserialized from a wire, RPC, or persisted record, so the defaults are genuinely unreachable. * Cover prune liveness gates and quarantine corrupt pid records * Make the prune delete-gate fail safe and refuse truncated pid salvage The prune switch shared #16900's delete-gate shape: an unhandled future verdict status fell through into rmSync, protected only by the lint exhaustiveness rule. Deletion is now opted into by a positively matched 'exited' via reclaimUnownedDaemonHostDir; a pinning test feeds an out-of-contract verdict and asserts the host dir survives. Pid salvage from corrupt records now requires the digit run to be terminated by a following non-digit byte. A tear inside the digits leaves a truncated prefix that is a different pid: probing it either quarantined a record on an unrelated process's death or, when the prefix collided with an immortal pid (Windows System pid 4), re-created the permanent prune veto for that record. Unterminated digits mean the writer died mid-write, so the record quarantines without consulting any probe. * fix(daemon): stop an in-flight pid publish from being read as a dead version publishDaemonPidFile creates the record before writing it (writeFileSync with flag 'wx'), so a concurrent launch can read a live daemon's record as empty. A two-process probe observed the empty window on 7 of 2273 reads. An empty record was not treated as corrupt at all: the parser's legacy bare-integer fallback coerces it to pid 0 (Number('') === 0) with appVersion null, so the scan skipped it as a pre-relocation daemon, left its version unpinned, and the prune reclaimed a running daemon's host image -- the exact destructive outcome this change exists to prevent, reached without any 'unverifiable' verdict. A pid that is not a positive integer names no process (process.kill(0, 0) probes the caller's own process group), so it is now a veto rather than a skip. Quarantine additionally refuses any record written in the last minute: an in-flight publish is by definition fresh, while a record left corrupt by a dead writer ages past the floor and is quarantined on a later launch. Fixed locally rather than in parseDaemonPidFile, whose null result also drives an unlink in daemon-stale-kill. Each gate is pinned by a test that fails individually when it is reverted. --------- Co-authored-by: Brennan Benson <brennanb2025@users.noreply.github.com>
23 lines
1.1 KiB
TypeScript
23 lines
1.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { salvagePidFromCorruptDaemonRecord } from './daemon-pid-file-parse'
|
|
|
|
describe('salvagePidFromCorruptDaemonRecord', () => {
|
|
it('salvages a pid whose digit run is terminated by a following byte', () => {
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid":4242,"startedAtMs":17')).toBe(4242)
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid": 4242, "startedAtMs"')).toBe(4242)
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid":4242}')).toBe(4242)
|
|
})
|
|
|
|
it('refuses digits at end-of-bytes: a tear inside the digits leaves a different pid', () => {
|
|
// Pid 42420 torn mid-digits — the surviving prefix 4242 must not be mistaken for a pid.
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid":4242')).toBe(null)
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid":4')).toBe(null)
|
|
})
|
|
|
|
it('refuses records with no usable pid field', () => {
|
|
expect(salvagePidFromCorruptDaemonRecord('not a daemon record')).toBe(null)
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid":0,"startedAtMs":17')).toBe(null)
|
|
expect(salvagePidFromCorruptDaemonRecord('{"pid":-42,')).toBe(null)
|
|
})
|
|
})
|