Skip to content

Commit f906472

Browse files
committed
runtime live e2e: add pr trigger and CI-E2E gate
1 parent 1c758a6 commit f906472

4 files changed

Lines changed: 83 additions & 16 deletions

File tree

.github/workflows/runtime-live-e2e.yml

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Runtime Live E2E
22

33
on:
4+
pull_request:
45
workflow_dispatch:
56
inputs:
67
pr_number:
@@ -15,9 +16,12 @@ permissions:
1516
jobs:
1617
claude-live:
1718
runs-on: ubuntu-latest
19+
environment: CI-E2E
1820
env:
1921
DISABLE_AUTOUPDATER: "1"
20-
PR_NUMBER: ${{ inputs.pr_number }}
22+
TRIGGER_SOURCE: ${{ github.event_name }}
23+
PR_NUMBER: ${{ github.event.pull_request.number }}
24+
DISPATCH_PR_NUMBER: ${{ inputs.pr_number }}
2125
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
2226
steps:
2327
- name: Check required secret
@@ -31,9 +35,21 @@ jobs:
3135
uses: actions/github-script@v7
3236
with:
3337
script: |
34-
const prNumber = Number(process.env.PR_NUMBER);
38+
const triggerSource = process.env.TRIGGER_SOURCE;
39+
let rawPrNumber;
40+
41+
if (triggerSource === "pull_request") {
42+
rawPrNumber = process.env.PR_NUMBER;
43+
} else if (triggerSource === "workflow_dispatch") {
44+
rawPrNumber = process.env.DISPATCH_PR_NUMBER;
45+
} else {
46+
core.setFailed(`Unsupported trigger source: ${JSON.stringify(triggerSource)}`);
47+
return;
48+
}
49+
50+
const prNumber = Number(rawPrNumber);
3551
if (!Number.isInteger(prNumber) || prNumber <= 0) {
36-
core.setFailed(`workflow_dispatch input pr_number must be a positive integer, got ${JSON.stringify(process.env.PR_NUMBER)}`);
52+
core.setFailed(`${triggerSource} PR number must be a positive integer, got ${JSON.stringify(rawPrNumber)}`);
3753
return;
3854
}
3955
@@ -59,6 +75,7 @@ jobs:
5975
6076
await core.summary
6177
.addHeading("Runtime Live E2E Provenance")
78+
.addRaw(`- Trigger source: ${triggerSource}\n`)
6279
.addRaw(`- PR number: #${prNumber}\n`)
6380
.addRaw(`- Tested workflow SHA: \`${context.sha}\`\n`)
6481
.addRaw(`- Current PR head SHA: \`${pull.head.sha}\`\n`)
@@ -107,8 +124,11 @@ jobs:
107124
108125
codex-live:
109126
runs-on: ubuntu-latest
127+
environment: CI-E2E
110128
env:
111-
PR_NUMBER: ${{ inputs.pr_number }}
129+
TRIGGER_SOURCE: ${{ github.event_name }}
130+
PR_NUMBER: ${{ github.event.pull_request.number }}
131+
DISPATCH_PR_NUMBER: ${{ inputs.pr_number }}
112132
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
113133
steps:
114134
- name: Check required secret
@@ -122,9 +142,21 @@ jobs:
122142
uses: actions/github-script@v7
123143
with:
124144
script: |
125-
const prNumber = Number(process.env.PR_NUMBER);
145+
const triggerSource = process.env.TRIGGER_SOURCE;
146+
let rawPrNumber;
147+
148+
if (triggerSource === "pull_request") {
149+
rawPrNumber = process.env.PR_NUMBER;
150+
} else if (triggerSource === "workflow_dispatch") {
151+
rawPrNumber = process.env.DISPATCH_PR_NUMBER;
152+
} else {
153+
core.setFailed(`Unsupported trigger source: ${JSON.stringify(triggerSource)}`);
154+
return;
155+
}
156+
157+
const prNumber = Number(rawPrNumber);
126158
if (!Number.isInteger(prNumber) || prNumber <= 0) {
127-
core.setFailed(`workflow_dispatch input pr_number must be a positive integer, got ${JSON.stringify(process.env.PR_NUMBER)}`);
159+
core.setFailed(`${triggerSource} PR number must be a positive integer, got ${JSON.stringify(rawPrNumber)}`);
128160
return;
129161
}
130162
@@ -150,6 +182,7 @@ jobs:
150182
151183
await core.summary
152184
.addHeading("Runtime Live E2E Provenance")
185+
.addRaw(`- Trigger source: ${triggerSource}\n`)
153186
.addRaw(`- PR number: #${prNumber}\n`)
154187
.addRaw(`- Tested workflow SHA: \`${context.sha}\`\n`)
155188
.addRaw(`- Current PR head SHA: \`${pull.head.sha}\`\n`)

docs/plans/runtime-live-e2e-pr-trigger-and-environment-gate.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,22 @@ The summary output should continue to show:
6868
- Workflow-structure inspection: verify trigger block, environment fields, and provenance logic for both event types. Cost/complexity: low. No E2E required.
6969
- Focused offline tests: update/add static tests for the PR-trigger and `CI-E2E` environment wiring. Cost/complexity: low-medium. No E2E required.
7070
- Optional GitHub smoke after merge: open or reuse a PR and confirm the runtime workflow appears on the PR and waits for `CI-E2E` approval before the live jobs run. Cost/complexity: medium. E2E required: yes, but not required to finish implementation in this cycle.
71+
72+
## Stage Report: implementation
73+
74+
- [x] Add `pull_request` triggering while keeping `workflow_dispatch`.
75+
`.github/workflows/runtime-live-e2e.yml` now includes both triggers, with `workflow_dispatch` retained for targeted reruns.
76+
- [x] Add `environment: CI-E2E` to both live jobs.
77+
Both `claude-live` and `codex-live` declare the `CI-E2E` environment, which is the approval gate for the live runtime checks.
78+
- [x] Resolve PR provenance correctly for both trigger paths.
79+
The provenance step branches on `github.event_name` and uses `github.event.pull_request.number` for PR runs or `inputs.pr_number` for manual dispatch runs.
80+
- [x] Update the operator docs for the new PR-native flow and rerun path.
81+
`tests/README.md` now explains the pending `CI-E2E` approval flow, the environment-scoped secrets, and the retained manual rerun command.
82+
- [x] Update offline coverage for the trigger/environment behavior.
83+
`tests/test_runtime_live_e2e_workflow.py` now checks the trigger block, job environments, provenance fields, and the README wording.
84+
- [x] Run focused verification for the edited workflow/docs/test files.
85+
`unset CLAUDECODE && uv run tests/test_runtime_live_e2e_workflow.py` is the targeted static check for this change set.
86+
87+
### Summary
88+
89+
Implemented the PR-native live E2E workflow shape with `CI-E2E` approval gating while keeping manual dispatch available for reruns. The workflow now carries event-specific provenance, the operator docs explain the approval flow, and the offline test coverage matches the new wiring.

tests/README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,13 @@ make test-e2e TEST=tests/test_gate_guardrail.py RUNTIME=codex
191191
uv run tests/test_gate_guardrail.py --runtime codex
192192
```
193193

194-
## Manual PR Runtime Live E2E
194+
## PR Runtime Live E2E
195195

196-
The expensive runtime-backed PR suite lives in `.github/workflows/runtime-live-e2e.yml`. It is a `workflow_dispatch` workflow, not an always-on `pull_request` workflow. A maintainer runs it only after the PR has been approved and the always-on static workflow is already green.
196+
The expensive runtime-backed PR suite lives in `.github/workflows/runtime-live-e2e.yml`. It now triggers on `pull_request` so the runtime jobs show up on the PR alongside Static CI, and it still supports `workflow_dispatch` for targeted reruns.
197197

198-
Invoke it from the Actions UI on the branch under test, or with:
198+
GitHub blocks each runtime job behind the `CI-E2E` environment review gate. Until an approved reviewer releases that environment, the job stays pending and cannot access the environment-scoped API keys.
199+
200+
Open a PR and then approve the pending `CI-E2E` deployment to release the live runtime checks. For targeted reruns or branch-local debugging, invoke the workflow manually from Actions or with:
199201

200202
```bash
201203
gh workflow run runtime-live-e2e.yml --ref <pr-branch> -f pr_number=<N>
@@ -206,17 +208,18 @@ This workflow runs exactly two jobs:
206208
- `claude-live`
207209
- `codex-live`
208210

209-
Required repository secrets:
211+
Required `CI-E2E` environment secrets:
210212

211213
- `ANTHROPIC_API_KEY` for `claude-live`
212214
- `OPENAI_API_KEY` for `codex-live`
213215

214-
Each job fails immediately with a clear message if its required secret is missing.
216+
Each job fails immediately with a clear message if its required secret is missing after the environment is approved.
215217

216218
This workflow is expected to report current live-suite status honestly. If a runtime test fails, the corresponding job stays red; shipping the manual CI wiring does not imply the Claude and Codex suites are already fully green.
217219

218220
Operators should expect each job summary to show the run provenance explicitly:
219221

222+
- Trigger source
220223
- PR number
221224
- Tested workflow SHA
222225
- Current PR head SHA

tests/test_runtime_live_e2e_workflow.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# /// script
33
# requires-python = ">=3.10"
44
# ///
5-
# ABOUTME: Static checks for the manual runtime live E2E workflow and its operator docs.
6-
# ABOUTME: Verifies the workflow stays manual, split into exactly two runtime jobs, and documents provenance/secrets.
5+
# ABOUTME: Static checks for the runtime live E2E workflow and its operator docs.
6+
# ABOUTME: Verifies PR/manual triggers, CI-E2E approval gating, and the two runtime jobs.
77

88
from __future__ import annotations
99

@@ -34,20 +34,24 @@ def section(text: str, heading: str) -> str:
3434
return "\n".join(lines)
3535

3636

37-
def test_runtime_live_e2e_workflow_exists_and_is_manual_only():
37+
def test_runtime_live_e2e_workflow_supports_pr_and_manual_triggers():
3838
text = read_workflow()
3939

4040
assert "workflow_dispatch:" in text
41-
assert "pull_request:" not in text
41+
assert "pull_request:" in text
4242
assert "pr_number:" in text
4343
assert "required: true" in text
4444

4545

4646
def test_runtime_live_e2e_workflow_has_exactly_two_runtime_jobs():
4747
text = read_workflow()
48+
claude_section = section(text, " claude-live")
49+
codex_section = section(text, " codex-live")
4850

4951
assert "\n claude-live:\n" in text
5052
assert "\n codex-live:\n" in text
53+
assert "environment: CI-E2E" in claude_section
54+
assert "environment: CI-E2E" in codex_section
5155
assert "path classifier" not in text.lower()
5256
assert "shard" not in text.lower()
5357
assert "matrix:" not in text
@@ -91,9 +95,14 @@ def test_runtime_live_e2e_workflow_lists_the_expected_commands_and_provenance_fi
9195
"same-repo",
9296
"fork",
9397
"Approval context",
98+
"Trigger source",
9499
):
95100
assert field in text
96101

102+
assert "github.event.pull_request.number" in text
103+
assert "inputs.pr_number" in text
104+
assert "TRIGGER_SOURCE" in text
105+
assert "DISPATCH_PR_NUMBER" in text
97106
assert "set -euo pipefail" in text
98107
assert "continue-on-error" not in text
99108
assert "|| true" not in text
@@ -104,7 +113,8 @@ def test_tests_readme_documents_runtime_live_e2e_workflow():
104113

105114
assert "runtime-live-e2e.yml" in text
106115
assert "workflow_dispatch" in text
107-
assert "after the PR has been approved" in text
116+
assert "pull_request" in text
117+
assert "CI-E2E" in text
108118
assert "claude-live" in text
109119
assert "codex-live" in text
110120
assert "ANTHROPIC_API_KEY" in text
@@ -114,4 +124,6 @@ def test_tests_readme_documents_runtime_live_e2e_workflow():
114124
assert "Current PR head SHA" in text
115125
assert "same-repo vs fork" in text
116126
assert "approval/reviewer context" in text
127+
assert "Trigger source" in text
117128
assert "job stays red" in text
129+
assert "pending `CI-E2E` deployment" in text

0 commit comments

Comments
 (0)