@@ -2720,6 +2720,79 @@ fanout.layer("ProviderServiceLive fanout", (it) => {
27202720 } ) ,
27212721 ) ;
27222722
2723+ it . effect ( "rejects an exact anchor result after its provider generation retires" , ( ) =>
2724+ Effect . gen ( function * ( ) {
2725+ const provider = yield * ProviderService . ProviderService ;
2726+ const threadId = asThreadId ( "thread-generation-replaced-absolute-anchor" ) ;
2727+ yield * provider . startSession ( threadId , {
2728+ provider : CODEX_DRIVER ,
2729+ providerInstanceId : codexInstanceId ,
2730+ threadId,
2731+ runtimeMode : "full-access" ,
2732+ } ) ;
2733+
2734+ const originalCapabilities = fanout . codex . adapter . capabilities ;
2735+ const originalAbsoluteRollback = fanout . codex . adapter . absoluteConversationRollback ;
2736+ const originalRuntimeFence = fanout . codex . adapter . runtimeFence ;
2737+ const current = yield * Ref . make ( true ) ;
2738+ const captureEntered = yield * Deferred . make < void > ( ) ;
2739+ const releaseCapture = yield * Deferred . make < void > ( ) ;
2740+ Object . assign ( fanout . codex . adapter , {
2741+ capabilities : { ...originalCapabilities , conversationRollback : "absolute" } ,
2742+ absoluteConversationRollback : {
2743+ isAvailable : ( ) => Effect . succeed ( true ) ,
2744+ captureAnchor : ( ) =>
2745+ Deferred . succeed ( captureEntered , undefined ) . pipe (
2746+ Effect . andThen ( Deferred . await ( releaseCapture ) ) ,
2747+ Effect . as ( { anchor : { privateLeaf : "stale-private" } , digest : "stale-digest" } ) ,
2748+ ) ,
2749+ inspectAnchor : ( ) => Effect . succeed ( { anchor : { } , digest : "unused" } ) ,
2750+ applyAnchor : ( ) => Effect . void ,
2751+ releaseAnchor : ( ) => Effect . void ,
2752+ } ,
2753+ runtimeFence : {
2754+ generation : { } ,
2755+ configRevision : "private-anchor-test-revision" ,
2756+ isCurrent : Ref . get ( current ) ,
2757+ } ,
2758+ } ) ;
2759+ yield * Effect . addFinalizer ( ( ) =>
2760+ Effect . sync ( ( ) => {
2761+ Object . assign ( fanout . codex . adapter , { capabilities : originalCapabilities } ) ;
2762+ if ( originalAbsoluteRollback === undefined ) {
2763+ delete ( fanout . codex . adapter as { absoluteConversationRollback ?: unknown } )
2764+ . absoluteConversationRollback ;
2765+ } else {
2766+ Object . assign ( fanout . codex . adapter , {
2767+ absoluteConversationRollback : originalAbsoluteRollback ,
2768+ } ) ;
2769+ }
2770+ if ( originalRuntimeFence === undefined ) {
2771+ delete ( fanout . codex . adapter as { runtimeFence ?: unknown } ) . runtimeFence ;
2772+ } else {
2773+ Object . assign ( fanout . codex . adapter , { runtimeFence : originalRuntimeFence } ) ;
2774+ }
2775+ } ) ,
2776+ ) ;
2777+
2778+ const anchorFiber = yield * provider . captureConversationAnchor ! ( {
2779+ threadId,
2780+ binding : {
2781+ kind : "source" ,
2782+ sourceRevision : 1 ,
2783+ checkpointRef : "refs/t3/checkpoints/private/source" as never ,
2784+ checkpointOid : "a" . repeat ( 40 ) ,
2785+ turnId : null ,
2786+ } ,
2787+ } ) . pipe ( Effect . forkChild ) ;
2788+ yield * Deferred . await ( captureEntered ) ;
2789+ yield * Ref . set ( current , false ) ;
2790+ yield * Deferred . succeed ( releaseCapture , undefined ) ;
2791+
2792+ assert . isTrue ( Exit . isFailure ( yield * Fiber . await ( anchorFiber ) ) ) ;
2793+ } ) ,
2794+ ) ;
2795+
27232796 it . effect ( "keeps Stop authoritative while an account transition is starting" , ( ) =>
27242797 Effect . gen ( function * ( ) {
27252798 const provider = yield * ProviderService . ProviderService ;
@@ -3883,6 +3956,23 @@ describe("agent browser access", () => {
38833956 let recoveredSession : ProviderSession | undefined ;
38843957 const recoveryAdapter : ProviderAdapterShape < ProviderAdapterError > = {
38853958 ...codex . adapter ,
3959+ capabilities : {
3960+ ...codex . adapter . capabilities ,
3961+ conversationRollback : "absolute" ,
3962+ } ,
3963+ absoluteConversationRollback : {
3964+ isAvailable : ( ) => Effect . succeed ( true ) ,
3965+ captureAnchor : ( ) => Effect . succeed ( { anchor : { } , digest : "unused" } ) ,
3966+ inspectAnchor : ( ) => Effect . succeed ( { anchor : { } , digest : "unused" } ) ,
3967+ applyAnchor : ( ) => Effect . void ,
3968+ releaseAnchor : ( ) => Effect . void ,
3969+ prepareRecovery : ( input ) =>
3970+ Effect . sync ( ( ) => {
3971+ assert . deepEqual ( input . sourceAnchor , { privateLeaf : "restart-source-private" } ) ;
3972+ assert . deepEqual ( input . desiredAnchor , { privateLeaf : "restart-target-private" } ) ;
3973+ order . push ( "quarantine" ) ;
3974+ } ) ,
3975+ } ,
38863976 recoverSession : ( input ) =>
38873977 Effect . gen ( function * ( ) {
38883978 assert . isDefined ( McpProviderSession . readMcpProviderSession ( threadId ) ) ;
@@ -3943,10 +4033,21 @@ describe("agent browser access", () => {
39434033 ) ;
39444034 yield * Effect . yieldNow ;
39454035
3946- yield * provider . recoverRestartSessions ! ( ) ;
4036+ yield * provider . recoverRestartSessions ! ( {
4037+ pendingAbsoluteRollbacks : new Map ( [
4038+ [
4039+ threadId ,
4040+ {
4041+ sourceAnchor : { privateLeaf : "restart-source-private" } ,
4042+ desiredAnchor : { privateLeaf : "restart-target-private" } ,
4043+ expectedAnchor : { privateLeaf : "restart-source-private" } ,
4044+ } ,
4045+ ] ,
4046+ ] ) ,
4047+ } ) ;
39474048 yield * Fiber . join ( consumer ) ;
39484049
3949- assert . deepEqual ( order , [ "mcp" , "recover" , "activate" ] ) ;
4050+ assert . deepEqual ( order , [ "mcp" , "recover" , "quarantine" , " activate"] ) ;
39504051 const [ recoveredEvent ] = yield * Ref . get ( recoveredEvents ) ;
39514052 assert . equal ( recoveredEvent ?. eventId , asEventId ( "evt-restart-adopted-output" ) ) ;
39524053 assert . equal ( recoveredEvent ?. turnId , recoveredTurnId ) ;
0 commit comments