From ce7e7a7590e6858e84a50c2a1532569310a34975 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Thu, 22 Oct 2026 11:23:40 +0000 Subject: [PATCH 1/4] test(tinysecurity-bus): cover abandoned execution outcome wire round-trip Extend the wire stability test to assert that the abandoned execution outcome deserializes from its wire name and serializes back to the same string. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- crates/tinysecurity-bus/tests/wire.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/tinysecurity-bus/tests/wire.rs b/crates/tinysecurity-bus/tests/wire.rs index 4f74996..e049746 100644 --- a/crates/tinysecurity-bus/tests/wire.rs +++ b/crates/tinysecurity-bus/tests/wire.rs @@ -114,6 +114,8 @@ fn all_execution_outcome_variants_have_stable_wire_names() assert_eq!(serde_json::to_value(decoded)?, *v); } assert!(serde_json::from_str::("\"future_unknown\"").is_err()); + let abandoned: ExecutionOutcome = serde_json::from_str("\"abandoned\"")?; + assert_eq!(serde_json::to_string(&abandoned)?, "\"abandoned\""); Ok(()) } #[test] From 1b13d1d2d77919aae2fb328689f675194baf0660 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Thu, 22 Oct 2026 11:24:14 +0000 Subject: [PATCH 2/4] feat(bus): add abandoned execution outcome Add an Abandoned variant to ExecutionOutcome for runs that were abandoned before their effects could be determined, and map the legacy "aborted" value to it instead of Cancelled. The contract version is bumped to 1.28 and the v1 fixture updated to match the new vocabulary. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- crates/tinysecurity-bus/src/approval.rs | 2 +- crates/tinysecurity-bus/src/approval_tests.rs | 2 +- crates/tinysecurity-bus/src/device_frame_tests.rs | 2 +- crates/tinysecurity-bus/src/device_pairing_tests.rs | 2 +- crates/tinysecurity-bus/src/future.rs | 4 +++- crates/tinysecurity-bus/src/lib.rs | 2 +- crates/tinysecurity-bus/tests/fixtures/v1.json | 3 ++- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/tinysecurity-bus/src/approval.rs b/crates/tinysecurity-bus/src/approval.rs index d75e7c8..79e24c3 100644 --- a/crates/tinysecurity-bus/src/approval.rs +++ b/crates/tinysecurity-bus/src/approval.rs @@ -300,7 +300,7 @@ impl ExecutionOutcome { match value { "success" => Ok(Self::Succeeded), "failure" => Ok(Self::Failed), - "aborted" => Ok(Self::Cancelled), + "aborted" => Ok(Self::Abandoned), _ => Err(crate::Error::InvalidArguments), } } diff --git a/crates/tinysecurity-bus/src/approval_tests.rs b/crates/tinysecurity-bus/src/approval_tests.rs index f4256b0..c230797 100644 --- a/crates/tinysecurity-bus/src/approval_tests.rs +++ b/crates/tinysecurity-bus/src/approval_tests.rs @@ -11,7 +11,7 @@ fn legacy_execution_outcomes_are_explicit_and_unknown_values_reject() { ); assert_eq!( ExecutionOutcome::from_legacy("aborted"), - Ok(ExecutionOutcome::Cancelled) + Ok(ExecutionOutcome::Abandoned) ); assert!(ExecutionOutcome::from_legacy("approved").is_err()); } diff --git a/crates/tinysecurity-bus/src/device_frame_tests.rs b/crates/tinysecurity-bus/src/device_frame_tests.rs index c3ed01f..1d5dedb 100644 --- a/crates/tinysecurity-bus/src/device_frame_tests.rs +++ b/crates/tinysecurity-bus/src/device_frame_tests.rs @@ -70,5 +70,5 @@ fn frame_operations_redact_both_payloads_without_changing_open_wire_shape() #[test] fn frame_member_name_is_stable_and_contract_version_is_bumped() { assert_eq!(crate::names::methods::DEVICE_FRAME2, "DeviceFrame2"); - assert_eq!(crate::CONTRACT_VERSION, (1, 27)); + assert_eq!(crate::CONTRACT_VERSION, (1, 28)); } diff --git a/crates/tinysecurity-bus/src/device_pairing_tests.rs b/crates/tinysecurity-bus/src/device_pairing_tests.rs index bde96c1..c417a30 100644 --- a/crates/tinysecurity-bus/src/device_pairing_tests.rs +++ b/crates/tinysecurity-bus/src/device_pairing_tests.rs @@ -85,7 +85,7 @@ fn refresh_and_close_are_distinct_transient_session_operations() { #[test] fn pairing_contract_is_versioned_after_the_frame_contract() { - assert_eq!(CONTRACT_VERSION, (1, 27)); + assert_eq!(CONTRACT_VERSION, (1, 28)); } #[test] diff --git a/crates/tinysecurity-bus/src/future.rs b/crates/tinysecurity-bus/src/future.rs index 6de58fa..b42dda0 100644 --- a/crates/tinysecurity-bus/src/future.rs +++ b/crates/tinysecurity-bus/src/future.rs @@ -20,7 +20,7 @@ pub enum ApprovalState { /// Executed state. Executed, } -/// `ExecutionOutcome` vocabulary, reserved until its engine is implemented. +/// Terminal execution result, including runs abandoned before their effects were known. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ExecutionOutcome { @@ -30,6 +30,8 @@ pub enum ExecutionOutcome { Failed, /// Cancelled state. Cancelled, + /// The in-flight execution was abandoned before its effects could be determined. + Abandoned, } /// `ReplyVerdict` vocabulary, reserved until its engine is implemented. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/tinysecurity-bus/src/lib.rs b/crates/tinysecurity-bus/src/lib.rs index 0933b5e..0c13ca7 100644 --- a/crates/tinysecurity-bus/src/lib.rs +++ b/crates/tinysecurity-bus/src/lib.rs @@ -65,7 +65,7 @@ pub use scoped_policy::*; pub use tool_trust::*; pub mod names; /// Current wire vocabulary version, independent of the artifact version. -pub const CONTRACT_VERSION: (u32, u32) = (1, 27); +pub const CONTRACT_VERSION: (u32, u32) = (1, 28); /// Whether a remote module's advertised version supplies this host's vocabulary. /// /// `version` is the provider version received by the host, not a host requirement diff --git a/crates/tinysecurity-bus/tests/fixtures/v1.json b/crates/tinysecurity-bus/tests/fixtures/v1.json index 17b1df3..159334a 100644 --- a/crates/tinysecurity-bus/tests/fixtures/v1.json +++ b/crates/tinysecurity-bus/tests/fixtures/v1.json @@ -590,7 +590,8 @@ "ExecutionOutcome": [ "succeeded", "failed", - "cancelled" + "cancelled", + "abandoned" ], "ReplyVerdict": [ "allow_once", From f3d760f669e08d8ab791460865b3d51e4b62a2fa Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Thu, 22 Oct 2026 11:24:31 +0000 Subject: [PATCH 3/4] test(approval): cover abandoned execution as immutable unknown effect Add a test asserting that recording an abandoned execution persists it as a terminal executed state with an unknown effect, that replaying the same request is idempotent, and that a later conflicting outcome is rejected. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- .../tinysecurity-approval/src/engine_tests.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/tinysecurity-approval/src/engine_tests.rs b/crates/tinysecurity-approval/src/engine_tests.rs index e5a0009..be3bc60 100644 --- a/crates/tinysecurity-approval/src/engine_tests.rs +++ b/crates/tinysecurity-approval/src/engine_tests.rs @@ -429,6 +429,32 @@ fn action(row: &ApprovalRow2, phase: u64) -> ApprovalActionRequest2 { } } #[tokio::test] +async fn abandoned_execution_is_persisted_as_an_immutable_unknown_effect() -> Result<()> { + let f = Arc::new(Fixture::default()); + let owner = engine(f, Arc::new(TestClock::default()))?; + let row = owner.begin(request()).await?; + owner.decide(decision(&row)).await?; + owner.consume(action(&row, 3)).await?; + + let terminal = RecordExecutionRequest2 { + action: action(&row, 4), + outcome: ExecutionOutcome::Abandoned, + }; + let finished = owner.record_execution(terminal.clone()).await?; + assert_eq!(finished.state, ApprovalLifecycle2::Executed); + assert_eq!(finished.outcome, Some(ExecutionOutcome::Abandoned)); + assert_eq!(owner.list(&request().scope_id).await?, vec![finished.clone()]); + assert_eq!(owner.record_execution(terminal).await?, finished); + + let mut cancelled = RecordExecutionRequest2 { + action: action(&row, 4), + outcome: ExecutionOutcome::Cancelled, + }; + cancelled.action.audit = audit(5); + assert!(owner.record_execution(cancelled).await.is_err()); + Ok(()) +} +#[tokio::test] async fn exact_human_ticket_first_winner_once_consumption_and_terminal_outcome() -> Result<()> { let f = Arc::new(Fixture::default()); let owner = engine(f, Arc::new(TestClock::default()))?; From a790c92a158b98d686b073c3e6e104970493f997 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Thu, 22 Oct 2026 11:24:52 +0000 Subject: [PATCH 4/4] test(bus): bump expected contract version to 1.28 Update the wire and scoped wire tests to expect contract version 1.28 instead of 1.27, and adjust the compatibility comment accordingly. The approval engine test assertion is only reformatted to satisfy rustfmt. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- crates/tinysecurity-approval/src/engine_tests.rs | 5 ++++- crates/tinysecurity-bus/tests/scoped_wire.rs | 6 +++--- crates/tinysecurity-bus/tests/wire.rs | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/tinysecurity-approval/src/engine_tests.rs b/crates/tinysecurity-approval/src/engine_tests.rs index be3bc60..f565fe2 100644 --- a/crates/tinysecurity-approval/src/engine_tests.rs +++ b/crates/tinysecurity-approval/src/engine_tests.rs @@ -443,7 +443,10 @@ async fn abandoned_execution_is_persisted_as_an_immutable_unknown_effect() -> Re let finished = owner.record_execution(terminal.clone()).await?; assert_eq!(finished.state, ApprovalLifecycle2::Executed); assert_eq!(finished.outcome, Some(ExecutionOutcome::Abandoned)); - assert_eq!(owner.list(&request().scope_id).await?, vec![finished.clone()]); + assert_eq!( + owner.list(&request().scope_id).await?, + vec![finished.clone()] + ); assert_eq!(owner.record_execution(terminal).await?, finished); let mut cancelled = RecordExecutionRequest2 { diff --git a/crates/tinysecurity-bus/tests/scoped_wire.rs b/crates/tinysecurity-bus/tests/scoped_wire.rs index 3b9bebe..8f03e9b 100644 --- a/crates/tinysecurity-bus/tests/scoped_wire.rs +++ b/crates/tinysecurity-bus/tests/scoped_wire.rs @@ -84,9 +84,9 @@ fn new_members_are_advertised_and_named_by_contract_constants() { #[test] fn contract_bump_is_additive_for_older_hosts_and_modules() { - assert_eq!(CONTRACT_VERSION, (1, 27)); - // A host built against 1.27 accepts this module and newer minors, not a 1.26 module. - assert!(is_compatible((1, 27))); + assert_eq!(CONTRACT_VERSION, (1, 28)); + // A host built against 1.28 accepts this module and newer minors, not a 1.27 module. + assert!(is_compatible((1, 28))); assert!(!is_compatible((1, 26))); assert!(!is_compatible((1, 25))); assert!(!is_compatible((1, 24))); diff --git a/crates/tinysecurity-bus/tests/wire.rs b/crates/tinysecurity-bus/tests/wire.rs index e049746..18bc777 100644 --- a/crates/tinysecurity-bus/tests/wire.rs +++ b/crates/tinysecurity-bus/tests/wire.rs @@ -345,7 +345,7 @@ fn redactor_init_is_additive_and_secrets_never_enter_public_info() serde_json::to_value(&config)?["redactor"]["secrets"][0], "literal-private" ); - assert_eq!(CONTRACT_VERSION, (1, 27)); + assert_eq!(CONTRACT_VERSION, (1, 28)); for (mode, wire) in [ (RedactMode::Text, "text"), (RedactMode::StructuredArguments, "structured_arguments"),