Files
wikideck/server/market/custody-incident.ts
T

30 lines
2.0 KiB
TypeScript
Executable File

import { randomUUID, createHash } from 'node:crypto';
import type { Pool } from 'pg';
import { z } from 'zod';
import { CUSTODY_WRITE_LOCK } from './postgres-schema.ts';
import { auditPostgres } from './postgres-custody.ts';
/** Operator-only acknowledgement AFTER externally reviewed repair/restore; never repairs or resets a journal. */
export async function resolveCustodyIncident(pool: Pool, evidenceId: string): Promise<{ resolutionId: string }> {
z.string().uuid().parse(evidenceId);
const c = await pool.connect();
try {
await c.query('BEGIN');
await c.query("SET LOCAL statement_timeout='10s'; SET LOCAL lock_timeout='3s'; SET LOCAL idle_in_transaction_session_timeout='10s'; SET LOCAL synchronous_commit=on");
await c.query('SELECT pg_advisory_xact_lock($1::bigint)', [CUSTODY_WRITE_LOCK]);
const allowed = await c.query("SELECT pg_has_role(current_user,nspowner,'MEMBER') AS owns FROM pg_namespace WHERE nspname='wk_custody'");
if (allowed.rows[0]?.owns !== true) throw new Error('Schema-owner approval required for integrity resolution');
const incident = await c.query("SELECT value FROM wk_custody.custody_meta WHERE key='integrity_incident_v1' FOR UPDATE");
if (!incident.rowCount) throw new Error('No unresolved custody integrity incident');
// Delete is UNCOMMITTED and exclusively locked. Any audit failure rolls it back, leaving the hold intact.
await c.query("DELETE FROM wk_custody.custody_meta WHERE key='integrity_incident_v1'");
await auditPostgres(c);
const resolutionId = randomUUID();
await c.query('INSERT INTO wk_custody.custody_meta VALUES($1,$2)', [`integrity_resolution:${resolutionId}`,
JSON.stringify({ version: 1, evidenceId, resolvedAt: Date.now(),
incidentDigest: createHash('sha256').update(incident.rows[0].value).digest('hex') })]);
await c.query('COMMIT'); return { resolutionId };
} catch (error) { await c.query('ROLLBACK').catch(() => {}); throw error; }
finally { c.release(); }
}