Skip to content

Commit da51596

Browse files
committed
Merge remote-tracking branch 'origin/main' into prep/3487
* origin/main: (79 commits) feat(ios): interpret React Native accessibility value semantics (#3589) fix(android): reinstall and reselect the test IME helper after it disappears (#3606) feat(snapshot): carry provider keyboard observations through acquisition (#3593) chore(web): bump managed agent-browser backend to 0.39.0 (#3591) fix(fold): report a dispatched pose as unverified when devicectl has no hinge-angle stream (#3587) feat(android): expose system-bar membership on snapshot nodes (#3585) fix(android): align ADB and Appium field values (#3584) test(closure): read committed JSON modules on the merge-base side (#3602) fix(ci): preserve manual Android suite timing (#3554) refactor(daemon): make the layer folders the declaration (PR 10) (#3520) refactor(move): extract the daemon server into @agent-device/daemon (11b) (#3546) refactor(daemon): package-local runtime fakes for the remaining daemon ports [11a-4] (#3544) refactor(daemon): cut daemon test edges into root data fixtures and fakes [11a-3] (#3517) refactor(11a-2): package-local fakes at the daemon ports; root-own command-surface composition tests (#3519) test(replay): verify Settings queries and bound scrolling (#3560) feat(snapshot): measure Android Appium viewport (#3586) fix(ios): verify a fill whose unidentified field moved while it kept focus (#3596) refactor(coverage): one fallow config with per-function CRAP ceilings (#3588) test: bound CLI promotion cases and align provider budget (#3590) fix(metro): report what the dev-server stop proved instead of assuming success (#3575) ...
2 parents e38bc9e + cfa9653 commit da51596

1,503 files changed

Lines changed: 33469 additions & 12564 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.device-evidence/CHECKLIST.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ appended warning:
9999
### Proof the review asked for on this lane
100100

101101
The false-attribution class is covered by unit tests rather than a device run, because the device
102-
cannot show a cache hit on command: `src/daemon/__tests__/capture-disclosure-target-activation.test.ts`
103-
pins that a cache-reused tree discloses nothing, and
104-
`src/daemon/interaction/internal/__tests__/interaction-target-activation-disclosure.test.ts` pins that
102+
cannot show a cache hit on command: `packages/daemon/src/execution/__tests__/selector-capture-runtime.test.ts`
103+
drives a session-snapshot cache hit and pins that the consumed tree earns no repair proof and
104+
discloses nothing, `packages/daemon/src/core/__tests__/capture-disclosure-target-activation.test.ts`
105+
pins that the response builder never borrows a repair the stored snapshot happens to carry, and
106+
`src/__tests__/daemon-composition/interaction/interaction-capture-disclosure.test.ts` pins that
105107
a press which consumed no capture is not disclosed against an older tree.
106108

107109
## Why the live activation lane is manual

‎.fallowrc.json‎

Lines changed: 73 additions & 298 deletions
Large diffs are not rendered by default.

‎.github/workflows/ci.yml‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,11 @@ jobs:
324324
# Gated on the coverage step's own outcome (#1781 A5): when `Run coverage`
325325
# fails, lcov.info is never written, so this step would just re-report that
326326
# failure as its own red ("no lcov report") instead of a coverage verdict.
327+
# `!cancelled()` keeps it running when an unrelated step around it failed;
328+
# it still needs `run-coverage` success because without lcov there is no
329+
# verdict — only the coverage step's own red.
327330
- name: Enforce changed-line coverage gate
328-
if: steps.run-coverage.outcome == 'success' && github.event_name == 'pull_request'
331+
if: ${{ !cancelled() && steps.run-coverage.outcome == 'success' && github.event_name == 'pull_request' }}
329332
env:
330333
AGENT_DEVICE_COVERAGE_WAIVER: ${{ contains(github.event.pull_request.labels.*.name, 'coverage-waiver') }}
331334
uses: ./.github/actions/run-gate
@@ -335,21 +338,23 @@ jobs:
335338
--base
336339
${{ github.event.pull_request.base.sha }}
337340
338-
# Report-only: scores the functions this PR touches with the coverage map the coverage step
339-
# wrote (fallow-crap.json), as a job summary and warning annotations. It never fails the job:
340-
# a tooling error marks only this step. `!cancelled()` keeps it running after a failed
341-
# changed-line gate.
342-
- name: Report coverage-based CRAP
341+
# Blocking: scores the functions this PR touches against the coverage map the coverage step
342+
# wrote, using the ceilings in `.fallowrc.json` and the CLI's `--max-crap` threshold, and
343+
# fails the job when one is at or above the threshold, or when the map cannot be trusted to
344+
# answer. `!cancelled()` keeps it running after a failed changed-line gate; it still needs
345+
# `run-coverage` to have succeeded, because without a coverage map there is no verdict to
346+
# report — only the coverage step's own red.
347+
- name: Check coverage-based CRAP of touched functions
343348
if: ${{ !cancelled() && steps.run-coverage.outcome == 'success' && github.event_name == 'pull_request' }}
344-
continue-on-error: true
345349
uses: ./.github/actions/run-gate
346350
with:
347351
gate: coverage-crap
348352
args: |
349353
--base
350354
${{ github.event.pull_request.base.sha }}
351355
352-
# On main, the same report covers every function: the backlog of untested complex code.
356+
# Report-only on main: the same scoring over every function is the backlog of untested
357+
# complex code, which `main` carries on purpose, so it stays advisory.
353358
- name: Report coverage-based CRAP for the whole repository
354359
if: ${{ !cancelled() && steps.run-coverage.outcome == 'success' && github.event_name == 'push' }}
355360
continue-on-error: true

‎.github/workflows/mutation-affected.yml‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Mutation Affected
1+
name: Mutation Harness Canary
22

33
# PR-side half of the decision-kernel mutation lane (issue #1415).
44
#
@@ -9,7 +9,7 @@ name: Mutation Affected
99
# spends mutants on one thing only — proving the harness still runs end to end
1010
# when the harness itself changes. `select` therefore returns an empty matrix
1111
# unless the diff touches the lane's own sources. Selecting on derived kernel
12-
# ownership instead would run the full ten-shard sweep on 24 of the last 40
12+
# ownership instead would run the full sweep on 24 of the last 40
1313
# merged PRs.
1414
#
1515
# On a harness diff the matrix is the canary (`LANE_CANARY`, selected by
@@ -22,14 +22,20 @@ name: Mutation Affected
2222
on:
2323
pull_request:
2424
paths:
25-
# Exactly `LANE_TOOLING` in scripts/mutation/run.ts plus this file — the
25+
# Exactly `LANE_TOOLING` in scripts/mutation/run.ts — the
2626
# only diffs that can produce a non-empty matrix, asserted both ways by
2727
# scripts/mutation/workflow.test.ts. A kernel or test path here would only
2828
# start a job that selects nothing.
2929
- 'scripts/mutation/**'
3030
- 'scripts/lib/**'
3131
- 'stryker.config.json'
32+
- 'vitest.mutation.config.ts'
33+
- 'vitest.config.ts'
34+
- '.github/workflows/mutation-weekly.yml'
3235
- '.github/workflows/mutation-affected.yml'
36+
- 'package.json'
37+
- 'pnpm-lock.yaml'
38+
- 'packages/*/package.json'
3339

3440
permissions:
3541
contents: read
@@ -40,7 +46,7 @@ concurrency:
4046

4147
jobs:
4248
select:
43-
name: Select affected kernels
49+
name: Select harness validation kernels
4450
runs-on: ubuntu-latest
4551
timeout-minutes: 10
4652
outputs:
@@ -71,13 +77,13 @@ jobs:
7177
# The self-test and the derivation run before any mutant, so a failure here
7278
# would leave the lane with no envelope at all (#1430).
7379
- name: Record a failed lane envelope
74-
if: failure()
80+
if: failure() || cancelled()
7581
run: |
7682
pnpm gate mutation --affected --fail-envelope \
7783
"affected selection failed before any mutant ran (run ${{ github.run_id }})" || true
7884
7985
- name: Upload selection envelope
80-
if: failure()
86+
if: failure() || cancelled()
8187
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
8288
with:
8389
name: mutation-affected-select
@@ -97,12 +103,15 @@ jobs:
97103
include: ${{ fromJSON(needs.select.outputs.modules) }}
98104
steps:
99105
- name: Checkout
106+
timeout-minutes: 1
100107
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
101108

102109
- name: Setup toolchain
110+
timeout-minutes: 2
103111
uses: ./.github/actions/setup-node-pnpm
104112

105113
- name: Run mutants for ${{ matrix.name }}
114+
timeout-minutes: 22
106115
uses: ./.github/actions/run-gate
107116
with:
108117
gate: mutation
@@ -113,7 +122,7 @@ jobs:
113122
${{ matrix.shard || '' }}
114123
115124
- name: Record a failed shard envelope
116-
if: failure()
125+
if: failure() || cancelled()
117126
run: |
118127
pnpm gate mutation --affected --modules ${{ matrix.module }} --fail-envelope \
119128
"shard ${{ matrix.name }} failed before producing a report (run ${{ github.run_id }})" || true
@@ -167,7 +176,7 @@ jobs:
167176
fi
168177
169178
- name: Record a failed lane envelope
170-
if: failure()
179+
if: failure() || cancelled()
171180
run: |
172181
pnpm gate mutation --affected --fail-envelope \
173182
"affected report failed before producing a score table (run ${{ github.run_id }})" || true

‎.github/workflows/mutation-weekly.yml‎

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ name: Mutation Weekly
55
# decorative"; a full-suite sweep is unaffordable, so the scope is the kernel
66
# registry in scripts/mutation/modules.ts and nothing else.
77
#
8-
# Sharded one job per module: the whole sweep is ~2,150 mutants, and the selector
9-
# module alone is ~1,280 of them, so a single job would sit near the 30-minute
10-
# acceptance budget on an ubuntu runner. The shards' JSON reports are merged into
11-
# one score table by the report job (`--report-dir`).
8+
# Sharded from the decision-kernel registry. Oversized sources are partitioned
9+
# at syntax boundaries; the report job merges all required shards into one table.
1210
#
1311
# The lane reports and never gates (#1457): the per-kernel table lands in the job
1412
# summary and the artifact, and a low score is an input for a human-authored
@@ -31,9 +29,7 @@ jobs:
3129
shard:
3230
name: Mutants (${{ matrix.name }})
3331
runs-on: ubuntu-latest
34-
# The acceptance budget is 30 minutes of wall clock for the lane, and shards
35-
# run in parallel, so the budget is per shard. The observed rate on a 2-core
36-
# runner is ~3s/mutant, which is why selectors (~1,280 mutants) is sliced.
32+
# Bound setup and mutation separately, leaving headroom for evidence and upload.
3733
timeout-minutes: 30
3834
strategy:
3935
fail-fast: false
@@ -42,24 +38,35 @@ jobs:
4238
# scripts/mutation/workflow.test.ts, which also pins --expect-shards to
4339
# the number of entries here.
4440
include:
45-
- { name: kernel-errors, module: kernel-errors }
41+
- { name: kernel-errors-1, module: kernel-errors, shard: 1/5 }
42+
- { name: kernel-errors-2, module: kernel-errors, shard: 2/5 }
43+
- { name: kernel-errors-3, module: kernel-errors, shard: 3/5 }
44+
- { name: kernel-errors-4, module: kernel-errors, shard: 4/5 }
45+
- { name: kernel-errors-5, module: kernel-errors, shard: 5/5 }
4646
- { name: daemon-ref-frame, module: daemon-ref-frame }
4747
- { name: interaction-settle, module: interaction-settle }
4848
- { name: scroll-edge-state, module: scroll-edge-state }
49-
- { name: selectors-1, module: selectors, shard: 1/4 }
50-
- { name: selectors-2, module: selectors, shard: 2/4 }
51-
- { name: selectors-3, module: selectors, shard: 3/4 }
52-
- { name: selectors-4, module: selectors, shard: 4/4 }
49+
- { name: selectors-1, module: selectors, shard: 1/8 }
50+
- { name: selectors-2, module: selectors, shard: 2/8 }
51+
- { name: selectors-3, module: selectors, shard: 3/8 }
52+
- { name: selectors-4, module: selectors, shard: 4/8 }
53+
- { name: selectors-5, module: selectors, shard: 5/8 }
54+
- { name: selectors-6, module: selectors, shard: 6/8 }
55+
- { name: selectors-7, module: selectors, shard: 7/8 }
56+
- { name: selectors-8, module: selectors, shard: 8/8 }
5357
- { name: target-annotation-serde, module: target-annotation-serde }
5458
- { name: snapshot-occlusion, module: snapshot-occlusion }
5559
steps:
5660
- name: Checkout
61+
timeout-minutes: 1
5762
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5863

5964
- name: Setup toolchain
65+
timeout-minutes: 2
6066
uses: ./.github/actions/setup-node-pnpm
6167

6268
- name: Run mutants for ${{ matrix.name }}
69+
timeout-minutes: 22
6370
uses: ./.github/actions/run-gate
6471
with:
6572
gate: mutation
@@ -73,7 +80,7 @@ jobs:
7380
# without this the artifact is absent and "shard failed" is indistinguishable
7481
# from "lane never ran". --fail-envelope leaves a real verdict untouched.
7582
- name: Record a failed shard envelope
76-
if: failure()
83+
if: failure() || cancelled()
7784
run: |
7885
pnpm gate mutation --modules ${{ matrix.module }} --fail-envelope \
7986
"shard ${{ matrix.name }} failed before producing a report (run ${{ github.run_id }})" || true
@@ -126,12 +133,12 @@ jobs:
126133
--report-dir
127134
.tmp/mutation/shards
128135
--expect-shards
129-
10
136+
18
130137
131138
# The self-test and the artifact download both run before the scoring step,
132139
# so a failure there would otherwise leave the aggregate lane with no envelope.
133140
- name: Record a failed lane envelope
134-
if: failure()
141+
if: failure() || cancelled()
135142
run: |
136143
pnpm gate mutation --fail-envelope \
137144
"weekly report job failed before producing a score table (run ${{ github.run_id }})" || true

‎.github/workflows/replays-manual.yml‎

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,7 @@ jobs:
5858
profile: pixel_7
5959
target: google_apis_playstore
6060
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -no-metrics
61-
script: |
62-
# android-emulator-runner executes this with /usr/bin/sh, which is dash on
63-
# ubuntu runners and rejects `-o pipefail`. The serial assignment below is
64-
# validated by the `test -n` guard instead.
65-
set -eu
66-
ANDROID_SERIAL="$(adb devices | awk 'NR > 1 && $2 == "device" { print $1; exit }')"
67-
test -n "$ANDROID_SERIAL"
68-
BOOT_FINISHED_AT="$(date +%s)"
69-
pnpm gate build
70-
pnpm clean:daemon
71-
adb -s "$ANDROID_SERIAL" install -r "${{ steps.fixture-app.outputs.apk-path }}"
72-
node --experimental-strip-types src/bin.ts replay examples/test-app/replays/drag-android.ad --platform android --serial "$ANDROID_SERIAL" --session nightly-android-target-drag --json
73-
pnpm clean:daemon
74-
FULL_STARTED_AT="$(date +%s)"
75-
AGENT_DEVICE_ANDROID_E2E=1 AGENT_DEVICE_ANDROID_E2E_TIER=full AGENT_DEVICE_ANDROID_SERIAL="$ANDROID_SERIAL" AGENT_DEVICE_FIXTURE_APP_PATH="${{ steps.fixture-app.outputs.apk-path }}" AGENT_DEVICE_FIXTURE_APP_ID="${{ steps.fixture-app.outputs.app-id }}" node --experimental-strip-types scripts/node-test-tmpdir.ts --test test/integration/smoke-android-emulator.test.ts
76-
FULL_FINISHED_AT="$(date +%s)"
77-
echo "Android setup before emulator: $((BOOT_FINISHED_AT - ${{ steps.android-setup.outputs.seconds }}))s"
78-
echo "Android full scenario wall time: $((FULL_FINISHED_AT - FULL_STARTED_AT))s"
79-
pnpm gate replay-android --retries 2 --artifacts-dir test/artifacts/replays-android --report-junit test/artifacts/replays-android.junit.xml
61+
script: sh scripts/replays/android-manual.sh "${{ steps.fixture-app.outputs.apk-path }}" "${{ steps.fixture-app.outputs.app-id }}" "${{ steps.android-setup.outputs.seconds }}"
8062

8163
- name: Upload Android artifacts
8264
if: always()

‎.oxfmtrc.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"scripts/maestro-conformance/corpus/**",
1313
"fallow-baselines/**",
1414
// One entry per line, so each pinned runner request changes as one reviewable diff line.
15-
"contracts/fixtures/runner-requests.json"
15+
"contracts/fixtures/runner-requests.json",
16+
// One captured node per line, so the real keyboard capture stays a compact reviewable table.
17+
"packages/capture-kit/src/snapshot-chrome.fixtures.ts"
1618
]
1719
}

‎AGENTS.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ normal app-driving startup follows the skill's routing card.
3636
in names, types, boundaries, and tests; reserve comments for public API docs, tool directives,
3737
and non-obvious constraints that cannot be encoded.
3838
- Key behavior on typed reasons and details, never error text; do not copy existing message sniffs.
39+
- A claim about a platform family in code, help, or docs holds for every backend in it (Appium as
40+
well as ADB on Android), or names the backends it covers.
3941
- Keep metadata at response level when it applies to the whole snapshot. Append warnings through
4042
the shared response builder; never replace prior warnings.
4143
- Before preserving a compatibility shape, run `git tag --contains <commit>`. Unreleased API has no
@@ -47,11 +49,11 @@ Read the declaration rather than maintaining a prose copy:
4749

4850
- commands and their surface, runtime-use, batch, and timeout traits:
4951
`packages/command-registry/src/registry.ts`
50-
- daemon route ownership and request-policy traits: `src/daemon/daemon-command-registry.ts`
52+
- daemon route ownership and request-policy traits: `packages/daemon/src/core/daemon-command-registry.ts`
5153
- interaction paths and guarantees: `packages/contracts/src/interaction-guarantees.ts`
5254
- canonical command names: `packages/command-registry/src/catalog.ts`
5355
- device runtime-use declarations and fact admission:
54-
`packages/command-registry/src/registry.ts`, `src/daemon/runtime-admission.ts`, and
56+
`packages/command-registry/src/registry.ts`, `packages/daemon/src/core/runtime-admission.ts`, and
5557
`src/platform-runtime-gateway.ts`
5658
- common command input fields, and which surface may write an input key (model, operator, retired):
5759
`src/commands/common-input-fields.ts` and `src/commands/input-audience.ts`
@@ -108,7 +110,7 @@ under `contracts/fixtures/`.
108110
## Runtime and diagnostics seams
109111

110112
Diagnostics use `@agent-device/host-kit/diagnostics`. Request diagnostics belong in the session request log;
111-
session artifact paths come from `src/daemon/session-artifact-paths.ts`. App/device logs remain in `app.log`;
113+
session artifact paths come from `packages/daemon/src/core/session-artifact-paths.ts`. App/device logs remain in `app.log`;
112114
Apple runner and xcodebuild output remains in `runner.log`.
113115

114116
Normalize failures with `normalizeError` and preserve `hint`, `diagnosticId`, `logPath`, and typed

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Carry provider keyboard facts through snapshots. Appium captures, including wait polls and fill pre-captures, add one sequential keyboard visibility probe with a 500 ms timeout. Probe results preserve tap-time docked-tree checks; unavailable probes leave visibility unknown.

0 commit comments

Comments
 (0)