refactor(daemon): one capture-input builder and one admit-then-bind step

Merged
TH
3mo ago
+389-321

Summary

Behaviour-neutral cleanup around the request-bound snapshot capture path. No descriptor changes its platform execution, no contract surface is added, and no workspace package is touched — the diff is five source files plus one test-helper import path. Three things:

  • One capture-input builder. buildRuntimeCaptureInput moves out of snapshot-runtime-binding.ts into snapshot-runtime-capture-input.ts — the one place a daemon request becomes neutral CaptureSnapshotInput. Its parameter list is exactly what snapshot/diff pass; nothing speculative.
  • One named admit-then-bind step. The snapshot/diff resolver's admission sequence — side-effect-free facts inspection, refusal before any binding, then exactly one bind on the device the admission was minted for — becomes admitAndBindSnapshotCapture, module-private. Same behaviour, one place.
  • handlers/find.ts splits, 600 → 346 lines. It was already past the 300-line tripwire and answered three questions at once. The target-capture policy (interactive-only tree plus find's two sparse-recovery re-captures) and the match-resolution pipeline (locator/selector matching, on-screen preference, interactive ranking, the ambiguous-match error) move to focused modules. Pure refactor: identical behaviour, response shapes, capture path, and tests.

Scope note

This PR was re-scoped twice under review, each time correctly:

  1. It began as the find cutover. Declaring find device-runtime claims its whole platform-execution projection (ADR 0019 §6), while find focus and find type still reach the device through dispatchCommand — Wave 5 surfaces — and a third edge of the same class, find <q> get text → dispatchCommand(device, 'read'), is shared with get. find keeps LEGACY_PLATFORM_EXECUTION, its capability bucket, its requireCommandSupported('find', …) admission, and its HARMONYOS_SUPPORTED_COMMANDS / WEB_QUERY_COMMANDS entries. Rule id R35 is withdrawn from the cutover table entirely rather than left half-claimed, and stays reserved for find's real cutover.

  2. It then carried the selector capture seam, and then CaptureSnapshotInput.signal with its exported captureSnapshotSignal helper and Apple/Linux composition. Both are now gone, removed for the same reason: ADR 0019 §10 prohibits a facet PR with no consuming command, and §6 admits an early behaviour-neutral substrate only when it is dead-code clean.

    On the signal contract specifically: an earlier revision of this body carried it with a caveat that no caller set the field. Review escalated that caveat to a blocker, and rightly — an unset contract field means production exercises only the undefined branch, and this PR could not carry a regression proving the per-poll abort and quiescence behaviour it claimed. The field, the helper, and the Apple/Linux composition sites now land in #1875 (wait), whose runWithinWaitDeadline is the route that actually sets it and which owns the regression proving the deadline aborts an in-flight capture, waits for it to quiesce, and produces capture-stalled rather than target-absent. The selector capture seam went to #1877 (get) on the same principle. Both were handed over as reviewed patches with design notes.

What remains has live production consumers today and is dead-code clean: check:production-exports is unchanged from main.

Validation

pnpm check:affected --run from a clean committed tree. Behaviour neutrality is carried by the existing suites rather than new assertions — snapshot, diff, and find keep their existing paths, and every touched command's test file is byte-identical to main, so they exercise the split find route unchanged. pnpm check:layering is green with the migrated-command list identical to main.

Tradeoffs and known gaps

  • Both extractions have a single consumer today. They are named steps, not deduplication — the second caller is the selector family, arriving with get. admitAndBindSnapshotCapture is deliberately module-private so this PR does not grow the export surface; get's patch flips it to exported, a one-word change.
  • Source size: root production TypeScript +3,394 B, workspace-package production TypeScript +0 B. Of the root figure, +1,250 B is the find.ts three-way split (module headers, imports, exports — the unavoidable cost of a split the tripwire required) and +2,144 B the two extractions, of which roughly 1.4 kB is the capture-input builder moving rather than new code. Packaged deltas: see the CI Size report on this head rather than a local comparison.
  • No behaviour, CLI, help, or docs change, so nothing in website/docs/** or skills/** was touched.

Touched files: 6. Scope narrowed three times; it never expanded.