@@ -4725,6 +4725,77 @@ function startFreshShieldsDownTimer(input: {
47254725 }
47264726}
47274727
4728+ function completeInterruptedShieldsDown (
4729+ sandboxName : string ,
4730+ state : LoadedShieldsState ,
4731+ retainedProviderTarget : AgentConfigTarget | null ,
4732+ opts : ShieldsDownOpts ,
4733+ ) : boolean {
4734+ if ( ! state . shieldsDown ) return false ;
4735+
4736+ // Provider release deliberately precedes route convergence and the final
4737+ // timer-bound transition commit. A process can therefore die after the
4738+ // durable provider claim is gone while the exact host transition remains
4739+ // in preparing. Treat that marker as recovery authority too: verify (or
4740+ // repair) mutable posture, converge the route, then commit it active.
4741+ const completionTarget =
4742+ retainedProviderTarget ??
4743+ resolveReleasedProviderShieldsDownTarget (
4744+ sandboxName ,
4745+ state ,
4746+ opts . allowLegacyHermesProtocol === true ,
4747+ ) ;
4748+ if ( ! completionTarget ) return false ;
4749+
4750+ const completion = prepareRecoveredShieldsDownCompletion ( sandboxName , completionTarget , state ) ;
4751+ // The provisional DOWN record can outlive a process that lost its
4752+ // provider-unlock response. Recovery first restores the retained plan's
4753+ // restrictive rollback. Reconcile the recorded mutable posture and verify
4754+ // it before treating this retry as complete.
4755+ try {
4756+ applyRecoveredShieldsDownForwardPolicy ( sandboxName , completion ) ;
4757+ if ( retainedProviderTarget ) {
4758+ runHermesProviderProtectionTransition (
4759+ sandboxName ,
4760+ retainedProviderTarget ,
4761+ "locked" ,
4762+ "locked" ,
4763+ ) ;
4764+ }
4765+ if ( completion . authority ) {
4766+ assertRecoveredShieldsDownAuthority ( sandboxName , completion , completion . authority . phase ) ;
4767+ }
4768+ unlockAgentConfigUnderMutationLock (
4769+ sandboxName ,
4770+ completionTarget ,
4771+ false ,
4772+ "provider-state-mutation-v2" ,
4773+ ) ;
4774+ if ( completion . authority ) {
4775+ assertRecoveredShieldsDownAuthority ( sandboxName , completion , completion . authority . phase ) ;
4776+ }
4777+ finishRecoveredHermesShieldsDown ( sandboxName , completion ) ;
4778+ } catch ( error ) {
4779+ return failRecoveredHermesShieldsDown (
4780+ sandboxName ,
4781+ completionTarget ,
4782+ state ,
4783+ completion ,
4784+ opts . allowLegacyHermesProtocol === true ,
4785+ error ,
4786+ opts . throwOnError ,
4787+ ) ;
4788+ }
4789+ if ( ! completion . alreadyCommitted ) {
4790+ if ( completion . authority ) {
4791+ assertRecoveredShieldsDownAuthority ( sandboxName , completion , "active" ) ;
4792+ }
4793+ appendAuditEntry ( completion . audit ) ;
4794+ }
4795+ console . log ( ` Recovered interrupted config unlock for ${ sandboxName } .` ) ;
4796+ return true ;
4797+ }
4798+
47284799function shieldsDownWithoutHostLock ( sandboxName : string , opts : ShieldsDownOpts = { } ) : void {
47294800 validateName ( sandboxName , "sandbox name" ) ;
47304801
@@ -4751,73 +4822,7 @@ function shieldsDownWithoutHostLock(sandboxName: string, opts: ShieldsDownOpts =
47514822 recoveredProviderTarget = retainedProviderTarget ;
47524823 }
47534824 const initialMode = deriveShieldsMode ( state , state . _hasStateFile ) ;
4754- if ( state . shieldsDown ) {
4755- // Provider release deliberately precedes route convergence and the final
4756- // timer-bound transition commit. A process can therefore die after the
4757- // durable provider claim is gone while the exact host transition remains
4758- // in preparing. Treat that marker as recovery authority too: verify (or
4759- // repair) mutable posture, converge the route, then commit it active.
4760- const completionTarget =
4761- retainedProviderTarget ??
4762- resolveReleasedProviderShieldsDownTarget (
4763- sandboxName ,
4764- state ,
4765- opts . allowLegacyHermesProtocol === true ,
4766- ) ;
4767- if ( completionTarget ) {
4768- const completion = prepareRecoveredShieldsDownCompletion (
4769- sandboxName ,
4770- completionTarget ,
4771- state ,
4772- ) ;
4773- // The provisional DOWN record can outlive a process that lost its
4774- // provider-unlock response. Recovery first restores the retained plan's
4775- // restrictive rollback. Reconcile the recorded mutable posture and
4776- // verify it before treating this retry as complete.
4777- try {
4778- applyRecoveredShieldsDownForwardPolicy ( sandboxName , completion ) ;
4779- if ( retainedProviderTarget ) {
4780- runHermesProviderProtectionTransition (
4781- sandboxName ,
4782- retainedProviderTarget ,
4783- "locked" ,
4784- "locked" ,
4785- ) ;
4786- }
4787- if ( completion . authority ) {
4788- assertRecoveredShieldsDownAuthority ( sandboxName , completion , completion . authority . phase ) ;
4789- }
4790- unlockAgentConfigUnderMutationLock (
4791- sandboxName ,
4792- completionTarget ,
4793- false ,
4794- "provider-state-mutation-v2" ,
4795- ) ;
4796- if ( completion . authority ) {
4797- assertRecoveredShieldsDownAuthority ( sandboxName , completion , completion . authority . phase ) ;
4798- }
4799- finishRecoveredHermesShieldsDown ( sandboxName , completion ) ;
4800- } catch ( error ) {
4801- return failRecoveredHermesShieldsDown (
4802- sandboxName ,
4803- completionTarget ,
4804- state ,
4805- completion ,
4806- opts . allowLegacyHermesProtocol === true ,
4807- error ,
4808- opts . throwOnError ,
4809- ) ;
4810- }
4811- if ( ! completion . alreadyCommitted ) {
4812- if ( completion . authority ) {
4813- assertRecoveredShieldsDownAuthority ( sandboxName , completion , "active" ) ;
4814- }
4815- appendAuditEntry ( completion . audit ) ;
4816- }
4817- console . log ( ` Recovered interrupted config unlock for ${ sandboxName } .` ) ;
4818- return ;
4819- }
4820- }
4825+ if ( completeInterruptedShieldsDown ( sandboxName , state , retainedProviderTarget , opts ) ) return ;
48214826
48224827 const timeoutSeconds = parseDuration ( opts . timeout || `${ DEFAULT_TIMEOUT_SECONDS } ` ) ;
48234828 const reason = opts . reason || null ;
0 commit comments