Skip to content

Commit a42eb8d

Browse files
bomly-guyclaude
andcommitted
docs: address review — ephemeral insertion discriminator, kind-preserving copies, SPDX link data
ADR-0036: the ID-keyed graph means same-detector contradicting records must coexist before consolidation classifies them, so the SDK insertion entry point assigns an ephemeral non-durable discriminator that consolidation finalizes (fold as gap, or replace with the durable facet) — the ephemeral form never reaches output or persistence. ADR-0037: the SDK ships a kind-preserving edge-copy/rename primitive so consolidation's edge rebuilds cannot flatten ingested relationship kinds to depends-on, with a guard test; and the per-entry carrier retains what source links require, captured at ingest — SPDX namespace plus document checksum, CycloneDX serial plus version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6a9aae6 commit a42eb8d

3 files changed

Lines changed: 25 additions & 5 deletions

File tree

dev-docs/SDK_MATURITY_PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Breaking in-process changes are allowed (v0 policy); the wire stays additive
141141
| 1.1 | `purlkit`: qualifiers/subpath-capable build/parse/canonicalize; the single purl-type ↔ ecosystem table (hex non-mapping recorded); `SplitEcosystemName`; canonical-ID rewrite entry point; import-boundary guard | v0.5.0 |
142142
| 1.2 | `spdxkit`: absorb `internal/licenseexpr` semantics (panic guards, `Valid`/`ValidateAll`/`Identifier`/`Compose`/`Satisfies`/`Extract`); deprecated-ID canonicalization via the audited replacement map relocated from `internal/sbom/transform.go` (the list marks deprecation; the map owns replacements); classification-by-validation; deterministic `LicenseRef-*` minting + extracted-text pairing; fix `matcherkit.NormalizeLicenseSet` to classify on write | v0.5.0 |
143143
| 1.3 | Identity (ADR-0036): facet definition, versioned content address, ecosystem-qualified `StableID` successor, `NewDependency` derivation; wire-compat fixtures extended | v0.6.0 |
144-
| 1.4 | Model fields (ADR-0037): supplier/originator/description/homepage; `ExternalReference`; `PackageLicense` declared/concluded + extracted text; digest-algorithm registry; set-aware scope ↔ CycloneDX mapping with its scalar projection rule; typed `DependencyEdge.Kind`; usage attribution (`PackageLocation` carries per-site scopes and relationship so the node-level union becomes derived; reachability becomes repeatable per-module-root evidence with the vulnerability annotation as derived summary — a conjunctive filter such as reachable ∧ runtime ∧ direct then joins evidence to locations within one module root, selecting one usage); per-`GraphEntry` document assertions; per-field-class merge helpers; boundary validation codecs + fuzz targets for every new parser | v0.7.0 |
144+
| 1.4 | Model fields (ADR-0037): supplier/originator/description/homepage; `ExternalReference`; `PackageLicense` declared/concluded + extracted text; digest-algorithm registry; set-aware scope ↔ CycloneDX mapping with its scalar projection rule; typed `DependencyEdge.Kind` with a kind-preserving edge-copy/rename primitive for graph reconstruction sites; usage attribution (`PackageLocation` carries per-site scopes and relationship so the node-level union becomes derived; reachability becomes repeatable per-module-root evidence with the vulnerability annotation as derived summary — a conjunctive filter such as reachable ∧ runtime ∧ direct then joins evidence to locations within one module root, selecting one usage); per-`GraphEntry` document assertions; per-field-class merge helpers; boundary validation codecs + fuzz targets for every new parser | v0.7.0 |
145145
| 1.5 | Metadata policy: document reserved `bomly.` prefix; deprecate `MetadataKeyDetectionLicenses` in favor of the typed license field | v0.7.0 |
146146

147147
Each SDK release is followed immediately by Dependabot-or-manual pin bumps in

dev-docs/adr/0036-dependency-identity-is-content-addressable.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,18 @@ facet from a node's own origin at creation would give the gap witness and
9393
the origin-bearing witness different IDs before consolidation ever ran,
9494
preventing the fold and duplicating nodes. So `NewDependency` derives the
9595
package-identity half only; the occurrence half defaults to empty, and the
96-
single consolidation entry point assigns non-default facets exactly to the
97-
records it has established as contradicting.
96+
single consolidation entry point assigns durable non-default facets exactly
97+
to the records it has established as contradicting. One mechanical
98+
consequence follows from the graph being keyed by ID: when a single
99+
detector emits two same-package records with different resolutions, both
100+
must coexist in the graph *before* consolidation can classify them — which
101+
is why today's `EnsureOccurrence` rewrites the second ID pre-insertion. The
102+
SDK insertion entry point therefore assigns an ephemeral, explicitly
103+
non-durable discriminator at insert time to keep contradicting records
104+
alive, and consolidation finalizes each one: fold it as a gap, or replace
105+
the ephemeral discriminator with the durable occurrence facet. The
106+
ephemeral form never appears in output or persistence — finalization
107+
happens before either.
98108

99109
**The readable ID.** `Dependency.ID` remains human-readable, because node IDs
100110
become CycloneDX bom-refs, SPDX element IDs, and `DependencyRefs` in scan

dev-docs/adr/0037-sbom-assertions-are-typed-sdk-model-fields.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,13 @@ each conjunct somewhere.
8585

8686
**Typed edges.** `DependencyEdge` gains an optional relationship kind
8787
(default depends-on; contains, describes, and the other members both formats
88-
need). Absence means depends-on, so the wire stays additive.
88+
need). Absence means depends-on, so the wire stays additive. Kinds must
89+
also survive graph reconstruction: consolidation rebuilds edges during
90+
identity rewrites and renames via `AddEdge(fromID, toID)`, which would
91+
silently flatten every ingested kind back to depends-on. The SDK therefore
92+
provides a kind-preserving edge-copy/rename primitive, every reconstruction
93+
site routes through it, and a guard test fails on kind-blind edge rebuilding
94+
in copy paths.
8995

9096
**A document-level carrier.** Document assertions (provenance, lifecycle,
9197
serial identity, primary-component assertions) attach to `GraphEntry`, next
@@ -107,7 +113,11 @@ defined, not implied: both formats give one document exactly one identity
107113
and one primary component, so a merged document *links* each source's
108114
identity rather than re-asserting it — SPDX `externalDocumentRefs`,
109115
CycloneDX an external reference of type `bom` carrying the source serial —
110-
while component-level assertions are preserved in full. The fixed-point
116+
while component-level assertions are preserved in full. The carrier retains
117+
what those link forms require, captured at ingest while the original bytes
118+
are still in hand: for SPDX, the source document namespace and a checksum
119+
over the original document (an `externalDocumentRef` is invalid without
120+
both); for CycloneDX, the source serial and version. The fixed-point
111121
round-trip promise is correspondingly scoped to single-source flows: one
112122
ingested document re-exported reproduces its own assertions; a merged
113123
export preserves component assertions and references its sources.

0 commit comments

Comments
 (0)