import assert from 'node:assert/strict' import { it } from 'node:test' import { CheckLedger, FakeContainerRunner, TurnLedger } from '../dist/index.js' import { reconcileCheckOrphans, reconcileOrphans } from '../dist/cli.js' // FIX #6: orphan reconciliation must preserve attempts (markCrashed, not reset). A daemon that // repeatedly crashes mid-turn must still respect the retry bound across restarts, instead of // silently resetting the retry counter (and therefore never giving up) every time it comes back up. const REQUEST_URI = 'at://did:plc:human/com.disnetdev.radial.artifactRequest/request-1' const REQUEST_CID = 'cid-request-1' const LABEL = 'radial.turn.orphan' it('reconcileOrphans kills the tracked container and transitions the row to crashed with attempts preserved (not deleted)', async () => { const ledger = new TurnLedger(':memory:', { retryBound: 5, cooldownMs: 60_000 }) // Seed one prior failed attempt, then a fresh "running" row (as if the daemon restarted right // after redispatching), so attempts is already > 0 going into this reconciliation. ledger.markRunning(REQUEST_URI, REQUEST_CID, { containerLabel: LABEL, checkoutPath: '/tmp/x' }) ledger.markCrashed(REQUEST_URI) assert.equal(ledger.get(REQUEST_URI).attempts, 1) ledger.markRunning(REQUEST_URI, REQUEST_CID, { containerLabel: LABEL, checkoutPath: '/tmp/x' }) assert.equal(ledger.get(REQUEST_URI).state, 'running') const runner = new FakeContainerRunner(async () => ({ exitCode: 0, timedOut: false })) // Seed a started "container" under the label the way a real orphan would be tracked. await runner.run({ label: LABEL, image: 'x', argv: [], env: {}, mounts: [], timeoutMs: 1000 }) const killed = [] const originalKill = runner.kill.bind(runner) runner.kill = async (idOrName) => { killed.push(idOrName) return originalKill(idOrName) } await reconcileOrphans(runner, ledger) assert.ok(killed.length > 0) assert.deepEqual(await runner.listByLabel(LABEL), []) const row = ledger.get(REQUEST_URI) assert.ok(row, 'the row must still exist — reconciliation must not delete it') assert.ok(row.state === 'crashed' || row.state === 'gave_up', `expected crashed/gave_up, got ${row.state}`) assert.equal(row.attempts, 2) // the pre-seeded attempt plus this reconciliation's attempt assert.deepEqual(ledger.running(), []) // no longer "running": the orphan is fully reconciled ledger.close() }) it('reconcileOrphans applied repeatedly across "restarts" eventually gives up instead of resetting the counter', async () => { const retryBound = 3 const ledger = new TurnLedger(':memory:', { retryBound, cooldownMs: 0 }) const runner = new FakeContainerRunner(async () => ({ exitCode: 0, timedOut: false })) for (let restart = 0; restart < retryBound; restart += 1) { ledger.markRunning(REQUEST_URI, REQUEST_CID, { containerLabel: LABEL, checkoutPath: '/tmp/x' }) await runner.run({ label: LABEL, image: 'x', argv: [], env: {}, mounts: [], timeoutMs: 1000 }) await reconcileOrphans(runner, ledger) } const row = ledger.get(REQUEST_URI) assert.equal(row.state, 'gave_up') assert.equal(row.attempts, retryBound) ledger.close() }) it('reconcileCheckOrphans kills the tracked container and transitions the check row to crashed (attempts preserved)', async () => { const CHECK_LABEL = 'radial.check.orphan' const key = { artifactUri: 'at://did:plc:human/com.disnetdev.radial.artifact/a1', artifactCid: 'cid-a1', commit: 'deadbeef' } const ledger = new CheckLedger(':memory:', { retryBound: 5, cooldownMs: 60_000 }) ledger.markRunning(key, { containerLabel: CHECK_LABEL }) ledger.markCrashed(key) assert.equal(ledger.get(key).attempts, 1) ledger.markRunning(key, { containerLabel: CHECK_LABEL }) assert.equal(ledger.get(key).state, 'running') const runner = new FakeContainerRunner(async () => ({ exitCode: 0, timedOut: false })) await runner.run({ label: CHECK_LABEL, image: 'x', argv: [], env: {}, mounts: [], timeoutMs: 1000 }) const killed = [] const originalKill = runner.kill.bind(runner) runner.kill = async (idOrName) => { killed.push(idOrName) return originalKill(idOrName) } await reconcileCheckOrphans(runner, ledger) assert.ok(killed.length > 0) assert.deepEqual(await runner.listByLabel(CHECK_LABEL), []) const row = ledger.get(key) assert.ok(row.state === 'crashed' || row.state === 'gave_up', `expected crashed/gave_up, got ${row.state}`) assert.equal(row.attempts, 2) assert.deepEqual(ledger.running(), []) ledger.close() })