ADR 0002: same-executable discovery helper for the app runtime

Date: 2026-10-14. Amends ADR 0001.

Decision

Blocking shell and UI Automation work runs in a short-lived helper process that is the app’s own executable started with one private marker (--elevenclock-rs-discovery-helper <generation>, parsed only by instance::Role). The app never calls UIA or enumerates Explorer’s children on its UI thread, and never claims a COM call can be cancelled: the only cancellation is terminating the process and observing its exit.

Why the same executable: the embedded per-monitor-v2 manifest (rectangles are wrong without it) and the exact build already ship; no second binary or installer entry is needed. Why a marker rather than a mode flag in the environment: a marker is visible in the process list and cannot be inherited by accident into unrelated children.

Activation (what makes the marker safe)

  1. The role is chosen before any single-instance or UI work, so a helper never takes the instance mutex, opens Settings, or touches preferences.
  2. The helper does nothing until it reads one of two exact handshake lines (r08-go:native, r08-go:uia) from stdin. The parent writes it only after assigning the process to a kill-on-close, one-process Windows job. Parent loss before assignment is EOF, and EOF exits without native work. Running the marker by hand (it is not a user command, is not documented for users and prints nothing) therefore reads no handshake and exits with code 2.
  3. The helper re-checks per-monitor-v2 awareness and exits otherwise (code 3).
  4. Output is one JSON report on a stdout redirected to a parent-created create_new temporary file, read only after observed exit, capped at 256 KiB and removed on every path. No HWND, HMONITOR or COM pointer crosses the boundary as a capability; the report’s hwnd field is a hint the UI thread revalidates (class, owning process) before every use.

Lifetime rules (all covered by tests)

Discovery mode

The handshake also selects the helper’s discovery mode. The parent always requests r08-go:uia, so the UIA inventory runs and the clock candidate may supply a clock rectangle after geometric validation; the parent decides, never the helper. The r08-go:native mode (no UIA, no COM) remains for supervision tests whose fixture process cannot run UIA. There is no build flag or runtime gate.

Residual risks and review points

Measured COM behaviour

Measured by tests/uia_lifetime.rs on hosted Server 2025 x64 and Windows 11 ARM64 (native and emulated x64):

No lifetime assumption or timeout needed changing from these measurements.