| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
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 天前 | |
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 天前 | |
feat!: verify stateful ref actions against fresh platform state An AX write that reported success could still leave the application unchanged, so a calling agent acted on the adapter's claim rather than on an observation. Stateful ref actions now execute exactly once and verify the result from a fresh read, and macOS mutation errors that do not prove non-delivery stop their fallback chains instead of applying a second mechanism against a target that may already have been mutated. Adds a local visual-debug artifact for snapshot and click, and a read-only loopback inspector for driving the CLI from a browser during development. BREAKING CHANGE: the response envelope moves to 2.4. Stateful ref actions can now return ACTION_FAILED for an observed postcondition contradiction, and ActionResult::from_execution takes only action and steps. | 1 个月前 | |
feat!: verify stateful ref actions against fresh platform state An AX write that reported success could still leave the application unchanged, so a calling agent acted on the adapter's claim rather than on an observation. Stateful ref actions now execute exactly once and verify the result from a fresh read, and macOS mutation errors that do not prove non-delivery stop their fallback chains instead of applying a second mechanism against a target that may already have been mutated. Adds a local visual-debug artifact for snapshot and click, and a read-only loopback inspector for driving the CLI from a browser during development. BREAKING CHANGE: the response envelope moves to 2.4. Stateful ref actions can now return ACTION_FAILED for an observed postcondition contradiction, and ActionResult::from_execution takes only action and steps. | 1 个月前 | |
feat!: verify stateful ref actions against fresh platform state An AX write that reported success could still leave the application unchanged, so a calling agent acted on the adapter's claim rather than on an observation. Stateful ref actions now execute exactly once and verify the result from a fresh read, and macOS mutation errors that do not prove non-delivery stop their fallback chains instead of applying a second mechanism against a target that may already have been mutated. Adds a local visual-debug artifact for snapshot and click, and a read-only loopback inspector for driving the CLI from a browser during development. BREAKING CHANGE: the response envelope moves to 2.4. Stateful ref actions can now return ACTION_FAILED for an observed postcondition contradiction, and ActionResult::from_execution takes only action and steps. | 1 个月前 | |
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 天前 | |
feat!: verify stateful ref actions against fresh platform state An AX write that reported success could still leave the application unchanged, so a calling agent acted on the adapter's claim rather than on an observation. Stateful ref actions now execute exactly once and verify the result from a fresh read, and macOS mutation errors that do not prove non-delivery stop their fallback chains instead of applying a second mechanism against a target that may already have been mutated. Adds a local visual-debug artifact for snapshot and click, and a read-only loopback inspector for driving the CLI from a browser during development. BREAKING CHANGE: the response envelope moves to 2.4. Stateful ref actions can now return ACTION_FAILED for an observed postcondition contradiction, and ActionResult::from_execution takes only action and steps. | 1 个月前 | |
feat!: verify stateful ref actions against fresh platform state An AX write that reported success could still leave the application unchanged, so a calling agent acted on the adapter's claim rather than on an observation. Stateful ref actions now execute exactly once and verify the result from a fresh read, and macOS mutation errors that do not prove non-delivery stop their fallback chains instead of applying a second mechanism against a target that may already have been mutated. Adds a local visual-debug artifact for snapshot and click, and a read-only loopback inspector for driving the CLI from a browser during development. BREAKING CHANGE: the response envelope moves to 2.4. Stateful ref actions can now return ACTION_FAILED for an observed postcondition contradiction, and ActionResult::from_execution takes only action and steps. | 1 个月前 | |
fix: guard the debug viewer image sink and unpin a flaky test deadline CodeQL flagged the artifact viewer assigning a value read out of the JSON island straight into an image src. That value is a base64 PNG this repository generates and validates, so it is not attacker-controlled today, but the sink itself was unguarded. An anchored validator now lives in the shared filter module alongside its own tests, and the identical unguarded sink in the inspector's screenshot view uses it too. The focus-order tests shared one 150ms budget. Three of them have to complete inside it and finish in under 70ms on a developer machine, but a slower runner took 155ms and failed the branch. Those three now get a ceiling generous enough to survive a slow runner, which costs nothing because each exits as soon as its own condition holds, while the disabled target case keeps a short budget because reaching the deadline is exactly what it asserts. | 1 个月前 | |
fix: guard the debug viewer image sink and unpin a flaky test deadline CodeQL flagged the artifact viewer assigning a value read out of the JSON island straight into an image src. That value is a base64 PNG this repository generates and validates, so it is not attacker-controlled today, but the sink itself was unguarded. An anchored validator now lives in the shared filter module alongside its own tests, and the identical unguarded sink in the inspector's screenshot view uses it too. The focus-order tests shared one 150ms budget. Three of them have to complete inside it and finish in under 70ms on a developer machine, but a slower runner took 155ms and failed the branch. Those three now get a ceiling generous enough to survive a slow runner, which costs nothing because each exits as soon as its own condition holds, while the disabled target case keeps a short budget because reaching the deadline is exactly what it asserts. | 1 个月前 | |
fix: guard the debug viewer image sink and unpin a flaky test deadline CodeQL flagged the artifact viewer assigning a value read out of the JSON island straight into an image src. That value is a base64 PNG this repository generates and validates, so it is not attacker-controlled today, but the sink itself was unguarded. An anchored validator now lives in the shared filter module alongside its own tests, and the identical unguarded sink in the inspector's screenshot view uses it too. The focus-order tests shared one 150ms budget. Three of them have to complete inside it and finish in under 70ms on a developer machine, but a slower runner took 155ms and failed the branch. Those three now get a ceiling generous enough to survive a slow runner, which costs nothing because each exits as soon as its own condition holds, while the disabled target case keeps a short budget because reaching the deadline is exactly what it asserts. | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 5 天前 | ||
| 5 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 14 天前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 1 个月前 |