1166 Commits
Author SHA1 Message Date
易良andyiliang114 708ba347d3 feat(ci): add disposable external PR runners (#16513)
* feat(ci): add disposable external PR runners

* test(ci): align platform lanes with disposable runner routing

---------

Co-authored-by: yiliang114 <[email protected]>
2026-11-07 14:24:33 +00:00
Shaojin WenandQwen-Coder cf29f90062 fix(ci): route the review/resolve permission gate to the ECS pool (#16482)
* fix(ci): route the review/resolve permission gate to the ECS pool

The authorize job gates every @qwen-code /review and /resolve command, but its
ECS routing required github.event.pull_request, which issue_comment and
workflow_dispatch payloads do not carry. Every command therefore fell through
to ubuntu-latest, and the gate sat in the hosted queue for hours (PR #15286:
3h10m for a 4-second job) while the ECS pool was idle.

Route issue_comment and workflow_dispatch to the persistent ECS pool, matching
qwen-autofix.yml's route/review-scan pattern for PAT-bearing API-only steps.
authorize runs no agent, checks out nothing, and processes no PR content, so
this does not widen the fork attack surface. PR-payload events keep the
same-repo fork guard; the hosted fallback and ECS kill switch are unchanged.

resolve-pr and publish-resolution stay on hosted by design (ephemerality); this
removes the gate wait only.

* fix(ci): harden the authorize ECS-routing pins and risk docs

Address review feedback on the gate routing change:
- Pin the checkout-free invariant the ECS routing depends on: a uses: step on
  authorize would execute contributor-authored files on the persistent pool
  with CI_BOT_PAT in the step env while the expression pin stayed green (R1-1).
  Scoped to authorize; mutation-witnessed (adding actions/checkout reds it).
- Record the trigger-set widening in the design-doc Risks (both languages):
  issue_comment makes the gate fireable by any GitHub user on a fork PR, and
  the should_review authorization fact is now produced on the shared host and
  consumed downstream without re-deriving (R1-2).
- Correct the precedent wording: route loads only the read-only github.token;
  review-scan is the job that loads CI_DEV_BOT_PAT on the pool.

* fix(ci): harden the shared-pool authorize gate against gh config reroute

Address the round-2 CHANGES_REQUESTED review (R2-1 Critical, R2-2):

R2-1: routing the gate to the persistent ecs-qwen pool moved its CI_BOT_PAT
gh api call onto a host with a shared, writable ~/.config/gh. A co-resident
survivor could plant http_unix_socket there, receive the PAT, and forge the
permission response downstream consumes as should_review. Prepend the same
pins as qwen-autofix.yml's route/review-scan — export GH_HOST=github.com plus
a private, guarded GH_CONFIG_DIR (a failed mktemp exits 1; the gate fails
closed). No `unset GH_TOKEN`: it is this step's only credential. Witnessed
with real gh 2.103.0: the as-written step leaks the PAT to a planted socket
and approves a stranger; the hardened step denies with the socket untouched.

R2-2: extend the checkout-free guard to the run: half — pin the step
inventory to the single permission step and forbid `${{` spliced into any run
body (event values must arrive via env:). Mutation-witnessed: removing the
preamble and adding a run-only second step each red the test.

Also correct the design-doc parity claim (both languages): the exposure
matches review-scan only WITH these gh pins; document the hardening.

* fix(ci): extend the ECS gate routing to triage and harden the gate pins

Two batches, verified together before push:

1. Extend the command-gate routing to qwen-triage.yml's authorize (the
   /triage, /tmux, /verify sibling blind spot): the same issue_comment /
   workflow_dispatch disjuncts, and — because that job has three steps that
   spend CI_BOT_PAT on gh — the same self-contained GH_HOST + guarded
   GH_CONFIG_DIR preamble on each, per the repo's per-step convention. Raise
   the recorded workflow size baseline to match the added hardening.

2. Address round-3 review (R2-2 fix-induced, R3-1): the gate pins tested
   substring presence only. Now evaluate the comment-stripped live lines and
   pin the guarded mktemp form + fail-closed shape, require GH_HOST /
   GH_CONFIG_DIR exported before the first gh api, and forbid executing
   repository code via relative paths or interpreters (the pool cwd is the
   reused $GITHUB_WORKSPACE). Applied to both the review and triage gate
   tests. Mutation-witnessed: an unguarded one-liner, a relocated preamble,
   and an appended `node ./…` line each red the tests.

Tests: qwen-pr-review-workflow.test.js 271, qwen-triage-workflow.test.mjs 131,
ci-runner-routing.test.mjs 103 — all pass; prettier + ESLint clean.

* fix(ci): supply RUNNER_TEMP to the triage gate replay tests; harden the gate pins

R4-1 (Critical): the new ${RUNNER_TEMP} gh-config preamble is the first
RUNNER_TEMP reference in the triage permission step, and three replay
harnesses in scripts/tests/qwen-triage-workflow.test.js execute that body
verbatim without setting RUNNER_TEMP — off-CI (npm run test:scripts) the
fail-closed guard exits 1 before writing should_run. Set RUNNER_TEMP to each
harness's existing mkdtempSync dir (the :1773 precedent); the workflow's
fail-closed guard is unchanged. Verified: the file is red under
`env -u RUNNER_TEMP` before and 203/203 green after; dropping the line reds it
again. (The review named two harnesses; running the full file surfaced a third.)

Also clear the round-4 deferred notes:
- R4-2: the /verify ack comment no longer promises a receipt "even when the
  sandbox lane is backed up" — the gate now also queues with the pool.
- R4-3/R4-4: the triage routing pin is now an exact expression equality, the
  step inventory is pinned, and PAT-bearing steps are identified by env
  GH_TOKEN rather than gh subcommand spelling.
- R3-1: tighten the no-repository-code deny-list (quote boundaries +
  interpreter as a word) so `bash -c './x.sh'` no longer slips past;
  mutation-witnessed in both the review and triage gate tests.

Tests: triage vitest 203 (off-CI), triage .mjs 131, review vitest 271,
ci-runner-routing 103 — all pass; prettier + ESLint clean; size gate green.

* fix(ci): correct the gate-routing narrative; close the round-5 verify gaps

R5-1 / F1 (narrative): the motivating incident is a /resolve, whose
end-to-end latency this PR does not change — resolve-pr stays on
ubuntu-latest, so only the gate wait is removed. State this in both
design-doc languages and the pinning test comment; the saving lands on
/review, /triage, /tmux, /verify (pool-routed downstream). Correct the
"cancelled without ever running" sentence to the run's timeline (resolve-pr
started when the gate finished and was cancelled ~29m36s later).

S1: the no-repository-code deny-list missed absolute-path interpreters
(/usr/bin/node x.js) and versioned Python (python3.11 x.py); widen the regex
(optional path prefix + python[0-9.]*) in both gate tests. Mutation-witnessed:
both previously-surviving mutants now red, clean tree green.

S2: each hardened gh step minted a $RUNNER_TEMP/*gh-config.* dir that nothing
removed; add trap 'rm -rf "$GH_CONFIG_DIR"' EXIT to all four steps (RUNNER_TEMP
survives between jobs on the pool).

S3: record the exact final workflow sizes in .size-baseline.

Tests: triage vitest 203 (off-CI, RUNNER_TEMP unset), review vitest 271,
triage .mjs 131, ci-runner-routing 103 — all pass; prettier + ESLint clean;
size gate green.

* fix(ci): pin the no-repository-code invariant on every triage gate step

The triage gate meta-test applied the relative-path/interpreter denylist only to PAT-bearing steps, so a repository-code execution line added to the credential-free budget step survived the suite (witnessed by mutation). The no-repository-code invariant is a job-wide safety premise of the ECS routing: even a credential-free step that runs a planted file leaves a survivor on the persistent host. Pin it on every step's comment-stripped live lines, and align both design-doc versions with the widened coverage and the witnessed mutation set.

Co-authored-by: Qwen-Coder <[email protected]>

* docs(ci): correct the routing-matrix coverage claim for the triage gate

The validation plan said the triage gate is pinned by the triage meta-test AND the ci-runner-routing matrix, but the matrix never reads either authorize job (it pins the sibling pool jobs). State the actual coverage in both language versions.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 13:47:42 +00:00
4d61487419 ci(release): only file issues on failure and reconcile stale ones (#15852)
* ci(release): file failure issues only for failures and close stale ones

The VSCode companion and Chrome extension notifiers filed an issue when a job
was merely cancelled, and nothing closed that issue once a later attempt
shipped, leaving a live failure report against a released version.

Drop the cancelled arm from both notifiers and add a success-gated
reconcile-failure job to each, reusing the shared release step. That step now
takes the title prefix and release version so all three release workflows can
close the bot-authored issue an earlier attempt filed.

Fixes #15840

* test(release): pin the version boundary when closing a stale issue

A nightly sibling of the same release sorts first in the gh issue list result, so dropping the " on " suffix from the title guard silently closes another release's issue. The fixture now covers that ordering and the boundary mutant is killed.

* test(release): tie each reconcile prefix to the title its notifier files

The fan-out suites asserted `FAILURE_TITLE_PREFIX` against a hand-written
constant, so renaming the notifier's `--title` left both green while the
real reconcile would match no issue and the stale release-failure issue
would stay open against a shipped release (QwenLM/qwen-code#15852).

Read the prefix out of the notifier's own `--title` argument and assert it
starts with the prefix the reconcile job passes, instead of restating the
literal a third time.

* test(ci): pin the version token and separator in the release title contract

reconcile-failure reads a notifier title through startswith(prefix + version
+ " on "), but the suite only pinned the prefix. A notifier that dropped
the version or separated it with something other than " on " closed
nothing while both suites stayed green, which is a silent failure of the
stale-issue cleanup (#15840).

Derive the rest of the title from the notifier's own --title, accept the
version variable with or without its :-unknown default, and assert the
reconcile job reads the same variable the notifier writes.

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: 2SinceJuly <[email protected]>
2026-11-07 12:52:38 +00:00
Shaojin WenandQwen-Coder 61aef512e7 feat(web-shell): offer a details side task from pending AskUser questions (#16458)
* feat(web-shell): offer a details side task from pending AskUser questions

While an AskUserQuestion is pending, the Web Shell hides the composer and
the btw shortcut ignores the pending-approval state, so a user who does not
understand the options can only answer blindly or ignore the question.

Add an "Ask for details" action to the pending panel. It opens a side task
whose prefilled prompt snapshots the question and options and asks for a
verified explanation — background, per-option meaning, differences, impact
and risks, and a recommendation — delivered as a self-contained single-file
HTML in controlled ASD-STE100-style sentences under .qwen/tmp/, previewable
in the right panel. The side task inherits the parent context but never
answers the question or touches the main session; the question stays
pending until the user answers or ignores it.

The action is main-view-only and gated on the session_side_task capability,
matching /btw side. No daemon, protocol, or settings changes.

* fix(web-shell): one details side task per question, reachable on narrow panels

Post-publication audit of the AskUser details action found three issues:

- Repeat clicks provisioned duplicate side tasks for the same pending
  question, each burning a model turn. Give the details tab a stable id
  derived from the parent session and the permission request id and upsert
  it, reusing the artifact panel's open-or-focus pattern; /btw side keeps
  its unique per-invocation draft id.
- The question snapshot dropped multiSelect, so the explanation prompt did
  not say options can combine. Append a [multiSelect] marker (strict true).
- On a 320px two-question panel the actions row overflowed to the left and
  pushed the details button out of the panel (left: -103px in a real-browser
  probe; scrollWidth misses left overflow on a flex-end row). Let the row
  wrap and pin a narrow-viewport regression test asserting every action
  button stays inside the panel and stays clickable.

Each fix carries a red/green witness: the repeat-click App test, the
multiSelect unit test, and a flex-wrap mutation of the narrow visuals test.

* fix(web-shell): keep the details tab id within the daemon clientId budget

The dedup fix derived the details tab id from the parent session id AND the
request id. SideTaskPanel derives the pane's daemon clientId as
side-task:<parentSessionId>:<tabId>, which with two uuids reached 138 chars —
over the daemon's MAX_CLIENT_ID_LENGTH (128). The child session load then
400s, the pane never connects, and the prefilled prompt is never sent.
Caught by re-running the real-daemon scenario on the post-fix head; mocked
unit tests could not see it.

Derive the stable id from the permission request id alone (a uuid; tabs are
already bucketed per parent session), bringing the clientId to 101 chars.
Add a SideTaskPanel regression test asserting the derived clientId stays
within 128 for uuid-length inputs; the old id shape fails it with 138.

* fix(web-shell): retain the details question-to-child association across tab close

Review found that deduplicating only against open tabs loses the existing
child when the details tab closes: clicking "Ask for details" again for the
same pending request provisioned a second child, and reopening the child
from the side-task catalog first did not deduplicate either (its tab id is
side-task:<sessionId>, not the details-request id).

Persist the pending-request-to-child association per parent session in
localStorage, written when the details child is created. On click, focus
whichever open tab shows the known child (details tab or catalog tab);
with no tab open, reopen one attached to the existing child session — no
draft, no initial prompt, so nothing is provisioned or sent again. One
entry per parent supersedes the resolved question's association, keeping
the store bounded; the association also survives reloads.

Regression tests cover both reported paths (close + re-click keeps exactly
one createSideTaskSession call and reopens with the child's sessionId and
no initialPrompt; catalog reopen + click focuses the single existing tab).
Red witness: dropping the association lookup fails the close + re-click
test with two creation calls.

* fix(web-shell): close the details association lifecycle gaps

Re-review of the association persistence found three lifecycle gaps, all
fixed here with regression tests and mutation witnesses:

- A child exists before its explanation prompt is admitted. Reopening a
  closed details tab now retains the prompt on the tab; the pane's existing
  empty-session guard delivers it exactly once (only while the child has no
  user prompt) and never re-sends it.
- Archiving a details child left its persisted association pointing at an
  unloadable session (the daemon's load route 409s session_archived).
  archiveSideTasks now invalidates associations pointing at archived
  children, so the next click provisions a fresh child.
- Closing the draft tab before creation resolved skipped the association
  write, orphaning the child and provisioning a duplicate on the next click.
  The parent identity is captured when creation starts, and the write no
  longer depends on the tab still existing.

Also pin two invariants the deep-verification report found unasserted:
the derived clientId budget with uuid-length inputs through the real tab-id
derivation (App test), and the formatter's array guard against non-array
objects (formatter test).

* fix(web-shell): supply the unsent details prompt to a catalog-reopened tab

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): treat media-only user prompts as interaction in the side-task guard

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): validate details child archive state at the read site and address review suggestions

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): replace stale details tab on fresh draft and re-check pending after the liveness query

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): replace a details tab bound to a different child than the validated binding

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 19:16:53 +08:00
易良andQwen-Coder 5890329390 fix(ci): probe the host egress proxy so mainland release hosts can build the sandbox image (#16281)
The docker-container driver's buildkitd inherits neither the runner's proxy env nor the host network namespace. On ecs-qwen-hz1-17/18 the DNS answer for registry-1.docker.io is poisoned, so the pinned node:22-slim resolution dies with a direct-dial timeout before any application step — five consecutive image-build failures (#16039, #16162) that forced ECS_RELEASE_HOST_RUNNER_ENABLED=false.

Probe the fleet-managed proxy marker and inject network=host plus env.*_PROXY driver opts only when the proxy answers a live registry probe. Hosted and HK runners have no marker file, so the probe emits nothing and their builds keep the default direct route. Verified live on hz1: unopted builder reproduces the incident, opted builder resolves the pinned digest for both platforms.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 08:31:15 +00:00
Shaojin WenandQwen-Coder f387c3a75a fix(ci): recreate git metadata before the publish-triage checkout (#16494)
* docs(ci): state the real publish-triage safety argument

The publish-triage header comment claimed a 'clean runner', but the job routes to the persistent ECS pool like publish-tmux and runs without a container. The guarantee that actually holds is trusted-code-only: a sparse checkout pinned to github.sha, never agent-provided code, and a publisher that imports only node builtins plus one relative helper, so a reused workspace offers no bare-specifier node_modules to hijack. Reword the comment to say that, per the non-blocking note in the #16468 re-review. Comment-only change; no job, timeout, or behavior delta.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): recreate git metadata before the publish-triage checkout

The patrol closeout on the comment rewrite checked the claims against the file and found two gaps. First, publish-triage was the only job reaching actions/checkout on the shared pool without recreating the workspace .git: an earlier agent run owned that workspace and could have planted hooks/config that checkout's git commands would execute in the one job holding the write PAT. Add the same Recreate runner Git metadata step the triage/tmux/verify jobs run before their checkouts, and add publish-triage to the qwen-runner-checkout-workflow matrix that behaviorally executes it (removal witness: deleting the step turns the new entry red). Second, narrow the header: drop the 'it is safe there' certification in favor of naming the controls, and replace 'sole writer' — the triage job's reaction and lifecycle host steps also write with the PAT — with 'the only path from the agent's drafts to GitHub'. Design pair updated in both languages.

Co-authored-by: Qwen-Coder <[email protected]>

* docs(ci): keep the new step comment off the PAT-holder claim

The publish-triage step comment and the design pair said the checkout runs in
"the job that holds the write PAT", which is the same overstatement this PR
removes: the triage job's reaction, status comment, and finalize step also
write with that PAT. Name the publisher as what it is instead, so the header's
new parenthetical and the design prose agree.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): pin the publish-triage safety claims and derive the checkout guard

Addresses the remaining review findings in this PR. R1-2: publish-tmux's banner still claimed a clean GitHub-hosted runner while its runs-on selects the ECS pool; reword it to the controls that actually hold (checks out nothing, runs no repository code), routing untouched. R1-4: the publisher's bare-specifier-free import closure is load-bearing for the pool argument but nothing pinned it; add a meta-test that scans the closure and matches it against the sparse-checkout list (witness: adding import 'yaml' reddens exactly that test). R2-2: the runner-checkout guard's hand-typed job list is what missed publish-triage; derive the qwen-triage entries from pool-routed jobs that check out, so the next such job reddens with no test edit (witnesses: a new pool checkout job without the reset, and deleting publish-triage's reset, both redden). R2-3: pin publish-triage's ownership-restore fallback chain with assertUnconditional (witness: deleting the sudo fallback reddens). R2-1: the design pair's Files-affected list still said the runner-checkout test needs no change; correct both languages and narrow the pinned-by parenthetical to what each suite actually pins.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): make the new pins layout-independent, floored, and complete

Round 3 found three holes in the pins added by the previous commit, all in the guards themselves. R3-1: specifiersOf used a single-line pattern, so Prettier-wrapped imports were invisible in both directions — a wrapped bare specifier kept the closure test green while the pool job would die ERR_MODULE_NOT_FOUND, and a wrapped relative one dropped a needed entry from the expected sparse list. Span newlines, hoist the scanner to module scope, and fixture-pin it (wrapped relative, wrapped bare, and the publisher's own multi-line node:fs import); those three fixtures were verified red against the old pattern and green after, and the sparse comparison stays exact equality. R3-2: the derived job set had no floor, so rewriting one pool job's runs-on into a vars lookup silently dropped its row; add an arrayContaining floor that names the lost job while still admitting new pool jobs with no test edit. R3-3: the ownership test's name promised the full fallback chain but assertUnconditional matches chmod unanchored, so deleting the sudo chmod arm stayed green; pin that arm in this test rather than the shared helper, which has five other call sites. Witnesses: wrapped bare import reddens the closure test, a vars-lookup runs-on reddens the floor naming publish-triage, and deleting the sudo chmod arm reddens the ownership pin — each with byte-level restore.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): prove the sparse checkout by loading it, not by scanning syntax

The APPROVED review deferred three findings against the closure guard, all instances of one root cause: the guard modelled JS module syntax with a regex. Proven on the shipped code — a 1164-character wrapped bare import left it green (the 400-char bound), and an out-of-tree helper turned the byte-offset slice into the garbage entry /.github/scripts/elper.mjs. Replace the scanner with the condition the pool actually creates: materialize exactly the sparse-checkout list into a temp tree, assert no node_modules exists above it, and import the publisher there. Sufficiency is the load succeeding; necessity is each .mjs entry omitted making it fail; the owner policy is tied by resolving the publisher's own new URL literal and requiring that exact path in the list, instead of an unanchored substring plus a hard-coded entry. This removes the scanner and its layout fixtures rather than patching them again — wrapped imports of any length, prose that looks like an import, and out-of-tree helpers are all covered by the load itself. Witnesses: the long wrapped bare import that was previously green now fails, as do a short bare import, an out-of-tree helper, a helper dropped from the list, a stale extra entry, and a moved owner-policy path; each with byte-level restore.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 15:21:36 +08:00
e99286813e fix(ci): preserve Maven suite results across cache write-back failures (#15274)
* fix(ci): scope the sdk-java test lock to the cache write-back (#15262)

The Managed Agent fast tier can fail without reporting a test result after waiting out the per-host SDK Java lock. #15128 scoped the MariaDB lane's lock to its cache write-back, but the test job still held the same lock across the SDK and Managed Agent Maven suites, so a long full-tier suite could starve the fast tier on that host.

Seed the test job's Maven repository from the host cache, run both suites unlocked from that job-local copy, and take the ticketed host lock only for the cache write-back. The write-back children close fd 9 so a cancelled job's survivor cannot retain the lock. Pin the new order and the write-back fd closures in the workflow tests, and update the fair-lock design docs and the workflow size baseline.

Fixes #15262

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): keep sdk-java cache write-back best-effort (#15262)

* fix(ci): keep the sdk-java gate result across the cache write-back (#15262)

The write-back runs after the suite, so the step's exit code must come from
the suite: both lanes now track the Maven status, demote only a lock-wait
timeout with a step-summary record, and redden when the queue itself is
unusable. The wait drops to 1800 s because it guards only the bounded copy;
the seed stops hiding cp's diagnosis while staying quiet on a fresh host;
the design docs record the head-of-queue stall the shorter hold does not
bound.

* fix(ci): revert the sdk-java lock helper to the PR footprint (#15262)

The previous round changed .github/scripts/sdk-java-lock.sh, which this PR never touched; the deterministic gate rejects that expansion. Restore the helper and keep both call sites on the pre-round `if !` shape, retaining the 1800 s wait and the step-summary record. Drop the status-2 pins and tests that only existed for the reverted helper, and record the unusable-queue gap as a follow-up in the design docs. The timeout-returns test stays.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): de-alias every resolver tracking file and guard an unusable lock queue (#15262)

* fix(ci): de-alias Maven install metadata in the sdk-java seed (#15262)

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): restore the per-job Maven seed binding (#15262)

* fix(ci): keep sdk-java resolution metadata out of the host cache (#15262)

The seed drops maven-metadata-*.xml hardlinks, but the write-back still
published job-local metadata with cp -n, where the first publisher's copy
could never be refreshed. Exclude it at both write-back sites and pin the
seed and write-back name sets to each other.

Also witness each suite-status exit with an exit-1 case, restore the lock
test's combined-stream assertion, and bring the design docs' acceptance
criteria and fault arms up to date.

* test(ci): carry the deferred-status helper into the sdk-java slices

The tolerated-skip arms now call mvn_report_failed_suite, which the step
defines above the lock block. Every slice that starts at lock_setup_fault()
or at the write-back therefore ran bash -e without the definition and died
with 127, which read as a code failure rather than a missing prelude.

* docs(ci): clarify remaining sdk-java cache hardening scope

* fix(ci): bound failed MySQL cache waits and exclude resolver residue

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): clear inherited resolver state in the Maven cache seed prune

The seed de-alias prunes carried -links +1, which restricted deletion to
the hardlinks cp -aln created in this job. RUNNER_TEMP survives across
jobs on the pool, so a previous job's mutable resolver state (nlink 1
*.lastUpdated, _remote.repositories, resolver-status.properties) survived
in the job-local repository and could redden an otherwise green Maven
lane. Drop -links +1 from all four seed prunes so inherited state is
cleared again; the write-back's -links 1 delta filter is unchanged and
the seed stays best-effort (2>/dev/null || true).

Move the deAlias test pin with the fix and add a regression witness that
plants single-link markers in the job-local repository before the seed
body runs and asserts they are gone (red with -links +1 restored, exit 1;
green with the fix, on macOS and Linux). Re-record the sdk-java.yml
.size-baseline entry (153970 bytes) and synchronize both design-doc
languages, including the nine-coordinate set the main merge brought in.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen Autofix <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-07 08:51:30 +08:00
Shaojin WenandQwen-Coder dd4515ca27 feat(ci): isolate triage agent credentials and run all triage on ECS (#16468)
* feat(ci): isolate triage agent credentials and run all triage on ECS

The triage agent carried a write PAT, so comment reruns and external fork-PR triage had to stay on ephemeral hosted runners while hosted capacity is tight and the ECS pool has headroom. Wire the token-isolation follow-up the workflow already documented: the agent reads GitHub with the job's read-only GITHUB_TOKEN and expresses every write as a draft; a separate publish job validates and executes drafts with the bot PAT, using one publisher script shared with local /triage runs. Contain the agent job, launch the CLI directly behind the hardened loopback model proxy, and move the deterministic maintainer resolver into the publisher. With no write credential in the agent environment, route every triage trigger to ECS behind the existing kill switch, and retire author_can_write. Markers, bot identity, and the finalize workflow's approve-on-green protocol are unchanged.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): bump triage CI budget and address triage review findings

The new publish-triage job crossed the .ci-budget ratchet (7->8 jobs, 280->290 declared timeout-minutes), aborting Lint & Static before any lint or helper test ran. Bump the budget entry, mirror the artifact-name fallback expression on the upload side so an early-death run's drafts stay addressable, and enforce SKILL.md's process-label rule at the publisher chokepoint (welcome-pr, maintainer, help wanted, good first issue) with fail-closed tests, aligning the issue-workflow instruction that previously contradicted the rule.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): harden the triage draft pipeline after an adversarial audit

Six findings from a multi-round pre-push audit, each with a mutation witness: kill lingering agent-user processes before the drafts strip/upload so a detached child cannot re-plant symlinks in the race window (upload-artifact follows them and RUNNER_TEMP is host-shared; unkillable survivors drop the draft set deny-by-default); lstat the draft containers themselves in the publisher preflight, since a symlinked bodies/ redirects the realpath containment base; reject cross-kind actions (review/close-pr on issues, close-issue on PRs); install util-linux for runuser as the sibling lanes do; make the mktemp'd proxy-wrapper directory traversable by the agent user so the gh/git wrappers do not silently fall out of its PATH; pin the artifact-name symmetry, the kill-before-strip ordering, and the toolchain install line. Design pair updated in both languages.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 18:17:45 +00:00
易良andQwen-Coder 8660f75430 fix(ci): refresh host assets on older PR checkouts (#16476)
* fix(ci): refresh host assets on older PR checkouts

Co-authored-by: Qwen-Coder <[email protected]>

* style(ci): format host asset regression cases

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 01:42:26 +08:00
Shaojin Wen 478c2f87ab feat(ci): make flaky-patrol rerun and no_action decisions visible (#16459)
* feat(ci): make flaky-patrol rerun and no_action decisions visible

The CI Failure Patrol posted only a hidden HTML marker when it re-ran a
flaky failure or could not classify one, so maintainers saw a red PR with
no explanation and clicked Update Branch — advancing the head, dismissing
approvals, and re-running the whole CI for a failure that was already
being handled on the same commit.

Post a short bilingual notice alongside the marker for both decisions:
the rerun notice states the transient cause and that no Update Branch is
needed; the no_action notice asks for manual review before rerunning or
updating the branch. The deterministic-failure comment is byte-identical,
and the post-success reset marker stays hidden.

Pin the visible notices, the marker round-trip through dedup and the
per-head action count, and reason escaping for all three actions.

* fix(ci): strip bidi overrides from flaky-patrol notice reasons

An adversarial audit of the visible patrol notices found that bidi
control characters (U+200E/U+200F, U+202A-U+202E, U+2066-U+2069) passed
through the reason escaping untouched. The reason is classifier output
derived from untrusted CI logs, and a right-to-left override can visually
rewrite the very sentence maintainers trust when deciding whether to
update a branch.

Drop those characters in the shared escaping helper so all three visible
decisions (and the deflake issue signature) are covered, and pin the
stripping for each action. Removing the strip line fails the new test.

* fix(ci): cover the Arabic letter mark in the patrol bidi strip

An escalated audit round compared the strip class against the full
Unicode Bidi_Control set and found U+061C (Arabic letter mark) missing;
it can reorder adjacent Arabic-script text in a visible notice. Add it
to the class and extend the per-action pin payload, so the class now
matches the complete Bidi_Control set. Removing just U+061C fails the
pin test.

* fix(ci): collapse newlines in patrol reasons and pin the audit gaps

The verification round found that a newline plus a line-start fence in a
classifier reason opens a block-level code fence in the visible notice:
the folded Chinese block and the tracking marker render as visible code.
Collapse newlines and their surrounding whitespace to a single space in
the shared escaping helper, keeping every reason single-line.

Close the coverage gaps the same round measured: feed all twelve
Bidi_Control code points individually, pin the English-before-fold and
Chinese-inside-fold placement of the deterministic comment, and assert
the fold's closing tag in all three notices.

Witnesses: removing the collapse, narrowing the bidi class, dropping
U+200F alone, swapping EN/ZH, or dropping the closing tag each fails the
suite; a marked rendering probe shows the fence swallowing the fold
before the fix and a clean render after; benign comment bodies remain
byte-identical to base.
2026-11-06 23:10:16 +08:00
liuxuely0626andluoyun ee1a9b1e1e fix(ci): move audio prebuilds to macOS 15 (#16464)
Co-authored-by: luoyun <[email protected]>
2026-11-06 22:47:15 +08:00
7cb46d9fed feat(hooks): add FileChanged notifications for local sessions (#13969)
* feat(hooks): add FileChanged notifications for local sessions

* fix(ci): preserve shell gate coverage and trim transcript data

* fix(ci): flush debug prompts and isolate git fixtures

* fix(hooks): respect ACP watcher ownership and skill registration

* test: stabilize hosted recovery and failed spawn fixtures

* fix(ci): budget the complete hosted integration fork

* fix(runtime-broker): accept MariaDB CSI guard metadata

* test: align schema baseline and hook dispatch waits

* fix(ci): allow hosted shared fork to finish all classes

* fix(hooks): preserve file watching across errors and cwd changes

* fix(ci): isolate reference archive extraction per run

* test(ci): keep reference archive checks dependency-free

* fix(hooks): constrain recursive watches and repair CI

* test(web-shell): synchronize verified connection return

* fix(ci): correct runtime broker callback indentation

* test(web-shell): match the managed workspace registration label

Keep the remote workspace registration smoke checks in sync with the service label added by unified workspace navigation. Preserve exact text matching and all registration and replay assertions.

* fix(ci): reduce standalone runtime fixture overhead

* fix(hooks): ignore implicit atomic-write temporary files

* test(managed-agent): recover initial node port conflicts

* fix(hooks): observe parents before discovery anchors disappear

* test(managed-agent): await stream cleanup before recovery teardown

* test(managed-agent): align hosted shell fixture capabilities

* test(managed-agent): let OS choose initial harness port

* fix(ci): restore runner-local Java cache permissions

* test(runtime-broker): use native execution profile paths

* test(managed-agent): bind valid workspace upload probes

* test(managed-agent): reuse the upload probe digest fixture

* test(managed-agent): bind hook fault listeners to available ports

* test(managed-agent): align the native profile path fixture

* test(managed-agent): Wait for projection refusal retries

* ci: Prepare daemon Java cache before Node preflight

* test(managed-agent): check channel model calls after delivery

* fix(hooks): observe explicitly registered symlink targets

* test(managed-agent): separate consumption from relay snapshots

* test(managed-agent): target tamper injection by resource key

* test(managed-agent): exhaust attestation EOF retry in fault gates

* fix(ci): bound RocketMQ release downloads

* test(tui): await the restored assistant before capturing history

* fix(ci): use a checksum-pinned RocketMQ mirror

* fix(ci): provision missing browser test fonts

* fix(ci): quote the font probe environment

* fix(ci): prepare browser fonts without elevation

* test(core): compare complete prune reports without ordering

* fix(ci): repair only runner-local Java tool caches

* test(managed-agent): qualify fixture JDBC templates

* fix: isolate reconnect hooks and cancellation test fixtures

Skip chat initialization so MCP reconnect cannot re-enable file hooks, and keep cancellation fixtures isolated from the background worktree scanner.

* fix(ci): tolerate a missing Java cache helper on resumed releases

Resuming an existing release checks out a tag that may predate the helper, which failed all five Java jobs before setup-java ran.

* fix: finish the main merge for harness deadlines and workflow size

Use the renamed settle-timeout accessor in the merged hosted-harness tests,
and record sdk-java.yml's new size after the resumed-release cache guard.

* style(managed-agent): restore import grouping in merged verification tests

* test(managed-agent): require consumed child result mirrors

* test(web-shell): center repository settings capture

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: yungsen.hys <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-06 21:43:01 +08:00
chencheng (云谦) 1b1f4455d9 ci: build the PR tree once and shorten the required path (#16450)
* ci: build the PR tree once and shorten the required path

The no-AK integration gate, the four web-shell smoke shards and Lint & Static each rebuilt the tree test_build already uploads, and the build cache rarely helped: most runs logged "29 of 29 workspaces changed" because the arch-keyed cache was split across the mixed x64/ARM64 pool and a prefix restore returned the newest entry instead of the base commit's.

- test_build restores the build its base commit saved (ci-build-cache.mjs key --at), drops runner.arch from the key, and runs the serve fast-path closure and core subpath checks against the built tree.
- The no-AK integration gate unpacks that tree and runs as three shards; integration_no_ak_result keeps the required check name.
- The web-shell smoke unpacks the same tree instead of building; consumers recopy the opentui native assets for their host (host-assets).
- The standalone runtime regression moves from unit shard 1 to shard 4.
- A java_only profile skips the TypeScript lanes for changes confined to the Java packages; Lint & Static runs the scripts/tests suites that name them.
- Drafts skip the integration gate and the browser smoke; ready_for_review starts the full run.

* ci: check the settings schema in test_build, which has the core dist

Lint & Static only had core's dist because the serve fast-path check built
it; with that check moved, Generate settings schema failed to import it.

* ci: keep the standalone regression on unit shard 1

Moving it did not shorten shard 1: the regression runs in the side lane,
in parallel with the CLI and core suites, and shard 1's time is its CLI
slice (576 s against 147 s on shard 4 for the same 181 files). That
imbalance is left to duration-based sharding.
2026-11-06 03:30:11 -08:00
易良andQwen-Coder 1a20792cfa 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]>
2026-11-06 16:41:48 +08:00
45f71b3714 fix(ci): apply the apt-mirror probe to the release docker lane too (#16278)
* fix(ci): apply the apt-mirror probe to the release docker lane too

The release docker integration lane builds the same sandbox image as the E2E
leg but never selected an apt mirror, and it prunes the builder cache on every
run so its apt layers are always cold. On mainland pool hosts that rebuilt
against the throttled deb.debian.org path (11-45 kB/s) — the same defect #16214
fixed for E2E, left unfixed here because it was out of scope (#16271).

Extract the probe (apt_mirror_probe_rate / select_apt_mirror) and a new
apply_apt_mirror helper into a shared .github/scripts/apt-mirror-probe.sh,
sourced by both run-e2e-tests.sh and run-release-docker-integration.sh. Each
calls apply_apt_mirror before build:sandbox, so a mainland host builds against
the VPC intranet mirror (6-9 MB/s, verified for E2E) while a healthy default is
left untouched. The Dockerfile APT_MIRROR ARG already exists from #16218.

Contract tests read the shared probe and pin the candidate loop and the source
statement (not substrings the comments also satisfy); both callers must apply
the probe before the build. Mutation-verified: removing the release call, a
candidate, or a source line each reddens the suite.

* fix(ci): carry the apt-mirror probe into the shard-retry test rig

run-e2e-tests.sh now sources its sibling apt-mirror-probe.sh at startup.
The shard-retry meta-test copies only run-e2e-tests.sh into a temp dir, so
the source resolved to a missing file and 'set -e' aborted before any shard
ran — all eight cases saw zero npm calls. Copy the probe file alongside the
script in the rig so the source resolves; sourcing only defines functions
(no network at source time) and the sandbox:none path never calls
apply_apt_mirror, so the rig stays hermetic.

Witness: CI at 315733fe showed all 8 e2e-shard-retry cases failing
(npmCalls expected 2, got 0); with the probe carried into the rig all 8
pass locally. eslint exit 0, prettier clean.

* fix(ci): address #16278 review — hermetic probe test, shebang, scope header

R1-1: the release-lane exec harness now stubs curl (per-URL rates) so the suite
no longer probes the live network; the npm stub logs BUILD_SANDBOX_FLAGS and two
execution tests assert the fast mirror is passed as --build-arg APT_MIRROR with
the CI label preserved, and that an all-slow probe adds no mirror arg. Flipping
the mirror-selection guard reddens them.

R1-2: add the inert shebang so file(1) reports text/x-shellscript and the
shellcheck lane enrols the sourced probe; a lint test pins the mime type.

R1-3: narrow the header to the two covered build sites and record the third
cold caller (release-sdk.yml build-sdk) as out of scope, tracked in #16317.

* test(ci): drive the shellcheck lane for the probe; copy scripts dir in shard harness

R2-1: the shebang test now runs the lane's own candidate selection with only
shellcheck stubbed, so a filter change dropping the probe is caught (removing the
shebang reddens it). R2-2: the shard-retry harness copies the whole .github/scripts
dir so any sourced sibling comes along instead of a hand-maintained file list.

* merge(ci): sync origin/main to pick up the pinned COREPACK_HOME gate

The lint-gate-freshness lane went red on 9b3d131cdb because main moved
.github/workflows/ci.yml at 2b9c4a98f4, and the shard-4 Linux lane died on
the very corepack 'pnpm.mjs' miss that commit fixes.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw2t0at7dy

* docs(ci): stop promising the release lane a benefit its routed host cannot get

`integration_docker` is pinned to `ecs-qwen-hk4-host`, or `ubuntu-latest` when
the ECS pool is unavailable. On the Hong Kong host `deb.debian.org` already
answers at MB/s and the VPC mirror is unreachable, so `select_apt_mirror`
correctly keeps the built-in source; on a hosted runner the same is true. The
call-site comment asserted a mainland benefit this call site cannot obtain, and
the always-cold builder cache it sits next to is unchanged.

The comment now says what the call does: apply the same probe the E2E leg uses,
so a host whose default is throttled builds against a faster endpoint, while a
host whose default is already fast keeps it. The `Fixes #16271` claim is
narrowed to `Refs #16271` in the PR body — the always-cold half stays open.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw420o4bfo

* fix(ci): keep the release docker lane script executable

The previous delivery of 6218478b62 went through the Git Data API, which
recorded .github/scripts/run-release-docker-integration.sh as 100644 while
the base has it 100755. release.yml runs these by bare path, so the mode is
load-bearing, and scripts/tests/release-workflow.test.js
> keeps the extracted release scripts executable already fails on it
(Linux unit tests shard 3/8 on 6218478b62).

Restore the recorded mode to 100755.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw48g5gafx

* test(ci): stage the probe fixture at its real path (#16278 R2-1)

The rewritten case kept its fixture at the temp-repo root, where the
lane's `^([^.]+)` candidate alternative already matches, so deleting the
`.*\.(sh|zsh|bash)` arm left it green. Put the file at
`.github/scripts/apt-mirror-probe.sh` — a leading dot makes the `.sh` arm
the only way it can be selected — teach `setup()` to create parent
directories for nested names, and assert the exact shellcheck argv.

Two-arm check on the lane's own run string: as authored, exit 0 with
`... --color=never .github/scripts/apt-mirror-probe.sh`; with the `.sh`
arm deleted, exit 1 ("matched no shell-script candidates").

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4cqh0dg3

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-11-06 15:42:59 +08:00
Shaojin WenandQwen-Coder 3663209c3d chore(ci): shorten stale PR window to 30 + 14 days (#16435)
* chore(ci): shorten stale PR window to 30 + 14 days

The open PR backlog keeps growing, and the 60 + 30 day window only reaches PRs inactive for two months, leaving the 30-60 day band untouched. Lower the stale threshold to 30 days and the close window to 14 days, and raise operations-per-run to 300 so the first sweep can label the backlog in one or two runs. The stale/close messages and the fleet-shepherd design docs are updated to match.

Co-authored-by: Qwen-Coder <[email protected]>

* docs: keep the stale-window reference in the design current

Avoid pinning the note to a merge-date guess: name the policy change instead, and reflow the paragraph to the file width.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 14:43:07 +08:00
Shaojin Wen 5d45b42940 feat(verify-pr): give the sandboxed round the PR's review threads (#16300)
* feat(verify-pr): give the sandboxed round the PR's review threads

The sandboxed /verify round had no token and no review context. Its only
inputs were the metadata snapshot and, on a follow-up round, the previous
report. On #13652, Critical R1-26 was confirmed and then marked fixed
three days before a round that published merge-ready without mentioning
it. Eight hours later the review bot, at the head that round had
certified, still could not rule out its residual.

The verify job's trusted resolve step now writes review-threads.json
beside pr.json. It holds every review thread, resolved ones included,
since resolving is the author's claim, together with each reviewer's
standing CHANGES_REQUESTED review. It comes from one paginated GraphQL
query reshaped with jq. The snapshot is best-effort: on failure no file
is written and the step warns.

verify-pr-prepare.mjs sends the same query and writes the same shape for
the local path. It removes a previous copy first, so a failed fetch
cannot leave a stale file. A test runs the workflow's block verbatim and
prepare's transform over the same pages and requires equal output.

The skill adds report item 4, Existing review findings. Each term the
verdict turns on is computed from snapshot fields:

- a thread is blocking when its first comment is tagged Critical,
  Blocker, P0 or P1 by an owner, member or collaborator;
- a blocking thread is open when it is unresolved, or when that comment
  belongs to a standing change request, resolved or not. That is the rule
  GitHub's review decision applies.

An open blocking thread left unmeasured rules out merge-ready, and with
nothing failed the verdict is inconclusive. Findings at fail 0 would
publish as "passed with non-blocking findings". Other blocking threads are
re-measured as budget allows, and the verdict line counts those left
unmeasured. Change request bodies are listed but do not hold the verdict,
because their severities are prose. The duty is also a line in the scope
budget. A missing file reads as unknown, never as "nothing open".

The file adds 2396 bytes, so the size baseline records the exact 378242.

* test(verify-pr): pin the review snapshot's jq guard, copy order and odd answers

- The CI/local parity test runs the step's real jq program. Like the
  file's four other jq cases, it now skips when jq is missing, instead of
  failing one assertion later with a message that names neither jq nor
  the missing tool.
- The local recipe must clear the cached review-threads.json before it
  copies prepare's file. The test pinned the three lines but not their
  order. With rm after cp, every local round would read the review state
  as unknown.
- prepare's two other failure arms are now driven through the CLI: an
  answer that is not JSON, and pages without a pull request. Each must
  leave no file and say why. An empty state there would read as "nothing
  open".

* test(verify-pr): drive the review snapshot through the shared gh harness

The review-snapshot case had its own copy of the executed-step harness:
a temp dir, a stub gh on PATH and GitHub's bash flags. makeGhHarness
exists so that fidelity contract lives in one place. The factory now
records every call's argv, returns its temp dir, and has an
empty-stdout arm for paginated reads, GH_STUB_EMPTY_LIST. The snapshot
case runs through it. Its failure arms are the factory's
GH_STUB_FAIL_LIST and GH_STUB_EMPTY_LIST.

Deleting the shared stub's fail arm, its empty-stdout arm or its argv
capture turns the case red, and so does recording only the first
argument.
2026-11-05 23:50:44 +08:00
Shaojin WenandQwen-Coder edb094fea3 test(managed-agent): gate lifecycle-v2 close with a stopped worker (#16123)
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-05 22:22:26 +08:00
c0782a8cb6 test(ci): pin the zero-skip timeout shape in awaitTwoSkipLines (#16384)
Refs #10737


Patrol-Run: qwen-issue-patrol/jmw3fvcf8y3

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-05 11:45:43 +00:00
Shaojin Wen ef9c891b8f fix(ci): clear the pinned corepack cache before every install (#16351)
* fix(ci): clear the pinned corepack cache before every install

The Linux test shards have been dying seconds into Install dependencies
with MODULE_NOT_FOUND on the cached pnpm's bin/pnpm.mjs since #16328
pinned COREPACK_HOME to runner.temp (main runs 41876940141, 41889761157,
41927887452; six different self-hosted hosts). The pin assumed runner.temp
is per-run, but self-hosted runners keep _work/_temp across jobs — the
lint job's isolated-HOME step already documents this. corepack trusts an
existing cache entry without re-verifying its files, so a partial pnpm
tree left by an earlier job fails every later job on that host.

Clear the pinned cache at the start of the shared install body in all
three jobs that copy it, enforcing the per-run contract the pin
documents. The guard keeps the reset a no-op where COREPACK_HOME is
unset (test_build) or already run-unique (lint). The test step still
reuses the cache install warms in the same job, and poisoned hosts
self-heal on their next job without manual cleanup.

Pin the reset and the install/test COREPACK_HOME match in the workflow
meta-test, and bump the size baseline for the added lines.

* fix(ci): scope the corepack cache reset to the shard-owned path

Review found two over-deletion shapes in the nonempty guard: a runner
that exports COREPACK_HOME would lose its entire shared cache in the
build job, and hosted lint would lose the cache its pnpm-store step had
just warmed, re-exposing the install to a registry outage. Match only
the exact shard-owned path instead; the build and lint copies become
unconditional no-ops and the three install bodies stay byte-identical.

Add an execution test that runs the shipped guard fragment under a
scrubbed environment across five cache shapes: it kills the dead-guard
mutant the string pins survived, and a widened-guard mutant dies on the
shared-cache survival cell. State the incident as observations with the
corruption source undetermined, and correct the stale per-run rationale
beside the pin.
2026-11-05 09:27:53 +08:00
Shaojin Wen 8a7c0d17d8 fix(managed-agent): find the Flyway scripts in the module jar the server ships them in (#16175)
* fix(managed-agent): find the Flyway scripts in the module jar the server ships them in

Since #15871 split the server into Maven modules, its Flyway scripts ship
in BOOT-INF/lib/qwen-managed-agent-persistence-*.jar rather than in
BOOT-INF/classes/db/. The upgrade command's check read only the classes,
so on every archive since then its database row warned "cannot compare the
Flyway scripts (no Flyway script found in ...)", and a release that changed
or reordered a script the installed database ran went unflagged.

migrationScripts() now also reads db/**/ at the root of each nested jar in
BOOT-INF/lib/. Spring Boot stores those uncompressed, so their directories
are read in place; a compressed one, which Spring Boot would not load, is
skipped. A script keeps its path as its name wherever it ships, so one moved
from the classes into a module compares as unchanged.

The reference distribution workflow now reads the scripts out of the exact
archive it built, so a later change of layout fails there rather than
turning check's row into a standing WARN.

* fix(managed-agent): read a Flyway script shipped twice with different contents as unreadable

The reader keys the scripts on their classpath path, and the nested jars
give that key a second source. Two copies of one path with different
contents left whichever the central directory listed last in the map, so
the database row compared one arbitrary copy, while the class loader would
hand Flyway the other. Matching copies are still one script; differing
ones now make the jar unreadable, which the row reports as "cannot compare
the Flyway scripts".

* fix(managed-agent): name both places that ship a differing Flyway script, and pin the local extra field

- When one script path ships twice with different contents, the error now
  names the two places, BOOT-INF/classes or the nested jar, so the
  operator need not unpack the jar to find the second copy.
- The test zips can carry a local-header extra field, as archivers add for
  timestamps or alignment, and the nested-jar case reads entries past one,
  so dropping that term from dataStart fails the suite.
2026-11-05 08:10:28 +08:00
Shaojin Wen b9e20830f7 fix(verify-pr): post flaky demotions on closed PRs and pin the blank class (#16135)
* fix(verify-pr): post flaky demotions on closed PRs and pin the blank class

The closed-PR skip from #16050 also caught the flaky variants of the
cancelled, failed-before-report and results-unavailable notices, and the
report body of a round that wrote no report.md. Those render ❌ and, on
a merged PR, name non-deterministic tests now on main, so they post
unless this run's own report already carries the demotion.

- publisher: the closed-PR skip leaves FLAKE_VERDICT=flaky alone,
  except beside this run's own report
- publisher: the gates line says its tests are "not counted
  individually above", since each suite run now counts as one
  assertion (zh: 不逐条计入上面的断言)
- checker tests: pin the blank class on their own (whitespace refused,
  U+0085 and U+FEFF accepted); before, only the shared parity table in
  the workflow suite caught a revert to trim()
- SKILL: rewrap the over-long local-path line

* fix(verify-pr): scope re-run advice to open PRs and pin the gate condition

The results-unavailable notice can now land on a merged PR, where the
verify lane refuses `@qwen-code /verify`: both variants now offer the
re-run only while the PR is open.

The flaky exemption keys on the gate verdict, which holds only because
the gate step runs on a prepared round; a test now pins that condition.

* fix(verify-pr): skip a flaky notice only beside a report that carries it

On a closed PR, a flaky notice with no live status of its own to
finalize is skipped beside this run's own report, as a duplicate of the
demotion that report carries. That report can come from an earlier
attempt whose gate did not diverge. A re-run attempt whose own live
status is missing (that post is best-effort) then lost its ❌ to the
run log, beside a report that never showed it.

A flaky verdict now adds a verify-flaky marker to the header, and the
closed-PR skip drops a flaky notice only when this run's own report
carries that marker in its header.

* test(verify-pr): pin the own-comment lookup's initializers

The publish step runs under `set -u`, and a failed comment listing or bot
identity lookup leaves OWN_ID, OWN_SUBSTANTIVE and OWN_FLAKY at their
initial values. Dropping any of those three lines kept the suite green:
no case made either lookup fail. On such a run the step would instead abort
with "unbound variable" and post nothing.

The upsert stub can now fail the listing or the identity lookup. Under
either failure, a report posts fresh rather than patching a status it cannot
see. A flaky notice on a closed PR also posts, because the lookup cannot
tell whether this run's report already carries the demotion. Deleting any
one initializer now fails the test on its own unbound variable, which the
harness now prints with the failed exit status.
2026-11-05 02:42:14 +08:00
易良andQwen-Coder 2b9c4a98f4 fix(ci): pin COREPACK_HOME for Linux test shards on cold-HOME runs (#16328)
The Linux unit-test shards set HOME to a fresh per-run directory for
isolation. scripts/tests/corepack-warmup.js (vitest globalSetup) spawns
`corepack pnpm --version` before any worker forks, and on a cold HOME
corepack downloads the pinned pnpm from registry.npmjs.org. The ECS
egress intermittently times out on that fetch (ETIMEDOUT to 104.16.9.34),
which kills the shard after every test file has already passed — today
that produced 7 PR-facing failures across random shard numbers (2/8,
5/8, 3/8, etc.), all on the same self-hosted host.

Install dependencies runs before the test step and warms the corepack
cache under the runner's real HOME, but the test step's HOME override
makes that cache invisible. Pin COREPACK_HOME to a per-run path in both
steps so they share the same cache without touching the HOME isolation.

Per-run, not persistent: corepack does not re-verify extracted entries
on cache hit, so a shared cache would be a poisoning channel (the same
reason lint_and_static keeps its COREPACK_HOME per-run).

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-05 01:19:56 +08:00
ed6760aa2c ci: reclaim qwen-autofix workflow headroom (#16069)
* ci: reclaim qwen-autofix workflow headroom

Co-authored-by: Qwen-Coder <[email protected]>

* ci: keep the qwen-autofix anchors the shape pins slice on

The headroom pass collapsed three comment blocks and merged two
`Full rationale` pointer lines. The workflow-shape tests use those exact
comments as slice boundaries for the real bash they execute, and the
pointer parser reads only the first `qwen-autofix.md#af-NNN` per line, so
four pins went red on Test (ubuntu).

Restore the `# Convergence-break mirror` and `# Convergence-signal circuit
breaker (#10107): the park action on` wording, and let every pointer id
spell its own anchor. No behavior change; refresh .size-baseline and the
doc byte count to the new file size.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvyc99g07m

* docs(ci): restore the rationale the autofix compression dropped

R1-1: af-085 answered only about the allowlist, so the worktree-config
file, its pairing with extensions.worktreeConfig, and the hooks-path
resolution ordering read as dead code next to a KEPT allowlist entry.
Restore both paragraphs in the anchor the yml already points at.

R1-6: the age-sweep comment pointed at af-149, which documents heartbeat
and pid liveness only, while the reclaim reason lived nowhere. State it
at both sweep sites.

R1-3: the design record hardcoded the workflow's byte count, which the
ratchet can invalidate without reddening anything. Name .size-baseline as
the single source of the number instead.

The bytes R1-6 adds land in .size-baseline (451750), so the recorded size
still equals `wc -c .github/workflows/qwen-autofix.yml`.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw0wf5wvb8

* fix(ci): repoint two hollow rationale pointers and restore two rationales

Address the four blocking hollow-pointer findings on the autofix size
extraction (each verified by extracting the anchor span at this head):
- gh-config mint (~4980): af-112 documents the git preamble, not the gh
  pins; repoint to af-149, which carries the GH_HOST/hermetic_gh material.
- heartbeat rm -rf (~3742): af-149 lacks the '-rf, not -f' doctrine;
  repoint to af-158, which states it verbatim.
- eval-marker parse (~2756): restore the POSITIONAL field-order rationale
  (.[0]=ts .[2]=round .[3]=win .[4]=run; every write site emits that order)
  into af-159.
- WORKDIR cleanup (~1584): restore the 'last step, after every reader
  including the artifact upload' ordering rule into af-148.

The two repoints are same-length, so qwen-autofix.yml stays 451750 bytes
(.size-baseline unchanged). Workflow meta-test suite 350/350 pass.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-11-04 19:50:51 +08:00
a5fec03684 fix(ci): fail Java lanes before disk exhaustion (#16068)
* fix(ci): fail Java lanes before disk exhaustion

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): gate the MySQL pool lane instead of a hosted job

The disk floor gate landed in hosted-harness-mysql, which runs on
ubuntu-latest, so `runner.environment == 'self-hosted'` is never true
there and the step was dead code. The job that actually shares the ECS
pool disk - mysql-integration, two Maven suites plus a per-leg MySQL
container - stayed ungated.

Move the step to mysql-integration, immediately after its checkout so
the sdk-java-workflow pin still holds. This also clears the three
Hosted real-process gates pins, which reject a conditioned step among
the setup steps that precede managed_dependencies in
hosted-harness-mysql, and reflow the new test to the repo's prettier
style.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvya43o17i

* style(ci): drop the blank line left behind in hosted-harness-mysql

Moving the disk floor gate out of hosted-harness-mysql left a stray blank
line between its checkout and 'Set up Java'. Restoring it makes the whole
workflow diff additive on mysql-integration only, so 'hosted-only jobs are
unchanged' holds byte for byte and an A/B census of that job reads clean.

Co-authored-by: Qwen-Coder <[email protected]>

* ci(sdk-java): record the real workflow size after the disk-floor gates

The six #16003 disk-floor gate steps grow sdk-java.yml from 154074 to
155650 bytes. main had already drifted 3742 bytes past the recorded
150332 — inside the 4096 allowance, but it left only 354 bytes of ratchet
headroom, so this real growth tripped Lint & Static.

Record the file's actual size rather than shrinking the gates: each one is
a three-line step calling .github/scripts/check-disk-floor.sh, and the
workflow test pins all six gated jobs.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw02ewzoa2

* fix(ci): derive the disk-floor corpus from runs-on and narrow the broker claim

Address the two blocking review findings on the Java disk-floor gate:

R1-1: the meta-test skipped any job without the gate and compared a closed
six-name list, so a newly pool-routed lane that forgot the gate stayed green,
and a neutered gate (continue-on-error) kept every assertion true. Derive the
corpus from each job's runs-on (same hazard as the setup-java corpus), require
gateIndex > -1 per pool-capable lane, and assert the gate carries no
continue-on-error. The derived set is the same six jobs today. Verified by
mutation: adding continue-on-error to a gate reddens it, and renaming a gate
(ungated pool job) reddens it with 'expected -1 to be greater than -1'.

R1-2: the step comment claimed RUNNER_TEMP covers the broker store, but the
absolute free-KiB floor cannot express the broker's used-ratio refusal (a
952 GiB volume at 94% used still has ample free bytes). Narrow the comment to
the gate's real scope (near-full filesystem / inode exhaustion, #10035) and
state the broker disk-threshold investigation stays out of scope, matching the
PR description. Bump .size-baseline for the comment change (155650 -> 155888).

sdk-java workflow meta-test 117 passed / 5 skipped; eslint exit 0, prettier
clean; check-workflow-size.sh passes.

* fix(ci): calibrate the Java disk floors and fail closed in the meta-test

Review round on this PR found the gate could not fire in the two shapes it
exists for, and that the meta-test could go green through the same drift it
was added to prevent.

R1-2: every lane ran the shared 2 GiB / 100k-inode default. The #16003
incident was a used-ratio refusal — the broker reported 0.94 while the
952 GiB volume still had tens of GiB free — so no absolute floor can express
that threshold, and 2 GiB is below what these lanes write: a host with 4 GiB
free passed the gate and then died mid-suite. Give the five heavy lanes the
repo's build-sized 8 GiB floor, leave flyway-migrations and the shared value
alone, and add an opt-in DISK_FLOOR_MAX_USED_PCT arm to check-disk-floor.sh
for the two broker lanes (90%).

R2-1/R2-2: the corpus came from `runs-on` text and skipped jobs outside it,
so a lane routed through an expression or a job output shipped ungated while
the suite stayed green, and nothing asserted the hosted-only jobs carry no
gate. Fail closed: an explicit hosted-only allowlist, a gate required for
every other job, a total-count assertion, and a negative assertion on those
two jobs. Pin the per-lane floors so dropping one is a test failure.

Verified: sdk-java-workflow suite 117 passed, 5 skipped; it goes red when a
gate is deleted, when one is added to a hosted job, and when a floor is
removed. check-disk-floor suite 12 passed, including four new ratio cases.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: qwen-code patrol <[email protected]>
2026-11-04 17:57:04 +08:00
易良andQwen-Coder b383efb854 fix(ci): widen the built-tree download window for eight shards (#16285)
* fix(ci): widen the built-tree download window for eight shards

Eight shards now pull the same unit-test-tree artifact at once, so the
download cap has to clear the blob's tail latency rather than the median.
On main the slow host class took 4m52s, and two consecutive runs expired
twice at the five-minute cap before the unpack ran, which failed the
required Linux check with no test involved.

Raise both attempts to fifteen minutes and pin the pair in the
build-to-shard handoff contract.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): size the shard download cap from the measured tail

Review flagged that the outgoing comment claimed a pool host had stalled
"for over a quarter of an hour", which the new fifteen-minute cap then
contradicts. That claim came in with the sharding commit and was never
tied to a run; neither incident can prove how far past five minutes the
tail went, so the comment now states only the measured distribution.

Also correct the number: the slowest completed shard took just under five
minutes, and it was a different shard that expired, on two consecutive
runs. Add the missing shard-side ceiling contract, mirroring the build
job's: every blocking step cap has to fire before the job cap, because a
cancelled job skips the failure-gated disk-pressure collector.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 17:41:55 +08:00
56fec7f3f5 ci: shard the Linux unit tests and web-shell smoke, and reuse cached builds (#16133)
* ci: shard the Linux unit-test lane eight ways behind one build

The Linux Test job ran 45-59 minutes: a ten-minute prepare build, then the CLI and core suites back to back. A new test_build job runs the prepare build once and uploads its outputs; eight shards install without the build, unpack them and run --shard=N/8 of every Vitest suite. Test (ubuntu-latest, Node 22.x) becomes an aggregate that passes only when the build and every shard pass.

* ci: record ci.yml's growth from the test_build prelude

test_build carries the same pre-checkout cleanup, checkout and install prelude as the test shards; ci-platform-lanes.test.js pins the two copies identical.

* ci: record the Linux lane's two new jobs in the PR-stage budget

test_build and the test_ubuntu aggregate add two jobs; the declared timeouts drop from 305 to 295 minutes.

* ci: shard the web-shell E2E smoke four ways

The smoke lane ran 44-46 minutes on every recent PR, about 33 of them in the browser tests at two workers, and is the longest lane once the Linux unit tests are sharded. Each shard runs --shard=N/4 of the smoke; the transcript gate runs on the first. web-shell E2E Smoke (ubuntu-latest, Node 22.x) becomes an aggregate over the shards.

* ci: bound the unit shards' tree download and retry it once

A pool host stalled 17 minutes in download-artifact while the other shards took under a minute.

* ci: rebuild only the workspaces a change reaches

scripts/ci-build-cache.mjs hashes each workspace's tracked build inputs (tests excluded) with its workspace dependencies' hashes and the shared root inputs. test_build and the web-shell smoke shards restore the newest cached build and rebuild only workspaces whose hash changed. A push to main always builds from scratch and saves, so a pull request starts from a clean build of main.

* docs(ci): describe the incremental build cache

* ci: keep the build cache path the same on every pool host

actions/cache versions an entry by its path; under runner.temp the path differs per host, so a run could never restore another host's save.

* ci: save the ARM64 build cache from the smoke lane, and run the aggregates on the shards' runners

The smoke shards run on the ARM64 pool, which test_build may not, so the first shard saves the clean build it made when its restore found nothing. The two aggregate checks queued 9 and 13 minutes for a hosted runner after their shards finished; they now take the shards' runners.

* ci: bound the sharded test step and skip shards when the build fails

Address the first automated review round on the Linux sharding lane:

- Gate every build-consuming shard step on `test_build` success. `!cancelled()`
  stays true when a needed job fails, so a failed build still started all eight
  shards, each paying a full install before dying at the missing artifact and
  reporting artifact plumbing as its failing step.
- Drop the test step cap to 45 minutes. The job cap is 60, so the old 110 could
  never fire; a step timeout keeps the diagnostic steps below it alive while a
  job timeout does not.
- Keep the built-tree artifact for a week so a late "re-run failed jobs" still
  finds it, and correct the trigger comment that still named three post-merge
  jobs when five now accept push.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: keep disk telemetry for a failed build and correct the design doc

`test_build` installs with the disk sampler and then builds for up to its
whole timeout, but the sampler dies with the install step's trap and no
collector ran, so an ENOSPC build (or the OOM that took a shard host) kept
no evidence. Give it the same failure-gated dump and last-step upload as
`lint_and_static`, under its own artifact name.

The incremental-lint design still said the test job keeps normal prepare
behaviour; this lane now installs with `QWEN_SKIP_PREPARE=1` and unpacks
the tree built once by `test_build`. Correct both language versions.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: share the built-tree definition and the build preflight, harden the guards

The archive step re-implemented `builtPaths()` from `scripts/ci-build-cache.mjs`,
so the tree the shards unpack had a second definition that could drift from the
one `pack` stores. A `paths` mode now prints that list for `tar -T -`, and a new
test pins the four artifact-name literals that tie the producer to the eight
consumers.

The cached branch of `build` also hand-reproduced a subset of `scripts/build.js`
and so skipped its ink-patch preflight: the same missing
`frame-controller.d.ts` self-healed on a cache miss and surfaced as ~50 tsc
errors inside ink's declarations on a hit. The preflight is now a function
reachable as `scripts/build.js --preflight` and runs before both branches.

The remaining review suggestions were all meta-test gaps: the standalone-runtime
expression and the `test_build`/`test` prelude were guarded by nothing that
compares them in order, `ci-build-cache.mjs`'s three CI modes were never
executed by a test, and the infra-classification filter's step name was not
joined to `ci.yml`. Each now has an assertion that goes red when the guard is
removed.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: bound the build steps so the failure collectors can run

Round-2 review found four issues in the previous round's fixes and one gap
in the new build tests.

A job timeout cancels the job and skips the failure-gated collectors, so the
`test_build` steps that can block now carry caps below the job's 45 minutes:
the install, the build, the pack and the archive. The install caps are
identical across `test_build`, `test` and `lint_and_static` so the prelude
parity guard still compares the three copies field for field. A test pins
every blocking cap under the job ceiling.

The `--preflight` early exit was only asserted as a string, so it could be
renamed with nothing going red while the cache-hit path silently ran the
full build. A spawned case now copies `scripts/build.js` into the fixture
and asserts the flag exits without generate, a workspace build or the
schema step.

The CLI mode tests write extension-less `#!/bin/sh` stubs and join PATH with
':', which cmd.exe cannot resolve; the Windows lane collects that file and
downgrades its failure to a warning, so it would have carried a permanently
red block. The suite is excluded on win32 alongside the other bash-fixture
suites.

The `test_build`/`test` intersection grew to 16 with the disk collector, but
the floor stayed at 15, leaving exactly one shared step uncompared; the floor
now matches the membership. The cache-hit-with-changes branch — the code
that decides what is rebuilt and what is deleted — gains a case: a changed
workspace is purged and rebuilt by directory, and an untouched workspace
keeps what the cache restored.

ci.yml's recorded size baseline is updated to the current file so the next
PR gets the full growth allowance back.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): keep cache fixture call logs outside the archived tree

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 14:52:12 +08:00
易良 d24198e866 fix(ci): repin the RHEL 10 rpm root to the current stream10 digest (#16261)
The pinned manifest for the RHEL 10 build root no longer resolves on
quay.io: `quay.io/v2/centos/centos/manifests/sha256:99b8fe27…` answers 404,
so every job using that container dies at "Initialize containers" with
`manifest unknown` before a single step runs (run 41759710053, job
126345260531). The tag still exists, and resolves to
sha256:511d3584… today.

Repin to that digest. Staying on a digest keeps the build root fixed for the
life of the release, which is why this was pinned in the first place; only
the value moves.
2026-11-04 03:14:02 +00:00
易良andQwen-Coder 4593a3b8f2 fix(ci): run desktop smoke headless on rpm roots without Xvfb (#16211)
* fix(ci): give the RHEL 10 desktop smoke a headless display

RHEL 10 removed xorg-x11-server-Xvfb from every repository: BaseOS,
AppStream, CRB, extras and EPEL 10 contain no such build, so the rpm
build-root dependency step failed one step after #16154 fixed the EPEL
bootstrap (`No match for argument: xorg-x11-server-Xvfb`, run 41642580745,
job 125940965630).

Keep Xvfb for the Fedora 44 leg and give the RHEL 10 smoke a real display
instead: a weston headless compositor with rootless Xwayland on top, since
GTK3 has no headless backend and WebKitGTK always needs a display server.
Export WAYLAND_DISPLAY before Xwayland is launched, wait for both sockets
with a hard timeout, and pin the app to X11 so the bridge is actually used.

Verified end to end on a Desktop Checks dispatch of this head (run
41652880267): the RHEL 10 rpm lane passes dependency installation and the
packaged smoke, and the Fedora 44 lane keeps its xvfb-run path. The three
workflow meta-tests that parse this file pass (161 passed, 1 skipped).

* fix(ci): key the rpm display arm on the matrix and harden its meta-test

Review round 2 findings on #16211:

- R2-1: the display arm keyed on an artifact_suffix literal while the EPEL
  bootstrap it depends on keys on matrix.epel. Declare matrix.display
  ('xvfb' on Fedora 44, 'weston' on RHEL 10) and branch on that, so a leg
  added later picks a server instead of inheriting an arm by accident.
- R2-2: the xvfb-run arm shipped a shell function through declare -f into a
  child bash without -e -u -o pipefail. Run the same npm command in both
  arms so both keep the step's error regime; assert no declare -f remains.
- R2-3 / R1-4: bound the shared package list by the conditional that follows
  it instead of a prose comment, ignore comments when checking it, and assert
  the xvfb/weston arms, the weston socket name against WAYLAND_DISPLAY, and
  the Xwayland display number against DISPLAY and the X11 socket.

* fix(ci): fail the rpm display arm closed and pin the smoke's wiring

R2-1: the install arm keyed on `matrix.display` but defaulted every unknown
value to the weston arm, which only works where the EPEL bootstrap above has
run. Turn it into an explicit `case` with an error arm, and assert from the
matrix that a non-Fedora root taking the weston arm has EPEL enabled, so a new
leg with `display: 'weston'` and `epel: 'false'` turns the meta-test red
instead of dying at `dnf install` in a release run.

R1-4: restore the two pins the previous rewrite dropped
(`weston --backend=headless-backend.so`, `XDG_RUNTIME_DIR`) and bind the rest
to the step's structure rather than to bare substrings: the weston readiness
wait must sit between the two launches, the helper must `return 1` on timeout,
and the EXIT trap must reap both daemons. Each added assertion was checked
against the mutation it names (all five turn red; the unmutated tree stays
green).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw1dkg4zbx

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 10:40:34 +08:00
易良 b5975157d1 fix(ci): drop the healthy-default apt gate and prefer the VPC intranet mirror (#16260)
* fix(ci): drop the healthy-default gate so a fast apt burst cannot veto the mirror

The merged probe (#16218) kept a third gate: override only when the default
measured below 250 kB/s. On the mainland hosts the deb.debian.org path is
bimodal — run 41743539611 on hz4-2 probed 372 kB/s (a fast 512 KiB burst), so
the gate kept the default, and the sustained 200 MB transfer then throttled to
45 kB/s and hit the 60-minute cap again.

Remove that gate. The two mirror-side gates already cover the cases it was
meant to protect: the 1 MB/s absolute floor rejects a "slow vs slower"
override, and the 4x factor keeps a genuinely fast default (the Hong Kong
hosts) on the built-in source. The hz4-2 sample (default 372 kB/s, mirror
1.97 MB/s) now selects the mirror; the Hong Kong and both-slow shapes are
unchanged. The contract test pins the floor, the factor, and the absence of
any healthy-default gate.

* fix(ci): prefer the VPC intranet apt mirror to bypass the capped public egress

The public mirrors still traverse the pool's capped 100 Mbps public egress that
throttles under concurrent load — the exact problem configure-maven-mirror.sh
solves for Central by preferring mirrors.cloud.aliyuncs.com, which answers from
inside the VPC and consumes no public bandwidth. Add it as the first apt probe
candidate. Off the VPC (hosted runners, Hong Kong hosts) it is unreachable, the
probe measures 0, and a reachable public mirror wins — so no host map is needed
and the probe self-verifies on each build.
2026-11-04 10:40:20 +08:00
易良 aaf523cfc4 fix(ci): probe the apt mirror so mainland E2E hosts can build the sandbox image (#16218)
* fix(ci): probe the apt mirror so mainland E2E hosts can build the sandbox image

The docker E2E leg builds the sandbox image per push under a SHA-scoped tag, so
on any host whose image store no longer holds a recent build, the two apt-get
layers are downloaded again. deb.debian.org serves the Hong Kong hosts at MB/s
and the mainland-China hosts at 11-20 kB/s, which turns that build into 2-3
hours against the job's 60-minute cap. Over the past week 97 of 127 docker legs
on the ecs-qwen-hz* hosts were cancelled this way, and the run reports
`cancelled` instead of a verdict.

Probe rather than pin a mirror or a host-name map: the build measures
deb.debian.org and two domestic mirrors, and passes --build-arg APT_MIRROR only
when the default is slower than 250 kB/s and a mirror beats it by 4x. The image
keeps deb.debian.org as its default source, so release builds, local sandbox
builds and the healthy Hong Kong hosts are unchanged, and no fleet inventory
needs to be maintained.

The probe's measurements are logged, so the next slow build can be diagnosed
from its own output.

Refs #16214

* fix(ci): require an absolutely fast mirror before overriding the apt source

The relative guard alone only asked a mirror to be 4x faster than the default,
so on a host where both endpoints were slow the mirror still "won" and moved
the host onto a source it never needed. Require 1 MB/s in absolute terms too:
below that the build is slow either way, and the extra cache key is not worth
it.

* test(ci): pin the apt-probe threshold values, not just the gate structure

The new contract test asserted the gate variables and comparisons but not
their values: mutating slow_default 10x up or fast_mirror 10x down left the
suite green while flipping the both-slow host shape onto a still-slow mirror
(review finding on #16218). Pin both literals with a word boundary — a plain
substring pin would still match the 10x-up mutation — and witness both
mutations reddening the suite before restoring the values.

* test(ci): require the mirror ARG in both Dockerfile stages

A single-match assertion survived deleting the runtime-stage ARG APT_MIRROR
(R1-1, mutation-witnessed by review): the builder declaration satisfied the
regex while the runtime apt layer silently kept the slow default. Count both
declarations instead; deleting either stage's ARG now reddens the suite.
2026-11-04 05:52:30 +08:00
a8e6c2ee57 ci: scope triage finalize concurrency to PR runs (#16070)
* ci: scope triage finalize concurrency to PR runs

Co-authored-by: Qwen-Coder <[email protected]>

* test(qwen-triage-finalize): compare the concurrency group with whitespace flattened

The group is a folded YAML scalar, so its rendered value keeps a newline
next to the more-indented continuation line. Pinning the literal string made
the assertion fail on the PR's own workflow file (Test (ubuntu-latest,
Node 22.x) at d190b876c4), while GitHub Actions treats whitespace inside
${{ }} as insignificant.

Compare the flattened expression instead, matching the normalizer already
used for multi-line conditions in capture-tmux-ci.test.js. The workflow file
does not change.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvyeef987p

* test(qwen-triage-finalize): pin the folded group without normalizing

The more-indented continuation made the folded scalar keep a literal
newline, which the whitespace normalizer hid, so the assertion no longer
pinned the committed value. Put the expression on one physical line so it
folds to the single-space form, assert the parsed value directly, and
correct the comment that credited the wrap to the formatter.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: qwen-code patrol <[email protected]>
2026-11-03 20:45:00 +08:00
qqqys d7f534edd6 ci(e2e): run the nightly suite with the unified agent loop on (#16192)
The nightly and on-demand E2E runs gain a Linux sandbox:none leg with
QWEN_CODE_ENABLE_UNIFIED_AGENT_LOOP=1, so the whole integration suite
also runs its headless and ACP sessions on the shared loop. This is the
scheduled job a surface's default change waits on. Pushes keep their
two legs.

The setting's description now says that headless and ACP sessions use
the shared loop, and that the interactive TUI and subagents keep their
own for now. The design doc describes the job in place of one that did
not exist.

Part of #15327.
2026-11-03 17:57:53 +08:00
276a447ef0 fix(ci): interleave PR review endpoint cohorts and report final routes (#16151)
* feat(ci): support same-model endpoint splits in PR review canary

The endpoint override derived the cohort by comparing the routed model
name against the configured canary/default models, so configuring the
SAME model on both cohorts — a pure quota split across two gateways —
labelled every PR 'default' and silently disabled the override.

Derive the cohort from the bucket predicate itself instead
(review_cohort_for_bucket: same clamp, same PR-number bucket test as the
model router), so model name equality no longer matters. The notice's
cohort field remains the attribution when both cohorts run one model.

* fix(ci): interleave review endpoint buckets and report final routes

Co-authored-by: Qwen-Coder <[email protected]>

* style(ci): apply Prettier to the review endpoint bucket test

The Lint & Static lane runs `node scripts/lint.js --prettier` on the
files a PR touches, and this PR's own test file was not formatted:
three `expect(...).toBe(...)` lines exceed the print width, so the
incremental lint step exited 1 on every head.

Formatting only - no assertion or fixture changed.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvzj4h0j9a

* fix(ci): share the endpoint host derivation and pin the cohort notice

- Extract openai_base_host() so the proxy bypass and the cohort notice
  derive the host from one place; the notice cannot name a host the
  traffic was not routed through.
- Drop the comments that still describe the removed model router.
- Pin the notice's bucket count (8% -> bucket 87/100), make the
  noKey/badUrl rows exact strings, and assert initial_openai_host so the
  three mutations that survived this suite now fail.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvznesli9g

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-11-03 02:20:46 +00:00
易良andQwen-Coder 5b7f9ec1c3 fix(ci): repair desktop RPM dependency and teardown checks (#16154)
* fix(ci): repair desktop rpm dependency and teardown checks

Co-authored-by: Qwen-Coder <[email protected]>

* test(desktop): use a counted space class in the EPEL checksum pin

`scripts/lint.js --eslint` runs on every file the PR touches, and the new
rpm dependency test spelled the two spaces in the pinned `echo` line as
literals: `no-regex-spaces` rejects them, so Lint & Static failed on the
incremental ESLint step.

`[0-9a-f]{64} {2}` matches the same two spaces the workflow emits; the
assertion still pins exactly what the release step writes.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvzj4h0j9a

* fix(desktop): keep the runtime group as a teardown target

Teardown collected daemon pids through the zombie-aware liveness predicate and
escalated SIGKILL only on leaders that were still alive, so a leader that had
already exited — reaped, replaced, or an unreaped zombie — silently dropped its
process group, which is teardown's only handle on it. The children the leader
spawned (MCP servers, tool subprocesses) then survived and kept writing under
the workspace about to be deleted, and the smoke reported a pass.

Collect every recorded pid, escalate on every collected group after the drain,
and keep the zombie-aware predicate on the "survived teardown" report so the
false positive this PR exists to fix stays fixed. The new fixture drives a
runtime whose leader exits while a writer in its group ignores SIGTERM.

Also pins the EPEL download URL and digest literals, drops the duplicate local
liveness predicate in favour of the shared helper, and covers the signal-probe
error arm (ESRCH gone; EPERM/EACCES alive, the rule the core helper documents).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvzpjyde9j

* fix(desktop): decide teardown on the runtime group, not its leader

The round-2 escalation signals the recorded pid's group (`kill(-pid)`), but
the verdict and the drain still asked `isProcessAlive(pid)`, which answers
"gone" as soon as the leader exits — the exact topology
`SMOKE_FIXTURE_EXIT_AFTER_SPAWN` reproduces. In that state `waitForExit`
returned on its first poll, so the 5 s drain was skipped and the
`survived teardown` branch was unreachable.

`isTeardownTargetAlive` probes the group first and falls back to the leader
where there is no group to signal (win32), so the drain and the leak verdict
cover the children the group can still hold.

Also drops the "mirrors the canonical … keep both in sync" claim from the
desktop probe's comment: it diverges from core's `isPidAlive` on the
pid <= 0 guard, the zombie exclusion on the EPERM/EACCES arm, and the
vanished-/proc answer. The comment now states those differences and why
teardown wants the dead-on-ENOENT direction.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvzrp44z9n

* fix(desktop): stop counting unreaped zombies as a live teardown group

`waitForExit` asks `isTeardownTargetAlive` whether a daemon's group still
exists. `kill(-pid, 0)` also answers for members that can no longer write, so
a daemon that exited and that the container's PID 1 has not reaped kept its
group "alive": the drain burned its five-second window, the second probe still
returned the pid, and teardown reported "the packaged runtime survived
teardown" for a runtime that was already gone.

On Linux the group probe now counts only members that can still write: scan
`/proc` for entries whose pgrp is the recorded pid, and report the group
stopped when every member is a zombie. A scan that observes no member keeps
the group probe's answer, so a hidden /proc cannot end a drain early, and the
group handle still wins over the leader handle.

Measured on a real zombie group (setsid + _exit, parent never reaps): /proc
state Z, `kill(-pgid, 0)` exit 0, `isProcessAlive` false, and
`isTeardownTargetAlive` true before this change, false after it.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvzy4lf49w

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-03 10:18:16 +08:00
jinyeandQwen-Coder 90eb13e4c6 refactor(managed-agent): Make runtime backends optional Maven modules (#16141)
* refactor(managed-agent): Split runtime backends into optional modules

Extract five backend implementations behind the shared provider SPI and support selected-backend executable distributions. Preserve SQL ownership, defer unavailable historical work, and make Spring own SSH connection cleanup.

Co-authored-by: Qwen-Coder <[email protected]>

* refactor(managed-agent): Narrow backend split to Maven modules

Keep optional backend dependencies and safe assembly while deferring selective standalone distributions. Restore default packaging and document the provider configuration.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-03 02:04:43 +00:00
易良andQwen-Coder 4dce337ea2 feat(ci): let the PR review canary cohort carry its own endpoint (#16130)
* feat(ci): let the PR review canary cohort carry its own endpoint

The review canary split (QWEN_PR_REVIEW_MODEL_CANARY/PERCENT) only moves
the model name; both cohorts share REVIEW_OPENAI_BASE_URL and its key, so
a rate-limited canary model also burns the shared gateway's quota — the
shape behind the 429-storm review timeouts.

Add an optional endpoint pair for the canary cohort:
QWEN_PR_REVIEW_BASE_URL_CANARY (variable) + REVIEW_OPENAI_API_KEY_CANARY
(secret). When the bucket routes to the canary and both are set, base URL
and key move with the model and the canary host joins NO_PROXY. A URL
without its own key, or an unparseable URL, degrades to the default
cohort with a warning — forwarding the default key to another provider
would leak it. A canary model identical to the default model disables the
override (buckets are indistinguishable). The cohort notice now prints
the resolved cohort so rollouts stay attributable.

Unset, behavior is byte-for-byte today's: model-only split on the shared
endpoint.

* fix(ci): drop inert canary NO_PROXY append and record the workflow's new size

The canary endpoint override appended its host to NO_PROXY, but nothing in this
job consumes it: configure_qwen_network unsets HTTPS_PROXY/https_proxy/
HTTP_PROXY/http_proxy before the override runs, qwen does not consult NO_PROXY
when building its proxy agent, and the gh/git wrappers that do get the proxy
back only talk to github.com. Removing the two exports also removes the comment
that claimed a proxy-bypass symmetry the step cannot produce.

Lint & Static was red in "Check workflow file size": 276955 bytes against a
recorded 272192 with a 4096 allowance. The growth is the feature itself (the
env pair, the override block, the resolved-cohort field), not prose that could
move into a sibling file, so the baseline moves with the file rather than the
rationale being cut to fit.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvz69ico8s

* refactor(ci): simplify canary endpoint validation

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: qwen-code patrol <[email protected]>
2026-11-02 23:22:43 +08:00
易良 c2921217ef ci: gate on the OS temp directory and shorten /tmp retention to 1d (#16126)
* ci: gate on the OS temp directory and shorten /tmp retention to 1d

The 2026-10-31 hz incident: /tmp tmpfs hit 100% inodes while the
workspace and RUNNER_TEMP sat on a data disk with millions free, so
every disk-floor gate was green and jobs died on mkdtemp ENOSPC
mid-suite. The gate now always checks the OS temp directory
(deduplicated against caller arguments); the tmpfiles policy drops
7d->1d because the measured 120-260k files/day steady state exceeds
the tmpfs inode ceiling at 7d. Mutation-verified: dropping the
os.tmpdir() check reds the suite.

* test(ci): cover the TMPDIR-unset default and pin the 1d retention

Review findings: the new OS-temp test never executed the production
incident path (TMPDIR unset -> /tmp default) — a stripped default
survived the suite green; and the 7d->1d retention change had no test
coverage — a revert shipped the incident again. Add the unset-TMPDIR
sub-run, a set-equality dedup assertion, and a qwen-docker-cleanup
suite pin on the exact tmpfiles line. Mutations verified red.

* test(ci): pin RUNNER_TEMP apart so the /tmp fallback test bites

The first version of the TMPDIR-unset test let the RUNNER_TEMP default
(/tmp) satisfy the assertion, so a stripped ${TMPDIR:-/tmp} default
survived green — the exact regression the test exists to catch. Pin
RUNNER_TEMP to a distinct directory; the /tmp sample can then only come
from the OS-temp append. Mutation now reds.

* test(ci): unset TMPDIR as empty string; destructure was a no-op

The previous fix still failed on clean trees: spreading a destructured
env cannot DELETE a key — process.env.TMPDIR re-entered via runGate's
own spread, so the /tmp fallback never fired and the test failed
against the correct script. An empty-string TMPDIR is bash-unset for
${TMPDIR:-/tmp} (and bare for the regression), which is the exact
distinction under test. Clean 8/8; stripped-default mutation reds.

* ci(ecs-runner): enforce the /tmp retention from the owned daily service

Review finding: the 1d policy's enforcement agent was the distro's
systemd-tmpfiles-clean.timer, which can be masked on hardened images and
reports 'static' (unverifiable via is-enabled) on stock Ubuntu — with
nothing in the qwen stack running systemd-tmpfiles --clean, the measured
120-260k files/day steady state regrows past the tmpfs inode ceiling and
the 2026-10-31 incident class recurs with every test green. Add
ExecStartPost=systemd-tmpfiles --clean --prefix=/tmp to the daily
qwen-docker-cleanup.service, pin it in its test suite, and document the
enforcement owner in the README verification block. Mutation-verified.
2026-11-02 22:02:36 +08:00
jinyeandQwen-Coder f643e579c5 fix(managed-agent): Restore profile-owned runtime verification (#16134)
Keep the shared Failsafe configuration in pluginManagement so ordinary
release verification does not activate environment-specific integration tests.
Preserve the dedicated profiles and executable artifact assertions.

Backport the same POM repair to the pinned 0.26.5 source with checked patch
digests, and verify both patches before applying either one.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 13:50:11 +00:00
38b71299c8 feat(desktop): build and publish rpm installers (#16060)
* feat(desktop): build and publish rpm installers

Add Fedora 44 and RHEL 10 release roots, strict RPM smoke checks, and artifact naming that keeps each root distinct.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(desktop): declare rpm runtime requirements and clear the yaml gate

The rpm legs built artifacts with no `Requires:` entries: Tauri's rpm bundler
writes them only from `bundle.linux.rpm.depends`, which nothing set, so
`verifyRpmDependencies` had no webkit soname and no glibc floor to find and the
release could not pass its own gate.

- Add `rpm-fedora44.conf.json` / `rpm-rhel10.conf.json` overlays declaring the
  webkit soname and each root's glibc floor (2.43 vs 2.39 cannot share one
  config), and pass the per-leg overlay to `tauri build --config`.
- Quote-free `container: ${{ matrix.container || null }}` stays as is with an
  inline yamllint disable: quoting the expression would turn a null container
  into an empty string. The shape test pinning that literal moves with it.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvy1jgjo76

* ci(desktop): record the rpm legs in the workflow size baseline

`desktop-release.yml` is now 49858 bytes, past its recorded 45089 plus the
4096 allowance, so `Lint & Static` fails on the ratchet before any linter runs.
The growth is the two rpm legs: their matrix entries, the per-leg overlay
wiring, and the roots' comments. Moving that prose out would not pay for it —
the legs themselves are the bytes — so the number moves with the file in the
same PR, as the gate asks.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvy1jgjo76

* style(desktop): apply prettier to the rpm legs and realign the workflow shape pin

Prettier flagged .github/workflows/desktop-release.yml and
packages/desktop/scripts/smoke-linux-installers.js on head 10acaadeba.
test-release.js pinned that container line with two spaces before the inline
yamllint disable comment, which prettier rewrites to one; .yamllint.yml has
min-spaces-from-content: 1, so the pin now follows the formatter instead of
blocking it.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvy3ombr79

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-11-02 20:50:05 +08:00
jinyeandQwen-Coder 85943db2b3 fix(ci): repair Javadoc for the 0.26.5 managed runtime release (#16114)
Apply the already merged documentation-only repair to the original release source before the unchanged full verification and missing-only publication gates.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 17:12:10 +08:00
Shaojin Wen d36fdbdfe5 test(managed-agent): gate detach while a tool result is unknown (#16098)
The MCP detach must refuse before it releases anything when an
operation is recovery_blocked/outcome_unknown. Start an ext://hold
read, kill the stdio fixture, and call the Harness detach route; the
case requires the documented refusal, no release control, and the
operation still unknown.

The Hosted fork moves by the case's measured 27.4 s, booked at 55 s.
2026-11-02 11:44:00 +08:00
Shaojin Wen 7b4b353750 test(managed-agent): gate the two-server first release refusal (#16089)
Extend HostedMcpRestartFaultGatesIT with the FG12d cell #15750 still
carried: a Session configured with two MCP servers, the first release
refused before forwarding, and the same-id retry releasing both.

The case adds fixture2 only for its own boot, waits for both
configurations to settle, and requires three release controls: 503,
the same operation id answered 200, then the other configuration's
release answered 200. Both fixture processes must exit.

The Hosted fork moves by the case's measured 16.2 s, booked at 32 s.
2026-11-02 02:46:13 +08:00
Shaojin Wenandverify 4a5c487681 test(managed-agent): gate the expired-cursor resync answer (#16076)
* test(managed-agent): gate the expired-cursor resync answer

FG9e now advances the Session stream's replay floor past a lagging
client and requires one `agent.session.resync_required` frame on both
the public and WebShell streams, with no event id and the floor and
snapshot cursors it must reload through. The same cursor on the plain
events route answers `409 cursor_expired`, and the floor itself stays
readable.

This closes the expired-cursor cell the FG8 tracker (#14163) deferred
and #15750 carried. The case measures 21.3 s locally; the Hosted fork
budget and Verify step move with it.

* test(managed-agent): wait for projection coverage before advancing the floor

Awaitility treats a lambda that returns without failing as satisfied,
so the coverage guard must assert rather than return. Without this a
poll that lands before the lead's Turn is projected would skip the
advance and dereference a null window.

* docs(managed-agent): correct the resync case's tracker references

FG9e now carries the third case, so the relay-hold sentence must name
only the two resume cases. The FG8 record no longer predicts a slice-2
landing and points at the FG9e delivery instead.

* ci(managed-agent): record the resync case's measured range

The 60 s fork-budget entry was written from one 21.3 s run; the final
audit runs measured 21–23 s, so state the range the budget covers.

* docs(managed-agent): fix the stale step-ceiling header and pin note

The Hosted job's opening parenthetical still summed 291 while the tail,
the Verify step and the job ceiling had moved to 292/202/325. Also say
the replay-floor flag is a default pin, not something currently off.

---------

Co-authored-by: verify <verify@local>
2026-11-02 00:28:04 +08:00
Shaojin Wen 0f91cdedae fix(verify-pr): count suite runs and skip notices on closed PRs (#16050)
Follow-ups deferred on #16013:

- A round whose only checks were existing suites had a total of 0
  once suites moved to gates, and the checker refuses that. The skill
  now counts each suite run as one assertion, as it already did for a
  mutant run.
- Gates that record no results (an empty list, or entries counting
  nothing) no longer silence the size warning. When no suite ran, the
  round says in the report what its count holds.
- The blank-suite rule is JSON whitespace on both sides. jq's \S and
  JS's trim() disagreed on U+0085 and U+FEFF, so the checker accepted
  a list the publisher dropped.

Also, a round can queue for hours: the publisher no longer posts a
notice on a PR that closed meanwhile, unless it is finalizing its own
live status. Four such notices landed on #16013 after it merged.
2026-11-01 07:55:09 +00:00
Shaojin WenandQwen-Coder 06c1f98971 fix(verify-pr): count test suites apart from the round's assertions (#16013)
* fix(verify-pr): count test suites apart from the round's assertions

Rounds folded whole workspace suites into assertions.json, so the
published "Scripted assertions" line measured the repo, not the round:
37 of the 252 counted reports in the 30-day census exceeded 1000, and
one round's 7737 held a few dozen checks of its own, the rest being
five suites counted again for every mutant run.

assertions.json gains gates: [{ suite, pass, fail, skipped? }], one
entry per existing test suite run as a targeted gate. The skill says
suite results and mutant runs stay out of pass/fail/total, and that a
gate red the PR causes still reaches the verdict as an assertion. The
checker refuses a malformed list and warns when over 1000 assertions
come with no gates. The publisher prints the gate totals on a line of
their own, numbers only, and does not let them decide the verdict.

Also, deferred from #15938: the paragraph for a merge-ready round with
failures now states the checker's rule instead of inviting the reader
to excuse the failures, and the artifact-check guard's empty-value legs
are pinned.

* fix(verify-pr): warn on empty gates and mirror the checker's shape rules

Address the three stage-2 triage suggestions: an empty gates list no
longer silences the suite-sized warning; the publisher's jq refuses
blank suite names and non-count skipped values (false/null) instead of
defaulting them, so a shape the checker refuses never renders totals;
and the design docs say 'all three' / '三者' where the rule count grew
from two to three.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(verify-pr): give the gates shape rule one witness and honest totals

Address the five R1 review suggestions:
- one shared fixture table drives both the publisher's jq and
  checkArtifacts, and both bound counts at 2^53-1, closing the 1e309
  divergence where jq clamped an overflow the checker refuses
- the skipped clause prints only when some entry records the field,
  so an unrecorded count is no longer published as a measured zero
- a trusted verdict over gate reds carries one advisory sentence in
  both languages pointing at the assertion ledger; still verdict-inert
- pin the skill's compensation sentence and name the gates-size
  warning in the local clear-list and the CI-path sentence
- bump the workflow size baseline to 375110: the parity comment, the
  conditional clause and the advisory exceed the remaining allowance

Co-authored-by: Qwen-Coder <[email protected]>

* fix(verify-pr): print the skip clause only for fully recorded gates

Address the two R2 review suggestions:
- the clause now requires every entry to record skipped; a mixed list
  published a partial sum as the run's skip total
- pin the advisory's trusted-verdict guard from the false side: an
  untrusted run with gate reds prints the totals but no advisory
- fixtures: a mixed list drops the clause, a recorded skipped:0 keeps
  it, and a timeout headline over gate reds stays advisory-free

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-01 02:26:31 +08:00
Shaojin Wen 143a8b0653 ci(sdk-java): run the Hosted admission gates even when the Hosted verify fails (#16010)
* ci(sdk-java): run the Hosted admission gates even when the Hosted verify fails

The admission step had no condition, so it ran only when every earlier
step passed. The Hosted verify fails most nights, and the step was skipped
on all 13 nights from 10-14 to 10-30 that the verify failed, including the
three since the Runtime Broker fault gates stopped being skipped the same
way. It ran on the other three nights and passed each time, in about two
minutes.

The step rebuilds the module with `clean verify` and needs only the CLI
bundle, the installed SDK and the MySQL service. Each gate class works
under its own random tenant, and on green nights it already runs against
the database the Hosted verify used. So it takes the condition the fault
gates and the O4 step use: run unless the run was cancelled, as long as
the SDK install succeeded.

The class check after it reads only the admission step's reports. It now
follows that step's outcome instead of the job status; otherwise it would
still skip after a red Hosted verify while the step it audits ran.

The pin for the fault-gate condition now covers both steps, and a new case
pins the class check's condition and position. The setup-step assertion
now names the offending steps in its message: vitest abbreviates the
array once a step name is long enough, and the install step's own name
already is.

* ci(sdk-java): keep the O4 gates reachable on red-verify nights

The admission step now runs on red-verify nights too, so the job's worst
case sums all four capped steps (291 min) plus the uncapped setup. Raise
the hosted-harness-mysql ceiling from 304 to 324 so a slow setup (the CLI
install retries three times) cannot cancel the job before the O4 gates,
whose !cancelled() condition would record them skipped.

Close the two pin gaps the review found: the red-verify condition test
gains the O4 row (all three conditioned steps are now pinned), and the
admission class-check test pins that the check step carries no
continue-on-error, so a dropped gate class still fails the job.

All three pins are mutation-witnessed at this head (ceiling back to 304,
O4 if: deleted, continue-on-error on the check step each redden exactly
their case); the nine sdk-java.yml parsing suites pass 709/709.

* ci(sdk-java): boot the admission gates on their own database and bump the size baseline

Now that the admission step runs after a red Hosted verify, it must not
share that verify's database. Managed schema validation refuses any table
outside the managed set, and four Hosted ITs create fg*_fire tables they
drop only during cleanup (the relay classes only while their Spring context
is active), while the isolation extension truncates only ma_ tables. A
class killed before its cleanup would leave one behind and fail both
admission gates at boot. On a MySQL 8.4.6 rig, an fg9b_fire table in
hosted_harness_test made the base command fail both classes with "Managed
schema must contain exactly the 38 target tables", while this command
passed both on a fresh qwen_admission_ci. The step now creates that
database the way O4 creates qwen_o4_ci.

The pin test adds the O4 row, pins that the class check has no
continue-on-error, custom shell or altered command, that no gate command
swallows its failure, and that admission boots on its own database. Both
steps' names are checked before use.

sdk-java.yml outgrew the ratchet: main's copy was already 3971 bytes over
its recorded 144802 without a bump, and this change adds the rest. Even
without its comments the file would exceed the ceiling, so the baseline
moves to the file's size.

* test(ci): pin the admission step whole and keep the install ahead of the verify

The admission step's command, env and shell are now pinned with literal
values. The hosted-gates class check counts a failed class as one that
ran, so an `||` fallback, a pipe, or -fn in the command or in MAVEN_ARGS
would have turned a red admission gate green, and a second -Dmysql.url
would have pointed it back at the shared database. A list of forbidden
substrings caught some of these and rejected harmless commands, so the
fault-gate and O4 rows go back to pinning only their condition and
continue-on-error; their commands are unchanged by this PR.

The three gate rows also pin that the SDK install comes before the Hosted
verify. After it, a red verify would skip the install and every gate with
it, on exactly the nights the condition is for.

Comments: the job comment opened with stale ceilings (162 + 20 + 50 + 20
= 252); it now gives the measured setup time (under 10 minutes on every
nightly from 10-14 to 10-30), states that what changed is the timed-out
Verify path, which used to skip admission, and says the CLI install makes
up to three attempts rather than retrying three times. The O4 row comment
says O4 has carried its condition since it was added.

* test(ci): derive the red-verify contract from the job and pin the setup allowance

The red-verify condition was asserted over three hand-listed step names,
so a gate added to the job later sat outside every assertion and a red
verify would skip it silently, as it skipped the admission gates. A new
case derives the contract from the steps after the Hosted verify:

- Only the two report checks that need the verify's output may run on a
  green job alone. Every other step's condition must be `always()`, or
  `${{ }}` around `always()`, `!cancelled()` or `failure()`, optionally
  narrowed by one step's outcome. Any other condition either has no
  status function, so GitHub prefixes `success() &&`, or still reads the
  job status. A narrowing step must itself run regardless of the verify.
- No step after the verify carries continue-on-error.
- Every step that invokes Maven, matched as a word so folded commands,
  wrappers and quoted paths count, carries the exact red-verify condition
  and a ceiling, so it enters the summed step budget.

The job-budget rows now carry the setup allowance (33 for the MySQL job,
15 for failover) and assert ceiling - steps against it, so raising a step
ceiling re-derives the allowance instead of quietly spending it.
2026-11-01 02:25:44 +08:00
易良 eaafba5d14 ci: route the classify_pr gate to ECS for every pull request (#16019)
The gate is the needs-prerequisite of every Linux lane, but its own
runner stayed association-gated: untrusted fork PRs queued the whole
chain on the scarce hosted pool (observed 30+ min on PR #15851).
classify executes only trusted base-SHA code with a read-only token —
a strict subset of the lint exposure accepted on 2026-10-31 — so route
it unconditionally to ECS, kill-switch excepted. The ubuntu_runner
association gate for the test/integration lanes is unchanged.

Routing battery: gate matrix across all associations, structural pin
against clause creep; association-clause mutation reds the suite.
2026-11-01 00:12:22 +08:00
Shaojin Wen 7bc00a3a08 feat(review): re-anchor on drift, run actionlint, account blockers, refuse retries (#16001)
* feat(review): re-anchor on drift, run actionlint, account blockers, refuse retries

Four fixes from the 2026-10-31 audit of seven review run ledgers
(#15057, #15377, #15385, #15578, #15853, #15955, #15974):

- Head drift posts the completed review at the new head (the re-anchor
  protocol) instead of abandoning the round — the maintainer's ruling
  on #15578, never landed in the skill until now. The once-per-review
  bound and the COMMENT cap survive; the marker withholds its anchor.
- script-lint runs actionlint's workflow-level checks (syntax, context
  availability, action refs) and gates them as [lint] Criticals; only
  the embedded run: shell stays deferred. #15974's unloadable workflow
  (runner context in jobs.env) is the regression test, measured against
  actionlint 1.7.12.
- pr-context writes a blocker inventory; compose-review cross-checks it
  against the round's blockerRulings and caps at COMMENT when the walk
  skipped a blocker (#15974's missed review-level blocker).
- submit documents --dry-run as the only sanctioned probe and refuses a
  re-post at the commit already landed (already-posted-this-commit) —
  the retry that replied this round's own roots back to themselves
  (#15853). The receipt records lastCommitId.

Each fix carries its rule text, pin tests, and a DESIGN.md incident.

* fix(review): isolate actionlint config and refresh lint on re-anchor

Both Criticals from the #16001 review, reproduced against the exact PR
source and closed structurally:

- The newly active actionlint invocation auto-loaded the reviewed repo's
  .github/actionlint.yaml, so a PR-added ignore-all rule suppressed every
  workflow diagnostic — including the unloadable-workflow expression this
  linter was activated to catch. It now runs against a private mkdtemp
  neutral config via -config-file (measured to override the discovery),
  fails closed when no private config can be created, and carries exactly
  the two trusted compatibility exceptions the repo's own lint script
  runs (self-hosted runner labels, environment.deployment schema lag).
  The regression plants the ignore-all config in a real temporary git
  repository and proves the expression finding survives.

- The re-anchor protocol dropped fixed authored findings but left the
  lint gate regenerating its findings from the old-head report, so a
  lint blocker the delta fixed was republished against the new commit.
  The protocol gains a refresh step (scratch worktree at the new head,
  script-lint --diff rewriting the report bound to the new diff) and a
  lintDiffPath compose field that makes the gate — body and ledger
  marker alike — hash the new diff as its freshness basis. Executable
  regressions pin both directions: fixed blocker gone (COMMENT, absent
  from body and marker), still-standing blocker keeps gating
  (REQUEST_CHANGES). A malformed lintDiffPath fails closed.

* fix(review): base-tree actionlint config, empty-diff parse, lane provisioning

Three findings from the round-3 review of #16001, each reproduced
against the exact source and closed:

- The private {} config blocked PR-planted suppressions but also dropped
  the repo's maintainer-merged compatibility exceptions (ECS runner
  labels, concurrency.queue, the $/ self-repository action reference),
  turning correct release-workflow changes into bogus [lint] Criticals.
  The trust boundary is now the MERGE-BASE tree's .github/actionlint.yaml
  (hex-shape-guarded git show, copied into the private file, {} only when
  the base has none); the head copy is never read, and the two trusted
  -ignore constants ride on top exactly as scripts/lint.js runs them.
  Pinned with a two-commit repo and a real-binary run showing the base
  exception honored while the planted ignore-all stays blocked.

- An empty refresh diff (the new head reverted the PR's last change) was
  treated as a parse failure: the range selection fell back to the old
  plan's context-inclusive hunks and promoted a pre-existing finding into
  a blocker the gate accepted as fresh (the empty diff has a valid hash).
  diffParsed now separates parse success from range count; empty and
  deletions-only diffs classify everything pre-existing. The reviewer's
  exact scenario is an executable regression.

- The lanes that run the gate provisioned no linters, so the new
  uninstalled-capping fired on CI and the real-binary regressions were
  skipIf'd out of every run. The review job and the unit-test lane now
  install the pinned, SHA-256-verified actionlint/shellcheck through the
  repo's own installer and promote the job-scoped dirs onto GITHUB_PATH
  (unconditional, advisory, Windows excluded). The 335 workflow meta-test
  pins pass unchanged.

* fix(review): lint in the worktree cwd, guard lint.js entry, pin the sha fence

Three gaps from the scripted deep verification of #16001 (50/55
assertions, 17 mutants), two of them in the round-3 fixes themselves:

- F1: actionlint resolves the config's paths: entries against its cwd,
  and the runner spawned it in the session checkout, so every
  path-scoped base exception was inert while the global keys kept
  working — the split that hid behind the labels-only test. The runner
  now receives the worktree root and spawns inside it with PWD aligned.
  The real-binary suite gains the path-scoped fixture it was missing: a
  benign concurrency.queue edit that must lint clean, and an attack cell
  where the base exception suppresses the schema-lag finding while the
  planted head ignore-all still fails to hide the expression.

- F2: the provisioning steps imported scripts/lint.js, whose
  unconditional top-level main() ran a full lint pass on import and
  never reached the install loop, silently under continue-on-error.
  lint.js now runs its CLI only when it is the entry point
  (import.meta.url guard, argv[1]-safe for node -e imports), and both
  steps assert each installer produced its binary before promoting the
  dir, so a future no-op install fails visibly instead of degrading to
  skipped tests.

- M15: the option-injection fence was unobservable — git's own
  rejection of a malformed option matches the guard's refusal, so the
  guard's mutation survived. A spawn spy now pins the fence directly:
  non-hex merge base never spawns git; a hex-shaped nonexistent one
  does (positive control with the exact argv).

Also acknowledges the verification's correction: the bot review's
$/prepare-release-runner example does not exist at this head (the base
config entry is precautionary); the queue exception at line 485 is the
one reachable path-scoped case, and F1 is pinned on exactly that shape.
2026-10-31 13:43:34 +00:00
812a8c7ccb fix(release): use the job token for the publish checkout (#15730)
* fix(release): use the job token for the publish checkout (#15727)

The publish checkout only reads the release SHA and disables persisted credentials, so the bot PAT it asked for was never used. Gating a read-only checkout on that secret turns any checkout-time auth failure into a failed release before any work runs; the push step already re-authenticates with the PAT it needs.

* fix(release): probe the bot PAT on dry runs (#15727)

The publish checkout no longer touches the bot PAT, so a dry-run rehearsal
stopped exercising the credential and a rotated PAT surfaced only on the
next live release. Probe it in a dry-run-only, non-blocking step placed
after the push step, where the live path legitimately holds it.

Widen the package-scripts pin so it catches any secret on the read-only
checkout and any CI_BOT_PAT consumer before the push step, not just the
exact step name and secret spelling. Bump release.yml's size baseline for
the added step.

* fix(release): check the bot PAT's push capability on dry runs (#15727)

* fix(release): drop the ineffective dry-run PAT probe (#15727)

The probe only ran on dry runs, where no release step consumes the bot
PAT, and its `|| echo` turned every failure into a green warning. Make
the publish checkout's job token explicit instead, and keep the pins
that no secret or bot PAT reaches it before the push step.

* test(release): pin the bot PAT out of the publish checkout (#15727)

The PR's Reviewer Test Plan names expect(checkoutStep).not.toContain('CI_BOT_PAT'), but neither test file contained it. Add the assertion before the job-token pin so the documented mutation fails on exactly the named assertion.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: 易良 <[email protected]>
2026-10-31 20:51:11 +08:00