Skip to content

Commit e3cb6f3

Browse files
committed
refactor: derive gate ownership from workflow structure
1 parent a1a8c6f commit e3cb6f3

33 files changed

Lines changed: 434 additions & 1289 deletions

‎.github/actions/boot-ios-test-simulator/action.yml‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,12 @@ runs:
2424
steps:
2525
- name: Resolve and boot iOS test simulator
2626
id: boot
27-
env:
28-
INPUT_RUNTIME_VERSION: ${{ inputs.runtime-version }}
29-
INPUT_PREFERRED_DEVICE_NAME: ${{ inputs.preferred-device-name }}
30-
INPUT_BOOT_TIMEOUT_SECONDS: ${{ inputs.boot-timeout-seconds }}
3127
run: |
3228
set -euo pipefail
33-
RUNTIME_VERSION="$INPUT_RUNTIME_VERSION"
29+
RUNTIME_VERSION="${{ inputs.runtime-version }}"
3430
RUNTIME_TOKEN="SimRuntime.iOS-${RUNTIME_VERSION//./-}"
3531
export RUNTIME_TOKEN
36-
export PREFERRED_DEVICE_NAME="$INPUT_PREFERRED_DEVICE_NAME"
32+
export PREFERRED_DEVICE_NAME="${{ inputs.preferred-device-name }}"
3733
UDID="$(
3834
xcrun simctl list devices -j | node -e '
3935
const fs = require("node:fs");
@@ -62,7 +58,7 @@ runs:
6258
echo "simulator-udid=$UDID" >> "$GITHUB_OUTPUT"
6359
xcrun simctl shutdown all || true
6460
xcrun simctl boot "$UDID" || true
65-
TIMEOUT_SECONDS="$INPUT_BOOT_TIMEOUT_SECONDS"
61+
TIMEOUT_SECONDS="${{ inputs.boot-timeout-seconds }}"
6662
set +e
6763
perl -e 'alarm shift; exec @ARGV' "$TIMEOUT_SECONDS" xcrun simctl bootstatus "$UDID" -b
6864
BOOTSTATUS_EXIT="$?"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Run registered gate
2+
description: Run one CHECK_CATALOG entry named structurally by the workflow
3+
inputs:
4+
gate:
5+
description: Registered CHECK_CATALOG id
6+
required: true
7+
args:
8+
description: Optional arguments, one per line
9+
required: false
10+
default: ''
11+
outputs:
12+
result:
13+
description: Last stdout line, for gates that produce a matrix or scalar
14+
value: ${{ steps.run.outputs.result }}
15+
runs:
16+
using: composite
17+
steps:
18+
- id: run
19+
shell: bash
20+
env:
21+
INPUT_GATE: ${{ inputs.gate }}
22+
INPUT_ARGS: ${{ inputs.args }}
23+
run: |
24+
set -euo pipefail
25+
args=()
26+
while IFS= read -r arg; do
27+
if [ -n "$arg" ]; then
28+
args+=("$arg")
29+
fi
30+
done <<< "$INPUT_ARGS"
31+
output_file="$(mktemp)"
32+
pnpm gate "$INPUT_GATE" "${args[@]}" | tee "$output_file"
33+
{
34+
echo 'result<<__GATE_OUTPUT__'
35+
tail -n 1 "$output_file"
36+
echo '__GATE_OUTPUT__'
37+
} >> "$GITHUB_OUTPUT"

‎.github/actions/setup-android-replay-host/action.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ runs:
6767

6868
- name: Package npm-bundled Android helpers
6969
if: inputs.package-helpers == 'true' && steps.android-helpers-cache.outputs.cache-hit != 'true'
70-
run: |
71-
pnpm gate android-helpers
72-
shell: bash
70+
uses: ./.github/actions/run-gate
71+
with:
72+
gate: android-helpers
7373

7474
- name: Verify packaged Android helpers
7575
if: inputs.package-helpers == 'true'

‎.github/actions/setup-apple-runner-build/action.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ runs:
7676

7777
- name: Build Apple runner artifacts on cache miss
7878
if: steps.restore-runner-build.outputs.cache-hit != 'true'
79-
run: pnpm gate "$INPUT_GATE"
80-
shell: bash
79+
uses: ./.github/actions/run-gate
80+
with:
81+
gate: ${{ inputs.gate }}
8182
env:
82-
INPUT_GATE: ${{ inputs.gate }}
8383
AGENT_DEVICE_IOS_RUNNER_DERIVED_PATH: ${{ inputs.derived-path }}
8484
AGENT_DEVICE_XCUITEST_PLATFORM: ${{ inputs.xcuitest-platform }}
8585
AGENT_DEVICE_XCUITEST_DESTINATION: ${{ inputs.xcuitest-destination }}

‎.github/actions/setup-fixture-app/action.yml‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,12 @@ runs:
9494
shell: bash
9595
env:
9696
GH_TOKEN: ${{ github.token }}
97-
INPUT_PLATFORM: ${{ inputs.platform }}
98-
INPUT_WAIT_FOR_ARTIFACT_SECONDS: ${{ inputs.wait-for-artifact-seconds }}
99-
INPUT_REQUIRE_ARTIFACT: ${{ inputs.require-artifact }}
10097
run: |
10198
DEST="${{ github.workspace }}/.tmp/fixture-app"
10299
bash "$GITHUB_ACTION_PATH/fetch-artifact.sh" \
103-
"$INPUT_PLATFORM" "$DEST" "$INPUT_WAIT_FOR_ARTIFACT_SECONDS" \
100+
"${{ inputs.platform }}" "$DEST" "${{ inputs.wait-for-artifact-seconds }}" \
104101
"${{ github.repository }}" "${{ github.event.pull_request.head.sha || github.sha }}" \
105-
"$GITHUB_ACTION_PATH" "$INPUT_REQUIRE_ARTIFACT"
102+
"$GITHUB_ACTION_PATH" "${{ inputs.require-artifact }}"
106103
107104
# Fallback only: the producer builds the same way. `--device generic` avoids
108105
# needing a booted simulator; Release embeds the bundle so no Metro is needed.

‎.github/workflows/ci.yml‎

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ jobs:
8585
uses: ./.github/actions/setup-node-pnpm
8686

8787
- name: Run oxlint
88-
run: pnpm gate lint
88+
uses: ./.github/actions/run-gate
89+
with: { gate: lint }
8990

9091
- name: Check formatting
91-
run: pnpm gate format
92+
uses: ./.github/actions/run-gate
93+
with: { gate: format }
9294

9395
layering-guard:
9496
name: Layering Guard
@@ -111,14 +113,16 @@ jobs:
111113
# Generalizes the former inline commands/-import grep into a structured
112114
# import-direction lint over the resolved graph. See scripts/layering/check.ts
113115
# and CONTEXT.md (Architecture: folder DAG + layering lint).
114-
run: pnpm gate layering
116+
uses: ./.github/actions/run-gate
117+
with: { gate: layering }
115118

116119
- name: Check the depgraph report agrees with the gate
117120
# scripts/depgraph reads the same model as the gate, so its inversion count must
118121
# reproduce TYPE_INVERSION_BASELINE. Free two-sources check: if the tree changes
119122
# and only one side is updated, this fails and names the difference. Runs here
120123
# rather than in its own job so the two can never be green independently.
121-
run: pnpm gate depgraph
124+
uses: ./.github/actions/run-gate
125+
with: { gate: depgraph }
122126

123127
# Tests for the TMPDIR redirection itself, hidden the same way as the check above.
124128
#
@@ -129,7 +133,8 @@ jobs:
129133
# Starting a nested Vitest seconds before the full instrumented suite is a contention
130134
# risk with nothing to gain.
131135
- name: Check the tmpdir redirection model
132-
run: pnpm gate tmpdir-leaks-model
136+
uses: ./.github/actions/run-gate
137+
with: { gate: tmpdir-leaks-model }
133138

134139
affected-selector:
135140
name: Affected-check Selector
@@ -150,18 +155,21 @@ jobs:
150155
# The selector is fail-open and advisory (GitHub CI stays authoritative),
151156
# so the gate only guards the derivation model.
152157
- name: Check affected-selector model
153-
run: pnpm gate affected-selector
158+
uses: ./.github/actions/run-gate
159+
with: { gate: affected-selector }
154160

155161
# The gate-of-gates (#1429). It shares this job because it validates the
156162
# same artifact the selector is built on — CHECK_CATALOG's `ciJobs` — and
157163
# because a gate that proves the other gates are wired must not be the one
158164
# gate sitting in its own job, green on its own. Deterministic and
159165
# network-free: every input is a file in the checkout.
160166
- name: Check the gate manifest model
161-
run: pnpm gate gate-manifest-model
167+
uses: ./.github/actions/run-gate
168+
with: { gate: gate-manifest-model }
162169

163170
- name: Check every gate is owned, wired, and reachable
164-
run: pnpm gate gate-manifest
171+
uses: ./.github/actions/run-gate
172+
with: { gate: gate-manifest }
165173

166174
maestro-conformance:
167175
name: Maestro Conformance Oracle
@@ -183,7 +191,8 @@ jobs:
183191
# device-backed layer 3 runs on the scheduled conformance-differential
184192
# workflow. See scripts/maestro-conformance/README.md.
185193
- name: Verify Maestro conformance fixtures
186-
run: pnpm gate maestro-conformance
194+
uses: ./.github/actions/run-gate
195+
with: { gate: maestro-conformance }
187196

188197
packaged-cli-node-22-12:
189198
name: Packaged CLI Node 22.12
@@ -197,9 +206,12 @@ jobs:
197206
uses: ./.github/actions/setup-node-pnpm
198207

199208
- name: Build CLI
200-
run: |
201-
pnpm gate build
202-
pnpm gate bundle-owner-files
209+
uses: ./.github/actions/run-gate
210+
with: { gate: build }
211+
212+
- name: Verify emitted chunk ownership
213+
uses: ./.github/actions/run-gate
214+
with: { gate: bundle-owner-files }
203215

204216
# The build runs on the default toolchain Node and the package is verified on the minimum
205217
# supported Node, so this job covers what a user on `engines.node` floor actually installs.
@@ -233,10 +245,16 @@ jobs:
233245
- name: Run Fallow audit
234246
env:
235247
FALLOW_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
236-
run: pnpm gate fallow --base "$FALLOW_BASE"
248+
uses: ./.github/actions/run-gate
249+
with:
250+
gate: fallow
251+
args: |
252+
--base
253+
${{ env.FALLOW_BASE }}
237254
238255
- name: Check for production-unused exports
239-
run: pnpm gate production-exports
256+
uses: ./.github/actions/run-gate
257+
with: { gate: production-exports }
240258

241259
replay-compat-provenance:
242260
# The frozen replay-compat corpus (#1417) claims each entry was published by
@@ -256,7 +274,8 @@ jobs:
256274
uses: ./.github/actions/setup-node-pnpm
257275

258276
- name: Verify corpus entries against their released blobs
259-
run: pnpm gate replay-compat
277+
uses: ./.github/actions/run-gate
278+
with: { gate: replay-compat }
260279

261280
released-surface-compat:
262281
# The daemon RPC wire ledger (#1432) is compared against the ledger as it
@@ -277,10 +296,12 @@ jobs:
277296
uses: ./.github/actions/setup-node-pnpm
278297

279298
- name: Verify the wire-compat rules
280-
run: pnpm gate wire-compat-model
299+
uses: ./.github/actions/run-gate
300+
with: { gate: wire-compat-model }
281301

282302
- name: Compare the daemon RPC wire surface against the last released tag
283-
run: pnpm gate daemon-wire-compat
303+
uses: ./.github/actions/run-gate
304+
with: { gate: daemon-wire-compat }
284305

285306
coverage:
286307
# Runs the full unit + provider-integration suites under coverage with
@@ -298,26 +319,30 @@ jobs:
298319
uses: ./.github/actions/setup-node-pnpm
299320

300321
- name: Test changed-line coverage gate
301-
run: pnpm gate coverage-model
322+
uses: ./.github/actions/run-gate
323+
with: { gate: coverage-model }
302324

303325
# The retry list is an enumerated set of owned waivers, so an expired entry
304326
# must fail before the suite runs rather than quietly keeping its retry.
305327
- name: Check contention retry policy
306-
run: pnpm gate contention-retry
328+
uses: ./.github/actions/run-gate
329+
with: { gate: contention-retry }
307330

308331
# Wrapped in the single-retry policy (#1419): a timeout-shaped failure in
309332
# an enumerated contention-flaky file reruns that file once and reports it
310333
# in the job summary. Assertion failures fail here on the first run.
311334
- name: Run coverage
312335
env:
313336
OUTPUT_ECONOMY_BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
314-
run: pnpm gate unit-ci
337+
uses: ./.github/actions/run-gate
338+
with: { gate: unit-ci }
315339
# The TMPDIR redirection both test lanes depend on (#1593/#1595). The check is a real
316340
# package script that no workflow ran: it is reachable only through `check:unit`, an
317341
# aggregate CI never invokes, so a leak regression could not fail a PR. Placed here
318342
# because this is the lane whose instrumented suite would leak a run directory.
319343
- name: Check for leaked temp directories
320-
run: pnpm gate tmpdir-leaks
344+
uses: ./.github/actions/run-gate
345+
with: { gate: tmpdir-leaks }
321346

322347
- name: Upload contention-retry envelope
323348
if: always()
@@ -335,7 +360,12 @@ jobs:
335360
if: always() && github.event_name == 'pull_request'
336361
env:
337362
AGENT_DEVICE_COVERAGE_WAIVER: ${{ contains(github.event.pull_request.labels.*.name, 'coverage-waiver') }}
338-
run: pnpm gate coverage --base "${{ github.event.pull_request.base.sha }}"
363+
uses: ./.github/actions/run-gate
364+
with:
365+
gate: coverage
366+
args: |
367+
--base
368+
${{ github.event.pull_request.base.sha }}
339369
340370
typecheck:
341371
name: Typecheck
@@ -349,14 +379,16 @@ jobs:
349379
uses: ./.github/actions/setup-node-pnpm
350380

351381
- name: Run typecheck
352-
run: pnpm gate typecheck
382+
uses: ./.github/actions/run-gate
383+
with: { gate: typecheck }
353384

354385
# CHECK_CATALOG has always claimed this job mirrors the `mcp-metadata`
355386
# check; until #1429 checked the claim, no PR job ran it at all, so
356387
# server.json/smithery.yaml drift only surfaced at publish time (where
357388
# publish-mcp-registry.yml duplicates the same command). Parse-only.
358389
- name: Check MCP registry metadata is in sync
359-
run: pnpm gate mcp-metadata
390+
uses: ./.github/actions/run-gate
391+
with: { gate: mcp-metadata }
360392

361393
freerange:
362394
name: FreeRange
@@ -373,7 +405,8 @@ jobs:
373405
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
374406

375407
- name: Check numeric ranges
376-
run: pnpm gate freerange
408+
uses: ./.github/actions/run-gate
409+
with: { gate: freerange }
377410

378411
integration:
379412
name: Integration Tests
@@ -387,33 +420,40 @@ jobs:
387420
uses: ./.github/actions/setup-node-pnpm
388421

389422
- name: Run integration tests
390-
run: |
391-
pnpm clean:daemon
392-
pnpm gate integration-node
423+
run: pnpm clean:daemon
424+
425+
- name: Execute integration tests
426+
uses: ./.github/actions/run-gate
427+
with: { gate: integration-node }
393428

394429
- name: Run seeded concurrency torture lane (fast PR sweep)
395430
# #1416's nightly torture lane lives under test/integration/nightly/, out
396431
# of the test:integration:node glob, so this is a *deliberate* fast PR
397432
# sweep (TORTURE_RUNS default 128 seeds, ~sub-second) — not an accidental
398433
# glob inclusion. The Concurrency Torture Nightly workflow sweeps a much
399434
# larger seed range on schedule.
400-
run: pnpm gate concurrency-torture
435+
uses: ./.github/actions/run-gate
436+
with: { gate: concurrency-torture }
401437

402438
- name: Run provider-backed integration tests
403-
run: pnpm gate provider-integration
439+
uses: ./.github/actions/run-gate
440+
with: { gate: provider-integration }
404441

405442
- name: Check Provider-backed integration architecture progress
406-
run: pnpm gate integration-progress
443+
uses: ./.github/actions/run-gate
444+
with: { gate: integration-progress }
407445

408446
# A build-cache lookup outage must degrade setup-fixture-app to an inline
409447
# build, not fail the caller. This drives that step's real shell against a
410448
# failing `gh`.
411449
- name: Setup-fixture-app cache-failure fallback
412-
run: pnpm gate fixture-fallback
450+
uses: ./.github/actions/run-gate
451+
with: { gate: fixture-fallback }
413452
# The trusted-artifact contract the device lanes rely on to decide a cached fixture
414453
# app is the one this commit expects. A real test file that no workflow ran.
415454
- name: Check the trusted fixture-artifact contract
416-
run: pnpm gate fixture-cache
455+
uses: ./.github/actions/run-gate
456+
with: { gate: fixture-cache }
417457

418458
web-smoke:
419459
name: Web Platform Smoke
@@ -431,9 +471,11 @@ jobs:
431471
node-version: '24.13'
432472

433473
- name: Run live web smoke
434-
run: |
435-
pnpm clean:daemon
436-
pnpm gate web-smoke
474+
run: pnpm clean:daemon
475+
476+
- name: Execute live web smoke
477+
uses: ./.github/actions/run-gate
478+
with: { gate: web-smoke }
437479

438480
- name: Upload web smoke artifacts
439481
if: always()

0 commit comments

Comments
 (0)