Decision Records
Two linked sets. The DR entries come from the conceptual design and capture product-level choices; the ADR entries come from the architecture design and capture structural ones. Where a DR and an ADR cover the same ground, both are listed — the DR explains why the product behaves that way, the ADR where that behaviour lives in the code.
Architecture decision records
| ID | Decision | Status |
|---|---|---|
| ADR-001 | Pure-function engine shared by UI, CLI and MCP | Accepted |
| ADR-002 | Zero runtime dependencies | Accepted |
| ADR-003 | Findings as the primary output, score secondary | Accepted |
| ADR-004 | Density-normalised scoring | Accepted (supersedes count-based, BUG-08) |
| ADR-005 | Conservative static-link default for DEPENDS_ON | Accepted |
| ADR-006 | Explicit linking-exception modelling | Accepted |
| ADR-007 | OR = best case plus an election note | Accepted |
| ADR-008 | Findings deduplicated per distributed unit | Accepted (supersedes per-hop, BUG-06) |
| ADR-009 | LicenseRef- with extracted text is resolved for copyleft purposes | Accepted (BUG-05) |
| ADR-010 | Identity = purl minus version, else name | Accepted |
| ADR-011 | Hand-written force layout instead of a graph library | Accepted |
| ADR-012 | MCP over stdio without the official SDK | Accepted |
| ADR-013 | Deploy a clean dist/, excluding tools/ | Accepted |
| ADR-014 | LLM excluded from the verdict path | Accepted |
| ADR-015 | Diff operates on analysis results, not raw SBOMs | Accepted |
| ADR-016 | Gate exit code is the CI contract | Accepted |
Design rationale decisions
| ID | Decision | Rejected alternative | Rationale in one line |
|---|---|---|---|
| DR-1 | Rules before code | Start from the graph UI | The domain model is the product; an AI-invented risk model cannot be defended in an audit |
| DR-2 | Browser-first, server-free | Backend service | Confidentiality, no hosting, no accounts, no installation |
| DR-3 | Zero runtime dependencies | React + D3 + Cytoscape | Must run on a locked-down workstation and survive years without dependency rot |
| DR-4 | Findings, not scores | Score-only dashboard | A score is meaningless in an audit; an evidence trail is everything |
| DR-5 | Density-normalised scoring | Weighted sum of finding counts | Counts saturate at 100 and destroy all signal |
| DR-6 | Conservative static-link default | Assume dynamic, or skip unknown | Automotive firmware is overwhelmingly static; over-reporting is the safe direction |
| DR-7 | Model linking exceptions explicitly | Treat all GPL as equally critical | False positives destroy supplier credibility |
| DR-8 | OR = most favourable + election note | Worst case, or silent best case | Dual licensing grants a real choice, but the choice must be recorded |
| DR-9 | Deduplicate findings per distributed unit | Per-hop findings | 22 identical entries for one component is unusable |
| DR-10 | LicenseRef- with text is resolved | Treat every LicenseRef- as a blocker | 30+ spurious HIGH findings bury the real ones |
| DR-11 | Engine pure, interfaces thin | Separate implementation per surface | One verdict, three surfaces; they cannot drift apart |
| DR-12 | The LLM explains, never decides | Let the model assign tiers | A non-deterministic component in the verdict path is unauditable |
| DR-13 | Deploy a clean dist/ | Deploy the repository root | tools/ are Node processes with filesystem and stdio access |
The two that matter most
ADR-001 / DR-11 — one verdict, three surfaces
src/risk-engine.js, src/license-db.js, src/spdx.js and src/diff.js contain no I/O.
The browser, the CLI and the MCP server are thin adapters over the same functions. File reading
happens in the adapters only.
This is what guarantees AG-2: the three surfaces cannot disagree, because they execute literally the same functions on the same input. It also means the engine is testable with nothing but a JSON object — which is why the engine suite needs no jsdom.
ADR-014 / DR-12 — the LLM is out of the verdict path
Every tool exposed by the MCP server returns data produced by the deterministic engine. Nothing in the protocol lets an agent set a tier, a severity or a score.
This is enforced structurally, not by a prompt instruction: the agent cannot influence the verdict even if it is asked to. The model may summarise findings, explain why a rule fired, and draft correspondence to the supplier. It may never decide.
Controlled changes
Two categories of change are treated as controlled, because they invalidate history:
| Change | Why it is controlled |
|---|---|
| Any edit to the license tier table | Affects every verdict ever produced; requires re-baselining all historical findings and legal re-review |
| Renumbering an existing finding rule ID | Findings are referenced in reports, diff matching and (Phase 2) waiver registers |
Append new rule IDs; never renumber. LIC-002 remains reserved for exactly this reason.