ADR-0072: EU AI Act conformity record (system-level, data-driven, honest)
Status: Accepted Date: 2026-06-26 Supersedes: none Superseded by: none Deciders: Adrian (Soft4U), Claude Opus 4.8
Decision context:
- Latency: off the hot path entirely. The record is assembled on demand (an officer/admin GET), from in-process configuration (the model-tier dict + the already-cached prompt registry) plus one best-effort tenant-branding SELECT. Rendering is the same WeasyPrint pass the existing compliance documents already use. No per-request, per-case, or workflow cost.
- Dependency surface: zero new packages. Reuses
report_service._build_env(Jinja2 + WeasyPrint),model_tiers(ADR-0029), the prompt registry (ADR-0026), and the existing auth/tenant deps. One new model module, one service, one template, one router. - Debuggability: the AI-component inventory is generated from the live config, so the document cannot silently drift from what the system runs; prompt provenance carries a SHA-256 source hash. Each obligation cites the concrete ADR/mechanism behind it.
- Reversibility: purely additive — one read-only endpoint, no schema change, no behaviour change to any existing path. Branch revert is clean.
- Blast radius: a new officer/admin read-only router (
/api/conformity/ai-act.pdf). Nothing else changes; the record is system-level (not per-case) and touches no case, workflow, or write path.
Context
The tool is a high-risk AI system under the EU AI Act (Annex III — it informs access to essential private financial services). Before a regulated institution (bank / PSP / fintech) can put it into production, its 2nd-line / procurement function must satisfy itself that the high-risk provider obligations (Art. 11 technical documentation, Art. 12 record-keeping, Art. 13 transparency, Art. 14 human oversight, Art. 15 accuracy & robustness) are met. This is a sales gate (roadmap gap J), not a nice-to-have.
The mechanisms that discharge those obligations already exist across the codebase — evidence bundles
(ADR-0021), immutable audit_events (ADR-0064), model tiers (ADR-0029), the prompt registry (ADR-0026),
maker-checker (ADR-0070), and the fail-closed "not assessed" contract (ADR-0067). What was missing was a
single, defensible, consolidating document that maps them onto the Articles — without rebuilding any
of them, and without over-claiming.
The trap to avoid is the same one M1 closed for our compliance outputs, now turned on our own
conformity claim: a glossy "fully compliant" marketing PDF that papers over the parts that are only
partially met or that depend on the deploying organisation. The calibration-review checklist
(docs/calibration-review-checklist.md) — presence ≠ evidence, honest empty-states — applies to us.
Decision
Add a system-level, data-driven, honestly-statused EU AI Act conformity record.
1. Data-driven assembly (app/services/ai_act_conformity_service.py,
app/models/ai_act_conformity.py). The record is built from the live configuration, not hand-written:
- Art. 11 component inventory is generated by iterating
model_tiers.AGENT_TIERS: for each agent we read its tier + resolved model (get_agent_tier/get_model_for_agent) and its prompt provenance from the registry (PromptRegistry.get()→ template name, version, and a 12-char SHA-256 of the source). A tiny_PROMPT_ALIASESmap covers the two agents whose prompt template is named differently from the agent key; everything else resolves 1:1. If the registry or a template is unavailable, provenance degrades to—honestly rather than fabricating a value. - The five obligations (Art. 11-15) are curated technical documentation, each carrying an honest
status drawn from a fixed vocabulary
("satisfied", "partial", "gap"), a plain summary, the concrete controls (how_met), the evidence citations (the real ADRs/mechanisms), and any residual gap.
2. Honest status — the M1 rule applied to ourselves. Art. 11-14 are satisfied and cite the
mechanisms that make them so. Art. 15 is partial, and says why: quality scoring, the fail-closed
contract and the standing calibration checklist exist, but continuous post-market accuracy monitoring /
back-testing against realised outcomes is not yet automated (roadmap M4/M7). A separate "known gaps /
organisational dependencies" section surfaces the SaaS-readiness items that the software alone cannot
discharge — SSO/SCIM, ISO 27001/42001/27701 certification, EU data-residency guarantees,
post-market monitoring, and the vendor-gated live data feeds — each labelled partial/gap with an
owner (provider / deployer / vendor). The record states the holes; it never reads as a blanket "compliant".
3. Rendering + endpoint. A new ai_act_conformity.html template extends the shared
compliance_doc_base.html chrome (so the record matches the premium dossier look) and renders through
the existing report_service._build_env. GET /api/conformity/ai-act.pdf (officer/admin JWT,
tenant-aware branding) returns the PDF. The endpoint is read-only and system-level: it loads no case and
writes nothing.
Consequences
Positive
- A deployer's procurement/2nd-line gets a single, defensible Art. 11-15 conformity document that cites concrete ADRs and DB-enforced mechanisms (ADR-0021/0064/0029/0026/0070/0067) — evidence, not marketing.
- The component inventory is generated from the running config, so it cannot drift from the deployed system, and prompt provenance is hash-pinned.
- The document is honest: a
partialobligation and the SSO/SCIM/ISO/residency dependencies are stated plainly, which is exactly what a sophisticated 2nd-line trusts (and what a glossy claim loses).
Negative
- The obligation narrative (purpose text,
how_metbullets) is authored content that must be kept in step as controls evolve; only the volatile provenance (models, prompt versions/hashes) is automatic. - It is the provider record; the deployer still owes its Art. 26 deployer obligations (oversight roster, input governance, end-user transparency) — called out in the document, not silently assumed.
Neutral
- System-level, not per-case: it reuses the case-document chrome but the masthead "subject" is the AI
system/provider, and
workflow_id/case_idstay empty.
Alternatives Considered
Alternative 1: a static, hand-maintained conformity PDF / marketing one-pager
Rejected — it drifts from the running system the moment a model tier or prompt changes, and it invites the
exact over-claiming M1 exists to prevent. Generating the inventory from model_tiers + the prompt
registry keeps the document true by construction.
Alternative 2: mark every obligation "satisfied"
Rejected — dishonest and self-defeating. A high-risk-AI 2nd-line reviewer will probe accuracy monitoring,
SSO/SCIM, ISO and data residency directly; a blanket-green document fails on first contact and burns
credibility. Honest partial/gap with a stated owner is more defensible and is the calibration-checklist
standard ("honest empty-states", "presence ≠ evidence") applied to our own conformity.
Alternative 3: per-case conformity inside each KYB report
Rejected — Art. 11-15 are properties of the system, not of an individual case. Embedding them per case would repeat (and risk inconsistency in) a system-level statement on every report. The case dossier already carries the per-case Art. 12 trail (evidence bundles + source appendix); this record is the system-level complement.