From 09dfd50a02eb26896c7ac649a8a00a7a35d6bc4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 04:39:39 -0700 Subject: [PATCH 1/2] test(bap): require typed external outcome reconciliation --- .../tests/external_outcome_reconciliation.rs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 crates/originweave-bap/tests/external_outcome_reconciliation.rs diff --git a/crates/originweave-bap/tests/external_outcome_reconciliation.rs b/crates/originweave-bap/tests/external_outcome_reconciliation.rs new file mode 100644 index 000000000..25a45c0fc --- /dev/null +++ b/crates/originweave-bap/tests/external_outcome_reconciliation.rs @@ -0,0 +1,101 @@ +use std::error::Error; + +use originweave_bap::{ + BapExternalOutcome, BapExternalOutcomeError, BapRecoveryDirective, BapTaskEvent, + BapTaskLifecycle, BapTaskState, +}; + +fn running_lifecycle() -> Result> { + let mut lifecycle = BapTaskLifecycle::new(); + lifecycle.apply(BapTaskEvent::Admit)?; + lifecycle.apply(BapTaskEvent::Start)?; + Ok(lifecycle) +} + +#[test] +fn confirmed_no_side_effect_allows_retry_without_mutating_lifecycle() -> Result<(), Box> { + let lifecycle = running_lifecycle()?; + let sequence = lifecycle.transition_sequence(); + + let directive = lifecycle.classify_external_outcome(BapExternalOutcome::ConfirmedNoSideEffect)?; + + assert_eq!(directive, BapRecoveryDirective::RetryCommand); + assert_eq!(lifecycle.state(), BapTaskState::Running); + assert_eq!(lifecycle.transition_sequence(), sequence); + Ok(()) +} + +#[test] +fn confirmed_side_effect_requires_post_condition_verification_without_mutation( +) -> Result<(), Box> { + let lifecycle = running_lifecycle()?; + let sequence = lifecycle.transition_sequence(); + + let directive = lifecycle.classify_external_outcome(BapExternalOutcome::ConfirmedSideEffect)?; + + assert_eq!(directive, BapRecoveryDirective::VerifyPostCondition); + assert_eq!(lifecycle.state(), BapTaskState::Running); + assert_eq!(lifecycle.transition_sequence(), sequence); + Ok(()) +} + +#[test] +fn unknown_outcome_requires_explicit_reconciliation_transition() -> Result<(), Box> { + let mut lifecycle = running_lifecycle()?; + + let directive = lifecycle.classify_external_outcome(BapExternalOutcome::Unknown)?; + + assert_eq!( + directive, + BapRecoveryDirective::RequireReconciliation { + outcome: BapExternalOutcome::Unknown, + } + ); + lifecycle.apply(BapTaskEvent::RequireReconciliation)?; + assert_eq!(lifecycle.state(), BapTaskState::ReconciliationRequired); + Ok(()) +} + +#[test] +fn explicit_reconciliation_outcome_preserves_its_distinct_cause() -> Result<(), Box> { + let lifecycle = running_lifecycle()?; + + let directive = lifecycle.classify_external_outcome(BapExternalOutcome::ReconciliationRequired)?; + + assert_eq!( + directive, + BapRecoveryDirective::RequireReconciliation { + outcome: BapExternalOutcome::ReconciliationRequired, + } + ); + Ok(()) +} + +#[test] +fn outcome_classification_fails_closed_outside_running_state() { + let lifecycle = BapTaskLifecycle::new(); + + let error = lifecycle + .classify_external_outcome(BapExternalOutcome::Unknown) + .err(); + + assert_eq!( + error, + Some(BapExternalOutcomeError::InvalidLifecycleState { + state: BapTaskState::Created, + }) + ); +} + +#[test] +fn outcome_error_has_stable_public_error_contract() { + let error = BapExternalOutcomeError::InvalidLifecycleState { + state: BapTaskState::WaitingForApproval, + }; + + assert_eq!( + error.to_string(), + "BAP external outcome cannot be classified from state WaitingForApproval" + ); + assert!(error.source().is_none()); +} From 384d54dc7b040e8ecac6c01dc47ef52cce7a1280 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 04:46:42 -0700 Subject: [PATCH 2/2] style(bap): apply canonical rustfmt to reconciliation RED --- .../tests/external_outcome_reconciliation.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/originweave-bap/tests/external_outcome_reconciliation.rs b/crates/originweave-bap/tests/external_outcome_reconciliation.rs index 25a45c0fc..da71ff85b 100644 --- a/crates/originweave-bap/tests/external_outcome_reconciliation.rs +++ b/crates/originweave-bap/tests/external_outcome_reconciliation.rs @@ -13,11 +13,13 @@ fn running_lifecycle() -> Result> { } #[test] -fn confirmed_no_side_effect_allows_retry_without_mutating_lifecycle() -> Result<(), Box> { +fn confirmed_no_side_effect_allows_retry_without_mutating_lifecycle() -> Result<(), Box> +{ let lifecycle = running_lifecycle()?; let sequence = lifecycle.transition_sequence(); - let directive = lifecycle.classify_external_outcome(BapExternalOutcome::ConfirmedNoSideEffect)?; + let directive = + lifecycle.classify_external_outcome(BapExternalOutcome::ConfirmedNoSideEffect)?; assert_eq!(directive, BapRecoveryDirective::RetryCommand); assert_eq!(lifecycle.state(), BapTaskState::Running); @@ -26,8 +28,8 @@ fn confirmed_no_side_effect_allows_retry_without_mutating_lifecycle() -> Result< } #[test] -fn confirmed_side_effect_requires_post_condition_verification_without_mutation( -) -> Result<(), Box> { +fn confirmed_side_effect_requires_post_condition_verification_without_mutation() +-> Result<(), Box> { let lifecycle = running_lifecycle()?; let sequence = lifecycle.transition_sequence(); @@ -60,7 +62,8 @@ fn unknown_outcome_requires_explicit_reconciliation_transition() -> Result<(), B fn explicit_reconciliation_outcome_preserves_its_distinct_cause() -> Result<(), Box> { let lifecycle = running_lifecycle()?; - let directive = lifecycle.classify_external_outcome(BapExternalOutcome::ReconciliationRequired)?; + let directive = + lifecycle.classify_external_outcome(BapExternalOutcome::ReconciliationRequired)?; assert_eq!( directive,