Skip to content

Commit 192dc36

Browse files
adr for conceptual view
1 parent feb530a commit 192dc36

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed

docs/adrs/00002-analysis-graph.md

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# 00002. Analysis graph API in Trustify
2+
3+
Date: 2025-01-23
4+
5+
## Status
6+
7+
DRAFT
8+
9+
## Context
10+
11+
This ADR is an addenda to [previous ADR](00001-graph-analytics.md) as an attempt to clarify the differences between the graph
12+
relationships we capture and the view we want to create from the forest of graphs.
13+
14+
Ingesting an sbom captures a set of trustify relationships, which are instantiated
15+
in the forest of graphs as;
16+
17+
```mermaid
18+
graph TD
19+
PackageA -->|CONTAINS| PackageOther
20+
PackageD -->|CONTAINED_BY| PackageA
21+
PackageA -->|DEPENDS_ON| PackageB
22+
PackageB -->|DEPENDENCY_OF| PackageA
23+
SBOMDOC1 -->|DESCRIBES| PackageA
24+
UpstreamComponent -->|ANCESTOR_OF| PackageA
25+
image.arch1 -->|VARIANT_OF| ImageIndex1
26+
image.arch2 -->|VARIANT_OF| ImageIndex1
27+
SBOMDOC2 -->|DESCRIBES| ImageIndex1
28+
29+
SBOMDOC3 -->|DESCRIBES| srpm_component
30+
binarycomponent1 -->|GENERATED_FROM| srpm_component
31+
binarycomponent2 -->|GENERATED_FROM| srpm_component
32+
```
33+
34+
Trustify relationships attempt to put an abstraction over relationships
35+
defined by any format of sbom (eg. cyclonedx, spdx).
36+
37+
This graph encapsulates provenance of sbom relationship though end users are unlikely
38+
to directly navigate such graphs, as it would mean forcing concrete understanding of relationship directionality.
39+
Even if we were to _normalise_ all relationships (similar to the rewrite of all CONTAINS into CONTAINED_BY) the
40+
resultant tree is still not quite right ... it is a good practice to keep logical model separate and not try to
41+
overload that model to serve as conceptual model.
42+
43+
The `api/v2/analysis` endpoints are responsible for building up the conceptual view. Where we want to query, filter and
44+
traverse on the following.
45+
46+
```mermaid
47+
graph TD
48+
SBOMDOC1 -->|DESCRIBES| PackageA
49+
PackageA -->|CONTAINS| PackageOther
50+
PackageA -->|CONTAINS| PackageD
51+
PackageA -->|DEPENDS| PackageB
52+
53+
SBOMDOC2 -->|DESCRIBES| ImageIndex1
54+
UpstreamComponent -->|ANCESTOR_OF| PackageA
55+
ImageIndex1 -->|VARIANT| image.arch1
56+
ImageIndex1 -->|VARIANT| image.arch2
57+
58+
SBOMDOC3 -->|DESCRIBES| srpm_component
59+
srpm_component -->|GENERATES| binarycomponent1
60+
srpm_component -->|GENERATES| binarycomponent2
61+
```
62+
63+
It is a feature that this conceptual model spans beyond traversal of just transitive software dependencies.
64+
65+
For example, searching for any node in above view, should let us traverse ancestors and descendents ... a
66+
few illustrative examples:
67+
68+
**Search for 'PackageA'**
69+
* component ancestors would be `[UpstreamComponent]`
70+
* component descendents would be the tree underneath 'PackageA' `[PackageOther,PackageD,PackageB]`
71+
72+
**Search for 'image.arch1'**
73+
* component ancestors would be `[ImageIndex1]`
74+
* component descendents would be `[]`
75+
76+
_Note: every node in the graph already knows its relationship to original SBOM so no need
77+
to enumerate DESCRIBES relationship ... though in the future we may see other artifacts (eg. sbom)
78+
DESCRIBES._
79+
80+
We should make it easy to visualise this conceptual model direct from the endpoints (ex. Accept: image/svg
81+
would pull down an svg representation).
82+
83+
## Decision
84+
85+
* Implement `api/v2/analysis/component`
86+
87+
payload returns immediate ancestor/descdendent relations (eg. 'one deep')
88+
```json
89+
{
90+
"sbom_id": "",
91+
"node_id": "",
92+
"purl": [
93+
""
94+
],
95+
"cpe": [],
96+
"name": "PackageA",
97+
"version": "",
98+
"published": "2024-12-19 18:04:12+00",
99+
"document_id": "urn:uuid:537c8dc3-6f66-3cac-b504-cc5fb0a09ece",
100+
"product_name": "",
101+
"product_version": "",
102+
"ancestor": [
103+
{
104+
"sbom_id": "",
105+
"node_id": "",
106+
"relationship": "AncestorOf",
107+
"purl": [
108+
""
109+
],
110+
"cpe": [],
111+
"name": "UpstreamPackage",
112+
"version": ""
113+
}
114+
],
115+
"descendent": [
116+
{
117+
"sbom_id": "",
118+
"node_id": "",
119+
"relationship": "Variant",
120+
"purl": [
121+
""
122+
],
123+
"cpe": [],
124+
"name": "PackageC",
125+
"version": ""
126+
}
127+
]
128+
}
129+
}
130+
```
131+
132+
Where items in `ancestor` array imply _Ancestor component_ VIEWRELATION _Searched component_ ... in the above example that would
133+
mean _UpstreamPackage_ **AncestorOF** _PackageA_
134+
135+
Where items in `descendent` array imply _Searched component_ VIEWRELATION _Descendent component_ ... in the above example that would
136+
mean _PackageA_ **AncestorOF** _PackageC_
137+
138+
139+
* Implement `api/v2/analysis/ancestor`
140+
payload returns all ancestor relations
141+
```json
142+
{
143+
"sbom_id": "",
144+
"node_id": "",
145+
"purl": [
146+
""
147+
],
148+
"cpe": [],
149+
"name": "",
150+
"version": "",
151+
"published": "2024-12-19 18:04:12+00",
152+
"document_id": "urn:uuid:537c8dc3-6f66-3cac-b504-cc5fb0a09ece",
153+
"product_name": "",
154+
"product_version": "",
155+
"ancestor": [
156+
{
157+
"sbom_id": "",
158+
"node_id": "",
159+
"relationship": "ANCESTOR_OF",
160+
"purl": [
161+
""
162+
],
163+
"cpe": [],
164+
"name": "",
165+
"version": ""
166+
}, {} ....
167+
]
168+
}
169+
```
170+
171+
The `ancestor` array contains a list of ancestors with the last item
172+
in the list being considered the _root component_.
173+
174+
* Implement `api/v2/analysis/descendent`
175+
returns all descendent relations
176+
```json
177+
{
178+
"sbom_id": "",
179+
"node_id": "",
180+
"purl": [
181+
""
182+
],
183+
"cpe": [],
184+
"name": "",
185+
"version": "",
186+
"published": "2024-12-19 18:04:12+00",
187+
"document_id": "urn:uuid:537c8dc3-6f66-3cac-b504-cc5fb0a09ece",
188+
"product_name": "",
189+
"product_version": "",
190+
"descendent": [
191+
{
192+
"sbom_id": "",
193+
"node_id": "",
194+
"relationship": "Variant",
195+
"purl": [
196+
""
197+
],
198+
"cpe": [],
199+
"name": "",
200+
"version": "",
201+
"descendent": [{} ....]
202+
}, {} ....
203+
]
204+
}
205+
```
206+
The `descendent` array contains a list of descendents with each component also containing any nested descendents.
207+
208+
* Implement `api/v2/analysis/relationship`
209+
raw endpoint for querying relations
210+
```json
211+
TBA
212+
```
213+
214+
* Document analysis graph API
215+
216+
217+
## Alternative approaches
218+
219+
**Directly use graphs:** It is likely that we will provide raw interface to the graphs (aka `api/v2/analysis/relationship`) though
220+
we do not want to move responsibility of building up the 'view' to a client so still need to provide the other endpoints for that.
221+
222+
**Build a new graph representing the conceptual model:** As graphs do not mutate, its not so far fetched to
223+
consider additionally generating a conceptual graph. It might be something we consider as an optimisation in
224+
the future though for now thinking it would be good to avoid the cost (ram, memory). The conceptual graph model might be
225+
considered a replacement for logical model though that would be flawed thinking as we always need the logical
226+
model to tell us relationship provenance eg. the logical model is absolutely required.
227+
228+
## Consequences
229+
230+
* I would rather not create a whole new set of View relationships (as I have outlined above) ... maybe there is a way to
231+
present relationship not as a pure scalar
232+
* Having a clear conceptual model will reduce cognitive load of having to mentally reparse graph relations
233+
* Align conceptual model means we can also do neat stuff like generate visual representations (mermaid, svg, etc)

0 commit comments

Comments
 (0)