Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
176 commits
Select commit Hold shift + click to select a range
8053117
test(core): define release manifest artifact admission contract
seonghobae Aug 22, 2026
663087f
test(core): format release manifest contract before semantic red
seonghobae Aug 22, 2026
69a1c4f
feat(core): add fail-closed release manifest identity primitive
seonghobae Aug 22, 2026
dac79ef
feat(core): expose release manifest identity contract
seonghobae Aug 22, 2026
db4990c
style(core): apply canonical release manifest formatting
seonghobae Aug 22, 2026
d611f20
test(core): close exact release manifest coverage branches
seonghobae Aug 22, 2026
489d332
docs(changelog): record release manifest identity primitive
seonghobae Aug 22, 2026
b5d5365
test(core): reject case-colliding release artifacts
seonghobae Aug 22, 2026
d640a41
fix(core): reject case-colliding release artifacts
seonghobae Aug 22, 2026
4627924
docs(changelog): record cross-platform artifact collision guard
seonghobae Aug 22, 2026
2d00a17
test(core): reject Windows device artifact names
seonghobae Aug 22, 2026
091b5a7
fix(core): reject Windows device artifact names
seonghobae Aug 22, 2026
10eeaed
style(core): apply canonical release-manifest formatting
seonghobae Aug 22, 2026
bcabafd
test(core): cover ordinary four-byte artifact basenames
seonghobae Aug 22, 2026
7f30d4e
docs(adr): record release manifest identity boundary
seonghobae Aug 22, 2026
cdd4bdd
docs: index proposed release manifest ADR
seonghobae Aug 22, 2026
441fab7
docs: index active release manifest ADR
seonghobae Aug 22, 2026
459631a
docs: trace release filename portability evidence
seonghobae Aug 22, 2026
d141483
test(core): require release build identity evidence
seonghobae Aug 22, 2026
78f2359
test(core): canonicalize release build identity regression
seonghobae Aug 22, 2026
000ff2d
feat(core): bind release build identity evidence
seonghobae Aug 22, 2026
ec2323b
style(core): canonicalize release build identity formatting
seonghobae Aug 22, 2026
a0d23b6
docs(adr): bind release build identity evidence
seonghobae Aug 22, 2026
7eddd3e
docs(changelog): record release build identity binding
seonghobae Aug 22, 2026
965d995
test(core): satisfy strict release manifest Clippy contract
seonghobae Aug 22, 2026
42ca2fd
test(release): reject unpinned Rust toolchains
seonghobae Aug 22, 2026
923fe8b
fix(release): bind exact pinned Rust toolchain
seonghobae Aug 22, 2026
eff600d
docs(adr): bind release identity to pinned Rust baseline
seonghobae Aug 22, 2026
547b53a
docs(changelog): record exact release toolchain binding
seonghobae Aug 22, 2026
fc8e51d
test(release): require exact toolchain error contract
seonghobae Aug 22, 2026
7e5ccf1
test(release): decouple exact pin from obsolete token bound
seonghobae Aug 22, 2026
a957ff6
fix(release): make pinned toolchain contract explicit
seonghobae Aug 22, 2026
c875358
test(release): reject null Git source identity
seonghobae Aug 22, 2026
cf17966
fix(release): reject null Git source identity
seonghobae Aug 22, 2026
7d7dd94
docs(release): reject Git zero-id as source identity
seonghobae Aug 22, 2026
bf29cb4
docs(release): record non-null Git identity rationale
seonghobae Aug 22, 2026
ac9f4ff
test(core): pin non-null release source error contract
seonghobae Aug 23, 2026
a7f995e
fix(core): align release source diagnostic with non-null identity
seonghobae Aug 23, 2026
070798d
docs(release): pin Git protocol references
seonghobae Aug 23, 2026
f7c3145
docs(release): pin doctoring Git references
seonghobae Aug 23, 2026
37d846f
test(release): reject Win32 COM0 and LPT0 aliases
seonghobae Aug 23, 2026
9ced1c2
fix(release): reject Win32 COM0 and LPT0 device aliases
seonghobae Aug 23, 2026
73d3453
docs(adr): record portable COM0 and LPT0 exclusion
seonghobae Aug 23, 2026
bfec859
fix(release): satisfy strict ASCII digit lint
seonghobae Aug 23, 2026
45f024d
docs(doctoring): record portable COM0 and LPT0 exclusions
seonghobae Aug 23, 2026
8145d40
docs(changelog): record portable release device aliases
seonghobae Aug 23, 2026
723f80b
test(core): require manifest-bound SPDX SBOM identity
seonghobae Aug 23, 2026
1e09c8c
feat(core): bind SPDX SBOM identity to release manifest
seonghobae Aug 23, 2026
6a24339
docs(changelog): record release SBOM identity boundary
seonghobae Aug 23, 2026
f4bd3b4
docs(doctoring): record SPDX 3.0.1 SBOM boundary
seonghobae Aug 23, 2026
69b5344
docs(adr): define release SBOM identity boundary
seonghobae Aug 23, 2026
5e27afa
docs(adr): index release SBOM identity decision
seonghobae Aug 23, 2026
f938d33
docs(index): include release SBOM ADR
seonghobae Aug 23, 2026
41ba34d
test(core): cover SBOM binding in one concrete iterator contract
seonghobae Aug 23, 2026
8b377ab
test(core): prove SBOM binding retains exact artifact identity
seonghobae Aug 23, 2026
5ec9bbb
fix(core): retain exact artifact identity in SBOM binding
seonghobae Aug 23, 2026
6d86b5f
test(core): bind SBOM identity to exact release manifest
seonghobae Aug 23, 2026
a89112d
fix(core): retain exact release manifest in SBOM binding
seonghobae Aug 23, 2026
29d960d
style(core): apply canonical Rust formatting
seonghobae Aug 23, 2026
e72b59b
test(core): reject self-describing release SBOM
seonghobae Aug 23, 2026
25cd851
fix(core): reject self-describing release SBOM
seonghobae Aug 23, 2026
ed3dfe6
test(core): require typed SBOM self-description denial
seonghobae Aug 23, 2026
8a72af1
docs(release): require non-circular SBOM identity
seonghobae Aug 23, 2026
3a39bab
docs(changelog): record SBOM self-description rejection
seonghobae Aug 23, 2026
91e7850
test(core): require inspectable SBOM identity binding
seonghobae Aug 23, 2026
eeff140
fix(core): expose exact SBOM binding identities
seonghobae Aug 23, 2026
5de7752
fix(core): preserve borrowed SBOM version accessor
seonghobae Aug 23, 2026
4a6825a
docs: record inspectable SBOM identity contract
seonghobae Aug 23, 2026
e18d002
docs: remove unrelated SBOM stack drift
seonghobae Aug 23, 2026
c73e07c
docs: restore inherited extension wording
seonghobae Aug 23, 2026
a9e9368
test(core): require complete release SBOM inventory
seonghobae Aug 23, 2026
97d9c71
fix(core): require complete release SBOM inventory
seonghobae Aug 23, 2026
70fe74a
docs(adr): require release SBOM inventory completeness
seonghobae Aug 23, 2026
4bed6b6
docs(changelog): record complete release SBOM inventory
seonghobae Aug 23, 2026
6879348
test(core): satisfy SBOM completeness quality gates
seonghobae Aug 23, 2026
e0a8d2d
test(core): cover SBOM completeness error contract
seonghobae Aug 23, 2026
26fa8e8
test(core): cover SBOM completeness in canonical binding suite
seonghobae Aug 23, 2026
b988116
test(core): require exact SPDX JSON-LD context identity
seonghobae Aug 23, 2026
30d77b7
feat(core): bind SPDX JSON-LD context identity
seonghobae Aug 23, 2026
aec7f6f
docs(adr): bind SPDX 3.0.1 JSON-LD context
seonghobae Aug 23, 2026
19ac78f
test(release): require bounded SPDX JSON-LD envelope verification
seonghobae Aug 23, 2026
9d01ded
feat(release): validate bounded SPDX JSON-LD envelope
seonghobae Aug 23, 2026
b6bdb93
test(release): keep validator callable unbound
seonghobae Aug 23, 2026
4507701
docs(adr): bind SPDX envelope verification limits
seonghobae Aug 23, 2026
8215a79
test(release): admit SPDX namespace context mappings
seonghobae Aug 23, 2026
4c34ae8
fix(release): accept SPDX namespace context mappings
seonghobae Aug 23, 2026
545a107
docs(adr): permit pinned SPDX context extensions
seonghobae Aug 23, 2026
c521039
test(release): keep SPDX context extensions local
seonghobae Aug 23, 2026
f242237
test(release): reject semantic JSON-LD context overrides
seonghobae Aug 23, 2026
483b97e
fix(release): pin SPDX context semantics fail closed
seonghobae Aug 23, 2026
eed3094
test(release): lock SPDX envelope to schema context
seonghobae Aug 23, 2026
0db65f2
docs(adr): fail closed on SPDX context composition
seonghobae Aug 23, 2026
69a00cd
docs(research): record SPDX context authority boundary
seonghobae Aug 23, 2026
dc04358
docs(changelog): record SPDX context fail-closed repair
seonghobae Aug 23, 2026
b6cc795
test(release): reproduce deeply nested SPDX parser failure
seonghobae Aug 23, 2026
03bb9a5
fix(release): fail closed on excessive SPDX JSON nesting
seonghobae Aug 23, 2026
a03815f
test(release): reject streaming SPDX input paths
seonghobae Aug 23, 2026
41befbe
test(release): make FIFO regression cleanup deterministic
seonghobae Aug 23, 2026
b729cde
fix(release): reject non-regular SPDX inputs
seonghobae Aug 23, 2026
724b0f2
test(release): bind SPDX bytes to declared artifact digest
seonghobae Aug 23, 2026
c40b046
fix(release): verify SPDX bytes against artifact digest
seonghobae Aug 23, 2026
04b983e
docs(release): record SPDX artifact digest binding
seonghobae Aug 23, 2026
d14221c
test(release): reject symlinked SPDX candidates
seonghobae Aug 24, 2026
6f339df
fix(release): reject indirect SPDX input paths
seonghobae Aug 24, 2026
46c8cbf
test(release): reject symlinked SBOM parent paths
seonghobae Aug 24, 2026
24616ea
fix(release): reject indirect SBOM ancestor paths
seonghobae Aug 24, 2026
2e44d2d
test(release): reject transient parent symlink swaps
seonghobae Aug 24, 2026
a7e9f50
fix(release): pin SPDX path traversal to directory descriptors
seonghobae Aug 24, 2026
2532493
test(release): reject numeric overflow in SPDX JSON
seonghobae Aug 24, 2026
3d065c2
fix(release): reject non-finite numeric overflow
seonghobae Aug 24, 2026
4601cdd
merge: reconcile main into release manifest artifact contract
seonghobae Aug 24, 2026
5c47f9d
test(release): pin manifest documentation truth
seonghobae Aug 24, 2026
1ca7bec
test(release): align Git citation revision contracts
seonghobae Aug 24, 2026
15648b3
docs(release): narrow manifest portability claims
seonghobae Aug 24, 2026
ff9be05
docs(release): align portability and Git evidence
seonghobae Aug 24, 2026
0aed213
test(release): cover changelog portability truth
seonghobae Aug 24, 2026
c2e581f
docs(release): bound changelog portability claim
seonghobae Aug 24, 2026
3e34a54
merge(main): converge release manifest contract
seonghobae Aug 24, 2026
9c631e0
merge(parent): converge release SBOM stack
seonghobae Aug 24, 2026
f9c98ad
test(release): reject final-path swap after SPDX open
seonghobae Aug 24, 2026
12f20d7
fix(release): revalidate SPDX leaf identity after read
seonghobae Aug 24, 2026
b375dad
docs(release): record direct SPDX file identity hardening
seonghobae Aug 24, 2026
1d1ab65
docs: mark merged MCP routing as protected-main truth
seonghobae Aug 24, 2026
bc898a3
test(release): reject parent identity replacement
seonghobae Aug 24, 2026
e0e8539
fix(release): bind SPDX parent directory identity
seonghobae Aug 24, 2026
b247baa
test(release): reject nested duplicate SPDX documents
seonghobae Aug 24, 2026
8caf156
fix(release): reject nested duplicate SPDX documents
seonghobae Aug 24, 2026
62120e5
test(release): prevent SPDX payload retention in parse errors
seonghobae Aug 24, 2026
f8abe9e
fix(release): detach SPDX payloads from parser errors
seonghobae Aug 24, 2026
90026e8
test(release): redact failed SPDX path evidence
seonghobae Aug 24, 2026
45071da
fix(release): redact SPDX file read causes
seonghobae Aug 24, 2026
8c1cde4
test(release): reject path retention in exception context
seonghobae Aug 25, 2026
9b38a59
fix(release): redact implicit file error context
seonghobae Aug 25, 2026
02829ce
test(release): catch post-read parent authority swap
seonghobae Aug 25, 2026
879c671
fix(release): revalidate parent identity after leaf check
seonghobae Aug 25, 2026
fa6eab3
test(release): reject oversized SPDX numeric tokens
seonghobae Aug 26, 2026
b4965d3
style(release): preserve canonical test formatting
seonghobae Aug 26, 2026
1051088
fix(release): bound SPDX JSON numeric tokens
seonghobae Aug 26, 2026
927ee3c
test(release): require CLI SBOM digest binding
seonghobae Aug 27, 2026
3a2db64
feat(release): bind SPDX CLI to manifest digest
seonghobae Aug 27, 2026
f023715
merge: reconcile release SBOM with current parent
seonghobae Aug 28, 2026
a9b1cc9
test(release): reject malformed SBOM paths safely
seonghobae Aug 28, 2026
44146e5
fix(release): redact malformed SBOM path errors
seonghobae Aug 28, 2026
6ab5252
test(release): reject nested-only SPDX document
seonghobae Sep 2, 2026
e42e8d1
fix(release): require top-level SPDX document
seonghobae Sep 2, 2026
c914438
docs(release): align SPDX document placement contract
seonghobae Sep 2, 2026
5d245c8
docs(doctoring): bind SPDX document placement
seonghobae Sep 2, 2026
f341ab9
docs(changelog): record SPDX placement hardening
seonghobae Sep 2, 2026
f3a519e
test(release): pin SBOM canonical-owner migration
seonghobae Sep 2, 2026
21a9a40
docs(release): define SBOM canonical-owner migration
seonghobae Sep 2, 2026
fae0aee
docs(release): require cross-platform secure SBOM admission
seonghobae Sep 2, 2026
6662d0a
test(release): pin Windows SBOM handle-identity contract
seonghobae Sep 3, 2026
9a82177
docs(release): bind Windows SBOM admission to handle identity
seonghobae Sep 3, 2026
e9c43d9
test(release): bound nested SPDX JSON containers
seonghobae Sep 3, 2026
3e2b4a3
fix(release): preflight SPDX JSON container fan-out
seonghobae Sep 3, 2026
0317263
test(release): cover SPDX container preflight escapes
seonghobae Sep 3, 2026
3436849
docs(release): trace SPDX parser resource bound
seonghobae Sep 3, 2026
9c03561
docs(changelog): record SPDX parser fan-out hardening
seonghobae Sep 3, 2026
3e2ad55
docs(adr): bind SPDX parser resource admission
seonghobae Sep 3, 2026
cbd0d96
test(release): expose scalar JSON fan-out amplification
seonghobae Sep 3, 2026
d301f8f
fix(release): bound scalar JSON fan-out before parse
seonghobae Sep 3, 2026
96e8dbd
test(release): separate container and scalar fan-out bounds
seonghobae Sep 3, 2026
6be9e8f
docs(release): trace scalar JSON fan-out bound
seonghobae Sep 3, 2026
9f2a515
docs(adr): include scalar JSON fan-out admission
seonghobae Sep 3, 2026
c06bc96
docs(doctoring): trace bounded SPDX JSON parsing
seonghobae Sep 3, 2026
7866c78
docs(changelog): include scalar SPDX parser fan-out bound
seonghobae Sep 3, 2026
84b57dd
docs(changelog): restore unrelated release notes
seonghobae Sep 3, 2026
16a2e1e
test(release): bound SPDX JSON nesting depth
seonghobae Sep 3, 2026
93f1d13
fix(release): reject excessive SPDX JSON nesting before parse
seonghobae Sep 3, 2026
5cc4f81
chore(release): restack SPDX binding on current manifest parent
seonghobae Sep 3, 2026
108fc9b
test(release): require depth budget in owner migration contract
seonghobae Sep 3, 2026
b71240b
revert: keep migration contract aligned with writable docs boundary
seonghobae Sep 3, 2026
13edf50
docs(release): record JSON nesting-depth invariant
seonghobae Sep 3, 2026
25eb15a
docs(release): index SBOM depth traceability
seonghobae Sep 3, 2026
ce04404
chore(release): restack SPDX binding on live manifest parent
seonghobae Sep 3, 2026
b0aee66
fix(release): make SPDX container bound reachable
seonghobae Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ All notable changes to OriginWeave are documented in this file. The format follo

## [Unreleased]

- Tightened the provisional SPDX JSON-LD container budget to a reachable pre-materialization bound and changed its regression input to shallow fan-out, so container exhaustion is distinguished from nesting-depth and structure-token failures.

- Refreshed the product-gap queue to 126 open pull requests (54 ready, 72 draft) after #190, #188, #185, #192, #182, #184, #115, #181, #116, #117, #118, #183, #114, #127, #112, #109, #186, #110, #108, #111, #174, and #113 were merged into their immediate stacked prerequisites. PRs #147, #146, #145, #144, #143, #142, #141, #139, #136, #132, #129, and #128 moved to ready after exact-head checks and thread review; these are queue-consolidation results, not protected-main shipment.

### Added
- Corrected the 2026-08-26 product-gap snapshot with current #229 presentation-identity evidence, stacked-only #205 integration evidence, current base/head pairs, the 126-PR queue count, explicit root-versus-child merge ordering, and the active GitHub counted-approval gate.
- Refreshed the product and technical gap baseline onto the 2026-08-26 live inventory: 126 open pull requests (54 ready, 72 draft), protected-main promotion of #168/#194/#196/#216/#151, a verified maintenance-loop record (supersession closure of #153, conflict reconciliations on #37/#149/#152/#173/#175, issue #212 option-(b) authorization on #43, Strix vuln-0001 homoglyph remediation on #124), provider-rerun outcome evidence, an organization review-pipeline congestion record, and refreshed merge-order queue guidance. Documentation evidence contracts were aligned to the same snapshot so the baseline, its dated markers, and the pinned exact-head rows cannot silently diverge.

- Added `originweave_core::release_acceptance`, a deterministic fail-closed benchmark release-decision contract that requires one authoritative result for every mandatory suite, bounds explicit buyer-visible limitations, rejects duplicate limitation claim identities, and rejects non-canonical surrounding whitespace rather than normalizing it into an alternate claim spelling.
- Added an inert SPDX 3.0.1 JSON-LD SBOM identity binding that requires the SBOM artifact and every non-SBOM release artifact to be exact members of the same release manifest, rejects empty, incomplete, foreign, duplicate, self-described, and case-drifted artifact identities, preserves a non-circular release-inventory-complete exact-digest join, exposes the complete retained manifest and exact described artifact identities for downstream verification/provenance composition, and does not claim SPDX content conformance, package/component completeness inside the SBOM, artifact authentication, provenance, signing, publication, installation, update, rollback, or release authority.
- Refreshed the product and technical gap baseline with the 2026-08-24 live inventory: 158 open pull requests (44 ready, 114 draft), refreshed exact base/head evidence for the #208–#222 release, enterprise-approval, BAP, and WARC/PROV chains, the governance issue additions #212 and #215, and a required-check provider-failure record for the fail-closed Strix re-dispatches on #208/#218/#220.
- Added a dated product and technical gap baseline that separates protected-main implementation truth, active pull-request evidence, live review/check blockers, and the next buyer-visible Phase 1 acceptance work.
- Refreshed the product and technical gap baseline with the current open-PR inventory and exact base/head evidence for the newest Chromium, BAP, extraction, WARC, and idempotency slices.
Expand Down Expand Up @@ -72,6 +75,11 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Security

- The provisional SPDX 3.0.1 release-envelope parser now rejects both excessive opening-container depth/fan-out and broader scalar/member fan-out before `json.loads`: opening containers are capped at 524,288 and total structural `[]{}:,` tokens at 1,048,576, counting only punctuation outside JSON strings. This prevents compact hostile documents below the 16 MiB byte ceiling from amplifying into an unbounded generic object tree while preserving structural-looking text inside string values. The eventual Rust-owned Release boundary must retain an equivalent or stricter pre-materialization resource invariant.
- The SPDX 3.0.1 release-envelope gate now requires exactly one top-level `SpdxDocument` under `@graph` and rejects both a nested-only document and any additional nested `SpdxDocument`, preventing recursive string/type discovery from substituting for the required serialization placement.
- The SPDX 3.0.1 release-envelope gate requires the exact versioned global-context string and rejects context arrays, `@import`, `@base`, `@vocab`, and alias/term-rebinding attempts until reviewed schema-aware validation can prove extension semantics without ambient or reinterpretation authority.
- SPDX JSON-LD release-file admission now requires a direct regular-file leaf reached through a no-follow descriptor-relative path walk and revalidates the final leaf's device/inode identity after the bounded read, so symlinks, FIFOs, swapped ancestors, and post-open pathname replacement fail closed instead of silently changing the artifact path identity.
- Explicit proxy server identifiers require ASCII decimal port tokens before numeric range parsing, preventing Rust-specific leading-plus spellings from widening proxy authority.
- Explicit proxy server identifiers require ASCII decimal port tokens before numeric range parsing, preventing Rust-specific leading-plus spellings from widening proxy authority.
- Release artifact admission rejects `COM0` through `COM9` and `LPT0` through `LPT9` case-insensitively, including extensions, as bounded filename identity hygiene. OneDrive and SharePoint impose additional restrictions, including `desktop.ini`, that this validator does not model; this is not a complete OneDrive or SharePoint synchronization-compatibility guarantee.
- Raw page content cannot become a trusted instruction.
Expand All @@ -81,8 +89,8 @@ All notable changes to OriginWeave are documented in this file. The format follo
- R3 and R4 approvals are bound to the exact action, target origin, and immutable digest of the complete canonical action intent; R5 legal consent is non-delegable.
- Shortened, integer, hexadecimal, and legacy octal-looking IPv4 host spellings are rejected so the policy origin cannot diverge from Chromium host interpretation.
- IPv4-mapped IPv6 is canonicalized before destination classification and pin comparison so mapped private or loopback addresses cannot bypass IPv4 policy.
- The default destination policy permits only public addresses and denies unspecified, loopback, private, shared, link-local, metadata, documentation, benchmarking, multicast, broadcast, transition, and protocol-reserved destinations.
- Azure platform IP `168.63.129.16` and Amazon EKS Pod Identity endpoints `169.254.170.23` and `fd00:ec2::23` are classified as metadata or platform services before broader public, link-local, or unique-local rules.
- The default destination policy permits only public addresses and denies unspecified, loopback, private, shared, link-local, metadata, documentation, benchmarking, multicast, broadcast, transition, unallocated, and protocol-reserved destinations.
- Azure platform IP `168.63.129.16` and Amazon EKS Pod Identity endpoints `169.254.170.23` and `fd00:ec2::23` are classified as metadata or platform services before broader address-range rules.
- Resolver answers are rejected when empty or larger than 256 addresses, preventing an unbounded resolver response from entering policy state.
- `localhost` may approve only loopback addresses, while literal IPv4 and IPv6 origins may approve only the exact canonical address encoded in the origin.
- Resolver answers must remain a non-empty subset of the origin-bound approved address set; any newly introduced address fails closed as a possible DNS-rebinding event.
Expand All @@ -105,4 +113,4 @@ All notable changes to OriginWeave are documented in this file. The format follo
- The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it.
- The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels.

[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
204 changes: 202 additions & 2 deletions crates/originweave-core/src/release_manifest.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Fail-closed identity binding for release artifacts.
//!
//! The types in this module are deliberately inert metadata contracts. They bind an exact
//! source commit, Chromium revision, release channel, build identity, and artifact digests
//! without granting signing, publication, installation, update, rollback, or release authority.
//! source commit, Chromium revision, release channel, build identity, artifact digests, and
//! release-SBOM identity without granting signing, publication, installation, update, rollback,
//! or release authority.

use std::collections::BTreeSet;
use std::error::Error;
Expand Down Expand Up @@ -157,6 +158,205 @@ impl fmt::Display for ReleaseArtifactError {

impl Error for ReleaseArtifactError {}

/// SPDX serialization identity admitted for release-SBOM evidence.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReleaseSbomFormat {
/// SPDX 3.0.1 serialized as JSON-LD.
Spdx30JsonLd,
}

impl ReleaseSbomFormat {
/// Return the exact SPDX specification version represented by this format.
#[must_use]
pub const fn spdx_specification_version(self) -> &'static str {
match self {
Self::Spdx30JsonLd => "3.0.1",
}
}

/// Return the exact global JSON-LD context URI required by this SPDX format.
///
/// This versioned identifier is inert serialization metadata for downstream validation and
/// serialization. It does not validate SBOM bytes, authenticate artifacts, or grant release
/// authority.
#[must_use]
pub const fn json_ld_context_uri(self) -> &'static str {
match self {
Self::Spdx30JsonLd => "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
}
}
}

/// Inert binding between one release manifest and its declared SPDX SBOM artifact identity.
///
/// This value proves only that the named SBOM and exact described artifact identities are already
/// admitted by the same immutable [`ReleaseManifest`]. It retains the complete bounded release
/// manifest identity and each described artifact digest, so any source, Chromium, channel, build,
/// artifact-inventory, or described-artifact change produces a different binding. It does not
/// parse or validate SPDX content, prove that the SBOM actually describes those artifacts,
/// authenticate any digest, or grant release authority.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReleaseSbomBinding {
release_manifest: ReleaseManifest,
format: ReleaseSbomFormat,
sbom_artifact: ReleaseArtifact,
described_artifact_names: Vec<String>,
described_artifacts: Vec<ReleaseArtifact>,
}

impl ReleaseSbomBinding {
/// Bind a declared SPDX SBOM artifact to every other artifact in one release manifest.
///
/// The complete bounded release-manifest identity is retained in the binding. The SBOM
/// artifact name and every described artifact name must match that manifest exactly. The SBOM
/// artifact itself cannot be admitted as one of its described artifacts. Every other manifest
/// artifact must be described exactly once; missing, foreign, self-described, and duplicate
/// entries fail closed. Exact described artifact identities plus their public names are sorted
/// deterministically. The release manifest's own admission bound therefore also bounds this
/// inventory.
pub fn new<'a, I>(
manifest: &ReleaseManifest,
sbom_artifact_name: &str,
format: ReleaseSbomFormat,
described_artifact_names: I,
) -> Result<Self, ReleaseSbomBindingError>
where
I: IntoIterator<Item = &'a str>,
{
let Some(sbom_artifact) = manifest
.artifacts()
.iter()
.find(|artifact| artifact.name() == sbom_artifact_name)
else {
return Err(ReleaseSbomBindingError::UnknownSbomArtifact);
};

let mut described_artifacts = Vec::new();
let mut seen = BTreeSet::new();
for artifact_name in described_artifact_names {
if artifact_name == sbom_artifact.name() {
return Err(ReleaseSbomBindingError::SelfDescribedSbomArtifact);
}
let Some(artifact) = manifest
.artifacts()
.iter()
.find(|artifact| artifact.name() == artifact_name)
else {
return Err(ReleaseSbomBindingError::UnknownDescribedArtifact);
};
if !seen.insert(artifact_name.to_owned()) {
return Err(ReleaseSbomBindingError::DuplicateDescribedArtifact);
}
described_artifacts.push(artifact.clone());
}
if described_artifacts.is_empty() {
return Err(ReleaseSbomBindingError::MissingDescribedArtifacts);
}
if described_artifacts.len() + 1 != manifest.artifacts().len() {
return Err(ReleaseSbomBindingError::IncompleteDescribedArtifacts);
}
described_artifacts.sort_by(|left, right| left.name.cmp(&right.name));
let described_artifact_names = described_artifacts
.iter()
.map(|artifact| artifact.name().to_owned())
.collect();

Ok(Self {
release_manifest: manifest.clone(),
format,
sbom_artifact: sbom_artifact.clone(),
described_artifact_names,
described_artifacts,
})
}

/// Return the complete exact release manifest retained by this SBOM binding.
///
/// This is inert identity evidence for downstream verification and provenance composition. It
/// does not authenticate the manifest, validate SBOM content, or grant release authority.
#[must_use]
pub const fn release_manifest(&self) -> &ReleaseManifest {
&self.release_manifest
}

/// Return the declared SPDX serialization format.
#[must_use]
pub const fn format(&self) -> ReleaseSbomFormat {
self.format
}

/// Return the exact SPDX specification version represented by the declared format.
#[must_use]
pub const fn spdx_specification_version(&self) -> &'static str {
self.format.spdx_specification_version()
}

/// Return the exact manifest-backed SBOM artifact identity.
#[must_use]
pub const fn sbom_artifact(&self) -> &ReleaseArtifact {
&self.sbom_artifact
}

/// Return described manifest artifact names in deterministic lexical order.
#[must_use]
pub fn described_artifact_names(&self) -> &[String] {
&self.described_artifact_names
}

/// Return exact described artifact identities in deterministic lexical order.
///
/// These name-and-digest identities are retained from the same admitted release manifest and
/// are suitable for later verification joins. Their presence alone does not authenticate
/// artifact bytes or prove that the SBOM content describes them.
#[must_use]
pub fn described_artifacts(&self) -> &[ReleaseArtifact] {
&self.described_artifacts
}
}

/// Validation error for release-SBOM identity binding.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReleaseSbomBindingError {
/// The named SBOM artifact is absent from the release manifest.
UnknownSbomArtifact,
/// No release artifacts were declared as described by the SBOM.
MissingDescribedArtifacts,
/// The SBOM omitted at least one non-SBOM artifact from the same release manifest.
IncompleteDescribedArtifacts,
/// The SBOM artifact was also declared as an artifact that the SBOM describes.
SelfDescribedSbomArtifact,
/// A described artifact name is absent from the release manifest.
UnknownDescribedArtifact,
/// A described artifact name was repeated.
DuplicateDescribedArtifact,
}

impl fmt::Display for ReleaseSbomBindingError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnknownSbomArtifact => formatter
.write_str("release SBOM artifact is not present in the bound release manifest"),
Self::MissingDescribedArtifacts => {
formatter.write_str("release SBOM must describe at least one release artifact")
}
Self::IncompleteDescribedArtifacts => {
formatter.write_str("release SBOM must describe every non-SBOM release artifact")
}
Self::SelfDescribedSbomArtifact => {
formatter.write_str("release SBOM must not describe its own artifact")
}
Self::UnknownDescribedArtifact => formatter.write_str(
"release SBOM describes an artifact that is not present in the bound release manifest",
),
Self::DuplicateDescribedArtifact => {
formatter.write_str("release SBOM repeats a described release artifact")
}
}
}
}

impl Error for ReleaseSbomBindingError {}

/// Deterministic, bounded identity manifest for one OriginWeave release candidate.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReleaseManifest {
Expand Down
Loading
Loading