Article 9 of the EU AI Act requires providers of high-risk AI systems to run a risk management system. This applies to providers anywhere that place such systems on the EU market. Annex III systems (the Act's list of high-risk uses, such as credit scoring and hiring tools) must meet it from 2 December 2027. Annex I systems (AI inside regulated products) must meet it from 2 August 2028. A provider who breaks it faces a fine of up to EUR 15,000,000 or 3% of total worldwide annual turnover, whichever is higher. SMEs pay the lower of the two. First, list which of your systems fall under Annex III or Annex I.
Article 9 describes a process, so no file can prove it
A repository cannot prove that a risk management system exists, because Article 9 describes work that people do over time. Article 9(1) says the system must be established, implemented, documented and maintained. Article 9(2) calls it a continuous iterative process, planned and run throughout the lifecycle, with regular systematic review and updating.
Article 9(2) names four steps. Identify the known and reasonably foreseeable risks to health, safety or fundamental rights. Estimate and evaluate risks, including under reasonably foreseeable misuse. Evaluate further risks using post-market monitoring data. Adopt targeted risk management measures.
Article 9(5) adds that the residual risk must be judged acceptable. Articles 9(6) to 9(8) require testing before market placement, against metrics and probabilistic thresholds defined in advance. Article 9(9) asks providers to consider adverse impact on people under 18 and other vulnerable groups.
The Act prescribes no file format or tool. It does make the documentation concrete. Annex IV point 5 requires a detailed description of the risk management system in the technical documentation. Article 17(1)(g) lists the system as an element of the quality management system.
The practical question is narrower than "are we compliant". Which of these steps leave a trace in the repository that a reviewer can inspect?
Four traces a repository can carry
These four traces are my engineering judgement, not a list from the Act. Each one ties to a specific paragraph.
1. A risk register in version control. Keep one entry per hazard, with likelihood, severity, measure, residual risk and the model version it was assessed against. This covers Article 9(2)(a) and (b). The commit history then shows the regular review and updating that Article 9(2) asks for. Feed post-market monitoring findings into the same register, which is the loop in Article 9(2)(c).
\`yaml
risk_register/R-014.yaml (illustrative)
id: R-014
hazard: Applicants with short employment history score lower for reasons unrelated to job fit
affects: fundamental rights (non-discrimination)
foreseeable_misuse: Recruiter uses the score as the only screening criterion
likelihood: medium
severity: high
measure: Score is advisory; recruiter records a reason when confirming or overriding
residual_risk: acceptable # Article 9(5), signed off by the risk owner
assessed_model_version: 2.3.0
reviewed_by: risk-owner
reviewed_on: 2027-03-02
`\
2. Thresholds committed before the test run. Article 9(8) requires testing against metrics and thresholds that were defined in advance. A thresholds file whose commit predates the test results is a timestamped record that the thresholds came first. Squashing or rewriting history destroys that record, so protect the branch.
3. Tests that name the risk they cover. A reviewer can then follow R-014 from hazard to measure to test. Add one test for each foreseeable misuse listed in the register.
\python
@pytest.mark.risk("R-014")
def test_short_history_not_penalised(model, thresholds):
gap = score_gap(model, group="short_history")
assert gap <= thresholds["R-014"]["max_score_gap"]
\\
4. A release gate that reads the register. The gate fails the build when a risk has no review for the model version being released, or no residual-risk sign-off. This makes the iteration in Article 9(2) a condition of shipping. As in the logging post, the gate has to block the release. A warning leaves nothing that anyone must act on.
\python
def gate(register, version):
open_items = [r.id for r in register
if r.assessed_model_version != version or not r.signed_off]
if open_items:
raise SystemExit(f"Risk review missing for {version}: {open_items}")
\\
None of this proves the review was good. It proves a review was recorded, in an order and at a time that someone can check. Whether the review was adequate is a judgement for the provider, and later for whoever assesses the system.
What Scanara checks for Article 9, and what it does not
Scanara's code scan checks Article 9 narrowly. Take a file that calls a model, such as .predict() or .generate(). The scan reports one Article 9 finding for that file. It stays silent only if the file also calls a function with a recognised name, such as log_hazard_review or record_lifecycle_risk_review. That is a presence check on a function name. It cannot tell whether the call reflects a real review. A clear result is a prompt for a person to look, not proof that the process exists.
A second set of keyword rules looks for risk management vocabulary, such as predefined metrics or vulnerable groups, in identifiers. When none matches, the scan reports that it found no signal for that paragraph. Four paragraphs have no code check at all: 9(3), 9(4), 9(7) and 9(10). They set scope, interpretation or options rather than a duty.
The process itself, meaning who reviewed which risk and why, is covered by a guided assessment. The customer answers the questions and Scanara stores the answers as evidence. Passing a scan does not mean an organisation is compliant.
Try it
Free tier, self-serve, no sales call: https://scanara.io/en/
The Act's own text is on EUR-Lex: https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32024R1689 (Article 9 is in Chapter III, Section 2). Our reference page for the article: https://scanara.io/en/learn/eu-ai-act/article-9-risk-management/
Where I might be wrong
I would like pushback on three points.
First, the Act does not say what format a reviewer accepts as evidence of the Article 9 process. Does a versioned register plus tests count, or will assessors only look at the Annex IV description? I do not know.
Second, "thresholds committed before results" depends on honest history. Is a commit timestamp strong enough, or does it need signed commits or an external log?
Third, a check that looks for a named helper function may teach teams to add a stub call to clear it. Is that signal worth having at all, or does it do more harm than good? I have no data on this and would like to hear from people who run similar gates.