Conceptual Design
| Field | Value |
|---|---|
| Document ID | CDD-OCSA-001 |
| Version | 1.0 |
| Status | Baseline |
| Date | 2026-09-12 |
1. Product concept
One sentence: Drop a supplier's SPDX SBOM into a browser, and get back a visual dependency map plus a prioritised, evidence-backed list of OSS license obligations — without the SBOM ever leaving your machine.
The product is deliberately a decision-support instrument for a human reviewer, not an automated compliance authority. Every output is framed as triage that a qualified engineer — and ultimately legal counsel — confirms. This framing is a design decision, not a hedge: it keeps the tool useful while preventing it from being misused as a legal verdict.
The three questions it answers
- What is in this software? — component inventory, versions, suppliers, purls.
- What does it oblige us to do? — obligation checklist, source-offer list, relinking list.
- What changed since the last milestone? — added/removed components, license changes, newly introduced copyleft, regressions.
What it deliberately does not do
- Does not decide whether something is legally acceptable.
- Does not require a server, account, or network access.
- Does not attempt to scan source code.
- Does not manage the approval workflow (Phase 2).
2. Functional map
3. Core domain concepts
See Domain Concepts for the full table. The eight concepts are Component, Effective license, Distributed unit, Linkage, Taint, Obligation, Finding and Identity.
The mental model the UI enforces
The graph view exists to make this picture instantly legible: red nodes are strong copyleft, red edges are static links, and a red ring means this is reachable from copyleft.
4. Design rationale
Every significant decision, with the alternative that was rejected.
DR-1 — Rules before code
Decision: Freeze the license tier table, obligations and gate criteria in a table before writing any code. Rationale: The domain model is the product. If an AI coding tool invents the risk model, the result cannot be defended to an OEM auditor. The tier table is the artefact legal will actually ratify. Rejected: Starting from the graph UI and inferring rules later — produces a pretty tool with an undefensible verdict.
DR-2 — Browser-first, server-free
Decision: All parsing and analysis run client-side in JavaScript. Rationale: Supplier SBOMs are confidential; NFR-01 forbids third-party transmission. A server would require hosting, accounts and a data-processing justification. A browser app sidesteps all of it and needs no installation. Rejected: Backend service — unnecessary infrastructure, and it would make reviewers reluctant to paste in real supplier data.
DR-3 — Zero runtime dependencies
Decision: No framework, no charting library, no graph library. The force layout is ~60 lines. Rationale: The tool must work on a locked-down corporate workstation with no npm access, must survive years without dependency rot, and must be deployable as a static folder. It also keeps the supply chain of the compliance tool itself trivial — an irony worth avoiding. Rejected: React + D3 + Cytoscape — three dependencies to review in a tool whose purpose is dependency review.
DR-4 — Findings, not scores
Decision: The primary output is a list of findings, each with rule ID, severity, evidence path, obligation and recommended action. A 0–100 score exists but is secondary. Rationale: A score is meaningless in an audit; an evidence trail is everything. Reviewers act on findings, and suppliers respond to findings. Rejected: Score-only dashboard — impossible to action or defend.
DR-5 — Density-normalised scoring
Decision: Score combines density (share of components that are critical/high/unknown/ tainted) with blocker density and a quality penalty — not a raw finding count. Rationale: Raw counts grow with SBOM size, so a large clean project would score worse than a small dirty one. Density keeps the score comparable across ECUs and milestones. Rejected: Weighted sum of finding counts — saturated at 100 on the first realistic SBOM, destroying all signal.
DR-6 — Conservative linkage default
Decision: When the SBOM only declares DEPENDS_ON, treat linkage as static.
Rationale: In automotive firmware, linking is overwhelmingly static. Assuming dynamic would
systematically under-report the most common real-world violation.
Rejected: Assuming dynamic (under-reports); assuming "unknown and skip" (silently drops the
majority of real SBOMs).
Mitigation: Every such finding states "linkage not declared", so the supplier can correct it.
DR-7 — Model linking exceptions explicitly
Decision: Handle WITH <exception> and maintain a relaxation table.
Rationale: GPL-2.0-or-later WITH u-boot-exception-2.0 is materially different from
GPL-2.0-only. Ignoring exceptions floods the report with false positives and destroys
credibility with suppliers — the fastest way to get a tool ignored.
Rejected: Treating all GPL as equally critical.
DR-8 — OR = most favourable, but flag the election
Decision: A OR B evaluates to the lower-risk tier and raises LIC-007 instructing the
reviewer to document which license was elected.
Rationale: Dual licensing genuinely grants a choice — scoring the worst case would be wrong.
But the choice must be recorded, because it determines the obligation actually owed.
Rejected: Worst-case for OR (over-reports); silent best-case (loses the audit trail).
DR-9 — Deduplicate findings per distributed unit
Decision: Propagation findings are emitted once per (copyleft source, root) pair. Rationale: The first implementation produced 22 identical entries for one AGPL component with several ancestors — unusable. Intermediate ancestors are still marked tainted, so no information is lost. Rejected: Per-hop findings — measured, and rejected on usability grounds.
DR-10 — LicenseRef- with extracted text is resolved for copyleft purposes
Decision: A custom license reference whose text is supplied does not raise the unresolved
blocker (LIC-003); it stays UNKNOWN tier for review.
Rationale: It is not an unknown-unknown. Raising a blocker for every proprietary LicenseRef-
produced 30+ HIGH findings and buried the real ones.
Rejected: Treating all LicenseRef- as blockers — measured to be unusable.
DR-11 — Engine stays pure; interfaces are thin
Decision: 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.
Rationale: One verdict, three surfaces. If the engine had I/O, the CLI and UI could drift
apart and produce different answers to the same question.
Rejected: Separate implementations per surface.
DR-12 — The LLM explains, never decides
Decision: In the MCP/agent layer, every verdict comes from the deterministic engine. The model may summarise, explain and draft correspondence. Rationale: A non-deterministic component in the verdict path makes the result unauditable. Rejected: Letting the model assign risk tiers.
DR-13 — Deploy a clean dist/, not the repository
Decision: A build script copies only browser-facing files to dist/, excluding tools/.
Rationale: tools/ contains Node processes with filesystem and stdio access. Publishing them
to a corporate URL is unnecessary exposure.
Rejected: Deploying the repository root.
5. Technologies and tools
| Layer | Choice | Rationale | Alternatives rejected |
|---|---|---|---|
| Language | JavaScript (ES 2022 modules) | Runs in browser and Node unchanged; no compile step | TypeScript (build step); Python (cannot run in browser) |
| Runtime | Node.js 18 or newer | Already available; ES module support | Deno, Bun (not standard on corporate machines) |
| UI framework | None — vanilla DOM | No install, no build, no dependency review | React, Vue |
| Graph rendering | Hand-written force layout on SVG | ~60 lines; full control; zero deps | D3-force, Cytoscape, vis-network |
| Charts | Hand-written SVG bars | Trivial need | Chart.js |
| Styling | Plain CSS with custom properties | Themeable, no preprocessor | Tailwind (build step) |
| Module system | Native ES modules | No bundler | Webpack, Vite |
| Test (engine) | Node's built-in assert-style script | Zero dependencies | Jest, Mocha |
| Test (UI) | jsdom | Runs the real DOM in Node; dev-only | Playwright (browser download) |
| Sample data | Hand-authored SPDX 2.3 JSON | Representative of real IVI/cockpit BOMs | Synthetic generator |
| Deployment | Cloudflare Pages (static) | Already used by the organisation; free tier; custom domain | Netlify, GitHub Pages |
| Agent interface | MCP over stdio, hand-written JSON-RPC | No SDK dependency | @modelcontextprotocol/sdk |
| Docs | Markdown | Version-controllable, readable anywhere | Confluence |
Tooling summary
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 22.22.2 | CLI, MCP server, tests, build script |
| jsdom | 30.0.1 | UI smoke test (dev-only, optional) |
Python http.server | 3.x | Local preview (any static server works) |
| Wrangler | latest | Cloudflare Pages deployment |
| Git | — | Version control, Pages Git integration |
6. Preliminary architecture
Layered view
Data flow — a single analysis
Deployment view
7. User interface concept
See the Dashboard Tour for the per-region breakdown.
Colour language (dark theme): red = critical, orange = high, yellow = medium, green = low, grey = unknown. Red edge = static link, blue = dynamic, dashed = build/dev. Ring = tainted.
8. Conceptual limitations
Accepted, documented, and surfaced to the user:
- Not legal advice. Legal must confirm CRITICAL/HIGH.
- Distributed-unit granularity. Compatibility conflicts are computed over a root's transitive subtree; components in separate processes do not actually conflict.
DEPENDS_ONis not a linkage statement. The conservative default over-reports by design.- No per-file analysis. Packages with
filesAnalyzed: falseare taken at their declared license. - License text is not parsed, only IDs plus a keyword heuristic for
LicenseRef-. - The score is a triage aid, not an SLA metric.