Article 9Upcoming 2 Dec 2027 · deferred

RiskForge

Version v2.0.0

Risk assessment CLI across 8 risk dimensions, with 37 questions drawn from EU AI Act Article 9 requirements and Annex III pattern matching. You run the assessment in a terminal, or from a YAML answers file (`--answers`) in scripts and CI. It exports a Risk Management File as JSON for your Annex IV technical documentation pack, with Markdown and PDF as other formats, and can sign the JSON with an Ed25519 key. It is not a substitute for notified-body conformity assessment.

Install

bashpip install riskforge==2.0.0

Needs Python 3.11 or newer.

Apache 2.0 · zero telemetry · source Regulation (EU) 2024/1689 (Article 9).

Verified run

RiskForge, tested on a hypothetical credit-scoring system

A hypothetical credit-scoring system, described from the documentation of the UCI German Credit dataset and assessed by a tester who worked only from public documentation and never read the code or the data files. Before any run, the tester listed what an Article 9 risk management file should contain, from the Article text alone. Sources: UCI Statlog (German Credit Data), CC BY 4.0; Regulation (EU) 2024/1689, Article 9.

Two full runs of the CLI from the same inputs

Tested
Version 2.0.0, 4 Nov 2026
Checks passed on the re-run
22 of 22
One-byte tampering detected
4 of 4 places
Article 9 contents present
9 of 22
Article 9 contents partial or missing
8 partial, 5 missing
Defects found
12 (1 high, 6 medium)

Known limits

  • The checks show the file is repeatable and tamper-evident: the same register always gives the same content digest, and one changed byte in the file's content or its signature fails riskforge verify --file with the trusted key, and one changed byte in a register file or the audit log fails riskforge verify on the project. The project check alone cannot see an audit log rebuilt with its head file rewritten to match; only a receipt kept apart from the project, checked with --receipt, catches that. Without --trusted-key, a file check shows only that the file is consistent with itself, since anyone can rebuild its signatures. The digest leaves out the file id, the export time and the provenance blocks, so a change there is not caught. They do not prove the risks are the right ones for a system. That stays the provider's judgement.
  • The tester's own run passed 19 of 21 checks. Its two failures were gate G3, which the replay then counted as failed checks. The replay now records gate results instead, and a re-run on the same inputs passed all 22.
  • In the tester's reading of Article 9, written before the run, five things a file should show have no place in it: reasonably foreseeable misuse, post-market monitoring data, whether a measure works by design or by information, the overall residual risk, and when testing was done. Another reader might split or merge those items differently.
  • Gate G3 failed on the tester's decisions: 14 of 28 risks stay above the risk appetite, because most controls were only planned. The run exported anyway, which is the case defect D1 below is about.
  • One tester wrote the inputs, partly on stated assumptions about the lender. Another tester would score some risks differently.

Defects the test found

  • D1, high: an export forced past a failing gate is not recorded in the signed JSON or the Markdown. Only the PDF says so, and the digest it prints does not cover that statement.
  • D2: a 12-month review date comes out 360 days after the assessment.
  • D3: most risk titles are the question text, cut at 120 characters, so a score does not say what it is for.
  • D4: cross-reference groups carry the NIST AI RMF and ISO/IEC 42001 references of one item only.
  • D5: test requirements are templates per dimension; a text-generation metric is set for a credit-scoring model.
  • D6: an unknown answer with no hint is scored 3 x 3 without saying so.
  • D7: init cannot record intended users, inputs, outputs or deployment context, yet the metadata gate passes.
  • Five low-severity defects are listed in the report: two questions with no article reference, the PDF layout, a help text, audit actor names and a wrong question id in a README example.

The recorded run

riskforge, terminal transcript
$ verification/run.sh --no-docker
PASS keygen: run-keys/logs/01-keygen.txt
PASS flow.a: 30 commands
PASS validate.a: gates failing: G3 (a result; exported with --force)
PASS digest.recomputed.a: file 72956cb3dcc9..., riskforge.api.content_digest 72956cb3dcc9...
PASS digest.formats.a: JSON field 72956cb3dcc9...; printed {'json': '72956cb3dcc9...', 'markdown': '72956cb3dcc9...', 'pdf': '72956cb3dcc9...'}; in Markdown text: True
PASS verify.api.a: verified with the trusted key
PASS export.markdown.a: 171 lines
PASS export.pdf.a: 22 pages, 177998 bytes
PASS flow.b: 30 commands
PASS validate.b: gates failing: G3 (a result; exported with --force)
...
PASS repeat.same_register: first export 72956cb3dcc9..., second export 72956cb3dcc9...
PASS tamper.rmf_content: byte 1078 changed: verify --file exit 2 (expected 2); restored: exit 0 (expected 0)
PASS tamper.rmf_signature: byte 79310 changed: verify --file exit 2 (expected 2); restored: exit 0 (expected 0)
PASS tamper.register: register.yaml byte 812 changed: verify exit 2 (expected 2); restored: exit 0 (expected 0)
PASS tamper.audit_log: audit.jsonl byte 18 changed: verify exit 2 (expected 2); restored: exit 0 (expected 0)
PASS repeat.two_runs: same RMF after ids and timestamps are replaced by placeholders
PASS repeat.gates: run a {'G1': 'PASS', 'G2': 'PASS', 'G3': 'FAIL', 'G4': 'PASS', ...}, run b {'G1': 'PASS', 'G2': 'PASS', 'G3': 'FAIL', 'G4': 'PASS', ...}
All 22 checks passed.

riskforge 2.0.0, captured 2026-11-04, on the German Credit inputs of the independent test (verification/ in aiexponent/riskforge). Trimmed for length; replay paced for display.

The checks and the exported file

The checks table is the results.json of the re-run on the tester's inputs. The tester's own results.json, with 19 of 21 under the earlier rule, is in the repository. The JSON file is the export from the tester's own run of riskforge 2.0.0, as committed in verification/results/rmf.json, signed with a key made for the test. Its digest differs from the one in the recording above, because every run makes new ids and timestamps; with those replaced, its content matches the re-run's. The signature covers the content digest, which leaves out the file id, the export time and the provenance blocks, so it shows only that the signed content is unchanged since that key signed it.

Results of the 22 checks

JSON tab: the signed Risk Management File from the tester's own run, as committed in the riskforge repository, with its chain proof and receipt

RiskForge verification results from a re-run on the tester's inputs: 22 of 22 checks passed across two runs, including one-byte tampering detected in the file content, its signature, a register file and the audit log, and the same digest across JSON, Markdown and PDF.

RiskForge records a provider’s own risk management decisions. A file that passes these checks is a repeatable, tamper-evident record, not a legal opinion, a conformity assessment or a certification.

Why RiskForge exists

Risk management system

Article 9 applies from 2 Dec 2027 for high-risk AI systems listed in Annex III, and from 2 Aug 2028 for those covered by Annex I. Regulation (EU) 2026/1744, the Digital Omnibus on AI (in force since 27 July 2026), deferred those dates from 2 August 2026 and 2 August 2027. What the article requires is a documented, lifecycle-long risk management system, not a one-time assessment. Failure to maintain it routes through the Article 16 provider obligations and is sanctionable up to €15M or 3% of global annual turnover under Article 99(4). The operational consequence: risk management must produce versioned, reviewable artefacts mapped to identified hazards, with testing evidence sufficient to defend the residual-risk judgement.