From 90398391dd7c6e7df3c3abd1e2350213f8fb3b85 Mon Sep 17 00:00:00 2001 From: Hare Date: Thu, 29 Oct 2026 05:29:18 +0900 Subject: [PATCH] test: verify Feature rollback restores typed result publication --- crates/worker/src/wip/ticket_native_tests.rs | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/crates/worker/src/wip/ticket_native_tests.rs b/crates/worker/src/wip/ticket_native_tests.rs index bd76f8c2c..ac1809f05 100644 --- a/crates/worker/src/wip/ticket_native_tests.rs +++ b/crates/worker/src/wip/ticket_native_tests.rs @@ -916,6 +916,60 @@ fn result_namespaces_follow_committed_registry_rollback_and_independent_host_bui ); } +// Production Feature installer + Objective composition + final WIP Host snapshot. +// Only Workspace transport is scripted; registration must perform no requests. +#[test] +fn feature_install_publication_snapshot_preserves_host_roots_and_rolls_back_new_markers() { + use crate::feature::{ + FeatureDescriptor, FeatureInstallContext, FeatureInstallError, FeatureModule, + FeatureRegistryBuilder, + }; + struct LateFailure; + impl FeatureModule for LateFailure { + fn descriptor(&self) -> FeatureDescriptor { + FeatureDescriptor::builtin("late-failure", "Late failure") + } + fn install(&self, _: &mut FeatureInstallContext<'_>) -> Result<(), FeatureInstallError> { + Err(FeatureInstallError::InvalidDescriptor("late failure".into())) + } + } + for fail in [false, true] { + let client = Arc::new(ScriptedWorkspaceClient::new([])); + let mut mounts = WipMountRegistry::new(); + let ticket = mounts.allocate_namespace("ticket", "cases").unwrap(); + crate::feature::builtin::ticket::mount_workspace_http_ticket_wip( + &mut mounts, + client.clone(), + crate::feature::builtin::ticket::TicketFeatureAccess::read_only(), + None, + &ticket, + ) + .unwrap(); + let before = WipHost::new(mounts.clone()); + let objective = mounts.allocate_namespace("objective", "goals").unwrap(); + let mut features = FeatureRegistryBuilder::new(); + crate::feature::builtin::objective::composition::compose( + &mut features, true, client.clone(), Some((objective, None)), + ) + .unwrap(); + if fail { + features.add_module(LateFailure); + } + let mut engine = Engine::<_, Mutable, ()>::new_annotated(DummyClient); + let report = features.install_into_engine_with_wip( + &mut engine, &mut crate::hook::HookRegistryBuilder::default(), Some(&mut mounts), + ); + assert_eq!(report.has_errors(), fail); + let after = WipHost::new(mounts); + assert_eq!(after.result_namespaces.root("ticket"), Some("/cases")); + assert_eq!(after.result_namespaces.root("objective"), (!fail).then_some("/goals")); + assert_eq!(before.result_namespaces.root("objective"), None); + assert_eq!(before.result_namespaces.root("ticket"), Some("/cases")); + assert_eq!(after.observe("/goals", 1).is_ok(), !fail); + assert!(client.requests.lock().unwrap().is_empty()); + } +} + #[tokio::test] async fn host_overwrites_forged_result_context_and_omits_unpublished_cross_domain_entries() { let mut detail = ticket_result_detail("T-42");