Skip to content

refactor: migrate snapshot to device runtime - #1779

Merged
thymikee merged 9 commits into
mainfrom
refactor/adr19-snapshot-unit
Aug 18, 2026
Merged

refactor: migrate snapshot to device runtime#1779
thymikee merged 9 commits into
mainfrom
refactor/adr19-snapshot-unit

Conversation

@thymikee

@thymikee thymikee commented Aug 16, 2026

Copy link
Copy Markdown
Member

Summary

Migrate the public snapshot command from legacy capability dispatch to the ADR 0019 request-scoped device runtime.

  • model the cutover as three exact-owner operations and four parametrized request plans, with one facts inspection and one runtime bind per handler
  • enforce R32 through a deliberately bounded positive seam: the route selects one normalized plan.use and passes that exact use to the shared inspectRequiredRuntimeUse; local snapshot admission policy is rejected
  • remove the custom identity-flow/alias tracker instead of extending it with another omission patch; the structural rule no longer depends on identifier spelling, aliases, destructuring, or object wrapping
  • preserve Apple, Android, HarmonyOS, Linux, web, WebDriver, and Limrun behavior, including macOS surfaces, Linux shaping, cancellation, watchOS denial, provider fail-closed behavior, and public-only sparse screenshot fallback
  • retire generic snapshot dispatch, static capability admission, descriptor dispatch projection, and two daemon-to-platform value edges
  • keep shared diff/find/wait/interaction capture consumers on one narrow interactor seam until their own command units migrate; no unrelated command descriptor was implicitly migrated
  • remove duplicate Linux/macOS desktop capture implementations and the thin host wrapper; production duplication remains flat at 5 groups / 207 lines
  • make the iOS smoke scroll helper require actual visibility before skipping a scroll, with capture-stall retries that do not consume scroll attempts

The measured snapshot-unit budget approved on #1739 was +10,209 B JS raw, +3,058 B JS gzip, +2,590 B npm tarball, and +10,226 B unpacked against its authorized base: #1739 (comment). Returning to a non-positive delta requires separately migrating the remaining legacy capture consumers and is intentionally outside this command-atomic unit.

Validation

  • rebased onto main at 60f6356b0
  • exact head: bea6ce36d
  • clean committed tree: pnpm check:affected --run passed all locally runnable selected gates before the chained force-with-lease push
  • affected Vitest/coverage run: 603 files / 4,907 tests passed; changed-line coverage 206/217 (94.93%)
  • R32 planted red includes the reported { device } object-wrapper bypass; the positive route-to-owner-facts seam rejects the local admission reimplementation without reconstructing JavaScript data flow
  • rebase conflict reconciliation retained main's caller-supplied replay-source model and the migrated narrow snapshot capture seam; focused replay/runtime tests passed 82/82
  • latest-main replay-source test exposed one stale generic snapshot mock after rebase; the owning test now uses the narrow snapshot fixture (red: 5.00s timeout, green: 5/5 in 20ms)
  • pnpm typecheck, pnpm check:fallow --base origin/main, pnpm format, package verification, wire compatibility, replay compatibility, and git diff --check passed through the aggregate gate
  • no local simulator or device command was run because benchmarks are active; GitHub native/device lanes remain authoritative
  • fresh exact-head GitHub checks were queued after the force-push; this update does not claim they are complete

Docs/skills are unchanged because this is an internal runtime cutover, consolidation, and enforcement correction with no public CLI or workflow behavior change.

@github-actions

github-actions Bot commented Aug 16, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.28 MB 2.29 MB +10.2 kB
JS gzip 751.0 kB 754.1 kB +3.1 kB
npm tarball 872.4 kB 874.9 kB +2.5 kB
npm unpacked 3.05 MB 3.06 MB +10.2 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.8 ms 28.2 ms -0.6 ms
CLI --help 70.1 ms 70.0 ms -0.1 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/sdk-batch-runner.js +1.7 kB +356 B
dist/src/internal/daemon.js -1.1 kB -329 B
dist/src/runtime4.js +953 B +245 B
dist/src/dispatch.js -486 B -136 B
dist/src/runtime2.js +517 B +114 B

@thymikee

Copy link
Copy Markdown
Member Author

Exact head 328f71fe9 is not ready yet.

  1. [P1] Complete the snapshot platform-policy cutover. src/daemon/snapshot-runtime-binding.ts:63-93 still decides native support with isIosSimulator(device) for --actions and isIosFamily(device) plus provider mode for the active-app requirement. ADR 0019 requires normalized-input execution planning and exact owner facts to own leaf/device/provider availability; moving these branches out of snapshot-runtime.ts into another daemon module does not reduce daemon platform behavior. Model the variants/preconditions in snapshot facts/use planning so the handler performs facts admission, preserves the existing pre-bind rejection semantics, and binds the selected operation once. Extend R32 so a planted daemon platform branch fails the cutover gate instead of being entrenched by the current guard test.

  2. [P1] Honor the unit's accepted move-dominated budget in this unit. The refactor(daemon): platform-free daemon — broader migration waves (successor to #1696) #1739 start record requires immediate JS gzip and npm tarball deltas <= 0, no new clone group, and shrinking daemon-to-platform edges. This head reports +6.2 kB raw, +1.9 kB gzip, +1.8 kB tarball, and +6.2 kB unpacked while explicitly deferring shared capture-consumer consolidation. Complete the planned relocation/deletion now, or obtain an explicit revised budget decision on the tracker with each growth contribution itemized before readiness; a follow-up cannot silently replace the recorded authorization.

CI is also owner-blocked: exact-head iOS Smoke fails at is visible id="automation-longpress". Because this PR rewrites the shared legacy capture seam used by is, classify it by rerunning in isolation and reproducing on plain current main under the same load before calling it infrastructure. All other completed authoritative checks are green. No ready-for-human label while these code findings and the failing lane remain.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed both P1 findings in e44dfb6.

  1. Snapshot policy now belongs to exact owner facts and normalized planning: three owner operations, four discriminated custom-actions/active-app plans, all required facts inspected before binding, and one concrete bind/invocation path. WebDriver and Limrun facts are exhaustive and fail closed when inactive or unsupported. R32 now rejects reconstructed isIosFamily, isIosSimulator, and providerOwned policy in daemon admission and covers all three operations. Parametrized planner/fact/handler tests include planted-red evidence.

  2. Safe capture consolidation removed the direct daemon macOS/Linux branches and reduced daemon-to-platform edges from 64 to 62 total and 45 to 43 value edges. Duplication remains unchanged at 5 groups / 207 lines. Final latest-main size is +10,414 B raw, +3,069 B gzip, +2,674 B tarball, and +10,431 B unpacked. I posted the required explicit revised-budget request on refactor(daemon): platform-free daemon — broader migration waves (successor to #1696) #1739: refactor(daemon): platform-free daemon — broader migration waves (successor to #1696) #1739 (comment). The PR stays draft pending that decision.

Clean committed-tree pnpm check:affected --run passed every runnable gate: 587 files / 4,816 tests, 95.37% changed-line coverage, plus format, lint, typecheck, layering, fallow, build, package, wire, and integration checks. Native/device lanes were left to GitHub; no local simulator/device command was run because benchmarks are active.

The previous iOS Smoke is not proven to be a snapshot production regression: its unchanged existence-oriented wait found an edge/offscreen node, skipped scrolling, then the visibility assertion correctly returned false; base passed by timing out the first wait and scrolling. The mechanically equivalent legacy capture seam does not explain that decision difference. Fresh iOS Smoke is running on this head and remains blocking; if it repeats, the owning smoke helper should require visibility before skipping scroll rather than weakening production visibility.

@thymikee

Copy link
Copy Markdown
Member Author

Exact head e44dfb61d fixes the original daemon-policy implementation finding, and all authoritative CI—including iOS Smoke—is green, but it is still not ready.

  1. [P1] Make R32 enforce the policy class, not three old spellings. snapshotPlatformPolicyBranchViolations only rejects the identifiers isIosFamily, isIosSimulator, and providerOwned. Equivalent planted daemon policy branches such as device.platform === 'apple' && device.kind === 'simulator' and facts.device.providerMode === 'provider-runtime' produce no violation, so the claimed “daemon platform/provider policy” cutover remains unenforced. Replace the spelling denylist with a structural admission invariant (or otherwise prove the allowed owner-facts/plan seam), and include planted-red cases for direct device-leaf and provider-mode branching. Do not grow another identifier exception list.

  2. [P1] The accepted size budget is still unresolved. refactor(daemon): platform-free daemon — broader migration waves (successor to #1696) #1739 contains a request to revise the budget, not an explicit maintainer decision. Exact head remains +10,414 B raw / +3,069 B gzip / +2,674 B tarball / +10,431 B unpacked against a recorded <=0 gzip/tarball gate. Keep the PR blocked until that request is explicitly approved or the unit meets the accepted budget.

  3. [P2] Refresh the PR body to exact-head evidence. It still reports the old +6.2/+1.9/+1.8 kB deltas and says CI is pending, while exact-head CI is fully green and the PR now spans 128 files (43 in the remediation delta). Record the touched-file count and explain the scope expansion as required by the PR guidance.

No ready-for-human label while the R32 proof and accepted budget remain unresolved.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed in b893163.

  • R32 no longer relies on the isIosFamily / isIosSimulator / providerOwned spelling denylist. Snapshot admission is now structurally restricted to resolveSnapshotRuntimePlan, plan.use.required, and the selected operation facts; any member access rooted at device.*, params.device.*, or facts.device.* is rejected.
  • Added planted direct device.platform + device.kind and facts.device.providerMode cases. With the detector disabled they fail 0/2; restored, the focused cutover suites pass 46/46 and full layering passes 187/187.
  • Clean-tree pnpm check:affected --run passed before the chained push. No simulator/device command was run because benchmarks are active.
  • Refreshed the PR body with the exact 129-path scope, 44-path remediation expansion, dependency/duplication evidence, and current exact size delta.

The budget point remains intentionally unresolved: +10,414 B raw / +3,069 B gzip / +2,674 B tarball / +10,431 B unpacked versus 856ff38 is not approved. The explicit request remains at #1739 (comment), and the PR stays draft. Exact-head CI is now running; the preceding production head e44dfb6 was fully green.

@thymikee

Copy link
Copy Markdown
Member Author

Final review remediation is complete at f4abac936.

  • addressed the owner-facts/runtime-plan and structural R32 findings
  • recorded the explicitly approved revised size budget on refactor(daemon): platform-free daemon — broader migration waves (successor to #1696) #1739
  • consolidated duplicate Linux/macOS desktop capture mechanics, removed the thin host wrapper, reused the shared AST helper, and reduced daemon-to-platform edges from 64 to 62 total (45 to 43 value)
  • kept production duplication flat at 5 groups / 207 lines
  • fixed the iOS smoke helper's existence-vs-visibility bug with planted-red and deterministic coverage
  • passed the clean committed-tree pnpm check:affected --run gate before every push

All deterministic exact-head checks and Android Smoke are green. The isolated iOS Smoke rerun remains in progress after an unrelated hardware-keyboard XCTest flake; per maintainer direction, readiness is not blocked on waiting for that run. No local simulator/device command was run while benchmarks are active.

@thymikee
thymikee marked this pull request as ready for review August 17, 2026 15:02
@thymikee

Copy link
Copy Markdown
Member Author

Closed the R32 alias/destructuring bypass in f5f4c929f.

  • the structural gate now follows bounded intraprocedural origins through chained aliases, assignments, direct/nested destructuring, and computed/static member reads
  • planted pre-fix cases failed for both alias and destructuring bypasses; the focused cutover suite is now 50/50 and the full layering gate is 190/190
  • pnpm check:affected --run && git push completed successfully from the clean committed tree
  • the previous head's iOS Smoke rerun completed fully green; exact-head CI is running after this tooling-only enforcement change

No local simulator/device lane was invoked while benchmarks are active.

@thymikee

Copy link
Copy Markdown
Member Author

Production migration is now architecturally sound, and the revised snapshot-unit size budget is explicitly authorized on #1739.

However, R32 remains bypassable and is not yet an honest regression gate. It traces known helper identifiers/aliases, but equivalent daemon policy still passes when expressed through a renamed helper call such as supportsIosSimulator(device), or object-rest identity such as const { ...identity } = device; identity.platform. Both contradict the PR claim that enforcement is independent of identifier spelling. Please redesign around the owning source of truth, or make the gate prove these equivalent forms red.

Also, the exact-head iOS Release check failed during action setup because GitHub codeload returned HTTP 429 while downloading gradle/actions; rerun is needed.

@thymikee

Copy link
Copy Markdown
Member Author

Addressed the remaining R32 bypasses in ca9e91d7f.

  • replaced member-read-only enforcement with bounded identity-flow enforcement at the admission seam
  • arbitrary helper calls now fail whenever an argument carries device/provider identity; only the two explicit non-policy sinks are permitted
  • object-rest destructuring preserves the source identity in the gate, so { ...identity } = device cannot launder platform leaves
  • planted pre-fix cases for supportsIosSimulator(device) and object-rest identity failed 2/2; the focused cutover suite is now 52/52 and full layering is 193/193
  • the implementation remains at the 300-line module tripwire
  • clean-tree pnpm check:affected --run && git push passed all 52 selected local gates

I also reran the failed iOS Release job. GitHub codeload returned the same HTTP 429 while downloading gradle/actions during setup on all three retries, before checkout or project code. The new exact-head workflows are now authoritative. No local simulator/device lane was invoked while benchmarks are active.

@thymikee

Copy link
Copy Markdown
Member Author

The prior renamed-helper and object-rest bypasses are fixed. However, R32 remains bypassable through object identity wrapping: const wrapped = { device }; if (wrapped.device.platform === "apple") return plan;. The tracker does not classify an ObjectExpression initializer as carrying the device origin, so wrapped.device.platform is not recognized as device identity. The recurring omission patches now demonstrate that this custom ~300-line flow tracker is the wrong architecture under repository guidance: it is reconstructing a scope/data-flow model rather than making the invariant impossible at the owning interface. Please redesign around the owning source of truth or a deliberately bounded positive seam. The exact-head Affected-check Selector also needs a rerun after its external codeload setup failure; all other checks and device evidence are green.

@thymikee
thymikee force-pushed the refactor/adr19-snapshot-unit branch from ca9e91d to 1b71fe4 Compare August 18, 2026 12:21
@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 18, 2026
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head 1b71fe43c: the prior R32 architecture blocker is resolved. The bespoke identity/data-flow tracker is gone; admission now uses one normalized plan and one owner-facts inspection at the positive route seam, while binding switches only on normalized plan kind and carries no platform/provider policy. The earlier object-wrapped bypass class no longer applies. No new code finding; all completed checks are green, with iOS Smoke still in progress. Code review is clean and ready-for-human.

@thymikee
thymikee force-pushed the refactor/adr19-snapshot-unit branch 2 times, most recently from 57962bf to ce3a701 Compare August 18, 2026 12:51
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the R32 bypass at the owning interface rather than adding more alias cases to the custom tracker.

  • Deleted the identity-flow tracker (including its alias/destructuring/object-flow model).
  • The snapshot route now selects one SnapshotRuntimePlan and passes its exact plan.use to the shared inspectRequiredRuntimeUse facts-first seam.
  • R32 positively requires that call shape and rejects any local inspectSnapshotCaptureAdmission policy beside binding.
  • Added the reported { device } object-wrapper case to the planted violations. It is rejected because the route is no longer allowed to implement admission from device identity at all.

Rebased onto main 3908559fe; exact head is ce3a701cd. A clean committed-tree pnpm check:affected --run passed all 53 runnable gates before the chained push (599 files / 4,896 tests). No simulator or device was run. Fresh exact-head GitHub checks are queued and remain authoritative for native/device lanes.

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head ce3a701 after the force-push: this is a pure rebase; the eight PR commits range-diff unchanged. The owner-facts admission seam and removal of the bespoke identity/data-flow tracker remain sound, with no new code finding. The PR is code-clean and retains ready-for-human, but final merge readiness waits for the newly restarted exact-head CI lanes.

@thymikee
thymikee force-pushed the refactor/adr19-snapshot-unit branch from ce3a701 to bea6ce3 Compare August 18, 2026 13:22
@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head bea6ce3: clean. The R32 owner-facts admission implementation and structural enforcement are unchanged from the previously reviewed head; the rebase mainly incorporates merged #1810. The only post-rebase delta is a narrow replay-source test mock for the snapshot-interactor capture seam, correctly isolating that test without hiding production-route coverage. Code review remains ready-for-human. Coverage/core gates and Android/Linux are green; iOS/macOS Smoke and Swift compile are still pending before merge.

@thymikee
thymikee merged commit d76e0f9 into main Aug 18, 2026
31 checks passed
@thymikee
thymikee deleted the refactor/adr19-snapshot-unit branch August 18, 2026 13:49
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-18 13:49 UTC

thymikee added a commit that referenced this pull request Aug 18, 2026
#1838)

#1779 added src/daemon/handlers/snapshot-interactor-capture.ts as a
vi.mock seam between snapshot-capture and core/interactors. Both of its
edges are value imports, and it sits on the path
request-generic-dispatch -> snapshot-capture -> (seam) -> core/interactors
-> register-builtins -> command-catalog -> ... -> daemon-command-registry,
so it joined the largest type-level SCC (46 -> 47 files, daemon-server
16 -> 17) and R9/R10 have failed on main since d76e0f9.

Load the interactor registry lazily, the same way
platform-runtime-local-application-interactors.ts reaches
core/interactors from above: the seam module is readable without the
interactor graph behind it, and the SCC is back at 46/16.

pnpm check:layering, typecheck, lint, and vitest src/daemon are green.
thymikee added a commit that referenced this pull request Aug 18, 2026
…e and rebase guidance

Retro follow-up (item 2). Adds docs/agents/adr-0019-unit.md — the order of
operations for one command unit with the declaration site for each step, the
evidence a unit review must carry, and what 'done' is not — so the pattern
rediscovered during the snapshot unit (#1779) is written down once.

testing.md: mock the seam the code under test consumes (fake inspectFacts /
bindDevice), not the generic dispatchCommand mock; a migrating command moves
its tests off the dispatch mock in the same PR.

AGENTS.md: fresh-worktree preflight (pnpm install + build in the worktree;
layering scan reads tracked files only) and concurrent-agent hygiene (one full
gate per host, verify subagent edits with git -C, one PR per worktree).

pull-requests.md: two readiness claims (published-and-reported vs merge-ready)
and the rebase rule — main has no up-to-date protection; rebase on conflict or
when `check:affected --base <merge-base> --head origin/main` names your surface.
thymikee added a commit that referenced this pull request Aug 18, 2026
…e and rebase guidance

Retro follow-up (item 2). Adds docs/agents/adr-0019-unit.md — the order of
operations for one command unit with the declaration site for each step, the
evidence a unit review must carry, and what 'done' is not — so the pattern
rediscovered during the snapshot unit (#1779) is written down once.

testing.md: mock the seam the code under test consumes (fake inspectFacts /
bindDevice), not the generic dispatchCommand mock; a migrating command moves
its tests off the dispatch mock in the same PR.

AGENTS.md: fresh-worktree preflight (pnpm install + build in the worktree;
layering scan reads tracked files only) and concurrent-agent hygiene (one full
gate per host, verify subagent edits with git -C, one PR per worktree).

pull-requests.md: two readiness claims (published-and-reported vs merge-ready)
and the rebase rule — main has no up-to-date protection; rebase on conflict or
when `check:affected --base <merge-base> --head origin/main` names your surface.
thymikee added a commit that referenced this pull request Aug 18, 2026
…e and rebase guidance (#1836)

* docs(agents): ADR 0019 unit checklist, owning-seam mock rule, worktree and rebase guidance

Retro follow-up (item 2). Adds docs/agents/adr-0019-unit.md — the order of
operations for one command unit with the declaration site for each step, the
evidence a unit review must carry, and what 'done' is not — so the pattern
rediscovered during the snapshot unit (#1779) is written down once.

testing.md: mock the seam the code under test consumes (fake inspectFacts /
bindDevice), not the generic dispatchCommand mock; a migrating command moves
its tests off the dispatch mock in the same PR.

AGENTS.md: fresh-worktree preflight (pnpm install + build in the worktree;
layering scan reads tracked files only) and concurrent-agent hygiene (one full
gate per host, verify subagent edits with git -C, one PR per worktree).

pull-requests.md: two readiness claims (published-and-reported vs merge-ready)
and the rebase rule — main has no up-to-date protection; rebase on conflict or
when `check:affected --base <merge-base> --head origin/main` names your surface.

* docs(agents): name the admitted-plan token in the ADR 0019 unit checklist (#1841)

* docs(agents): merge-ready owes live evidence only for changed device-facing paths

* docs(agents): the unit checklist documents the admission API on main; #1841 updates the row when it lands
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant