Skip to content

Implements conditional subschemas based on component type - #774

Open
stevespringett wants to merge 1 commit into
2.0-devfrom
2.0-dev-conditional-component-type
Open

Implements conditional subschemas based on component type#774
stevespringett wants to merge 1 commit into
2.0-devfrom
2.0-dev-conditional-component-type

Conversation

@stevespringett

Copy link
Copy Markdown
Member

Implements and closes #638

Signed-off-by: Steve Springett <steve@springett.us>
@stevespringett stevespringett added this to the 2.0 milestone Jan 13, 2026
@stevespringett
stevespringett requested a review from a team as a code owner January 13, 2026 00:39
@stevespringett stevespringett added the CDX 2.0 related to release v2.0 label Jan 13, 2026
@stevespringett stevespringett linked an issue Jan 13, 2026 that may be closed by this pull request

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements conditional subschemas for component properties based on component type, as specified in issue #638. The changes enforce that certain properties (modelCard, data, cryptoProperties) can only be used with specific component types.

Changes:

  • Removed modelCard, data, and cryptoProperties from the main component properties section
  • Added conditional subschemas using allOf with if-then-else logic to enforce type-specific property usage
  • Added a new conditional requirement for hardware components to prevent swid and purl specification

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread schema/2.0/model/cyclonedx-component-2.0.schema.json
Comment thread schema/2.0/model/cyclonedx-component-2.0.schema.json
@Mehrn0ush

Mehrn0ush commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Hi @stevespringett — quick catch on “Version Range Requirement” (around line 281), unless I’m misunderstanding the intent:

Right now the rule triggers on the presence of versionRange, and the then clause only constrains isExternal to be true if isExternal is present. Since isExternal is not required there, an object can still include versionRange while omitting isExternal, and it will pass validation.

If the intent is “versionRange may only be used when isExternal is true” (as the versionRange field description suggests), a more direct pattern would be:

{
  "if": { "required": ["versionRange"] },
  "then": {
    "properties": { "isExternal": { "const": true } },
    "required": ["isExternal"]
  }
}

One small wording consistency nit: the Version Range rule description currently reads “versionRange must not be present when isExternal is false”, while the versionRange field description says “May only be used if isExternal is true.” These are closely related, but it might be worth aligning the phrasing so the intent is unambiguous (and easier to carry into spec text later).

@stevespringett

Copy link
Copy Markdown
Member Author

@Mehrn0ush thank you for the thorough review.

Right now the rule triggers on the presence of versionRange, and the then clause only constrains isExternal to be true if isExternal is present. Since isExternal is not required there, an object can still include versionRange while omitting isExternal, and it will pass validation.

I believe that is accurate and captures the current behavior in v1.7

If the intent is “versionRange may only be used when isExternal is true” (as the versionRange field description suggests), a more direct pattern would be:

@jkowalleck I know you worked on this. What are your thoughts. I attempted to keep the existing logic in place - and I think I have.

One small wording consistency nit: the Version Range rule description currently reads “versionRange must not be present when isExternal is false”, while the versionRange field description says “May only be used if isExternal is true.” These are closely related, but it might be worth aligning the phrasing so the intent is unambiguous (and easier to carry into spec text later).

I'm open to improving the wording. Suggestions.

As far as the design goes. This issue passes the public RFC comment period.

@stevespringett

Copy link
Copy Markdown
Member Author

Actually, this was a defect, so no comment period was necessary.

@Mehrn0ush

Copy link
Copy Markdown
Contributor

I'm open to improving the wording. Suggestions.

Thanks — on wording, I think it really depends on whether we’re just clarifying the text or actually fixing the defect.

If we fix the defect and require isExternal whenever versionRange is present (i.e., versionRange is only valid with isExternal: true), then the current phrasing works well and could even be made more direct:

• Rule: “versionRange may only be used when isExternal is true.”
• Field: “May only be used if .isExternal is set to true.”

If, however, we keep the current behavior (where versionRange can appear without isExternal), then the wording should probably be softened to avoid implying stricter validation than the schema enforces, e.g.:

• “If versionRange is specified and isExternal is present, isExternal MUST be true.”

Happy to go with whichever direction makes the most sense here.

@jkowalleck

jkowalleck commented Aug 15, 2026

Copy link
Copy Markdown
Member

Your current approach makes the spec messy with all those constraints glued on the thing.
Better give them names and extend from a base, instead of using conditional restrictions.


alternative approach:

after

the current component should be split in

  • baseComponent - core properties of a component
  • serviceComponent - inherit from baseComponent and add service-specific properties and requirements
  • softwareComponent - inherit from baseComponent and add {application,framework,library,...}-specific properties and requirements
  • hardwareComponent - inherit from baseComponent and add service-specific properties and requirements
  • fileComponent - inherit from baseComponent and add file-specific properties and requirements
  • dataComponent - inherit from baseComponent and add data-specific properties and requirements
  • ... and so on - for each component type

after that split, the current component becomes a oneOf
[ { "$ref": "3$defs/serviceComponent" }, ... ]

put this idea into a ticket: #1024

"description": "Requirement: 'versionRange' must not be present when 'isExternal' is `false`.",
"if": {
"properties": { "isExternal": { "const": false } }
"required": ["versionRange"]

@jkowalleck jkowalleck Aug 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intention:
? Requirement: 'versionRange' must not be present when 'isExternal' is false.

before change:
if isExternal is set to false, then versionRange must not be present.
-or- only if isExternal is true, then versionRange may be set.

isExternal versionRange present result/valid per rule
0 0 1
0 1 0
1 0 1
1 1 1

after change:
if versionRange is set, then isExternal must be true
-or- only if isExternal is true, then versionRange may be set.

isExternal versionRange present result/valid per rule
0 0 1
0 1 0
1 0 1
1 1 1

while technically the "before" and "after" work the same, i find the "bnefore" better since it is more on the actual textuial description>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that "before" and "after" are logically equivalent as truth tables — that part isn't in question. My preference for "after" (versionRange present → isExternal must be true) is really about consistency: the versionRange field description already says "May only be used if isExternal is true," which is that same direction. Wording the rule description the other way just means a reader has to mentally contrapose one against the other to confirm they agree.

Both truth tables treat isExternal as strictly binary (true/false), but in the schema it can also be absent. The current if block keys on "isExternal": {"const": false}, which won't match a missing property — so an object with versionRange set and isExternal omitted entirely still passes validation today. That's the actual defect I was pointing at originally, not just the wording. Whichever phrasing we land on, I think the spec text and the schema logic both need to explicitly account for the "isExternal omitted" case, not just true/false.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CDX 2.0 related to release v2.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CycloneDX 2.0 - Constrain Properties to Applicable Types

4 participants