Skip to main content

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​

IDDecisionStatus
ADR-001Pure-function engine shared by UI, CLI and MCPAccepted
ADR-002Zero runtime dependenciesAccepted
ADR-003Findings as the primary output, score secondaryAccepted
ADR-004Density-normalised scoringAccepted (supersedes count-based, BUG-08)
ADR-005Conservative static-link default for DEPENDS_ONAccepted
ADR-006Explicit linking-exception modellingAccepted
ADR-007OR = best case plus an election noteAccepted
ADR-008Findings deduplicated per distributed unitAccepted (supersedes per-hop, BUG-06)
ADR-009LicenseRef- with extracted text is resolved for copyleft purposesAccepted (BUG-05)
ADR-010Identity = purl minus version, else nameAccepted
ADR-011Hand-written force layout instead of a graph libraryAccepted
ADR-012MCP over stdio without the official SDKAccepted
ADR-013Deploy a clean dist/, excluding tools/Accepted
ADR-014LLM excluded from the verdict pathAccepted
ADR-015Diff operates on analysis results, not raw SBOMsAccepted
ADR-016Gate exit code is the CI contractAccepted

Design rationale decisions​

IDDecisionRejected alternativeRationale in one line
DR-1Rules before codeStart from the graph UIThe domain model is the product; an AI-invented risk model cannot be defended in an audit
DR-2Browser-first, server-freeBackend serviceConfidentiality, no hosting, no accounts, no installation
DR-3Zero runtime dependenciesReact + D3 + CytoscapeMust run on a locked-down workstation and survive years without dependency rot
DR-4Findings, not scoresScore-only dashboardA score is meaningless in an audit; an evidence trail is everything
DR-5Density-normalised scoringWeighted sum of finding countsCounts saturate at 100 and destroy all signal
DR-6Conservative static-link defaultAssume dynamic, or skip unknownAutomotive firmware is overwhelmingly static; over-reporting is the safe direction
DR-7Model linking exceptions explicitlyTreat all GPL as equally criticalFalse positives destroy supplier credibility
DR-8OR = most favourable + election noteWorst case, or silent best caseDual licensing grants a real choice, but the choice must be recorded
DR-9Deduplicate findings per distributed unitPer-hop findings22 identical entries for one component is unusable
DR-10LicenseRef- with text is resolvedTreat every LicenseRef- as a blocker30+ spurious HIGH findings bury the real ones
DR-11Engine pure, interfaces thinSeparate implementation per surfaceOne verdict, three surfaces; they cannot drift apart
DR-12The LLM explains, never decidesLet the model assign tiersA non-deterministic component in the verdict path is unauditable
DR-13Deploy a clean dist/Deploy the repository roottools/ 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:

ChangeWhy it is controlled
Any edit to the license tier tableAffects every verdict ever produced; requires re-baselining all historical findings and legal re-review
Renumbering an existing finding rule IDFindings 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.