-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Motivation
When two or more SBOMs are merged, we currently:
- Identify “root” nodes (zero incoming edges) and attach each to the system object.
- Log any directed cycles, but leave them unconnected if they don’t include a root.
Unattached (“floating”) cycles can lead to incomplete graphs and missing relationships in downstream consumers.
Expected Behavior
- Detect each cycle in the merged graph.
- For any cycle that contains no root node, pick one representative node (e.g. at random) and create a
Contains(or configured) relationship from the system object to that node.
After this change, every component in the merged SBOM—whether part of an acyclic tree or a standalone cycle—will be reachable from the top‐level system.
Actual Behavior
# …inside merge():
roots = [n for n, deg in merged_sbom.graph.in_degree() if deg == 0]
logger.info(f"ROOT NODES: {roots}")
cycles = list(nx.simple_cycles(merged_sbom.graph))
if cycles:
logger.warning(f"SBOM CYCLE(S) DETECTED: {cycles}")
else:
logger.info("No cycles detected in SBOM graph")
# …no further handling of floating cycles
Floating cycles are merely logged; no relationships are added. This logic was implemented in PR #433
Steps to Reproduce
-
Create two minimal SBOM inputs that, when merged, form a directed cycle (A → B → C → A) and do not connect to any other component.
-
Run:
surfactant merge --add_system sbom1.json sbom2.json
-
Observe in the logs:
SBOM CYCLE(S) DETECTED: [['A','B','C']] -
Inspect the output SBOM: neither A, B, nor C is attached to the system object.
Proposed Fix
-
After detecting cycles, filter out any cycles that intersect the
rootslist. -
For each remaining (“floating”) cycle:
import random floating_cycles = [c for c in cycles if not any(node in roots for node in c)] for cycle in floating_cycles: # choose a random cycle member anchor = random.choice(cycle) merged_sbom.create_relationship( system_obj.UUID, anchor, system_relationship ) logger.info(f"Attached floating cycle {cycle} via node {anchor}")
-
Add unit tests in
tests/test_merge.pyto verify that both tree‐structured components and standalone cycles end up reachable from the system.
Additional Notes
- Allow deterministic seeding of
randomin tests for reproducibility. - Consider making the “anchor” selection configurable (e.g. always pick the first node) if determinism is preferred.
- Update documentation to reflect that all components—including cycles—will be connected to the system object.