-
Notifications
You must be signed in to change notification settings - Fork 0
358 lines (322 loc) · 14.8 KB
/
Copy pathe2e-cli.yml
File metadata and controls
358 lines (322 loc) · 14.8 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
name: e2e-cli
# Toolchain-heavy CLI runtime E2E gate. ci.yml deliberately defers this (see its
# header: "the toolchain-heavy CLI runtime e2e ... which spawns aspire + docker +
# postgres ... deferred Phase-2 repo process automation umbrella"). This workflow
# is that gate, split into two tiers over `deno task e2e:cli`:
#
# - scaffold-static — deno-only suites (scaffold.service, scaffold.contracts,
# scaffold.plugins): init, generated-surface discovery, registry generation,
# plugin doctor, and generated typecheck. No aspire / docker / postgres, so it
# runs green on a stock runner. This is the immediate scaffold-output
# regression gate and is intended to become a required check once observed
# green.
#
# - scaffold-runtime — the full merge-readiness suite
# (`deno task e2e:cli run scaffold.runtime --cleanup`): scaffold + official
# plugins (workers/sagas/triggers/streams) + DB init/generate/seed + generated
# typecheck + Aspire AppHost boot + HTTP/OTEL behavior + cleanup. Needs the
# standalone `aspire` CLI, .NET, and Docker. ADDITIVE UNTIL OBSERVED GREEN
# (mirrors the `quality` lane rollout in ci.yml): the Aspire-in-CI bring-up is
# unverified and likely needs a debugging pass before this job is promoted to a
# required check via branch protection.
#
# Triggers: every PR to main or an integration branch (`feat/**`, `epic/**`), PLUS
# any other PR carrying the `e2e-cli-gate` label, PLUS manual dispatch.
#
# Skip policy (see `.github/scripts/ci-classify-changes.ts`): the `classify` job
# derives `run_static` / `run_runtime` from the PR diff and labels. Both expensive
# jobs still START and report SUCCESS, but short-circuit to a "skipped-by-policy"
# step when their work is not needed:
#
# - docs-only diff (only `**/*.md(x)`, `docs/`, `.llm/`, `.agents/`, `.claude/`,
# and never `packages/`/`plugins/`/`apps/`/`deno.json*`/`deno.lock`/
# `.github/workflows/`) -> skip both.
# - `ci:skip-scaffold` -> skip scaffold-static; `ci:skip-e2e` -> skip
# scaffold-runtime; `ci:full` -> force both regardless.
#
# The jobs run + report SUCCESS rather than using `paths-ignore`/job-level `if`,
# so they never strand a required status check if promoted to one later. The
# cheap required trio (quality / check-test / deps-report) lives in ci.yml and is
# NOT touched by this policy. Release gates (publish, e2e-cli-prod*) have no skip.
#
# FAIL-CLOSED RULE: a skip requires `classify` to SUCCEED with an explicit
# `run_*=false`. If classify fails for any reason, the expensive jobs run in
# full (`needs.classify.result != 'success'` forces RUN=true); only a `skipped`
# classify (the pre-existing applicability gate) skips them, as before.
# Rename-aware diff: `--name-status -M` feeds BOTH sides of renames/copies so a
# source->docs rename can never classify docs-only.
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: e2e-cli-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
classify:
name: classify changes
# Applicability gate: PRs to main or integration branch families,
# `e2e-cli-gate`-labeled PRs, or manual dispatch exercise this workflow.
if: >-
github.event_name == 'workflow_dispatch' ||
github.event.pull_request.base.ref == 'main' ||
startsWith(github.event.pull_request.base.ref, 'feat/') ||
startsWith(github.event.pull_request.base.ref, 'epic/') ||
contains(github.event.pull_request.labels.*.name, 'e2e-cli-gate')
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
run_static: ${{ steps.decide.outputs.run_static }}
run_runtime: ${{ steps.decide.outputs.run_runtime }}
docs_only: ${{ steps.decide.outputs.docs_only }}
reason: ${{ steps.decide.outputs.reason }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: "2.9.0"
- name: Self-check the classifier
run: >-
deno test --allow-read --allow-write --allow-env
.github/scripts/ci-classify-changes.test.ts
- name: Compute changed files (merge-base diff, rename-aware)
id: diff
if: github.event_name == 'pull_request'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
# `--name-status -M` (NOT `--name-only`) so renames/copies report BOTH
# sides: a `packages/cli/a.ts -> docs/a.md` rename must surface the
# impacting source path, not just the docs destination (rename hole
# found in adversarial review). The classifier parses this format and
# feeds old+new for R*/C* and the deleted path for D.
run: |
echo "changed<<__EOF__" >> "$GITHUB_OUTPUT"
git diff --name-status -M "$BASE_SHA...$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "__EOF__" >> "$GITHUB_OUTPUT"
- name: Decide run/skip
id: decide
env:
EVENT_NAME: ${{ github.event_name }}
CHANGED_NAME_STATUS: ${{ steps.diff.outputs.changed }}
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: deno run --allow-read --allow-write --allow-env .github/scripts/ci-classify-changes.ts
scaffold-static:
name: scaffold-static (deno-only)
needs: classify
# Job always starts (so its status reports); real work is guarded by the
# classifier so docs-only / `ci:skip-scaffold` short-circuit to SUCCESS.
# FAIL-CLOSED: if `classify` FAILED (setup/diff/output error), this job still
# runs and RUN defaults to true — a skip requires classify to have SUCCEEDED
# with an explicit `run_static=false`. `skipped` classify (applicability gate:
# not a PR to main / no `e2e-cli-gate` label) keeps the old skip behavior.
if: ${{ !cancelled() && needs.classify.result != 'skipped' }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
RUN: ${{ needs.classify.result != 'success' || needs.classify.outputs.run_static == 'true' }}
SKIP_REASON: ${{ needs.classify.outputs.reason }}
steps:
- name: Skipped by policy
if: env.RUN != 'true'
# REASON contains raw changed-file names — never interpolate it into
# shell source; pass via env and print with printf.
run: |
printf '::notice::scaffold-static skipped by policy. %s\n' "$SKIP_REASON"
- name: Checkout
if: env.RUN == 'true'
uses: actions/checkout@v4
- name: Setup Deno
if: env.RUN == 'true'
uses: denoland/setup-deno@v2
with:
deno-version: "2.9.0"
- name: Install workspace dependencies
if: env.RUN == 'true'
run: deno install
- name: scaffold.service (init + generated discovery + typecheck)
if: env.RUN == 'true'
run: deno task e2e:cli run scaffold.service --format pretty
- name: scaffold.contracts (init + contract discovery + typecheck)
if: env.RUN == 'true'
run: deno task e2e:cli run scaffold.contracts --format pretty
- name: scaffold.plugins (official plugins + registry gen + plugin doctor)
if: env.RUN == 'true'
run: deno task e2e:cli run scaffold.plugins --format pretty
scaffold-runtime:
name: scaffold-runtime (aspire + docker + postgres)
needs: classify
# Job always starts (so its status reports); real work is guarded by the
# classifier so docs-only / `ci:skip-e2e` short-circuit to SUCCESS. Heavy;
# additive until observed green.
# FAIL-CLOSED: if `classify` FAILED, this job still runs and RUN defaults to
# true — a skip requires classify to have SUCCEEDED with an explicit
# `run_runtime=false`. `skipped` classify (applicability gate) keeps the old
# skip behavior.
if: ${{ !cancelled() && needs.classify.result != 'skipped' }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
RUN: ${{ needs.classify.result != 'success' || needs.classify.outputs.run_runtime == 'true' }}
SKIP_REASON: ${{ needs.classify.outputs.reason }}
steps:
- name: Skipped by policy
if: env.RUN != 'true'
# REASON contains raw changed-file names — never interpolate it into
# shell source; pass via env and print with printf.
run: |
printf '::notice::scaffold-runtime skipped by policy. %s\n' "$SKIP_REASON"
- name: Checkout
if: env.RUN == 'true'
uses: actions/checkout@v4
- name: Setup Deno
if: env.RUN == 'true'
uses: denoland/setup-deno@v2
with:
deno-version: "2.9.0"
- name: Setup .NET
if: env.RUN == 'true'
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Install Aspire CLI
if: env.RUN == 'true'
env:
ASPIRE_CLI_VERSION: "13.4.6"
run: |
dotnet tool install Aspire.Cli --tool-path "$HOME/.aspire/bin" --version "$ASPIRE_CLI_VERSION"
echo "$HOME/.aspire/bin" >> "$GITHUB_PATH"
- name: Aspire CLI preflight
if: env.RUN == 'true'
run: |
aspire_version="$(aspire --version)"
echo "$aspire_version"
case "$aspire_version" in
13.4.*) ;;
*) echo "Expected Aspire CLI 13.4.x, got $aspire_version" >&2; exit 1 ;;
esac
- name: Install workspace dependencies
if: env.RUN == 'true'
run: deno install
- name: Full scaffold runtime E2E (one pass, with cleanup)
if: env.RUN == 'true'
run: deno task e2e:cli run scaffold.runtime --cleanup --format pretty
- name: Upload E2E report artifact
if: always() && env.RUN == 'true'
uses: actions/upload-artifact@v4
with:
name: e2e-cli-scaffold-runtime-report
path: |
.llm/tmp/**/report*.json
.llm/tmp/**/report*.ndjson
**/e2e-report*.json
if-no-files-found: ignore
desktop-native-linux:
name: desktop-native-linux (deb + signed updater)
needs: classify
if: ${{ !cancelled() && needs.classify.result != 'skipped' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: "2.9.0"
- name: Install native packaging and desktop prerequisites
run: >-
sudo apt-get update && sudo apt-get install --yes
bsdiff libgtk-3-0 libwebkit2gtk-4.1-0
- name: Install workspace dependencies
run: deno install
- name: Native desktop package, install, update, and rollback (one pass)
id: native_suite
# TEMPORARY NON-BLOCKING POLICY: remove step tolerance when the packaged
# runtime ships the #841 verification op. See the G7 drift entry
# "Packaged runtime lacks the #841 verification op" for fail-closed evidence.
continue-on-error: true
run: deno task e2e:cli run deploy.desktop-native --cleanup --format pretty
- name: Summarize native desktop upstream gap (LOUD, never masks suite evidence)
if: always()
env:
EVIDENCE: .llm/tmp/desktop-native-e2e/evidence.json
NATIVE_SUITE_OUTCOME: ${{ steps.native_suite.outcome }}
DRIFT_URL: >-
${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/.llm/runs/beta11-cli--orchestrator/slices/g7-457-e2e/drift.md#2026-07-18--packaged-runtime-lacks-the-841-verification-op
run: |
echo '::warning title=DESKTOP NATIVE E2E IS NON-BLOCKING PENDING UPSTREAM OP::The suite remains fail-closed; inspect the structured evidence and linked drift investigation.'
{
printf '# ⚠️ DESKTOP NATIVE LINUX: NON-BLOCKING UPSTREAM GAP — step outcome: `%s` (failure tolerated)\n' "$NATIVE_SUITE_OUTCOME"
echo
echo 'The native suite ran unchanged. Its failure is retained as evidence; CI policy alone is temporarily non-blocking pending the owner rescope decision.'
echo
printf '[Upstream-gap investigation and flip-back condition](%s)\n' "$DRIFT_URL"
echo
echo '## Structured suite evidence'
echo '```json'
if [ -f "$EVIDENCE" ]; then
sed -n '1,240p' "$EVIDENCE"
else
echo '{"status":"FAIL","reason":"structured evidence file was not produced"}'
fi
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload native desktop evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-cli-desktop-native-linux-report
path: .llm/tmp/desktop-native-e2e/evidence.json
if-no-files-found: warn
lane-visibility:
name: scaffold CI lane visibility
needs: [classify, scaffold-static, scaffold-runtime, desktop-native-linux]
if: ${{ always() && github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Summarize scaffold lane outcomes
env:
CLASSIFY_RESULT: ${{ needs.classify.result }}
RUN_STATIC: ${{ needs.classify.outputs.run_static }}
RUN_RUNTIME: ${{ needs.classify.outputs.run_runtime }}
STATIC_RESULT: ${{ needs.scaffold-static.result }}
RUNTIME_RESULT: ${{ needs.scaffold-runtime.result }}
DESKTOP_NATIVE_LINUX_RESULT: ${{ needs.desktop-native-linux.result }}
run: |
describe_scaffold_lane() {
selected="$1"
result="$2"
if [ "$CLASSIFY_RESULT" = "success" ] && [ "$selected" != "true" ]; then
printf 'skipped by policy'
elif [ "$result" = "skipped" ]; then
printf 'not scheduled'
else
printf 'ran (%s)' "$result"
fi
}
if [ "$CLASSIFY_RESULT" = "skipped" ]; then
classify_outcome="not scheduled (unsupported base and no opt-in label)"
else
classify_outcome="ran ($CLASSIFY_RESULT)"
fi
static_outcome="$(describe_scaffold_lane "$RUN_STATIC" "$STATIC_RESULT")"
runtime_outcome="$(describe_scaffold_lane "$RUN_RUNTIME" "$RUNTIME_RESULT")"
{
echo "## Scaffold CI lane visibility"
echo
echo "A successful scaffold job can be a policy skip; this table uses classifier outputs to distinguish it from real execution."
echo
echo "| Lane | Outcome |"
echo "| --- | --- |"
printf '| `classify` | %s |\n' "$classify_outcome"
printf '| `scaffold-static` | %s |\n' "$static_outcome"
printf '| `scaffold-runtime` | %s |\n' "$runtime_outcome"
} >> "$GITHUB_STEP_SUMMARY"