Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0465a32
test(core): define release manifest artifact admission contract
seonghobae Aug 22, 2026
296add6
test(core): format release manifest contract before semantic red
seonghobae Aug 22, 2026
b300d19
feat(core): add fail-closed release manifest identity primitive
seonghobae Aug 22, 2026
4077ece
feat(core): expose release manifest identity contract
seonghobae Aug 22, 2026
40a7a74
style(core): apply canonical release manifest formatting
seonghobae Aug 22, 2026
3d13e54
test(core): close exact release manifest coverage branches
seonghobae Aug 22, 2026
28d4d48
docs(changelog): record release manifest identity primitive
seonghobae Aug 22, 2026
8444244
test(core): reject case-colliding release artifacts
seonghobae Aug 22, 2026
c9ce1f7
fix(core): reject case-colliding release artifacts
seonghobae Aug 22, 2026
279d136
docs(changelog): record cross-platform artifact collision guard
seonghobae Aug 22, 2026
a5ef8ae
test(core): reject Windows device artifact names
seonghobae Aug 22, 2026
4c159cb
fix(core): reject Windows device artifact names
seonghobae Aug 22, 2026
19ee0a8
style(core): apply canonical release-manifest formatting
seonghobae Aug 22, 2026
f51c964
test(core): cover ordinary four-byte artifact basenames
seonghobae Aug 22, 2026
d3ce942
docs(adr): record release manifest identity boundary
seonghobae Aug 22, 2026
222e622
docs: index proposed release manifest ADR
seonghobae Aug 22, 2026
5f9fe3b
docs: index active release manifest ADR
seonghobae Aug 22, 2026
c02d46a
docs: trace release filename portability evidence
seonghobae Aug 22, 2026
3d18700
test(core): require release build identity evidence
seonghobae Aug 22, 2026
cf3a4d3
test(core): canonicalize release build identity regression
seonghobae Aug 22, 2026
78d31dc
feat(core): bind release build identity evidence
seonghobae Aug 22, 2026
8e75f34
style(core): canonicalize release build identity formatting
seonghobae Aug 22, 2026
520142a
docs(adr): bind release build identity evidence
seonghobae Aug 22, 2026
a902fb4
docs(changelog): record release build identity binding
seonghobae Aug 22, 2026
f2eef7e
test(core): satisfy strict release manifest Clippy contract
seonghobae Aug 22, 2026
5fadaf9
test(release): reject unpinned Rust toolchains
seonghobae Aug 22, 2026
3ac70fd
fix(release): bind exact pinned Rust toolchain
seonghobae Aug 22, 2026
11d7e3f
docs(adr): bind release identity to pinned Rust baseline
seonghobae Aug 22, 2026
c3cb0fc
docs(changelog): record exact release toolchain binding
seonghobae Aug 22, 2026
3e9bea4
test(release): require exact toolchain error contract
seonghobae Aug 22, 2026
e264711
test(release): decouple exact pin from obsolete token bound
seonghobae Aug 22, 2026
db06d75
fix(release): make pinned toolchain contract explicit
seonghobae Aug 22, 2026
4de5adc
test(release): reject null Git source identity
seonghobae Aug 22, 2026
813cc53
fix(release): reject null Git source identity
seonghobae Aug 22, 2026
971b308
docs(release): reject Git zero-id as source identity
seonghobae Aug 22, 2026
58ed003
docs(release): record non-null Git identity rationale
seonghobae Aug 22, 2026
bf2abb3
test(core): pin non-null release source error contract
seonghobae Aug 23, 2026
7de7b8e
fix(core): align release source diagnostic with non-null identity
seonghobae Aug 23, 2026
17903a6
docs(release): pin Git protocol references
seonghobae Aug 23, 2026
58e7271
docs(release): pin doctoring Git references
seonghobae Aug 23, 2026
21c48c8
test(release): reject Win32 COM0 and LPT0 aliases
seonghobae Aug 23, 2026
7af2b25
fix(release): reject Win32 COM0 and LPT0 device aliases
seonghobae Aug 23, 2026
dea6277
docs(adr): record portable COM0 and LPT0 exclusion
seonghobae Aug 23, 2026
e0d7d5b
fix(release): satisfy strict ASCII digit lint
seonghobae Aug 23, 2026
1f8729f
docs(doctoring): record portable COM0 and LPT0 exclusions
seonghobae Aug 23, 2026
a379a63
docs(changelog): record portable release device aliases
seonghobae Aug 23, 2026
9c7bc54
test(release): pin manifest documentation truth
seonghobae Aug 24, 2026
9255078
test(release): align Git citation revision contracts
seonghobae Aug 24, 2026
47fcb74
docs(release): narrow manifest portability claims
seonghobae Aug 24, 2026
2b0edc2
docs(release): align portability and Git evidence
seonghobae Aug 24, 2026
d438a4a
test(release): cover changelog portability truth
seonghobae Aug 24, 2026
a632575
docs(release): bound changelog portability claim
seonghobae Aug 24, 2026
94d5e1f
docs(doctoring): merge portable device-exclusion citations
seonghobae Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +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, 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.
Expand Down Expand Up @@ -71,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, 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.
Expand Down
3 changes: 3 additions & 0 deletions crates/originweave-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
361 changes: 361 additions & 0 deletions crates/originweave-core/src/release_manifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
//! Fail-closed identity binding for release artifacts.
//!
//! The types in this module are deliberately inert metadata contracts. They bind an exact
//! source commit, Chromium revision, release channel, build identity, and artifact digests
//! without granting signing, publication, installation, update, rollback, or release authority.

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,
}

/// 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 build identity from the exact repository-pinned Rust toolchain and lock digest.
///
/// 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,
) -> Result<Self, ReleaseBuildIdentityError> {
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 repository-pinned 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 does not match the exact repository-pinned baseline.
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 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",
),
}
}
}

impl Error for ReleaseBuildIdentityError {}

/// 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 or trailing punctuation, or Windows reserved device
/// basenames (including those basenames followed by extensions).
pub fn new(name: &str, sha256_digest: &str) -> Result<Self, ReleaseArtifactError> {
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,
build_identity: ReleaseBuildIdentity,
artifacts: Vec<ReleaseArtifact>,
}

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, 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<I>(
source_commit: &str,
chromium_revision: &str,
channel: ReleaseChannel,
build_identity: ReleaseBuildIdentity,
artifacts: I,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
) -> Result<Self, ReleaseManifestError>
where
I: IntoIterator<Item = ReleaseArtifact>,
{
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);
Comment thread
seonghobae marked this conversation as resolved.
}
if !artifact_names.insert(artifact.name.to_ascii_lowercase()) {
return Err(ReleaseManifestError::DuplicateArtifactName);
}
admitted.push(artifact);
}
Comment thread
seonghobae marked this conversation as resolved.
if admitted.is_empty() {
return Err(ReleaseManifestError::MissingArtifacts);
}
admitted.sort_by(|left, right| left.name.cmp(&right.name));
Comment thread
seonghobae marked this conversation as resolved.

Ok(Self {
source_commit: source_commit.to_owned(),
chromium_revision: chromium_revision.to_owned(),
channel,
build_identity,
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 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] {
&self.artifacts
}
}

/// Validation error for release-manifest identity evidence.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReleaseManifestError {
/// 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,
/// No release artifacts were supplied.
MissingArtifacts,
/// Artifact inventory exceeds the bounded release-manifest limit.
TooManyArtifacts,
/// Artifact inventory repeats an ASCII-case-folded 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 a non-null 40-digit lowercase Git object identity",
),
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("..")
|| windows_reserved_device_basename(name)
{
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'-'))
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

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"))
&& bytes[3].is_ascii_digit()
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
}
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.

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'))
&& source_commit.bytes().any(|byte| byte != b'0')
}
Comment thread
seonghobae marked this conversation as resolved.

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'@')
})
}

fn valid_toolchain(toolchain: &str) -> bool {
toolchain == "1.97.1"
}
Comment thread
seonghobae marked this conversation as resolved.
Loading
Loading