Commit Graph
1240 Commits
Author SHA1 Message Date
Shaojin WenandQwen-Coder 68c9fa27fb feat(verify-pr): add local Docker recipe with a host-side model proxy (#16077)
* feat(verify-pr): add local Docker recipe with a host-side model proxy

A local /verify-pr round needs the same isolation the CI lane provides,
but the skill only documented the boundary check, not how to run the
model credential through it. Add scripts/verify-pr-model-proxy.mjs, a
host-side relay that holds the real key and lets the container call the
model with a per-run bearer token, mirroring the CI lane's inline proxy
(POST /chat/completions only, nonce health check, per-chunk idle
watchdog). The listen address is a flag so the proxy can bind the Docker
bridge, and readiness is printed as one JSON line.

The SKILL recipe pairs it with a docker run template: the token travels
as QWEN_VERIFY_PROXY_TOKEN (not OPENAI_API_KEY, which the isolation gate
refuses), and the container's provider entry reads it via envKey. The CI
lane keeps its inline copy — the proxy holds the real key, so it must
not execute a script from the PR-head checkout.

Verified: 11 new pin tests plus all existing verify-pr suites (120
passed), and a real docker smoke — the gate passes inside
node:22-bookworm with the proxy token, refuses OPENAI_API_KEY with exit
4, and a container POST reaches a mock upstream with the real key
injected and the /v1 prefix preserved.

* fix(verify-pr): harden the model proxy after adversarial audit

Audit round 1 found the relay forwarded client hop-by-hop headers
(connection, transfer-encoding, expect, proxy-authorization, ...) to the
upstream, which RFC 7230 forbids and which fights undici's own framing
for the relayed stream. Strip them, with a chunked-body test that pins
the behaviour. Round 1 also found the CLI entry had no coverage: add
tests for the missing-config exit code and the printed JSON coordinates
(token override, base-path prefix, no real-key leak, health nonce), and
document that the printed baseUrl uses the listen address, so binding
0.0.0.0 yields a URL clients cannot dial.

Rounds 2 and 3 (open-ended re-read plus adversarial passes over the
updated diff) found nothing further. All verify-pr suites: 123 passed.

* fix(verify-pr): address review findings in the model proxy and checker

Round-1 review (12 inline findings at 70aed891bf). Proxy: abort the
upstream when the client disconnects before headers instead of paying
for a completion nobody reads (R1-1); make the idle watchdog pause-aware,
re-arm on drain, and match the client's own 240 s stream tolerance
(R1-3); bracket IPv6 listen hosts via an exported formatter and normalize
base paths once (R1-12). Checker: add --context so a host-held snapshot
pins the expected head instead of the writable artifact copy, and refuse
an unusable snapshot rather than silently falling back (R1-9).

Each guard is pinned by a test that was verified to go red when the guard
is removed (six mutations: pre-header abort, pause guard, per-chunk
re-arm, body error handler, IPv6 bracket, --context).

* docs(verify-pr): harden the local container recipe and record the design

Recipe: run the key-holding proxy and the checker from a trusted
checkout, never the PR tree (R1-6, R1-9); background the proxy with a pid
file and stop it at the end (R1-4); read the key instead of inlining it
(R1-5); use a named detached container driven by docker exec (R1-7);
require a standalone clone, the host uid and a fresh HOME (R1-8); mount
the full host-held metadata snapshot rather than the four-key artifact
copy (R1-10); and attest only inside, after rejecting credential-shaped
ignored files (R1-11). Add the bilingual design record (R1-2), cross-link
it from the local-round design, and pin the recipe's trust lines in tests
so reverting them turns the suite red.

* fix(verify-pr): address round-2 verification findings

Round 2 at 3509c2d1aa reported one Low and four Suggestions.

- Relay the exact <basePath>/chat/completions path. A suffix match also
  forwarded /admin/chat/completions to the upstream.
- Pin the response content-encoding strip with a gzip upstream: undici
  hands the proxy decoded bytes, so forwarding the header makes the client
  gunzip plain text.
- Reject credential-shaped ignored entries at any depth (a nested
  secrets/.env included) instead of only a root .env/.env.*.
- Name the round's engine as the released CLI and offer the tree build for
  a round that must exercise the PR's own CLI.

The CI inline copies keep their existing two-header strip: that lane is
untouched by design and a change there needs its own workflow review.

Tests: 141 passed. Three mutations verified red (exact path, gzip header,
nested credential pathspec). The review-pr check at this PR is red because
the bot lane exhausted ten retries on "429 rpm exceeded", not because of
this diff.

* fix(verify-pr): close round-3 trust-boundary findings

Round 3 at 3ccf38697d reported two Criticals against the recipe, one
fix-induced Critical, and four Suggestions.

- prepare, the credential scan, the isolation gate and the checker now all
  come from <trusted-clone>; prepare runs with the PR clone as its cwd so
  the HEAD check still applies (R2-1).
- The base commit's .qwen is extracted and mounted read-only over the
  tree, so the round's instructions and project settings cannot come from
  the PR (R2-2).
- The attestation is passed to the shell that starts the round, because
  every docker exec is a fresh shell (R1-11, fix-induced).
- The proxy token is exported from proxy.json and passed with a bare -e;
  the file is chmod 600 and removed at cleanup (R2-3).
- Connection-nominated hop-by-hop headers are stripped (R2-6).
- Recipe pins now enumerate every checker call site, require a non-empty
  docker-run slice, and seed the writable checker snapshot so a fallback
  regression is caught (R2-4, R2-5).
- New verify-pr-credential-scan.mjs lists ignored files one by one,
  refuses credential-shaped names at any depth, and leaves node_modules
  and dist alone — replacing the pathspec check that refused every
  installed or built tree (round-3 Low).

Tests: 148 passed; ten mutations verified red.

* fix(verify-pr): address round-4 review findings

Four P2 findings at 441a523927:

- The credential scan now limits git enumeration with credential pathspecs
  and depth-aware node_modules/dist excludes, so an installed tree's
  multi-megabyte ignored listing never overflows the output buffer.
- Those excludes apply inside workspace packages too, so packages/*/dist
  and packages/*/node_modules are not mistaken for project credentials.
- The recipe waits for proxy.json to carry a token and validates the
  exported value, so a truncated background write cannot reach Docker.
- The pre-header disconnect test waits until the mock upstream has
  recorded the request before destroying the downstream socket, instead
  of racing the proxy's own (correct) early abort.

Tests: 150 passed; three mutations verified red (enumeration pathspecs,
excludes, readiness loop).

* fix(verify-pr): refuse nested repositories in the credential scan

Round-4 verification at 19b14b1a8f left three Suggestions.

- A nested git repository is now refused instead of silently skipped: git
  does not descend into it, so a .env under tmp/pr-clone/ would pass. A
  vendored .git under node_modules/dist stays excluded like the files there.
- isExcluded is removed: the pathspecs already exclude those directories,
  and the surviving JS filter was unpinnable.
- The spaced Connection-nomination list is pinned; dropping trim made the
  proxy answer 502.
- The node_modules/dist credential exclusions are documented as deliberate.

Tests: 152 passed; two mutations verified red (nested refusal, trim).

* fix(verify-pr): address round-5 review findings

Four Criticals:
- The key entry works without a TTY: an interactive shell prompts, an
  agent's shell reads REVIEW_OPENAI_KEY_FILE and fails with a named error.
- prepare disables fsmonitor, hooks and credential helpers on the
  PR-controlled clone, and the scan refuses credential-bearing remote URLs.
- The proxy token is re-derived from proxy.json in the docker run step,
  because each step is its own shell.
- The credential scan covers .env~, *.p12, id_rsa*, gha-creds-*, and the
  other credential families the repo's own .gitignore names.

Suggestions:
- Guard call site moved to <trusted-clone>, <clone> defined for watch mode.
- The newest verify comment is snapshotted as previous-report.md.
- The real key is unset once the proxy owns it; the token is unset at cleanup.
- `{1..100}` replaces seq for macOS; --user and the proxy token's
  non-credential status are pinned; the scan abort is structural (`|| exit 1`).
- Step 6 wording matches the base-pin; the docs narrow the base-pin scope for
  root AGENTS.md/QWEN.md, list the real CI divergence, explain content-encoding
  in English, fix the zh token wording, and record the checker --context
  contract and the ownership attribution.
- The readiness gate compares the health nonce; --port is removed.

Test gaps closed: gzip header assertion, mid-body disconnect, reset event,
PUT, bare-base exact path, positive header passthrough, 502 transport
failure, full paused-stream equality, token shape, publish --context pin,
checker second positional, scan path fixtures, headers-only idle window.

Tests: 159 passed; twelve mutations verified red.

* fix(verify-pr): close round-5 verification findings

Two Lows reproduced at b5c717a5d5:
- prepare's hardening named clean filters but only disabled fsmonitor,
  hooks and credential helpers. It now enumerates every local
  filter.*.clean/smudge/process and empties it with a -c override before
  any git read; a planted-filter fixture proves the override with a
  positive control that fires without it.
- The nested-repository refusal over-refused installed trees whose
  node_modules holds a real vendored git repo (git reports the directory
  entry, which the exclude pathspec cannot match). The enumeration is now
  segment-filtered in JS, so vendored dependencies stay excluded while a
  nested project repository is still refused.

Suggestions and nits:
- The remote-URL guard reads url.*.insteadOf as well as remote.*.url and
  refuses any http(s) userinfo, including a token used as the username;
  ssh://git@ stays benign.
- Public keys (.pub) are no longer treated as credentials, and a
  backup.env~ fixture pins the trailing-tilde family.

Tests: 163 passed; six mutations verified red (filter overrides, vendored
segment filter, userinfo breadth, insteadOf enumeration, public-key
exclusion, backup.env~ pathspec).

* fix(verify-pr): close round-6 verification findings

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

* fix(verify-pr): close round-7 verification findings

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

* fix(verify-pr): address round-8 review findings

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

* fix(verify-pr): close round-9 review findings

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

* fix(verify-pr): close round-10 review findings

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-03 14:13:59 +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
Shaojin Wen 14de8a035e feat(verify-pr): inherit --publish consent on same-session re-verification (#16112)
* feat(verify-pr): inherit --publish consent on same-session re-verification

A maintainer who ran `/verify-pr <n> --publish` and then typed the
"有更新了,重新验证" follow-up trigger had to confirm publishing again:
the skill inherited the previous round's coordinates but deliberately
excluded publish consent, so every re-verification of an already
published PR repeated a confirmation the maintainer had just given.

Let a follow-up round inherit publish consent from the most recent
round for the same PR in the same session. The round must announce the
inheritance in one line before posting, `--no-publish` on the trigger
opts out and breaks the chain, and the most-recent-round semantics keep
a quiet round from reviving an older consent. Cross-session rounds
still require an explicit request: the phrase alone posts nothing, and
a published comment found on the PR is evidence of a past round, not
consent for a new one. The CI lane and watch mode are unchanged.

Update the two pin tests that fixed the old per-invocation-only rule
and pin the inheritance, announcement, opt-out, and cross-session
boundaries.

* fix(verify-pr): carry inherited publish consent through follow-up chains

Review of the first round found the consent chain broke after a single
inheritance: R1 invoked with --publish, R2 a bare trigger that inherited
and published, R3 a bare trigger whose most recent round (R2) was not
itself invoked with --publish — so R3 fell back to asking again, the
exact friction this change exists to remove.

Model consent as a per-PR state of the session instead of keying it to
the latest invocation: a --publish invocation grants it, a fresh
/verify-pr round without --publish or a trigger carrying --no-publish
withdraws it, and a bare trigger keeps it unchanged, so consecutive
bare triggers publish under the original grant until a round withdraws
it. Watch rounds keep their own standing consent and never change this
state. Also close the two review suggestions: the Hard rules bullet now
recognizes a request inherited within the same session, and the
argument hint lists --no-publish so the opt-out is discoverable.

Pins cover the grant/withdraw/keep mechanics, the three-round chain,
the watch boundary, the Hard rules clause, and the updated hint.

* fix(verify-pr): grant session consent on any explicit publish request

Second review round found two gaps. The design docs for the assets
coordinates still stated that publishing consent is per invocation,
which reads as an invariant and could silently revert the feature;
both language versions now defer to the skill's session-consent rule
while keeping "coordinates are not consent". And the grant keyed on
the literal --publish flag, while the publish rule also accepts an
explicit request in words — a words-request round published but seeded
no consent, splitting the friction this PR removes by request form.
The grant now rides on the explicit request, flag or words alike, and
the withdrawal matches it.

Also close the deep verification's coverage survivors: the withdrawal
is pinned as one contiguous sentence (a fresh round keeping consent
now fails), the chain's upper bound, the announcement's naming duty,
and the evidence-of-a-past-round clause each have pins, and an
inherited publish now carries its one-line announcement into the
comment body immediately before the attribution footer, so a PR
reader can tell it from a per-round request.
2026-11-02 22:22:00 +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 d9d6fc4a4b refactor(managed-agent): split Agent and Session stores (#16090)
* test(managed-agent): audit lock order across store collaborators

Record the bilingual Store decomposition contract and entry ownership,
transaction, lock and caller inventories. Follow final collaborator fields
across source files while preserving owner-bound facade populations and
existing ambiguity, transaction and compiled-member checks.

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

* refactor(managed-agent): split Agent and Session stores

Move Agent and Journal responsibilities into immutable internal collaborators while retaining transaction facades and compatibility bridges. Resolve Journal dependencies at construction and migrate factories, fixtures, lock auditing and the bilingual implementation contract.

Record focused verification and outstanding full-gate requirements separately.

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

* fix(managed-agent): repair store decomposition verification

Keep declared field receivers visible across method-local type scopes while retaining fail-closed nested class handling. Preserve the Hosted CSI dependency identity assertion at its new owner.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 12:46:43 +00:00
qqqysandShaojin Wen 9c96f5aa82 feat(mods): run prompt.submit hooks in headless runs (#15905)
* feat(mods): run prompt.submit hooks in headless runs

Mods' prompt.submit hooks can rewrite, add context to, or drop each headless prompt before UserPromptSubmit, @ processing and the transcript see it. Terminals keep skipping these hooks until they raise the event. Includes the design document for the four steps.

* test(mods): update bundled worker load response expectation

* fix(mods): preserve downstream prompt submit outcomes

* fix(mods): isolate prompt fallback from host continuations

* fix(mods): preserve latest local prompt outcome in recovery

* fix(mods): hear only a message's text and take any answer after next

A stream-json message with an image block reached the prompt.submit
hooks as its serialized blocks, so they were skipped as too long or saw
JSON noise, and a rewrite carried that JSON into humanInput and
turn.start. The hooks now hear its text blocks; a rewrite replaces them
and the other blocks follow it.

A hook's answer after its next changes nothing, but it was still
validated, so a hook that only watches and returns nothing, or spreads
the event back, failed and logged a skip on every prompt. Only a drop
there is now checked.

* fix(mods): keep prompt.submit to typed text and drop empty rewrites

- Show only the rewritten text in the transcript and UserPromptSubmit,
  not the serialized non-text blocks of a stream-json message.
- Skip prompt.submit for SDK messages that answer a tool call or carry
  tool blocks, and for messages without text.
- Drop a prompt the hooks rewrote to nothing instead of sending the
  model an empty prompt.

* fix(mods): send each prompt context once on its own

The Mods' prompt.submit context and UserPromptSubmit's additionalContext
share one hidden block, and the block was only skipped when the
conversation already held it whole. A Mod whose context changes every
turn made every block new, so an unchanged UserPromptSubmit context went
to the model again each turn.

The injected part now records the contexts it joins, and a context counts
as delivered when an earlier block holds it, so only the contexts the
conversation lacks are sent. A block without that record, as after
resume, still counts as one context.

* fix(mods): keep a message's blocks in the transcript after a rewrite

A stream-json message with blocks other than text is recorded with those
blocks serialized after its text. A prompt.submit rewrite recorded the
rewritten text alone, so the same kind of message showed two shapes in the
transcript. The rewritten prompt is now recorded with the blocks after it.

Adds the headless E2E test plan and its results.

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-11-02 09:14:14 +00: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
易良andQwen-Coder 292c0da525 test(install): pin Windows PATH persistence contract (#16072)
* test(install): pin Windows PATH persistence contract

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

* test(install): format the pinned PATH assertion

Prettier collapses the single-string expect(...).toContain(...) call at 76
columns, which the incremental lint gate rejected on the previous head.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-02 15:44:05 +08:00
Shaojin Wen 0a7f260704 fix(managed-agent): reach the listeners the server resolves in upgrade start and verify (#16051)
* fix(managed-agent): reach the listeners the server resolves in upgrade start and verify

start and verify derived the control-plane URL from server.env's
QWEN_MANAGED_AGENT_SERVER_ADDRESS and SERVER_PORT, and the Harness URL from
its QWEN_MANAGED_AGENT_HARNESS_BASE_URL. A port or address set anywhere else
(config/application.yml, an argument, SPRING_APPLICATION_JSON, a relaxed
SERVER_ADDRESS) or a management.server.port of its own made start wait out
--timeout on a port nothing served.

ResolvedConfiguration now also prints serverUrl, healthUrl and
harnessBaseUrl as Spring binds them: server.port, server.address and
server.ssl, with a wildcard reached on loopback, and the health endpoint by
Spring Boot's management-port rules. start resolves them before it changes
any unit, verify before its rows. --server-url still names the control
plane; a port of 0 or below refuses until it does; a configuration that
does not resolve falls back to the server.env URLs with a notice.

* fix(managed-agent): keep the health name for a blank path mapping

A blank management.endpoints.web.path-mapping.health, such as an unset
${HEALTH_PATH:}, is ignored by Spring Boot's MappingWebEndpointPathMapper,
so the endpoint stays at /actuator/health. The resolver turned it into an
empty segment and sent start and verify to /actuator, where start waited
out its timeout on a healthy server. Fall back to the name when the
mapping is blank, as Spring does.

Also from review:
- the fallback notice says only the Harness URL comes from server.env
  when --server-url names the control plane;
- start's success line names the API URL next to the health endpoint;
- verify with --server-url resolves the configuration only for --tenant,
  as it did before, since it does not reach the Harness.

* fix(managed-agent): refuse listener URLs fetch cannot dial in upgrade start and verify

A link-local server or management address keeps its IPv6 zone in the
resolved URL (http://[fe80:0:0:0:0:0:0:1%1]:8080), and the WHATWG URL
parser behind fetch rejects every zone spelling. The health probe caught
the TypeError as "not UP", so start waited out --timeout on a URL it could
never send. The same held for an out-of-range port, a server.env address
with a zone on the fallback path, and a --server-url that does not parse.

listeners() now checks both URLs with URL.canParse after the no-fixed-port
refusal, before any unit changes, and names the zone when that is why. The
resolver test pins that the zone is kept, so the script can say so. The
verify test lists the health probe with the session reads instead of
slicing it off.
2026-11-02 12:57:52 +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
易良andyiliang114 7f774219ae test(opentui): run the Windows preview installer suite through cmd.exe (#16028)
* test(opentui): launch the Windows preview entry through cmd.exe

The PowerShell preview installer preservation suite failed on every
nightly Windows run: the seeded qwen.cmd launcher printed nothing, and
the leftover batch child kept the file locked so teardown hit EBUSY.

PowerShell was only a way to run a .cmd from Node. Its `&` call plus
`exit $LASTEXITCODE` returned before the batch child wrote anything, so
run the entry through cmd.exe instead (`shell: true`).

PowerShell also writes its own startup cache under
%LOCALAPPDATA%\Microsoft\Windows\PowerShell. The fixture pointed
USERPROFILE at the home tree the assertions treat as installer-owned, so
every successful test failed on that cache. Point APPDATA and
LOCALAPPDATA outside the compared home.

* test(opentui): compare home files, not shell-created folders

With the launcher fixed, every case in the suite now reaches the
preservation assertion and fails on Windows because PowerShell creates
its own empty %USERPROFILE%\AppData folder shells on first run. Those
are the shell's account directories, not installer output.

Redirect APPDATA/LOCALAPPDATA outside the fixture home (so the startup
cache file stays out too) and compare only the home's file contents.

---------

Co-authored-by: yiliang114 <[email protected]>
2026-11-01 10:53:33 +00: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
93231beca5 test(managed-agent): assert A3 sibling call binding (#15995)
Co-authored-by: wang1408 <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-01 06:57:12 +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
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
edde639992 feat(cli): support exact-version updates without npm discovery (#11486)
* feat(cli): support exact-version updates without npm discovery

* fix(cli): repair build and close exact-version update review gaps (#11486)

The merge into this branch left two copies of the semver default import
in standalone-update.ts, so packages/cli no longer compiled (TS2300) and
every downstream gate went red. Drop the added duplicate, keeping main's
import position.

Also address the remaining review findings on the exact-version update:

- Verify the extracted archive's manifest.json version matches the
  requested version before activation, alongside the existing executable
  smoke test. The manifest is the field the installation reads back and
  the prepared-update gate compares against; an inflated manifest version
  would otherwise silently freeze all future prepared updates.
- Pin the yargs builder contract: --target-version must reach the handler
  as argv.targetVersion under the production .strict() parser, going red
  if the builder is dropped or the option renamed.
- Add the issue #11484 acceptance-criterion-7 E2E test: update a
  disposable standalone install to an exact version served from a local
  HTTPS release root while the npm registry sink records zero requests,
  asserting the installed manifest version and rollback retention.
- Document that an explicit target is not persisted: automatic updates
  move a standalone install forward again next session, and keeping an
  exact older version requires general.enableAutoUpdate: false.
- Add the Chinese version of the design doc with reciprocal language
  links.

* fix(cli): close standalone update review gaps from round 2 (#11486)

Address the second-round review findings on the exact-version update:

- Validate the extracted archive's full manifest identity (name, target,
  version) before activation instead of version alone. A mirror artifact
  with the right version but no name passed every check and installed,
  after which the directory silently stopped classifying as a managed
  standalone install; the gate now rejects it and preserves the install.
- Guard both version comparisons with normalizeVersion's own predicates
  (SEMVER_RE and semver.valid), so a regex-valid but semver-invalid
  version like 01.2.3 hits the intended mismatch message instead of
  throwing "Invalid version format" out of the comparison.
- Rename the manifest gate's message to name the archive check rather
  than the smoke test, which has not run yet at that point.
- Make the target-version E2E hermetic: pin QWEN_REQUIRE_SIGNATURE,
  QWEN_CODE_LANG, and QWEN_HOME per spawn (witnessed by staging hostile
  ambient values inside the test), re-seed the fixture installation in
  beforeEach so vitest task retries never inherit the post-activation
  tree, and guard the afterAll server-close promises so an early
  beforeAll failure no longer hangs the hook and leaks the scratch root.
- Pin the documented "explicit targets permit same-version reinstalls
  and downgrades" guarantee with performStandaloneUpdate cases that go
  red if the prepared-route staleness check ever moves into the shared
  path.
- Correct the fixture's header comment: local-git-clone classification
  requires the resolved CLI path inside the cwd, not merely a cwd inside
  a git worktree.

* fix(cli): close standalone archive gate review gaps from round 3 (#11486)

Validate the archive's runtime flavor and executable layout against the
installed build before activation, share one version predicate and one
runtime-layout predicate instead of hand-inlined copies, give every gate
predicate a discriminating test, keep the E2E registry sink honest with a
discovery control, and document the manifest identity contract.

* fix(cli): pin the git shim's module scope in the reverse-apply screen test (#11486)

The test shadows git with an extensionless Node shim under tmpdir. Node
derives the module system of an extensionless file from the nearest
ancestor package.json, so a "type": "module" package.json above tmpdir
(the runner image ships /tmp/package.json) loads the CJS shim as ESM:
it dies on "require is not defined" before forwarding to real git, the
restore's ls-files -v spawn never arms the stamp, and the screen test
fails without the screen ever being asked. Pin {"type":"commonjs"} in
the shim directory so the shim loads the way its body is written,
regardless of what sits above tmpdir.

* fix(cli): flavor-aware standalone archive routing and launcher entry-point gate (#11486)

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

* fix(cli): sanitize --target-version rejection echo and bun test fixture

normalizeVersion throws with the raw flag value interpolated, and the new
--target-version error path echoed it to stderr unsanitized, so terminal
escape sequences (OSC-52, C0/C1, bidi overrides) supplied via argv reached
the user's terminal verbatim. Collapse whitespace and strip escapes,
control and format characters at the write site, mirroring the existing
sanitizeForStderr pipeline, and pin it with an escape-carrying reject case.

Also stage the real bun archive layout in the standalone-update fixture
(bun/bin/bun plus the node/bin/node compat shim installBunRuntime writes)
so the smoke test's two-hop execution chain has a witness: shortening the
staged shim now reds the bun-flavor update test with "Smoke test failed".

* fix(cli): accept pre-v0.23.0 standalone manifests without runtime (#11486)

* fix(cli): address target-version review feedback (#11486)

* fix(cli): keep update recovery guidance and gate win32 test (#11486)

Preserve the updater's manual-recovery command by using a 4,096-character bound for its own failure text; the 500-character bound remains for the untrusted --target-version echo.

Gate the pre-v0.23.0 manifest success-path test with the same win32 skip as its sibling tests, since atomicReplace returns 'deferred' there.

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

* fix(cli): keep update echo sanitizer at its call site (#11486)

Move the bounded stderr sanitizer back next to the update command so the shared stdio helper keeps its existing surface, and raise the hosted plan wait to the 10s bound the rest of its file uses so the full-suite gate no longer times out under load.

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

* fix(cli): raise hosted plan wait to the file's 10s bound (#11486)

* test(cli): describe the registry sink's real enforcement (#11486)

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

* fix(ci): stage the standalone archive layout in the runtime harness (#11486)

applyStandaloneUpdate rejects an extracted archive that lacks bin/qwen and
the bundled runtime, but the harness payload carried neither, so
npm run test:standalone:runtimes:ci died in node/clean before any scenario
ran. Stage the layout create-standalone-package.js ships - bin/qwen plus the
bun flavor's node/bin/node mirror shim - so the fixture matches a real
archive and the update scenarios exercise the gate instead of tripping it.

* fix(cli): gate standalone archives on cli-entry.js (#11486)

The pre-activation layout gate required the installer's launcher and runtime paths, but the repository's standalone-runtime harness repacks a runnable archive without them, so npm run test:standalone:runtimes:ci died in node/clean and the required Test job stayed red. The harness lives in maintainer-only scripts/ territory, so relax the updater's archive gate to the documented v0.18.1 floor: lib/cli-entry.js. smokeTest still runs the bundled runtime, lib/cli.js and lib/cli-entry.js before activation, and isStandaloneInstallDir keeps using the full layout for classification.

Revert the harness edit rejected by the footprint gate, and update the design/user docs and the binless fixture test to the relaxed contract.

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

* fix(cli): make launcher-less standalone updates launchable (#11486)

The update gate accepts archives from the v0.18.1 floor without the
installer's bin/qwen launcher (the standalone-runtime harness repacks one
that way), but installation detection and the PATH wrapper still required
it. A successful update could therefore leave a tree that neither launched
nor classified as standalone.

Share one layout contract between the gate and detection: the CLI entry
point plus the bundled runtime. Detection still accepts the legacy
launcher-only layout. When a tree has no launcher, ensureBinWrapper writes
a wrapper that execs lib/cli-entry.js through the bundled interpreter and
refreshes a stale wrapper that targeted the removed launcher. Update the
design and user docs and cover the classifier, wrapper and refresh paths.

* fix(cli): resolve a restartable launcher for launcher-less standalone installs (#11486)

Accepting launcher-less install trees made the daemon's one-click update
arm a restart against a hardcoded bin/qwen that such a tree does not have:
activation succeeded, the restart threw ENOENT, and the update was
reported as failed. Resolve the PATH wrapper the updater maintains, which
lives outside the install and is refreshed when an archive ships no
launcher, and fall back to the archive's own launcher; offer manual
instructions when neither exists. Only a wrapper that names the install
may restart it.

Skip the launcher-less acceptance test on win32 like its siblings: its
fixture is Unix-shaped and a Windows host derives win32-x64, which
validateTarget rejects.

---------

Co-authored-by: 西岐 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: qwen-code-bot <[email protected]>
2026-10-31 13:42:54 +00:00
Shaojin Wen 94448d368d fix(verify-pr): keep the mutation matrix off the tree live cells measure on (#15999)
* fix(verify-pr): keep the mutation matrix off the tree live cells measure on

A local round interleaved its mutation matrix with 80 live daemon cells
on one tree. A build inside a mutation row propagated the mutant into
dist/, and the hand-patched repair still carried the mutant expression,
so every live cell and the whole matrix were withdrawn and the round
shipped as inconclusive.

Require a dedicated tree for the mutation matrix, gate every daemon or
dist-level harness launch on a scripted dist-versus-source anchor check
with a mismatch-reporting control, and forbid hand-patching dist to
repair a contaminated build. Pin the new rule in the workflow meta-test.

* test(verify-pr): pin the launch gate's coverage and control clauses

Review on 0d06209b2d showed the four rig-isolation pins left two
clauses of the launch gate unasserted: deleting "covers every PR
anchor" or the mismatch-reporting control still passed 4/4. Pin both
so a future edit cannot silently weaken the gate.

Also name the clean-dist launch gate in the mutation-runner pointer,
so the section that proves a mutant reached dist and the rule that
proves the measurement tree's dist is clean cross-reference each other.

* fix(verify-pr): define the launch gate's reference side and isolate per-tree artifacts

Review on 21014c30d6 (R1-1/R1-2/R1-3, RC1-RC5): the gate compared dist
against an unnamed source, so a self-consistent mutant build passed it;
"PR anchor" was undefined and collided with the mutation-anchor sense;
the control proved liveness, not refusal; the gate had no carve-out for
the producing row's own run; and tree isolation stopped at the checkout
while the workspace links, the shared sandbox tag, and the single Maven
repository stayed host-global.

Name the reference side as a sha256 manifest of each measurement arm's
clean dist recorded before the matrix starts, retire the undefined
anchor term, require the gate to refuse a known-mutant build, exempt the
producing row's own launches, and extend isolation to node_modules, the
sandbox image tag, and the Maven repository. Replace the fragment pins
with the whole bullet pinned inside its section plus the whole pointer
line; a 17-row mutation acceptance goes red on every row, and a
real-file M8 run turned the suite red before a byte-identical restore.

* fix(verify-pr): qualify the matrix-tree copies and name both sandbox levers

Review on ff867ec035: the cp -al recipe dropped the per-package
qualifier the install strategy already requires (root marked 17.0.1
vs packages/core marked 15.0.12 shadow for real), hardlinks share
inodes so a writing row truncates the round's copy, the sandbox clause
named only the launch-side variable while the build reads
QWEN_SANDBOX_IMAGE_TAG and --sandbox-image outranks the export, and the
pin's section slice failed open when the closing heading was renamed.

Carry the qualifier and the never-symlink rule, require real copies
from the store for rows that write under node_modules, name the build
lever and the flag precedence, and assert the slice bounds. The
acceptance matrix grows to 26 rows, all red on mutation including both
heading renames; a real-file rename run turned the suite red before a
byte-identical restore.

* fix(verify-pr): scope the sandbox image per launch and bound the pin slice by the next heading

Review on b99f52a5e3: <tree> sat where a Docker tag must go and the
export's value was never spelled out; a shell-wide export leaks the
matrix image into every later cell; the two cp -al copies disagreed on
when hardlinks are safe (the base build's postinstall step writes under
node_modules); the slice bound was a named heading, so an inserted
section could shelter a moved bullet; and the prose named a build
variable no test pinned at its read site.

Spell a sanitized label and the full image reference, inject the
variable per launch instead of exporting it, carry the write caveat in
both copies with an explicit copy import method (pnpm's auto falls back
to hardlinks without clones), bound the slice on the next heading, and
pin process.env.QWEN_SANDBOX_IMAGE_TAG in build_sandbox.js. The
acceptance matrix grows to 30 rows, all red on mutation; real-file
heading-insertion and variable-rename runs each turned the suite red
before byte-identical restores. Merged main (#15988, #16002); the suite
now runs 197 tests, all green.
2026-10-31 21:08:39 +08: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
Shaojin Wen 35daec35ea fix(verify-pr): keep the verdict line a plain paragraph and check the comment footer (#16002)
* fix(verify-pr): keep the verdict line a plain paragraph and check the comment footer

Three verification rounds on PR #15984 shipped their opening verdict as a
single Markdown heading: the round title and the whole ~950-character
verdict paragraph shared one `#` line, which GitHub renders as one giant
H1 — a wall of bold text instead of a readable lead. All three comments
also went out without the attribution footer the publish contract
requires.

The skill only said "verdict line first" without pinning the Markdown
shape, and the consistency checker only checked that the verdict word
and head OID appear in the first ten lines, so both defects passed every
gate.

- The skill now pins the verdict line to a plain paragraph (a round
  title may lead it as plain text, never as a heading), with the
  measured #15984 example and a template.
- Local rounds assemble the exact posted body — report content plus the
  resolved footer — into comment.md, post that file, and read the
  comment back against it byte for byte; report.md stays footer-free by
  contract.
- The checker warns when the lead line is a heading carrying the
  verdict or an over-long heading (GFM indents of up to three spaces
  included), and when comment.md lacks a well-formed attribution
  footer. Warnings, not errors: a cosmetic defect must not make the CI
  publisher refuse an otherwise consistent verdict.
- Pin tests reproduce the shipped shape from the real round-3 comment,
  and the workflow meta-test pins the new skill text.

* fix(verify-pr): accept CRLF line endings in the comment-body checks

Review of #16002 reproduced a Windows-authored comment.md failing the
anchored footer regex: splitting only on \n left a stray \r on the
footer line, so a valid attribution was reported missing and the local
publish gate sent the artifact back for unnecessary repair. GitHub
renders CRLF and LF identically.

Normalize CRLF (and lone CR) to LF before the line-based checks on
report.md and comment.md, and pin LF/CRLF parity: the same valid footer
passes under both endings, a missing footer and a heading lead still
warn under CRLF, and the reviewer's CLI probe is covered end to end.
2026-10-31 11:02:46 +00:00
Shaojin Wen d811674c55 fix(verify-pr): keep each verify round in its own comment (#15988)
* fix(verify-pr): keep each verify round in its own comment

Verify jobs are serialized per PR, but their publishers queue on hosted
runners, so a round's live status is often still "running" when the
next round's verify job starts. The status step took that comment over
and both publishers then wrote into it, so one round's report replaced
the other's. A cancelled pending run's notice could likewise replace
another round's live status.

The status comment now carries a run marker. The status step reuses a
live status only when it is this run's own (a re-run attempt), and the
publisher writes only into that status, posting fresh otherwise.
WEAK_BODY no longer decides anything and is removed.

Also, from the #15966 follow-ups: evidence images are collected from
the round the comment publishes instead of from every round, oldest
first, and the pre-agent sweep comment no longer describes the
head -1 ordering.

* fix(verify-pr): let a re-run publisher find its own comment

The publisher looked only for this run's live status, but the body it
publishes carries neither the running marker nor the run marker. A
publish job re-run after it had posted therefore found nothing and
posted the report a second time. Every terminal body now keeps the run
marker right after the leading verify marker, and the publisher matches
its own run's comment whether it is still the live status or already
the report. The status step still reuses only a live status.

Tests now pin the status body's leading markers in order, use a re-run
attempt number distinct from the run id, check the run marker in every
terminal body, and check that the evidence skip count comes from the
published round.

* fix(verify-pr): never let a notice replace a published report

A publish job re-run on its own downloads its own attempt's artifact,
which the verify job never uploaded, so it always renders "results
unavailable". Since the previous commit matched this run's comment
whatever it held, that notice replaced the round's report. The lookup
now also reports whether this run's comment carries findings, and a
body without findings is posted beside it instead. A notice may still
replace this run's live status or its earlier notice.

Every body now opens with a header written once, before the branches:
the verify marker, the run marker and, when the body carries findings,
the substantive marker, ended by a blank line. The build-failure body's
substantive marker moves up into it. Both lookups that choose a comment
to write into read markers only from the header, so a marker quoted
further down a body is text.

Tests pin the notice/report gate in both directions, the header of every
terminal body and that it is written once, header-only matching in both
lookups, and that the status step does not reuse this run's report.

* fix(verify-pr): read the substantive marker from the header for the snapshot

The previous-round snapshot was the last lookup still matching its
marker anywhere in a body, so a newer notice quoting the substantive
marker below its header would have been snapshotted in place of the
newest real report. It now reads the header like the other lookups.
Build-failure reports posted before the header change carry the marker
below their header and are skipped by the snapshot during the
transition.

Tests add that notice shape to the snapshot case, and render the
reportless body to check it carries no substantive marker.
2026-10-31 18:56:36 +08:00
jinyeandQwen-Coder 4cd3372bb6 fix(managed-agent): close post-split verification gaps (#15927)
* test(managed-agent): close post-split verification gaps

Establish an active response before probing caller cancellation, retain close deadlines, run both independent embedding hosts from the full MySQL lane, and record post-merge acceptance boundaries.

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

* ci(managed-agent): keep embedding checks within workflow size gate

Move the existing embedding-host verification commands unchanged into a shell script so sdk-java.yml stays within the existing growth allowance. Extend the workflow guards to inspect the script and retain the test and cache-lock requirements.

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

* fix(cli): finish hosted settle budget call migration

Replace the 25 remaining references to the removed settle timeout constant with the existing per-test budget function. This repairs the TypeScript compilation failure exposed by the main merge while preserving the new shared deadline policy.

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

* fix(managed-agent): preserve Hosted fault checks after module split

Preserve the typed stream-idle failure and its duration across the SDK boundary. Update fault-injection fixtures for wrapped connectors and the verification module directory, and wait for the documented pending Items projection within the existing bounded poll.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 18:31:00 +08:00
1886aa7221 test(ci): pin the supersede kill-dir prefix to the workflow's (#15963)
The supersede kill dir is minted under RUNNER_TEMP with a fixed prefix and
never exported, so the replay's forge stub can only find it by listing that
prefix. The literal was written twice — once in the workflow, once in the
stub glob — and renaming one side alone left the stub globbing nothing: the
forged kill record never landed and the refusal replay still passed without
exercising the refusal.

Derive the production prefix from the workflow text at module scope and pin
the stub glob to it, so a one-sided rename is red instead of vacuous green.
The stub case line now consumes that shared fixture string.


Patrol-Run: qwen-issue-patrol/jmvvrdn39qo

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 18:00:45 +08:00
Shaojin Wen c262f8e767 test(managed-agent): gate the B2 attempt/stream identities and live SSE view (#15993)
* test(managed-agent): gate the B2 attempt/stream identities and live SSE view

The model-round arm already proved reissue parity, retraction ordering and
admission across two replacements, but not the B2 matrix row's identity and
live-view observations: a recovery reusing a dead generation's attempt or
stream identity, or a control plane leaking orphaned prefixes to a connected
client, would pass CI.

Extend the existing harness-only model-round arm (no new CI step, no serve
hook, no production change) with:

- Attempt identities: three distinct journal attemptIds, one new attempt per
  streaming generation, checked before the shared audit so a reused id (which
  collides the journal command ids and dedups later attempts) is named as the
  root cause, not the downstream text leak.
- Stream identities: the public identity model verified on the real stack is
  a stable per-Turn assistant item_id plus a content Part that a delta
  continues only while the previous delta keeps its identity; retraction
  blanks the dead deltas' identities, so each replacement must open a new
  Part. Assert pairwise distinct Parts across the two dead prefixes (live
  capture) and the surviving answer (reload), one shared assistant item, and
  blanked prefix deltas in reload AND replay.
- Live SSE view: one runner SSE client connects before the first prefix and
  stays open across both Harness crashes (the stream is control-plane-owned);
  it must observe both prefixes, both retraction announcements and exactly
  one terminal, and its converged view (deltas after the last retraction, per
  the payload-less stream.reconciled reload contract) must equal the REST
  reload and a Last-Event-ID: 0 replay. Per-phase liveness guards keep every
  observation non-vacuous.

Real-stack (.75) evidence: head green three times, including the final runner;
M1 (deterministic attemptId) killed at the named attempt-identity assertion;
M2 (retractOriginal short-circuit) killed at the B2 retraction-ordering
observation with both dead prefixes leaking; M3 (constant messageId) red at an
upstream publication barrier, recorded honestly as an upstream-guard
observation rather than a B2-specific witness. The SSE parser, identity
readers and gates carry behavioural tests and contract pins; a self-audit
fix aborts pending SSE captures in the main finally so an early failure exits
promptly.

* test(managed-agent): harden the B2 arm per review (R1-1..R1-10)

R1-1 (Critical): the teardown abort rejected the never-awaited live-capture
promise, so an early arm failure died mid-cleanup as an unhandled AbortError.
Mark the capture handled at creation (the audit's await still sees the real
rejection), rewrap abort/budget errors as B2 live SSE, and abort observers in
the main finally. Behavioural abort-semantics test plus a source pin.

R1-2 (Critical): "exactly one terminal" could never fail — the capture returned
on the first terminal frame. The terminal is now a progress signal: a bounded
drain window (drainMs, default 2s) keeps reading, and the audit counts terminals
of the original turn over the whole capture. Behavioural double-terminal test;
deleting the drain reds it (verified).

R1-3/R1-4: every identity group must actually carry the shared assistant item
(a null-item generation no longer passes via the union), and the replay matches
the surviving answer's content Part — the stream identity — not just the
per-Turn-constant item_id.

R1-5: drive the shared SseObserver (content-type, id===sequence, event===type
wire assertions) instead of a third private parse loop; the split-frame witness
now exercises the shared observer directly.

R1-6: lift the view audit into top-level assertB2StreamViews so behavioural
tests execute every clause (double terminal, shared Part, missing item,
blanking, replay Part, converged text); deleting the item check reds the
missing-item case (verified).

R1-7/R1-8: one shared readJournalManagedEvents HEX decode for all four journal
readers; one shared textDeltas selector (deltaSequences keeps its empty-match
throw, b2DeltaIdentity tolerates it).

R1-9/R1-10: design docs (both languages) name the shipped helpers/signatures;
evidence goes through the shared writeEvidenceFile into an explicitly named
model-round-<epoch> directory, never a borrowed packaged-undefined one.

Real stack: .75 head green with the refactored runner; M1 re-run confirms the
early-failure path now reports the B2 attempt-identity error and completes
teardown. Contract suites 138/138, typecheck and lint green.

* test(managed-agent): audit the B2 live answer identity and replay terminal type

F5 (P2): assertB2StreamViews extracted the live answer's identity but never
checked it — a live stream delivering the surviving answer under a dead
generation's Part, with no identity, or under a foreign item passed as long as
the persisted views were correct. The live answer must now carry an identity
equal to the reload answer's item AND Part. Three live-only counterexamples
(dead Part, null identity, foreign item) plus a verified delete-control.

F6 (P2): the replay branch counted terminals but never checked the type — a
turn.failed/turn.cancelled replay with the same turn id, text and identities
passed, so a reconnecting client could be shown a failure while live/reload
show success. The replay terminal must now be turn.completed; two
counterexamples plus a verified delete-control.

F7: the design acceptance criterion claimed a named mutation red for every new
signal while the same doc records M3 as an upstream-guard observation. Both
languages now name the evidence per signal (M1 named, M2 joint witnessing, M3
upstream-guard) and list the view-audit clauses with their counterexamples.

Adversarial completeness: five more counterexamples (live failure terminal,
missing earlier prefix, single retraction, misordered retraction, unblanked
replay prefix) and readPublicSse now wraps every failure path (observer
failure, abort, incomplete capture) with a B2-prefixed error.

Verified: the four F5/F6 counterexamples fail against the pre-fix helper and
pass after; deleting either new clause reds its cases. Contract suites 138/138,
typecheck/lint/Prettier green, .75 model-round head green with the new runner.

* test(managed-agent): pin every B2 audit clause and de-flake the SSE wait stub

The CI Test lane reddened on the readPublicSse behavioural test: the injected
waitUntil stub budgeted iterations while the drain predicate reads the wall
clock, so a fast runner exhausted 10k setImmediate turns before the 100ms
drain window ended (3/3 retries, same signature). The stub now budgets wall
time like the real waitUntil; the drain delete-control still reds the named
double-terminal case.

Close the verification report's S1 coverage gaps: the 11 audit clauses that
survived single-clause deletion (converged live/replay text, first retraction
ordering, shared-item across prefix groups, second reload prefix blanking,
replay terminal count, missing replay prefix frames, replay answer item, and
the observer-failure / incomplete-capture catch branches) now have permanent
behavioural counterexamples, each verified to red when its named clause is
deleted. Both design languages distinguish load-bearing clauses (delete-
control verified) from redundant-defence clauses (sibling-covered by design).
2026-10-31 09:56:45 +00:00
8ca595c687 fix(vscode): gate the companion vitest worker cap on the shared pool (#15940)
* fix(vscode): gate the companion vitest worker cap on the shared pool

packages/vscode-ide-companion was the one guarded config that raised its
test ceilings for the ECS pool without touching worker count. Every
sibling (core, cli) pairs the timeout ternary with a maxWorkers one, so a
25%-of-cores vitest run on a host carrying several concurrent jobs was
the only thing keeping this workspace from adding to the oversubscription
it was already widening its timeouts for.

Add the same ternary core carries — '25%' on `ecs-qwen-*`, undefined
otherwise — so the cap exists only where the contention does.

scripts/tests/unit-vitest-configs.test.ts pins it from both sides in the
autofix gate clamps case: the ECS arm now reads the companion config
alongside core/cli and expects the gate's own --maxWorkers value, and a
second, separately re-imported arm with RUNNER_NAME unset expects
`maxWorkers` to be undefined. The off-pool arm is what makes the pin
load-bearing: a flattened `maxWorkers: '25%'` passes every pool-lane
assertion, and only the second import round sees it (mutation-checked).

Refs #10695

Co-authored-by: Qwen-Coder<[email protected]>
Patrol-Run: qwen-issue-patrol/jmvva8cvtq7

* test(vscode): correct the config-clamp comments the reviewer disproved

The reviewer was right on both statements I added: the autofix gate does not
launch vscode-ide-companion, and normal CI does not run this workspace
unlimited. run-unit-test-lanes.sh exports VITEST_MAX_THREADS (halved for the
side lane, 2 where unset) and vitest resolves
poolOptions.threads.maxThreads before config.maxWorkers, so the config value
is a default for runs carrying no such env, not the lane ceiling.

Comments only; the clamp and its pins are unchanged.

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

* test(vscode): move the companion worker-cap pin to the bundle-guard block

The review disproved the clamp-array coupling: the gate passes its
clamps on the command line for whatever changed packages it launches,
so the companion config does not need to track that array. The
bundle-guard block already re-imports this config per runner name;
extend it with the maxWorkers expectation (ECS '25%', foreign and
unset undefined) instead of a second stub/reset/import round in the
clamp test. The unset arm keeps the stronger mutation coverage.

Clamp block restored to its pre-PR membership and comment.

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 17:50:28 +08:00
ca7d487ce9 ci: route Lint & Static to ECS for every pull request (#15974)
* ci: route Lint & Static to ECS for every pull request

* ci: isolate HOME and complete routing pins for lint_and_static

- lint_and_static admits untrusted forks to the shared pool, so pin a
  job-level HOME/USERPROFILE under runner.temp and create it before the
  first write (git safe.directory, npm config).
- Derive the classifier output from each job's own runs-on in the ceiling
  helper, so a timeout pointed at the wrong output throws instead of
  being substituted; parameterise runnerOn the same way.
- Pin test's own runs-on to ubuntu_runner and add the HOME-isolation
  guard for every lane routed past the association gate.
- Correct the qwen-autofix premise and the integration_no_ak comment,
  and record the routing decision in a bilingual design note.

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

* fix(ci): isolate lint lane HOME and restore routing guard strength

Resolve R1-1 (Critical): per-job HOME for lint_and_static so untrusted
fork code cannot plant git/npm config a later trusted job inherits;
shared warm caches re-pointed via getent + XDG_DATA_HOME/COREPACK_HOME.
Resolve R1-2: timeout ceilings derive the output name from each job's
own runs-on. Resolve R1-3: explicit ubuntu_runner pin for test.

* docs(ci): record lint ECS fork-routing design and correct stale premises

Resolve R1-5: bilingual design record (container tradeoff, acceptance,
residual risks), qwen-autofix.md section 38 premise, no_ak scoping comment.

* fix(ci): publish run-unique isolated HOME from step env, not job env

Resolve P1/R1-1: jobs.<job_id>.env cannot read the runner context, so the
workflow failed GitHub validation entirely; publish HOME/USERPROFILE via
GITHUB_ENV from the first step instead. Resolve R2-1: RUNNER_TEMP survives
across jobs on this pool, so the path is run-unique, cleared before create,
and reclaimed by an always() step before the failure collector. Resolve
R2-3: share content-verified linter/corepack caches via RUNNER_TOOL_CACHE.
Resolve R2-5: move the HOME guard into the PR-gated routing battery with a
single-lane set pin; resolve wenshao R2-4 with one fleet ladder for both
outputs. Witness: actionlint 1.7.12 exits clean on ci.yml; six mutations
each red the matching guard.

* docs(ci): correct lint routing record per round-2 review

Supersede link points at qwen-autofix.md section 38 (R2-4); read-only token
and HOME-relative write class stated precisely (R2-2); residual risks are
examples not a census, naming web_shell_e2e_smoke and other PAT pool jobs
(wenshao R2-3); association gate recorded as throughput policy, not an
enforceable boundary (R2-6); dispatch-over-fleet priority and per-lane
hosted ceilings (P3); cache cost list and PR-gated test placement updated.

* chore(ci): bump ci.yml size ratchet for merged tmpdir routing and HOME isolation

Growth is real: #15979's helper-test tmpdir routing arrived via the main
merge the freshness gate required, plus this PR's run-unique HOME
mechanism. 156377 -> 161281 bytes.

* fix(ci): keep COREPACK_HOME per-run; shared cache is a poisoning channel

Corepack does not re-verify extracted entries on cache hit (reproduced
locally on 0.36.0 with the repo's pnpm pin: tampered entry executes with
the recorded hash unchanged, network disabled). Move COREPACK_HOME inside
the run-unique HOME; XDG_CACHE_HOME stays shared because lint.js
SHA-256-verifies linter archives on every hit. Guard and bilingual record
updated; shared-corepack mutation reds the guard.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-31 16:35:58 +08:00
11b40836d3 fix(ci): exempt non-TUI packages from the parity impact gate (#15937)
* fix(ci): exempt non-TUI packages from the parity impact gate

* fix(ci): exempt the manifest-less SDK trees from the parity gate

The exclusion list was audited from workspace manifests, so
packages/sdk-java, packages/sdk-python and packages/zed-extension — none of
which carries a package.json — stayed outside it and still selected the
component lane for a PR touching only them.

Add the three trees to the exclusion, pin each with a single-file case in the
classifier contract suite, and record why they are hand-listed in the design
doc pair.

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

* fix(ci): name the policy exclusion and cover the CUA driver tree

The exclusion branch skipped every matched path before recording a
reason, so a PR excluded on policy reported the same "No TUI impact"
as a PR whose paths the classifier never mapped. Record the reason once
inside that branch (R1-2).

packages/cua-driver is outside the --cli-only closure: node-repl
declares @qwen-code/cua-sdk, but the build filter pulls node-repl
without its dependencies and no TUI suite imports the SDK. Exempt it
with a single-file row and a rename row (R2-1).

The workspace-excluded trees were a third hand-maintained copy of the
set pnpm-workspace.yaml states, with nothing reconciling them. A new
case reads pnpm-workspace.yaml and asserts every excluded tree still
classifies as no-impact (R2-2).

The design docs claimed the excluded packages are never built by these
gates. npm run bundle compiles integrations/external-context into
dist/mem0 through hard-coded esbuild entries, so say what actually
happens and name esbuild entries and copy_bundle_assets copies as
inputs the manifest audit cannot see (R2-3).

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

* test(ci): reconcile workspace negations by value, not literal spelling

* test(ci): pin the policy reason for every excluded workspace tree

R4-1: the reconciliation test only asserted outputs.component, the value the
classifier starts with, so narrowing reasons.add(...) for any tree except
cua-driver silently reverted the human-facing diagnostic to "No TUI impact"
while all 163 tests stayed green.

- assert outputs.reason inside the loop, not only for the CUA driver tree
- extract excludedTrees(yamlText) and feed it a fixture with a trailing-comment
  negation and an !integrations/... negation, so the by-value derivation is
  exercised for the spellings its comment claims

Witness (both mutants, unmutated suite 163/163 green):
- reasons.add narrowed to packages/cua-driver/: reason assertion red
- derivation reverted to the old literal regex: fixture assertion red

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

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 15:49:57 +08:00
易良andQwen-Coder cd4c917fcf fix(ci): route helper-test tmpdir off the host /tmp tmpfs (#15979)
* fix(ci): route helper-test tmpdir off the host /tmp tmpfs

* test(ci): pin the helper-battery tmpdir routing against comments

Two gaps in the #15979 change:

- The pin used toContain/indexOf on the raw run block, so prefixing the
  export with `# ` disabled the routing while the guard stayed green.
  Filter comment lines before asserting, like the lint-history pin already
  does, and pin the private dir + EXIT trap the step now creates.
- The step routed TMPDIR straight into the shared RUNNER_TEMP root. Copy
  the unit lanes' second half too: mktemp -d under RUNNER_TEMP and reap it
  on EXIT, so a cancelled battery does not leave its fixture tree behind
  (its own after() hook does not run on a signal).

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 15:35:03 +08:00
Shaojin Wen bc8ad53f95 test(managed-agent): gate the B3 round-and-cost-budget crash window (#15981)
* test(managed-agent): gate the B3 round-and-cost-budget crash window

Matrix row B3 ("after earlier tools") had focused/integration evidence
only: nothing crashed a replacement after several tool rounds and proved
the numeric round counter and the turn cost ledger survive it. A recovery
that restarts the round at 0 or zeroes the token ledger would pass CI.

Package the W2 window in hosted-failover-e2e: a multi-round fake fixture
runs two earlier tools (each its own approval, execution and file effect),
the replacement is crashed while the later post-tool request is held on
the fake-model side (no new serve hook), and the next generation must,
from MySQL alone after scrashing the crashed disk, resume the saved
logical round, restore the TurnBudget snapshot and per-model token ledger
(asserted as per-model output-token totals with non-vacuity guards, since
an unrestored ledger is a truthy {}), reissue the saved request deep-equal,
retract the published prefix, and project one terminal. "Cost budget" here
is the snapshot plus the ledger, not a cumulative spend gate.

Real-stack (.75) evidence: head green twice (round 2->2, budget 500000
deep-equal, ledger 600->700 = restored 600 + final round 100, parity equal,
both earlier Actions decided under their original IDs); three named
mutation kills (round reset, ledger wipe, snapshot re-baselining). The
execution-count assertion is defense-in-depth in this window: three
mutation cells survived because exactly-once rides three independent
guards (replay routing, continuation settled-batch adoption, Runtime
runtimeCallId idempotency), documented in the design.

No production behavior change; failover ceiling 26 -> 27 arms (285 min).

* test(managed-agent): harden the B3 arm per review (R1-1..R1-4)

R1-1: the W2 rationale claimed the saved request carries the budget; it
carries only the round (budget lives on the attempt's route/usage
resources). Corrected in both design languages.

R1-2: the behavioural fixture could not distinguish a newest-first from
an oldest-first budget/models scan, and the ledger assertion had only
text pins. Add a discriminating fixture (older attempt with a different
budget and smaller ledger) and lift the cost block into a top-level
assertB3CostContinuity helper with injected-state behavioural cases:
healthy growth passes; null-budget snapshot, missing snapshot,
re-baselined snapshot, empty before-ledger, zeroed after-ledger and
shrunk per-model totals all throw.

R1-3: the budget non-vacuity guard tested the snapshot object, but a
non-null snapshot with budget: null is a legal production state (lost
+Nk directive), so null == null passed vacuously. Guard the numeric
field instead.

R1-4: site-anchor the B3 post-tool request count (? 3) like the
neighbouring R1-7/F1 pins so a conflict resolution back to the pre-PR
ternary reds at unit speed, not only in the real-stack lane.

Each fix has a demonstrated witness: oldest-first scan, deleted ? 3
clause, object-truthy guard and candidates->prompt repath each red the
new cases/pins; all restored. Contract suites 132/132, typecheck green,
and the .75 head arm re-run with the hardened runner is green (round
2->2, budget 500000 deep-equal, ledger 600->700).
2026-10-31 05:36:12 +00:00
Shaojin Wen f87fd6a037 test(ci): pin the setup steps the Broker fault-gate condition relies on (#15964)
The fault-gate step runs when steps.managed_dependencies succeeded,
which stands in for "the CLI bundle exists" only because the install
step runs after every step before it succeeded. A step skipped by its
own if:, or one whose failure continue-on-error absorbs, fails nothing,
so either on the bundle step would let the install run and the gates
spawn a missing dist/cli.js. The pin only checked the install step's
own if:, and both shapes passed it.

Pin that no step up to the install carries if: or continue-on-error,
and correct the count in both comments: the verify failed on 10 of the
14 nights to 10-27; the 11th skipped night was a cancelled run, which
!cancelled() still skips.
2026-10-31 05:20:55 +00:00
Shaojin Wen 32245f1d5b test(managed-agent): anchor the A3 original-plan count pin and correct evidence prose (#15977)
Post-merge follow-up to #15945. The fast-tier contract suite anchored only the
expectedExecutions count ternary; the bare `? 2 : 1` also matches
expectedOriginalPlanCalls, so narrowing that site (2 -> 1) stayed green (F1).
Add a tightly anchored pin (whitespace-only gap, so it cannot span to the
expectedExecutions ternary) and demonstrate the 2 -> 1 mutation reds it.

Correct the A3 design docs (EN + zh-CN) to match the recorded evidence: the
R1-4/R1-5 decided-sibling and settled_at checks are proven load-bearing by
fast-tier contract-pin reds, while the real-stack witnesses red at earlier
recovery guards (load cancelled/expired guard; adoption digest conflict), so
they are defense in depth. Drop the claim that a hook matching/non-matching
behavioral unit test exists (it is deferred), and state that the captured plan
order is request order, not necessarily the model's part order.
2026-10-31 11:56:50 +08:00
Shaojin Wen 674d751bcd fix(verify-pr): publish the newest round and correct two skill claims (#15966)
Follow-up to #15938.

The publisher took the first round directory in sort order. Rounds are
named pr<n>-verify-<YYYYMMDD>-<HHMMSS>, so that was the oldest one: with
an earlier complete round left beside the newest, the earlier report,
counts and verdict headlined the run, and under the new findings rule an
old zero-failure round would publish as passed. Take the last one
instead, for the report and for a reportless run's count line, which now
also keeps to depth 2.

The skill's harness-fault rule said CI refuses findings over failures
that name no PR cause. The checker only refuses that when a failures
ledger is present; without one it warns, and the round still publishes
as not passed. The headline rule now says it applies to findings, since
blocked stays not passed at any count.

Tests cover two complete rounds, a word outside the four (blanked, with
the warning, and never echoed), a reportless run's counts, and the trust
switch in the vocabulary pin; the unused VERDICT_TXT hook is gone.
2026-10-31 11:43:55 +08:00
易良 46d825d115 Revert "fix(ci): isolate web-shell smoke from shared runner networks (#15886)" (#15961)
This reverts commit cc6de38cb5.
2026-10-31 02:36:35 +00:00
Shaojin Wen b3e9ce0cd1 test(managed-agent): gate the A3 mixed-sibling approval crash window (#15945)
* test(managed-agent): gate the A3 mixed-sibling approval crash window

Package the native half of recovery-matrix row A3 (#14481) as a
hosted-failover-e2e arm: crash a replacement inside a two-call approval
batch (sibling[0] decided, sibling[1] still requested) and prove the
surviving siblings recover from MySQL alone — earlier decisions persist,
the pending sibling keeps its original Action/deadline and earns its own
Allow, and both calls settle once each in their original order.

The rewritten-input / Hook-no-rerun half stays a commandStep follow-up
because a native continuation refuses Hook/MCP sessions.

* chore: re-trigger CI after the main merge

The web 'Update branch' merge incorporated main's .prettierignore (lint gate
freshness) but did not trigger a CI run on the updated head; this empty commit
re-triggers the lanes on the merged tree.

* test(managed-agent): harden the A3 arm per review (R1-1..R1-7)

Address the seven inline review suggestions on the mixed-sibling approval arm:
read the decided sibling and require it still decided with its original deadline
before answering the pending one (R1-4); gate execution settle order on settled_at
along the plan (R1-5); add an ascending-link behavioural case so a first-match
plan-link selection reds (R1-6); anchor the execution-count pin and pin the
per-call identity loop (R1-7). Correct the design docs in both languages: the
fixture is two write_file calls, the validation plan lists the reds actually
produced, and the arm proves matrix clauses 1-2 plus a narrower pending-sibling
observation, not the rewritten-input clause 3 (R1-1/R1-2/R1-3).
2026-10-31 10:25:56 +08:00
Shaojin Wen 10b48ab972 fix(verify-pr): run the artifact check in the CI publisher (#15938)
* fix(verify-pr): run the artifact check in the CI publisher

The CI publisher validated only the assertion count triple. The failure
ledger and the per-commit mutation claim were checked by
verify-pr-check.mjs, which local rounds run before posting and CI never
did, so the sandboxed lane could publish shapes every local round refuses.
The publisher now runs that checker from its trusted checkout; a refusal,
or a checker that cannot run, leaves the agent verdict untrusted and the
comment says why.

A trusted findings verdict with no failed assertion is now published as
"passed with findings" instead of "not passed". Over the last 30 days, 94
of 150 published findings reports had zero failed assertions, and the
reports among the latest 18 run artifacts that hit this case all
described their findings as non-blocking. The skill now states the
counting rule that headline relies on: a defect reproduced at head is a
failed assertion attributed to the PR, not an expected-red pass.

* fix(verify-pr): bind the headline to the checked report directory

The report and the assertion counts were picked by two independent
finds, so a run that left an earlier round directory holding only
assertions.json could headline a later report with that directory's
zero: "passed with findings" over a report whose own counts carry a
PR-attributed failure. The counts now come from the report's own
directory when there is a report, and the checker must read the same
verdict the run reported beside that report; a mismatch is a refusal.

A test now runs the checker through its relative production path and
pins the checkout layout that path relies on, since every other case
injects an absolute path.

The comments added to the triage workflow are trimmed, and its size
baseline moves to 370027 bytes: the remaining growth is the checker
wiring and its bilingual explanation in the comment body, which the
job has no sibling script or document to hold.

* fix(verify-pr): read only the checker summary and say which check failed

The publisher matched the reported verdict anywhere in the checker log,
and the warnings above the summary can spell out an agent-chosen
evidence filename, so it now reads only the last stdout line. Node's own
errors go to a separate stream that the publisher prints when the
checker cannot run. Warnings are still never printed, and a test now
pins that.

The untrusted-verdict paragraph pointed every state at "errors in the
run log", which two of the three states never print. A refusal still
points at its errors; a verdict that differs from the one beside the
report, and a checker that could not run, each get a sentence of their
own.

The publisher checks out scripts/lib/ as a directory instead of naming
one helper. The production-path test runs the checker from exactly the
paths that checkout fetches, so an import outside them fails the suite
whatever form it takes. That replaces the import-list check, which
could not see side-effect imports.

The local-round design record no longer says the checker merely mirrors
the publisher, in either language. The size baseline moves to the
file's new size, 370588 bytes.

* fix(verify-pr): read the verdict from the report's own directory

The verify job picks the agent verdict by its own search, which can land
on a leftover round directory holding only verdict.txt while the report
comes from a later, complete one. The previous fix flagged that as a
mismatch, blaming a verdict file that was consistent with its own
report. The publisher now reads the verdict from the report's directory
itself, with the same four-word allowlist, so the report, the counts
and the verdict always come from one round, and the mismatch state is
gone.

The vocabulary pin now covers both places the step spells out the four
words: the allowlist and the headline labels. The Chinese note for a
checker that cannot run is asserted too.

The design record no longer promises that every checker rule change
reaches CI in the same commit, since the publish step still validates
the count triple and refuses merge-ready over failures on its own.
Decision 4 now rests on the reason that still holds: legitimate rounds
can lack a screenshot or a cited head. Both languages changed together.

* fix(verify-pr): read verdict.txt once, beside the published report

The verify job read its own copy of verdict.txt from whichever round
directory its search found first, emitted it as agent_verdict, and wrote
it to its step summary, while the publisher read the word again from the
report's directory. With a leftover round directory the two could name
different verdicts in the same run. Drop the verify job's reader and its
output so the publisher is the only reader, and:

- warn when the report's directory has no usable verdict.txt instead of
  blanking the word silently;
- bound the report search to depth 2 so a report.md nested in a round
  cannot stand in for the round's own;
- drop the unreachable "report.md missing" warning.

Tests pin the single reader, the report-only round (no borrowed counts
or word), the nested report, and blocked staying not-passed over an
all-green ledger. The skill's harness-fault rule now states what CI
actually publishes for findings over non-PR failures.
2026-10-31 10:14:55 +08:00
Shaojin Wen e7fe14ee83 docs(managed-agent): generate the server configuration reference from its metadata (#15939)
* docs(managed-agent): generate the server configuration reference from its metadata

The server has about 180 settings, 84 of them mapped to QWEN_MANAGED_AGENT_*
variables, and no list of them with their variables, defaults and limits:
operators read a 2000-line developer README for them.

- The server module runs spring-boot-configuration-processor once more,
  over ManagedAgentProperties only (the two Druid beans share a prefix,
  which the processor refuses), and writes the configuration metadata.
  The compiled classes are unchanged. runtime-broker.ssh is marked
  @NestedConfigurationProperty, and STRICT_HARNESS_KEYS @Getter(NONE), so
  the metadata lists the one and not the other.
- configuration-descriptions.json holds an English and a Chinese
  description for each setting and section.
- scripts/generate-managed-agent-configuration-reference.mjs writes
  docs/users/managed-agent-configuration-reference{,.zh-CN}.md from the
  metadata, the packaged application.yml (variables and defaults), the
  ${qwen.managed-agent.*} placeholders in the main sources (settings read
  outside the properties class) and the descriptions. --check fails on a
  setting without a description, a description of no setting, or a stale
  page; the reference workflow runs it after the Java build.
- The archive ships the pages as docs/configuration{,.zh-CN}.md, and the
  deployment guide and the README link them.

* fix(managed-agent): link the shipped documents by their archive names

The archive renames each document it ships, so a link written against
the repository, such as the configuration reference's language switch or
the guide's link to it, named a file the archive does not have. The
packager now points every link between shipped documents at the
document's name in the archive and leaves every other link as written.

* build(managed-agent): take the configuration processor's version from the BOM

spring-boot.version is defined nowhere in this build, so the processor
path's <version> only resolved because the compiler plugin falls back to
dependency management. Say so by naming no version.

* test(managed-agent): name no tracked Markdown in a link fixture

classify-profile.test.mjs requires every Markdown file a test names to be
in TEST_READ_MARKDOWN, and the kept-link fixture's docs/runtime-broker.md
matched packages/managed-agent-server/docs/runtime-broker.md, which no
test reads. The fixture only needs a link to a file the archive does not
ship, so it names one that does not exist.
2026-10-31 03:23:30 +08:00
Shaojin Wen 0c1b42fa31 test(managed-agent): gate the B3a finished-before-commit crash window (#15881)
* test(managed-agent): gate the B3a finished-before-commit crash window

Package the recovery-matrix B3a row: crash the replacement Harness after its post-tool main stream records the durable Finished marker but before the assistant output commits. A test-gated hook (QWEN_E2E_CRASH_BEFORE_ASSISTANT_COMMIT, set only on the crashing generation, never in production) holds the turn in that window; the runner asserts the barrier landed, kills the generation, and requires the next one to reissue the saved request deep-equal and project one terminal with the recovered answer rather than compensate an already-committed output. Adds the failover arm, CI step and ceiling (25 arms), contract pins, the serve env-guard allowance, and bilingual design/README updates.

* test(managed-agent): harden the B3a arm barrier, hold and contract pins

Address review R1-1..R1-4: assert the durable crash precondition (the approved execution SETTLED) from MySQL before the SIGKILL like the sibling crash arms, instead of trusting the hook log alone; wire the test-gated hold to the Turn abort signal so a leaked flag cannot park a real Session uncancellably; pin the crashing/recovery generation call sites and the parameter default rather than bare identifiers; and pin the combined deep-equal parity gate so reverting it to after-result-only reds the fast-tier contract suite.

* test(managed-agent): assert the B3a crash window from the durable journal

The finished-before-commit barrier proved only that the approved execution
settled (executionAtCrash=SETTLED) and that the hook printed its log line. It
did not read the post-tool main model attempt's durable Finished marker, so a
hold moved above the model layer's usage-only output_committed commit could
crash with no durable Finished marker and still pass every downstream check
(the recovery reader also accepts a saved request whose attempt is started).

Before the SIGKILL, read the same journal the recovery reads
(ma_session_journal_tx) and assert findHostedCommittedModelOutput's own
discriminator: the latest <promptId>:main: model.attempt is output_committed
and no assistant message.committed follows it. Retain the attempt identity and
sequence in approval-crash-barrier.json. Add a window-shaped fast-tier contract
pin for the new assertion, keeping the existing pins.

Verified on real MySQL/Spring/Broker: head green (attempt retained, parity
equal); moving the hold above output_committed reds at the new pre-crash
assertion; removing the hook still reds at the barrier timeout; moving the hook
after the assistant commit now reds at the new post-commit assertion.

* test(cli): cover the finished-before-commit hold's abort release

The QWEN_E2E_CRASH_BEFORE_ASSISTANT_COMMIT hold's abort wiring had no test that reds without it: the env-guard only counts AST reads, the meta-tests grep source text, and the packaged arm SIGKILLs the pending generation, so no lane exercised the release path. Arm the module-scope switch via a vi.hoisted env stub set before the static import under a dedicated Session id, then drive a real turn into the hold and assert the abort settles it cancelled with no assistant message.committed; a non-matching-id case pins that the hold never fires. Removing the release/addEventListener wiring makes the matching case time out. Test-only; the single env read and the hold's park-until-killed behaviour are unchanged.

* test(managed-agent): pin the B3a hold id-guard and window arming

R4-1: run the non-matching finished-before-commit hold case before the
abort case so the module-scope one-way latch is still unset, letting the
session-id comparison (not a spent latch) suppress the hold. Deleting the
id conjunct now reds the non-matching case.

R4-2: pin the finishedBeforeCommitWindow arming ternary condition, so
narrowing its guard can no longer silently delete the arm's only
durable-window check while the scripts suite stays green.

* test(managed-agent): exercise the B3a window helper on injected journal rows

Address the round-5 deferred suggestion: finishedBeforeCommitWindow had only source-spelling pins. Add a behavioural test that injects runMysql through the existing load() machinery and asserts the latest-attempt selection and both throw branches on journal rows (the output_committed window holds; a started or newer-started latest attempt reds before the Finished marker; a following assistant commit reds after it; role/prompt/sequence filters hold). Witness: mutating the latest-attempt index to mainAttempts[0] — a change the source pins survive — reds the behavioural test.
2026-10-30 18:15:45 +00:00
烽宁 4bd9ed0143 fix(cli): share hosted harness deadlines and bail after CI retries (#14752) 2026-10-30 23:54:26 +08:00
504556b6a9 ci(e2e): time-box the artifact download retry and the two uncapped legs (#15917)
Each leg downloads the shared build archive twice: once, and again from
the retry that only fires when the first attempt failed. Only the first
attempt carried timeout-minutes, so a retry that stalls held its runner
with no ceiling at all — up to GitHub's 360-minute default. Run
37119220504 (#13284) already showed both attempts lost inside one
window on a macOS shard while its sibling downloaded the same artifact.

Two limbs:

- Give the four 'Download build artifact (retry)' steps
  timeout-minutes: 10. The value is not new: the four first-attempt
  downloads in the same jobs already pin 10.
- Give e2e-test-macos and isolated-nightly a job-level
  timeout-minutes: 60. The two other artifact-consuming legs
  (e2e-test-linux, e2e-interactive-opentui) already use 60, and 60 is
  also the ceiling the shard-retry budget gate in run-e2e-tests.sh
  takes its 25-minute reserve out of. `build` keeps its own 30 — it
  produces the archive rather than consuming it.

web-shell-browser-regression has the same missing job ceiling but is
left alone: it is outside this finding's scope and does not consume the
build artifact.

The retry steps keep their absence of continue-on-error — the first
attempt's continue-on-error is what makes the retry reachable, and
copying it onto the retry would let a stalled download pass silently
into the unpack step. isolated-nightly's job-level continue-on-error is
untouched, so its 60-minute ceiling frees the runner without changing
the leg's conclusion.

Pinned in scripts/tests/e2e-workflow.test.js: both limbs, the retry's
visible failure, and a regression guard over the already-landed values
(build 30, the two consuming legs 60, the four first-attempt steps 10).

Refs #12132


Patrol-Run: qwen-issue-patrol/jmvuv88frpw

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-30 23:43:13 +08:00
jinyeandQwen-Coder f3af3dd857 refactor(managed-agent): Split server into Maven modules (#15871)
* refactor(managed-agent): split server into Maven modules

Separate contracts, orchestration, persistence, adapters, assembly, and verification while retaining the existing service delivery model. Move tests and build consumers together and document local evidence and pending CI acceptance.

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

* refactor(managed-agent): reduce configuration and SDK boilerplate

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

* refactor(managed-agent): narrow contracts and simplify test scaffolding

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

* fix(managed-agent): repair verification after module migration

Resolve shared fixtures and worker paths from the Maven module root, update CI packaging assertions, and align existing test fixtures with the extracted contracts. Restore restricted Workspace constructors and remove redundant test visibility changes without altering inherited MySQL coverage.

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

* test(managed-agent): align remaining Harness and OSS fixtures

Preserve SDK refusal messages and register the managed OSS adapter at fixture injection points. Keep the existing retry and client shutdown assertions intact.

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

* style(managed-agent): order Hosted cleanup Harness imports

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

* test(managed-agent): fix recovery smoke fixture and import order

Supply the required transcript coverage in the recovery browser fixture and wait for history before testing a complete handoff. Preserve its existing safety and submission assertions. Order the cleanup coordinator test imports for Checkstyle.

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-30 14:56:35 +00:00
Shaojin Wen e3f12f54f9 feat(verify-pr): reuse the current tree behind a clean-head prepare gate (#15913)
* feat(verify-pr): reuse the current tree behind a clean-head prepare gate

A local round already runs the head side of its A/B in the current tree, but nothing said so and nothing checked it: preparing on the wrong branch silently measured code the report never names, and the round snapshot carried only the head OID, leaving every base reference to re-derive the HEAD^1 warning the skill repeated in three places.

Prepare now refuses a HEAD other than the PR head (naming the checkout to run) and any uncommitted change, tracked or untracked, before it writes anything; it never stashes for the maintainer. The status probe pins --untracked-files=normal so a status.showUntrackedFiles=no config cannot hide files from the gate. The round snapshot records the full coordinates (repo, pr, baseRefOid, headRefOid), and the skill consolidates its three HEAD^1 warnings into one rule: local coordinates come from that snapshot, and the clean current tree is the head side, reusing its install and build.

The isolation boundary is unchanged: the tree gate runs on the host coordinate path only, and --require-isolation still guards PR-code execution.

* fix(verify-pr): make build reuse conditional on head freshness

Review reproduced a clean head checkout whose gitignored dist still ran an earlier commit's code while prepare advertised unconditional build reuse. The gate attests source identity only, so the success line now says to verify or refresh the install/build first, the skill makes reuse conditional on establishing the artifacts were produced for this head (otherwise rebuild per the local install strategy), and the design document records the boundary in both languages. The PR body's risk section matches.

* fix(verify-pr): close the index-flag hole and make refusals actionable

Review found four gaps in the tree gate: skip-worktree / assume-unchanged entries (which sparse checkouts set en masse) hide tracked divergence from git status, so a partial tree passed with the gate's own clean stamp; the dirty-tree remedy looped because a plain git stash leaves untracked files behind; the printed checkout remedy's detached-HEAD round trip was untested; and the fixture re-declared the repository's tmp/ ignore rule instead of pinning it.

The gate now refuses any index entry that is not a plain cached H, the dirty refusal names the entries and a stash form that clears them, a detached HEAD at the PR head is pinned to reach exit 0, and a coupling test fails if the real .gitignore stops ignoring the round's own artifacts. Each guard carries a mutation-verified test, and the four-step recipe was driven by hand against a scratch clone.
2026-10-30 22:50:30 +08:00
易良andQwen-Coder cc6de38cb5 fix(ci): isolate web-shell smoke from shared runner networks (#15886)
* fix(ci): isolate web-shell smoke from shared runner networks

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

* docs(ci): clarify hosted smoke font guard behavior

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

* docs(ci): explain dormant smoke pool steps

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

* docs(ci): stop overstating which dormant smoke guards are pinned

The retained comment claimed every self-hosted step's guard is pinned by
checkout/preflight tests. Only two of six are: `Recreate runner Git
metadata` (scripts/tests/qwen-runner-checkout-workflow.test.js) and
`Use pre-installed Node.js (self-hosted)`
(scripts/tests/no-ak-integration-ci.test.js). `Check host font baseline
(self-hosted)` is named by no test file, and its
`runner.environment == 'self-hosted'` guard can never be true under
`runs-on: 'ubuntu-latest'` — so it is parked, not pinned.

Say which two are pinned and that the font baseline guard is parked, and
re-measure the ci.yml size ratchet entry (155625 -> 155708).

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-30 19:43:04 +08:00
5cba899e46 docs(ci): correct the qwen-autofix job cap and budget table to match the workflow (#15914)
Two entries in .github/workflows/qwen-autofix.md restated review-address
bounds that the authoritative workflow no longer has:

- af-039 said "The 300-minute job cap puts the worst case at 5
  runner-hours per slot (100 across the fleet at 20)". The cap is
  timeout-minutes: 345 (qwen-autofix.yml:3670), so it becomes 5.75
  runner-hours per slot and 115 across the fleet at the max-parallel
  fallback of 20 (:3679).
- af-050's worst-case budget table listed "Repair 20" and
  "worst case 280 => job timeout 300". The Repair deterministic
  rejection step is bounded at 70 minutes (:5540, "the 60-minute budget
  below plus the same 10-minute margin"), so the row becomes 70 and the
  table's own six rows sum to 330, quoted against the real 345 cap. The
  trailing "on ubuntu-latest, whose own ceiling is 360" clause is left
  as-is: 345 < 360 keeps it coherent, and re-deriving the conditional
  runs-on expression (:3669) is out of scope.

The table total is 330, leaving 15 minutes against the 345 cap. The
workflow comment at :3667-3668 separately describes a 25m reserve for
the four-step unarmed chain (130+60+70+60 = 320), which excludes the
setup and report rows this table itemises. The two are deliberately not
conflated here.

Adds a test that derives every quoted figure from qwen-autofix.yml and
requires the doc to agree, so neither entry can drift silently again.
The existing sum check reads only the workflow and structurally cannot
see a doc that restates the same numbers in prose.

Witness:
- red, doc unmodified: "x keeps the af-039 and af-050 budget prose in
  step with the job cap -> expected '# `qwen-autofix.yml` - design
  record...' to contain '345-minute job cap'"; Tests 9 failed | 327
  passed | 14 skipped (350), rc=1
- green, both sites fixed: that test passes; Tests 8 failed | 328
  passed | 14 skipped (350), rc=1. The 8 are pre-existing
  local-environment noise, identical to the unmodified-base baseline
  (Tests 8 failed | 327 passed | 14 skipped (349), rc=1).


Patrol-Run: qwen-issue-patrol/jmvuqxwvapt

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-30 19:42:46 +08:00
7c9e79e70b perf(release): reuse verified native audio prebuilds across releases (#15854)
* perf(release): reuse verified native audio prebuilds across releases

Every release rebuilds the five native audio prebuilds even when nothing
that affects those binaries changed. The build is short; the wait is not.
For v0.26.3 the Linux x64 prebuild queued 74 minutes to execute for 50
seconds, and the collection step queued again after it — and `publish`
depends on that collection, so the whole release sits behind it.

Fingerprint the audio build inputs and reuse an artifact from an earlier
successful official main release when the fingerprint matches. The key
covers the audio package, the dependency lock, the package manager pin and
the contents of the producing workflow and this helper, so the Node pin and
the prebuildify target arguments are part of it. The package version is
excluded deliberately: no part of the native build reads it, and including
it would make reuse impossible between releases.

Reuse means executing bytes an earlier run produced, so provenance is
checked rather than assumed: exact fingerprint name, unexpired, same
repository for both run and head run, and a completed successful
release.yml run on main from workflow_dispatch or schedule. The pinned
download-artifact already verifies the artifact digest.

Retention is checked differently at selection and consumption. Selection
requires 48 hours of headroom so the artifact outlives the release;
consumers require only that it has not expired. Re-applying the margin
against a later clock would reject an artifact the release had already
committed to, after the native matrix was skipped and with no fallback.

Lookup, trial download and payload validation are all non-fatal, and the
reuse ID is published only once a trial download has validated; an empty ID
runs the full matrix, which is today's behaviour. The publisher does not
trust that decision: it recomputes the fingerprint from its own checkout,
re-verifies the source run, and validates the payload again, fatally — by
then the matrix was skipped, so a bad artifact must stop the release.

This does not depend on ECS_RELEASE_HOST_RUNNER_ENABLED and takes effect
with the switch unset.

The collector needed three fixes to be a trustworthy producer. Its
`rm -rf -- "$GITHUB_WORKSPACE/packages/audio-capture/prebuilds"` resolved
through whatever `packages` was on a persistent host: with a leftover
symlink it deleted an external tree and exited 0, and the following
download wrote through the same symlink. It now wipes through a shared
preflight that canonicalises the workspace and refuses paths resolving
outside it. It also ran `node` with no Node contract, inheriting Node from
the hosted image by accident. And its release-only label pinned `x64` while
the runners holding that label are ARM64, which never matches and queues
until timeout.

Contract tests pin the wiring, each confirmed to fail against an injected
regression. Design doc in both languages.

* fix(release): reconcile audio reuse workflow contracts

Share the pre-checkout wipe, preserve the workflow size cap, and let fingerprint failures rebuild native audio.

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

* fix(ci): use block-style release permissions

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

* fix(release): drop x64 from publish's release-host expression

The review's unresolved item: collect got the ARM64 label fix but publish kept
["self-hosted", "linux", "x64", "ecs-qwen-release-host"], which can never
match the ARM64 machines holding that label, so the OIDC-carrying job would
queue forever once ECS_RELEASE_HOST_RUNNER_ENABLED is true. Same expression as
the collect fix, same one-word drop.

* docs(release): clarify audio reuse rollout and validation

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

* test(release): cover artifact overwrite and contained symlink guards

* test(web-shell): include transcript coverage in recovery fixture

Bring the fixture correction from #15883 into this branch to address the observed smoke failure.

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

* fix(release): make audio cache seeding best effort

Keep the validated same-run artifact usable when retaining a keyed copy fails. Pin the reuse wiring and correct the retention and producer fixtures without changing release trust boundaries.

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

* chore(test): apply prettier to the audio prebuild fixtures

* docs(release): clarify audio reuse run provenance

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

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: zzj <[email protected]>
2026-10-30 11:27:38 +00:00