mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-11 12:12:19 -10:00
fix(ci): keep the pooled visuals capture fork-only
The capture's runs-on was rewritten in acbbc6d9 to read only
github.repository and the kill-switch, so every same-repo PR was routed
onto the disposable pool too — contradicting the design doc's "trusted
and same-repository PR routes stay unchanged" rule and the PR's own
purpose (moving fork CI off GitHub-hosted runners, not relocating jobs
that already had a lane).
The expression now carries the fork clause: untrusted fork PRs ride the
ephemeral pool, same-repo PRs stay hosted. The routed timeout follows the
same fork term (90 pool / 45 hosted), the routing matrix pins both arms
per association, and both design docs record the fork-only scope.
Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
1 parent
ed4d460580
commit
f90e7f0960
4 files changed
+55
-26
No files matched your search
@@ -93,6 +93,10 @@ function evalRunsOn(expression, { ecsDisabled, eventName, sameRepo, assoc }) {
|
||||
/github\.event\.pull_request\.head\.repo\.full_name == github\.repository/,
|
||||
String(sameRepo),
|
||||
],
|
||||
[
|
||||
/github\.event\.pull_request\.head\.repo\.full_name != github\.repository/,
|
||||
String(!sameRepo),
|
||||
],
|
||||
[
|
||||
/contains\(fromJSON\('\["OWNER","MEMBER","COLLABORATOR"\]'\), github\.event\.pull_request\.author_association\)/,
|
||||
String(TRUSTED.includes(assoc)),
|
||||
@@ -877,16 +881,20 @@ describe('credential-free jobs on the disposable pool', () => {
|
||||
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.
|
||||
// The pool exists for untrusted fork PRs only: same-repo PRs stay on
|
||||
// the hosted lane, where this job ran before the migration.
|
||||
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'/);
|
||||
// The expression reads neither head.repo nor author_association (the
|
||||
// repo clause is substituted to a constant), so one association sweep
|
||||
// covers every fork and same-repo shape.
|
||||
assert.match(
|
||||
runsOn,
|
||||
/github\.event\.pull_request\.head\.repo\.full_name != github\.repository/,
|
||||
'capture must keep the fork clause: only fork PRs route to the disposable pool',
|
||||
);
|
||||
for (const assoc of ASSOCIATIONS) {
|
||||
// Fork PR: the disposable pool, whoever authored it.
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled: false,
|
||||
@@ -895,8 +903,23 @@ describe('credential-free jobs on the disposable pool', () => {
|
||||
assoc,
|
||||
}),
|
||||
EPHEMERAL,
|
||||
`capture assoc='${assoc}'`,
|
||||
`capture fork assoc='${assoc}'`,
|
||||
);
|
||||
// Same-repo PR: stays hosted — the disposable pool must never serve
|
||||
// trusted runs, and the kill-switch question never arises there.
|
||||
for (const ecsDisabled of [false, true]) {
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled,
|
||||
eventName: 'pull_request',
|
||||
sameRepo: true,
|
||||
assoc,
|
||||
}),
|
||||
JSON.parse(HOSTED),
|
||||
`capture same-repo assoc='${assoc}' ecsDisabled=${ecsDisabled}`,
|
||||
);
|
||||
}
|
||||
// Kill-switch reverts fork runs to hosted too.
|
||||
assert.deepEqual(
|
||||
evalRunsOn(runsOn, {
|
||||
ecsDisabled: true,
|
||||
@@ -905,7 +928,7 @@ describe('credential-free jobs on the disposable pool', () => {
|
||||
assoc,
|
||||
}),
|
||||
JSON.parse(HOSTED),
|
||||
'kill-switch must revert capture to hosted',
|
||||
`kill-switch must revert fork capture to hosted assoc='${assoc}'`,
|
||||
);
|
||||
}
|
||||
// Only the event-scoped read-only GITHUB_TOKEN may reach the disposable
|
||||
@@ -994,13 +1017,14 @@ describe('credential-free jobs on the disposable pool', () => {
|
||||
Number(diskFloor?.env?.DISK_FLOOR_MAX_USED_PCT) < 89,
|
||||
'the ceiling must refuse the measured 89%-used failure slot',
|
||||
);
|
||||
// The pool pays two cold installs (measured: 22m18s install, Chromium
|
||||
// still downloading at 36m48s), so the hosted 45-minute cap cancels
|
||||
// the job before any render. The ceiling is routed like runs-on —
|
||||
// runner.environment is not available in timeout-minutes.
|
||||
// The fork pool pays two cold installs (measured: 22m18s install,
|
||||
// Chromium still downloading at 36m48s), so the hosted 45-minute cap
|
||||
// cancels the job before any render. The ceiling is routed like
|
||||
// runs-on — runner.environment is not available in timeout-minutes —
|
||||
// keyed off the same fork term: pool (fork, not disabled) 90, else 45.
|
||||
assert.equal(
|
||||
String(doc.jobs.capture['timeout-minutes']),
|
||||
"${{ vars.MAINTAINER_ECS_RUNNER_DISABLED == 'true' && 45 || 90 }}",
|
||||
"${{ (github.event.pull_request.head.repo.full_name != github.repository && vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true') && 90 || 45 }}",
|
||||
);
|
||||
// Unpinned workers size Chromium from the HOST's cores inside a 4-CPU
|
||||
// container: the first pooled run lost its runner to CPU/memory
|
||||
|
||||
@@ -56,15 +56,16 @@ jobs:
|
||||
capture:
|
||||
name: 'Capture web-shell visuals (ubuntu-latest, Node 22.x)'
|
||||
if: "${{ github.repository == 'QwenLM/qwen-code' }}"
|
||||
# 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"]'') }}'
|
||||
# The disposable pool exists for untrusted fork PRs only (design doc:
|
||||
# "Trusted and same-repository PR routes stay unchanged"). Same-repo PRs
|
||||
# stay on the hosted lane, where this job ran before. The kill-switch
|
||||
# reverts forks to hosted too, where every step behaves as before.
|
||||
runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && github.event.pull_request.head.repo.full_name != github.repository) && fromJSON(''["self-hosted", "ecs-qwen-ephemeral"]'') || fromJSON(''["ubuntu-latest"]'') }}'
|
||||
# Cold-store pool runs measured 22m+ installs before any render, so the
|
||||
# pool arm gets 90 and hosted keeps 45. runner.environment is not
|
||||
# available here; key off the same variable runs-on reads. Rationale and
|
||||
# measurements: docs/design/2026-11-07-ephemeral-container-runners.md.
|
||||
timeout-minutes: "${{ vars.MAINTAINER_ECS_RUNNER_DISABLED == 'true' && 45 || 90 }}"
|
||||
# fork pool arm gets 90 and same-repo/hosted keeps 45. runner.environment
|
||||
# is not available here; key off the same fork term runs-on reads.
|
||||
# Rationale and measurements: docs/design/2026-11-07-ephemeral-container-runners.md.
|
||||
timeout-minutes: "${{ (github.event.pull_request.head.repo.full_name != github.repository && vars.MAINTAINER_ECS_RUNNER_DISABLED != 'true') && 90 || 45 }}"
|
||||
steps:
|
||||
- name: 'Checkout PR head'
|
||||
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
|
||||
|
||||
@@ -183,9 +183,12 @@ keep their current routing. This avoids migrating browser system dependencies
|
||||
that the new image has not verified, and leaves the routing image relationship
|
||||
of the existing classifier job and the other workflows unchanged.
|
||||
|
||||
Update 2026-11-08: the credential-free Web-shell Visuals capture job has
|
||||
moved onto this pool; the main CI `Test (ubuntu-latest, Node 22.x)`
|
||||
aggregate gate was tried here and moved back off (below).
|
||||
Update 2026-11-08: the credential-free Web-shell Visuals capture job
|
||||
moves onto this pool **for untrusted fork PRs only** — same-repo PRs stay
|
||||
on the hosted lane, where the job ran before (the original rule above,
|
||||
"trusted and same-repository PR routes stay unchanged", still governs).
|
||||
The main CI `Test (ubuntu-latest, Node 22.x)` aggregate gate was tried
|
||||
here and moved back off (below).
|
||||
The capture job matches the pool's security
|
||||
model exactly (untrusted PR code, read-only token, only the event-scoped
|
||||
`GITHUB_TOKEN`), which supersedes the browser-dependency exclusion above for
|
||||
|
||||
@@ -144,10 +144,11 @@ JIT 提供正常协议下的单任务注册语义;宿主机管理程序负责
|
||||
保持当前路由。这样不把新镜像尚未验证的浏览器系统依赖一起迁入,也不改变
|
||||
现有分类 job 与其他 workflow 的路由镜像关系。
|
||||
|
||||
2026-11-08 更新:无凭据的 Web-shell Visuals capture 任务已迁入本池;
|
||||
主 CI 的 `Test (ubuntu-latest, Node 22.x)` 聚合门禁曾迁到这里、又迁了
|
||||
回去(见下)。capture
|
||||
任务与本池安全模型完全一致(不可信 PR 代码、只读 token、仅事件级
|
||||
2026-11-08 更新:无凭据的 Web-shell Visuals capture 任务迁入本池,
|
||||
**仅限非可信 fork PR**——同仓 PR 留在托管泳道(即本任务迁移前所在
|
||||
位置;上文「受信与同仓 PR 路由不变」的规则仍然有效)。主 CI 的
|
||||
`Test (ubuntu-latest, Node 22.x)` 聚合门禁曾迁到这里、又迁了回去
|
||||
(见下)。capture 任务与本池安全模型完全一致(不可信 PR 代码、只读 token、仅事件级
|
||||
`GITHUB_TOKEN`),因此上面「不迁浏览器依赖」的排除对这一个任务不再适用:
|
||||
它在 self-hosted 前置检查后复用镜像自带 Node,先过按本泳道标定的磁盘
|
||||
水位门禁(剩余 ≥8 GiB 且已用 ≤85%——共享的 2 GiB 默认值曾放行一个
|
||||
|
||||
Reference in new issue
Block a user