{#- A missing value keeps its row, so an auditor sees which fields are not filled in. -#} {% macro or_na(value) %}{% if value %}{{ value }}{% else %}Not specified{% endif %}{% endmacro %}

EU AI Act Article 9
Risk Management File

{{ system.name }}

Version {{ system.version }}

{{ register.items | length }} risk items
{{ register.open_items() | length }} open above appetite
{{ register.knowledge_gaps() | length }} knowledge gaps
Provider{{ system.provider_name }}
Assessor{{ register.assessor_name }}, {{ register.assessor_role }}
Assessment date{{ register.assessment_date.strftime('%d %B %Y') }}
Review date{{ register.review_date.strftime('%d %B %Y') if register.review_date else 'Not set' }}
Annex III category{{ category_label }}
Readiness gates{{ status_line }}

Content digest (SHA-256)
{{ rmf.sha256_hash }}

1. Executive summary

Purpose: {{ system.purpose }}

Annex III category: {{ category_label }}

Self-classification documented: {{ 'Yes' if system.annex_iii_self_classification_documented else 'No, see gate G2' }}

Risk items: {{ register.items | length }} in total. Before treatment, {{ register.items_above_appetite() | length }} scored above the risk appetite threshold of {{ register.risk_appetite_threshold }}; after treatment, {{ register.open_items() | length }} remain open.

Knowledge gaps: {{ register.knowledge_gaps() | length }}

System description

FieldValue
System ID{{ system.id }}
Name{{ system.name }}
Version{{ system.version }}
Provider{{ system.provider_name }}
Provider contact{{ or_na(system.provider_contact) }}
Intended users{{ or_na(system.intended_users | join(', ')) }}
Inputs{{ or_na(system.inputs | join(', ')) }}
Outputs{{ or_na(system.outputs | join(', ')) }}
Deployment context{{ or_na(system.deployment_context) }}

{{ rmf.disclosure }}

2. Methodology

The assessment uses the RiskForge question bank, version {{ register.question_bank_version }}, across {{ dimensions | length }} risk dimensions: {{ dimensions | map('replace', '_', ' ') | join(', ') }}.

Each risk is scored for likelihood and severity on a five-point scale. The risk score is likelihood times severity, from 1 to 25. Bands: low 1 to 4, medium 5 to 9, high 10 to 16, critical 17 to 25. Likelihood and severity are scored again after mitigation to give the residual score.

A risk is open while its residual score is above the risk appetite threshold of {{ register.risk_appetite_threshold }} and it has not been accepted with a written rationale. Gate G3 fails while any risk is open.

Risk patterns applied: {{ patterns_used | join(', ') if patterns_used else 'none' }}.

Readiness gates G1 to G8 are the checks riskforge validate runs. A FAIL gate blocks export unless the export is forced. Results at export:

{% for gate in gates %}{{ gate.gate_id }} {{ gate.status.value }}{% if not loop.last %}, {% endif %}{% endfor %}

3. Risk register

Risk appetite threshold: {{ register.risk_appetite_threshold }} (residual scores above it need mitigation or acceptance).

{% for dim in dimensions %} {% set dim_items = items | selectattr('dimension.value', 'equalto', dim) | list %}

3.{{ loop.index }} {{ dim.replace('_', ' ').capitalize() }}

{% if dim_items %} {% for item in dim_items %}
{{ item.title | display_title }} {{ item.risk_band.upper() }} ({{ item.risk_score }})

{{ item.description }}

Likelihood{{ item.likelihood.name }} ({{ item.likelihood.value }}) Severity{{ item.severity.name }} ({{ item.severity.value }})
Residual likelihood{{ item.residual_likelihood.name }} ({{ item.residual_likelihood.value }}) Residual severity{{ item.residual_severity.name }} ({{ item.residual_severity.value }})
Source{{ item.source }} Regulatory status{{ item.regulatory_status }}
{% if item.article_refs %}

Article refs: {{ item.article_refs | join(', ') }}

{% endif %} {% set nist = item.nist_rmf_refs or ([item.nist_rmf_ref] if item.nist_rmf_ref else []) %} {% set iso = item.iso42001_refs or ([item.iso42001_ref] if item.iso42001_ref else []) %} {% if nist or iso %}

{% if nist %}NIST AI RMF: {{ nist | join(', ') }}{% endif %}{% if nist and iso %} | {% endif %}{% if iso %}ISO/IEC 42001: {{ iso | join(', ') }}{% endif %}

{% endif %} {% if item.mitigations %}
Mitigations:
    {% for m in item.mitigations %}
  • {{ m.control_type.title() }} [{{ m.status }}]: {{ m.description }}{% if m.is_vague %} (flagged: generic language){% endif %}{% if m.recorded_by %}Recorded by: {{ m.recorded_by }}{% endif %}
  • {% endfor %}
{% endif %} {% if item.accepted %}
Accepted: {{ item.acceptance_rationale }}
Accepted by: {{ item.accepted_by or 'not recorded' }}
{% endif %} {% if item.knowledge_gap %}
Knowledge gap: its test requirements are listed in section 5.
{% endif %}
{% endfor %} {% else %}

No risk items recorded for this dimension.

{% endif %} {% endfor %}

4. Residual risk summary

{% for item in items | sort(attribute='title') | sort(attribute='residual_risk_score', reverse=True) %} {% endfor %}
RiskInherentResidualMitigationsDecision and rationale
{{ item.title | display_title }} {{ item.risk_score }} ({{ item.risk_band }}) {{ item.residual_risk_score }} ({{ item.residual_risk_band }}) {{ item.mitigations | length }} {% if item.accepted %}Accepted: {{ item.acceptance_rationale }}{% elif item.residual_risk_score > register.risk_appetite_threshold %}Open{% else %}Within appetite{% endif %}

5. Article 9(6) to 9(8) test requirements

{% if rmf.test_requirements %} {% for t in rmf.test_requirements %} {% endfor %}
Risk itemMetricThresholdArticle
{{ t.risk_item_id | string | truncate(8, True, '') }} {{ t.metric_type }}{{ t.threshold_range }}{{ t.article_ref }}
{% else %}

No test requirements saved. Run riskforge tests generate to derive them from the risk items.

{% endif %}

6. Audit metadata

Every change to this register is recorded in a SHA-256 hash-chained audit trail stored in .riskforge/audit.jsonl. The JSON, PDF and Markdown exports of one register state show the same content digest. The rmf.exported entry of this export, recorded after the file was written, holds the content digest and the SHA-256 of this file.

Content digest (SHA-256){{ rmf.sha256_hash }}
RMF schema version{{ rmf.rmf_schema_version }}
Question bank version{{ register.question_bank_version }}
Generated byRiskForge {{ riskforge_version }}
Generated at{{ rmf.generated_at.strftime('%d %B %Y %H:%M UTC') }}
Chain proof{% if rmf.chain_proof %}{{ rmf.chain_proof.entries | length }} entries of the {{ rmf.chain_proof.scope }} audit chain, seq {{ rmf.chain_proof.entries[0].seq }} to {{ rmf.chain_proof.entries[-1].seq }}{% else %}Not included{% endif %}
Receipt{% if rmf.receipt %}seq {{ rmf.receipt.seq }}, entry hash {{ rmf.receipt.entry_hash }}{% if rmf.receipt.key_id %}, signed by {{ rmf.receipt.key_id }}{% endif %}{% else %}Not included{% endif %}
Signatures{% if rmf.signatures %}{% for s in rmf.signatures %}{{ s.key_id }} ({{ s.algorithm }}){% if not loop.last %}, {% endif %}{% endfor %}{% else %}Unsigned{% endif %}
Sign-offs{% if rmf.signoffs %}{% for s in rmf.signoffs %}{{ s.approver }}: {{ s.decision }} (version {{ s.version_number }}){% if not loop.last %}; {% endif %}{% endfor %}{% else %}None recorded{% endif %}

Change log

{% if change_log %}

Entries of the chain proof for this system and for the project.

{% for e in change_log %} {% endfor %}
SeqTime (UTC)EventActor
{{ e.seq }}{{ e.timestamp.strftime('%Y-%m-%d %H:%M') }}{{ e.event }}{{ e.actor.identity }}
{% else %}

This export carries no chain proof.

{% endif %}

Verify integrity at any time: riskforge verify. Check the JSON export of this file with riskforge verify --file.