refactor(capture-kit): lift the iOS snapshot route into the capture-kit router
Summary
Step 3 of #4205. This PR moves the iOS snapshot route core into packages/capture-kit/src/snapshot-router/, the platform-neutral router from ADR 0038. Both iOS Simulator channels bind to it: the local guest bridge (createAppleSnapshotRoute) and the runner-served bridge (createRunnerSnapshotBridge). The Apple route core is deleted. No capture routes twice, no adapter wraps the old route, and no module re-exports a moved declaration.
Refresh design
The router keeps apart the two operations that origin/main kept apart:
- Forced rediscovery. Every channel declares a required
refreshTarget. The stale-target fallback always calls it. On origin/main both channels forced a rediscovery here. The first revision of this PR let the local channel use ordinary resolution, which could republish the cached generation after the bridge reported a newer one. This revision fixes that. - Refresh before a disabled circuit is trusted. Every channel declares a required
resolutionVerifiesIdentity. Local:true, because its resolver checks the cached process start time. Runner:false, because itsresolveTargetreturns the cache without asking the runner. Only the runner refreshes before it trusts a disabled circuit, so the local channel keeps main's cost on that path. - The local resolver no longer takes a
'refresh'flag. It now has a separaterefreshTarget, the same shape as the runner bridge.snapshot-target.test.tscallsrefreshTargetand keeps its assertions. - New test in
snapshot-route.test.ts: with the real local resolver, after a stale-target refusal (same pid and start time, new launch label) the fallback stamps the rediscovered generation. If the fallback usesresolveTarget, this test and the mocked stale-target test both fail (mutation checked). If either channel'sresolutionVerifiesIdentityis flipped, 2 local route tests or 3 runner tests fail.
Shims removed
snapshot-source/types.ts and snapshot-source/errors.ts no longer re-export SnapshotSourceFailure, SnapshotSourceFailureKind, SnapshotSourceOutcome or SnapshotSourceError. The snapshot-source facade no longer re-exports the moved types, and its fallow exemption no longer lists SnapshotSourceFailureKind. Every importer now imports from @agent-device/capture-kit/snapshot-source-channel. The imports are type-only or reuse an edge that already existed through errors.ts, so the eager closure does not grow.
What has fewer owners or paths than origin/main
- Constructors: 3 → 1.
createSnapshotBridgeRoute,createSnapshotBridgeRouteCoreandcreateAppleSnapshotRoute's use of the core → onecreateSnapshotRouter.createAppleSnapshotRouteis now only the local channel binding, about 70 lines (it was 833). - Channel declarations: the runner channel had one inline copy in
runner-runtime.tsand three copies inrunner-snapshot-bridge.test.ts. NowcreateRunnerSnapshotBridgereturns the channel itself, so there is one declaration. - Host fields the route reads: 2 → 1. On main the route read
snapshot.presentIosAcquisitionandappleApplications.hasLiveRunnerSession, and the runner had to supply a live-session port. Now the router takes only the presenter function. The live-runner check is part of the local channel'sawaitsDiscovery. - Refresh paths: main had a
'refresh'mode flag on both resolvers and also an optionalrefreshTarget?that the runner filled by calling its own resolver with the flag. Now each channel has onerefreshTargetand no flag. When the router refreshes is one explicit declaration (resolutionVerifiesIdentity). It no longer depends on whether an optional member is present. - Discovery wait: main used the channel's
isDiscoveryPending?and the host'shasLiveRunnerSession. Now it is one member,awaitsDiscovery?.
What did not get simpler: the lift itself did not change the settlement path count. Step 2 had already merged the fallback settlers into one settleFallback. Eligibility still has one owner, routeEligibility. That owner now reads servesDevice from the channel instead of calling hasSimulatorBridge directly. That member is a cost of platform neutrality: both channels pass the same function.
Validation
Tested SHA 7805744edd.
pnpm check:affected --run --head 7805744edd: all runnable checks passed (exactCommit=true). GitHub-authoritative checks were skipped locally.- vitest for capture-kit, platform-apple,
test/repo-guards/snapshot-bridge-refusals.test.tsand the daemonsnapshot-runtime-route.test.ts: all passed. pnpm typecheck,pnpm lint,pnpm check:layeringandnode --test scripts/layering/*.test.ts(477) passed.- The router-boundary positive control now reads the tracked manifests and covers
apple-runnerandtv-cdp(Cubic). A control run that removedtv-cdpfrom the policy failed the test. - Live parity (local iOS Simulator, Limrun iOS): the coordinator reruns these checks.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.