[2.0] fixes on schema and test resources - #1001
Conversation
a03e560 to
905e58a
Compare
- [x] Java tests - functional - [x] JS tests - functional - WIP - found some false-positives/false-negatives - due to errors in test resources - [x] JS tests - schema validate - includes #996 - includes #992 - JS tests - semantics - [x] #998 - [x] #995 - [x] PHP tests - functional added tests only, no schema/res fixes - they are prepared in - #997 - #1001 --------- Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
| "$ref": "#/$defs/timestamp/$defs/withMilliseconds", | ||
| "title": "Created Timestamp", | ||
| "description": "The time at which this signature was originally created. The creator can use any time it deems most appropriate as the time the signature was created, but it MUST be precise to the nearest millisecond (exactly three digits after the decimal place in seconds). The created property MUST NOT be changed when creating a new version of the signature.", | ||
| "$comment": "Normative source: ITU-T X.590 clause 6.3.1", |
There was a problem hiding this comment.
consolidated to #/$defs/timestamp/$defs/withMilliseconds
| "title": "Modified Timestamp", | ||
| "description": "The time that this particular version of the signature was last modified. The creator can use any time it deems most appropriate as the time that this version of the signature was modified, but it MUST be precise to the nearest millisecond (exactly three digits after the decimal place in seconds). The modified property MUST be later than or equal to the value of the created property. If the created and modified properties are the same, then this is the first version of the signature.", | ||
| "$comment": "Normative source: ITU-T X.590 clause 6.3.1. Cross-field invariant: 'modified' MUST be greater than or equal to 'created'. JSON Schema 2020-12 cannot compare two sibling property values, so this constraint is NOT enforced by this schema and MUST be validated by the consuming tool or conformance suite.", | ||
| "pattern": "^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\\.[0-9]{3}Z$" |
There was a problem hiding this comment.
consolidated to #/$defs/timestamp/$defs/withMilliseconds
There was a problem hiding this comment.
file was removed, since the relevant test case did not exist.
the componentCHoice does not require/have a name
❓ was this intentional/correct
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
There was a problem hiding this comment.
Pull request overview
Updates the CycloneDX 2.0 JSON schemas and test fixtures to align with recent model changes (notably parties, signatures, extensible properties, and stricter schema semantics), and extends the JS semantic test suite to enforce additional schema invariants.
Changes:
- Refactors many 2.0 test resources from legacy fields (e.g.,
publisher,supplier,manufacturer,authors,signature) to 2.0 structures (parties,signatures), and corrects cryptography-related shapes. - Tightens/clarifies schema semantics (e.g.,
additionalProperties: falsein more places, explicit property presence inoneOf/anyOfbranches,refLinkTypeusage). - Adds extensible-properties valid/invalid fixtures and adjusts JS semantic tests for the updated schema patterns.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/src/test/resources/2.0/valid-standard-2.0.json | Updates signature fixture to signatures[] and new JSS field names. |
| tools/src/test/resources/2.0/valid-service-2.0.json | Migrates publisher to parties role-based representation. |
| tools/src/test/resources/2.0/valid-perspective-2.0.json | Migrates supplier to parties role-based representation. |
| tools/src/test/resources/2.0/valid-patent-2.0.json | Migrates manufacturer to parties and updates organization URL structure. |
| tools/src/test/resources/2.0/valid-machine-learning-considerations-env-2.0.json | Migrates supplier/publisher to parties. |
| tools/src/test/resources/2.0/valid-machine-learning-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-name-with-text-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-name-with-licensing-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-name-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-id-with-text-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-id-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-expression-with-licensing-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-expression-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-license-choice-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-formulation-2.0.json | Migrates publisher to parties. |
| tools/src/test/resources/2.0/valid-external-reference-2.0.json | Migrates publisher to parties and adds new externalReference types. |
| tools/src/test/resources/2.0/valid-extensible-properties.json | Adds a valid extensible-properties fixture (new file). |
| tools/src/test/resources/2.0/valid-cryptography-implementation-2.0.json | Updates cryptography fixtures to match schema (e.g., arrays where required). |
| tools/src/test/resources/2.0/valid-cryptography-full-2.0.json | Updates cryptography fixtures (e.g., arrays for refs/platform). |
| tools/src/test/resources/2.0/valid-cryptography-certificate-advanced-2.0.json | Updates certificate fixtures and adds algorithm asset + algorithmRef arrays. |
| tools/src/test/resources/2.0/valid-cryptography-certificate-2.0.json | Fixes ref and expands crypto assets; updates platform arrays. |
| tools/src/test/resources/2.0/valid-component-swid-full-2.0.json | Migrates authors to parties with author role. |
| tools/src/test/resources/2.0/valid-component-swid-2.0.json | Migrates authors to parties with author role. |
| tools/src/test/resources/2.0/valid-component-choice-xor-2.0.json | Migrates supplier to parties for component-choice samples. |
| tools/src/test/resources/2.0/valid-component-choice-or-2.0.json | Migrates supplier to parties for component-choice samples. |
| tools/src/test/resources/2.0/valid-component-choice-nested-2.0.json | Migrates nested supplier to nested parties. |
| tools/src/test/resources/2.0/valid-component-choice-in-nested-components-2.0.json | Migrates nested supplier to nested parties. |
| tools/src/test/resources/2.0/valid-component-choice-and-2.0.json | Migrates supplier to parties for AND operator sample. |
| tools/src/test/resources/2.0/valid-bom-2.0.json | Broad migration of manufacturer/supplier/authors/publisher to parties, including contacts and URLs. |
| tools/src/test/resources/2.0/valid-attestation-2.0.json | Migrates multiple signature objects to signatures[] throughout. |
| tools/src/test/resources/2.0/invalid-extensible-properties.json | Adds invalid extensible-properties fixture (new file). |
| tools/src/test/resources/2.0/invalid-component-choice-missing-name-2.0.json | Removes an invalid test resource fixture. |
| tools/src/test/js/schema-v2/json-schema-semantic-tests.js | Updates semantic tests for refLinkType exception and additionalProperties handling. |
| schema/2.0/model/cyclonedx-vulnerability-2.0.schema.json | Adds additionalProperties: false and fixes required-property existence assertions. |
| schema/2.0/model/cyclonedx-patent-2.0.schema.json | Switches certain refs from refType to refLinkType. |
| schema/2.0/model/cyclonedx-party-2.0.schema.json | Makes oneOf branches explicitly assert the required property exists. |
| schema/2.0/model/cyclonedx-metadata-2.0.schema.json | Adds additionalProperties: false to an object schema. |
| schema/2.0/model/cyclonedx-license-2.0.schema.json | Makes oneOf branches explicitly assert the required property exists. |
| schema/2.0/model/cyclonedx-jss_X590_2023_10-2.0.schema.json | Consolidates millisecond timestamp handling and adds explicit property presence in anyOf. |
| schema/2.0/model/cyclonedx-formulation-2.0.schema.json | Makes oneOf branches explicitly assert the required property exists. |
| schema/2.0/model/cyclonedx-declaration-2.0.schema.json | Adjusts required fields in a oneOf branch (e.g., signatures). |
| schema/2.0/model/cyclonedx-data-2.0.schema.json | Makes oneOf branches explicitly assert the required property exists. |
| schema/2.0/model/cyclonedx-cryptography-2.0.schema.json | Refactors some oneOf branches/types and shifts refs to refLinkType. |
| schema/2.0/model/cyclonedx-component-2.0.schema.json | Improves mutual-exclusion logic annotation and adds property presence to not. |
| schema/2.0/model/cyclonedx-common-2.0.schema.json | Makes refLinkType inheritance explicit; refactors extensible-properties schema; adjusts base mixins; adds placeholder for organizationalEntityOrContact. |
| schema/2.0/model/cyclonedx-citation-2.0.schema.json | Makes anyOf/oneOf branches explicitly assert required property exists. |
| schema/2.0/model/cyclonedx-annotation-2.0.schema.json | Makes oneOf branches explicitly assert required property exists. |
| schema/2.0/model/cyclonedx-ai-modelcard-2.0.schema.json | Removes redundant type alongside $ref. |
| schema/2.0/cyclonedx-2.0.schema.json | Replaces root additionalProperties: false with unevaluatedProperties: false to support extensibility mixins. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
|
had Fable 5 analyze the state of the schema. my query: this are the results:CycloneDX 2.0 JSON Schema Structure ReviewScope: branch 1. Concrete issues found1.1 References that escape the
|
| # | File | Location (JSON pointer) | Issue |
|---|---|---|---|
| 1 | schema/2.0/model/cyclonedx-cryptography-2.0.schema.json |
#/$defs/cryptoProperties/properties/algorithmProperties/properties/algorithmFamily/$ref and .../ellipticCurve/$ref |
"../../cryptography-defs.schema.json#/definitions/…" points outside the versioned 2.0 tree to schema/cryptography-defs.schema.json. The pointer itself resolves (the target has definitions/algorithmFamiliesEnum and definitions/ellipticCurvesEnum), but: (a) the target is a draft-07 schema while all 2.0 schemas are 2020-12; (b) resolving the relative ref against the model file's $id yields https://cyclonedx.org/schema/cryptography-defs.schema.json, while the target declares $id: http://cyclonedx.org/schema/cryptography-defs.schema.json (http vs https mismatch — strict resolvers that key their registry by $id will fail); (c) it is unversioned, so 2.0 validation results can change whenever the shared file is regenerated. Fix: copy/generate a versioned, 2020-12 $defs-based snapshot into schema/2.0/ (e.g. schema/2.0/cyclonedx-cryptography-defs-2.0.schema.json) with an https://cyclonedx.org/schema/2.0/... $id, and reference that. |
| 2 | schema/2.0/model/cyclonedx-license-2.0.schema.json |
#/$defs/license/properties/id/$ref |
"../../spdx.schema.json" — same class of problem: target is draft-07, unversioned, outside schema/2.0/, and declares $id: http://cyclonedx.org/schema/spdx.schema.json while the ref resolves to the https:// URI. Fix: ship a versioned SPDX enum snapshot under schema/2.0/ (as 1.x does with a sibling spdx.schema.json in the same directory) or at minimum align the $id scheme. |
These two are the only refs that do not stay within schema/2.0/; every other $ref (relative path + #/$defs/... pointer) resolves to an existing file and an existing definition.
1.2 Orphaned / dead definitions
| # | File | Location | Issue & suggested fix |
|---|---|---|---|
| 3 | model/cyclonedx-common-2.0.schema.json |
#/$defs/organizationalEntityOrContact |
Content is literally { "$comment": "TODO" } — an unfinished stub, referenced by nothing. As a schema it is true (accepts anything). Fix: implement it (e.g. anyOf of organizationalEntity / organizationalContact) or delete it before release. |
| 4 | model/cyclonedx-common-2.0.schema.json |
#/$defs/baseObject |
A "base object for all CycloneDX entities" mixin (extensibleProperties + properties + externalReferences) that no schema composes. Its own $comment describes an architecture ("concrete leaf schemas composing this mixin must close themselves with unevaluatedProperties: false") that is not implemented anywhere. Fix: either wire it into the entity definitions or remove it. See also issue 8 — this orphan is the symptom of a larger design gap. |
| 5 | model/cyclonedx-common-2.0.schema.json |
#/$defs/base64 |
Unreferenced. attachment.content is a plain string and does not use it even when encoding: "base64" is set. Fix: reference it from attachment (e.g. via if/then on encoding) or remove it. |
| 6 | model/cyclonedx-common-2.0.schema.json |
#/$defs/timestamp |
Unreferenced. Every consumer needing a timestamp uses inline format: date-time, and the JSS file carries its own, stricter timestamp def. Fix: make the model files reference common#/$defs/timestamp (or delete it). Note its regex is also weaker than the JSS one (accepts 2023-13-99T…) — see issue 10. |
| 7 | schema/2.0/cyclonedx-api-2.0.schema.json |
whole file | An empty shell: type: object, empty $defs, no properties/required — it validates any JSON object. The README describes it as "the normative API-focused schema", which it currently isn't. It references no model files at all. Fix: flesh it out or clearly mark it as a placeholder in the README. |
There are no orphaned model files — all 22 model files are reachable from the root schema.
1.3 unevaluatedProperties / extensibility composition
| # | File | Location | Issue & suggested fix |
|---|---|---|---|
| 8 | root + all model files | root #/allOf + #/unevaluatedProperties; e.g. component, service, vulnerability… defs |
The root composition itself is correct: in draft 2020-12, unevaluatedProperties: false does see through allOf/$ref, so root-level properties and the ext:* patternProperties from extensibleProperties both remain allowed while everything else is rejected. However, the pattern is only applied at the document root. All nested entity objects (component, service, vulnerability, party, hash, etc. — 100+ objects) are closed with additionalProperties: false and do not compose extensibleProperties. Consequently ext:* extension properties are legal only at the BOM top level, contradicting the extensibleProperties description ("enables organizations … to safely introduce custom properties") and the mixin comments in common (issues 3–4). Fix: decide the intended granularity. If entities should be extensible: give each entity allOf: [{$ref: …extensibleProperties}] and replace additionalProperties: false with unevaluatedProperties: false (this is exactly what the orphaned baseObject was built for). If only the root should be extensible: update the extensibleProperties description/comments and delete baseObject. |
| 9 | model/cyclonedx-jss_X590_2023_10-2.0.schema.json |
#/$defs/signatureObject/unevaluatedProperties |
unevaluatedProperties: true is a no-op — it is identical to omitting the keyword. The $comment explains the openness is intentional (X.590 §6.2 allows extra metadata), so the behaviour is fine, but the keyword adds noise and suggests a misunderstanding. Fix: drop the keyword and keep the $comment, or keep it only if you want an explicit marker (then say so in the comment). |
1.4 Duplicate definitions of the same concept
| # | Concept | Files | Issue & suggested fix |
|---|---|---|---|
| 10 | timestamp |
common#/$defs/timestamp vs jss_X590…#/$defs/timestamp |
Two different regexes for the same concept: common's ^\d{4}-\d{2}-\d{2}T…$ accepts invalid dates (2023-13-99); JSS's validates month/day/hour ranges. Common's is also completely unused (issue 6). Fix: keep one canonical def (JSS one is normative to X.590 and may stay, but common should adopt equivalent strictness) and reference it. |
| 11 | postalAddress |
common#/$defs/postalAddress vs party#/$defs/postalAddress |
Two divergent address models are both in active use: party's adds isoCode and coordinates; common's is used from organizationalEntity. Same title, different shapes → the same real-world address validates differently depending on which parent object holds it. Fix: keep one (party's superset) and have common $ref it, or move it wholly into common. |
| 12 | identifier |
component, party, jss_X590… (each #/$defs/identifier) |
Three unrelated concepts share one def name: component = "identifiers asserted by a party", party = "authority-issued identifier (scheme/value)", JSS = "UUID". Not a resolution problem (refs are file-scoped) but confusing for readers and for bundlers/codegen that flatten names. Fix: rename to assertedIdentifier / authorityIdentifier / signatureId (or similar). |
| 13 | hashAlgorithm |
common vs jss_X590… |
common = enum of SHA-256-style names; JSS = free-form IANA lowercase (sha-256) pattern. Same name, incompatible value spaces. Justifiable (X.590 mandates IANA names) but deserves a rename (e.g. ianaHashAlgorithm in JSS) or a $comment cross-reference. |
| 14 | mediaType |
common#/$defs/mediaType vs component#/$defs/component/properties/mime-type |
Component's mime-type re-declares the identical pattern ^[-+a-z0-9.]+/[-+a-z0-9.]+$ inline instead of $ref-ing common#/$defs/mediaType; the property is also named mime-type while everything else says "media type". Fix: $ref the common def; consider renaming the property (breaking) or at least noting the alias. |
| 15 | party vs organizationalEntity | party-2.0 vs common-2.0 |
Two parallel "who" models coexist: the rich party/organization/person/persona model (referenced from exactly one place: component#/$defs/…parties) and the legacy organizationalEntity/organizationalContact (referenced ~20×, incl. metadata). If party is the 2.0 direction, most of the spec doesn't use it yet. Fix: converge (e.g. make organizationalEntity an alias/$ref into the party model), or document the intended split. |
1.5 Naming / structural inconsistencies
| # | File / location | Issue & suggested fix |
|---|---|---|
| 16 | model/cyclonedx-jss_X590_2023_10-2.0.schema.json (filename + $id) |
The standard is ITU-T X.590 (JSON Signature Scheme) — the filename drops the dot (X590) and mixes snake_case (jss_X590_2023_10) into an otherwise all-kebab-case directory (ai-modelcard, release-notes, …). It is not a typo for X.509, but it reads like one. Fix: rename to e.g. cyclonedx-jss-x590-2.0.schema.json (edition date in the file's $comment/description, not the filename) and update $id plus the one inbound ref from common. |
| 17 | Titles across model files | Convention is CycloneDX <Thing> Model (Annotation, Citation, Common, …) but two deviate: CycloneDX AI Model Card and CycloneDX Model for JSON Signature Scheme (JSS). Fix: normalize (e.g. CycloneDX AI Model Card Model is awkward — pick and document one convention). |
| 18 | root #/properties/specFormat |
Single-value "enum": ["CycloneDX"] — in 2020-12 the idiomatic form is "const": "CycloneDX". Same for ai-modelcard energyMeasure.unit (["kWh"]), co2Measure.unit (["tCO2eq"]) and common attachment.encoding (["base64"]). Fix: use const (non-breaking, purely stylistic). |
| 19 | root #/properties/specVersion |
Only type: string with an example of "2.0" — any string validates ("1.4", "banana"). Every 1.x schema pins this with an enum. Fix: "const": "2.0" (or an enum if patch versions are anticipated). |
| 20 | model/cyclonedx-jss_X590_2023_10-2.0.schema.json #/$defs/timestamp/$defs/withMilliseconds |
A $defs block nested inside another definition, referenced via the unusual pointer #/$defs/timestamp/$defs/withMilliseconds. Legal, and the $comment marks it internal, but it is the only place this idiom is used and some tooling (doc generators, bundlers) handles nested $defs poorly. Fix (optional): hoist to a top-level $defs/timestampWithMilliseconds. |
| 21 | model/cyclonedx-definition-2.0.schema.json |
The file exists to hold a single $defs/definitions wrapper (standards + patents). Also note the root BOM property is itself called definitions — a domain term that collides visually with the JSON-Schema keyword; inherited from 1.6, so acceptable, but the def-named-definitions-inside-$defs in this file compounds the confusion. Fix (optional): fold into standard/patent files or rename the def. |
1.6 Circular references
| # | Cycle | Assessment |
|---|---|---|
| 22 | cyclonedx-component-2.0 ⇄ cyclonedx-ai-modelcard-2.0 (component → modelCard/graphicsCollection; ai-modelcard → component#/$defs/componentData) |
The only cross-file cycle. Harmless for validation (recursion is data-bounded), but it defeats simple topological bundling/codegen. Fix (optional): move componentData (and friends) into cyclonedx-data-2.0.schema.json, which already exists for that purpose — this would break the cycle cleanly. |
| 23 | Self-recursion inside component (children/pedigree), formulation (workflow steps), JSS (signatureObject counter-signature chain), extensibleProperties/propertyValue |
All intentional and well-formed; no action needed. |
2. Aspects checked and found to be fine
$refresolution: all 433 refs resolve; every#/$defs/...pointer targets an existing definition. No dangling pointers withinschema/2.0/. The only caveats are the two out-of-tree targets (issues 1–2).$idconsistency: all 24 files declare$idexactly matchinghttps://cyclonedx.org/schema/2.0/<path>for their actual path/filename. No mismatches inside the 2.0 tree.$schemadeclarations: present in every file, allhttps://json-schema.org/draft/2020-12/schema. No missing or mixed-draft declarations within the 2.0 tree.- Meta-schema validity: every file passes validation against the 2020-12 meta-schema.
- Orphaned files: none — all model files are reachable from the root. Apart from the four
$defsentries incommon(issues 3–6), every definition in every file is referenced at least once. $defsusage: consistently$defs(never legacydefinitions) in all 2.0 files; the "definitions" hits in the root and definition model are domain property/def names, not the keyword.- Root
unevaluatedProperties: false+allOfcomposition: semantically correct at the root — draft 2020-12 annotation flow makes theext:*patternProperties from the referencedextensiblePropertiescount as "evaluated". (The gap is coverage, not correctness — issue 8.) - Regex patterns: all 24
pattern/patternPropertiesexpressions compile and use only ECMA-262-safe constructs (no lookbehind, no\pwithout flags, etc.). Escaping is correct. - Enum/const misuse: no duplicate enum values, no type-inconsistent enums; only the stylistic single-value enums noted in issue 18.
- Keyword usage: no draft-04 leftovers (boolean
exclusiveMinimum,id, etc.);formatvalues used (date,date-time,idn-email,iri-reference,uri,uuid) are all standard 2020-12 vocabulary;if/then/else,prefixItems-free arrays, andpatternPropertiesare used correctly. - Model-file guard convention: every model file consistently uses root
"type": "null"so the file root cannot accidentally be used as a document schema — applied uniformly across all 22 files. - Dependency graph: clean layering otherwise —
commonis the shared base (its only dependency is the JSS file), root fans out to the 13 top-level model refs, and the one problematic cycle is issue 22.
3. Summary
The modular structure is fundamentally sound: refs resolve, $ids are consistent, and the files are
valid 2020-12. The material problems are (a) the two out-of-tree, cross-draft, http/https-mismatched
external refs (crypto-defs, SPDX), (b) the extensibility architecture being only half-built
(baseObject orphaned, ext:* only usable at the root while every nested object is closed with
additionalProperties: false), (c) the empty API entry point, and (d) several divergent duplicate
definitions (postalAddress, timestamp, party-vs-organizationalEntity) that will produce
inconsistent validation of identical data.
There was a problem hiding this comment.
removed the test case, since the name was not required at all ...
fixes or related to
additionalProperties=false#995requiredproperties actually exist #996bom-refmust be of typerefType#998besides those fixes above, this is in the PR:
refLinkTypeinherit explicitextensibleProertiesJSS$commentsextensiblePropertiesinvalid-component-choice-missing-name-2.0.json- as thenamewas optional or did not exist.❓ was this intentional/correct
tests for the things are based on: