refactor(capture-kit): share the bounded app-log poller (#2616)

Draft
TH
8d ago
+682-305

Refs #2616

Extracts the bounded app-log poller into @agent-device/capture-kit/app-log-polling and makes Limrun its first consumer. Deduplicates acquisition rollback, the poll state machine, the bounded read with its own settle-on-timeout, mark filtering, KMP tail-overlap de-duplication, finish memoization, disposal, and cleanup-pending semantics.

Repurposed. This PR previously based on the #2112 Doublespeed stack to convert both providers at once. That base predates the released v0.21.x wire ledger, so Compatibility & Provenance could never go green here (its "removing wire surface a released peer still sends … an ack cannot cover it" group is exactly the behind-a-release case). Doublespeed is not merged, so this now lands on main alone and Doublespeed's 30-line adoption follows #2112.

Scope

  • packages/capture-kit/src/app-log-polling.ts — the shared poller, published as the ./app-log-polling subpath.
  • packages/provider-limrun/src/app-log-poller.ts — 182 lines become a 32-line adapter. Provider identity stays where it belongs: backendForReader and the cleanup wording are adapter-local.
  • packages/provider-limrun/src/request-cancellation.ts — awaitLimrunOperation loses export; this poller was its only external caller, and it is still used by the request-operation drain in the same module.

Two deliberate narrowing decisions

  • Subpath export, not the façade. capture-kit/src/index.ts falls under ADR-0019's merge-base NO-GROWTH rule, so an eager façade export would grow a locked closure. The subpath matches the existing ./ios-snapshot-acquisition convention, and all 668 eager-closure-budgets cases pass with no pin edit.
  • AppLogPollerReader.readLogs takes no signal. main's Limrun reader is uncancelable and is currently called without one, so the shared contract forwards nothing and stays honest. The AbortController is internal: it drives clock.sleep and settles the bounded read on timeout, which is what makes teardown safe against a hung provider read. Widening the reader contract belongs with the first provider that can actually honour it.

The already-aborted path keeps the abandoned read's rejection handled, preserving awaitLimrunOperation's behaviour rather than the leakier shape this branch had on the old base.

Checks

check:affected --run passes. Locally: typecheck, check:layering (new subpath declared in the package-boundaries enumeration), pnpm gate fallow --base <main> → no issues in 8 changed files, lint, 18 app-log/request-cancellation tests, 668 eager-closure cases, and daemon-wire-compat → 178 declarations, 0 changed, 0 removed, 0 added against v0.21.6.

Follow-up

Doublespeed converts on top of #2112; #2616 stays open until both providers read one implementation.