mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
ci: route the no-AK integration gate to ECS for every pull request (#16132)
* ci: route the no-AK integration gate to ECS for every pull request
Same trust class as lint_and_static (2026-11-02 extension of the
2026-10-31 decision): the integration step executes PR code, but the
lane carries zero secrets by construction — every model key is emptied
at the step and the job's only token is the read-only checkout grant.
The gate's step-level runner.temp HOME becomes run-unique and is
cleared before use (the R2-1 lesson: RUNNER_TEMP survives across jobs
on this pool). test and integration_cli keep the association-gated
ubuntu_runner. Mutation-verified: runs-on revert, fixed HOME, and a
real key in the PR-code step each red the routing battery.
* fix(ci): isolate no-AK HOME from the first step; value-pinned zero-secrets
Round-2 review findings: (1) Critical — the gate-scoped HOME missed the
lane's actual fork-code start: checkout's local action and the install
(lifecycle scripts plus scripts/prepare.js, un-skipped) ran under the
runner's real shared home with a shared COREPACK_HOME — the exact
cross-job poisoning channel the lint acceptance closed. Mirror lint:
first-step GITHUB_ENV run-unique HOME + COREPACK_HOME inside it +
shared verified XDG cache + always() reclamation. (2) Critical — the
zero-secrets guard was name-based and missed odd-named secrets; enforce
by VALUE (every non-path env key empty, no secrets.* anywhere in the
gate). (3) runs-on literal pin tightened (timeout line also mentions
lint_runner). Docs: EN single-lane phrasing, stale merge-queue claim,
2026-11-02 paragraph rewritten for the first-step shape. Six mutations
red (first-step removal, shared corepack, odd-name secret, no cleanup,
runs-on revert, fixed HOME).
* docs(ci): correct the enforcement-order claim and stale trust-basis note
Round-2 review: the doc claimed the routing battery runs before
dependency install — false (HELPER_TESTS imports yaml, runs after
install, and not at all on github_ci_only PRs' own runs); and the
ci.yml trust-basis comment still described the round-1 gate-scoped
HOME shape. State the actual timing and detection latency, and the
shipped first-step shape.
* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming
Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list (pinned
by both suites). The 'Configure persistent npm cache' step name promised
persistence the per-run HOME removes; rename it and record the accepted
cold-install cost (shared pnpm store stays a follow-up).
* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming
Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list. The
value whitelist only iterates present keys, so the deletion mutation
escaped it; pin the key's presence-and-empty in the battery and the
28-key literal list. The 'Configure persistent npm cache' step name
promised persistence the per-run HOME removes; rename and record the
accepted cold-install cost (shared pnpm store stays a follow-up).
* test(ci): pin the no-AK grant, HOME template and reclamation path
Three assertions in the no-AK routing battery proved less than they read:
- the job's `permissions` was never inspected, so the read-only-checkout
grant that justifies routing untrusted fork code onto the pool could be
widened without a red test;
- `create.includes('HOME=')` is satisfied by `COREPACK_HOME=` and
`XDG_CACHE_HOME=`, so deleting the real HOME/USERPROFILE publication kept
the test green;
- the cleanup only had to contain the `qwen-no-ak-home-` prefix, so a path
that dropped `-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}` would still pass
while removing a directory nothing used.
Pin the grant by value, the publication by its printf template, and the
reclamation by the exact run-unique path the create step builds.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw48g5gafx
* test(ci): pin the realized HOME values in the shared ungated lane loop
R2-1: assert the create `home=` line, the gate QWEN_HOME and the cleanup
command by value, and require the exact set of `$GITHUB_ENV` writers. An
`echo` stand-in for the cleanup, a `-v2` create path and an appended second
HOME writer all kept the battery green under containment checks.
R1-2 / R1-1: hoist those pins into the shared `ungated` loop so
`lint_and_static` gets them too. Its create step was pinned by
`includes('HOME=')` (satisfied by `COREPACK_HOME=`) and its cleanup by
`includes('qwen-')`, so a dropped `HOME=%s` or a retargeted `rm -rf` survived.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4h0sl6g9
* test(ci): red on later republishes of the isolated HOME keys
R3-1: the GITHUB_ENV equality guard read step 0 only, so a later step that
republished HOME reverted the run-unique home by last-write-wins while every
assertion stayed green — the isolation would silently stop applying to the
steps that run PR code. Scan the remaining steps for isolated-key
$GITHUB_ENV writers and for step-level HOME/USERPROFILE/COREPACK_HOME/
XDG_CACHE_HOME overrides, leaving the npm cache step's own key a legitimate
later write.
Mutation-verified on this head: appending printf 'HOME=%s\n' ... to
integration_no_ak's 'Configure per-run npm cache (self-hosted)' step and
adding a step-level `env: HOME` each turn the suite red (103 pass -> 102
pass / 1 fail); reverting restores 103/103.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4ng9wvgi
---------
Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
1 parent
738c4bbf7e
commit
1a20792cfa
7 files changed
+303
-80
No files matched your search
@@ -151,7 +151,8 @@ const timeoutMinutesOn = (job, runner) => {
|
||||
// The same `|| '["ubuntu-latest"]'` default the runs-on expression carries:
|
||||
// a missing output (classify_pr skipped) is the hosted routing. The caller
|
||||
// passes the value of the output the job itself reads — ubuntu_runner for
|
||||
// the association-gated lanes, lint_runner for lint_and_static.
|
||||
// the association-gated lanes, lint_runner for lint_and_static and
|
||||
// integration_no_ak.
|
||||
const effective = runner || HOSTED_RUNNER;
|
||||
const output = runnerOutputOf(job);
|
||||
let expr = String(ci.jobs[job]['timeout-minutes'])
|
||||
@@ -752,10 +753,11 @@ describe('post-merge push lane', () => {
|
||||
it('routes lint_and_static through the classify_pr lint output', () => {
|
||||
// runs-on is where the contributor code this describe's other pins talk
|
||||
// about actually executes. classify_pr's pick_runner publishes TWO
|
||||
// routings: ubuntu_runner keeps the fork-trust boundary for test and the
|
||||
// integration lanes, while lint_runner routes every pull request —
|
||||
// routings: ubuntu_runner keeps the fork-trust boundary for test and
|
||||
// integration_cli, while lint_runner routes every pull request —
|
||||
// untrusted forks included — to the fleet (maintainer decision
|
||||
// 2026-10-31, shared-pool code-execution risk accepted). A literal pool
|
||||
// 2026-10-31, shared-pool code-execution risk accepted, extended to
|
||||
// integration_no_ak 2026-11-02). A literal pool
|
||||
// label array here would bypass the classifier indirection whole (and
|
||||
// desync from timeout-minutes, which reads the same output), and reading
|
||||
// ubuntu_runner would silently restore the hosted fork lane. Whole-
|
||||
|
||||
@@ -444,8 +444,14 @@ describe('no-AK integration CI wiring', () => {
|
||||
" name: 'Integration Tests (no-AK, No Sandbox)'",
|
||||
);
|
||||
expect(gateJob).toContain(" needs: 'classify_pr'");
|
||||
// Same runner routing as the Ubuntu gate, never a hard-coded pool.
|
||||
expect(gateJob).toContain('needs.classify_pr.outputs.ubuntu_runner');
|
||||
// Same trust class as lint (PR code on the pool, zero secrets by
|
||||
// construction — 2026-11-02 extension of the 2026-10-31 decision),
|
||||
// never a hard-coded pool. Pin the runs-on line itself: the timeout line
|
||||
// also mentions lint_runner, so a bare substring stays green under a
|
||||
// runs-on-only revert.
|
||||
expect(gateJob).toContain(
|
||||
"runs-on: '${{ fromJSON(needs.classify_pr.outputs.lint_runner",
|
||||
);
|
||||
const jobIf = gateJob
|
||||
.split('\n')
|
||||
.find((line) => line.startsWith(' if:'));
|
||||
@@ -597,19 +603,22 @@ describe('no-AK integration CI wiring', () => {
|
||||
expect(gateStep.indexOf(integrationTypecheckCommand)).toBeLessThan(
|
||||
gateStep.indexOf(`npm run ${NO_AK_SCRIPT}`),
|
||||
);
|
||||
// The job's first step publishes the run-unique HOME via GITHUB_ENV
|
||||
// (2026-11-02 round-2: fork code starts at checkout/install, not at the
|
||||
// gate — HOME/USERPROFILE are job-wide, not gate-scoped); the gate keeps
|
||||
// QWEN_HOME + the key scrub.
|
||||
expect(gateStep).toContain(
|
||||
"QWEN_HOME: '${{ runner.temp }}/qwen-no-ak-home/.qwen'",
|
||||
"QWEN_HOME: '${{ runner.temp }}/qwen-no-ak-home-${{ github.run_id }}-${{ github.run_attempt }}/.qwen'",
|
||||
);
|
||||
expect(gateStep).toContain('timeout-minutes: 20');
|
||||
expect(gateStep).toContain(
|
||||
"\n HOME: '${{ runner.temp }}/qwen-no-ak-home'",
|
||||
);
|
||||
expect(gateStep).toContain(
|
||||
"\n USERPROFILE: '${{ runner.temp }}/qwen-no-ak-home'",
|
||||
);
|
||||
// HOME/USERPROFILE are job-wide now (first-step GITHUB_ENV), so the
|
||||
// gate must not re-pin them step-locally.
|
||||
expect(gateStep).not.toContain('\n HOME: ');
|
||||
expect(gateStep).not.toContain('\n USERPROFILE: ');
|
||||
for (const key of [
|
||||
'API_KEY',
|
||||
'ANTHROPIC_API_KEY',
|
||||
'ANTHROPIC_AUTH_TOKEN',
|
||||
'ANTHROPIC_BASE_URL',
|
||||
'ANTHROPIC_MODEL',
|
||||
'BAILIAN_CODING_PLAN_API_KEY',
|
||||
|
||||
Reference in new issue
Block a user