Files
qwen-code/docs
Shaojin WenandQwen-Coder b69338b143 feat(managed-agent): H4b child Session runtime (#13550)
* feat(managed-agent): H4a child agent and child acceptance record contract

Define the H4 delivery map as six slices and land the first: the
managed-child_run body for kind child_agent and the
managed-child_acceptance body, both dispatched inside the
managed-child_run/child_acceptance schema version 1 records, with
commit-time cross-record consistency and resource closure rules and
dual-language shared fixtures. The record-body registry's static task
kind becomes a function of the record so child_agent projects the
existing child_agent task kind while child_acceptance projects no
task. No domain is enabled for submission and no runtime path changes;
enabling child_run and child_acceptance is H4b's explicit,
server-first-deployed step.

Refs #12380, #12827 (slice H4a)

* fix(managed-agent): Tighten the child agent record contract from review

Answer the three review findings on the H4a contract: the result and
its terminal receipt now change only together and only in the revision
that settles the run, so a half-result can never wedge the chain; the
child Session identity is required once the execution proves creation
(running_attached or settled), not merely forbidden before it; and a
terminal child agent record freezes everything but its delivery line,
so a stop request can never be added to terminal evidence. Fixes are
mirrored in TypeScript and Java with new shared fixture witnesses and
eight killed targeted mutants across both validators.

Refs #12380, #12827 (slice H4a)

* fix(managed-agent): Close the H4a real-stack verified Java parity gap

The real-stack verification on the PR head found the Java child_agent
validator skipped the identity checks the TypeScript one makes on
childRunId, ownerScopeId and rootSessionId: bodies the writer would
refuse were stored (HTTP 200), and a Session holding one could not be
reopened - exactly the disagreement the server-first enablement order
depends on the store rejecting. Apply the verified candidate intact:
three id() calls at the end of requireChildAgent plus three shared
fixture cases, closing the differential to zero in both languages.

Fold in the regression-protection gap found with it: the store-level
acceptance binding test (run kind, completion call, receipt digest and
the launch input's resource closure) and the two authority witnesses
for the receipt binding and the closure, each proven red by its
matching mutant in both languages. The H4b delivery-direction
observation (delivery can outrun or contradict an acceptance) is
recorded as such in the design's follow-up section, in both languages;
it is not blocking for this slice.

Refs #12380, #12827 (slice H4a)

* fix(managed-agent): Gate shell capture admission on the shell-only parser

Round-2 real-stack verification found the remaining shell-only child_run
consumer missed in the parser widening: LocalShellStreamResultSession
proves a background Shell's start before admitting an output capture by
looking up the record by the capture's executionCallId, and since H4a
the widened parser would also parse a child_agent record as that proof.
A child agent with a matching call id would be admitted as a proven
background Shell start. It now parses with parseChildShellRun like the
cli callers, and a new witness refuses a child_agent record at
admission; the same-domain monitor confusion suite passes unchanged.

Fold in the remaining regression-protection gaps the same review round
named, one authority case and one store chain closing the settling
revision's result and receipt resources, mutants J7b/T7b killed by
each. the three F1 id removals JF1-JF3 and the reverse-parser witness
are proven red as well.

Refs #12380, #12827 (slice H4a)

* fix(managed-agent): Close the H4a child-agent contract's enforcement gaps

Real-stack review of #13505 (bot R1 round plus human P1/P2 round) found
the child_agent validator certifying records its own design forbids, with
the terminal freeze then locking every omission in permanently:

- require the dispatched definition pin and the Runtime binding that hosts
  an attached child Session, in both languages (the successor rules make
  both unaddable once dispatched);
- refuse Windows drive specs in workingDirectory in both languages;
- read resultVersion with a finiteness guard in Java, so 1e400 refuses
  instead of throwing NumberFormatException;
- bind a first-level child's rootSessionId to the journal's Session at
  commit time on both sides, and drop two successor clauses the terminal
  freeze made unreachable;
- teach verifyWorkspaceRestore to skip child_agent records instead of
  misparsing them as shells, give the shell-only parser its own refusal
  clause, dedupe the shell stop-reason logic onto the shared helpers with
  the same clause order in both languages, and derive ChildRunKind from
  the union instead of a dead parallel spelling.

The shared fixture corpora pin every new rule in both languages and the
74 pre-existing entries keep their merge-base formatting; the replay
harnesses now fail loudly on an unresolvable template; six successor
fixtures that never reached their named clause are restated or dropped;
eleven unwitnessed validator terms gain cases; the taskKindOf probes run
on real records; and the design docs correct the nine stale claims their
review round measured (citations, the unknown-delivery wording, the
depth-cap relationship, V48+).

* feat(managed-agent): H4b child Session runtime

Slice H4b of #12827 (Stage H of proposal #12380), stacked on H4a's
child/acceptance record contract: the child Session runtime — the four
producers of the H4a bodies, the first cross-Session dispatcher, the
close cascade, and kind-selective enablement, on the server-first order.

Parent side: the Agent tool joins the Hosted Workspace Shell-lane
declaration set behind a per-kind gate; startChildRun commits the launch
intent with the definition pin and the derived record key before any
side effect; the foreground arm waits on the committed chain alone and
answers from evidence, the background arm returns a task handle and
takes its result through a durable notification input plus wake, with
accepted delivered after the answer and consumed after the consuming
turn settles — two independent, separately observable facts.

Control plane: V51 lineage columns, the delivery-pending index and the
relay ledger qwen_managed_child_result_relay (the reserved task-event
name stays reserved); createChildSession under the parent's exact
binding stamps parent/root lineage idempotently from the launch record
key; the relay drives creation, attaches, copies the terminal result
and receipt digest-bound, submits the acceptance, marks orphaned at a
closing parent and unknown past bounded retries, never re-executing.
The close cascade in SessionLifecycleCoordinator cancels non-terminal
children through the ordinary close pipeline before the Harness close.

Gates: child_acceptance joins the plain enabled list; child_run gains
MANAGED_SESSION_ENABLED_CHILD_RUN_KINDS=['child_agent'] so H3's shell
stays disabled — and the owed delivery-direction decision landing as
the reverse commit-time check in both languages (delivery reaches
accepted/consumed only with its acceptance record, and never returns
to unknown/rejected after one). Public OpenAPI, Flyway-adjacent schema
and WebShell surface do not move.

Validated: core 2310/2310 and cli serve sweeps green (timing-sensitive
flakes documented in .qwen/e2e-tests/managed-child-session-runtime.md,
including one pre-existing failure reproduced on the base tree);
managed-agent-server surefire green except one pre-existing integration
failure reproduced on the base tree; 12 targeted mutations (7 TS + 5
Java) all killed; dual-language contract replays green.

* fix(managed-agent): Close the H4b review round's relay, cascade and wake-consumption gaps

The review round found five child-runtime gaps this commit closes:

- findChildLineage returned a null row for a root Session through
  Stream.findFirst() and threw, breaking every Hosted Session create.
- The relay binding probe read the tool-session Runtime record, which a
  text-only child never acquires; it now reads the physical Runtime
  binding that warm() creates when the Hosted tool turn is constructed,
  and the tool-session read path is retired with it.
- The close cascade treated a child created but not yet attached as
  unstarted; it now resolves the child through the relay ledger before
  judging, and closes it through its own Session lifecycle (admitted
  idempotent close, owed until terminal Session status) instead of a
  bare daemon DELETE — child creation inherits the parent creator's
  workspace grant so that admission passes the creator checks.
- Journal debt (stop or terminal revisions faltering) re-arms the
  whole close instead of being dropped, with the acceptance-answered
  early-out restricted to arms past delivering so the relay still owes
  its own mark_accepted.
- The wake-turn pump treated its blocked sentinel branches as settle
  proof and consumed the delivered child result without a Turn; the
  consumption wrapper now requires a genuine settle.

Terminal-classified relay rows also leave the discovery set so a
grown ledger cannot starve the fleet scan, the funnel routes conflict
and record refusals as typed errors instead of message matching, the
turn-settle flush keeps the unconsumed remainder owed when it dies
mid-commit, and the design documents carry the corrected semantics in
both languages.

* fix(managed-agent): pass the MariaDB-lane Java checkstyle gates

The pushed H4b review fixes left a trailing-whitespace javadoc line, two
imports eroded by the relay binding probe change, and an empty switch
default written in a shape both checkstyle rules reject; the lane runs
checkstyle:check that the local mvn test path does not.

* fix(managed-agent): reconcile the H4b create→attach window, lost delivering advance, and attach-less settle

The second review round proved three gaps in the first:

- A child whose create answered (Session and lineage committed) while its
  ledger advance died was judged unstarted by the close cascade: the
  cascade now consults the relay ledger and then the committed lineage
  row, so a missing ledger id never proves not_started.
- Settling such a ledger-only child committed close_scope(started=true)
  over a body whose childSessionId is null, which the record parsers must
  reject: the cascade first replays the dispatch_started/attach chain
  from the proven evidence — the bodys own committed dispatch facts, or
  the physical Runtime binding the child warms with — so the settling
  revision always commits over a chain the parsers accept.
- An accept committed while the delivering advance was lost left the
  ledger watching with the answered acceptance short-circuiting every
  scan: answered acceptance short-circuits only arms the relay never
  owed, and a watching row reconciles through the same idempotent walk
  (replay the result, the acceptance, then the advance).

* fix(managed-agent): tolerate repair journal failures and replay committed child results

The review round on the create-attach reconciliation found two more:

- The repair ops' refusal/transport-ambiguous throws are plain
  RuntimeExceptions (DaemonHttpException, MutationOutcomeUnknownException),
  which escaped the IllegalStateException guard and skipped the child's
  own physical close — the reconstruction's journal debt no longer
  preempts the physical effect.
- A commit_result whose acceptance reply was lost minted a second terminal
  revision over frozen result references: settleCompleted now answers the
  committed copies after byte-equality with the published resources, so a
  replay returns the original result instead of a revision the terminal
  rule must refuse.

* fix(managed-agent): expect the hook-listed Agent tool in the hosted tool-turn E2E driver

H4b advertises the Agent tool on depth-0 Shell-lane Sessions behind the
child kind gate, so a shell-profile fake-model assertion pinning exactly
[edit, monitor, read_file, run_shell_command, write_file] legitimately
now reads agent first; the other lanes keep their exact vocabulary as
designed (files-driver unchanged).

* fix(managed-agent): commit tool-arm consumption only after durable Turn settlement

The tool-arm consumption flush fired before the turn_result commit: a
crash between them, or a turn-result write that keeps failing through
the outer retry, left child records durably consumed while the consuming
Turn never settled. The flush now runs only after the Turn settlement
commits — the wake turn's requirement applied to the tool arm too; and a
mid-flush death still keeps the remainder owed in the queue, so the
recovery drive can finish each committed side.

* fix(managed-agent): wire the real H4b stack end to end

A real-stack verification rig of head proved three blockers and three
follow-ups, every one confined to seams mocks could not reach:

- The relay got a null Runtime Broker in the real Spring app: the owned
  service never became a bean, so scan never walked a single pending
  row. Its owner now exposes service() and the configuration registers
  it with destroyMethod = "" (lifecycle stays with the Broker), and the
  relay tolerates missing wiring as a retry instead of a null drive.
- terminalResultText filtered part_type = 'text' while the only writer
  stores assistant text as 'output_text'; the query and its fixture now
  agree with the written vocabulary.
- The sent-arm notification escaped its full text into the bundled
  input, so an XML-hostile 20 KiB result rejected accept at the inline
  bound: the notification now carries the design's bounded summary —
  escape-then-bounded below the inline cap with a marker — and full
  bytes stay on the acceptance record. Its <task-id> is the task_ id the
  launch itself returned.
- A foreground child + any tool deadlocked on the shared Workspace mount
  with its waiting parent: the mount is taken only for a batch with a
  non-agent tool, never held across an agent-only wait.
- A done child kept an ACTIVE Session and its worker forever: the relay
  now admits the child idempotent lifecycle close (shared with the close
  cascade through a new ChildLifecycleAdmissions component).
- A child still RUNNING no longer eats the attempt budget — deferred
  without attempt++, scheduling is not failure — and the discovery set
  resurfaces delivering ledger rows whose record raced past accepting,
  so their owed mark_accepted/classify-done step always completes.

* fix(managed-agent): close the round-3 seams the second rig and bot proved

The re-verified real stack and the bot's CHANGES_REQUESTED round found
the rest of the same family:

- The bundled notification still wedged: the bound measured the XML
  text, but publication JSON-serializes the envelope — and the wake
  turn's own message envelope would blow next. Bound now: packaged
  JSON bytes against a 48 KiB budget (the design's summary, never the
  whole answer), computed over the multiline-friendly stripping that
  keeps newlines instead of gluing lines.
- Refuse a foreground agent call when the parent turn already holds the
  Workspace mount or the batch has a non-agent sibling — the deadlock
  cannot start rather than burn to the deadline.
- A redriven (recovery-continuation) turn consuming child results now
  drains the same per-id consumption flush; previously its declare-only
  path would silently pass the queue to the next unrelated turn.
- Discovery resurfaces also the watching arm behind consumed/accepted
  deliveries, and deferral/leased rows no longer occupy the bounded
  fleet window ahead of real due work.
- classify can't be rewritten by a stale claimant (owner guarded in the
  WHERE), and the Runtime binding probe answers only live READY rows —
  newest READY, not the lexical max across inert generations.
- A done child's close admission runs before classify(done): the row
  stays owed and retried until admitted, so a falter keeps the close
  recoverable rather than an invisible ACTIVE orphan.
- Gradle-proof witnesses everywhere the round was blind:
  RuntimeBrokerConfigurationTest pins the bean's existence, and
  RuntimeBrokerServiceBindingReadTest pins the READY-only filtering.

* fix(managed-agent): answer the bot's full round review, first batch of fills

Review round answered inline by thread:
- Replay halves of the funnel locked into the one serialization:
  settleCompleted (guard + publications + commit) and accept (check,
  notification, commit) can no longer mint on a racing re-drive.
- Notification text: multiline strip with code-point slices (no glued
  lines, no unpaired surrogates), bounded by packaged JSON bytes under a
  48 KiB budget per the design's summary shape.
- Foreground agent already refused while the mount is held or the batch
  shares with a non-agent tool — deadlock never enters v1.
- Discovery pulls the watching arm when consumed ran ahead; deferred and
  leased rows no longer squat the bounded window; classify is owner-
  guarded; pending discovery also serves the watching arm behind a
  committed acceptance.
- Binding probe answers only READY (newest READY), never the
  lexical-max across inert generations.
- Tool-arm completion witness pins the no-notification shape; coordinator
  cascade test now asserts ordered sequences and the derived dispatchId.
- New witnesses: discovery columns, consumed pre/post-acceptance halved,
  child_agent wake intake, config bean pins, binding-read, authority gate
  commit, multiline/JSON size bounds.
- RuntimeBrokerService delete-safe javadoc row and creation-key delegation;
  docs: six ops belong to managed-child-operations, decision 5 sent-arm
  pinned as the bounded summary + complete acceptance, and the plan says
  the reverse gate is pinned at authority/store level in both languages.

* fix(managed-agent): refuse a malformed accept-notification as 400

Present-but-wrong-shape notification values were silently dropped by
`object()`, so an accept op arrived with nothing. A non-object or a
missing-description notification is now a typed refusal instead of a
silent no-op.

* fix(managed-agent): expect V51 in the workspace migration golden list

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

* fix(managed-agent): answer the round-4 review and the R1-5/R1-9 recovery gaps

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

* fix(managed-agent): pin the child terminal-result query's two decisions (R1-90)

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

* fix(managed-agent): recheck mount ownership after hook side effects and settle the fixture wake before close

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

* fix(managed-agent): translate the oversized launch envelope into its byte_limit refusal (R1-8)

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

* fix(managed-agent): close the R1-round's deferred Criticals and pin their regressions

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

* fix(managed-agent): fire PostToolUse for admitted agent launches (R1-62)

* fix(managed-agent): keep the give-up's close-and-settle debt alive and read durable dispatch evidence on resume

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

* fix(managed-agent): renumber the H4b migration to V52 past main's V51 lifecycle

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

* fix(sdk-java): give the unconfirmed lifecycle-detach heartbeat room on busy runners

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

* fix(managed-agent): reconcile attach from the parent record at give-up and admit claimed child cleanup past the lifecycle fence

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

* fix(managed-agent): give the fence its claim kind and keep stale authority out of the client

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

* fix(managed-agent): authorize claimed child cleanup through the lifecycle commit gate

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

* fix(managed-agent): settle Shell-lane parents and children without Workspace close

SessionLifecycleService admits protocol 1 only for the Files profile;
the Shell lane keeps the protocol-0 close whose child cascade passes
ordinary authorization, so a H4b parent close/delete no longer ends
RECOVERY_BLOCKED workspace_lifecycle_profile_unavailable.

ChildResultRelay no longer holds a parent's settlement hostage to a
close the host cannot dispatch: without Workspace-close support the
relay still settles failed and over-bound child records and retires
the ledger row instead of retrying forever.

The harness-session route test now pins the lifecycle authority
stamped on the Store client at verb-commit time.

* fix(managed-agent): retain the child-close obligation as a discoverable close debt

On a host that cannot close Workspace Sessions at all, retiring the
relay row alongside the parent settlement discarded the only durable
record that still owed the child Session its close: the child stayed
ACTIVE with no ledger scan and no close cascade able to find it again,
even after close capability returned.

The settlement now still commits on time, but the row parks as
close_debt — claimed, named by the child id, due on the heartbeat, and
surfaced by the discovery page's cancelled arm — until a capable scan
admits the close and only then retires. A parked debt outlives the
orphaned early-out and never re-enters the give-up chain, whose
settle-and-classify already happened for this row.

* fix(managed-agent): renumber the child lineage relay migration to V53

#13163 took V52 for the mutation-attempt sequence while the branch was
in flight, so the merged tree carried two V52 migrations and Flyway
refused every context. The golden-list IT now expects 48-53 and passes
against a live MySQL on the merged migrations; the module's 1439 tests
pass with the renumbered layout.

* fix(managed-agent): reconcile the close obligation before orphaned classification

The settlement of a child run can be committed while its close_debt
ledger write never lands — a crash or a lost reply between them. If
the parent then closes or deletes before the next scan, the settled
task is already invisible to the close cascade's live-scope walk, and
the orphaned classification erased the ledger row that was the only
durable holder of the owed close: child ACTIVE, zero CLOSE operations,
zero future discovery.

The non-ACTIVE-parent branch now reconciles before it classifies: a
settled delivery whose child Session still stands (row id or committed
lineage, short of CLOSED/DELETED) parks as close_debt instead and
discharges through the ordinary admission. Unsettled rows classify
orphaned exactly as before — the cascade owned their stop.

* fix(managed-agent): read the close capability once in the give-up chain

The admission order and the retention flag each re-read closeSupported();
a TOCTOU flip between the two could mark the debt settled without ever
admitting the close. One cached read now feeds both.

* test(managed-agent): pin the give-up chain to a single capability read

Sequencing closeSupported() true-then-false must still admit the close
and call hte accessor exactly once — the TOCTOU window between a double
read is what lost the debt silently in the first place.

* docs(managed-agent): renumber the design's migration cites to V53 and record the close debt

The renumbers (V51 took main's lifecycle, V52 #13163's mutation attempt)
left both language versions citing a migration id that no longer exists,
and the close-retention machinery added since the doc landed had no
ledger description. Both versions stay synchronized: V53 everywhere plus
the close_debt / orphan-reconciliation paragraph in decision 8.

* fix(managed-agent): retain the close obligation at task settlement, from every intermediate ledger state

Task settlement leads delivery settlement: the run record settles and
projects task completed while delivery still says accepting, so the
close cascade's live-scope walk never sees it — the retention predicate
must read task-settled evidence, not terminal delivery alone.

The discovery page's settled arm now admits every non-terminal ledger
state too: a give-up whose settle committed but whose close_debt write
failed sat in binding, outside the old arm-2 list, permanently
undiscoverable even before a parent close. Arm 2 now mirrors arm 1's
terminal-only exclusion; the in-flight work sets stay disjoint by
delivery, and the due/lease predicates still own eligibility.

* fix(managed-agent): never re-enter the walk for a settled-cancelled record

Widening arm 2 to every non-terminal ledger state made the settled arm
discover a creating/binding row whose create-or-fail reply or terminal
classification write was lost. On the next scan work() replayed the
record into create() and actually launched a child already recorded as
creation_failed / not_started_proven.

The walk now reconciles such rows instead: a standing child owes exactly
its close (parked close_debt, ordinary discharge), and with none the row
classifies unknown. The settled arm keeps reaching them; only the
creation/execution arms close.

* fix(managed-agent): keep retained close debts from starving new child work

On a host that cannot close Workspace Sessions, every finished child
parks a close_debt row due again every five seconds, and the discovery
page ordered by created_at always served those oldest rows first —
about 150-200 delegations in, the freshest launch is never reached and
no new child Session is ever created (real-stack evidence on the macOS
rig).

Breaking the starvation breaks nothing of the retention: due debts now
sort after every other owed row on the page, and a host that cannot
discharge a debt at all revisits it every 300 seconds instead of every
heartbeat — nothing could land before a restart returns the capability,
and a capable scan still discharges on its very next visit.

* fix(managed-agent): fence relay creation recovery against settled evidence at every seam

The same lost-write interruption can hide behind three seams, and each
authorized the wrong outcome on its own snapshot:

- The claimed walk read the discovery page's captured delivery. A record
  settled between the page and the claim retired or relaunched on the
  stale snapshot; retirement authorization now re-reads the committed
  projection (falling back to the page only when the row is gone).
- A stalled creator whose relay lease lapsed minted the Session anyway
  after another worker's give-up committed creation_failed and retired:
  the creation admission never read the run's terminal verdict. It now
  fences that mint inside its own transaction with FOR UPDATE on the
  run's task line, so a settled record refuses and unanswered replay
  still answers.
- A give-up with a lost relay session id resolved no attach and read
  no lineage: it claimed creation_failed while a real child ran. The
  child now resolves from the committed lineage before the proof, and
  a vetoed attach replay consults the child's own committed Turn
  before started=false may be claimed.

* fix(managed-agent): never admit children queued behind a cancelled wait

A batch whose foreground wait was abandoned by cancellation returned
control to the batch loop, which then admitted every agent call queued
behind it — work the cancelled turn never started, including a
successful 'started in the background' answer. The admission gate now
checks the abort signal before describing, bounding or stamping
anything; keeping an already-launched child running stays documented.

* fix(managed-agent): close the launch-admission edges codex flagged behind the quota line

Three adjacent admission edges all authorized against the wrong party:

- Quotas counted the replayed child: a re-driven batch whose siblings
  filled the active run quota got count_limit for the launch it was
  already running. The record lookup now precedes the quota verdict,
  and children.admit's replay identity answers as before.
- The background workaround was recommended where it deadlocks: a
  Session-owned mount (Hook catalog or MCP owner) survives the Turn
  and releases only at Session close, so a background child launched
  under it parks on workspace_busy. The background arm refuses there,
  and only turn-sourced holds keep the foreground hint.
- A wake turn's id embeds its commissioning run id, so chained
  background launches grew one suffix per hop into the 128-char
  lineage bound by the third hop. The launch key from a wake turn is
  now a sha256-truncated digest of the same stable name — replay
  deterministic, hop-bounded, and lineage-parser-legal at every hop.

* fix(managed-agent): never let a verdict race past a still-committing settlement

Two seams in the same family both read pre-collapse evidence and then
acted durably on it:

- Between the walk's read of the projection and the orphaned
  classification commit, another worker's settlement can still land —
  and the classification would orphan the child that settlement just
  made owable. The verdict now locks the record row FOR UPDATE inside
  one transaction for the re-read: losing that race only says the truth
  settled, and exactly one fresh evaluation assigns the close debt
  instead of retirement.
- A give-up's  verdict committed on the no-child proof
  while the stalled creator resumed and minted between the proof and
  the failure commit. At the commit seam, the lineage is re-read: new
  child evidence arrives after the proof owes one more bounded wait,
  never the confidently wrong pairing.

* fix(runtime-broker): read every page for the newest READY binding

findLatestBindingByHarnessSession returned from inside the first page
that held any READY row (and advanced the keyset cursor only when it
held none), so a greater-id READY binding on a later page was never
considered — the live Runtime identity for a child's first controlled
binding could pin a stale generation on generation-lite bindings. The
whole bucket now traverses, keeping the greatest READY id seen, and
returns after the traversal ends.

* fix(managed-agent): retention defaults on the standing child, never the racing verdict

A classification now orphaned only children that no longer stand —
whichever side wins the settle-vs-verdict race, a child that still
owns its Session keeps its one discoverable holder, ending the
orphan-burial shape in both directions at once (forward settle-then-
verdict and inverse verdict-then-settle). The atomic
classify-when-unsettled from R20 folded away in favour of the simpler
default; orphaned remains for the child who genuinely stands no more.

* fix(managed-agent): stamp out three launch-side certification gaps codex pinned

- A non-plain-object lineage field on session create now knows it:
  arrays (which object() maps to null) no longer stand in for accepted
  ancestry; only absent/null lineage is root. Anything else is the
  debuggable 400 invalid_hosted_lineage, never a 200 root that carries
  a malformed ancestry silently.
- The restarted-wake-turn recovery probe asks the durable record about
  child dispatch under the same collapsed key the launcher wrote —
  acceptChildAgent's wake-turn key derivation is now shared with the
  PostToolUse re-query (sha256-truncated digest of  identities), so resume finds it instead of losing
  ded though no test pinned its wake shape before.
- A lifecycle claim stamped by the children/operations middleware now
  restores on EVERY exit of the route — including the early-refusal
  arms (invalid operation, unavailable profile, blocked session) that
  previously ran before the try and leaked the claim onto the shared
  store client for the rest of the process where a future claim's
  generations match no longer comparison.

* fix(managed-agent): prove close debt by fact and address the fence by key

Two avoidance-class truths respected:

- A close debt whose child no longer stands is discharged by fact,
  not retried by suspicion — dischargeCloseDebt now re-reads the
  child's status before it admits, so an already-closed/deleted child
  never faces a permanently-refused admission, and an unproven-null
  read does not discharge against an unconfirmed child that we know
  exists. Default mock fixtures now answer ACTIVE for a child whose
  recovery would need it.
- The child-creation terminal-truth fence addresses the parent's real
  launch row by primary key (session scope + record key, the same key
  the idempotent admission derives), so its FOR UPDATE never becomes
  a cross-tenant table scan against MySQL's default REPEATABLE READ.

* fix(managed-agent): settle close-road blockers by evidence, not retry or wire-code faith

Three segments, each adjudicated prior to code:

- On a host that cannot close Workspaces, the close cascade's child
  close admission is fake-owed, and the delivery taxonomy gets a plain
  IllegalStateException it cannot file — the parent retries forever
  under an unnameable retry instead of the typed blocked code it
  already uses when the cascade had nothing to do. On such a host the
  admission is simply skipped, and the parent close proceeds into that
  typed gate.
- A lineage-named child with no body facts, no ledger creation key
  and no READY binding has no replay source: that evidence settles as
  creation-never-attached with started: false once, not an eternally
  re-armed debt on empty proof.
- A give-up's creation_failed verdict is now proved off the record's
  own transition field: intent (legally holding no attach or past
  dispatch successors) is answered never-started were nothing physical
  contradicts; dispatch_started or past is answered started; a
  physically-ran child with an intent record whose binding cannot
  name its dispatch answer gets deferred instead of invented verdicts.

* fix(managed-agent): let a never-started verdict name its minted Session

The create→attach window leaves exactly one durable fact — the minted
child Session's lineage — and the terminal revision that retires such a
run could never record it: the validators required the Session to stay
null until a dispatch admitted it, to have a Runtime binding, and for
creation_failed in particular to be anonymous. A retired run then left
its mint behind with nothing able to discover it again.

The contract now lets a not_started_proven settlement carry
childSessionId with no binding — bounded to that one terminal, the only
state with no dispatch to host from. The fail/close_scope operations
accept an optional childSessionId, the builders restate the same id
replay-safely and refuse a rename with child_operation_record, and the
shared fixtures pin the newly-lawful shapes in both languages.

Pins: core record/operation suites (190) replay the amended fixtures
and the naming/rename rules; the harness-session route pin drives the
wire contract end to end (named 202, replay 202, rename 409, malformed
400). Prettier, ESLint and tsc clean in cli and core.

* fix(managed-agent): arbitrate terminal verdicts against the mint at commit

R22's give-up wedge: executionState read the runtime_state projection
(unbound/provisioning/ready) while the caller compared execution enum
values, so intent and dispatch-started rows never matched — every row
looked started, the refused started verdicts repeated forever, and the
owed work never retired. The proof now reads the committed record body
through its inline resource; an unreadable body owes a bounded retry,
never a guessed verdict, and a self-proven not_started_proven execution
pairs the unstarted verdict directly.

R21's inverse race (a give-up verdict evaluated over an empty lineage,
then the mint lands) closes at commit time: ingestion of any
not_started_proven terminal revision locks the run's own row FOR UPDATE
— the same row the creation fence reads — and requires the verdict to
name exactly the Session the lineage proves minted, or refuses the
commit. The refused verdict defers the relay, whose retry re-reads the
lineage and names the child; every give-up verdict now names its child
whenever the child is known.

Mutation witnesses, all reverted byte-identical afterwards: a
runtime_state read fails the execution-body pin (an intent fixture
answers unbound); a no-arbitration gate fails the two refusal pins and
passes the two admission pins; dropping the verdict's naming fails the
relay's intent-family naming pin.

Suites: relay 36/36, relay store 13/13 (new execution-body pin and the
never-started pairing), verdict-reconcile decision table 4/4, creation
fence 3/3, and the full managed-agent-server verify green (checkstyle,
SpotBugs, 117 suites).

* fix(managed-agent): bind the close cascade's started call to any-state evidence

A creation key is an attempt id, never a start, and a READY binding's
absence says nothing — R22 reproduced both halves at this head: the
cascade wedged a parent in CLOSING behind a ledger-known child whose
binding had retired (the repair demanded warmth the retired row could
never offer again), and it committed not_started_proven over a child
whose own Turn had durably completed. A start is now proven by the
body's committed dispatch facts, by a binding row in ANY state (the new
findLatestBindingByHarnessSessionAnyState — a retired RELEASED/LOST row
still proves its dispatch and names the repair's identity, while the
relay's repair-for-warmth read stays READY-only), or by the child
Session's own durable Turn.

With none of the three, the never-started pairing settles started:false
and names the Session the lineage minted; with any, the repair rebuilds
dispatch and attach from that evidence and the settlement commits
started:true. The READY-read Javadoc is also reworded to name the rule
the method implements (the greatest READY binding id across the full
traversal; generation is not consulted), closing the R1-16 follow-up
note that the old comment argued against the rule.

Mutation witness, reverted byte-identical afterwards: dropping the
binding/Turn evidence from the started call fails both new cascade pins
with the exact R22 shape (started=false over proven execution). Suites:
coordinator 11/11 (two new reproductions plus the named unstarted
baseline), broker binding-read 2/2, and the managed-agent-server
verify green with the broker chain reinstalled. Design amended in both
languages.

* fix(managed-agent): close the arbitration on the production topology, the retired binding, and the pre-admission Turn

R23 reran the preserved schedules on the arbitration head and broke each
side of it in one class apiece:

- The verdict/mint seam reopened on MySQL 8.0.46 REPEATABLE READ: the
  committing transaction's snapshot was established by its ordinary reads
  long before the verdict's row lock, so a plain lineage SELECT missed a
  mint committed after that snapshot — the unnamed never-started verdict
  still committed, exactly the interleave the seam exists for. The
  lineage read now runs FOR UPDATE as well: a locking read always sees
  the latest committed data on either InnoDB default, and takes the same
  extension-then-session lock order the mint uses. Witness on live
  MariaDB REPEATABLE READ with two connections: a plain read goes RED
  ("Expecting code to raise a throwable" — the gate accepted the mint-
  after-snapshot verdict), the locking read stays green; the H2 decision
  table is unchanged. The temporary MariaDB used for the witness is torn
  down after the run.
- The give-up's physical-child repair required READY, so a child whose
  binding retired (RELEASED) before the record recovered wedged the
  attempt budget behind an answer that could never come — no failure, no
  close admission, no settlement. Terminal reconciliation needs the
  historical dispatch identity, not warmth, so the repair replays from
  the any-state read too. The relay-side pin drives dispatch/attach/fail
  off a retired row whose READY answer is empty; its RED mutation witness
  (READY-only lookup) leaves the same row parked in binding, exactly the
  repro's shape.
- A Turn no longer proves a start by existing: only its G3 dispatch pair
  (submission mark or harness epoch) does, mirroring the cancellation
  store's own predicate. A Turn the coordinator failed before warm-up or
  submission is a pre-admission failure and settles the named
  never-started pairing instead of sending the repair after a binding
  that never existed — R23's parent-CLOSING wedge, pinned in the relay
  (creation_failed settles named, no repair attempt) and the cascade
  (close_scope started:false named, parent completes). The RED mutation
  witness for both qualifications (dropping the dispatch pair) wedges
  all three pins exactly as reproduced.

All mutation witnesses were reverted byte-identical (sha256/cmp checked).
Suites: relay 38/38, relay store 13/13, coordinator 12/12, gate 4/4, and
the full managed-agent-server verify green. Design amended in both
languages.

* fix(managed-agent): fence the never-started verdict against runnable Turns, and keep its refusal rollbackable

R24's two probes held against the arbitration head, both in fresh code
from this branch:

- A live ACCEPTED child Turn waiting for its coordinator shares the
  undispatched shape with the terminal pre-admission failure, so the
  give-up's new dispatch-pair qualification minted creation_failed over
  a Turn the coordinator then claimed, submitted and completed (the
  close-incapable host has no admission guard to catch it). The relay
  now defers bounded on any Turn that neither dispatched nor terminally
  failed pre-admission; only the terminal undispatched half settles the
  named never-started pairing. Pin: an enqueued ACCEPTED Turn sends
  nothing and parks at binding until the admission settles FAILED, when
  the named verdict commits; its mutation witness (deducting the
  qualification) fires the verdict at once, exactly the reproduction.
  The close cascade needed no sibling branch — the active-Turn close
  admission already continues past the same window there, and the
  terminal truth settles on the next walk (verified at coordinator
  12/12 without touching its flow).
- The gate's 409 reached the Hosted writer as a generic record
  rejection, which the HTTP client's refusal classification did not
  recognize: ManagedSessionAuthority latched writeFailure and the
  relay's corrected named retry could never commit on the resident
  parent. The gate now answers its own child_run_lineage_minted code,
  and the client maps it to ManagedSessionCommitRejectedError — the
  rollbackable non-commit class the authority rethrows without the
  latch. Pinned at all three levels: H2 decision table and the MySQL IT
  assert the exact wire code, and the TS client suite drives a one-shot
  409 into a typed rejection (writesStopped false) followed by the
  corrected retry committing (two attempts, one landed); its mutation
  witness (mapping removed) rethrows a plain HttpError.

Witnesses were reverted byte-identical (sha256/cmp). Suites: relay 39/39,
store 13/13, coordinator 12/12, gate 4/4, TS http store 65/65, full
managed-agent-server verify green. Design amended in both languages.

* fix(managed-agent): treat the withdrawn submission mark as evidence-neutral, never as non-admission proof

R25's probe held against the intent reconciliation: G3's
withdrawSubmissionAttempted after a lost admission reply leaves the Turn
CANCELLED with neither dispatch mark nor epoch — the identical field
shape as a genuine pre-admission failure — while the historical binding
(READY → retired) remains. The terminal-shape check certified that as
never-started, converting an unanswered admission into a proved
non-admission: the relay then committed creation_failed over a child
whose admission outcome nobody had disproved.

The rule is now ordered by what can actually prove the verdict. An
outstanding Turn (live, neither dispatched nor terminal pre-admission)
always owes the bounded defer; the proven pair or the historical binding
row takes the dispatch replay with the same identity — a terminal status
never upgrades G3's reset to a proof. Only their joint absence plus a
terminal pre-admission failure settles the named never-started pairing.
The legacy space closes the same way it always did: no Turn, no binding,
no pair.

Pin: a CANCELLED Turn with no marks drives the dispatch/attach replay
from the binding's identity and settles child_failed named, never
creation_failed. Mutation witness (dropping the binding from the
dispatch-evidence disjunction) fires the never-started verdict in the
exact repro shape and is reverted byte-identical afterwards. Suites:
relay 40/40, store 13/13, coordinator 12/12, gate 4/4, full
managed-agent-server verify green. Design amended in both languages.

* fix(managed-agent): renumber the lineage relay migration to V54 after main's V52/V53

The uniqueness gate flags the duplicate version: main landed
V53__managed_workspace_roles.sql after the last incorporation, so the
relay's V53__managed_child_lineage_relay.sql collides with it. Same
remedy as the V52 collision: the ledger moves to V54, its two code
comments and the migration-rank golden list (WorkspaceMigrationMySqlIT,
now "48".."54") move with it, and both design languages name V54 where
they named V53.

Verified green at full depth: runtime-broker + qwencode reinstalled
chain-first (the shared ~/.m2 alpha jar had been overwritten by a
sibling worktree's main build, which lacked the any-state read), the
stale target/classes copy of the old V53 file removed (Maven's
incremental resources copy otherwise ships it alongside the rename and
re-applies the whole ledger as a duplicate version), and the full
managed-agent-server verify passes with 117 suites, no failures.

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-09 06:04:29 +00:00
..
2025-12-05 10:51:57 +08:00
2025-12-19 15:52:11 +08:00