Files
xinmi/scripts/wiki_recovery_closures.cjs
T

67 lines
3.4 KiB
JavaScript
Raw Normal View History

// 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 }