From a508b5a9af451424f35f509798f8684d249889c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 07:32:01 -0700 Subject: [PATCH 01/72] test(core): define fail-closed release benchmark decision contract --- .../tests/release_acceptance.rs | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 crates/originweave-core/tests/release_acceptance.rs diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs new file mode 100644 index 000000000..170bd18e9 --- /dev/null +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -0,0 +1,162 @@ +#![allow(clippy::expect_used)] + +use originweave_core::release_acceptance::{ + BenchmarkSuite, BenchmarkSuiteOutcome, ReleaseDecision, ReleaseDecisionError, decide_release, +}; + +fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { + BenchmarkSuite::ALL + .into_iter() + .map(|suite| (suite, BenchmarkSuiteOutcome::Passed)) + .collect() +} + +#[test] +fn complete_passing_evidence_is_accepted_without_declared_limitations() { + let report = decide_release(passing_results(), false).expect("complete unique suite evidence"); + + assert_eq!(report.decision(), ReleaseDecision::Accepted); + assert!(report.failed_suites().is_empty()); + assert!(report.inconclusive_suites().is_empty()); + assert!(report.missing_suites().is_empty()); +} + +#[test] +fn complete_passing_evidence_preserves_declared_limitation_decision() { + let report = decide_release(passing_results(), true).expect("complete unique suite evidence"); + + assert_eq!( + report.decision(), + ReleaseDecision::AcceptedWithDeclaredLimitations + ); +} + +#[test] +fn every_mandatory_suite_is_required_for_acceptance() { + for omitted_suite in BenchmarkSuite::ALL { + let evidence = passing_results() + .into_iter() + .filter(|(suite, _)| *suite != omitted_suite) + .collect::>(); + + let report = decide_release(evidence, false).expect("remaining suite identities are unique"); + + assert_eq!(report.decision(), ReleaseDecision::Inconclusive); + assert_eq!(report.missing_suites(), &[omitted_suite]); + assert!(report.failed_suites().is_empty()); + } +} + +#[test] +fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance() { + for inconclusive_suite in BenchmarkSuite::ALL { + let evidence = passing_results() + .into_iter() + .map(|(suite, outcome)| { + if suite == inconclusive_suite { + (suite, BenchmarkSuiteOutcome::Inconclusive) + } else { + (suite, outcome) + } + }) + .collect::>(); + + let report = decide_release(evidence, true).expect("suite identities are unique"); + + assert_eq!(report.decision(), ReleaseDecision::Inconclusive); + assert_eq!(report.inconclusive_suites(), &[inconclusive_suite]); + } +} + +#[test] +fn any_known_threshold_failure_rejects_release_and_identifies_the_suite() { + for failed_suite in BenchmarkSuite::ALL { + let evidence = passing_results() + .into_iter() + .map(|(suite, outcome)| { + if suite == failed_suite { + (suite, BenchmarkSuiteOutcome::Failed) + } else { + (suite, outcome) + } + }) + .collect::>(); + + let report = decide_release(evidence, true).expect("suite identities are unique"); + + assert_eq!(report.decision(), ReleaseDecision::Rejected); + assert_eq!(report.failed_suites(), &[failed_suite]); + } +} + +#[test] +fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { + let report = decide_release( + [ + ( + BenchmarkSuite::ControlledDeterministic, + BenchmarkSuiteOutcome::Failed, + ), + ( + BenchmarkSuite::WebCompatibility, + BenchmarkSuiteOutcome::Inconclusive, + ), + ], + false, + ) + .expect("suite identities are unique"); + + assert_eq!(report.decision(), ReleaseDecision::Rejected); + assert_eq!( + report.failed_suites(), + &[BenchmarkSuite::ControlledDeterministic] + ); + assert_eq!( + report.inconclusive_suites(), + &[BenchmarkSuite::WebCompatibility] + ); + assert_eq!( + report.missing_suites(), + &[ + BenchmarkSuite::SecurityAdversarial, + BenchmarkSuite::ReliabilityRecovery, + BenchmarkSuite::EnterpriseOperability, + ] + ); +} + +#[test] +fn duplicate_suite_evidence_fails_closed_instead_of_overwriting_results() { + for duplicate_suite in BenchmarkSuite::ALL { + let error = decide_release( + [ + (duplicate_suite, BenchmarkSuiteOutcome::Passed), + (duplicate_suite, BenchmarkSuiteOutcome::Failed), + ], + false, + ) + .expect_err("duplicate suite evidence must fail closed"); + + assert_eq!(error, ReleaseDecisionError::DuplicateSuite(duplicate_suite)); + assert_eq!( + error.to_string(), + format!( + "benchmark release evidence contains duplicate suite: {}", + duplicate_suite.as_str() + ) + ); + let standard_error: &dyn std::error::Error = &error; + assert!(standard_error.source().is_none()); + } +} + +#[test] +fn decision_is_independent_of_evidence_input_order() { + let mut reversed = passing_results(); + reversed.reverse(); + + assert_eq!( + decide_release(reversed, false).expect("suite identities are unique"), + decide_release(passing_results(), false).expect("suite identities are unique") + ); +} From 3608eec1e8ffddc09b3a43ed098733a99e7da9f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 07:35:31 -0700 Subject: [PATCH 02/72] style(core): apply canonical release decision test formatting --- crates/originweave-core/tests/release_acceptance.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 170bd18e9..295779f0b 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -39,7 +39,8 @@ fn every_mandatory_suite_is_required_for_acceptance() { .filter(|(suite, _)| *suite != omitted_suite) .collect::>(); - let report = decide_release(evidence, false).expect("remaining suite identities are unique"); + let report = + decide_release(evidence, false).expect("remaining suite identities are unique"); assert_eq!(report.decision(), ReleaseDecision::Inconclusive); assert_eq!(report.missing_suites(), &[omitted_suite]); From 426eb21fa06b58e291ee3d2ca6896eeaa60ad50a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 07:39:23 -0700 Subject: [PATCH 03/72] feat(core): implement fail-closed release benchmark decision --- crates/originweave-core/src/lib.rs | 186 +++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index b6ed55ff2..9404678f3 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1089,3 +1089,189 @@ pub fn evaluate_extension_access( } ExtensionAccessDecision::Allow } + +/// Deterministic release-acceptance aggregation for the commercial benchmark gate. +pub mod release_acceptance { + use std::fmt; + + /// One mandatory benchmark suite in the release acceptance contract. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum BenchmarkSuite { + /// Controlled local fixtures with deterministic post-condition oracles. + ControlledDeterministic, + /// Stable web compatibility tasks for the declared support profile. + WebCompatibility, + /// Hostile security cases that measure unauthorized authority or disclosure. + SecurityAdversarial, + /// Crash, timeout, retry, reconciliation, cleanup, and restore behavior. + ReliabilityRecovery, + /// Enterprise isolation, identity, policy, audit, and operator controls. + EnterpriseOperability, + } + + impl BenchmarkSuite { + /// Every mandatory benchmark suite in canonical release-report order. + pub const ALL: [Self; 5] = [ + Self::ControlledDeterministic, + Self::WebCompatibility, + Self::SecurityAdversarial, + Self::ReliabilityRecovery, + Self::EnterpriseOperability, + ]; + + /// Return the stable snake-case suite identifier used by benchmark evidence. + #[must_use] + pub const fn as_str(self) -> &'static str { + match self { + Self::ControlledDeterministic => "controlled_deterministic_suite", + Self::WebCompatibility => "web_compatibility_suite", + Self::SecurityAdversarial => "security_adversarial_suite", + Self::ReliabilityRecovery => "reliability_recovery_suite", + Self::EnterpriseOperability => "enterprise_operability_suite", + } + } + + const fn index(self) -> usize { + match self { + Self::ControlledDeterministic => 0, + Self::WebCompatibility => 1, + Self::SecurityAdversarial => 2, + Self::ReliabilityRecovery => 3, + Self::EnterpriseOperability => 4, + } + } + } + + /// Evaluated outcome for one mandatory benchmark suite. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum BenchmarkSuiteOutcome { + /// Every threshold required for the declared profile passed. + Passed, + /// At least one mandatory threshold is known to have failed. + Failed, + /// Evidence is insufficient to establish either pass or threshold failure. + Inconclusive, + } + + /// Deterministic release decision produced from mandatory suite evidence. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum ReleaseDecision { + /// Every mandatory suite passed for the full declared support profile. + Accepted, + /// Every mandatory suite passed after buyer-visible limitations were declared. + AcceptedWithDeclaredLimitations, + /// At least one mandatory suite is known to have failed its threshold. + Rejected, + /// No known threshold failure exists, but mandatory evidence is incomplete. + Inconclusive, + } + + /// Fail-closed input error while constructing a release decision. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum ReleaseDecisionError { + /// The same suite appeared more than once instead of one authoritative result. + DuplicateSuite(BenchmarkSuite), + } + + impl fmt::Display for ReleaseDecisionError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::DuplicateSuite(suite) => write!( + formatter, + "benchmark release evidence contains duplicate suite: {}", + suite.as_str() + ), + } + } + } + + impl std::error::Error for ReleaseDecisionError {} + + /// Release decision together with exact mandatory-suite evidence gaps and failures. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct ReleaseDecisionReport { + decision: ReleaseDecision, + failed_suites: Vec, + inconclusive_suites: Vec, + missing_suites: Vec, + } + + impl ReleaseDecisionReport { + /// Return the deterministic release decision. + #[must_use] + pub const fn decision(&self) -> ReleaseDecision { + self.decision + } + + /// Return suites with a known mandatory-threshold failure. + #[must_use] + pub fn failed_suites(&self) -> &[BenchmarkSuite] { + &self.failed_suites + } + + /// Return suites whose supplied evidence was explicitly inconclusive. + #[must_use] + pub fn inconclusive_suites(&self) -> &[BenchmarkSuite] { + &self.inconclusive_suites + } + + /// Return mandatory suites for which no outcome was supplied. + #[must_use] + pub fn missing_suites(&self) -> &[BenchmarkSuite] { + &self.missing_suites + } + } + + /// Produce one deterministic release decision from mandatory suite outcomes. + /// + /// Duplicate suite evidence fails closed rather than selecting an arbitrary + /// result. A known mandatory-threshold failure is always rejected, even when + /// other suites are missing or inconclusive; all such evidence gaps remain in + /// the returned report. Without a known failure, missing or inconclusive + /// evidence is never promoted to acceptance. + pub fn decide_release( + results: I, + has_declared_limitations: bool, + ) -> Result + where + I: IntoIterator, + { + let mut outcomes = [None; BenchmarkSuite::ALL.len()]; + for (suite, outcome) in results { + let slot = &mut outcomes[suite.index()]; + if slot.is_some() { + return Err(ReleaseDecisionError::DuplicateSuite(suite)); + } + *slot = Some(outcome); + } + + let mut failed_suites = Vec::new(); + let mut inconclusive_suites = Vec::new(); + let mut missing_suites = Vec::new(); + for suite in BenchmarkSuite::ALL { + match outcomes[suite.index()] { + Some(BenchmarkSuiteOutcome::Passed) => {} + Some(BenchmarkSuiteOutcome::Failed) => failed_suites.push(suite), + Some(BenchmarkSuiteOutcome::Inconclusive) => inconclusive_suites.push(suite), + None => missing_suites.push(suite), + } + } + + let decision = if !failed_suites.is_empty() { + ReleaseDecision::Rejected + } else if !inconclusive_suites.is_empty() || !missing_suites.is_empty() { + ReleaseDecision::Inconclusive + } else if has_declared_limitations { + ReleaseDecision::AcceptedWithDeclaredLimitations + } else { + ReleaseDecision::Accepted + }; + + Ok(ReleaseDecisionReport { + decision, + failed_suites, + inconclusive_suites, + missing_suites, + }) + } +} From ed6ac0debfa2f1353cf85e11ec9e8203bfc0e2e5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 07:43:37 -0700 Subject: [PATCH 04/72] test(core): cover duplicate vector release evidence --- crates/originweave-core/tests/release_acceptance.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 295779f0b..7922dd684 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -151,6 +151,18 @@ fn duplicate_suite_evidence_fails_closed_instead_of_overwriting_results() { } } +#[test] +fn duplicate_suite_evidence_in_vector_input_also_fails_closed() { + let duplicate_suite = BenchmarkSuite::ControlledDeterministic; + let mut evidence = passing_results(); + evidence.push((duplicate_suite, BenchmarkSuiteOutcome::Failed)); + + assert_eq!( + decide_release(evidence, false), + Err(ReleaseDecisionError::DuplicateSuite(duplicate_suite)) + ); +} + #[test] fn decision_is_independent_of_evidence_input_order() { let mut reversed = passing_results(); From f92eccc01a57cd9851a08c9c94faefa8718c2265 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:28:47 -0700 Subject: [PATCH 05/72] test(core): require explicit release limitations --- .../tests/release_acceptance.rs | 99 +++++++++++++++---- 1 file changed, 78 insertions(+), 21 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 7922dd684..6b8001925 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -1,7 +1,6 @@ -#![allow(clippy::expect_used)] - use originweave_core::release_acceptance::{ - BenchmarkSuite, BenchmarkSuiteOutcome, ReleaseDecision, ReleaseDecisionError, decide_release, + BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, ReleaseDecision, ReleaseDecisionError, + decide_release, }; fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { @@ -11,24 +10,71 @@ fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { .collect() } +fn declared_limitation() -> DeclaredLimitation { + let Ok(limitation) = DeclaredLimitation::new( + "linux_arm64", + "Linux ARM64 is not included in the declared release support profile.", + ) else { + panic!("fixture limitation must be valid"); + }; + limitation +} + #[test] fn complete_passing_evidence_is_accepted_without_declared_limitations() { - let report = decide_release(passing_results(), false).expect("complete unique suite evidence"); + let Ok(report) = decide_release(passing_results(), &[]) else { + panic!("complete unique suite evidence must produce a report"); + }; assert_eq!(report.decision(), ReleaseDecision::Accepted); assert!(report.failed_suites().is_empty()); assert!(report.inconclusive_suites().is_empty()); assert!(report.missing_suites().is_empty()); + assert!(report.declared_limitations().is_empty()); } #[test] -fn complete_passing_evidence_preserves_declared_limitation_decision() { - let report = decide_release(passing_results(), true).expect("complete unique suite evidence"); +fn complete_passing_evidence_preserves_declared_limitation_details() { + let limitation = declared_limitation(); + let Ok(report) = decide_release(passing_results(), std::slice::from_ref(&limitation)) else { + panic!("complete unique suite evidence must produce a report"); + }; assert_eq!( report.decision(), ReleaseDecision::AcceptedWithDeclaredLimitations ); + assert_eq!(report.declared_limitations(), &[limitation]); +} + +#[test] +fn limitation_requires_an_unsupported_claim() { + assert_eq!( + DeclaredLimitation::new( + " ", + "A buyer-visible consequence must not stand without the narrowed claim.", + ), + Err(ReleaseDecisionError::EmptyLimitationClaim) + ); +} + +#[test] +fn limitation_requires_a_buyer_visible_consequence() { + assert_eq!( + DeclaredLimitation::new("linux_arm64", "\t\n"), + Err(ReleaseDecisionError::EmptyLimitationConsequence) + ); +} + +#[test] +fn limitation_exposes_the_exact_narrowed_claim_and_consequence() { + let limitation = declared_limitation(); + + assert_eq!(limitation.unsupported_claim(), "linux_arm64"); + assert_eq!( + limitation.buyer_consequence(), + "Linux ARM64 is not included in the declared release support profile." + ); } #[test] @@ -39,8 +85,9 @@ fn every_mandatory_suite_is_required_for_acceptance() { .filter(|(suite, _)| *suite != omitted_suite) .collect::>(); - let report = - decide_release(evidence, false).expect("remaining suite identities are unique"); + let Ok(report) = decide_release(evidence, &[]) else { + panic!("remaining suite identities must be unique"); + }; assert_eq!(report.decision(), ReleaseDecision::Inconclusive); assert_eq!(report.missing_suites(), &[omitted_suite]); @@ -61,11 +108,15 @@ fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance() { } }) .collect::>(); + let limitation = declared_limitation(); - let report = decide_release(evidence, true).expect("suite identities are unique"); + let Ok(report) = decide_release(evidence, std::slice::from_ref(&limitation)) else { + panic!("suite identities must be unique"); + }; assert_eq!(report.decision(), ReleaseDecision::Inconclusive); assert_eq!(report.inconclusive_suites(), &[inconclusive_suite]); + assert_eq!(report.declared_limitations(), &[limitation]); } } @@ -82,17 +133,21 @@ fn any_known_threshold_failure_rejects_release_and_identifies_the_suite() { } }) .collect::>(); + let limitation = declared_limitation(); - let report = decide_release(evidence, true).expect("suite identities are unique"); + let Ok(report) = decide_release(evidence, std::slice::from_ref(&limitation)) else { + panic!("suite identities must be unique"); + }; assert_eq!(report.decision(), ReleaseDecision::Rejected); assert_eq!(report.failed_suites(), &[failed_suite]); + assert_eq!(report.declared_limitations(), &[limitation]); } } #[test] fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { - let report = decide_release( + let Ok(report) = decide_release( [ ( BenchmarkSuite::ControlledDeterministic, @@ -103,9 +158,10 @@ fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { BenchmarkSuiteOutcome::Inconclusive, ), ], - false, - ) - .expect("suite identities are unique"); + &[], + ) else { + panic!("suite identities must be unique"); + }; assert_eq!(report.decision(), ReleaseDecision::Rejected); assert_eq!( @@ -129,14 +185,15 @@ fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { #[test] fn duplicate_suite_evidence_fails_closed_instead_of_overwriting_results() { for duplicate_suite in BenchmarkSuite::ALL { - let error = decide_release( + let Err(error) = decide_release( [ (duplicate_suite, BenchmarkSuiteOutcome::Passed), (duplicate_suite, BenchmarkSuiteOutcome::Failed), ], - false, - ) - .expect_err("duplicate suite evidence must fail closed"); + &[], + ) else { + panic!("duplicate suite evidence must fail closed"); + }; assert_eq!(error, ReleaseDecisionError::DuplicateSuite(duplicate_suite)); assert_eq!( @@ -158,7 +215,7 @@ fn duplicate_suite_evidence_in_vector_input_also_fails_closed() { evidence.push((duplicate_suite, BenchmarkSuiteOutcome::Failed)); assert_eq!( - decide_release(evidence, false), + decide_release(evidence, &[]), Err(ReleaseDecisionError::DuplicateSuite(duplicate_suite)) ); } @@ -169,7 +226,7 @@ fn decision_is_independent_of_evidence_input_order() { reversed.reverse(); assert_eq!( - decide_release(reversed, false).expect("suite identities are unique"), - decide_release(passing_results(), false).expect("suite identities are unique") + decide_release(reversed, &[]), + decide_release(passing_results(), &[]) ); } From df7b164c1bbe88c1cf2d88a08e91da94c3ab732f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:30:41 -0700 Subject: [PATCH 06/72] test(core): format release limitation regression --- crates/originweave-core/tests/release_acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 6b8001925..d466dfc77 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -1,6 +1,6 @@ use originweave_core::release_acceptance::{ - BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, ReleaseDecision, ReleaseDecisionError, - decide_release, + BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, ReleaseDecision, + ReleaseDecisionError, decide_release, }; fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { From 7f859b663f134c838a5dfa288c38dd09bdc2bd42 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:35:10 -0700 Subject: [PATCH 07/72] fix(core): bind release limitations to buyer consequences --- crates/originweave-core/src/lib.rs | 77 ++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 9404678f3..a19b97170 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1153,6 +1153,53 @@ pub mod release_acceptance { Inconclusive, } + /// One explicit narrowed release claim and its buyer-visible consequence. + /// + /// An accepted-with-limitations decision cannot be produced from an opaque + /// boolean. Every limitation must name the unsupported claim and state the + /// consequence that a buyer must account for in the declared support profile. + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct DeclaredLimitation { + unsupported_claim: String, + buyer_consequence: String, + } + + impl DeclaredLimitation { + /// Construct one explicit buyer-visible release limitation. + /// + /// Whitespace-only claims or consequences fail closed because they cannot + /// narrow a release claim or communicate a usable buyer consequence. + pub fn new( + unsupported_claim: impl Into, + buyer_consequence: impl Into, + ) -> Result { + let unsupported_claim = unsupported_claim.into(); + if unsupported_claim.trim().is_empty() { + return Err(ReleaseDecisionError::EmptyLimitationClaim); + } + let buyer_consequence = buyer_consequence.into(); + if buyer_consequence.trim().is_empty() { + return Err(ReleaseDecisionError::EmptyLimitationConsequence); + } + Ok(Self { + unsupported_claim, + buyer_consequence, + }) + } + + /// Return the exact unsupported or narrowed release claim. + #[must_use] + pub fn unsupported_claim(&self) -> &str { + &self.unsupported_claim + } + + /// Return the exact consequence exposed to buyers and operators. + #[must_use] + pub fn buyer_consequence(&self) -> &str { + &self.buyer_consequence + } + } + /// Deterministic release decision produced from mandatory suite evidence. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReleaseDecision { @@ -1169,6 +1216,10 @@ pub mod release_acceptance { /// Fail-closed input error while constructing a release decision. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReleaseDecisionError { + /// A declared limitation did not identify the unsupported release claim. + EmptyLimitationClaim, + /// A declared limitation did not state the buyer-visible consequence. + EmptyLimitationConsequence, /// The same suite appeared more than once instead of one authoritative result. DuplicateSuite(BenchmarkSuite), } @@ -1176,6 +1227,12 @@ pub mod release_acceptance { impl fmt::Display for ReleaseDecisionError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::EmptyLimitationClaim => { + formatter.write_str("declared release limitation must name an unsupported claim") + } + Self::EmptyLimitationConsequence => formatter.write_str( + "declared release limitation must state a buyer-visible consequence", + ), Self::DuplicateSuite(suite) => write!( formatter, "benchmark release evidence contains duplicate suite: {}", @@ -1194,6 +1251,7 @@ pub mod release_acceptance { failed_suites: Vec, inconclusive_suites: Vec, missing_suites: Vec, + declared_limitations: Vec, } impl ReleaseDecisionReport { @@ -1220,6 +1278,12 @@ pub mod release_acceptance { pub fn missing_suites(&self) -> &[BenchmarkSuite] { &self.missing_suites } + + /// Return the exact buyer-visible limitations retained with this decision. + #[must_use] + pub fn declared_limitations(&self) -> &[DeclaredLimitation] { + &self.declared_limitations + } } /// Produce one deterministic release decision from mandatory suite outcomes. @@ -1228,10 +1292,12 @@ pub mod release_acceptance { /// result. A known mandatory-threshold failure is always rejected, even when /// other suites are missing or inconclusive; all such evidence gaps remain in /// the returned report. Without a known failure, missing or inconclusive - /// evidence is never promoted to acceptance. + /// evidence is never promoted to acceptance. Accepted-with-limitations requires + /// at least one validated [`DeclaredLimitation`], so the decision cannot be + /// detached from the exact narrowed claim and buyer-visible consequence. pub fn decide_release( results: I, - has_declared_limitations: bool, + declared_limitations: &[DeclaredLimitation], ) -> Result where I: IntoIterator, @@ -1261,10 +1327,10 @@ pub mod release_acceptance { ReleaseDecision::Rejected } else if !inconclusive_suites.is_empty() || !missing_suites.is_empty() { ReleaseDecision::Inconclusive - } else if has_declared_limitations { - ReleaseDecision::AcceptedWithDeclaredLimitations - } else { + } else if declared_limitations.is_empty() { ReleaseDecision::Accepted + } else { + ReleaseDecision::AcceptedWithDeclaredLimitations }; Ok(ReleaseDecisionReport { @@ -1272,6 +1338,7 @@ pub mod release_acceptance { failed_suites, inconclusive_suites, missing_suites, + declared_limitations: declared_limitations.to_vec(), }) } } From 7ccc804b0f11e390d5ebf6c23532bf94a75c77e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:41:44 -0700 Subject: [PATCH 08/72] style(core): apply canonical release contract formatting --- crates/originweave-core/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index a19b97170..54c0ef62e 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1227,9 +1227,8 @@ pub mod release_acceptance { impl fmt::Display for ReleaseDecisionError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::EmptyLimitationClaim => { - formatter.write_str("declared release limitation must name an unsupported claim") - } + Self::EmptyLimitationClaim => formatter + .write_str("declared release limitation must name an unsupported claim"), Self::EmptyLimitationConsequence => formatter.write_str( "declared release limitation must state a buyer-visible consequence", ), From 07021d4587588f20bb8789ad5295895eafdd24e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:44:11 -0700 Subject: [PATCH 09/72] test(core): satisfy strict release contract linting --- .../tests/release_acceptance.rs | 117 ++++++++++-------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index d466dfc77..9c65bf5e0 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -10,41 +10,38 @@ fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { .collect() } -fn declared_limitation() -> DeclaredLimitation { - let Ok(limitation) = DeclaredLimitation::new( +fn declared_limitation() -> Result { + DeclaredLimitation::new( "linux_arm64", "Linux ARM64 is not included in the declared release support profile.", - ) else { - panic!("fixture limitation must be valid"); - }; - limitation + ) } #[test] -fn complete_passing_evidence_is_accepted_without_declared_limitations() { - let Ok(report) = decide_release(passing_results(), &[]) else { - panic!("complete unique suite evidence must produce a report"); - }; +fn complete_passing_evidence_is_accepted_without_declared_limitations( +) -> Result<(), ReleaseDecisionError> { + let report = decide_release(passing_results(), &[])?; assert_eq!(report.decision(), ReleaseDecision::Accepted); assert!(report.failed_suites().is_empty()); assert!(report.inconclusive_suites().is_empty()); assert!(report.missing_suites().is_empty()); assert!(report.declared_limitations().is_empty()); + Ok(()) } #[test] -fn complete_passing_evidence_preserves_declared_limitation_details() { - let limitation = declared_limitation(); - let Ok(report) = decide_release(passing_results(), std::slice::from_ref(&limitation)) else { - panic!("complete unique suite evidence must produce a report"); - }; +fn complete_passing_evidence_preserves_declared_limitation_details( +) -> Result<(), ReleaseDecisionError> { + let limitation = declared_limitation()?; + let report = decide_release(passing_results(), std::slice::from_ref(&limitation))?; assert_eq!( report.decision(), ReleaseDecision::AcceptedWithDeclaredLimitations ); assert_eq!(report.declared_limitations(), &[limitation]); + Ok(()) } #[test] @@ -67,36 +64,58 @@ fn limitation_requires_a_buyer_visible_consequence() { } #[test] -fn limitation_exposes_the_exact_narrowed_claim_and_consequence() { - let limitation = declared_limitation(); +fn limitation_errors_have_deterministic_standard_error_contracts() { + let cases = [ + ( + ReleaseDecisionError::EmptyLimitationClaim, + "declared release limitation must name an unsupported claim", + ), + ( + ReleaseDecisionError::EmptyLimitationConsequence, + "declared release limitation must state a buyer-visible consequence", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + let standard_error: &dyn std::error::Error = &error; + assert!(standard_error.source().is_none()); + } +} + +#[test] +fn limitation_exposes_the_exact_narrowed_claim_and_consequence( +) -> Result<(), ReleaseDecisionError> { + let limitation = declared_limitation()?; assert_eq!(limitation.unsupported_claim(), "linux_arm64"); assert_eq!( limitation.buyer_consequence(), "Linux ARM64 is not included in the declared release support profile." ); + Ok(()) } #[test] -fn every_mandatory_suite_is_required_for_acceptance() { +fn every_mandatory_suite_is_required_for_acceptance() -> Result<(), ReleaseDecisionError> { for omitted_suite in BenchmarkSuite::ALL { let evidence = passing_results() .into_iter() .filter(|(suite, _)| *suite != omitted_suite) .collect::>(); - let Ok(report) = decide_release(evidence, &[]) else { - panic!("remaining suite identities must be unique"); - }; + let report = decide_release(evidence, &[])?; assert_eq!(report.decision(), ReleaseDecision::Inconclusive); assert_eq!(report.missing_suites(), &[omitted_suite]); assert!(report.failed_suites().is_empty()); } + Ok(()) } #[test] -fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance() { +fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance( +) -> Result<(), ReleaseDecisionError> { for inconclusive_suite in BenchmarkSuite::ALL { let evidence = passing_results() .into_iter() @@ -108,20 +127,20 @@ fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance() { } }) .collect::>(); - let limitation = declared_limitation(); + let limitation = declared_limitation()?; - let Ok(report) = decide_release(evidence, std::slice::from_ref(&limitation)) else { - panic!("suite identities must be unique"); - }; + let report = decide_release(evidence, std::slice::from_ref(&limitation))?; assert_eq!(report.decision(), ReleaseDecision::Inconclusive); assert_eq!(report.inconclusive_suites(), &[inconclusive_suite]); assert_eq!(report.declared_limitations(), &[limitation]); } + Ok(()) } #[test] -fn any_known_threshold_failure_rejects_release_and_identifies_the_suite() { +fn any_known_threshold_failure_rejects_release_and_identifies_the_suite( +) -> Result<(), ReleaseDecisionError> { for failed_suite in BenchmarkSuite::ALL { let evidence = passing_results() .into_iter() @@ -133,21 +152,21 @@ fn any_known_threshold_failure_rejects_release_and_identifies_the_suite() { } }) .collect::>(); - let limitation = declared_limitation(); + let limitation = declared_limitation()?; - let Ok(report) = decide_release(evidence, std::slice::from_ref(&limitation)) else { - panic!("suite identities must be unique"); - }; + let report = decide_release(evidence, std::slice::from_ref(&limitation))?; assert_eq!(report.decision(), ReleaseDecision::Rejected); assert_eq!(report.failed_suites(), &[failed_suite]); assert_eq!(report.declared_limitations(), &[limitation]); } + Ok(()) } #[test] -fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { - let Ok(report) = decide_release( +fn known_failure_remains_rejected_when_other_evidence_is_incomplete( +) -> Result<(), ReleaseDecisionError> { + let report = decide_release( [ ( BenchmarkSuite::ControlledDeterministic, @@ -159,9 +178,7 @@ fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { ), ], &[], - ) else { - panic!("suite identities must be unique"); - }; + )?; assert_eq!(report.decision(), ReleaseDecision::Rejected); assert_eq!( @@ -180,30 +197,32 @@ fn known_failure_remains_rejected_when_other_evidence_is_incomplete() { BenchmarkSuite::EnterpriseOperability, ] ); + Ok(()) } #[test] fn duplicate_suite_evidence_fails_closed_instead_of_overwriting_results() { for duplicate_suite in BenchmarkSuite::ALL { - let Err(error) = decide_release( - [ - (duplicate_suite, BenchmarkSuiteOutcome::Passed), - (duplicate_suite, BenchmarkSuiteOutcome::Failed), - ], - &[], - ) else { - panic!("duplicate suite evidence must fail closed"); - }; - - assert_eq!(error, ReleaseDecisionError::DuplicateSuite(duplicate_suite)); + let expected_error = ReleaseDecisionError::DuplicateSuite(duplicate_suite); assert_eq!( - error.to_string(), + decide_release( + [ + (duplicate_suite, BenchmarkSuiteOutcome::Passed), + (duplicate_suite, BenchmarkSuiteOutcome::Failed), + ], + &[], + ), + Err(expected_error) + ); + + assert_eq!( + expected_error.to_string(), format!( "benchmark release evidence contains duplicate suite: {}", duplicate_suite.as_str() ) ); - let standard_error: &dyn std::error::Error = &error; + let standard_error: &dyn std::error::Error = &expected_error; assert!(standard_error.source().is_none()); } } From d6659da240e69b48ac377cb78934270dd1f1c78a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:45:48 -0700 Subject: [PATCH 10/72] style(core): apply canonical release test formatting --- .../tests/release_acceptance.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 9c65bf5e0..c1f9a5318 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -18,8 +18,8 @@ fn declared_limitation() -> Result { } #[test] -fn complete_passing_evidence_is_accepted_without_declared_limitations( -) -> Result<(), ReleaseDecisionError> { +fn complete_passing_evidence_is_accepted_without_declared_limitations() +-> Result<(), ReleaseDecisionError> { let report = decide_release(passing_results(), &[])?; assert_eq!(report.decision(), ReleaseDecision::Accepted); @@ -31,8 +31,8 @@ fn complete_passing_evidence_is_accepted_without_declared_limitations( } #[test] -fn complete_passing_evidence_preserves_declared_limitation_details( -) -> Result<(), ReleaseDecisionError> { +fn complete_passing_evidence_preserves_declared_limitation_details() +-> Result<(), ReleaseDecisionError> { let limitation = declared_limitation()?; let report = decide_release(passing_results(), std::slice::from_ref(&limitation))?; @@ -84,8 +84,8 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { } #[test] -fn limitation_exposes_the_exact_narrowed_claim_and_consequence( -) -> Result<(), ReleaseDecisionError> { +fn limitation_exposes_the_exact_narrowed_claim_and_consequence() -> Result<(), ReleaseDecisionError> +{ let limitation = declared_limitation()?; assert_eq!(limitation.unsupported_claim(), "linux_arm64"); @@ -114,8 +114,8 @@ fn every_mandatory_suite_is_required_for_acceptance() -> Result<(), ReleaseDecis } #[test] -fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance( -) -> Result<(), ReleaseDecisionError> { +fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance() +-> Result<(), ReleaseDecisionError> { for inconclusive_suite in BenchmarkSuite::ALL { let evidence = passing_results() .into_iter() @@ -139,8 +139,8 @@ fn explicit_inconclusive_suite_evidence_cannot_be_promoted_to_acceptance( } #[test] -fn any_known_threshold_failure_rejects_release_and_identifies_the_suite( -) -> Result<(), ReleaseDecisionError> { +fn any_known_threshold_failure_rejects_release_and_identifies_the_suite() +-> Result<(), ReleaseDecisionError> { for failed_suite in BenchmarkSuite::ALL { let evidence = passing_results() .into_iter() @@ -164,8 +164,8 @@ fn any_known_threshold_failure_rejects_release_and_identifies_the_suite( } #[test] -fn known_failure_remains_rejected_when_other_evidence_is_incomplete( -) -> Result<(), ReleaseDecisionError> { +fn known_failure_remains_rejected_when_other_evidence_is_incomplete() +-> Result<(), ReleaseDecisionError> { let report = decide_release( [ ( From a326340ae50cb9df57b103b56c867f73df680f52 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:00:52 -0700 Subject: [PATCH 11/72] test(core): reject control characters in release limitations --- .../tests/release_acceptance.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index c1f9a5318..70f84496d 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -63,6 +63,24 @@ fn limitation_requires_a_buyer_visible_consequence() { ); } +#[test] +fn limitation_rejects_control_characters_in_release_metadata() { + assert!( + DeclaredLimitation::new( + "linux_arm64\nforged_release_claim", + "Linux ARM64 is unsupported." + ) + .is_err() + ); + assert!( + DeclaredLimitation::new( + "linux_arm64", + "Linux ARM64 is unsupported.\rforged_release_consequence" + ) + .is_err() + ); +} + #[test] fn limitation_errors_have_deterministic_standard_error_contracts() { let cases = [ From 29b93ff19a30d3b41b41e0318f3952dae7b83869 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:09:55 -0700 Subject: [PATCH 12/72] fix(core): reject control characters in release limitations --- crates/originweave-core/src/lib.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 54c0ef62e..8dbc4e406 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1167,8 +1167,8 @@ pub mod release_acceptance { impl DeclaredLimitation { /// Construct one explicit buyer-visible release limitation. /// - /// Whitespace-only claims or consequences fail closed because they cannot - /// narrow a release claim or communicate a usable buyer consequence. + /// Empty/whitespace-only values and embedded control characters fail closed because + /// they cannot safely represent one unambiguous buyer-visible release limitation. pub fn new( unsupported_claim: impl Into, buyer_consequence: impl Into, @@ -1177,10 +1177,16 @@ pub mod release_acceptance { if unsupported_claim.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationClaim); } + if unsupported_claim.chars().any(char::is_control) { + return Err(ReleaseDecisionError::InvalidLimitationClaim); + } let buyer_consequence = buyer_consequence.into(); if buyer_consequence.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationConsequence); } + if buyer_consequence.chars().any(char::is_control) { + return Err(ReleaseDecisionError::InvalidLimitationConsequence); + } Ok(Self { unsupported_claim, buyer_consequence, @@ -1218,8 +1224,12 @@ pub mod release_acceptance { pub enum ReleaseDecisionError { /// A declared limitation did not identify the unsupported release claim. EmptyLimitationClaim, + /// A declared limitation claim contained a control character. + InvalidLimitationClaim, /// A declared limitation did not state the buyer-visible consequence. EmptyLimitationConsequence, + /// A declared limitation consequence contained a control character. + InvalidLimitationConsequence, /// The same suite appeared more than once instead of one authoritative result. DuplicateSuite(BenchmarkSuite), } @@ -1229,9 +1239,14 @@ pub mod release_acceptance { match self { Self::EmptyLimitationClaim => formatter .write_str("declared release limitation must name an unsupported claim"), + Self::InvalidLimitationClaim => formatter + .write_str("declared release limitation claim contains a control character"), Self::EmptyLimitationConsequence => formatter.write_str( "declared release limitation must state a buyer-visible consequence", ), + Self::InvalidLimitationConsequence => formatter.write_str( + "declared release limitation consequence contains a control character", + ), Self::DuplicateSuite(suite) => write!( formatter, "benchmark release evidence contains duplicate suite: {}", From b206245d7550d4f5d78e3803c33c61f440889a0d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:10:33 -0700 Subject: [PATCH 13/72] test(core): assert release limitation validation errors --- .../tests/release_acceptance.rs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 70f84496d..739682aec 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -65,19 +65,19 @@ fn limitation_requires_a_buyer_visible_consequence() { #[test] fn limitation_rejects_control_characters_in_release_metadata() { - assert!( + assert_eq!( DeclaredLimitation::new( "linux_arm64\nforged_release_claim", "Linux ARM64 is unsupported." - ) - .is_err() + ), + Err(ReleaseDecisionError::InvalidLimitationClaim) ); - assert!( + assert_eq!( DeclaredLimitation::new( "linux_arm64", "Linux ARM64 is unsupported.\rforged_release_consequence" - ) - .is_err() + ), + Err(ReleaseDecisionError::InvalidLimitationConsequence) ); } @@ -88,10 +88,18 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { ReleaseDecisionError::EmptyLimitationClaim, "declared release limitation must name an unsupported claim", ), + ( + ReleaseDecisionError::InvalidLimitationClaim, + "declared release limitation claim contains a control character", + ), ( ReleaseDecisionError::EmptyLimitationConsequence, "declared release limitation must state a buyer-visible consequence", ), + ( + ReleaseDecisionError::InvalidLimitationConsequence, + "declared release limitation consequence contains a control character", + ), ]; for (error, expected_message) in cases { From b4ccf732decc84d10fef34170efe4392ae9bc6d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:14:00 -0700 Subject: [PATCH 14/72] docs(changelog): record release limitation metadata hardening --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d17419927..a9e17c711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security +- Release-acceptance limitation metadata rejects embedded control characters so buyer-visible narrowed claims and consequences cannot contain forged line breaks. - 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 1d8d1ac7093d8f90193b9f892516ba1acb3ec79f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:28:57 -0700 Subject: [PATCH 15/72] test(core): reject ambiguous release limitation formatting --- .../tests/release_acceptance.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 739682aec..cb1670fde 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -81,6 +81,26 @@ fn limitation_rejects_control_characters_in_release_metadata() { ); } +#[test] +fn limitation_rejects_ambiguous_unicode_formatting_characters() { + for character in ['\u{202e}', '\u{200b}', '\u{00ad}', '\u{2066}', '\u{feff}'] { + assert_eq!( + DeclaredLimitation::new( + format!("linux_arm64{character}forged_release_claim"), + "Linux ARM64 is unsupported." + ), + Err(ReleaseDecisionError::InvalidLimitationClaim) + ); + assert_eq!( + DeclaredLimitation::new( + "linux_arm64", + format!("Linux ARM64 is unsupported.{character}forged_release_consequence") + ), + Err(ReleaseDecisionError::InvalidLimitationConsequence) + ); + } +} + #[test] fn limitation_errors_have_deterministic_standard_error_contracts() { let cases = [ From 38bdc6fc4e963d5708138c9083dfeb74a23f7de8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:34:27 -0700 Subject: [PATCH 16/72] fix(core): reject ambiguous release limitation presentation --- crates/originweave-core/src/lib.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 8dbc4e406..ade2126eb 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1167,7 +1167,7 @@ pub mod release_acceptance { impl DeclaredLimitation { /// Construct one explicit buyer-visible release limitation. /// - /// Empty/whitespace-only values and embedded control characters fail closed because + /// Empty/whitespace-only values and ambiguous presentation characters fail closed because /// they cannot safely represent one unambiguous buyer-visible release limitation. pub fn new( unsupported_claim: impl Into, @@ -1177,14 +1177,20 @@ pub mod release_acceptance { if unsupported_claim.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationClaim); } - if unsupported_claim.chars().any(char::is_control) { + if unsupported_claim + .chars() + .any(disallowed_release_limitation_character) + { return Err(ReleaseDecisionError::InvalidLimitationClaim); } let buyer_consequence = buyer_consequence.into(); if buyer_consequence.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationConsequence); } - if buyer_consequence.chars().any(char::is_control) { + if buyer_consequence + .chars() + .any(disallowed_release_limitation_character) + { return Err(ReleaseDecisionError::InvalidLimitationConsequence); } Ok(Self { @@ -1206,6 +1212,15 @@ pub mod release_acceptance { } } + fn disallowed_release_limitation_character(character: char) -> bool { + let code_point = character as u32; + character.is_control() + || matches!( + code_point, + 0x00ad | 0x061c | 0x200b..=0x200f | 0x2028..=0x202e | 0x2060..=0x206f | 0xfeff + ) + } + /// Deterministic release decision produced from mandatory suite evidence. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReleaseDecision { @@ -1224,11 +1239,11 @@ pub mod release_acceptance { pub enum ReleaseDecisionError { /// A declared limitation did not identify the unsupported release claim. EmptyLimitationClaim, - /// A declared limitation claim contained a control character. + /// A declared limitation claim contained an unsafe presentation character. InvalidLimitationClaim, /// A declared limitation did not state the buyer-visible consequence. EmptyLimitationConsequence, - /// A declared limitation consequence contained a control character. + /// A declared limitation consequence contained an unsafe presentation character. InvalidLimitationConsequence, /// The same suite appeared more than once instead of one authoritative result. DuplicateSuite(BenchmarkSuite), From c9fcca865a8fffaed2ff5b0d263fc1951a88fb1d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:36:46 -0700 Subject: [PATCH 17/72] test(core): require precise limitation validation errors --- .../tests/release_acceptance.rs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index cb1670fde..70fbe8a95 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -83,7 +83,10 @@ fn limitation_rejects_control_characters_in_release_metadata() { #[test] fn limitation_rejects_ambiguous_unicode_formatting_characters() { - for character in ['\u{202e}', '\u{200b}', '\u{00ad}', '\u{2066}', '\u{feff}'] { + for character in [ + '\u{00ad}', '\u{061c}', '\u{200b}', '\u{200f}', '\u{2028}', '\u{202e}', '\u{2060}', + '\u{2066}', '\u{206f}', '\u{feff}', + ] { assert_eq!( DeclaredLimitation::new( format!("linux_arm64{character}forged_release_claim"), @@ -101,6 +104,21 @@ fn limitation_rejects_ambiguous_unicode_formatting_characters() { } } +#[test] +fn limitation_preserves_unambiguous_international_buyer_text() -> Result<(), ReleaseDecisionError> { + let limitation = DeclaredLimitation::new( + "한국어_운영환경", + "이 운영환경은 현재 지원 범위에 포함되지 않습니다.", + )?; + + assert_eq!(limitation.unsupported_claim(), "한국어_운영환경"); + assert_eq!( + limitation.buyer_consequence(), + "이 운영환경은 현재 지원 범위에 포함되지 않습니다." + ); + Ok(()) +} + #[test] fn limitation_errors_have_deterministic_standard_error_contracts() { let cases = [ @@ -110,7 +128,7 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { ), ( ReleaseDecisionError::InvalidLimitationClaim, - "declared release limitation claim contains a control character", + "declared release limitation claim contains an unsafe presentation character", ), ( ReleaseDecisionError::EmptyLimitationConsequence, @@ -118,7 +136,7 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { ), ( ReleaseDecisionError::InvalidLimitationConsequence, - "declared release limitation consequence contains a control character", + "declared release limitation consequence contains an unsafe presentation character", ), ]; From 57d63a216d7bc90668e8d12fc7cec8acf546be65 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:41:53 -0700 Subject: [PATCH 18/72] fix(core): report unsafe limitation presentation precisely --- crates/originweave-core/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index ade2126eb..694225c75 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1254,13 +1254,14 @@ pub mod release_acceptance { match self { Self::EmptyLimitationClaim => formatter .write_str("declared release limitation must name an unsupported claim"), - Self::InvalidLimitationClaim => formatter - .write_str("declared release limitation claim contains a control character"), + Self::InvalidLimitationClaim => formatter.write_str( + "declared release limitation claim contains an unsafe presentation character", + ), Self::EmptyLimitationConsequence => formatter.write_str( "declared release limitation must state a buyer-visible consequence", ), Self::InvalidLimitationConsequence => formatter.write_str( - "declared release limitation consequence contains a control character", + "declared release limitation consequence contains an unsafe presentation character", ), Self::DuplicateSuite(suite) => write!( formatter, From 468c32cb0909ab76c634b30adad6df691874d7cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:45:03 -0700 Subject: [PATCH 19/72] docs: record release limitation presentation hardening --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e17c711..d01880d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security -- Release-acceptance limitation metadata rejects embedded control characters so buyer-visible narrowed claims and consequences cannot contain forged line breaks. +- Release-acceptance limitation metadata rejects embedded controls plus bidirectional, invisible, and other ambiguous Unicode presentation characters while preserving ordinary international text, so buyer-visible narrowed claims and consequences cannot forge or visually reorder release statements. - 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 363b9ee2511019b6d1dadd7d5c60be0183bba121 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 09:49:27 -0700 Subject: [PATCH 20/72] test(core): reject Mongolian vowel separator in limitations --- crates/originweave-core/tests/release_acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 70fbe8a95..a2da1c666 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -84,8 +84,8 @@ fn limitation_rejects_control_characters_in_release_metadata() { #[test] fn limitation_rejects_ambiguous_unicode_formatting_characters() { for character in [ - '\u{00ad}', '\u{061c}', '\u{200b}', '\u{200f}', '\u{2028}', '\u{202e}', '\u{2060}', - '\u{2066}', '\u{206f}', '\u{feff}', + '\u{00ad}', '\u{061c}', '\u{180e}', '\u{200b}', '\u{200f}', '\u{2028}', '\u{202e}', + '\u{2060}', '\u{2066}', '\u{206f}', '\u{feff}', ] { assert_eq!( DeclaredLimitation::new( From b54ed108397d513030cba7c4513c58cc8b76d9bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:02:02 -0700 Subject: [PATCH 21/72] fix(core): reject U+180E in release limitation metadata --- crates/originweave-core/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 694225c75..97116f180 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -267,7 +267,7 @@ impl BrowsingContextId { Ok(Self(value)) } - /// Return the validated browsing-context identifier. + /// Return the browsing-context identifier. #[must_use] pub const fn value(self) -> u64 { self.0 @@ -1217,7 +1217,7 @@ pub mod release_acceptance { character.is_control() || matches!( code_point, - 0x00ad | 0x061c | 0x200b..=0x200f | 0x2028..=0x202e | 0x2060..=0x206f | 0xfeff + 0x00ad | 0x061c | 0x180e | 0x200b..=0x200f | 0x2028..=0x202e | 0x2060..=0x206f | 0xfeff ) } From 71820f06bc31721f6effc15b106423ae39e0c41e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:03:47 -0700 Subject: [PATCH 22/72] docs(core): restore validated identifier wording --- crates/originweave-core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 97116f180..62addb8ca 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -267,7 +267,7 @@ impl BrowsingContextId { Ok(Self(value)) } - /// Return the browsing-context identifier. + /// Return the validated browsing-context identifier. #[must_use] pub const fn value(self) -> u64 { self.0 From 7f8639a8b7a539aa80df969596ee87ab82776f1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:10:29 -0700 Subject: [PATCH 23/72] test(core): require Unicode 17 default-ignorable rejection --- .../tests/release_acceptance_unicode17.rs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 crates/originweave-core/tests/release_acceptance_unicode17.rs diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs new file mode 100644 index 000000000..854513e06 --- /dev/null +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -0,0 +1,72 @@ +use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionError}; + +#[test] +fn limitation_rejects_unicode_17_default_ignorable_code_points() { + // Unicode 17.0.0 DerivedCoreProperties.txt, Default_Ignorable_Code_Point. + // Endpoints plus a midpoint make every reviewed inclusive range executable evidence. + let ranges = [ + (0x00ad_u32, 0x00ad_u32), + (0x034f, 0x034f), + (0x061c, 0x061c), + (0x115f, 0x1160), + (0x17b4, 0x17b5), + (0x180b, 0x180f), + (0x200b, 0x200f), + (0x202a, 0x202e), + (0x2060, 0x206f), + (0x3164, 0x3164), + (0xfe00, 0xfe0f), + (0xfeff, 0xfeff), + (0xffa0, 0xffa0), + (0xfff0, 0xfff8), + (0x1bca0, 0x1bca3), + (0x1d173, 0x1d17a), + (0xe0000, 0xe0fff), + ]; + + for (start, end) in ranges { + let midpoint = start + (end - start) / 2; + for code_point in [start, midpoint, end] { + let character = char::from_u32(code_point) + .expect("Unicode 17 default-ignorable range contains only scalar values"); + + assert_eq!( + DeclaredLimitation::new( + format!("linux_arm64{character}forged_release_claim"), + "Linux ARM64 is unsupported.", + ), + Err(ReleaseDecisionError::InvalidLimitationClaim), + "U+{code_point:04X} must be rejected in the unsupported claim", + ); + assert_eq!( + DeclaredLimitation::new( + "linux_arm64", + format!("Linux ARM64 is unsupported.{character}forged_release_consequence"), + ), + Err(ReleaseDecisionError::InvalidLimitationConsequence), + "U+{code_point:04X} must be rejected in the buyer consequence", + ); + } + } +} + +#[test] +fn limitation_does_not_blanket_reject_unicode_17_whitespace() -> Result<(), ReleaseDecisionError> { + let medium_mathematical_space = '\u{205f}'; + let ideographic_space = '\u{3000}'; + + let limitation = DeclaredLimitation::new( + format!("east{ideographic_space}asia"), + format!("Support is limited{medium_mathematical_space}to the declared profile."), + )?; + + assert_eq!( + limitation.unsupported_claim(), + format!("east{ideographic_space}asia") + ); + assert_eq!( + limitation.buyer_consequence(), + format!("Support is limited{medium_mathematical_space}to the declared profile.") + ); + Ok(()) +} From 3e1dfecf6fbdf0c408933922a9483d5957362720 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:16:39 -0700 Subject: [PATCH 24/72] fix(core): enforce Unicode 17 default-ignorable policy --- crates/originweave-core/src/lib.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 62addb8ca..1f4f679e9 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1217,7 +1217,23 @@ pub mod release_acceptance { character.is_control() || matches!( code_point, - 0x00ad | 0x061c | 0x180e | 0x200b..=0x200f | 0x2028..=0x202e | 0x2060..=0x206f | 0xfeff + 0x00ad + | 0x034f + | 0x061c + | 0x115f..=0x1160 + | 0x17b4..=0x17b5 + | 0x180b..=0x180f + | 0x200b..=0x200f + | 0x2028..=0x202e + | 0x2060..=0x206f + | 0x3164 + | 0xfe00..=0xfe0f + | 0xfeff + | 0xffa0 + | 0xfff0..=0xfff8 + | 0x1bca0..=0x1bca3 + | 0x1d173..=0x1d17a + | 0xe0000..=0xe0fff ) } From 5fc1c2f30ae590e7c64c53232915f18440e8a86b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:19:15 -0700 Subject: [PATCH 25/72] docs: pin release metadata Unicode policy --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01880d34..0bb25c0f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security -- Release-acceptance limitation metadata rejects embedded controls plus bidirectional, invisible, and other ambiguous Unicode presentation characters while preserving ordinary international text, so buyer-visible narrowed claims and consequences cannot forge or visually reorder release statements. +- Release-acceptance limitation metadata rejects embedded controls, U+2028/U+2029 line and paragraph separators, and Unicode 17.0.0 `Default_Ignorable_Code_Point` characters while preserving ordinary international text, so buyer-visible narrowed claims and consequences cannot forge or invisibly alter release statements. - 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. @@ -55,7 +55,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Shortened, integer, hexadecimal, and legacy octal-looking IPv4 host spellings are rejected so the policy origin cannot diverge from Chromium host interpretation. - IPv4-mapped IPv6 is canonicalized before destination classification and pin comparison so mapped private or loopback addresses cannot bypass IPv4 policy. - The default destination policy permits only public addresses and denies unspecified, loopback, private, shared, link-local, metadata, documentation, benchmarking, multicast, broadcast, transition, and protocol-reserved destinations. -- Azure platform IP `168.63.129.16` and Amazon EKS Pod Identity endpoints `169.254.170.23` and `fd00:ec2::23` are classified as metadata or platform services before broader public, link-local, or unique-local rules. +- Azure platform IP `168.63.129.16` and Amazon EKS Pod Identity endpoints `169.254.170.23` and `fd00:ec2::23` are classified as metadata or platform services before broader address-range rules. - Resolver answers are rejected when empty or larger than 256 addresses, preventing an unbounded resolver response from entering policy state. - `localhost` may approve only loopback addresses, while literal IPv4 and IPv6 origins may approve only the exact canonical address encoded in the origin. - Resolver answers must remain a non-empty subset of the origin-bound approved address set; any newly introduced address fails closed as a possible DNS-rebinding event. From 0e9edb481d13a6779d9915b59eb7a44f928ceac1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 10:31:42 -0700 Subject: [PATCH 26/72] test(core): satisfy strict Unicode regression lint --- .../originweave-core/tests/release_acceptance_unicode17.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index 854513e06..f2bd5b020 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -1,7 +1,7 @@ use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionError}; #[test] -fn limitation_rejects_unicode_17_default_ignorable_code_points() { +fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), &'static str> { // Unicode 17.0.0 DerivedCoreProperties.txt, Default_Ignorable_Code_Point. // Endpoints plus a midpoint make every reviewed inclusive range executable evidence. let ranges = [ @@ -28,7 +28,7 @@ fn limitation_rejects_unicode_17_default_ignorable_code_points() { let midpoint = start + (end - start) / 2; for code_point in [start, midpoint, end] { let character = char::from_u32(code_point) - .expect("Unicode 17 default-ignorable range contains only scalar values"); + .ok_or("reviewed Unicode 17 default-ignorable range must contain scalar values")?; assert_eq!( DeclaredLimitation::new( @@ -48,6 +48,8 @@ fn limitation_rejects_unicode_17_default_ignorable_code_points() { ); } } + + Ok(()) } #[test] From db784dd5cdabdec0ec2079eaac21b1eb8ddcf6ea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:03:34 -0700 Subject: [PATCH 27/72] test(core): bound release limitation metadata --- .../release_acceptance_resource_bounds.rs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 crates/originweave-core/tests/release_acceptance_resource_bounds.rs diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs new file mode 100644 index 000000000..60ecdb93a --- /dev/null +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -0,0 +1,104 @@ +use originweave_core::release_acceptance::{ + BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, ReleaseDecision, + ReleaseDecisionError, MAX_DECLARED_RELEASE_LIMITATIONS, MAX_RELEASE_LIMITATION_TEXT_BYTES, + decide_release, +}; + +fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { + BenchmarkSuite::ALL + .into_iter() + .map(|suite| (suite, BenchmarkSuiteOutcome::Passed)) + .collect() +} + +#[test] +fn limitation_metadata_enforces_exact_utf8_byte_budget() -> Result<(), ReleaseDecisionError> { + let maximum_claim = "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES); + let maximum_consequence = "x".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES); + let limitation = DeclaredLimitation::new(maximum_claim.clone(), maximum_consequence.clone())?; + + assert_eq!(limitation.unsupported_claim(), maximum_claim.as_str()); + assert_eq!( + limitation.buyer_consequence(), + maximum_consequence.as_str() + ); + assert_eq!( + DeclaredLimitation::new( + "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1), + "bounded buyer consequence" + ), + Err(ReleaseDecisionError::LimitationClaimTooLong) + ); + assert_eq!( + DeclaredLimitation::new( + "bounded_claim", + "x".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1) + ), + Err(ReleaseDecisionError::LimitationConsequenceTooLong) + ); + Ok(()) +} + +#[test] +fn limitation_byte_budget_applies_to_international_text() { + let korean_character = "가"; + let repeated = korean_character.repeat( + MAX_RELEASE_LIMITATION_TEXT_BYTES / korean_character.len() + 1, + ); + assert!(repeated.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES); + assert_eq!( + DeclaredLimitation::new(repeated, "지원 범위를 설명하는 구매자 안내"), + Err(ReleaseDecisionError::LimitationClaimTooLong) + ); +} + +#[test] +fn release_report_bounds_declared_limitation_count_before_cloning() +-> Result<(), ReleaseDecisionError> { + let limitation = DeclaredLimitation::new( + "linux_arm64", + "Linux ARM64 is outside the declared support profile.", + )?; + let maximum = vec![limitation.clone(); MAX_DECLARED_RELEASE_LIMITATIONS]; + let report = decide_release(passing_results(), &maximum)?; + + assert_eq!( + report.decision(), + ReleaseDecision::AcceptedWithDeclaredLimitations + ); + assert_eq!( + report.declared_limitations().len(), + MAX_DECLARED_RELEASE_LIMITATIONS + ); + + let too_many = vec![limitation; MAX_DECLARED_RELEASE_LIMITATIONS + 1]; + assert_eq!( + decide_release(passing_results(), &too_many), + Err(ReleaseDecisionError::TooManyDeclaredLimitations) + ); + Ok(()) +} + +#[test] +fn release_resource_limit_errors_have_deterministic_standard_error_contracts() { + let cases = [ + ( + ReleaseDecisionError::LimitationClaimTooLong, + "declared release limitation claim exceeds the byte budget", + ), + ( + ReleaseDecisionError::LimitationConsequenceTooLong, + "declared release limitation consequence exceeds the byte budget", + ), + ( + ReleaseDecisionError::TooManyDeclaredLimitations, + "benchmark release decision contains too many declared limitations", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + let standard_error: &dyn std::error::Error = &error; + assert!(standard_error.source().is_none()); + } +} From 6cefbfc36a8a5c1fe19048910b18ffb6b1a18f88 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:05:00 -0700 Subject: [PATCH 28/72] test(core): format release limitation bounds regression --- .../tests/release_acceptance_resource_bounds.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 60ecdb93a..94729086a 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -1,7 +1,6 @@ use originweave_core::release_acceptance::{ - BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, ReleaseDecision, - ReleaseDecisionError, MAX_DECLARED_RELEASE_LIMITATIONS, MAX_RELEASE_LIMITATION_TEXT_BYTES, - decide_release, + BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, MAX_DECLARED_RELEASE_LIMITATIONS, + MAX_RELEASE_LIMITATION_TEXT_BYTES, ReleaseDecision, ReleaseDecisionError, decide_release, }; fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { @@ -18,10 +17,7 @@ fn limitation_metadata_enforces_exact_utf8_byte_budget() -> Result<(), ReleaseDe let limitation = DeclaredLimitation::new(maximum_claim.clone(), maximum_consequence.clone())?; assert_eq!(limitation.unsupported_claim(), maximum_claim.as_str()); - assert_eq!( - limitation.buyer_consequence(), - maximum_consequence.as_str() - ); + assert_eq!(limitation.buyer_consequence(), maximum_consequence.as_str()); assert_eq!( DeclaredLimitation::new( "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1), @@ -42,9 +38,8 @@ fn limitation_metadata_enforces_exact_utf8_byte_budget() -> Result<(), ReleaseDe #[test] fn limitation_byte_budget_applies_to_international_text() { let korean_character = "가"; - let repeated = korean_character.repeat( - MAX_RELEASE_LIMITATION_TEXT_BYTES / korean_character.len() + 1, - ); + let repeated = + korean_character.repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES / korean_character.len() + 1); assert!(repeated.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES); assert_eq!( DeclaredLimitation::new(repeated, "지원 범위를 설명하는 구매자 안내"), From 540ae25add8153354ec52499ca90a390378f5c9d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:11:10 -0700 Subject: [PATCH 29/72] fix(core): bound release limitation resources --- crates/originweave-core/src/lib.rs | 50 ++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index 1f4f679e9..efa4aac5d 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1094,6 +1094,12 @@ pub fn evaluate_extension_access( pub mod release_acceptance { use std::fmt; + /// Maximum UTF-8 byte length retained for either buyer-visible limitation field. + pub const MAX_RELEASE_LIMITATION_TEXT_BYTES: usize = 1024; + + /// Maximum number of buyer-visible limitations retained in one release report. + pub const MAX_DECLARED_RELEASE_LIMITATIONS: usize = 64; + /// One mandatory benchmark suite in the release acceptance contract. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum BenchmarkSuite { @@ -1167,8 +1173,9 @@ pub mod release_acceptance { impl DeclaredLimitation { /// Construct one explicit buyer-visible release limitation. /// - /// Empty/whitespace-only values and ambiguous presentation characters fail closed because - /// they cannot safely represent one unambiguous buyer-visible release limitation. + /// Empty/whitespace-only values, fields exceeding the fixed UTF-8 byte budget, + /// and ambiguous presentation characters fail closed because they cannot safely + /// represent one unambiguous, resource-bounded buyer-visible release limitation. pub fn new( unsupported_claim: impl Into, buyer_consequence: impl Into, @@ -1177,6 +1184,9 @@ pub mod release_acceptance { if unsupported_claim.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationClaim); } + if unsupported_claim.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { + return Err(ReleaseDecisionError::LimitationClaimTooLong); + } if unsupported_claim .chars() .any(disallowed_release_limitation_character) @@ -1187,6 +1197,9 @@ pub mod release_acceptance { if buyer_consequence.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationConsequence); } + if buyer_consequence.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { + return Err(ReleaseDecisionError::LimitationConsequenceTooLong); + } if buyer_consequence .chars() .any(disallowed_release_limitation_character) @@ -1255,12 +1268,18 @@ pub mod release_acceptance { pub enum ReleaseDecisionError { /// A declared limitation did not identify the unsupported release claim. EmptyLimitationClaim, + /// A declared limitation claim exceeded the fixed UTF-8 byte budget. + LimitationClaimTooLong, /// A declared limitation claim contained an unsafe presentation character. InvalidLimitationClaim, /// A declared limitation did not state the buyer-visible consequence. EmptyLimitationConsequence, + /// A declared limitation consequence exceeded the fixed UTF-8 byte budget. + LimitationConsequenceTooLong, /// A declared limitation consequence contained an unsafe presentation character. InvalidLimitationConsequence, + /// One release report supplied more buyer-visible limitations than the fixed resource budget. + TooManyDeclaredLimitations, /// The same suite appeared more than once instead of one authoritative result. DuplicateSuite(BenchmarkSuite), } @@ -1270,15 +1289,23 @@ pub mod release_acceptance { match self { Self::EmptyLimitationClaim => formatter .write_str("declared release limitation must name an unsupported claim"), + Self::LimitationClaimTooLong => formatter + .write_str("declared release limitation claim exceeds the byte budget"), Self::InvalidLimitationClaim => formatter.write_str( "declared release limitation claim contains an unsafe presentation character", ), Self::EmptyLimitationConsequence => formatter.write_str( "declared release limitation must state a buyer-visible consequence", ), + Self::LimitationConsequenceTooLong => formatter.write_str( + "declared release limitation consequence exceeds the byte budget", + ), Self::InvalidLimitationConsequence => formatter.write_str( "declared release limitation consequence contains an unsafe presentation character", ), + Self::TooManyDeclaredLimitations => formatter.write_str( + "benchmark release decision contains too many declared limitations", + ), Self::DuplicateSuite(suite) => write!( formatter, "benchmark release evidence contains duplicate suite: {}", @@ -1334,13 +1361,14 @@ pub mod release_acceptance { /// Produce one deterministic release decision from mandatory suite outcomes. /// - /// Duplicate suite evidence fails closed rather than selecting an arbitrary - /// result. A known mandatory-threshold failure is always rejected, even when - /// other suites are missing or inconclusive; all such evidence gaps remain in - /// the returned report. Without a known failure, missing or inconclusive - /// evidence is never promoted to acceptance. Accepted-with-limitations requires - /// at least one validated [`DeclaredLimitation`], so the decision cannot be - /// detached from the exact narrowed claim and buyer-visible consequence. + /// Duplicate suite evidence and excessive declared-limitation cardinality fail + /// closed rather than selecting or retaining an attacker-controlled unbounded set. + /// A known mandatory-threshold failure is always rejected, even when other suites + /// are missing or inconclusive; all such evidence gaps remain in the returned + /// report. Without a known failure, missing or inconclusive evidence is never + /// promoted to acceptance. Accepted-with-limitations requires at least one + /// validated [`DeclaredLimitation`], so the decision cannot be detached from the + /// exact narrowed claim and buyer-visible consequence. pub fn decide_release( results: I, declared_limitations: &[DeclaredLimitation], @@ -1348,6 +1376,10 @@ pub mod release_acceptance { where I: IntoIterator, { + if declared_limitations.len() > MAX_DECLARED_RELEASE_LIMITATIONS { + return Err(ReleaseDecisionError::TooManyDeclaredLimitations); + } + let mut outcomes = [None; BenchmarkSuite::ALL.len()]; for (suite, outcome) in results { let slot = &mut outcomes[suite.index()]; From 913195138585c865f21ff7a7c0b5fe3ac13cafc5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:13:31 -0700 Subject: [PATCH 30/72] docs: record release limitation resource bounds --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb25c0f4..ba05d3e52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Changed +- Bounded each buyer-visible release-limitation field to 1,024 UTF-8 bytes and each release report to 64 declared limitations, rejecting oversize metadata and excessive cardinality before the report clones retained limitation state. - Separated logical origin authority from resolved network destination authority; an origin grant no longer implies permission to connect to every resolver result. - Separated resolved-address authorization from direct transport evidence; an approved IP now becomes a usable stream only after the operating system reports the exact requested IP and port. - Separated exact TCP peer proof from authenticated TLS service identity; an observed peer becomes an authenticated HTTPS stream only after explicit-root, fixed-time, SAN-bound WebPKI verification over that same stream. From 62ef5ef27e6c46d04d37c7dc5188d8d6e233ff79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:24:33 -0700 Subject: [PATCH 31/72] test(core): cover release decision vector branches --- .../release_acceptance_resource_bounds.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 94729086a..3bd6b4f5d 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -74,6 +74,56 @@ fn release_report_bounds_declared_limitation_count_before_cloning() Ok(()) } +#[test] +fn resource_bounds_vector_iterator_preserves_every_release_decision_branch() +-> Result<(), ReleaseDecisionError> { + assert_eq!( + decide_release(passing_results(), &[])?.decision(), + ReleaseDecision::Accepted + ); + + let mut failed = passing_results(); + failed[0].1 = BenchmarkSuiteOutcome::Failed; + assert_eq!( + decide_release(failed, &[])?.decision(), + ReleaseDecision::Rejected + ); + + let mut inconclusive = passing_results(); + inconclusive[0].1 = BenchmarkSuiteOutcome::Inconclusive; + assert_eq!( + decide_release(inconclusive, &[])?.decision(), + ReleaseDecision::Inconclusive + ); + + let mut missing = passing_results(); + assert!(missing.pop().is_some()); + assert_eq!( + decide_release(missing, &[])?.decision(), + ReleaseDecision::Inconclusive + ); + + assert_eq!( + decide_release( + vec![ + ( + BenchmarkSuite::ControlledDeterministic, + BenchmarkSuiteOutcome::Passed, + ), + ( + BenchmarkSuite::ControlledDeterministic, + BenchmarkSuiteOutcome::Passed, + ), + ], + &[], + ), + Err(ReleaseDecisionError::DuplicateSuite( + BenchmarkSuite::ControlledDeterministic + )) + ); + Ok(()) +} + #[test] fn release_resource_limit_errors_have_deterministic_standard_error_contracts() { let cases = [ From b31ed013713d4d709ee1ecb4f499d229fc3da642 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:30:58 -0700 Subject: [PATCH 32/72] test(core): align release bound inputs for exact branch evidence --- .../release_acceptance_resource_bounds.rs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 3bd6b4f5d..373f76562 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -14,22 +14,23 @@ fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { fn limitation_metadata_enforces_exact_utf8_byte_budget() -> Result<(), ReleaseDecisionError> { let maximum_claim = "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES); let maximum_consequence = "x".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES); - let limitation = DeclaredLimitation::new(maximum_claim.clone(), maximum_consequence.clone())?; + let limitation = DeclaredLimitation::new( + maximum_claim.as_str(), + maximum_consequence.as_str(), + )?; assert_eq!(limitation.unsupported_claim(), maximum_claim.as_str()); assert_eq!(limitation.buyer_consequence(), maximum_consequence.as_str()); + + let oversized_claim = "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1); assert_eq!( - DeclaredLimitation::new( - "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1), - "bounded buyer consequence" - ), + DeclaredLimitation::new(oversized_claim.as_str(), "bounded buyer consequence"), Err(ReleaseDecisionError::LimitationClaimTooLong) ); + + let oversized_consequence = "x".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1); assert_eq!( - DeclaredLimitation::new( - "bounded_claim", - "x".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES + 1) - ), + DeclaredLimitation::new("bounded_claim", oversized_consequence.as_str()), Err(ReleaseDecisionError::LimitationConsequenceTooLong) ); Ok(()) @@ -42,7 +43,7 @@ fn limitation_byte_budget_applies_to_international_text() { korean_character.repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES / korean_character.len() + 1); assert!(repeated.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES); assert_eq!( - DeclaredLimitation::new(repeated, "지원 범위를 설명하는 구매자 안내"), + DeclaredLimitation::new(repeated.as_str(), "지원 범위를 설명하는 구매자 안내"), Err(ReleaseDecisionError::LimitationClaimTooLong) ); } From 89745f2e72a26e51485ea50076438d36ae6b0325 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:35:55 -0700 Subject: [PATCH 33/72] test(core): consolidate release decision coverage owner --- .../tests/release_acceptance.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index a2da1c666..0b6f0cd95 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -313,3 +313,33 @@ fn decision_is_independent_of_evidence_input_order() { decide_release(passing_results(), &[]) ); } + +#[test] +fn release_report_bounds_declared_limitation_count_before_cloning() +-> Result<(), ReleaseDecisionError> { + let limitation = declared_limitation()?; + let maximum = vec![ + limitation.clone(); + originweave_core::release_acceptance::MAX_DECLARED_RELEASE_LIMITATIONS + ]; + let report = decide_release(passing_results(), &maximum)?; + + assert_eq!( + report.decision(), + ReleaseDecision::AcceptedWithDeclaredLimitations + ); + assert_eq!( + report.declared_limitations().len(), + originweave_core::release_acceptance::MAX_DECLARED_RELEASE_LIMITATIONS + ); + + let too_many = vec![ + limitation; + originweave_core::release_acceptance::MAX_DECLARED_RELEASE_LIMITATIONS + 1 + ]; + assert_eq!( + decide_release(passing_results(), &too_many), + Err(ReleaseDecisionError::TooManyDeclaredLimitations) + ); + Ok(()) +} From 52fcda0b69f70077da9e74ff56d57cd8c97f6d3c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:36:11 -0700 Subject: [PATCH 34/72] test(core): remove duplicate release decision monomorph --- .../release_acceptance_resource_bounds.rs | 92 +------------------ 1 file changed, 2 insertions(+), 90 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 373f76562..e4d655557 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -1,23 +1,12 @@ use originweave_core::release_acceptance::{ - BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, MAX_DECLARED_RELEASE_LIMITATIONS, - MAX_RELEASE_LIMITATION_TEXT_BYTES, ReleaseDecision, ReleaseDecisionError, decide_release, + DeclaredLimitation, MAX_RELEASE_LIMITATION_TEXT_BYTES, ReleaseDecisionError, }; -fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { - BenchmarkSuite::ALL - .into_iter() - .map(|suite| (suite, BenchmarkSuiteOutcome::Passed)) - .collect() -} - #[test] fn limitation_metadata_enforces_exact_utf8_byte_budget() -> Result<(), ReleaseDecisionError> { let maximum_claim = "c".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES); let maximum_consequence = "x".repeat(MAX_RELEASE_LIMITATION_TEXT_BYTES); - let limitation = DeclaredLimitation::new( - maximum_claim.as_str(), - maximum_consequence.as_str(), - )?; + let limitation = DeclaredLimitation::new(maximum_claim.as_str(), maximum_consequence.as_str())?; assert_eq!(limitation.unsupported_claim(), maximum_claim.as_str()); assert_eq!(limitation.buyer_consequence(), maximum_consequence.as_str()); @@ -48,83 +37,6 @@ fn limitation_byte_budget_applies_to_international_text() { ); } -#[test] -fn release_report_bounds_declared_limitation_count_before_cloning() --> Result<(), ReleaseDecisionError> { - let limitation = DeclaredLimitation::new( - "linux_arm64", - "Linux ARM64 is outside the declared support profile.", - )?; - let maximum = vec![limitation.clone(); MAX_DECLARED_RELEASE_LIMITATIONS]; - let report = decide_release(passing_results(), &maximum)?; - - assert_eq!( - report.decision(), - ReleaseDecision::AcceptedWithDeclaredLimitations - ); - assert_eq!( - report.declared_limitations().len(), - MAX_DECLARED_RELEASE_LIMITATIONS - ); - - let too_many = vec![limitation; MAX_DECLARED_RELEASE_LIMITATIONS + 1]; - assert_eq!( - decide_release(passing_results(), &too_many), - Err(ReleaseDecisionError::TooManyDeclaredLimitations) - ); - Ok(()) -} - -#[test] -fn resource_bounds_vector_iterator_preserves_every_release_decision_branch() --> Result<(), ReleaseDecisionError> { - assert_eq!( - decide_release(passing_results(), &[])?.decision(), - ReleaseDecision::Accepted - ); - - let mut failed = passing_results(); - failed[0].1 = BenchmarkSuiteOutcome::Failed; - assert_eq!( - decide_release(failed, &[])?.decision(), - ReleaseDecision::Rejected - ); - - let mut inconclusive = passing_results(); - inconclusive[0].1 = BenchmarkSuiteOutcome::Inconclusive; - assert_eq!( - decide_release(inconclusive, &[])?.decision(), - ReleaseDecision::Inconclusive - ); - - let mut missing = passing_results(); - assert!(missing.pop().is_some()); - assert_eq!( - decide_release(missing, &[])?.decision(), - ReleaseDecision::Inconclusive - ); - - assert_eq!( - decide_release( - vec![ - ( - BenchmarkSuite::ControlledDeterministic, - BenchmarkSuiteOutcome::Passed, - ), - ( - BenchmarkSuite::ControlledDeterministic, - BenchmarkSuiteOutcome::Passed, - ), - ], - &[], - ), - Err(ReleaseDecisionError::DuplicateSuite( - BenchmarkSuite::ControlledDeterministic - )) - ); - Ok(()) -} - #[test] fn release_resource_limit_errors_have_deterministic_standard_error_contracts() { let cases = [ From 71c18e7d3713cfea436e2bc960e130cff155ea70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 11:40:12 -0700 Subject: [PATCH 35/72] test(core): unify release decision iterator coverage --- crates/originweave-core/tests/release_acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 0b6f0cd95..0f2422cad 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -231,7 +231,7 @@ fn any_known_threshold_failure_rejects_release_and_identifies_the_suite() fn known_failure_remains_rejected_when_other_evidence_is_incomplete() -> Result<(), ReleaseDecisionError> { let report = decide_release( - [ + vec![ ( BenchmarkSuite::ControlledDeterministic, BenchmarkSuiteOutcome::Failed, @@ -270,7 +270,7 @@ fn duplicate_suite_evidence_fails_closed_instead_of_overwriting_results() { let expected_error = ReleaseDecisionError::DuplicateSuite(duplicate_suite); assert_eq!( decide_release( - [ + vec![ (duplicate_suite, BenchmarkSuiteOutcome::Passed), (duplicate_suite, BenchmarkSuiteOutcome::Failed), ], From 73bca937423311867fb9de31a797121063f077bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 12:32:50 -0700 Subject: [PATCH 36/72] test(core): cover borrowed limitation validation exits --- .../release_acceptance_resource_bounds.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index e4d655557..489f2230b 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -25,6 +25,26 @@ fn limitation_metadata_enforces_exact_utf8_byte_budget() -> Result<(), ReleaseDe Ok(()) } +#[test] +fn borrowed_limitation_text_covers_every_validation_exit() { + assert_eq!( + DeclaredLimitation::new("", "bounded buyer consequence"), + Err(ReleaseDecisionError::EmptyLimitationClaim) + ); + assert_eq!( + DeclaredLimitation::new("bounded_claim", ""), + Err(ReleaseDecisionError::EmptyLimitationConsequence) + ); + assert_eq!( + DeclaredLimitation::new("forged\nclaim", "bounded buyer consequence"), + Err(ReleaseDecisionError::InvalidLimitationClaim) + ); + assert_eq!( + DeclaredLimitation::new("bounded_claim", "forged\nconsequence"), + Err(ReleaseDecisionError::InvalidLimitationConsequence) + ); +} + #[test] fn limitation_byte_budget_applies_to_international_text() { let korean_character = "가"; From 6fc9a9f8d48f3ce31f8e8bac1700a4f30c04f71d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 12:38:10 -0700 Subject: [PATCH 37/72] test(core): exhaust Unicode 17 ignorable ranges --- .../tests/release_acceptance_unicode17.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index f2bd5b020..71f8a99cb 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -1,9 +1,11 @@ use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionError}; +const UNICODE_17_DEFAULT_IGNORABLE_CODE_POINT_COUNT: usize = 4_174; + #[test] fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), &'static str> { - // Unicode 17.0.0 DerivedCoreProperties.txt, Default_Ignorable_Code_Point. - // Endpoints plus a midpoint make every reviewed inclusive range executable evidence. + // Unicode 17.0.0 DerivedCoreProperties.txt (2025-07-30), + // Default_Ignorable_Code_Point. The reviewed ranges contain exactly 4,174 code points. let ranges = [ (0x00ad_u32, 0x00ad_u32), (0x034f, 0x034f), @@ -23,12 +25,13 @@ fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), & (0x1d173, 0x1d17a), (0xe0000, 0xe0fff), ]; + let mut tested_code_points = 0_usize; for (start, end) in ranges { - let midpoint = start + (end - start) / 2; - for code_point in [start, midpoint, end] { + for code_point in start..=end { let character = char::from_u32(code_point) .ok_or("reviewed Unicode 17 default-ignorable range must contain scalar values")?; + tested_code_points += 1; assert_eq!( DeclaredLimitation::new( @@ -49,6 +52,10 @@ fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), & } } + assert_eq!( + tested_code_points, UNICODE_17_DEFAULT_IGNORABLE_CODE_POINT_COUNT, + "reviewed Unicode 17 Default_Ignorable_Code_Point ranges must match the authoritative cardinality", + ); Ok(()) } From eae0eec77f343ecdf6413247da515a48583e9140 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 14:14:01 -0700 Subject: [PATCH 38/72] docs: clarify conservative Unicode release metadata --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba05d3e52..2008a60c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Security -- Release-acceptance limitation metadata rejects embedded controls, U+2028/U+2029 line and paragraph separators, and Unicode 17.0.0 `Default_Ignorable_Code_Point` characters while preserving ordinary international text, so buyer-visible narrowed claims and consequences cannot forge or invisibly alter release statements. +- Release-acceptance limitation metadata rejects embedded controls, U+2028/U+2029 line and paragraph separators, and Unicode 17.0.0 `Default_Ignorable_Code_Point` characters as a deliberately conservative high-assurance metadata profile. This prevents invisible or presentation-dependent release claims, but intentionally does not promise unrestricted natural-language typography: Unicode 17 documents legitimate orthographic uses for U+200C ZWNJ and U+200D ZWJ, so text requiring join controls is outside this bounded metadata profile. - 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. @@ -77,4 +77,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file From 955d24b8a3eca9819e219932dfad3aaece61707f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 17:08:05 -0700 Subject: [PATCH 39/72] test(core): reject conflicting release limitation claims --- .../tests/release_acceptance.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 0f2422cad..957d7ca49 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -314,6 +314,25 @@ fn decision_is_independent_of_evidence_input_order() { ); } +#[test] +fn conflicting_consequences_for_one_limitation_claim_fail_closed() +-> Result<(), ReleaseDecisionError> { + let first = DeclaredLimitation::new( + "linux_arm64", + "Linux ARM64 is excluded from the support profile.", + )?; + let conflicting = DeclaredLimitation::new( + "linux_arm64", + "Linux ARM64 is supported only for evaluation deployments.", + )?; + + assert!( + decide_release(passing_results(), &[first, conflicting]).is_err(), + "one unsupported claim must not retain contradictory buyer consequences" + ); + Ok(()) +} + #[test] fn release_report_bounds_declared_limitation_count_before_cloning() -> Result<(), ReleaseDecisionError> { From b71d6ba3948b799155d177874ab00fa8e2a9d526 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 17:12:30 -0700 Subject: [PATCH 40/72] fix(core): reject duplicate release limitation claims --- crates/originweave-core/src/lib.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/originweave-core/src/lib.rs b/crates/originweave-core/src/lib.rs index efa4aac5d..a208546b6 100644 --- a/crates/originweave-core/src/lib.rs +++ b/crates/originweave-core/src/lib.rs @@ -1280,6 +1280,8 @@ pub mod release_acceptance { InvalidLimitationConsequence, /// One release report supplied more buyer-visible limitations than the fixed resource budget. TooManyDeclaredLimitations, + /// More than one limitation used the same unsupported claim identity. + DuplicateLimitationClaim, /// The same suite appeared more than once instead of one authoritative result. DuplicateSuite(BenchmarkSuite), } @@ -1306,6 +1308,9 @@ pub mod release_acceptance { Self::TooManyDeclaredLimitations => formatter.write_str( "benchmark release decision contains too many declared limitations", ), + Self::DuplicateLimitationClaim => formatter.write_str( + "benchmark release decision contains duplicate limitation claim", + ), Self::DuplicateSuite(suite) => write!( formatter, "benchmark release evidence contains duplicate suite: {}", @@ -1361,14 +1366,15 @@ pub mod release_acceptance { /// Produce one deterministic release decision from mandatory suite outcomes. /// - /// Duplicate suite evidence and excessive declared-limitation cardinality fail - /// closed rather than selecting or retaining an attacker-controlled unbounded set. - /// A known mandatory-threshold failure is always rejected, even when other suites - /// are missing or inconclusive; all such evidence gaps remain in the returned - /// report. Without a known failure, missing or inconclusive evidence is never - /// promoted to acceptance. Accepted-with-limitations requires at least one - /// validated [`DeclaredLimitation`], so the decision cannot be detached from the - /// exact narrowed claim and buyer-visible consequence. + /// Duplicate suite evidence, duplicate buyer-visible limitation claim identities, + /// and excessive declared-limitation cardinality fail closed rather than selecting + /// or retaining ambiguous or attacker-controlled release metadata. A known + /// mandatory-threshold failure is always rejected, even when other suites are + /// missing or inconclusive; all such evidence gaps remain in the returned report. + /// Without a known failure, missing or inconclusive evidence is never promoted to + /// acceptance. Accepted-with-limitations requires at least one validated + /// [`DeclaredLimitation`], so the decision cannot be detached from the exact + /// narrowed claim and buyer-visible consequence. pub fn decide_release( results: I, declared_limitations: &[DeclaredLimitation], @@ -1380,6 +1386,13 @@ pub mod release_acceptance { return Err(ReleaseDecisionError::TooManyDeclaredLimitations); } + let mut limitation_claims = std::collections::BTreeSet::new(); + for limitation in declared_limitations { + if !limitation_claims.insert(limitation.unsupported_claim()) { + return Err(ReleaseDecisionError::DuplicateLimitationClaim); + } + } + let mut outcomes = [None; BenchmarkSuite::ALL.len()]; for (suite, outcome) in results { let slot = &mut outcomes[suite.index()]; From 5c0478985edccdda33851903282145056ddc2ba6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 17:14:25 -0700 Subject: [PATCH 41/72] test(core): pin duplicate limitation identity contract --- .../tests/release_acceptance.rs | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 957d7ca49..dbac815d1 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -1,6 +1,6 @@ use originweave_core::release_acceptance::{ - BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, ReleaseDecision, - ReleaseDecisionError, decide_release, + BenchmarkSuite, BenchmarkSuiteOutcome, DeclaredLimitation, MAX_DECLARED_RELEASE_LIMITATIONS, + ReleaseDecision, ReleaseDecisionError, decide_release, }; fn passing_results() -> Vec<(BenchmarkSuite, BenchmarkSuiteOutcome)> { @@ -138,6 +138,10 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { ReleaseDecisionError::InvalidLimitationConsequence, "declared release limitation consequence contains an unsafe presentation character", ), + ( + ReleaseDecisionError::DuplicateLimitationClaim, + "benchmark release decision contains duplicate limitation claim", + ), ]; for (error, expected_message) in cases { @@ -326,21 +330,39 @@ fn conflicting_consequences_for_one_limitation_claim_fail_closed() "Linux ARM64 is supported only for evaluation deployments.", )?; - assert!( - decide_release(passing_results(), &[first, conflicting]).is_err(), - "one unsupported claim must not retain contradictory buyer consequences" + assert_eq!( + decide_release(passing_results(), &[first, conflicting]), + Err(ReleaseDecisionError::DuplicateLimitationClaim) ); Ok(()) } #[test] -fn release_report_bounds_declared_limitation_count_before_cloning() +fn duplicate_limitation_claim_fails_closed_even_when_consequence_matches() -> Result<(), ReleaseDecisionError> { let limitation = declared_limitation()?; - let maximum = vec![ - limitation.clone(); - originweave_core::release_acceptance::MAX_DECLARED_RELEASE_LIMITATIONS - ]; + + assert_eq!( + decide_release( + passing_results(), + &[limitation.clone(), limitation], + ), + Err(ReleaseDecisionError::DuplicateLimitationClaim) + ); + Ok(()) +} + +#[test] +fn release_report_bounds_declared_limitation_count_before_cloning() +-> Result<(), ReleaseDecisionError> { + let maximum = (0..MAX_DECLARED_RELEASE_LIMITATIONS) + .map(|index| { + DeclaredLimitation::new( + format!("unsupported_profile_{index}"), + "This profile is excluded from the declared support profile.", + ) + }) + .collect::, _>>()?; let report = decide_release(passing_results(), &maximum)?; assert_eq!( @@ -349,13 +371,17 @@ fn release_report_bounds_declared_limitation_count_before_cloning() ); assert_eq!( report.declared_limitations().len(), - originweave_core::release_acceptance::MAX_DECLARED_RELEASE_LIMITATIONS + MAX_DECLARED_RELEASE_LIMITATIONS ); - let too_many = vec![ - limitation; - originweave_core::release_acceptance::MAX_DECLARED_RELEASE_LIMITATIONS + 1 - ]; + let too_many = (0..=MAX_DECLARED_RELEASE_LIMITATIONS) + .map(|index| { + DeclaredLimitation::new( + format!("unsupported_profile_{index}"), + "This profile is excluded from the declared support profile.", + ) + }) + .collect::, _>>()?; assert_eq!( decide_release(passing_results(), &too_many), Err(ReleaseDecisionError::TooManyDeclaredLimitations) From 49e98fba6974219b3bb0336c822b12667f1e1c03 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 17:17:54 -0700 Subject: [PATCH 42/72] test(core): apply canonical rustfmt to duplicate limitation regression --- crates/originweave-core/tests/release_acceptance.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index dbac815d1..dd4f5501f 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -343,10 +343,7 @@ fn duplicate_limitation_claim_fails_closed_even_when_consequence_matches() let limitation = declared_limitation()?; assert_eq!( - decide_release( - passing_results(), - &[limitation.clone(), limitation], - ), + decide_release(passing_results(), &[limitation.clone(), limitation],), Err(ReleaseDecisionError::DuplicateLimitationClaim) ); Ok(()) From 1f4793604990c5251c76c6972db69d0181e9e928 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 05:26:12 -0700 Subject: [PATCH 43/72] feat(core): restore release acceptance contract on current module layout --- .../src/release_acceptance.rs | 344 ++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 crates/originweave-core/src/release_acceptance.rs diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs new file mode 100644 index 000000000..b0d23a228 --- /dev/null +++ b/crates/originweave-core/src/release_acceptance.rs @@ -0,0 +1,344 @@ +//! Deterministic fail-closed release acceptance for commercial benchmark evidence. +//! +//! This module aggregates only explicit mandatory-suite outcomes and bounded, +//! buyer-visible limitations. It does not execute benchmarks, infer missing +//! evidence, authenticate artifacts, or grant release authority. + +use std::fmt; + +/// Maximum UTF-8 byte length retained for either buyer-visible limitation field. +pub const MAX_RELEASE_LIMITATION_TEXT_BYTES: usize = 1024; + +/// Maximum number of buyer-visible limitations retained in one release report. +pub const MAX_DECLARED_RELEASE_LIMITATIONS: usize = 64; + +/// One mandatory benchmark suite in the release acceptance contract. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum BenchmarkSuite { + /// Controlled local fixtures with deterministic post-condition oracles. + ControlledDeterministic, + /// Stable web compatibility tasks for the declared support profile. + WebCompatibility, + /// Hostile security cases that measure unauthorized authority or disclosure. + SecurityAdversarial, + /// Crash, timeout, retry, reconciliation, cleanup, and restore behavior. + ReliabilityRecovery, + /// Enterprise isolation, identity, policy, audit, and operator controls. + EnterpriseOperability, +} + +impl BenchmarkSuite { + /// Every mandatory benchmark suite in canonical release-report order. + pub const ALL: [Self; 5] = [ + Self::ControlledDeterministic, + Self::WebCompatibility, + Self::SecurityAdversarial, + Self::ReliabilityRecovery, + Self::EnterpriseOperability, + ]; + + /// Return the stable snake-case suite identifier used by benchmark evidence. + #[must_use] + pub const fn as_str(self) -> &'static str { + match self { + Self::ControlledDeterministic => "controlled_deterministic_suite", + Self::WebCompatibility => "web_compatibility_suite", + Self::SecurityAdversarial => "security_adversarial_suite", + Self::ReliabilityRecovery => "reliability_recovery_suite", + Self::EnterpriseOperability => "enterprise_operability_suite", + } + } + + const fn index(self) -> usize { + match self { + Self::ControlledDeterministic => 0, + Self::WebCompatibility => 1, + Self::SecurityAdversarial => 2, + Self::ReliabilityRecovery => 3, + Self::EnterpriseOperability => 4, + } + } +} + +/// Evaluated outcome for one mandatory benchmark suite. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BenchmarkSuiteOutcome { + /// Every threshold required for the declared profile passed. + Passed, + /// At least one mandatory threshold is known to have failed. + Failed, + /// Evidence is insufficient to establish either pass or threshold failure. + Inconclusive, +} + +/// One explicit narrowed release claim and its buyer-visible consequence. +/// +/// An accepted-with-limitations decision cannot be produced from an opaque +/// boolean. Every limitation must name the unsupported claim and state the +/// consequence that a buyer must account for in the declared support profile. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DeclaredLimitation { + unsupported_claim: String, + buyer_consequence: String, +} + +impl DeclaredLimitation { + /// Construct one explicit buyer-visible release limitation. + /// + /// Empty/whitespace-only values, fields exceeding the fixed UTF-8 byte budget, + /// and ambiguous presentation characters fail closed because they cannot safely + /// represent one unambiguous, resource-bounded buyer-visible release limitation. + pub fn new( + unsupported_claim: impl Into, + buyer_consequence: impl Into, + ) -> Result { + let unsupported_claim = unsupported_claim.into(); + if unsupported_claim.trim().is_empty() { + return Err(ReleaseDecisionError::EmptyLimitationClaim); + } + if unsupported_claim.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { + return Err(ReleaseDecisionError::LimitationClaimTooLong); + } + if unsupported_claim + .chars() + .any(disallowed_release_limitation_character) + { + return Err(ReleaseDecisionError::InvalidLimitationClaim); + } + let buyer_consequence = buyer_consequence.into(); + if buyer_consequence.trim().is_empty() { + return Err(ReleaseDecisionError::EmptyLimitationConsequence); + } + if buyer_consequence.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { + return Err(ReleaseDecisionError::LimitationConsequenceTooLong); + } + if buyer_consequence + .chars() + .any(disallowed_release_limitation_character) + { + return Err(ReleaseDecisionError::InvalidLimitationConsequence); + } + Ok(Self { + unsupported_claim, + buyer_consequence, + }) + } + + /// Return the exact unsupported or narrowed release claim. + #[must_use] + pub fn unsupported_claim(&self) -> &str { + &self.unsupported_claim + } + + /// Return the exact consequence exposed to buyers and operators. + #[must_use] + pub fn buyer_consequence(&self) -> &str { + &self.buyer_consequence + } +} + +fn disallowed_release_limitation_character(character: char) -> bool { + let code_point = character as u32; + character.is_control() + || matches!( + code_point, + 0x00ad + | 0x034f + | 0x061c + | 0x115f..=0x1160 + | 0x17b4..=0x17b5 + | 0x180b..=0x180f + | 0x200b..=0x200f + | 0x2028..=0x202e + | 0x2060..=0x206f + | 0x3164 + | 0xfe00..=0xfe0f + | 0xfeff + | 0xffa0 + | 0xfff0..=0xfff8 + | 0x1bca0..=0x1bca3 + | 0x1d173..=0x1d17a + | 0xe0000..=0xe0fff + ) +} + +/// Deterministic release decision produced from mandatory suite evidence. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReleaseDecision { + /// Every mandatory suite passed for the full declared support profile. + Accepted, + /// Every mandatory suite passed after buyer-visible limitations were declared. + AcceptedWithDeclaredLimitations, + /// At least one mandatory suite is known to have failed its threshold. + Rejected, + /// No known threshold failure exists, but mandatory evidence is incomplete. + Inconclusive, +} + +/// Fail-closed input error while constructing a release decision. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReleaseDecisionError { + /// A declared limitation did not identify the unsupported release claim. + EmptyLimitationClaim, + /// A declared limitation claim exceeded the fixed UTF-8 byte budget. + LimitationClaimTooLong, + /// A declared limitation claim contained an unsafe presentation character. + InvalidLimitationClaim, + /// A declared limitation did not state the buyer-visible consequence. + EmptyLimitationConsequence, + /// A declared limitation consequence exceeded the fixed UTF-8 byte budget. + LimitationConsequenceTooLong, + /// A declared limitation consequence contained an unsafe presentation character. + InvalidLimitationConsequence, + /// One release report supplied more buyer-visible limitations than the fixed resource budget. + TooManyDeclaredLimitations, + /// More than one limitation used the same unsupported claim identity. + DuplicateLimitationClaim, + /// The same suite appeared more than once instead of one authoritative result. + DuplicateSuite(BenchmarkSuite), +} + +impl fmt::Display for ReleaseDecisionError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::EmptyLimitationClaim => formatter + .write_str("declared release limitation must name an unsupported claim"), + Self::LimitationClaimTooLong => formatter + .write_str("declared release limitation claim exceeds the byte budget"), + Self::InvalidLimitationClaim => formatter.write_str( + "declared release limitation claim contains an unsafe presentation character", + ), + Self::EmptyLimitationConsequence => formatter.write_str( + "declared release limitation must state a buyer-visible consequence", + ), + Self::LimitationConsequenceTooLong => formatter.write_str( + "declared release limitation consequence exceeds the byte budget", + ), + Self::InvalidLimitationConsequence => formatter.write_str( + "declared release limitation consequence contains an unsafe presentation character", + ), + Self::TooManyDeclaredLimitations => formatter + .write_str("benchmark release decision contains too many declared limitations"), + Self::DuplicateLimitationClaim => formatter + .write_str("benchmark release decision contains duplicate limitation claim"), + Self::DuplicateSuite(suite) => write!( + formatter, + "benchmark release evidence contains duplicate suite: {}", + suite.as_str() + ), + } + } +} + +impl std::error::Error for ReleaseDecisionError {} + +/// Release decision together with exact mandatory-suite evidence gaps and failures. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReleaseDecisionReport { + decision: ReleaseDecision, + failed_suites: Vec, + inconclusive_suites: Vec, + missing_suites: Vec, + declared_limitations: Vec, +} + +impl ReleaseDecisionReport { + /// Return the deterministic release decision. + #[must_use] + pub const fn decision(&self) -> ReleaseDecision { + self.decision + } + + /// Return suites with a known mandatory-threshold failure. + #[must_use] + pub fn failed_suites(&self) -> &[BenchmarkSuite] { + &self.failed_suites + } + + /// Return suites whose supplied evidence was explicitly inconclusive. + #[must_use] + pub fn inconclusive_suites(&self) -> &[BenchmarkSuite] { + &self.inconclusive_suites + } + + /// Return mandatory suites for which no outcome was supplied. + #[must_use] + pub fn missing_suites(&self) -> &[BenchmarkSuite] { + &self.missing_suites + } + + /// Return the exact buyer-visible limitations retained with this decision. + #[must_use] + pub fn declared_limitations(&self) -> &[DeclaredLimitation] { + &self.declared_limitations + } +} + +/// Produce one deterministic release decision from mandatory suite outcomes. +/// +/// Duplicate suite evidence, duplicate buyer-visible limitation claim identities, +/// and excessive declared-limitation cardinality fail closed rather than selecting +/// or retaining ambiguous or attacker-controlled release metadata. A known +/// mandatory-threshold failure is always rejected, even when other suites are +/// missing or inconclusive; all such evidence gaps remain in the returned report. +/// Without a known failure, missing or inconclusive evidence is never promoted to +/// acceptance. Accepted-with-limitations requires at least one validated +/// [`DeclaredLimitation`], so the decision cannot be detached from the exact +/// narrowed claim and buyer-visible consequence. +pub fn decide_release( + results: I, + declared_limitations: &[DeclaredLimitation], +) -> Result +where + I: IntoIterator, +{ + if declared_limitations.len() > MAX_DECLARED_RELEASE_LIMITATIONS { + return Err(ReleaseDecisionError::TooManyDeclaredLimitations); + } + + let mut limitation_claims = std::collections::BTreeSet::new(); + for limitation in declared_limitations { + if !limitation_claims.insert(limitation.unsupported_claim()) { + return Err(ReleaseDecisionError::DuplicateLimitationClaim); + } + } + + let mut outcomes = [None; BenchmarkSuite::ALL.len()]; + for (suite, outcome) in results { + let slot = &mut outcomes[suite.index()]; + if slot.is_some() { + return Err(ReleaseDecisionError::DuplicateSuite(suite)); + } + *slot = Some(outcome); + } + + let mut failed_suites = Vec::new(); + let mut inconclusive_suites = Vec::new(); + let mut missing_suites = Vec::new(); + for suite in BenchmarkSuite::ALL { + match outcomes[suite.index()] { + Some(BenchmarkSuiteOutcome::Passed) => {} + Some(BenchmarkSuiteOutcome::Failed) => failed_suites.push(suite), + Some(BenchmarkSuiteOutcome::Inconclusive) => inconclusive_suites.push(suite), + None => missing_suites.push(suite), + } + } + + let decision = if !failed_suites.is_empty() { + ReleaseDecision::Rejected + } else if !inconclusive_suites.is_empty() || !missing_suites.is_empty() { + ReleaseDecision::Inconclusive + } else if declared_limitations.is_empty() { + ReleaseDecision::Accepted + } else { + ReleaseDecision::AcceptedWithDeclaredLimitations + }; + + Ok(ReleaseDecisionReport { + decision, + failed_suites, + inconclusive_suites, + missing_suites, + declared_limitations: declared_limitations.to_vec(), + }) +} From b1cab20748141c3114f08c2473001d27458c47d2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 05:28:17 -0700 Subject: [PATCH 44/72] feat(core): export release acceptance contract --- crates/originweave-core/src/root.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/originweave-core/src/root.rs b/crates/originweave-core/src/root.rs index 7acced460..c47a136d4 100644 --- a/crates/originweave-core/src/root.rs +++ b/crates/originweave-core/src/root.rs @@ -13,3 +13,5 @@ pub use contracts::*; /// Stateless MCP routing validation that maps only explicit tools to typed actions. pub mod mcp; +/// Deterministic fail-closed release benchmark acceptance aggregation. +pub mod release_acceptance; From 4626c71559437ff10738d98e3a98bdb7ae7e1bf2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 05:35:14 -0700 Subject: [PATCH 45/72] style(core): apply canonical rustfmt to release acceptance --- .../src/release_acceptance.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index b0d23a228..3d3f532e2 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -201,19 +201,19 @@ pub enum ReleaseDecisionError { impl fmt::Display for ReleaseDecisionError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::EmptyLimitationClaim => formatter - .write_str("declared release limitation must name an unsupported claim"), - Self::LimitationClaimTooLong => formatter - .write_str("declared release limitation claim exceeds the byte budget"), + Self::EmptyLimitationClaim => { + formatter.write_str("declared release limitation must name an unsupported claim") + } + Self::LimitationClaimTooLong => { + formatter.write_str("declared release limitation claim exceeds the byte budget") + } Self::InvalidLimitationClaim => formatter.write_str( "declared release limitation claim contains an unsafe presentation character", ), - Self::EmptyLimitationConsequence => formatter.write_str( - "declared release limitation must state a buyer-visible consequence", - ), - Self::LimitationConsequenceTooLong => formatter.write_str( - "declared release limitation consequence exceeds the byte budget", - ), + Self::EmptyLimitationConsequence => formatter + .write_str("declared release limitation must state a buyer-visible consequence"), + Self::LimitationConsequenceTooLong => formatter + .write_str("declared release limitation consequence exceeds the byte budget"), Self::InvalidLimitationConsequence => formatter.write_str( "declared release limitation consequence contains an unsafe presentation character", ), From 28cd9deb2563985497005e9fa29bc08b974b23a0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 05:57:27 -0700 Subject: [PATCH 46/72] test(core): reject ambiguous release limitation whitespace --- .../release_acceptance_canonical_text.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 crates/originweave-core/tests/release_acceptance_canonical_text.rs diff --git a/crates/originweave-core/tests/release_acceptance_canonical_text.rs b/crates/originweave-core/tests/release_acceptance_canonical_text.rs new file mode 100644 index 000000000..a60af4c18 --- /dev/null +++ b/crates/originweave-core/tests/release_acceptance_canonical_text.rs @@ -0,0 +1,30 @@ +use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionError}; + +#[test] +fn limitation_rejects_surrounding_whitespace_that_changes_claim_identity() { + for unsupported_claim in [" linux_arm64", "linux_arm64 ", "\tlinux_arm64"] { + assert_eq!( + DeclaredLimitation::new( + unsupported_claim, + "Linux ARM64 is excluded from the support profile.", + ), + Err(ReleaseDecisionError::InvalidLimitationClaim), + "surrounding whitespace must not create a second spelling for one claim identity: {unsupported_claim:?}", + ); + } +} + +#[test] +fn limitation_rejects_surrounding_whitespace_in_buyer_consequence() { + for buyer_consequence in [ + " Linux ARM64 is excluded from the support profile.", + "Linux ARM64 is excluded from the support profile. ", + "Linux ARM64 is excluded from the support profile.\t", + ] { + assert_eq!( + DeclaredLimitation::new("linux_arm64", buyer_consequence), + Err(ReleaseDecisionError::InvalidLimitationConsequence), + "buyer-visible consequence must have one canonical boundary spelling: {buyer_consequence:?}", + ); + } +} From 2b7d24091aea43782e54bb103ac3a17d5b591173 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 06:00:55 -0700 Subject: [PATCH 47/72] fix(core): canonicalize release limitation boundaries --- crates/originweave-core/src/release_acceptance.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index 3d3f532e2..bb4e12870 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -85,9 +85,10 @@ pub struct DeclaredLimitation { impl DeclaredLimitation { /// Construct one explicit buyer-visible release limitation. /// - /// Empty/whitespace-only values, fields exceeding the fixed UTF-8 byte budget, - /// and ambiguous presentation characters fail closed because they cannot safely - /// represent one unambiguous, resource-bounded buyer-visible release limitation. + /// Empty/whitespace-only values, surrounding whitespace, fields exceeding the + /// fixed UTF-8 byte budget, and ambiguous presentation characters fail closed + /// because they cannot safely represent one canonical, resource-bounded + /// buyer-visible release limitation. pub fn new( unsupported_claim: impl Into, buyer_consequence: impl Into, @@ -96,6 +97,9 @@ impl DeclaredLimitation { if unsupported_claim.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationClaim); } + if unsupported_claim.trim() != unsupported_claim { + return Err(ReleaseDecisionError::InvalidLimitationClaim); + } if unsupported_claim.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { return Err(ReleaseDecisionError::LimitationClaimTooLong); } @@ -109,6 +113,9 @@ impl DeclaredLimitation { if buyer_consequence.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationConsequence); } + if buyer_consequence.trim() != buyer_consequence { + return Err(ReleaseDecisionError::InvalidLimitationConsequence); + } if buyer_consequence.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { return Err(ReleaseDecisionError::LimitationConsequenceTooLong); } From ef7265399e5b72a7869374098bc9d908f36e611d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 06:06:17 -0700 Subject: [PATCH 48/72] docs(changelog): record release acceptance contract --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116c02fe6..d85e084de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added +- Added `originweave_core::release_acceptance`, a deterministic fail-closed benchmark release-decision contract that requires one authoritative result for every mandatory suite, bounds explicit buyer-visible limitations, rejects duplicate limitation claim identities, and rejects non-canonical surrounding whitespace rather than normalizing it into an alternate claim spelling. - 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. @@ -89,4 +90,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file From 562ab1c44b71fcf004bbcf15ad4989a3daf65847 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 06:08:11 -0700 Subject: [PATCH 49/72] docs(doctoring): pin Unicode 17 limitation basis --- docs/doctoring.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/doctoring.md b/docs/doctoring.md index 693840f63..375b82fa9 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -24,6 +24,10 @@ RFC 6454 defines a web origin as the scheme, host, and port tuple that browsers RFC 9700 is the current Best Current Practice for OAuth 2.0 security. It requires access tokens to be restricted in lifetime and treats long-lived bearer credentials as a standing authorization risk. An OriginWeave `extension_grant` that matches extension identity, session, browsing context, and canonical origin but has no exclusive expiry remains usable after the Agent Task window ends. OriginWeave therefore requires the grant to carry an exclusive `expires_at_epoch_seconds` deadline and the request to carry trusted `now_epoch_seconds`. Evaluation fails closed when `now >= expires_at`, matching the existing sensitive-handle exclusive-expiry rule. Page, extension, and model clocks are not trusted time. This slice does not bind task identity, install an extension, or mint Agent capabilities from Manifest V3 permissions. +### Release-limitation presentation safety + +Unicode 17.0 defines `Default_Ignorable_Code_Point` in the Unicode Character Database and records the exact derived set in the versioned `DerivedCoreProperties.txt` data file. Those characters can be invisible or alter presentation without supplying an ordinary visible glyph. OriginWeave therefore treats the Unicode 17.0 derived property as a pinned presentation-safety input for buyer-visible release-limitation metadata, in addition to rejecting control characters and non-canonical leading or trailing whitespace. The admitted text is not silently normalized: accepted content retains its exact bytes, while ambiguous presentation characters and surrounding whitespace fail closed so one release claim cannot acquire multiple stored spellings. This is a bounded metadata-identity policy, not a claim of complete Unicode spoofing resistance or semantic text equivalence. + ### Resolved destination and redirect safety Canonical origin identity is not a network-destination authorization. The IANA IPv4 and IPv6 Special-Purpose Address Space registries enumerate blocks whose source, destination, forwardability, globally reachable, and protocol-reserved properties differ. Both registries were last updated on 9 October 2025 and explicitly warn that registry presence does not guarantee routability in a particular local or global context. RFC 6890 established the common special-purpose registry fields, and RFC 8190 replaced the ambiguous `global` field with `globally reachable`. @@ -174,6 +178,8 @@ The Rust Project Developers. (2026). *Ipv6Addr in std::net* (Rust 1.97.1) [Softw The Rust Project Developers. (2026). *TcpStream in std::net* (Rust 1.97.1) [Software documentation]. https://doc.rust-lang.org/stable/std/net/struct.TcpStream.html +The Unicode Consortium. (2025). *DerivedCoreProperties-17.0.0.txt* [Data file]. https://www.unicode.org/Public/17.0.0/ucd/DerivedCoreProperties.txt + Web Hypertext Application Technology Working Group. (2026). *URL standard*. https://url.spec.whatwg.org/ World Wide Web Consortium. (2013). *PROV-O: The PROV ontology*. https://www.w3.org/TR/prov-o/ From b203eb448754e62572c47d888ff48729cfb2970a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 10:22:26 -0700 Subject: [PATCH 50/72] test(release): cover canonical limitation acceptance path --- .../tests/release_acceptance_canonical_text.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_canonical_text.rs b/crates/originweave-core/tests/release_acceptance_canonical_text.rs index a60af4c18..f7edb1ce6 100644 --- a/crates/originweave-core/tests/release_acceptance_canonical_text.rs +++ b/crates/originweave-core/tests/release_acceptance_canonical_text.rs @@ -1,5 +1,20 @@ use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionError}; +#[test] +fn limitation_accepts_canonical_boundary_text() { + let limitation = DeclaredLimitation::new( + "linux_arm64", + "Linux ARM64 is excluded from the support profile.", + ) + .expect("canonical limitation text must remain accepted"); + + assert_eq!(limitation.unsupported_claim(), "linux_arm64"); + assert_eq!( + limitation.buyer_consequence(), + "Linux ARM64 is excluded from the support profile." + ); +} + #[test] fn limitation_rejects_surrounding_whitespace_that_changes_claim_identity() { for unsupported_claim in [" linux_arm64", "linux_arm64 ", "\tlinux_arm64"] { From d6673dad3a5cabadeadcb7f4e5cdddd8e989190a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 10:29:24 -0700 Subject: [PATCH 51/72] test(release): cover canonical limitation constructor branches --- .../release_acceptance_canonical_text.rs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_canonical_text.rs b/crates/originweave-core/tests/release_acceptance_canonical_text.rs index f7edb1ce6..678fa90c4 100644 --- a/crates/originweave-core/tests/release_acceptance_canonical_text.rs +++ b/crates/originweave-core/tests/release_acceptance_canonical_text.rs @@ -5,13 +5,28 @@ fn limitation_accepts_canonical_boundary_text() { let limitation = DeclaredLimitation::new( "linux_arm64", "Linux ARM64 is excluded from the support profile.", - ) - .expect("canonical limitation text must remain accepted"); + ); + + assert_eq!( + limitation + .as_ref() + .map(|value| (value.unsupported_claim(), value.buyer_consequence())), + Ok(( + "linux_arm64", + "Linux ARM64 is excluded from the support profile." + )) + ); +} - assert_eq!(limitation.unsupported_claim(), "linux_arm64"); +#[test] +fn limitation_rejects_empty_fields_for_the_canonical_string_input_shape() { + assert_eq!( + DeclaredLimitation::new("", "Linux ARM64 is excluded from the support profile."), + Err(ReleaseDecisionError::EmptyLimitationClaim), + ); assert_eq!( - limitation.buyer_consequence(), - "Linux ARM64 is excluded from the support profile." + DeclaredLimitation::new("linux_arm64", ""), + Err(ReleaseDecisionError::EmptyLimitationConsequence), ); } From 6affc40814cd8d542ae4a5bbfea0d8100851bf74 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 10:35:44 -0700 Subject: [PATCH 52/72] test(release): close generic limitation coverage gaps --- .../tests/release_acceptance_unicode17.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index 71f8a99cb..6db557872 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -2,6 +2,18 @@ use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionEr const UNICODE_17_DEFAULT_IGNORABLE_CODE_POINT_COUNT: usize = 4_174; +#[test] +fn generic_constructor_input_shapes_cover_fail_closed_empty_boundaries() { + assert_eq!( + DeclaredLimitation::new(String::new(), "Linux ARM64 is unsupported."), + Err(ReleaseDecisionError::EmptyLimitationClaim), + ); + assert_eq!( + DeclaredLimitation::new("linux_arm64", String::new()), + Err(ReleaseDecisionError::EmptyLimitationConsequence), + ); +} + #[test] fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), &'static str> { // Unicode 17.0.0 DerivedCoreProperties.txt (2025-07-30), From bc2c1433f419a252bec90fc4088ac236ffca0e8e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 11:37:29 -0700 Subject: [PATCH 53/72] test(release): cover generic limitation success paths --- .../tests/release_acceptance_unicode17.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index 6db557872..c8cccbd38 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -8,10 +8,24 @@ fn generic_constructor_input_shapes_cover_fail_closed_empty_boundaries() { DeclaredLimitation::new(String::new(), "Linux ARM64 is unsupported."), Err(ReleaseDecisionError::EmptyLimitationClaim), ); + assert!( + DeclaredLimitation::new( + String::from("linux_arm64"), + "Linux ARM64 is unsupported." + ) + .is_ok() + ); assert_eq!( DeclaredLimitation::new("linux_arm64", String::new()), Err(ReleaseDecisionError::EmptyLimitationConsequence), ); + assert!( + DeclaredLimitation::new( + "linux_arm64", + String::from("Linux ARM64 is unsupported.") + ) + .is_ok() + ); } #[test] From a730423498782cff43e8ff2742777d4ba8f3d045 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 11:41:43 -0700 Subject: [PATCH 54/72] style(release): apply canonical rustfmt to coverage regression --- .../tests/release_acceptance_unicode17.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index c8cccbd38..faf9369e4 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -9,22 +9,14 @@ fn generic_constructor_input_shapes_cover_fail_closed_empty_boundaries() { Err(ReleaseDecisionError::EmptyLimitationClaim), ); assert!( - DeclaredLimitation::new( - String::from("linux_arm64"), - "Linux ARM64 is unsupported." - ) - .is_ok() + DeclaredLimitation::new(String::from("linux_arm64"), "Linux ARM64 is unsupported.").is_ok() ); assert_eq!( DeclaredLimitation::new("linux_arm64", String::new()), Err(ReleaseDecisionError::EmptyLimitationConsequence), ); assert!( - DeclaredLimitation::new( - "linux_arm64", - String::from("Linux ARM64 is unsupported.") - ) - .is_ok() + DeclaredLimitation::new("linux_arm64", String::from("Linux ARM64 is unsupported.")).is_ok() ); } From 51c8ee82e6fd2fa0baf9e8ad76870f3c4e375580 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 11:47:06 -0700 Subject: [PATCH 55/72] test(release): cover generic success paths in owning test crate --- crates/originweave-core/tests/release_acceptance.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index dd4f5501f..14cd2f8e4 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -17,6 +17,16 @@ fn declared_limitation() -> Result { ) } +#[test] +fn generic_constructor_input_shapes_cover_success_paths_in_this_test_crate() { + assert!( + DeclaredLimitation::new(String::from("linux_arm64"), "Linux ARM64 is unsupported.").is_ok() + ); + assert!( + DeclaredLimitation::new("linux_arm64", String::from("Linux ARM64 is unsupported.")).is_ok() + ); +} + #[test] fn complete_passing_evidence_is_accepted_without_declared_limitations() -> Result<(), ReleaseDecisionError> { From 0a9bef98dc5a1e866207d8cf3999c0defeb9a83f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 12:39:05 -0700 Subject: [PATCH 56/72] test(release): cover canonical whitespace validation exits --- .../tests/release_acceptance_resource_bounds.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 489f2230b..36664773d 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -31,10 +31,18 @@ fn borrowed_limitation_text_covers_every_validation_exit() { DeclaredLimitation::new("", "bounded buyer consequence"), Err(ReleaseDecisionError::EmptyLimitationClaim) ); + assert_eq!( + DeclaredLimitation::new(" bounded_claim", "bounded buyer consequence"), + Err(ReleaseDecisionError::InvalidLimitationClaim) + ); assert_eq!( DeclaredLimitation::new("bounded_claim", ""), Err(ReleaseDecisionError::EmptyLimitationConsequence) ); + assert_eq!( + DeclaredLimitation::new("bounded_claim", "bounded buyer consequence "), + Err(ReleaseDecisionError::InvalidLimitationConsequence) + ); assert_eq!( DeclaredLimitation::new("forged\nclaim", "bounded buyer consequence"), Err(ReleaseDecisionError::InvalidLimitationClaim) From 0744979eeb88d5524aa4602b0317eacaaa6cd9ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:34:30 -0700 Subject: [PATCH 57/72] test(core): reproduce non-NFC release limitation identity --- .../release_acceptance_canonical_text.rs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_canonical_text.rs b/crates/originweave-core/tests/release_acceptance_canonical_text.rs index 678fa90c4..7fb4c171e 100644 --- a/crates/originweave-core/tests/release_acceptance_canonical_text.rs +++ b/crates/originweave-core/tests/release_acceptance_canonical_text.rs @@ -58,3 +58,38 @@ fn limitation_rejects_surrounding_whitespace_in_buyer_consequence() { ); } } + +#[test] +fn limitation_rejects_non_nfc_claim_identity() { + let nfc_claim = "caf\u{e9}"; + let canonically_equivalent_nfd_claim = "cafe\u{301}"; + + assert!( + DeclaredLimitation::new( + nfc_claim, + "This normalized claim remains a supported buyer-visible spelling.", + ) + .is_ok(), + "NFC international text must remain admissible", + ); + assert_eq!( + DeclaredLimitation::new( + canonically_equivalent_nfd_claim, + "This decomposed spelling must not create a second claim identity.", + ), + Err(ReleaseDecisionError::InvalidLimitationClaim), + "canonically equivalent NFD text must not bypass limitation identity", + ); +} + +#[test] +fn limitation_rejects_non_nfc_buyer_consequence() { + assert_eq!( + DeclaredLimitation::new( + "linux_arm64", + "Cafe\u{301} support is excluded from this profile.", + ), + Err(ReleaseDecisionError::InvalidLimitationConsequence), + "buyer-visible consequences must use one canonical Unicode spelling", + ); +} From 952da8745b6a18fc2ea9423e725e748a06be55aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:43:53 -0700 Subject: [PATCH 58/72] fix(core): reject non-NFC release limitation text --- Cargo.lock | 27 +++++++++++++++++++ crates/originweave-core/Cargo.toml | 1 + .../src/release_acceptance.rs | 25 +++++++++++------ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2ada3c4e..249eecc70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,6 +266,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "originweave-core" version = "0.1.0" +dependencies = [ + "unicode-normalization", +] [[package]] name = "originweave-destination" @@ -554,6 +557,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "typenum" version = "1.20.1" @@ -566,6 +584,15 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "untrusted" version = "0.9.0" diff --git a/crates/originweave-core/Cargo.toml b/crates/originweave-core/Cargo.toml index 517e41217..dcda2a6c4 100644 --- a/crates/originweave-core/Cargo.toml +++ b/crates/originweave-core/Cargo.toml @@ -14,6 +14,7 @@ publish = false path = "src/root.rs" [dependencies] +unicode-normalization = "=0.1.25" [lints] workspace = true diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index bb4e12870..352de14c3 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -6,6 +6,8 @@ use std::fmt; +use unicode_normalization::is_nfc; + /// Maximum UTF-8 byte length retained for either buyer-visible limitation field. pub const MAX_RELEASE_LIMITATION_TEXT_BYTES: usize = 1024; @@ -85,10 +87,11 @@ pub struct DeclaredLimitation { impl DeclaredLimitation { /// Construct one explicit buyer-visible release limitation. /// - /// Empty/whitespace-only values, surrounding whitespace, fields exceeding the - /// fixed UTF-8 byte budget, and ambiguous presentation characters fail closed - /// because they cannot safely represent one canonical, resource-bounded - /// buyer-visible release limitation. + /// Empty/whitespace-only values, surrounding whitespace, non-NFC Unicode, + /// fields exceeding the fixed UTF-8 byte budget, and ambiguous presentation + /// characters fail closed because they cannot safely represent one canonical, + /// resource-bounded buyer-visible release limitation. Accepted text is retained + /// byte-for-byte; this constructor never normalizes caller input implicitly. pub fn new( unsupported_claim: impl Into, buyer_consequence: impl Into, @@ -103,6 +106,9 @@ impl DeclaredLimitation { if unsupported_claim.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { return Err(ReleaseDecisionError::LimitationClaimTooLong); } + if !is_nfc(&unsupported_claim) { + return Err(ReleaseDecisionError::InvalidLimitationClaim); + } if unsupported_claim .chars() .any(disallowed_release_limitation_character) @@ -119,6 +125,9 @@ impl DeclaredLimitation { if buyer_consequence.len() > MAX_RELEASE_LIMITATION_TEXT_BYTES { return Err(ReleaseDecisionError::LimitationConsequenceTooLong); } + if !is_nfc(&buyer_consequence) { + return Err(ReleaseDecisionError::InvalidLimitationConsequence); + } if buyer_consequence .chars() .any(disallowed_release_limitation_character) @@ -189,13 +198,13 @@ pub enum ReleaseDecisionError { EmptyLimitationClaim, /// A declared limitation claim exceeded the fixed UTF-8 byte budget. LimitationClaimTooLong, - /// A declared limitation claim contained an unsafe presentation character. + /// A declared limitation claim was not canonical NFC text or was presentation-unsafe. InvalidLimitationClaim, /// A declared limitation did not state the buyer-visible consequence. EmptyLimitationConsequence, /// A declared limitation consequence exceeded the fixed UTF-8 byte budget. LimitationConsequenceTooLong, - /// A declared limitation consequence contained an unsafe presentation character. + /// A limitation consequence was not canonical NFC text or was presentation-unsafe. InvalidLimitationConsequence, /// One release report supplied more buyer-visible limitations than the fixed resource budget. TooManyDeclaredLimitations, @@ -215,14 +224,14 @@ impl fmt::Display for ReleaseDecisionError { formatter.write_str("declared release limitation claim exceeds the byte budget") } Self::InvalidLimitationClaim => formatter.write_str( - "declared release limitation claim contains an unsafe presentation character", + "declared release limitation claim is not canonical NFC text or contains an unsafe presentation character", ), Self::EmptyLimitationConsequence => formatter .write_str("declared release limitation must state a buyer-visible consequence"), Self::LimitationConsequenceTooLong => formatter .write_str("declared release limitation consequence exceeds the byte budget"), Self::InvalidLimitationConsequence => formatter.write_str( - "declared release limitation consequence contains an unsafe presentation character", + "declared release limitation consequence is not canonical NFC text or contains an unsafe presentation character", ), Self::TooManyDeclaredLimitations => formatter .write_str("benchmark release decision contains too many declared limitations"), From 8f86ae1bf72fb3a0daa2a74ba9a033600633e69e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:48:53 -0700 Subject: [PATCH 59/72] fix(core): preserve limitation error compatibility --- crates/originweave-core/src/release_acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index 352de14c3..fb3cef0e5 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -224,14 +224,14 @@ impl fmt::Display for ReleaseDecisionError { formatter.write_str("declared release limitation claim exceeds the byte budget") } Self::InvalidLimitationClaim => formatter.write_str( - "declared release limitation claim is not canonical NFC text or contains an unsafe presentation character", + "declared release limitation claim contains an unsafe presentation character", ), Self::EmptyLimitationConsequence => formatter .write_str("declared release limitation must state a buyer-visible consequence"), Self::LimitationConsequenceTooLong => formatter .write_str("declared release limitation consequence exceeds the byte budget"), Self::InvalidLimitationConsequence => formatter.write_str( - "declared release limitation consequence is not canonical NFC text or contains an unsafe presentation character", + "declared release limitation consequence contains an unsafe presentation character", ), Self::TooManyDeclaredLimitations => formatter .write_str("benchmark release decision contains too many declared limitations"), From 3a155e4662c0d5497533425025ca77d70cc511e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:53:22 -0700 Subject: [PATCH 60/72] test(core): cover NFC rejection exits in exact coverage --- .../tests/release_acceptance_resource_bounds.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 36664773d..8116e96c4 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -35,6 +35,10 @@ fn borrowed_limitation_text_covers_every_validation_exit() { DeclaredLimitation::new(" bounded_claim", "bounded buyer consequence"), Err(ReleaseDecisionError::InvalidLimitationClaim) ); + assert_eq!( + DeclaredLimitation::new("cafe\u{301}", "bounded buyer consequence"), + Err(ReleaseDecisionError::InvalidLimitationClaim) + ); assert_eq!( DeclaredLimitation::new("bounded_claim", ""), Err(ReleaseDecisionError::EmptyLimitationConsequence) @@ -43,6 +47,10 @@ fn borrowed_limitation_text_covers_every_validation_exit() { DeclaredLimitation::new("bounded_claim", "bounded buyer consequence "), Err(ReleaseDecisionError::InvalidLimitationConsequence) ); + assert_eq!( + DeclaredLimitation::new("bounded_claim", "cafe\u{301} buyer consequence"), + Err(ReleaseDecisionError::InvalidLimitationConsequence) + ); assert_eq!( DeclaredLimitation::new("forged\nclaim", "bounded buyer consequence"), Err(ReleaseDecisionError::InvalidLimitationClaim) @@ -87,4 +95,4 @@ fn release_resource_limit_errors_have_deterministic_standard_error_contracts() { let standard_error: &dyn std::error::Error = &error; assert!(standard_error.source().is_none()); } -} +} \ No newline at end of file From ad149e357c94a4ec050e5452be2f81b82f9b4b2e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 13:54:34 -0700 Subject: [PATCH 61/72] docs: record release limitation NFC contract --- docs/doctoring.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/doctoring.md b/docs/doctoring.md index 375b82fa9..64e362bf3 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -28,6 +28,8 @@ RFC 9700 is the current Best Current Practice for OAuth 2.0 security. It require Unicode 17.0 defines `Default_Ignorable_Code_Point` in the Unicode Character Database and records the exact derived set in the versioned `DerivedCoreProperties.txt` data file. Those characters can be invisible or alter presentation without supplying an ordinary visible glyph. OriginWeave therefore treats the Unicode 17.0 derived property as a pinned presentation-safety input for buyer-visible release-limitation metadata, in addition to rejecting control characters and non-canonical leading or trailing whitespace. The admitted text is not silently normalized: accepted content retains its exact bytes, while ambiguous presentation characters and surrounding whitespace fail closed so one release claim cannot acquire multiple stored spellings. This is a bounded metadata-identity policy, not a claim of complete Unicode spoofing resistance or semantic text equivalence. +Unicode Standard Annex #15, revision 57 for Unicode 17.0.0, defines canonical equivalence and NFC and states that normalized equivalent strings have a unique binary representation. A release limitation is an identity-bearing buyer artifact, so OriginWeave rejects canonically equivalent non-NFC spellings instead of silently rewriting them. The production boundary uses only `unicode_normalization::is_nfc`; accepted strings remain byte-for-byte caller input. Rust's standard library does not provide Unicode normalization, so `unicode-normalization` is pinned exactly to 0.1.25. The reviewed crate implements UAX #15 normalization, declares Rust 1.36+ compatibility (below OriginWeave's Rust 1.97.1 baseline), is dual MIT/Apache-2.0 licensed, and adds only `tinyvec`/`tinyvec_macros` transitively in this workspace lockfile. The dependency is narrow, deterministic, non-networked, and maintained through the existing locked-dependency/security-scan process; any future Unicode-version or crate-version movement requires renewed normalization and supply-chain review. + ### Resolved destination and redirect safety Canonical origin identity is not a network-destination authorization. The IANA IPv4 and IPv6 Special-Purpose Address Space registries enumerate blocks whose source, destination, forwardability, globally reachable, and protocol-reserved properties differ. Both registries were last updated on 9 October 2025 and explicitly warn that registry presence does not guarantee routability in a particular local or global context. RFC 6890 established the common special-purpose registry fields, and RFC 8190 replaced the ambiguous `global` field with `globally reachable`. @@ -180,6 +182,10 @@ The Rust Project Developers. (2026). *TcpStream in std::net* (Rust 1.97.1) [Soft The Unicode Consortium. (2025). *DerivedCoreProperties-17.0.0.txt* [Data file]. https://www.unicode.org/Public/17.0.0/ucd/DerivedCoreProperties.txt +The Unicode Consortium. (2025, July 30). *Unicode Standard Annex #15: Unicode normalization forms* (Revision 57, Unicode 17.0.0). https://www.unicode.org/reports/tr15/ + +Unicode-RS Project Developers. (2025). *unicode-normalization 0.1.25* [Computer software]. https://docs.rs/unicode-normalization/0.1.25/unicode_normalization/ + Web Hypertext Application Technology Working Group. (2026). *URL standard*. https://url.spec.whatwg.org/ World Wide Web Consortium. (2013). *PROV-O: The PROV ontology*. https://www.w3.org/TR/prov-o/ From cd33b3befabf2cf2efdd8c375d4293dc4408119b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 15:05:58 -0700 Subject: [PATCH 62/72] test(core): restore canonical release acceptance formatting --- .../tests/release_acceptance_resource_bounds.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs index 8116e96c4..fd45e0e6d 100644 --- a/crates/originweave-core/tests/release_acceptance_resource_bounds.rs +++ b/crates/originweave-core/tests/release_acceptance_resource_bounds.rs @@ -95,4 +95,4 @@ fn release_resource_limit_errors_have_deterministic_standard_error_contracts() { let standard_error: &dyn std::error::Error = &error; assert!(standard_error.source().is_none()); } -} \ No newline at end of file +} From 5568cce8196d2663e7fff25c8212f212a7c172c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:08:15 -0700 Subject: [PATCH 63/72] test(core): expose misleading limitation diagnostics --- .../release_acceptance_canonical_text.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_canonical_text.rs b/crates/originweave-core/tests/release_acceptance_canonical_text.rs index 7fb4c171e..f141baed0 100644 --- a/crates/originweave-core/tests/release_acceptance_canonical_text.rs +++ b/crates/originweave-core/tests/release_acceptance_canonical_text.rs @@ -93,3 +93,26 @@ fn limitation_rejects_non_nfc_buyer_consequence() { "buyer-visible consequences must use one canonical Unicode spelling", ); } + +#[test] +fn invalid_canonical_text_errors_describe_all_rejected_causes() { + let claim_error = DeclaredLimitation::new( + " linux_arm64", + "Linux ARM64 is excluded from the support profile.", + ) + .expect_err("surrounding claim whitespace must remain invalid"); + assert_eq!( + claim_error.to_string(), + "declared release limitation claim is not canonical or contains an unsafe presentation character" + ); + + let consequence_error = DeclaredLimitation::new( + "linux_arm64", + "Cafe\u{301} support is excluded from this profile.", + ) + .expect_err("non-NFC consequence text must remain invalid"); + assert_eq!( + consequence_error.to_string(), + "declared release limitation consequence is not canonical or contains an unsafe presentation character" + ); +} From 68afef30964d2d9e4f6e4499b8b9e2db4003a748 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:11:05 -0700 Subject: [PATCH 64/72] fix(core): make limitation diagnostics match validation --- crates/originweave-core/src/release_acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index fb3cef0e5..a7db2a760 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -224,14 +224,14 @@ impl fmt::Display for ReleaseDecisionError { formatter.write_str("declared release limitation claim exceeds the byte budget") } Self::InvalidLimitationClaim => formatter.write_str( - "declared release limitation claim contains an unsafe presentation character", + "declared release limitation claim is not canonical or contains an unsafe presentation character", ), Self::EmptyLimitationConsequence => formatter .write_str("declared release limitation must state a buyer-visible consequence"), Self::LimitationConsequenceTooLong => formatter .write_str("declared release limitation consequence exceeds the byte budget"), Self::InvalidLimitationConsequence => formatter.write_str( - "declared release limitation consequence contains an unsafe presentation character", + "declared release limitation consequence is not canonical or contains an unsafe presentation character", ), Self::TooManyDeclaredLimitations => formatter .write_str("benchmark release decision contains too many declared limitations"), From 473a22e32d2cd11f6fa17faa9c6ddf57115641b0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:11:44 -0700 Subject: [PATCH 65/72] test(core): align limitation error contract --- crates/originweave-core/tests/release_acceptance.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance.rs b/crates/originweave-core/tests/release_acceptance.rs index 14cd2f8e4..3e37fab18 100644 --- a/crates/originweave-core/tests/release_acceptance.rs +++ b/crates/originweave-core/tests/release_acceptance.rs @@ -138,7 +138,7 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { ), ( ReleaseDecisionError::InvalidLimitationClaim, - "declared release limitation claim contains an unsafe presentation character", + "declared release limitation claim is not canonical or contains an unsafe presentation character", ), ( ReleaseDecisionError::EmptyLimitationConsequence, @@ -146,7 +146,7 @@ fn limitation_errors_have_deterministic_standard_error_contracts() { ), ( ReleaseDecisionError::InvalidLimitationConsequence, - "declared release limitation consequence contains an unsafe presentation character", + "declared release limitation consequence is not canonical or contains an unsafe presentation character", ), ( ReleaseDecisionError::DuplicateLimitationClaim, From 1d1ed877ce485bf11366cd9f0bf981d5241b8f85 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:14:14 -0700 Subject: [PATCH 66/72] test(core): keep diagnostic regression clippy-clean --- .../tests/release_acceptance_canonical_text.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/originweave-core/tests/release_acceptance_canonical_text.rs b/crates/originweave-core/tests/release_acceptance_canonical_text.rs index f141baed0..2d7840af3 100644 --- a/crates/originweave-core/tests/release_acceptance_canonical_text.rs +++ b/crates/originweave-core/tests/release_acceptance_canonical_text.rs @@ -96,23 +96,21 @@ fn limitation_rejects_non_nfc_buyer_consequence() { #[test] fn invalid_canonical_text_errors_describe_all_rejected_causes() { - let claim_error = DeclaredLimitation::new( + let claim_result = DeclaredLimitation::new( " linux_arm64", "Linux ARM64 is excluded from the support profile.", - ) - .expect_err("surrounding claim whitespace must remain invalid"); + ); assert_eq!( - claim_error.to_string(), - "declared release limitation claim is not canonical or contains an unsafe presentation character" + claim_result.as_ref().map_err(ToString::to_string), + Err("declared release limitation claim is not canonical or contains an unsafe presentation character".to_owned()) ); - let consequence_error = DeclaredLimitation::new( + let consequence_result = DeclaredLimitation::new( "linux_arm64", "Cafe\u{301} support is excluded from this profile.", - ) - .expect_err("non-NFC consequence text must remain invalid"); + ); assert_eq!( - consequence_error.to_string(), - "declared release limitation consequence is not canonical or contains an unsafe presentation character" + consequence_result.as_ref().map_err(ToString::to_string), + Err("declared release limitation consequence is not canonical or contains an unsafe presentation character".to_owned()) ); } From e8c63a907a5b6000584b491171e3e414586bfb68 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:34:57 -0700 Subject: [PATCH 67/72] test(core): pin release line-separator rejection --- .../tests/release_acceptance_unicode17.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index faf9369e4..da3efef1b 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -77,6 +77,28 @@ fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), & Ok(()) } +#[test] +fn limitation_rejects_line_and_paragraph_separators_beyond_default_ignorable_set() { + for (name, separator) in [('U+2028', '\u{2028}'), ('U+2029', '\u{2029}')] { + assert_eq!( + DeclaredLimitation::new( + format!("linux_arm64{separator}forged_release_claim"), + "Linux ARM64 is unsupported.", + ), + Err(ReleaseDecisionError::InvalidLimitationClaim), + "{name} must be rejected in the unsupported claim to prevent line-forging ambiguity", + ); + assert_eq!( + DeclaredLimitation::new( + "linux_arm64", + format!("Linux ARM64 is unsupported.{separator}forged_release_consequence"), + ), + Err(ReleaseDecisionError::InvalidLimitationConsequence), + "{name} must be rejected in the buyer consequence to prevent line-forging ambiguity", + ); + } +} + #[test] fn limitation_does_not_blanket_reject_unicode_17_whitespace() -> Result<(), ReleaseDecisionError> { let medium_mathematical_space = '\u{205f}'; From eac2014bf0e642953bed2c71e5fe963900b22286 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 24 Aug 2026 20:41:09 -0700 Subject: [PATCH 68/72] test(core): fix release separator test labels --- crates/originweave-core/tests/release_acceptance_unicode17.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-core/tests/release_acceptance_unicode17.rs b/crates/originweave-core/tests/release_acceptance_unicode17.rs index da3efef1b..eccd90e89 100644 --- a/crates/originweave-core/tests/release_acceptance_unicode17.rs +++ b/crates/originweave-core/tests/release_acceptance_unicode17.rs @@ -79,7 +79,7 @@ fn limitation_rejects_unicode_17_default_ignorable_code_points() -> Result<(), & #[test] fn limitation_rejects_line_and_paragraph_separators_beyond_default_ignorable_set() { - for (name, separator) in [('U+2028', '\u{2028}'), ('U+2029', '\u{2029}')] { + for (name, separator) in [("U+2028", '\u{2028}'), ("U+2029", '\u{2029}')] { assert_eq!( DeclaredLimitation::new( format!("linux_arm64{separator}forged_release_claim"), From 0f3a7f4dddb63717e34ae9fe14c10ec418d6ede3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 12:10:30 -0700 Subject: [PATCH 69/72] test(release): reject semantically empty limitation text --- ...elease_acceptance_meaningful_limitation.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs diff --git a/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs b/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs new file mode 100644 index 000000000..744a47e74 --- /dev/null +++ b/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs @@ -0,0 +1,28 @@ +use originweave_core::release_acceptance::{DeclaredLimitation, ReleaseDecisionError}; + +#[test] +fn punctuation_only_limitation_claim_does_not_name_an_unsupported_claim() { + assert_eq!( + DeclaredLimitation::new("---", "Linux ARM64 is excluded from the support profile."), + Err(ReleaseDecisionError::InvalidLimitationClaim) + ); +} + +#[test] +fn punctuation_only_limitation_consequence_does_not_state_a_buyer_consequence() { + assert_eq!( + DeclaredLimitation::new("linux_arm64", "..."), + Err(ReleaseDecisionError::InvalidLimitationConsequence) + ); +} + +#[test] +fn international_alphanumeric_limitation_text_remains_admissible() { + assert!( + DeclaredLimitation::new( + "한국어_운영환경", + "이 운영환경은 현재 지원 범위에 포함되지 않습니다.", + ) + .is_ok() + ); +} From c38bfd1dd94b34eb0fadc45814d095a0edc23a69 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 12:13:18 -0700 Subject: [PATCH 70/72] fix(release): require meaningful limitation text --- crates/originweave-core/src/release_acceptance.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index a7db2a760..312344a30 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -87,11 +87,12 @@ pub struct DeclaredLimitation { impl DeclaredLimitation { /// Construct one explicit buyer-visible release limitation. /// - /// Empty/whitespace-only values, surrounding whitespace, non-NFC Unicode, - /// fields exceeding the fixed UTF-8 byte budget, and ambiguous presentation - /// characters fail closed because they cannot safely represent one canonical, - /// resource-bounded buyer-visible release limitation. Accepted text is retained - /// byte-for-byte; this constructor never normalizes caller input implicitly. + /// Empty/whitespace-only or punctuation-only values, surrounding whitespace, + /// non-NFC Unicode, fields exceeding the fixed UTF-8 byte budget, and ambiguous + /// presentation characters fail closed because they cannot safely represent one + /// canonical, resource-bounded buyer-visible release limitation. Accepted text + /// is retained byte-for-byte; this constructor never normalizes caller input + /// implicitly. pub fn new( unsupported_claim: impl Into, buyer_consequence: impl Into, @@ -112,6 +113,7 @@ impl DeclaredLimitation { if unsupported_claim .chars() .any(disallowed_release_limitation_character) + || !unsupported_claim.chars().any(char::is_alphanumeric) { return Err(ReleaseDecisionError::InvalidLimitationClaim); } @@ -131,6 +133,7 @@ impl DeclaredLimitation { if buyer_consequence .chars() .any(disallowed_release_limitation_character) + || !buyer_consequence.chars().any(char::is_alphanumeric) { return Err(ReleaseDecisionError::InvalidLimitationConsequence); } From 9650d2af82beb78b5afc932b249fabfcb2170323 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 12:17:36 -0700 Subject: [PATCH 71/72] test(release): cover meaningful-text scan branches --- ...release_acceptance_meaningful_limitation.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs b/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs index 744a47e74..0dfb20ba3 100644 --- a/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs +++ b/crates/originweave-core/tests/release_acceptance_meaningful_limitation.rs @@ -16,6 +16,24 @@ fn punctuation_only_limitation_consequence_does_not_state_a_buyer_consequence() ); } +#[test] +fn meaningful_text_may_begin_with_allowed_punctuation() { + assert!( + DeclaredLimitation::new( + "-linux_arm64", + "Linux ARM64 is excluded from the support profile.", + ) + .is_ok() + ); + assert!( + DeclaredLimitation::new( + "linux_arm64", + "... Linux ARM64 remains outside the support profile.", + ) + .is_ok() + ); +} + #[test] fn international_alphanumeric_limitation_text_remains_admissible() { assert!( From 911ea33d8a5aca7673307bb6fdcad4b450f5c111 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 13:14:34 -0700 Subject: [PATCH 72/72] fix(core): make limitation validation monomorphic --- crates/originweave-core/src/release_acceptance.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/originweave-core/src/release_acceptance.rs b/crates/originweave-core/src/release_acceptance.rs index 312344a30..a3655de52 100644 --- a/crates/originweave-core/src/release_acceptance.rs +++ b/crates/originweave-core/src/release_acceptance.rs @@ -97,7 +97,13 @@ impl DeclaredLimitation { unsupported_claim: impl Into, buyer_consequence: impl Into, ) -> Result { - let unsupported_claim = unsupported_claim.into(); + Self::from_owned_text(unsupported_claim.into(), buyer_consequence.into()) + } + + fn from_owned_text( + unsupported_claim: String, + buyer_consequence: String, + ) -> Result { if unsupported_claim.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationClaim); } @@ -117,7 +123,6 @@ impl DeclaredLimitation { { return Err(ReleaseDecisionError::InvalidLimitationClaim); } - let buyer_consequence = buyer_consequence.into(); if buyer_consequence.trim().is_empty() { return Err(ReleaseDecisionError::EmptyLimitationConsequence); }