AISA v2.0 / Technical documentation / verify-ledger.sql
verify-ledger.sql
SQL · 72 lines · 3,774 bytes · wiki path 10 Architecture/db/SrdSupport/verify-ledger.sql · download the raw file · cited from Data Model — `SRD_SUPPORT`
Same folder: 001-work-and-planning.sql · 002-decisions-and-grants.sql · 003-evidence.sql · 004-ledger.sql · 005-registry.sql · 006-memory.sql · 007-policy-and-operations.sql · 008-immutability-and-payload-guards.sql · 009-seed-platform-policy.sql · 010-runtime-execution.sql · acceptance-install-clean.sql · acceptance-upgrade.sql · grant-runtime.sql · install-clean.sql · lint_ddl.py · upgrade.sql · verify-schema.sql
SET DEFINE OFF
SET SERVEROUTPUT ON
WHENEVER SQLERROR EXIT SQL.SQLCODE
-- verify-ledger.sql — Architecture § 3.2: "verify-ledger.sql compares the chain head against the last exported seal".
-- Three checks, each a hard failure:
-- 1. link continuity — every record's PREV_HASH is the RECORD_HASH of the record before it (by SEQ_NO);
-- 2. seal integrity — every seal's HEAD_RECORD_HASH is the RECORD_HASH at its HEAD_SEQ_NO and its RECORD_COUNT is that SEQ_NO;
-- 3. export currency — the newest exported seal is not older than two seal intervals (one interval late = the ledger-lag
-- alert, D66; two = this script fails, because the RPO is the audit gap — Delivery § 4).
-- Recomputing RECORD_HASH itself needs the SERDICA-JCS-1 canonicaliser (AI.Contracts) and is the exporter's job; this script
-- checks the chain's SHAPE from inside the database, which is what a DBA-side rewrite would have to fake.
DECLARE
v_breaks INTEGER;
v_bad_seals INTEGER;
v_head_seq NUMBER(19);
v_head_hash NVARCHAR2(64);
v_last_seal NUMBER(19);
v_last_export TIMESTAMP(6);
v_interval INTERVAL DAY TO SECOND := INTERVAL '15' MINUTE;
v_interval_s VARCHAR2(64);
BEGIN
-- 1. link continuity
SELECT COUNT(*) INTO v_breaks
FROM (SELECT "PREV_HASH", LAG("RECORD_HASH") OVER (ORDER BY "SEQ_NO") AS expected_prev, "SEQ_NO"
FROM "SRD_SUPPORT"."AUDIT_LEDGER")
WHERE expected_prev IS NOT NULL AND "PREV_HASH" <> expected_prev;
IF v_breaks > 0 THEN
RAISE_APPLICATION_ERROR(-20080, 'ledger chain broken: ' || v_breaks || ' record(s) whose PREV_HASH is not the previous RECORD_HASH');
END IF;
-- 2. seal integrity
SELECT COUNT(*) INTO v_bad_seals
FROM "SRD_SUPPORT"."LEDGER_SEALS" s
JOIN "SRD_SUPPORT"."AUDIT_LEDGER" l ON l."SEQ_NO" = s."HEAD_SEQ_NO"
WHERE s."HEAD_RECORD_HASH" <> l."RECORD_HASH" OR s."RECORD_COUNT" <> s."HEAD_SEQ_NO";
IF v_bad_seals > 0 THEN
RAISE_APPLICATION_ERROR(-20081, 'ledger seals disagree with the chain: ' || v_bad_seals || ' seal(s)');
END IF;
-- 3. export currency
SELECT MAX("SEQ_NO") INTO v_head_seq FROM "SRD_SUPPORT"."AUDIT_LEDGER";
IF v_head_seq IS NULL THEN
DBMS_OUTPUT.PUT_LINE('verify-ledger: empty ledger — nothing to verify');
RETURN;
END IF;
SELECT "RECORD_HASH" INTO v_head_hash FROM "SRD_SUPPORT"."AUDIT_LEDGER" WHERE "SEQ_NO" = v_head_seq;
BEGIN
SELECT JSON_VALUE("VALUE", '$') INTO v_interval_s
FROM "SRD_SUPPORT"."POLICY_VALUES"
WHERE "SCOPE" = 'platform' AND "KEY" = 'ledger.export_interval' AND "STATE" = 'published'
ORDER BY "VERSION" DESC FETCH FIRST 1 ROW ONLY;
v_interval := TO_DSINTERVAL(v_interval_s);
EXCEPTION WHEN OTHERS THEN NULL; -- keep the 15-minute default
END;
SELECT MAX(s."SEAL_NO"), MAX(e."ATTEMPTED_ON_UTC") INTO v_last_seal, v_last_export
FROM "SRD_SUPPORT"."LEDGER_SEALS" s
JOIN "SRD_SUPPORT"."LEDGER_EXPORTS" e ON e."SEAL_NO" = s."SEAL_NO" AND e."STATE" = 'exported';
IF v_last_seal IS NULL THEN
RAISE_APPLICATION_ERROR(-20082, 'ledger has ' || v_head_seq || ' records and no exported seal — the off-database copy does not exist');
END IF;
IF v_last_export < SYS_EXTRACT_UTC(SYSTIMESTAMP) - (v_interval * 2) THEN
RAISE_APPLICATION_ERROR(-20083, 'last exported seal ' || v_last_seal || ' at ' || TO_CHAR(v_last_export, 'YYYY-MM-DD HH24:MI:SS') || ' UTC is older than two export intervals');
END IF;
DBMS_OUTPUT.PUT_LINE('verify-ledger: OK — head SEQ_NO ' || v_head_seq || ' hash ' || v_head_hash || '; last exported seal ' || v_last_seal || ' at ' || TO_CHAR(v_last_export, 'YYYY-MM-DD HH24:MI:SS') || ' UTC');
END;
/