ADR-0079: Risk & Risk-Control Items (Traceability Model — Stage B)¶
- Status: accepted (Nicolas 2026-07-18 — decisions confirmed at the foot of this ADR).
- Date: 2026-07-18
- Deciders: Nicolas Burri, design by Claude.
- Builds on: ADR-0078 (generalized trace items — this is purely register two new types), ADR-0077 (the requirement item, the template), the traceability model (traceability-model.md §2/§3, view 2).
- Standard: ISO 14971 (risk management for medical devices) is the domain shape below.
Context¶
The traceability model's biggest audited gap is the risk thread: hazard → hazardous situation
→ harm, with an estimated risk that risk controls reduce to a residual risk, and each control
implemented and verified. Nicolas's scope line (2026-07-18): risk and risk-control ITEMS + a
risk-score RECORDING are in the tool; the scoring scale and how the level is calculated are
CONTENT (a controlled document — the Risk Management Plan / risk policy). The tool records
"S3 × P2 = High, per
ADR-0078 made this additive: a new item type = register a TraceItemType (tiptap node + fence +
fixed attribute schema) and its frontend twin; the registry/link/suspect/coverage engines do not
change (proven by TraceItemGeneralizationTest). Stage B registers two types.
Decisions (each marked with the recommendation for review)¶
D-A — Two types: risk and risk-control [recommend: yes]¶
A risk is estimated and evaluated; a control is a measure that mitigates one-or-more risks and is itself implemented and verified. The relationship is many-to-many (one risk → several controls; one control → several risks) and a control carries its own outward edges — neither fits inside the other's attributes. So two sibling types, connected by links, not one type with a sub-role.
D-B — The scheme reference: link to the Risk Management Plan, don't bind it [recommend: doc-relation for Stage B]¶
The scores are recorded tokens; their meaning is the scope's Risk Management Plan. Two ways to tie a risk to that plan:
- (rec) Document relation (minimal). The Risk Analysis document that contains the risk items
already
references/derives-fromthe Risk Management Plan via existing document relations (ADR-0021). The scheme linkage exists at document granularity with zero new mechanism; the risk items just carry tokens. - (alt) Scope-level scheme binding (enforced). Declare the scheme document once per scope (ADR-0073 config surface); every risk inherits it; when the plan's version changes, all risk scores go suspect (re-evaluate) via the ADR-0078 mechanism at the binding level. Stronger and very much on-theme for 14971 ("change your risk policy → re-evaluate your risks"), but more build.
Recommendation: ship the doc-relation form in Stage B; keep the scope-binding + suspect-on- scheme-change as a flagged Stage B+ enhancement if you want the tool to enforce one scheme per scope and drive automatic re-evaluation. This keeps scoring firmly as content and Stage B tight.
D-C — Residual risk: a second score triple on the risk item [recommend: yes]¶
Residual risk is the net effect of a risk's controls on that risk, so it belongs on the risk
item, not on a control and not as a separate "phase" item. The risk item carries two triples —
initial and residual — plus a small acceptability outcome. No phase-discriminated duplicate items.
D-D — Score tokens are free text, not a tool vocabulary [recommend: yes]¶
Risk scales genuinely vary (3×3, 5×5, qualitative) — that is a proven variation point, so per the opinionated principle the severity/probability/level values are short free tokens supplied from the scheme, not a fixed tool enum. The tool records them; it does not validate them against a matrix (that would pull the scheme into the tool, which your line puts in content).
Proposed model¶
risk item — node risk, fence :::risk¶
Mirrors one row of a 14971 risk table. Fixed attributes (all recorded; the tool never computes a level from S and P):
| Attribute | Meaning |
|---|---|
hazard |
the hazard (short) |
hazardousSituation |
the exposure circumstance (short) |
harm |
the potential harm (short) |
initialSeverity / initialP1 / initialP2 / initialRiskLevel |
pre-control estimate (tokens). P1 = probability of the hazardous situation arising; P2 = probability it then leads to harm (ISO 14971 two-factor probability). The tool records P1, P2 and the level; it does not compute P1×P2 or the level. |
residualSeverity / residualP1 / residualP2 / residualRiskLevel |
post-control estimate (tokens) |
residualAcceptability |
evaluation outcome — enum acceptable | not-acceptable (benefit-risk justification goes in the body / a linked document) |
Block body = the descriptive narrative (sequence of events, rationale). The full structured row (hazard/situation/harm structured, both score triples, acceptability) was chosen over the leaner scores-only variant (Nicolas, 2026-07-18, Q-1).
risk-control item — node riskControl, fence :::risk-control¶
| Attribute | Meaning |
|---|---|
controlKind |
14971 §7.1 priority order — enum inherent-safety-by-design | protective-measure | information-for-safety. Normative (from the standard), so a fixed vocabulary is correct here. |
Body = the control description. Implementation and verification are edges, not attributes.
Edges (extend the existing type-agnostic link vocabulary)¶
| Edge | Source → Target | New? |
|---|---|---|
mitigates |
risk-control → risk |
new edge type |
implemented-by |
risk-control → requirement (design-element in Stage C) |
new edge type |
verifies |
document → risk-control |
existing edge type, control is just a new target (additive per ADR-0078) |
Suspect directions fall out correctly: re-score a risk → its controls' mitigates links go suspect
(re-review adequacy); change a requirement → the control's implemented-by link goes suspect.
Governance (pays off the deferred ADR-0078 generalization)¶
Generalize may_define_requirements → a per-(scope, type) may-define flag: risk definable in
Risk-Analysis document types, risk-control in Risk-Analysis / Design types — configured per scope,
exactly like requirements. The per-(scope, type) key rule generalizes likewise (RISK-*, RC-*).
Keys remain one scope-wide keyspace across all types (ADR-0078).
View — the risk traceability table (per scope)¶
Rows = risks; for each: initial score → its controls (mitigates⁻¹) → each control's implementing
requirement (implemented-by) and verification (verifies⁻¹) → residual score + acceptability.
Gap flags (the completeness property): risk with no control, control not implemented, control not
verified, residual not acceptable. A read-model in the shape of the ADR-0077 coverage panel.
Non-goals (Stage B)¶
No score computation (S×P→level is content); no fixed score vocabulary (scales are content);
no verification-case items (verification is document-level here — items are Stage C); no
design-element type (implemented-by targets requirements for now — Stage C); no benefit-risk
engine (recorded as residualAcceptability + narrative); no automatic re-evaluation on scheme
change (the flagged Stage B+ enhancement under D-B).
Consequences¶
- The risk thread becomes traceable at item level and links into the V-model through
implemented-by→ requirement → (its verification), so a single trace runs hazard → control → requirement → verification → residual. - The per-(scope, type) may-define + key-rule generalization lands here and serves every later type.
- Stage C (validation + GSPR) and Stage D (completeness surface, change-impact, baselines) remain additive on the same substrate.
Decisions confirmed (Nicolas, 2026-07-18)¶
- Q-1 — risk attribute richness → the full structured row (hazard/situation/harm + both score triples + acceptability), not the leaner scores-only variant.
- Q-2 — scheme reference → doc-relation now (the Risk Analysis document references its Risk Management Plan via existing relations); the enforced scope-level scheme binding + re-evaluation suspect is deferred to Stage B+.
- Q-3 —
residualAcceptability→ kept as a recorded evaluation outcome. - Q-4 — probability → split P1 × P2 (P1 = probability of the hazardous situation; P2 = probability it leads to harm) for both the initial and residual estimates.
Clarification (2026-07-19): an acceptable initial risk needs no reduction¶
Round-2 play-test (Nicolas 2026-07-19). ISO 14971 does not require risk reduction when the initial
risk is already acceptable — the risk is accepted as-is, and the absence of a mitigating control is by
design, not a completeness gap. Two consequences follow. Neither changes any decision above, and — the
hard constraint — no serialization / attribute change: the storage attribute keeps its name
residualAcceptability.
-
Acceptability is the current-evaluation outcome, not a residual-only field. In the risk dialog the acceptability control moves OUT of the collapsible "Residual risk" subsection and is relabelled "Acceptability (current evaluation)"; it always shows (an acceptable initial risk records its acceptability without ever touching residual scores). The residual S/P1/P2/level triple stays in the subsection. Acceptability is therefore not treated as "a residual was recorded" — only the residual score tokens are.
-
Gap derivation (
RiskTraceabilityService). A risk with zero mitigating controls AND no residual score recorded AND acceptability ==acceptableis complete — "accepted without reduction" — and the "risk without control" flag must NOT fire (acceptedWithoutReduction, new derived flag; not stored). The other control-less cases are unchanged: a residual score recorded without a control still raisesresidualWithoutControl; a missing ornot-acceptableacceptability without a control still raises the "risk without control" gap. The aggregaterisksWithoutControlnow counts only that genuine gap (it no longer double-counts a residual-without-control row, which has its own count). The panel/grid render the accepted-without-reduction row neutrally (a positive note, no red flag).
A companion UX fix in the same round (not an ADR-0079 semantic change): the completeness gap flags in the
risk-traceability panel/grid gained an in-context remedy tooltip naming where and how to author the
clearing link (e.g. "open the control's document as a draft and add an Implemented by link"), because
the derivation was found correct — both control gaps clear the moment the correct edge is authored
(proven end-to-end by RiskTraceItemTest) — so the reported "flags don't clear" was a discoverability
problem, not a bug.
Clarification 2 (2026-07-19): implemented-by targets a requirement item OR a document¶
Decided by Nicolas (2026-07-19). The Stage-B implemented-by edge (risk-control → requirement) modelled the
common case — a control realized by a product requirement — but ISO 14971 §7.1 recognises control
kinds that are not implemented by a product requirement: an information-for-safety control
(labelling, a warning, a user-manual instruction) or an organizational control (a training / operating
procedure) is implemented by a procedure or work instruction — an SOP/WI document, not a requirement
item. Forcing every control to name a requirement would either fabricate a requirement that does not exist or
leave a genuinely-implemented control flagged "not implemented" forever.
Decision. An implemented-by link may target a requirement item (as in Stage B) OR a DOCUMENT.
The trace_link table already carries the document-target shape (V055 — target_document_id, no key) that
the type-agnostic link path stores, so this is no schema change: it is a picker + derivation + guidance
change only.
- Derivation (
RiskTraceabilityService). A control'simplemented-bylinks are resolved to a mixed list of implementation targets — a requirement key (resolved in the registry, as before) and a document target (rendered as its human id + title, resolved via the RLS-bounded doc display; an invisible/disposed target degrades to inert text, never a leak). The "control not implemented" gap clears when the control carries anyimplemented-bytarget of either form — a document target counts. - UI. For
implemented-bythe trace-links add-flow offers a target-mode toggle — a requirement item (the existing item autocomplete) OR a document (SOP/WI) (the shared RLS-scoped document typeahead) — and POSTs thetargetDocumentIdshape for a document. The risk-traceability panel/grid render document implementation targets distinctly (a document marker + the title on hover) alongside requirement keys. ThehintNotImplementedremedy names both forms. - Scope. Only
implemented-byis broadened to documents here (the control-realization edge). Other edges keep their existing target vocabularies;verifiesalready targets a document-level source or an item, unchanged.
Rationale. This aligns the tool with the 14971 control taxonomy — organizational / information-for-safety controls are implemented by procedures and labelling, which live as controlled documents in the QMS, not as product requirements. It keeps scoring/vocabulary as content (unchanged) and adds no attribute or table.