CREATE TABLE ledger_operating_state ( singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton), mode text NOT NULL CHECK (mode IN ('NORMAL','GL_ONLY')), revision bigint NOT NULL CHECK (revision >= 0), actor_id text NOT NULL, reason text NOT NULL, changed_at timestamptz NOT NULL DEFAULT clock_timestamp() ); INSERT INTO ledger_operating_state (mode,revision,actor_id,reason) VALUES ('NORMAL',0,'system','initial mode'); CREATE TABLE ledger_mode_changes ( revision bigint PRIMARY KEY, previous_mode text NOT NULL CHECK (previous_mode IN ('NORMAL','GL_ONLY')), mode text NOT NULL CHECK (mode IN ('NORMAL','GL_ONLY')), actor_id text NOT NULL CHECK (btrim(actor_id) <> ''), reason text NOT NULL CHECK (btrim(reason) <> ''), reconciliation_confirmed boolean NOT NULL, changed_at timestamptz NOT NULL ); CREATE TRIGGER mode_changes_reject_mutation BEFORE UPDATE OR DELETE ON ledger_mode_changes FOR EACH ROW EXECUTE FUNCTION reject_ledger_mutation(); CREATE TRIGGER operating_state_reject_delete BEFORE DELETE ON ledger_operating_state FOR EACH ROW EXECUTE FUNCTION reject_ledger_mutation(); -- A shared lock is held through each posting; toggles drain writers. CREATE FUNCTION guard_journal_authority() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE current_mode text; BEGIN SELECT mode INTO STRICT current_mode FROM ledger_operating_state WHERE singleton FOR SHARE; IF NEW.metadata->>'gl_execution'='GL_ONLY' AND current_mode<>'GL_ONLY' AND NOT EXISTS (SELECT 1 FROM journals WHERE idempotency_key=NEW.idempotency_key) THEN RAISE EXCEPTION 'GL_ONLY mode is required' USING ERRCODE='PGL01'; END IF; IF current_mode='GL_ONLY' AND COALESCE(NEW.metadata->>'gl_execution','')<>'GL_ONLY' AND NOT EXISTS (SELECT 1 FROM journals WHERE idempotency_key=NEW.idempotency_key) THEN RAISE EXCEPTION 'new GL_ONLY effects must use internal execution' USING ERRCODE='PGL01'; END IF; RETURN NEW; END; $$; CREATE TRIGGER journals_guard_authority BEFORE INSERT ON journals FOR EACH ROW EXECUTE FUNCTION guard_journal_authority(); -- Both credits and debits lock accounts, including normal recorders. CREATE OR REPLACE FUNCTION guard_entry_insert() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF (SELECT sealed_at IS NOT NULL FROM journals WHERE id=NEW.journal_id) THEN RAISE EXCEPTION 'cannot append to a sealed journal' USING ERRCODE='55000'; END IF; PERFORM id FROM ledger_accounts WHERE id=NEW.account_id AND class IN ('USER_AVAILABLE','USER_FROZEN') FOR UPDATE; RETURN NEW; END; $$; CREATE OR REPLACE FUNCTION guard_journal_seal() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE current_mode text; BEGIN IF OLD.sealed_at IS NOT NULL OR NEW.sealed_at IS NULL OR (to_jsonb(NEW)-'sealed_at') IS DISTINCT FROM (to_jsonb(OLD)-'sealed_at') THEN RAISE EXCEPTION 'journals are immutable except for initial sealing' USING ERRCODE='55000'; END IF; IF (SELECT count(*) FROM journal_entries WHERE journal_id=NEW.id)<2 THEN RAISE EXCEPTION 'journal requires at least two entries' USING ERRCODE='23514'; END IF; IF EXISTS (SELECT asset_id FROM journal_entries WHERE journal_id=NEW.id GROUP BY asset_id HAVING sum(amount)<>0) THEN RAISE EXCEPTION 'journal is not balanced per asset' USING ERRCODE='23514'; END IF; SELECT mode INTO STRICT current_mode FROM ledger_operating_state WHERE singleton; IF current_mode='GL_ONLY' THEN IF NEW.blockchain_network<>'' OR NEW.blockchain_transaction_hash<>'' OR NEW.blockchain_ledger_sequence<>'' OR EXISTS (SELECT 1 FROM journal_entries e JOIN ledger_accounts a ON a.id=e.account_id WHERE e.journal_id=NEW.id AND a.class='EXTERNAL_BLOCKCHAIN') THEN RAISE EXCEPTION 'external blockchain movements are unavailable in GL_ONLY' USING ERRCODE='PGL01'; END IF; IF EXISTS ( SELECT a.id FROM ledger_accounts a JOIN journal_entries e ON e.account_id=a.id JOIN journals j ON j.id=e.journal_id WHERE a.class IN ('USER_AVAILABLE','USER_FROZEN') AND a.id IN (SELECT account_id FROM journal_entries WHERE journal_id=NEW.id) AND (j.sealed_at IS NOT NULL OR j.id=NEW.id) GROUP BY a.id HAVING sum(e.amount)<0 ) THEN RAISE EXCEPTION 'insufficient ledger funds' USING ERRCODE='PGL02'; END IF; END IF; RETURN NEW; END; $$; CREATE FUNCTION guard_settlement_authority() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE current_mode text; BEGIN SELECT mode INTO STRICT current_mode FROM ledger_operating_state WHERE singleton FOR SHARE; IF current_mode='GL_ONLY' AND NOT EXISTS (SELECT 1 FROM kuknos_settlements WHERE idempotency_key=NEW.idempotency_key) THEN RAISE EXCEPTION 'blockchain settlement is disabled in GL_ONLY' USING ERRCODE='PGL01'; END IF; RETURN NEW; END; $$; CREATE TRIGGER settlements_guard_authority BEFORE INSERT ON kuknos_settlements FOR EACH ROW EXECUTE FUNCTION guard_settlement_authority();