Skip to content

feat: add support for Bom.annotations - #1035

Open
saquibsaifee wants to merge 9 commits into
CycloneDX:mainfrom
saquibsaifee:feat-bom-annotations-10219399216754954684
Open

saquibsaifee wants to merge 9 commits into
CycloneDX:mainfrom
saquibsaifee:feat-bom-annotations-10219399216754954684

Conversation

@saquibsaifee

Copy link
Copy Markdown
Contributor

Description

Addresses one of the missing features outlined in #578 for full CycloneDX v1.5 schema support. This PR adds the Annotation and Annotator models and integrates them into Bom.annotations. It also includes a corresponding test fixture and updated snapshots.

AI Tool Disclosure

  • My contribution includes AI-generated content, as disclosed below:
    • AI Tools: Gemini Jules
    • LLMs and versions: Gemini 3.1 Pro

Affirmation

@saquibsaifee
saquibsaifee requested a review from a team as a code owner September 1, 2026 01:50
@codacy-production

codacy-production Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 65 complexity · 0 duplication

Metric Results
Complexity 65
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@read-the-docs-community

read-the-docs-community Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Adds Annotation and Annotator models and integrates them into
Bom.annotations. The property is gated with @serializable.view for
schema versions 1.5, 1.6, and 1.7.

- cyclonedx/model/annotation.py: new Annotator and Annotation classes
- cyclonedx/model/bom.py: replaces the TODO placeholder with a live
  annotations property
- tests/test_model_annotation.py: 8 unit tests, 100% coverage
- tests/_data/models.py: get_bom_with_annotations() fixture

Closes CycloneDX#578 (Missing annotations on Bom)

Signed-off-by: saquibsaifee <saquibsaifee2@gmail.com>
@saquibsaifee
saquibsaifee force-pushed the feat-bom-annotations-10219399216754954684 branch from cbcd7d3 to b9285d4 Compare September 1, 2026 16:48
- Wire get_bom_with_annotations() into the integration test fixtures
  by moving it before the all_get_bom_funct_* tuples so it is
  auto-discovered
- Add it to all_get_bom_funct_with_incomplete_deps (no dep graph set)
- Fix serialization: bom-ref uses json_name/xml_name decorators and
  bom_ref_from_str to avoid double-wrapping on deserialization
- Fix subjects: use _AnnotationSubject XML wrapper with a custom
  _SubjectsSerializationHelper that handles both JSON (plain strings)
  and XML (<subject ref='...'/> elements) correctly
- Generate all 14 snapshot binaries (JSON + XML for schema 1.0–1.7)

Signed-off-by: saquibsaifee <saquibsaifee2@gmail.com>
Both classes are stored in SortedSet so they require __lt__.
Adopt the _ComparableTuple pattern used by Vulnerability and
Service for __eq__, __lt__, and __hash__ consistency.

Also align _SubjectsSerializationHelper.deserialize return type
with xml_denormalize — both now return set[_AnnotationSubject].

Signed-off-by: saquibsaifee <saquibsaifee2@gmail.com>
@saquibsaifee

Copy link
Copy Markdown
Contributor Author

@jkowalleck does this look promising?

@saquibsaifee
saquibsaifee force-pushed the feat-bom-annotations-10219399216754954684 branch 2 times, most recently from aa83269 to 2f24862 Compare September 1, 2026 18:55
Replace getter/setter and eq/hash unit tests with sort and
invalid-construction tests, matching the pattern used by all
other test_model_*.py files.

Signed-off-by: saquibsaifee <saquibsaifee2@gmail.com>
@jkowalleck jkowalleck added enhancement New feature or request schema 1.5 labels Sep 15, 2026

jkowalleck commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

review done by Claude Fable 5.1, prompt: "do a review, comment your findings"


I checked out the branch, ran the new tests plus the output/deserialize suites, and probed the model manually (all annotator variants, multiple subjects, auto-generated bom-refs, JSON/XML round-trips, schema validation for 1.5/1.6/1.7, flake8/mypy). Overall this looks solid and the model matches the annotations definition of the 1.5–1.7 schemas (all required fields, oneOf annotator, bom-ref optional; signature omitted like everywhere else in the lib).

✅ What works

  • JSON & XML output for 1.5/1.6/1.7 validate against the bundled schemas, including annotator organization/individual/component/service, multiple subjects and annotations without bom-ref.
  • Annotations are correctly dropped for views < 1.5.
  • JSON ⇄ model ⇄ XML round-trips are equal when bom-refs are explicit.
  • xml_sequence(90) slots correctly between vulnerabilities (80) and the reserved formulation (100) / definitions (110).
  • flake8 + mypy are clean on the new module; Annotator enforces exactly-one at construction.

🔴 Should be addressed before merge

  1. docs/schema-support.rst not updated – line 53 still says bom.annotations | No. This PR flips that to "Yes".
  2. BomRefDiscriminator.from_bom() (cyclonedx/contrib/bom/utils.py) does not include annotations. It only gathers bom-refs from components, services and vulnerabilities. Consequently Annotation.bom_ref and the bom_ref of an Annotator.component / Annotator.service are neither de-duplicated nor assigned a value on output. Concretely: a BOM with a component bom-ref="dup" and an annotation bom-ref="dup" serializes without complaint (I verified this), yielding a document with non-unique bom-refs. Please extend from_bom() (and its docstring) to also yield a.bom_ref for bom.annotations, plus a.annotator.component.bom_ref / a.annotator.service.bom_ref where present (including nested components of the annotator component, if you want to be thorough).

🟡 Suggestions / design questions

  1. _AnnotationSubject leaks through the public API. Annotation.subjects is typed/returns SortedSet[_AnnotationSubject], so consumers iterating annotation.subjects get a non-public class and have to go through .ref to reach the BomRef; the setter also accepts the private type. Since XML needs <subject ref="…"/> while JSON wants a plain string list, a wrapper is understandable (the lib does something similar with Dependency), but maybe it should either be made public (e.g. AnnotationSubject) and documented, or hidden entirely by having the helper produce Elements in xml_normalize so the property can be SortedSet[BomRef]. Maintainers' call – just flagging the API surface.
  2. subjects may be empty. The schema does not enforce minItems, so this isn't a validity problem, but an annotation with no subjects is semantically meaningless. Consider at least documenting it, or validating like Annotator does.
  3. Annotator setters bypass the exactly-one invariant. Construction raises, but afterwards one can set all four to None (or two to non-None) and the object serializes fine → schema-invalid output. Other models in this lib have the same pattern, so this is consistent, but worth a note in the docstring or a check at serialization time.
  4. get_bom_with_annotations is listed in all_get_bom_funct_with_incomplete_deps unnecessarily. I removed it locally and test_deserialize_json/xml still pass; the fixture has no dependencies, so it doesn't belong in that "incomplete dependency graph" set.
  5. Test fixture could be more representative. The fixture annotates the non-existent ref subject-1 and only covers the organization annotator. Adding a second fixture (or extending this one) that points at an actual component/service and uses a component or individual annotator would make the snapshots exercise the view logic for nested models.
  6. Minor: Annotator.__repr__ returns <Annotator id=…>, which is not very helpful; e.g. <Annotator organization=… individual=… component=… service=…> would aid debugging. Also __lt__ on _AnnotationSubject reimplements str(a) < str(b) instead of delegating to BomRef.__lt__.

ℹ️ Not covered (fine to defer)

  • Bom.validate() does not check that subjects resolve to known bom-refs; dependencies get that check, annotations arguably could too, but it's not strictly required by the schema (subjects may also be BOM-Links).
  • BomRef subjects are not re-linked to the actual component BomRef instances on deserialization — same behaviour as Dependency, so consistent.

Nice work overall; items 1 and 2 are the ones I'd consider blocking.

- Update docs/schema-support.rst to mark bom.annotations as supported
- Extend BomRefDiscriminator.from_bom() to include annotations and annotator component/service bom-refs
- Refactor Annotation.subjects to use SortedSet[BomRef] directly, removing _AnnotationSubject
- Update Annotator.__repr__ to display all annotator fields
- Add docstrings explaining invariants on Annotator and Annotation.subjects
- Enrich get_bom_with_annotations fixture with component, service, complete dependencies, and multiple annotator variants
- Remove get_bom_with_annotations from all_get_bom_funct_with_incomplete_deps
- Update integration test snapshots for get_bom_with_annotations

Signed-off-by: Saquib Saifee <66195765+saquibsaifee@users.noreply.github.com>
@saquibsaifee
saquibsaifee force-pushed the feat-bom-annotations-10219399216754954684 branch from 327fb56 to 2cbcf92 Compare November 7, 2026 00:48
@saquibsaifee

Copy link
Copy Markdown
Contributor Author

@jkowalleck, have addressed the suggestions.

This branch has not been deployed

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

Labels

enhancement New feature or request schema 1.5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants