@@ -205,6 +205,20 @@ extension RunnerTests {
205205 }
206206 }
207207
208+ /// The reason a capture's verdict carries. An XCTest-backed tier that recovered a deferred plan
209+ /// ran on the bounded probe's short slice, so it reports that constraint ('budget') rather than
210+ /// the pre-selection ('deferred') the plan was seeded with.
211+ static func recoveredVerdictReason (
212+ backend : SnapshotBackendKind ,
213+ state : SnapshotXCTestChannelPlanState ,
214+ firstFailure : ( reason : String , code : String ) ?
215+ ) -> ( reason : String , code : String ) ? {
216+ guard backend . usesXCTestAccessibilityChannel , state == . deferredToIndependentBackend else {
217+ return firstFailure
218+ }
219+ return xcTestChannelStateFirstFailure ( . boundedXCTestProbe )
220+ }
221+
208222 /// Pure gate: a capture is planned as penalized when the channel penalty is
209223 /// active OR the daemon pinned the private-AX backend (same-backend evidence
210224 /// probe) — both mean "do not enter XCTest tree work first, and stamp the
@@ -250,13 +264,21 @@ extension RunnerTests {
250264 }
251265 let availablePlan = plan . filter { availableBackends . contains ( $0 ) }
252266 let recoveryPlan = availablePlan . filter { ! $0 . usesXCTestAccessibilityChannel }
253- let boundedProbePlan = availablePlan . filter ( \. usesXCTestAccessibilityChannel )
254- // The independent backend reads only the app it can match as the active AX application, so
255- // an out-of-process surface over the app (the Save Password sheet) leaves it empty for the
256- // whole penalty. The bounded XCTest probe stays behind it as the last tier.
267+ if ! recoveryPlan . isEmpty {
268+ // The independent backend reads only the app it can match as the active AX application, so
269+ // an out-of-process surface over the app (the Save Password sheet) leaves it empty for the
270+ // whole penalty. The tree, on the bounded probe's short slice, stays behind it; the query
271+ // sweep does not, since its grind is what the penalty keeps off the main thread.
272+ return EffectiveSnapshotCapturePlan (
273+ plan : recoveryPlan + availablePlan . filter { $0 == . recursiveTree } ,
274+ xCTestChannelState : . deferredToIndependentBackend ,
275+ treeCaptureSliceBudgetOverride : Self . penalizedXCTestProbeTreeSliceBudget ,
276+ preferredBackend : nil
277+ )
278+ }
257279 return EffectiveSnapshotCapturePlan (
258- plan : recoveryPlan + boundedProbePlan ,
259- xCTestChannelState : recoveryPlan . isEmpty ? . boundedXCTestProbe : . deferredToIndependentBackend ,
280+ plan : availablePlan . filter ( \ . usesXCTestAccessibilityChannel ) ,
281+ xCTestChannelState : . boundedXCTestProbe ,
260282 treeCaptureSliceBudgetOverride : Self . penalizedXCTestProbeTreeSliceBudget ,
261283 preferredBackend : nil
262284 )
@@ -387,6 +409,11 @@ extension RunnerTests {
387409 }
388410
389411 let recovered = kind != effectivePlan . first || effective . xCTestChannelState != . normal
412+ let verdictReason = Self . recoveredVerdictReason (
413+ backend : kind ,
414+ state : effective . xCTestChannelState ,
415+ firstFailure : firstFailure
416+ )
390417 if recovered {
391418 NSLog (
392419 " AGENT_DEVICE_RUNNER_SNAPSHOT_RECOVERED backend=%@ reason=%@ " ,
@@ -398,7 +425,7 @@ extension RunnerTests {
398425 capture ,
399426 backend : kind ,
400427 state : recovered ? . recovered : . healthy ,
401- reason : recovered || firstFailure ? . code == " requested-backend " ? firstFailure : nil
428+ reason : recovered || firstFailure ? . code == " requested-backend " ? verdictReason : nil
402429 )
403430 }
404431
0 commit comments