| Files | Last commit | Last update |
|---|---|---|
fix(macos): resolve native menus and verify lifecycle transitions (#376) * fix(macos): resolve native menus and verify lifecycle transitions Give app-owned menus process identity without a document window. Verify activation with fresh target AX focus and retain exact process generations. Reconcile caller waits after uncertain delivery without repeating the action. BREAKING CHANGE: JSON envelopes use 2.6; macOS app-owned menu snapshots omit data.window. * fix: keep headed preflight lookup inside the macos adapter Avoid an extra ref resolution on Linux and Windows while preserving strict native menu classification on macOS. Update the native inventory test to pass the bridge's explicit full-inventory argument. * fix: preserve native menu ownership through actions and waits * fix: preserve window identity across title changes Reuse the stored window identity check so a renamed window cannot satisfy a disappearance wait. Keep process-generation checks and return the current title. * refactor: share post-action observation and bound native recovery Observe window-owned refs through their original window so waits survive a closing sheet. Preserve partial Linux window reads until target lookup, and retry only transient macOS surface failures. * fix: preserve focus results and complete observation contracts Keep successful Linux focus results when a later read is incomplete and retain child counts across macOS fallback reads. Share saved process identity validation, clarify surface recovery, and cover incomplete reads and renamed windows. Register the shared observer in the CLI helper inventory. * style: match rustfmt output for CI * fix: preserve process-owned refs in post-action waits * style: format process owner observation guard | 3 days ago | |
feat: integrate Linux support with privacy hardening (#311) * chore: remove three uncalled lab scripts No workflow, hook, script or test calls them: a symlink helper for local skills, a root-only uinput stub whose legs were never written, and an unrun self-test of the npm wrapper smoke, which CI runs directly. * docs: remove stale evaluation records and unreferenced diagrams The architecture diagrams and the Slack token illustration were not linked from anywhere and showed Linux as planned. The acceptance audit was a withdrawn status record. Two solution docs were folded into the docs that already carried their context, so each lesson has one home. Validated in the docs removal ledger (docs-3, 6, 8, 11, 12, 13). * docs: correct solution docs that contradict the code Many learnings named moved files, deleted tests, stale function signatures, or rules the code no longer follows (bare refs accepted, native apps never activated, one box lock instead of two slots). Each now cites the code that holds today, and none cites the planning docs or the untracked dogfood reports. * docs: correct CONCEPTS.md against the shipped adapters Several definitions described one platform as if it were every platform, missed the Linux adapter, cited probe rows that measure something else, used a screenshot flag that does not exist, and called dogfood reports committed. Session Manifest folds into Session and the must-catch fixture into Gate Self-Test, where each was already half defined. * docs: correct contributor docs, the FAQ, the JSON contract and runbooks CONTRIBUTING.md now points to CLAUDE.md for the engineering rules instead of keeping a second copy that had drifted (a MockAdapter that does not exist, an E2E command that exits without the exclusive-desktop variable). The JSON contract described an unknown disposition the code no longer emits, the FAQ repeated the README platform table, and the Windows runner runbook cited plan ids and a trigger branch that merged. * ci: assert the release-ffi unwind profile once per release ffi-release-panic-guard already runs the identical grep and publish-github needs it, so the copy in ffi-release-gates added nothing. * chore: let release-please bump the Linux skill version skills/agent-desktop-linux/SKILL.md carries the x-release-please-version marker and is published with the other skills, but was missing from extra-files, so its version would have stayed behind. * docs: make the test harness READMEs runnable as written Several harness commands failed as documented (the Linux consumer run without AGENT_DESKTOP_WM_ROOT, the discovery inversion without a header path, a headless probe binary that swiftc names differently) and the fixtures README listed files that do not exist. The Linux E2E guide drops plan history and keeps what an operator needs to run and read it. * Revert "chore: stop shipping the jev-desktop skill" This reverts commit 34826e104af928a345dbdfeaa83a9a9b72745fb0. * docs: fold the ref-root viewport lesson into the clipping doc The shared clipping doc already states the same incident and the repair that superseded it, so the lesson keeps one home. Validated in the docs removal ledger (docs-14). * docs: say jev-desktop needs a repository clone, and match it to its scripts The skill teaches node scripts/jev/*.mjs, which ship in no installed package, so it now says up front that it needs a clone of the repository and a TYPESAFE_API_KEY. Checked against the scripts: run.mjs retries 429/503/529 twice while act.mjs does not, --bin and --execute belong to act.mjs only, act.mjs sends longer values and the ancestor path, why is null on act, the stop list and exit codes were incomplete, and JEV_VERIFY_MS was missing. * docs: correct FFI handle, wait and last-error text against the header ad_free_handle works after the adapter is destroyed (native_handle.rs tests prove it), so the rule is to free handles before destroying the adapter only for use, not for release. The wait bullet now uses the header's AdWaitMode field names, AdActionStep lists mechanism and verified, the delivery-semantics accessor copies values instead of lending a pointer, and the JSON-string table gains ad_execute_by_ref_timeout, ad_trace_show and ad_trace_export. * ci: cut duplicate jobs and steps from the CI workflow Each removed piece repeated a check that another job already runs on the same OS, or depended on a file that is leaving the repository: - platform-check: three legs were subsets of clippy --all-targets on the same runner; the windows-11-arm leg is now a clippy step in test-windows-arm. - ffi-header-drift, ffi-python-smoke and ffi-passthrough now run as conditional steps on the ffi-panic-guard legs that used the same OS, so the header drift gate and the stub checks keep running on every PR. - check-release-consistency.sh and check-npm-package.js run in the supply-chain workflow on every PR, push and weekly, and need no OS. - the ubuntu bash3 compatibility run was weaker than the macOS Bash 3.2 run. - trash and trash-cli installs had no caller left. - check-phases-ledger-citations.ps1 read docs/phases.md, which leaves the repository, and would fail the Windows lane. The viewer_filter.js test moves here from the inspector workflow, so the shipped viewer code keeps a CI test. * chore: remove the unshipped inspector web UI tools/inspector is a local developer viewer that no shipped artifact, skill or release step uses, and its workflow only tested it. The viewer_filter.js test it also ran now lives in the CI workflow. * ci: give the release dry run a concurrency group and state why it stands alone The reason it is a separate read-only workflow lived only in docs/phases.md, which leaves the repository. A concurrency group stops a busy PR from queueing many full rehearsals, and trash-cli had no caller left. * ci: run the Windows capability probes only when probe files change The probes measure the hosted runner. The one contract they check against crate code, secure-value withholding, is also a plain unit test in the Windows CI lane, so code-only PRs no longer pay for a 30-minute probe run. * docs: document the jev scripts beside the code The jev-desktop skill left the shipped skill set, so its usage notes move next to the scripts they describe, checked against the current flags and environment variables. * chore: remove an unreferenced benchmark screenshot No file links to it. * docs: fix three FFI ownership and panic claims a review disproved A codex review proved three overstatements against source: ad_abi_version returns a constant with no panic trap, so only entrypoints that can panic catch_unwind; last-error strings are borrowed and never freed, so not every returned pointer has a free function; and ad_free_handle validates the adapter before it accepts a null handle. Remaining em-dashes in the FFI references became plain punctuation. * refactor(core): drop two helpers that nothing calls RefStore::for_tests and ErrorPayload::with_recovery had no caller in any crate or test. They sit in public modules, so rustc could not flag them. Recovery hints are still attached by recovery_for_code. Ledger rows A-01, A-02. * test(ffi): drop an unused re-export from the shared test module No ffi integration test names AdIdentifierKind through the common module; the file-level allow(unused_imports) hid it. Ledger row A-08. * ci: keep the 20-minute FFI timeout on legs that only run the panic probe Only the aarch64-apple-darwin leg gained the header drift and stub smoke steps, so only it needs the longer bound. * test(linux): drop live probes that only print timings semantic_click_latency_probe and the vocabulary report, timed pass and label timings asserted nothing and no gate reads their output. The vocabulary live test keeps every assertion path. Ledger rows A-12, A-13. * refactor(linux): drop stale dead_code allows on display errors DisplayCause, as_str and display_cause_error all have production callers, so the allows only hid any future dead code. Ledger row A-15. * refactor(windows): compile the canonical lock path only for tests Only the lease tests call canonical_lock_path, so a cfg(test) gate replaces the allow(dead_code) and the doc no longer promises a future caller. Ledger row A-16. * docs: cut the README to 130 lines and move install detail to docs/install.md The README keeps the pitch, install, the observe-act loop, the agent section, the command table and a plain platform table. Install-script allowlisting, attestation, the source build, per-OS permissions and platform footnotes move to docs/install.md unchanged. * docs: record the parameterized-replacement findings beside their probes The payload shape, the background no-op and the input-context route are what a maintainer needs when running replace_range_probe.swift, so they belong in the harness README rather than in a one-trial log. * docs: state what the jev scripts actually send and restore --no-values does not withhold text passed with --text, the clipboard is only put back when it held text, and a run reads the screen more than once per turn. The README said otherwise. * chore: stop naming a removed workflow in the redaction gate comments windows-e2e.yml no longer exists; the -Path override is described by what it does. * docs: note parameterized attributes in the real-app probe guide A setter that does nothing does not prove an editor has no write path; the probe lists parameterized operations such as AXReplaceRangeWithText, and the replacement range is in UTF-16 units. * docs: remove the Numbers replacement trial log now its findings live with the probe The real-app README carries the payload shape, the background no-op, the input-context route and the inventory lesson. Validated in the docs removal ledger (docs-15, codex and opencode both REMOVE). * chore: make infra comments match the repository The gitignore comment said no normalized twin is committed, the Windows e2e runner said no CI workflow touches it, one gate comment named a function that is being removed, and another described docs/phases.md as in the repository. * docs: make the PR template gate commands run as written The E2E line exited 2 without AGENT_DESKTOP_E2E_EXCLUSIVE=1, a string HOST_PKGS reaches cargo as one argument in zsh, and the skill checklist left out the Linux skill. * fix(npm): point the postinstall skill hint at commands that exist claude mcp add-skill is not a Claude Code subcommand. The hint now names the binary's own skills command and the skills installer the README documents. * docs: drop the jev README now that the jev-desktop skill documents the scripts Two descriptions of the same scripts would drift; the skill is the one that ships. * docs: shape the README for users, contributors and agents The fast path stays on top (install, a quick start, the agent section), followed by reference sections and a documentation map that points each reader to the right file. It restores facts the shorter version dropped: headless-by-default delivery, PowerShell ref quoting, the readable JSON sample, the accessibility-tree row and the per-OS package sets. * docs: name the CI job that now runs the FFI Python smoke The ffi-python-smoke job was folded into the macOS leg of the ffi-panic-guard job, so two docs pointed at a job that no longer runs. * refactor(core): share empty_trace_dir_error between trace show and export The two copies were byte-identical, so one pub(crate) helper in trace_read now serves both calls. Ledger row B-07. * ci: give the Linux live e2e job an explicit read-only token The job inherited an empty permission set, unlike its macOS twin; checkout needs contents: read and nothing more. * ci: pin the probe workflow's checkout to the same release as every other workflow It was the only workflow left on v7.0.0. * docs(core): make two rustdocs match the code they describe transform_tree prunes on is_ref_able, not on role alone, and the drill-down completeness note belongs on run_from_ref_with_context, not on count_nodes. * docs: state the jev read cost for run.mjs as well as act.mjs lead-infra's review found the skill gave the snapshot cost for act.mjs only. run.mjs reads the screen before and after each action (none after DRILL or WIDEN), and both scripts add a surface snapshot when an overlay is open and no region is pinned (desktop.mjs:62-76, act.mjs:236-237). The other five corrections already held in the merged text. * fix(cli): list linux among the skills get aliases skills get linux already resolves, but the help text left it out. * test(visual-debug): stop naming the removed inspector in a test env var The capture child marker now says what it marks, since tools/inspector is gone. * refactor: replace per-crate ensure_budget copies with Deadline::ensure_remaining Twelve private macOS helpers, two private Linux helpers, and the shared Linux bus and Windows permissions helpers all had the same body. One Deadline method now carries it, with unit tests. The DeliveryTracker variants, the mouse and chain variants, and Linux input/deliver.rs keep their own checks because they add delivery details. Ledger row B-01. * docs: restore rules and definitions an earlier cut dropped CLAUDE.md again states the @generated exemption, that every non-doc comment form is rejected while //! module docs are allowed, and that expect and panic are test-only like unwrap. CONCEPTS.md again says a ref-addressed drag picks up at the bounds center (and the slider workaround) and names the must-catch and must-pass fixture halves. * docs: restore two oracle contracts to the Linux E2E guide The rewrite dropped what the pointer delivery legs read back and what the dead-owner recovery leg requires, both of which an operator needs to judge a failing leg. * chore: keep the gitignore note on which capture twins are tracked Per-area probe directories commit their normalized twins; only the twins under probes/<platform>/captures/ are regenerated and untracked. * docs: say in Coordinate Fallback that raw --xy input focuses nothing A raw coordinate resolves with no ref and no window identity, so the caller owns where it lands; the fallback entry now states it directly. * refactor(windows): drop file-level dead_code allows in system modules Windows clippy with -D warnings passes with all twelve removed, so every item in those files is used and the allows only hid future dead code. The menu source agreement test no longer cites the allow. Ledger row A-22. * refactor(windows): parse w-<n> window ids in one place observe and surfaces carried byte-identical parsers with the same InvalidArgs message; window_ops now owns parse_window_id next to window_id. Ledger row B-08. * refactor(linux): share the already-satisfied pointer select result The combo and row select paths built the same skipped XTEST.click result with their own copy of the route constant; select_pointer now holds one. Ledger row B-11c. * refactor(linux): share clipboard delivery helpers between X11 and Wayland set_clear and wayland carried the same verify grace, fresh deadline, and not-delivered and unverified error builders; delivery.rs now holds one copy and messages stay with the callers. Ledger row B-11d. * refactor: build stale-evidence errors with AdapterError::stale_ref_because Three adapter copies of stale_evidence_error repeated the body of the existing core constructor, so the resolvers now call it directly. A core unit test pins its code, suggestion and disposition. Ledger row B-02. * refactor: move the live-evidence completeness helpers into core incomplete_live_evidence, essential_live_evidence_complete and known_actions were byte-identical across the adapters and work only on core locator types; live_element now owns them, with unit tests. Ledger row B-05. * refactor: move notification identity verification into core The macOS and Windows checks were byte-identical over core notification types, so core now exports verify_notification_identity with unit tests. Linux keeps its own check because its suggestion and disposition differ. Ledger row B-06. * chore: point the stale-ref gate at the constructor callers now use stale_evidence_error is gone; evidence-refused refs now build their error with AdapterError::stale_ref_because, so the comment and the failure hint name that. * test(e2e-linux): drop plan requirement ids from leg descriptions R9, R19, R22, R24, R32, R33 and R35 named requirements in a plan that has left the repository, so the prefixes meant nothing to a reader. Nothing parses the requirement text; leg ids are unchanged. * chore: stop the privacy scan comment from claiming the repo ships planning docs * test(e2e-linux): drop duplicate legs and dead harness helpers Six legs repeated the observation of a stronger surviving leg on the same tiers, two unit tests restated another test's assertions, and several helpers had no caller. Each row was validated by two independent models (ledger TR-145..175). * test(e2e-windows): drop a racing zero-timeout leg and dead fixture code The delayed-enable zero-timeout leg raced the fixture timer while the permanently disabled leg proves the same no-wait refusal; the click row's mechanism key is now asserted instead of ignored, and an unused fixture overload and an always-true self-test line are gone (ledger TR-149, TR-151, TR-152, TR-186). * test(e2e): fold the absolute-timeout exit code into the deadline test The removed test repeated the descendant-deadline test and added only the 124 exit code, which the survivor now asserts (ledger TR-185). * ci: run the four hermetic scripts self-tests in the fmt job perf_ab_probe_test.py, test-linux-live-gate.py, test-session-env.sh and test-remote-linux.sh guard the perf report math, live-gate shard and receipt rules, desktop environment selection, and the remote kill guard, yet no job ran them; each is stub-driven, needs no display or network, and takes about a second. The two that start private sessions stay manual (one already runs in linux-e2e). * test: make every self-rerun test prove its child ran the target test A test that re-runs its own binary with a libtest filter passed with zero tests run when the filter was wrong, which already hid the Linux cursor overlay check (its filter missed the overlay module). Each self-rerun site now asserts the child printed "1 passed; 0 failed"; the overlay filter is corrected, and the interactive clipboard test compiles again under its feature. * test(e2e-linux): drop duplicate contract self-tests and dead test helpers Registry and gate checks were asserted twice; the surviving registry test now also refuses an empty registry so it cannot pass vacuously (ledger TR-177..183). * test(core): drop tests that restate a stronger test or only exercise a mock Each removed test was matched by a surviving test at the same or a stronger boundary, or asserted only a mock's or a constant's own value; two merges keep the unique assertions (role round trip, toggleable negatives) in the survivors (ledger TR-001..019). * test(macos): drop duplicate and tautological tests, fold unique assertions into survivors Survivors now carry the sibling-radio suggestion, the AXOpen stale classification and the native plus DOM identifier pair (ledger TR-058..074). * test: drop CLI and conformance tests that only exercise their own mock The conformance window-identity tests asserted the mock adapter's behavior, and the version and wait-metadata assertions repeated stronger tests (ledger TR-142, TR-143, TR-161..163). * test(ffi): drop duplicate C ABI tests and keep their unique checks in survivors Typed discriminant round trips, out-of-range values, a second trapped panic and the last-error pointer across successful calls now live in the surviving tests (ledger TR-035..044). * test(windows): drop duplicate dispatch, notification and state tests The adapter's inline tests were byte-identical to adapter_tests.rs; the R2 advertisement tests now live as rung call counts and isolated advertisement cases in the action and tree tests (ledger TR-073..091). * refactor(linux): import test-only step types in the select tests, not production select_row.rs and select_pointer_combo.rs carried cfg(test) imports only for their tests' use super::*; the tests now import ActionStep and StepMechanism. * test(e2e): add error-envelope and batch failure legs to the macOS harness Agents branch on exit code and error.code for a bare @e1, an unknown app, a missing surface and a parse error, and on batch's continue versus stop-on-error results; no live leg proved them. Self-tests check every expected code exists in the core enum and that each leg asserts code and exit together. * test(e2e-linux): add error-envelope and batch failure legs Two registered legs drive the real binary on both X11 tiers: recoverable error envelopes (INVALID_ARGS for a bare @e1, APP_NOT_FOUND with exit 1) and batch per-entry failure with and without --stop-on-error, observed on the app itself. Pure verdict tests with lying fakes keep them checkable without a desktop. * test(e2e-windows): add error-envelope and batch failure legs Eight Reliability legs cover INVALID_ARGS, SNAPSHOT_NOT_FOUND, STALE_REF for an absent element, APP_NOT_FOUND and ELEMENT_NOT_FOUND with exit codes, and batch halt versus continue with per-entry codes. Not yet run: they need a Windows host (contract -SelfTest, the U7 self-tests, then the live suite). * refactor(core): route deadline clock reads and poll sleeps through one test-freezable seam Several core tests flaked on loaded CI runners because their deadlines ran on the real clock. Deadline now reads time and sleeps through crate-private now() and sleep(), which outside cfg(test) are the same std calls; tests can freeze and advance a thread-local virtual clock. Public API and production behavior are unchanged (codex review: no behavior change). * test(macos): replace the inventory retry wall-clock assertion with counted attempts The test asserted more than one attempt inside a real budget, failed on 6 branches when a slow runner fit only one, and 45b28ce9 only raised the budget to 500 ms. The retry and the partial-inventory refusal are now proven by attempt counts on a fake capture that never consumes its deadline. * fix(windows): confirm a target's exit on its process handle before reporting a menu-wait read error A window that stops pumping during teardown reads as APP_UNRESPONSIVE before its process object is signaled, so the single liveness re-check lost the race and the wait reported the transient code instead of STALE_REF (failed on 7 branches). The wait now blocks on the process handle for at most the remaining deadline, capped at one second, before it keeps the read error. First run on Windows CI. * test(linux): drop duplicate input, close, tray and tie-break tests Each removed test repeated a survivor at the same boundary; the unique recovery text, baseline counts and tie cases moved into the survivors (ledger TR-046..057). * test(e2e): add stale-ref and key-combo legs to the macOS harness The removed-element leg accepted four error codes; it now requires STALE_REF with a safe retry. A RefChurn leg proves STALE_REF and AMBIGUOUS_TARGET after an in-place UI swap, and a press leg proves combos by reading the field itself. * test(e2e-linux): add stale-ref-after-UI-change and key-combo legs The stale leg refreshes nothing between a real menu change and the click and requires STALE_REF with its recovery; the press leg checks ctrl+a, backspace and return by AT-SPI text, not by the command's own ok. * test(e2e-windows): add stale-ref contract and key-combo legs The removable row now reports clicks, so a stale ref proven by refusal also proves nothing was delivered; press legs cover ctrl+a, shift+tab, return and the blocked alt+f4 refusal. Not yet run: needs a Windows host. * docs: point the live-gate runbook at the focus leg that stays The lift leg duplicated lifecycle.focus_window_active_reads and never read the restoration it named, so the tests cleanup removes it; the retained live owner is the active-window read. * test(core): run the settle-loop and artifact-lock budget tests on the virtual clock These tests failed on loaded Windows and macOS runners because a real 50 ms lock wait or settle window expired before the code under test ran; a 16 x 20 ms retry wrapper hid it. They now freeze the virtual clock, so lock polls and settle sleeps advance time deterministically, and the retry wrapper is gone. * fix(windows): treat E_UNEXPECTED on a UIA read as a transient failure A provider tearing down a window or shell host answers one read with E_UNEXPECTED; it was classified terminal and narrowed to a non-retryable APP_UNRESPONSIVE, so one read ended a whole wait (failed on 4 branches). It is now a retryable unresponsive read inside the caller's existing deadline; E_FAIL stays terminal. First run on Windows CI. * test(linux): make vacuous assertions able to fail Repaired tests named a behavior their assertions could not detect (a negative check that any value passed, a banned-name list with dead entries, an unverified result asserted only as not verified); each now checks the stated contract. * test(macos): make vacuous assertions able to fail, each proven by a production mutation Process-group kill, semantic-only disclosure chains, single capture, strategy fall-through, scan tolerance, endpoint naming, overlay acknowledgement, the select depth cap and the name-plan slots now fail when the behavior breaks. * test(ffi): make C ABI tests assert what their names promise Modifier cap, JSON error forwarding, invalid discriminants, envelope fields, dirty out-params on a worker thread, wait error envelopes and the session adapter's fail-closed path each had an assertion that could not fail; every repair was proven by breaking the production path once. * test(e2e): make harness self-tests fail for the regressions they name * test(e2e-linux): make harness self-tests and a menu leg assert structured results The menu timeout leg now checks the structured wait_timeout details, and the oracle, runner, session-sweep, workflow and verdict self-tests assert the specific failure each one names. * test(e2e-windows): replace diagnostic scaffolding and soft guards with real assertions The headed double-click leg used debug output, a fixed sleep and one read; it now uses Assert-Effect like its siblings. The handle-set and overlay controls no longer skip silently, and the Chromium rate leg fails when every sample is stale. Not yet run: needs a Windows host. * test(core): drop find, get, wait, session and trace tests that repeat stronger tests The removed find tests ran a cfg(test)-only snapshot collector production never uses; that collector's count helper went with them. Merges keep the observed-role negative, the null-bounds case, the conflicting wait flags as a table and the identifier preference in the survivors (ledger TR-020..034). * test(windows): drop input fake-sink, policy, clipboard and surface tests that repeat survivors The keyboard and mouse send tests only checked that the test fake stored what it was given; the clipboard, chain and surface merges keep their unique cases as table rows or assertions in the survivors (ledger TR-092..106). * test(windows): drop tree property, hit-test and resolve tests that repeat survivors Each removed test had an identical or stronger twin in the same module family (ledger TR-124..140). * test(linux): make clipboard, keyboard-frame, remap, capture and close tests assert their contracts The remap test now proves the deadline cancelled after the remap and that the keymap was restored, the off-screen capture test places its window instead of skipping, and the close gate is checked for both graceful and forced closes. * test: make CLI dispatch, overlay and real-app tests assert what they claim Real-app tests now fail when their precondition is missing instead of passing silently, the overlay show test pins POLICY_DENIED, and the conformance test checks each capability's outcome, not only that the call returned. * test(real-apps): assert the Numbers failure stop and drop a dead plan citation * test(macos): make capture-scope, screenshot and child-label tests assert their contracts The capture-scope tests now run the production scope filter instead of a test copy of it (the single-pid case is kept beside the pid-set case), the screenshot timeout checks the child is gone, and the child-label rule is checked for every role it names. * test(core): order concurrent snapshot writers instead of retrying lock timeouts, and pin tray ids Eight writers polled one lock with no fairness and a 30 s retry hid starvation that failed CI on four branches; the writers now take turns, and a single-thread test on the frozen clock proves a held store lock still blocks a writer. A new test pins that allocation keeps a RuntimeId identifier and its kind, which no test covered after the tray identity change. * test(windows): drop lifecycle, launch, HRESULT and signal tests that repeat survivors Merges move the exit-code serialization cases into core's process-state tests and the HRESULT detail strings and generation checks into the surviving Windows tests (ledger TR-108..121). * test(windows): drop two duplicate tree tests and make hit-test and resolve tests assert their contracts hit_test now judges a probe through judge_probe, so a probe miss is tested on the production path (same behavior: a miss is Unknown, a hit is judged); the fixture teardown, minimized-window, anchor and timeout tests assert what they name (ledger TR-122, TR-123, TF-144..157). * test(linux): make capture-cap, tie, key-owner, menu and tray tests assert their contracts * test(core): run live-locator and ref-action wait tests on the virtual clock These tests had their budgets widened and an exact attempt count loosened to 'at least one' to survive slow runners; on the frozen clock the retry loop's own sleeps advance time, so the original exact counts and small budgets hold. * test(windows): drop the setup-time stopwatch from the headless notification wait refusal The elapsed < 2 s assertion measured COM and adapter setup and failed twice on loaded runners; POLICY_DENIED is not retryable, so a burned deadline would already surface as TIMEOUT and fail the code assertion. * test(windows): decide modern-capture frame-wait expiry inside the wait, not by wall time The fallback test asserted TIMEOUT against an 80 ms budget that real capture setup could consume first (failing as INTERNAL); the cfg(test) frame hold now starts its budget inside the frame wait, and the pattern test waits for a frame showing the fixture instead of sleeping 500 ms. Production frame wait unchanged. * test(windows): signal the menu fixture's open state from real menu mode The fixture announced the popup before TrackPopupMenu entered menu mode and ignored its result, so the chromium composition test compared two live reads that could straddle the flip; readiness now comes from menu mode itself and the comparison waits until both sources agree. * test(linux): make tray, signal, settle, walker and shell-owner tests assert their contracts The error-code boundary test now enumerates codes exhaustively at compile time instead of a hand-typed list of 16, and the remaining repairs check the behavior their names state. * test(macos): replace real-clock budgets and stopwatches with counted attempts and events Stabilize-loop tests could expire before their first attempt on a slow runner; they now use budgets the fakes never consume and assert exact attempt counts, and the overlay acknowledgement tests block on a release channel instead of a 700 ms sleep. * test: isolate command-host sockets per test and assert batch deadlines by value Fixed socket and symlink names keyed by pid were never removed, so pid reuse could fail a later run; each test now owns a removed-on-drop directory. Batch deadline tests assert the budget the adapter received instead of wall time. * test(ffi): isolate the round-trip test's state root and require the snapshot log event The round-trip test set HOME under its own lock but left AGENT_DESKTOP_HOME alone, so a developer's state root leaked in; it now uses the shared isolated home helper. Log-callback tests no longer fall back to the first delivery. * test(core): make actionability, trace, find and close tests assert their contracts Each repaired test was proven by breaking its production path once. The benchmark example's rect test moved into the library's rect tests so it runs in CI, and the find roles hint is now tested through execute instead of a cfg(test)-only copy, which is deleted. * test(windows): make dispatch, envelope, scroll and fixture tests assert their contracts Permissive Err arms, restated literals and a self-comparing fixture expectation are replaced by assertions on the delivered outcome. * test(linux): make tree walker, projection and placement tests assert their contracts * test(core): make session, overlay-routing, hover and pointer tests assert their contracts Session precedence tests pass the env value directly instead of mutating the process environment, gc is actually exercised on an unreadable manifest, the skeleton golden compares the whole tree, and hover counts receiver checks instead of timing them. * test(windows): make drag, capture, HRESULT, signal and wait tests assert their contracts Assertions true by construction (an intersection member found in its own set, an assert_ne after an exact assert_eq, a uniqueness loop over a test-local list) are replaced or removed, and the remaining checks name the behavior they guard. * test(linux): make resolve tests assert the unread-region refusal they name * test(core): drop the null ref_id from the skeleton golden fixture The skeleton golden test now compares the whole serialized tree, and the serializer omits a missing ref_id, so the fixture must not carry one. * test(core): make ref, refs, trace-read and policy tests assert their contracts The XSS gate now asserts the exact escaped payload, the oversize-refmap guard is exercised through the production writer, poll and wait tests count resolves instead of timing them, and a new test pins identifier kinds through RefEntry identity. Each repair was proven by breaking its production path once. * test(windows): make lifecycle, overlay spawn and shell-surface tests assert their contracts A tautological ambiguous-target shape check now serializes the production value, a self-comparing error-code helper is gone, and the taskbar test no longer returns early on an empty tree. * test(npm): drop duplicate platform and wrapper tests and check the real staging directory The staging-cleanup tests read the destination, which installArchive never stages in, so a cleanup regression still passed; they now compare the package bin directory before and after. Duplicate platform-table and wrapper tests are gone, with their unique helper-name and Trash-warning checks kept in the survivors (ledger TR-189, TR-191..193, TF-187, TF-188). * ci: run the Numbers sheet-script self-test in the fmt job numbers_sheets_test.py drives numbers-sheets.sh against a fake binary in a temporary home and proves its failure stop; it is hermetic and nothing ran it. * test: nest extra test modules so moved anchors keep room under the line cap ref_action.rs (400 lines) and the Windows tree/resolve.rs (398 lines) become their folders' mod.rs in the restructure and must then declare their new child modules. Their extra test modules are declared from the files' tests modules instead, so both anchors stay under 400 lines. The nested test files import their parent by absolute path, keeping what `super::` meant before. The Linux action scan walks src/actions recursively, so the banned-call scan keeps reading the action files that move into select/, scroll/ and write/, and it asserts files from each of those groups are still scanned. * refactor(cli): give the command host and diagnostics their own folders Nine command_host*.rs files and three diagnostic*.rs files sat loose in src/, so the binary's root read as a flat list. Each family now lives in a folder whose mod.rs is the former entry file; the helpers were already #[path] children, so no module path changes. * refactor(core): group core's leaf types into response, input, notifications, storage and wait Sixty-seven of core's 270 root files are the envelope and error model, the input and notification parameter types, the on-disk state root, deadlines and signals, plus adapter and session helpers. They now sit in folders named after the concepts in CONCEPTS.md, so a reader finds a type by its domain instead of scanning one flat list. Module paths gain one segment; root re-exports in lib.rs keep every public name unchanged, and adapter becomes a public module because it now holds the public hit_test, live_element and native_handle modules. * refactor(core): group core's tree, action, system and snapshot types by domain The accessibility-tree model, the Action model and its execution types, app and window and process types, snapshot building and the context module were 115 loose root files. They now live in tree/, actions/, system/, snapshot/ and context/, named like the platform crates' layers so a type a platform crate uses from its input/ or system/ lives under the same name in core. The stray actionability tests move into actionability/. Two test files import one module once so the longer paths keep them under 400 lines. * refactor(core): group refs, ref actions, locators and traces into their folders The ref map, ref store, allocation and identity files, the headless ref action flow, the locator with its live evaluator, and the trace writer with its reader were the last loose families at core's root. Each family now lives under the module that already anchored it (refs.rs, ref_action.rs, locator.rs and trace.rs become their folders' mod.rs), so those module paths do not change. trace becomes public because it now holds the public trace_read and trace_sanitize modules. lib.rs is the only file left at the root. commands/ stays flat: its contract tests require commands/<name>.rs. * refactor(macos): group the macOS layers into concept folders Each layer held one flat list: 62 files in tree/, 68 in system/, 45 in actions/. Concepts with four or more sources now have a folder named with the vocabulary all three platform crates share: tree/resolve/ and tree/properties/, actions/chain/ and actions/select/, input/clipboard/, keyboard/ and mouse/, system/app/, process/, window/ and permissions/. cf_type.rs leaves the crate root. The cursor-overlay and permission-guide Objective-C bridges move next to the Rust files that declare them, with build.rs and the native test imports following. File names do not change. * refactor(windows): group the Windows tree layer into concept folders tree/ held 117 files in one folder. Ref re-identification, the Win32 test fixtures, the UIA walkers, the property reads and the hit test now each have a folder (resolve/, fixture/, walk/, properties/, hit_test/), using the same names as the other platform crates. The fixtures' self-rerun filters and the hit-test scan labels follow the moves, and the public tree paths used by the uia_tree_dump example and the resolution probe are updated with them. * refactor(windows): group the Windows system and input layers into concept folders system/ held about 70 loose sources and input/ about 26. Capture, windows, shell surfaces, wait signals, the COM runtime glue, menus and the shared test seams now each have a folder under system/, and clipboard, keyboard and mouse each have one under input/, with the names the other platform crates use. Public names in lib.rs do not change. Four clipboard helpers now sit under the clipboard module's existing cfg(target_os = "windows"); the crate only builds for Windows, so the set of compiled and run code is the same. * refactor(linux): group the Linux tree layer into concept folders tree/ held 209 files in one folder, the most crowded in the repository. It now has thirteen concept folders (walk, resolve, hit_test, menu, surfaces, placement, live_read, observe, properties, element, vocabulary, capabilities, live) and keeps two loose sources. All live rigs and live suites sit in tree/live/, so the live gate selects them by one prefix; its module strings, its self-test and the runbook follow the moves, and the PARALLEL_READS entry for a module that never existed is dropped. * refactor(linux): group the Linux action, input and X11 layers into concept folders actions/ held 95 files and input/ about 57 loose files, and system/x11/ (87 files) and system/window/ (57 files) had grown past one screen. Selection, scrolling, writes and live rigs now have folders under actions/; keyboard, mouse, physical targeting and the clipboard owner have folders under input/; the X11 client splits into capture, control, monitor, stacking and window, and the window operation pipeline gets window/window_op/. system/ keeps the shape it already had. The live gate's module strings follow the moves. * ci: fail the build when platform code leaks or a crate's layout drifts Platform isolation was checked only through cargo tree on CI and one PowerShell scan, so a source-level leak or a file dropped in the wrong layer reached review unnoticed. scripts/check-architecture.sh checks manifests through cargo metadata and sources through a small lexer: core depends only on its listed crates, only the binary and FFI wire platform crates, no platform crate reaches another, core keeps OS-conditional code in an exact allow-list, every crate keeps its top-level layout, capabilities are implemented in their layer, core domains do not reach commands, and tests are declared beside their code. It plants a violation of every rule before it scans, so a rule that stops detecting fails the gate. It runs in the pre-commit hook and next to the source rules in CI; the cargo tree steps stay as the transitive check. * docs: describe the crate layout and the guard that keeps it CLAUDE.md described the platform layers but not where core's files or a layer's sub-concepts go, so the next contributor or agent would guess. It now states core's domain folders, the shared platform layout and subfolder vocabulary, the file-naming and test-placement rules, and the grounding rule that enforces them. * docs: follow the source restructure in path citations The restructure moved test and module files into feature folders, so three code-span paths and a handful of folder-relative citations in the solution docs, CONCEPTS.md and two test READMEs named files that no longer exist. * test: point the e2e error-code contract at the moved error_code.rs error_code.rs moved to crates/core/src/response/ in the core restructure. The e2e harness contract test builds the path from parts, so the literal path rewrite did not see it and two contract tests failed to open the file. * docs: re-point short-form file citations at their moved files Seven citations named a moved file by its path relative to its crate (for example system/shell_surface_raise.rs), a form the full-path rewrite did not match. Each now names the file's new folder. Short citations of files that did not move, such as the Windows actions/ labels, are unchanged. * test(e2e): read written fields directly and run the host ref-churn leg fully hosted The first live run failed five legs on the harness, not the product: batch and ref-churn checks read SwiftUI echo labels that set-value does not promise to update, and the host-client variant enabled the host only for one command and used a non-private temporary directory, so the host refused it with INVALID_ARGS. The legs now read the field's own value, and the whole host run uses the host and the private temp dir. The stale-ref and close-app assertions are unchanged: they expose pre-existing product defects. * test(e2e): skip three known product defects by exact signature and end the churn fixture by pid No failing leg ships, and no real defect is hidden. The scroll, default-CLI stale-ref and close-app cases skip only when their known defect signature appears, naming the defect, file and origin commit; any other result still asserts, so each case re-arms itself once the product is fixed. When close-app leaves the RefChurn fixture running, the leg ends its own process and asserts it is gone. * fix(macos): let close-app end a process that has no LaunchServices launch date Termination refused whenever NSRunningApplication reported no launch date, so close-app could not end an app launched outside LaunchServices, contrary to the rule that absent evidence is not conflicting evidence. The bridge now passes whether a date is present, and only a present date more than five seconds from the verified instance refuses; pid, start-time token and exit checks are unchanged (codex review: only the intended change). * fix(macos): reveal a clipped scroll area through its enclosing scroll view Scroll-into-view looked for the clipping viewport starting at the target, so a target that is itself a scroll area became its own viewport, produced no direction, and stayed clipped (ACTION_FAILED, live offscreen). The original walk still runs unchanged; only when it returns the target itself does the search repeat from the target's parent (codex review: only the intended change). * test(e2e): assert scroll and close-app again now that both defects are fixed The two signature skips would hide a regression of the fixed defects, so the scroll case asserts unconditionally again and a surviving churn fixture after close-app fails the leg while the leg still ends its own process. Only the default-CLI stale-ref skip remains, for the documented aliasing gap. * test(e2e): gate two more known product defects by signature and stop the command host Live, an unknown-app lookup returned TIMEOUT because NSWorkspace listed one pid twice and the app inventory refuses a list with a skipped record; that case and the batch entry using it now accept only that exact signature, naming the defect. The default-CLI sibling check shares the documented aliasing defect. The hosted ref-churn run now stops the command host it started, which otherwise kept the desktop lease for its 15-minute idle life. * fix(e2e): parse only stdout from captured command output The binary prints its JSON envelope on stdout and tracing warnings on stderr. Every capture the suite parses as JSON merged stderr, so one warning line made json.loads fail. A WebKit WebContent process that NSWorkspace lists twice now makes every --app lookup warn about a duplicate pid; AE4 read an empty window list and aborted the suite. All captures now discard stderr, matching find_target, and a harness self-test refuses a capture that merges it again. * chore: move runbooks and residual review notes out of git into the local planning folder They are operator and review working notes, not product docs: no code, test, workflow or doc reads them. They now live with the other planning material in the globally ignored .compound-engineering/ folder. * test: restore two test assertions the repair pass over-tightened and stop echoing a session id CI showed both repairs were wrong, not the product. The Windows dispatch test now accepts any deliberate refusal again (headless double-click is refused with POLICY_DENIED by design), as it did before. The macOS helper timeout test asserts it returns within a second again, which is what its name promises; the replacement assertion held on one machine and tested nothing about blocking. The overlay test message now prints only the field it checks, so it no longer writes a session id into a failure message (CodeQL rust/cleartext-logging). * docs: tighten test-quality learnings and note the dropped accname agreement test * docs: point two measurement learnings at the code and scripts that hold them * docs: correct the Windows hardening claim in the CI-lane learning * docs: point the snapshot window citation at current code and tighten policy learnings * docs: drop a cited regression test that does not exist * docs: tighten accessibility runtime learnings without changing their facts * docs: tighten Numbers and Xcode learning wording * docs: use the measured popover timings in the wait-for-closed learning * docs: tighten Xcode and native learnings; keep the empty-search learning until merged * docs: split one dense sentence in the agent contract * docs: tighten prose in the contributor and output docs * docs: tighten the find-name learning wording * docs: tighten the concepts glossary wording without changing its terms * docs: tighten test README prose and keep every command and link * docs: cut the learnings store to the lessons that are still true and live nowhere else Two reviewers checked every learning (codex Sol proposed, codex Luna validated with the burden of proof on deletion); a learning stayed whenever they disagreed. Removed dated evaluation logs, trial records and lessons already carried by code, tests, CLAUDE.md, CONCEPTS.md or the skills; moved the one unique lesson of five learnings into a surviving one; trimmed the rest to rule, fix and one evidence pointer. The two remote dev-box learnings are gone: they described one maintainer's machines, not the product. 52 files and about 46,700 words become 15 files and about 11,100 words. Citations in the e2e harness and test READMEs now point at the surviving learnings. * docs(windows): stop citing two learnings that were removed The rule each comment cited is stated in the comment itself, so the pointer is dropped rather than re-aimed. * chore: remove tooling and paths tied to one maintainer's dev machines agent-desktop is a product for any team, so the repository no longer carries a remote-box workflow: remote-linux.sh (worktree sync to an SSH alias), box-slot.sh (a lock on that box), session-env.sh (importing that box's desktop session) and their two self-tests are removed, with their fmt-job lines. Private-session sessions now default to ${XDG_RUNTIME_DIR:-/tmp}/agent-desktop-sessions, overridable with AGENT_DESKTOP_SESSION_BASE, at every site that creates, discovers or cleans them (shell wrappers, live gate, e2e runner, Rust live tests); the window manager defaults to the system install. Docs and the Linux probe ledger no longer cite personal paths. * test(e2e-linux): take the stale-ref process baseline after the app is running The leg read the process list before Calculator started, so a correct refusal failed its own 'must not end the app' check. The unit test now models the launched process and fails on the old ordering. * fix(linux-tests): keep each OS user's private sessions under their own base Without XDG_RUNTIME_DIR every user shared /tmp/agent-desktop-sessions, so the second user could not create a session. The fallback is now /tmp/agent-desktop-sessions-<uid>, created 0700, at every site that computes it. * test(macos): restore the screenshot timeout test the repair pass over-tightened The repair also required the killed child to be reaped inside the product's cleanup deadline; on a loaded CI runner the product truthfully reports that it was not, so the assertion tested the runner, not the timeout. * fix(linux-tests): let the live gate merge receipts without creating a session base --merge starts no sessions, so it no longer needs a writable session base; the self-test proves it with a base that cannot be created. * test(linux): make the proc-scan test fail with a diagnosis instead of a bare unwrap It failed once on CI with no evidence. The helper no longer proceeds silently when the child never becomes ready, and a missed scan reports what /proc shows for the child, so the next occurrence names its cause. * fix: harden desktop boundaries and bound native operations * fix: install inspector signal handlers before readiness * test(linux): stop running renamed copies of sleep, which Ubuntu 26.04 no longer supports GitHub's ubuntu-latest now includes Ubuntu 26.04 runners, where coreutils is a transitional package over the uutils multi-call binary. That binary picks its tool from argv[0], so a copy of sleep renamed for a test exits at once and the scan finds a zombie. proc_scan failed on every 26.04 run, on branches without this cleanup too. One shared helper now copies the standalone system shell and blocks it on a held stdin pipe, which keeps the test's name on any Linux; the five tests that renamed sleep use it. * test(linux): keep blocking fixtures alive in the background without stdin A background command in a non-interactive shell gets /dev/null as stdin before any redirection, so the orphan launcher's blocking copy read EOF and exited. The copied shell now loops on 'sleep 1' instead of reading stdin; that keeps it alive under its own name in the foreground and the background alike, and lets both launchers return to their original form. * test: make native fixtures portable across CI hosts * test: use an owned process fixture for Linux discovery * fix: preserve trace readability and clear stale overlays * perf: avoid repeated trace ledger replacements * fix: reject shared helper writes and isolate Linux test fixtures * fix(linux): list every top-level window the registry exposes, whatever its role The window list admitted a root child only for roles 23, 16 and 69, a modal panel or a showing filler. Toolkits give a top-level window the role of its widget, so the GTK3 Save As file chooser (19), a Qt message box (2) and a Qt push button shown as a window (43) were never listed, while the modal gate, which reads every root child, refused focus because of the same dialog (L41-1). A root child that implements Component is now a top-level, and one predicate decides it for listing, frame identity, menu inventory and the shell content window. Dialog-ness is one predicate too: the AT-SPI dialog roles read from libatspi (alert, color chooser, dialog, file chooser, font chooser, L41-4), used by the window list, focus, sheets and nested dialogs. A blank, showing top-level that holds only menus is a popup menu surface (the GTK3 context menu and Chromium menus, L41-2, L18-7): menu observation keeps it and the window list drops it. The size identity for unnamed windows applies to every toolkit without global positions instead of GTK4 role-39 panels only, and the duplicate reports_global_position copy is gone. * fix(linux): attribute a launch handed to a running instance by its new window Launching thunar with the session's daemon running failed with "exited with status 0 without a verified application owner" although a new Thunar window opened (L41-6): the launcher forwards to `Thunar --daemon` and exits, and the attach rule also compares the argument list. After a clean launcher exit with no strict owner, the one running process of the same canonical executable that shows a top-level window absent from its pre-launch baseline now owns the launch, and with --activate that window is reported. Several such processes stay ambiguous, none keeps the failure. Strict argument identity for attaching before a spawn is unchanged. * fix(linux): publish hidden and offscreen in snapshots from the action gate's rule A snapshot gave Mousepad's hidden toolbar button a ref with no state, and the headless click then refused it as offscreen (L41-5): the snapshot projection never emitted hidden or offscreen, while the live projection the gate reads derived both. The snapshot now applies the same visibility functions to the facts its walk already holds (states, extents, frame, scroll container, minimized frame) with no extra reads; an input it lacks stays unknown and adds no token. The sentinel and placed-rectangle tests, copied in four places, now live in one module, readiness recognises both web document roles (L3-13), and the GTK4 popup origin is chosen by one rule for snapshot and live reads. * fix(linux): rebuild the desktop alias cache after an edit inside one timestamp tick The in-place desktop edit test failed 2 of 5 runs on unchanged code: the cache compared inode, length and ctime, and an edit of the same length inside one coarse kernel tick keeps the same ctime, so a stale alias index was served. Like git's racily clean rule, a cached index is now trusted only when every stamp is older than its build time minus a tick margin; otherwise it is rebuilt on the next call. * test(linux): add widget-zoo fixtures and the ref-contract e2e legs The bar for a snapshot is that every ref it gives out lands when its action is performed headless, verified by an independent read, or carries hidden, offscreen or disabled and is refused with no effect. Three fixture windows (GTK3, GTK4, Qt) expose the standard widgets, each logging its own effect, and the ref_contract legs drive every ref of a fresh headless snapshot against the log and raw AT-SPI. The window legs open the fixture's dialog, file chooser, message box and top-level button and require each to be listed, reachable as a sheet when modal, and operable to completion. The harness's own window oracle counts Component root children, the same rule as the product (L41-1). * docs(probes): record top-level, popup, visibility and launch measurements Area L41 records the toolkit census that disproves the role-based window rule of L8-2, the popup shapes that are and are not root children, the closed-dialog husks, the libatspi dialog role numbers, the snapshot and gate disagreement on Mousepad's hidden toolbar, and the Thunar daemon hand-off. The environment table names the XFCE container the new rows were measured in. * refactor(linux): state the unplaced-node test without a double negation * fix(linux): recheck racy desktop files by content instead of rebuilding the alias index Rebuilding whenever a desktop file was newer than the build tick made every lookup right after an edit rebuild, so the cache was never reused (the in-place edit test failed 5 of 5). As in git's racily clean rule, only the files that changed within the tick keep a content hash; equal stamps reuse the index unless one of those files now reads different. The test passes 10 of 10. * fix: avoid unsafe at-spi property and interface reads * test: verify linux refs against addressed raw controls * fix: include display bounds in snapshot visibility * docs: explain thunar file views and literal newlines * docs: keep file-view guidance application independent * fix: observe and resolve showing alert surfaces * fix: restore checks after the Linux cleanup merge * fix: serialize trace emission and repair review gates * refactor: align native security types with module boundaries * fix: select option rows through their advertised semantic verb * fix: avoid sibling scrollbar mutations during reveal * fix: retain unanswered scoped surface reads * fix: activate exported menus outside the accessibility dispatcher * test: select the active chooser filename field * docs: record semantic reliability evidence and headless policy * test: repair independent Linux workflow oracles * fix: preserve menu fallback and stop expired launches * fix: await queued windows and reclaim stale clipboard records * fix: preserve clipboard scan timeout codes * fix: verify combo commits and bound optional snapshot reads * fix: complete linux ref delivery and safe refusal contracts (#307) * fix: complete linux ref delivery and safe refusal contracts * fix: complete Wayland menu and chooser ref contracts * test: report unsupported action setup as unexercised * test: separate concurrent overlay fixture sockets * test: enforce snapshot promises in linux ref contracts (#308) * test: align harness checks with retained native refs * fix: prevent private desktop data from leaking through diagnostics * fix(ci): reflect successful writes in the JEV fixture | 14 days ago | |
fix(macos): reach the Dock, menu bar status items and system panels (#480) * refactor: remove the macOS command host Every CLI call and batch item now runs in its own process, as on Windows and Linux, instead of being forwarded to a long-lived background host. - delete src/command_host, its process test and the libc dependency - drop the forwarding branches in main and batch, needs_caller_process, and the host env scrubbing in the permission guide - add a process test that commands start no host and connect to no host socket - ask macOS users upgrading from 0.10.0 or later to restart once, note that permissions apply per CLI call, and that old refs must be retaken * fix(macos): find leftover command hosts by process, not by file Leftover hosts are now found from this user's process list: a shipped agent-desktop executable whose environment carries the host marker. Each one is checked by owner and start time before SIGTERM, and stopped with SIGKILL if it is still running after 500 ms. The cleanup no longer reads or depends on any file, so the old socket directory does not matter. * fix(macos): stop leftover hosts whatever the executable is named Old hosts are found by the environment marker alone, since a copied or renamed binary still carries it. npm install now runs the new binary once on macOS, so a leftover host stops at upgrade instead of at the next call. Tested against real 1.0.2 hosts, including one started from a renamed copy: both stop on npm install and the bin entry stays hard-linked. * refactor(macos): trim the leftover host stopper The first agent-desktop call after the upgrade stops old hosts, so the npm install step is not needed. Old hosts have no SIGTERM handler, so SIGTERM alone ends them and the grace wait and SIGKILL are gone. The tests keep the cases that guard safety: only an exact environment marker counts, unmarked processes are left alone, and no command reaches or removes a host socket. Checked against two real 1.0.2 hosts, one started from a renamed copy: both exit after one new call. * fix(macos): stop leftover hosts with SIGKILL A host started with SIGTERM ignored, or one that is stopped, survives SIGTERM. The process is chosen by its owner and environment marker, so the signal adds no risk to other processes. * fix(npm): stop leftover macOS hosts during install Without this, an old host keeps serving between the upgrade and the first agent-desktop call. The install runs the new binary once after linking on every successful path. A failure only warns and names the command that stops the hosts; the install and the link are unaffected. Calls still stop hosts too, which covers installs that skip scripts. * fix(macos): note the repeated-row ref limit and cheapen the host sweep Without the host, a ref to a row in a list whose rows share an identifier can act on another row after the list changes. The CHANGELOG and the macOS skill say so, and ref_churn skips the checks that relied on the host until identity-based resolution lands. The sweep now reads each process's arguments once into a reused buffer and checks start times only for marked processes, which cuts its cost from about 5.7 ms to about 4 ms per call on a Mac running around 730 processes. Checked in a VM against real 1.0.2 hosts: normal, renamed, and one ignoring SIGTERM while stopped all exit. Also: the decoy-socket test fails if any other host file appears, a new test proves a marked copy running version is not killed, the stale host-kill loop is gone from the E2E harness, and the identity doc now limits its retained-token claim to macOS. * fix(macos): stop only agent-desktop hosts and drop the pinned namespace The sweep killed any process with the old host marker in its environment, including a test Python process. It now also requires the running process's code-signing identifier to be agent-desktop's, read with csops. Every release is ad-hoc signed by cargo with an identifier like agent_desktop-<hash>, which survives renaming and deletion of the executable, so renamed and unlinked hosts are still stopped. The check runs only for marked processes, so the per-call cost does not change. Checked in a VM: real 1.0.2 hosts (normal, renamed, and one ignoring SIGTERM while stopped) exit after one call, and a marked Python process keeps running. The pinned session namespace had no production caller once the host was gone, so its field, builder, guards and tests are removed. * feat(identity): resolve refs through retained objects and a shared capture rule macOS refs now carry a retained object from the accessibility remote-token calls, looked up at runtime: a ref resolves to that exact element in a later process, and a dead or mismatched one returns STALE_REF without a search. Refs without one resolve only when they were unique over complete evidence at capture and exactly one live element matches the same predicate now; anything else is refused. Linux paths and Windows runtime ids no longer count as identity. The in-memory retained session from the removed host is deleted. * docs: redraw ref resolution for retained objects and the capture rule * refactor(identity): share one predicate between capture and resolution * fix(macos): trust a live token without an owning-window check, retry busy reads, resolve anchors by token * fix(identity): count capture uniqueness from live evidence and confirm each landing The census now reads the observed evidence, so a twin holding an id second-hand or an unread field still counts. A path landing is re-checked against the recorded predicate before it is used, a resolution under a held lease reuses it, and shell-surface refs find their container again. * refactor(macos): resolve saved refs only through element tokens Tokenless refs already go through the shared capture rule in core, so the macOS path fast path and broad search had no CLI caller left. FFI find now lands its fresh match through the locator anchor on every platform. * docs: describe how refs are re-identified on each platform * refactor(windows): drop the strict resolver that no longer has a caller Saved refs on Windows resolve through core's capture rule and the locator anchor, and FFI find lands through the anchor too, so the strict path walk, broad search, tie-break and chrome repair tiers were dead. The verified process stamp check moved into an anchor test. * fix(identity): prove skeleton and drill refs over one complete window read A shallow capture without element tokens now reads the whole window once, counts uniqueness there, and presents its skeleton or subtree cut from that same read, so skeleton anchors and drilled refs can resolve on Linux and Windows. A drill reuses the walk its root resolution performs. Captures whose refs all carry element tokens keep their single walk. * test(e2e): check the APP_NOT_FOUND hint without a case inside command substitution macOS ships bash 3.2, which cannot parse a case pattern's closing paren inside $( ), so the assertion failed with a syntax error even though the hint was right. * fix(identity): check the full predicate before dispatch and keep re-drills to their own capture Pre-dispatch validation compared only a native id, so a recycled Linux path with new text could still take the action. A re-drill compared paths across captures and could prune its own root. Drill depth now counts from the drilled element, and a cut boundary that can never become a ref no longer forces a whole-window read. * test(e2e): assert removed-row refs are refused again The ref churn checks were skipped while a removed row's ref could land on its surviving neighbour. Element tokens fix that, so the checks run again, and the CHANGELOG limitation becomes the fix it now is. * docs: drop the window check and old predicate name from the ref diagram * fix(ffi): regenerate the C header for the exact-entry refusal docs * fix(identity): re-check identity on every interval keystroke and keep the lease for final pointer checks Interval typing reused one handle after the first keystroke, so a replacement element could take the rest of the text; each keystroke now repeats the live identity check under its lease. Final hover and drag checks dropped the held lease and could wait on it again, and a tokenless drill skipped the depth validation every other snapshot runs. * test: drop the pointer lease stub; lease reuse is covered by the renderer test * test: remove the dangling pointer lease test module * fix(identity): prove find refs unique over the whole-window read they came from find materialized every tokenless ref with unique_at_capture false, so a unique match could not be used on Linux or Windows. It now runs the same census over its own observation when that observation is a complete whole-window read. * test(windows): let the hit-test envelope refs resolve through an element token * fix(identity): drop path identity on Linux and legacy anchor gates on Windows, prove rooted find refs Linux menu refs scope a tokenless entry to the open popup instead of refusing it, and tests that relied on an AT-SPI path as identity now refuse replacements through the predicate. Windows anchors land by position and leave identity to core's full-predicate check. A tokenless find --root evaluates over the whole-window read its root resolution performs, so its refs carry that read's capture proof. * fix(identity): one capture proof for every published ref, confirmed landings in FFI find, popup-bound menu refs Every tokenless ref is now proven by its own recorded predicate over the whole-window read it was published from, at snapshot, drill, find and hydration alike. FFI find re-identifies a landed handle before publishing it. A Linux menu ref keeps its popup binding through positional landing and is refused without it. * fix(identity): bump the FFI ABI for the exact-entry refusal, share the exhaustive check, align docs The exact-entry FFI calls now always refuse, so the ABI major moves to 7 and the docs say so. Capture proof and tokenless resolution share one ObservedTree::is_exhaustive check, LivePredicate moves to its own file, and AGENTS.md, the CHANGELOG, the Linux skill and the Windows twin E2E leg describe the capture rule as shipped. * fix(identity): offer a skeleton drill anchor only when it can be re-identified A boundary container is offered as a drill anchor only when it carries an element token or the capture proof found it once, so twins and unread windows no longer hand out anchors that fail when drilled. * refactor(identity): narrow this change to macOS element addresses The Linux and Windows capture rule moves to a stacked follow-up. This leaves the macOS element-address resolution on top of the existing resolvers, with the core and the other platforms as they were before this branch. * feat(identity): resolve Linux and Windows refs through a capture rule Linux and Windows expose no durable element address, so a ref there resolves only when one complete whole-window read proved it unique at capture and exactly one live element matches its full predicate now. Every place that publishes a ref proves it the same way, positional landings are confirmed against the predicate, FFI find re-identifies a landing before publishing it, and Linux menu refs keep their popup binding. The Windows strict resolver and the macOS path search lose their last callers and are deleted. * fix(identity): validate popup context before rooted reads publish refs Rooted find and snapshot drills accepted a matching predicate from a replacement Linux popup. The shared saved-ref observation now checks the captured popup binding against the same observation before refs are published. * fix(identity): keep shallow captures and read the window once per action Snapshots keep a finished shallow read and flag refs they could not prove with identity_unproven. A ref action reads the window once and still runs the full rule under the lease. Capture and resolution read the same evidence, including the forced renderer flag. Hydration keeps the proven predicate, skeleton anchors fall back to the nearest proven ancestor, token-rooted drills read only their subtree unless it holds a tokenless ref, macOS tokenless anchors land by position, interval typing reuses its first handle, and exact FFI entries refuse with ACTION_NOT_SUPPORTED. * fix(macos): read menu bar status items and the Dock Menu bar apps usually have no window, so `snapshot --surface menubar` failed with WINDOW_NOT_FOUND. Even with a window it read the app's File/Edit menu, not its status item. `dock` and `menu-bar-extras` were accepted by the CLI but rejected on macOS. - `menubar` and `menu-bar-extras` take the owner from the app list when the app has no window. The result then has no `window` field and its refs carry no window id. - `menu-bar-extras` with no `--app` reads the system status items (MenuBarAgent on current macOS; Control Center or SystemUIServer on older releases). With `--app` it reads that app's own status item. - `dock` reads the Dock's item list. - `--app` also matches the executable name, so `ControlCenter` finds "Control Center". Windows and Linux are unchanged: they reject both new surfaces before routing. Checked on Darwin 27 (MenuBarAgent) and in a macOS 26.4 VM (Control Center fallback), including ref reads from both surfaces. * fix(macos): observe windowless apps through their process System panels and menu bar apps have no layer-0 window, so the window listing never sees them. `snapshot --app "Control Center"` failed with WINDOW_NOT_FOUND while its panel was on screen. Headed clicks on Dock and status-item refs failed because they had no window to focus. - When an app has no listable window, `window` and `focused` snapshots read its process. macOS roots them at the AX application element, so the Control Center panel and its Bluetooth detail, Notification Center, the Dock and DockHelper menus are reachable through `snapshot`/`find --app`. The Dock surface reuses the same root. - Refs from that root re-resolve from the app element, which fixes `find` returning STALE_REF on those apps. - Headed input skips window focus for a ref with no source window, and skips the frontmost/main checks when the element's window is a floating panel. The final hit test still gates every click. - Windows and Linux map an empty window id to WINDOW_NOT_FOUND, so their behavior is unchanged. Window commands still use the CG inventory, because they need CG window numbers. Verified in a macOS 26.4 VM: opening Control Center, drilling into Bluetooth and opening Bluetooth Settings, the Dock context menu, Dock launches and Notification Center, headless and headed, plus TextEdit headed clicks as a regression check. * fix(linux): keep window id decoding strict for windowless owners The empty-id guard sat in the shared window id decoder, which also validates ids callers type, so an empty id stopped being INVALID_ARGS. Move the guard to the observation root, where a windowless process owner actually arrives, and leave decode as it was. * refactor: name the process-owner convention in core A background app with no window is observed through its process, and that owner was an ad hoc empty window id checked in several crates. Add WindowInfo::for_process, WindowInfo::is_process_owner and RefSource::is_process_rooted, and route every check and builder through them so no adapter or consumer can drift from the convention. * fix: tighten process-owner observation after review - Only background apps (menu bar, tray, Dock, system panels) fall back to their process; a foreground app with no window keeps WINDOW_NOT_FOUND, and the app is looked up once with its errors propagated. focused no longer claims a process fallback. - Refs from an app root must match their saved bounds, so a replacement control cannot pass strict resolution. - snapshot --root from a process-rooted ref reports the live process owner instead of requiring a window. - --app matches the executable name in the scoped app inventory too. - One AdapterError::app_has_no_window shared by core and the Windows and Linux guards; docs say when data.window is omitted. * refactor: trim process-owner tests, headed checks and Dock root The Dock root is the generic app root, so it reuses that path for observation, re-resolution and find; only menu-bar-extras keeps its own AXExtrasMenuBar root. Drop a redundant messaging timeout before the AXMain read, and fold the new tests into one table that still fails if any of the fallback, routing or drill-down behaviors breaks. * fix(macos): resolve a retained ref after its element moves A ref captured while a panel animates saves transient bounds, and an unverified scope then demanded those bounds, so the settled element read as gone. When the ref holds the native object it captured and the candidate is that same object, geometry adds no evidence. A replacement control is a different object and still fails. Adds an E2E scenario for the Dock, menu bar status items, the Control Center panel and the executable-name --app match, each checked by independent observation. * refactor: name process ownership for refs on any surface is_process_rooted covers only the Window surface, where the path starts at the application element. Refs captured through dock, menu-bar-extras or a windowless menubar are process-owned too; is_process_owned names that, and is_process_rooted builds on it. * fix: address review findings for process-owned refs - snapshot --root drills any process-owned ref, not only window-surface ones. - Headed focus skips only for process-owned refs and reports whether it focused, so hover and drag no longer claim focus they did not take; other windowless refs keep main's refusal. - Process-owned sources are verified by pid and process instance instead of the application-name heuristic. - The E2E scenario restores volume on any exit and skips the Dock launch when its prerequisite is missing; drop a test that only exercised a fabricated shortcut. * fix(identity): prove refs in allocation coordinates and cut Linux read cost The capture proof looked refs up by native child index while allocation writes positions in the projected tree, so after a walker skipped a sibling a unique ref was published unproven. The proof now walks the allocation path through the observation's own child lists, and projection keeps those lists. A capture on an adapter without element tokens now reads the whole window once instead of a shallow walk followed by a whole-window read, and the Linux walker keeps up to 32 nodes in flight per level instead of 8, so a wide level costs one round of calls. * chore(architecture): refuse any new listener and the removed host marker Every CLI call is its own process that serves nothing. The architecture check now fails when production code opens a socket, TCP, UDP or named-pipe listener outside a reviewed allowlist (Chrome's debug-port probe and the two cursor overlay endpoints), or names the removed command host's environment marker outside the code that stops leftover hosts. Test files and inline test modules are not scanned. Each case is planted in the self-test, and a stale allowlist entry fails too. * perf(linux): read only identity when re-counting a saved ref's window Resolving a tokenless ref re-reads its whole window to prove it is still unique. That read now asks for identity evidence only, and the Linux walker skips extents, actions and the GTK4 placement pass for it, which the count never uses. Capture, drill and rooted find reads keep their full evidence. Measured on Linux against the previous head, get and is drop by 9 to 40 ms per call with every run still resolving. * feat(macos): select list rows and click status items for real when headed - A list row with no press action is clickable when a container within two levels owns a settable selection: click selects it through the container and verifies the readback, so result lists like Spotlight's can be chosen and opened with Return. A row selected this way never presses a button inside it. Disabled controls are unchanged. - With --headed, a status item in an app's extras menu bar gets a real click, because many open their popover only on mouse input. Only an app's own menu bar keeps press-only delivery. Headless clicks stay semantic and never fall back to physical input. * fix(macos): scope selection-owner answers to one observation The per-parent cache lived for the whole thread, so a wait loop, a batch or a long-lived library caller reused a container's answer after the container changed and kept stale Click and enabled states on its rows. observe_tree now opens the cache for one walk and drops it after; any read outside an observation computes a fresh answer. * fix(macos): keep focus checks and app matching strict for windowless apps - Headed physical input skips the frontmost and main-window checks only for a background app or a status item. A regular app's target with no window is refused again, and a missing AXMain no longer skips the checks. - --app prefers a display-name match; a bundle ID or executable name counts only when no app has that name, so two apps never merge into one window lookup. - Selection-owner answers live on the observation's usage instead of a thread-local, are cached per ancestor so table cells reuse their container, and a failed AXParent or AXEnabled read marks the read incomplete instead of being cached as an answer. An unknown never authorizes pressing inside a row. - AvailableActionsRead is back to seven fields; the container verdict is returned beside it. - Core tests capture the windowless ref through build() and refuse a window that appears after capture. docs/json-output.md, the FFI rustdoc and header, and the macOS skill describe the omitted data.window, the focus rules and alias matching. - The E2E scenario closes the Control Center panel by clicking its item again, checks it closed and that the volume was restored, and adds a --headed click on the item. Tested: cargo test -p agent-desktop-core --lib on Linux (one trace test fails the same way on the base in this environment), fmt, file-size, architecture and phase-reference checks. macOS code and the E2E scenario were not run locally. * fix(snapshot): leave the shallow fallback time when the whole-window read runs long On adapters without element tokens a capture reads the whole window first. That read could spend the whole deadline, so the shallow fallback started expired and returned TIMEOUT instead of the shallow snapshot. The whole-window attempt now gets three quarters of the remaining time. The skills also no longer claim that an unnamed control is identified by role alone or that a re-drill keeps refs inside the drilled subtree. * fix: resolve a process-owned ref to its live process even after the app opens a window A ref captured from a windowless background app went stale for snapshot --root as soon as the app opened a window, because recorded_window took the new window and the owner-kind check rejected it before the token was tried. A process-owned ref's owner is its live process, so recorded_window now revalidates that process by pid, instance and name and returns its process owner directly. * refactor: trim the windowless-surface changes Inline a one-use test helper, drop a completeness check the selection probe already makes, collapse the verdict cache miss and the absent action list into direct returns, fold the Windows empty-owner guard into its match, drop a redundant serde default, let the E2E scenario keep its ref in the cleanup-visible variable, and point the macOS page at the surface rules instead of repeating them. No behavior change. * fix(find): read a token root's subtree alone instead of the whole window find --root on a root carrying an element token observed the whole window to re-identify it, though a token re-identifies itself. Drill-down already reads such a root's subtree directly. Both now go through one helper, read_saved_root: a token root is read as its subtree and stands when the subtree publishes no tokenless ref; otherwise, and for every tokenless root, the whole-window capture proof still applies. * perf(linux): do not wait for a web window to settle when resolving a saved ref Resolving a tokenless ref re-reads its whole window to prove it is still unique. For a Chromium or Electron window that read ran the settle loop: three equal node counts 200 ms apart, so every action paid about 400 ms of sleep. On a page that kept changing the loop gave up, the observation was marked incomplete, and the ref was refused as unproven even though the element was identifiable. Settling belongs to capture, where refs are published and proven unique. A read that only resolves a saved ref now walks once. Ambiguity, an absent element and a walk cut short by its budget are still refused. * fix(find): treat unsettled addressability as publishable in a token-rooted find The subtree fast path counted only nodes already known to be ref-able, so a tokenless member whose actions were still unread passed the check, and hydration could then publish it without a window-wide capture proof; acting on it later failed with identity_not_unique. An unsettled node now counts as addressable, as an unread token already counts as absent, so such a subtree falls back to the window proof. * chore(deps): move async-executor off the yanked 1.14.0 cargo deny fails the audit on a yanked crate. 1.14.1 is the latest compatible release; nothing else in the lock file moves. * test(macos): give the zombie process-group test a deadline load cannot exhaust The 100 ms budget only bounded how fast an unrelated /usr/bin/true exits, so a busy machine skipped the final group probe and failed the test. The reap poll returns as soon as the child exits, so a 5 s ceiling keeps the test as fast while removing the flake. * docs: correct macOS shell-surface docs and test process-owned refs The observation command page still said macOS has no shell surfaces, and the surfaces page and skill said a shell surface never takes --app, which is wrong for macOS menu-bar-extras. Two core tests now pin the process-owner path: a ref captured from a windowless app resolves from its live process without the window list and goes stale after a relaunch, and a locator observation of a process owner saves a process-owned ref. * fix(macos): answer open-system-surface through the shared surface contract macOS never implemented open_system_surface, so every call fell to the trait default and said the whole command was unsupported, even for dock and menu-bar-extras, which snapshot already reads. Those two are always on screen, so opening one now returns it through the same resolve path, behind the same --headed floor as Windows and Linux. Any other kind gets a refusal that names the kinds to use and how to open Control Center or Notification Center. * fix(macos): stop reporting failures for actions that worked - A press that keeps focus on the same editor but replaces its content now counts as an observed effect. Notes' New Note answers AXPress with -25205 while creating the note, and the click reported ACTION_FAILED. - A stepped control's value is re-read briefly after each step before the loop decides it stopped moving. A one-step-late readback made set-value give up halfway on the Control Center volume slider. - When a step jumps over the target, the error now says the step skips that value and names the two values the control can reach. - open-system-surface on macOS no longer needs --headed: the Dock and the menu bar extras are always on screen, so nothing is raised. * fix: answer an unavailable surface kind with INVALID_ARGS A caller that asks for a surface kind this OS does not have used to get PLATFORM_NOT_SUPPORTED, which reads as the whole capability being missing. Only the argument was wrong, so every platform now answers INVALID_ARGS, names the kind, and lists the kinds this OS accepts. Genuine platform limits (no shell session, no tray watcher) keep their code. Docs and skills follow, and note that macOS open-system-surface returns dock and menu-bar-extras headless. * fix: keep PLATFORM_NOT_SUPPORTED when an adapter roots no surface The INVALID_ARGS change also caught adapters that support no snapshot surface at all, such as the FFI stub adapter and a Linux session with no bus. There the argument is fine and the platform cannot snapshot, so that case returns PLATFORM_NOT_SUPPORTED again. A kind the platform lacks while it roots others stays INVALID_ARGS. The Windows capability test now expects INVALID_ARGS for quick-settings, matching the Windows skill. * refactor(macos): move list-row selection out and shorten the step error List-row selection (rows with no press action, selected through their container) is unrelated to the Dock, menu bar and windowless-app fixes, so it moves to its own pull request. The stepped-control failure now has one message: the steps cannot reach the target, with the last step's before and after values. * fix: tie press evidence to the target and leave out a windowless surface's window - A focused editor that keeps focus and changes its text counts as a press's effect only when it sits in the pressed element's own window. Text changing in an unrelated window no longer verifies a press. - open-system-surface leaves `window` out when no window presents the surface (the macOS Dock and menu bar extras), as snapshot already does, instead of returning an empty id. - macOS shell-surface lookup reports an incomplete app inventory instead of concluding the surface is absent. - A windowless owner with no title is refused instead of falling through to the app's only window. - Core and Linux share one builder for the unavailable-surface refusal. BREAKING CHANGE: a surface kind the OS does not have now returns INVALID_ARGS instead of PLATFORM_NOT_SUPPORTED (an adapter with no surfaces still returns PLATFORM_NOT_SUPPORTED), and on macOS open-system-surface for dock and menu-bar-extras succeeds headless with no `window` in its data. * docs: document macOS open-system-surface and the INVALID_ARGS surface contract macOS supports dock and menu-bar-extras headless and returns no window. An unavailable surface kind returns INVALID_ARGS; an adapter with no surfaces still returns PLATFORM_NOT_SUPPORTED. * fix(macos): count a focus move inside the pressed element's window as its effect A press whose return code proves nothing was verified only when focus landed on the pressed element or the same focused editor changed its text. Pressing Notes' New Note while focus sat in the notes list moved focus into the new note's editor, which neither rule saw, so a press that created the note reported ACTION_FAILED. Any focus move or content change inside the pressed element's own window now counts; changes in other windows still prove nothing. --------- Co-authored-by: vercel-security-reviewer-slack[bot] <321849240+vercel-security-reviewer-slack[bot]@users.noreply.github.com> Co-authored-by: roboeve[bot] <311457182+roboeve[bot]@users.noreply.github.com> | 5 days ago | |
fix(macos): resolve native menus and verify lifecycle transitions (#376) * fix(macos): resolve native menus and verify lifecycle transitions Give app-owned menus process identity without a document window. Verify activation with fresh target AX focus and retain exact process generations. Reconcile caller waits after uncertain delivery without repeating the action. BREAKING CHANGE: JSON envelopes use 2.6; macOS app-owned menu snapshots omit data.window. * fix: keep headed preflight lookup inside the macos adapter Avoid an extra ref resolution on Linux and Windows while preserving strict native menu classification on macOS. Update the native inventory test to pass the bridge's explicit full-inventory argument. * fix: preserve native menu ownership through actions and waits * fix: preserve window identity across title changes Reuse the stored window identity check so a renamed window cannot satisfy a disappearance wait. Keep process-generation checks and return the current title. * refactor: share post-action observation and bound native recovery Observe window-owned refs through their original window so waits survive a closing sheet. Preserve partial Linux window reads until target lookup, and retry only transient macOS surface failures. * fix: preserve focus results and complete observation contracts Keep successful Linux focus results when a later read is incomplete and retain child counts across macOS fallback reads. Share saved process identity validation, clarify surface recovery, and cover incomplete reads and renamed windows. Register the shared observer in the CLI helper inventory. * style: match rustfmt output for CI * fix: preserve process-owned refs in post-action waits * style: format process owner observation guard | 3 days ago | |
docs: simplify the README and repo docs (#481) * docs: simplify the README and repo docs The README opened with vague paragraphs and internal detail, and the FAQ used feature tables with marketing claims. Rewrite them to match the docs site: say what the tool does, show the commands, link out for the rest. - README: computer-use tagline, short install and quick start, the architecture diagram, links to agent-browser.dev/desktop. - FAQ, install, CONTRIBUTING and the FFI, benchmark and test READMEs: cut filler and fix claims the code contradicts (Windows DLL name, ad_init is optional, FFI threading, Homebrew/Scoop status, TMPDIR). - Skills: small wording fixes only; no rule changed. * docs: use --allow-scripts for global installs in install guide npm install-scripts approve only edits a project's package.json and fails for global installs, so it can't fix the global install shown above. Both npm 11 and 12 accept --allow-scripts on npm install -g. Keep the approve command for project installs only. * fix: point blocked-install users at commands that work The launcher notice and the docs site told npm 12 users to run npm install-scripts approve, which fails for global installs, and labeled the --allow-scripts reinstall as npm 11 only. Both npm 11 and 12 accept --allow-scripts on npm install -g, and re-running it over a blocked install links the native binary. In a project, approving alone does nothing until the script runs, and a plain npm install does not re-run it. npm rebuild agent-desktop does, so the project step now says that. Tested on npm 11.20 and 12.2 with throwaway prefixes and projects. * Fix: The macOS installation guide now claims that commands require a private TMPDIR for a background command host, although the merged CLI no longer runs that host, directing users to troubleshoot a nonexistent installation requirement. This commit fixes the issue reported at docs/install.md:56 The new merge base removed `src/command_host`; `src/main.rs` now stops legacy hosts and runs commands directly. `src/tests/command_host_absent.rs` also asserts that commands never connect to or create host socket files. This paragraph reintroduces the removed architecture and its former `INVALID_ARGS` condition into the installation requirements, misleading users with custom temporary directories. The suggested patch removes only the obsolete host/TMPDIR sentences. Verified against the current entry point and absence tests; `git diff --check` passes. Native macOS tests were not run. Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: lahfir <nmhlahfir2@gmail.com> --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> | 5 days ago | |
feat(identity): resolve Linux and Windows refs through a capture rule (#442) * feat(identity): resolve Linux and Windows refs through a capture rule Linux and Windows expose no durable element address, so a ref there resolves only when one complete whole-window read proved it unique at capture and exactly one live element matches its full predicate now. Every place that publishes a ref proves it the same way, positional landings are confirmed against the predicate, FFI find re-identifies a landing before publishing it, and Linux menu refs keep their popup binding. The Windows strict resolver and the macOS path search lose their last callers and are deleted. * fix(identity): validate popup context before rooted reads publish refs Rooted find and snapshot drills accepted a matching predicate from a replacement Linux popup. The shared saved-ref observation now checks the captured popup binding against the same observation before refs are published. * fix(identity): keep shallow captures and read the window once per action Snapshots keep a finished shallow read and flag refs they could not prove with identity_unproven. A ref action reads the window once and still runs the full rule under the lease. Capture and resolution read the same evidence, including the forced renderer flag. Hydration keeps the proven predicate, skeleton anchors fall back to the nearest proven ancestor, token-rooted drills read only their subtree unless it holds a tokenless ref, macOS tokenless anchors land by position, interval typing reuses its first handle, and exact FFI entries refuse with ACTION_NOT_SUPPORTED. * fix(identity): prove refs in allocation coordinates and cut Linux read cost The capture proof looked refs up by native child index while allocation writes positions in the projected tree, so after a walker skipped a sibling a unique ref was published unproven. The proof now walks the allocation path through the observation's own child lists, and projection keeps those lists. A capture on an adapter without element tokens now reads the whole window once instead of a shallow walk followed by a whole-window read, and the Linux walker keeps up to 32 nodes in flight per level instead of 8, so a wide level costs one round of calls. * perf(linux): read only identity when re-counting a saved ref's window Resolving a tokenless ref re-reads its whole window to prove it is still unique. That read now asks for identity evidence only, and the Linux walker skips extents, actions and the GTK4 placement pass for it, which the count never uses. Capture, drill and rooted find reads keep their full evidence. Measured on Linux against the previous head, get and is drop by 9 to 40 ms per call with every run still resolving. * fix(snapshot): leave the shallow fallback time when the whole-window read runs long On adapters without element tokens a capture reads the whole window first. That read could spend the whole deadline, so the shallow fallback started expired and returned TIMEOUT instead of the shallow snapshot. The whole-window attempt now gets three quarters of the remaining time. The skills also no longer claim that an unnamed control is identified by role alone or that a re-drill keeps refs inside the drilled subtree. * fix(find): read a token root's subtree alone instead of the whole window find --root on a root carrying an element token observed the whole window to re-identify it, though a token re-identifies itself. Drill-down already reads such a root's subtree directly. Both now go through one helper, read_saved_root: a token root is read as its subtree and stands when the subtree publishes no tokenless ref; otherwise, and for every tokenless root, the whole-window capture proof still applies. * perf(linux): do not wait for a web window to settle when resolving a saved ref Resolving a tokenless ref re-reads its whole window to prove it is still unique. For a Chromium or Electron window that read ran the settle loop: three equal node counts 200 ms apart, so every action paid about 400 ms of sleep. On a page that kept changing the loop gave up, the observation was marked incomplete, and the ref was refused as unproven even though the element was identifiable. Settling belongs to capture, where refs are published and proven unique. A read that only resolves a saved ref now walks once. Ambiguity, an absent element and a walk cut short by its budget are still refused. * fix(find): treat unsettled addressability as publishable in a token-rooted find The subtree fast path counted only nodes already known to be ref-able, so a tokenless member whose actions were still unread passed the check, and hydration could then publish it without a window-wide capture proof; acting on it later failed with identity_not_unique. An unsettled node now counts as addressable, as an unread token already counts as absent, so such a subtree falls back to the window proof. * chore(deps): move async-executor off the yanked 1.14.0 cargo deny fails the audit on a yanked crate. 1.14.1 is the latest compatible release; nothing else in the lock file moves. * test(macos): give the zombie process-group test a deadline load cannot exhaust The 100 ms budget only bounded how fast an unrelated /usr/bin/true exits, so a busy machine skipped the final group probe and failed the test. The reap poll returns as soon as the child exits, so a 5 s ceiling keeps the test as fast while removing the flake. --------- Co-authored-by: vercel-security-reviewer-slack[bot] <321849240+vercel-security-reviewer-slack[bot]@users.noreply.github.com> Co-authored-by: roboeve[bot] <311457182+roboeve[bot]@users.noreply.github.com> | 5 days ago | |
fix: harden native accessibility targeting and delivery (#210) * fix: harden native accessibility targeting and delivery * test: check nonblocking restoration without kernel flag assumptions * fix: enforce command-host namespace and resolve review findings Pin the host session namespace through batch item contexts and session administration so foreign snapshot namespaces cannot be mutated through the experimental host, defer retained-token pruning instead of blocking dispatch on inventory failure, and serve endpoints from the private per-user temp directory instead of shared /tmp. Also deduplicate deadline conversion, replace the collection bool with SelectSearch, skip depth-cap child reads for menu search, reuse the candidate NodeRead for text-field matching, share host timeout constants, fix the stale-owner error message, and document the optional find name field. * test: give the live-value adapter its own file get_consistency_tests.rs defined two test adapters, FailedLiveRead and LiveValue, against the one-struct-per-file rule. LiveValue and the one test that uses it move to get_live_value_tests.rs, loaded as a child module so the shared get_property helper stays in one place. | 1 month ago | |
feat: integrate Linux support with privacy hardening (#311) * chore: remove three uncalled lab scripts No workflow, hook, script or test calls them: a symlink helper for local skills, a root-only uinput stub whose legs were never written, and an unrun self-test of the npm wrapper smoke, which CI runs directly. * docs: remove stale evaluation records and unreferenced diagrams The architecture diagrams and the Slack token illustration were not linked from anywhere and showed Linux as planned. The acceptance audit was a withdrawn status record. Two solution docs were folded into the docs that already carried their context, so each lesson has one home. Validated in the docs removal ledger (docs-3, 6, 8, 11, 12, 13). * docs: correct solution docs that contradict the code Many learnings named moved files, deleted tests, stale function signatures, or rules the code no longer follows (bare refs accepted, native apps never activated, one box lock instead of two slots). Each now cites the code that holds today, and none cites the planning docs or the untracked dogfood reports. * docs: correct CONCEPTS.md against the shipped adapters Several definitions described one platform as if it were every platform, missed the Linux adapter, cited probe rows that measure something else, used a screenshot flag that does not exist, and called dogfood reports committed. Session Manifest folds into Session and the must-catch fixture into Gate Self-Test, where each was already half defined. * docs: correct contributor docs, the FAQ, the JSON contract and runbooks CONTRIBUTING.md now points to CLAUDE.md for the engineering rules instead of keeping a second copy that had drifted (a MockAdapter that does not exist, an E2E command that exits without the exclusive-desktop variable). The JSON contract described an unknown disposition the code no longer emits, the FAQ repeated the README platform table, and the Windows runner runbook cited plan ids and a trigger branch that merged. * ci: assert the release-ffi unwind profile once per release ffi-release-panic-guard already runs the identical grep and publish-github needs it, so the copy in ffi-release-gates added nothing. * chore: let release-please bump the Linux skill version skills/agent-desktop-linux/SKILL.md carries the x-release-please-version marker and is published with the other skills, but was missing from extra-files, so its version would have stayed behind. * docs: make the test harness READMEs runnable as written Several harness commands failed as documented (the Linux consumer run without AGENT_DESKTOP_WM_ROOT, the discovery inversion without a header path, a headless probe binary that swiftc names differently) and the fixtures README listed files that do not exist. The Linux E2E guide drops plan history and keeps what an operator needs to run and read it. * Revert "chore: stop shipping the jev-desktop skill" This reverts commit 34826e104af928a345dbdfeaa83a9a9b72745fb0. * docs: fold the ref-root viewport lesson into the clipping doc The shared clipping doc already states the same incident and the repair that superseded it, so the lesson keeps one home. Validated in the docs removal ledger (docs-14). * docs: say jev-desktop needs a repository clone, and match it to its scripts The skill teaches node scripts/jev/*.mjs, which ship in no installed package, so it now says up front that it needs a clone of the repository and a TYPESAFE_API_KEY. Checked against the scripts: run.mjs retries 429/503/529 twice while act.mjs does not, --bin and --execute belong to act.mjs only, act.mjs sends longer values and the ancestor path, why is null on act, the stop list and exit codes were incomplete, and JEV_VERIFY_MS was missing. * docs: correct FFI handle, wait and last-error text against the header ad_free_handle works after the adapter is destroyed (native_handle.rs tests prove it), so the rule is to free handles before destroying the adapter only for use, not for release. The wait bullet now uses the header's AdWaitMode field names, AdActionStep lists mechanism and verified, the delivery-semantics accessor copies values instead of lending a pointer, and the JSON-string table gains ad_execute_by_ref_timeout, ad_trace_show and ad_trace_export. * ci: cut duplicate jobs and steps from the CI workflow Each removed piece repeated a check that another job already runs on the same OS, or depended on a file that is leaving the repository: - platform-check: three legs were subsets of clippy --all-targets on the same runner; the windows-11-arm leg is now a clippy step in test-windows-arm. - ffi-header-drift, ffi-python-smoke and ffi-passthrough now run as conditional steps on the ffi-panic-guard legs that used the same OS, so the header drift gate and the stub checks keep running on every PR. - check-release-consistency.sh and check-npm-package.js run in the supply-chain workflow on every PR, push and weekly, and need no OS. - the ubuntu bash3 compatibility run was weaker than the macOS Bash 3.2 run. - trash and trash-cli installs had no caller left. - check-phases-ledger-citations.ps1 read docs/phases.md, which leaves the repository, and would fail the Windows lane. The viewer_filter.js test moves here from the inspector workflow, so the shipped viewer code keeps a CI test. * chore: remove the unshipped inspector web UI tools/inspector is a local developer viewer that no shipped artifact, skill or release step uses, and its workflow only tested it. The viewer_filter.js test it also ran now lives in the CI workflow. * ci: give the release dry run a concurrency group and state why it stands alone The reason it is a separate read-only workflow lived only in docs/phases.md, which leaves the repository. A concurrency group stops a busy PR from queueing many full rehearsals, and trash-cli had no caller left. * ci: run the Windows capability probes only when probe files change The probes measure the hosted runner. The one contract they check against crate code, secure-value withholding, is also a plain unit test in the Windows CI lane, so code-only PRs no longer pay for a 30-minute probe run. * docs: document the jev scripts beside the code The jev-desktop skill left the shipped skill set, so its usage notes move next to the scripts they describe, checked against the current flags and environment variables. * chore: remove an unreferenced benchmark screenshot No file links to it. * docs: fix three FFI ownership and panic claims a review disproved A codex review proved three overstatements against source: ad_abi_version returns a constant with no panic trap, so only entrypoints that can panic catch_unwind; last-error strings are borrowed and never freed, so not every returned pointer has a free function; and ad_free_handle validates the adapter before it accepts a null handle. Remaining em-dashes in the FFI references became plain punctuation. * refactor(core): drop two helpers that nothing calls RefStore::for_tests and ErrorPayload::with_recovery had no caller in any crate or test. They sit in public modules, so rustc could not flag them. Recovery hints are still attached by recovery_for_code. Ledger rows A-01, A-02. * test(ffi): drop an unused re-export from the shared test module No ffi integration test names AdIdentifierKind through the common module; the file-level allow(unused_imports) hid it. Ledger row A-08. * ci: keep the 20-minute FFI timeout on legs that only run the panic probe Only the aarch64-apple-darwin leg gained the header drift and stub smoke steps, so only it needs the longer bound. * test(linux): drop live probes that only print timings semantic_click_latency_probe and the vocabulary report, timed pass and label timings asserted nothing and no gate reads their output. The vocabulary live test keeps every assertion path. Ledger rows A-12, A-13. * refactor(linux): drop stale dead_code allows on display errors DisplayCause, as_str and display_cause_error all have production callers, so the allows only hid any future dead code. Ledger row A-15. * refactor(windows): compile the canonical lock path only for tests Only the lease tests call canonical_lock_path, so a cfg(test) gate replaces the allow(dead_code) and the doc no longer promises a future caller. Ledger row A-16. * docs: cut the README to 130 lines and move install detail to docs/install.md The README keeps the pitch, install, the observe-act loop, the agent section, the command table and a plain platform table. Install-script allowlisting, attestation, the source build, per-OS permissions and platform footnotes move to docs/install.md unchanged. * docs: record the parameterized-replacement findings beside their probes The payload shape, the background no-op and the input-context route are what a maintainer needs when running replace_range_probe.swift, so they belong in the harness README rather than in a one-trial log. * docs: state what the jev scripts actually send and restore --no-values does not withhold text passed with --text, the clipboard is only put back when it held text, and a run reads the screen more than once per turn. The README said otherwise. * chore: stop naming a removed workflow in the redaction gate comments windows-e2e.yml no longer exists; the -Path override is described by what it does. * docs: note parameterized attributes in the real-app probe guide A setter that does nothing does not prove an editor has no write path; the probe lists parameterized operations such as AXReplaceRangeWithText, and the replacement range is in UTF-16 units. * docs: remove the Numbers replacement trial log now its findings live with the probe The real-app README carries the payload shape, the background no-op, the input-context route and the inventory lesson. Validated in the docs removal ledger (docs-15, codex and opencode both REMOVE). * chore: make infra comments match the repository The gitignore comment said no normalized twin is committed, the Windows e2e runner said no CI workflow touches it, one gate comment named a function that is being removed, and another described docs/phases.md as in the repository. * docs: make the PR template gate commands run as written The E2E line exited 2 without AGENT_DESKTOP_E2E_EXCLUSIVE=1, a string HOST_PKGS reaches cargo as one argument in zsh, and the skill checklist left out the Linux skill. * fix(npm): point the postinstall skill hint at commands that exist claude mcp add-skill is not a Claude Code subcommand. The hint now names the binary's own skills command and the skills installer the README documents. * docs: drop the jev README now that the jev-desktop skill documents the scripts Two descriptions of the same scripts would drift; the skill is the one that ships. * docs: shape the README for users, contributors and agents The fast path stays on top (install, a quick start, the agent section), followed by reference sections and a documentation map that points each reader to the right file. It restores facts the shorter version dropped: headless-by-default delivery, PowerShell ref quoting, the readable JSON sample, the accessibility-tree row and the per-OS package sets. * docs: name the CI job that now runs the FFI Python smoke The ffi-python-smoke job was folded into the macOS leg of the ffi-panic-guard job, so two docs pointed at a job that no longer runs. * refactor(core): share empty_trace_dir_error between trace show and export The two copies were byte-identical, so one pub(crate) helper in trace_read now serves both calls. Ledger row B-07. * ci: give the Linux live e2e job an explicit read-only token The job inherited an empty permission set, unlike its macOS twin; checkout needs contents: read and nothing more. * ci: pin the probe workflow's checkout to the same release as every other workflow It was the only workflow left on v7.0.0. * docs(core): make two rustdocs match the code they describe transform_tree prunes on is_ref_able, not on role alone, and the drill-down completeness note belongs on run_from_ref_with_context, not on count_nodes. * docs: state the jev read cost for run.mjs as well as act.mjs lead-infra's review found the skill gave the snapshot cost for act.mjs only. run.mjs reads the screen before and after each action (none after DRILL or WIDEN), and both scripts add a surface snapshot when an overlay is open and no region is pinned (desktop.mjs:62-76, act.mjs:236-237). The other five corrections already held in the merged text. * fix(cli): list linux among the skills get aliases skills get linux already resolves, but the help text left it out. * test(visual-debug): stop naming the removed inspector in a test env var The capture child marker now says what it marks, since tools/inspector is gone. * refactor: replace per-crate ensure_budget copies with Deadline::ensure_remaining Twelve private macOS helpers, two private Linux helpers, and the shared Linux bus and Windows permissions helpers all had the same body. One Deadline method now carries it, with unit tests. The DeliveryTracker variants, the mouse and chain variants, and Linux input/deliver.rs keep their own checks because they add delivery details. Ledger row B-01. * docs: restore rules and definitions an earlier cut dropped CLAUDE.md again states the @generated exemption, that every non-doc comment form is rejected while //! module docs are allowed, and that expect and panic are test-only like unwrap. CONCEPTS.md again says a ref-addressed drag picks up at the bounds center (and the slider workaround) and names the must-catch and must-pass fixture halves. * docs: restore two oracle contracts to the Linux E2E guide The rewrite dropped what the pointer delivery legs read back and what the dead-owner recovery leg requires, both of which an operator needs to judge a failing leg. * chore: keep the gitignore note on which capture twins are tracked Per-area probe directories commit their normalized twins; only the twins under probes/<platform>/captures/ are regenerated and untracked. * docs: say in Coordinate Fallback that raw --xy input focuses nothing A raw coordinate resolves with no ref and no window identity, so the caller owns where it lands; the fallback entry now states it directly. * refactor(windows): drop file-level dead_code allows in system modules Windows clippy with -D warnings passes with all twelve removed, so every item in those files is used and the allows only hid future dead code. The menu source agreement test no longer cites the allow. Ledger row A-22. * refactor(windows): parse w-<n> window ids in one place observe and surfaces carried byte-identical parsers with the same InvalidArgs message; window_ops now owns parse_window_id next to window_id. Ledger row B-08. * refactor(linux): share the already-satisfied pointer select result The combo and row select paths built the same skipped XTEST.click result with their own copy of the route constant; select_pointer now holds one. Ledger row B-11c. * refactor(linux): share clipboard delivery helpers between X11 and Wayland set_clear and wayland carried the same verify grace, fresh deadline, and not-delivered and unverified error builders; delivery.rs now holds one copy and messages stay with the callers. Ledger row B-11d. * refactor: build stale-evidence errors with AdapterError::stale_ref_because Three adapter copies of stale_evidence_error repeated the body of the existing core constructor, so the resolvers now call it directly. A core unit test pins its code, suggestion and disposition. Ledger row B-02. * refactor: move the live-evidence completeness helpers into core incomplete_live_evidence, essential_live_evidence_complete and known_actions were byte-identical across the adapters and work only on core locator types; live_element now owns them, with unit tests. Ledger row B-05. * refactor: move notification identity verification into core The macOS and Windows checks were byte-identical over core notification types, so core now exports verify_notification_identity with unit tests. Linux keeps its own check because its suggestion and disposition differ. Ledger row B-06. * chore: point the stale-ref gate at the constructor callers now use stale_evidence_error is gone; evidence-refused refs now build their error with AdapterError::stale_ref_because, so the comment and the failure hint name that. * test(e2e-linux): drop plan requirement ids from leg descriptions R9, R19, R22, R24, R32, R33 and R35 named requirements in a plan that has left the repository, so the prefixes meant nothing to a reader. Nothing parses the requirement text; leg ids are unchanged. * chore: stop the privacy scan comment from claiming the repo ships planning docs * test(e2e-linux): drop duplicate legs and dead harness helpers Six legs repeated the observation of a stronger surviving leg on the same tiers, two unit tests restated another test's assertions, and several helpers had no caller. Each row was validated by two independent models (ledger TR-145..175). * test(e2e-windows): drop a racing zero-timeout leg and dead fixture code The delayed-enable zero-timeout leg raced the fixture timer while the permanently disabled leg proves the same no-wait refusal; the click row's mechanism key is now asserted instead of ignored, and an unused fixture overload and an always-true self-test line are gone (ledger TR-149, TR-151, TR-152, TR-186). * test(e2e): fold the absolute-timeout exit code into the deadline test The removed test repeated the descendant-deadline test and added only the 124 exit code, which the survivor now asserts (ledger TR-185). * ci: run the four hermetic scripts self-tests in the fmt job perf_ab_probe_test.py, test-linux-live-gate.py, test-session-env.sh and test-remote-linux.sh guard the perf report math, live-gate shard and receipt rules, desktop environment selection, and the remote kill guard, yet no job ran them; each is stub-driven, needs no display or network, and takes about a second. The two that start private sessions stay manual (one already runs in linux-e2e). * test: make every self-rerun test prove its child ran the target test A test that re-runs its own binary with a libtest filter passed with zero tests run when the filter was wrong, which already hid the Linux cursor overlay check (its filter missed the overlay module). Each self-rerun site now asserts the child printed "1 passed; 0 failed"; the overlay filter is corrected, and the interactive clipboard test compiles again under its feature. * test(e2e-linux): drop duplicate contract self-tests and dead test helpers Registry and gate checks were asserted twice; the surviving registry test now also refuses an empty registry so it cannot pass vacuously (ledger TR-177..183). * test(core): drop tests that restate a stronger test or only exercise a mock Each removed test was matched by a surviving test at the same or a stronger boundary, or asserted only a mock's or a constant's own value; two merges keep the unique assertions (role round trip, toggleable negatives) in the survivors (ledger TR-001..019). * test(macos): drop duplicate and tautological tests, fold unique assertions into survivors Survivors now carry the sibling-radio suggestion, the AXOpen stale classification and the native plus DOM identifier pair (ledger TR-058..074). * test: drop CLI and conformance tests that only exercise their own mock The conformance window-identity tests asserted the mock adapter's behavior, and the version and wait-metadata assertions repeated stronger tests (ledger TR-142, TR-143, TR-161..163). * test(ffi): drop duplicate C ABI tests and keep their unique checks in survivors Typed discriminant round trips, out-of-range values, a second trapped panic and the last-error pointer across successful calls now live in the surviving tests (ledger TR-035..044). * test(windows): drop duplicate dispatch, notification and state tests The adapter's inline tests were byte-identical to adapter_tests.rs; the R2 advertisement tests now live as rung call counts and isolated advertisement cases in the action and tree tests (ledger TR-073..091). * refactor(linux): import test-only step types in the select tests, not production select_row.rs and select_pointer_combo.rs carried cfg(test) imports only for their tests' use super::*; the tests now import ActionStep and StepMechanism. * test(e2e): add error-envelope and batch failure legs to the macOS harness Agents branch on exit code and error.code for a bare @e1, an unknown app, a missing surface and a parse error, and on batch's continue versus stop-on-error results; no live leg proved them. Self-tests check every expected code exists in the core enum and that each leg asserts code and exit together. * test(e2e-linux): add error-envelope and batch failure legs Two registered legs drive the real binary on both X11 tiers: recoverable error envelopes (INVALID_ARGS for a bare @e1, APP_NOT_FOUND with exit 1) and batch per-entry failure with and without --stop-on-error, observed on the app itself. Pure verdict tests with lying fakes keep them checkable without a desktop. * test(e2e-windows): add error-envelope and batch failure legs Eight Reliability legs cover INVALID_ARGS, SNAPSHOT_NOT_FOUND, STALE_REF for an absent element, APP_NOT_FOUND and ELEMENT_NOT_FOUND with exit codes, and batch halt versus continue with per-entry codes. Not yet run: they need a Windows host (contract -SelfTest, the U7 self-tests, then the live suite). * refactor(core): route deadline clock reads and poll sleeps through one test-freezable seam Several core tests flaked on loaded CI runners because their deadlines ran on the real clock. Deadline now reads time and sleeps through crate-private now() and sleep(), which outside cfg(test) are the same std calls; tests can freeze and advance a thread-local virtual clock. Public API and production behavior are unchanged (codex review: no behavior change). * test(macos): replace the inventory retry wall-clock assertion with counted attempts The test asserted more than one attempt inside a real budget, failed on 6 branches when a slow runner fit only one, and 45b28ce9 only raised the budget to 500 ms. The retry and the partial-inventory refusal are now proven by attempt counts on a fake capture that never consumes its deadline. * fix(windows): confirm a target's exit on its process handle before reporting a menu-wait read error A window that stops pumping during teardown reads as APP_UNRESPONSIVE before its process object is signaled, so the single liveness re-check lost the race and the wait reported the transient code instead of STALE_REF (failed on 7 branches). The wait now blocks on the process handle for at most the remaining deadline, capped at one second, before it keeps the read error. First run on Windows CI. * test(linux): drop duplicate input, close, tray and tie-break tests Each removed test repeated a survivor at the same boundary; the unique recovery text, baseline counts and tie cases moved into the survivors (ledger TR-046..057). * test(e2e): add stale-ref and key-combo legs to the macOS harness The removed-element leg accepted four error codes; it now requires STALE_REF with a safe retry. A RefChurn leg proves STALE_REF and AMBIGUOUS_TARGET after an in-place UI swap, and a press leg proves combos by reading the field itself. * test(e2e-linux): add stale-ref-after-UI-change and key-combo legs The stale leg refreshes nothing between a real menu change and the click and requires STALE_REF with its recovery; the press leg checks ctrl+a, backspace and return by AT-SPI text, not by the command's own ok. * test(e2e-windows): add stale-ref contract and key-combo legs The removable row now reports clicks, so a stale ref proven by refusal also proves nothing was delivered; press legs cover ctrl+a, shift+tab, return and the blocked alt+f4 refusal. Not yet run: needs a Windows host. * docs: point the live-gate runbook at the focus leg that stays The lift leg duplicated lifecycle.focus_window_active_reads and never read the restoration it named, so the tests cleanup removes it; the retained live owner is the active-window read. * test(core): run the settle-loop and artifact-lock budget tests on the virtual clock These tests failed on loaded Windows and macOS runners because a real 50 ms lock wait or settle window expired before the code under test ran; a 16 x 20 ms retry wrapper hid it. They now freeze the virtual clock, so lock polls and settle sleeps advance time deterministically, and the retry wrapper is gone. * fix(windows): treat E_UNEXPECTED on a UIA read as a transient failure A provider tearing down a window or shell host answers one read with E_UNEXPECTED; it was classified terminal and narrowed to a non-retryable APP_UNRESPONSIVE, so one read ended a whole wait (failed on 4 branches). It is now a retryable unresponsive read inside the caller's existing deadline; E_FAIL stays terminal. First run on Windows CI. * test(linux): make vacuous assertions able to fail Repaired tests named a behavior their assertions could not detect (a negative check that any value passed, a banned-name list with dead entries, an unverified result asserted only as not verified); each now checks the stated contract. * test(macos): make vacuous assertions able to fail, each proven by a production mutation Process-group kill, semantic-only disclosure chains, single capture, strategy fall-through, scan tolerance, endpoint naming, overlay acknowledgement, the select depth cap and the name-plan slots now fail when the behavior breaks. * test(ffi): make C ABI tests assert what their names promise Modifier cap, JSON error forwarding, invalid discriminants, envelope fields, dirty out-params on a worker thread, wait error envelopes and the session adapter's fail-closed path each had an assertion that could not fail; every repair was proven by breaking the production path once. * test(e2e): make harness self-tests fail for the regressions they name * test(e2e-linux): make harness self-tests and a menu leg assert structured results The menu timeout leg now checks the structured wait_timeout details, and the oracle, runner, session-sweep, workflow and verdict self-tests assert the specific failure each one names. * test(e2e-windows): replace diagnostic scaffolding and soft guards with real assertions The headed double-click leg used debug output, a fixed sleep and one read; it now uses Assert-Effect like its siblings. The handle-set and overlay controls no longer skip silently, and the Chromium rate leg fails when every sample is stale. Not yet run: needs a Windows host. * test(core): drop find, get, wait, session and trace tests that repeat stronger tests The removed find tests ran a cfg(test)-only snapshot collector production never uses; that collector's count helper went with them. Merges keep the observed-role negative, the null-bounds case, the conflicting wait flags as a table and the identifier preference in the survivors (ledger TR-020..034). * test(windows): drop input fake-sink, policy, clipboard and surface tests that repeat survivors The keyboard and mouse send tests only checked that the test fake stored what it was given; the clipboard, chain and surface merges keep their unique cases as table rows or assertions in the survivors (ledger TR-092..106). * test(windows): drop tree property, hit-test and resolve tests that repeat survivors Each removed test had an identical or stronger twin in the same module family (ledger TR-124..140). * test(linux): make clipboard, keyboard-frame, remap, capture and close tests assert their contracts The remap test now proves the deadline cancelled after the remap and that the keymap was restored, the off-screen capture test places its window instead of skipping, and the close gate is checked for both graceful and forced closes. * test: make CLI dispatch, overlay and real-app tests assert what they claim Real-app tests now fail when their precondition is missing instead of passing silently, the overlay show test pins POLICY_DENIED, and the conformance test checks each capability's outcome, not only that the call returned. * test(real-apps): assert the Numbers failure stop and drop a dead plan citation * test(macos): make capture-scope, screenshot and child-label tests assert their contracts The capture-scope tests now run the production scope filter instead of a test copy of it (the single-pid case is kept beside the pid-set case), the screenshot timeout checks the child is gone, and the child-label rule is checked for every role it names. * test(core): order concurrent snapshot writers instead of retrying lock timeouts, and pin tray ids Eight writers polled one lock with no fairness and a 30 s retry hid starvation that failed CI on four branches; the writers now take turns, and a single-thread test on the frozen clock proves a held store lock still blocks a writer. A new test pins that allocation keeps a RuntimeId identifier and its kind, which no test covered after the tray identity change. * test(windows): drop lifecycle, launch, HRESULT and signal tests that repeat survivors Merges move the exit-code serialization cases into core's process-state tests and the HRESULT detail strings and generation checks into the surviving Windows tests (ledger TR-108..121). * test(windows): drop two duplicate tree tests and make hit-test and resolve tests assert their contracts hit_test now judges a probe through judge_probe, so a probe miss is tested on the production path (same behavior: a miss is Unknown, a hit is judged); the fixture teardown, minimized-window, anchor and timeout tests assert what they name (ledger TR-122, TR-123, TF-144..157). * test(linux): make capture-cap, tie, key-owner, menu and tray tests assert their contracts * test(core): run live-locator and ref-action wait tests on the virtual clock These tests had their budgets widened and an exact attempt count loosened to 'at least one' to survive slow runners; on the frozen clock the retry loop's own sleeps advance time, so the original exact counts and small budgets hold. * test(windows): drop the setup-time stopwatch from the headless notification wait refusal The elapsed < 2 s assertion measured COM and adapter setup and failed twice on loaded runners; POLICY_DENIED is not retryable, so a burned deadline would already surface as TIMEOUT and fail the code assertion. * test(windows): decide modern-capture frame-wait expiry inside the wait, not by wall time The fallback test asserted TIMEOUT against an 80 ms budget that real capture setup could consume first (failing as INTERNAL); the cfg(test) frame hold now starts its budget inside the frame wait, and the pattern test waits for a frame showing the fixture instead of sleeping 500 ms. Production frame wait unchanged. * test(windows): signal the menu fixture's open state from real menu mode The fixture announced the popup before TrackPopupMenu entered menu mode and ignored its result, so the chromium composition test compared two live reads that could straddle the flip; readiness now comes from menu mode itself and the comparison waits until both sources agree. * test(linux): make tray, signal, settle, walker and shell-owner tests assert their contracts The error-code boundary test now enumerates codes exhaustively at compile time instead of a hand-typed list of 16, and the remaining repairs check the behavior their names state. * test(macos): replace real-clock budgets and stopwatches with counted attempts and events Stabilize-loop tests could expire before their first attempt on a slow runner; they now use budgets the fakes never consume and assert exact attempt counts, and the overlay acknowledgement tests block on a release channel instead of a 700 ms sleep. * test: isolate command-host sockets per test and assert batch deadlines by value Fixed socket and symlink names keyed by pid were never removed, so pid reuse could fail a later run; each test now owns a removed-on-drop directory. Batch deadline tests assert the budget the adapter received instead of wall time. * test(ffi): isolate the round-trip test's state root and require the snapshot log event The round-trip test set HOME under its own lock but left AGENT_DESKTOP_HOME alone, so a developer's state root leaked in; it now uses the shared isolated home helper. Log-callback tests no longer fall back to the first delivery. * test(core): make actionability, trace, find and close tests assert their contracts Each repaired test was proven by breaking its production path once. The benchmark example's rect test moved into the library's rect tests so it runs in CI, and the find roles hint is now tested through execute instead of a cfg(test)-only copy, which is deleted. * test(windows): make dispatch, envelope, scroll and fixture tests assert their contracts Permissive Err arms, restated literals and a self-comparing fixture expectation are replaced by assertions on the delivered outcome. * test(linux): make tree walker, projection and placement tests assert their contracts * test(core): make session, overlay-routing, hover and pointer tests assert their contracts Session precedence tests pass the env value directly instead of mutating the process environment, gc is actually exercised on an unreadable manifest, the skeleton golden compares the whole tree, and hover counts receiver checks instead of timing them. * test(windows): make drag, capture, HRESULT, signal and wait tests assert their contracts Assertions true by construction (an intersection member found in its own set, an assert_ne after an exact assert_eq, a uniqueness loop over a test-local list) are replaced or removed, and the remaining checks name the behavior they guard. * test(linux): make resolve tests assert the unread-region refusal they name * test(core): drop the null ref_id from the skeleton golden fixture The skeleton golden test now compares the whole serialized tree, and the serializer omits a missing ref_id, so the fixture must not carry one. * test(core): make ref, refs, trace-read and policy tests assert their contracts The XSS gate now asserts the exact escaped payload, the oversize-refmap guard is exercised through the production writer, poll and wait tests count resolves instead of timing them, and a new test pins identifier kinds through RefEntry identity. Each repair was proven by breaking its production path once. * test(windows): make lifecycle, overlay spawn and shell-surface tests assert their contracts A tautological ambiguous-target shape check now serializes the production value, a self-comparing error-code helper is gone, and the taskbar test no longer returns early on an empty tree. * test(npm): drop duplicate platform and wrapper tests and check the real staging directory The staging-cleanup tests read the destination, which installArchive never stages in, so a cleanup regression still passed; they now compare the package bin directory before and after. Duplicate platform-table and wrapper tests are gone, with their unique helper-name and Trash-warning checks kept in the survivors (ledger TR-189, TR-191..193, TF-187, TF-188). * ci: run the Numbers sheet-script self-test in the fmt job numbers_sheets_test.py drives numbers-sheets.sh against a fake binary in a temporary home and proves its failure stop; it is hermetic and nothing ran it. * test: nest extra test modules so moved anchors keep room under the line cap ref_action.rs (400 lines) and the Windows tree/resolve.rs (398 lines) become their folders' mod.rs in the restructure and must then declare their new child modules. Their extra test modules are declared from the files' tests modules instead, so both anchors stay under 400 lines. The nested test files import their parent by absolute path, keeping what `super::` meant before. The Linux action scan walks src/actions recursively, so the banned-call scan keeps reading the action files that move into select/, scroll/ and write/, and it asserts files from each of those groups are still scanned. * refactor(cli): give the command host and diagnostics their own folders Nine command_host*.rs files and three diagnostic*.rs files sat loose in src/, so the binary's root read as a flat list. Each family now lives in a folder whose mod.rs is the former entry file; the helpers were already #[path] children, so no module path changes. * refactor(core): group core's leaf types into response, input, notifications, storage and wait Sixty-seven of core's 270 root files are the envelope and error model, the input and notification parameter types, the on-disk state root, deadlines and signals, plus adapter and session helpers. They now sit in folders named after the concepts in CONCEPTS.md, so a reader finds a type by its domain instead of scanning one flat list. Module paths gain one segment; root re-exports in lib.rs keep every public name unchanged, and adapter becomes a public module because it now holds the public hit_test, live_element and native_handle modules. * refactor(core): group core's tree, action, system and snapshot types by domain The accessibility-tree model, the Action model and its execution types, app and window and process types, snapshot building and the context module were 115 loose root files. They now live in tree/, actions/, system/, snapshot/ and context/, named like the platform crates' layers so a type a platform crate uses from its input/ or system/ lives under the same name in core. The stray actionability tests move into actionability/. Two test files import one module once so the longer paths keep them under 400 lines. * refactor(core): group refs, ref actions, locators and traces into their folders The ref map, ref store, allocation and identity files, the headless ref action flow, the locator with its live evaluator, and the trace writer with its reader were the last loose families at core's root. Each family now lives under the module that already anchored it (refs.rs, ref_action.rs, locator.rs and trace.rs become their folders' mod.rs), so those module paths do not change. trace becomes public because it now holds the public trace_read and trace_sanitize modules. lib.rs is the only file left at the root. commands/ stays flat: its contract tests require commands/<name>.rs. * refactor(macos): group the macOS layers into concept folders Each layer held one flat list: 62 files in tree/, 68 in system/, 45 in actions/. Concepts with four or more sources now have a folder named with the vocabulary all three platform crates share: tree/resolve/ and tree/properties/, actions/chain/ and actions/select/, input/clipboard/, keyboard/ and mouse/, system/app/, process/, window/ and permissions/. cf_type.rs leaves the crate root. The cursor-overlay and permission-guide Objective-C bridges move next to the Rust files that declare them, with build.rs and the native test imports following. File names do not change. * refactor(windows): group the Windows tree layer into concept folders tree/ held 117 files in one folder. Ref re-identification, the Win32 test fixtures, the UIA walkers, the property reads and the hit test now each have a folder (resolve/, fixture/, walk/, properties/, hit_test/), using the same names as the other platform crates. The fixtures' self-rerun filters and the hit-test scan labels follow the moves, and the public tree paths used by the uia_tree_dump example and the resolution probe are updated with them. * refactor(windows): group the Windows system and input layers into concept folders system/ held about 70 loose sources and input/ about 26. Capture, windows, shell surfaces, wait signals, the COM runtime glue, menus and the shared test seams now each have a folder under system/, and clipboard, keyboard and mouse each have one under input/, with the names the other platform crates use. Public names in lib.rs do not change. Four clipboard helpers now sit under the clipboard module's existing cfg(target_os = "windows"); the crate only builds for Windows, so the set of compiled and run code is the same. * refactor(linux): group the Linux tree layer into concept folders tree/ held 209 files in one folder, the most crowded in the repository. It now has thirteen concept folders (walk, resolve, hit_test, menu, surfaces, placement, live_read, observe, properties, element, vocabulary, capabilities, live) and keeps two loose sources. All live rigs and live suites sit in tree/live/, so the live gate selects them by one prefix; its module strings, its self-test and the runbook follow the moves, and the PARALLEL_READS entry for a module that never existed is dropped. * refactor(linux): group the Linux action, input and X11 layers into concept folders actions/ held 95 files and input/ about 57 loose files, and system/x11/ (87 files) and system/window/ (57 files) had grown past one screen. Selection, scrolling, writes and live rigs now have folders under actions/; keyboard, mouse, physical targeting and the clipboard owner have folders under input/; the X11 client splits into capture, control, monitor, stacking and window, and the window operation pipeline gets window/window_op/. system/ keeps the shape it already had. The live gate's module strings follow the moves. * ci: fail the build when platform code leaks or a crate's layout drifts Platform isolation was checked only through cargo tree on CI and one PowerShell scan, so a source-level leak or a file dropped in the wrong layer reached review unnoticed. scripts/check-architecture.sh checks manifests through cargo metadata and sources through a small lexer: core depends only on its listed crates, only the binary and FFI wire platform crates, no platform crate reaches another, core keeps OS-conditional code in an exact allow-list, every crate keeps its top-level layout, capabilities are implemented in their layer, core domains do not reach commands, and tests are declared beside their code. It plants a violation of every rule before it scans, so a rule that stops detecting fails the gate. It runs in the pre-commit hook and next to the source rules in CI; the cargo tree steps stay as the transitive check. * docs: describe the crate layout and the guard that keeps it CLAUDE.md described the platform layers but not where core's files or a layer's sub-concepts go, so the next contributor or agent would guess. It now states core's domain folders, the shared platform layout and subfolder vocabulary, the file-naming and test-placement rules, and the grounding rule that enforces them. * docs: follow the source restructure in path citations The restructure moved test and module files into feature folders, so three code-span paths and a handful of folder-relative citations in the solution docs, CONCEPTS.md and two test READMEs named files that no longer exist. * test: point the e2e error-code contract at the moved error_code.rs error_code.rs moved to crates/core/src/response/ in the core restructure. The e2e harness contract test builds the path from parts, so the literal path rewrite did not see it and two contract tests failed to open the file. * docs: re-point short-form file citations at their moved files Seven citations named a moved file by its path relative to its crate (for example system/shell_surface_raise.rs), a form the full-path rewrite did not match. Each now names the file's new folder. Short citations of files that did not move, such as the Windows actions/ labels, are unchanged. * test(e2e): read written fields directly and run the host ref-churn leg fully hosted The first live run failed five legs on the harness, not the product: batch and ref-churn checks read SwiftUI echo labels that set-value does not promise to update, and the host-client variant enabled the host only for one command and used a non-private temporary directory, so the host refused it with INVALID_ARGS. The legs now read the field's own value, and the whole host run uses the host and the private temp dir. The stale-ref and close-app assertions are unchanged: they expose pre-existing product defects. * test(e2e): skip three known product defects by exact signature and end the churn fixture by pid No failing leg ships, and no real defect is hidden. The scroll, default-CLI stale-ref and close-app cases skip only when their known defect signature appears, naming the defect, file and origin commit; any other result still asserts, so each case re-arms itself once the product is fixed. When close-app leaves the RefChurn fixture running, the leg ends its own process and asserts it is gone. * fix(macos): let close-app end a process that has no LaunchServices launch date Termination refused whenever NSRunningApplication reported no launch date, so close-app could not end an app launched outside LaunchServices, contrary to the rule that absent evidence is not conflicting evidence. The bridge now passes whether a date is present, and only a present date more than five seconds from the verified instance refuses; pid, start-time token and exit checks are unchanged (codex review: only the intended change). * fix(macos): reveal a clipped scroll area through its enclosing scroll view Scroll-into-view looked for the clipping viewport starting at the target, so a target that is itself a scroll area became its own viewport, produced no direction, and stayed clipped (ACTION_FAILED, live offscreen). The original walk still runs unchanged; only when it returns the target itself does the search repeat from the target's parent (codex review: only the intended change). * test(e2e): assert scroll and close-app again now that both defects are fixed The two signature skips would hide a regression of the fixed defects, so the scroll case asserts unconditionally again and a surviving churn fixture after close-app fails the leg while the leg still ends its own process. Only the default-CLI stale-ref skip remains, for the documented aliasing gap. * test(e2e): gate two more known product defects by signature and stop the command host Live, an unknown-app lookup returned TIMEOUT because NSWorkspace listed one pid twice and the app inventory refuses a list with a skipped record; that case and the batch entry using it now accept only that exact signature, naming the defect. The default-CLI sibling check shares the documented aliasing defect. The hosted ref-churn run now stops the command host it started, which otherwise kept the desktop lease for its 15-minute idle life. * fix(e2e): parse only stdout from captured command output The binary prints its JSON envelope on stdout and tracing warnings on stderr. Every capture the suite parses as JSON merged stderr, so one warning line made json.loads fail. A WebKit WebContent process that NSWorkspace lists twice now makes every --app lookup warn about a duplicate pid; AE4 read an empty window list and aborted the suite. All captures now discard stderr, matching find_target, and a harness self-test refuses a capture that merges it again. * chore: move runbooks and residual review notes out of git into the local planning folder They are operator and review working notes, not product docs: no code, test, workflow or doc reads them. They now live with the other planning material in the globally ignored .compound-engineering/ folder. * test: restore two test assertions the repair pass over-tightened and stop echoing a session id CI showed both repairs were wrong, not the product. The Windows dispatch test now accepts any deliberate refusal again (headless double-click is refused with POLICY_DENIED by design), as it did before. The macOS helper timeout test asserts it returns within a second again, which is what its name promises; the replacement assertion held on one machine and tested nothing about blocking. The overlay test message now prints only the field it checks, so it no longer writes a session id into a failure message (CodeQL rust/cleartext-logging). * docs: tighten test-quality learnings and note the dropped accname agreement test * docs: point two measurement learnings at the code and scripts that hold them * docs: correct the Windows hardening claim in the CI-lane learning * docs: point the snapshot window citation at current code and tighten policy learnings * docs: drop a cited regression test that does not exist * docs: tighten accessibility runtime learnings without changing their facts * docs: tighten Numbers and Xcode learning wording * docs: use the measured popover timings in the wait-for-closed learning * docs: tighten Xcode and native learnings; keep the empty-search learning until merged * docs: split one dense sentence in the agent contract * docs: tighten prose in the contributor and output docs * docs: tighten the find-name learning wording * docs: tighten the concepts glossary wording without changing its terms * docs: tighten test README prose and keep every command and link * docs: cut the learnings store to the lessons that are still true and live nowhere else Two reviewers checked every learning (codex Sol proposed, codex Luna validated with the burden of proof on deletion); a learning stayed whenever they disagreed. Removed dated evaluation logs, trial records and lessons already carried by code, tests, CLAUDE.md, CONCEPTS.md or the skills; moved the one unique lesson of five learnings into a surviving one; trimmed the rest to rule, fix and one evidence pointer. The two remote dev-box learnings are gone: they described one maintainer's machines, not the product. 52 files and about 46,700 words become 15 files and about 11,100 words. Citations in the e2e harness and test READMEs now point at the surviving learnings. * docs(windows): stop citing two learnings that were removed The rule each comment cited is stated in the comment itself, so the pointer is dropped rather than re-aimed. * chore: remove tooling and paths tied to one maintainer's dev machines agent-desktop is a product for any team, so the repository no longer carries a remote-box workflow: remote-linux.sh (worktree sync to an SSH alias), box-slot.sh (a lock on that box), session-env.sh (importing that box's desktop session) and their two self-tests are removed, with their fmt-job lines. Private-session sessions now default to ${XDG_RUNTIME_DIR:-/tmp}/agent-desktop-sessions, overridable with AGENT_DESKTOP_SESSION_BASE, at every site that creates, discovers or cleans them (shell wrappers, live gate, e2e runner, Rust live tests); the window manager defaults to the system install. Docs and the Linux probe ledger no longer cite personal paths. * test(e2e-linux): take the stale-ref process baseline after the app is running The leg read the process list before Calculator started, so a correct refusal failed its own 'must not end the app' check. The unit test now models the launched process and fails on the old ordering. * fix(linux-tests): keep each OS user's private sessions under their own base Without XDG_RUNTIME_DIR every user shared /tmp/agent-desktop-sessions, so the second user could not create a session. The fallback is now /tmp/agent-desktop-sessions-<uid>, created 0700, at every site that computes it. * test(macos): restore the screenshot timeout test the repair pass over-tightened The repair also required the killed child to be reaped inside the product's cleanup deadline; on a loaded CI runner the product truthfully reports that it was not, so the assertion tested the runner, not the timeout. * fix(linux-tests): let the live gate merge receipts without creating a session base --merge starts no sessions, so it no longer needs a writable session base; the self-test proves it with a base that cannot be created. * test(linux): make the proc-scan test fail with a diagnosis instead of a bare unwrap It failed once on CI with no evidence. The helper no longer proceeds silently when the child never becomes ready, and a missed scan reports what /proc shows for the child, so the next occurrence names its cause. * fix: harden desktop boundaries and bound native operations * fix: install inspector signal handlers before readiness * test(linux): stop running renamed copies of sleep, which Ubuntu 26.04 no longer supports GitHub's ubuntu-latest now includes Ubuntu 26.04 runners, where coreutils is a transitional package over the uutils multi-call binary. That binary picks its tool from argv[0], so a copy of sleep renamed for a test exits at once and the scan finds a zombie. proc_scan failed on every 26.04 run, on branches without this cleanup too. One shared helper now copies the standalone system shell and blocks it on a held stdin pipe, which keeps the test's name on any Linux; the five tests that renamed sleep use it. * test(linux): keep blocking fixtures alive in the background without stdin A background command in a non-interactive shell gets /dev/null as stdin before any redirection, so the orphan launcher's blocking copy read EOF and exited. The copied shell now loops on 'sleep 1' instead of reading stdin; that keeps it alive under its own name in the foreground and the background alike, and lets both launchers return to their original form. * test: make native fixtures portable across CI hosts * test: use an owned process fixture for Linux discovery * fix: preserve trace readability and clear stale overlays * perf: avoid repeated trace ledger replacements * fix: reject shared helper writes and isolate Linux test fixtures * fix(linux): list every top-level window the registry exposes, whatever its role The window list admitted a root child only for roles 23, 16 and 69, a modal panel or a showing filler. Toolkits give a top-level window the role of its widget, so the GTK3 Save As file chooser (19), a Qt message box (2) and a Qt push button shown as a window (43) were never listed, while the modal gate, which reads every root child, refused focus because of the same dialog (L41-1). A root child that implements Component is now a top-level, and one predicate decides it for listing, frame identity, menu inventory and the shell content window. Dialog-ness is one predicate too: the AT-SPI dialog roles read from libatspi (alert, color chooser, dialog, file chooser, font chooser, L41-4), used by the window list, focus, sheets and nested dialogs. A blank, showing top-level that holds only menus is a popup menu surface (the GTK3 context menu and Chromium menus, L41-2, L18-7): menu observation keeps it and the window list drops it. The size identity for unnamed windows applies to every toolkit without global positions instead of GTK4 role-39 panels only, and the duplicate reports_global_position copy is gone. * fix(linux): attribute a launch handed to a running instance by its new window Launching thunar with the session's daemon running failed with "exited with status 0 without a verified application owner" although a new Thunar window opened (L41-6): the launcher forwards to `Thunar --daemon` and exits, and the attach rule also compares the argument list. After a clean launcher exit with no strict owner, the one running process of the same canonical executable that shows a top-level window absent from its pre-launch baseline now owns the launch, and with --activate that window is reported. Several such processes stay ambiguous, none keeps the failure. Strict argument identity for attaching before a spawn is unchanged. * fix(linux): publish hidden and offscreen in snapshots from the action gate's rule A snapshot gave Mousepad's hidden toolbar button a ref with no state, and the headless click then refused it as offscreen (L41-5): the snapshot projection never emitted hidden or offscreen, while the live projection the gate reads derived both. The snapshot now applies the same visibility functions to the facts its walk already holds (states, extents, frame, scroll container, minimized frame) with no extra reads; an input it lacks stays unknown and adds no token. The sentinel and placed-rectangle tests, copied in four places, now live in one module, readiness recognises both web document roles (L3-13), and the GTK4 popup origin is chosen by one rule for snapshot and live reads. * fix(linux): rebuild the desktop alias cache after an edit inside one timestamp tick The in-place desktop edit test failed 2 of 5 runs on unchanged code: the cache compared inode, length and ctime, and an edit of the same length inside one coarse kernel tick keeps the same ctime, so a stale alias index was served. Like git's racily clean rule, a cached index is now trusted only when every stamp is older than its build time minus a tick margin; otherwise it is rebuilt on the next call. * test(linux): add widget-zoo fixtures and the ref-contract e2e legs The bar for a snapshot is that every ref it gives out lands when its action is performed headless, verified by an independent read, or carries hidden, offscreen or disabled and is refused with no effect. Three fixture windows (GTK3, GTK4, Qt) expose the standard widgets, each logging its own effect, and the ref_contract legs drive every ref of a fresh headless snapshot against the log and raw AT-SPI. The window legs open the fixture's dialog, file chooser, message box and top-level button and require each to be listed, reachable as a sheet when modal, and operable to completion. The harness's own window oracle counts Component root children, the same rule as the product (L41-1). * docs(probes): record top-level, popup, visibility and launch measurements Area L41 records the toolkit census that disproves the role-based window rule of L8-2, the popup shapes that are and are not root children, the closed-dialog husks, the libatspi dialog role numbers, the snapshot and gate disagreement on Mousepad's hidden toolbar, and the Thunar daemon hand-off. The environment table names the XFCE container the new rows were measured in. * refactor(linux): state the unplaced-node test without a double negation * fix(linux): recheck racy desktop files by content instead of rebuilding the alias index Rebuilding whenever a desktop file was newer than the build tick made every lookup right after an edit rebuild, so the cache was never reused (the in-place edit test failed 5 of 5). As in git's racily clean rule, only the files that changed within the tick keep a content hash; equal stamps reuse the index unless one of those files now reads different. The test passes 10 of 10. * fix: avoid unsafe at-spi property and interface reads * test: verify linux refs against addressed raw controls * fix: include display bounds in snapshot visibility * docs: explain thunar file views and literal newlines * docs: keep file-view guidance application independent * fix: observe and resolve showing alert surfaces * fix: restore checks after the Linux cleanup merge * fix: serialize trace emission and repair review gates * refactor: align native security types with module boundaries * fix: select option rows through their advertised semantic verb * fix: avoid sibling scrollbar mutations during reveal * fix: retain unanswered scoped surface reads * fix: activate exported menus outside the accessibility dispatcher * test: select the active chooser filename field * docs: record semantic reliability evidence and headless policy * test: repair independent Linux workflow oracles * fix: preserve menu fallback and stop expired launches * fix: await queued windows and reclaim stale clipboard records * fix: preserve clipboard scan timeout codes * fix: verify combo commits and bound optional snapshot reads * fix: complete linux ref delivery and safe refusal contracts (#307) * fix: complete linux ref delivery and safe refusal contracts * fix: complete Wayland menu and chooser ref contracts * test: report unsupported action setup as unexercised * test: separate concurrent overlay fixture sockets * test: enforce snapshot promises in linux ref contracts (#308) * test: align harness checks with retained native refs * fix: prevent private desktop data from leaking through diagnostics * fix(ci): reflect successful writes in the JEV fixture | 14 days ago | |
feat: integrate Linux support with privacy hardening (#311) * chore: remove three uncalled lab scripts No workflow, hook, script or test calls them: a symlink helper for local skills, a root-only uinput stub whose legs were never written, and an unrun self-test of the npm wrapper smoke, which CI runs directly. * docs: remove stale evaluation records and unreferenced diagrams The architecture diagrams and the Slack token illustration were not linked from anywhere and showed Linux as planned. The acceptance audit was a withdrawn status record. Two solution docs were folded into the docs that already carried their context, so each lesson has one home. Validated in the docs removal ledger (docs-3, 6, 8, 11, 12, 13). * docs: correct solution docs that contradict the code Many learnings named moved files, deleted tests, stale function signatures, or rules the code no longer follows (bare refs accepted, native apps never activated, one box lock instead of two slots). Each now cites the code that holds today, and none cites the planning docs or the untracked dogfood reports. * docs: correct CONCEPTS.md against the shipped adapters Several definitions described one platform as if it were every platform, missed the Linux adapter, cited probe rows that measure something else, used a screenshot flag that does not exist, and called dogfood reports committed. Session Manifest folds into Session and the must-catch fixture into Gate Self-Test, where each was already half defined. * docs: correct contributor docs, the FAQ, the JSON contract and runbooks CONTRIBUTING.md now points to CLAUDE.md for the engineering rules instead of keeping a second copy that had drifted (a MockAdapter that does not exist, an E2E command that exits without the exclusive-desktop variable). The JSON contract described an unknown disposition the code no longer emits, the FAQ repeated the README platform table, and the Windows runner runbook cited plan ids and a trigger branch that merged. * ci: assert the release-ffi unwind profile once per release ffi-release-panic-guard already runs the identical grep and publish-github needs it, so the copy in ffi-release-gates added nothing. * chore: let release-please bump the Linux skill version skills/agent-desktop-linux/SKILL.md carries the x-release-please-version marker and is published with the other skills, but was missing from extra-files, so its version would have stayed behind. * docs: make the test harness READMEs runnable as written Several harness commands failed as documented (the Linux consumer run without AGENT_DESKTOP_WM_ROOT, the discovery inversion without a header path, a headless probe binary that swiftc names differently) and the fixtures README listed files that do not exist. The Linux E2E guide drops plan history and keeps what an operator needs to run and read it. * Revert "chore: stop shipping the jev-desktop skill" This reverts commit 34826e104af928a345dbdfeaa83a9a9b72745fb0. * docs: fold the ref-root viewport lesson into the clipping doc The shared clipping doc already states the same incident and the repair that superseded it, so the lesson keeps one home. Validated in the docs removal ledger (docs-14). * docs: say jev-desktop needs a repository clone, and match it to its scripts The skill teaches node scripts/jev/*.mjs, which ship in no installed package, so it now says up front that it needs a clone of the repository and a TYPESAFE_API_KEY. Checked against the scripts: run.mjs retries 429/503/529 twice while act.mjs does not, --bin and --execute belong to act.mjs only, act.mjs sends longer values and the ancestor path, why is null on act, the stop list and exit codes were incomplete, and JEV_VERIFY_MS was missing. * docs: correct FFI handle, wait and last-error text against the header ad_free_handle works after the adapter is destroyed (native_handle.rs tests prove it), so the rule is to free handles before destroying the adapter only for use, not for release. The wait bullet now uses the header's AdWaitMode field names, AdActionStep lists mechanism and verified, the delivery-semantics accessor copies values instead of lending a pointer, and the JSON-string table gains ad_execute_by_ref_timeout, ad_trace_show and ad_trace_export. * ci: cut duplicate jobs and steps from the CI workflow Each removed piece repeated a check that another job already runs on the same OS, or depended on a file that is leaving the repository: - platform-check: three legs were subsets of clippy --all-targets on the same runner; the windows-11-arm leg is now a clippy step in test-windows-arm. - ffi-header-drift, ffi-python-smoke and ffi-passthrough now run as conditional steps on the ffi-panic-guard legs that used the same OS, so the header drift gate and the stub checks keep running on every PR. - check-release-consistency.sh and check-npm-package.js run in the supply-chain workflow on every PR, push and weekly, and need no OS. - the ubuntu bash3 compatibility run was weaker than the macOS Bash 3.2 run. - trash and trash-cli installs had no caller left. - check-phases-ledger-citations.ps1 read docs/phases.md, which leaves the repository, and would fail the Windows lane. The viewer_filter.js test moves here from the inspector workflow, so the shipped viewer code keeps a CI test. * chore: remove the unshipped inspector web UI tools/inspector is a local developer viewer that no shipped artifact, skill or release step uses, and its workflow only tested it. The viewer_filter.js test it also ran now lives in the CI workflow. * ci: give the release dry run a concurrency group and state why it stands alone The reason it is a separate read-only workflow lived only in docs/phases.md, which leaves the repository. A concurrency group stops a busy PR from queueing many full rehearsals, and trash-cli had no caller left. * ci: run the Windows capability probes only when probe files change The probes measure the hosted runner. The one contract they check against crate code, secure-value withholding, is also a plain unit test in the Windows CI lane, so code-only PRs no longer pay for a 30-minute probe run. * docs: document the jev scripts beside the code The jev-desktop skill left the shipped skill set, so its usage notes move next to the scripts they describe, checked against the current flags and environment variables. * chore: remove an unreferenced benchmark screenshot No file links to it. * docs: fix three FFI ownership and panic claims a review disproved A codex review proved three overstatements against source: ad_abi_version returns a constant with no panic trap, so only entrypoints that can panic catch_unwind; last-error strings are borrowed and never freed, so not every returned pointer has a free function; and ad_free_handle validates the adapter before it accepts a null handle. Remaining em-dashes in the FFI references became plain punctuation. * refactor(core): drop two helpers that nothing calls RefStore::for_tests and ErrorPayload::with_recovery had no caller in any crate or test. They sit in public modules, so rustc could not flag them. Recovery hints are still attached by recovery_for_code. Ledger rows A-01, A-02. * test(ffi): drop an unused re-export from the shared test module No ffi integration test names AdIdentifierKind through the common module; the file-level allow(unused_imports) hid it. Ledger row A-08. * ci: keep the 20-minute FFI timeout on legs that only run the panic probe Only the aarch64-apple-darwin leg gained the header drift and stub smoke steps, so only it needs the longer bound. * test(linux): drop live probes that only print timings semantic_click_latency_probe and the vocabulary report, timed pass and label timings asserted nothing and no gate reads their output. The vocabulary live test keeps every assertion path. Ledger rows A-12, A-13. * refactor(linux): drop stale dead_code allows on display errors DisplayCause, as_str and display_cause_error all have production callers, so the allows only hid any future dead code. Ledger row A-15. * refactor(windows): compile the canonical lock path only for tests Only the lease tests call canonical_lock_path, so a cfg(test) gate replaces the allow(dead_code) and the doc no longer promises a future caller. Ledger row A-16. * docs: cut the README to 130 lines and move install detail to docs/install.md The README keeps the pitch, install, the observe-act loop, the agent section, the command table and a plain platform table. Install-script allowlisting, attestation, the source build, per-OS permissions and platform footnotes move to docs/install.md unchanged. * docs: record the parameterized-replacement findings beside their probes The payload shape, the background no-op and the input-context route are what a maintainer needs when running replace_range_probe.swift, so they belong in the harness README rather than in a one-trial log. * docs: state what the jev scripts actually send and restore --no-values does not withhold text passed with --text, the clipboard is only put back when it held text, and a run reads the screen more than once per turn. The README said otherwise. * chore: stop naming a removed workflow in the redaction gate comments windows-e2e.yml no longer exists; the -Path override is described by what it does. * docs: note parameterized attributes in the real-app probe guide A setter that does nothing does not prove an editor has no write path; the probe lists parameterized operations such as AXReplaceRangeWithText, and the replacement range is in UTF-16 units. * docs: remove the Numbers replacement trial log now its findings live with the probe The real-app README carries the payload shape, the background no-op, the input-context route and the inventory lesson. Validated in the docs removal ledger (docs-15, codex and opencode both REMOVE). * chore: make infra comments match the repository The gitignore comment said no normalized twin is committed, the Windows e2e runner said no CI workflow touches it, one gate comment named a function that is being removed, and another described docs/phases.md as in the repository. * docs: make the PR template gate commands run as written The E2E line exited 2 without AGENT_DESKTOP_E2E_EXCLUSIVE=1, a string HOST_PKGS reaches cargo as one argument in zsh, and the skill checklist left out the Linux skill. * fix(npm): point the postinstall skill hint at commands that exist claude mcp add-skill is not a Claude Code subcommand. The hint now names the binary's own skills command and the skills installer the README documents. * docs: drop the jev README now that the jev-desktop skill documents the scripts Two descriptions of the same scripts would drift; the skill is the one that ships. * docs: shape the README for users, contributors and agents The fast path stays on top (install, a quick start, the agent section), followed by reference sections and a documentation map that points each reader to the right file. It restores facts the shorter version dropped: headless-by-default delivery, PowerShell ref quoting, the readable JSON sample, the accessibility-tree row and the per-OS package sets. * docs: name the CI job that now runs the FFI Python smoke The ffi-python-smoke job was folded into the macOS leg of the ffi-panic-guard job, so two docs pointed at a job that no longer runs. * refactor(core): share empty_trace_dir_error between trace show and export The two copies were byte-identical, so one pub(crate) helper in trace_read now serves both calls. Ledger row B-07. * ci: give the Linux live e2e job an explicit read-only token The job inherited an empty permission set, unlike its macOS twin; checkout needs contents: read and nothing more. * ci: pin the probe workflow's checkout to the same release as every other workflow It was the only workflow left on v7.0.0. * docs(core): make two rustdocs match the code they describe transform_tree prunes on is_ref_able, not on role alone, and the drill-down completeness note belongs on run_from_ref_with_context, not on count_nodes. * docs: state the jev read cost for run.mjs as well as act.mjs lead-infra's review found the skill gave the snapshot cost for act.mjs only. run.mjs reads the screen before and after each action (none after DRILL or WIDEN), and both scripts add a surface snapshot when an overlay is open and no region is pinned (desktop.mjs:62-76, act.mjs:236-237). The other five corrections already held in the merged text. * fix(cli): list linux among the skills get aliases skills get linux already resolves, but the help text left it out. * test(visual-debug): stop naming the removed inspector in a test env var The capture child marker now says what it marks, since tools/inspector is gone. * refactor: replace per-crate ensure_budget copies with Deadline::ensure_remaining Twelve private macOS helpers, two private Linux helpers, and the shared Linux bus and Windows permissions helpers all had the same body. One Deadline method now carries it, with unit tests. The DeliveryTracker variants, the mouse and chain variants, and Linux input/deliver.rs keep their own checks because they add delivery details. Ledger row B-01. * docs: restore rules and definitions an earlier cut dropped CLAUDE.md again states the @generated exemption, that every non-doc comment form is rejected while //! module docs are allowed, and that expect and panic are test-only like unwrap. CONCEPTS.md again says a ref-addressed drag picks up at the bounds center (and the slider workaround) and names the must-catch and must-pass fixture halves. * docs: restore two oracle contracts to the Linux E2E guide The rewrite dropped what the pointer delivery legs read back and what the dead-owner recovery leg requires, both of which an operator needs to judge a failing leg. * chore: keep the gitignore note on which capture twins are tracked Per-area probe directories commit their normalized twins; only the twins under probes/<platform>/captures/ are regenerated and untracked. * docs: say in Coordinate Fallback that raw --xy input focuses nothing A raw coordinate resolves with no ref and no window identity, so the caller owns where it lands; the fallback entry now states it directly. * refactor(windows): drop file-level dead_code allows in system modules Windows clippy with -D warnings passes with all twelve removed, so every item in those files is used and the allows only hid future dead code. The menu source agreement test no longer cites the allow. Ledger row A-22. * refactor(windows): parse w-<n> window ids in one place observe and surfaces carried byte-identical parsers with the same InvalidArgs message; window_ops now owns parse_window_id next to window_id. Ledger row B-08. * refactor(linux): share the already-satisfied pointer select result The combo and row select paths built the same skipped XTEST.click result with their own copy of the route constant; select_pointer now holds one. Ledger row B-11c. * refactor(linux): share clipboard delivery helpers between X11 and Wayland set_clear and wayland carried the same verify grace, fresh deadline, and not-delivered and unverified error builders; delivery.rs now holds one copy and messages stay with the callers. Ledger row B-11d. * refactor: build stale-evidence errors with AdapterError::stale_ref_because Three adapter copies of stale_evidence_error repeated the body of the existing core constructor, so the resolvers now call it directly. A core unit test pins its code, suggestion and disposition. Ledger row B-02. * refactor: move the live-evidence completeness helpers into core incomplete_live_evidence, essential_live_evidence_complete and known_actions were byte-identical across the adapters and work only on core locator types; live_element now owns them, with unit tests. Ledger row B-05. * refactor: move notification identity verification into core The macOS and Windows checks were byte-identical over core notification types, so core now exports verify_notification_identity with unit tests. Linux keeps its own check because its suggestion and disposition differ. Ledger row B-06. * chore: point the stale-ref gate at the constructor callers now use stale_evidence_error is gone; evidence-refused refs now build their error with AdapterError::stale_ref_because, so the comment and the failure hint name that. * test(e2e-linux): drop plan requirement ids from leg descriptions R9, R19, R22, R24, R32, R33 and R35 named requirements in a plan that has left the repository, so the prefixes meant nothing to a reader. Nothing parses the requirement text; leg ids are unchanged. * chore: stop the privacy scan comment from claiming the repo ships planning docs * test(e2e-linux): drop duplicate legs and dead harness helpers Six legs repeated the observation of a stronger surviving leg on the same tiers, two unit tests restated another test's assertions, and several helpers had no caller. Each row was validated by two independent models (ledger TR-145..175). * test(e2e-windows): drop a racing zero-timeout leg and dead fixture code The delayed-enable zero-timeout leg raced the fixture timer while the permanently disabled leg proves the same no-wait refusal; the click row's mechanism key is now asserted instead of ignored, and an unused fixture overload and an always-true self-test line are gone (ledger TR-149, TR-151, TR-152, TR-186). * test(e2e): fold the absolute-timeout exit code into the deadline test The removed test repeated the descendant-deadline test and added only the 124 exit code, which the survivor now asserts (ledger TR-185). * ci: run the four hermetic scripts self-tests in the fmt job perf_ab_probe_test.py, test-linux-live-gate.py, test-session-env.sh and test-remote-linux.sh guard the perf report math, live-gate shard and receipt rules, desktop environment selection, and the remote kill guard, yet no job ran them; each is stub-driven, needs no display or network, and takes about a second. The two that start private sessions stay manual (one already runs in linux-e2e). * test: make every self-rerun test prove its child ran the target test A test that re-runs its own binary with a libtest filter passed with zero tests run when the filter was wrong, which already hid the Linux cursor overlay check (its filter missed the overlay module). Each self-rerun site now asserts the child printed "1 passed; 0 failed"; the overlay filter is corrected, and the interactive clipboard test compiles again under its feature. * test(e2e-linux): drop duplicate contract self-tests and dead test helpers Registry and gate checks were asserted twice; the surviving registry test now also refuses an empty registry so it cannot pass vacuously (ledger TR-177..183). * test(core): drop tests that restate a stronger test or only exercise a mock Each removed test was matched by a surviving test at the same or a stronger boundary, or asserted only a mock's or a constant's own value; two merges keep the unique assertions (role round trip, toggleable negatives) in the survivors (ledger TR-001..019). * test(macos): drop duplicate and tautological tests, fold unique assertions into survivors Survivors now carry the sibling-radio suggestion, the AXOpen stale classification and the native plus DOM identifier pair (ledger TR-058..074). * test: drop CLI and conformance tests that only exercise their own mock The conformance window-identity tests asserted the mock adapter's behavior, and the version and wait-metadata assertions repeated stronger tests (ledger TR-142, TR-143, TR-161..163). * test(ffi): drop duplicate C ABI tests and keep their unique checks in survivors Typed discriminant round trips, out-of-range values, a second trapped panic and the last-error pointer across successful calls now live in the surviving tests (ledger TR-035..044). * test(windows): drop duplicate dispatch, notification and state tests The adapter's inline tests were byte-identical to adapter_tests.rs; the R2 advertisement tests now live as rung call counts and isolated advertisement cases in the action and tree tests (ledger TR-073..091). * refactor(linux): import test-only step types in the select tests, not production select_row.rs and select_pointer_combo.rs carried cfg(test) imports only for their tests' use super::*; the tests now import ActionStep and StepMechanism. * test(e2e): add error-envelope and batch failure legs to the macOS harness Agents branch on exit code and error.code for a bare @e1, an unknown app, a missing surface and a parse error, and on batch's continue versus stop-on-error results; no live leg proved them. Self-tests check every expected code exists in the core enum and that each leg asserts code and exit together. * test(e2e-linux): add error-envelope and batch failure legs Two registered legs drive the real binary on both X11 tiers: recoverable error envelopes (INVALID_ARGS for a bare @e1, APP_NOT_FOUND with exit 1) and batch per-entry failure with and without --stop-on-error, observed on the app itself. Pure verdict tests with lying fakes keep them checkable without a desktop. * test(e2e-windows): add error-envelope and batch failure legs Eight Reliability legs cover INVALID_ARGS, SNAPSHOT_NOT_FOUND, STALE_REF for an absent element, APP_NOT_FOUND and ELEMENT_NOT_FOUND with exit codes, and batch halt versus continue with per-entry codes. Not yet run: they need a Windows host (contract -SelfTest, the U7 self-tests, then the live suite). * refactor(core): route deadline clock reads and poll sleeps through one test-freezable seam Several core tests flaked on loaded CI runners because their deadlines ran on the real clock. Deadline now reads time and sleeps through crate-private now() and sleep(), which outside cfg(test) are the same std calls; tests can freeze and advance a thread-local virtual clock. Public API and production behavior are unchanged (codex review: no behavior change). * test(macos): replace the inventory retry wall-clock assertion with counted attempts The test asserted more than one attempt inside a real budget, failed on 6 branches when a slow runner fit only one, and 45b28ce9 only raised the budget to 500 ms. The retry and the partial-inventory refusal are now proven by attempt counts on a fake capture that never consumes its deadline. * fix(windows): confirm a target's exit on its process handle before reporting a menu-wait read error A window that stops pumping during teardown reads as APP_UNRESPONSIVE before its process object is signaled, so the single liveness re-check lost the race and the wait reported the transient code instead of STALE_REF (failed on 7 branches). The wait now blocks on the process handle for at most the remaining deadline, capped at one second, before it keeps the read error. First run on Windows CI. * test(linux): drop duplicate input, close, tray and tie-break tests Each removed test repeated a survivor at the same boundary; the unique recovery text, baseline counts and tie cases moved into the survivors (ledger TR-046..057). * test(e2e): add stale-ref and key-combo legs to the macOS harness The removed-element leg accepted four error codes; it now requires STALE_REF with a safe retry. A RefChurn leg proves STALE_REF and AMBIGUOUS_TARGET after an in-place UI swap, and a press leg proves combos by reading the field itself. * test(e2e-linux): add stale-ref-after-UI-change and key-combo legs The stale leg refreshes nothing between a real menu change and the click and requires STALE_REF with its recovery; the press leg checks ctrl+a, backspace and return by AT-SPI text, not by the command's own ok. * test(e2e-windows): add stale-ref contract and key-combo legs The removable row now reports clicks, so a stale ref proven by refusal also proves nothing was delivered; press legs cover ctrl+a, shift+tab, return and the blocked alt+f4 refusal. Not yet run: needs a Windows host. * docs: point the live-gate runbook at the focus leg that stays The lift leg duplicated lifecycle.focus_window_active_reads and never read the restoration it named, so the tests cleanup removes it; the retained live owner is the active-window read. * test(core): run the settle-loop and artifact-lock budget tests on the virtual clock These tests failed on loaded Windows and macOS runners because a real 50 ms lock wait or settle window expired before the code under test ran; a 16 x 20 ms retry wrapper hid it. They now freeze the virtual clock, so lock polls and settle sleeps advance time deterministically, and the retry wrapper is gone. * fix(windows): treat E_UNEXPECTED on a UIA read as a transient failure A provider tearing down a window or shell host answers one read with E_UNEXPECTED; it was classified terminal and narrowed to a non-retryable APP_UNRESPONSIVE, so one read ended a whole wait (failed on 4 branches). It is now a retryable unresponsive read inside the caller's existing deadline; E_FAIL stays terminal. First run on Windows CI. * test(linux): make vacuous assertions able to fail Repaired tests named a behavior their assertions could not detect (a negative check that any value passed, a banned-name list with dead entries, an unverified result asserted only as not verified); each now checks the stated contract. * test(macos): make vacuous assertions able to fail, each proven by a production mutation Process-group kill, semantic-only disclosure chains, single capture, strategy fall-through, scan tolerance, endpoint naming, overlay acknowledgement, the select depth cap and the name-plan slots now fail when the behavior breaks. * test(ffi): make C ABI tests assert what their names promise Modifier cap, JSON error forwarding, invalid discriminants, envelope fields, dirty out-params on a worker thread, wait error envelopes and the session adapter's fail-closed path each had an assertion that could not fail; every repair was proven by breaking the production path once. * test(e2e): make harness self-tests fail for the regressions they name * test(e2e-linux): make harness self-tests and a menu leg assert structured results The menu timeout leg now checks the structured wait_timeout details, and the oracle, runner, session-sweep, workflow and verdict self-tests assert the specific failure each one names. * test(e2e-windows): replace diagnostic scaffolding and soft guards with real assertions The headed double-click leg used debug output, a fixed sleep and one read; it now uses Assert-Effect like its siblings. The handle-set and overlay controls no longer skip silently, and the Chromium rate leg fails when every sample is stale. Not yet run: needs a Windows host. * test(core): drop find, get, wait, session and trace tests that repeat stronger tests The removed find tests ran a cfg(test)-only snapshot collector production never uses; that collector's count helper went with them. Merges keep the observed-role negative, the null-bounds case, the conflicting wait flags as a table and the identifier preference in the survivors (ledger TR-020..034). * test(windows): drop input fake-sink, policy, clipboard and surface tests that repeat survivors The keyboard and mouse send tests only checked that the test fake stored what it was given; the clipboard, chain and surface merges keep their unique cases as table rows or assertions in the survivors (ledger TR-092..106). * test(windows): drop tree property, hit-test and resolve tests that repeat survivors Each removed test had an identical or stronger twin in the same module family (ledger TR-124..140). * test(linux): make clipboard, keyboard-frame, remap, capture and close tests assert their contracts The remap test now proves the deadline cancelled after the remap and that the keymap was restored, the off-screen capture test places its window instead of skipping, and the close gate is checked for both graceful and forced closes. * test: make CLI dispatch, overlay and real-app tests assert what they claim Real-app tests now fail when their precondition is missing instead of passing silently, the overlay show test pins POLICY_DENIED, and the conformance test checks each capability's outcome, not only that the call returned. * test(real-apps): assert the Numbers failure stop and drop a dead plan citation * test(macos): make capture-scope, screenshot and child-label tests assert their contracts The capture-scope tests now run the production scope filter instead of a test copy of it (the single-pid case is kept beside the pid-set case), the screenshot timeout checks the child is gone, and the child-label rule is checked for every role it names. * test(core): order concurrent snapshot writers instead of retrying lock timeouts, and pin tray ids Eight writers polled one lock with no fairness and a 30 s retry hid starvation that failed CI on four branches; the writers now take turns, and a single-thread test on the frozen clock proves a held store lock still blocks a writer. A new test pins that allocation keeps a RuntimeId identifier and its kind, which no test covered after the tray identity change. * test(windows): drop lifecycle, launch, HRESULT and signal tests that repeat survivors Merges move the exit-code serialization cases into core's process-state tests and the HRESULT detail strings and generation checks into the surviving Windows tests (ledger TR-108..121). * test(windows): drop two duplicate tree tests and make hit-test and resolve tests assert their contracts hit_test now judges a probe through judge_probe, so a probe miss is tested on the production path (same behavior: a miss is Unknown, a hit is judged); the fixture teardown, minimized-window, anchor and timeout tests assert what they name (ledger TR-122, TR-123, TF-144..157). * test(linux): make capture-cap, tie, key-owner, menu and tray tests assert their contracts * test(core): run live-locator and ref-action wait tests on the virtual clock These tests had their budgets widened and an exact attempt count loosened to 'at least one' to survive slow runners; on the frozen clock the retry loop's own sleeps advance time, so the original exact counts and small budgets hold. * test(windows): drop the setup-time stopwatch from the headless notification wait refusal The elapsed < 2 s assertion measured COM and adapter setup and failed twice on loaded runners; POLICY_DENIED is not retryable, so a burned deadline would already surface as TIMEOUT and fail the code assertion. * test(windows): decide modern-capture frame-wait expiry inside the wait, not by wall time The fallback test asserted TIMEOUT against an 80 ms budget that real capture setup could consume first (failing as INTERNAL); the cfg(test) frame hold now starts its budget inside the frame wait, and the pattern test waits for a frame showing the fixture instead of sleeping 500 ms. Production frame wait unchanged. * test(windows): signal the menu fixture's open state from real menu mode The fixture announced the popup before TrackPopupMenu entered menu mode and ignored its result, so the chromium composition test compared two live reads that could straddle the flip; readiness now comes from menu mode itself and the comparison waits until both sources agree. * test(linux): make tray, signal, settle, walker and shell-owner tests assert their contracts The error-code boundary test now enumerates codes exhaustively at compile time instead of a hand-typed list of 16, and the remaining repairs check the behavior their names state. * test(macos): replace real-clock budgets and stopwatches with counted attempts and events Stabilize-loop tests could expire before their first attempt on a slow runner; they now use budgets the fakes never consume and assert exact attempt counts, and the overlay acknowledgement tests block on a release channel instead of a 700 ms sleep. * test: isolate command-host sockets per test and assert batch deadlines by value Fixed socket and symlink names keyed by pid were never removed, so pid reuse could fail a later run; each test now owns a removed-on-drop directory. Batch deadline tests assert the budget the adapter received instead of wall time. * test(ffi): isolate the round-trip test's state root and require the snapshot log event The round-trip test set HOME under its own lock but left AGENT_DESKTOP_HOME alone, so a developer's state root leaked in; it now uses the shared isolated home helper. Log-callback tests no longer fall back to the first delivery. * test(core): make actionability, trace, find and close tests assert their contracts Each repaired test was proven by breaking its production path once. The benchmark example's rect test moved into the library's rect tests so it runs in CI, and the find roles hint is now tested through execute instead of a cfg(test)-only copy, which is deleted. * test(windows): make dispatch, envelope, scroll and fixture tests assert their contracts Permissive Err arms, restated literals and a self-comparing fixture expectation are replaced by assertions on the delivered outcome. * test(linux): make tree walker, projection and placement tests assert their contracts * test(core): make session, overlay-routing, hover and pointer tests assert their contracts Session precedence tests pass the env value directly instead of mutating the process environment, gc is actually exercised on an unreadable manifest, the skeleton golden compares the whole tree, and hover counts receiver checks instead of timing them. * test(windows): make drag, capture, HRESULT, signal and wait tests assert their contracts Assertions true by construction (an intersection member found in its own set, an assert_ne after an exact assert_eq, a uniqueness loop over a test-local list) are replaced or removed, and the remaining checks name the behavior they guard. * test(linux): make resolve tests assert the unread-region refusal they name * test(core): drop the null ref_id from the skeleton golden fixture The skeleton golden test now compares the whole serialized tree, and the serializer omits a missing ref_id, so the fixture must not carry one. * test(core): make ref, refs, trace-read and policy tests assert their contracts The XSS gate now asserts the exact escaped payload, the oversize-refmap guard is exercised through the production writer, poll and wait tests count resolves instead of timing them, and a new test pins identifier kinds through RefEntry identity. Each repair was proven by breaking its production path once. * test(windows): make lifecycle, overlay spawn and shell-surface tests assert their contracts A tautological ambiguous-target shape check now serializes the production value, a self-comparing error-code helper is gone, and the taskbar test no longer returns early on an empty tree. * test(npm): drop duplicate platform and wrapper tests and check the real staging directory The staging-cleanup tests read the destination, which installArchive never stages in, so a cleanup regression still passed; they now compare the package bin directory before and after. Duplicate platform-table and wrapper tests are gone, with their unique helper-name and Trash-warning checks kept in the survivors (ledger TR-189, TR-191..193, TF-187, TF-188). * ci: run the Numbers sheet-script self-test in the fmt job numbers_sheets_test.py drives numbers-sheets.sh against a fake binary in a temporary home and proves its failure stop; it is hermetic and nothing ran it. * test: nest extra test modules so moved anchors keep room under the line cap ref_action.rs (400 lines) and the Windows tree/resolve.rs (398 lines) become their folders' mod.rs in the restructure and must then declare their new child modules. Their extra test modules are declared from the files' tests modules instead, so both anchors stay under 400 lines. The nested test files import their parent by absolute path, keeping what `super::` meant before. The Linux action scan walks src/actions recursively, so the banned-call scan keeps reading the action files that move into select/, scroll/ and write/, and it asserts files from each of those groups are still scanned. * refactor(cli): give the command host and diagnostics their own folders Nine command_host*.rs files and three diagnostic*.rs files sat loose in src/, so the binary's root read as a flat list. Each family now lives in a folder whose mod.rs is the former entry file; the helpers were already #[path] children, so no module path changes. * refactor(core): group core's leaf types into response, input, notifications, storage and wait Sixty-seven of core's 270 root files are the envelope and error model, the input and notification parameter types, the on-disk state root, deadlines and signals, plus adapter and session helpers. They now sit in folders named after the concepts in CONCEPTS.md, so a reader finds a type by its domain instead of scanning one flat list. Module paths gain one segment; root re-exports in lib.rs keep every public name unchanged, and adapter becomes a public module because it now holds the public hit_test, live_element and native_handle modules. * refactor(core): group core's tree, action, system and snapshot types by domain The accessibility-tree model, the Action model and its execution types, app and window and process types, snapshot building and the context module were 115 loose root files. They now live in tree/, actions/, system/, snapshot/ and context/, named like the platform crates' layers so a type a platform crate uses from its input/ or system/ lives under the same name in core. The stray actionability tests move into actionability/. Two test files import one module once so the longer paths keep them under 400 lines. * refactor(core): group refs, ref actions, locators and traces into their folders The ref map, ref store, allocation and identity files, the headless ref action flow, the locator with its live evaluator, and the trace writer with its reader were the last loose families at core's root. Each family now lives under the module that already anchored it (refs.rs, ref_action.rs, locator.rs and trace.rs become their folders' mod.rs), so those module paths do not change. trace becomes public because it now holds the public trace_read and trace_sanitize modules. lib.rs is the only file left at the root. commands/ stays flat: its contract tests require commands/<name>.rs. * refactor(macos): group the macOS layers into concept folders Each layer held one flat list: 62 files in tree/, 68 in system/, 45 in actions/. Concepts with four or more sources now have a folder named with the vocabulary all three platform crates share: tree/resolve/ and tree/properties/, actions/chain/ and actions/select/, input/clipboard/, keyboard/ and mouse/, system/app/, process/, window/ and permissions/. cf_type.rs leaves the crate root. The cursor-overlay and permission-guide Objective-C bridges move next to the Rust files that declare them, with build.rs and the native test imports following. File names do not change. * refactor(windows): group the Windows tree layer into concept folders tree/ held 117 files in one folder. Ref re-identification, the Win32 test fixtures, the UIA walkers, the property reads and the hit test now each have a folder (resolve/, fixture/, walk/, properties/, hit_test/), using the same names as the other platform crates. The fixtures' self-rerun filters and the hit-test scan labels follow the moves, and the public tree paths used by the uia_tree_dump example and the resolution probe are updated with them. * refactor(windows): group the Windows system and input layers into concept folders system/ held about 70 loose sources and input/ about 26. Capture, windows, shell surfaces, wait signals, the COM runtime glue, menus and the shared test seams now each have a folder under system/, and clipboard, keyboard and mouse each have one under input/, with the names the other platform crates use. Public names in lib.rs do not change. Four clipboard helpers now sit under the clipboard module's existing cfg(target_os = "windows"); the crate only builds for Windows, so the set of compiled and run code is the same. * refactor(linux): group the Linux tree layer into concept folders tree/ held 209 files in one folder, the most crowded in the repository. It now has thirteen concept folders (walk, resolve, hit_test, menu, surfaces, placement, live_read, observe, properties, element, vocabulary, capabilities, live) and keeps two loose sources. All live rigs and live suites sit in tree/live/, so the live gate selects them by one prefix; its module strings, its self-test and the runbook follow the moves, and the PARALLEL_READS entry for a module that never existed is dropped. * refactor(linux): group the Linux action, input and X11 layers into concept folders actions/ held 95 files and input/ about 57 loose files, and system/x11/ (87 files) and system/window/ (57 files) had grown past one screen. Selection, scrolling, writes and live rigs now have folders under actions/; keyboard, mouse, physical targeting and the clipboard owner have folders under input/; the X11 client splits into capture, control, monitor, stacking and window, and the window operation pipeline gets window/window_op/. system/ keeps the shape it already had. The live gate's module strings follow the moves. * ci: fail the build when platform code leaks or a crate's layout drifts Platform isolation was checked only through cargo tree on CI and one PowerShell scan, so a source-level leak or a file dropped in the wrong layer reached review unnoticed. scripts/check-architecture.sh checks manifests through cargo metadata and sources through a small lexer: core depends only on its listed crates, only the binary and FFI wire platform crates, no platform crate reaches another, core keeps OS-conditional code in an exact allow-list, every crate keeps its top-level layout, capabilities are implemented in their layer, core domains do not reach commands, and tests are declared beside their code. It plants a violation of every rule before it scans, so a rule that stops detecting fails the gate. It runs in the pre-commit hook and next to the source rules in CI; the cargo tree steps stay as the transitive check. * docs: describe the crate layout and the guard that keeps it CLAUDE.md described the platform layers but not where core's files or a layer's sub-concepts go, so the next contributor or agent would guess. It now states core's domain folders, the shared platform layout and subfolder vocabulary, the file-naming and test-placement rules, and the grounding rule that enforces them. * docs: follow the source restructure in path citations The restructure moved test and module files into feature folders, so three code-span paths and a handful of folder-relative citations in the solution docs, CONCEPTS.md and two test READMEs named files that no longer exist. * test: point the e2e error-code contract at the moved error_code.rs error_code.rs moved to crates/core/src/response/ in the core restructure. The e2e harness contract test builds the path from parts, so the literal path rewrite did not see it and two contract tests failed to open the file. * docs: re-point short-form file citations at their moved files Seven citations named a moved file by its path relative to its crate (for example system/shell_surface_raise.rs), a form the full-path rewrite did not match. Each now names the file's new folder. Short citations of files that did not move, such as the Windows actions/ labels, are unchanged. * test(e2e): read written fields directly and run the host ref-churn leg fully hosted The first live run failed five legs on the harness, not the product: batch and ref-churn checks read SwiftUI echo labels that set-value does not promise to update, and the host-client variant enabled the host only for one command and used a non-private temporary directory, so the host refused it with INVALID_ARGS. The legs now read the field's own value, and the whole host run uses the host and the private temp dir. The stale-ref and close-app assertions are unchanged: they expose pre-existing product defects. * test(e2e): skip three known product defects by exact signature and end the churn fixture by pid No failing leg ships, and no real defect is hidden. The scroll, default-CLI stale-ref and close-app cases skip only when their known defect signature appears, naming the defect, file and origin commit; any other result still asserts, so each case re-arms itself once the product is fixed. When close-app leaves the RefChurn fixture running, the leg ends its own process and asserts it is gone. * fix(macos): let close-app end a process that has no LaunchServices launch date Termination refused whenever NSRunningApplication reported no launch date, so close-app could not end an app launched outside LaunchServices, contrary to the rule that absent evidence is not conflicting evidence. The bridge now passes whether a date is present, and only a present date more than five seconds from the verified instance refuses; pid, start-time token and exit checks are unchanged (codex review: only the intended change). * fix(macos): reveal a clipped scroll area through its enclosing scroll view Scroll-into-view looked for the clipping viewport starting at the target, so a target that is itself a scroll area became its own viewport, produced no direction, and stayed clipped (ACTION_FAILED, live offscreen). The original walk still runs unchanged; only when it returns the target itself does the search repeat from the target's parent (codex review: only the intended change). * test(e2e): assert scroll and close-app again now that both defects are fixed The two signature skips would hide a regression of the fixed defects, so the scroll case asserts unconditionally again and a surviving churn fixture after close-app fails the leg while the leg still ends its own process. Only the default-CLI stale-ref skip remains, for the documented aliasing gap. * test(e2e): gate two more known product defects by signature and stop the command host Live, an unknown-app lookup returned TIMEOUT because NSWorkspace listed one pid twice and the app inventory refuses a list with a skipped record; that case and the batch entry using it now accept only that exact signature, naming the defect. The default-CLI sibling check shares the documented aliasing defect. The hosted ref-churn run now stops the command host it started, which otherwise kept the desktop lease for its 15-minute idle life. * fix(e2e): parse only stdout from captured command output The binary prints its JSON envelope on stdout and tracing warnings on stderr. Every capture the suite parses as JSON merged stderr, so one warning line made json.loads fail. A WebKit WebContent process that NSWorkspace lists twice now makes every --app lookup warn about a duplicate pid; AE4 read an empty window list and aborted the suite. All captures now discard stderr, matching find_target, and a harness self-test refuses a capture that merges it again. * chore: move runbooks and residual review notes out of git into the local planning folder They are operator and review working notes, not product docs: no code, test, workflow or doc reads them. They now live with the other planning material in the globally ignored .compound-engineering/ folder. * test: restore two test assertions the repair pass over-tightened and stop echoing a session id CI showed both repairs were wrong, not the product. The Windows dispatch test now accepts any deliberate refusal again (headless double-click is refused with POLICY_DENIED by design), as it did before. The macOS helper timeout test asserts it returns within a second again, which is what its name promises; the replacement assertion held on one machine and tested nothing about blocking. The overlay test message now prints only the field it checks, so it no longer writes a session id into a failure message (CodeQL rust/cleartext-logging). * docs: tighten test-quality learnings and note the dropped accname agreement test * docs: point two measurement learnings at the code and scripts that hold them * docs: correct the Windows hardening claim in the CI-lane learning * docs: point the snapshot window citation at current code and tighten policy learnings * docs: drop a cited regression test that does not exist * docs: tighten accessibility runtime learnings without changing their facts * docs: tighten Numbers and Xcode learning wording * docs: use the measured popover timings in the wait-for-closed learning * docs: tighten Xcode and native learnings; keep the empty-search learning until merged * docs: split one dense sentence in the agent contract * docs: tighten prose in the contributor and output docs * docs: tighten the find-name learning wording * docs: tighten the concepts glossary wording without changing its terms * docs: tighten test README prose and keep every command and link * docs: cut the learnings store to the lessons that are still true and live nowhere else Two reviewers checked every learning (codex Sol proposed, codex Luna validated with the burden of proof on deletion); a learning stayed whenever they disagreed. Removed dated evaluation logs, trial records and lessons already carried by code, tests, CLAUDE.md, CONCEPTS.md or the skills; moved the one unique lesson of five learnings into a surviving one; trimmed the rest to rule, fix and one evidence pointer. The two remote dev-box learnings are gone: they described one maintainer's machines, not the product. 52 files and about 46,700 words become 15 files and about 11,100 words. Citations in the e2e harness and test READMEs now point at the surviving learnings. * docs(windows): stop citing two learnings that were removed The rule each comment cited is stated in the comment itself, so the pointer is dropped rather than re-aimed. * chore: remove tooling and paths tied to one maintainer's dev machines agent-desktop is a product for any team, so the repository no longer carries a remote-box workflow: remote-linux.sh (worktree sync to an SSH alias), box-slot.sh (a lock on that box), session-env.sh (importing that box's desktop session) and their two self-tests are removed, with their fmt-job lines. Private-session sessions now default to ${XDG_RUNTIME_DIR:-/tmp}/agent-desktop-sessions, overridable with AGENT_DESKTOP_SESSION_BASE, at every site that creates, discovers or cleans them (shell wrappers, live gate, e2e runner, Rust live tests); the window manager defaults to the system install. Docs and the Linux probe ledger no longer cite personal paths. * test(e2e-linux): take the stale-ref process baseline after the app is running The leg read the process list before Calculator started, so a correct refusal failed its own 'must not end the app' check. The unit test now models the launched process and fails on the old ordering. * fix(linux-tests): keep each OS user's private sessions under their own base Without XDG_RUNTIME_DIR every user shared /tmp/agent-desktop-sessions, so the second user could not create a session. The fallback is now /tmp/agent-desktop-sessions-<uid>, created 0700, at every site that computes it. * test(macos): restore the screenshot timeout test the repair pass over-tightened The repair also required the killed child to be reaped inside the product's cleanup deadline; on a loaded CI runner the product truthfully reports that it was not, so the assertion tested the runner, not the timeout. * fix(linux-tests): let the live gate merge receipts without creating a session base --merge starts no sessions, so it no longer needs a writable session base; the self-test proves it with a base that cannot be created. * test(linux): make the proc-scan test fail with a diagnosis instead of a bare unwrap It failed once on CI with no evidence. The helper no longer proceeds silently when the child never becomes ready, and a missed scan reports what /proc shows for the child, so the next occurrence names its cause. * fix: harden desktop boundaries and bound native operations * fix: install inspector signal handlers before readiness * test(linux): stop running renamed copies of sleep, which Ubuntu 26.04 no longer supports GitHub's ubuntu-latest now includes Ubuntu 26.04 runners, where coreutils is a transitional package over the uutils multi-call binary. That binary picks its tool from argv[0], so a copy of sleep renamed for a test exits at once and the scan finds a zombie. proc_scan failed on every 26.04 run, on branches without this cleanup too. One shared helper now copies the standalone system shell and blocks it on a held stdin pipe, which keeps the test's name on any Linux; the five tests that renamed sleep use it. * test(linux): keep blocking fixtures alive in the background without stdin A background command in a non-interactive shell gets /dev/null as stdin before any redirection, so the orphan launcher's blocking copy read EOF and exited. The copied shell now loops on 'sleep 1' instead of reading stdin; that keeps it alive under its own name in the foreground and the background alike, and lets both launchers return to their original form. * test: make native fixtures portable across CI hosts * test: use an owned process fixture for Linux discovery * fix: preserve trace readability and clear stale overlays * perf: avoid repeated trace ledger replacements * fix: reject shared helper writes and isolate Linux test fixtures * fix(linux): list every top-level window the registry exposes, whatever its role The window list admitted a root child only for roles 23, 16 and 69, a modal panel or a showing filler. Toolkits give a top-level window the role of its widget, so the GTK3 Save As file chooser (19), a Qt message box (2) and a Qt push button shown as a window (43) were never listed, while the modal gate, which reads every root child, refused focus because of the same dialog (L41-1). A root child that implements Component is now a top-level, and one predicate decides it for listing, frame identity, menu inventory and the shell content window. Dialog-ness is one predicate too: the AT-SPI dialog roles read from libatspi (alert, color chooser, dialog, file chooser, font chooser, L41-4), used by the window list, focus, sheets and nested dialogs. A blank, showing top-level that holds only menus is a popup menu surface (the GTK3 context menu and Chromium menus, L41-2, L18-7): menu observation keeps it and the window list drops it. The size identity for unnamed windows applies to every toolkit without global positions instead of GTK4 role-39 panels only, and the duplicate reports_global_position copy is gone. * fix(linux): attribute a launch handed to a running instance by its new window Launching thunar with the session's daemon running failed with "exited with status 0 without a verified application owner" although a new Thunar window opened (L41-6): the launcher forwards to `Thunar --daemon` and exits, and the attach rule also compares the argument list. After a clean launcher exit with no strict owner, the one running process of the same canonical executable that shows a top-level window absent from its pre-launch baseline now owns the launch, and with --activate that window is reported. Several such processes stay ambiguous, none keeps the failure. Strict argument identity for attaching before a spawn is unchanged. * fix(linux): publish hidden and offscreen in snapshots from the action gate's rule A snapshot gave Mousepad's hidden toolbar button a ref with no state, and the headless click then refused it as offscreen (L41-5): the snapshot projection never emitted hidden or offscreen, while the live projection the gate reads derived both. The snapshot now applies the same visibility functions to the facts its walk already holds (states, extents, frame, scroll container, minimized frame) with no extra reads; an input it lacks stays unknown and adds no token. The sentinel and placed-rectangle tests, copied in four places, now live in one module, readiness recognises both web document roles (L3-13), and the GTK4 popup origin is chosen by one rule for snapshot and live reads. * fix(linux): rebuild the desktop alias cache after an edit inside one timestamp tick The in-place desktop edit test failed 2 of 5 runs on unchanged code: the cache compared inode, length and ctime, and an edit of the same length inside one coarse kernel tick keeps the same ctime, so a stale alias index was served. Like git's racily clean rule, a cached index is now trusted only when every stamp is older than its build time minus a tick margin; otherwise it is rebuilt on the next call. * test(linux): add widget-zoo fixtures and the ref-contract e2e legs The bar for a snapshot is that every ref it gives out lands when its action is performed headless, verified by an independent read, or carries hidden, offscreen or disabled and is refused with no effect. Three fixture windows (GTK3, GTK4, Qt) expose the standard widgets, each logging its own effect, and the ref_contract legs drive every ref of a fresh headless snapshot against the log and raw AT-SPI. The window legs open the fixture's dialog, file chooser, message box and top-level button and require each to be listed, reachable as a sheet when modal, and operable to completion. The harness's own window oracle counts Component root children, the same rule as the product (L41-1). * docs(probes): record top-level, popup, visibility and launch measurements Area L41 records the toolkit census that disproves the role-based window rule of L8-2, the popup shapes that are and are not root children, the closed-dialog husks, the libatspi dialog role numbers, the snapshot and gate disagreement on Mousepad's hidden toolbar, and the Thunar daemon hand-off. The environment table names the XFCE container the new rows were measured in. * refactor(linux): state the unplaced-node test without a double negation * fix(linux): recheck racy desktop files by content instead of rebuilding the alias index Rebuilding whenever a desktop file was newer than the build tick made every lookup right after an edit rebuild, so the cache was never reused (the in-place edit test failed 5 of 5). As in git's racily clean rule, only the files that changed within the tick keep a content hash; equal stamps reuse the index unless one of those files now reads different. The test passes 10 of 10. * fix: avoid unsafe at-spi property and interface reads * test: verify linux refs against addressed raw controls * fix: include display bounds in snapshot visibility * docs: explain thunar file views and literal newlines * docs: keep file-view guidance application independent * fix: observe and resolve showing alert surfaces * fix: restore checks after the Linux cleanup merge * fix: serialize trace emission and repair review gates * refactor: align native security types with module boundaries * fix: select option rows through their advertised semantic verb * fix: avoid sibling scrollbar mutations during reveal * fix: retain unanswered scoped surface reads * fix: activate exported menus outside the accessibility dispatcher * test: select the active chooser filename field * docs: record semantic reliability evidence and headless policy * test: repair independent Linux workflow oracles * fix: preserve menu fallback and stop expired launches * fix: await queued windows and reclaim stale clipboard records * fix: preserve clipboard scan timeout codes * fix: verify combo commits and bound optional snapshot reads * fix: complete linux ref delivery and safe refusal contracts (#307) * fix: complete linux ref delivery and safe refusal contracts * fix: complete Wayland menu and chooser ref contracts * test: report unsupported action setup as unexercised * test: separate concurrent overlay fixture sockets * test: enforce snapshot promises in linux ref contracts (#308) * test: align harness checks with retained native refs * fix: prevent private desktop data from leaking through diagnostics * fix(ci): reflect successful writes in the JEV fixture | 14 days ago | |
feat: integrate Linux support with privacy hardening (#311) * chore: remove three uncalled lab scripts No workflow, hook, script or test calls them: a symlink helper for local skills, a root-only uinput stub whose legs were never written, and an unrun self-test of the npm wrapper smoke, which CI runs directly. * docs: remove stale evaluation records and unreferenced diagrams The architecture diagrams and the Slack token illustration were not linked from anywhere and showed Linux as planned. The acceptance audit was a withdrawn status record. Two solution docs were folded into the docs that already carried their context, so each lesson has one home. Validated in the docs removal ledger (docs-3, 6, 8, 11, 12, 13). * docs: correct solution docs that contradict the code Many learnings named moved files, deleted tests, stale function signatures, or rules the code no longer follows (bare refs accepted, native apps never activated, one box lock instead of two slots). Each now cites the code that holds today, and none cites the planning docs or the untracked dogfood reports. * docs: correct CONCEPTS.md against the shipped adapters Several definitions described one platform as if it were every platform, missed the Linux adapter, cited probe rows that measure something else, used a screenshot flag that does not exist, and called dogfood reports committed. Session Manifest folds into Session and the must-catch fixture into Gate Self-Test, where each was already half defined. * docs: correct contributor docs, the FAQ, the JSON contract and runbooks CONTRIBUTING.md now points to CLAUDE.md for the engineering rules instead of keeping a second copy that had drifted (a MockAdapter that does not exist, an E2E command that exits without the exclusive-desktop variable). The JSON contract described an unknown disposition the code no longer emits, the FAQ repeated the README platform table, and the Windows runner runbook cited plan ids and a trigger branch that merged. * ci: assert the release-ffi unwind profile once per release ffi-release-panic-guard already runs the identical grep and publish-github needs it, so the copy in ffi-release-gates added nothing. * chore: let release-please bump the Linux skill version skills/agent-desktop-linux/SKILL.md carries the x-release-please-version marker and is published with the other skills, but was missing from extra-files, so its version would have stayed behind. * docs: make the test harness READMEs runnable as written Several harness commands failed as documented (the Linux consumer run without AGENT_DESKTOP_WM_ROOT, the discovery inversion without a header path, a headless probe binary that swiftc names differently) and the fixtures README listed files that do not exist. The Linux E2E guide drops plan history and keeps what an operator needs to run and read it. * Revert "chore: stop shipping the jev-desktop skill" This reverts commit 34826e104af928a345dbdfeaa83a9a9b72745fb0. * docs: fold the ref-root viewport lesson into the clipping doc The shared clipping doc already states the same incident and the repair that superseded it, so the lesson keeps one home. Validated in the docs removal ledger (docs-14). * docs: say jev-desktop needs a repository clone, and match it to its scripts The skill teaches node scripts/jev/*.mjs, which ship in no installed package, so it now says up front that it needs a clone of the repository and a TYPESAFE_API_KEY. Checked against the scripts: run.mjs retries 429/503/529 twice while act.mjs does not, --bin and --execute belong to act.mjs only, act.mjs sends longer values and the ancestor path, why is null on act, the stop list and exit codes were incomplete, and JEV_VERIFY_MS was missing. * docs: correct FFI handle, wait and last-error text against the header ad_free_handle works after the adapter is destroyed (native_handle.rs tests prove it), so the rule is to free handles before destroying the adapter only for use, not for release. The wait bullet now uses the header's AdWaitMode field names, AdActionStep lists mechanism and verified, the delivery-semantics accessor copies values instead of lending a pointer, and the JSON-string table gains ad_execute_by_ref_timeout, ad_trace_show and ad_trace_export. * ci: cut duplicate jobs and steps from the CI workflow Each removed piece repeated a check that another job already runs on the same OS, or depended on a file that is leaving the repository: - platform-check: three legs were subsets of clippy --all-targets on the same runner; the windows-11-arm leg is now a clippy step in test-windows-arm. - ffi-header-drift, ffi-python-smoke and ffi-passthrough now run as conditional steps on the ffi-panic-guard legs that used the same OS, so the header drift gate and the stub checks keep running on every PR. - check-release-consistency.sh and check-npm-package.js run in the supply-chain workflow on every PR, push and weekly, and need no OS. - the ubuntu bash3 compatibility run was weaker than the macOS Bash 3.2 run. - trash and trash-cli installs had no caller left. - check-phases-ledger-citations.ps1 read docs/phases.md, which leaves the repository, and would fail the Windows lane. The viewer_filter.js test moves here from the inspector workflow, so the shipped viewer code keeps a CI test. * chore: remove the unshipped inspector web UI tools/inspector is a local developer viewer that no shipped artifact, skill or release step uses, and its workflow only tested it. The viewer_filter.js test it also ran now lives in the CI workflow. * ci: give the release dry run a concurrency group and state why it stands alone The reason it is a separate read-only workflow lived only in docs/phases.md, which leaves the repository. A concurrency group stops a busy PR from queueing many full rehearsals, and trash-cli had no caller left. * ci: run the Windows capability probes only when probe files change The probes measure the hosted runner. The one contract they check against crate code, secure-value withholding, is also a plain unit test in the Windows CI lane, so code-only PRs no longer pay for a 30-minute probe run. * docs: document the jev scripts beside the code The jev-desktop skill left the shipped skill set, so its usage notes move next to the scripts they describe, checked against the current flags and environment variables. * chore: remove an unreferenced benchmark screenshot No file links to it. * docs: fix three FFI ownership and panic claims a review disproved A codex review proved three overstatements against source: ad_abi_version returns a constant with no panic trap, so only entrypoints that can panic catch_unwind; last-error strings are borrowed and never freed, so not every returned pointer has a free function; and ad_free_handle validates the adapter before it accepts a null handle. Remaining em-dashes in the FFI references became plain punctuation. * refactor(core): drop two helpers that nothing calls RefStore::for_tests and ErrorPayload::with_recovery had no caller in any crate or test. They sit in public modules, so rustc could not flag them. Recovery hints are still attached by recovery_for_code. Ledger rows A-01, A-02. * test(ffi): drop an unused re-export from the shared test module No ffi integration test names AdIdentifierKind through the common module; the file-level allow(unused_imports) hid it. Ledger row A-08. * ci: keep the 20-minute FFI timeout on legs that only run the panic probe Only the aarch64-apple-darwin leg gained the header drift and stub smoke steps, so only it needs the longer bound. * test(linux): drop live probes that only print timings semantic_click_latency_probe and the vocabulary report, timed pass and label timings asserted nothing and no gate reads their output. The vocabulary live test keeps every assertion path. Ledger rows A-12, A-13. * refactor(linux): drop stale dead_code allows on display errors DisplayCause, as_str and display_cause_error all have production callers, so the allows only hid any future dead code. Ledger row A-15. * refactor(windows): compile the canonical lock path only for tests Only the lease tests call canonical_lock_path, so a cfg(test) gate replaces the allow(dead_code) and the doc no longer promises a future caller. Ledger row A-16. * docs: cut the README to 130 lines and move install detail to docs/install.md The README keeps the pitch, install, the observe-act loop, the agent section, the command table and a plain platform table. Install-script allowlisting, attestation, the source build, per-OS permissions and platform footnotes move to docs/install.md unchanged. * docs: record the parameterized-replacement findings beside their probes The payload shape, the background no-op and the input-context route are what a maintainer needs when running replace_range_probe.swift, so they belong in the harness README rather than in a one-trial log. * docs: state what the jev scripts actually send and restore --no-values does not withhold text passed with --text, the clipboard is only put back when it held text, and a run reads the screen more than once per turn. The README said otherwise. * chore: stop naming a removed workflow in the redaction gate comments windows-e2e.yml no longer exists; the -Path override is described by what it does. * docs: note parameterized attributes in the real-app probe guide A setter that does nothing does not prove an editor has no write path; the probe lists parameterized operations such as AXReplaceRangeWithText, and the replacement range is in UTF-16 units. * docs: remove the Numbers replacement trial log now its findings live with the probe The real-app README carries the payload shape, the background no-op, the input-context route and the inventory lesson. Validated in the docs removal ledger (docs-15, codex and opencode both REMOVE). * chore: make infra comments match the repository The gitignore comment said no normalized twin is committed, the Windows e2e runner said no CI workflow touches it, one gate comment named a function that is being removed, and another described docs/phases.md as in the repository. * docs: make the PR template gate commands run as written The E2E line exited 2 without AGENT_DESKTOP_E2E_EXCLUSIVE=1, a string HOST_PKGS reaches cargo as one argument in zsh, and the skill checklist left out the Linux skill. * fix(npm): point the postinstall skill hint at commands that exist claude mcp add-skill is not a Claude Code subcommand. The hint now names the binary's own skills command and the skills installer the README documents. * docs: drop the jev README now that the jev-desktop skill documents the scripts Two descriptions of the same scripts would drift; the skill is the one that ships. * docs: shape the README for users, contributors and agents The fast path stays on top (install, a quick start, the agent section), followed by reference sections and a documentation map that points each reader to the right file. It restores facts the shorter version dropped: headless-by-default delivery, PowerShell ref quoting, the readable JSON sample, the accessibility-tree row and the per-OS package sets. * docs: name the CI job that now runs the FFI Python smoke The ffi-python-smoke job was folded into the macOS leg of the ffi-panic-guard job, so two docs pointed at a job that no longer runs. * refactor(core): share empty_trace_dir_error between trace show and export The two copies were byte-identical, so one pub(crate) helper in trace_read now serves both calls. Ledger row B-07. * ci: give the Linux live e2e job an explicit read-only token The job inherited an empty permission set, unlike its macOS twin; checkout needs contents: read and nothing more. * ci: pin the probe workflow's checkout to the same release as every other workflow It was the only workflow left on v7.0.0. * docs(core): make two rustdocs match the code they describe transform_tree prunes on is_ref_able, not on role alone, and the drill-down completeness note belongs on run_from_ref_with_context, not on count_nodes. * docs: state the jev read cost for run.mjs as well as act.mjs lead-infra's review found the skill gave the snapshot cost for act.mjs only. run.mjs reads the screen before and after each action (none after DRILL or WIDEN), and both scripts add a surface snapshot when an overlay is open and no region is pinned (desktop.mjs:62-76, act.mjs:236-237). The other five corrections already held in the merged text. * fix(cli): list linux among the skills get aliases skills get linux already resolves, but the help text left it out. * test(visual-debug): stop naming the removed inspector in a test env var The capture child marker now says what it marks, since tools/inspector is gone. * refactor: replace per-crate ensure_budget copies with Deadline::ensure_remaining Twelve private macOS helpers, two private Linux helpers, and the shared Linux bus and Windows permissions helpers all had the same body. One Deadline method now carries it, with unit tests. The DeliveryTracker variants, the mouse and chain variants, and Linux input/deliver.rs keep their own checks because they add delivery details. Ledger row B-01. * docs: restore rules and definitions an earlier cut dropped CLAUDE.md again states the @generated exemption, that every non-doc comment form is rejected while //! module docs are allowed, and that expect and panic are test-only like unwrap. CONCEPTS.md again says a ref-addressed drag picks up at the bounds center (and the slider workaround) and names the must-catch and must-pass fixture halves. * docs: restore two oracle contracts to the Linux E2E guide The rewrite dropped what the pointer delivery legs read back and what the dead-owner recovery leg requires, both of which an operator needs to judge a failing leg. * chore: keep the gitignore note on which capture twins are tracked Per-area probe directories commit their normalized twins; only the twins under probes/<platform>/captures/ are regenerated and untracked. * docs: say in Coordinate Fallback that raw --xy input focuses nothing A raw coordinate resolves with no ref and no window identity, so the caller owns where it lands; the fallback entry now states it directly. * refactor(windows): drop file-level dead_code allows in system modules Windows clippy with -D warnings passes with all twelve removed, so every item in those files is used and the allows only hid future dead code. The menu source agreement test no longer cites the allow. Ledger row A-22. * refactor(windows): parse w-<n> window ids in one place observe and surfaces carried byte-identical parsers with the same InvalidArgs message; window_ops now owns parse_window_id next to window_id. Ledger row B-08. * refactor(linux): share the already-satisfied pointer select result The combo and row select paths built the same skipped XTEST.click result with their own copy of the route constant; select_pointer now holds one. Ledger row B-11c. * refactor(linux): share clipboard delivery helpers between X11 and Wayland set_clear and wayland carried the same verify grace, fresh deadline, and not-delivered and unverified error builders; delivery.rs now holds one copy and messages stay with the callers. Ledger row B-11d. * refactor: build stale-evidence errors with AdapterError::stale_ref_because Three adapter copies of stale_evidence_error repeated the body of the existing core constructor, so the resolvers now call it directly. A core unit test pins its code, suggestion and disposition. Ledger row B-02. * refactor: move the live-evidence completeness helpers into core incomplete_live_evidence, essential_live_evidence_complete and known_actions were byte-identical across the adapters and work only on core locator types; live_element now owns them, with unit tests. Ledger row B-05. * refactor: move notification identity verification into core The macOS and Windows checks were byte-identical over core notification types, so core now exports verify_notification_identity with unit tests. Linux keeps its own check because its suggestion and disposition differ. Ledger row B-06. * chore: point the stale-ref gate at the constructor callers now use stale_evidence_error is gone; evidence-refused refs now build their error with AdapterError::stale_ref_because, so the comment and the failure hint name that. * test(e2e-linux): drop plan requirement ids from leg descriptions R9, R19, R22, R24, R32, R33 and R35 named requirements in a plan that has left the repository, so the prefixes meant nothing to a reader. Nothing parses the requirement text; leg ids are unchanged. * chore: stop the privacy scan comment from claiming the repo ships planning docs * test(e2e-linux): drop duplicate legs and dead harness helpers Six legs repeated the observation of a stronger surviving leg on the same tiers, two unit tests restated another test's assertions, and several helpers had no caller. Each row was validated by two independent models (ledger TR-145..175). * test(e2e-windows): drop a racing zero-timeout leg and dead fixture code The delayed-enable zero-timeout leg raced the fixture timer while the permanently disabled leg proves the same no-wait refusal; the click row's mechanism key is now asserted instead of ignored, and an unused fixture overload and an always-true self-test line are gone (ledger TR-149, TR-151, TR-152, TR-186). * test(e2e): fold the absolute-timeout exit code into the deadline test The removed test repeated the descendant-deadline test and added only the 124 exit code, which the survivor now asserts (ledger TR-185). * ci: run the four hermetic scripts self-tests in the fmt job perf_ab_probe_test.py, test-linux-live-gate.py, test-session-env.sh and test-remote-linux.sh guard the perf report math, live-gate shard and receipt rules, desktop environment selection, and the remote kill guard, yet no job ran them; each is stub-driven, needs no display or network, and takes about a second. The two that start private sessions stay manual (one already runs in linux-e2e). * test: make every self-rerun test prove its child ran the target test A test that re-runs its own binary with a libtest filter passed with zero tests run when the filter was wrong, which already hid the Linux cursor overlay check (its filter missed the overlay module). Each self-rerun site now asserts the child printed "1 passed; 0 failed"; the overlay filter is corrected, and the interactive clipboard test compiles again under its feature. * test(e2e-linux): drop duplicate contract self-tests and dead test helpers Registry and gate checks were asserted twice; the surviving registry test now also refuses an empty registry so it cannot pass vacuously (ledger TR-177..183). * test(core): drop tests that restate a stronger test or only exercise a mock Each removed test was matched by a surviving test at the same or a stronger boundary, or asserted only a mock's or a constant's own value; two merges keep the unique assertions (role round trip, toggleable negatives) in the survivors (ledger TR-001..019). * test(macos): drop duplicate and tautological tests, fold unique assertions into survivors Survivors now carry the sibling-radio suggestion, the AXOpen stale classification and the native plus DOM identifier pair (ledger TR-058..074). * test: drop CLI and conformance tests that only exercise their own mock The conformance window-identity tests asserted the mock adapter's behavior, and the version and wait-metadata assertions repeated stronger tests (ledger TR-142, TR-143, TR-161..163). * test(ffi): drop duplicate C ABI tests and keep their unique checks in survivors Typed discriminant round trips, out-of-range values, a second trapped panic and the last-error pointer across successful calls now live in the surviving tests (ledger TR-035..044). * test(windows): drop duplicate dispatch, notification and state tests The adapter's inline tests were byte-identical to adapter_tests.rs; the R2 advertisement tests now live as rung call counts and isolated advertisement cases in the action and tree tests (ledger TR-073..091). * refactor(linux): import test-only step types in the select tests, not production select_row.rs and select_pointer_combo.rs carried cfg(test) imports only for their tests' use super::*; the tests now import ActionStep and StepMechanism. * test(e2e): add error-envelope and batch failure legs to the macOS harness Agents branch on exit code and error.code for a bare @e1, an unknown app, a missing surface and a parse error, and on batch's continue versus stop-on-error results; no live leg proved them. Self-tests check every expected code exists in the core enum and that each leg asserts code and exit together. * test(e2e-linux): add error-envelope and batch failure legs Two registered legs drive the real binary on both X11 tiers: recoverable error envelopes (INVALID_ARGS for a bare @e1, APP_NOT_FOUND with exit 1) and batch per-entry failure with and without --stop-on-error, observed on the app itself. Pure verdict tests with lying fakes keep them checkable without a desktop. * test(e2e-windows): add error-envelope and batch failure legs Eight Reliability legs cover INVALID_ARGS, SNAPSHOT_NOT_FOUND, STALE_REF for an absent element, APP_NOT_FOUND and ELEMENT_NOT_FOUND with exit codes, and batch halt versus continue with per-entry codes. Not yet run: they need a Windows host (contract -SelfTest, the U7 self-tests, then the live suite). * refactor(core): route deadline clock reads and poll sleeps through one test-freezable seam Several core tests flaked on loaded CI runners because their deadlines ran on the real clock. Deadline now reads time and sleeps through crate-private now() and sleep(), which outside cfg(test) are the same std calls; tests can freeze and advance a thread-local virtual clock. Public API and production behavior are unchanged (codex review: no behavior change). * test(macos): replace the inventory retry wall-clock assertion with counted attempts The test asserted more than one attempt inside a real budget, failed on 6 branches when a slow runner fit only one, and 45b28ce9 only raised the budget to 500 ms. The retry and the partial-inventory refusal are now proven by attempt counts on a fake capture that never consumes its deadline. * fix(windows): confirm a target's exit on its process handle before reporting a menu-wait read error A window that stops pumping during teardown reads as APP_UNRESPONSIVE before its process object is signaled, so the single liveness re-check lost the race and the wait reported the transient code instead of STALE_REF (failed on 7 branches). The wait now blocks on the process handle for at most the remaining deadline, capped at one second, before it keeps the read error. First run on Windows CI. * test(linux): drop duplicate input, close, tray and tie-break tests Each removed test repeated a survivor at the same boundary; the unique recovery text, baseline counts and tie cases moved into the survivors (ledger TR-046..057). * test(e2e): add stale-ref and key-combo legs to the macOS harness The removed-element leg accepted four error codes; it now requires STALE_REF with a safe retry. A RefChurn leg proves STALE_REF and AMBIGUOUS_TARGET after an in-place UI swap, and a press leg proves combos by reading the field itself. * test(e2e-linux): add stale-ref-after-UI-change and key-combo legs The stale leg refreshes nothing between a real menu change and the click and requires STALE_REF with its recovery; the press leg checks ctrl+a, backspace and return by AT-SPI text, not by the command's own ok. * test(e2e-windows): add stale-ref contract and key-combo legs The removable row now reports clicks, so a stale ref proven by refusal also proves nothing was delivered; press legs cover ctrl+a, shift+tab, return and the blocked alt+f4 refusal. Not yet run: needs a Windows host. * docs: point the live-gate runbook at the focus leg that stays The lift leg duplicated lifecycle.focus_window_active_reads and never read the restoration it named, so the tests cleanup removes it; the retained live owner is the active-window read. * test(core): run the settle-loop and artifact-lock budget tests on the virtual clock These tests failed on loaded Windows and macOS runners because a real 50 ms lock wait or settle window expired before the code under test ran; a 16 x 20 ms retry wrapper hid it. They now freeze the virtual clock, so lock polls and settle sleeps advance time deterministically, and the retry wrapper is gone. * fix(windows): treat E_UNEXPECTED on a UIA read as a transient failure A provider tearing down a window or shell host answers one read with E_UNEXPECTED; it was classified terminal and narrowed to a non-retryable APP_UNRESPONSIVE, so one read ended a whole wait (failed on 4 branches). It is now a retryable unresponsive read inside the caller's existing deadline; E_FAIL stays terminal. First run on Windows CI. * test(linux): make vacuous assertions able to fail Repaired tests named a behavior their assertions could not detect (a negative check that any value passed, a banned-name list with dead entries, an unverified result asserted only as not verified); each now checks the stated contract. * test(macos): make vacuous assertions able to fail, each proven by a production mutation Process-group kill, semantic-only disclosure chains, single capture, strategy fall-through, scan tolerance, endpoint naming, overlay acknowledgement, the select depth cap and the name-plan slots now fail when the behavior breaks. * test(ffi): make C ABI tests assert what their names promise Modifier cap, JSON error forwarding, invalid discriminants, envelope fields, dirty out-params on a worker thread, wait error envelopes and the session adapter's fail-closed path each had an assertion that could not fail; every repair was proven by breaking the production path once. * test(e2e): make harness self-tests fail for the regressions they name * test(e2e-linux): make harness self-tests and a menu leg assert structured results The menu timeout leg now checks the structured wait_timeout details, and the oracle, runner, session-sweep, workflow and verdict self-tests assert the specific failure each one names. * test(e2e-windows): replace diagnostic scaffolding and soft guards with real assertions The headed double-click leg used debug output, a fixed sleep and one read; it now uses Assert-Effect like its siblings. The handle-set and overlay controls no longer skip silently, and the Chromium rate leg fails when every sample is stale. Not yet run: needs a Windows host. * test(core): drop find, get, wait, session and trace tests that repeat stronger tests The removed find tests ran a cfg(test)-only snapshot collector production never uses; that collector's count helper went with them. Merges keep the observed-role negative, the null-bounds case, the conflicting wait flags as a table and the identifier preference in the survivors (ledger TR-020..034). * test(windows): drop input fake-sink, policy, clipboard and surface tests that repeat survivors The keyboard and mouse send tests only checked that the test fake stored what it was given; the clipboard, chain and surface merges keep their unique cases as table rows or assertions in the survivors (ledger TR-092..106). * test(windows): drop tree property, hit-test and resolve tests that repeat survivors Each removed test had an identical or stronger twin in the same module family (ledger TR-124..140). * test(linux): make clipboard, keyboard-frame, remap, capture and close tests assert their contracts The remap test now proves the deadline cancelled after the remap and that the keymap was restored, the off-screen capture test places its window instead of skipping, and the close gate is checked for both graceful and forced closes. * test: make CLI dispatch, overlay and real-app tests assert what they claim Real-app tests now fail when their precondition is missing instead of passing silently, the overlay show test pins POLICY_DENIED, and the conformance test checks each capability's outcome, not only that the call returned. * test(real-apps): assert the Numbers failure stop and drop a dead plan citation * test(macos): make capture-scope, screenshot and child-label tests assert their contracts The capture-scope tests now run the production scope filter instead of a test copy of it (the single-pid case is kept beside the pid-set case), the screenshot timeout checks the child is gone, and the child-label rule is checked for every role it names. * test(core): order concurrent snapshot writers instead of retrying lock timeouts, and pin tray ids Eight writers polled one lock with no fairness and a 30 s retry hid starvation that failed CI on four branches; the writers now take turns, and a single-thread test on the frozen clock proves a held store lock still blocks a writer. A new test pins that allocation keeps a RuntimeId identifier and its kind, which no test covered after the tray identity change. * test(windows): drop lifecycle, launch, HRESULT and signal tests that repeat survivors Merges move the exit-code serialization cases into core's process-state tests and the HRESULT detail strings and generation checks into the surviving Windows tests (ledger TR-108..121). * test(windows): drop two duplicate tree tests and make hit-test and resolve tests assert their contracts hit_test now judges a probe through judge_probe, so a probe miss is tested on the production path (same behavior: a miss is Unknown, a hit is judged); the fixture teardown, minimized-window, anchor and timeout tests assert what they name (ledger TR-122, TR-123, TF-144..157). * test(linux): make capture-cap, tie, key-owner, menu and tray tests assert their contracts * test(core): run live-locator and ref-action wait tests on the virtual clock These tests had their budgets widened and an exact attempt count loosened to 'at least one' to survive slow runners; on the frozen clock the retry loop's own sleeps advance time, so the original exact counts and small budgets hold. * test(windows): drop the setup-time stopwatch from the headless notification wait refusal The elapsed < 2 s assertion measured COM and adapter setup and failed twice on loaded runners; POLICY_DENIED is not retryable, so a burned deadline would already surface as TIMEOUT and fail the code assertion. * test(windows): decide modern-capture frame-wait expiry inside the wait, not by wall time The fallback test asserted TIMEOUT against an 80 ms budget that real capture setup could consume first (failing as INTERNAL); the cfg(test) frame hold now starts its budget inside the frame wait, and the pattern test waits for a frame showing the fixture instead of sleeping 500 ms. Production frame wait unchanged. * test(windows): signal the menu fixture's open state from real menu mode The fixture announced the popup before TrackPopupMenu entered menu mode and ignored its result, so the chromium composition test compared two live reads that could straddle the flip; readiness now comes from menu mode itself and the comparison waits until both sources agree. * test(linux): make tray, signal, settle, walker and shell-owner tests assert their contracts The error-code boundary test now enumerates codes exhaustively at compile time instead of a hand-typed list of 16, and the remaining repairs check the behavior their names state. * test(macos): replace real-clock budgets and stopwatches with counted attempts and events Stabilize-loop tests could expire before their first attempt on a slow runner; they now use budgets the fakes never consume and assert exact attempt counts, and the overlay acknowledgement tests block on a release channel instead of a 700 ms sleep. * test: isolate command-host sockets per test and assert batch deadlines by value Fixed socket and symlink names keyed by pid were never removed, so pid reuse could fail a later run; each test now owns a removed-on-drop directory. Batch deadline tests assert the budget the adapter received instead of wall time. * test(ffi): isolate the round-trip test's state root and require the snapshot log event The round-trip test set HOME under its own lock but left AGENT_DESKTOP_HOME alone, so a developer's state root leaked in; it now uses the shared isolated home helper. Log-callback tests no longer fall back to the first delivery. * test(core): make actionability, trace, find and close tests assert their contracts Each repaired test was proven by breaking its production path once. The benchmark example's rect test moved into the library's rect tests so it runs in CI, and the find roles hint is now tested through execute instead of a cfg(test)-only copy, which is deleted. * test(windows): make dispatch, envelope, scroll and fixture tests assert their contracts Permissive Err arms, restated literals and a self-comparing fixture expectation are replaced by assertions on the delivered outcome. * test(linux): make tree walker, projection and placement tests assert their contracts * test(core): make session, overlay-routing, hover and pointer tests assert their contracts Session precedence tests pass the env value directly instead of mutating the process environment, gc is actually exercised on an unreadable manifest, the skeleton golden compares the whole tree, and hover counts receiver checks instead of timing them. * test(windows): make drag, capture, HRESULT, signal and wait tests assert their contracts Assertions true by construction (an intersection member found in its own set, an assert_ne after an exact assert_eq, a uniqueness loop over a test-local list) are replaced or removed, and the remaining checks name the behavior they guard. * test(linux): make resolve tests assert the unread-region refusal they name * test(core): drop the null ref_id from the skeleton golden fixture The skeleton golden test now compares the whole serialized tree, and the serializer omits a missing ref_id, so the fixture must not carry one. * test(core): make ref, refs, trace-read and policy tests assert their contracts The XSS gate now asserts the exact escaped payload, the oversize-refmap guard is exercised through the production writer, poll and wait tests count resolves instead of timing them, and a new test pins identifier kinds through RefEntry identity. Each repair was proven by breaking its production path once. * test(windows): make lifecycle, overlay spawn and shell-surface tests assert their contracts A tautological ambiguous-target shape check now serializes the production value, a self-comparing error-code helper is gone, and the taskbar test no longer returns early on an empty tree. * test(npm): drop duplicate platform and wrapper tests and check the real staging directory The staging-cleanup tests read the destination, which installArchive never stages in, so a cleanup regression still passed; they now compare the package bin directory before and after. Duplicate platform-table and wrapper tests are gone, with their unique helper-name and Trash-warning checks kept in the survivors (ledger TR-189, TR-191..193, TF-187, TF-188). * ci: run the Numbers sheet-script self-test in the fmt job numbers_sheets_test.py drives numbers-sheets.sh against a fake binary in a temporary home and proves its failure stop; it is hermetic and nothing ran it. * test: nest extra test modules so moved anchors keep room under the line cap ref_action.rs (400 lines) and the Windows tree/resolve.rs (398 lines) become their folders' mod.rs in the restructure and must then declare their new child modules. Their extra test modules are declared from the files' tests modules instead, so both anchors stay under 400 lines. The nested test files import their parent by absolute path, keeping what `super::` meant before. The Linux action scan walks src/actions recursively, so the banned-call scan keeps reading the action files that move into select/, scroll/ and write/, and it asserts files from each of those groups are still scanned. * refactor(cli): give the command host and diagnostics their own folders Nine command_host*.rs files and three diagnostic*.rs files sat loose in src/, so the binary's root read as a flat list. Each family now lives in a folder whose mod.rs is the former entry file; the helpers were already #[path] children, so no module path changes. * refactor(core): group core's leaf types into response, input, notifications, storage and wait Sixty-seven of core's 270 root files are the envelope and error model, the input and notification parameter types, the on-disk state root, deadlines and signals, plus adapter and session helpers. They now sit in folders named after the concepts in CONCEPTS.md, so a reader finds a type by its domain instead of scanning one flat list. Module paths gain one segment; root re-exports in lib.rs keep every public name unchanged, and adapter becomes a public module because it now holds the public hit_test, live_element and native_handle modules. * refactor(core): group core's tree, action, system and snapshot types by domain The accessibility-tree model, the Action model and its execution types, app and window and process types, snapshot building and the context module were 115 loose root files. They now live in tree/, actions/, system/, snapshot/ and context/, named like the platform crates' layers so a type a platform crate uses from its input/ or system/ lives under the same name in core. The stray actionability tests move into actionability/. Two test files import one module once so the longer paths keep them under 400 lines. * refactor(core): group refs, ref actions, locators and traces into their folders The ref map, ref store, allocation and identity files, the headless ref action flow, the locator with its live evaluator, and the trace writer with its reader were the last loose families at core's root. Each family now lives under the module that already anchored it (refs.rs, ref_action.rs, locator.rs and trace.rs become their folders' mod.rs), so those module paths do not change. trace becomes public because it now holds the public trace_read and trace_sanitize modules. lib.rs is the only file left at the root. commands/ stays flat: its contract tests require commands/<name>.rs. * refactor(macos): group the macOS layers into concept folders Each layer held one flat list: 62 files in tree/, 68 in system/, 45 in actions/. Concepts with four or more sources now have a folder named with the vocabulary all three platform crates share: tree/resolve/ and tree/properties/, actions/chain/ and actions/select/, input/clipboard/, keyboard/ and mouse/, system/app/, process/, window/ and permissions/. cf_type.rs leaves the crate root. The cursor-overlay and permission-guide Objective-C bridges move next to the Rust files that declare them, with build.rs and the native test imports following. File names do not change. * refactor(windows): group the Windows tree layer into concept folders tree/ held 117 files in one folder. Ref re-identification, the Win32 test fixtures, the UIA walkers, the property reads and the hit test now each have a folder (resolve/, fixture/, walk/, properties/, hit_test/), using the same names as the other platform crates. The fixtures' self-rerun filters and the hit-test scan labels follow the moves, and the public tree paths used by the uia_tree_dump example and the resolution probe are updated with them. * refactor(windows): group the Windows system and input layers into concept folders system/ held about 70 loose sources and input/ about 26. Capture, windows, shell surfaces, wait signals, the COM runtime glue, menus and the shared test seams now each have a folder under system/, and clipboard, keyboard and mouse each have one under input/, with the names the other platform crates use. Public names in lib.rs do not change. Four clipboard helpers now sit under the clipboard module's existing cfg(target_os = "windows"); the crate only builds for Windows, so the set of compiled and run code is the same. * refactor(linux): group the Linux tree layer into concept folders tree/ held 209 files in one folder, the most crowded in the repository. It now has thirteen concept folders (walk, resolve, hit_test, menu, surfaces, placement, live_read, observe, properties, element, vocabulary, capabilities, live) and keeps two loose sources. All live rigs and live suites sit in tree/live/, so the live gate selects them by one prefix; its module strings, its self-test and the runbook follow the moves, and the PARALLEL_READS entry for a module that never existed is dropped. * refactor(linux): group the Linux action, input and X11 layers into concept folders actions/ held 95 files and input/ about 57 loose files, and system/x11/ (87 files) and system/window/ (57 files) had grown past one screen. Selection, scrolling, writes and live rigs now have folders under actions/; keyboard, mouse, physical targeting and the clipboard owner have folders under input/; the X11 client splits into capture, control, monitor, stacking and window, and the window operation pipeline gets window/window_op/. system/ keeps the shape it already had. The live gate's module strings follow the moves. * ci: fail the build when platform code leaks or a crate's layout drifts Platform isolation was checked only through cargo tree on CI and one PowerShell scan, so a source-level leak or a file dropped in the wrong layer reached review unnoticed. scripts/check-architecture.sh checks manifests through cargo metadata and sources through a small lexer: core depends only on its listed crates, only the binary and FFI wire platform crates, no platform crate reaches another, core keeps OS-conditional code in an exact allow-list, every crate keeps its top-level layout, capabilities are implemented in their layer, core domains do not reach commands, and tests are declared beside their code. It plants a violation of every rule before it scans, so a rule that stops detecting fails the gate. It runs in the pre-commit hook and next to the source rules in CI; the cargo tree steps stay as the transitive check. * docs: describe the crate layout and the guard that keeps it CLAUDE.md described the platform layers but not where core's files or a layer's sub-concepts go, so the next contributor or agent would guess. It now states core's domain folders, the shared platform layout and subfolder vocabulary, the file-naming and test-placement rules, and the grounding rule that enforces them. * docs: follow the source restructure in path citations The restructure moved test and module files into feature folders, so three code-span paths and a handful of folder-relative citations in the solution docs, CONCEPTS.md and two test READMEs named files that no longer exist. * test: point the e2e error-code contract at the moved error_code.rs error_code.rs moved to crates/core/src/response/ in the core restructure. The e2e harness contract test builds the path from parts, so the literal path rewrite did not see it and two contract tests failed to open the file. * docs: re-point short-form file citations at their moved files Seven citations named a moved file by its path relative to its crate (for example system/shell_surface_raise.rs), a form the full-path rewrite did not match. Each now names the file's new folder. Short citations of files that did not move, such as the Windows actions/ labels, are unchanged. * test(e2e): read written fields directly and run the host ref-churn leg fully hosted The first live run failed five legs on the harness, not the product: batch and ref-churn checks read SwiftUI echo labels that set-value does not promise to update, and the host-client variant enabled the host only for one command and used a non-private temporary directory, so the host refused it with INVALID_ARGS. The legs now read the field's own value, and the whole host run uses the host and the private temp dir. The stale-ref and close-app assertions are unchanged: they expose pre-existing product defects. * test(e2e): skip three known product defects by exact signature and end the churn fixture by pid No failing leg ships, and no real defect is hidden. The scroll, default-CLI stale-ref and close-app cases skip only when their known defect signature appears, naming the defect, file and origin commit; any other result still asserts, so each case re-arms itself once the product is fixed. When close-app leaves the RefChurn fixture running, the leg ends its own process and asserts it is gone. * fix(macos): let close-app end a process that has no LaunchServices launch date Termination refused whenever NSRunningApplication reported no launch date, so close-app could not end an app launched outside LaunchServices, contrary to the rule that absent evidence is not conflicting evidence. The bridge now passes whether a date is present, and only a present date more than five seconds from the verified instance refuses; pid, start-time token and exit checks are unchanged (codex review: only the intended change). * fix(macos): reveal a clipped scroll area through its enclosing scroll view Scroll-into-view looked for the clipping viewport starting at the target, so a target that is itself a scroll area became its own viewport, produced no direction, and stayed clipped (ACTION_FAILED, live offscreen). The original walk still runs unchanged; only when it returns the target itself does the search repeat from the target's parent (codex review: only the intended change). * test(e2e): assert scroll and close-app again now that both defects are fixed The two signature skips would hide a regression of the fixed defects, so the scroll case asserts unconditionally again and a surviving churn fixture after close-app fails the leg while the leg still ends its own process. Only the default-CLI stale-ref skip remains, for the documented aliasing gap. * test(e2e): gate two more known product defects by signature and stop the command host Live, an unknown-app lookup returned TIMEOUT because NSWorkspace listed one pid twice and the app inventory refuses a list with a skipped record; that case and the batch entry using it now accept only that exact signature, naming the defect. The default-CLI sibling check shares the documented aliasing defect. The hosted ref-churn run now stops the command host it started, which otherwise kept the desktop lease for its 15-minute idle life. * fix(e2e): parse only stdout from captured command output The binary prints its JSON envelope on stdout and tracing warnings on stderr. Every capture the suite parses as JSON merged stderr, so one warning line made json.loads fail. A WebKit WebContent process that NSWorkspace lists twice now makes every --app lookup warn about a duplicate pid; AE4 read an empty window list and aborted the suite. All captures now discard stderr, matching find_target, and a harness self-test refuses a capture that merges it again. * chore: move runbooks and residual review notes out of git into the local planning folder They are operator and review working notes, not product docs: no code, test, workflow or doc reads them. They now live with the other planning material in the globally ignored .compound-engineering/ folder. * test: restore two test assertions the repair pass over-tightened and stop echoing a session id CI showed both repairs were wrong, not the product. The Windows dispatch test now accepts any deliberate refusal again (headless double-click is refused with POLICY_DENIED by design), as it did before. The macOS helper timeout test asserts it returns within a second again, which is what its name promises; the replacement assertion held on one machine and tested nothing about blocking. The overlay test message now prints only the field it checks, so it no longer writes a session id into a failure message (CodeQL rust/cleartext-logging). * docs: tighten test-quality learnings and note the dropped accname agreement test * docs: point two measurement learnings at the code and scripts that hold them * docs: correct the Windows hardening claim in the CI-lane learning * docs: point the snapshot window citation at current code and tighten policy learnings * docs: drop a cited regression test that does not exist * docs: tighten accessibility runtime learnings without changing their facts * docs: tighten Numbers and Xcode learning wording * docs: use the measured popover timings in the wait-for-closed learning * docs: tighten Xcode and native learnings; keep the empty-search learning until merged * docs: split one dense sentence in the agent contract * docs: tighten prose in the contributor and output docs * docs: tighten the find-name learning wording * docs: tighten the concepts glossary wording without changing its terms * docs: tighten test README prose and keep every command and link * docs: cut the learnings store to the lessons that are still true and live nowhere else Two reviewers checked every learning (codex Sol proposed, codex Luna validated with the burden of proof on deletion); a learning stayed whenever they disagreed. Removed dated evaluation logs, trial records and lessons already carried by code, tests, CLAUDE.md, CONCEPTS.md or the skills; moved the one unique lesson of five learnings into a surviving one; trimmed the rest to rule, fix and one evidence pointer. The two remote dev-box learnings are gone: they described one maintainer's machines, not the product. 52 files and about 46,700 words become 15 files and about 11,100 words. Citations in the e2e harness and test READMEs now point at the surviving learnings. * docs(windows): stop citing two learnings that were removed The rule each comment cited is stated in the comment itself, so the pointer is dropped rather than re-aimed. * chore: remove tooling and paths tied to one maintainer's dev machines agent-desktop is a product for any team, so the repository no longer carries a remote-box workflow: remote-linux.sh (worktree sync to an SSH alias), box-slot.sh (a lock on that box), session-env.sh (importing that box's desktop session) and their two self-tests are removed, with their fmt-job lines. Private-session sessions now default to ${XDG_RUNTIME_DIR:-/tmp}/agent-desktop-sessions, overridable with AGENT_DESKTOP_SESSION_BASE, at every site that creates, discovers or cleans them (shell wrappers, live gate, e2e runner, Rust live tests); the window manager defaults to the system install. Docs and the Linux probe ledger no longer cite personal paths. * test(e2e-linux): take the stale-ref process baseline after the app is running The leg read the process list before Calculator started, so a correct refusal failed its own 'must not end the app' check. The unit test now models the launched process and fails on the old ordering. * fix(linux-tests): keep each OS user's private sessions under their own base Without XDG_RUNTIME_DIR every user shared /tmp/agent-desktop-sessions, so the second user could not create a session. The fallback is now /tmp/agent-desktop-sessions-<uid>, created 0700, at every site that computes it. * test(macos): restore the screenshot timeout test the repair pass over-tightened The repair also required the killed child to be reaped inside the product's cleanup deadline; on a loaded CI runner the product truthfully reports that it was not, so the assertion tested the runner, not the timeout. * fix(linux-tests): let the live gate merge receipts without creating a session base --merge starts no sessions, so it no longer needs a writable session base; the self-test proves it with a base that cannot be created. * test(linux): make the proc-scan test fail with a diagnosis instead of a bare unwrap It failed once on CI with no evidence. The helper no longer proceeds silently when the child never becomes ready, and a missed scan reports what /proc shows for the child, so the next occurrence names its cause. * fix: harden desktop boundaries and bound native operations * fix: install inspector signal handlers before readiness * test(linux): stop running renamed copies of sleep, which Ubuntu 26.04 no longer supports GitHub's ubuntu-latest now includes Ubuntu 26.04 runners, where coreutils is a transitional package over the uutils multi-call binary. That binary picks its tool from argv[0], so a copy of sleep renamed for a test exits at once and the scan finds a zombie. proc_scan failed on every 26.04 run, on branches without this cleanup too. One shared helper now copies the standalone system shell and blocks it on a held stdin pipe, which keeps the test's name on any Linux; the five tests that renamed sleep use it. * test(linux): keep blocking fixtures alive in the background without stdin A background command in a non-interactive shell gets /dev/null as stdin before any redirection, so the orphan launcher's blocking copy read EOF and exited. The copied shell now loops on 'sleep 1' instead of reading stdin; that keeps it alive under its own name in the foreground and the background alike, and lets both launchers return to their original form. * test: make native fixtures portable across CI hosts * test: use an owned process fixture for Linux discovery * fix: preserve trace readability and clear stale overlays * perf: avoid repeated trace ledger replacements * fix: reject shared helper writes and isolate Linux test fixtures * fix(linux): list every top-level window the registry exposes, whatever its role The window list admitted a root child only for roles 23, 16 and 69, a modal panel or a showing filler. Toolkits give a top-level window the role of its widget, so the GTK3 Save As file chooser (19), a Qt message box (2) and a Qt push button shown as a window (43) were never listed, while the modal gate, which reads every root child, refused focus because of the same dialog (L41-1). A root child that implements Component is now a top-level, and one predicate decides it for listing, frame identity, menu inventory and the shell content window. Dialog-ness is one predicate too: the AT-SPI dialog roles read from libatspi (alert, color chooser, dialog, file chooser, font chooser, L41-4), used by the window list, focus, sheets and nested dialogs. A blank, showing top-level that holds only menus is a popup menu surface (the GTK3 context menu and Chromium menus, L41-2, L18-7): menu observation keeps it and the window list drops it. The size identity for unnamed windows applies to every toolkit without global positions instead of GTK4 role-39 panels only, and the duplicate reports_global_position copy is gone. * fix(linux): attribute a launch handed to a running instance by its new window Launching thunar with the session's daemon running failed with "exited with status 0 without a verified application owner" although a new Thunar window opened (L41-6): the launcher forwards to `Thunar --daemon` and exits, and the attach rule also compares the argument list. After a clean launcher exit with no strict owner, the one running process of the same canonical executable that shows a top-level window absent from its pre-launch baseline now owns the launch, and with --activate that window is reported. Several such processes stay ambiguous, none keeps the failure. Strict argument identity for attaching before a spawn is unchanged. * fix(linux): publish hidden and offscreen in snapshots from the action gate's rule A snapshot gave Mousepad's hidden toolbar button a ref with no state, and the headless click then refused it as offscreen (L41-5): the snapshot projection never emitted hidden or offscreen, while the live projection the gate reads derived both. The snapshot now applies the same visibility functions to the facts its walk already holds (states, extents, frame, scroll container, minimized frame) with no extra reads; an input it lacks stays unknown and adds no token. The sentinel and placed-rectangle tests, copied in four places, now live in one module, readiness recognises both web document roles (L3-13), and the GTK4 popup origin is chosen by one rule for snapshot and live reads. * fix(linux): rebuild the desktop alias cache after an edit inside one timestamp tick The in-place desktop edit test failed 2 of 5 runs on unchanged code: the cache compared inode, length and ctime, and an edit of the same length inside one coarse kernel tick keeps the same ctime, so a stale alias index was served. Like git's racily clean rule, a cached index is now trusted only when every stamp is older than its build time minus a tick margin; otherwise it is rebuilt on the next call. * test(linux): add widget-zoo fixtures and the ref-contract e2e legs The bar for a snapshot is that every ref it gives out lands when its action is performed headless, verified by an independent read, or carries hidden, offscreen or disabled and is refused with no effect. Three fixture windows (GTK3, GTK4, Qt) expose the standard widgets, each logging its own effect, and the ref_contract legs drive every ref of a fresh headless snapshot against the log and raw AT-SPI. The window legs open the fixture's dialog, file chooser, message box and top-level button and require each to be listed, reachable as a sheet when modal, and operable to completion. The harness's own window oracle counts Component root children, the same rule as the product (L41-1). * docs(probes): record top-level, popup, visibility and launch measurements Area L41 records the toolkit census that disproves the role-based window rule of L8-2, the popup shapes that are and are not root children, the closed-dialog husks, the libatspi dialog role numbers, the snapshot and gate disagreement on Mousepad's hidden toolbar, and the Thunar daemon hand-off. The environment table names the XFCE container the new rows were measured in. * refactor(linux): state the unplaced-node test without a double negation * fix(linux): recheck racy desktop files by content instead of rebuilding the alias index Rebuilding whenever a desktop file was newer than the build tick made every lookup right after an edit rebuild, so the cache was never reused (the in-place edit test failed 5 of 5). As in git's racily clean rule, only the files that changed within the tick keep a content hash; equal stamps reuse the index unless one of those files now reads different. The test passes 10 of 10. * fix: avoid unsafe at-spi property and interface reads * test: verify linux refs against addressed raw controls * fix: include display bounds in snapshot visibility * docs: explain thunar file views and literal newlines * docs: keep file-view guidance application independent * fix: observe and resolve showing alert surfaces * fix: restore checks after the Linux cleanup merge * fix: serialize trace emission and repair review gates * refactor: align native security types with module boundaries * fix: select option rows through their advertised semantic verb * fix: avoid sibling scrollbar mutations during reveal * fix: retain unanswered scoped surface reads * fix: activate exported menus outside the accessibility dispatcher * test: select the active chooser filename field * docs: record semantic reliability evidence and headless policy * test: repair independent Linux workflow oracles * fix: preserve menu fallback and stop expired launches * fix: await queued windows and reclaim stale clipboard records * fix: preserve clipboard scan timeout codes * fix: verify combo commits and bound optional snapshot reads * fix: complete linux ref delivery and safe refusal contracts (#307) * fix: complete linux ref delivery and safe refusal contracts * fix: complete Wayland menu and chooser ref contracts * test: report unsupported action setup as unexercised * test: separate concurrent overlay fixture sockets * test: enforce snapshot promises in linux ref contracts (#308) * test: align harness checks with retained native refs * fix: prevent private desktop data from leaking through diagnostics * fix(ci): reflect successful writes in the JEV fixture | 14 days ago | |
chore: remove the personal funding link, badges and name references before the org transfer (#332) * chore: remove the personal funding link and the shields.io badges The repository moves to the vercel-labs organization. A personal Sponsors button does not belong on an organization repository, and the badge row named the old owner in every image URL. * chore: remove personal name references from the docs and test fixtures The repository moves to the vercel-labs organization, so the docs name the project maintainers instead of one person. The test fixtures use neutral git identities and a local tap name. Repository URLs keep the current owner until the transfer. | 9 days ago | |
fix(macos): resolve native menus and verify lifecycle transitions (#376) * fix(macos): resolve native menus and verify lifecycle transitions Give app-owned menus process identity without a document window. Verify activation with fresh target AX focus and retain exact process generations. Reconcile caller waits after uncertain delivery without repeating the action. BREAKING CHANGE: JSON envelopes use 2.6; macOS app-owned menu snapshots omit data.window. * fix: keep headed preflight lookup inside the macos adapter Avoid an extra ref resolution on Linux and Windows while preserving strict native menu classification on macOS. Update the native inventory test to pass the bridge's explicit full-inventory argument. * fix: preserve native menu ownership through actions and waits * fix: preserve window identity across title changes Reuse the stored window identity check so a renamed window cannot satisfy a disappearance wait. Keep process-generation checks and return the current title. * refactor: share post-action observation and bound native recovery Observe window-owned refs through their original window so waits survive a closing sheet. Preserve partial Linux window reads until target lookup, and retry only transient macOS surface failures. * fix: preserve focus results and complete observation contracts Keep successful Linux focus results when a later read is incomplete and retain child counts across macOS fallback reads. Share saved process identity validation, clarify surface recovery, and cover incomplete reads and renamed windows. Register the shared observer in the CLI helper inventory. * style: match rustfmt output for CI * fix: preserve process-owned refs in post-action waits * style: format process owner observation guard | 3 days ago | |
docs: simplify the README and repo docs (#481) * docs: simplify the README and repo docs The README opened with vague paragraphs and internal detail, and the FAQ used feature tables with marketing claims. Rewrite them to match the docs site: say what the tool does, show the commands, link out for the rest. - README: computer-use tagline, short install and quick start, the architecture diagram, links to agent-browser.dev/desktop. - FAQ, install, CONTRIBUTING and the FFI, benchmark and test READMEs: cut filler and fix claims the code contradicts (Windows DLL name, ad_init is optional, FFI threading, Homebrew/Scoop status, TMPDIR). - Skills: small wording fixes only; no rule changed. * docs: use --allow-scripts for global installs in install guide npm install-scripts approve only edits a project's package.json and fails for global installs, so it can't fix the global install shown above. Both npm 11 and 12 accept --allow-scripts on npm install -g. Keep the approve command for project installs only. * fix: point blocked-install users at commands that work The launcher notice and the docs site told npm 12 users to run npm install-scripts approve, which fails for global installs, and labeled the --allow-scripts reinstall as npm 11 only. Both npm 11 and 12 accept --allow-scripts on npm install -g, and re-running it over a blocked install links the native binary. In a project, approving alone does nothing until the script runs, and a plain npm install does not re-run it. npm rebuild agent-desktop does, so the project step now says that. Tested on npm 11.20 and 12.2 with throwaway prefixes and projects. * Fix: The macOS installation guide now claims that commands require a private TMPDIR for a background command host, although the merged CLI no longer runs that host, directing users to troubleshoot a nonexistent installation requirement. This commit fixes the issue reported at docs/install.md:56 The new merge base removed `src/command_host`; `src/main.rs` now stops legacy hosts and runs commands directly. `src/tests/command_host_absent.rs` also asserts that commands never connect to or create host socket files. This paragraph reintroduces the removed architecture and its former `INVALID_ARGS` condition into the installation requirements, misleading users with custom temporary directories. The suggested patch removes only the obsolete host/TMPDIR sentences. Verified against the current entry point and absence tests; `git diff --check` passes. Native macOS tests were not run. Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> Co-authored-by: lahfir <nmhlahfir2@gmail.com> --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com> | 5 days ago | |
feat: integrate Linux support with privacy hardening (#311) * chore: remove three uncalled lab scripts No workflow, hook, script or test calls them: a symlink helper for local skills, a root-only uinput stub whose legs were never written, and an unrun self-test of the npm wrapper smoke, which CI runs directly. * docs: remove stale evaluation records and unreferenced diagrams The architecture diagrams and the Slack token illustration were not linked from anywhere and showed Linux as planned. The acceptance audit was a withdrawn status record. Two solution docs were folded into the docs that already carried their context, so each lesson has one home. Validated in the docs removal ledger (docs-3, 6, 8, 11, 12, 13). * docs: correct solution docs that contradict the code Many learnings named moved files, deleted tests, stale function signatures, or rules the code no longer follows (bare refs accepted, native apps never activated, one box lock instead of two slots). Each now cites the code that holds today, and none cites the planning docs or the untracked dogfood reports. * docs: correct CONCEPTS.md against the shipped adapters Several definitions described one platform as if it were every platform, missed the Linux adapter, cited probe rows that measure something else, used a screenshot flag that does not exist, and called dogfood reports committed. Session Manifest folds into Session and the must-catch fixture into Gate Self-Test, where each was already half defined. * docs: correct contributor docs, the FAQ, the JSON contract and runbooks CONTRIBUTING.md now points to CLAUDE.md for the engineering rules instead of keeping a second copy that had drifted (a MockAdapter that does not exist, an E2E command that exits without the exclusive-desktop variable). The JSON contract described an unknown disposition the code no longer emits, the FAQ repeated the README platform table, and the Windows runner runbook cited plan ids and a trigger branch that merged. * ci: assert the release-ffi unwind profile once per release ffi-release-panic-guard already runs the identical grep and publish-github needs it, so the copy in ffi-release-gates added nothing. * chore: let release-please bump the Linux skill version skills/agent-desktop-linux/SKILL.md carries the x-release-please-version marker and is published with the other skills, but was missing from extra-files, so its version would have stayed behind. * docs: make the test harness READMEs runnable as written Several harness commands failed as documented (the Linux consumer run without AGENT_DESKTOP_WM_ROOT, the discovery inversion without a header path, a headless probe binary that swiftc names differently) and the fixtures README listed files that do not exist. The Linux E2E guide drops plan history and keeps what an operator needs to run and read it. * Revert "chore: stop shipping the jev-desktop skill" This reverts commit 34826e104af928a345dbdfeaa83a9a9b72745fb0. * docs: fold the ref-root viewport lesson into the clipping doc The shared clipping doc already states the same incident and the repair that superseded it, so the lesson keeps one home. Validated in the docs removal ledger (docs-14). * docs: say jev-desktop needs a repository clone, and match it to its scripts The skill teaches node scripts/jev/*.mjs, which ship in no installed package, so it now says up front that it needs a clone of the repository and a TYPESAFE_API_KEY. Checked against the scripts: run.mjs retries 429/503/529 twice while act.mjs does not, --bin and --execute belong to act.mjs only, act.mjs sends longer values and the ancestor path, why is null on act, the stop list and exit codes were incomplete, and JEV_VERIFY_MS was missing. * docs: correct FFI handle, wait and last-error text against the header ad_free_handle works after the adapter is destroyed (native_handle.rs tests prove it), so the rule is to free handles before destroying the adapter only for use, not for release. The wait bullet now uses the header's AdWaitMode field names, AdActionStep lists mechanism and verified, the delivery-semantics accessor copies values instead of lending a pointer, and the JSON-string table gains ad_execute_by_ref_timeout, ad_trace_show and ad_trace_export. * ci: cut duplicate jobs and steps from the CI workflow Each removed piece repeated a check that another job already runs on the same OS, or depended on a file that is leaving the repository: - platform-check: three legs were subsets of clippy --all-targets on the same runner; the windows-11-arm leg is now a clippy step in test-windows-arm. - ffi-header-drift, ffi-python-smoke and ffi-passthrough now run as conditional steps on the ffi-panic-guard legs that used the same OS, so the header drift gate and the stub checks keep running on every PR. - check-release-consistency.sh and check-npm-package.js run in the supply-chain workflow on every PR, push and weekly, and need no OS. - the ubuntu bash3 compatibility run was weaker than the macOS Bash 3.2 run. - trash and trash-cli installs had no caller left. - check-phases-ledger-citations.ps1 read docs/phases.md, which leaves the repository, and would fail the Windows lane. The viewer_filter.js test moves here from the inspector workflow, so the shipped viewer code keeps a CI test. * chore: remove the unshipped inspector web UI tools/inspector is a local developer viewer that no shipped artifact, skill or release step uses, and its workflow only tested it. The viewer_filter.js test it also ran now lives in the CI workflow. * ci: give the release dry run a concurrency group and state why it stands alone The reason it is a separate read-only workflow lived only in docs/phases.md, which leaves the repository. A concurrency group stops a busy PR from queueing many full rehearsals, and trash-cli had no caller left. * ci: run the Windows capability probes only when probe files change The probes measure the hosted runner. The one contract they check against crate code, secure-value withholding, is also a plain unit test in the Windows CI lane, so code-only PRs no longer pay for a 30-minute probe run. * docs: document the jev scripts beside the code The jev-desktop skill left the shipped skill set, so its usage notes move next to the scripts they describe, checked against the current flags and environment variables. * chore: remove an unreferenced benchmark screenshot No file links to it. * docs: fix three FFI ownership and panic claims a review disproved A codex review proved three overstatements against source: ad_abi_version returns a constant with no panic trap, so only entrypoints that can panic catch_unwind; last-error strings are borrowed and never freed, so not every returned pointer has a free function; and ad_free_handle validates the adapter before it accepts a null handle. Remaining em-dashes in the FFI references became plain punctuation. * refactor(core): drop two helpers that nothing calls RefStore::for_tests and ErrorPayload::with_recovery had no caller in any crate or test. They sit in public modules, so rustc could not flag them. Recovery hints are still attached by recovery_for_code. Ledger rows A-01, A-02. * test(ffi): drop an unused re-export from the shared test module No ffi integration test names AdIdentifierKind through the common module; the file-level allow(unused_imports) hid it. Ledger row A-08. * ci: keep the 20-minute FFI timeout on legs that only run the panic probe Only the aarch64-apple-darwin leg gained the header drift and stub smoke steps, so only it needs the longer bound. * test(linux): drop live probes that only print timings semantic_click_latency_probe and the vocabulary report, timed pass and label timings asserted nothing and no gate reads their output. The vocabulary live test keeps every assertion path. Ledger rows A-12, A-13. * refactor(linux): drop stale dead_code allows on display errors DisplayCause, as_str and display_cause_error all have production callers, so the allows only hid any future dead code. Ledger row A-15. * refactor(windows): compile the canonical lock path only for tests Only the lease tests call canonical_lock_path, so a cfg(test) gate replaces the allow(dead_code) and the doc no longer promises a future caller. Ledger row A-16. * docs: cut the README to 130 lines and move install detail to docs/install.md The README keeps the pitch, install, the observe-act loop, the agent section, the command table and a plain platform table. Install-script allowlisting, attestation, the source build, per-OS permissions and platform footnotes move to docs/install.md unchanged. * docs: record the parameterized-replacement findings beside their probes The payload shape, the background no-op and the input-context route are what a maintainer needs when running replace_range_probe.swift, so they belong in the harness README rather than in a one-trial log. * docs: state what the jev scripts actually send and restore --no-values does not withhold text passed with --text, the clipboard is only put back when it held text, and a run reads the screen more than once per turn. The README said otherwise. * chore: stop naming a removed workflow in the redaction gate comments windows-e2e.yml no longer exists; the -Path override is described by what it does. * docs: note parameterized attributes in the real-app probe guide A setter that does nothing does not prove an editor has no write path; the probe lists parameterized operations such as AXReplaceRangeWithText, and the replacement range is in UTF-16 units. * docs: remove the Numbers replacement trial log now its findings live with the probe The real-app README carries the payload shape, the background no-op, the input-context route and the inventory lesson. Validated in the docs removal ledger (docs-15, codex and opencode both REMOVE). * chore: make infra comments match the repository The gitignore comment said no normalized twin is committed, the Windows e2e runner said no CI workflow touches it, one gate comment named a function that is being removed, and another described docs/phases.md as in the repository. * docs: make the PR template gate commands run as written The E2E line exited 2 without AGENT_DESKTOP_E2E_EXCLUSIVE=1, a string HOST_PKGS reaches cargo as one argument in zsh, and the skill checklist left out the Linux skill. * fix(npm): point the postinstall skill hint at commands that exist claude mcp add-skill is not a Claude Code subcommand. The hint now names the binary's own skills command and the skills installer the README documents. * docs: drop the jev README now that the jev-desktop skill documents the scripts Two descriptions of the same scripts would drift; the skill is the one that ships. * docs: shape the README for users, contributors and agents The fast path stays on top (install, a quick start, the agent section), followed by reference sections and a documentation map that points each reader to the right file. It restores facts the shorter version dropped: headless-by-default delivery, PowerShell ref quoting, the readable JSON sample, the accessibility-tree row and the per-OS package sets. * docs: name the CI job that now runs the FFI Python smoke The ffi-python-smoke job was folded into the macOS leg of the ffi-panic-guard job, so two docs pointed at a job that no longer runs. * refactor(core): share empty_trace_dir_error between trace show and export The two copies were byte-identical, so one pub(crate) helper in trace_read now serves both calls. Ledger row B-07. * ci: give the Linux live e2e job an explicit read-only token The job inherited an empty permission set, unlike its macOS twin; checkout needs contents: read and nothing more. * ci: pin the probe workflow's checkout to the same release as every other workflow It was the only workflow left on v7.0.0. * docs(core): make two rustdocs match the code they describe transform_tree prunes on is_ref_able, not on role alone, and the drill-down completeness note belongs on run_from_ref_with_context, not on count_nodes. * docs: state the jev read cost for run.mjs as well as act.mjs lead-infra's review found the skill gave the snapshot cost for act.mjs only. run.mjs reads the screen before and after each action (none after DRILL or WIDEN), and both scripts add a surface snapshot when an overlay is open and no region is pinned (desktop.mjs:62-76, act.mjs:236-237). The other five corrections already held in the merged text. * fix(cli): list linux among the skills get aliases skills get linux already resolves, but the help text left it out. * test(visual-debug): stop naming the removed inspector in a test env var The capture child marker now says what it marks, since tools/inspector is gone. * refactor: replace per-crate ensure_budget copies with Deadline::ensure_remaining Twelve private macOS helpers, two private Linux helpers, and the shared Linux bus and Windows permissions helpers all had the same body. One Deadline method now carries it, with unit tests. The DeliveryTracker variants, the mouse and chain variants, and Linux input/deliver.rs keep their own checks because they add delivery details. Ledger row B-01. * docs: restore rules and definitions an earlier cut dropped CLAUDE.md again states the @generated exemption, that every non-doc comment form is rejected while //! module docs are allowed, and that expect and panic are test-only like unwrap. CONCEPTS.md again says a ref-addressed drag picks up at the bounds center (and the slider workaround) and names the must-catch and must-pass fixture halves. * docs: restore two oracle contracts to the Linux E2E guide The rewrite dropped what the pointer delivery legs read back and what the dead-owner recovery leg requires, both of which an operator needs to judge a failing leg. * chore: keep the gitignore note on which capture twins are tracked Per-area probe directories commit their normalized twins; only the twins under probes/<platform>/captures/ are regenerated and untracked. * docs: say in Coordinate Fallback that raw --xy input focuses nothing A raw coordinate resolves with no ref and no window identity, so the caller owns where it lands; the fallback entry now states it directly. * refactor(windows): drop file-level dead_code allows in system modules Windows clippy with -D warnings passes with all twelve removed, so every item in those files is used and the allows only hid future dead code. The menu source agreement test no longer cites the allow. Ledger row A-22. * refactor(windows): parse w-<n> window ids in one place observe and surfaces carried byte-identical parsers with the same InvalidArgs message; window_ops now owns parse_window_id next to window_id. Ledger row B-08. * refactor(linux): share the already-satisfied pointer select result The combo and row select paths built the same skipped XTEST.click result with their own copy of the route constant; select_pointer now holds one. Ledger row B-11c. * refactor(linux): share clipboard delivery helpers between X11 and Wayland set_clear and wayland carried the same verify grace, fresh deadline, and not-delivered and unverified error builders; delivery.rs now holds one copy and messages stay with the callers. Ledger row B-11d. * refactor: build stale-evidence errors with AdapterError::stale_ref_because Three adapter copies of stale_evidence_error repeated the body of the existing core constructor, so the resolvers now call it directly. A core unit test pins its code, suggestion and disposition. Ledger row B-02. * refactor: move the live-evidence completeness helpers into core incomplete_live_evidence, essential_live_evidence_complete and known_actions were byte-identical across the adapters and work only on core locator types; live_element now owns them, with unit tests. Ledger row B-05. * refactor: move notification identity verification into core The macOS and Windows checks were byte-identical over core notification types, so core now exports verify_notification_identity with unit tests. Linux keeps its own check because its suggestion and disposition differ. Ledger row B-06. * chore: point the stale-ref gate at the constructor callers now use stale_evidence_error is gone; evidence-refused refs now build their error with AdapterError::stale_ref_because, so the comment and the failure hint name that. * test(e2e-linux): drop plan requirement ids from leg descriptions R9, R19, R22, R24, R32, R33 and R35 named requirements in a plan that has left the repository, so the prefixes meant nothing to a reader. Nothing parses the requirement text; leg ids are unchanged. * chore: stop the privacy scan comment from claiming the repo ships planning docs * test(e2e-linux): drop duplicate legs and dead harness helpers Six legs repeated the observation of a stronger surviving leg on the same tiers, two unit tests restated another test's assertions, and several helpers had no caller. Each row was validated by two independent models (ledger TR-145..175). * test(e2e-windows): drop a racing zero-timeout leg and dead fixture code The delayed-enable zero-timeout leg raced the fixture timer while the permanently disabled leg proves the same no-wait refusal; the click row's mechanism key is now asserted instead of ignored, and an unused fixture overload and an always-true self-test line are gone (ledger TR-149, TR-151, TR-152, TR-186). * test(e2e): fold the absolute-timeout exit code into the deadline test The removed test repeated the descendant-deadline test and added only the 124 exit code, which the survivor now asserts (ledger TR-185). * ci: run the four hermetic scripts self-tests in the fmt job perf_ab_probe_test.py, test-linux-live-gate.py, test-session-env.sh and test-remote-linux.sh guard the perf report math, live-gate shard and receipt rules, desktop environment selection, and the remote kill guard, yet no job ran them; each is stub-driven, needs no display or network, and takes about a second. The two that start private sessions stay manual (one already runs in linux-e2e). * test: make every self-rerun test prove its child ran the target test A test that re-runs its own binary with a libtest filter passed with zero tests run when the filter was wrong, which already hid the Linux cursor overlay check (its filter missed the overlay module). Each self-rerun site now asserts the child printed "1 passed; 0 failed"; the overlay filter is corrected, and the interactive clipboard test compiles again under its feature. * test(e2e-linux): drop duplicate contract self-tests and dead test helpers Registry and gate checks were asserted twice; the surviving registry test now also refuses an empty registry so it cannot pass vacuously (ledger TR-177..183). * test(core): drop tests that restate a stronger test or only exercise a mock Each removed test was matched by a surviving test at the same or a stronger boundary, or asserted only a mock's or a constant's own value; two merges keep the unique assertions (role round trip, toggleable negatives) in the survivors (ledger TR-001..019). * test(macos): drop duplicate and tautological tests, fold unique assertions into survivors Survivors now carry the sibling-radio suggestion, the AXOpen stale classification and the native plus DOM identifier pair (ledger TR-058..074). * test: drop CLI and conformance tests that only exercise their own mock The conformance window-identity tests asserted the mock adapter's behavior, and the version and wait-metadata assertions repeated stronger tests (ledger TR-142, TR-143, TR-161..163). * test(ffi): drop duplicate C ABI tests and keep their unique checks in survivors Typed discriminant round trips, out-of-range values, a second trapped panic and the last-error pointer across successful calls now live in the surviving tests (ledger TR-035..044). * test(windows): drop duplicate dispatch, notification and state tests The adapter's inline tests were byte-identical to adapter_tests.rs; the R2 advertisement tests now live as rung call counts and isolated advertisement cases in the action and tree tests (ledger TR-073..091). * refactor(linux): import test-only step types in the select tests, not production select_row.rs and select_pointer_combo.rs carried cfg(test) imports only for their tests' use super::*; the tests now import ActionStep and StepMechanism. * test(e2e): add error-envelope and batch failure legs to the macOS harness Agents branch on exit code and error.code for a bare @e1, an unknown app, a missing surface and a parse error, and on batch's continue versus stop-on-error results; no live leg proved them. Self-tests check every expected code exists in the core enum and that each leg asserts code and exit together. * test(e2e-linux): add error-envelope and batch failure legs Two registered legs drive the real binary on both X11 tiers: recoverable error envelopes (INVALID_ARGS for a bare @e1, APP_NOT_FOUND with exit 1) and batch per-entry failure with and without --stop-on-error, observed on the app itself. Pure verdict tests with lying fakes keep them checkable without a desktop. * test(e2e-windows): add error-envelope and batch failure legs Eight Reliability legs cover INVALID_ARGS, SNAPSHOT_NOT_FOUND, STALE_REF for an absent element, APP_NOT_FOUND and ELEMENT_NOT_FOUND with exit codes, and batch halt versus continue with per-entry codes. Not yet run: they need a Windows host (contract -SelfTest, the U7 self-tests, then the live suite). * refactor(core): route deadline clock reads and poll sleeps through one test-freezable seam Several core tests flaked on loaded CI runners because their deadlines ran on the real clock. Deadline now reads time and sleeps through crate-private now() and sleep(), which outside cfg(test) are the same std calls; tests can freeze and advance a thread-local virtual clock. Public API and production behavior are unchanged (codex review: no behavior change). * test(macos): replace the inventory retry wall-clock assertion with counted attempts The test asserted more than one attempt inside a real budget, failed on 6 branches when a slow runner fit only one, and 45b28ce9 only raised the budget to 500 ms. The retry and the partial-inventory refusal are now proven by attempt counts on a fake capture that never consumes its deadline. * fix(windows): confirm a target's exit on its process handle before reporting a menu-wait read error A window that stops pumping during teardown reads as APP_UNRESPONSIVE before its process object is signaled, so the single liveness re-check lost the race and the wait reported the transient code instead of STALE_REF (failed on 7 branches). The wait now blocks on the process handle for at most the remaining deadline, capped at one second, before it keeps the read error. First run on Windows CI. * test(linux): drop duplicate input, close, tray and tie-break tests Each removed test repeated a survivor at the same boundary; the unique recovery text, baseline counts and tie cases moved into the survivors (ledger TR-046..057). * test(e2e): add stale-ref and key-combo legs to the macOS harness The removed-element leg accepted four error codes; it now requires STALE_REF with a safe retry. A RefChurn leg proves STALE_REF and AMBIGUOUS_TARGET after an in-place UI swap, and a press leg proves combos by reading the field itself. * test(e2e-linux): add stale-ref-after-UI-change and key-combo legs The stale leg refreshes nothing between a real menu change and the click and requires STALE_REF with its recovery; the press leg checks ctrl+a, backspace and return by AT-SPI text, not by the command's own ok. * test(e2e-windows): add stale-ref contract and key-combo legs The removable row now reports clicks, so a stale ref proven by refusal also proves nothing was delivered; press legs cover ctrl+a, shift+tab, return and the blocked alt+f4 refusal. Not yet run: needs a Windows host. * docs: point the live-gate runbook at the focus leg that stays The lift leg duplicated lifecycle.focus_window_active_reads and never read the restoration it named, so the tests cleanup removes it; the retained live owner is the active-window read. * test(core): run the settle-loop and artifact-lock budget tests on the virtual clock These tests failed on loaded Windows and macOS runners because a real 50 ms lock wait or settle window expired before the code under test ran; a 16 x 20 ms retry wrapper hid it. They now freeze the virtual clock, so lock polls and settle sleeps advance time deterministically, and the retry wrapper is gone. * fix(windows): treat E_UNEXPECTED on a UIA read as a transient failure A provider tearing down a window or shell host answers one read with E_UNEXPECTED; it was classified terminal and narrowed to a non-retryable APP_UNRESPONSIVE, so one read ended a whole wait (failed on 4 branches). It is now a retryable unresponsive read inside the caller's existing deadline; E_FAIL stays terminal. First run on Windows CI. * test(linux): make vacuous assertions able to fail Repaired tests named a behavior their assertions could not detect (a negative check that any value passed, a banned-name list with dead entries, an unverified result asserted only as not verified); each now checks the stated contract. * test(macos): make vacuous assertions able to fail, each proven by a production mutation Process-group kill, semantic-only disclosure chains, single capture, strategy fall-through, scan tolerance, endpoint naming, overlay acknowledgement, the select depth cap and the name-plan slots now fail when the behavior breaks. * test(ffi): make C ABI tests assert what their names promise Modifier cap, JSON error forwarding, invalid discriminants, envelope fields, dirty out-params on a worker thread, wait error envelopes and the session adapter's fail-closed path each had an assertion that could not fail; every repair was proven by breaking the production path once. * test(e2e): make harness self-tests fail for the regressions they name * test(e2e-linux): make harness self-tests and a menu leg assert structured results The menu timeout leg now checks the structured wait_timeout details, and the oracle, runner, session-sweep, workflow and verdict self-tests assert the specific failure each one names. * test(e2e-windows): replace diagnostic scaffolding and soft guards with real assertions The headed double-click leg used debug output, a fixed sleep and one read; it now uses Assert-Effect like its siblings. The handle-set and overlay controls no longer skip silently, and the Chromium rate leg fails when every sample is stale. Not yet run: needs a Windows host. * test(core): drop find, get, wait, session and trace tests that repeat stronger tests The removed find tests ran a cfg(test)-only snapshot collector production never uses; that collector's count helper went with them. Merges keep the observed-role negative, the null-bounds case, the conflicting wait flags as a table and the identifier preference in the survivors (ledger TR-020..034). * test(windows): drop input fake-sink, policy, clipboard and surface tests that repeat survivors The keyboard and mouse send tests only checked that the test fake stored what it was given; the clipboard, chain and surface merges keep their unique cases as table rows or assertions in the survivors (ledger TR-092..106). * test(windows): drop tree property, hit-test and resolve tests that repeat survivors Each removed test had an identical or stronger twin in the same module family (ledger TR-124..140). * test(linux): make clipboard, keyboard-frame, remap, capture and close tests assert their contracts The remap test now proves the deadline cancelled after the remap and that the keymap was restored, the off-screen capture test places its window instead of skipping, and the close gate is checked for both graceful and forced closes. * test: make CLI dispatch, overlay and real-app tests assert what they claim Real-app tests now fail when their precondition is missing instead of passing silently, the overlay show test pins POLICY_DENIED, and the conformance test checks each capability's outcome, not only that the call returned. * test(real-apps): assert the Numbers failure stop and drop a dead plan citation * test(macos): make capture-scope, screenshot and child-label tests assert their contracts The capture-scope tests now run the production scope filter instead of a test copy of it (the single-pid case is kept beside the pid-set case), the screenshot timeout checks the child is gone, and the child-label rule is checked for every role it names. * test(core): order concurrent snapshot writers instead of retrying lock timeouts, and pin tray ids Eight writers polled one lock with no fairness and a 30 s retry hid starvation that failed CI on four branches; the writers now take turns, and a single-thread test on the frozen clock proves a held store lock still blocks a writer. A new test pins that allocation keeps a RuntimeId identifier and its kind, which no test covered after the tray identity change. * test(windows): drop lifecycle, launch, HRESULT and signal tests that repeat survivors Merges move the exit-code serialization cases into core's process-state tests and the HRESULT detail strings and generation checks into the surviving Windows tests (ledger TR-108..121). * test(windows): drop two duplicate tree tests and make hit-test and resolve tests assert their contracts hit_test now judges a probe through judge_probe, so a probe miss is tested on the production path (same behavior: a miss is Unknown, a hit is judged); the fixture teardown, minimized-window, anchor and timeout tests assert what they name (ledger TR-122, TR-123, TF-144..157). * test(linux): make capture-cap, tie, key-owner, menu and tray tests assert their contracts * test(core): run live-locator and ref-action wait tests on the virtual clock These tests had their budgets widened and an exact attempt count loosened to 'at least one' to survive slow runners; on the frozen clock the retry loop's own sleeps advance time, so the original exact counts and small budgets hold. * test(windows): drop the setup-time stopwatch from the headless notification wait refusal The elapsed < 2 s assertion measured COM and adapter setup and failed twice on loaded runners; POLICY_DENIED is not retryable, so a burned deadline would already surface as TIMEOUT and fail the code assertion. * test(windows): decide modern-capture frame-wait expiry inside the wait, not by wall time The fallback test asserted TIMEOUT against an 80 ms budget that real capture setup could consume first (failing as INTERNAL); the cfg(test) frame hold now starts its budget inside the frame wait, and the pattern test waits for a frame showing the fixture instead of sleeping 500 ms. Production frame wait unchanged. * test(windows): signal the menu fixture's open state from real menu mode The fixture announced the popup before TrackPopupMenu entered menu mode and ignored its result, so the chromium composition test compared two live reads that could straddle the flip; readiness now comes from menu mode itself and the comparison waits until both sources agree. * test(linux): make tray, signal, settle, walker and shell-owner tests assert their contracts The error-code boundary test now enumerates codes exhaustively at compile time instead of a hand-typed list of 16, and the remaining repairs check the behavior their names state. * test(macos): replace real-clock budgets and stopwatches with counted attempts and events Stabilize-loop tests could expire before their first attempt on a slow runner; they now use budgets the fakes never consume and assert exact attempt counts, and the overlay acknowledgement tests block on a release channel instead of a 700 ms sleep. * test: isolate command-host sockets per test and assert batch deadlines by value Fixed socket and symlink names keyed by pid were never removed, so pid reuse could fail a later run; each test now owns a removed-on-drop directory. Batch deadline tests assert the budget the adapter received instead of wall time. * test(ffi): isolate the round-trip test's state root and require the snapshot log event The round-trip test set HOME under its own lock but left AGENT_DESKTOP_HOME alone, so a developer's state root leaked in; it now uses the shared isolated home helper. Log-callback tests no longer fall back to the first delivery. * test(core): make actionability, trace, find and close tests assert their contracts Each repaired test was proven by breaking its production path once. The benchmark example's rect test moved into the library's rect tests so it runs in CI, and the find roles hint is now tested through execute instead of a cfg(test)-only copy, which is deleted. * test(windows): make dispatch, envelope, scroll and fixture tests assert their contracts Permissive Err arms, restated literals and a self-comparing fixture expectation are replaced by assertions on the delivered outcome. * test(linux): make tree walker, projection and placement tests assert their contracts * test(core): make session, overlay-routing, hover and pointer tests assert their contracts Session precedence tests pass the env value directly instead of mutating the process environment, gc is actually exercised on an unreadable manifest, the skeleton golden compares the whole tree, and hover counts receiver checks instead of timing them. * test(windows): make drag, capture, HRESULT, signal and wait tests assert their contracts Assertions true by construction (an intersection member found in its own set, an assert_ne after an exact assert_eq, a uniqueness loop over a test-local list) are replaced or removed, and the remaining checks name the behavior they guard. * test(linux): make resolve tests assert the unread-region refusal they name * test(core): drop the null ref_id from the skeleton golden fixture The skeleton golden test now compares the whole serialized tree, and the serializer omits a missing ref_id, so the fixture must not carry one. * test(core): make ref, refs, trace-read and policy tests assert their contracts The XSS gate now asserts the exact escaped payload, the oversize-refmap guard is exercised through the production writer, poll and wait tests count resolves instead of timing them, and a new test pins identifier kinds through RefEntry identity. Each repair was proven by breaking its production path once. * test(windows): make lifecycle, overlay spawn and shell-surface tests assert their contracts A tautological ambiguous-target shape check now serializes the production value, a self-comparing error-code helper is gone, and the taskbar test no longer returns early on an empty tree. * test(npm): drop duplicate platform and wrapper tests and check the real staging directory The staging-cleanup tests read the destination, which installArchive never stages in, so a cleanup regression still passed; they now compare the package bin directory before and after. Duplicate platform-table and wrapper tests are gone, with their unique helper-name and Trash-warning checks kept in the survivors (ledger TR-189, TR-191..193, TF-187, TF-188). * ci: run the Numbers sheet-script self-test in the fmt job numbers_sheets_test.py drives numbers-sheets.sh against a fake binary in a temporary home and proves its failure stop; it is hermetic and nothing ran it. * test: nest extra test modules so moved anchors keep room under the line cap ref_action.rs (400 lines) and the Windows tree/resolve.rs (398 lines) become their folders' mod.rs in the restructure and must then declare their new child modules. Their extra test modules are declared from the files' tests modules instead, so both anchors stay under 400 lines. The nested test files import their parent by absolute path, keeping what `super::` meant before. The Linux action scan walks src/actions recursively, so the banned-call scan keeps reading the action files that move into select/, scroll/ and write/, and it asserts files from each of those groups are still scanned. * refactor(cli): give the command host and diagnostics their own folders Nine command_host*.rs files and three diagnostic*.rs files sat loose in src/, so the binary's root read as a flat list. Each family now lives in a folder whose mod.rs is the former entry file; the helpers were already #[path] children, so no module path changes. * refactor(core): group core's leaf types into response, input, notifications, storage and wait Sixty-seven of core's 270 root files are the envelope and error model, the input and notification parameter types, the on-disk state root, deadlines and signals, plus adapter and session helpers. They now sit in folders named after the concepts in CONCEPTS.md, so a reader finds a type by its domain instead of scanning one flat list. Module paths gain one segment; root re-exports in lib.rs keep every public name unchanged, and adapter becomes a public module because it now holds the public hit_test, live_element and native_handle modules. * refactor(core): group core's tree, action, system and snapshot types by domain The accessibility-tree model, the Action model and its execution types, app and window and process types, snapshot building and the context module were 115 loose root files. They now live in tree/, actions/, system/, snapshot/ and context/, named like the platform crates' layers so a type a platform crate uses from its input/ or system/ lives under the same name in core. The stray actionability tests move into actionability/. Two test files import one module once so the longer paths keep them under 400 lines. * refactor(core): group refs, ref actions, locators and traces into their folders The ref map, ref store, allocation and identity files, the headless ref action flow, the locator with its live evaluator, and the trace writer with its reader were the last loose families at core's root. Each family now lives under the module that already anchored it (refs.rs, ref_action.rs, locator.rs and trace.rs become their folders' mod.rs), so those module paths do not change. trace becomes public because it now holds the public trace_read and trace_sanitize modules. lib.rs is the only file left at the root. commands/ stays flat: its contract tests require commands/<name>.rs. * refactor(macos): group the macOS layers into concept folders Each layer held one flat list: 62 files in tree/, 68 in system/, 45 in actions/. Concepts with four or more sources now have a folder named with the vocabulary all three platform crates share: tree/resolve/ and tree/properties/, actions/chain/ and actions/select/, input/clipboard/, keyboard/ and mouse/, system/app/, process/, window/ and permissions/. cf_type.rs leaves the crate root. The cursor-overlay and permission-guide Objective-C bridges move next to the Rust files that declare them, with build.rs and the native test imports following. File names do not change. * refactor(windows): group the Windows tree layer into concept folders tree/ held 117 files in one folder. Ref re-identification, the Win32 test fixtures, the UIA walkers, the property reads and the hit test now each have a folder (resolve/, fixture/, walk/, properties/, hit_test/), using the same names as the other platform crates. The fixtures' self-rerun filters and the hit-test scan labels follow the moves, and the public tree paths used by the uia_tree_dump example and the resolution probe are updated with them. * refactor(windows): group the Windows system and input layers into concept folders system/ held about 70 loose sources and input/ about 26. Capture, windows, shell surfaces, wait signals, the COM runtime glue, menus and the shared test seams now each have a folder under system/, and clipboard, keyboard and mouse each have one under input/, with the names the other platform crates use. Public names in lib.rs do not change. Four clipboard helpers now sit under the clipboard module's existing cfg(target_os = "windows"); the crate only builds for Windows, so the set of compiled and run code is the same. * refactor(linux): group the Linux tree layer into concept folders tree/ held 209 files in one folder, the most crowded in the repository. It now has thirteen concept folders (walk, resolve, hit_test, menu, surfaces, placement, live_read, observe, properties, element, vocabulary, capabilities, live) and keeps two loose sources. All live rigs and live suites sit in tree/live/, so the live gate selects them by one prefix; its module strings, its self-test and the runbook follow the moves, and the PARALLEL_READS entry for a module that never existed is dropped. * refactor(linux): group the Linux action, input and X11 layers into concept folders actions/ held 95 files and input/ about 57 loose files, and system/x11/ (87 files) and system/window/ (57 files) had grown past one screen. Selection, scrolling, writes and live rigs now have folders under actions/; keyboard, mouse, physical targeting and the clipboard owner have folders under input/; the X11 client splits into capture, control, monitor, stacking and window, and the window operation pipeline gets window/window_op/. system/ keeps the shape it already had. The live gate's module strings follow the moves. * ci: fail the build when platform code leaks or a crate's layout drifts Platform isolation was checked only through cargo tree on CI and one PowerShell scan, so a source-level leak or a file dropped in the wrong layer reached review unnoticed. scripts/check-architecture.sh checks manifests through cargo metadata and sources through a small lexer: core depends only on its listed crates, only the binary and FFI wire platform crates, no platform crate reaches another, core keeps OS-conditional code in an exact allow-list, every crate keeps its top-level layout, capabilities are implemented in their layer, core domains do not reach commands, and tests are declared beside their code. It plants a violation of every rule before it scans, so a rule that stops detecting fails the gate. It runs in the pre-commit hook and next to the source rules in CI; the cargo tree steps stay as the transitive check. * docs: describe the crate layout and the guard that keeps it CLAUDE.md described the platform layers but not where core's files or a layer's sub-concepts go, so the next contributor or agent would guess. It now states core's domain folders, the shared platform layout and subfolder vocabulary, the file-naming and test-placement rules, and the grounding rule that enforces them. * docs: follow the source restructure in path citations The restructure moved test and module files into feature folders, so three code-span paths and a handful of folder-relative citations in the solution docs, CONCEPTS.md and two test READMEs named files that no longer exist. * test: point the e2e error-code contract at the moved error_code.rs error_code.rs moved to crates/core/src/response/ in the core restructure. The e2e harness contract test builds the path from parts, so the literal path rewrite did not see it and two contract tests failed to open the file. * docs: re-point short-form file citations at their moved files Seven citations named a moved file by its path relative to its crate (for example system/shell_surface_raise.rs), a form the full-path rewrite did not match. Each now names the file's new folder. Short citations of files that did not move, such as the Windows actions/ labels, are unchanged. * test(e2e): read written fields directly and run the host ref-churn leg fully hosted The first live run failed five legs on the harness, not the product: batch and ref-churn checks read SwiftUI echo labels that set-value does not promise to update, and the host-client variant enabled the host only for one command and used a non-private temporary directory, so the host refused it with INVALID_ARGS. The legs now read the field's own value, and the whole host run uses the host and the private temp dir. The stale-ref and close-app assertions are unchanged: they expose pre-existing product defects. * test(e2e): skip three known product defects by exact signature and end the churn fixture by pid No failing leg ships, and no real defect is hidden. The scroll, default-CLI stale-ref and close-app cases skip only when their known defect signature appears, naming the defect, file and origin commit; any other result still asserts, so each case re-arms itself once the product is fixed. When close-app leaves the RefChurn fixture running, the leg ends its own process and asserts it is gone. * fix(macos): let close-app end a process that has no LaunchServices launch date Termination refused whenever NSRunningApplication reported no launch date, so close-app could not end an app launched outside LaunchServices, contrary to the rule that absent evidence is not conflicting evidence. The bridge now passes whether a date is present, and only a present date more than five seconds from the verified instance refuses; pid, start-time token and exit checks are unchanged (codex review: only the intended change). * fix(macos): reveal a clipped scroll area through its enclosing scroll view Scroll-into-view looked for the clipping viewport starting at the target, so a target that is itself a scroll area became its own viewport, produced no direction, and stayed clipped (ACTION_FAILED, live offscreen). The original walk still runs unchanged; only when it returns the target itself does the search repeat from the target's parent (codex review: only the intended change). * test(e2e): assert scroll and close-app again now that both defects are fixed The two signature skips would hide a regression of the fixed defects, so the scroll case asserts unconditionally again and a surviving churn fixture after close-app fails the leg while the leg still ends its own process. Only the default-CLI stale-ref skip remains, for the documented aliasing gap. * test(e2e): gate two more known product defects by signature and stop the command host Live, an unknown-app lookup returned TIMEOUT because NSWorkspace listed one pid twice and the app inventory refuses a list with a skipped record; that case and the batch entry using it now accept only that exact signature, naming the defect. The default-CLI sibling check shares the documented aliasing defect. The hosted ref-churn run now stops the command host it started, which otherwise kept the desktop lease for its 15-minute idle life. * fix(e2e): parse only stdout from captured command output The binary prints its JSON envelope on stdout and tracing warnings on stderr. Every capture the suite parses as JSON merged stderr, so one warning line made json.loads fail. A WebKit WebContent process that NSWorkspace lists twice now makes every --app lookup warn about a duplicate pid; AE4 read an empty window list and aborted the suite. All captures now discard stderr, matching find_target, and a harness self-test refuses a capture that merges it again. * chore: move runbooks and residual review notes out of git into the local planning folder They are operator and review working notes, not product docs: no code, test, workflow or doc reads them. They now live with the other planning material in the globally ignored .compound-engineering/ folder. * test: restore two test assertions the repair pass over-tightened and stop echoing a session id CI showed both repairs were wrong, not the product. The Windows dispatch test now accepts any deliberate refusal again (headless double-click is refused with POLICY_DENIED by design), as it did before. The macOS helper timeout test asserts it returns within a second again, which is what its name promises; the replacement assertion held on one machine and tested nothing about blocking. The overlay test message now prints only the field it checks, so it no longer writes a session id into a failure message (CodeQL rust/cleartext-logging). * docs: tighten test-quality learnings and note the dropped accname agreement test * docs: point two measurement learnings at the code and scripts that hold them * docs: correct the Windows hardening claim in the CI-lane learning * docs: point the snapshot window citation at current code and tighten policy learnings * docs: drop a cited regression test that does not exist * docs: tighten accessibility runtime learnings without changing their facts * docs: tighten Numbers and Xcode learning wording * docs: use the measured popover timings in the wait-for-closed learning * docs: tighten Xcode and native learnings; keep the empty-search learning until merged * docs: split one dense sentence in the agent contract * docs: tighten prose in the contributor and output docs * docs: tighten the find-name learning wording * docs: tighten the concepts glossary wording without changing its terms * docs: tighten test README prose and keep every command and link * docs: cut the learnings store to the lessons that are still true and live nowhere else Two reviewers checked every learning (codex Sol proposed, codex Luna validated with the burden of proof on deletion); a learning stayed whenever they disagreed. Removed dated evaluation logs, trial records and lessons already carried by code, tests, CLAUDE.md, CONCEPTS.md or the skills; moved the one unique lesson of five learnings into a surviving one; trimmed the rest to rule, fix and one evidence pointer. The two remote dev-box learnings are gone: they described one maintainer's machines, not the product. 52 files and about 46,700 words become 15 files and about 11,100 words. Citations in the e2e harness and test READMEs now point at the surviving learnings. * docs(windows): stop citing two learnings that were removed The rule each comment cited is stated in the comment itself, so the pointer is dropped rather than re-aimed. * chore: remove tooling and paths tied to one maintainer's dev machines agent-desktop is a product for any team, so the repository no longer carries a remote-box workflow: remote-linux.sh (worktree sync to an SSH alias), box-slot.sh (a lock on that box), session-env.sh (importing that box's desktop session) and their two self-tests are removed, with their fmt-job lines. Private-session sessions now default to ${XDG_RUNTIME_DIR:-/tmp}/agent-desktop-sessions, overridable with AGENT_DESKTOP_SESSION_BASE, at every site that creates, discovers or cleans them (shell wrappers, live gate, e2e runner, Rust live tests); the window manager defaults to the system install. Docs and the Linux probe ledger no longer cite personal paths. * test(e2e-linux): take the stale-ref process baseline after the app is running The leg read the process list before Calculator started, so a correct refusal failed its own 'must not end the app' check. The unit test now models the launched process and fails on the old ordering. * fix(linux-tests): keep each OS user's private sessions under their own base Without XDG_RUNTIME_DIR every user shared /tmp/agent-desktop-sessions, so the second user could not create a session. The fallback is now /tmp/agent-desktop-sessions-<uid>, created 0700, at every site that computes it. * test(macos): restore the screenshot timeout test the repair pass over-tightened The repair also required the killed child to be reaped inside the product's cleanup deadline; on a loaded CI runner the product truthfully reports that it was not, so the assertion tested the runner, not the timeout. * fix(linux-tests): let the live gate merge receipts without creating a session base --merge starts no sessions, so it no longer needs a writable session base; the self-test proves it with a base that cannot be created. * test(linux): make the proc-scan test fail with a diagnosis instead of a bare unwrap It failed once on CI with no evidence. The helper no longer proceeds silently when the child never becomes ready, and a missed scan reports what /proc shows for the child, so the next occurrence names its cause. * fix: harden desktop boundaries and bound native operations * fix: install inspector signal handlers before readiness * test(linux): stop running renamed copies of sleep, which Ubuntu 26.04 no longer supports GitHub's ubuntu-latest now includes Ubuntu 26.04 runners, where coreutils is a transitional package over the uutils multi-call binary. That binary picks its tool from argv[0], so a copy of sleep renamed for a test exits at once and the scan finds a zombie. proc_scan failed on every 26.04 run, on branches without this cleanup too. One shared helper now copies the standalone system shell and blocks it on a held stdin pipe, which keeps the test's name on any Linux; the five tests that renamed sleep use it. * test(linux): keep blocking fixtures alive in the background without stdin A background command in a non-interactive shell gets /dev/null as stdin before any redirection, so the orphan launcher's blocking copy read EOF and exited. The copied shell now loops on 'sleep 1' instead of reading stdin; that keeps it alive under its own name in the foreground and the background alike, and lets both launchers return to their original form. * test: make native fixtures portable across CI hosts * test: use an owned process fixture for Linux discovery * fix: preserve trace readability and clear stale overlays * perf: avoid repeated trace ledger replacements * fix: reject shared helper writes and isolate Linux test fixtures * fix(linux): list every top-level window the registry exposes, whatever its role The window list admitted a root child only for roles 23, 16 and 69, a modal panel or a showing filler. Toolkits give a top-level window the role of its widget, so the GTK3 Save As file chooser (19), a Qt message box (2) and a Qt push button shown as a window (43) were never listed, while the modal gate, which reads every root child, refused focus because of the same dialog (L41-1). A root child that implements Component is now a top-level, and one predicate decides it for listing, frame identity, menu inventory and the shell content window. Dialog-ness is one predicate too: the AT-SPI dialog roles read from libatspi (alert, color chooser, dialog, file chooser, font chooser, L41-4), used by the window list, focus, sheets and nested dialogs. A blank, showing top-level that holds only menus is a popup menu surface (the GTK3 context menu and Chromium menus, L41-2, L18-7): menu observation keeps it and the window list drops it. The size identity for unnamed windows applies to every toolkit without global positions instead of GTK4 role-39 panels only, and the duplicate reports_global_position copy is gone. * fix(linux): attribute a launch handed to a running instance by its new window Launching thunar with the session's daemon running failed with "exited with status 0 without a verified application owner" although a new Thunar window opened (L41-6): the launcher forwards to `Thunar --daemon` and exits, and the attach rule also compares the argument list. After a clean launcher exit with no strict owner, the one running process of the same canonical executable that shows a top-level window absent from its pre-launch baseline now owns the launch, and with --activate that window is reported. Several such processes stay ambiguous, none keeps the failure. Strict argument identity for attaching before a spawn is unchanged. * fix(linux): publish hidden and offscreen in snapshots from the action gate's rule A snapshot gave Mousepad's hidden toolbar button a ref with no state, and the headless click then refused it as offscreen (L41-5): the snapshot projection never emitted hidden or offscreen, while the live projection the gate reads derived both. The snapshot now applies the same visibility functions to the facts its walk already holds (states, extents, frame, scroll container, minimized frame) with no extra reads; an input it lacks stays unknown and adds no token. The sentinel and placed-rectangle tests, copied in four places, now live in one module, readiness recognises both web document roles (L3-13), and the GTK4 popup origin is chosen by one rule for snapshot and live reads. * fix(linux): rebuild the desktop alias cache after an edit inside one timestamp tick The in-place desktop edit test failed 2 of 5 runs on unchanged code: the cache compared inode, length and ctime, and an edit of the same length inside one coarse kernel tick keeps the same ctime, so a stale alias index was served. Like git's racily clean rule, a cached index is now trusted only when every stamp is older than its build time minus a tick margin; otherwise it is rebuilt on the next call. * test(linux): add widget-zoo fixtures and the ref-contract e2e legs The bar for a snapshot is that every ref it gives out lands when its action is performed headless, verified by an independent read, or carries hidden, offscreen or disabled and is refused with no effect. Three fixture windows (GTK3, GTK4, Qt) expose the standard widgets, each logging its own effect, and the ref_contract legs drive every ref of a fresh headless snapshot against the log and raw AT-SPI. The window legs open the fixture's dialog, file chooser, message box and top-level button and require each to be listed, reachable as a sheet when modal, and operable to completion. The harness's own window oracle counts Component root children, the same rule as the product (L41-1). * docs(probes): record top-level, popup, visibility and launch measurements Area L41 records the toolkit census that disproves the role-based window rule of L8-2, the popup shapes that are and are not root children, the closed-dialog husks, the libatspi dialog role numbers, the snapshot and gate disagreement on Mousepad's hidden toolbar, and the Thunar daemon hand-off. The environment table names the XFCE container the new rows were measured in. * refactor(linux): state the unplaced-node test without a double negation * fix(linux): recheck racy desktop files by content instead of rebuilding the alias index Rebuilding whenever a desktop file was newer than the build tick made every lookup right after an edit rebuild, so the cache was never reused (the in-place edit test failed 5 of 5). As in git's racily clean rule, only the files that changed within the tick keep a content hash; equal stamps reuse the index unless one of those files now reads different. The test passes 10 of 10. * fix: avoid unsafe at-spi property and interface reads * test: verify linux refs against addressed raw controls * fix: include display bounds in snapshot visibility * docs: explain thunar file views and literal newlines * docs: keep file-view guidance application independent * fix: observe and resolve showing alert surfaces * fix: restore checks after the Linux cleanup merge * fix: serialize trace emission and repair review gates * refactor: align native security types with module boundaries * fix: select option rows through their advertised semantic verb * fix: avoid sibling scrollbar mutations during reveal * fix: retain unanswered scoped surface reads * fix: activate exported menus outside the accessibility dispatcher * test: select the active chooser filename field * docs: record semantic reliability evidence and headless policy * test: repair independent Linux workflow oracles * fix: preserve menu fallback and stop expired launches * fix: await queued windows and reclaim stale clipboard records * fix: preserve clipboard scan timeout codes * fix: verify combo commits and bound optional snapshot reads * fix: complete linux ref delivery and safe refusal contracts (#307) * fix: complete linux ref delivery and safe refusal contracts * fix: complete Wayland menu and chooser ref contracts * test: report unsupported action setup as unexercised * test: separate concurrent overlay fixture sockets * test: enforce snapshot promises in linux ref contracts (#308) * test: align harness checks with retained native refs * fix: prevent private desktop data from leaking through diagnostics * fix(ci): reflect successful writes in the JEV fixture | 14 days ago | |
chore: remove the personal funding link, badges and name references before the org transfer (#332) * chore: remove the personal funding link and the shields.io badges The repository moves to the vercel-labs organization. A personal Sponsors button does not belong on an organization repository, and the badge row named the old owner in every image URL. * chore: remove personal name references from the docs and test fixtures The repository moves to the vercel-labs organization, so the docs name the project maintainers instead of one person. The test fixtures use neutral git identities and a local tap name. Repository URLs keep the current owner until the transfer. | 9 days ago | |
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. | 3 months ago |
| Files | Last commit | Last update |
|---|---|---|
| 3 days ago | ||
| 14 days ago | ||
| 5 days ago | ||
| 3 days ago | ||
| 5 days ago | ||
| 5 days ago | ||
| 1 month ago | ||
| 14 days ago | ||
| 14 days ago | ||
| 14 days ago | ||
| 9 days ago | ||
| 3 days ago | ||
| 5 days ago | ||
| 14 days ago | ||
| 9 days ago | ||
| 3 months ago |