From dc0b20a8460e391fdd646e7028b31edd6d104bfc Mon Sep 17 00:00:00 2001 From: Pavel Shukhman Date: Sat, 29 Aug 2026 17:12:24 -0400 Subject: [PATCH 1/4] feat(perspective): add pre-defined perspective identity with reference form Adds an optional 'predefined' field to the perspective model, identifying a well-known perspective published in the CycloneDX perspectives catalog and incorporating the published definition by reference, so tooling can recognize the perspective without matching on free-text names. A perspective either declares a pre-defined identity or defines its own mappings inline; mixing the two is not permitted, keeping the published definition the single source of truth for what a pre-defined perspective contains. Initial enum values: model-card, pqc-readiness. Valid/invalid fixtures added covering the reference form, the inline form, enum rejection, and the forbidden mixed form. Bundled schemas are left to the post-merge bundle workflow. Signed-off-by: Pavel Shukhman --- .../cyclonedx-perspective-2.0.schema.json | 42 +++++++++++++++++-- .../invalid-perspective-predefined-2.0.json | 15 +++++++ ...alid-perspective-predefined-mixed-2.0.json | 23 ++++++++++ .../2.0/valid-perspective-predefined-2.0.json | 32 ++++++++++++++ 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 tools/src/test/resources/2.0/invalid-perspective-predefined-2.0.json create mode 100644 tools/src/test/resources/2.0/invalid-perspective-predefined-mixed-2.0.json create mode 100644 tools/src/test/resources/2.0/valid-perspective-predefined-2.0.json diff --git a/schema/2.0/model/cyclonedx-perspective-2.0.schema.json b/schema/2.0/model/cyclonedx-perspective-2.0.schema.json index 217cf5870..ccae9150a 100644 --- a/schema/2.0/model/cyclonedx-perspective-2.0.schema.json +++ b/schema/2.0/model/cyclonedx-perspective-2.0.schema.json @@ -18,14 +18,37 @@ "title": "Perspective", "description": "A domain-specific view that identifies the types of data relevant to a particular audience and provides optional terminology mappings to facilitate interpretation. Perspectives enable tooling to generate filtered views, translate terminology, and validate document completeness against audience-specific requirements.", "additionalProperties": false, - "required": [ - "name", - "mappings" + "oneOf": [ + { + "$comment": "Reference form: incorporates the published pre-defined perspective by reference; inline mappings shall not be provided.", + "properties": { + "predefined": true, + "mappings": false + }, + "required": [ + "predefined" + ] + }, + { + "$comment": "Inline form: the perspective is fully defined in the document and shall not declare a pre-defined identity.", + "properties": { + "predefined": false, + "name": true, + "mappings": true + }, + "required": [ + "name", + "mappings" + ] + } ], "properties": { "bom-ref": { "$ref": "cyclonedx-common-2.0.schema.json#/$defs/refType" }, + "predefined": { + "$ref": "#/$defs/preDefinedPerspective" + }, "name": { "type": "string", "title": "Perspective Name", @@ -66,6 +89,19 @@ } } }, + "preDefinedPerspective": { + "type": "string", + "title": "Pre-Defined Perspective", + "description": "Identifies a well-known, pre-defined perspective published in the CycloneDX perspectives catalog, incorporating the published definition by reference and enabling tooling to recognize the perspective without matching on free-text names. A perspective declaring a pre-defined identity shall not provide inline mappings; a perspective defining its own mappings shall omit this field.", + "enum": [ + "model-card", + "pqc-readiness" + ], + "meta:enum": { + "model-card": "A transparency view for machine learning models following the industry-standard model card structure, covering model details, intended use, training data and procedure, quantitative analysis, technical limitations, and ethical and environmental considerations.", + "pqc-readiness": "A view for assessing readiness for the post-quantum cryptography migration, covering cryptographic inventory, quantum resistance of the cryptography in use, and cryptographic agility." + } + }, "perspectiveMapping": { "type": "object", "title": "Perspective Mapping", diff --git a/tools/src/test/resources/2.0/invalid-perspective-predefined-2.0.json b/tools/src/test/resources/2.0/invalid-perspective-predefined-2.0.json new file mode 100644 index 000000000..8c069c097 --- /dev/null +++ b/tools/src/test/resources/2.0/invalid-perspective-predefined-2.0.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://cyclonedx.org/schema/2.0/cyclonedx-2.0.schema.json", + "specFormat": "CycloneDX", + "specVersion": "2.0", + "version": 1, + "metadata": { + "timestamp": "2026-08-29T12:00:00Z" + }, + "perspectives": [ + { + "bom-ref": "perspective-1", + "predefined": "threat-model" + } + ] +} diff --git a/tools/src/test/resources/2.0/invalid-perspective-predefined-mixed-2.0.json b/tools/src/test/resources/2.0/invalid-perspective-predefined-mixed-2.0.json new file mode 100644 index 000000000..82600905b --- /dev/null +++ b/tools/src/test/resources/2.0/invalid-perspective-predefined-mixed-2.0.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://cyclonedx.org/schema/2.0/cyclonedx-2.0.schema.json", + "specFormat": "CycloneDX", + "specVersion": "2.0", + "version": 1, + "metadata": { + "timestamp": "2026-08-29T12:00:00Z" + }, + "perspectives": [ + { + "bom-ref": "perspective-1", + "predefined": "pqc-readiness", + "name": "PQC Readiness", + "mappings": [ + { + "expression": "$.components[?(@.type=='cryptographic-asset')]", + "nativeName": "Cryptographic Inventory", + "relevance": "required" + } + ] + } + ] +} diff --git a/tools/src/test/resources/2.0/valid-perspective-predefined-2.0.json b/tools/src/test/resources/2.0/valid-perspective-predefined-2.0.json new file mode 100644 index 000000000..56ae47452 --- /dev/null +++ b/tools/src/test/resources/2.0/valid-perspective-predefined-2.0.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://cyclonedx.org/schema/2.0/cyclonedx-2.0.schema.json", + "specFormat": "CycloneDX", + "specVersion": "2.0", + "serialNumber": "urn:uuid:9d2e4a1b-7c3f-4e8a-b1d6-2f5c8e9a0b3d", + "version": 1, + "metadata": { + "timestamp": "2026-08-29T12:00:00Z" + }, + "perspectives": [ + { + "bom-ref": "perspective-1", + "predefined": "model-card" + }, + { + "bom-ref": "perspective-2", + "name": "Crypto Inventory", + "description": "An inline perspective fully defined in the document.", + "domains": [ + "cryptographic-security" + ], + "mappings": [ + { + "expression": "$.components[?(@.type=='cryptographic-asset')]", + "nativeName": "Cryptographic Inventory", + "relevance": "required", + "weight": 1.0 + } + ] + } + ] +} From f7efd6aa95085924802a4a95af9b06d87b0382f7 Mon Sep 17 00:00:00 2001 From: Pavel Shukhman Date: Sat, 29 Aug 2026 17:13:22 -0400 Subject: [PATCH 2/4] feat(perspectives): add model card pre-defined perspective catalog entry Second entry in the perspectives catalog, following the delivery shape of the PQC readiness perspective: a complete, minimal, valid 2.0 document containing only the perspective. This is the definition incorporated by reference when a document declares the pre-defined perspective 'model-card'. The mappings express the industry-standard model card structure over the CycloneDX 2.0 decomposition of the former first-class modelCard entity: intrinsic technical characteristics in modelProperties (scoped to components of type machine-learning-model, the only type that may carry them), training datasets as components of type data, intended use cases as use case definitions, and ethical and fairness considerations as risk model entries. Assumes the AI/ML model properties proposed in CycloneDX/specification#990. Signed-off-by: Pavel Shukhman --- perspectives/model-card-perspective.json | 168 +++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 perspectives/model-card-perspective.json diff --git a/perspectives/model-card-perspective.json b/perspectives/model-card-perspective.json new file mode 100644 index 000000000..d75c7aa87 --- /dev/null +++ b/perspectives/model-card-perspective.json @@ -0,0 +1,168 @@ +{ + "$schema": "https://cyclonedx.org/schema/2.0/cyclonedx-2.0.schema.json", + "specFormat": "CycloneDX", + "specVersion": "2.0", + "version": 1, + "metadata": { + "timestamp": "2026-08-29T12:00:00Z" + }, + "perspectives": [ + { + "bom-ref": "perspective-model-card", + "name": "Model Card", + "description": "Defines the data elements of a machine learning model card, following the industry-standard model card structure: model details, intended use, training data and procedure, quantitative analysis, technical limitations, and ethical, fairness, and environmental considerations. The mappings project that structure onto the CycloneDX 2.0 decomposition of the former first-class modelCard entity: intrinsic technical characteristics live in modelProperties, which may only appear on components of type machine-learning-model; training datasets are components of type data; intended use cases are use case definitions referenced from the model; and ethical and fairness considerations are entries in the document's risk model. This perspective assumes the AI/ML model properties proposed in CycloneDX/specification#990.", + "domains": [ + "machine-learning", + "artificial-intelligence", + "transparency", + "ethics" + ], + "mappings": [ + { + "expression": "$.components[?(@.type=='machine-learning-model')]['name','version','description']", + "nativeName": "Model Details", + "nativeDescription": "The identifying facts of the model: its name, version, and a description of what it is and does.", + "relevance": "required", + "weight": 1.0, + "rationale": "A model card is meaningless without stating which model, and which revision of it, the card describes." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].parties[?(@.roles[?(@.role=='supplier')])]", + "nativeName": "Developed By", + "nativeDescription": "The organization or individuals responsible for developing and supplying the model.", + "relevance": "required", + "weight": 0.9, + "rationale": "Accountability for a model's behaviour requires knowing who produced it. Expressed through the party model with the supplier role." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].licenses", + "nativeName": "License", + "nativeDescription": "The license under which the model, and by extension its weights, may be used.", + "relevance": "required", + "weight": 0.8, + "rationale": "Model cards conventionally state usage terms; license determines whether a given use is permitted at all." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.tasks", + "nativeName": "Supported Tasks", + "nativeDescription": "The machine learning tasks the model is designed to perform.", + "relevance": "required", + "weight": 0.9, + "rationale": "Tasks anchor the card: they determine the applicable inputs, outputs, and evaluation metrics." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.learningTypes", + "nativeName": "Learning Paradigms", + "nativeDescription": "The learning paradigms applied when training the model, such as supervised or reinforcement learning.", + "relevance": "recommended", + "weight": 0.5, + "rationale": "Helps readers judge what kinds of data and feedback shaped the model's behaviour." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.architecture", + "nativeName": "Model Architecture", + "nativeDescription": "The architecture family and structural characteristics of the model.", + "relevance": "recommended", + "weight": 0.7, + "rationale": "Architecture contextualizes capability and performance claims and supports reproducibility." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties['inputs','outputs']", + "nativeName": "Input and Output Parameters", + "nativeDescription": "The modalities, formats, and constraints of the data the model consumes and produces.", + "relevance": "recommended", + "weight": 0.6, + "rationale": "Input and output specifications define the model's operational envelope and integration contract." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties['parameterCount','quantization']", + "nativeName": "Model Size and Quantization", + "nativeDescription": "The parameter count of the model and any quantization applied to its weights.", + "relevance": "optional", + "weight": 0.3, + "rationale": "Size and quantization inform deployment cost and can affect accuracy relative to the unquantized model." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.training", + "nativeName": "Training Data and Procedure", + "nativeDescription": "How the model was trained: the training formula and the datasets used.", + "relevance": "recommended", + "weight": 0.8, + "rationale": "Training data provenance is central to assessing bias, capability boundaries, and data protection obligations." + }, + { + "expression": "$.components[?(@.type=='data')]", + "nativeName": "Datasets", + "nativeDescription": "Dataset components referenced from the model's training information, carrying dataset composition, governance, and sensitive-data declarations.", + "relevance": "recommended", + "weight": 0.6, + "rationale": "Training references resolve to components of type data; the dataset detail a card reader needs lives on those components." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.evaluation", + "nativeName": "Quantitative Analysis", + "nativeDescription": "Evaluation results: performance metrics, per-slice measurements, confidence intervals, and supporting graphics.", + "relevance": "recommended", + "weight": 0.8, + "rationale": "Metrics, including slice-level results, substantiate capability claims and surface performance disparities between groups." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.useCases", + "nativeName": "Intended Use", + "nativeDescription": "References to the use cases the model is intended for.", + "relevance": "required", + "weight": 0.9, + "rationale": "Intended use separates in-scope application from misuse; it is the card section most consulted by adopters and assessors." + }, + { + "expression": "$.definitions.useCases", + "nativeName": "Use Case Definitions", + "nativeDescription": "The use case definitions that the model's intended-use references resolve to.", + "relevance": "recommended", + "weight": 0.6, + "rationale": "The model links to use cases by reference; the definitions carry the actual descriptions a card reader needs." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].parties[?(@.roles[?(@.role=='end-user')])]", + "nativeName": "Intended Users", + "nativeDescription": "The audiences the model is intended to be used by.", + "relevance": "recommended", + "weight": 0.5, + "rationale": "Stating who the model is for frames the expertise assumed of its operators. Expressed through the party model with the end-user role." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.limitations", + "nativeName": "Technical Limitations", + "nativeDescription": "Known technical limitations of the model, including constraints on accuracy, reasoning, scalability, and appropriate use, and relevant performance tradeoffs.", + "relevance": "required", + "weight": 0.9, + "rationale": "Limitations are the card's primary safeguard against use outside the model's competence." + }, + { + "expression": "$.risks.risks[?(@.domains[?(@.type=='ethical')])]", + "nativeName": "Ethical Considerations", + "nativeDescription": "Risks in the ethical domain associated with the model, including affected parties, benefits, harms, and mitigations.", + "relevance": "required", + "weight": 0.9, + "rationale": "Ethical considerations are expressed as entries in the document's risk model rather than as card-local prose, gaining structured likelihood, impact, and response data." + }, + { + "expression": "$.risks.risks[?(@.inherentRisk.impact.categories[?(@=='fairness' || @=='bias')])]", + "nativeName": "Fairness Assessments", + "nativeDescription": "Risks whose impact is categorized as fairness or bias, describing groups at risk and observed disparities.", + "relevance": "recommended", + "weight": 0.6, + "rationale": "Fairness assessments identify demographic or group-level performance disparities; slice-level evaluation metrics provide their quantitative backing." + }, + { + "expression": "$.components[?(@.type=='machine-learning-model')].modelProperties.environmental", + "nativeName": "Environmental Considerations", + "nativeDescription": "Energy consumption and carbon cost of model activities such as training and inference.", + "relevance": "recommended", + "weight": 0.5, + "rationale": "Environmental impact is an established model card section and increasingly a reporting obligation." + } + ] + } + ] +} From 95afc9410dcb48d296dac7a663be887fb4bc80a5 Mon Sep 17 00:00:00 2001 From: Pavel Shukhman Date: Sat, 29 Aug 2026 17:45:50 -0400 Subject: [PATCH 3/4] feat(perspective): restrict reference form to bom-ref and predefined The reference form previously excluded only inline mappings, leaving name, description, domains, externalReferences, and properties legal alongside a pre-defined identity. That allowed documents to locally shadow published fields (e.g. a different name or domains) with no defined precedence. The reference form now forbids all inline content except bom-ref, so the published definition is unambiguously the single source of truth. Invalid fixture added covering annotation of a pre-defined perspective. Signed-off-by: Pavel Shukhman --- .../cyclonedx-perspective-2.0.schema.json | 11 ++++++++--- ...-perspective-predefined-annotated-2.0.json | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 tools/src/test/resources/2.0/invalid-perspective-predefined-annotated-2.0.json diff --git a/schema/2.0/model/cyclonedx-perspective-2.0.schema.json b/schema/2.0/model/cyclonedx-perspective-2.0.schema.json index ccae9150a..81ff539a8 100644 --- a/schema/2.0/model/cyclonedx-perspective-2.0.schema.json +++ b/schema/2.0/model/cyclonedx-perspective-2.0.schema.json @@ -20,10 +20,15 @@ "additionalProperties": false, "oneOf": [ { - "$comment": "Reference form: incorporates the published pre-defined perspective by reference; inline mappings shall not be provided.", + "$comment": "Reference form: the perspective is the published pre-defined perspective, incorporated by reference. Only bom-ref may accompany the pre-defined identity; all other inline content is forbidden so the published definition remains the single source of truth.", "properties": { "predefined": true, - "mappings": false + "name": false, + "description": false, + "domains": false, + "mappings": false, + "externalReferences": false, + "properties": false }, "required": [ "predefined" @@ -92,7 +97,7 @@ "preDefinedPerspective": { "type": "string", "title": "Pre-Defined Perspective", - "description": "Identifies a well-known, pre-defined perspective published in the CycloneDX perspectives catalog, incorporating the published definition by reference and enabling tooling to recognize the perspective without matching on free-text names. A perspective declaring a pre-defined identity shall not provide inline mappings; a perspective defining its own mappings shall omit this field.", + "description": "Identifies a well-known, pre-defined perspective published in the CycloneDX perspectives catalog, incorporating the published definition by reference and enabling tooling to recognize the perspective without matching on free-text names. A perspective declaring a pre-defined identity shall not provide any inline content other than bom-ref; a perspective defining its own content shall omit this field.", "enum": [ "model-card", "pqc-readiness" diff --git a/tools/src/test/resources/2.0/invalid-perspective-predefined-annotated-2.0.json b/tools/src/test/resources/2.0/invalid-perspective-predefined-annotated-2.0.json new file mode 100644 index 000000000..7875d0345 --- /dev/null +++ b/tools/src/test/resources/2.0/invalid-perspective-predefined-annotated-2.0.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://cyclonedx.org/schema/2.0/cyclonedx-2.0.schema.json", + "specFormat": "CycloneDX", + "specVersion": "2.0", + "version": 1, + "metadata": { + "timestamp": "2026-08-29T12:00:00Z" + }, + "perspectives": [ + { + "bom-ref": "perspective-1", + "predefined": "model-card", + "name": "Threat Model", + "domains": [ + "cryptographic-security" + ] + } + ] +} From ebb5184e91a1dbcdb2009f4c5f19b1f439473058 Mon Sep 17 00:00:00 2001 From: Pavel Shukhman Date: Sat, 29 Aug 2026 17:46:55 -0400 Subject: [PATCH 4/4] fix(perspectives): align Datasets mapping description with its expression The expression selects all components of type data; JSONPath cannot follow references, so the selection is necessarily broader than the datasets referenced from the model's training information. Soften the description so prose and expression agree. Signed-off-by: Pavel Shukhman --- perspectives/model-card-perspective.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perspectives/model-card-perspective.json b/perspectives/model-card-perspective.json index d75c7aa87..48df302c1 100644 --- a/perspectives/model-card-perspective.json +++ b/perspectives/model-card-perspective.json @@ -93,7 +93,7 @@ { "expression": "$.components[?(@.type=='data')]", "nativeName": "Datasets", - "nativeDescription": "Dataset components referenced from the model's training information, carrying dataset composition, governance, and sensitive-data declarations.", + "nativeDescription": "Dataset components, typically referenced from the model's training information, carrying dataset composition, governance, and sensitive-data declarations.", "relevance": "recommended", "weight": 0.6, "rationale": "Training references resolve to components of type data; the dataset detail a card reader needs lives on those components."