Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
ba36a27
test(core): require zero-event safety confidence bound
seonghobae Aug 28, 2026
f787b0a
feat(core): quantify zero-event safety evidence
seonghobae Aug 28, 2026
d42fe50
style(core): apply canonical rustfmt
seonghobae Aug 28, 2026
40fc45d
test(core): require release reports to retain zero-event evidence
seonghobae Aug 28, 2026
1659348
feat(core): retain named zero-event safety evidence
seonghobae Aug 28, 2026
72bbde3
style(core): apply canonical rustfmt to zero-event report regression
seonghobae Aug 28, 2026
dff17d9
test(core): cover zero-event report decision branches
seonghobae Aug 28, 2026
bae7a69
test(core): cover zero-event limitation fail-closed paths
seonghobae Aug 28, 2026
6e1ef04
style(core): apply canonical rustfmt to zero-event limitation tests
seonghobae Aug 28, 2026
1c58111
refactor(core): keep release decision coverage non-generic
seonghobae Aug 28, 2026
ad4d06e
test(core): bind zero-event metric registry cardinality
seonghobae Aug 29, 2026
8203360
fix(core): bind zero-event metric registry cardinality
seonghobae Aug 29, 2026
29a4e1a
docs(research): doctor zero-event safety bounds
seonghobae Aug 29, 2026
5b2f495
test(core): exercise metric ordering without index registry
seonghobae Aug 29, 2026
0b4c5f1
fix(core): remove duplicate zero-event ordering authorities
seonghobae Aug 29, 2026
59e173e
test(core): classify benchmark failure evidence
seonghobae Aug 29, 2026
34871af
test(core): format benchmark failure regression
seonghobae Aug 29, 2026
d37c43e
fix(core): classify benchmark failure evidence
seonghobae Aug 29, 2026
df875ac
test(core): require complete benchmark failure taxonomy
seonghobae Aug 29, 2026
7005d7a
fix(core): complete benchmark failure taxonomy
seonghobae Aug 29, 2026
966ceed
test(core): require classified benchmark evidence retention
seonghobae Aug 29, 2026
1d87ef4
test(core): format classified benchmark evidence regression
seonghobae Aug 29, 2026
26b44d8
feat(core): retain classified benchmark failure evidence
seonghobae Aug 29, 2026
86aa4a8
test(core): bound classified benchmark evidence consumption
seonghobae Aug 29, 2026
eea4ce9
style(core): apply canonical rustfmt to benchmark regression
seonghobae Aug 29, 2026
31c6980
fix(core): bound classified benchmark evidence ingestion
seonghobae Aug 29, 2026
0d65df1
style(core): apply canonical rustfmt to bounded evidence repair
seonghobae Aug 29, 2026
c9cc525
test(core): satisfy strict lint for bounded benchmark evidence regres…
seonghobae Aug 29, 2026
9338ea2
test(core): preserve release metadata validation precedence
seonghobae Aug 29, 2026
45274f2
style(core): apply canonical formatting to metadata precedence regres…
seonghobae Aug 29, 2026
ba106f6
fix(core): preserve release metadata validation precedence
seonghobae Aug 29, 2026
8490b48
test(core): cover classified partial evidence path
seonghobae Aug 29, 2026
7ed3cc1
test(core): require executable zero-event safety thresholds
seonghobae Aug 29, 2026
cac38e5
style(core): apply canonical rustfmt to threshold regression
seonghobae Aug 29, 2026
fbf5ab2
feat(core): evaluate zero-event safety thresholds
seonghobae Aug 29, 2026
ea1b6e0
style(core): apply canonical threshold formatting
seonghobae Aug 29, 2026
4549476
test(core): require fail-closed zero-event safety gate
seonghobae Aug 29, 2026
93bf83d
style(core): apply canonical safety-gate formatting
seonghobae Aug 29, 2026
9e29ec3
feat(core): evaluate declared zero-event safety requirements
seonghobae Aug 29, 2026
3f0ac0f
feat(core): expose zero-event safety gate
seonghobae Aug 29, 2026
39d2f74
style(core): apply canonical safety-gate formatting
seonghobae Aug 29, 2026
2adc609
test(core): satisfy strict panic-free safety gate contracts
seonghobae Aug 29, 2026
e00fe13
test(core): cover exact zero-event threshold boundary
seonghobae Aug 29, 2026
41e32d3
fix(core): compare exact zero-event threshold boundaries
seonghobae Aug 29, 2026
3463c14
style(core): apply canonical zero-event threshold formatting
seonghobae Aug 29, 2026
50b488f
test(core): bound zero-event safety inputs
seonghobae Aug 29, 2026
0593ad5
fix(core): bound zero-event safety gate inputs
seonghobae Aug 29, 2026
82e4755
fix(core): bound release zero-event observations
seonghobae Aug 29, 2026
09c210d
style(core): apply canonical zero-event test formatting
seonghobae Aug 29, 2026
3c7ebc5
test(docs): require release-acceptance architecture contract
seonghobae Aug 29, 2026
bed2303
docs(adr): record benchmark release acceptance boundary
seonghobae Aug 29, 2026
dc2cb95
docs(adr): index release acceptance decision
seonghobae Aug 29, 2026
a6dadae
docs(architecture): document active release evidence boundary
seonghobae Aug 29, 2026
40a6981
docs(index): discover release acceptance ADR
seonghobae Aug 29, 2026
e26debc
test(core): require safety thresholds in commercial release decision
seonghobae Aug 29, 2026
fb67eeb
test(core): format commercial release threshold regression
seonghobae Aug 29, 2026
d9a7811
fix(core): apply safety thresholds to commercial release acceptance
seonghobae Aug 29, 2026
d02bd1a
style(core): apply canonical rustfmt to safety release evaluator
seonghobae Aug 29, 2026
285b90d
style(core): apply canonical rustfmt to release regression
seonghobae Aug 29, 2026
7736117
test(core): avoid panic assertions in release gate regressions
seonghobae Aug 29, 2026
f03f807
docs(core): fix commercial release rustdoc link
seonghobae Aug 29, 2026
7f64004
docs(adr): bind safety thresholds into commercial acceptance evidence
seonghobae Aug 29, 2026
c446280
test(core): cover commercial release error propagation
seonghobae Aug 29, 2026
d25468e
test(core): cover Vec commercial release outcomes
seonghobae Aug 29, 2026
0ef1d68
style(core): apply canonical rustfmt
seonghobae Aug 29, 2026
5c77645
fix(core): satisfy strict error propagation lint
seonghobae Aug 29, 2026
17d5051
docs(changelog): record commercial release evidence APIs
seonghobae Aug 29, 2026
3671ab8
test(core): reject partial commercial safety policy
seonghobae Aug 31, 2026
4bd81bc
fix(core): require complete commercial safety policy
seonghobae Aug 31, 2026
e48e529
test(core): cover complete commercial safety policy
seonghobae Aug 31, 2026
6792be0
test(core): avoid panic fixture in safety gate regression
seonghobae Aug 31, 2026
dbe428f
docs(adr): bind complete commercial safety policy
seonghobae Aug 31, 2026
cbf8bfc
style(core): apply canonical rustfmt to safety regression
seonghobae Aug 31, 2026
5a4dac3
test(ddd): reject release acceptance inside shared core
seonghobae Sep 1, 2026
fc3b0c2
refactor(ddd): isolate release acceptance bounded context
seonghobae Sep 1, 2026
e370d38
test(ddd): enforce release compatibility as deprecation only
seonghobae Sep 1, 2026
335bd11
test(ddd): require release tests under release context
seonghobae Sep 1, 2026
3df63a1
test(ddd): forbid core depending on release context
seonghobae Sep 1, 2026
6483298
refactor(ddd): complete release bounded-context ownership
seonghobae Sep 1, 2026
77bfd6a
build: lock release bounded-context package
seonghobae Sep 1, 2026
86fe70f
test(ddd): move remaining release tests to owning context
seonghobae Sep 1, 2026
e38b1a5
test(docs): require release-context migration contract
seonghobae Sep 1, 2026
99c2b17
docs(ddd): bind release acceptance to release context
seonghobae Sep 1, 2026
79406cd
ci(repair): apply PR240 DDD documentation correction
seonghobae Sep 1, 2026
47ab81b
docs(release): correct PR240 DDD ownership contract
github-actions[bot] Sep 1, 2026
cf2dde4
chore(release): adopt protected main after CI queue mitigation
seonghobae Sep 3, 2026
c7d6c20
chore(release): adopt protected main #280 without rewriting history
seonghobae Sep 3, 2026
76f30fe
chore(release): adopt current protected main
seonghobae Sep 4, 2026
24930a3
Merge remote-tracking branch 'origin/main' into codex/adopt-main-pr240
seonghobae Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 213 additions & 9 deletions crates/originweave-core/src/release_acceptance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,143 @@ pub enum BenchmarkSuiteOutcome {
Inconclusive,
}

/// Zero-observed-event safety evidence with an explicit one-sided confidence bound.
///
/// This value records the exact number of independent Bernoulli trials and a
/// confidence level expressed in basis points. It deliberately does not turn
/// zero observed events into a claim of zero true risk. Instead,
/// [`Self::upper_event_rate`] reports the exact one-sided Clopper-Pearson upper
/// confidence bound for the event probability when zero events were observed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZeroEventSafetyEvidence {
trial_count: u64,
confidence_basis_points: u16,
}

impl ZeroEventSafetyEvidence {
/// Construct zero-event safety evidence from a nonzero trial count and a
/// confidence level in `1..=9999` basis points.
pub fn new(
trial_count: u64,
confidence_basis_points: u16,
) -> Result<Self, ReleaseDecisionError> {
if trial_count == 0 {
return Err(ReleaseDecisionError::MissingSafetyTrials);
}
if confidence_basis_points == 0 || confidence_basis_points >= 10_000 {
return Err(ReleaseDecisionError::InvalidSafetyConfidenceBasisPoints);
}
Ok(Self {
trial_count,
confidence_basis_points,
})
}

/// Return the exact number of zero-event trials represented by this evidence.
#[must_use]
pub const fn trial_count(self) -> u64 {
self.trial_count
}

/// Return the requested one-sided confidence level in basis points.
#[must_use]
pub const fn confidence_basis_points(self) -> u16 {
self.confidence_basis_points
}

/// Return the exact one-sided binomial upper confidence bound for the event rate.
///
/// For zero observed events in `n` trials and confidence `c`, this is
/// `1 - (1 - c)^(1/n)`. The `exp_m1` form avoids avoidable cancellation when
/// the bound is very small. The result is evidence about the sampled event
/// rate, not proof that the underlying event probability is zero.
#[must_use]
pub fn upper_event_rate(self) -> f64 {
let confidence = f64::from(self.confidence_basis_points) / 10_000.0;
let alpha = 1.0 - confidence;
-(alpha.ln() / self.trial_count as f64).exp_m1()
}
}

/// One named safety event class whose zero-observation evidence may be retained.
///
/// The declaration order is also the stable canonical report order. These
/// metrics are evidence labels only: retaining one does not itself satisfy a
/// release threshold or grant release authority.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ZeroEventSafetyMetric {
/// An attempted action completed without the required authority.
UnauthorizedAction,
/// Prompt injection changed protected behavior or authority.
PromptInjectionSuccess,
/// Stale or superseded authority was incorrectly accepted.
StaleAuthorityAcceptance,
/// A protected value was disclosed outside its authorized context.
ProtectedValueDisclosure,
/// Execution obtained authority broader than the approved capability set.
AuthorityEscalation,
}

impl ZeroEventSafetyMetric {
/// Every named zero-event safety metric in canonical release-report order.
pub const ALL: [Self; 5] = [
Self::UnauthorizedAction,
Self::PromptInjectionSuccess,
Self::StaleAuthorityAcceptance,
Self::ProtectedValueDisclosure,
Self::AuthorityEscalation,
];
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Outdated

/// Return the stable snake-case identifier used in retained release evidence.
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::UnauthorizedAction => "unauthorized_action_rate",
Self::PromptInjectionSuccess => "prompt_injection_success_rate",
Self::StaleAuthorityAcceptance => "stale_authority_acceptance_rate",
Self::ProtectedValueDisclosure => "protected_value_disclosure_rate",
Self::AuthorityEscalation => "authority_escalation_rate",
}
}

const fn index(self) -> usize {
match self {
Self::UnauthorizedAction => 0,
Self::PromptInjectionSuccess => 1,
Self::StaleAuthorityAcceptance => 2,
Self::ProtectedValueDisclosure => 3,
Self::AuthorityEscalation => 4,
}
}
}

/// One named zero-observed-event safety measurement retained in a release report.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZeroEventSafetyObservation {
metric: ZeroEventSafetyMetric,
evidence: ZeroEventSafetyEvidence,
}

impl ZeroEventSafetyObservation {
/// Bind a named safety metric to its exact zero-event statistical evidence.
#[must_use]
pub const fn new(metric: ZeroEventSafetyMetric, evidence: ZeroEventSafetyEvidence) -> Self {
Self { metric, evidence }
}

/// Return the named safety metric represented by this observation.
#[must_use]
pub const fn metric(self) -> ZeroEventSafetyMetric {
self.metric
}

/// Return the exact trial count and confidence declaration for this metric.
#[must_use]
pub const fn evidence(self) -> ZeroEventSafetyEvidence {
self.evidence
}
}

/// One explicit narrowed release claim and its buyer-visible consequence.
///
/// An accepted-with-limitations decision cannot be produced from an opaque
Expand Down Expand Up @@ -202,6 +339,12 @@ pub enum ReleaseDecision {
/// Fail-closed input error while constructing a release decision.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReleaseDecisionError {
/// A zero-event safety claim did not include any observed trial.
MissingSafetyTrials,
/// The requested zero-event safety confidence was outside `1..=9999` basis points.
InvalidSafetyConfidenceBasisPoints,
/// More than one zero-event observation used the same named safety metric.
DuplicateZeroEventSafetyMetric(ZeroEventSafetyMetric),
/// A declared limitation did not identify the unsupported release claim.
EmptyLimitationClaim,
/// A declared limitation claim exceeded the fixed UTF-8 byte budget.
Expand All @@ -225,6 +368,17 @@ pub enum ReleaseDecisionError {
impl fmt::Display for ReleaseDecisionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingSafetyTrials => {
formatter.write_str("zero-event safety evidence requires at least one trial")
}
Self::InvalidSafetyConfidenceBasisPoints => formatter.write_str(
"zero-event safety confidence must be between 1 and 9999 basis points",
),
Self::DuplicateZeroEventSafetyMetric(metric) => write!(
formatter,
"benchmark release evidence contains duplicate zero-event safety metric: {}",
metric.as_str()
),
Self::EmptyLimitationClaim => {
formatter.write_str("declared release limitation must name an unsupported claim")
}
Expand Down Expand Up @@ -264,6 +418,7 @@ pub struct ReleaseDecisionReport {
inconclusive_suites: Vec<BenchmarkSuite>,
missing_suites: Vec<BenchmarkSuite>,
declared_limitations: Vec<DeclaredLimitation>,
zero_event_safety_observations: Vec<ZeroEventSafetyObservation>,
}

impl ReleaseDecisionReport {
Expand Down Expand Up @@ -296,26 +451,62 @@ impl ReleaseDecisionReport {
pub fn declared_limitations(&self) -> &[DeclaredLimitation] {
&self.declared_limitations
}

/// Return named zero-event safety evidence in canonical metric order.
#[must_use]
pub fn zero_event_safety_observations(&self) -> &[ZeroEventSafetyObservation] {
&self.zero_event_safety_observations
}
}

/// 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.
/// This compatibility entrypoint retains no named zero-event safety observations.
/// Use [`decide_release_with_zero_event_safety`] when the release report must carry
/// explicit zero-observation statistical evidence.
pub fn decide_release<I>(
results: I,
declared_limitations: &[DeclaredLimitation],
) -> Result<ReleaseDecisionReport, ReleaseDecisionError>
where
I: IntoIterator<Item = (BenchmarkSuite, BenchmarkSuiteOutcome)>,
{
let mut results = results.into_iter();
decide_release_from_iterator(&mut results, declared_limitations, &[])
}

/// Produce a deterministic release decision while retaining named zero-event evidence.
///
/// Duplicate suite evidence, duplicate buyer-visible limitation claim identities,
/// duplicate zero-event metric identities, and excessive declared-limitation
/// cardinality fail closed rather than selecting ambiguous 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 suite evidence is
/// never promoted to acceptance. Named zero-event observations are retained in
/// canonical metric order and remain statistical evidence only: this function
/// does not interpret their confidence bounds as an independent release threshold.
pub fn decide_release_with_zero_event_safety<I>(
results: I,
declared_limitations: &[DeclaredLimitation],
zero_event_safety_observations: &[ZeroEventSafetyObservation],
) -> Result<ReleaseDecisionReport, ReleaseDecisionError>
where
I: IntoIterator<Item = (BenchmarkSuite, BenchmarkSuiteOutcome)>,
{
let mut results = results.into_iter();
decide_release_from_iterator(
&mut results,
declared_limitations,
zero_event_safety_observations,
)
}

fn decide_release_from_iterator(
results: &mut dyn Iterator<Item = (BenchmarkSuite, BenchmarkSuiteOutcome)>,
declared_limitations: &[DeclaredLimitation],
zero_event_safety_observations: &[ZeroEventSafetyObservation],
) -> Result<ReleaseDecisionReport, ReleaseDecisionError> {
if declared_limitations.len() > MAX_DECLARED_RELEASE_LIMITATIONS {
return Err(ReleaseDecisionError::TooManyDeclaredLimitations);
}
Expand All @@ -327,6 +518,18 @@ where
}
}

let mut seen_zero_event_metrics = [false; ZeroEventSafetyMetric::ALL.len()];
for observation in zero_event_safety_observations {
let metric = observation.metric();
let seen = &mut seen_zero_event_metrics[metric.index()];
if *seen {
return Err(ReleaseDecisionError::DuplicateZeroEventSafetyMetric(metric));
}
*seen = true;
}
let mut canonical_zero_event_safety_observations = zero_event_safety_observations.to_vec();
canonical_zero_event_safety_observations.sort_by_key(|observation| observation.metric());

let mut outcomes = [None; BenchmarkSuite::ALL.len()];
for (suite, outcome) in results {
let slot = &mut outcomes[suite.index()];
Expand Down Expand Up @@ -364,5 +567,6 @@ where
inconclusive_suites,
missing_suites,
declared_limitations: declared_limitations.to_vec(),
zero_event_safety_observations: canonical_zero_event_safety_observations,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use originweave_core::release_acceptance::{ReleaseDecisionError, ZeroEventSafetyEvidence};

#[test]
fn zero_observed_events_report_an_exact_one_sided_binomial_upper_bound()
-> Result<(), ReleaseDecisionError> {
let evidence = ZeroEventSafetyEvidence::new(100, 9_500)?;

assert_eq!(evidence.trial_count(), 100);
assert_eq!(evidence.confidence_basis_points(), 9_500);
assert!((evidence.upper_event_rate() - 0.029_513_049_607_039_932).abs() < 1.0e-15);
Ok(())
}

#[test]
fn zero_event_bound_requires_at_least_one_trial() {
assert_eq!(
ZeroEventSafetyEvidence::new(0, 9_500),
Err(ReleaseDecisionError::MissingSafetyTrials)
);
}

#[test]
fn zero_event_bound_rejects_zero_or_complete_confidence() {
assert_eq!(
ZeroEventSafetyEvidence::new(100, 0),
Err(ReleaseDecisionError::InvalidSafetyConfidenceBasisPoints)
);
assert_eq!(
ZeroEventSafetyEvidence::new(100, 10_000),
Err(ReleaseDecisionError::InvalidSafetyConfidenceBasisPoints)
);
}

#[test]
fn more_zero_event_trials_tighten_the_same_confidence_bound() -> Result<(), ReleaseDecisionError> {
let one_hundred = ZeroEventSafetyEvidence::new(100, 9_500)?;
let one_thousand = ZeroEventSafetyEvidence::new(1_000, 9_500)?;

assert!(one_thousand.upper_event_rate() < one_hundred.upper_event_rate());
Ok(())
}

#[test]
fn zero_event_bound_errors_have_deterministic_standard_error_contracts() {
let cases = [
(
ReleaseDecisionError::MissingSafetyTrials,
"zero-event safety evidence requires at least one trial",
),
(
ReleaseDecisionError::InvalidSafetyConfidenceBasisPoints,
"zero-event safety confidence must be between 1 and 9999 basis points",
),
];

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());
}
}
Loading
Loading