Skip to content

SBOM: make group the parent's name, as CycloneDX defines the field - #193

Merged
villelaitila merged 3 commits into
softagram:mainfrom
villelaitila:feature/sbom-group-is-a-name
Aug 28, 2026
Merged

SBOM: make group the parent's name, as CycloneDX defines the field#193
villelaitila merged 3 commits into
softagram:mainfrom
villelaitila:feature/sbom-group-is-a-name

Conversation

@villelaitila

@villelaitila villelaitila commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Answers §7 of sbom-export-feedback.txt, which round 3 kept as-is and flagged
as contested and worth a second opinion. On re-examination the reporter is
right, and the case for changing is stronger than the one they made.

What they reported

group is /SaaS/AccessManagement rather than AccessManagement. 38
full-path values map 1:1 onto 38 bare names, the model has one root, and
elementPath and serialNumber are unique across all 689 documents, so the
path prefix disambiguates nothing while embedding the one thing that has
actually changed — the root renamed TalenomSoftwareSaaS in August.

Three claims, tested rather than assumed

1. The disambiguation is not observed anywhere. Round 3 measured three
estates (73→73, 78→78, 11→11). Seven more were measured for this change —
CrossTranslate, google_workspace_mcp, aikeittiostudio ×2, google-search-console-mcp,
df-softagram — all single-root, zero collisions. Ten estates, no instance of
the case the path guards against.

2. Our own test does not support the behaviour. The docstring of
test_inlined_mirror_is_told_apart_from_its_host_by_its_published_location calls
it "the case the feature exists for". Switching group to bare names and
updating only that test's expected strings, it passes: the two shared
components are separated by the parent names GroupA/GroupB, not by the paths.

3. The change is cheap. Full suite with the source change: 8 failures, every
one a string-representation assertion (assert 'GroupA' == '/OrgName/GroupA').
No behavioural break.

The argument the reporter did not make, which is the strongest one

CycloneDX defines group as "a shortened, single name of the company or project
that produced the component"
and says "whitespace and special characters should
be avoided"
. Examples: apache, org.apache.commons, apache.org. A
leading-slash path is not that.

And group is an identity field. Putting the estate root — the least stable
segment of the path — inside it means an organisational rename re-identifies
every component beneath it at once. That is not hypothetical: it fired across 689
documents in a live estate. The absence of benefit is what they measured; this is
the active harm.

Notably docs/data-formats.md already conceded both points as caveats — "the
first path segment is the estate root and is not stable", "group holds a path,
not a package namespace" — and kept the behaviour anyway. A caveat documenting a
known defect is a weaker position than removing the defect for eight string edits.

The cost, accepted knowingly

Two same-named groups under different roots now share a group value. That is
covered by a test which says so —
test_groups_sharing_a_name_under_different_roots_stay_distinguishable — and
shows identity does not rest on it: softagram:elementPath and the document
serial still separate them. group also does not feed the purl (verified: 0 of
2633 components on a real model), so purl-based identity is untouched.

Tests

Five new tests state why, not just what:

  • test_group_is_a_name_not_a_path / test_component_group_is_a_name_not_a_path — spec conformance
  • test_group_is_unchanged_by_an_estate_root_rename — the harm, failing before this change
  • test_the_estate_root_is_still_recoverable_after_the_change — nothing is lost
  • test_groups_sharing_a_name_under_different_roots_stay_distinguishable — the accepted collision

559 passed, 1 skipped. Verified on a real 77-document model: groups are now
softagram-engine / softagram-live / softagram-web-frontend, zero values
containing / or whitespace, elementPath unchanged at
/Softagram/softagram-engine/.github.

Downstream

This is a breaking output change for anyone parsing the path out of group; they
should read softagram:elementPath, which is unchanged. softagram-live#3993 is
already shipping a CycloneDX format change (specVersion 1.7 → 1.6), so landing
this in the same release costs consumers one migration instead of two.

Needs a release and a pin bump in softagram-live#3993 before it reaches anyone.

Migration note — one-time component re-keying

This change re-keys every internal component for consumers that identify
components by group + name + version — Dependency-Track does exactly
this for components without a purl. On the first upload after the upgrade,
group changes from the full parent path (/Estate/GroupA) to the bare
parent name (GroupA): vulnerability-audit history detaches from the old
identities and components may appear duplicated until the old entries are
cleaned up. This is a one-time cost, taken deliberately in the same customer
upgrade as the 1.16.0 SBOM fixes so integrations migrate once, not twice.

Consumers that parsed group for the element path must read the
softagram:elementPath property instead — it still carries the full path
verbatim and remains the identity-bearing field. Two same-named groups under
different roots now share a group value by design; softagram:elementPath
tells them apart. Consumers indexing a multi-SBOM response by group alone
must switch to that property for the same reason.

group carried the parent's full path so that two identically named groups
under different roots stayed apart. That guarded a collision no measured
estate has, at two costs every estate pays.

CycloneDX defines group as "a shortened, single name of the company or
project that produced the component" and asks that special characters be
avoided. `/Estate/AccessManagement` is neither, and a consumer rendering
group as a package coordinate shows a path.

The heavier cost is stability. The first path segment is the estate root,
the least stable part of the path, and group is an identity field for
consumers such as Dependency-Track. Renaming an estate therefore
re-identified every component beneath it at once -- observed in a live
estate across 689 documents when its root was renamed.

Against that, the disambiguation was not observed at all: across ten real
estates every full-path group mapped one-to-one onto its bare name. Nor did
it hold up in our own tests. The case documented as "the case the feature
exists for", test_inlined_mirror_is_told_apart_from_its_host_by_its_
published_location, passes unchanged with bare names, because what separates
its two 'shared' components is the parent names GroupA and GroupB, not the
paths.

The collision is now accepted knowingly and covered by a test that states
it: two same-named groups under different roots share a group value, and
softagram:elementPath and the document serial still tell them apart. The
full path is not lost -- elementPath publishes it verbatim, and consumers
who parsed it out of group should read it there.

docs/data-formats.md carried both problems as caveats rather than fixes;
those are replaced by a description of the trade actually made. The
2026-08-11 design spec is marked partly superseded rather than rewritten.
@softagram-bot

Copy link
Copy Markdown

Softagram Impact Report for pull/193 (head commit: 098141c)

TL;DR Arch. Impact: -3 | Changed code files: 2 | Directly impacted code files: 4

⭐ Change Overview

Showing the changed files, dependency changes and the impact - click for full size
(Open in Softagram Desktop for full details)

⭐ Details of Dependency Changes (diagram)

details of dependency changes - click for full size
(Open in Softagram Desktop for full details)

🤖 AGENTS - machine-readable impact data (2 files changed, 4 impacted, +11/-0 deps)

Change overview

Head 098141ce1787 vs base 7a77895a542b. 2 code files changed. 4 unchanged files directly depend on the changed files (see Impacted files). Dependencies: 11 added, 0 removed. New external components: 0. Removed external components: 0.

Added dependencies (11)

from to type roles signal
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/_estate sgraph/src> sgraph/sgraph.py/SGraph import test→prod expands test coverage
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_groups_sharing_a_name_under_different_roots_stay_distinguishable sgraph/src> sgraph/sgraph.py/SGraph import test→prod expands test coverage
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_component_group_is_a_name_not_a_path External/Python/sgraph/converters/sbom_cyclonedx_generator/generate_multi_from_sgraph import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_component_group_is_a_name_not_a_path External/Python/Usual dependencies import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_group_is_a_name_not_a_path External/Python/sgraph/converters/sbom_cyclonedx_generator/generate_multi_from_sgraph import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_group_is_a_name_not_a_path External/Python/Usual dependencies import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_group_is_unchanged_by_an_estate_root_rename External/Python/sgraph/converters/sbom_cyclonedx_generator/generate_multi_from_sgraph import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_groups_sharing_a_name_under_different_roots_stay_distinguishable External/Python/sgraph/converters/sbom_cyclonedx_generator/generate_multi_from_sgraph import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_the_estate_root_is_still_recoverable_after_the_change External/Python/sgraph/converters/sbom_cyclonedx_generator/generate_multi_from_sgraph import test→external external dependency
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_groups_sharing_a_name_under_different_roots_stay_distinguishable sgraph/tests/converters/sbom_cyclonedx_generator_test.py/find_property func_ref test→test regular
sgraph/tests/converters/sbom_cyclonedx_generator_test.py/test_the_estate_root_is_still_recoverable_after_the_change sgraph/tests/converters/sbom_cyclonedx_generator_test.py/find_property func_ref test→test regular

Removed dependencies (0)

None.

Impacted files (4)

Unchanged files that directly depend on files changed in this PR - check them for behavioral impact. Grouped by changed file; dependent paths starting with ./ are relative to the changed file's directory:

changed file directly impacted dependents
sgraph/src/sgraph/converters/sbom_cyclonedx_generator.py 4: ./external_identification.py, sgraph/src/sgraph/graphdataservice.py, sgraph/tests/converters/test_external_identification.py, sgraph/tests/converters/test_external_root_semantics.py

Complete data

[]

📄 Full report

Impact Report explained. Give feedback on this report to support@softagram.com

@villelaitila
villelaitila merged commit a529ecf into softagram:main Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants