From cf10723d5d620c910e4e3b5d84fae211baef47fd Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 13 Aug 2026 08:48:24 +0100 Subject: [PATCH 1/3] docs: explain the licence expression for a compliance reviewer The three-term SPDX expression will reach a customer's licence scanner before it reaches a developer, and two of the three terms are LicenseRef- values that match no template in any scanner's corpus. The expected result is a review queue item raised by someone with no context, in a conversation the project is not part of. This section is written for that reader: that OR is a choice rather than an accumulation, why two terms are LicenseRef- and that resolving as unknown is the mechanism working, which term applies to whom, and that a licensee should record their single concluded term rather than carrying the disjunction into their own SBOM. Licence conditions stay in LICENSE.md - the routing table says which term to read, not what it permits. The worked snippet is CycloneDX rather than a vendor policy syntax, so it is something we can state correctly. Co-Authored-By: Claude Opus 5 (1M context) --- docs/security/sbom.md | 89 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/docs/security/sbom.md b/docs/security/sbom.md index 14925c67..60d9e702 100644 --- a/docs/security/sbom.md +++ b/docs/security/sbom.md @@ -82,6 +82,95 @@ Key fields worth reading: | `metadata.component.licenses[0].expression` | `PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial` — an SPDX expression, because the library is offered under three alternative licences and the recipient chooses. Only the Noncommercial identifier is on the SPDX License List; the other two are `LicenseRef-`. | | `metadata.properties[solidsyslog:source-tree-sha256]` | Content-tree hash: SHA-256 of a sorted list of ` ` lines for every tracked file in `Core/` + `Platform/` plus the root-level `CMakeLists.txt`, `CMakePresets.json`, `LICENSE.md`, and `LICENSES/`, at the commit. Reproducible byte-for-byte from any clone, with no dependency on `git archive` output format or git version. | +## Reading the licence expression + +Written for a compliance reviewer holding SolidSyslog as an item in a review +queue. If you are choosing a licence rather than reviewing one, +[`LICENSE.md`](../../LICENSE.md) is the document you want instead. + +`metadata.component.licenses[0].expression` reads: + +```text +PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial +``` + +**`OR` is a choice, not an accumulation.** SPDX defines the operator as +alternatives: a recipient relies on one term and complies with that one. The +component is not encumbered by all three at once, and nothing obliges a +recipient to satisfy the noncommercial term if they hold a different one. + +This expression is the whole licence question for the component. SolidSyslog +vendors no third-party source, so the `components` array is empty and there is +no transitive licence graph underneath it — see +[why the platform backends are not components](#why-the-platform-backends-are-not-components). + +### Why two of the three are `LicenseRef-` + +Only `PolyForm-Noncommercial-1.0.0` appears on the +[SPDX License List](https://spdx.org/licenses/). The other two do not, for +different reasons: + +| Term | Why it is a `LicenseRef-` | +|---|---| +| `LicenseRef-PolyForm-Internal-Use-1.0.0` | A published, unmodified PolyForm licence that SPDX has not listed. The verbatim text ships in the repository and is linked from [`LICENSE.md`](../../LICENSE.md). | +| `LicenseRef-COSOSO-Commercial` | A negotiated agreement between COSOSO and the licensee. It has no single public text, so no published identifier could describe it. | + +Expect a scanner to resolve both as unknown or custom and raise a review item. +**That is the mechanism working, not a finding against the component.** A +`LicenseRef-` is how SPDX names a licence its list does not cover, and for the +PolyForm term the full text is in the repository for a reviewer who needs to +read it. + +### Which term applies + +| If your organisation is | The term you rely on | +|---|---| +| Evaluating, porting, integrating or testing SolidSyslog internally — commercial organisations included | `LicenseRef-PolyForm-Internal-Use-1.0.0` | +| Distributing it for a noncommercial purpose | `PolyForm-Noncommercial-1.0.0` | +| Supplying, selling or otherwise making available a commercial product, device, firmware or service containing it | `LicenseRef-COSOSO-Commercial` | + +This table says only which term to read. The conditions each one attaches are +in [`LICENSE.md`](../../LICENSE.md), which is the authoritative statement. + +### What to record in your own SBOM + +The expression above is SolidSyslog's **declared** licence — what COSOSO offers +to any recipient. It is not a statement about your organisation's position. + +Once you have taken one of the three, record **that single term** as the +concluded licence for this component in your own product SBOM. Carrying the +disjunction forward re-raises the same review item on every rebuild, and +misstates your position to anyone reading your SBOM downstream. + +For a commercial licensee: + +```json +{ + "type": "library", + "name": "SolidSyslog", + "version": "", + "purl": "pkg:github/cososo-ltd/solid-syslog@", + "licenses": [ + { "expression": "LicenseRef-COSOSO-Commercial" } + ] +} +``` + +Substitute whichever single term you rely on. + +### A policy rule you can adopt + +Stated in prose rather than a vendor syntax, because the encoding differs +across scanning platforms: + +> SolidSyslog is a disjunctively multi-licensed component. Resolve it to the +> single term this organisation holds, record that as the concluded licence, +> and close the item. Do not assess the component against the noncommercial +> term unless that is the term being relied on. + +If your review turns up a question this page does not answer, ask before +escalating it internally: . + ## How to generate one (rehearsal) Each run produces a CycloneDX 1.5 JSON file, validated against the spec by From f4ad108827a93ee6d97eff9c627fac01b0bcd9d5 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 13 Aug 2026 09:25:40 +0100 Subject: [PATCH 2/3] docs: resolve the LicenseRef terms to documents, not to nothing An SPDX expression has nowhere to carry a URL, so both LicenseRef- terms reached a scanner as bare identifiers matching no template. "Unknown with no reference" is what a licence policy denies automatically; "unknown with a resolvable reference" is what a reviewer can act on. The SBOM now carries an externalReferences entry of type license for each of the three terms. Also, from review: - the Internal Use row understated our own grant. PolyForm Internal Use covers the internal business operations of you and your company, internal deployment included, not just evaluation and porting. - the page told a reader not to embed the agreement text because an SBOM is distributed, then handed them purchaseOrder and licensee without the same caution. It now raises the question for those fields too. - the scanner paragraph asserted behaviour we have no data for, and told someone else's compliance function to close an item. Both are now conditional: an unknown identifier is not evidence of a defect, and not a resolution either. Co-Authored-By: Claude Opus 5 (1M context) --- docs/security/sbom.md | 92 +++++++++++++++++++++++++++++-------- sbom/sbom.cdx.json.template | 15 ++++++ 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/docs/security/sbom.md b/docs/security/sbom.md index 60d9e702..f342926b 100644 --- a/docs/security/sbom.md +++ b/docs/security/sbom.md @@ -79,6 +79,7 @@ Key fields worth reading: | `metadata.component.version` | The value from `.release-please-manifest.json` at the time of generation. Pre-release: `0.0.0`. | | `metadata.component.purl` | Package URL keyed to the exact commit SHA — unambiguous pointer back to the source. | | `metadata.component.supplier.name` | `Cozens Software Solutions Limited (COSOSO)`. | +| `metadata.component.externalReferences[type=license]` | One per term in the expression, so a scanner can resolve each to a document instead of leaving it unknown. See [reading the licence expression](#reading-the-licence-expression). | | `metadata.component.licenses[0].expression` | `PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial` — an SPDX expression, because the library is offered under three alternative licences and the recipient chooses. Only the Noncommercial identifier is on the SPDX License List; the other two are `LicenseRef-`. | | `metadata.properties[solidsyslog:source-tree-sha256]` | Content-tree hash: SHA-256 of a sorted list of ` ` lines for every tracked file in `Core/` + `Platform/` plus the root-level `CMakeLists.txt`, `CMakePresets.json`, `LICENSE.md`, and `LICENSES/`, at the commit. Reproducible byte-for-byte from any clone, with no dependency on `git archive` output format or git version. | @@ -115,17 +116,27 @@ different reasons: | `LicenseRef-PolyForm-Internal-Use-1.0.0` | A published, unmodified PolyForm licence that SPDX has not listed. The verbatim text ships in the repository and is linked from [`LICENSE.md`](../../LICENSE.md). | | `LicenseRef-COSOSO-Commercial` | A negotiated agreement between COSOSO and the licensee. It has no single public text, so no published identifier could describe it. | -Expect a scanner to resolve both as unknown or custom and raise a review item. -**That is the mechanism working, not a finding against the component.** A -`LicenseRef-` is how SPDX names a licence its list does not cover, and for the -PolyForm term the full text is in the repository for a reviewer who needs to -read it. +If your scanner reports these identifiers as unknown or custom, that result is +expected. A `LicenseRef-` is how SPDX names a licence its list does not cover, +so there is no template in any scanner's corpus for it to match — and for the +PolyForm term the verbatim text ships in the repository, for a reviewer who +needs to read it. + +An unknown identifier is not, by itself, evidence of a licence defect. Neither +is it a resolution: the item stays open until you have confirmed which of the +three terms your organisation relies on, and hold the evidence for it. + +So that the terms resolve to documents rather than to nothing, the SBOM carries +an `externalReferences` entry of type `license` for each of the three: the +canonical URL for each PolyForm licence, and the enquiry route for the +commercial one. An SPDX expression has nowhere to put a URL, which is why they +are attached to the component instead. ### Which term applies | If your organisation is | The term you rely on | |---|---| -| Evaluating, porting, integrating or testing SolidSyslog internally — commercial organisations included | `LicenseRef-PolyForm-Internal-Use-1.0.0` | +| Using SolidSyslog in the internal business operations of you and your company — which covers evaluation, porting, integration and testing, and internal deployment, commercial organisations included | `LicenseRef-PolyForm-Internal-Use-1.0.0` | | Distributing it for a noncommercial purpose | `PolyForm-Noncommercial-1.0.0` | | Supplying, selling or otherwise making available a commercial product, device, firmware or service containing it | `LicenseRef-COSOSO-Commercial` | @@ -142,21 +153,61 @@ concluded licence for this component in your own product SBOM. Carrying the disjunction forward re-raises the same review item on every rebuild, and misstates your position to anyone reading your SBOM downstream. -For a commercial licensee: +For either PolyForm term an expression is enough, because the identifier +resolves to a published text: ```json -{ - "type": "library", - "name": "SolidSyslog", - "version": "", - "purl": "pkg:github/cososo-ltd/solid-syslog@", - "licenses": [ - { "expression": "LicenseRef-COSOSO-Commercial" } - ] -} +"licenses": [ + { "expression": "PolyForm-Noncommercial-1.0.0" } +] ``` -Substitute whichever single term you rely on. +`LicenseRef-COSOSO-Commercial` needs more. It names a class of negotiated +agreement rather than your particular contract, so on its own it tells a +downstream reader of your SBOM nothing about what was granted. CycloneDX has +fields for exactly this — use the named-licence form: + +```json +"licenses": [ + { + "license": { + "name": "COSOSO Commercial Licence", + "url": "https://www.cososo.co.uk/#contact", + "licensing": { + "licensor": { + "organization": { "name": "Cozens Software Solutions Limited" } + }, + "licensee": { + "organization": { "name": "" } + }, + "purchaseOrder": "", + "licenseTypes": ["oem"], + "expiration": "" + } + } + } +] +``` + +`licenseTypes` takes values from CycloneDX's own enumeration — `oem`, +`appliance`, `perpetual`, `subscription` and others — so pick whichever +describes your agreement. A `licenses` array is *either* a list of named +licences *or* exactly one expression; the two forms cannot be mixed. + +**Do not put the agreement itself in `license.text`.** A negotiated commercial +agreement is confidential between the parties, and an SBOM is a document you +distribute. The reference is what belongs here; the terms are not. + +The same question applies to `purchaseOrder` and `licensee`. They are exactly +right in an internal compliance record, but if you pass this SBOM on to your +own customers, your commercial arrangements travel with it. Decide which of +these fields belong in the copy you distribute and which stay in the copy you +keep — `name` and `url` alone are enough to resolve the identifier. + +Whichever term applies, keep the evidence for it — the agreement reference for +a commercial licence, or a record of the permitted purpose relied on for a +PolyForm one — in your compliance record alongside the SBOM entry. That is what +lets the next reviewer resolve this without repeating your work. ### A policy rule you can adopt @@ -164,9 +215,10 @@ Stated in prose rather than a vendor syntax, because the encoding differs across scanning platforms: > SolidSyslog is a disjunctively multi-licensed component. Resolve it to the -> single term this organisation holds, record that as the concluded licence, -> and close the item. Do not assess the component against the noncommercial -> term unless that is the term being relied on. +> single term this organisation holds, record that as the concluded licence +> together with the evidence supporting it, and close the item on that basis. +> Do not assess the component against the noncommercial term unless that is the +> term being relied on. If your review turns up a question this page does not answer, ask before escalating it internally: . diff --git a/sbom/sbom.cdx.json.template b/sbom/sbom.cdx.json.template index c6f089e2..4059bef0 100644 --- a/sbom/sbom.cdx.json.template +++ b/sbom/sbom.cdx.json.template @@ -51,6 +51,21 @@ } ], "externalReferences": [ + { + "type": "license", + "url": "https://polyformproject.org/licenses/noncommercial/1.0.0", + "comment": "Full text for PolyForm-Noncommercial-1.0.0. A verbatim copy also ships at LICENSES/PolyForm-Noncommercial-1.0.0.md and is covered by solidsyslog:source-tree-sha256." + }, + { + "type": "license", + "url": "https://polyformproject.org/licenses/internal-use/1.0.0", + "comment": "Full text for LicenseRef-PolyForm-Internal-Use-1.0.0, which SPDX does not list. A verbatim copy also ships at LICENSES/PolyForm-Internal-Use-1.0.0.md and is covered by solidsyslog:source-tree-sha256." + }, + { + "type": "license", + "url": "https://www.cososo.co.uk/#contact", + "comment": "LicenseRef-COSOSO-Commercial is a negotiated agreement with no single public text; this is the enquiry route." + }, { "type": "vcs", "url": "https://github.com/cososo-ltd/solid-syslog" From 78006688ae73b822565cd00a4dafbf537cfc0867 Mon Sep 17 00:00:00 2001 From: David Cozens Date: Thu, 13 Aug 2026 11:31:01 +0100 Subject: [PATCH 3/3] docs: stop claiming an expression resolves a LicenseRef Two inaccuracies from review, both of which would have propagated into a reader's own SBOM. The page said an expression was enough "for either PolyForm term". That contradicted its own argument three sections earlier: PolyForm Internal Use is a LicenseRef, so the identifier does not resolve even though the text is published. Anyone following it would have produced exactly the unresolvable entry this page exists to prevent. Split into the SPDX-listed case (expression) and the LicenseRef case (named licence with a URL). The commercial external reference was type=license pointing at a contact form. That type means the URL of a licence file, so it promised a tool a document and would have handed it a marketing page - a false resolution, worse than the bare unknown. It is now type=other with a comment saying why. Not support or website: this is where a licence is obtained, and a less-wrong type is still a wrong one. If a public commercial licensing document ever exists, that reference should become type=license and point at it. Co-Authored-By: Claude Opus 5 (1M context) --- docs/security/sbom.md | 37 +++++++++++++++++++++++++++++-------- sbom/sbom.cdx.json.template | 4 ++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/docs/security/sbom.md b/docs/security/sbom.md index f342926b..5122f698 100644 --- a/docs/security/sbom.md +++ b/docs/security/sbom.md @@ -79,7 +79,7 @@ Key fields worth reading: | `metadata.component.version` | The value from `.release-please-manifest.json` at the time of generation. Pre-release: `0.0.0`. | | `metadata.component.purl` | Package URL keyed to the exact commit SHA — unambiguous pointer back to the source. | | `metadata.component.supplier.name` | `Cozens Software Solutions Limited (COSOSO)`. | -| `metadata.component.externalReferences[type=license]` | One per term in the expression, so a scanner can resolve each to a document instead of leaving it unknown. See [reading the licence expression](#reading-the-licence-expression). | +| `metadata.component.externalReferences[type=license]` | One per PolyForm term, so a scanner resolves each to its canonical text instead of leaving it unknown. The commercial term has no licence document to point at and carries an `other` reference to the enquiry route instead. See [reading the licence expression](#reading-the-licence-expression). | | `metadata.component.licenses[0].expression` | `PolyForm-Noncommercial-1.0.0 OR LicenseRef-PolyForm-Internal-Use-1.0.0 OR LicenseRef-COSOSO-Commercial` — an SPDX expression, because the library is offered under three alternative licences and the recipient chooses. Only the Noncommercial identifier is on the SPDX License List; the other two are `LicenseRef-`. | | `metadata.properties[solidsyslog:source-tree-sha256]` | Content-tree hash: SHA-256 of a sorted list of ` ` lines for every tracked file in `Core/` + `Platform/` plus the root-level `CMakeLists.txt`, `CMakePresets.json`, `LICENSE.md`, and `LICENSES/`, at the commit. Reproducible byte-for-byte from any clone, with no dependency on `git archive` output format or git version. | @@ -127,10 +127,15 @@ is it a resolution: the item stays open until you have confirmed which of the three terms your organisation relies on, and hold the evidence for it. So that the terms resolve to documents rather than to nothing, the SBOM carries -an `externalReferences` entry of type `license` for each of the three: the -canonical URL for each PolyForm licence, and the enquiry route for the -commercial one. An SPDX expression has nowhere to put a URL, which is why they -are attached to the component instead. +an `externalReferences` entry of type `license` for each PolyForm licence, +pointing at its canonical URL. An SPDX expression has nowhere to put a URL, +which is why they are attached to the component instead. + +`LicenseRef-COSOSO-Commercial` has no such entry. There is no public document to +point at, and type `license` means the URL of a licence file — labelling an +enquiry form as one would resolve the identifier to something that is not a +licence. The enquiry route is carried as an `other` reference instead, with a +comment saying what it is. ### Which term applies @@ -153,8 +158,8 @@ concluded licence for this component in your own product SBOM. Carrying the disjunction forward re-raises the same review item on every rebuild, and misstates your position to anyone reading your SBOM downstream. -For either PolyForm term an expression is enough, because the identifier -resolves to a published text: +`PolyForm-Noncommercial-1.0.0` is on the SPDX list, so an expression carries +everything a tool needs to identify it: ```json "licenses": [ @@ -162,7 +167,23 @@ resolves to a published text: ] ``` -`LicenseRef-COSOSO-Commercial` needs more. It names a class of negotiated +The other two are `LicenseRef-` terms, and an expression has nowhere to put a +name or a URL for them — which is the whole reason they resolve as unknown. Use +CycloneDX's named-licence form instead. PolyForm Internal Use is a published +document, so its canonical URL is enough: + +```json +"licenses": [ + { + "license": { + "name": "PolyForm Internal Use License 1.0.0", + "url": "https://polyformproject.org/licenses/internal-use/1.0.0" + } + } +] +``` + +`LicenseRef-COSOSO-Commercial` needs more again. It names a class of negotiated agreement rather than your particular contract, so on its own it tells a downstream reader of your SBOM nothing about what was granted. CycloneDX has fields for exactly this — use the named-licence form: diff --git a/sbom/sbom.cdx.json.template b/sbom/sbom.cdx.json.template index 4059bef0..f624ce75 100644 --- a/sbom/sbom.cdx.json.template +++ b/sbom/sbom.cdx.json.template @@ -62,9 +62,9 @@ "comment": "Full text for LicenseRef-PolyForm-Internal-Use-1.0.0, which SPDX does not list. A verbatim copy also ships at LICENSES/PolyForm-Internal-Use-1.0.0.md and is covered by solidsyslog:source-tree-sha256." }, { - "type": "license", + "type": "other", "url": "https://www.cososo.co.uk/#contact", - "comment": "LicenseRef-COSOSO-Commercial is a negotiated agreement with no single public text; this is the enquiry route." + "comment": "How to obtain LicenseRef-COSOSO-Commercial. Deliberately not type=license: that type means the URL of a licence file, and this agreement is negotiated per licensee with no public document to link. Labelling an enquiry form as a licence would resolve the identifier to something that is not one." }, { "type": "vcs",