From 0465a3215fd469b040582223cc0a78d2278cde97 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:53:55 -0700 Subject: [PATCH 01/53] test(core): define release manifest artifact admission contract --- .../tests/release_manifest.rs | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 crates/originweave-core/tests/release_manifest.rs diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs new file mode 100644 index 000000000..edb16481c --- /dev/null +++ b/crates/originweave-core/tests/release_manifest.rs @@ -0,0 +1,209 @@ +use std::error::Error; + +use originweave_core::release_manifest::{ + MAX_RELEASE_ARTIFACTS, MAX_RELEASE_ARTIFACT_NAME_BYTES, MAX_RELEASE_REVISION_BYTES, + ReleaseArtifact, ReleaseArtifactError, ReleaseChannel, ReleaseManifest, ReleaseManifestError, +}; + +const SOURCE_COMMIT: &str = "0123456789abcdef0123456789abcdef01234567"; +const CHROMIUM_REVISION: &str = "150.0.7871.129"; + +fn sha256_digest(hex_digit: char) -> String { + format!("sha256:{}", hex_digit.to_string().repeat(64)) +} + +#[test] +fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result<(), Box> { + let runtime = ReleaseArtifact::new( + "originweave-linux-x86_64.tar.zst", + &sha256_digest('a'), + )?; + let sbom = ReleaseArtifact::new("originweave.spdx.json", &sha256_digest('b'))?; + + let manifest = ReleaseManifest::new( + SOURCE_COMMIT, + CHROMIUM_REVISION, + ReleaseChannel::Stable, + [sbom, runtime], + )?; + + assert_eq!(manifest.source_commit(), SOURCE_COMMIT); + assert_eq!(manifest.chromium_revision(), CHROMIUM_REVISION); + assert_eq!(manifest.channel(), ReleaseChannel::Stable); + assert_eq!(manifest.artifacts().len(), 2); + assert_eq!( + manifest.artifacts()[0].name(), + "originweave-linux-x86_64.tar.zst" + ); + assert_eq!( + manifest.artifacts()[0].sha256_digest(), + sha256_digest('a') + ); + assert_eq!(manifest.artifacts()[1].name(), "originweave.spdx.json"); + assert_eq!( + manifest.artifacts()[1].sha256_digest(), + sha256_digest('b') + ); + Ok(()) +} + +#[test] +fn release_artifact_rejects_ambiguous_names_and_noncanonical_digests() { + let valid_digest = sha256_digest('c'); + let overlong_name = "a".repeat(MAX_RELEASE_ARTIFACT_NAME_BYTES + 1); + + for invalid_name in [ + "", + ".hidden", + "artifact/child.bin", + "artifact\\child.bin", + "artifact..bin", + "artifact-.bin-", + overlong_name.as_str(), + ] { + assert_eq!( + ReleaseArtifact::new(invalid_name, &valid_digest), + Err(ReleaseArtifactError::InvalidName) + ); + } + + for invalid_digest in [ + "", + "sha256:abc", + "SHA256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sha256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "sha256:gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg", + ] { + assert_eq!( + ReleaseArtifact::new("originweave.bin", invalid_digest), + Err(ReleaseArtifactError::InvalidDigest) + ); + } +} + +#[test] +fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evidence( +) -> Result<(), Box> { + let artifact = ReleaseArtifact::new("originweave.bin", &sha256_digest('d'))?; + + for invalid_commit in [ + "", + "0123456789abcdef0123456789abcdef0123456", + "0123456789ABCDEF0123456789ABCDEF01234567", + "g123456789abcdef0123456789abcdef01234567", + ] { + assert_eq!( + ReleaseManifest::new( + invalid_commit, + CHROMIUM_REVISION, + ReleaseChannel::Development, + [artifact.clone()], + ), + Err(ReleaseManifestError::InvalidSourceCommit) + ); + } + + let overlong_revision = "a".repeat(MAX_RELEASE_REVISION_BYTES + 1); + for invalid_revision in [ + "", + " chromium-150", + "chromium 150", + "chromium/150", + "chromium-150-", + overlong_revision.as_str(), + ] { + assert_eq!( + ReleaseManifest::new( + SOURCE_COMMIT, + invalid_revision, + ReleaseChannel::Beta, + [artifact.clone()], + ), + Err(ReleaseManifestError::InvalidChromiumRevision) + ); + } + + assert_eq!( + ReleaseManifest::new( + SOURCE_COMMIT, + CHROMIUM_REVISION, + ReleaseChannel::Stable, + std::iter::empty(), + ), + Err(ReleaseManifestError::MissingArtifacts) + ); + + let duplicate = ReleaseArtifact::new("originweave.bin", &sha256_digest('e'))?; + assert_eq!( + ReleaseManifest::new( + SOURCE_COMMIT, + CHROMIUM_REVISION, + ReleaseChannel::Stable, + [artifact.clone(), duplicate], + ), + Err(ReleaseManifestError::DuplicateArtifactName) + ); + + let mut too_many = Vec::new(); + for index in 0..=MAX_RELEASE_ARTIFACTS { + too_many.push(ReleaseArtifact::new( + &format!("artifact-{index}.bin"), + &sha256_digest('f'), + )?); + } + assert_eq!( + ReleaseManifest::new( + SOURCE_COMMIT, + CHROMIUM_REVISION, + ReleaseChannel::Stable, + too_many, + ), + Err(ReleaseManifestError::TooManyArtifacts) + ); + Ok(()) +} + +#[test] +fn release_manifest_errors_are_standard_source_free_rust_errors() { + let artifact_errors = [ + ( + ReleaseArtifactError::InvalidName, + "release artifact name is not a canonical bounded leaf name", + ), + ( + ReleaseArtifactError::InvalidDigest, + "release artifact digest must be sha256: followed by 64 lowercase hexadecimal digits", + ), + ]; + for (error, expected) in artifact_errors { + assert_eq!(error.to_string(), expected); + assert!(Error::source(&error).is_none()); + } + + let manifest_errors = [ + ( + ReleaseManifestError::InvalidSourceCommit, + "release source commit must be exactly 40 lowercase hexadecimal digits", + ), + ( + ReleaseManifestError::InvalidChromiumRevision, + "Chromium revision must be a canonical bounded release token", + ), + ( + ReleaseManifestError::MissingArtifacts, + "release manifest must contain at least one artifact", + ), + ( + ReleaseManifestError::TooManyArtifacts, + "release manifest exceeds the artifact-count limit", + ), + ( + ReleaseManifestError::DuplicateArtifactName, + "release manifest contains a duplicate artifact name", + ), + ]; + for (error, expected) in manifest_errors { + assert_eq!(error.to_string(), expected); + assert!(Error::source(&error).is_none()); + } +} From 296add6cc65a7e0f134b75899876c2c31bce7ae4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:55:29 -0700 Subject: [PATCH 02/53] test(core): format release manifest contract before semantic red --- .../tests/release_manifest.rs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index edb16481c..cd63693db 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -1,7 +1,7 @@ use std::error::Error; use originweave_core::release_manifest::{ - MAX_RELEASE_ARTIFACTS, MAX_RELEASE_ARTIFACT_NAME_BYTES, MAX_RELEASE_REVISION_BYTES, + MAX_RELEASE_ARTIFACT_NAME_BYTES, MAX_RELEASE_ARTIFACTS, MAX_RELEASE_REVISION_BYTES, ReleaseArtifact, ReleaseArtifactError, ReleaseChannel, ReleaseManifest, ReleaseManifestError, }; @@ -13,11 +13,9 @@ fn sha256_digest(hex_digit: char) -> String { } #[test] -fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result<(), Box> { - let runtime = ReleaseArtifact::new( - "originweave-linux-x86_64.tar.zst", - &sha256_digest('a'), - )?; +fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result<(), Box> +{ + let runtime = ReleaseArtifact::new("originweave-linux-x86_64.tar.zst", &sha256_digest('a'))?; let sbom = ReleaseArtifact::new("originweave.spdx.json", &sha256_digest('b'))?; let manifest = ReleaseManifest::new( @@ -35,15 +33,9 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< manifest.artifacts()[0].name(), "originweave-linux-x86_64.tar.zst" ); - assert_eq!( - manifest.artifacts()[0].sha256_digest(), - sha256_digest('a') - ); + assert_eq!(manifest.artifacts()[0].sha256_digest(), sha256_digest('a')); assert_eq!(manifest.artifacts()[1].name(), "originweave.spdx.json"); - assert_eq!( - manifest.artifacts()[1].sha256_digest(), - sha256_digest('b') - ); + assert_eq!(manifest.artifacts()[1].sha256_digest(), sha256_digest('b')); Ok(()) } @@ -82,8 +74,8 @@ fn release_artifact_rejects_ambiguous_names_and_noncanonical_digests() { } #[test] -fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evidence( -) -> Result<(), Box> { +fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evidence() +-> Result<(), Box> { let artifact = ReleaseArtifact::new("originweave.bin", &sha256_digest('d'))?; for invalid_commit in [ From b300d19df859577fe7a409bebeefcadc12eaeb32 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:57:29 -0700 Subject: [PATCH 03/53] feat(core): add fail-closed release manifest identity primitive --- .../originweave-core/src/release_manifest.rs | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 crates/originweave-core/src/release_manifest.rs diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs new file mode 100644 index 000000000..d95de87ae --- /dev/null +++ b/crates/originweave-core/src/release_manifest.rs @@ -0,0 +1,255 @@ +//! 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, and artifact digests without granting +//! signing, publication, installation, update, rollback, or release authority. + +use std::collections::BTreeSet; +use std::error::Error; +use std::fmt; + +/// Maximum number of artifacts admitted by one release manifest. +pub const MAX_RELEASE_ARTIFACTS: usize = 64; +/// Maximum UTF-8 byte length admitted for one canonical artifact leaf name. +pub const MAX_RELEASE_ARTIFACT_NAME_BYTES: usize = 128; +/// Maximum UTF-8 byte length admitted for one Chromium revision token. +pub const MAX_RELEASE_REVISION_BYTES: usize = 128; + +/// Buyer-visible release channel bound by a release manifest. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReleaseChannel { + /// Stable release channel. + Stable, + /// Beta release channel. + Beta, + /// Development release channel. + Development, +} + +/// One canonical release artifact identity. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReleaseArtifact { + name: String, + sha256_digest: String, +} + +impl ReleaseArtifact { + /// Construct one artifact from a canonical leaf name and lowercase SHA-256 digest. + /// + /// The digest must use the exact `sha256:` prefix followed by 64 lowercase hexadecimal + /// digits. Artifact names are ASCII leaf names and cannot contain path separators, + /// traversal-like double dots, leading punctuation, or trailing punctuation. + pub fn new(name: &str, sha256_digest: &str) -> Result { + if !valid_artifact_name(name) { + return Err(ReleaseArtifactError::InvalidName); + } + if !valid_sha256_digest(sha256_digest) { + return Err(ReleaseArtifactError::InvalidDigest); + } + Ok(Self { + name: name.to_owned(), + sha256_digest: sha256_digest.to_owned(), + }) + } + + /// Return the canonical artifact leaf name. + #[must_use] + pub fn name(&self) -> &str { + &self.name + } + + /// Return the canonical lowercase `sha256:` artifact digest. + #[must_use] + pub fn sha256_digest(&self) -> &str { + &self.sha256_digest + } +} + +/// Validation error for one release artifact. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReleaseArtifactError { + /// The artifact name is not a canonical bounded leaf name. + InvalidName, + /// The artifact digest is not a canonical lowercase SHA-256 digest. + InvalidDigest, +} + +impl fmt::Display for ReleaseArtifactError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidName => { + formatter.write_str("release artifact name is not a canonical bounded leaf name") + } + Self::InvalidDigest => formatter.write_str( + "release artifact digest must be sha256: followed by 64 lowercase hexadecimal digits", + ), + } + } +} + +impl Error for ReleaseArtifactError {} + +/// Deterministic, bounded identity manifest for one OriginWeave release candidate. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReleaseManifest { + source_commit: String, + chromium_revision: String, + channel: ReleaseChannel, + artifacts: Vec, +} + +impl ReleaseManifest { + /// Construct an inert release manifest from exact identity evidence. + /// + /// Source identity is a full 40-digit lowercase Git commit SHA. Chromium revision is a + /// bounded canonical ASCII token. Artifact names must be unique and are sorted + /// deterministically before storage. Constructing this value does not authenticate any + /// artifact and does not authorize release or installation. + pub fn new( + source_commit: &str, + chromium_revision: &str, + channel: ReleaseChannel, + artifacts: I, + ) -> Result + where + I: IntoIterator, + { + if !valid_source_commit(source_commit) { + return Err(ReleaseManifestError::InvalidSourceCommit); + } + if !valid_revision(chromium_revision) { + return Err(ReleaseManifestError::InvalidChromiumRevision); + } + + let mut admitted = Vec::new(); + let mut artifact_names = BTreeSet::new(); + for artifact in artifacts { + if admitted.len() >= MAX_RELEASE_ARTIFACTS { + return Err(ReleaseManifestError::TooManyArtifacts); + } + if !artifact_names.insert(artifact.name.clone()) { + return Err(ReleaseManifestError::DuplicateArtifactName); + } + admitted.push(artifact); + } + if admitted.is_empty() { + return Err(ReleaseManifestError::MissingArtifacts); + } + admitted.sort_by(|left, right| left.name.cmp(&right.name)); + + Ok(Self { + source_commit: source_commit.to_owned(), + chromium_revision: chromium_revision.to_owned(), + channel, + artifacts: admitted, + }) + } + + /// Return the exact lowercase source commit bound by this manifest. + #[must_use] + pub fn source_commit(&self) -> &str { + &self.source_commit + } + + /// Return the canonical Chromium revision token bound by this manifest. + #[must_use] + pub fn chromium_revision(&self) -> &str { + &self.chromium_revision + } + + /// Return the release channel bound by this manifest. + #[must_use] + pub const fn channel(&self) -> ReleaseChannel { + self.channel + } + + /// Return artifacts sorted deterministically by canonical name. + #[must_use] + pub fn artifacts(&self) -> &[ReleaseArtifact] { + &self.artifacts + } +} + +/// Validation error for release-manifest identity evidence. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReleaseManifestError { + /// Source commit is not a full lowercase Git SHA-1 identity. + InvalidSourceCommit, + /// Chromium revision is not a canonical bounded release token. + InvalidChromiumRevision, + /// No release artifacts were supplied. + MissingArtifacts, + /// Artifact inventory exceeds the bounded release-manifest limit. + TooManyArtifacts, + /// Artifact inventory repeats a canonical artifact name. + DuplicateArtifactName, +} + +impl fmt::Display for ReleaseManifestError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidSourceCommit => formatter.write_str( + "release source commit must be exactly 40 lowercase hexadecimal digits", + ), + Self::InvalidChromiumRevision => { + formatter.write_str("Chromium revision must be a canonical bounded release token") + } + Self::MissingArtifacts => { + formatter.write_str("release manifest must contain at least one artifact") + } + Self::TooManyArtifacts => { + formatter.write_str("release manifest exceeds the artifact-count limit") + } + Self::DuplicateArtifactName => { + formatter.write_str("release manifest contains a duplicate artifact name") + } + } + } +} + +impl Error for ReleaseManifestError {} + +fn valid_artifact_name(name: &str) -> bool { + if name.is_empty() + || name.len() > MAX_RELEASE_ARTIFACT_NAME_BYTES + || !name.is_ascii() + || name.contains("..") + { + return false; + } + let bytes = name.as_bytes(); + bytes[0].is_ascii_alphanumeric() + && bytes[bytes.len() - 1].is_ascii_alphanumeric() + && bytes.iter().all(|byte| { + byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-') + }) +} + +fn valid_sha256_digest(digest: &str) -> bool { + let Some(hex) = digest.strip_prefix("sha256:") else { + return false; + }; + hex.len() == 64 + && hex + .bytes() + .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f')) +} + +fn valid_source_commit(source_commit: &str) -> bool { + source_commit.len() == 40 + && source_commit + .bytes() + .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f')) +} + +fn valid_revision(revision: &str) -> bool { + if revision.is_empty() || revision.len() > MAX_RELEASE_REVISION_BYTES || !revision.is_ascii() { + return false; + } + let bytes = revision.as_bytes(); + bytes[0].is_ascii_alphanumeric() + && bytes[bytes.len() - 1].is_ascii_alphanumeric() + && bytes.iter().all(|byte| { + byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'+' | b':' | b'@') + }) +} From 4077ecedfe1e1318c75be3807aea98864b99d9f5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 12:01:08 -0700 Subject: [PATCH 04/53] feat(core): expose release manifest identity contract --- crates/originweave-core/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index e33a7e7e5..663a9a4bd 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -7,6 +7,9 @@ #![forbid(unsafe_code)] #![deny(missing_docs)] +/// Fail-closed identity binding for release artifacts. +pub mod release_manifest; + use std::collections::BTreeSet; use std::fmt; use std::net::{Ipv4Addr, Ipv6Addr}; From 40a7a748d614febe7fc08e53e751e6def8299b9c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 12:02:55 -0700 Subject: [PATCH 05/53] style(core): apply canonical release manifest formatting --- crates/originweave-core/src/release_manifest.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index d95de87ae..2ae774f25 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -188,9 +188,8 @@ pub enum ReleaseManifestError { impl fmt::Display for ReleaseManifestError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InvalidSourceCommit => formatter.write_str( - "release source commit must be exactly 40 lowercase hexadecimal digits", - ), + Self::InvalidSourceCommit => formatter + .write_str("release source commit must be exactly 40 lowercase hexadecimal digits"), Self::InvalidChromiumRevision => { formatter.write_str("Chromium revision must be a canonical bounded release token") } @@ -220,9 +219,9 @@ fn valid_artifact_name(name: &str) -> bool { let bytes = name.as_bytes(); bytes[0].is_ascii_alphanumeric() && bytes[bytes.len() - 1].is_ascii_alphanumeric() - && bytes.iter().all(|byte| { - byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-') - }) + && bytes + .iter() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-')) } fn valid_sha256_digest(digest: &str) -> bool { From 3d13e54994dd31557230c9cc9603d6e42901e583 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 12:07:16 -0700 Subject: [PATCH 06/53] test(core): close exact release manifest coverage branches --- .../tests/release_manifest.rs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index cd63693db..3e52ea205 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -22,7 +22,7 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - [sbom, runtime], + vec![sbom, runtime], )?; assert_eq!(manifest.source_commit(), SOURCE_COMMIT); @@ -36,6 +36,18 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< assert_eq!(manifest.artifacts()[0].sha256_digest(), sha256_digest('a')); assert_eq!(manifest.artifacts()[1].name(), "originweave.spdx.json"); assert_eq!(manifest.artifacts()[1].sha256_digest(), sha256_digest('b')); + + let punctuation = ReleaseArtifact::new("originweave_cli-x86_64.bin", &sha256_digest('7'))?; + let punctuation_manifest = ReleaseManifest::new( + SOURCE_COMMIT, + "chromium_150-0+build:1@stable", + ReleaseChannel::Development, + vec![punctuation], + )?; + assert_eq!( + punctuation_manifest.chromium_revision(), + "chromium_150-0+build:1@stable" + ); Ok(()) } @@ -51,6 +63,7 @@ fn release_artifact_rejects_ambiguous_names_and_noncanonical_digests() { "artifact\\child.bin", "artifact..bin", "artifact-.bin-", + "artifact-µ.bin", overlong_name.as_str(), ] { assert_eq!( @@ -89,7 +102,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi invalid_commit, CHROMIUM_REVISION, ReleaseChannel::Development, - [artifact.clone()], + vec![artifact.clone()], ), Err(ReleaseManifestError::InvalidSourceCommit) ); @@ -102,6 +115,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi "chromium 150", "chromium/150", "chromium-150-", + "chromium-µ", overlong_revision.as_str(), ] { assert_eq!( @@ -109,7 +123,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, invalid_revision, ReleaseChannel::Beta, - [artifact.clone()], + vec![artifact.clone()], ), Err(ReleaseManifestError::InvalidChromiumRevision) ); @@ -120,7 +134,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - std::iter::empty(), + Vec::new(), ), Err(ReleaseManifestError::MissingArtifacts) ); @@ -131,7 +145,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - [artifact.clone(), duplicate], + vec![artifact.clone(), duplicate], ), Err(ReleaseManifestError::DuplicateArtifactName) ); From 28d4d485e2612ec481303ffcd8571560d1585636 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 12:12:48 -0700 Subject: [PATCH 07/53] docs(changelog): record release manifest identity primitive --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f747adeae..b664d33d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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. +- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, and deterministic bounded artifact leaf names with lowercase SHA-256 digests without granting signing, publication, installation, update, rollback, or release authority. - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. From 8444244985336f2f74531eb97b4dbadffed032e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:22:48 -0700 Subject: [PATCH 08/53] test(core): reject case-colliding release artifacts --- crates/originweave-core/tests/release_manifest.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index 3e52ea205..5085dfc93 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -150,6 +150,17 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi Err(ReleaseManifestError::DuplicateArtifactName) ); + let case_collision = ReleaseArtifact::new("ORIGINWEAVE.BIN", &sha256_digest('6'))?; + assert_eq!( + ReleaseManifest::new( + SOURCE_COMMIT, + CHROMIUM_REVISION, + ReleaseChannel::Stable, + vec![artifact.clone(), case_collision], + ), + Err(ReleaseManifestError::DuplicateArtifactName) + ); + let mut too_many = Vec::new(); for index in 0..=MAX_RELEASE_ARTIFACTS { too_many.push(ReleaseArtifact::new( From c9ce1f70f3206e363593f3296361c60b4a69f723 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:25:27 -0700 Subject: [PATCH 09/53] fix(core): reject case-colliding release artifacts --- crates/originweave-core/src/release_manifest.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 2ae774f25..06e570744 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -102,9 +102,11 @@ impl ReleaseManifest { /// Construct an inert release manifest from exact identity evidence. /// /// Source identity is a full 40-digit lowercase Git commit SHA. Chromium revision is a - /// bounded canonical ASCII token. Artifact names must be unique and are sorted - /// deterministically before storage. Constructing this value does not authenticate any - /// artifact and does not authorize release or installation. + /// bounded canonical ASCII token. Artifact names must be unique under ASCII case folding + /// so one manifest cannot bind two names that collide on a case-insensitive target + /// filesystem; original spelling is preserved and artifacts are sorted deterministically + /// before storage. Constructing this value does not authenticate any artifact and does not + /// authorize release or installation. pub fn new( source_commit: &str, chromium_revision: &str, @@ -127,7 +129,7 @@ impl ReleaseManifest { if admitted.len() >= MAX_RELEASE_ARTIFACTS { return Err(ReleaseManifestError::TooManyArtifacts); } - if !artifact_names.insert(artifact.name.clone()) { + if !artifact_names.insert(artifact.name.to_ascii_lowercase()) { return Err(ReleaseManifestError::DuplicateArtifactName); } admitted.push(artifact); @@ -181,7 +183,7 @@ pub enum ReleaseManifestError { MissingArtifacts, /// Artifact inventory exceeds the bounded release-manifest limit. TooManyArtifacts, - /// Artifact inventory repeats a canonical artifact name. + /// Artifact inventory repeats an ASCII-case-folded artifact name. DuplicateArtifactName, } From 279d136a45a63c9448a52003117174c90f4480ed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:26:08 -0700 Subject: [PATCH 10/53] docs(changelog): record cross-platform artifact collision guard --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b664d33d8..f45fad3bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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. -- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, and deterministic bounded artifact leaf names with lowercase SHA-256 digests without granting signing, publication, installation, update, rollback, or release authority. +- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, and deterministic bounded artifact leaf names with lowercase SHA-256 digests; artifact identity is unique under ASCII case folding so case-only names cannot collide on case-insensitive target filesystems, without granting signing, publication, installation, update, rollback, or release authority. - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. From a5ef8ae980f50716922b1e65d92a5b494a48cf2c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:32:31 -0700 Subject: [PATCH 11/53] test(core): reject Windows device artifact names --- .../tests/release_manifest.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index 5085dfc93..fbcbeec2e 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -64,11 +64,28 @@ fn release_artifact_rejects_ambiguous_names_and_noncanonical_digests() { "artifact..bin", "artifact-.bin-", "artifact-µ.bin", + "CON", + "con.zip", + "PRN.tar.zst", + "aux.bin", + "NUL.spdx.json", + "COM1", + "com9.zip", + "LPT1", + "lpt9.tar.zst", overlong_name.as_str(), ] { assert_eq!( ReleaseArtifact::new(invalid_name, &valid_digest), - Err(ReleaseArtifactError::InvalidName) + Err(ReleaseArtifactError::InvalidName), + "reserved or ambiguous artifact name must fail closed: {invalid_name}" + ); + } + + for valid_name in ["console.bin", "com10.bin", "lpt10.tar.zst"] { + assert!( + ReleaseArtifact::new(valid_name, &valid_digest).is_ok(), + "non-device artifact name must remain admissible: {valid_name}" ); } From 4c159cb152aba7ef1059c5bb4fce6e7d8c437203 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:35:46 -0700 Subject: [PATCH 12/53] fix(core): reject Windows device artifact names --- .../originweave-core/src/release_manifest.rs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 06e570744..d7c90af2e 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -38,7 +38,8 @@ impl ReleaseArtifact { /// /// The digest must use the exact `sha256:` prefix followed by 64 lowercase hexadecimal /// digits. Artifact names are ASCII leaf names and cannot contain path separators, - /// traversal-like double dots, leading punctuation, or trailing punctuation. + /// traversal-like double dots, leading or trailing punctuation, or Windows reserved device + /// basenames (including those basenames followed by extensions). pub fn new(name: &str, sha256_digest: &str) -> Result { if !valid_artifact_name(name) { return Err(ReleaseArtifactError::InvalidName); @@ -215,6 +216,7 @@ fn valid_artifact_name(name: &str) -> bool { || name.len() > MAX_RELEASE_ARTIFACT_NAME_BYTES || !name.is_ascii() || name.contains("..") + || windows_reserved_device_basename(name) { return false; } @@ -226,6 +228,27 @@ fn valid_artifact_name(name: &str) -> bool { .all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-')) } +fn windows_reserved_device_basename(name: &str) -> bool { + let basename = match name.find('.') { + Some(dot_index) => &name[..dot_index], + None => name, + }; + + if basename.eq_ignore_ascii_case("CON") + || basename.eq_ignore_ascii_case("PRN") + || basename.eq_ignore_ascii_case("AUX") + || basename.eq_ignore_ascii_case("NUL") + { + return true; + } + + let bytes = basename.as_bytes(); + bytes.len() == 4 + && (basename[..3].eq_ignore_ascii_case("COM") + || basename[..3].eq_ignore_ascii_case("LPT")) + && matches!(bytes[3], b'1'..=b'9') +} + fn valid_sha256_digest(digest: &str) -> bool { let Some(hex) = digest.strip_prefix("sha256:") else { return false; From 19ee0a81f8fb8c38017d8f52c264a54e079fbd05 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:37:19 -0700 Subject: [PATCH 13/53] style(core): apply canonical release-manifest formatting --- crates/originweave-core/src/release_manifest.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index d7c90af2e..972ff4fe7 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -244,8 +244,7 @@ fn windows_reserved_device_basename(name: &str) -> bool { let bytes = basename.as_bytes(); bytes.len() == 4 - && (basename[..3].eq_ignore_ascii_case("COM") - || basename[..3].eq_ignore_ascii_case("LPT")) + && (basename[..3].eq_ignore_ascii_case("COM") || basename[..3].eq_ignore_ascii_case("LPT")) && matches!(bytes[3], b'1'..=b'9') } From f51c964fab842b212b1d4c4cacbba3d0663034c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:41:54 -0700 Subject: [PATCH 14/53] test(core): cover ordinary four-byte artifact basenames --- crates/originweave-core/tests/release_manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index fbcbeec2e..8155af484 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -82,7 +82,7 @@ fn release_artifact_rejects_ambiguous_names_and_noncanonical_digests() { ); } - for valid_name in ["console.bin", "com10.bin", "lpt10.tar.zst"] { + for valid_name in ["console.bin", "com10.bin", "lpt10.tar.zst", "data.bin"] { assert!( ReleaseArtifact::new(valid_name, &valid_digest).is_ok(), "non-device artifact name must remain admissible: {valid_name}" From d3ce9423f02638f4299eaaa981e38e53a65eef54 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:44:39 -0700 Subject: [PATCH 15/53] docs(adr): record release manifest identity boundary --- docs/adr/0015-release-manifest-identity.md | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/adr/0015-release-manifest-identity.md diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md new file mode 100644 index 000000000..093e372df --- /dev/null +++ b/docs/adr/0015-release-manifest-identity.md @@ -0,0 +1,101 @@ +# ADR 0015: Release manifest identity boundary + +- Status: Proposed +- Date: 2026-08-23 + +## Context + +Issue #201 requires commercial OriginWeave releases to bind an exact OriginWeave source identity, Chromium revision, release channel, and the artifacts buyers install or verify. That larger release lifecycle will later add signing, SBOM/SLSA provenance, updater trust, rollback, platform support, and operational acceptance. Those authorities do not yet exist in the current source tree. + +A smaller durable boundary is nevertheless required before packaging work can safely compose: one deterministic, bounded manifest identity for a release candidate. Without an explicit contract, equivalent release inventories can be represented differently, case-insensitive target filesystems can collapse distinct names, and host-specific device namespaces can reinterpret an apparent artifact leaf name. + +## Decision drivers + +- One release-candidate identity must not depend on caller ordering. +- Artifact references must remain leaf identities rather than filesystem paths. +- The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms. +- Manifest construction must remain inert metadata admission and must not grant signing, publication, installation, update, rollback, or release authority. +- Inputs must be bounded and fail closed before later packaging, signing, or updater layers consume them. + +## Assumptions and authority boundaries + +- Source identity is a full 40-character lowercase Git commit SHA. +- Chromium identity is a bounded canonical ASCII release token; this ADR does not claim that the token alone authenticates Chromium bytes. +- A release channel is explicit (`Stable`, `Beta`, or `Development`) metadata, not authorization to publish or promote a release. +- Every admitted artifact carries a bounded ASCII leaf name and an exact lowercase `sha256:` digest. +- The manifest contains no private signing material, credentials, secrets, installer authority, network authority, or update authority. +- Later release systems must independently authenticate the build, signer, provenance, platform package, updater metadata, and operational acceptance evidence. + +## Options considered + +### Caller-order manifest with host-local filenames + +Rejected. Caller ordering makes identity representation unstable, and host-local filename rules can produce collisions or device-name reinterpretation on another supported platform. + +### Canonical manifest that normalizes stored spelling + +Rejected for this slice. Destructively rewriting admitted artifact spelling would make the manifest differ from the exact release artifact identity that packaging and verification need to preserve. + +### Canonical admission with preserved spelling and collision guards + +Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, and separately reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. + +## Decision + +OriginWeave release-manifest admission is a deterministic, bounded, fail-closed identity contract: + +1. `source_commit` must be exactly 40 lowercase hexadecimal digits. +2. `chromium_revision` must be a non-empty bounded canonical ASCII token. +3. `channel` must be an explicit `ReleaseChannel` variant. +4. The artifact inventory must be non-empty and contain at most 64 entries. +5. Each artifact name must be a bounded ASCII leaf name containing only alphanumerics, `.`, `_`, and `-`; it cannot contain path separators, traversal-like `..`, leading punctuation, or trailing punctuation. +6. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9` are rejected case-insensitively, including when followed by an extension. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. +7. Artifact names must be unique under ASCII case folding while their original admitted spelling is retained. +8. Each artifact digest must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. +9. Admitted artifacts are stored in deterministic name order. +10. Validation errors remain typed, deterministic standard Rust errors. + +Constructing or possessing a valid manifest does **not** authenticate an artifact, prove reproducibility, prove provenance, verify a signature, establish a signing identity, authorize a release channel, publish software, install software, update software, roll software back, or satisfy release acceptance. + +## Consequences + +The release candidate receives one bounded artifact-identity representation that is stable across caller order and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. + +The contract is intentionally narrower than issue #201's final release manifest. Additional fields such as toolchain identity, adapter versions, build environment, dependency locks, signing identity, timestamp, SBOM/provenance references, and platform package identity remain future reviewed work rather than being inferred from this primitive. + +## Failure and degraded behavior + +Malformed, ambiguous, duplicate, unbounded, or empty identity evidence fails closed before a manifest is produced. There is no fallback that silently rewrites an invalid name, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. + +A caller that cannot provide canonical evidence does not receive a release manifest. That failure is not converted into permission to sign, publish, install, or update through another path. + +## Security / privacy / governance impact + +The boundary reduces path-confusion and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. + +## Tests and acceptance evidence + +The owning `originweave-core` tests must cover valid deterministic ordering, exact digests, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel access, and deterministic standard error contracts. + +Owned production function, line, region, and branch coverage remains exactly 100% on the unchanged reviewed head. CI/security/scanner evidence is exact-head evidence only; predecessor or model-only evidence cannot satisfy acceptance. + +## Migration and rollback + +This is a new admission primitive with no protected-main persisted manifest migration. If the contract proves incompatible before acceptance, the Proposed ADR and owning feature branch can be revised or withdrawn without granting legacy inputs grandfathered authority. + +After acceptance and external release artifacts depend on this schema, any incompatible identity change requires an explicit versioning/migration decision rather than silent parser broadening. + +## Open follow-ups + +- Complete issue #201's signed cross-platform distribution and updater trust architecture. +- Bind toolchain, adapter, build-environment, dependency-lock, SBOM/SLSA provenance, signing, timestamp, and platform-package evidence through separately reviewed contracts. +- Define manifest serialization/versioning before any durable external release-manifest format is promised. +- Add signing-key rotation, compromise response, update rollback/freeze protection, and platform release acceptance without transferring authority from this metadata type. + +## Supersession / reversal conditions + +Supersede this ADR when a versioned external release-manifest specification replaces the in-process identity primitive, or when supported-platform packaging requires a stronger canonical filename identity. Reversal must preserve fail-closed artifact identity and cannot make possession of metadata equivalent to release authority. + +## References + +Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file From 222e62286c7692f30ba329584d5ca114bdfb6486 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:55:36 -0700 Subject: [PATCH 16/53] docs: index proposed release manifest ADR --- docs/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 1ea57ad29..a9e520be9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -89,10 +89,11 @@ The second group exists only on this documentation branch until the branch integ ### Proposed decisions introduced by active feature work +- [ADR 0015: Release manifest identity boundary](adr/0015-release-manifest-identity.md) - [ADR 0016: BAP task lifecycle and state authority](adr/0016-bap-task-lifecycle-authority.md) -ADR 0016 is owned by this active BAP lifecycle feature branch and remains Proposed. Its presence here makes the branch documentation graph complete without presenting the decision or implementation as protected-main truth before integration. +ADR 0015 is active-PR architecture evidence for the release-manifest identity slice. ADR 0016 is owned by this active BAP lifecycle feature branch. Both remain Proposed. Their presence here makes the branch documentation graph complete without presenting either decision or implementation as protected-main truth before integration. -After protected-main integration, retain this subsection only when it is intentionally serving as historical provenance; otherwise protected-main reconciliation must remove it. In either case, integration alone does not change ADR 0016 from Proposed or assert implementation maturity. +After protected-main integration, retain this subsection only when it is intentionally serving as historical provenance; otherwise protected-main reconciliation must remove it. In either case, integration alone does not change these ADRs from Proposed or assert implementation maturity. See the [ADR index](adr/README.md) for status rules, required decision structure, supersession rules, and active feature ADRs. The index and each ADR's own status metadata must agree; a PR body, chat transcript, automation prompt, or stale issue reference cannot change ADR status. From 5f9fe3ba108cb9db0b1c5d0f0f841d088ddfc04f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 13:56:27 -0700 Subject: [PATCH 17/53] docs: index active release manifest ADR --- docs/adr/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/adr/README.md b/docs/adr/README.md index 5f9e2a878..69994b9b1 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -61,11 +61,12 @@ ADR 0013 and ADR 0014 exist only on this documentation branch until it integrate | ADR | Decision | Status | Governs | |---|---|---|---| +| [0015](0015-release-manifest-identity.md) | Release manifest identity boundary | Proposed | bounded release-candidate metadata identity, deterministic artifact ordering, cross-platform filename collision guards, and explicit exclusion of signing/publication/install/update authority | | [0016](0016-bap-task-lifecycle-authority.md) | BAP task lifecycle and state authority | Proposed | BAP task states, transitions, recovery validation, transition sequencing, and authority separation | -ADR 0016 belongs to the active BAP lifecycle feature branch. Indexing it makes the branch documentation graph complete while preserving its Proposed lifecycle and active-PR, non-protected-main maturity. +ADR 0015 is active-PR architecture evidence for the release-manifest identity slice. ADR 0016 belongs to the active BAP lifecycle feature branch. Both remain Proposed. Indexing them makes the branch documentation graph complete without promoting either decision or its implementation to protected-main truth. -After protected-main integration, retain this subsection only when it is intentionally serving as historical provenance; otherwise protected-main reconciliation must remove it. In either case, integration alone does not change ADR 0016 from Proposed or assert implementation maturity. +After protected-main integration, retain this subsection only when it is intentionally serving as historical provenance; otherwise protected-main reconciliation must remove it. In either case, integration alone does not change these ADRs from Proposed or assert implementation maturity. Other active feature PRs may contain additional Proposed ADRs. Those files are not part of this canonical documentation line until integrated or deliberately reconciled here. Historical PR checks, stale branch state, or chat decisions never transfer ADR acceptance across a changed head. From c02d46a9890ad830bb4259daec648381fd391a22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:00:10 -0700 Subject: [PATCH 18/53] docs: trace release filename portability evidence --- docs/doctoring.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/doctoring.md b/docs/doctoring.md index ec51daaf3..cd40d85ea 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -82,6 +82,10 @@ Credential-free TLS evidence records the canonical origin, TCP peers, reference The test-only rcgen 0.14.8 dependency creates a local CA and deterministic certificate-policy scenarios. It is not part of production arithmetic or trust. Tests cover trusted DNS identity, Common Name non-fallback, wrong name, untrusted root, expired and not-yet-valid validity, exact IPv4 and IPv6 SAN identity, TLS 1.2 and TLS 1.3, required and optional ALPN, and equality between TLS origin and TCP authority. +### Release artifact filename portability + +Microsoft's Win32 filename guidance requires applications not to assume case sensitivity and reserves device basenames including `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9`, including those names followed by extensions. The same documentation also identifies non-ASCII superscript-digit device aliases. OriginWeave's release-manifest identity therefore rejects ASCII-case-folded duplicate artifact names and those reserved ASCII device basenames; its ASCII-only artifact grammar separately excludes the superscript aliases. These checks make one admitted artifact inventory portable across case-sensitive and default case-insensitive target filesystems without rewriting the stored artifact spelling. They remain identity hygiene only and do not grant signing, publication, installation, update, rollback, or release authority. + ### Crawling policy RFC 9309 standardizes robots parsing, matching, error handling, and caching. It also states that robots rules are not access authorization. OriginWeave therefore requires robots evidence for public crawler mode while maintaining authentication, terms, rate, privacy, and retention policy as separate controls. @@ -156,6 +160,8 @@ Lodderstedt, T., Bradley, J., Labunets, A., & Fett, D. (2025). *OAuth 2.0 securi Microsoft. (2025, July 25). *Azure IP address 168.63.129.16 overview*. Microsoft Learn. https://learn.microsoft.com/azure/virtual-network/what-is-ip-address-168-63-129-16 +Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file + Model Context Protocol. (2026, July 28). *Specification: 2026-07-28*. https://modelcontextprotocol.io/specification/2026-07-28 Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2025). *Learning to orchestrate agents in natural language with the Conductor* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2512.04388 @@ -196,4 +202,4 @@ World Wide Web Consortium. (2026, June 1). *WebDriver BiDi* (W3C Working Draft). Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2025). *TRINITY: An evolved LLM coordinator* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2512.04695 -Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., Cheng, X., Ou, T., Bisk, Y., Fried, D., Alon, U., & Neubig, G. (2023). *WebArena: A realistic web environment for building autonomous agents*. arXiv. https://doi.org/10.48550/arXiv.2307.13854 +Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., Cheng, X., Ou, T., Bisk, Y., Fried, D., Alon, U., & Neubig, G. (2023). *WebArena: A realistic web environment for building autonomous agents*. arXiv. https://doi.org/10.48550/arXiv.2307.13854 \ No newline at end of file From 3d187006a04e837994c82d625e24dc5135a42961 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:26:04 -0700 Subject: [PATCH 19/53] test(core): require release build identity evidence --- .../tests/release_manifest_build_identity.rs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 crates/originweave-core/tests/release_manifest_build_identity.rs diff --git a/crates/originweave-core/tests/release_manifest_build_identity.rs b/crates/originweave-core/tests/release_manifest_build_identity.rs new file mode 100644 index 000000000..02fbbcef5 --- /dev/null +++ b/crates/originweave-core/tests/release_manifest_build_identity.rs @@ -0,0 +1,71 @@ +use std::error::Error; + +use originweave_core::release_manifest::{ + MAX_RELEASE_TOOLCHAIN_BYTES, ReleaseBuildIdentity, ReleaseBuildIdentityError, +}; + +fn sha256_digest(hex_digit: char) -> String { + format!("sha256:{}", hex_digit.to_string().repeat(64)) +} + +#[test] +fn release_build_identity_binds_exact_toolchain_and_dependency_lock() -> Result<(), Box> { + let identity = ReleaseBuildIdentity::new("1.97.1", &sha256_digest('a'))?; + + assert_eq!(identity.rust_toolchain(), "1.97.1"); + assert_eq!(identity.dependency_lock_sha256(), sha256_digest('a')); + Ok(()) +} + +#[test] +fn release_build_identity_rejects_ambiguous_or_unbounded_evidence() { + let digest = sha256_digest('b'); + let overlong_toolchain = "a".repeat(MAX_RELEASE_TOOLCHAIN_BYTES + 1); + + for invalid_toolchain in [ + "", + " 1.97.1", + "1.97.1 ", + "rust 1.97.1", + "1.97.1/nightly", + "1.97.1-µ", + overlong_toolchain.as_str(), + ] { + assert_eq!( + ReleaseBuildIdentity::new(invalid_toolchain, &digest), + Err(ReleaseBuildIdentityError::InvalidRustToolchain), + ); + } + + for invalid_digest in [ + "", + "sha256:abc", + "SHA256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "sha256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "sha256:gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg", + ] { + assert_eq!( + ReleaseBuildIdentity::new("1.97.1", invalid_digest), + Err(ReleaseBuildIdentityError::InvalidDependencyLockDigest), + ); + } +} + +#[test] +fn release_build_identity_errors_are_standard_source_free_rust_errors() { + let errors = [ + ( + ReleaseBuildIdentityError::InvalidRustToolchain, + "release Rust toolchain is not a canonical bounded token", + ), + ( + ReleaseBuildIdentityError::InvalidDependencyLockDigest, + "release dependency lock digest must be sha256: followed by 64 lowercase hexadecimal digits", + ), + ]; + + for (error, expected) in errors { + assert_eq!(error.to_string(), expected); + assert!(Error::source(&error).is_none()); + } +} From cf3a4d3ba9cc1fd9964a35e9c69c3d5f2aaebdbd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:28:12 -0700 Subject: [PATCH 20/53] test(core): canonicalize release build identity regression --- .../originweave-core/tests/release_manifest_build_identity.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_manifest_build_identity.rs b/crates/originweave-core/tests/release_manifest_build_identity.rs index 02fbbcef5..86d485427 100644 --- a/crates/originweave-core/tests/release_manifest_build_identity.rs +++ b/crates/originweave-core/tests/release_manifest_build_identity.rs @@ -9,7 +9,8 @@ fn sha256_digest(hex_digit: char) -> String { } #[test] -fn release_build_identity_binds_exact_toolchain_and_dependency_lock() -> Result<(), Box> { +fn release_build_identity_binds_exact_toolchain_and_dependency_lock() -> Result<(), Box> +{ let identity = ReleaseBuildIdentity::new("1.97.1", &sha256_digest('a'))?; assert_eq!(identity.rust_toolchain(), "1.97.1"); From 78d31dc5ff7466a069c62915d69c225a7f4add32 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:32:05 -0700 Subject: [PATCH 21/53] feat(core): bind release build identity evidence --- .../originweave-core/src/release_manifest.rs | 104 +++++++++++++++++- .../tests/release_manifest.rs | 21 +++- 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 972ff4fe7..879739f41 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -1,8 +1,8 @@ //! 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, and artifact digests without granting -//! signing, publication, installation, update, rollback, or release authority. +//! source commit, Chromium revision, release channel, build identity, and artifact digests +//! without granting signing, publication, installation, update, rollback, or release authority. use std::collections::BTreeSet; use std::error::Error; @@ -14,6 +14,8 @@ pub const MAX_RELEASE_ARTIFACTS: usize = 64; pub const MAX_RELEASE_ARTIFACT_NAME_BYTES: usize = 128; /// Maximum UTF-8 byte length admitted for one Chromium revision token. pub const MAX_RELEASE_REVISION_BYTES: usize = 128; +/// Maximum UTF-8 byte length admitted for one canonical Rust toolchain token. +pub const MAX_RELEASE_TOOLCHAIN_BYTES: usize = 64; /// Buyer-visible release channel bound by a release manifest. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -26,6 +28,73 @@ pub enum ReleaseChannel { Development, } +/// Exact build identity retained by one release manifest. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReleaseBuildIdentity { + rust_toolchain: String, + dependency_lock_sha256: String, +} + +impl ReleaseBuildIdentity { + /// Construct bounded build identity from a canonical Rust toolchain token and lock digest. + /// + /// The dependency-lock digest must use the exact `sha256:` prefix followed by 64 lowercase + /// hexadecimal digits. Constructing this value does not prove reproducibility or authenticate + /// the build environment; it only prevents those two identity fields from being omitted or + /// represented ambiguously in a release manifest. + pub fn new( + rust_toolchain: &str, + dependency_lock_sha256: &str, + ) -> Result { + if !valid_toolchain(rust_toolchain) { + return Err(ReleaseBuildIdentityError::InvalidRustToolchain); + } + if !valid_sha256_digest(dependency_lock_sha256) { + return Err(ReleaseBuildIdentityError::InvalidDependencyLockDigest); + } + Ok(Self { + rust_toolchain: rust_toolchain.to_owned(), + dependency_lock_sha256: dependency_lock_sha256.to_owned(), + }) + } + + /// Return the exact canonical Rust toolchain token. + #[must_use] + pub fn rust_toolchain(&self) -> &str { + &self.rust_toolchain + } + + /// Return the exact lowercase `sha256:` dependency-lock digest. + #[must_use] + pub fn dependency_lock_sha256(&self) -> &str { + &self.dependency_lock_sha256 + } +} + +/// Validation error for release build-identity evidence. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReleaseBuildIdentityError { + /// Rust toolchain is not a canonical bounded token. + InvalidRustToolchain, + /// Dependency-lock digest is not a canonical lowercase SHA-256 digest. + InvalidDependencyLockDigest, +} + +impl fmt::Display for ReleaseBuildIdentityError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidRustToolchain => { + formatter.write_str("release Rust toolchain is not a canonical bounded token") + } + Self::InvalidDependencyLockDigest => formatter.write_str( + "release dependency lock digest must be sha256: followed by 64 lowercase hexadecimal digits", + ), + } + } +} + +impl Error for ReleaseBuildIdentityError {} + /// One canonical release artifact identity. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ReleaseArtifact { @@ -96,22 +165,25 @@ pub struct ReleaseManifest { source_commit: String, chromium_revision: String, channel: ReleaseChannel, + build_identity: ReleaseBuildIdentity, artifacts: Vec, } impl ReleaseManifest { /// Construct an inert release manifest from exact identity evidence. /// - /// Source identity is a full 40-digit lowercase Git commit SHA. Chromium revision is a - /// bounded canonical ASCII token. Artifact names must be unique under ASCII case folding + /// Source identity is a full 40-digit lowercase Git commit SHA. Chromium revision and Rust + /// toolchain are bounded canonical ASCII tokens, while dependency-lock identity is a + /// canonical lowercase SHA-256 digest. Artifact names must be unique under ASCII case folding /// so one manifest cannot bind two names that collide on a case-insensitive target /// filesystem; original spelling is preserved and artifacts are sorted deterministically - /// before storage. Constructing this value does not authenticate any artifact and does not - /// authorize release or installation. + /// before storage. Constructing this value does not authenticate any artifact, prove + /// reproducibility, or authorize release or installation. pub fn new( source_commit: &str, chromium_revision: &str, channel: ReleaseChannel, + build_identity: ReleaseBuildIdentity, artifacts: I, ) -> Result where @@ -144,6 +216,7 @@ impl ReleaseManifest { source_commit: source_commit.to_owned(), chromium_revision: chromium_revision.to_owned(), channel, + build_identity, artifacts: admitted, }) } @@ -166,6 +239,12 @@ impl ReleaseManifest { self.channel } + /// Return the exact build identity bound by this manifest. + #[must_use] + pub const fn build_identity(&self) -> &ReleaseBuildIdentity { + &self.build_identity + } + /// Return artifacts sorted deterministically by canonical name. #[must_use] pub fn artifacts(&self) -> &[ReleaseArtifact] { @@ -276,3 +355,16 @@ fn valid_revision(revision: &str) -> bool { byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'+' | b':' | b'@') }) } + +fn valid_toolchain(toolchain: &str) -> bool { + if toolchain.is_empty() || toolchain.len() > MAX_RELEASE_TOOLCHAIN_BYTES || !toolchain.is_ascii() + { + return false; + } + let bytes = toolchain.as_bytes(); + bytes[0].is_ascii_alphanumeric() + && bytes[bytes.len() - 1].is_ascii_alphanumeric() + && bytes.iter().all(|byte| { + byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'+') + }) +} diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index 8155af484..c6fe23076 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -2,7 +2,8 @@ use std::error::Error; use originweave_core::release_manifest::{ MAX_RELEASE_ARTIFACT_NAME_BYTES, MAX_RELEASE_ARTIFACTS, MAX_RELEASE_REVISION_BYTES, - ReleaseArtifact, ReleaseArtifactError, ReleaseChannel, ReleaseManifest, ReleaseManifestError, + ReleaseArtifact, ReleaseArtifactError, ReleaseBuildIdentity, ReleaseChannel, ReleaseManifest, + ReleaseManifestError, }; const SOURCE_COMMIT: &str = "0123456789abcdef0123456789abcdef01234567"; @@ -12,6 +13,11 @@ fn sha256_digest(hex_digit: char) -> String { format!("sha256:{}", hex_digit.to_string().repeat(64)) } +fn build_identity() -> ReleaseBuildIdentity { + ReleaseBuildIdentity::new("1.97.1", &sha256_digest('9')) + .expect("release build identity fixture must be canonical") +} + #[test] fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result<(), Box> { @@ -22,12 +28,18 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, + build_identity(), vec![sbom, runtime], )?; assert_eq!(manifest.source_commit(), SOURCE_COMMIT); assert_eq!(manifest.chromium_revision(), CHROMIUM_REVISION); assert_eq!(manifest.channel(), ReleaseChannel::Stable); + assert_eq!(manifest.build_identity().rust_toolchain(), "1.97.1"); + assert_eq!( + manifest.build_identity().dependency_lock_sha256(), + sha256_digest('9') + ); assert_eq!(manifest.artifacts().len(), 2); assert_eq!( manifest.artifacts()[0].name(), @@ -42,6 +54,7 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< SOURCE_COMMIT, "chromium_150-0+build:1@stable", ReleaseChannel::Development, + build_identity(), vec![punctuation], )?; assert_eq!( @@ -119,6 +132,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi invalid_commit, CHROMIUM_REVISION, ReleaseChannel::Development, + build_identity(), vec![artifact.clone()], ), Err(ReleaseManifestError::InvalidSourceCommit) @@ -140,6 +154,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, invalid_revision, ReleaseChannel::Beta, + build_identity(), vec![artifact.clone()], ), Err(ReleaseManifestError::InvalidChromiumRevision) @@ -151,6 +166,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, + build_identity(), Vec::new(), ), Err(ReleaseManifestError::MissingArtifacts) @@ -162,6 +178,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, + build_identity(), vec![artifact.clone(), duplicate], ), Err(ReleaseManifestError::DuplicateArtifactName) @@ -173,6 +190,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, + build_identity(), vec![artifact.clone(), case_collision], ), Err(ReleaseManifestError::DuplicateArtifactName) @@ -190,6 +208,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, + build_identity(), too_many, ), Err(ReleaseManifestError::TooManyArtifacts) From 8e75f34c69114ee2986c010c3909340dc0bedfce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:34:21 -0700 Subject: [PATCH 22/53] style(core): canonicalize release build identity formatting --- crates/originweave-core/src/release_manifest.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 879739f41..5db6e15a5 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -357,14 +357,16 @@ fn valid_revision(revision: &str) -> bool { } fn valid_toolchain(toolchain: &str) -> bool { - if toolchain.is_empty() || toolchain.len() > MAX_RELEASE_TOOLCHAIN_BYTES || !toolchain.is_ascii() + if toolchain.is_empty() + || toolchain.len() > MAX_RELEASE_TOOLCHAIN_BYTES + || !toolchain.is_ascii() { return false; } let bytes = toolchain.as_bytes(); bytes[0].is_ascii_alphanumeric() && bytes[bytes.len() - 1].is_ascii_alphanumeric() - && bytes.iter().all(|byte| { - byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'+') - }) + && bytes + .iter() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'+')) } From 520142a310c4d2022a113cce00d850d4759e3483 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:38:24 -0700 Subject: [PATCH 23/53] docs(adr): bind release build identity evidence --- docs/adr/0015-release-manifest-identity.md | 43 ++++++++++++---------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md index 093e372df..49440dd92 100644 --- a/docs/adr/0015-release-manifest-identity.md +++ b/docs/adr/0015-release-manifest-identity.md @@ -5,13 +5,14 @@ ## Context -Issue #201 requires commercial OriginWeave releases to bind an exact OriginWeave source identity, Chromium revision, release channel, and the artifacts buyers install or verify. That larger release lifecycle will later add signing, SBOM/SLSA provenance, updater trust, rollback, platform support, and operational acceptance. Those authorities do not yet exist in the current source tree. +Issue #201 requires commercial OriginWeave releases to bind an exact OriginWeave source identity, Chromium revision, release channel, build identity, and the artifacts buyers install or verify. That larger release lifecycle will later add signing, SBOM/SLSA provenance, updater trust, rollback, platform support, and operational acceptance. Those authorities do not yet exist in the current source tree. -A smaller durable boundary is nevertheless required before packaging work can safely compose: one deterministic, bounded manifest identity for a release candidate. Without an explicit contract, equivalent release inventories can be represented differently, case-insensitive target filesystems can collapse distinct names, and host-specific device namespaces can reinterpret an apparent artifact leaf name. +A smaller durable boundary is nevertheless required before packaging work can safely compose: one deterministic, bounded manifest identity for a release candidate. Without an explicit contract, equivalent release inventories can be represented differently, exact build inputs can be omitted or represented ambiguously, case-insensitive target filesystems can collapse distinct names, and host-specific device namespaces can reinterpret an apparent artifact leaf name. ## Decision drivers - One release-candidate identity must not depend on caller ordering. +- The manifest must not omit or ambiguously represent the exact Rust toolchain and dependency-lock identity used for the candidate. - Artifact references must remain leaf identities rather than filesystem paths. - The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms. - Manifest construction must remain inert metadata admission and must not grant signing, publication, installation, update, rollback, or release authority. @@ -22,6 +23,8 @@ A smaller durable boundary is nevertheless required before packaging work can sa - Source identity is a full 40-character lowercase Git commit SHA. - Chromium identity is a bounded canonical ASCII release token; this ADR does not claim that the token alone authenticates Chromium bytes. - A release channel is explicit (`Stable`, `Beta`, or `Development`) metadata, not authorization to publish or promote a release. +- Rust toolchain identity is a non-empty bounded canonical ASCII token; binding that token does not prove that a build actually used it. +- Dependency-lock identity is an exact lowercase `sha256:` digest; binding that digest does not authenticate the dependency source, build environment, or resulting artifact. - Every admitted artifact carries a bounded ASCII leaf name and an exact lowercase `sha256:` digest. - The manifest contains no private signing material, credentials, secrets, installer authority, network authority, or update authority. - Later release systems must independently authenticate the build, signer, provenance, platform package, updater metadata, and operational acceptance evidence. @@ -38,7 +41,7 @@ Rejected for this slice. Destructively rewriting admitted artifact spelling woul ### Canonical admission with preserved spelling and collision guards -Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, and separately reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. +Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. ## Decision @@ -47,35 +50,37 @@ OriginWeave release-manifest admission is a deterministic, bounded, fail-closed 1. `source_commit` must be exactly 40 lowercase hexadecimal digits. 2. `chromium_revision` must be a non-empty bounded canonical ASCII token. 3. `channel` must be an explicit `ReleaseChannel` variant. -4. The artifact inventory must be non-empty and contain at most 64 entries. -5. Each artifact name must be a bounded ASCII leaf name containing only alphanumerics, `.`, `_`, and `-`; it cannot contain path separators, traversal-like `..`, leading punctuation, or trailing punctuation. -6. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9` are rejected case-insensitively, including when followed by an extension. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. -7. Artifact names must be unique under ASCII case folding while their original admitted spelling is retained. -8. Each artifact digest must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. -9. Admitted artifacts are stored in deterministic name order. -10. Validation errors remain typed, deterministic standard Rust errors. - -Constructing or possessing a valid manifest does **not** authenticate an artifact, prove reproducibility, prove provenance, verify a signature, establish a signing identity, authorize a release channel, publish software, install software, update software, roll software back, or satisfy release acceptance. +4. `rust_toolchain` must be a non-empty bounded canonical ASCII token beginning and ending with an alphanumeric byte; internal bytes are limited to alphanumerics, `.`, `_`, `-`, and `+`. +5. `dependency_lock_sha256` must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. +6. The artifact inventory must be non-empty and contain at most 64 entries. +7. Each artifact name must be a bounded ASCII leaf name containing only alphanumerics, `.`, `_`, and `-`; it cannot contain path separators, traversal-like `..`, leading punctuation, or trailing punctuation. +8. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9` are rejected case-insensitively, including when followed by an extension. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. +9. Artifact names must be unique under ASCII case folding while their original admitted spelling is retained. +10. Each artifact digest must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. +11. Admitted artifacts are stored in deterministic name order. +12. Validation errors remain typed, deterministic standard Rust errors. + +Constructing or possessing a valid manifest does **not** authenticate an artifact, prove that the claimed toolchain or dependency lock was used, prove reproducibility, prove provenance, verify a signature, establish a signing identity, authorize a release channel, publish software, install software, update software, roll software back, or satisfy release acceptance. ## Consequences -The release candidate receives one bounded artifact-identity representation that is stable across caller order and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. +The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records exact Rust toolchain and dependency-lock evidence, and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. -The contract is intentionally narrower than issue #201's final release manifest. Additional fields such as toolchain identity, adapter versions, build environment, dependency locks, signing identity, timestamp, SBOM/provenance references, and platform package identity remain future reviewed work rather than being inferred from this primitive. +The contract is intentionally narrower than issue #201's final release manifest. Additional fields such as adapter versions, build environment, signing identity, timestamp, SBOM/provenance references, and platform package identity remain future reviewed work rather than being inferred from this primitive. ## Failure and degraded behavior -Malformed, ambiguous, duplicate, unbounded, or empty identity evidence fails closed before a manifest is produced. There is no fallback that silently rewrites an invalid name, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. +Malformed, ambiguous, duplicate, unbounded, or empty identity evidence fails closed before a manifest is produced. Invalid Rust toolchain or dependency-lock identity is rejected before a manifest can carry that build evidence. There is no fallback that silently rewrites an invalid name, substitutes another toolchain or dependency-lock digest, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. A caller that cannot provide canonical evidence does not receive a release manifest. That failure is not converted into permission to sign, publish, install, or update through another path. ## Security / privacy / governance impact -The boundary reduces path-confusion and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. +The boundary reduces omitted-build-identity, path-confusion, and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. ## Tests and acceptance evidence -The owning `originweave-core` tests must cover valid deterministic ordering, exact digests, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel access, and deterministic standard error contracts. +The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, valid and malformed Rust toolchain tokens, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. Owned production function, line, region, and branch coverage remains exactly 100% on the unchanged reviewed head. CI/security/scanner evidence is exact-head evidence only; predecessor or model-only evidence cannot satisfy acceptance. @@ -88,13 +93,13 @@ After acceptance and external release artifacts depend on this schema, any incom ## Open follow-ups - Complete issue #201's signed cross-platform distribution and updater trust architecture. -- Bind toolchain, adapter, build-environment, dependency-lock, SBOM/SLSA provenance, signing, timestamp, and platform-package evidence through separately reviewed contracts. +- Bind adapter, build-environment, SBOM/SLSA provenance, signing, timestamp, and platform-package evidence through separately reviewed contracts. - Define manifest serialization/versioning before any durable external release-manifest format is promised. - Add signing-key rotation, compromise response, update rollback/freeze protection, and platform release acceptance without transferring authority from this metadata type. ## Supersession / reversal conditions -Supersede this ADR when a versioned external release-manifest specification replaces the in-process identity primitive, or when supported-platform packaging requires a stronger canonical filename identity. Reversal must preserve fail-closed artifact identity and cannot make possession of metadata equivalent to release authority. +Supersede this ADR when a versioned external release-manifest specification replaces the in-process identity primitive, or when supported-platform packaging requires a stronger canonical filename identity. Reversal must preserve fail-closed build and artifact identity and cannot make possession of metadata equivalent to release authority. ## References From a902fb43c558901fe56821e92036708003cfb75f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:39:22 -0700 Subject: [PATCH 24/53] docs(changelog): record release build identity binding --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f45fad3bb..93c193fd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,8 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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. -- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, and deterministic bounded artifact leaf names with lowercase SHA-256 digests; artifact identity is unique under ASCII case folding so case-only names cannot collide on case-insensitive target filesystems, without granting signing, publication, installation, update, rollback, or release authority. + +- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, bounded canonical Rust toolchain, exact lowercase dependency-lock SHA-256 evidence, and deterministic bounded artifact leaf names with lowercase SHA-256 digests; build identity remains metadata rather than reproducibility proof, and artifact identity is unique under ASCII case folding so case-only names cannot collide on case-insensitive target filesystems, without granting signing, publication, installation, update, rollback, or release authority. - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. From f2eef7e66b1b3a5814ddf24800d9f388af7aee8d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:42:27 -0700 Subject: [PATCH 25/53] test(core): satisfy strict release manifest Clippy contract --- .../tests/release_manifest.rs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index c6fe23076..b2ab4144e 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -2,8 +2,8 @@ use std::error::Error; use originweave_core::release_manifest::{ MAX_RELEASE_ARTIFACT_NAME_BYTES, MAX_RELEASE_ARTIFACTS, MAX_RELEASE_REVISION_BYTES, - ReleaseArtifact, ReleaseArtifactError, ReleaseBuildIdentity, ReleaseChannel, ReleaseManifest, - ReleaseManifestError, + ReleaseArtifact, ReleaseArtifactError, ReleaseBuildIdentity, ReleaseBuildIdentityError, + ReleaseChannel, ReleaseManifest, ReleaseManifestError, }; const SOURCE_COMMIT: &str = "0123456789abcdef0123456789abcdef01234567"; @@ -13,9 +13,8 @@ fn sha256_digest(hex_digit: char) -> String { format!("sha256:{}", hex_digit.to_string().repeat(64)) } -fn build_identity() -> ReleaseBuildIdentity { +fn build_identity() -> Result { ReleaseBuildIdentity::new("1.97.1", &sha256_digest('9')) - .expect("release build identity fixture must be canonical") } #[test] @@ -28,7 +27,7 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - build_identity(), + build_identity()?, vec![sbom, runtime], )?; @@ -54,7 +53,7 @@ fn release_manifest_binds_and_canonicalizes_exact_artifact_evidence() -> Result< SOURCE_COMMIT, "chromium_150-0+build:1@stable", ReleaseChannel::Development, - build_identity(), + build_identity()?, vec![punctuation], )?; assert_eq!( @@ -132,7 +131,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi invalid_commit, CHROMIUM_REVISION, ReleaseChannel::Development, - build_identity(), + build_identity()?, vec![artifact.clone()], ), Err(ReleaseManifestError::InvalidSourceCommit) @@ -154,7 +153,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, invalid_revision, ReleaseChannel::Beta, - build_identity(), + build_identity()?, vec![artifact.clone()], ), Err(ReleaseManifestError::InvalidChromiumRevision) @@ -166,7 +165,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - build_identity(), + build_identity()?, Vec::new(), ), Err(ReleaseManifestError::MissingArtifacts) @@ -178,7 +177,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - build_identity(), + build_identity()?, vec![artifact.clone(), duplicate], ), Err(ReleaseManifestError::DuplicateArtifactName) @@ -190,7 +189,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - build_identity(), + build_identity()?, vec![artifact.clone(), case_collision], ), Err(ReleaseManifestError::DuplicateArtifactName) @@ -208,7 +207,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi SOURCE_COMMIT, CHROMIUM_REVISION, ReleaseChannel::Stable, - build_identity(), + build_identity()?, too_many, ), Err(ReleaseManifestError::TooManyArtifacts) From 5fadaf9eacd7beeb924d4ed6a879c63474093b12 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:29:20 -0700 Subject: [PATCH 26/53] test(release): reject unpinned Rust toolchains --- .../originweave-core/tests/release_manifest_build_identity.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/originweave-core/tests/release_manifest_build_identity.rs b/crates/originweave-core/tests/release_manifest_build_identity.rs index 86d485427..655de400d 100644 --- a/crates/originweave-core/tests/release_manifest_build_identity.rs +++ b/crates/originweave-core/tests/release_manifest_build_identity.rs @@ -25,6 +25,10 @@ fn release_build_identity_rejects_ambiguous_or_unbounded_evidence() { for invalid_toolchain in [ "", + "stable", + "beta", + "nightly", + "1.98.0", " 1.97.1", "1.97.1 ", "rust 1.97.1", From 3ac70fda48eee8d0e87d4d02a0aecc6d4886f427 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:32:53 -0700 Subject: [PATCH 27/53] fix(release): bind exact pinned Rust toolchain --- crates/originweave-core/src/release_manifest.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 5db6e15a5..cc571912a 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -357,16 +357,5 @@ fn valid_revision(revision: &str) -> bool { } fn valid_toolchain(toolchain: &str) -> bool { - if toolchain.is_empty() - || toolchain.len() > MAX_RELEASE_TOOLCHAIN_BYTES - || !toolchain.is_ascii() - { - return false; - } - let bytes = toolchain.as_bytes(); - bytes[0].is_ascii_alphanumeric() - && bytes[bytes.len() - 1].is_ascii_alphanumeric() - && bytes - .iter() - .all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'+')) + toolchain == "1.97.1" } From 11d7e3fe8815026c812a6c6446275fe9459f1549 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:33:31 -0700 Subject: [PATCH 28/53] docs(adr): bind release identity to pinned Rust baseline --- docs/adr/0015-release-manifest-identity.md | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md index 49440dd92..d69c95e22 100644 --- a/docs/adr/0015-release-manifest-identity.md +++ b/docs/adr/0015-release-manifest-identity.md @@ -7,12 +7,13 @@ Issue #201 requires commercial OriginWeave releases to bind an exact OriginWeave source identity, Chromium revision, release channel, build identity, and the artifacts buyers install or verify. That larger release lifecycle will later add signing, SBOM/SLSA provenance, updater trust, rollback, platform support, and operational acceptance. Those authorities do not yet exist in the current source tree. -A smaller durable boundary is nevertheless required before packaging work can safely compose: one deterministic, bounded manifest identity for a release candidate. Without an explicit contract, equivalent release inventories can be represented differently, exact build inputs can be omitted or represented ambiguously, case-insensitive target filesystems can collapse distinct names, and host-specific device namespaces can reinterpret an apparent artifact leaf name. +A smaller durable boundary is nevertheless required before packaging work can safely compose: one deterministic, bounded manifest identity for a release candidate. Without an explicit contract, equivalent release inventories can be represented differently, exact build inputs can be omitted or represented ambiguously, moving toolchain aliases can resolve to different compiler identities over time, case-insensitive target filesystems can collapse distinct names, and host-specific device namespaces can reinterpret an apparent artifact leaf name. ## Decision drivers - One release-candidate identity must not depend on caller ordering. - The manifest must not omit or ambiguously represent the exact Rust toolchain and dependency-lock identity used for the candidate. +- The Rust toolchain field must not accept moving aliases or an alternate compiler version as though it were the repository-pinned build identity. - Artifact references must remain leaf identities rather than filesystem paths. - The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms. - Manifest construction must remain inert metadata admission and must not grant signing, publication, installation, update, rollback, or release authority. @@ -23,7 +24,7 @@ A smaller durable boundary is nevertheless required before packaging work can sa - Source identity is a full 40-character lowercase Git commit SHA. - Chromium identity is a bounded canonical ASCII release token; this ADR does not claim that the token alone authenticates Chromium bytes. - A release channel is explicit (`Stable`, `Beta`, or `Development`) metadata, not authorization to publish or promote a release. -- Rust toolchain identity is a non-empty bounded canonical ASCII token; binding that token does not prove that a build actually used it. +- Rust toolchain identity is the exact protected-repository baseline `1.97.1`; moving aliases such as `stable`, `beta`, and `nightly`, and alternate versions, are not admissible release build identities. Binding the exact token does not prove that a build actually used it. - Dependency-lock identity is an exact lowercase `sha256:` digest; binding that digest does not authenticate the dependency source, build environment, or resulting artifact. - Every admitted artifact carries a bounded ASCII leaf name and an exact lowercase `sha256:` digest. - The manifest contains no private signing material, credentials, secrets, installer authority, network authority, or update authority. @@ -41,7 +42,7 @@ Rejected for this slice. Destructively rewriting admitted artifact spelling woul ### Canonical admission with preserved spelling and collision guards -Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. +Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. ## Decision @@ -50,7 +51,7 @@ OriginWeave release-manifest admission is a deterministic, bounded, fail-closed 1. `source_commit` must be exactly 40 lowercase hexadecimal digits. 2. `chromium_revision` must be a non-empty bounded canonical ASCII token. 3. `channel` must be an explicit `ReleaseChannel` variant. -4. `rust_toolchain` must be a non-empty bounded canonical ASCII token beginning and ending with an alphanumeric byte; internal bytes are limited to alphanumerics, `.`, `_`, `-`, and `+`. +4. `rust_toolchain` must equal the protected repository's exact pinned Rust toolchain `1.97.1`. Moving aliases (`stable`, `beta`, `nightly`) and alternate versions fail closed until the protected baseline and this binding decision are deliberately changed together. 5. `dependency_lock_sha256` must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. 6. The artifact inventory must be non-empty and contain at most 64 entries. 7. Each artifact name must be a bounded ASCII leaf name containing only alphanumerics, `.`, `_`, and `-`; it cannot contain path separators, traversal-like `..`, leading punctuation, or trailing punctuation. @@ -64,23 +65,25 @@ Constructing or possessing a valid manifest does **not** authenticate an artifac ## Consequences -The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records exact Rust toolchain and dependency-lock evidence, and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. +The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. + +Changing the protected Rust baseline now requires deliberate convergence of the repository toolchain pin, release-manifest admission contract, tests, and this ADR. A moving channel alias cannot silently change the compiler identity represented by a release manifest. The contract is intentionally narrower than issue #201's final release manifest. Additional fields such as adapter versions, build environment, signing identity, timestamp, SBOM/provenance references, and platform package identity remain future reviewed work rather than being inferred from this primitive. ## Failure and degraded behavior -Malformed, ambiguous, duplicate, unbounded, or empty identity evidence fails closed before a manifest is produced. Invalid Rust toolchain or dependency-lock identity is rejected before a manifest can carry that build evidence. There is no fallback that silently rewrites an invalid name, substitutes another toolchain or dependency-lock digest, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. +Malformed, ambiguous, duplicate, unbounded, empty, moving-alias, or alternate-version identity evidence fails closed before a manifest is produced. A Rust toolchain other than the exact protected baseline, or an invalid dependency-lock identity, is rejected before a manifest can carry that build evidence. There is no fallback that silently rewrites an invalid name, substitutes another toolchain or dependency-lock digest, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. A caller that cannot provide canonical evidence does not receive a release manifest. That failure is not converted into permission to sign, publish, install, or update through another path. ## Security / privacy / governance impact -The boundary reduces omitted-build-identity, path-confusion, and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. +The boundary reduces omitted-build-identity, moving-toolchain ambiguity, path-confusion, and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. ## Tests and acceptance evidence -The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, valid and malformed Rust toolchain tokens, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. +The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. Owned production function, line, region, and branch coverage remains exactly 100% on the unchanged reviewed head. CI/security/scanner evidence is exact-head evidence only; predecessor or model-only evidence cannot satisfy acceptance. @@ -88,7 +91,7 @@ Owned production function, line, region, and branch coverage remains exactly 100 This is a new admission primitive with no protected-main persisted manifest migration. If the contract proves incompatible before acceptance, the Proposed ADR and owning feature branch can be revised or withdrawn without granting legacy inputs grandfathered authority. -After acceptance and external release artifacts depend on this schema, any incompatible identity change requires an explicit versioning/migration decision rather than silent parser broadening. +After acceptance and external release artifacts depend on this schema, any incompatible identity change requires an explicit versioning/migration decision rather than silent parser broadening. A future Rust upgrade must change the repository baseline and release-manifest contract together rather than relying on a moving alias. ## Open follow-ups @@ -99,7 +102,7 @@ After acceptance and external release artifacts depend on this schema, any incom ## Supersession / reversal conditions -Supersede this ADR when a versioned external release-manifest specification replaces the in-process identity primitive, or when supported-platform packaging requires a stronger canonical filename identity. Reversal must preserve fail-closed build and artifact identity and cannot make possession of metadata equivalent to release authority. +Supersede this ADR when a versioned external release-manifest specification replaces the in-process identity primitive, when the protected Rust baseline changes under a reviewed migration, or when supported-platform packaging requires a stronger canonical filename identity. Reversal must preserve fail-closed build and artifact identity and cannot make possession of metadata equivalent to release authority. ## References From c3cb0fc5c233cd75f01469159a7c4249375a8908 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:34:46 -0700 Subject: [PATCH 29/53] docs(changelog): record exact release toolchain binding --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93c193fd9..bbaa0fd63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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. -- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, bounded canonical Rust toolchain, exact lowercase dependency-lock SHA-256 evidence, and deterministic bounded artifact leaf names with lowercase SHA-256 digests; build identity remains metadata rather than reproducibility proof, and artifact identity is unique under ASCII case folding so case-only names cannot collide on case-insensitive target filesystems, without granting signing, publication, installation, update, rollback, or release authority. +- Added a fail-closed release-manifest identity primitive that binds an exact lowercase source commit, bounded canonical Chromium revision, explicit release channel, the exact repository-pinned Rust 1.97.1 toolchain, exact lowercase dependency-lock SHA-256 evidence, and deterministic bounded artifact leaf names with lowercase SHA-256 digests; build identity remains metadata rather than reproducibility proof, moving toolchain aliases and alternate versions fail closed, and artifact identity is unique under ASCII case folding so case-only names cannot collide on case-insensitive target filesystems, without granting signing, publication, installation, update, rollback, or release authority. - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. From 3e9bea4dd8f7dd502a502fd27d1ec51d6a71c454 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:35:04 -0700 Subject: [PATCH 30/53] test(release): require exact toolchain error contract --- .../originweave-core/tests/release_manifest_build_identity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_manifest_build_identity.rs b/crates/originweave-core/tests/release_manifest_build_identity.rs index 655de400d..ac890bdb9 100644 --- a/crates/originweave-core/tests/release_manifest_build_identity.rs +++ b/crates/originweave-core/tests/release_manifest_build_identity.rs @@ -61,7 +61,7 @@ fn release_build_identity_errors_are_standard_source_free_rust_errors() { let errors = [ ( ReleaseBuildIdentityError::InvalidRustToolchain, - "release Rust toolchain is not a canonical bounded token", + "release Rust toolchain must match the exact repository-pinned baseline", ), ( ReleaseBuildIdentityError::InvalidDependencyLockDigest, From e26471125d470de94e297cce5e736d7d1684a1f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:36:39 -0700 Subject: [PATCH 31/53] test(release): decouple exact pin from obsolete token bound --- .../tests/release_manifest_build_identity.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/originweave-core/tests/release_manifest_build_identity.rs b/crates/originweave-core/tests/release_manifest_build_identity.rs index ac890bdb9..fd9a2852e 100644 --- a/crates/originweave-core/tests/release_manifest_build_identity.rs +++ b/crates/originweave-core/tests/release_manifest_build_identity.rs @@ -1,8 +1,6 @@ use std::error::Error; -use originweave_core::release_manifest::{ - MAX_RELEASE_TOOLCHAIN_BYTES, ReleaseBuildIdentity, ReleaseBuildIdentityError, -}; +use originweave_core::release_manifest::{ReleaseBuildIdentity, ReleaseBuildIdentityError}; fn sha256_digest(hex_digit: char) -> String { format!("sha256:{}", hex_digit.to_string().repeat(64)) @@ -21,7 +19,7 @@ fn release_build_identity_binds_exact_toolchain_and_dependency_lock() -> Result< #[test] fn release_build_identity_rejects_ambiguous_or_unbounded_evidence() { let digest = sha256_digest('b'); - let overlong_toolchain = "a".repeat(MAX_RELEASE_TOOLCHAIN_BYTES + 1); + let overlong_toolchain = "a".repeat(65); for invalid_toolchain in [ "", From db06d75d341bd8795fe6644106ef938e4f770aa9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:37:07 -0700 Subject: [PATCH 32/53] fix(release): make pinned toolchain contract explicit --- .../originweave-core/src/release_manifest.rs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index cc571912a..43bacfcf4 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -14,8 +14,6 @@ pub const MAX_RELEASE_ARTIFACTS: usize = 64; pub const MAX_RELEASE_ARTIFACT_NAME_BYTES: usize = 128; /// Maximum UTF-8 byte length admitted for one Chromium revision token. pub const MAX_RELEASE_REVISION_BYTES: usize = 128; -/// Maximum UTF-8 byte length admitted for one canonical Rust toolchain token. -pub const MAX_RELEASE_TOOLCHAIN_BYTES: usize = 64; /// Buyer-visible release channel bound by a release manifest. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -36,12 +34,13 @@ pub struct ReleaseBuildIdentity { } impl ReleaseBuildIdentity { - /// Construct bounded build identity from a canonical Rust toolchain token and lock digest. + /// Construct build identity from the exact repository-pinned Rust toolchain and lock digest. /// - /// The dependency-lock digest must use the exact `sha256:` prefix followed by 64 lowercase - /// hexadecimal digits. Constructing this value does not prove reproducibility or authenticate - /// the build environment; it only prevents those two identity fields from being omitted or - /// represented ambiguously in a release manifest. + /// The Rust toolchain must match the protected repository baseline exactly; moving aliases + /// and alternate versions fail closed. The dependency-lock digest must use the exact + /// `sha256:` prefix followed by 64 lowercase hexadecimal digits. Constructing this value does + /// not prove reproducibility or authenticate the build environment; it only prevents those + /// two identity fields from being omitted or represented ambiguously in a release manifest. pub fn new( rust_toolchain: &str, dependency_lock_sha256: &str, @@ -58,7 +57,7 @@ impl ReleaseBuildIdentity { }) } - /// Return the exact canonical Rust toolchain token. + /// Return the exact repository-pinned Rust toolchain token. #[must_use] pub fn rust_toolchain(&self) -> &str { &self.rust_toolchain @@ -74,7 +73,7 @@ impl ReleaseBuildIdentity { /// Validation error for release build-identity evidence. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReleaseBuildIdentityError { - /// Rust toolchain is not a canonical bounded token. + /// Rust toolchain does not match the exact repository-pinned baseline. InvalidRustToolchain, /// Dependency-lock digest is not a canonical lowercase SHA-256 digest. InvalidDependencyLockDigest, @@ -83,9 +82,8 @@ pub enum ReleaseBuildIdentityError { impl fmt::Display for ReleaseBuildIdentityError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InvalidRustToolchain => { - formatter.write_str("release Rust toolchain is not a canonical bounded token") - } + Self::InvalidRustToolchain => formatter + .write_str("release Rust toolchain must match the exact repository-pinned baseline"), Self::InvalidDependencyLockDigest => formatter.write_str( "release dependency lock digest must be sha256: followed by 64 lowercase hexadecimal digits", ), @@ -172,13 +170,13 @@ pub struct ReleaseManifest { impl ReleaseManifest { /// Construct an inert release manifest from exact identity evidence. /// - /// Source identity is a full 40-digit lowercase Git commit SHA. Chromium revision and Rust - /// toolchain are bounded canonical ASCII tokens, while dependency-lock identity is a - /// canonical lowercase SHA-256 digest. Artifact names must be unique under ASCII case folding - /// so one manifest cannot bind two names that collide on a case-insensitive target - /// filesystem; original spelling is preserved and artifacts are sorted deterministically - /// before storage. Constructing this value does not authenticate any artifact, prove - /// reproducibility, or authorize release or installation. + /// Source identity is a full 40-digit lowercase Git commit SHA. Chromium revision is a + /// bounded canonical ASCII token, Rust toolchain identity is the exact repository pin, and + /// dependency-lock identity is a canonical lowercase SHA-256 digest. Artifact names must be + /// unique under ASCII case folding so one manifest cannot bind two names that collide on a + /// case-insensitive target filesystem; original spelling is preserved and artifacts are + /// sorted deterministically before storage. Constructing this value does not authenticate any + /// artifact, prove reproducibility, or authorize release or installation. pub fn new( source_commit: &str, chromium_revision: &str, From 4de5adc78001d9ae84967451c88ae16d3b17b6a1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 15:59:25 -0700 Subject: [PATCH 33/53] test(release): reject null Git source identity --- crates/originweave-core/tests/release_manifest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index b2ab4144e..a57e7c5fa 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -122,6 +122,7 @@ fn release_manifest_rejects_invalid_identity_missing_duplicate_and_unbounded_evi for invalid_commit in [ "", + "0000000000000000000000000000000000000000", "0123456789abcdef0123456789abcdef0123456", "0123456789ABCDEF0123456789ABCDEF01234567", "g123456789abcdef0123456789abcdef01234567", From 813cc53efc44e685fbcc7d335587e34cc5271cf1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 16:02:52 -0700 Subject: [PATCH 34/53] fix(release): reject null Git source identity --- crates/originweave-core/src/release_manifest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 43bacfcf4..a393486be 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -340,6 +340,7 @@ fn valid_source_commit(source_commit: &str) -> bool { && source_commit .bytes() .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f')) + && source_commit.bytes().any(|byte| byte != b'0') } fn valid_revision(revision: &str) -> bool { From 971b3084e6f2f55e8fd87a3f9bf19119d0cd9731 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 16:05:14 -0700 Subject: [PATCH 35/53] docs(release): reject Git zero-id as source identity --- docs/adr/0015-release-manifest-identity.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md index d69c95e22..6a3541d0a 100644 --- a/docs/adr/0015-release-manifest-identity.md +++ b/docs/adr/0015-release-manifest-identity.md @@ -9,11 +9,14 @@ Issue #201 requires commercial OriginWeave releases to bind an exact OriginWeave A smaller durable boundary is nevertheless required before packaging work can safely compose: one deterministic, bounded manifest identity for a release candidate. Without an explicit contract, equivalent release inventories can be represented differently, exact build inputs can be omitted or represented ambiguously, moving toolchain aliases can resolve to different compiler identities over time, case-insensitive target filesystems can collapse distinct names, and host-specific device namespaces can reinterpret an apparent artifact leaf name. +Git protocol grammar distinguishes a 40-zero `zero-id` from ordinary object identifiers, and push protocol uses that zero-id as the sentinel for absent/create/delete reference state. A release manifest that admitted the all-zero value as a concrete source commit could therefore claim an exact source identity that does not identify a real Git object. OriginWeave rejects that sentinel at admission rather than relying on later publication or provenance layers to reinterpret it. + ## Decision drivers - One release-candidate identity must not depend on caller ordering. - The manifest must not omit or ambiguously represent the exact Rust toolchain and dependency-lock identity used for the candidate. - The Rust toolchain field must not accept moving aliases or an alternate compiler version as though it were the repository-pinned build identity. +- Source commit identity must distinguish a concrete Git object identifier from Git's all-zero protocol sentinel. - Artifact references must remain leaf identities rather than filesystem paths. - The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms. - Manifest construction must remain inert metadata admission and must not grant signing, publication, installation, update, rollback, or release authority. @@ -21,7 +24,7 @@ A smaller durable boundary is nevertheless required before packaging work can sa ## Assumptions and authority boundaries -- Source identity is a full 40-character lowercase Git commit SHA. +- Source identity is a full 40-character lowercase Git commit SHA and must not be Git's all-zero `zero-id` sentinel. - Chromium identity is a bounded canonical ASCII release token; this ADR does not claim that the token alone authenticates Chromium bytes. - A release channel is explicit (`Stable`, `Beta`, or `Development`) metadata, not authorization to publish or promote a release. - Rust toolchain identity is the exact protected-repository baseline `1.97.1`; moving aliases such as `stable`, `beta`, and `nightly`, and alternate versions, are not admissible release build identities. Binding the exact token does not prove that a build actually used it. @@ -42,13 +45,13 @@ Rejected for this slice. Destructively rewriting admitted artifact spelling woul ### Canonical admission with preserved spelling and collision guards -Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. +Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, reject Git's all-zero source-identity sentinel, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. ## Decision OriginWeave release-manifest admission is a deterministic, bounded, fail-closed identity contract: -1. `source_commit` must be exactly 40 lowercase hexadecimal digits. +1. `source_commit` must be exactly 40 lowercase hexadecimal digits and must not be the all-zero Git `zero-id` sentinel. 2. `chromium_revision` must be a non-empty bounded canonical ASCII token. 3. `channel` must be an explicit `ReleaseChannel` variant. 4. `rust_toolchain` must equal the protected repository's exact pinned Rust toolchain `1.97.1`. Moving aliases (`stable`, `beta`, `nightly`) and alternate versions fail closed until the protected baseline and this binding decision are deliberately changed together. @@ -65,7 +68,7 @@ Constructing or possessing a valid manifest does **not** authenticate an artifac ## Consequences -The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. +The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records a concrete non-null Git source identity plus the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. Changing the protected Rust baseline now requires deliberate convergence of the repository toolchain pin, release-manifest admission contract, tests, and this ADR. A moving channel alias cannot silently change the compiler identity represented by a release manifest. @@ -73,17 +76,17 @@ The contract is intentionally narrower than issue #201's final release manifest. ## Failure and degraded behavior -Malformed, ambiguous, duplicate, unbounded, empty, moving-alias, or alternate-version identity evidence fails closed before a manifest is produced. A Rust toolchain other than the exact protected baseline, or an invalid dependency-lock identity, is rejected before a manifest can carry that build evidence. There is no fallback that silently rewrites an invalid name, substitutes another toolchain or dependency-lock digest, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. +Malformed, ambiguous, duplicate, unbounded, empty, null-sentinel, moving-alias, or alternate-version identity evidence fails closed before a manifest is produced. A source identity equal to Git's 40-zero `zero-id`, a Rust toolchain other than the exact protected baseline, or an invalid dependency-lock identity is rejected before a manifest can carry that evidence. There is no fallback that silently rewrites an invalid name, substitutes another source commit, toolchain, or dependency-lock digest, accepts an alternate digest representation, drops an artifact, or substitutes another release channel. A caller that cannot provide canonical evidence does not receive a release manifest. That failure is not converted into permission to sign, publish, install, or update through another path. ## Security / privacy / governance impact -The boundary reduces omitted-build-identity, moving-toolchain ambiguity, path-confusion, and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. +The boundary reduces null-source-identity, omitted-build-identity, moving-toolchain ambiguity, path-confusion, and cross-platform filename ambiguity without introducing credentials or protected values. It does not alter GitHub governance, reviewer authority, release signing authority, or protected-main policy. Scheduled development agents remain unable to merge, tag, publish, sign, or change release authority. ## Tests and acceptance evidence -The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. +The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, one concrete non-null 40-character source commit, rejection of Git's all-zero source sentinel, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. Owned production function, line, region, and branch coverage remains exactly 100% on the unchanged reviewed head. CI/security/scanner evidence is exact-head evidence only; predecessor or model-only evidence cannot satisfy acceptance. @@ -106,4 +109,8 @@ Supersede this ADR when a versioned external release-manifest specification repl ## References +Git. (2025). *gitprotocol-common documentation*. https://git-scm.com/docs/gitprotocol-common + +Git. (2026). *gitprotocol-pack documentation*. https://git-scm.com/docs/gitprotocol-pack + Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file From 58ed00341a53178e1a7fe55f9acbd62b67f23d79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 16:10:54 -0700 Subject: [PATCH 36/53] docs(release): record non-null Git identity rationale --- docs/doctoring.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/doctoring.md b/docs/doctoring.md index cd40d85ea..e62e4c601 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -86,6 +86,10 @@ The test-only rcgen 0.14.8 dependency creates a local CA and deterministic certi Microsoft's Win32 filename guidance requires applications not to assume case sensitivity and reserves device basenames including `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9`, including those names followed by extensions. The same documentation also identifies non-ASCII superscript-digit device aliases. OriginWeave's release-manifest identity therefore rejects ASCII-case-folded duplicate artifact names and those reserved ASCII device basenames; its ASCII-only artifact grammar separately excludes the superscript aliases. These checks make one admitted artifact inventory portable across case-sensitive and default case-insensitive target filesystems without rewriting the stored artifact spelling. They remain identity hygiene only and do not grant signing, publication, installation, update, rollback, or release authority. +### Git source identity admission + +Git's protocol grammar distinguishes an ordinary 40-hex object identifier from `zero-id = 40*"0"`. The pack protocol uses that all-zero sentinel to represent absent refs and create/delete reference state rather than a concrete source object. OriginWeave therefore requires release-manifest `source_commit` and WARC PROV `software_commit_sha` values to be canonical lowercase 40-hex identifiers with at least one nonzero digit. This is an identity-admission rule only: it prevents a null Git sentinel from being represented as a concrete software revision, but does not prove repository reachability, GitHub authenticity, commit trust, provenance, or release authority. + ### Crawling policy RFC 9309 standardizes robots parsing, matching, error handling, and caching. It also states that robots rules are not access authorization. OriginWeave therefore requires robots evidence for public crawler mode while maintaining authentication, terms, rate, privacy, and retention policy as separate controls. @@ -144,6 +148,10 @@ Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC 9110). Fugu Team, Sakana AI. (2026). *Sakana Fugu technical report* [Technical report]. arXiv. https://doi.org/10.48550/arXiv.2606.21228 +Git. (2025). *gitprotocol-common documentation*. https://git-scm.com/docs/gitprotocol-common + +Git. (2026). *gitprotocol-pack documentation*. https://git-scm.com/docs/gitprotocol-pack + Huston, G., & Buraglio, N. (2024). *Expanding the IPv6 documentation space* (RFC 9637). Internet Engineering Task Force. https://doi.org/10.17487/RFC9637 Internet Assigned Numbers Authority. (2025, October 9). *IPv4 special-purpose address space*. https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml From bf2abb32c5df14d85bfe679f3966ed43424501c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 18:05:33 -0700 Subject: [PATCH 37/53] test(core): pin non-null release source error contract --- crates/originweave-core/tests/release_manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index a57e7c5fa..942fae1fe 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -236,7 +236,7 @@ fn release_manifest_errors_are_standard_source_free_rust_errors() { let manifest_errors = [ ( ReleaseManifestError::InvalidSourceCommit, - "release source commit must be exactly 40 lowercase hexadecimal digits", + "release source commit must be a non-null 40-digit lowercase Git object identity", ), ( ReleaseManifestError::InvalidChromiumRevision, From 7de7b8eaa2553b93e01369146d3bb9f0dd0e2132 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 18:08:11 -0700 Subject: [PATCH 38/53] fix(core): align release source diagnostic with non-null identity --- crates/originweave-core/src/release_manifest.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index a393486be..211632ac7 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -253,7 +253,7 @@ impl ReleaseManifest { /// Validation error for release-manifest identity evidence. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReleaseManifestError { - /// Source commit is not a full lowercase Git SHA-1 identity. + /// Source commit is not a non-null full lowercase 40-hex Git object identity. InvalidSourceCommit, /// Chromium revision is not a canonical bounded release token. InvalidChromiumRevision, @@ -268,8 +268,9 @@ pub enum ReleaseManifestError { impl fmt::Display for ReleaseManifestError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::InvalidSourceCommit => formatter - .write_str("release source commit must be exactly 40 lowercase hexadecimal digits"), + Self::InvalidSourceCommit => formatter.write_str( + "release source commit must be a non-null 40-digit lowercase Git object identity", + ), Self::InvalidChromiumRevision => { formatter.write_str("Chromium revision must be a canonical bounded release token") } From 17903a676496065f3899408a8f71a3861e40f5bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 19:43:22 -0700 Subject: [PATCH 39/53] docs(release): pin Git protocol references --- docs/adr/0015-release-manifest-identity.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md index 6a3541d0a..15d1d611a 100644 --- a/docs/adr/0015-release-manifest-identity.md +++ b/docs/adr/0015-release-manifest-identity.md @@ -109,8 +109,8 @@ Supersede this ADR when a versioned external release-manifest specification repl ## References -Git. (2025). *gitprotocol-common documentation*. https://git-scm.com/docs/gitprotocol-common +Git. (2026). *gitprotocol-common documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-common/2.55.0 -Git. (2026). *gitprotocol-pack documentation*. https://git-scm.com/docs/gitprotocol-pack +Git. (2026). *gitprotocol-pack documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-pack/2.55.0 Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file From 58e72715cdf46ee11c4dadfa49e8b67f0810478c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 19:44:38 -0700 Subject: [PATCH 40/53] docs(release): pin doctoring Git references --- docs/doctoring.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/doctoring.md b/docs/doctoring.md index e62e4c601..0f6166f97 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -148,9 +148,9 @@ Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC 9110). Fugu Team, Sakana AI. (2026). *Sakana Fugu technical report* [Technical report]. arXiv. https://doi.org/10.48550/arXiv.2606.21228 -Git. (2025). *gitprotocol-common documentation*. https://git-scm.com/docs/gitprotocol-common +Git. (2026). *gitprotocol-common documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-common/2.55.0 -Git. (2026). *gitprotocol-pack documentation*. https://git-scm.com/docs/gitprotocol-pack +Git. (2026). *gitprotocol-pack documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-pack/2.55.0 Huston, G., & Buraglio, N. (2024). *Expanding the IPv6 documentation space* (RFC 9637). Internet Engineering Task Force. https://doi.org/10.17487/RFC9637 From 21c48c82fd40bb8743fcfde6ccbedca9b73f7f18 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 20:35:23 -0700 Subject: [PATCH 41/53] test(release): reject Win32 COM0 and LPT0 aliases --- crates/originweave-core/tests/release_manifest.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/originweave-core/tests/release_manifest.rs b/crates/originweave-core/tests/release_manifest.rs index 942fae1fe..531b13685 100644 --- a/crates/originweave-core/tests/release_manifest.rs +++ b/crates/originweave-core/tests/release_manifest.rs @@ -81,8 +81,12 @@ fn release_artifact_rejects_ambiguous_names_and_noncanonical_digests() { "PRN.tar.zst", "aux.bin", "NUL.spdx.json", + "COM0", + "com0.zip", "COM1", "com9.zip", + "LPT0", + "lpt0.tar.zst", "LPT1", "lpt9.tar.zst", overlong_name.as_str(), From 7af2b25280dc89797aa23684de4ed2fcd4b2e25a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 20:37:01 -0700 Subject: [PATCH 42/53] fix(release): reject Win32 COM0 and LPT0 device aliases --- crates/originweave-core/src/release_manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 211632ac7..14dd8023b 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -323,7 +323,7 @@ fn windows_reserved_device_basename(name: &str) -> bool { let bytes = basename.as_bytes(); bytes.len() == 4 && (basename[..3].eq_ignore_ascii_case("COM") || basename[..3].eq_ignore_ascii_case("LPT")) - && matches!(bytes[3], b'1'..=b'9') + && matches!(bytes[3], b'0'..=b'9') } fn valid_sha256_digest(digest: &str) -> bool { From dea62771be209c9799b700f00fb83eec2ff1ba8a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 20:38:06 -0700 Subject: [PATCH 43/53] docs(adr): record portable COM0 and LPT0 exclusion --- docs/adr/0015-release-manifest-identity.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md index 15d1d611a..92679d625 100644 --- a/docs/adr/0015-release-manifest-identity.md +++ b/docs/adr/0015-release-manifest-identity.md @@ -18,7 +18,7 @@ Git protocol grammar distinguishes a 40-zero `zero-id` from ordinary object iden - The Rust toolchain field must not accept moving aliases or an alternate compiler version as though it were the repository-pinned build identity. - Source commit identity must distinguish a concrete Git object identifier from Git's all-zero protocol sentinel. - Artifact references must remain leaf identities rather than filesystem paths. -- The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms. +- The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms and common buyer synchronization paths. - Manifest construction must remain inert metadata admission and must not grant signing, publication, installation, update, rollback, or release authority. - Inputs must be bounded and fail closed before later packaging, signing, or updater layers consume them. @@ -45,7 +45,7 @@ Rejected for this slice. Destructively rewriting admitted artifact spelling woul ### Canonical admission with preserved spelling and collision guards -Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, reject Git's all-zero source-identity sentinel, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved Win32 device names. +Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, reject Git's all-zero source-identity sentinel, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved or operationally device-like across supported Windows and Microsoft synchronization paths. ## Decision @@ -58,7 +58,7 @@ OriginWeave release-manifest admission is a deterministic, bounded, fail-closed 5. `dependency_lock_sha256` must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. 6. The artifact inventory must be non-empty and contain at most 64 entries. 7. Each artifact name must be a bounded ASCII leaf name containing only alphanumerics, `.`, `_`, and `-`; it cannot contain path separators, traversal-like `..`, leading punctuation, or trailing punctuation. -8. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9` are rejected case-insensitively, including when followed by an extension. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. +8. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM0` through `COM9`, and `LPT0` through `LPT9` are rejected case-insensitively, including when followed by an extension. Microsoft Win32 filename guidance explicitly reserves `COM1`-`COM9` and `LPT1`-`LPT9` and documents `COM0` as a possible Win32 namespace symlink; Microsoft OneDrive and SharePoint additionally reject `COM0`-`COM9` and `LPT0`-`LPT9`. OriginWeave deliberately adopts that stricter portable artifact-name set rather than accept a release leaf name that can become a device or synchronization conflict in a buyer environment. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. 9. Artifact names must be unique under ASCII case folding while their original admitted spelling is retained. 10. Each artifact digest must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. 11. Admitted artifacts are stored in deterministic name order. @@ -68,7 +68,7 @@ Constructing or possessing a valid manifest does **not** authenticate an artifac ## Consequences -The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records a concrete non-null Git source identity plus the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids known case-insensitive and Win32 device-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. +The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records a concrete non-null Git source identity plus the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids known case-insensitive, Win32 device-name, and Microsoft synchronization-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. Changing the protected Rust baseline now requires deliberate convergence of the repository toolchain pin, release-manifest admission contract, tests, and this ADR. A moving channel alias cannot silently change the compiler identity represented by a release manifest. @@ -86,7 +86,7 @@ The boundary reduces null-source-identity, omitted-build-identity, moving-toolch ## Tests and acceptance evidence -The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, one concrete non-null 40-character source commit, rejection of Git's all-zero source sentinel, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, Win32 reserved device basenames with and without extensions, neighboring admissible names, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. +The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, one concrete non-null 40-character source commit, rejection of Git's all-zero source sentinel, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, `COM0`-`COM9` and `LPT0`-`LPT9` device/synchronization-conflict basenames with and without extensions, neighboring admissible names such as `COM10` and `LPT10`, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. Owned production function, line, region, and branch coverage remains exactly 100% on the unchanged reviewed head. CI/security/scanner evidence is exact-head evidence only; predecessor or model-only evidence cannot satisfy acceptance. @@ -114,3 +114,5 @@ Git. (2026). *gitprotocol-common documentation (Git 2.55.0)*. https://git-scm.co Git. (2026). *gitprotocol-pack documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-pack/2.55.0 Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file + +Microsoft. (n.d.). *Restrictions and limitations in OneDrive and SharePoint*. Microsoft Support. Retrieved August 23, 2026, from https://support.microsoft.com/en-US/onedrive/restrictions-and-limitations-in-onedrive-and-sharepoint From e0d7d5bdd9078ee1d5fb51922fe1d4977cbcc4a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 20:41:50 -0700 Subject: [PATCH 44/53] fix(release): satisfy strict ASCII digit lint --- crates/originweave-core/src/release_manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/src/release_manifest.rs b/crates/originweave-core/src/release_manifest.rs index 14dd8023b..2611163d8 100644 --- a/crates/originweave-core/src/release_manifest.rs +++ b/crates/originweave-core/src/release_manifest.rs @@ -323,7 +323,7 @@ fn windows_reserved_device_basename(name: &str) -> bool { let bytes = basename.as_bytes(); bytes.len() == 4 && (basename[..3].eq_ignore_ascii_case("COM") || basename[..3].eq_ignore_ascii_case("LPT")) - && matches!(bytes[3], b'0'..=b'9') + && bytes[3].is_ascii_digit() } fn valid_sha256_digest(digest: &str) -> bool { From 1f8729fc44b0904715d77c414f69cf7e7688cd99 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 20:46:35 -0700 Subject: [PATCH 45/53] docs(doctoring): record portable COM0 and LPT0 exclusions --- docs/doctoring.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/doctoring.md b/docs/doctoring.md index 0f6166f97..5209d09f7 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -84,7 +84,7 @@ The test-only rcgen 0.14.8 dependency creates a local CA and deterministic certi ### Release artifact filename portability -Microsoft's Win32 filename guidance requires applications not to assume case sensitivity and reserves device basenames including `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9`, including those names followed by extensions. The same documentation also identifies non-ASCII superscript-digit device aliases. OriginWeave's release-manifest identity therefore rejects ASCII-case-folded duplicate artifact names and those reserved ASCII device basenames; its ASCII-only artifact grammar separately excludes the superscript aliases. These checks make one admitted artifact inventory portable across case-sensitive and default case-insensitive target filesystems without rewriting the stored artifact spelling. They remain identity hygiene only and do not grant signing, publication, installation, update, rollback, or release authority. +Microsoft's Win32 filename guidance requires applications not to assume case sensitivity and reserves device basenames including `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9`, including those names followed by extensions. The same documentation identifies non-ASCII superscript-digit device aliases and also documents `COM0` as a possible `Global??` Win32 namespace symlink. Separately, Microsoft's current OneDrive and SharePoint restrictions reject `COM0` through `COM9` and `LPT0` through `LPT9`. OriginWeave therefore adopts the stricter portable release-artifact deny set: ASCII-case-folded duplicate names and `CON`, `PRN`, `AUX`, `NUL`, `COM0` through `COM9`, and `LPT0` through `LPT9`, including extensions, are rejected so an admitted artifact does not become a device or synchronization conflict in a buyer environment. The ASCII-only grammar separately excludes the superscript aliases. These checks preserve the original artifact spelling and remain identity hygiene only; they do not grant signing, publication, installation, update, rollback, or release authority. ### Git source identity admission @@ -170,7 +170,11 @@ Microsoft. (2025, July 25). *Azure IP address 168.63.129.16 overview*. Microsoft Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file +<<<<<<< HEAD Model Context Protocol. (2026, July 28). *Specification: 2026-07-28*. https://modelcontextprotocol.io/specification/2026-07-28 +======= +Microsoft. (n.d.). *Restrictions and limitations in OneDrive and SharePoint*. Microsoft Support. Retrieved August 23, 2026, from https://support.microsoft.com/en-US/onedrive/restrictions-and-limitations-in-onedrive-and-sharepoint +>>>>>>> 45f024dc (docs(doctoring): record portable COM0 and LPT0 exclusions) Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2025). *Learning to orchestrate agents in natural language with the Conductor* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2512.04388 @@ -210,4 +214,4 @@ World Wide Web Consortium. (2026, June 1). *WebDriver BiDi* (W3C Working Draft). Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2025). *TRINITY: An evolved LLM coordinator* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2512.04695 -Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., Cheng, X., Ou, T., Bisk, Y., Fried, D., Alon, U., & Neubig, G. (2023). *WebArena: A realistic web environment for building autonomous agents*. arXiv. https://doi.org/10.48550/arXiv.2307.13854 \ No newline at end of file +Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., Cheng, X., Ou, T., Bisk, Y., Fried, D., Alon, U., & Neubig, G. (2023). *WebArena: A realistic web environment for building autonomous agents*. arXiv. https://doi.org/10.48550/arXiv.2307.13854 From a379a63ed46cc49e43851b875134dd457bb8a4ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 20:47:10 -0700 Subject: [PATCH 46/53] docs(changelog): record portable release device aliases --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbaa0fd63..908d6de47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security - 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, so package names cannot become Windows device or Microsoft synchronization conflicts in buyer environments. - Raw page content cannot become a trusted instruction. - Raw secrets are rejected and secret-capable actions require an opaque broker handle. - Crawler mode is read-only, must pair with the public-crawl purpose, and fails closed without an applicable robots-policy decision. From 9c7bc5460384cac55c0c5b40c3b543c75ba123b0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 00:07:36 -0700 Subject: [PATCH 47/53] test(release): pin manifest documentation truth --- ...release_manifest_documentation_contract.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/test_release_manifest_documentation_contract.py diff --git a/tests/test_release_manifest_documentation_contract.py b/tests/test_release_manifest_documentation_contract.py new file mode 100644 index 000000000..5de8c445e --- /dev/null +++ b/tests/test_release_manifest_documentation_contract.py @@ -0,0 +1,44 @@ +"""Regression contracts for release-manifest documentation truth and source citations.""" + +from __future__ import annotations + +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +ADR_PATH = ROOT / "docs/adr/0015-release-manifest-identity.md" +DOCTORING_PATH = ROOT / "docs/doctoring.md" + + +class ReleaseManifestDocumentationContractTests(unittest.TestCase): + """Keep release-manifest portability and Git-source claims narrower than the code proves.""" + + def test_sync_compatibility_claim_matches_the_actual_filename_validator(self) -> None: + """Reserved-device guards must not be presented as full OneDrive/SharePoint compatibility.""" + adr = ADR_PATH.read_text(encoding="utf-8") + doctoring = DOCTORING_PATH.read_text(encoding="utf-8") + + for text in (adr, doctoring): + with self.subTest(document=text[:40]): + self.assertIn("desktop.ini", text) + self.assertIn( + "not a complete OneDrive or SharePoint synchronization-compatibility guarantee", + text, + ) + + self.assertNotIn("common buyer synchronization paths", adr) + self.assertNotIn("does not become a device or synchronization conflict", adr) + + def test_git_protocol_references_pin_the_verified_manual_revisions(self) -> None: + """Protocol evidence must resolve to the manual revisions that contain the cited rules.""" + for path in (ADR_PATH, DOCTORING_PATH): + text = path.read_text(encoding="utf-8") + with self.subTest(path=path): + self.assertIn("gitprotocol-common/2.50.0", text) + self.assertIn("gitprotocol-pack/2.54.0", text) + self.assertNotIn("gitprotocol-common/2.55.0", text) + self.assertNotIn("gitprotocol-pack/2.55.0", text) + + +if __name__ == "__main__": + unittest.main() From 9255078e9b797179d1333f033ce6892c54f22aa0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 00:09:08 -0700 Subject: [PATCH 48/53] test(release): align Git citation revision contracts --- tests/test_release_manifest_documentation_contract.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_release_manifest_documentation_contract.py b/tests/test_release_manifest_documentation_contract.py index 5de8c445e..c666b96d8 100644 --- a/tests/test_release_manifest_documentation_contract.py +++ b/tests/test_release_manifest_documentation_contract.py @@ -31,9 +31,15 @@ def test_sync_compatibility_claim_matches_the_actual_filename_validator(self) -> def test_git_protocol_references_pin_the_verified_manual_revisions(self) -> None: """Protocol evidence must resolve to the manual revisions that contain the cited rules.""" + expected_citations = ( + "Git. (2025). *gitprotocol-common documentation (Git 2.50.0)*.", + "Git. (2026). *gitprotocol-pack documentation (Git 2.54.0)*.", + ) for path in (ADR_PATH, DOCTORING_PATH): text = path.read_text(encoding="utf-8") with self.subTest(path=path): + for citation in expected_citations: + self.assertIn(citation, text) self.assertIn("gitprotocol-common/2.50.0", text) self.assertIn("gitprotocol-pack/2.54.0", text) self.assertNotIn("gitprotocol-common/2.55.0", text) From 47fcb7425fba49a456d607e7c3cdf731cf6a2e0e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 00:11:55 -0700 Subject: [PATCH 49/53] docs(release): narrow manifest portability claims --- docs/adr/0015-release-manifest-identity.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/adr/0015-release-manifest-identity.md b/docs/adr/0015-release-manifest-identity.md index 92679d625..367668513 100644 --- a/docs/adr/0015-release-manifest-identity.md +++ b/docs/adr/0015-release-manifest-identity.md @@ -18,7 +18,7 @@ Git protocol grammar distinguishes a 40-zero `zero-id` from ordinary object iden - The Rust toolchain field must not accept moving aliases or an alternate compiler version as though it were the repository-pinned build identity. - Source commit identity must distinguish a concrete Git object identifier from Git's all-zero protocol sentinel. - Artifact references must remain leaf identities rather than filesystem paths. -- The same manifest must be unambiguous on supported case-sensitive and case-insensitive platforms and common buyer synchronization paths. +- The same manifest must avoid the explicitly modeled ASCII case-fold and Windows reserved-device basename collisions; that bounded rule is not a complete OneDrive or SharePoint synchronization-compatibility guarantee. - Manifest construction must remain inert metadata admission and must not grant signing, publication, installation, update, rollback, or release authority. - Inputs must be bounded and fail closed before later packaging, signing, or updater layers consume them. @@ -45,7 +45,7 @@ Rejected for this slice. Destructively rewriting admitted artifact spelling woul ### Canonical admission with preserved spelling and collision guards -Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, reject Git's all-zero source-identity sentinel, and reject names whose ASCII-case-folded identities collide or whose basenames are reserved or operationally device-like across supported Windows and Microsoft synchronization paths. +Selected. Preserve the admitted artifact spelling, sort artifacts deterministically by that spelling, bind exact build-identity fields separately, require the repository-pinned Rust compiler identity, reject Git's all-zero source-identity sentinel, and reject names whose ASCII-case-folded identities collide or whose basenames fall within the explicitly modeled Windows reserved-device deny set. ## Decision @@ -58,7 +58,7 @@ OriginWeave release-manifest admission is a deterministic, bounded, fail-closed 5. `dependency_lock_sha256` must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. 6. The artifact inventory must be non-empty and contain at most 64 entries. 7. Each artifact name must be a bounded ASCII leaf name containing only alphanumerics, `.`, `_`, and `-`; it cannot contain path separators, traversal-like `..`, leading punctuation, or trailing punctuation. -8. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM0` through `COM9`, and `LPT0` through `LPT9` are rejected case-insensitively, including when followed by an extension. Microsoft Win32 filename guidance explicitly reserves `COM1`-`COM9` and `LPT1`-`LPT9` and documents `COM0` as a possible Win32 namespace symlink; Microsoft OneDrive and SharePoint additionally reject `COM0`-`COM9` and `LPT0`-`LPT9`. OriginWeave deliberately adopts that stricter portable artifact-name set rather than accept a release leaf name that can become a device or synchronization conflict in a buyer environment. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. +8. Artifact basenames `CON`, `PRN`, `AUX`, `NUL`, `COM0` through `COM9`, and `LPT0` through `LPT9` are rejected case-insensitively, including when followed by an extension. Microsoft Win32 filename guidance explicitly reserves `COM1`-`COM9` and `LPT1`-`LPT9` and documents `COM0` as a possible Win32 namespace symlink; Microsoft OneDrive and SharePoint also restrict additional names, including `desktop.ini`, that this validator does not model. OriginWeave deliberately enforces the listed reserved-device deny set as bounded filename identity hygiene. This is not a complete OneDrive or SharePoint synchronization-compatibility guarantee; packaging or synchronization validation for a target service must separately enforce that service's full current restrictions. The artifact grammar already rejects the non-ASCII superscript-digit Win32 aliases. 9. Artifact names must be unique under ASCII case folding while their original admitted spelling is retained. 10. Each artifact digest must be exactly `sha256:` followed by 64 lowercase hexadecimal digits. 11. Admitted artifacts are stored in deterministic name order. @@ -68,7 +68,7 @@ Constructing or possessing a valid manifest does **not** authenticate an artifac ## Consequences -The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records a concrete non-null Git source identity plus the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids known case-insensitive, Win32 device-name, and Microsoft synchronization-name collisions. Packaging, signing, provenance, and update layers can compose on top of this contract without inheriting ambient authority from it. +The release candidate receives one bounded build-and-artifact identity representation that is stable across caller order, records a concrete non-null Git source identity plus the exact repository-pinned Rust toolchain and dependency-lock evidence, and avoids the explicitly modeled ASCII case-fold and Windows reserved-device basename ambiguities. Packaging, signing, provenance, synchronization validation, and update layers can compose on top of this contract without inheriting ambient authority from it. Changing the protected Rust baseline now requires deliberate convergence of the repository toolchain pin, release-manifest admission contract, tests, and this ADR. A moving channel alias cannot silently change the compiler identity represented by a release manifest. @@ -86,7 +86,7 @@ The boundary reduces null-source-identity, omitted-build-identity, moving-toolch ## Tests and acceptance evidence -The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, one concrete non-null 40-character source commit, rejection of Git's all-zero source sentinel, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, `COM0`-`COM9` and `LPT0`-`LPT9` device/synchronization-conflict basenames with and without extensions, neighboring admissible names such as `COM10` and `LPT10`, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. +The owning `originweave-core` tests must cover valid deterministic ordering, exact artifact and dependency-lock digests, one concrete non-null 40-character source commit, rejection of Git's all-zero source sentinel, the exact pinned Rust toolchain, moving aliases and alternate toolchain versions, malformed toolchain inputs, identifier bounds, malformed names, path/traversal-like names, case-only collisions, `COM0`-`COM9` and `LPT0`-`LPT9` reserved-device basenames with and without extensions, neighboring admissible names such as `COM10` and `LPT10`, exact inventory bounds, duplicate names, channel and build-identity access, and deterministic standard error contracts. Documentation regression tests also keep this bounded filename claim distinct from broader OneDrive/SharePoint restrictions such as `desktop.ini`. Owned production function, line, region, and branch coverage remains exactly 100% on the unchanged reviewed head. CI/security/scanner evidence is exact-head evidence only; predecessor or model-only evidence cannot satisfy acceptance. @@ -109,9 +109,9 @@ Supersede this ADR when a versioned external release-manifest specification repl ## References -Git. (2026). *gitprotocol-common documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-common/2.55.0 +Git. (2025). *gitprotocol-common documentation (Git 2.50.0)*. https://git-scm.com/docs/gitprotocol-common/2.50.0 -Git. (2026). *gitprotocol-pack documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-pack/2.55.0 +Git. (2026). *gitprotocol-pack documentation (Git 2.54.0)*. https://git-scm.com/docs/gitprotocol-pack/2.54.0 Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file From 2b0edc25f5abe0711d79305895eabfd81af4e9c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 00:14:45 -0700 Subject: [PATCH 50/53] docs(release): align portability and Git evidence --- docs/doctoring.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/doctoring.md b/docs/doctoring.md index 5209d09f7..88a199066 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -84,7 +84,7 @@ The test-only rcgen 0.14.8 dependency creates a local CA and deterministic certi ### Release artifact filename portability -Microsoft's Win32 filename guidance requires applications not to assume case sensitivity and reserves device basenames including `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9`, including those names followed by extensions. The same documentation identifies non-ASCII superscript-digit device aliases and also documents `COM0` as a possible `Global??` Win32 namespace symlink. Separately, Microsoft's current OneDrive and SharePoint restrictions reject `COM0` through `COM9` and `LPT0` through `LPT9`. OriginWeave therefore adopts the stricter portable release-artifact deny set: ASCII-case-folded duplicate names and `CON`, `PRN`, `AUX`, `NUL`, `COM0` through `COM9`, and `LPT0` through `LPT9`, including extensions, are rejected so an admitted artifact does not become a device or synchronization conflict in a buyer environment. The ASCII-only grammar separately excludes the superscript aliases. These checks preserve the original artifact spelling and remain identity hygiene only; they do not grant signing, publication, installation, update, rollback, or release authority. +Microsoft's Win32 filename guidance requires applications not to assume case sensitivity and reserves device basenames including `CON`, `PRN`, `AUX`, `NUL`, `COM1` through `COM9`, and `LPT1` through `LPT9`, including those names followed by extensions. The same documentation identifies non-ASCII superscript-digit device aliases and also documents `COM0` as a possible `Global??` Win32 namespace symlink. Separately, Microsoft's current OneDrive and SharePoint restrictions reject `COM0` through `COM9` and `LPT0` through `LPT9` and additional names including `desktop.ini`. OriginWeave therefore adopts a bounded release-artifact identity deny set: ASCII-case-folded duplicate names and `CON`, `PRN`, `AUX`, `NUL`, `COM0` through `COM9`, and `LPT0` through `LPT9`, including extensions, are rejected. The ASCII-only grammar separately excludes the superscript aliases. This bounded filename rule is not a complete OneDrive or SharePoint synchronization-compatibility guarantee; packaging or synchronization validation for a target service must separately enforce that service's full current restrictions. These checks preserve the original artifact spelling and remain identity hygiene only; they do not grant signing, publication, installation, update, rollback, or release authority. ### Git source identity admission @@ -148,9 +148,9 @@ Fielding, R., Nottingham, M., & Reschke, J. (2022). *HTTP semantics* (RFC 9110). Fugu Team, Sakana AI. (2026). *Sakana Fugu technical report* [Technical report]. arXiv. https://doi.org/10.48550/arXiv.2606.21228 -Git. (2026). *gitprotocol-common documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-common/2.55.0 +Git. (2025). *gitprotocol-common documentation (Git 2.50.0)*. https://git-scm.com/docs/gitprotocol-common/2.50.0 -Git. (2026). *gitprotocol-pack documentation (Git 2.55.0)*. https://git-scm.com/docs/gitprotocol-pack/2.55.0 +Git. (2026). *gitprotocol-pack documentation (Git 2.54.0)*. https://git-scm.com/docs/gitprotocol-pack/2.54.0 Huston, G., & Buraglio, N. (2024). *Expanding the IPv6 documentation space* (RFC 9637). Internet Engineering Task Force. https://doi.org/10.17487/RFC9637 @@ -214,4 +214,4 @@ World Wide Web Consortium. (2026, June 1). *WebDriver BiDi* (W3C Working Draft). Xu, J., Sun, Q., Schwendeman, P., Nielsen, S., Cetin, E., & Tang, Y. (2025). *TRINITY: An evolved LLM coordinator* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2512.04695 -Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., Cheng, X., Ou, T., Bisk, Y., Fried, D., Alon, U., & Neubig, G. (2023). *WebArena: A realistic web environment for building autonomous agents*. arXiv. https://doi.org/10.48550/arXiv.2307.13854 +Zhou, S., Xu, F. F., Zhu, H., Zhou, X., Lo, R., Sridhar, A., Cheng, X., Ou, T., Bisk, Y., Fried, D., Alon, U., & Neubig, G. (2023). *WebArena: A realistic web environment for building autonomous agents*. arXiv. https://doi.org/10.48550/arXiv.2307.13854 \ No newline at end of file From d438a4a9f91db5901fb12a75edc1ffc7824795bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 00:16:19 -0700 Subject: [PATCH 51/53] test(release): cover changelog portability truth --- tests/test_release_manifest_documentation_contract.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_release_manifest_documentation_contract.py b/tests/test_release_manifest_documentation_contract.py index c666b96d8..3c0a4c18e 100644 --- a/tests/test_release_manifest_documentation_contract.py +++ b/tests/test_release_manifest_documentation_contract.py @@ -7,6 +7,7 @@ ROOT = pathlib.Path(__file__).resolve().parents[1] ADR_PATH = ROOT / "docs/adr/0015-release-manifest-identity.md" +CHANGELOG_PATH = ROOT / "CHANGELOG.md" DOCTORING_PATH = ROOT / "docs/doctoring.md" @@ -16,9 +17,10 @@ class ReleaseManifestDocumentationContractTests(unittest.TestCase): def test_sync_compatibility_claim_matches_the_actual_filename_validator(self) -> None: """Reserved-device guards must not be presented as full OneDrive/SharePoint compatibility.""" adr = ADR_PATH.read_text(encoding="utf-8") + changelog = CHANGELOG_PATH.read_text(encoding="utf-8") doctoring = DOCTORING_PATH.read_text(encoding="utf-8") - for text in (adr, doctoring): + for text in (adr, changelog, doctoring): with self.subTest(document=text[:40]): self.assertIn("desktop.ini", text) self.assertIn( @@ -28,6 +30,10 @@ def test_sync_compatibility_claim_matches_the_actual_filename_validator(self) -> self.assertNotIn("common buyer synchronization paths", adr) self.assertNotIn("does not become a device or synchronization conflict", adr) + self.assertNotIn( + "cannot become Windows device or Microsoft synchronization conflicts", + changelog, + ) def test_git_protocol_references_pin_the_verified_manual_revisions(self) -> None: """Protocol evidence must resolve to the manual revisions that contain the cited rules.""" From a632575ae0254f8f87265b6533a1e519d40952df Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 00:17:47 -0700 Subject: [PATCH 52/53] docs(release): bound changelog portability claim --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 908d6de47..fd26dc8b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security - 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, so package names cannot become Windows device or Microsoft synchronization conflicts in buyer environments. +- 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. - Raw secrets are rejected and secret-capable actions require an opaque broker handle. - Crawler mode is read-only, must pair with the public-crawl purpose, and fails closed without an applicable robots-policy decision. From 94d5e1f12c959243d107c6f7bfff24faf995d633 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 16:01:48 +0900 Subject: [PATCH 53/53] docs(doctoring): merge portable device-exclusion citations --- docs/doctoring.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/doctoring.md b/docs/doctoring.md index 88a199066..73b34cfd4 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -170,11 +170,9 @@ Microsoft. (2025, July 25). *Azure IP address 168.63.129.16 overview*. Microsoft Microsoft. (n.d.). *Naming files, paths, and namespaces*. Microsoft Learn. Retrieved August 23, 2026, from https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file -<<<<<<< HEAD -Model Context Protocol. (2026, July 28). *Specification: 2026-07-28*. https://modelcontextprotocol.io/specification/2026-07-28 -======= Microsoft. (n.d.). *Restrictions and limitations in OneDrive and SharePoint*. Microsoft Support. Retrieved August 23, 2026, from https://support.microsoft.com/en-US/onedrive/restrictions-and-limitations-in-onedrive-and-sharepoint ->>>>>>> 45f024dc (docs(doctoring): record portable COM0 and LPT0 exclusions) + +Model Context Protocol. (2026, July 28). *Specification: 2026-07-28*. https://modelcontextprotocol.io/specification/2026-07-28 Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2025). *Learning to orchestrate agents in natural language with the Conductor* [Preprint]. arXiv. https://doi.org/10.48550/arXiv.2512.04388