Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits.
- Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state.
- Credential-safe verified action-outcome evidence that binds action kind, canonical target origin, complete action-intent digest, bounded browser post-condition kind, same-clock dispatch and observation timestamps, and exact provenance; construction rejects unverified or rejected post-conditions and any observation timestamp that predates action dispatch while allowing equal coarse-clock ticks.
- Credential-free browser-task interruption evidence that distinguishes renderer crashes, browser-process exits, and forced context closure; records whether an external effect may have committed; requires browser-context closure, resource reclamation, and evidence finalization for recovery completion; and permits retry only when interruption occurred before any external effect and cleanup is complete, otherwise requiring quarantine.
- Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement.
- Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge.
- Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation.
Expand Down
111 changes: 111 additions & 0 deletions crates/originweave-evidence/src/browser_task_interruption.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/// Browser/runtime interruption category recorded for one Agent Task.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BrowserTaskInterruptionKind {
/// The renderer process serving the task crashed or became unavailable.
RendererCrash,
/// The browser process exited while the task was active.
BrowserProcessExit,
/// The task's browser context was forcibly closed by a trusted runtime boundary.
ForcedContextClose,
}

/// What is known about externally visible effects when the interruption occurred.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ExternalEffectDisposition {
/// The trusted runtime established that interruption occurred before any external effect.
InterruptedBeforeExternalEffect,
/// An externally visible effect may already have committed and must be reconciled.
MayHaveCommitted,
}

/// Whether the interrupted task may be retried without first reconciling ambiguous state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum RetryDisposition {
/// Cleanup is complete and no external effect could have committed.
SafeToRetry,
/// Retry is unsafe until recovery or external-state reconciliation completes.
QuarantineRequired,
}

/// Credential-free recovery evidence for an interrupted browser task.
///
/// The value records caller-supplied facts only. It does not detect browser crashes,
/// prove cleanup, reconcile external effects, or dispatch a retry by itself.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BrowserTaskInterruptionEvidence {
interruption_kind: BrowserTaskInterruptionKind,
external_effect_disposition: ExternalEffectDisposition,
browser_context_closed: bool,
resources_reclaimed: bool,
evidence_finalized: bool,
}

impl BrowserTaskInterruptionEvidence {
/// Create one interruption evidence value from trusted runtime observations.
#[must_use]
pub const fn new(
interruption_kind: BrowserTaskInterruptionKind,
external_effect_disposition: ExternalEffectDisposition,
browser_context_closed: bool,
resources_reclaimed: bool,
evidence_finalized: bool,
) -> Self {
Self {
interruption_kind,
external_effect_disposition,
browser_context_closed,
resources_reclaimed,
evidence_finalized,
}
}

/// Return the recorded interruption category.
#[must_use]
pub const fn interruption_kind(&self) -> BrowserTaskInterruptionKind {
self.interruption_kind
}

/// Return the recorded external-effect disposition.
#[must_use]
pub const fn external_effect_disposition(&self) -> ExternalEffectDisposition {
self.external_effect_disposition
}

/// Return whether the task browser context is confirmed closed.
#[must_use]
pub const fn browser_context_closed(&self) -> bool {
self.browser_context_closed
}

/// Return whether task-owned runtime resources are confirmed reclaimed.
#[must_use]
pub const fn resources_reclaimed(&self) -> bool {
self.resources_reclaimed
}

/// Return whether interruption evidence is confirmed finalized.
#[must_use]
pub const fn evidence_finalized(&self) -> bool {
self.evidence_finalized
}

/// Return true only when context closure, resource reclamation, and evidence finalization all completed.
#[must_use]
pub const fn recovery_complete(&self) -> bool {
self.browser_context_closed && self.resources_reclaimed && self.evidence_finalized
}

/// Derive whether a retry is safe from external-effect and cleanup evidence.
#[must_use]
pub const fn retry_disposition(&self) -> RetryDisposition {
if matches!(
self.external_effect_disposition,
ExternalEffectDisposition::InterruptedBeforeExternalEffect
) && self.recovery_complete()
{
RetryDisposition::SafeToRetry
} else {
RetryDisposition::QuarantineRequired
}
}
}
5 changes: 5 additions & 0 deletions crates/originweave-evidence/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
#![deny(missing_docs)]

mod action_outcome;
mod browser_task_interruption;
mod sensitive_access;

pub use action_outcome::{
PostConditionKind, VerifiedActionOutcomeError, VerifiedActionOutcomeEvidence,
};
pub use browser_task_interruption::{
BrowserTaskInterruptionEvidence, BrowserTaskInterruptionKind, ExternalEffectDisposition,
RetryDisposition,
};
pub use sensitive_access::{
MAX_SENSITIVE_FIELD_COUNT, MAX_SENSITIVE_IDENTIFIER_BYTES, SensitiveAccessClass,
SensitiveAccessEvidence, SensitiveAccessEvidenceInput, SensitiveAccessOutcome,
Expand Down
96 changes: 96 additions & 0 deletions crates/originweave-evidence/tests/browser_task_interruption.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use originweave_evidence::{
BrowserTaskInterruptionEvidence, BrowserTaskInterruptionKind, ExternalEffectDisposition,
RetryDisposition,
};

#[test]
fn interruption_before_external_effect_is_retryable_after_complete_cleanup() {
let evidence = BrowserTaskInterruptionEvidence::new(
BrowserTaskInterruptionKind::RendererCrash,
ExternalEffectDisposition::InterruptedBeforeExternalEffect,
true,
true,
true,
);

assert_eq!(
evidence.interruption_kind(),
BrowserTaskInterruptionKind::RendererCrash
);
assert_eq!(
evidence.external_effect_disposition(),
ExternalEffectDisposition::InterruptedBeforeExternalEffect
);
assert!(evidence.browser_context_closed());
assert!(evidence.resources_reclaimed());
assert!(evidence.evidence_finalized());
assert!(evidence.recovery_complete());
assert_eq!(evidence.retry_disposition(), RetryDisposition::SafeToRetry);
}

#[test]
fn ambiguous_external_effect_requires_quarantine_even_after_cleanup() {
let evidence = BrowserTaskInterruptionEvidence::new(
BrowserTaskInterruptionKind::BrowserProcessExit,
ExternalEffectDisposition::MayHaveCommitted,
true,
true,
true,
);

assert_eq!(
evidence.retry_disposition(),
RetryDisposition::QuarantineRequired
);
assert!(evidence.recovery_complete());
}

#[test]
fn forced_context_close_is_recorded_without_inventing_external_effect() {
let evidence = BrowserTaskInterruptionEvidence::new(
BrowserTaskInterruptionKind::ForcedContextClose,
ExternalEffectDisposition::InterruptedBeforeExternalEffect,
true,
true,
true,
);

assert_eq!(
evidence.interruption_kind(),
BrowserTaskInterruptionKind::ForcedContextClose
);
assert_eq!(evidence.retry_disposition(), RetryDisposition::SafeToRetry);
}

#[test]
fn incomplete_cleanup_requires_quarantine_even_before_an_external_effect() {
for evidence in [
BrowserTaskInterruptionEvidence::new(
BrowserTaskInterruptionKind::RendererCrash,
ExternalEffectDisposition::InterruptedBeforeExternalEffect,
false,
true,
true,
),
BrowserTaskInterruptionEvidence::new(
BrowserTaskInterruptionKind::RendererCrash,
ExternalEffectDisposition::InterruptedBeforeExternalEffect,
true,
false,
true,
),
BrowserTaskInterruptionEvidence::new(
BrowserTaskInterruptionKind::RendererCrash,
ExternalEffectDisposition::InterruptedBeforeExternalEffect,
true,
true,
false,
),
] {
assert!(!evidence.recovery_complete());
assert_eq!(
evidence.retry_disposition(),
RetryDisposition::QuarantineRequired
);
}
}
Loading