refactor(capture-kit): share the bounded provider app-log poller
Purpose and evidence
PR #2112 (8dad6500a31178e4f11ec32fe981ce069aa75041) adds an approximately 200-line app-log poller with the same acquisition rollback, polling state, tail overlap, finish memoization and disposal mechanics as Limrun. Fixes to these mechanics would require coordinated provider edits.
Evidence: Doublespeed poller and packages/provider-limrun/src/app-log-poller.ts (compare the exact merged base before implementation). The PR acknowledges a shared-capture follow-up but no issue currently owns it.
Required behavior and boundary
Extract the shared polling mechanics into existing packages/capture-kit, consistent with ADR 0019. A narrow input should contain a disposable reader, backend identity, app bundle ID, output path and the existing clock/output ports. Conceptually the reader provides readLogs(appBundleId, lineLimit, signal): Promise<string> plus async disposal.
Providers keep lease/session identity, authentication, descriptor codecs, connection verification, reconnect and exact-owner admission. Adapt provider-specific cancellation at their boundary: Limrun currently settles an uncancelable read through awaitLimrunOperation. Doublespeed's poller supplies a signal; its reconnect reader forwards it, while currentAppLogReader currently drops it. Characterize both reader paths and do not silently change cancellation semantics during extraction. Do not imply abort proves the underlying request ended. Preserve current behavior for late results and rejections.
Keep the current polling interval, read timeout, line limit, recovery states, output append/marker behavior, backend selection and user-visible errors. Provider-specific cleanup messages remain explicit input or adapter-owned formatting; shared policy must not name Limrun or Doublespeed. Correcting the reader cancellation difference is separate behavior-changing work. Do not turn this into a generic provider framework or broaden it to recording/perf.
Completion and validation
- First compare both implementations and list intentional differences. Stop if one common lifecycle would erase a provider-specific ownership or cancellation guarantee.
- Move unchanged common tests with their code, adding characterization only for uncovered boundary differences. Cover tail overlap/rotation, mark filtering, initial output acquisition failure, read failure/recovery, timeout with a signal-ignoring reader, late rejection, finish during an in-flight read, repeated finish and disposal failure.
- Retain provider-specific reconnect, descriptor and admission tests. Assert backend identity and existing error semantics independently of the new helper.
- Both providers use one implementation of the shared polling lifecycle and tail algorithm; source shrinks overall after adapters, and no obsolete copy remains. No new package or provider-specific identifier in shared polling policy.
pnpm check:affected --run passes. Provider integration and coverage remain GitHub-authoritative; record unavailable live provider evidence rather than claiming it. Report before/after production LOC and exact provider-specific differences retained.
Dependencies and readiness
Blocked by: #2112 landing. Re-audit both pollers at that merged head before implementation. This is a follow-up, not a reason to enlarge or block #2112. #2544 is daemon capture-admission extraction and #2541 provider-device admission; neither owns this polling mechanism.
Effort: M. Risk: medium (adoption, cancellation and disposal order).
refactor(capture-kit): share the bounded provider app-log poller
Purpose and evidence
PR #2112 (
8dad6500a31178e4f11ec32fe981ce069aa75041) adds an approximately 200-line app-log poller with the same acquisition rollback, polling state, tail overlap, finish memoization and disposal mechanics as Limrun. Fixes to these mechanics would require coordinated provider edits.Evidence: Doublespeed poller and
packages/provider-limrun/src/app-log-poller.ts(compare the exact merged base before implementation). The PR acknowledges a shared-capture follow-up but no issue currently owns it.Required behavior and boundary
Extract the shared polling mechanics into existing
packages/capture-kit, consistent with ADR 0019. A narrow input should contain a disposable reader, backend identity, app bundle ID, output path and the existing clock/output ports. Conceptually the reader providesreadLogs(appBundleId, lineLimit, signal): Promise<string>plus async disposal.Providers keep lease/session identity, authentication, descriptor codecs, connection verification, reconnect and exact-owner admission. Adapt provider-specific cancellation at their boundary: Limrun currently settles an uncancelable read through
awaitLimrunOperation. Doublespeed's poller supplies a signal; its reconnect reader forwards it, whilecurrentAppLogReadercurrently drops it. Characterize both reader paths and do not silently change cancellation semantics during extraction. Do not imply abort proves the underlying request ended. Preserve current behavior for late results and rejections.Keep the current polling interval, read timeout, line limit, recovery states, output append/marker behavior, backend selection and user-visible errors. Provider-specific cleanup messages remain explicit input or adapter-owned formatting; shared policy must not name Limrun or Doublespeed. Correcting the reader cancellation difference is separate behavior-changing work. Do not turn this into a generic provider framework or broaden it to recording/perf.
Completion and validation
pnpm check:affected --runpasses. Provider integration and coverage remain GitHub-authoritative; record unavailable live provider evidence rather than claiming it. Report before/after production LOC and exact provider-specific differences retained.Dependencies and readiness
Blocked by: #2112 landing. Re-audit both pollers at that merged head before implementation. This is a follow-up, not a reason to enlarge or block #2112. #2544 is daemon capture-admission extraction and #2541 provider-device admission; neither owns this polling mechanism.
Effort: M. Risk: medium (adoption, cancellation and disposal order).