// Closure ledger for terminal wiki recovery attempts. // // A recovery attempt that reached a terminal budget state can never continue, so // it must not block unrelated first attempts forever. Closing it is a reviewed // decision: it needs an explicit record naming the exact attempt, its terminal // status and error, a written reason and a timestamp. Anything that does not // match the row we are looking at fails closed, and a pending attempt can never // be closed while it is still running. const fs = require('node:fs') const path = require('node:path') const assert = require('node:assert/strict') const { terminalRecoveryStatuses } = require('./wiki_recovery_policy.cjs') const closureFile = 'closures.json' const closureKey = (tenant, deadLetterId, attempt) => `${tenant}:${deadLetterId}:${attempt}` function parseClosures(parsed, tenant) { assert.equal(parsed.tenant_id, tenant, 'Closure ledger belongs to another tenant') assert.ok(Array.isArray(parsed.closures), 'Closure ledger must list closures') const closures = new Map() for (const closure of parsed.closures) { assert.ok(Number.isSafeInteger(closure.dead_letter_id) && closure.dead_letter_id >= 1 && closure.dead_letter_id <= 33, 'Only original dead letters 1..33 can be closed') assert.ok(Number.isSafeInteger(closure.attempt_number) && closure.attempt_number >= 1, 'A closure needs the exact attempt number') assert.ok(terminalRecoveryStatuses.includes(closure.terminal_status), 'Only terminal attempts can be closed') assert.match(closure.error_code || '', /^[A-Z0-9_]{3,64}$/, 'A closure needs the recorded terminal error code') assert.ok(typeof closure.reason === 'string' && closure.reason.trim().length >= 12, 'A closure needs a written reason') assert.ok(Number.isFinite(Date.parse(closure.closed_at)), 'A closure needs a reviewable timestamp') const key = closureKey(tenant, closure.dead_letter_id, closure.attempt_number) assert.ok(!closures.has(key), 'Closure ledger cannot close the same attempt twice') closures.set(key, closure) } return closures } function loadClosures(directory, tenant) { if (!directory) return new Map() let raw try { raw = fs.readFileSync(path.join(directory, closureFile), 'utf8') } catch (error) { // A missing ledger simply means nothing has been closed yet. if (error.code === 'ENOENT') return new Map() throw error } return parseClosures(JSON.parse(raw), tenant) } function closureFor(row, closures, tenant) { const closure = closures?.get(closureKey(row.tenant_id ?? tenant, row.dead_letter_id, Number(row.attempt_number || 1))) if (!closure) return null // The closure names one terminal attempt. A row that is still running (or that // resolved without a terminal budget state) is simply not covered by it yet. if (!terminalRecoveryStatuses.includes(row.recovery_status)) return null assert.ok(!row.pending && !(row.pending_finalize > 0), 'A pending attempt cannot be closed') assert.equal(closure.terminal_status, row.recovery_status, 'Closure does not match the recorded terminal status') assert.equal(closure.error_code, row.error_code, 'Closure does not match the recorded terminal error') return closure } function annotateClosed(rows, closures, tenant) { return rows.map(row => ({ ...row, closed: closureFor(row, closures, tenant) !== null })) } module.exports = { loadClosures, parseClosures, closureFor, annotateClosed, closureKey, closureFile }