Packages

development-driven-specs β€” the dds toolchain (protocol 2) as a Mix task. A thin shim over the bundled instruments (Python, stdlib); never a port.

Current section

Files

Jump to
dds priv toolchain canon docs risks-README.md
Raw

priv/toolchain/canon/docs/risks-README.md

# The risks we manage (the spine)
> 🚧 **dds-stub** β€” a scaffold awaiting human work; the holes (`TODO`,
> `needs-ratification`, `⚠`) are the worklist. Remove this line when none
> remain.
This is the **spine**: the set of concern-classes for which this product
must hold a deliberate strategy. It is not a catalog of data, and it is
not itself a strategy β€” a *strategy is an ADR*. This register indexes
*what must be decided*; each risk resolves to the ADR(s) that decide it,
and (where one exists) the external-register projection that validates
the decision against reality (see [`../registers.md`](../registers.md)).
**One file per risk.** Each `docs/risks/<id>.md` in this directory is one
risk; the filename stem is the risk id. The ids are the join keys every
risk reference resolves against β€” an ADR's `Risk:` trailer, an external
register's `Implicates` edge β€” so renaming a file is a repo- and
register-wide edit.
Read the register as a yardstick. For each risk the question is binary
and auditable: **is there a strategy, and does reality conform to it?** A
risk with no strategy is a finding, not a silence.
## How a risk is described
Each risk file carries four sections:
- **Stake** β€” the harm, in its own currency (what *goes wrong*, not the
topic).
- **Currency** β€” what is at risk. This keeps the risk *distinct* from
its neighbours; two risks with the same currency are one risk.
- **No strategy looks like** β€” the absence condition: the harm's
SIGNATURE, so a gap is detectable rather than a matter of taste.
Deliberately absent: a "validation question" or any requirement-shaped
text. **Risk files name harms; ADRs make demands.** Whether a risk is
managed is COMPUTED, not authored here: "is there a strategy" is the
coverage table (`Risk:` trailers β†’ Accepted ADRs β€” `/dds-check`'s hard
tier), and "does reality conform" is judged conformance of each
covering ADR's OWN Rule against the witnessed facts β€” the Rule is the
yardstick, written where the decision lives, unable to drift from the
strategy because it IS the strategy. A hand-authored per-risk question
is a second yardstick that shadows the ADRs and pulls decision-shaped
content (which control, which legal ground) into the harm-shaped file
β€” the two-worlds line, crossed. (Adequacy of a covering ADR to its
risk is judged at ratification, by the improve pass, and by the
conformance judge reading stake against Rule β€” the stake itself is
that judge's input.)
A fifth section, **Current state**, records the honest answer today, with
its strategy ADR or its gap.
The file ends with its structured trailers, in this order:
- **`Class: <name>`** β€” zero or more lines, one per fact class this
risk covers, from the five-name vocabulary (`gating` Β· `erasure` Β·
`audit` Β· `surface` Β· `credential`). This is the ONE binding between
the risk (and, through `Risk:` trailers, its covering ADRs) and the
coverage table's rows β€” ratified content, verdicted by the check (an
unknown name is a structural finding). A risk outside the witnessed
classes simply carries none.
- **`Addressed by: <adr-stem>, <adr-stem>`** β€” exactly one line, bare
stems (no backticks), comma-separated β€” the machine-readable
risk→strategy link; present only where a covering ADR exists (files
with a GAP carry no `Addressed by:` line). Each stem must resolve to
a `docs/adr/` file, and the named ADR's `Risk:` trailer must point
back (coverage closure, checked in both directions).
## Residuals β€” the ledger of known gaps
A **residual** is a known, specific, still-unmitigated gap: the named
remainder after the strategies are applied. Residuals are the unit
hardening status is counted in β€” "what exactly is still exposed, which
of it have we consciously accepted, and is the open count going down."
They live in a structured `## Residuals` section of the risk file, one
line each:
```
## Residuals
- `res.audit-view-unpoliced` Β· open Β· the audit_log_entries view
re-exposes full version history with no policies block
[lib/teamology/utils/resources/audit_log_entry.ex | "use Ash.Resource,"]
- `res.oban-args-emails` Β· accepted 2026-07-14 (bound-data-retention) Β·
rendered emails with student names sit in oban_jobs.args until the
retention rule lands
[lib/teamology/workers/email_sender.ex | "Oban.Worker"]
```
The shape, per line:
- **id** β€” `res.<kebab-slug>`, unique across the whole corpus. One gap,
one identity: an ADR or another risk that mentions the same gap
writes `see res.<id>` instead of re-describing it.
- **state** β€” `open` Β· `accepted <YYYY-MM-DD> (<by-or-adr>)` Β·
`closed <sha7>`. `accepted` means consciously carried, with the
decision (or deciding ADR) named; `closed` keeps the line as history.
- **one sentence** β€” the gap, concretely.
- **witness** β€” `[<file> | "<verbatim quote>"]`, quote-verified by the
check exactly like a witness fact: a residual whose quote no longer
verifies is reported *stale β€” close or re-witness?*, so an
acknowledgment can never outlive the code it describes. (Only
`closed` residuals may omit the witness.)
Residuals are AUTHORED BY HUMANS and ratified by merge β€” the check
DISCOVERS candidates (its findings are proto-residuals) but never
writes them; a checker that can acknowledge its own findings can
absolve itself. Every check finding has exactly three exits, all
through a PR:
1. **fix the code** β€” the finding vanishes on the next run;
2. **ratify a residual** β€” the gap is real and consciously carried;
3. **sharpen the claim** β€” the finding was noise because an ADR was too
vague: the ADR gains the missing scope boundary or named exception,
and the finding dissolves because the demand, properly stated, never
covered the case.
There is deliberately NO fourth exit β€” no suppression list, no dismiss
channel, no known-issues config. Its absence is load-bearing: every
false positive is forced to become a permanent sharpening of the
claims corpus instead of a tombstone in a config file.
## Signals β€” witnessed evidence a class is live here
A **signal** is a witnessed, finding-shaped fact the schema-level
derivations can't see (e.g. "impersonation grants are Logger-only") β€”
evidence that a fact class is PRESENT in this codebase, feeding the
coverage table's presence column. Signals live in a `## Signals`
section of the risk that covers them, one line each, and bind to
classes through that risk's `Class:` declarations:
```
## Signals
- impersonation grants are Logger-only
[lib/app/grants.ex | "Logger.info("]
```
The shape, per line: the fact in one sentence, then the witness
`[<file> | "<verbatim quote>"]`, quote-verified exactly like a
residual's β€” a signal whose quote no longer verifies is reported
*stale β€” re-witness or remove*. Signals are authored by `/dds-sync`'s
witnessing pass and ratified by merge; the check never invents one. A
signal in a risk with no `Class:` declaration is a structural finding
β€” a signal needs a class home, and a class no risk declares forces the
risk into existence.
What residuals may and may not acknowledge: a residual acknowledges a
SPECIFIC finding (a structural, cube, or judged gap at a named
artifact). It cannot acknowledge an uncovered fact class β€” the hard
tier's floor. An uncovered class needs an ADR (an
`Accepted (interim, …)` acceptance is fine); coverage is the one thing
that may never be residualized away.
## How the spine is derived (do not pattern-match a checklist)
Derive bottom-up: tag every load-bearing thing the codebase does with its
*stake*, then cluster the stakes by **currency**. The clusters are the
risks. A top-down, regulation-shaped list reliably *misses* the stakes
that are not its frame β€” integrity of the core output, cost, auditability,
availability β€” and a coverage yardstick with those holes certifies "all
managed" while real concerns sit off the chart.
A risk earns its place when its stake is distinct in currency from its
neighbours; a candidate that shares another's currency collapses into it,
and a candidate that is a single decision (not a churning class) is an
ADR, not a risk.
## How coverage closes
An ADR that is a strategy for a risk carries one trailer line **per risk
it genuinely addresses** β€” repeat the line; do not pad:
```
Risk: <risk-id>
```
and the risk file names its strategies back (`Addressed by:`, above).
A strategy that is `Accepted` but carries `Conformance: partial` /
`pending` (see [`../conventions.md` Β§1](../conventions.md)) covers the
risk **in design only** β€” the risk file's Current state names the
realization gap, and the risk is not yet covered in practice.
Coverage is then computable in both directions: a risk whose `Current
state` names no covering ADR is a **GAP**, and where an external register
projects `Implicates` edges onto these ids, an implicated risk with no
strategy is a gap the register can surface. An ADR often *touches*
several risks while being a real strategy for only one β€” claim the one;
the others **remain gaps** until a strategy exists for each. That is the
model working, not a defect: never claim a risk an ADR does not actually
address just to silence the gap.
## The risks
> TODO β€” derived from this product's code by `/dds-sync`, ratified by
> the relevant authority. The set commonly includes some of:
> confidentiality of subject data Β· authorization & authentication Β·
> authority to process Β· data across trust boundaries Β· secrets &
> credentials Β· integrity/correctness of the core output Β· accountability
> & auditability Β· cost & resource governance Β· availability. Derive the
> actual set; do not adopt this list.
## Excluded β€” and why
Record the exclusions so the next reader does not re-litigate them
(e.g. privileged capabilities are *things* that implicate risks, not a
concern-class; routes are code-sourced with nothing governed; the docs
themselves are the *why* layer). TODO per this product.
Codified-by: TODO β€” the code surfaces from which the risks were derived.