mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-11 04:02:45 -10:00
ci: route the Test aggregate gate and web-shell visuals capture to the disposable pool
Both jobs match the ecs-qwen-ephemeral pool's security model: the Test aggregate runs one shell line and checks out nothing, and the visuals capture builds untrusted PR code with a read-only token and no long-lived secrets. Routing them off the hosted queue removes the 9-minute waits the one-line gate saw after its shards finished, and takes a 45-minute render job off hosted capacity. The MAINTAINER_ECS_RUNNER_DISABLED kill-switch reverts both to hosted. The E2E OpenTUI leg deliberately stays hosted: it carries long-lived model secrets, which the disposable pool's boundary forbids until a separate trusted pool exists. Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
1 parent
e4e28d7ee0
commit
acbbc6d9a1
4 files changed
+113
-9
No files matched your search
@@ -797,6 +797,105 @@ describe('ci.yml lint HOME isolation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('credential-free jobs on the disposable pool', () => {
|
||||
const EPHEMERAL = ['self-hosted', 'ecs-qwen-ephemeral'];
|
||||
|
||||
it('ci.yml test_ubuntu: the checkout-free gate rides the disposable pool', () => {
|
||||
// The gate runs one `test` shell line and checks out nothing, so every
|
||||
// trigger and association may share the disposable pool with the fork
|
||||
// shards; only the kill-switch reverts it to hosted.
|
||||
const job = ciDoc.jobs.test_ubuntu;
|
||||
const runsOn = String(job['runs-on']);
|
||||
assert.match(runsOn, /github\.repository == 'QwenLM\/qwen-code'/);
|
||||
for (const eventName of [
|
||||
'pull_request',
|
||||
'push',
|
||||
'merge_group',
|
||||
'workflow_dispatch',
|
||||
]) {
|
||||
for (const sameRepo of [true, false]) {
|
||||
for (const assoc of ASSOCIATIONS) {
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled: false,
|
||||
eventName,
|
||||
sameRepo,
|
||||
assoc,
|
||||
}),
|
||||
EPHEMERAL,
|
||||
`gate ${eventName} sameRepo=${sameRepo} assoc='${assoc}'`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled: true,
|
||||
eventName,
|
||||
sameRepo,
|
||||
assoc,
|
||||
}),
|
||||
JSON.parse(HOSTED),
|
||||
`kill-switch must revert the gate on ${eventName}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.ok(
|
||||
!job.steps.some((s) =>
|
||||
String(s.uses || '').startsWith('actions/checkout'),
|
||||
),
|
||||
'the gate must stay checkout-free to keep the disposable-pool routing',
|
||||
);
|
||||
});
|
||||
|
||||
it('web-shell-visuals.yml capture: untrusted renders ride the disposable pool', () => {
|
||||
// The capture builds and renders untrusted PR code with a read-only
|
||||
// token and no secrets — the disposable pool's exact security model.
|
||||
// The privileged publisher stays wherever it was; only capture moves.
|
||||
const doc = parse(
|
||||
readFileSync(join(workflowsDir, 'web-shell-visuals.yml'), 'utf8'),
|
||||
);
|
||||
const runsOn = String(doc.jobs.capture['runs-on']);
|
||||
assert.match(runsOn, /github\.repository == 'QwenLM\/qwen-code'/);
|
||||
for (const assoc of ASSOCIATIONS) {
|
||||
for (const sameRepo of [true, false]) {
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled: false,
|
||||
eventName: 'pull_request',
|
||||
sameRepo,
|
||||
assoc,
|
||||
}),
|
||||
EPHEMERAL,
|
||||
`capture sameRepo=${sameRepo} assoc='${assoc}'`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled: true,
|
||||
eventName: 'pull_request',
|
||||
sameRepo,
|
||||
assoc,
|
||||
}),
|
||||
JSON.parse(HOSTED),
|
||||
'kill-switch must revert capture to hosted',
|
||||
);
|
||||
}
|
||||
}
|
||||
// Only the event-scoped read-only GITHUB_TOKEN may reach the disposable
|
||||
// pool through this workflow; any other secret reference breaks the
|
||||
// pool's no-long-lived-secrets boundary.
|
||||
const text = readFileSync(
|
||||
join(workflowsDir, 'web-shell-visuals.yml'),
|
||||
'utf8',
|
||||
);
|
||||
const secretRefs = text.match(/secrets\.[A-Za-z_]+/g) ?? [];
|
||||
assert.deepEqual(
|
||||
[...new Set(secretRefs)],
|
||||
['secrets.GITHUB_TOKEN'],
|
||||
'capture must not reference any secret beyond the read-only event token',
|
||||
);
|
||||
assert.equal(doc.permissions.contents, 'read');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ci.yml web_shell_e2e_smoke runner routing', () => {
|
||||
const expression = String(ciDoc.jobs.web_shell_e2e_smoke['runs-on'])
|
||||
.replace(/^\$\{\{\s*/, '')
|
||||
|
||||
@@ -1520,9 +1520,10 @@ jobs:
|
||||
name: 'Test (ubuntu-latest, Node 22.x)'
|
||||
needs: ['classify_pr', 'test_build', 'test']
|
||||
if: "${{ !cancelled() && github.event_name != 'schedule' }}"
|
||||
# The shards' runners: a hosted runner queued this one-line check for
|
||||
# 9 minutes after the last shard finished.
|
||||
runs-on: '${{ fromJSON(needs.classify_pr.outputs.ubuntu_runner || ''["ubuntu-latest"]'') }}'
|
||||
# A one-line aggregate that checks out nothing: the disposable pool runs
|
||||
# it on every trigger (a hosted runner queued it for 9 minutes after the
|
||||
# last shard finished), with the kill-switch as the only hosted fallback.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "ecs-qwen-ephemeral"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
||||
@@ -9,8 +9,9 @@ name: 'Web-shell Visuals'
|
||||
#
|
||||
# Security model: this workflow BUILDS AND RENDERS untrusted PR code, so it runs
|
||||
# on the `pull_request` trigger (fork PRs get a read-only token and NO secrets),
|
||||
# on an ephemeral hosted runner, with `contents: read` and no secrets of its
|
||||
# own. It produces only image/video bytes as an artifact. The privileged step
|
||||
# on a disposable runner (the ecs-qwen-ephemeral pool; hosted only under the
|
||||
# kill-switch), with `contents: read` and no secrets of its own.
|
||||
# It produces only image/video bytes as an artifact. The privileged step
|
||||
# that needs a write token — pushing the images and commenting on the PR — lives
|
||||
# in the separate workflow_run workflow that never checks out PR code.
|
||||
on:
|
||||
@@ -55,7 +56,10 @@ jobs:
|
||||
capture:
|
||||
name: 'Capture web-shell visuals (ubuntu-latest, Node 22.x)'
|
||||
if: "${{ github.repository == 'QwenLM/qwen-code' }}"
|
||||
runs-on: 'ubuntu-latest'
|
||||
# The disposable pool matches this job's security model exactly (untrusted
|
||||
# PR code, read-only token, no secrets) and takes it off the hosted queue.
|
||||
# The kill-switch reverts to hosted, where every step behaves as before.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'') && fromJSON(''["self-hosted", "ecs-qwen-ephemeral"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
# Leave room for cold dependency setup before both render passes.
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
|
||||
@@ -390,10 +390,10 @@ it('reports the required Linux check only when the build and every shard pass',
|
||||
const gate = ci.jobs.test_ubuntu;
|
||||
expect(gate.name).toBe('Test (ubuntu-latest, Node 22.x)');
|
||||
expect(gate.needs).toEqual(['classify_pr', 'test_build', 'test']);
|
||||
// The gate remains on the original association-gated runner, while test
|
||||
// shards can opt into the disposable pool for untrusted forks.
|
||||
// The gate checks out nothing, so it rides the disposable pool on every
|
||||
// trigger; only the kill-switch reverts it to hosted.
|
||||
expect(gate['runs-on']).toBe(
|
||||
'${{ fromJSON(needs.classify_pr.outputs.ubuntu_runner || \'["ubuntu-latest"]\') }}',
|
||||
"${{ (github.repository == 'QwenLM/qwen-code' && vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true') && fromJSON('[\"self-hosted\", \"ecs-qwen-ephemeral\"]') || fromJSON('[\"ubuntu-latest\"]') }}",
|
||||
);
|
||||
expect(condOf('test_ubuntu')).toBe(condOf('test'));
|
||||
expect(gate.steps[0].run).toBe(
|
||||
|
||||
Reference in new issue
Block a user