-
Notifications
You must be signed in to change notification settings - Fork 194
146 lines (131 loc) · 6.53 KB
/
Copy pathconformance-differential.yml
File metadata and controls
146 lines (131 loc) · 6.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
name: Conformance Differential
# Layer 3 of the Maestro conformance oracle: run a small set of flows through
# BOTH real Maestro and agent-device on a live device and compare app-observable
# outcomes, plus assert engine-side timing invariants over agent-device's own
# replay trace. This is the only layer that can catch settle-loop ordering, which
# has no reflectable upstream constant.
#
# Scheduled: this path is proven end-to-end (run 29504440599 executed both
# engines against the real fixture app). Scenarios that currently diverge are
# DECLARED via `knownDivergence` in differential/scenarios.ts with a tracking
# issue, so the schedule stays green on known gaps and fails only on undeclared
# ones — the same contract layer 1 has. A declaration that stops reproducing also
# fails, so a fix cannot land while leaving the oracle blind.
on:
schedule:
- cron: "0 5 * * *"
workflow_dispatch:
inputs:
only:
description: "Single scenario id to run (default: all)"
required: false
permissions:
contents: read
# setup-fixture-app looks up the cached test-app binary via the artifacts API.
actions: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
AGENT_DEVICE_CLI: "--experimental-strip-types src/bin.ts"
DIFFERENTIAL_ONLY: ${{ github.event.inputs.only || '' }}
# CI should not phone home, and it keeps `maestro --version` to just the
# version instead of prefixing an analytics notice.
MAESTRO_CLI_NO_ANALYTICS: "1"
jobs:
differential-ios:
name: iOS Conformance Differential
runs-on: macos-26
timeout-minutes: 90
env:
IOS_RUNTIME_VERSION: "26.2"
AGENT_DEVICE_STATE_DIR: ${{ github.workspace }}/.tmp/agent-device-state
AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH: ${{ github.workspace }}/.tmp/ios-runner-derived
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup toolchain
uses: ./.github/actions/setup-node-pnpm
- name: Setup Apple replay
uses: ./.github/actions/setup-apple-replay
with:
derived-path: ${{ env.AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH }}
cache-key-prefix: ios-runner-prebuilt
cache-key-suffix: -ios-${{ env.IOS_RUNTIME_VERSION }}
build-command: sh ./scripts/build-xcuitest-apple.sh
xcuitest-platform: ios
- name: Boot simulator
uses: ./.github/actions/boot-ios-test-simulator
with:
runtime-version: ${{ env.IOS_RUNTIME_VERSION }}
preferred-device-name: iPhone 17 Pro
# Installs the cached Release test-app and refreshes its JS. Shared with any
# other job that needs a controlled app to drive (see #320) — the artifact
# is repo-wide, so once the producer builds a fingerprint every workflow
# gets it as a download.
- name: Setup fixture app
id: fixture-app
uses: ./.github/actions/setup-fixture-app
with:
device-name: iPhone 17 Pro
# The action guarantees *an* app is installed; this asserts it is the one
# the scenarios actually target, so a fixture rename cannot leave the
# differential driving the wrong app.
- name: Verify the installed app is the one the scenarios target
run: |
set -euo pipefail
EXPECTED=$(node --experimental-strip-types -e \
"import('./scripts/maestro-conformance/differential/scenarios.ts').then(m => console.log(m.DIFFERENTIAL_APP_ID))")
ACTUAL="${{ steps.fixture-app.outputs.app-id }}"
echo "expected=$EXPECTED actual=$ACTUAL (source=${{ steps.fixture-app.outputs.source }})"
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "::error::Installed fixture app is $ACTUAL but the scenarios target $EXPECTED; they would fail vacuously."
exit 1
fi
# Pin the Maestro CLI to the SAME version the oracle pins for layers 1-2,
# read from pinned-upstream.json so the two can never drift apart. The
# installer honours MAESTRO_VERSION; assert what actually landed.
- name: Install pinned Maestro CLI
run: |
set -euo pipefail
MAESTRO_VERSION=$(node -p "require('./scripts/maestro-conformance/pinned-upstream.json').version")
export MAESTRO_VERSION
echo "Pinning Maestro CLI to $MAESTRO_VERSION (from pinned-upstream.json)"
curl -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name: Verify the Maestro CLI matches the oracle pin
run: |
set -euo pipefail
EXPECTED=$(node -p "require('./scripts/maestro-conformance/pinned-upstream.json').version")
# `maestro --version` can print notices (e.g. the analytics banner)
# around the version, so match the semver line rather than the whole
# output. Empty ACTUAL fails the comparison below, as it should.
ACTUAL=$(maestro --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | tail -1 || true)
echo "expected=$EXPECTED actual=$ACTUAL"
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "::error::Layer-3 Maestro CLI is $ACTUAL but the oracle pins $EXPECTED. A differential against a different Maestro proves nothing about the pinned behavior."
exit 1
fi
# --trace-root lets the runner find agent-device's replay-timing.ndjson and
# assert engine-side invariants (e.g. the settle loop latches instead of
# burning its budget) — outcome parity alone cannot see that.
- name: Run differential
run: |
node --experimental-strip-types scripts/maestro-conformance/differential/run.ts \
--platform ios \
--out-dir "${{ github.workspace }}/.tmp/conformance-differential" \
--trace-root "${{ github.workspace }}/.agent-device" \
${DIFFERENTIAL_ONLY:+--only "$DIFFERENTIAL_ONLY"}
# Keep the trace, not just the verdict: the engine-side invariants are
# computed FROM replay-timing.ndjson, so without it a report saying
# "invariant violated: tapRetries was 0" cannot be audited or debugged
# after the runner is gone.
- name: Upload report and trace
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: conformance-differential-ios
path: |
.tmp/conformance-differential/differential-report.json
.agent-device/**/replay-timing.ndjson
if-no-files-found: ignore