Append-only at the database. Hash-chained across rows.
The application layer does not get a vote. Postgres triggers reject UPDATE and DELETE. A sha256 chain across rows means any insert between two rows is detectable with a single SQL query.
Enforced where it counts
Most "audit log" implementations enforce immutability in application code. That works until someone has psql access. BondFoundry moves the rule into the database, where it survives the application being compromised.
CREATE OR REPLACE FUNCTION reject_mutation()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
RAISE EXCEPTION 'audit_log is append-only (tg_op=%, row=%s)',
TG_OP, OLD.id;
END;
$$;
CREATE TRIGGER audit_immutable
BEFORE UPDATE OR DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION reject_mutation(); Tamper-evidence in one query
Each row stores the sha256 of (previous_row_hash || serialized_payload || sequence_number). Verifying the chain is a single recursive SQL query. Inserts between two rows break the chain at the join point.
- sha256 over (prev_hash || payload || seq)
- Sequence numbers folded into chain input (handles identical timestamps cleanly)
- Verify with `bondfoundry-finos verify-chain`
- O(N) walk; runs in seconds on millions of rows
Every row maps to a control
The `framework_ref` column is NOT NULL with a CHECK constraint that validates against the live AIGF risk taxonomy. A mis-mapped tool call fails the write rather than silently passing and becoming invisible to the coverage report.