Commit Graph

1715 Commits

Author SHA1 Message Date
Michał Pierzchała 7672454304 refactor: move interaction handlers behind daemon facade 2026-09-01 21:48:10 +02:00
Michał Pierzchała b15121ffc8 test(ios): establish snapshot convergence baselines and permanent evidence (#2204)
* test(ios): add snapshot convergence evidence harness

* fix(ios): satisfy benchmark CI guards

* fix(ios): constrain benchmark proxy routes

* fix(ios-benchmark): enforce cell admission evidence

* fix(ios-benchmark): protect benchmark state ownership

* fix(ios-benchmark): use proxy port flag

* fix(ios-benchmark): let proxy choose an ephemeral port

* test(ios-benchmark): keep CLI process seam local

* fix(ios-benchmark): parse proxy startup envelope

* fix(ios-benchmark): bind proxy lease to simulator

* fix(ios-benchmark): keep fresh proxy CLI sessions isolated

* fix(ios-benchmark): preserve async timeout evidence

* docs(ios-benchmark): retain exact-head evidence

* test(ios): reveal offscreen alert fixture controls

* test(ios): reset alert between relaunch samples

* test(ios): admit native alert snapshots

* docs(ios): publish snapshot convergence corpus

* chore(ios): format benchmark evidence

* fix(ios-benchmark): admit proxy fixture anchors

* docs(ios): republish exact-head benchmark corpus

* fix(size): make publish asset evidence hermetic

* style(size): format package evidence test

* test(size): update publish preparation contracts

* fix: retire stale utils layering zone

* test: pin shared publish asset owner

* test: verify preserved size reporter closure

* fix: move mutation ownership to snapshot module

* test(ios): add snapshot convergence evidence harness

* fix(ios): satisfy benchmark CI guards

* fix(ios): constrain benchmark proxy routes

* fix(ios-benchmark): enforce cell admission evidence

* fix(ios-benchmark): protect benchmark state ownership

* fix(ios-benchmark): use proxy port flag

* fix(ios-benchmark): let proxy choose an ephemeral port

* test(ios-benchmark): keep CLI process seam local

* fix(ios-benchmark): parse proxy startup envelope

* fix(ios-benchmark): bind proxy lease to simulator

* fix(ios-benchmark): keep fresh proxy CLI sessions isolated

* fix(ios-benchmark): preserve async timeout evidence

* docs(ios-benchmark): retain exact-head evidence

* test(ios): reveal offscreen alert fixture controls

* test(ios): reset alert between relaunch samples

* test(ios): admit native alert snapshots

* docs(ios): publish snapshot convergence corpus

* chore(ios): format benchmark evidence

* fix(ios-benchmark): admit proxy fixture anchors

* docs(ios): republish exact-head benchmark corpus

* fix(size): make publish asset evidence hermetic

* test(size): update publish preparation contracts

* fix: keep git-state gates out of mutation sandboxes
2026-09-01 21:39:05 +02:00
Michał Pierzchała 04fb10dde5 docs: adopt Simlock-backed Host device allocation (#2220)
* docs: adopt Simlock-backed Host device allocation

* docs: link ADR 0021 implementation tracker

* docs: define terminal allocation failures
2026-09-01 21:38:41 +02:00
Michał Pierzchała b643d0f761 docs: clarify technical issue requirements (#2226) 2026-09-01 19:52:03 +02:00
Michał Pierzchała a4e68053f2 refactor: move utilities to owning modules (#2225) 2026-09-01 19:45:46 +02:00
Michał Pierzchała 110c08c947 refactor(transport): move shared host mechanics (#2221) 2026-09-01 19:45:28 +02:00
Michał Pierzchała 1826b2e68b refactor(ios): integrate runner with snapshot engine (#2214)
* refactor(ios): integrate runner with snapshot engine

* fix(ios): preserve macOS runner snapshots

* refactor(ios): keep runner presentation device-aware

* fix(ios): validate runner scroll presentation

* fix(ios): close presenter package boundaries

* fix(ios): preserve snapshot source lineage

* test(ios): colocate snapshot engine coverage

* fix(ios): settle post-merge audit checks

* test(ios): fix manifest parity lint

* refactor(ios): simplify runner source walk

* test(ios): cover shared package source fixture

* fix(ios): close post-merge audit gaps

* perf(ios): avoid bundling acquired snapshot path
2026-09-01 18:36:09 +02:00
Michał Pierzchała 911945a8a6 refactor(provision-kit): move install source config (#2217) 2026-09-01 18:35:39 +02:00
Michał Pierzchała 4388aeafe0 fix: preserve physical iOS session health check (#2218) 2026-09-01 18:30:48 +02:00
Michał Pierzchała 544a804965 refactor(daemon): move session observability behind facade (#2216) 2026-09-01 16:39:01 +02:00
Michał Pierzchała a8ee397168 test(ios): add snapshot engine conformance gates (#2213)
* test(ios): add snapshot engine conformance gates

* test(ios): align differential acquisition inputs

* fix(ios): gate Swift differential on macOS

* test(ios): keep differential coverage host-aware

* test(ios): own snapshot differential on macOS
2026-09-01 15:59:31 +02:00
Michał Pierzchała 02116ccdd8 refactor(ios): implement snapshot engine (#2211)
* refactor(ios): implement snapshot engine

* fix(ios): finish snapshot engine ownership move
2026-09-01 15:59:30 +02:00
Michał Pierzchała 868f8f90ee refactor(ios): extract snapshot presenter (#2206)
* refactor(ios): extract snapshot presenter

* refactor(ios): consume shared snapshot presenter

* fix: unblock snapshot presenter CI
2026-09-01 15:59:30 +02:00
Michał Pierzchała 7646a73b1b perf: avoid redundant physical iOS runner health check (#2215)
* perf: avoid redundant physical iOS runner health check

* test: isolate iOS runner prewarm coverage
2026-09-01 15:30:00 +02:00
Michał Pierzchała af2c65da1f refactor(url): split client and Metro URL ownership (#2210)
* refactor: split client and Metro URL ownership (#2146)

* fix: preserve URL ownership loading contract (#2146)

* fix: preserve Metro bundle URL construction

* fix: keep the moved base URL implementation verbatim
2026-09-01 12:25:42 +02:00
Michał Pierzchała fd4ab84166 refactor(ios): define snapshot acquisition and presentation contracts (#2203)
* refactor(ios): define snapshot acquisition and presentation contracts

* refactor(ios): isolate snapshot planning exports
2026-09-01 11:33:18 +02:00
Michał Pierzchała 81a9cb2b3c feat(daemon): establish interaction application facade (#2205)
* feat: establish interaction application facade (#2177)

* fix(daemon): narrow interaction runtime request seam
2026-09-01 10:46:54 +02:00
Michał Pierzchała 1522126f1f refactor: move close lifecycle behind session facade (#2212) 2026-09-01 10:25:59 +02:00
Michał Pierzchała b042045522 refactor(output): split presentation owners (#2202)
* refactor(output): split presentation owners

* fix(output): keep candidate rendering in surface owners
2026-09-01 07:50:25 +02:00
Michał Pierzchała 3afd154c80 perf: retain iOS runner across physical relaunch (#2200) 2026-08-31 21:41:30 +02:00
Michał Pierzchała 010f09bf0d refactor(daemon): move open lifecycle behind session facade (#2201) 2026-08-31 21:18:58 +02:00
Michał Pierzchała 42dc9adb5d fix: address security scanner findings (#2182)
* fix: address security scanner findings

* fix: close image-size parser review gap

* test: prove zero-length image box regressions

* fix: keep fixture fingerprint output machine-readable

* test: align fixture fallback with fingerprint owner
2026-08-31 20:38:44 +02:00
Michał Pierzchała c86bf6feb5 refactor(cli): move process-exit lifecycle to CLI boundary (#2193) 2026-08-31 20:16:25 +02:00
Michał Pierzchała 28f80ddb40 refactor(cli): move update-check policy (#2194) 2026-08-31 20:16:16 +02:00
Michał Pierzchała 71e82aee77 test(kernel): move source-value coverage beside kernel owner (#2187) 2026-08-31 20:16:04 +02:00
Michał Pierzchała 5f5b8e827c refactor(app-log): move policy into daemon ownership (#2185) 2026-08-31 19:06:49 +02:00
Michał Pierzchała d53825a4b7 refactor(recording): move video validation policy (#2186) 2026-08-31 19:01:48 +02:00
Michał Pierzchała f3aabff12d refactor(snapshot): move Android helper presentation (#2184) 2026-08-31 19:01:39 +02:00
Michał Pierzchała 8591f47dd3 refactor: extract daemon session lifecycle inventory facade (#2183)
* refactor: extract session lifecycle inventory facade

* test: cover session inventory failure response
2026-08-31 19:01:00 +02:00
Michał Pierzchała afdbaa807f refactor(host-kit): move verified-file ownership (#2180)
* refactor(host-kit): move verified-file ownership

* fix(host-kit): update verified-file closure budgets

* chore(tests): remove retired exec fault fixture
2026-08-31 18:13:20 +02:00
Michał Pierzchała 1d5ddb54a3 refactor(snapshot): move snapshot policy utilities (#2135) (#2181)
* refactor(snapshot): move snapshot policy utilities (#2135)

* fix(snapshot): preserve CLI eager closure
2026-08-31 17:45:54 +02:00
Michał Pierzchała 7e784be98f refactor: move Swift cache policy to recording ownership (#2179) 2026-08-31 16:50:56 +02:00
Michał Pierzchała 8dba2b489d fix(daemon): compose stale-claim recovery from the dead owner's state dir (#2171)
* fix(daemon): compose stale-claim recovery from the dead owner's state dir (#2168)

The daemon's acquire-time reconciliation and startup sweep ran recovery
through the daemon's single composed gateway, so settling a foreign
state dir's dead claim cleared owned-process records by bare session id in
the RECONCILING daemon's own store — a live same-named session (default is
common across worktrees) lost its owned-processes.json to a foreign claim's
cleanup, while the dead owner's marker survived. Live-reproduced on main.

Every production reconciliation path — open-time admission, transient
admission, the startup sweep, and the daemonless CLI release — now goes
through createOwnerScopedDeviceClaimReconciler: one recovery composition per
claim, bound to that claim's recorded state dir (owned-process store and
session artifacts included), disposed after the transaction. Gateway state is
instance-scoped, so disposal shuts down only the transaction's own handles.

* test: prove the startup sweep composes recovery from the claim's state dir

Review follow-up on #2171: the unit tests exercised the owner-scoped
reconciler directly, so reverting the sweep callsite to the caller-scoped
reconciler stayed green. This regression boots the real daemon runtime with a
foreign dead claim whose recorded simctl recorder still runs as an orphan,
next to a live same-named session record in the daemon's own state dir
(purpose outside the startup reaper's selection). It proves the sweep
terminates the orphan, settles the foreign recording open -> completed,
clears the owner's process record in the OWNER's state dir, and leaves the
daemon's own record byte-identical. Observed red against a reverted sweep
callsite.

* test: prove open-time reconciliation composes recovery from the owner state dir

Review follow-ups on #2171:

- The acquire/open wiring had only live evidence. The new regression drives a
  real open through the production request router against a device whose
  foreign dead claim carries an orphaned recorder and durable recording
  evidence, next to a live same-named session record under the daemon's own
  state dir. It proves open-time reconciliation terminates the orphan,
  settles the foreign recording, clears the owner's process record in the
  OWNER's state dir, leaves the daemon's own record byte-identical, and hands
  the claim to the opening session. Observed red against a reverted router
  callsite.
- The startup-sweep regression now shuts its daemon runtime down in finally,
  so an assertion failure cannot leak the runtime.

* test: restore an absent claims-dir override by deletion, not assignment

Review follow-up on #2171: both new regressions (and the pre-existing
prune-log test they mirrored) restored AGENT_DEVICE_CLAIMS_DIR by plain
assignment, which turns an absent previous value into the literal string
"undefined" and leaks global test state. Absent values are now restored by
deleting the variable.
2026-08-31 16:48:20 +02:00
Michał Pierzchała d330a679e2 fix(apple): switch to manual code signing when a provisioning profile is set (#2172)
* fix(apple): switch to manual code signing when a provisioning profile is set

CODE_SIGN_STYLE was hardcoded to Automatic even when
AGENT_DEVICE_IOS_PROVISIONING_PROFILE was configured, so xcodebuild rejected
the resulting PROVISIONING_PROFILE_SPECIFIER + CODE_SIGN_STYLE=Automatic
combination with "conflicting provisioning settings" on physical-device runs.

Fixes #2153

* fix: satisfy formatting and the test-file size ratchet

- oxfmt: wrap the long array literal in the new manual-signing test.
- runner-client.test.ts was already pinned at the 1000-line tripwire
  (1577 lines); adding a test grew it past the pin, which the ratchet
  test rejects by design ("extract instead of adding to a file over
  the tripwire"). Extract the pure runner-cache-metadata.ts build-
  settings tests (signing, bundle, performance, sandbox args) into a
  new runner-cache-metadata.test.ts, shrinking runner-client.test.ts
  to 1441 lines and lowering its pin to match.
2026-08-31 16:28:27 +02:00
Michał Pierzchała f513b1d4ae refactor: extract daemon replay behind one application facade (#2166)
* refactor: extract daemon replay behind application facade

* fix: address replay facade review findings

* test: close replay ownership import scan gap

* fix: tighten replay capability boundaries
2026-08-31 15:43:33 +02:00
Michał Pierzchała cfed1ae567 chore: remove stray validation recording (#2169)
A live-validation screen recording landed in the repository root through an
overly broad git add -A during the #2162 review round. Recordings default
their output path to the working directory; nothing references this file.
2026-08-31 15:02:53 +02:00
Michał Pierzchała 34e8cbb7a2 docs+ux: make device ownership discoverable end to end (#2165)
* docs+ux: make device ownership discoverable end to end

Complete the #1320 agent experience so 'busy? -> inspect -> choose or
release' is discoverable from every surface an agent actually reads:

- devices now projects the blocking claim owner per row (claimedBy with
  session and workspace, observe-policy projection; provably dead owners are
  excluded because the next open replaces them automatically), so an agent
  told a device is busy can pick a free one from the same listing.
- help debugging gains a 'Device busy and ownership' section separating the
  two DEVICE_IN_USE flavors and their exact recoveries.
- AGENTS.md documents both flavors; docs/agents/device-verification.md
  retires the last ps/kill recovery guidance in favor of device status,
  daemon stop --state-dir, and device release --stale (Stage 5 of #1320).
- ADR-0010 no longer calls DEVICE_IN_USE 'the only retriable code' without
  naming the claim path's non-retriable override.
- The rendered cross-worktree claim error gains a help-conformance quiz case
  binding (sample-output-device-claim-inspects-owner).
- README points at device status / device release --stale.

Part of #1320.

* fix: key ownership projection by canonical device identity end to end

Review findings on #2165:

- blockingClaimOwnersByDevice keyed claims and inventory rows by bare
  device.id, so a live Android claim could project claimedBy onto an
  unrelated same-id Apple/Harmony/Vega row, with scan order picking the
  displayed owner. Both sides now use the canonical local device key
  (claim.deviceKey against canonicalLocalDeviceKey of the row's claim
  identity). The cross-family same-id regression was observed red against
  the bare-id keying.
- The projection is now asserted across every hop the PR promises: client
  normalization preserves well-formed claimedBy and drops malformed ones,
  and the devices CLI formatter carries it through JSON data and renders
  the text line (MCP shares the same serialization).
2026-08-31 14:32:29 +02:00
Michał Pierzchała 2d2aa6c670 feat: add stale device claim release and dead-end recovery guidance (#2162)
* feat: add stale device claim release and dead-end recovery guidance

Close the #1320 recovery loop for claims no daemon can settle on its own:

- agent-device device release --stale settles a provably dead owner's durable
  resources through the same exact-owner reconciliation open and daemon
  startup use, then clears the claim last — daemonlessly, composing a
  local-only platform gateway in the CLI process. Live, uncertain, PID-reused,
  and corrupt claims always fail closed and are reported with the reason.
- DEVICE_IN_USE conflicts whose recorded owner provably cannot release
  (dead or superseded) now carry the exact release command as their recovery
  instead of a status inspection that dead-ended.
- device status --stale now offers the matching release command when provably
  dead owners are listed.
- daemon stop now warns in text output when a claim was orphaned (previously
  visible only via --json) and names the status/release commands.

Part of #1320.

* test: cover release refusal branches, text rendering, and orphan warnings

Changed-line coverage on the stale-release slice was 69.33% against the 70%
gate: the refusal-reason branches, the text-mode outcome rendering with the
live-owner hint, and the daemon stop orphaned-claim warning had no tests.
Cover them directly; the misnamed-claim-file refusal is also pinned.

* test: prove resources gate stale release and the scan-to-lock race stays closed

Review follow-ups on #2162:

- Two end-to-end CLI regressions run device release --stale through the real
  local gateway against a dead owner whose state dir holds an attributable
  durable screen-recording/app-log record: an owner-mismatched record and
  unreadable resource evidence both retain the claim (app-log-owner-mismatch,
  app-log-descriptor-invalid), proving cleanup must reach a terminal state
  before the claim can be deleted.
- A deterministic race regression holds the per-device claim lock while a
  release is in flight, replaces the claim with a successor before releasing
  the lock, and proves the transaction reports changed without reconciling or
  touching the successor's claim.

* fix: bind stale-claim recovery to the dead owner's state dir

Review P1 on #2162: the CLI composed one gateway from the caller's state
dir, so recovery for a foreign stale claim could clear the caller's live
owned-process record when both used the same session name — Apple recording
cleanup clears by session id through the gateway-composed store.

Recovery is now composed per claim, with the owned-process record store and
session artifact paths bound to the stale claim's recorded state dir, and
disposed after each transaction. The regression writes two dead claims with
one shared session name in different state dirs and proves each recovery is
composed from that claim's own state dir, never the caller's.
2026-08-31 14:32:29 +02:00
Michał Pierzchała 5fddf1ce14 feat(apple): reclaim retained runners under device-claim authority (#2160)
* feat(apple): reclaim retained runners under device-claim authority

Implement the #1320 retained-runner rule: a daemon holding the host-global
device claim may stop and replace a warm XCTest runner whose owner no longer
holds that claim. Previously worktree B's open failed with an unstructured
COMMAND_FAILED (IOS_RUNNER_OWNED_BY_OTHER_DAEMON) for up to five minutes
after worktree A closed, until A's daemon idled out.

- Runner leases now record deviceClaimProtocol: 1; takeover is gated on the
  lease declaring claim arbitration, so owners from pre-claims builds are
  never preempted.
- The claim-authority probe is daemon-bound through the existing runner-owner
  seam and answers from the claim store by process identity; unbound embedders
  answer false and keep today's refusal.
- Disposal now skips device-wide runner container-app termination when the
  on-disk lease is owned by someone else, so the losing daemon's idle stop or
  shutdown cannot kill the successor's runner on the shared simulator.
- Help topics updated: the live-owner runner rejection now names claim
  arbitration instead of being unconditional.

Live-validated with two daemons sharing one claim store against a throwaway
simulator: live-owner open still rejects with structured DEVICE_IN_USE;
after close, the contender opens in ~8s with the lease re-owned while the
loser daemon is still alive; stopping the loser afterwards leaves the
winner's runner healthy.

* fix(apple): make disposal ownership check and device-wide teardown one lease-locked operation

Review P1 on #2160: cleanupRunnerSessionResources read the on-disk lease
token and then terminated the device-global runner container bundles without
holding the runner-lease lock, so a loser could pass its check while a
successor's reclaim was in flight and then kill the successor's runner.

The ownership check, the container-app termination, and the lease release now
run as one operation under the runner-lease lock — the same lock a successor
holds for its entire reclaim-and-publish window — on every disposal path.
Callers already inside the lock (startup abort, lease-publish failure,
stopIosRunnerSession) declare leaseLockHeld instead of re-acquiring the
non-reentrant lock; if the lock cannot be acquired, all device-wide teardown
is skipped and an own unreleased lease turns stale on process exit.

The two-owner regression starts loser disposal inside a held successor
window, completes the takeover, and proves the loser neither terminates the
successor's runner nor touches its lease. Observed red against the pre-fix
code. Live-revalidated twice with overlapping daemon stop and takeover open
on a real simulator.

* fix(apple): match runner takeover authority by canonical device identity

Review P1 on #2160: the authority probe matched any active claim with the
same raw device id, while claims are canonically scoped by platform family +
Apple OS + id — an Android claim whose serial equals an Apple runner id could
authorize destructive takeover of a live Apple runner (and the prior probe
regression used an Android fixture, blessing exactly that).

The probe now receives the full DeviceInfo through the runner-owner seam and
the daemon answers with an exact canonical-local-device-key lookup: family,
Apple OS, and id must all match the held claim, which also replaces the store
scan with a direct keyed read. prepareRunnerLeaseForStartup takes the device
rather than a bare id so the lease seam can never degrade the match.

Adversarial regressions: a same-id Android claim grants no authority for the
Apple device (and the true Apple claim does) at the probe, and the lease-seam
test pins that the probe receives full family/OS identity, never a bare id.
2026-08-31 14:32:28 +02:00
Michał Pierzchała 3982cc865b fix: show named sessions in scoped inventory (#2164) 2026-08-31 14:30:30 +02:00
Michał Pierzchała 70fb783729 fix: reuse canonical Maestro visibility context (#2156)
* fix: reuse canonical Maestro visibility context

* perf: skip empty Maestro visibility filtering

* refactor: centralize snapshot visibility context
2026-08-31 13:41:15 +02:00
Michał Pierzchała f6d5757690 fix(maestro): measure settle outcomes directly (#2155)
* fix(maestro): measure settle outcomes directly

* test(maestro): prove settle metric execution
2026-08-31 13:37:04 +02:00
Michał Pierzchała b7e4b5aa7b fix: improve ref-based interaction recovery (#2167) 2026-08-31 13:36:48 +02:00
Michał Pierzchała 48eb1b06e3 feat: open Limrun uploaded apps (#2110)
* feat: open Limrun uploaded apps

* refactor: generalize deferred provider app selection

* refactor: fold limrun preinstall into allocation

* refactor: keep app selection provider independent

* refactor: enforce provider-independent commands

* refactor: restrict provider imports from commands

* refactor: keep provider guard lightweight

* fix: enforce provider-independent commands

* fix: enforce provider catalog lease admission

* refactor: deepen provider app catalog admission

* refactor: simplify provider capability projection

* fix: restrict Limrun uploaded apps on public daemon

* refactor: separate provider app catalog lookup

* fix: simplify uploaded app provider boundaries
2026-08-31 13:01:18 +02:00
Michał Pierzchała a79f0d2e81 feat: add symbol-aware depgraph authority overlay (#2157)
* feat: add depgraph authority overlay

* fix: centralize depgraph authority declarations

* refactor: make authority overlay declaration-driven
2026-08-31 11:54:16 +02:00
Michał Pierzchała caa3dc23f9 refactor: dissolve caller-side src/replay into command and CLI owners (#2151)
* refactor: dissolve caller-side replay ownership

* fix: remove replay test-only export

* fix: restore replay loader promise boundary
2026-08-31 10:01:16 +02:00
Michał Pierzchała bf849ed0e6 fix: use canonical viewport fallback for rootless touch targeting (#2122)
* fix: use canonical viewport fallback for touch targeting

* test: complete viewport promotion fixtures
2026-08-31 10:00:56 +02:00
Michał Pierzchała f152827447 refactor(snapshot): move text-surface into snapshot presentation (#2152) 2026-08-31 08:10:48 +02:00
Michał Pierzchała 4244691e1e refactor(layering): centralize architecture ownership (#2150) 2026-08-31 08:09:39 +02:00
Michał Pierzchała dd988847c8 test: characterize replay application behavior (#2137) 2026-08-31 08:08:50 +02:00