Skip to content

feat: add strict native absence assertion - #2245

Merged
thymikee merged 2 commits into
mainfrom
feat/is-absent-2236
Sep 3, 2026
Merged

feat: add strict native absence assertion#2245
thymikee merged 2 commits into
mainfrom
feat/is-absent-2236

Conversation

@thymikee

@thymikee thymikee commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

Adds the strict native is absent <selector> assertion for zero matches in one readable, complete, unscoped, full-depth accessibility capture. It uses the existing readAny selector match domain, requests no rectangles, refuses --scope and --depth, and fails closed with typed evidence for sparse, truncated, or unreadable captures. Existing is hidden semantics are unchanged.

The predicate is available in the CLI, MCP metadata, SDK types, and help/docs, with both positional forms supported:

agent-device is absent 'label="Loading..."'
agent-device is 'label="Loading..."' absent

Present failures report only the match count and bounded first-match identity/text fields; multiple matches advise refining the selector. Authored is absent observations record without target-v1 annotation, and replay failures remain action-failures rather than identity mismatches.

Refs #2236.

PR A coordination:

  • Base: main
  • Exact base commit: d04e213bcc841cd8d75361844aec871cbbd5a831
  • Branch: feat/is-absent-2236
  • Exact PR A head: 97c555c3876e1ee4f2041302a820113db0c6cf43
  • PR B (wait absent) must stack on this exact head (feat/is-absent-2236 at 97c555c3876e1ee4f2041302a820113db0c6cf43), not on main.
  • No merge requested; leave this PR for review.

Validation

  • Planted-red regressions were observed before the fix for strict absence positional/runtime behavior, cached-negative freshness, bounded first-match evidence, vocabulary/docs completeness, and the quality-less legacy iOS root-only sparse-capture case. Live iOS and Android scenarios now close a fixture control, assert it is absent, and verify the command path.
  • Focused final unit suite: 10 files, 84 tests passed. Structural closure and file-size gates: 2 files, 410 tests passed. Typecheck and repository formatting checks passed.
  • pnpm check:affected --run on this exact base/head: the first run was host-contended (4,328/4,345 tests passed); the retry passed 5,204/5,205 tests, with one unchanged provider-backed iOS lifecycle test timing out at its 5-second budget. That test passed in isolation (3/3), and all other runnable checks in the retry were green. GitHub remains authoritative for native/device/coverage lanes.
  • Independent adversarial read-only review of the final diff reported no valid in-scope findings.
  • Native/device, GitHub CI, and full coverage lanes were not run locally; GitHub remains authoritative for those lanes. This PR is published for review and is not merged.
  • Touched files: 40. Docs/ADR updated: website/docs/docs/commands.md, examples/test-app/README.md, and ADR 0012 replay semantics.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-03 06:02 UTC

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.58 MB 2.58 MB +4.1 kB
JS gzip 868.4 kB 869.6 kB +1.2 kB
npm bundled raw 2.58 MB 2.58 MB +4.1 kB
npm bundled gzip 868.4 kB 869.6 kB +1.2 kB
npm tarball 1.02 MB 1.02 MB +1.3 kB
npm unpacked 3.46 MB 3.46 MB +4.4 kB
npm clean-installed 3.46 MB 3.46 MB +4.4 kB

npm unpacked components

Component Base Current Diff
JS / dist source 2.74 MB 2.74 MB +4.4 kB
Apple runner source/project 544.4 kB 544.4 kB 0 B
Apple snapshot presentation source 33.8 kB 33.8 kB 0 B
macOS helper source 54.8 kB 54.8 kB 0 B
Android helper artifacts 43.3 kB 43.3 kB 0 B
Other package files 46.3 kB 46.3 kB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 24.3 ms 25.2 ms +0.9 ms
CLI --help 62.1 ms 65.3 ms +3.2 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/runtime.js +3.1 kB +852 B
dist/src/registry.js +446 B +176 B
dist/src/app-inventory-contract.js +287 B +93 B
dist/src/sdk-batch-runner.js +108 B +68 B
dist/src/cli-help.js +22 B +17 B

Top changed packed files

Packed file Base Current Diff
dist/src/runtime.js 69.2 kB 72.2 kB +3.1 kB
dist/src/registry.js 170.4 kB 170.9 kB +446 B
dist/src/client-types.d.ts 56.9 kB 57.2 kB +291 B
dist/src/app-inventory-contract.js 51.7 kB 52.0 kB +287 B
dist/src/selector-runtime.js 18.5 kB 18.6 kB +160 B
dist/src/sdk-batch-runner.js 82.1 kB 82.2 kB +108 B
dist/src/cli-help.js 92.3 kB 92.3 kB +22 B
dist/src/snapshot-runtime.js 8.6 kB 8.6 kB -5 B
dist/src/session2.js 217.7 kB 217.7 kB -3 B

@thymikee

thymikee commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

BLOCKED — the public contract is unchosen and this head has two correctness defects plus owner-action CI failures.

  1. [P1] Record the maintainer contract before landing a public absence surface. Feature request: native absence assertion (is absent / wait gone) #2236 is still needs-triage, and its maintainer decision explicitly says work must wait for a choice between zero selector matches vs zero visible matches, canonical is vs polling wait, and replay/error behavior. This PR chooses strict zero-match is absent, changes CLI/MCP/SDK/daemon and amends ADR 0012 without that decision. Resolve and record the contract first.

  2. [P1] Force a fresh capture for negative proof. The daemon sets forceFresh only for wait/find/web-rect reads. is absent requests no rects, so it can reuse a same-presentation session snapshot younger than 750 ms and return pass after the UI has mounted a matching element asynchronously. Negative proof must bypass both request-local and session caches. Add a route regression that seeds an absent cached tree, makes the bound fresh capture present, and proves the assertion fails.

  3. [P2] Actually bound first-match evidence. stableFirstMatch uses capped local identity fields, then copies extractNodeText(node) verbatim into firstMatch.text; that helper only trims. A long accessibility value therefore makes error details unbounded despite the PR contract and response-budget rule. Apply an explicit UTF-8 byte cap, avoid duplicating label as text, and plant an oversized-value regression.

  4. [P1 owner-action CI] Fix the structural failures at their owners. Coverage’s 9,236 ordinary tests pass, then repository guards fail: the new contracts export has no eager-closure row; ad-replay 61→62, maestro 110→111, selectors 54→55, CLI 379→382, and descriptor registry 71→72 exceed exact pins; selector-read.test.ts is a new 1,079-line over-tripwire file. Do not merely raise every budget. Keep runtime predicate vocabulary/capture errors behind the existing selector owners so unrelated entry surfaces stay lazy, and extract the absence/is runtime plus its tests along the source boundary—selector-read.ts is now 592 lines, beyond the repository’s extract-before-adding threshold.

The zero-match readAny domain, sparse/truncated fail-closed behavior, positional/API projection and replay action-failure classification otherwise look sound. After the contract and code are corrected, include practical iOS and Android evidence for an element unmounting between captures; current smoke lanes do not demonstrate the new command path. Do not apply ready-for-human while Coverage is red or #2236 remains undecided.

@thymikee
thymikee force-pushed the feat/is-absent-2236 branch from b3c898a to 60a3f0e Compare September 2, 2026 20:06
@thymikee
thymikee force-pushed the feat/is-absent-2236 branch from 60a3f0e to 97c555c Compare September 2, 2026 20:42
@thymikee

thymikee commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the maintainer review on the current PR A head:

  • Contract: recorded strict zero-match readAny semantics for is absent, with ordinary observation recording, action-failure replay classification, and ADR 0012 updates; hidden remains unchanged.
  • Freshness: is absent now forces a fresh capture past both request-local and session caches; the regression seeds a recent cached absence and proves a fresh matching node fails the assertion.
  • Evidence: first-match identity and text are bounded to 256 UTF-8 bytes, duplicate label text is omitted, and multiple matches advise refining the selector without making geometry/visibility claims; oversized evidence is covered.
  • Ownership and CI: extracted the is runtime/tests into selector-is, kept the CLI flag refusal at its owning selector surface, and added the exact eager-closure rows; no compatibility shim or generic fallback was added.
  • Live evidence: iOS simulator and Android emulator scenarios close a fixture control, assert is absent, and verify the command path after the UI settles.

The preview and size-report bot comments are metadata only; there are no inline review threads or submitted reviews to resolve. The unchanged provider-backed iOS lifecycle timeout in local affected-gate retry passed in isolation; GitHub CI/device evidence remains authoritative.

PR A base: main @ d04e213bcc841cd8d75361844aec871cbbd5a831
PR A branch: feat/is-absent-2236
PR A exact head: 97c555c3876e1ee4f2041302a820113db0c6cf43

PR B (wait absent) must stack on this exact head, not on main.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 2, 2026
@thymikee

thymikee commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

READY at 97c555c3876e1ee4f2041302a820113db0c6cf43.

The current head is the same resolved head previously reviewed: it forces fresh capture for is absent, caps first-match text at 256 UTF-8 bytes without label duplication, records the strict replay contract in ADR 0012, and includes iOS Simulator and Android Emulator unmounted-control scenarios.

All exact-head CI lanes are now green; merge state is CLEAN. No concrete findings.

@thymikee
thymikee merged commit 2371ba9 into main Sep 3, 2026
20 checks passed
@thymikee
thymikee deleted the feat/is-absent-2236 branch September 3, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant