fix(orchestration): stop certifying an unproven process exit

An unproven stop wrote stage=process_exited with termination_reason=unknown,
and the fleet projection read that stage alone as a death certificate, so
worker-list and worker-show published liveness=exited and nextAction=recover
for a possibly-live agent. Only certify the stage when the cause was observed.
This commit is contained in:
Jinwoo-H
2026-09-04 15:21:13 -04:00
parent 9806af4258
commit c2c242dd8a
2 changed files with 41 additions and 1 deletions
@@ -420,6 +420,43 @@ describe('fleet liveness and attention after a host verdict', () => {
})
})
it('refuses to certify a process_exited stage whose cause was never observed', () => {
const projected = projectOrchestrationFleet({
workers: [
worker('1', {
workerStage: 'process_exited',
workerState: 'failed',
terminationReason: 'unknown'
})
],
statuses: [],
now: 10_000
})
expect(projected.workers[0]!.liveness).toEqual({
verdict: 'unverifiable',
reason: 'missing_status'
})
expect(projected.workers[0]!.nextAction.kind).toBe('inspect')
})
it('certifies a process_exited stage whose exit was observed', () => {
const projected = projectOrchestrationFleet({
workers: [
worker('1', {
workerStage: 'process_exited',
workerState: 'failed',
terminationReason: 'exited'
})
],
statuses: [],
now: 10_000
})
expect(projected.workers[0]!.liveness).toEqual({
verdict: 'exited',
source: 'execution_host'
})
})
it('keeps an unverifiable worker on inspect: absence is never authority to stop', () => {
const now = 10 * AGENT_STATUS_STALE_AFTER_MS
const projected = projectOrchestrationFleet({
@@ -26,7 +26,10 @@ const SETTLED_WORKER_STATES = new Set(['succeeded', 'failed', 'stopped', 'abando
* `unknown` is a death certificate — regardless of which state the worker settled into. */
function hasCertifiedExit(worker: FleetLivenessSubject): boolean {
return (
worker.workerStage === 'process_exited' ||
// `process_exited` is written from the same cause as the reason beside it, and
// `unknown` there means a stop was issued and no exit was ever observed. A null
// reason is a pre-v29 row whose stage write was the only exit record.
(worker.workerStage === 'process_exited' && worker.terminationReason !== 'unknown') ||
worker.terminationReason === 'operator_close' ||
worker.terminationReason === 'signaled' ||
worker.terminationReason === 'exited'