@@ -26,6 +26,11 @@ use locality_protocol::{
2626 canonical_writable_metadata_sha256,
2727} ;
2828use localityd:: remote_truth:: { ReplicaArchive , ReplicaArchiveEncoding } ;
29+ #[ cfg( not( all(
30+ unix,
31+ any( target_vendor = "apple" , target_os = "linux" , target_os = "android" )
32+ ) ) ) ]
33+ use localityd:: replica_materializer:: ReplicaMaterializationError ;
2934use localityd:: workspace_archive:: {
3035 WorkspaceArchiveLimits , WorkspaceArchiveSink , validate_workspace_tar,
3136} ;
@@ -1488,6 +1493,10 @@ fn marker_forged_after_journal_is_rejected_immediately_before_exchange() {
14881493 ) ;
14891494}
14901495
1496+ #[ cfg( all(
1497+ unix,
1498+ any( target_vendor = "apple" , target_os = "linux" , target_os = "android" )
1499+ ) ) ]
14911500#[ test]
14921501fn marker_forged_after_exchange_is_rejected_before_cleanup ( ) {
14931502 let fixture = fixture ( ) ;
@@ -1532,6 +1541,75 @@ fn marker_forged_after_exchange_is_rejected_before_cleanup() {
15321541 ) ;
15331542}
15341543
1544+ #[ cfg( not( all(
1545+ unix,
1546+ any( target_vendor = "apple" , target_os = "linux" , target_os = "android" )
1547+ ) ) ) ]
1548+ #[ test]
1549+ fn post_exchange_marker_hook_fails_closed_when_exchange_is_unsupported ( ) {
1550+ let fixture = fixture ( ) ;
1551+ let contract = contract ( & fixture) ;
1552+ let directory = TestDirectory :: new ( "unsupported-forged-marker-before-cleanup" ) ;
1553+ materialize_workspace_archive_durable (
1554+ ReplicaArchive :: new (
1555+ ReplicaArchiveEncoding :: Identity ,
1556+ Cursor :: new ( archive ( & fixture, & contract. control ) ) ,
1557+ ) ,
1558+ & directory. root ( ) ,
1559+ WorkspaceMaterializationLimits :: default ( ) ,
1560+ & contract. session ,
1561+ & contract. offer ,
1562+ )
1563+ . expect ( "publish old generation" ) ;
1564+ let active_receipt = load_workspace_publication_receipt ( & directory. root ( ) )
1565+ . expect ( "load active receipt" )
1566+ . expect ( "active receipt" ) ;
1567+ let staged = stage_workspace_archive (
1568+ ReplicaArchive :: new (
1569+ ReplicaArchiveEncoding :: Identity ,
1570+ Cursor :: new ( archive ( & fixture, & contract. control ) ) ,
1571+ ) ,
1572+ & directory. root ( ) ,
1573+ WorkspaceMaterializationLimits :: default ( ) ,
1574+ & contract. session ,
1575+ & contract. offer ,
1576+ )
1577+ . expect ( "stage replacement" ) ;
1578+ let staging_path = staged. staging_path ( ) . to_path_buf ( ) ;
1579+ let mut forge = ForgeMarkerAt {
1580+ checkpoint : WorkspacePublicationCheckpoint :: ReceiptDurable ,
1581+ generation : staging_path. clone ( ) ,
1582+ } ;
1583+
1584+ let error = publish_staged_workspace_with_hooks ( staged, & directory. root ( ) , & mut forge)
1585+ . expect_err ( "platform must fail closed before a post-exchange hook" ) ;
1586+ match error {
1587+ WorkspaceMaterializationError :: Filesystem ( ReplicaMaterializationError :: Publish ( source) ) => {
1588+ assert_eq ! ( source. kind( ) , io:: ErrorKind :: Unsupported )
1589+ }
1590+ other => panic ! ( "expected typed unsupported exchange error, got {other:?}" ) ,
1591+ }
1592+
1593+ assert_eq ! (
1594+ load_workspace_publication_receipt( & directory. root( ) )
1595+ . expect( "reload active receipt" )
1596+ . expect( "active receipt survives" ) ,
1597+ active_receipt
1598+ ) ;
1599+ assert_eq ! (
1600+ fs:: read( directory. root( ) . join( "Sales/README.md" ) ) . expect( "active root survives" ) ,
1601+ b"Public\n "
1602+ ) ;
1603+ assert ! ( !staging_path. exists( ) , "recovery removes unused staging" ) ;
1604+ assert ! (
1605+ !directory
1606+ . 0
1607+ . join( ".locality-Locality.publication.json" )
1608+ . exists( ) ,
1609+ "recovery clears the failed publication journal"
1610+ ) ;
1611+ }
1612+
15351613#[ test]
15361614fn reissued_same_profile_secret_recovers_but_rotated_key_fails_closed ( ) {
15371615 let fixture = fixture ( ) ;
0 commit comments