The decision authority that survives a regulator.
Cedar substrate. ADL surface. Per-evaluation transcript chained to a qualified electronic timestamp (D-Trust QTSP + Sigstore Cosign). Customer-portable signed bundle the regulator can replay offline. block wins over everything; flag_for_review paired with auto_approve resolves to auto-approval (Auto-Approval Engine merger) — fixed semantics, every match, every time.
Live today: Cedar substrate evaluation, ADL compile, per-evaluation transcripts (with cedarRequestHash verifier digest + policyVersionHash immutability), SHA-256 chain, Auto-Approval merger. Gated on founder action: D-Trust QTSP qualified-timestamp signing (commercial contract in procurement), Sigstore Cosign keyless OIDC (GitHub Actions setup), pre-built signed standalone replay binary (Phase 3.1). Until each gate fires the corresponding layer runs as a deterministic mock preserving chain integrity. See court-admissibility for the per-gate detail.
{
"trace": "trc_847b3f...",
"action": "flag_for_review",
"matchedPolicy": {
"name": "Hold low-confidence loan denials",
"priority": 1
},
"reason": "Policy \"Hold low-confidence loan denials\" required human review."
}This resolution returns 202 with action: hold_for_review and a reviewId. A block action would return 403 with code: ADJ_BLOCKED_BY_POLICY.Author in ADL. Compile to Cedar. Chain every match.
ADL v0.1 is the typed authoring surface — predicate calls, not a JSON object tree. The compiler lowers ADL to AWS Cedar (CNCF Sandbox, formal semantics), pins it to an immutable PolicyVersion via SHA-256 chain, and writes one PolicyTranscript per match. Resolution order: block always wins; flag_for_review paired with auto_approve resolves to auto-approval; otherwise flag_for_review > notify > standalone auto_approve > approve.
ADL is the customer-authoring surface. Predicates come from a curated registry — each one carries a domain (decimal / string / bool) so the compiler catches type errors before the policy ever runs. The example holds every loan-underwriter trace under 0.7 confidence for human review, scoped to one organizationId and one workspaceId.
name "Hold low-confidence loan denials"
priority 1
enabled true
when agent_equals("loan_underwriter")
and confidence_below(0.7)
then flag_for_reviewThe compiler lowers ADL through an intermediate representation to AWS Cedar. The Cedar text + cedarSchema + canonical hash get written to a new PolicyVersion record, hash-linked to the previous version. Append-only: every version exists forever. A regulator reading a 2030 transcript fetches the 2026 policy text in force at that moment.
// backend/services/policyVersionService.js
const { cedarPolicyText, cedarSchema } =
policyCompiler.compileV1ToCedar(policy);
const canonicalContentHash = sha256(
canonicalJson({ policy, cedarPolicyText, cedarSchema })
);
await PolicyVersion.create({
organizationId, // Cardinal Rule #1 — org-first index
policyId,
canonicalContentHash, // SHA-256 hex, 64 chars
priorVersionHash, // null for genesis, else last version's hash
cedarPolicyText,
cedarSchema,
lifecycleState: 'draft', // 'draft' | 'shadow' | 'canary' | 'live'
algoSuite: 'sha256-jcs-rfc8785',
schemaVersion: 'adjudon-policy-version-v1',
});The engine walks active policies in priority order, keeps the first match per action type, and writes one immutable PolicyTranscript per fired policy — chained via SHA-256, signed by D-Trust QTSP at the daily Merkle anchor. auto_approve (Auto-Approval Engine merger) never overrides a block from a different policy on the same trace. The hard-rule contract holds.
// backend/services/policyEngine.js + policyTranscriptService.js
const fired = [];
for (const p of policies) {
if (!policyMatches(p, trace)) continue;
fired.push(p);
for (const a of p.actions || []) {
if (a.type === 'block') blockPolicy ||= p;
if (a.type === 'flag_for_review') flagPolicy ||= p;
if (a.type === 'auto_approve') autoApprovePolicy ||= p;
if (a.type === 'notify') notifyPolicy ||= p;
}
}
// One transcript per fired policy — append-only, PII-scrubbed at write
for (const policy of fired) {
await policyTranscriptService.emit({
organizationId, policyId: policy._id,
policyVersionHash: policy.versionHash, // pins to immutable PolicyVersion
traceId, decision, firedConditions,
cedarRequestHash: sha256(canonicalJson(cedarRequest)),
algoSuite: 'sha256-jcs-rfc8785',
});
}
if (blockPolicy) return { action: 'block', matchedPolicy: blockPolicy };
if (flagPolicy) return { action: 'flag_for_review', matchedPolicy: flagPolicy };
if (autoApprovePolicy) return { action: 'allow', autoApproved: true, matchedPolicy: autoApprovePolicy };
if (notifyPolicy) return { action: 'allow', matchedPolicy: notifyPolicy, notify: true };
return { action: 'allow' };The trace controller reads the engine's action and returns the matching HTTP code. block returns 403 with code ADJ_BLOCKED_BY_POLICY and writes a blocked trace. flag_for_review returns 202 with action: hold_for_review and creates a Review Queue item. auto_approve bypasses the queue and writes an AutoApprovalDecision. Everything else lands as 201.
// trace ingestion — backend/controllers/traceController.js
const policyResult = await evaluatePolicies(trace, organizationId, workspaceId);
if (policyResult.action === 'block') {
finalTraceData.status = 'blocked';
// hash-chain append, alerts, webhook 'trace.blocked' — all fired before response
return fail(res, 403, policyResult.reason, 'ADJ_BLOCKED_BY_POLICY',
{ allowed: false, action: 'block', traceId, id });
}
if (policyResult.action === 'flag_for_review') {
finalTraceData.status = 'flagged';
// ReviewItem created, webhook 'trace.hold_for_review' fired
return ok(res, { traceId, id, reviewId },
{ allowed: false, action: 'hold_for_review', message }, 202);
}
if (policyResult.autoApproved) {
finalTraceData.status = 'success';
// AutoApprovalDecision written, ReviewItem skipped (hard-rule preserved upstream)
return ok(res, { traceId, id, autoApproved: true }, {}, 201);
}
// approve / notify-only / no-match
return ok(res, { message: 'Decision Trace successfully recorded', traceId, id }, {}, 201);Three things the engine refuses.
Three engineering choices distinguish the Policy Engine from a probabilistic AI-firewall: ADL predicates are typed and compile to AWS Cedar's formal semantics, conflicts resolve by published action ladder, and user-supplied regex patterns compile through Google's RE2 engine — full regex syntax, linear-time matching, no catastrophic backtracking. None of the three is configurable; all three are mechanical.
Ambiguous semantics
ADL predicates are typed at the registry level — confidence_below(decimal), status_equals(string), human_override_enabled(bool). The compiler rejects arity + type mismatches before a policy reaches the substrate. AWS Cedar (CNCF Sandbox, peer-reviewed formal semantics) evaluates the compiled form. No Rego inference, no yaml-with-jinja, no customer-defined eval order.
Surprise outcomes
Two policies match the same trace? block always wins. If flag_for_review and auto_approve both fire, the trace resolves as auto-approved (Auto-Approval Engine merger — LD-9). Otherwise flag_for_review > notify > standalone auto_approve > approve. Order is burnt into the evaluator. No customer-tunable priority math. No probability mixing.
ReDoS payloads
User-supplied regex patterns compile through Google's RE2 engine. A pattern like (a+)+b evaluates in linear time on any input — RE2 has no backtracking-based execution path, so adversarial patterns cannot trigger exponential blow-up. Operators get full regex syntax (\d, {n,m}, character classes, anchors); the engine just refuses the algorithmic shape that makes ReDoS possible.
Four articles, one artefact each.
Each regulator-article maps to one concrete Policy Engine artefact — not a marketing claim, a record class. The auditor reads them directly. PolicyTranscript for Art. 14 oversight; the 3-role approval queue for BaFin MaRisk functional separation; the portable bundle for DORA Art. 30 exit; the insurance feed for AI underwriting.
| Article | Obligation | Policy Engine artefact |
|---|---|---|
| Art. 14 | Human oversight on high-risk AI — natural persons must be able to monitor, intervene, and override. | One immutable PolicyTranscript per fired policy — pinned to the policyVersionHash, fields scrubbed at write per Cardinal Rule #4, chained via SHA-256 (RFC 8785 JCS), surfaced in the Review Queue with reviewer + enforcer identities. |
| BaFin MaRisk AT 4.3.1 | Functional separation in IT-supported processes — author, reviewer, and approver must be distinct natural persons. | The approval workflow rejects with THREE_ROLE_VIOLATION when submittedBy, reviewedBy, or enforcedBy overlap. Break-glass requires a typed-confirm phrase + 50-char justification, all recorded on the audit chain. |
| DORA Art. 30 | ICT third-party exit plan — financial entities must be able to leave a critical provider without losing audit evidence. | The portable bundle export wraps every PolicyVersion, PolicyTranscript, review outcome, and effectiveness snapshot — dual-signed (D-Trust QTSP + Sigstore Cosign) and verifiable offline via the bundled adjudon-replay binary. See portable-bundle. |
| §371a Abs. 3 ZPO | Electronic records with a qualified electronic timestamp carry statutory evidentiary presumption in German civil court. | Daily Merkle anchor over every PolicyTranscript chained to a D-Trust qSeal (eIDAS Art. 35 qualified electronic seal). See court-admissibility. |
Two policies match. Block beats flag.
You've seen four code blocks, three refusals, four article-anchors. Next: write a policy in ADL, watch the immutable PolicyVersion chain grow, and read which trace got which transcript. The first call lands at the engineer who wrote the compiler — not at an SDR.