From 43b45301bee5f370573d8452046a3f431cd9b51f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:54:29 +0900 Subject: [PATCH 1/5] test(evidence): require browser interruption recovery evidence --- .../tests/browser_task_interruption.rs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 crates/originweave-evidence/tests/browser_task_interruption.rs diff --git a/crates/originweave-evidence/tests/browser_task_interruption.rs b/crates/originweave-evidence/tests/browser_task_interruption.rs new file mode 100644 index 00000000..97ba14fa --- /dev/null +++ b/crates/originweave-evidence/tests/browser_task_interruption.rs @@ -0,0 +1,92 @@ +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 recovery_is_incomplete_until_context_resources_and_evidence_are_all_closed() { + let context_open = BrowserTaskInterruptionEvidence::new( + BrowserTaskInterruptionKind::RendererCrash, + ExternalEffectDisposition::InterruptedBeforeExternalEffect, + false, + true, + true, + ); + let resources_live = BrowserTaskInterruptionEvidence::new( + BrowserTaskInterruptionKind::RendererCrash, + ExternalEffectDisposition::InterruptedBeforeExternalEffect, + true, + false, + true, + ); + let evidence_open = BrowserTaskInterruptionEvidence::new( + BrowserTaskInterruptionKind::RendererCrash, + ExternalEffectDisposition::InterruptedBeforeExternalEffect, + true, + true, + false, + ); + + assert!(!context_open.recovery_complete()); + assert!(!resources_live.recovery_complete()); + assert!(!evidence_open.recovery_complete()); +} From 3ffe7370b470f8be84a0f4d2ab082cdecc895d61 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 10:05:14 +0900 Subject: [PATCH 2/5] test(evidence): require cleanup before retry --- .../tests/browser_task_interruption.rs | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/crates/originweave-evidence/tests/browser_task_interruption.rs b/crates/originweave-evidence/tests/browser_task_interruption.rs index 97ba14fa..ad133116 100644 --- a/crates/originweave-evidence/tests/browser_task_interruption.rs +++ b/crates/originweave-evidence/tests/browser_task_interruption.rs @@ -63,30 +63,34 @@ fn forced_context_close_is_recorded_without_inventing_external_effect() { } #[test] -fn recovery_is_incomplete_until_context_resources_and_evidence_are_all_closed() { - let context_open = BrowserTaskInterruptionEvidence::new( - BrowserTaskInterruptionKind::RendererCrash, - ExternalEffectDisposition::InterruptedBeforeExternalEffect, - false, - true, - true, - ); - let resources_live = BrowserTaskInterruptionEvidence::new( - BrowserTaskInterruptionKind::RendererCrash, - ExternalEffectDisposition::InterruptedBeforeExternalEffect, - true, - false, - true, - ); - let evidence_open = BrowserTaskInterruptionEvidence::new( - BrowserTaskInterruptionKind::RendererCrash, - ExternalEffectDisposition::InterruptedBeforeExternalEffect, - true, - true, - false, - ); - - assert!(!context_open.recovery_complete()); - assert!(!resources_live.recovery_complete()); - assert!(!evidence_open.recovery_complete()); +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 + ); + } } From 81ef6f4075b6d75062351c705cb7bbc5a648c74b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 10:05:37 +0900 Subject: [PATCH 3/5] feat(evidence): add interruption recovery evidence --- .../src/browser_task_interruption.rs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 crates/originweave-evidence/src/browser_task_interruption.rs diff --git a/crates/originweave-evidence/src/browser_task_interruption.rs b/crates/originweave-evidence/src/browser_task_interruption.rs new file mode 100644 index 00000000..c321867a --- /dev/null +++ b/crates/originweave-evidence/src/browser_task_interruption.rs @@ -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 + } + } +} From d9f824df1a587592d205a868fad34af16d13e687 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 10:06:24 +0900 Subject: [PATCH 4/5] feat(evidence): export interruption evidence --- crates/originweave-evidence/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/originweave-evidence/src/lib.rs b/crates/originweave-evidence/src/lib.rs index 184390f1..9ab40012 100644 --- a/crates/originweave-evidence/src/lib.rs +++ b/crates/originweave-evidence/src/lib.rs @@ -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, From 9d9ebffee234ed4ab662dab7850bd08450ec365b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 10:10:09 +0900 Subject: [PATCH 5/5] docs(changelog): record interruption recovery evidence --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1ce5509..084eddbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.