@@ -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