Skip to content

feat(compliance): the EU AI Act and ISO/IEC 42001, mapped clause by clause - #78

Merged
ExposureGuard merged 3 commits into
mainfrom
feat/framework-mappings
Oct 4, 2026
Merged

ExposureGuard merged 3 commits into
mainfrom
feat/framework-mappings

Conversation

@ExposureGuard

Copy link
Copy Markdown
Owner

Stacked on #77 (the register adds the agent_register section this maps). Merge that first, then retarget to main.

The evidence pack has mapped to SOC 2 since it existed. That is one framework, and the buyers this product is for get asked about more than one. This adds the two that matter, mapped at the clause level with four rules enforced by tests rather than intentions:

  1. "Contributes to", never "satisfies". Every clause names what the evidence does and what it does not cover. A clause with a contribution and no gap reads as a claim that the evidence closes it — test_every_regulatory_clause_states_what_it_does_not_cover fails the build if one is missing.
  2. Who the duty falls on is recorded. Article 12 is largely the provider's obligation (build the system so it can log); Article 26 is the deployer's (keep the logs, monitor the system). Haldir's customer is usually the deployer. Collapsing the two tells someone they have met an obligation belonging to whoever built the model — there's a test for that split.
  3. High-risk is a precondition, stated as one. Arts 12/26 attach to systems classified high-risk under Annex III. Haldir cannot classify a system, and the mapping says so instead of implying otherwise.
  4. Only clauses that can be cited are cited. A.6.2.x is miscatalogued by more than one published catalogue; the mapping uses the numbers confirmed across sources and leaves the rest out. A wrong control number in an evidence pack is worse than a missing one.

Shape

haldir_frameworks.py The mapping — EU AI Act (Art. 12(1), 12(2)–(3), 26(5), 26(6)'s six-month floor, 14, 15) and ISO/IEC 42001 (A.6.2.8 event logs, A.6.2.6 operation & monitoring, A.6.2.4 V&V, A.9.4 intended use, A.9.2–A.9.3 responsible use)
framework_report() Renders it for the pack, and groups the readiness score's existing seven checks by the clauses they speak to — no second scoring engine, no second set of signals
The pack Gains a frameworks section — inside the signed digest, and static, so it cannot drift on its own
Both rendered forms The mappings render in markdown and HTML, so the auditor-readable document carries the gaps next to the contributions (signature moves to 10)

Unmeasured clauses are reported, not scored. Most of the Act and most of Annex A is organisational; a clause nothing measures returns measured: false with no state. A number invented for it would be exactly the overclaim this module exists to prevent.

Verification

  • 1116 tests, flake8 clean, mypy clean over 31 files
  • tests/test_frameworks.py also checks the direction that bit while writing it: every section a clause references is a real pack section (it caught proxy and alerting — the latter is the score key for the webhooks section, which is exactly how that slip happens)
  • Both renderers inspected; digest verified stable with the new section inside it

Sources for the clause citations: EU AI Act Art. 12 logging mandate, Art. 26(6) deployer retention, ISO 42001 Annex A control catalogue.

🤖 Generated with Claude Code

…lause

The evidence pack has mapped to SOC 2 since it existed. That is one framework,
and the buyers this product is for are asked about more than one — so the
document now speaks to the three it can defend, with four rules enforced by
tests rather than by good intentions:

1. **"Contributes to", never "satisfies".** Every clause names what the
   evidence does *and what it does not cover*. A clause with a contribution
   and no gap reads as a claim that the evidence closes it; a test fails the
   build if one is missing.
2. **Who the duty falls on is recorded.** Article 12 is largely the
   *provider's* obligation (build the system so it can log); Article 26 is the
   *deployer's* (keep the logs, monitor the system). Haldir's customer is
   usually the deployer, and collapsing the two tells someone they have met an
   obligation belonging to whoever built the model.
3. **High-risk is a precondition, stated as one.** Arts 12 and 26 attach to
   systems classified high-risk under Annex III. Haldir cannot classify a
   system and does not pretend to.
4. **Only clauses that can be cited are cited.** A.6.2.x is miscatalogued by
   more than one vendor; the mapping uses the numbers confirmed across
   published catalogues and leaves the rest out. A wrong control number in an
   evidence pack is worse than a missing one.

`haldir_frameworks.py` holds the mapping; `framework_report()` renders it for
the pack and groups the readiness score's existing seven checks by the clauses
they speak to — no second scoring engine, no second set of signals. A clause
nothing measures is reported `measured: false` rather than scored, because
most of the Act and most of Annex A is organisational and a number invented
for it would be the overclaim the module exists to prevent.

The mappings render in both forms (markdown and HTML), so the auditor-readable
document carries the gaps next to the contributions. The signature section
moves to 10 in both, and the numbering assertions move with it.

Tests: `tests/test_frameworks.py` — clauses reference only real pack sections
(this caught `proxy` and `alerting` while it was being written; `alerting` is
the *score* key for the `webhooks` section), checks map to clauses that exist,
every regulatory clause states its gap, the provider/deployer split survives,
Article 26(6)'s six-month floor stays recorded, unmeasured clauses carry no
state, and pass wins over fail for a clause with two checks.

Verified: 1116 tests, flake8, mypy over 31 files, both rendered forms checked,
digest stable with the new section inside it.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sterling Ivey and others added 2 commits October 4, 2026 12:49
`mypy.ini` again — both sides add modules to one explicit list. Resolved by
union (client_ip, registry, frameworks all present) rather than by picking a
side, and `openapi.json` regenerated.

Co-Authored-By: Claude Code <noreply@anthropic.com>
main moved when #77 landed after my first merge, and GitHub recomputes the
merge against the current base — so the conflict returned. Same union
resolution on `mypy.ini`, spec regenerated, suite run.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@ExposureGuard
ExposureGuard merged commit e7c3531 into main Oct 4, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant