agent-desktop/docs/solutions/logic-errors/progressive-snapshot-review-contract-2026-04-16.md
Lahfir 3f322728b4
feat!: implement Playwright-grade foundation contract
Settle the Playwright-grade reliability contract in agent-desktop-core
before the Windows/Linux adapters are built, so they inherit it instead
of redesigning it. Every command now observes, waits, verifies, and
reports honestly instead of firing blindly.

Highlights: capability-supertrait split of PlatformAdapter with
not_supported() defaults; canonical role/state vocabulary with live
`is --property visible`; display enumeration (`list-displays`) and honest
`--screen` with scale factor; truthful Automation permission; `native_id`
identity spine; window-id-first resolution; serializable `LocatorQuery`
with live `find`; default-on auto-wait before every ref action; three-way
`hit_test` occlusion gate; `scroll_into_view` in core; core accessible-name
precedence; typed `ActionStep` delivery tier; `ProcessState` and
`APP_UNRESPONSIVE`; `LaunchOptions`; baseline-diff desktop signals
(`wait --event`); typed clipboard (`Text`/`Image`/`FileUrls`); mouse
modifier chords and `mouse-wheel`. Hardened through a 35-reviewer pass with
independent validation and a green live e2e gate (109/0), plus a
head-vs-main performance comparison harness.

BREAKING CHANGE: default-on auto-wait changes the timing of every
previously-untouched ref-action call (bounded 5000 ms default; `--timeout-ms 0`
restores single-shot). `ENVELOPE_VERSION` is now `2.1` (adds the
`APP_UNRESPONSIVE` code and process state in error details). FFI ABI major
is `3` (append-only struct evolution; `wait --event` is intentionally not
exposed over FFI). The legacy string clipboard API is removed in favor of
typed content. `key-down`/`key-up` fail closed until daemon-owned held input
exists. `close-app` verifies termination and the osascript fallback path is
removed. `--text` matching is subtree containment: `find --text X --first`
returns the outermost matching container.
2026-07-20 00:21:38 -07:00

3 KiB

title date category module problem_type component symptoms root_cause resolution_type severity tags
Keep progressive snapshots namespace-scoped and ref-safe 2026-07-11 logic-errors crates/core snapshot and refs logic_error tooling
A drill-down mutates refs outside its requested subtree or reports a window unrelated to its root ref.
Malformed or cross-namespace refs are classified as stale instead of invalid input.
Skeleton output hides truncation or creates refs that cannot be resolved safely.
logic_error code_fix high
snapshot
drill-down
refs
sessions
skeleton
contracts

Keep progressive snapshots namespace-scoped and ref-safe

Problem

Progressive observation has two different operations: a full window snapshot creates a new ref map, while snapshot --root replaces only the selected subtree in an existing snapshot. Treating either as an unconstrained merge makes old refs appear valid after their evidence has changed.

Solution

  • Resolve --root with ref_token::resolve_ref_target before loading a map. A qualified ref supplies its snapshot; a bare ref requires the explicit snapshot argument. Both are looked up only through RefStore::for_session(context.session_id()).
  • Re-resolve the saved root entry strictly before observing its subtree. A missing, stale, or ambiguous live root fails closed; it is never replaced by a positional guess.
  • Use RefStore::update_existing_snapshot to remove only descendants owned by that root, then allocate replacements through the shared ref_alloc::allocate_refs owner. RefAllocScope preserves the root and absolute path evidence for the replacement entries.
  • A normal snapshot writes a fresh map. Skeleton mode is only a shallow observation policy: it clamps depth to three and leaves children_count on truncated nodes. Named structural anchors can receive drill-down refs, but inert containers do not become actionable merely because they were visible.
  • Derive the response window from the root entry's process identity with window_lookup::find_window_for_process; never synthesize a plausible window response.

Why This Works

The selected session is a hard namespace boundary, qualification removes snapshot ambiguity, and strict resolution protects the gap between observation and a later read. Replacing one rooted subtree preserves unrelated refs while ensuring no stale descendants survive a re-drill.

Prevention

  • Test full-snapshot replacement and rooted-subtree replacement separately.
  • Test qualified and bare ref parsing, including mismatched snapshot IDs.
  • Assert every truncation path exposes a boundary marker instead of silently dropping descendants.
  • Keep ref allocation in crates/core/src/ref_alloc.rs; full snapshots and drill-downs must not grow separate allocators.