refactor(capture-kit): encode post-gesture stabilization baseline provenance
Summary
Encode post-gesture stabilization baseline provenance in a discriminated union so a post-action rebased baseline can never be treated as pre-action evidence — the bug class of #3868 (a backend flip mid-poll laundered a rebased baseline into a bogus stale/no-effect claim). Replaces the baselineRebased boolean, the parallel baseline locals, and the needsBaselineDistrust parameter with PostGestureBaseline (pre-action | post-action variants carrying signature + comparison key together), a PostGestureStabilityDecision union where only accept-stale carries the pre-action payload, and a pre-action-typed baseline on the session pending record whose sole writer is the distrust authorization. 8 files. Behavior and timing preserved exactly: rebase only at the quiet-pair checkpoint, the rebasing pair cannot settle, further key changes rebase again, same budgets/minPolls/timeout/marker retirement. No CLI/API change. Ref #3868, #3875.
Production LOC (3 modules): 1037 → 1065 (+28); the invariant moves from boolean plumbing to types and their documentation.
Validation
Tested SHA: 2d0c931c2f (base 6ea9f81733, merged with origin/main 483a130bd2).
pnpm check:affected --run --head 2d0c931c2f: all runnable checks passed, exactCommit receipt (typecheck/layering/fallow/build/vitest-related). Prior receipts at 54fe25e662 and ea374a29ff lineage also clean.tsc -b tsconfig.typecheck.jsonclean; a scratch demo of six invalid states was rejected by typecheck (post-action intopending.baseline, payload access ondistrust, keyless baseline, etc.); the durable half now lives as an@ts-expect-errortest inpost-gesture-stability.test.ts.- Focused suites 54/54;
packages/daemon/src/core/__tests__+packages/capture-kit/src1487 passed. - Historical red: #3868's regression tests applied to
dffcd864c3^fail there ("a quiet pair on a rebased baseline settles instead of distrusting it to the cap"). - Mutations on head each fail existing tests: rebase keeps
pre-action(6 fail); settle on the rebasing pair (5 fail); subset classifier replaces full-surface equality (2 fail). - Live simulator (iPhone 17 Pro / iOS 27, owned UDID, forced
private-axroute, recorded at ea374a29ff; the later merge touched only Swift runner test files and one daemon test, no TS runtime): a no-op scroll's pending baseline reachespost_gesture_snapshot_stale_accept(6 attempts, ~4s cap) with the no-effect warning — identical to base (same phase, attempts, ~3.9–4.0s). A poll whose serving surface flips mid-loop logspost_gesture_snapshot_baseline_rebasedthen settles viapost_gesture_snapshot_stabilized(3 attempts, 1770ms) with no stale accept and no no-effect claim — the #3868 class bounded at the rebase, live. Settled surfaces return in 449–550ms on base and head alike.
CI obligations: device lanes as configured. Unresolved risks: none identified beyond shared-type churn.
🤖 Generated with Claude Code