Change-Trace JSON Schema Contract
Version: 1.0.0 Status: Implemented Last Updated: 2026-05-30
Overview
This document defines the JSON schema for StellaOps Change-Trace artifacts (.cdxchange.json) — the deterministic record of what changed between two scans or two binaries, and how that change moves trust. All implementations must conform to this schema for interoperability.
Audience: Scanner engineers producing Change-Trace artifacts, CLI/UI consumers reading them, and integrators embedding them into CycloneDX BOMs. For the scoring math behind the trustDelta/summary fields, see the Change-Trace Trust-Delta Contract.
The canonical implementation lives in StellaOps.Scanner.ChangeTrace:
- Models:
src/Scanner/__Libraries/StellaOps.Scanner.ChangeTrace/Models/(ChangeTrace,PackageDelta,SymbolDelta,ByteDelta,TrustDelta,ChangeTraceSummary). - Serialization:
Serialization/ChangeTraceSerializer.cs. - Validation:
Validation/ChangeTraceValidator.cs. - CycloneDX embedding/export:
CycloneDx/ChangeTraceEvidenceExtension.cs. - CLI:
src/Cli/StellaOps.Cli/Commands/ChangeTraceCommandGroup.cs.
Naming/casing note (grounded in
ChangeTraceSerializer): the canonical serializer appliesJsonNamingPolicy.CamelCaseto both property names andJsonStringEnumConverter. Therefore enum values are emitted in camelCase on the wire (e.g.upgraded,riskDown,vendorBackport,reduced,down). The C# enum members are PascalCase (e.g.Upgraded,RiskDown); the deserializer is case-insensitive, so hand-authored fixtures using PascalCase values are accepted on read but are not what the serializer produces.
Schema Identifier
Schema identifier: stella.change-trace/1.0 (root "schema" field; const ChangeTrace.SchemaVersion)
File Extension: .cdxchange.json (default JSON export extension; ChangeTraceCommandGroup)
The MIME type
application/vnd.stella.change-trace+jsonand the$schema/https://stella-ops.org/schemas/...JSON-Schema URL referenced in earlier drafts are not present in the implementation. The root model (ChangeTrace.cs) has no$schemaproperty and no published JSON Schema document; the only authoritative identifier is theschemastring field.
Root Object
Grounded in Models/ChangeTrace.cs (ChangeTrace record).
{
"schema": "stella.change-trace/1.0",
"subject": { ... },
"basis": { ... },
"deltas": [ ... ],
"summary": { ... },
"commitment": { ... },
"attestation": { ... }
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
schema | string | Yes | Schema version identifier. Defaults to stella.change-trace/1.0 (ChangeTrace.SchemaVersion). |
subject | object | Yes | Subject artifact being compared. |
basis | object | Yes | Analysis basis and configuration (scan IDs, policies, diff methods, engine version, analyzedAt). |
deltas | array | Yes (may be empty) | Array of package deltas. Sorted by purl (ordinal) on serialization. |
summary | object | Yes | Aggregated summary metrics. |
commitment | object | No | RFC 8785 + SHA-256 commitment hash. Omitted when null. |
attestation | object | No | Reference to a DSSE attestation. Omitted when null. |
Note: there is no top-level
analyzedAtoralgorithmVersionfield. The analysis timestamp lives atbasis.analyzedAt; the engine version lives atbasis.engineVersion.
Commitment Object
Grounded in ChangeTraceCommitment (Models/ChangeTrace.cs) and ChangeTraceSerializer.ComputeCommitmentHash.
| Property | Type | Required | Description |
|---|---|---|---|
sha256 | string | Yes | SHA-256 hash of the canonical JSON, computed with the commitment and attestation fields removed. |
algorithm | string | No | Defaults to RFC8785+SHA256. |
Attestation Object
Grounded in ChangeTraceAttestationRef (Models/ChangeTrace.cs).
| Property | Type | Required | Description |
|---|---|---|---|
predicateType | string | Yes | Predicate type of the DSSE attestation. |
envelopeDigest | string | No | Digest of the DSSE envelope. |
Subject Object
Grounded in ChangeTraceSubject (Models/ChangeTrace.cs).
{
"type": "oci.image",
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"purl": "pkg:oci/debian/bookworm@sha256:e3b0c442...",
"name": "debian:bookworm"
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Artifact type: oci.image, binary, or package. |
digest | string | Yes | Digest of the artifact (e.g., sha256:...). |
purl | string | No | Package URL, if applicable. |
name | string | No | Human-readable artifact name. |
The
imageRef,fromDigest, andtoDigestfields from earlier drafts do not exist. The single subjectdigestidentifies the artifact; the “before”/“after” linkage is expressed viabasis.fromScanId/basis.toScanIdbelow.
Basis Object
Grounded in ChangeTraceBasis (Models/ChangeTrace.cs). Carries the analysis configuration and the before/after linkage.
{
"scanId": "scan-20260112-backport-libssl",
"fromScanId": "scan-20260111-libssl-3.0.9-1+deb12u2",
"toScanId": "scan-20260112-libssl-3.0.9-1+deb12u3",
"policies": ["lattice:default@v3"],
"diffMethod": ["pkg", "symbol"],
"engineVersion": "1.0.0",
"engineDigest": "sha256:...",
"analyzedAt": "2026-01-12T10:00:00+00:00"
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
scanId | string | Yes | Primary scan identifier for this comparison. |
fromScanId | string | No | Scan ID of the “before” state. |
toScanId | string | No | Scan ID of the “after” state. |
policies | array<string> | No | Lattice policies applied during analysis. |
diffMethod | array<string> | No | Diff methods used: pkg, symbol, byte. |
engineVersion | string | Yes | Version of the engine that produced this trace. |
engineDigest | string | No | Digest of the engine binary/source for reproducibility. |
analyzedAt | string | Yes | ISO 8601 timestamp when analysis was performed. |
PackageDelta Object
Grounded in PackageDelta (Models/PackageDelta.cs).
{
"scope": "pkg",
"purl": "pkg:deb/debian/libssl3@3.0.9-1+deb12u3?distro=debian-12",
"name": "libssl3",
"fromVersion": "3.0.9-1+deb12u2",
"toVersion": "3.0.9-1+deb12u3",
"changeType": "upgraded",
"explain": "vendorBackport",
"evidence": { ... },
"trustDelta": { ... },
"symbolDeltas": [ ... ],
"byteDeltas": [ ... ]
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
scope | string | Yes | Always pkg for package deltas. |
purl | string | Yes | Package URL identifying the package. |
name | string | Yes | Human-readable package name. |
fromVersion | string | Yes | Version before the change. |
toVersion | string | Yes | Version after the change. |
changeType | enum | Yes | Type of change (see ChangeType Enum). |
explain | enum | Yes | Explanation category (see Explanation Enum). |
evidence | object | Yes | Evidence supporting the change classification. |
trustDelta | object | No | Trust impact calculation (null when not computed). |
symbolDeltas | array | No | Symbol-level deltas (sorted by name on serialization). |
byteDeltas | array | No | Byte-level deltas (sorted by offset on serialization). |
Field names corrected from earlier drafts: the nested arrays are
symbolDeltas/byteDeltas(notsymbols/bytes),nameandexplainandevidenceare required,fromVersion/toVersionare required (not nullable), andtrustDeltais optional (not required).
ChangeType Enum
Grounded in PackageChangeType (Models/PackageDelta.cs). Serialized camelCase.
| Value (wire) | C# member | Description |
|---|---|---|
added | Added | Package added in target. |
removed | Removed | Package removed in target. |
modified | Modified | Package modified (general change). |
upgraded | Upgraded | Version upgraded. |
downgraded | Downgraded | Version downgraded. |
rebuilt | Rebuilt | Rebuilt without version change. |
There is no
unchangedorpatchedmember inPackageChangeType(those existed only in earlier drafts). A general change ismodified; security-patch intent is conveyed byexplain: securityPatchand by the symbol-levelpatchedchange type, not by the packagechangeType.
Explanation Enum
Grounded in PackageChangeExplanation (Models/PackageDelta.cs). Carried in the explain field. Serialized camelCase.
| Value (wire) | C# member | Description |
|---|---|---|
vendorBackport | VendorBackport | Vendor backport of upstream fixes. |
upstreamUpgrade | UpstreamUpgrade | Standard upstream version upgrade. |
securityPatch | SecurityPatch | Security patch applied. |
rebuild | Rebuild | Rebuilt without source changes. |
flagChange | FlagChange | Compilation flags / build options changed. |
newDependency | NewDependency | New dependency added. |
removedDependency | RemovedDependency | Dependency removed. |
unknown | Unknown | Change reason could not be determined. |
PackageDeltaEvidence Object
Grounded in PackageDeltaEvidence (Models/PackageDelta.cs). Required on every package delta.
{
"patchIds": ["DSA-5678-1"],
"cveIds": ["CVE-2026-12345"],
"symbolsChanged": 1,
"bytesChanged": 512,
"functions": ["ssl3_get_record"],
"verificationMethod": "CFGHash",
"confidence": 0.98
}
| Property | Type | Required | Description |
|---|---|---|---|
patchIds | array<string> | No | Patch identifiers associated with the change. |
cveIds | array<string> | No | CVE identifiers addressed by the change. |
symbolsChanged | integer | No | Number of symbols changed in this package. |
bytesChanged | integer (int64) | No | Total bytes changed in this package. |
functions | array<string> | No | Function names affected. |
verificationMethod | string | No | Method used to verify the change. |
confidence | number | No | Confidence for the classification (0.0–1.0). |
SymbolDelta Object
Grounded in SymbolDelta (Models/SymbolDelta.cs).
{
"scope": "symbol",
"name": "ssl3_get_record",
"changeType": "patched",
"fromHash": "sha256:abc123",
"toHash": "sha256:def456",
"sizeDelta": 64,
"cfgBlockDelta": 2,
"similarity": 0.98,
"confidence": 0.95,
"matchMethod": "CFGHash",
"explanation": "Security patch for buffer overflow",
"matchedChunks": [0, 1, 2, 5, 6]
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
scope | string | Yes | Always symbol for symbol deltas. |
name | string | Yes | Symbol/function name. |
changeType | enum | Yes | Type of symbol change. |
fromHash | string | No | Hash of the symbol in the “before” state. |
toHash | string | No | Hash of the symbol in the “after” state. |
sizeDelta | integer | No | Size change in bytes. |
cfgBlockDelta | integer | No | CFG basic-block count change (nullable). |
similarity | number | No | Similarity score between before/after (0.0–1.0). |
confidence | number | No | Match confidence (0.0–1.0). |
matchMethod | string | No | Match method, e.g. CFGHash, InstructionHash, SemanticHash. |
explanation | string | No | Human-readable explanation. |
matchedChunks | array<integer> | No | Indices of matched instruction chunks. |
Field name corrected: the symbol name field is
name(notsymbolName). The model also addsscope,fromHash,toHash,similarity, andmatchedChunks, which earlier drafts omitted.
SymbolChangeType Enum
Grounded in SymbolChangeType (Models/SymbolDelta.cs). Serialized camelCase.
| Value (wire) | C# member | Description |
|---|---|---|
unchanged | Unchanged | No change detected. |
added | Added | Symbol added in target. |
removed | Removed | Symbol removed in target. |
modified | Modified | Symbol modified (general). |
patched | Patched | Security/bug-fix patch detected. |
ByteDelta Object
Grounded in ByteDelta (Models/ByteDelta.cs).
{
"scope": "byte",
"offset": 4096,
"size": 2048,
"fromHash": "sha256:abc123...",
"toHash": "sha256:def456...",
"section": ".text",
"context": "Function: ssl3_get_record"
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
scope | string | Yes | Always byte for byte deltas. |
offset | integer (int64) | Yes | Byte offset where the change begins. |
size | integer | Yes | Size of the changed region in bytes. |
fromHash | string | Yes | Rolling hash of the “before” bytes. |
toHash | string | Yes | Rolling hash of the “after” bytes. |
section | string | No | Binary section name (e.g., .text, .data). |
context | string | No | Optional context description. |
Privacy Note: Raw byte content is never included; only hashes are stored.
TrustDelta Object
Grounded in TrustDelta (Models/TrustDelta.cs) and Scoring/TrustDeltaCalculator.cs.
{
"reachabilityImpact": "reduced",
"exploitabilityImpact": "down",
"score": -0.27,
"beforeScore": 0.85,
"afterScore": 0.62,
"proofSteps": [
"CVE-2026-12345 affects ssl3_get_record",
"Function patched in 3.0.9-1+deb12u3",
"CFG match: 0.98 similarity",
"Reachable call paths: 3 -> 0 after patch"
]
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
reachabilityImpact | enum | Yes | Impact on code reachability. |
exploitabilityImpact | enum | Yes | Impact on exploitability. |
score | number | Yes | Trust delta value (-1.0 to +1.0). Negative = risk reduction, positive = risk increase. Rounded to 2 decimals by the calculator. |
beforeScore | number | No | Trust score before the change (nullable). |
afterScore | number | No | Trust score after the change (nullable). |
proofSteps | array<string> | No | Human-readable proof steps; generation order is preserved. |
Corrected from earlier drafts:
beforeScore,afterScore, andproofStepsare optional (nullable / default-empty), not required. Score sign semantics perTrustDeltaCalculator:delta = (beforeTrust - afterTrust) / max(beforeTrust, minDenom), so a negative score means risk went down.
ReachabilityImpact Enum
Grounded in ReachabilityImpact (Models/TrustDelta.cs). Serialized camelCase.
| Value (wire) | C# member | Description |
|---|---|---|
unchanged | Unchanged | No change in reachability. |
reduced | Reduced | Fewer reachable paths. |
increased | Increased | More reachable paths. |
eliminated | Eliminated | All vulnerable paths eliminated. |
introduced | Introduced | New reachable paths introduced. |
ExploitabilityImpact Enum
Grounded in ExploitabilityImpact (Models/TrustDelta.cs). Serialized camelCase.
| Value (wire) | C# member | Description |
|---|---|---|
unchanged | Unchanged | No change in exploitability. |
down | Down | Exploitability decreased. |
up | Up | Exploitability increased. |
eliminated | Eliminated | Vulnerability eliminated. |
introduced | Introduced | New vulnerability introduced. |
Summary Object
Grounded in ChangeTraceSummary (Models/ChangeTraceSummary.cs).
{
"changedPackages": 1,
"changedSymbols": 1,
"changedBytes": 512,
"riskDelta": -0.27,
"verdict": "riskDown",
"beforeRiskScore": 0.85,
"afterRiskScore": 0.62
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
changedPackages | integer | Yes | Total packages with changes. |
changedSymbols | integer | Yes | Total symbols with changes. |
changedBytes | integer (int64) | Yes | Total bytes changed across all packages. |
riskDelta | number | Yes | Aggregated risk delta score. |
verdict | enum | Yes | Overall verdict (see Verdict Values). |
beforeRiskScore | number | No | Risk score before changes (nullable). |
afterRiskScore | number | No | Risk score after changes (nullable). |
Field names corrected from earlier drafts: counts are
changedPackages/changedSymbols/changedBytes(notpackagesChangedetc.); the aggregate score field isriskDelta(nottrustDelta); the verdict field isverdict(notoverallVerdict). There are nopackagesAdded/packagesRemovedfields in the summary.
Verdict Values
Grounded in ChangeTraceVerdict (Models/ChangeTraceSummary.cs) and ChangeTraceBuilder.ComputeVerdict(riskDelta). Serialized camelCase.
| Value (wire) | C# member | Risk Delta Range | Description |
|---|---|---|---|
riskDown | RiskDown | < -0.3 | Risk decreased significantly. |
neutral | Neutral | -0.3 ≤ delta ≤ 0.3 | No significant risk change. |
riskUp | RiskUp | > 0.3 | Risk increased significantly. |
inconclusive | Inconclusive | N/A | Unable to determine (enum value exists; not produced by ComputeVerdict, which returns only the three threshold-based verdicts). |
Full Example
Based on the golden fixture src/Scanner/__Tests/StellaOps.Scanner.ChangeTrace.Tests/Golden/backport_libssl.json. Enum values shown in the camelCase form produced by the canonical serializer (the fixture itself stores PascalCase, which the case-insensitive deserializer also accepts).
{
"schema": "stella.change-trace/1.0",
"subject": {
"type": "oci.image",
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"purl": "pkg:oci/debian/bookworm@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"name": "debian:bookworm"
},
"basis": {
"scanId": "scan-20260112-backport-libssl",
"fromScanId": "scan-20260111-libssl-3.0.9-1+deb12u2",
"toScanId": "scan-20260112-libssl-3.0.9-1+deb12u3",
"policies": ["lattice:default@v3"],
"diffMethod": ["pkg", "symbol"],
"engineVersion": "1.0.0",
"analyzedAt": "2026-01-12T10:00:00+00:00"
},
"deltas": [
{
"scope": "pkg",
"purl": "pkg:deb/debian/libssl3@3.0.9-1+deb12u3?distro=debian-12",
"name": "libssl3",
"fromVersion": "3.0.9-1+deb12u2",
"toVersion": "3.0.9-1+deb12u3",
"changeType": "upgraded",
"explain": "vendorBackport",
"evidence": {
"patchIds": ["DSA-5678-1"],
"cveIds": ["CVE-2026-12345"],
"symbolsChanged": 1,
"bytesChanged": 512,
"functions": ["ssl3_get_record"],
"verificationMethod": "CFGHash",
"confidence": 0.98
},
"trustDelta": {
"reachabilityImpact": "reduced",
"exploitabilityImpact": "down",
"score": -0.27,
"beforeScore": 0.85,
"afterScore": 0.62,
"proofSteps": [
"CVE-2026-12345 affects ssl3_get_record",
"Function patched in 3.0.9-1+deb12u3",
"CFG match: 0.98 similarity",
"Reachable call paths: 3 -> 0 after patch"
]
},
"symbolDeltas": [
{
"scope": "symbol",
"name": "ssl3_get_record",
"changeType": "patched",
"fromHash": "sha256:abc123",
"toHash": "sha256:def456",
"sizeDelta": 64,
"cfgBlockDelta": 2,
"similarity": 0.98,
"confidence": 0.95,
"matchMethod": "CFGHash",
"explanation": "Security patch for buffer overflow",
"matchedChunks": [0, 1, 2, 5, 6]
}
],
"byteDeltas": []
}
],
"summary": {
"changedPackages": 1,
"changedSymbols": 1,
"changedBytes": 512,
"riskDelta": -0.27,
"verdict": "riskDown",
"beforeRiskScore": 0.85,
"afterRiskScore": 0.62
},
"commitment": {
"sha256": "<computed RFC8785+SHA256 hash>",
"algorithm": "RFC8785+SHA256"
}
}
Serialization Rules
Grounded in ChangeTraceSerializer (which delegates canonicalization to StellaOps.Canonical.Json.CanonJson) and ChangeTraceSerializer.SortTrace / SortPackageDelta.
- Key ordering: Object member keys are emitted in ordinal (UTF-16 code-unit) order by
CanonJson.WriteElementSortedafter NFC normalization — not the host language’s locale-aware “alphabetical” order. This is the RFC 8785 member-ordering rule. - Array ordering (applied by
ChangeTraceSerializer.SortTrace):deltas: Sorted bypurlusingStringComparer.Ordinal.symbolDeltas: Sorted bynameusingStringComparer.Ordinal.byteDeltas: Sorted byoffset(numeric ascending).proofSteps: Generation order preserved (not sorted).
- Number formatting: Per
CanonJsonnumber normalization (e.g.-0is written as0). - String escaping:
UnsafeRelaxedJsonEscapingon the System.Text.Json options; final canonical bytes follow RFC 8785 viaCanonJson.Canonicalize. - Null handling:
DefaultIgnoreCondition = WhenWritingNull— null-valued optional properties (commitment,attestation,purl, etc.) are omitted entirely. - Timestamps:
DateTimeOffsetserialized by System.Text.Json (round-trip “O” form). The golden fixtures use offset form (2026-01-12T10:00:00+00:00).
RFC 8785 Compliance
All JSON output must conform to RFC 8785 (JSON Canonicalization Scheme) for digest computation.
Validation
JSON Schema
No published JSON Schema document exists. The
https://stella-ops.org/schemas/change-trace/1.0/schema.jsonURL referenced in earlier drafts is not implemented — there is no$schemaproperty on the model and no schema file is served. Validation is performed programmatically byChangeTraceValidator(below); the only authoritative identifier is theschemastring field (stella.change-trace/1.0).
Validation Command
stella change-trace verify <file>
Grounded in ChangeTraceCommandGroup.BuildVerifyCommand. Takes a positional file argument (any path; .cdxchange.json by convention) and an optional --strict flag (fail when any warnings are present). Exit codes follow ChangeTraceExitCodes (see CLI section).
Required Fields (validator errors)
Grounded in ChangeTraceValidator. A missing/empty value for any of the following produces an error (invalidates the trace):
schemasubject(object),subject.type,subject.digestbasis(object),basis.scanIdsummary(object);changedPackages/changedSymbols/changedBytesmust each be non-negative- each delta’s
purl(whendeltasis non-empty) commitment.sha256(only when acommitmentobject is present)
An empty deltas array is valid (means “no changes”).
The following produce warnings only (do not invalidate): non-standard schema prefix, subject.digest without an algorithm prefix, missing basis.engineVersion, missing basis.analyzedAt, missing basis.diffMethod, out-of-range riskDelta / delta confidence / trustDelta.score, a delta with neither fromVersion nor toVersion, and a missing commitment.algorithm.
The
subject.imageRef,subject.fromDigest,subject.toDigest, and top-levelanalyzedAtfields listed in earlier drafts as required do not exist in the model or the validator.
CLI
Grounded in src/Cli/StellaOps.Cli/Commands/ChangeTraceCommandGroup.cs. The change-trace command group exposes three subcommands:
stella change-trace build
Builds a change trace comparing two scans or two binary files.
| Option | Alias | Required | Description |
|---|---|---|---|
--from | Yes | Source scan ID or binary file path. | |
--to | Yes | Target scan ID or binary file path. | |
--include-byte-diff | No | Include byte-level diffing (slower). | |
--output | -o | No | Output file path (default: stdout). |
--format | -f | No | json (default), table, or summary. |
If both --from and --to resolve to existing files, a binary comparison is performed; otherwise they are treated as scan IDs. The exit code reflects the verdict: RiskDown/Neutral -> Success (0), RiskUp -> RiskUp (2), anything else -> Inconclusive (3).
stella change-trace export
Exports an existing change-trace JSON file in another format.
| Option | Alias | Required | Description |
|---|---|---|---|
--input | -i | Yes | Input change-trace JSON file. |
--format | -f | No | json (default), cyclonedx, bundle. |
--output | -o | No | Output file path. |
--cdx-embedded | No | Embed in a CycloneDX BOM as a change-trace extension. | |
--cdx-bom | No | Existing CycloneDX BOM to embed the trace into. |
Default output filename when --output is omitted: trace-export.cdxchange.json for plain JSON export, and trace-export.cdx.json for CycloneDX export.
stella change-trace verify
Validates a change-trace file (see Validation Command above).
Exit Codes
Grounded in ChangeTraceExitCodes:
| Code | Name | Meaning |
|---|---|---|
| 0 | Success | Success, or RiskDown/Neutral verdict. |
| 1 | Error | General error. |
| 2 | RiskUp | RiskUp verdict (trust delta indicates increased risk). |
| 3 | Inconclusive | Unable to determine verdict. |
| 4 | FileNotFound | Input file not found. |
| 5 | ValidationFailed | Validation failed / invalid JSON. |
| 6 | ServiceUnavailable | Service not available. |
CycloneDX Integration
Grounded in CycloneDx/ChangeTraceEvidenceExtension.cs and ChangeTraceEvidenceOptions. Options: IncludeProofSteps (default true), IncludeSymbolDeltas (default true), IncludeByteDeltas (default false), MaxDeltas (default 100), SpecVersion (default "1.7").
Corrected from earlier drafts. The extension is not attached to a per-component
evidence.extensionsarray, theextensionTypeisstella.change-trace(a dot, not a hyphen), and the payload lives under achangeTraceobject (notextension). The embeddedchangeTraceobject mirrors the trace structure (schema/subject/basis/summary/commitment/deltas) with a reduced per-delta projection — there is no top-levelchangeType/symbolsshape.
Embedded Mode (EmbedInCycloneDx)
The extension object is appended to the BOM root extensions array (the array is created if absent):
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"extensions": [
{
"extensionType": "stella.change-trace",
"changeTrace": {
"schema": "stella.change-trace/1.0",
"subject": { "type": "oci.image", "digest": "sha256:...", "purl": "...", "name": "..." },
"basis": { "scanId": "...", "engineVersion": "1.0.0", "analyzedAt": "...", "fromScanId": "...", "toScanId": "...", "policies": [ ... ], "diffMethod": [ ... ] },
"summary": { "changedPackages": 1, "changedSymbols": 1, "changedBytes": 512, "riskDelta": -0.27, "verdict": "riskdown" },
"commitment": { "sha256": "...", "algorithm": "RFC8785+SHA256" },
"deltas": [
{
"purl": "pkg:deb/debian/libssl3@3.0.9-1+deb12u3",
"name": "libssl3",
"fromVersion": "3.0.9-1+deb12u2",
"toVersion": "3.0.9-1+deb12u3",
"changeType": "upgraded",
"explain": "vendorbackport",
"evidence": { "symbolsChanged": 1, "bytesChanged": 512, "confidence": 0.98 },
"trustDelta": { "score": -0.27, "reachabilityImpact": "reduced", "exploitabilityImpact": "down", "proofSteps": [ ... ] },
"symbolDeltas": [ { "name": "ssl3_get_record", "changeType": "patched" } ]
}
]
}
}
]
}
Notes grounded in BuildExtensionObject / BuildDeltaNode:
- Enum values in the extension are lowercased via
ToString().ToLowerInvariant()(e.g.verdict: "riskdown",explain: "vendorbackport") — these are not the camelCase forms the canonical serializer uses; they are flattened to all-lowercase here. - The per-delta
evidenceprojection contains onlysymbolsChanged,bytesChanged, andconfidence(rounded to 4 dp);riskDelta/score/confidenceare rounded to 4 decimals. symbolDeltas(max 50) carry onlyname+changeType;byteDeltas(max 20) carry onlysection,offset,size; both are included only when theirInclude*option is set.- When
trace.Deltas.Length > MaxDeltas, thechangeTraceobject also carries"truncated": trueand"totalDeltas": <count>.
Standalone Mode (ExportAsStandalone)
Produces a minimal CycloneDX document wrapping the same changeTrace extension:
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:<random>",
"version": 1,
"metadata": {
"timestamp": "<basis.analyzedAt 'O'>",
"tools": [ { "vendor": "StellaOps", "name": "ChangeTrace", "version": "<basis.engineVersion>" } ]
},
"extensions": [ { "extensionType": "stella.change-trace", "changeTrace": { ... } } ],
"components": [
{ "type": "container", "bom-ref": "change-trace-subject", "purl": "<subject.purl>", "hashes": [ { "alg": "SHA-256", "content": "<digest sans algo prefix>" } ] }
]
}
The components entry is emitted only when subject.purl is set. The serialNumber is a random urn:uuid (non-deterministic, observability only).
File Extensions
- Plain change-trace JSON export:
.cdxchange.json(CLI defaulttrace-export.cdxchange.json). - CycloneDX export (
--format cyclonedx/--cdx-embedded):.cdx.json(CLI defaulttrace-export.cdx.json).
Version History
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2026-01-12 | Initial release |
