Objective
ECOA / Regulation B requires a specific adverse-action reason on every decline, and TRAIGA, Colorado's bias rules, and the EU AI Act (insurance = high-risk) all now expect model lineage and a reconstructable decision. If the model lives outside the record, those reason codes get reverse-engineered weeks later. This recipe keeps the decision, its reason codes, and its model version in one tamper-evident record — the notice and the examiner evidence come from the same place.
Step 1: An immutable underwriting decision record
CREATE IMMUTABLE TABLE uw_decisions (
application_id TEXT,
model_version TEXT,
score DOUBLE,
decision TEXT, -- approve / decline / refer
reason_codes TEXT, -- ECOA / Reg B top contributing factors
decided_by TEXT -- model proposal + human approver
) WITH (ENCRYPTION = 'AES256GCM');
Omit
WITH (ENCRYPTION = 'AES256GCM')if you have not setAIDB_IMMUTABLE_MASTER_KEY; the table is still append-only and hash-chained (tamper-evident) without it.
Step 2: Record a decision with its reason codes
INSERT INTO uw_decisions VALUES (
'APP-20260728-3391',
'uw_risk_v4',
0.71,
'decline',
'debt-to-income ratio; recent delinquency count; thin credit file',
'model uw_risk_v4 + K. Osei, underwriter'
);
Step 3: Prove the record was never altered
VERIFY TABLE uw_decisions;
is_valid | message
---------+----------------------------------------------------------------
true | chain verified: 1 record(s) ... per-record lineage anchored
| (tamper-evident); ...
Why it matters
The adverse-action notice is generated from the same record the examiner reviews, with the model version attached — no reverse-engineering, no drift between the notice and the audit. Test the portfolio for fairness with disparate-impact testing, and see the agent form in loan-underwriting decision traceability.