fix(ios): report the real failure reason on a tree-pinned capture terminal

TH
10h ago
+132-6

Summary

A regular iOS snapshot pinned to the XCTest tree backend (internal snapshotPreferredBackend, used by the perf evidence probe) on a screen whose tree rejects with the #758 kAXErrorIllegalArgument signature ended sparse while reporting snapshotQuality.reasonCode: requested-backend. The plan seeded the pin decision into its first-failure slot, so the real tier failure never replaced it: callers read a hostile-screen rejection as "pin honored, nothing degraded", and the host's viewport refusal hid the typed reason.

The pin's answer now travels in its own slot. A tier that actually failed owns the verdict reason on failing terminals; an accepted payload still reports requested-backend (what snapshot-forced-tree reads as proof the pin was honored). The breaker's deferred/budget seeds keep their established contract. Touched files: 2 Swift files (runner classifier + in-bundle tests). No scope expansion; unpinned, raw, and private-AX-pin behavior unchanged.

Validation

Head 3dad2ba9fb (base f3d2619abe).

  • pnpm check:affected --run --head 3dad2ba9fb…: all runnable checks passed, exactCommit=true (receipt run-hayMl1); device/native lanes GitHub-authoritative.
  • New Swift in-bundle tests: red on base (requested-backend ≠ ax-rejected), green at head; 5 adjacent plan/pin/penalty tests pass; pnpm check:xctest-selection clean.
  • Live on iPhone-17-Pro simulator (fixture app, heavy feed), same forced-backend command:
    • base: pinned-tree regular → sparse/tree/requested-backend (bug)
    • head: pinned-tree regular → sparse/tree/ax-rejected with the real kAXErrorIllegalArgument reason
    • unpinned regular healthy (95 nodes) and pinned-tree raw unchanged both sides.

Unresolved risk: none known; CI device lanes confirm.

🤖 Generated with Claude Code