* feat(managed-agent): ship a configuration template in the reference distribution
The reference archive carried no application.yml, so the Runtime credential
key ring — which cannot be expressed through QWEN_MANAGED_AGENT_* variables
because relaxed binding normalises map keys — forced every rotating deployment
to create a file and pass --spring.config.additional-location by hand.
Ship config/application.example.yml and have both launchers load
config/application.yml from the bundle root when it exists, leaving an
operator-supplied SPRING_CONFIG_ADDITIONAL_LOCATION in effect.
* fix(managed-agent): pass the distribution config through the environment
The shared launcher injected --spring.config.additional-location before the
operator's arguments, so the operator-recovery entry, which dispatches on
args[0] and the exact argument count, could no longer run inspect, prepare or
complete.
Export SPRING_CONFIG_ADDITIONAL_LOCATION instead and leave the argument vector
untouched for both launchers. Also make the template's active key pair use the
mapped placeholders, so a copied example still honours the documented
environment override.
* test(managed-agent): isolate the launcher test from an ambient config variable
The test spread process.env, so a machine that already exported
SPRING_CONFIG_ADDITIONAL_LOCATION changed the launcher's behaviour and failed
the new assertions. Remove the variable from the base environment; the
explicit operator-override case still sets it.
* test(cli): give turn-settling waits the slow-host budget
The file synchronises every hosted turn by polling the daemon until the
pipeline reports a terminal state, with a 10 s budget. On a loaded runner
that pipeline outlasts the budget, so the suite fails on whichever
assertion happens to be waiting at the time -- different tests on
different days (#14406, #14023), all with work still in flight.
Route those waits through the slow-host policy the file already applies
in three places, and stay below the 60 s per-test ceiling so a genuine
hang still fails inside the test.
* test(cli): clamp the settle budget to the lane's per-test ceiling
Follow-up to the review on the 45 s budget: isSlowTestHost()'s loadavg
branch can also be true on hosts whose per-test timeout is 15 s
(GitHub-hosted runners, the env -i verification child), where a 45 s
ceiling is unreachable and a slow settle degrades to a bare runner
timeout. Clamp to the ceiling the lane actually sets (mirrors
packages/cli/vitest.config.ts) so the assertion failure stays the first
thing to surface; also note the module-load evaluation in the comment
and move the constant after the ID group.
* test(cli): budget the waits the cited flakes actually hit
The sweep converted the 245 sites that already carried `timeout: 10_000`,
but every failure the description cites ran on a bare `vi.waitFor` and so
on vitest's 1 s default. Give those waits the same slow-host budget, and
correct the comment to name the 1 s default instead of claiming file-wide
coverage.
Two waits stay short on purpose: the finally-block cleanups in
`reports a reconcile against a live wake turn without settling its run`
and in the `cancel`/`deadline` MCP test are cleanup, not proof — at the
settle budget a wedged turn burns the whole per-test ceiling and reports
a bare timeout instead of the assertion. The `R3-2` wait for `modelWaiting`
is bounded by the 10 s deadline the fixture submits, past which `/cancel`
would land after the deadline that test pins.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmvegdno2gr
* test(cli): keep the recoveryBlocked assertion on the Skills wait
The conversion at the `loads published Skills…` wait reformatted the block
and dropped `expect(status.body.recoveryBlocked).toBe(false)`, weakening
the test. Restore it inside the same wait.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmvegdno2gr
---------
Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
An empty bound Session otherwise waits until its first tool call to start the shared worker. Session-create warm starts that worker at creation, and only under Workspace isolation, so an unused Session neither pins its own worker nor loses the never-initialized close receipt.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(managed-agent): bound lifecycle and collector guard waits
Bound the first guard acquisition and lifecycle completion row waits while preserving lock order, fencing and shorter statement budgets.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): narrow A.3 to initial publication waits
Restore placement, CSI and public Session/event-stream behavior to the base implementation. Keep the reported first publication acquisitions bounded and retain a publication-only lifecycle regression witness.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): record remaining unbounded publication acquisitions
The F2 rewrite narrowed A.3 to the seven bounded sites and dropped the only
line that tracked the unbounded publication acquisitions as open work, so the
doc read as if every lane were covered. List them under Follow-ups: the
lifecycle close/archive/delete admission (ManagedAgentStore.beginLifecycle)
and the o3-backfill pair in ManagedToolResultStore.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmve7t0jzg8
* fix(managed-agent): skip a contended tenant per candidate, not the sweep
The bounded acquisition now answers 503 tenant_guard_contended inside three
per-candidate sweep loops that had no per-candidate handler: snapshot expiry
and projection-body collection in ManagedProjectionResourceCollector, and
SessionResourceCollectionCollector's claim loop. The first contended candidate
threw out of the loop and, because SessionResourceCollectionCollector.runOnce
calls projections.runOnce unguarded, also skipped that tick's ledger and claim
phases. The skipped candidate keeps its old due time, so the ordering
re-selected it on every tick and no tenant advanced.
Each of the three loops already runs one transaction per candidate, so the
refusal is absorbed per candidate after that transaction rolls back.
ManagedToolResultStore.claim() is deliberately untouched: its candidates share
a single transaction, where a catch would change transaction semantics.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmve7t0jzg8
* fix(managed-agent): retry the projector pass when a tenant guard is contended
`ManagedToolResultStore.claim()` and `admitsLegacyProjection()` now refuse with
`ApiException(503, tenant_guard_contended)` once the tenant guard is held past
`GUARD_ACQUIRE_TIMEOUT_SECONDS`. Both calls sit in `ManagedToolResultProjector`'s
8-claim loop, outside the only handler in `tick()`, so the refusal escaped the
`@Scheduled` method and Spring's default handler logged an ERROR with a stack
trace on every pass. Wrap the loop in the same catch-and-retry shape the two
sibling collectors already use: the claim lapses back to due and the next tick
retries.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmvec3c4pgi
* fix(managed-agent): keep the projector claim loop outside the backfill handler
A refused backfill must not gate tool-result projection: that head-of-line
stall is exactly what the bound is meant to remove. The claim loop keeps its
own catch, so both escape routes (claim() and admitsLegacyProjection() via
project()) are absorbed while the backfill arm keeps its original scope.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmvec3c4pgi
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
* feat(web-shell): add approval mode allowlist
* fix(web-shell): complete allowlist i18n and test mocks
* fix(web-shell): harden approval mode allowlist per review
- Restrict ui.allowedApprovalModes to operator scopes: a repository's
workspace settings can no longer remove an operator's mode
restriction, and workspace-scope writes are refused.
- Report when a valid-but-hidden mode is selected instead of a silent
no-op, in both App and ChatPane, and pin the plan exemption.
- Close an open mode dropdown when the offered list becomes empty.
- Stabilize the host allowedApprovalModes array so inline literals do
not churn the composer and customization memos.
- Share the cycle step as nextApprovalMode in planMode.ts so the App
and ChatPane copies cannot drift.
- Complete DaemonSettingDescriptor fixtures with the required default
field and scope the README live-refresh claim to host Prop changes.
* fix(web-shell): fail closed on approval-mode read errors and pin review follow-ups
* fix(web-shell): scope the approval-mode focus handoff and gate its settings read
Round-4 review follow-ups:
- The focus handoff matched the shared toolbar-popover attribute on the
document-root active element, so it could steal focus from the model
dropdown or another editor instance. It now fires only for this
instance's own mode control, via a dedicated data-web-shell-mode-popover
marker plus toolbar containment, and only once the control is gone.
- The trigger case never fired in a real browser because a focused button
blurs to the body the moment it becomes disabled. Focus ownership is now
recorded by the control's wrapper, so the handoff works after that loss
without an ungated body check that would steal focus on mount.
- The handoff now also covers a narrowing list unmounting the focused row
while the picker stays open, and its disabled-state fallback skips
disabled targets, falling back to the enabled composer.
- The allowlist read is gated on the daemon advertising the
workspace-qualified settings features, so an older daemon keeps the
default list instead of failing closed for the whole session; a
reconnect retries a failed read.
- The approval dialog re-anchors the highlight by row identity, so a
same-length list swap cannot leave Enter committing a row the user
never highlighted.
- The e2e stub answers the scoped and primary routes with different lists
so applying the wrong workspace's read fails, and a new e2e empties the
allowlist under an open picker to pin the browser-level handoff.
- The design record (both languages) now documents the capability gate
and the fail-closed first-read outcome.
* fix(web-shell): repair approval-mode focus handoff and dialog highlight re-anchor (#14345)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): witness the FG12d release ordering (#14447)
The release gate saw a settled mcp-status in the wire and a released
record, and read them as an ordering proof. Both can exist without the
state having waited for the answer.
Park the settled mcp-status reply inside the Broker proxy. While it is
held, assert the record is still `releasing` and settle_resources is
still unanswered, then deliver the reply and expect `released`. The case
also fails if the reply was never parked, so the witness cannot pass
vacuously. Both design docs record the new evidence.
* test(managed-agent): address the FG12d review notes
Run the Broker proxy on a virtual-thread executor and shut it down in
teardown, so a parked reply no longer freezes every other Broker exchange
and a later cell can arm both holds without stalling until the 120 s
expiry. Release the parked reply in a finally, so a failed in-window
assertion does not make teardown wait out that expiry either.
Replace the stale MariaDB measurement and acceptance lines in both
design docs with the MySQL 8.0.46 runs, reported as a range.
* docs(managed-agent): correct the FG12d MariaDB note
The new base carries #14618, which accepts MariaDB numeric booleans in
CHECK validation. MariaDB 10.11 now validates the managed schema and the
restart class is 6/6 there in 40.5 s, so the design docs no longer claim
it cannot run. Record the MySQL and MariaDB measurements together.
---------
Co-authored-by: 易良 <1204183885@qq.com>
* test(managed-agent): add the FG11c Harness-crash fault gates (#14446)
* test(managed-agent): harden the FG11c crash gates
* docs(managed-agent): recompose the FG11c budget after the FG12b merge
* test(managed-agent): hold the scanner while the FG11c crash cases arm
The manual run in `turn-crash` and `settled-crash` races a minute
boundary: a scheduled occurrence admitted during setup fires the same
Session and leaves a second chain of `fireRun` commands, so the exact
`:1`, `:2` assertion fails for a reason the case does not gate.
Park the scanner for the setup, and release it once the crash is in
place so the next minute slot still drives the reconcile.
`settled-crash` also arms the revision-3 hold after its POST, but the
Harness kicks the wake pump before answering `fire_run`, so the settle
can reach the store first and the case then waits for a hold that
never fires. Derive the run id, arm the hold, and POST after it.
* docs(managed-agent): retarget the FG11c c3 witness to the method
Running c3 as written — the open path skips `reconcileRuns` — left
`settled-crash` green in 39.6 s with the mutation proven to reach the
chunk loaded from `dist/cli.js`: the crashed Harness is re-driven by the
next slot's `reconcile_run`, and that route reaches `reconcileRuns()` too,
so the single-site mutation is covered by the other caller.
Short-circuiting `HostedAutomationSession.reconcileRuns()` turns the case
red as the table claims: `expected "completed"` times out at the case's
terminal assertion after 216.5 s. Retarget the witness and correct the
finding that named the open path as revision 3's only committer.
* docs(managed-agent): restate the FG11c budget on the FG12f baseline
* docs(managed-agent): record the runtime-broker checkstyle attribution
* docs(managed-agent): name the surviving reconcile path behind the c3 green
* docs(managed-agent): restate the FG11c budget on the FG13a baseline
---------
Co-authored-by: 易良 <1204183885@qq.com>
* test(managed-agent): drop the hook-cancel reply through a broker proxy
Front the embedded broker with the existing forwarding proxy and arm a
DROP on the hook-cancel control reply. The owner already aborted, so the
gate must resolve the same cancellation through a later status poll, drain
the unit exactly once, and never issue a second physical Hook call.
* test(managed-agent): budget the FG13d dropped-reply cancel cell
The class gains a fourth fixture, so the shared Hosted fork grows by 150 s:
healthy 4908, fork 5518, Verify 105 minutes and job 200, with the three
pinned comments kept in step.
* docs(managed-agent): record the FG13d dropped-reply cancel case and witness
* test(managed-agent): drop the FG13a hook-execute reply through the broker proxy
The class fronts the embedded broker with the forwarding proxy and drops
the dispatch reply after the worker admitted the operation. The retry must
resolve the same operation through the owner's status: one physical call,
one allow, the record settled with outcome=success, and the drop recorded
in the proxy log. Adds 150 s to the shared fork budget and documents the
matching mutation witness.
* test(managed-agent): drop the FG13a hook-status reply and settle on the next poll
A dropped poll answer cannot prove the end, so the gate must record the
honest unknown first; the later poll of the same owner settles the same
receipt without a second physical call. Adds the second cell and 150 s to
the shared fork budget, with the matching witness in the design.
* test(managed-agent): tighten the FG13a reply-loss assertions and witness
The status cell asserted the allow before the one-second hold ended, so a
healthy recovery could fail; assert it past settlement instead. The
execute cell now requires exactly one tool result for the emitted call,
and the execute witness targets the status fallback it actually exercises
rather than the unreached execute deduplication.
* test(managed-agent): keep the proxy drop log in the FG13a evidence bundle
Both FG13a cells assert on proxy.log to prove the reply was actually
dropped, but the log was not copied with the other evidence files, so a
lane failure uploaded a bundle without the line that explains it.
Also drops a comment block that was duplicated verbatim above the
HarnessAttach record.
* test(managed-agent): keep the proxy drop log in the FG13d evidence bundle
The cancel-drop cell proves the reply was dropped by asserting on
proxy.log, but the log was not copied with the other evidence files, so
a lane failure uploaded a bundle without the line that explains it.
---------
Co-authored-by: 易良 <1204183885@qq.com>
The V10 plan-mode migration is the first schema change to use a bare
boolean literal in a CHECK constraint. MariaDB stores such a clause with
0/1 and drops precedence-inferable parentheses, while MySQL keeps the
keyword; the catalog validator's checkShape never applied the
false->0/true->1 normalization that expressionTokens and the generated
column path already used, so ManagedSchema.initialize/validate failed on
every broker boot against MariaDB and the mysql-integration lane lost
767 tests. The H2 fast tier skips check-clause comparison, so the
push-time gate stayed green.
checkShape now normalizes boolean literals through the existing
numericBooleans helper before shaping, and a witness test pins both V10
clauses as MariaDB 10.11.18 actually stores them, plus four drift arms
that must still be rejected. Removing the normalization makes the
witness fail with the exact production message.
Also re-indent the release-drain .thenAccept body that #14568's chain
restructure left at the old depth; the runtime-broker checkstyle gate
rejected it, failing every nightly job whose verify phase runs it.
The two cancellation-fence it.each blocks (and two sibling call sites) in
hosted-mcp-session.test.ts poll vi.waitFor with vitest's hard-coded 1s
default window around conditions reached through the shared main-CI host's
scheduling and fsynced durable-write stalls. On the post-merge run —
coverage enabled, four forks on a contended ECS host — that window can be
exceeded even though nothing is wrong; the stalled poll rethrows its last
assertion ('expected false to be true') after 1s. Which of the six phases
exceeds the window on a given run is a dice roll, matching the mixed
pass/fail pattern in the issue.
Reproduced the failure signature by injecting a 1.2s stall ahead of each
block's 'entered' flag: all six phases failed at the first waitFor with
'expected false to be true'. With the explicit 10s window the same probe
passes; without the probe the full file stays green (49/49). The logic
under test is deterministic, so widening the poll window is the
root-cause fix — the same fix#13323 applied to the sibling Hook fence
suite. Assertions unchanged.
* docs(managed-agent): draft the FG12b and FG12e fault-gate designs
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): correct the FG12b and FG12e designs after audit
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): address the FG12b and FG12e design review
* test(managed-agent): add the FG12b MCP store fault gates
The eight-case ledger and a test-only faulting record store wrapper are in place and compile clean. The class is not yet green on JDK 21 + MariaDB: store-reject-settled, -releasing, -drained, -released and -reload-chain still fail. The shared fork budget re-pin (hostedForkHealthySeconds, CI fork literal, pom and workflow comments) is not done; only the roster entry is updated.
* docs(managed-agent): close the remaining FG12b/e review findings
* test(managed-agent): re-pin the FG12b fork budget
The FG12b store-failure class is green 8/8 on JDK 21 + MariaDB (49.0 s measured). Re-pin hostedForkHealthySeconds to 4858 s, the CI fork to 5468 s, the Verify step to 102 minutes, and the job step sum to 182, with the workflow, pom and meta-test comments updated in the same diff.
* test(managed-agent): harden the FG12b store fault gates
Round 1 audit fixes: the settled cell asserts the dispatch_started record and zero response rows after the failed commit, reopens the stopped authority with the original operation/runtime pins, and reaches that rollback assertion under a skip-rejection mutation (red: expected dispatch_started, was settled). The released cell pins the exact owner-release path and requires two confirmed 200/released releases, with exactly one resend on the next detach. Owner/storage retention now compares the exact runtime-session/generation identity, reload-chain asserts the settled rev 2 and no duplicate chain, and the static gate is cleared in teardown. Local evidence is MariaDB 10.11, not the lane's MySQL 8.4.6. Budget: 120 s allowance for the measured 50.3 s class -> healthy 4878 s, fork 5488 s, Verify 102 minutes.
* docs(managed-agent): design the FG12f stream gap gates
* test(managed-agent): hold the MCP fixture call until the test releases it
* style(managed-agent): format the FG12b design tables
* docs(managed-agent): correct the FG12b budget provenance and release cell
* test(managed-agent): drop the unused FG12c helpers from the FG12b class
* style(managed-agent): realign the FG12b design tables
* test(managed-agent): add the FG12f MCP stream-gap gates
One MCP tool call is parked inside the stdio fixture while a client reads the Session stream, the client is cut mid-call, and the resumed client must continue the same ledger exactly once. The public route resumes with Last-Event-ID and the WebShell route with afterSequence, so a cursor that is ignored replays and reddens the union on a duplicate instead of timing out. The third case replaces the configuration while the call is held and pins that the running Session keeps its revision, its server and one contiguous stream.
* docs(managed-agent): re-pin the FG12f budget to the merged base
The parent branch merged main's event-stream liveness gates, which added 440 s to the shared fork's healthy sum before this class. The measurement is unchanged (104.6 s, 210 s budgeted) but the totals it lands on are now 5803 s healthy, 6403 s fork and a 117-minute Verify step.
* test(managed-agent): drive the real MCP configuration route in the FG12f replacement case
The third case now posts the Harness-private configuration route while the call is held, so the committed configuration generation advances to revision 2 and the released call keeps reading it; the immutable Agent-definition append it used before never reached the replace path. All three cases also pin the fixture's physical effect: one mcp.effect record and one durable proof row, so a resume that re-invokes the tool reddens even when the published completion is unchanged.
* test(managed-agent): pin the FG12f replacement case to the public definition route
The Harness-private configuration route requires the client id the Broker mints for the Harness session, which a Spring-created Session never exposes, so the case answered 404. The case now commits a new MCP server revision through the public definition route while the call is parked and pins that the running Session keeps revision 1, the replacement is stored once, one server start exists, and the fixture's physical append happens exactly once.
* docs(managed-agent): narrow the FG12f replacement case to what it pins
The case commits a new MCP server revision while the call is held; it does not replace the running Session's MCP connection. The Harness-private configuration route cannot carry that here because it requires the client id the Broker mints, which a Spring-created Session never exposes, so it answers hosted_session_not_found. The design now records runtime connection replacement as a gap instead of claiming it.
* test(managed-agent): re-pin the FG12f budget to the merged-base measurement
The three cases measure 121.1 s on the merged base rather than the 104.6 s they measured before it, so the class is budgeted at 2x (242 s): the healthy sum becomes 5985 s, the fork 6585 s and the Verify step stays at 120 minutes.
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
* test(e2e): widen the channel-worker sandbox budgets (#14584)
The channel-less-boot scenario starts a nested Docker-sandbox ACP session
that loads the linked channel extension (#10318), so its cost is bounded by
the daemon's initialize budget rather than by the fixture's host-side work.
Run 39121969242 spent 139.9s across three attempts on a contended
sandbox:docker shard - one attempt hit the 60s ceiling - while the same test
passes in 16.3s on a healthy docker shard and the same job's sandboxed CLI
starts took 22-26s each.
Raise the fixture's initialize budget from 60s to 120s and the scenario's
ceiling from 60s to 240s. Every assertion and input is unchanged; only the
ceilings move.
* fix(channel): let the worker's daemon requests outlast a slow sandbox start
The channel worker's DaemonClient used the SDK's 30s default request budget,
which also caps POST /session - the call that waits for the ACP child's
initialize inside the workspace sandbox. That client deadline, not the
daemon's initialize budget, is what turned a slow start into
"Could not create task": #14584's failing attempts returned about 40s after
the test began, worker connect plus the 30s cutoff.
Give the worker a request budget that covers a 120s sandbox-scale initialize
budget with headroom. prompt() is already exempt from fetchTimeoutMs, and the
supervisor's own startup/delivery budgets stay at 30s, so only the daemon
calls that can legitimately be slow get the wider window.
Refs #14584.
* test(e2e): correct the channel-worker budget comment (#14584)
The fixture no longer matches qwen-serve-routes.test.ts - the sibling fixtures
stay at 60s - and the 240s ceiling absorbs two full 120s starts, not four: the
scenario creates two tasks before the daemon restart and reloads both after.
Say both plainly.
* revert: drop the client-wide request budget in favor of #14588
The client-wide fetchTimeoutMs also capped session resume at 180s, so a daemon
advertising a restore budget above 170s regressed, and it bypassed #14588's
derived session-operation deadline for the very caller that hit the bug. #14588
owns the client-side deadline; this PR keeps only the fixture ceilings, which
the third failed attempt needed on its own.
Refs #14584, #14588.
* test(e2e): give the channel-worker ceiling room for the scenario's fixed cost
The 240s ceiling was exactly 2 x 120s, so two slow starts at the full budget
left nothing for the daemon spawn, worker boot, channel connect and restart
(~4.5s measured on a quiet host, more in the sandbox). Raise it to 300s and say
what the extra 60s buys.
---------
Co-authored-by: 易良 <1204183885@qq.com>
* docs(managed-agent): draft the FG12b and FG12e fault-gate designs
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): correct the FG12b and FG12e designs after audit
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* docs(managed-agent): address the FG12b and FG12e design review
* test(managed-agent): add the FG12b MCP store fault gates
The eight-case ledger and a test-only faulting record store wrapper are in place and compile clean. The class is not yet green on JDK 21 + MariaDB: store-reject-settled, -releasing, -drained, -released and -reload-chain still fail. The shared fork budget re-pin (hostedForkHealthySeconds, CI fork literal, pom and workflow comments) is not done; only the roster entry is updated.
* docs(managed-agent): close the remaining FG12b/e review findings
* test(managed-agent): re-pin the FG12b fork budget
The FG12b store-failure class is green 8/8 on JDK 21 + MariaDB (49.0 s measured). Re-pin hostedForkHealthySeconds to 4858 s, the CI fork to 5468 s, the Verify step to 102 minutes, and the job step sum to 182, with the workflow, pom and meta-test comments updated in the same diff.
* test(managed-agent): harden the FG12b store fault gates
Round 1 audit fixes: the settled cell asserts the dispatch_started record and zero response rows after the failed commit, reopens the stopped authority with the original operation/runtime pins, and reaches that rollback assertion under a skip-rejection mutation (red: expected dispatch_started, was settled). The released cell pins the exact owner-release path and requires two confirmed 200/released releases, with exactly one resend on the next detach. Owner/storage retention now compares the exact runtime-session/generation identity, reload-chain asserts the settled rev 2 and no duplicate chain, and the static gate is cleared in teardown. Local evidence is MariaDB 10.11, not the lane's MySQL 8.4.6. Budget: 120 s allowance for the measured 50.3 s class -> healthy 4878 s, fork 5488 s, Verify 102 minutes.
* style(managed-agent): format the FG12b design tables
* docs(managed-agent): correct the FG12b budget provenance and release cell
* test(managed-agent): drop the unused FG12c helpers from the FG12b class
* style(managed-agent): realign the FG12b design tables
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
* fix(managed-agent): free Workspace storage after a refused release
A worker that refuses, times out, or hangs on release skipped the final
ownership.release, leaving ma_storage_owner.holder_key stranded. Every later
acquire then failed 409 workspace_busy, and nothing else cleared the row.
The worker now bounds its memoized release and answers before the Broker's
HTTP timeout, mapping a refusal to managed_runtime_owner_hold_pending and a
deadline to managed_runtime_release_timeout. A stale attempt cannot retire a
Session that was re-acquired after the timeout.
The Broker passes those codes through, and the drain cascade escalates one
refusal to the durable stop it already owns: stop the worker, persist the
receipt, finish the Session locally, then clear ownership through the new
transport hook. That hook clears only an exact holder whose binding is
DRAINING with a matching drain receipt, so a mere transport failure still
retains ownership as the acquire side requires.
* fix(managed-agent): keep a worker release refusal retryable
providerFailure() already recognized managed_runtime_owner_hold_pending but
returned it non-retryable, so releaseSavedSession rewrote it to
workspace_close_identity_unverified and the drain escalation never matched.
Mark the ownership codes retryable and recognize release_timeout on the same
path; the provider transport test pins both codes.
* fix(managed-agent): gate the retired-holder scan on a persisted receipt
On the plain close path the receipt from stopAcknowledgedRuntime reaches the
row only in the final CAS, so releaseRetired could not prove anything and the
scan only re-read every retired Session. Scan only when renewal.current already
carries the receipt; that path has cleared every holder by acknowledgement,
while the absence and escalation paths persist the receipt before the scan.
* test(managed-agent): pin the release-retired proof predicate
Cover both sides of the safety boundary: a DRAINING binding with a matching
receipt clears the holder, and the same binding without a receipt while the
holder is set returns false without calling ownership.release.
* test(managed-agent): drop the hook-cancel reply through a broker proxy
Front the embedded broker with the existing forwarding proxy and arm a
DROP on the hook-cancel control reply. The owner already aborted, so the
gate must resolve the same cancellation through a later status poll, drain
the unit exactly once, and never issue a second physical Hook call.
* test(managed-agent): budget the FG13d dropped-reply cancel cell
The class gains a fourth fixture, so the shared Hosted fork grows by 150 s:
healthy 4908, fork 5518, Verify 105 minutes and job 200, with the three
pinned comments kept in step.
* docs(managed-agent): record the FG13d dropped-reply cancel case and witness
* feat(managed-agent): bound half-open Hosted event streams behind a keepalive capability (#14135)
The Harness advertises its events-route keepalive as event-keepalive/1,
and the Java client arms the idle watchdog only against a Harness that
advertises it, so a rolling upgrade never aborts the quiet streams of an
older Harness. The abort is now the typed HostedHarnessStreamIdleException:
the coordinator retries it through the existing re-attach path and never
settles or cancels the Turn on silence alone. The watchdog ticks every
tenth of the bound, so an abort lands 45 to 49.5 s after the last byte.
HostedEventStreamLivenessIT runs the probes on the packaged chain behind
a TCP relay that holds the events connection half-open or stalls it, and
the hosted-harness-mysql fork budget grows to 4793 s. A design note
records the numbers, the capability, the outcome and the other Hosted
streams.
Closes#14135
* test(managed-agent): frame relayed requests and see virtual-thread readers in the liveness gate
The event-stream relay classified a connection, rewrote its Host header
and matched keepalives within single reads, so a request or a keepalive
split across TCP segments escaped the stall and half-open injection or
the cadence count. It now buffers each request head, forwards the body
by its Content-Length, matches keepalives across reads, and records and
resets a request it cannot frame. HostedEventStreamRelayTest pins each
split.
The half-open probe checked reader release with Thread.getAllStackTraces(),
which leaves out the virtual threads Turn readers run on. It now reads a
JDK thread dump, and first requires that dump to show the parked reader.
* test(managed-agent): count each keepalive one relay read carries (#14135)
The relay recorded one stamp per read that held a keepalive, so two keepalives in one read counted once, and the split-keepalive case could report a size of 1 whenever the pieces happened to land together. It now counts every match in the read window; the carried tail is a byte shorter than a keepalive, so none counts twice. A new case sends two keepalives in one write.
* test(sdk-java): park the published-window probe mid-interval (#14135)
The watchdog's ticks run from the stream's creation, and the probe parked right after it. A 2000 ms bound is a whole number of half-budget 1000 ms ticks, so that park sat on a tick of the mutant too, and wherever the scheduler ran a few ms late (macOS) the mutant aborted at ~2000 ms and passed. Parking 300 ms in puts the park mid-interval for both ticks: the shipped 200 ms tick aborts at 2100 ms and a 1000 ms tick at 2700 ms, so the limit drops to 2400 ms.
Keep per-Session root and head waits from holding the publication guard through the database lock-wait timeout, while preserving ordinary writer behavior and shorter configured statement budgets.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
When `rg` is not installed, the file-search crawler printed an alarming
block of raw failure diagnostics for a single failed spawn, and printed
it twice:
[crawler] command spawn failed: rg --files --no-require-git --hidden ENOENT Error: spawn rg ENOENT
[crawler] falling back to fdir (ripgrep unavailable)
[crawler] command failed: rg --files --no-require-git --hidden exit=-2
Two defects, fixed separately:
- The ripgrep probe is an optional-capability probe whose failure is
already narrated by the "falling back to fdir (ripgrep unavailable)"
warn, so its raw spawn diagnostics are pure noise. Pass
`silentOnFailure: true`, matching the three git probes that already do.
- `runCommand` consulted its `settled` flag only inside `finalize()`,
which guards `resolve()`. The logging sites sit outside that guard, so
when a spawn fails the `error` handler logs "command spawn failed" and
the `close` handler then re-reports the very same failure as "command
failed ... exit=-2" (libuv surfacing UV_ENOENT as the close code on
Linux). Add a `!settled` leg to the close handler's condition.
Deliberately out of scope: routing the probe through a resolved ripgrep
binary, and memoizing "rg is unavailable". The `git ls-files --cached`
call is also left non-silent on purpose - its degradation is already
narrated, and keeping it loud leaves the double-report observable.
Refs #14537
Patrol-Run: qwen-issue-patrol/jmvdlnotoa2
Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
A reconcile_run repair checked only session.active, which the wake pump
does not hold while it reads an input, projects the transcript or
classifies a crash. A repair that finished inside one of those awaits
left the pass acting on a consumed input, and the Session ended
recovery-blocked until a reload (#14538). A message stop's settle of a
crashed message turn had the same race.
HostedMonitorWakeScheduler.exclusive() runs work with no pass in flight
and none able to start. A pass in flight hands the pump straight to the
first waiter when it returns, and the pump stays held until that work
returns. A pass still running after the wait keeps the pump and the
work never runs; later callers do not wait for the hold in flight when
that wait ran out. Closing the pump lets its waiters go without their
work.
Both routes settle inside it, waiting at most HOSTED_WAKE_PASS_WAIT_MS
(10 s) for a pass, and only when they have something to settle. Each
re-reads its guard after the wait, since a prompt or a close may have
claimed the Session meanwhile:
- reconcile_run: a load pass that settled the crash leaves it nothing
to repair, and it answers with the run that pass failed, which lets
the occurrence resolve. A pass that ended blocked is repaired by the
route as before.
- A message stop leaves a crash that the pass left blocked to the
pump's blocked cycle, and anything a prompt now holds to the relay's
next stop.
The pump also re-arms when a pass ends blocked and its settle-verify
read finds nothing. A stopped Session's message input is filtered out
of that read, so such a pass armed no retry, and the block stayed until
an unrelated kick or a reload.
Fixes#14538
When the store refuses a tool publication reservation at a limit, the
Hosted Harness answers the refused batch as not started (#14452). For a
429 it told every call "Retry after the running tools finish." That is
wrong for two shapes of batch:
- A batch with more output-capturing calls than its Session could ever
hold at once can never reserve. The model was told to wait and could
repeat the same batch every turn.
- Calls that need no reservation at all (a read, an edit, an agent)
got the same hint, although no limit applied to them.
The store now puts `admits` into the refusal's details (and its WARN
line): how many reservations of this size the Session could hold at
once under every limit. The tenant and Session byte limits are reduced
by the Session's own used bytes, which stay charged until its output is
collected after it retires. Nothing else is subtracted: a pending
finish can still give its terminal back, an open capture gives back
what it holds beyond its used bytes, and other Sessions retire. So
`admits` is an upper bound, and a 429 always admits at least one.
ManagedSessionStoreHttpError carries the envelope's other scalar fields
as `details`, including when the retry loop rewraps a transient or
contended failure.
The Harness compares the batch's reservations with `admits`:
- More than it: the capturing calls are told the Session can run at
most that many of them at a time and to send at most that many in one
batch, and the refused reservation is not retried.
- Otherwise, a batch of several capturing calls is also told it may
send fewer of them, since fewer may fit while other tools run. A
single capturing call keeps the old answer.
- Calls that reserve nothing are told the batch was refused for other
calls, and that they can go in a batch without them. This applies
under a 409 as well.
A 409 keeps its answer to the capturing calls, and so does a 429
without a usable count, apart from the "send fewer" hint above.
Refs #14518 (items 1 and 2).
A third method of HostedAutomationFaultGatesIT, with four cases:
- store-transport: the store commits a manual run's revision 1 and its
answer to the Harness is lost; the store client's retry meets the
commit the store holds, and the run fires once with no restart;
- revision-1 / revision-2: the journal refuses one revision of a manual
run's fire; after the store client's three attempts the caller gets a
retryable 503 automation_operation_unknown, the Session writes nothing
more until it is reloaded, and after a cold restart the same key fires
the same run once;
- projection: the schedule mirror's projection of an update is refused
inside the commit's transaction, so the revision rolls back with it;
after a restart the retry commits revision 2 once.
Only this method fronts the Session Store with a relay and runs a 15 s
writer lease, so a restarted Harness takes the Session over quickly.
Four mutation witnesses are red, one of them a bundle patch of the
Harness's write latch. Found while writing it: right after a restart the
public routes pass the cold Harness's writer conflict through as
409 managed_session_writer_conflict, which the gate retries through.
Also folds in #14551's two teardown nits: each trigger drop gets its own
try, and the refusal table is dropped too.
The method measures 62.1-72.4 s and is budgeted at ~145 s: the healthy
sum moves to 5033 s and the fork to 5643 s, inside the Verify step's 105
minutes.
A recurring task that ages out is fired raw one last time and then
deleted. That final fire rebuilt its job from the on-disk task record,
so it carried the previous fire's lastFiredAt -- or none at all for a
task that never fired -- instead of a stamp for this fire. Consumers
read lastFiredAt as "Triggered at" and fall back to their own clock
when it is missing, which a buffered delivery makes arbitrarily late.
Stamp the final jobs with the detection minute the way the catch-up
branch already does, and pin it with a test that fails on the previous
behaviour.
Part of #11738
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Keep the exclusive root UPSERT as the single initialization path before creating the Session event stream.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(managed-agent): verify ambiguous Linux workspace birth times
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): pin the #14461 recovery accessors in the session lock-order audit (#14505)
#14461 added hasLiveTurn and markTurnRecoveryBlocked to
ManagedAgentStore without updating the pinned turn-row accessor set in
SessionRowLockOrderTest, so the exact-set assertion fails on any tree
containing both — including this PR's merge commit, whose fast tier is
red because of it. hasLiveTurn is a read-only helper and
markTurnRecoveryBlocked is fenced by the live dispatch lease rather than
the session-row lock, and the audit finds no lock-ordering violation in
either, so both belong in the pinned accessor set.
* docs(managed-agent): note Maven report output breaks the root ESLint sweep (#14505)
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost>
* fix(managed-agent): preserve rotated keys during operator recovery
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): scope-key the Hosted lane's ma_resource helper reads
The FG7c adapter-stop and FG6c crash gates timed out in the hosted
process-fault lane when their test helpers read ma_resource by raw
tenant/session/resource IDs: the table's indexes are resource_key and
(session_scope_key, resource_id), so those reads full-scan a table the
whole shared fork accumulates into. Lead the same reads with the
session scope key, keeping the raw IDs as residual predicates — the
shape production reads and HostedGateTestSupport already use.
Timeouts and assertions are unchanged.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
The hook published canUp/canDown for history navigation buttons that were
never built, and recomputed them on every push, navigation step and reset.
Each recompute allocated a fresh object, so React re-rendered the host
composer six times per navigation cycle to update state with no reader.
Remove the state, its sync helper, the six call sites and the stale comment
describing the missing buttons, and add a render-count witness so the wasted
re-renders cannot return silently.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(channels/base): strip zero-ink code points from display projections
PROMPT_UNSAFE_INVISIBLES enumerated the C1 block, Cf and the variation
selectors, so a projection built only from Default_Ignorable fillers
(U+3164/U+115F/U+1160/U+FFA0), U+2800 or a lone surrogate survived
untouched: the line rendered as empty while still carrying
attacker-chosen bytes.
Derive the set from Unicode properties the way git-remotes.ts already
does, and list U+2800 plus the lone-surrogate range explicitly since
neither is Default_Ignorable. The u flag matches by code point, so an
intact surrogate pair never lands in that term.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(channels/base): keep advertised attachment paths byte-resolvable (#14510)
The widened PROMPT_UNSAFE_INVISIBLES class also backs sanitizePromptPath,
which renders the `saved to:` path sent to the model, while adapters store
attachments under the original basename (DingTalk: basename(fileName)). A
filename carrying a zero-ink char (U+3164 HANGUL FILLER, U+2800 BRAILLE
PATTERN BLANK, a lone surrogate) was advertised with a space in its place,
so the read-file tool got ENOENT for a file that exists on disk.
Keep a path-scoped strip set at the pre-widening class (line-breakers, bidi,
C1, Cf, variation selectors) for sanitizePromptPath; zero-ink chars cannot
break or reorder the rendered line, so stripping them buys nothing and costs
resolvability. Adds a regression asserting byte-identity for both chars.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
* test(managed-agent): fix the FG12d post-merge evidence and ledger P3s
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): resolve the FG12d case letter before boot can fail
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
A workspace-scoped delta must not reach a session admitted at a different root, and the change must still be acknowledged. The user-scope positive case landed in #14525; this is its discriminating pair, so dropping the workspace filter turns it red.
Refs #12737
* test(managed-agent): add the FG11b Spring-side automation store-fault gates (#14446)
A second method of HostedAutomationFaultGatesIT, on the FG11a boot, with
six cases sharing one minute boundary:
- occurrence-insert: a refused occurrence record fires nothing, and the
slot fires once after the refusal drops;
- occurrence-settle: a refused settle keeps the occurrence firing and
re-driven against the same run, one run chain and one model Turn;
- watermark: a refused watermark advance re-derives the slot every tick,
and its recorded decision stands;
- receipt-claim / receipt-settle: a refused request receipt relays
nothing, or re-drives the same operation id once it drops;
- revision-race: a fire parked while an update commits revision 2 meets
automation_revision_stale and is re-pinned and fired once, never
skipped.
Refusals are tenant-fenced MySQL triggers (the FG9b/FG10c pattern); the
relay gains a hold that parks an operation before the Harness. Six
mutation witnesses are red; one single-layer watermark mutation is
equivalent and recorded. The Harness-side store faults are slice 2.
The method measures 30.8-64.5 s locally and is budgeted at ~130 s: the
healthy sum moves to 4738 s and the fork to 5348 s, inside the Verify
step's 100 minutes.
* test(managed-agent): pin FG11b's recovered occurrence to the refused slot (#14446)
Review round 1: occurrence-insert took the first recorded scheduled
occurrence after the refusal dropped, without checking it was the slot the
trigger refused. A regression that skipped past the refused slot, letting
the watermark advance, would pass on the next minute's fire.
The refusal row now records what it refused (the occurrence key, or the
watermark slot). occurrence-insert and occurrence-settle check that every
refusal names one slot and that the slot they recover is that one.
A/B with a mutation that swallows the refused decision so the watermark
advances past it: the previous assertions stayed green (163.3 s, the next
minute fired in its place); the new ones go red at "every refusal is the
one slot's record".
---------
Co-authored-by: 易良 <1204183885@qq.com>
The sealed-Managed arm of the lock-record validator is the only thing
that stands between a corrupt commit proof and a certified takeover
being handed that corrupt proof, yet nothing pinned it: mutating any one
of its four checks left the whole deterministic suite green, and a real
daemon archived the corrupted seal.
Corrupting only the commit proof of an otherwise valid sealed record now
has a witness, so acquire fails closed on a malformed lock instead of
treating the record as a seal. A fractional, textual, negative or absent
sequence and a short, uppercase, non-hex or array-wrapped hash are each
rejected; a zero sequence stays a genuine seal, pinning the bound as
>= 0 rather than > 0.
Each of the four checks is now load-bearing: dropping the safe-integer
guard, the non-negative bound, the string guard or the 64-hex pattern
turns at least one new case red.
Part of #13443
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The main CI watcher and the PR scan share one infrastructure-signature
list. A per-host SDK Java test lock starving past its flock budget carried
no signature, so main run 39104863059 filed #14550 for a lane in which no
test ever ran.
The annotation names a lock budget, not an assertion: several runner
instances share one HOME on the ECS pool, so a fast-tier job can wait
behind sibling Maven holds that outlast its own acquisition budget. Main
runs now rerun that class once instead of filing, and a red check on a PR
is reran once; the existing attempt-2 guard still files when the
starvation is not transient.
The new pattern also covers the E2E and release docker lock messages,
which share the same starvation semantics.
* test(managed-agent): cover workspace-change request validation
A workspace change with a non-integer revision or an unknown kind is rejected before it applies anything, and neither answer can be mistaken for the acknowledgement the Bridge expects. Pin both.
Refs #12737
* test(managed-agent): discriminate the workspace-change validation error
Assert the validation message instead of any throw, and use a non-string kind, so removing a check cannot pass the case through an unrelated TypeError.
Refs #14525
* test(managed-agent): pin that a user-scope permission change reaches a worktree session
A user-scope permission delta must reach every live session in the child, including one admitted at a worktree root. The workspace-scope filter must not silently drop it while the change is acknowledged. This is the audience half of the permission path; the workspace-filter negative case follows.
Refs #14525
* test(managed-agent): keep the unknown-string kind rejection covered
Switching the second case to a non-string kind pinned the type check but stopped exercising the switch default arm. Add a third case with an unknown string kind and assert its own message, so the default rejection cannot be removed while the suite stays green.
Refs #14525
---------
Co-authored-by: 易良 <1204183885@qq.com>
* test(managed-agent): add the FG12d MCP release fault gate (#14447)
* test(managed-agent): pin the FG12d release and settle witnesses
Require the held release dispatch to answer running before the kill, so
a synchronously settled release cannot satisfy the lookup witness; filter
the status witness to a settled 200 for the original release id; assert
the released server exits once instead of claiming it survives; accept
the lifecycle 409 conflicts while retrying settle_resources; and correct
the docs' saveEffects/status/timing claims.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(managed-agent): tighten the FG12d settle retry and status target
Retry only hosted_turn_active on the lifecycle route; a
hosted_lifecycle_operation_conflict cannot self-heal and should fail
with its body. Join the settled-status witness to the original
runtimeSessionId, and correct the docs' latest measured seconds.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
* test(managed-agent): pin the FG13d cancel against a stopped worker
SIGSTOP the durable worker while the command holds, then cancel through
the Harness route. The owner cannot answer the lookup, so the record must
stay recovery_blocked/outcome_unknown and keep the unit populated instead
of inventing an end. Adds 150 s to the shared fork budget and records the
matching mutation witness in the design.
* test(managed-agent): hold the stopped-worker fixture indefinitely
SIGSTOP freezes the worker, not the fixture's own hold timer: a 60 s hold
would end during the two 30 s owner lookups, kill the child and write the
allow, so a correct implementation could fail on scheduling. Hold until
teardown instead, matching the worker-kill cells.
---------
Co-authored-by: 易良 <1204183885@qq.com>
The reconcile_run route can run a crashed wake turn's aftermath itself:
when the Session's pump is not running one, the route claims the turn
slot, settles the crash, lifts the recovery block and answers
`repaired: true`. No test reached that branch. The existing "repairs
through the reconcile route while blocked" test is settled by the
pump's own aftermath, and its route only reports. Dropping the branch,
dropping `repaired = true`, dropping the route's liftRecoveryBlock,
dropping its claim of the turn slot, or dropping the `finally` that
hands the slot back all kept every test green.
The new test lets the pump's first aftermath pass lose its Broker
release, which leaves the Session recovery-blocked with the crashed
input pending, and parks every later kick so the pump cannot retry. It
waits until a retry kick has been parked, so the pump's pass is over.
A reconcile whose release is refused too must answer `repaired: false`
and leave the block and the input in place. Once the Broker answers,
the next reconcile must consume the input while holding the turn slot,
answer `repaired: true`, release once, consume exactly once, lift the
block and hand the slot back. A further reconcile only reports the
settled run, and the Session closes with 204.
Co-authored-by: 易良 <1204183885@qq.com>
* test(managed-agent): add the FG11a automation lost-reply fault gates (#14446)
The first packaged lane that turns automation on: in-JVM Spring with the
scanner running, a real Harness behind a relay that drops one committed
answer, real MySQL and a model stub.
Eight cases share one boot and one minute boundary:
- create-reply, update-reply (a newer update lands meanwhile),
noop-update-reply and retire-reply: the retry answers the committed
revision and never commits again or over a newer one;
- manual-run-reply and scheduled-fire-reply: the re-drive meets the
same arun_ run, with one run chain and one model Turn;
- reconcile-reply: a lost reconcile answer leaves the slot skipped
under its own refusal, never unknown;
- public-reply: a lost public 202 replays the receipt without a second
Harness operation.
Five mutation witnesses are red; one equivalent mutation is recorded in
the design. The fixture runs one Turn per Session first: the automation
verbs only load a Session on the Harness, so one created without input
is refused 404 managed_session_not_found.
The class measures 46.3-85.0 s locally; it is budgeted at ~170 s, so the
shared fork's healthy sum moves to 4359 s and its timeout to 4969 s,
inside the Verify step's 95 minutes.
* test(managed-agent): keep FG11a's clocked cases off the scanner until armed (#14446)
Review round 1:
- The scanner ran from boot, so a minute boundary between a definition's
create and its armed fault, or its held manual run, let a slot fire
first: attempts 0 for scheduled-fire-reply, 409 automation_run_skipped
for reconcile-reply. SchedulerHold now parks the scanner's only thread
while the three clocked cases set up; manual runs fire on the request
thread and are unaffected.
- assertOneRun collected every fire and run command of the schedule, so a
legitimate second slot on a slow runner read as a duplicate. The
exactly-once checks are now scoped to the occurrence under test.
- The design docs give the Verify step's 95 minutes, describe both fixes,
and say a first red skips the later cases, which are re-run per case.