Skip to main content

Extending the Rules

All domain policy lives in declarative tables in src/license-db.js. Adding knowledge is data entry, not programming — which is the point of ADR-007 and AG-7.

:::warning Changing a tier is a controlled change The tier table affects every verdict ever produced. A change requires re-baselining all historical findings and legal re-review. Adding a new license is routine; changing an existing tier is not. See Decision Records. :::

Add a license​

Add an entry to LICENSE_RULES. Matching is longest-prefix, case-insensitive, so lgpl-2. covers LGPL-2.0-only, LGPL-2.1-or-later and LGPL-2.1+.

{ match: 'lgpl-2.', tier: 'HIGH', cat: 'weak-copyleft',
obl: ['notice', 'state-changes', 'source-modified', 'relinking'],
note: 'Static linking in monolithic firmware is the most common compliance gap.' }
FieldMeaning
matchLowercase prefix to match against the license id
tierOne of CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
catCategory — drives propagation behaviour (strong / weak / file copyleft, permissive, …)
oblObligation keys from the OBLIGATIONS catalogue
noteFree-text rationale, surfaced in the UI

Available obligation keys: notice, state-changes, source-full, source-modified, relinking, network-source, install-info, patent, no-endorsement, advertising, non-commercial.

Relax a license via an exception​

Add to LINKING_EXCEPTIONS. relax: 1 steps the tier down the ladder LOW → MEDIUM → HIGH → CRITICAL; relax: 0 records the note but does not downgrade.

'u-boot-exception-2.0': { relax: 1,
note: 'U-Boot exception: static linking of GPL-2.0+ U-Boot into firmware is permitted.' },
'linux-syscall-note': { relax: 0,
note: 'Syscall note: user-space programs calling the kernel are not derivative works.' },

:::caution The ladder excludes UNKNOWN on purpose If UNKNOWN were on the ladder, CRITICAL would relax onto UNKNOWN and produce a verdict that looks like missing data rather than copyleft. That was BUG-02. :::

Add a known incompatibility​

Add to COMPAT. Matching is by family prefix, in either direction.

{ a: 'gpl-2.0', b: 'apache-2.0', sev: 'HIGH',
why: 'Apache-2.0 patent/indemnity terms are additional restrictions under GPL-2.0.' }

The why string is surfaced verbatim in the finding, so a reviewer sees why two licenses conflict rather than just that they do.

Add a finding rule​

New rules go in src/risk-engine.js, taking the next free LIC-0xx.

:::danger Never renumber an existing rule ID LIC-002 is reserved but unimplemented precisely to keep the numbering stable. Findings are referenced in reports, in diff matching, and — in Phase 2 — in waiver registers. Append; do not renumber. :::

Other extension points​

ExtensionWhereEffort
CycloneDX inputA new parser producing the same normalised model as parseSpdxDays
New export formatA new renderer in src/report.jsHours
Enrichment (OSV, ClearlyDefined, deps.dev)A post-parse step keyed on purlDays
Hosted APIA Worker with a fetch handler wrapping risk-engine.jsDays
Persistence and workflowA backend service; the engine is unchangedWeeks

Enrichment worth adding later​

All optional, and the tool must stay usable offline:

  • SPDX license list validation (also fixes TD-09)
  • ClearlyDefined and deps.dev lookups by purl
  • OSV / NVD CVE correlation
  • A LicenseRef- text classifier, once you have real legal-reviewed samples

Hosted API caveat​

tools/mcp-server.mjs cannot run on Workers — there is no stdin and no filesystem. If you want a hosted API, wrap src/risk-engine.js in a Worker with a fetch handler. The engine is pure and has no Node dependencies, so this is straightforward.