Skip to content

feat(managed-agent): H4e-a team record contract - #13811

Merged
wenshao merged 2 commits into
mainfrom
feat/managed-agent-h4e-a-team-records
Oct 10, 2026
Merged

wenshao merged 2 commits into
mainfrom
feat/managed-agent-h4e-a-team-records

Conversation

@wenshao

@wenshao wenshao commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This is the record-contract half of slice H4e of the managed child-agent work (#12827, Stage H), tracked by #13745. The issue asked for three items: the team record domains (E1), detach to an independent durable owner (E2), and the close cascade under teams (E3). The issue's triage left three questions for a human, and the design settles them. H4e is split like H4d: this PR lands E1 as records, and the runtime follows as H4e-b, together with E3 and everything else the H4a delivery map gives to H4e (the seven team tools, plan resolution, member shutdown, the mailbox relay that H4d-a moved here, legacy team import, and the managed Agent tool's name for teammate routing). Detach stays the separate "Detach" follow-up that the H4a map already lists, and the stale pointer in the H4 design that sent its owner question to H4c now points there. The team domains are the durable form of the existing Legacy team model, not a parallel one: they keep its sanitized names, the reserved leader, the board statuses, the mailbox message types and plan approval.

Four record bodies are added, all held in the lead Session's journal and all disabled for submission. The team state is the roster and the lifecycle: members are append-only, join one per revision and only while the team is active, and the lifecycle runs active, closing, deleted, one step at a time. A team task is one board item with the Legacy fields; in_progress needs an owner, deletion is terminal, and dependencies are stored once, as blockedBy, and only grow, so a write never has to update two tasks to keep one edge. Whether a dependency still blocks is read from the blocker's status. A team message is one mailbox message to one recipient, shaped like the H4d-a outbox entry: settled from its first revision, with a session delivery line, a target fixed at handover, and the target's input named once accepted. Its kind is plain text or one of the six Legacy mailbox types, each with a fixed direction between the leader and a member. A team plan is the approval state of one plan request, keyed by the D6 action that asks the leader: it waits, then settles with a decision, or is cancelled when the plan is withdrawn or superseded.

Commit-time rules in both the TypeScript authority and the Java store bind every team record to its lead. The team must be led by this Session. A joining member must be a live child Session run of this Session that no other team lists. A task, message or plan request opens only in an active team of this Session, so a closing team drains without taking new work. A task number is unique in its team, an owner is the leader or a member, and a new dependency stays in the team and closes no cycle. A message travels between the leader and members and targets the lead Session or the Session its member's run attached, so a message to a member not attached yet stays planned. A plan request comes from a member that requires plan mode. The authority also checks these when it rebuilds a reopened log, and the bodies' content references join the resource closure on both sides.

Shared fixtures (125 cases, 57 successor pairs) replay identically in both languages. No team record projects a task. There is no migration and no public contract change. The design is recorded in both languages, and the H4, H4a and H4d-a designs point to it.

Why it's needed

Teams are what let several child agents work together, and on the Managed path they do not exist: the four team domains are registered names with no body, so nothing about a team can be committed, rebuilt or checked. The Legacy team model works, but it lives in the lead's process and its local files. On the Managed path the Runtime can be reclaimed and the Harness replaced, so every team fact the runtime will rely on has to be a committed record first. This PR pins those facts and the rules that keep them consistent, ahead of the runtime that will produce them, in the same server-first order as the earlier slices.

Reviewer Test Plan

How to verify

  • Shared fixtures: every team state, task, message and plan case and successor pair is accepted or refused identically in TypeScript and Java, and each invalid case is refused on the clause the fixture names. Every existing contract corpus replays unchanged.
  • Gate: the real domain gate refuses each of the four domains before anything is published.
  • Authority, with the gate lifted only to plant records: a team opens, gains members one at a time, closes and is deleted; its tasks, messages and plan requests chain to their ends; a reopened log rebuilds every chain. Each commit-time rule refuses its violation, and a record opened while the team was active still moves after the team starts closing.
  • Java store: the same chains commit over H2 in MySQL mode, the same rules refuse with the store's rejection code, and no team row projects a task.

Evidence (Before & After)

N/A: a record contract that stays disabled for submission, so nothing is user-visible. Verification output:

  • Core: the whole managed-runtime directory, 58 files and 3160 tests, all passing.
  • Java managed-agent-server: the full clean verify checkstyle:check: 1625 surefire tests, 0 failures, 2 skipped; 0 Checkstyle violations; 0 SpotBugs findings.
  • Mutation checks: every new guard was disabled in turn and its witness went red in its own language: 22 of 22 authority mutants and 22 of 22 Java store and validator mutants were killed, plus 6 TypeScript validator spot checks.
  • npm run typecheck, ESLint and Prettier on the changed files: clean.

Tested on

OS Status
🍏 macOS ⚠ïļ
🊟 Windows ⚠ïļ
🐧 Linux ✅

Environment (optional)

Unit and contract suites only: vitest for core, Maven surefire on H2 (MySQL mode) for managed-agent-server, JDK 25. No MySQL/MariaDB integration lane was run locally, and this change has no migration.

Risk & Scope

  • Main risk or tradeoff: the record rules are frozen ahead of their producers, so H4e-b inherits them. The design records four questions H4e-b still owes: the member-side receipt for a message, the input binding of a message to the leader, what a closing lead may still commit, and how a continued member stays on the roster. In Java, the task-number check reads the Session's team tasks when a task opens and the one-team check reads its teams when a member joins, because no index holds those values; both happen far less often than ordinary revisions.
  • Not validated / out of scope: the H4e-b runtime (team tools, teammate routing, mailbox relay, plan resolution, member shutdown, the close cascade over members, legacy import, enablement); detach; cross-workspace teams; a public team resource (Multi-Agent API: an agent-identity dimension on the public contract, so multi-agent execution is attributable, tree-shaped and interruptible #13785). The MySQL/MariaDB integration lanes run in CI.
  • Breaking changes / migration notes: none. There is no migration and no OpenAPI change, and no team domain is enabled. team_state:<n>, team_task:<n>, team_message:<n> and team_plan:<n> event ids are now reserved for Stage H records, as every registered body's are; no writer produces those id shapes.

Design: English · įŪ€ä―“äļ­æ–‡

Linked Issues

Part of #13745 (E1; E2 moves to the Detach follow-up and E3 to H4e-b, so the issue stays open). Part of #12827.

äļ­æ–‡čŊŽ

čŋ™äļŠ PR 做䚆äŧ€äđˆ

čŋ™æ˜Ŋ managed child-agent å·Ĩä―œ(#12827,Stage H)äļ­ H4e åˆ‡į‰‡įš„čŪ°å―•åĨ‘įšĶé‚Ģäļ€åŠ,į”ą #13745 跟čļŠã€‚čŊĨ issue 提䚆äļ‰éĄđ:å›Ē队čŪ°å―• domain(E1)、detach åˆ°į‹ŽįŦ‹įš„æŒäđ…æ‰€æœ‰č€…(E2),äŧĨ及å›Ē队äļ‹įš„å…ģé—­įš§č”(E3)。issue įš„åˆ†čŊŠį•™äļ‹äš†äļ‰äļŠéœ€čĶäššæĨåŪšįš„é—Ūéǘ,朎čŪūčŪĄįŧ™å‡šäš†į­”æĄˆã€‚H4e 像 H4d ä ·äļ€åˆ†äļšäšŒ:朎 PR äŧĨčŪ°å―•įš„å―Ēåžč―åœ° E1,čŋčĄŒæ—ķéšåŽä―œäļš H4e-b,čŋžåŒ E3 äŧĨ及 H4a äšĪäŧ˜åœ°å›ū分įŧ™ H4e įš„å…ķä―™å†…åŪđ(äļƒäļŠå›Ē队å·Ĩ具、čŪĄåˆ’č́å†ģ、成员å…ģ停、H4d-a į§ŧ到čŋ™é‡Œįš„ mailbox äļ­įŧ§ã€æ—§å›Ē队åŊžå…Ĩ,äŧĨ及į”Ļ䚎 teammate č·Ŋį”ąįš„ managed Agent å·Ĩ具 name)。detach äŧæ˜Ŋ H4a 地å›ūäļ­å·ēå•åˆ—įš„ "Detach" 后įŧ­å·Ĩä―œ,H4 čŪūčŪĄäļ­æŠŠå…ķæ‰€æœ‰č€…é—ŪéǘäšĪįŧ™ H4c įš„čŋ‡æ—ķæŒ‡å‘įŽ°å·ēæ”đäļšæŒ‡å‘é‚Ģ里。å›Ē队 domain æ˜ŊįŽ°æœ‰ Legacy å›Ē队æĻĄåž‹įš„æŒäđ…å―Ēæ€,而äļæ˜Ŋåđķåˆ—įš„åĶäļ€åĨ—:åۃæēŋį”Ļįŧčŋ‡æļ…æī—įš„åå­—ã€äŋį•™å leader、äŧŧåŠĄæŋįŠķ态、mailbox æķˆæŊįąŧ型äļŽčŪĄåˆ’åŪĄæ‰đ。

新åĒžå››äļŠčŪ°å―•ä―“,å…ĻéƒĻäŋå­˜åœĻ lead Session įš„ journal äļ­,äļ”å…ĻéƒĻäļåž€æ”ū提äšĪ。å›Ē队įŠķ态æ˜Ŋ名册äļŽį”Ÿå‘―å‘Ļ期:成员及čŋ―加,æŊäļŠäŋŪčŪĒ加å…Ĩäļ€äļŠ,äļ”åŠčƒ―åœĻå›Ē队äļš active æ—ķ加å…Ĩ;į”Ÿå‘―å‘Ļ期äļš active、closing、deleted,æŊæŽĄä­Ĩ。å›Ē队äŧŧåŠĄæ˜Ŋäļ€äļŠåļĶ Legacy 字æŪĩįš„äŧŧåŠĄæŋæĄį›Ū;in_progress 需č́ owner,删é™Īæ˜Ŋįŧˆæ€,äūčĩ–及按 blockedBy äŋå­˜äļ€æŽĄäļ”及åĒžäļå‡,因æ­Īäļ€æŽĄå†™å…Ĩæ°ļčŋœäļéœ€č́äļšįŧäĄčūđ而æ›ī新äļĪäļŠäŧŧåŠĄã€‚äūčĩ–æ˜ŊåĶäŧåœĻé˜ŧåĄž,į”ąé˜ŧåĄžæ–đįš„įŠķ态čŊŧ凚。å›Ē队æķˆæŊæ˜Ŋ发įŧ™äļ€äļŠæŽĨæ”ķæ–đįš„äĄ mailbox æķˆæŊ,å―ĒįŠķäļŽ H4d-a įš„ outbox æĄį›Ūäļ€č‡ī:äŧŽįŽŽäļ€äļŠäŋŪčŪĒčĩ·å°ąæ˜Ŋ settled,åļĶäĄ session 投递įšŋ,į›Ū标åœĻäšĪæŽĨæ—ķ囚åۚ,åđķåœĻ accepted 后写明į›Ūæ ‡įš„ input。å…ķ kind æ˜ŊįšŊ文朎,æˆ–å…­į§ Legacy mailbox įąŧ型äđ‹äļ€,æŊį§éƒ―有 leader äļŽæˆå‘˜äđ‹é—īįš„å›šåۚæ–đ向。å›Ē队čŪĄåˆ’æ˜Ŋäļ€äļŠčŪĄåˆ’čŊ·æą‚įš„åŪĄæ‰đįŠķ态,äŧĨčŊ· leader 做å†ģåŪšįš„ D6 action äļšé”Ū:åŪƒå…ˆį­‰åū…,į„ķ后åļĶį€å†ģåۚ settled,或åœĻčŪĄåˆ’čĒŦæ’Ī回或čĒŦ取äŧĢæ—ķ cancelled。

TypeScript authority äļŽ Java store äļ­įš„æäšĪæ—ķč§„åˆ™æŠŠæŊæĄå›Ē队čŪ°å―•įŧ‘åۚ到å…ķ lead。å›Ē队åŋ…éĄŧį”ąæœŽ Session édžåŊžã€‚加å…Ĩįš„æˆå‘˜åŋ…éĄŧæ˜Ŋ朎 Session äļ€äļŠå­˜æīŧįš„ã€æēĄæœ‰å…ķäŧ–å›Ēé˜Ÿåˆ—å‡šįš„ child Session run。äŧŧåŠĄã€æķˆæŊ或čŪĄåˆ’čŊ·æą‚åŠčƒ―åœĻ朎 Session äļ€äļŠ active įš„å›Ē队äļ­åž€åŊ,因æ­Ī closing äļ­įš„å›Ē队åœĻæ”ķå°ūæ—ķäļå†æŽĨæ”ķ新å·Ĩä―œã€‚äŧŧåŠĄįž–å·åœĻå›Ē队内å”Ŋäļ€,owner æ˜Ŋ leader 或成员,新äūčĩ–į•™åœĻå›Ē队内äļ”äļå―ĒæˆįŽŊ。æķˆæŊåœĻ leader äļŽæˆå‘˜äđ‹é—ī侠递,į›Ū标æ˜Ŋ lead Session 或čŊĨ成员 run 所 attach įš„ Session,因æ­Ī发įŧ™å°šæœŠ attach įš„æˆå‘˜įš„æķˆæŊåœį•™åœĻ planned。čŪĄåˆ’čŊ·æą‚æĨ臩čĶæą‚ plan mode įš„æˆå‘˜ã€‚authority åœĻ重åŧšé‡æ–°æ‰“åž€įš„æ—Ĩåŋ—æ—ķäđŸäžšæĢ€æŸĨčŋ™äš›č§„则,čŪ°å―•ä―“äļ­įš„内åŪđåž•į”ĻåœĻäļĪäū§éƒ―įšģå…Ĩčĩ„暐闭包。

å…ąäšŦ fixture(125 äļŠį”Ļäū‹ã€57 äļŠåŽįŧ§åŊđ)åœĻäļĪį§čŊ­čĻ€äļ­įŧ™å‡šåیå…Ļį›ļåŒįš„įŧ“果。å›Ē队čŪ°å―•äļæŠ•å―ą task。æēĄæœ‰čŋį§ŧ,äđŸæēĄæœ‰å…Žå…ąåĨ‘įšĶ变æ›ī。čŪūčŪĄäŧĨäļĪį§čŊ­čĻ€čŪ°å―•,H4、H4a äļŽ H4d-a įš„čŪūčŪĄå‡å·ē指向åŪƒã€‚

äļšäŧ€äđˆéœ€č́

å›Ē队čŪĐåΚäļŠ child agent čƒ―åĪŸååŒå·Ĩä―œ,而 Managed č·Ŋåū„äļŠåđķäļå­˜åœĻå›Ē队:四äļŠå›Ē队 domain 及æ˜ŊæēĄæœ‰čŪ°å―•ä―“įš„å·ēį™ŧčŪ°åå­—,因æ­Īå›Ēé˜Ÿįš„äŧŧä―•äš‹åŪžéƒ―æ— æģ•提äšĪ、重åŧšæˆ–æĢ€æŸĨ。Legacy å›Ē队æĻĄåž‹åŊäŧĨå·Ĩä―œ,ä―†åŪƒåŠå­˜åœĻ䚎 lead įš„čŋ›įĻ‹åŠå…ķ朎地文äŧķäļ­ã€‚åœĻ Managed č·Ŋåū„äļŠ,Runtime åŊčƒ―čĒŦ回æ”ķ、Harness åŊčƒ―čĒŦæ›ŋæĒ,因æ­ĪčŋčĄŒæ—ķ将äūčĩ–įš„æŊäļŠå›Ē队之åŪžéƒ―åŋ…éĄŧ先成äļšå·ē提äšĪįš„čŪ°å―•ã€‚æœŽ PR åœĻäš§į”Ÿčŋ™äš›äš‹åŪžįš„čŋčĄŒæ—ķäđ‹å‰,先把čŋ™äš›äš‹åŪžåŠįŧå…ķäļ€č‡īįš„č§„åˆ™å›šåۚäĨ,éĩåūŠäļŽäđ‹å‰åˆ‡į‰‡į›ļåŒįš„ server å…ˆčĄŒéĄšåšã€‚

čŊ„åŪĄæĩ‹čŊ•čŪĄåˆ’

åĶ‚ä―•éŠŒčŊ

  • å…ąäšŦ fixture:å›Ē队įŠķ态、äŧŧåŠĄã€æķˆæŊäļŽčŪĄåˆ’įš„æŊäļŠį”Ļäū‹å’ŒåŽįŧ§åŊđ,åœĻ TypeScript äļŽ Java äļ­čĒŦ同样地æŽĨ受或拒įŧ,æŊäļŠæ— æ•ˆį”Ļäū‹éƒ―按 fixture æŒ‡åįš„å­åĨčĒŦ拒įŧã€‚įŽ°æœ‰įš„æŊäļŠåĨ‘įšĶčŊ­æ–™éƒ―原样回æ”ū通čŋ‡ã€‚
  • é—Ļį́:ᜟåŪžįš„ domain é—Ļį́åœĻäŧŧä―•å†…åŪđ发åļƒäđ‹å‰æ‹’įŧå››äļŠ domain äļ­įš„æŊäļ€äļŠã€‚
  • Authority(及äļšå†™å…ĨčŪ°å―•č€Œæ”ū垀é—Ļį́):å›Ē队垀åŊ、逐äļŠåŠ å…Ĩ成员、čŋ›å…Ĩ closing åđķčĒŦ删é™Ī;å…ķäŧŧåŠĄã€æķˆæŊäļŽčŪĄåˆ’čŊ·æą‚įš„é“ūå„č‡Ščĩ°åˆ°įŧˆį‚đ;é‡æ–°æ‰“åž€įš„æ—Ĩåŋ—䞚重åŧšæŊæĄé“ū。æŊæĄæäšĪæ—ķč§„åˆ™éƒ―æ‹’įŧį›ļåš”įš„čŋč§„,而å›Ē队äŧäļš active æ—ķ垀åŊįš„čŪ°å―•,åœĻå›Ē队垀始 closing äđ‹åŽäŧčƒ―前čŋ›ã€‚
  • Java store:åŒæ ·įš„é“ūåœĻ MySQL æĻĄåžįš„ H2 ääšĪ,åŒæ ·įš„č§„åˆ™äŧĨ store įš„æ‹’įŧį æ‹’įŧ,å›Ē队行äļæŠ•å―ą task。

čŊæŪ(前后åŊđæŊ”)

äļé€‚į”Ļ:čŋ™æ˜ŊäŋæŒį́į”Ļįš„čŪ°å―•åĨ‘įšĶ,æēĄæœ‰į”Ļ户åŊč§įš„å˜åŒ–ã€‚éŠŒčŊčū“凚:

  • Core:æ•īäļŠ managed-runtime į›Ūå―•,58 äļŠæ–‡äŧķ、3160 äļŠæĩ‹čŊ•,å…ĻéƒĻ通čŋ‡ã€‚
  • Java managed-agent-server:åیæ•īįš„ clean verify checkstyle:check:surefire 1625 äļŠæĩ‹čŊ•,0 åĪąčīĨ、2 č·ģčŋ‡;Checkstyle 0 čŋč§„;SpotBugs 0 é—ŪéĒ˜ã€‚
  • 变垂æĢ€æŸĨ:äūæŽĄį́į”ĻæŊäļŠæ–°åĒžåۈåŦ,å…ķ见čŊæĩ‹čŊ•åœĻå„č‡ŠčŊ­čĻ€äļ­å˜įšĒ:authority įš„ 22 äļŠå˜åž‚äļŽ Java store åŠæ ĄéŠŒå™Ļįš„ 22 äļŠå˜åž‚å…ĻéƒĻčĒŦ杀æ­ŧ,åĶ有 6 äļŠ TypeScript æ ĄéŠŒå™ĻæŠ―æŸĨ。
  • 变æ›ī文äŧķäļŠįš„ npm run typecheck、ESLint äļŽ Prettier:无é—ŪéĒ˜ã€‚

æĩ‹čŊ•åđģ台

OS įŠķ态
🍏 macOS ⚠ïļ
🊟 Windows ⚠ïļ
🐧 Linux ✅

įŽŊåǃ(åŊ选)

åŠč·‘äš†å•å…ƒäļŽåĨ‘įšĶæĩ‹čŊ•:core į”Ļ vitest,managed-agent-server į”Ļ H2(MySQL æĻĄåž)äļŠįš„ Maven surefire,JDK 25。朎地æēĄæœ‰č·‘ MySQL/MariaDB 集成æĩ‹čŊ•,朎变æ›īäđŸæēĄæœ‰čŋį§ŧ。

éĢŽé™ĐäļŽčŒƒå›ī

  • äļŧč́éĢŽé™Đæˆ–å–čˆ:čŪ°å―•č§„åˆ™å…ˆäšŽå…ķį”Ÿäš§č€…čĒŦ囚åۚ,因æ­Ī H4e-b 将įŧ§æ‰ŋåۃäŧŽã€‚čŪūčŪĄčŪ°å―•äš† H4e-b äŧæŽ įš„å››äļŠé—Ūéǘ:成员äļ€äū§įš„æķˆæŊ回执、发įŧ™ leader įš„æķˆæŊįš„ input įŧ‘åŪšã€closing äļ­įš„ lead čŋ˜čƒ―提äšĪäŧ€äđˆ,äŧĨ及čĒŦįŧ­č·‘įš„æˆå‘˜åĶ‚ä―•į•™åœĻ名册äļ­ã€‚åœĻ Java äļ­,äŧŧåŠĄįž–å·æĢ€æŸĨåœĻäŧŧåŠĄåž€åŊæ—ķčŊŧ取朎 Session įš„å›Ē队äŧŧåŠĄ,单äļ€å›Ē队æĢ€æŸĨåœĻ成员加å…Ĩæ—ķčŊŧ取朎 Session įš„å›Ē队,因äļšæēĄæœ‰įīĒåž•äŋå­˜čŋ™äš›å€ž;čŋ™äļĪč€…įš„å‘į”ŸéĒ‘įŽ‡éƒ―čŋœä―ŽäšŽæ™Ū通äŋŪčŪĒ。
  • 朊銌čŊ/äļåœĻ范å›ī内:H4e-b čŋčĄŒæ—ķ(å›Ē队å·Ĩ具、teammate č·Ŋį”ąã€mailbox äļ­įŧ§ã€čŪĄåˆ’č́å†ģ、成员å…ģ停、åŊđæˆå‘˜įš„å…ģé—­įš§č”ã€æ—§å›Ē队åŊžå…Ĩ、åŊį”Ļ);detach;č·Ļå·Ĩä―œåŒšå›Ē队;å…Žå…ąå›Ē队čĩ„暐(Multi-Agent API: an agent-identity dimension on the public contract, so multi-agent execution is attributable, tree-shaped and interruptible #13785)。MySQL/MariaDB 集成æĩ‹čŊ•åœĻ CI äļ­čŋčĄŒã€‚
  • į ī坏性变æ›ī/čŋį§ŧčŊŽ:无。æēĄæœ‰čŋį§ŧ,æēĄæœ‰ OpenAPI 变æ›ī,äđŸæēĄæœ‰åŊį”Ļäŧŧä―•å›Ē队 domain。team_state:<n>、team_task:<n>、team_message:<n> äļŽ team_plan:<n> å―Ēåžįš„äš‹äŧķ id įŽ°åœĻäŋį•™įŧ™ Stage H čŪ°å―•,äļŽæŊäļŠå·ēį™ŧčŪ°čŪ°å―•ä―“ä ·;æēĄæœ‰å†™å…Ĩæ–đäš§į”Ÿčŋ™äš› id å―ĒįŠķ。

å…ģ联 Issue

åąžäšŽ #13745(E1;E2 į§ŧ到 Detach 后įŧ­å·Ĩä―œ,E3 į§ŧ到 H4e-b,因æ­ĪčŊĨ issue äŋæŒæ‰“垀)ã€‚åąžäšŽ #12827。

The record-contract half of slice H4e of #12827, tracked by #13745. H4e
is split like H4d: the team domains land as records first, and the
runtime follows as H4e-b, together with the close cascade under teams
(#13745 E3) and the rest of what the H4a map gives H4e. Detach (#13745
E2) stays the separate Detach follow-up of the H4a map. The team domains
are the durable form of the Legacy team model, not a parallel one. All
four stay disabled for submission, and the Java store validates them
ahead of any writer.

- managed-team_state v1 (chain teamId): the roster and the lifecycle.
  Members are append-only, join one per revision and only while the team
  is active; membershipRevision is one more than the member count; the
  lifecycle runs active -> closing -> deleted one step at a time, with a
  purely logical run that is admitted until the team is deleted.
- managed-team_task v1 (chain taskId): one board item with the Legacy
  fields and a team-unique number. in_progress needs an owner, deleted is
  terminal, and dependencies are stored once, as blockedBy, and only
  grow; whether one still blocks is read from the blocker's status.
- managed-team_message v1 (chain messageId): one mailbox message to one
  recipient, shaped like the H4d-a outbox entry, with plain text or one
  of the six Legacy mailbox types, each with a fixed direction between
  the leader and a member.
- managed-team_plan v1 (chain requestId): the approval state of one plan
  request, keyed by the D6 action that asks the leader; it waits, then
  settles with a decision or is cancelled when withdrawn or superseded.
- Commit-time rules in the TS authority and the Java store, also checked
  when the authority rebuilds a log: the team is led by this Session; a
  member joins as a live child Session run of this Session that no other
  team lists; a task, message or plan request opens only in an active
  team, so a closing team drains; task numbers are unique per team,
  owners are the leader or members, and new dependencies stay in the
  team and close no cycle; messages travel between the leader and
  members and target the lead Session or the Session the member's run
  attached; plan requests come from members that require plan mode.
  Content references join the resource closure on both sides.
- Shared fixtures managed-team-record/1 (125 cases, 57 successor pairs)
  replay in both languages; no team record projects a task. No migration
  and no public contract change.

Design: docs/design/2026-10-10-managed-agent-teams.md (en + zh-CN); the
H4, H4a and H4d-a designs point to it, and the H4 design's detach-owner
question now points to the Detach follow-up instead of H4c.
@qwen-code-review-bot

qwen-code-review-bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

⚠ïļ Deferred approval not posted — the PR head moved (or the PR closed) after the review of 12be226; approving now would attest to unreviewed code. Re-run @qwen-code /triage on the new head. finalize run

⚠ïļ åŧķčŋŸåŪĄæ‰đ朊提äšĪ —— åŪĄæŸĨ 12be226 äđ‹åŽ PR head å·ē变æ›ī或 PR å·ēå…ģ闭æ­Īæ—ķåŪĄæ‰đ侚äļšæœŠåŪĄæŸĨįš„äŧĢ᠁背äđĶ。čŊ·åœĻ新 head äļŠé‡æ–°čŋčĄŒ @qwen-code /triage。æŸĨįœ‹ finalize čŋčĄŒ

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed commit 12be226486ae50183be37c1e068167fcc389eff6.

One P2 finding: a malformed team-message successor dereferences a missing recipient before the immutable-field check can reject it. The same defect was reproduced through the TypeScript authority and the Java store; the inline comment covers both implementations.

Validation: 410 existing TypeScript tests and 17 existing Java tests passed, and the full repository build and typechecking passed. Targeted regression witnesses fail because the actual exceptions are TypeError / NullPointerException instead of the expected record-conflict exceptions. In both witnesses, the rejected attempt leaves the committed revision unchanged and the corrected successor succeeds.

This review covers the record contract, resource closure, ownership rules, replay, and matching English/Chinese designs. Runtime enablement and the explicitly deferred H4e-b work remain outside this PR's scope. Reproduction used the authority and H2 store; no live CLI or MySQL/MariaDB E2E result is claimed.

Comment on lines +1457 to +1459
JsonNode child = recordBody(scopeKey, sessionId,
"child_run", memberWith(team, "name", to)
.get("childRunId").textValue(),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Validate the successor's recipient before dereferencing it

After committing a planned message from leader to alice, submit the same messageId with to: "carol" (absent from the roster), targetSessionId: "session-run-1", and delivery accepting. Because previous != null skips the participant check and isSuccessor runs only after requireTeamRecord, memberWith(...) returns null and this dereference throws NullPointerException instead of the normal 409 managed_session_extension_record_rejected. The API's generic exception handler maps it to 500. The equivalent TypeScript path in managed-session-authority.ts:2480–2484 throws TypeError: Cannot read properties of undefined (reading 'childRunId').

Both failures were reproduced on this commit; corrected successors still commit successfully. Validate the immutable fields or recipient membership before this lookup in both implementations. Add authority/store regression cases that change an existing message's recipient to an unknown member and assert the normal conflict exception and an unchanged committed revision.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4851c23.

The party check now runs on every revision in both implementations, not only when the message opens. The roster only grows and from/to are fixed, so a lawful successor always passes, while one that readdresses the message to a name outside the roster is refused with the normal record conflict ("Team message must travel between the leader and members of its team.") before anything looks its recipient up.

Regression witnesses, both confirmed red before the fix (TypeError in the authority, NullPointerException in the store) and green after:

  • Authority: after committing message-1 planned, an accepting successor addressed to carol is refused with ManagedSessionConflictError and the rule's message, and the committed record stays at revision 1 with its original body; the corrected successor then commits.
  • Store: the same successor is refused with 409 managed_session_extension_record_rejected and the rule's message, and the Session's committed revisions are unchanged.

I also rechecked the other lookups the team rules make ahead of the chain rules (the joining member's child run, new blockers and the cycle walk, the team itself); each either handles a missing record as a refusal or only follows ids that an earlier commit already verified.

äļ­æ–‡čŊŽ

å·ēåœĻ 4851c23 äŋŪåĪã€‚

äļĪį§åŪžįŽ°įŽ°åœĻéƒ―åœĻæŊäļŠäŋŪčŪĒäļŠæĢ€æŸĨæķˆæŊįš„å‚äļŽč€…,而äļåŠæ˜ŊåœĻæķˆæŊ垀åŊæ—ķæĢ€æŸĨ。名册及åĒžäļå‡,from/to æ˜Ŋ囚åŪšå­—æŪĩ,因æ­Ī合æģ•įš„åŽįŧ§æ€ŧčƒ―é€ščŋ‡;č€ŒæŠŠæķˆæŊæ”đ投įŧ™åå†ŒåĪ–åå­—įš„åŽįŧ§,侚åœĻæŸĨæ‰ūæŽĨæ”ķæ–đäđ‹å‰äŧĨæ­Ģåļļįš„čŪ°å―•å†ēįŠ("Team message must travel between the leader and members of its team.")čĒŦ拒įŧã€‚

å›žå―’č§čŊåœĻäŋŪåĪå‰å‡äļšįšĒ(authority äļ­äļš TypeError,store äļ­äļš NullPointerException),äŋŪåĪåŽäļšįŧŋ:

  • Authority:提äšĪ message-1 įš„ planned 后,发įŧ™ carol įš„ accepting 后įŧ§čĒŦäŧĨ ManagedSessionConflictError 及čŊĨč§„åˆ™įš„æķˆæŊ拒įŧ,å·ē提äšĪčŪ°å―•äŧåœåœĻäŋŪčŪĒ 1、内åŪđäļå˜;随后æ­ĢįĄŪįš„åŽįŧ§åŊäŧĨ提äšĪ。
  • Store:同äļ€äļŠåŽįŧ§äŧĨ 409 managed_session_extension_record_rejected 及čŊĨč§„åˆ™įš„æķˆæŊčĒŦ拒įŧ,čŊĨ Session å·ē提äšĪįš„äŋŪčŪĒæ•°äļå˜ã€‚

我äđŸåĪæŸĨ乆å›Ēé˜Ÿč§„åˆ™åœĻé“ūč§„åˆ™äđ‹å‰åšįš„å…ķäŧ–æŸĨæ‰ū(加å…Ĩæˆå‘˜įš„ child run、新åĒžįš„é˜ŧåĄžæ–đäļŽįŽŊæĢ€æĩ‹éåŽ†ã€å›Ē队朎čšŦ):åۃäŧŽč́äđˆæŠŠčŪ°å―•įžšåĪąå―“ä―œæ‹’įŧå΄ᐆ,č́äđˆåŠæēŋį€äđ‹å‰įš„æäšĪå·ēįŧæ ļ驌čŋ‡įš„ id æŸĨæ‰ū。

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — every required heading is present, the Evidence section correctly says N/A for a contract that ships disabled, and the design doc is linked in both languages.

Problem: Real, and I checked it against the base tree rather than taking the description's word. MANAGED_SESSION_DOMAINS already lists team_state / team_task / team_message / team_plan, but MANAGED_EXTENSION_RECORD_BODIES has no entry for any of them and they are absent from MANAGED_SESSION_ENABLED_DOMAINS. So the four names are genuinely body-less today: nothing about a team can be committed, chained, rebuilt or refused on the Managed path. This is an observed gap in the code, not theoretical hardening.

Direction: Aligned. Landing the record contract inert, ahead of the runtime that produces it, is the convention this codebase already documents for itself — the base managed-session-records.ts says the Java store "validates both bodies since H6a and deploys before any writer, keeping the server-first order". This PR follows that order rather than inventing one, and it is a tracked slice of #12827 Stage H under #13745. Keeping the Legacy team vocabulary (sanitized names, reserved leader, board statuses, the six mailbox types, plan approval) instead of designing a parallel model is the right call — I verified the constants rather than trusting the comments: MAX_TEAMMATES = 10 (packages/core/src/agents/team/types.ts:180) and MAX_METADATA_BYTES = 32_768 (packages/core/src/agents/team/tasks.ts:43) both match MANAGED_TEAM_LIMITS / ManagedTeamRecords.

Size: Core paths are touched (packages/core/src/**) and the change spans two packages (packages/core + packages/sdk-java), so here is the breakdown of the 6,898 changed lines:

Bucket Lines
Production logic — TS (managed-team-record.ts 738, managed-session-authority.ts 163, managed-extension-projection.ts 50) 951
Production logic — Java (ManagedTeamRecords.java 487, ManagedExtensionRecordStore.java 182, ManagedExtensionProjection.java 22) 691
Production logic total 1,642
Tests (*.test.ts 1,141 + Java src/test/java 610) 1,751
Contract fixtures (managed-team-record-v1.fixtures.json 3,100 + projection fixtures 6) 3,106
Design docs (EN + zh-CN, and the pointer updates) 399

At 1,642 production lines this is over both the 500-line maintainer-awareness mark and the 1,000-line large-PR advisory, so I am recording it plainly. It does not change the outcome here: the two-tier core gate in AGENTS.md applies to external PRs and exempts maintainer-authored ones, and this PR is authored by a repo admin who is also a listed owner in .github/issue-owners.json. The advisory is informational in any case, and the size is mostly not logic — 47% of the diff is one machine-replayed fixture corpus and 26% is tests.

Approach: The scope feels right, and notably this PR is the split rather than something that needs splitting: H4e was cut into H4e-a (records, this PR) and H4e-b (runtime), the same way H4d was cut. Everything expensive and user-visible is deferred — the seven team tools, plan resolution, member shutdown, the mailbox relay, legacy import, enablement, detach, the close cascade. What remains is the minimum that can express the rules at all. I considered whether team_state alone could land first, but the other three domains all bind to a team and their commit-time rules are the point of the slice, so a team_state-only contract could not state them. Cutting further would ship a contract that cannot enforce itself.

Reuse is good: no parallel validator utility is introduced. The TS side builds on assertManagedSessionStableId, assertManagedSessionDurableRef, assertManagedSessionDigest, boundedString, ManagedSessionRecordError, parseExtensionRun and isExtensionRunSuccessor; the Java side on ManagedExtensionRecords.closed/id/count/durableRef/digest/boundedText/requireRun/isRunSuccessor/same. I found no unrelated changes, drive-by refactors or formatting churn — the six doc edits are all consequence of the H4e re-scope, and each is mirrored in the Chinese version.

Risk: No elevated risk signals. The Stage 1e revert-correlated path scan matched nothing (no content generator, streaming parser, shell execution, MCP, LSP, ACP, relaunch or sandbox paths). The change is also structurally inert: the four domains stay out of MANAGED_SESSION_ENABLED_DOMAINS, this PR does not touch that file, and assertManagedSessionDomainEnabled is called on the commit path (managed-session-authority.ts:1379), so no caller can submit a team record after this merges. No migration, no OpenAPI change, no user-visible surface.

Moving on to code review. 🔍

äļ­æ–‡čŊŽ

感č°ĒčīĄįŒŪ

æĻĄæŋåیæ•ī ✓ —— 所有åŋ…需标éĒ˜éƒ―åœĻEvidence ä åŊđäļ€äļŠ"į́į”Ļ提äšĪ"įš„åĨ‘įšĶæ­ĢįĄŪ地写䚆 N/AčŪūčŪĄæ–‡æĄĢ双čŊ­é“ūæŽĨé―å…Ļ。

é—ŪéĒ˜ïžš ᜟåŪžå­˜åœĻïžŒč€Œäˆ‘æ˜ŊåŊđį…§ base äŧĢį æ ‘æ ļåŪžįš„ïžŒæēĄæœ‰åŠé‡‡äŋĄæčŋ°ã€‚MANAGED_SESSION_DOMAINS 里å·ēįŧåˆ—䚆 team_state / team_task / team_message / team_planïžŒä―† MANAGED_EXTENSION_RECORD_BODIES 里四äļŠéƒ―æēĄæœ‰čŪ°å―•ä―“ïžŒMANAGED_SESSION_ENABLED_DOMAINS 里äđŸæēĄæœ‰åۃäŧŽã€‚所äŧĨčŋ™å››äļŠåå­—į›Ū前įĄŪåŪžåŠæœ‰åå­—æēĄæœ‰å†…åŪđïžšåœĻ Managed č·Ŋåū„äļŠïžŒå›Ēé˜Ÿįš„äŧŧä―•äš‹åŪžéƒ―æ— æģ•提äšĪ、é“ū垏čŋ―加、重åŧšæˆ–æ‹’įŧã€‚čŋ™æ˜ŊäŧĢį é‡ŒåŊ观æĩ‹åˆ°įš„įĐšįžšïžŒäļæ˜ŊᐆčŪšæ€§åŠ å›šã€‚

æ–đ向 åŊđé―ã€‚čŪĐčŪ°å―•åĨ‘įšĶ先äŧĨ"į́į”Ļ"įŠķæ€č―åœ°ã€å†į”ąäš§į”ŸåŪƒįš„čŋčĄŒæ—ķ跟äļŠïžŒæ­Ģæ˜Ŋčŋ™äļŠäŧĢį åš“č‡Šå·ąå·ēįŧå†™æ˜Žįš„įšĶåۚ —— base įš„ managed-session-records.ts å°ąå†™į€ Java store "臩 H6a čĩ·æ ĄéЌäļĪį§čŪ°å―•ä―“ïžŒåđķ先䚎äŧŧä―• writer éƒĻį―ēäŋæŒ server å…ˆčĄŒįš„éĄšåš"。朎 PR éĩåūŠčŋ™äļŠéĄšåšïžŒč€Œäļæ˜ŊåĶ造äļ€åĨ—åđķäļ”åۃæ˜Ŋ #12827 Stage H äļ‹į”ą #13745 跟čļŠįš„æ—ĒåŪšåˆ‡į‰‡ã€‚æēŋį”Ļ Legacy å›Ē队čŊæą‡ïžˆæļ…æī—čŋ‡įš„名字、äŋį•™å leader、äŧŧåŠĄæŋįŠķæ€ã€å…­į§ mailbox įąŧ型、čŪĄåˆ’åŪĄæ‰đïž‰č€Œäļæ˜ŊčŪūčŪĄäļ€åĨ—åđķ列æĻĄåž‹ïžŒčŋ™äļŠåˆĪ断æ˜ŊåŊđįš„ —— 我æ ļåŪžäš†åļļ量朎čšŦ而äļæ˜Ŋ及äŋĄæģĻ释MAX_TEAMMATES = 10packages/core/src/agents/team/types.ts:180äļŽ MAX_METADATA_BYTES = 32_768packages/core/src/agents/team/tasks.ts:43ïž‰éƒ―äļŽ MANAGED_TEAM_LIMITS / ManagedTeamRecords äļ€č‡ī。

规æĻĄïžš 朎 PR č§Ķ及æ ļåŋƒč·Ŋåū„packages/core/src/**åđķč·ĻäļĪäļŠåŒ…packages/core + packages/sdk-java因æ­Ī把 6,898 行æ”đåŠĻ拆č§ĢåĶ‚äļŠčĄĻïžšį”Ÿäš§é€ŧčū‘合čŪĄ 1,642 čĄŒïžˆTS 951 + Java 691æĩ‹čŊ• 1,751 čĄŒïžŒåĨ‘įšĶ fixture 3,106 čĄŒïžŒčŪūčŪĄæ–‡æĄĢ 399 čĄŒã€‚

1,642 čĄŒį”Ÿäš§é€ŧčū‘同æ—ķčķ…čŋ‡äš† 500 čĄŒįš„įŧĪ者å…ģæģĻįšŋ和 1,000 čĄŒįš„åΧ PR 提įĪšįšŋ所äŧĨ我åĶ‚åŪžčŪ°å―•ã€‚ä―†čŋ™äļæ”đ变įŧ“čŪšïžšAGENTS.md įš„æ ļåŋƒäļĪįš§é—ĻįĶé’ˆåŊđįš„æ˜ŊåĪ–éƒĻ PRįŧĪč€…æœŽäššæäšĪįš„ PR åąžäšŽčąå…čŒƒå›īïžŒč€ŒæœŽ PR įš„ä―œč€…æ—Ēæ˜Ŋäŧ“åš“ admin、äđŸæ˜Ŋ .github/issue-owners.json äļ­åˆ—åįš„ ownerã€‚ä―•å†ĩčŊĨ提įĪšæœŽčšŦ及æ˜ŊäŋĄæŊæ€§įš„ïžŒč€Œäļ”čŋ™äš›čĄŒæ•°åΧéƒĻ分åđķäļæ˜Ŋé€ŧčū‘äŧĢ᠁ —— 47% æ˜Ŋäļ€äŧ―į”ąæœšå™Ļ双čŊ­å›žæ”ūįš„ fixture čŊ­æ–™ïžŒ26% æ˜Ŋæĩ‹čŊ•。

æ–đæĄˆïžš 范å›īåˆį†ïžŒč€Œäļ”倞åū—æģĻæ„įš„æ˜Ŋïžščŋ™äļŠ PR 朎čšŦå°ąæ˜Ŋæ‹†åˆ†įš„įŧ“æžœïžŒč€Œäļæ˜Ŋäļ€äļŠéœ€čĶå†æ‹†įš„ PR —— H4e čĒŦ切成 H4e-ačŪ°å―•ïžŒåģ朎 PRäļŽ H4e-bčŋčĄŒæ—ķäļŽ H4d įš„åˆ‡æģ•äļ€č‡ī。所有昂čīĩäļ”į”Ļ户åŊč§įš„éƒĻåˆ†éƒ―čĒŦæŽĻ后䚆äļƒäļŠå›Ē队å·Ĩ具、čŪĄåˆ’č́å†ģ、成员å…ģ停、mailbox äļ­įŧ§ã€æ—§å›Ē队åŊžå…Ĩ、åŊį”Ļ垀å…ģ、detach、å…ģé—­įš§č”ã€‚å‰Đäļ‹įš„æ­Ģæ˜Ŋ"čƒ―æŠŠč§„åˆ™čĄĻčūū凚æĨ"įš„æœ€å°é›†åˆã€‚æˆ‘äđŸč€ƒč™‘čŋ‡čƒ―åĶå…ˆåŠč―åœ° team_stateïžŒä―†åĶåĪ–äļ‰äļŠ domain éƒ―č́įŧ‘åۚ到å›Ēé˜ŸïžŒč€ŒåۃäŧŽįš„æäšĪæ—ķč§„åˆ™æ­Ģæ˜ŊæœŽåˆ‡į‰‡įš„é‡į‚đ所äŧĨ及有 team_state įš„åĨ‘įšĶ无æģ•čĄĻčŋ°čŋ™äš›č§„åˆ™ã€‚å†į å°ąäžšäšĪäŧ˜äļ€äļŠæ— æģ•č‡Šæˆ‘įšĶæŸįš„åĨ‘įšĶ。

åĪį”Ļ做åū—åĨ―ïžšæēĄæœ‰åž•å…Ĩåđķåˆ—įš„æ ĄéŠŒå·Ĩ具。TS äū§åŧšįŦ‹åœĻ assertManagedSessionStableId、assertManagedSessionDurableRef、assertManagedSessionDigest、boundedString、ManagedSessionRecordError、parseExtensionRun äļŽ isExtensionRunSuccessor äđ‹äļŠïž›Java äū§åŧšįŦ‹åœĻ ManagedExtensionRecords.closed/id/count/durableRef/digest/boundedText/requireRun/isRunSuccessor/same äđ‹äļŠã€‚æēĄæœ‰å‘įŽ°æ— å…ģæ”đåŠĻã€éĄšæ‰‹é‡æž„æˆ–æ žåžåŒ–å™Šå̰ —— 六åĪ„æ–‡æĄĢäŋŪæ”đéƒ―æ˜Ŋ H4e 重新划åۚ范å›īįš„åŋ…į„ķįŧ“果äŊäļ€åĪ„éƒ―åœĻäļ­æ–‡į‰ˆåŒæ­Ĩ䚆。

éĢŽé™Đïžš æ— å‡įš§éĢŽé™ĐäŋĄå·ã€‚Stage 1e įš„ revert į›ļå…ģč·Ŋåū„æ‰Ŧ描æēĄæœ‰å‘―äļ­äŧŧä―•äļ€éĄđäļæķ‰åŠå†…åŪđį”Ÿæˆå™Ļ、æĩåžč§Ģ析å™Ļ、shell æ‰§čĄŒã€MCP、LSP、ACP、relaunch 或 sandbox č·Ŋåū„。æ”đåŠĻåœĻįŧ“æž„äļŠäđŸæ˜Ŋæƒ°æ€§įš„ïžšå››äļŠ domain äŧį„ķäļåœĻ MANAGED_SESSION_ENABLED_DOMAINS äļ­ïžŒæœŽ PR æēĄæœ‰äŋŪæ”đčŊĨ文äŧķïžŒč€ŒæäšĪč·Ŋåū„äļŠäžšč°ƒį”Ļ assertManagedSessionDomainEnabledmanaged-session-authority.ts:1379因æ­Ī合åđķ后æēĄæœ‰äŧŧä―•č°ƒį”Ļæ–đčƒ―æäšĪå›Ē队čŪ°å―•ã€‚æ— čŋį§ŧ、无 OpenAPI 变æ›ī、无į”Ļ户åŊ见éĒ。

čŋ›å…ĨäŧĢ᠁åŪĄæŸĨ 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 12be226486ae50183be37c1e068167fcc389eff6 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Code review

I wrote down what I would do before opening the diff: follow the existing managed-*-record.ts + contracts/*-v1.fixtures.json sibling pattern that the base tree already has a dozen instances of, put the invariants in the one existing authority rather than a new validation layer, mirror them in the Java store, express cross-language agreement as one shared fixture corpus replayed by both, and ship it disabled. The PR does exactly that, and in two places it is more careful than my baseline — it matches the ordering of validation clauses across languages, not just their content, and it reuses the H4d-a outbox shape for the mailbox entry instead of inventing one.

The cross-language parity holds. That is the main thing that can silently rot in a two-validator contract, so I walked all four domains clause by clause rather than spot-checking. Every rule matches, including the rejection strings: lead binding, append-one-member-per-revision, the lifecycle stepping one state at a time, deleted frozen, task-number uniqueness scoped to the team, in_progress requiring an owner, blockedBy growing only and staying in-team, the cycle check (same DFS, same "only new edges can close a cycle" reasoning, same seen guard), message direction per kind, the target/input set-once rules, and plan approval requiring a plan-mode member. Validation order matches too — both sides check closed → run shape → party names → decision/feedback → identity/count/ref fields last — which is what makes "refused on the clause the fixture names" a meaningful assertion rather than a lucky substring. The resource-closure additions agree as well: TS lists refs per domain, Java iterates the five ref field names and skips absent ones, same net effect, and team_state correctly contributes none.

Non-blocking, one thing for H4e-b to inherit carefully. The message-target path assumes a recipient named when the message opened is still on the roster — team.members.find(...)! in the TS authority and memberWith(team, "name", to).get("childRunId") in the Java store. That is sound today, and I traced why: parties are in MESSAGE_FIXED_KEYS so to cannot change, the roster is append-only, and takesPart is enforced at open. But it is sound because of the invariant the PR's own open questions list flags as unsettled — "how a continued member stays on the roster". If H4e-b ever makes the roster shrinkable, this stops being a clean rejection and becomes a TS TypeError / Java NPE on the commit path. Worth a guard or at least a comment when that slice lands. Not a blocker here, and unreachable today since the domains cannot be submitted.

I also checked the as { teamId: string } downcast in the new authority block, which looked risky out of context. It is safe: parsed is typed ReturnType<ManagedExtensionRecordBody['parse']>, so parsed.record is the already-validated, frozen body by the time this runs. The re-parse (parseTeamTask(parsed.record)) is redundant work but matches how the sibling session_message branch already reads its refs, so it is convention rather than noise.

No correctness bugs, no security concerns, no regressions found. No AGENTS.md violations: ESM, no any, kebab-case filenames, collocated tests, vi.hoisted() used for the mock the vi.mock() factory consumes (the documented CLI-test gotcha), and comments only where the why is non-obvious — which in this diff is mostly the Legacy-constant provenance and the "only new edges" reasoning, both genuinely worth having.

Design docs are complete in both languages. EN and zh-CN have identical 15-heading structure (Problem and scope → Current state → Decisions → Records → the four record subsections → Non-goals → Files affected → Validation → Acceptance criteria → Open questions → Follow-up work), and all six pointer updates into the H4, H4a and H4d-a docs are mirrored in the Chinese versions. No translation gap.

Files changed (20 of 20 shown)
File What changed
packages/core/src/managed-runtime/managed-team-record.ts The four record bodies: parse, isStart and isSuccessor for each, plus the shared limits, the leader name and the kind/direction tables.
packages/core/src/managed-runtime/managed-session-authority.ts Commit-time rules binding every team record to its lead, and the three new resource-closure branches.
packages/core/src/managed-runtime/managed-extension-projection.ts Registers the four bodies, each projecting no task.
packages/core/src/managed-runtime/contracts/managed-team-record-v1.fixtures.json The shared corpus both languages replay: 5 templates, 125 cases, 57 successor pairs, limits and key sets.
packages/core/src/managed-runtime/contracts/managed-extension-projection-v1.fixtures.json Records the four new domains and their null task kind.
packages/core/src/managed-runtime/managed-team-record.test.ts Replays the corpus in TS; also asserts each domain stays out of the enabled list and that the gate throws.
packages/core/src/managed-runtime/managed-session-authority.team.test.ts 13 cases over the real commit and rebuild paths, with the gate lifted by a hoisted flag only to plant records.
packages/sdk-java/.../store/ManagedTeamRecords.java The Java mirror of the four validators, matching clause order and messages.
packages/sdk-java/.../store/ManagedExtensionRecordStore.java The Java mirror of the lead-binding rules, plus the ref-closure check and a domain-body read helper.
packages/sdk-java/.../store/ManagedExtensionProjection.java Registers the four bodies on the Java side.
packages/sdk-java/.../ManagedTeamRecordContractTest.java Replays the same core fixture file, walking up to find it, and pins the exact clause on every refusal.
packages/sdk-java/.../ManagedTeamStoreTest.java 6 cases committing the same chains over H2 in MySQL mode and asserting the store's rejection codes.
docs/design/2026-10-10-managed-agent-teams.md New design doc: decisions, the four records, non-goals, validation, acceptance criteria, open questions.
docs/design/2026-10-10-managed-agent-teams.zh-CN.md Chinese version, structurally identical.
docs/design/2026-10-04-managed-child-agents.md Redirects the stale detach-owner pointer from H4c to the Detach follow-up.
docs/design/2026-10-04-managed-child-agents.zh-CN.md Same pointer fix, Chinese.
docs/design/2026-10-06-managed-child-agent-runtime.md Splits the H4e delivery-map row into record contract (H4e-a) and runtime (H4e-b).
docs/design/2026-10-06-managed-child-agent-runtime.zh-CN.md Same split, Chinese.
docs/design/2026-10-09-managed-session-messages.md Points its H4e row at the new design.
docs/design/2026-10-09-managed-session-messages.zh-CN.md Same pointer, Chinese.

Testing evidence

This is an unattended CI run, so per the triage rules I did not build or execute anything from this PR — the evidence below is the PR's own CI, read through the API for the reviewed commit. No check has failed.

Final CI results for 12be226 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Hosted process fault gates / MySQL 8.4 / Java 21 ðŸšŦ cancelled
Runtime Broker and Managed Agent MariaDB / Java 21 ðŸšŦ cancelled
Test (ubuntu-latest, Node 22.x) ðŸšŦ cancelled
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ðŸšŦ cancelled
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Flyway migration version uniqueness ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
macos-latest / Java 21 ✅ success
Real daemon E2E / Java 11 ✅ success
ubuntu-latest / Java 11 ✅ success
ubuntu-latest / Java 17 ✅ success
ubuntu-latest / Java 21 ✅ success
windows-latest / Java 21 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / æŊäļŠæĢ€æŸĨ名äļ€čĄŒïžˆå–最新äļ€æŽĄčŋčĄŒïž‰ïžŒįœį•Ĩ skippedåĪąčīĨéĄđ排åœĻ最前。

Two workflow runs with event == pull_request were still unfinished at review time, and 58 further checks are skipped — those are the conditional bot-orchestration and path-filtered jobs (review-pr, verify, tmux-testing, publish-*, precheck-pr and friends), which is the normal shape for this repo, not a gap. The triage check in flight is this job. The still-pending items that actually matter for this diff are the Node unit suite and the Java 21 / MySQL 8.4 / MariaDB lanes, since those are what run the two contract suites and the H2 store tests. I did not poll or wait on them; the finalize pass rewrites the table above once CI settles.

What CI does substantiate is the PR's central claim. The cross-language agreement is not an assertion in the description — it is two suites, managed-team-record.test.ts under vitest and ManagedTeamRecordContractTest under Maven surefire, both reading the same file at packages/core/src/managed-runtime/contracts/managed-team-record-v1.fixtures.json (the Java test walks up parent directories to find it). When those two go green in the lanes above, the agreement claim is pinned by CI, not by the author. I verified the suites are not vacuous by reading them: all 89 invalid cases assert the specific clause substring via expect(...).toThrow(fixture.error) / assertTrue(refused.getMessage().contains(...)), isStart is asserted on every case valid or invalid, the TS side additionally asserts deep-freeze and the returned recordId, and the disabled-domain test flips the hoisted enablement.teams flag to false and asserts both the exact gate message and publishedBodies(...) === 0 for all four domains. I also confirmed the corpus matches the description: 125 cases and 57 successor pairs, with limits equal to MANAGED_TEAM_LIMITS.

Sandboxed verification would settle the one claim CI cannot: @qwen-code /verify — that the new commit-time guards actually fail closed. The description reports 22 of 22 authority mutants and 22 of 22 Java store/validator mutants killed, plus 6 TS validator spot checks; that is the author's own mutation run, not something CI reproduces, and it is precisely the load-bearing question for a contract whose entire value is its refusals. A green suite proves the fixtures pass; an A/B run against the base build would prove that deleting a guard turns a fixture red. Worth naming since the author has write access and can trigger it directly.

Not verified, with reasons: the mutation-testing numbers (author's claim, no CI lane reproduces them); the Java clean verify checkstyle:check totals of 1,625 surefire tests / 0 Checkstyle violations / 0 SpotBugs findings (author's local run — CI's Java lanes are still in flight at review time, so I am not treating them as confirmed); and the core managed-runtime figure of 58 files / 3,160 tests (same reason). Real-scenario tmux testing is N/A on two counts: this is an unattended CI run, which never drives the TUI, and there is nothing user-visible to drive — the four domains remain disabled for submission, so no caller can produce a team record through the product.

äļ­æ–‡čŊŽ

äŧĢ᠁åŪĄæŸĨ

我åœĻ打垀 diff äđ‹å‰å…ˆå†™äļ‹äš†č‡Šå·ąäžšæ€Žäđˆåšïžšæēŋį”Ļ base 树里å·ē有十几äļŠåŪžäū‹įš„ managed-*-record.ts + contracts/*-v1.fixtures.json åŒįš§æĻĄåžïžŒæŠŠäļå˜é‡æ”ūčŋ›å”Ŋäļ€įš„é‚ĢäļŠ authority 而äļæ˜Ŋ新垀äļ€åą‚æ ĄéŠŒïžŒåœĻ Java store é‡Œé•œåƒåŒæ ·įš„č§„åˆ™ïžŒį”Ļäļ€äŧ―čĒŦ双čŊ­å›žæ”ūįš„å…ąäšŦ fixture čŊ­æ–™æĨčĄĻčūūč·ĻčŊ­čĻ€äļ€č‡ī性åđķäŧĨį́į”ĻįŠķ态äšĪäŧ˜ã€‚čŋ™äļŠ PR æ­Ģæ˜Ŋčŋ™äđˆåšįš„ïžŒč€Œäœ‰äļĪåĪ„æŊ”æˆ‘įš„åŸšįšŋæ›īįŧ†č‡ī —— åۃč·ĻčŊ­čĻ€åŊđé―äš†æ ĄéŠŒå­åĨįš„éĄšåšïžŒäļåŠæ˜Ŋ内åŪđïž›mailbox æĄį›ŪåĪį”Ļ乆 H4d-a įš„ outbox å―ĒįŠķïžŒč€Œäļæ˜ŊåĶ造äļ€äļŠã€‚

č·ĻčŊ­čĻ€äļ€č‡ī性成įŦ‹ã€‚ čŋ™æ˜ŊåŒæ ĄéŠŒå™ĻåĨ‘įšĶ里最åŪđæ˜“æ‚„æ‚„č…åįš„éƒĻ分所äŧĨ我逐äļŠ domain é€æĄåŊđį…§ïžŒæēĄæœ‰åŠæŠ―æŸĨã€‚æ‰€æœ‰č§„åˆ™éƒ―äļ€č‡ī包拎拒įŧäŋĄæŊ字įŽĶäļēïžšlead įŧ‘åŪšã€æŊäļŠäŋŪčŪĒ及čŋ―加äļ€åæˆå‘˜ã€į”Ÿå‘―å‘Ļ期æŊæŽĄåŠčĩ°ä­Ĩ、deleted å†ŧįŧ“、äŧŧåŠĄįž–å·åœĻå›Ē队内å”Ŋäļ€ã€in_progress åŋ…éĄŧ有 owner、blockedBy 及åĒžäļ”į•™åœĻå›Ē队内、įŽŊæĢ€æĩ‹ïžˆåŒæ ·įš„ DFSã€åŒæ ·įš„"及有新čūđčƒ―æˆįŽŊ"æŽĻį†ã€åŒæ ·įš„ seen äŋæŠĪ、æŊį§æķˆæŊįąŧåž‹įš„æ–đå‘č§„åˆ™ã€target/input 及čŪūį―Ūäļ€æŽĄįš„č§„åˆ™ïžŒäŧĨ及čŪĄåˆ’åŪĄæ‰đčĶæą‚æˆå‘˜åĪ„äšŽ plan modeã€‚æ ĄéŠŒéĄšåšäđŸäļ€č‡ī —— äļĪäū§éƒ―æ˜Ŋ closed → run å―ĒįŠķ → 参äļŽæ–đ名字 → decision/feedback → 最后才æ˜Ŋ identity/count/ref 字æŪĩ —— čŋ™æ­Ģæ˜Ŋ"按 fixture æŒ‡åįš„å­åĨčĒŦ拒įŧ"成äļšæœ‰æ„äđ‰æ–­čĻ€ã€č€Œäļæ˜Ŋįǰ巧子äļēå‘―äļ­įš„原因。čĩ„æšé—­åŒ…įš„æ–°åĒžäđŸäļ€č‡īïžšTS 按 domain 列凚 refJava 遍历䚔äļŠ ref 字æŪĩ名åđķč·ģčŋ‡äļå­˜åœĻįš„ïžŒå‡€æ•ˆæžœį›ļåŒïžŒč€Œ team_state æ­ĢįĄŪ地äļ€äļŠéƒ―äļčīĄįŒŪ。

非é˜ŧåĄžïžŒæœ‰äļ€åĪ„éœ€č́ H4e-b 小åŋƒįŧ§æ‰ŋ。 æķˆæŊį›Ū标č§Ģ析č·Ŋåū„假čŪū"æķˆæŊ垀åŊæ—ķčĒŦæŒ‡åįš„æŽĨæ”ķæ–đäŧåœĻ名册äļŠ" —— TS authority é‡Œįš„ team.members.find(...)! äļŽ Java store é‡Œįš„ memberWith(team, "name", to).get("childRunId")。čŋ™åœĻäŧŠåĪĐæ˜Ŋ成įŦ‹įš„我čŋ―䚆原因参äļŽæ–đåœĻ MESSAGE_FIXED_KEYS äļ­æ‰€äŧĨ to äļäžšå˜ïžŒåå†ŒåŠčŋ―加äļ”垀åŊæ—ķåžšåˆķæ ĄéŠŒäš† takesPartã€‚ä―†åŪƒæˆįŦ‹æ°æ°äūčĩ–䚎朎 PR č‡Šå·ąåœĻ垀æ”ūé—ŪéĒ˜é‡Œåˆ—äļšæœŠåŪšįš„é‚ĢæĄäļå˜é‡ ——"įŧ­äŧŧ成员åĶ‚ä―•į•™åœĻ名册äļŠ"。åĶ‚æžœ H4e-b čŪĐ名册变åū—åŊįžĐ减čŋ™é‡Œå°ąäļå†æ˜Ŋåđēå‡€įš„æ‹’įŧïžŒč€ŒäžšåœĻ提äšĪč·Ŋåū„äļŠå˜æˆ TS įš„ TypeError 或 Java įš„ NPEã€‚į­‰é‚ĢäļŠåˆ‡į‰‡č―地æ—ķ倞åū—加äļ€é“äŋæŠĪ或č‡ģ少äĄæģĻ释。åœĻ朎 PR 里äļæ˜Ŋé˜ŧåĄžéĄđïžŒč€Œäļ”äŧŠåĪĐäļåŊčūū —— čŋ™äš› domain 无æģ•čĒŦ提äšĪ。

我äđŸæĢ€æŸĨ䚆新åĒž authority äŧĢį å—é‡Œįš„ as { teamId: string } 向äļ‹č―Žåž‹ïžŒč„ąįĶŧäļŠä–‡įœ‹åŪƒåƒæ˜Ŋ有éĢŽé™Đįš„ã€‚åۃæ˜ŊåŪ‰å…Ļįš„ïžšparsed įš„įąŧ型æ˜Ŋ ReturnType<ManagedExtensionRecordBody['parse']>所äŧĨæ‰§čĄŒåˆ°čŋ™é‡Œæ—ķ parsed.record å·ēįŧæ˜Ŋæ ĄéŠŒčŋ‡åđķå†ŧįŧ“įš„čŪ°å―•ä―“ã€‚é‡æ–°č§Ģ析parseTeamTask(parsed.record)įĄŪåŪžæ˜Ŋå†—ä―™å·Ĩä―œïžŒä―†čŋ™äļŽåŒįš§ session_message 分æ”ŊčŊŧ取 ref įš„æ—Ē有写æģ•äļ€č‡īïžŒåąžäšŽįšĶåŪšč€Œéžå™ŠåĢ°ã€‚

æœŠå‘įŽ°æ­ĢįĄŪæ€§įžšé™·ã€åŪ‰å…Ļé—ŪéĒ˜æˆ–å›žå―’ã€‚æœŠå‘įŽ°čŋå AGENTS.md äđ‹åĪ„ïžšESM、无 any、kebab-case 文äŧķ名、æĩ‹čŊ•äļŽæšį åŒį›Ūå―•ã€vi.mock() å·Ĩ厂æķˆčīđįš„ mock į”Ļ乆 vi.hoisted()æ­Ģæ˜Ŋ文æĄĢ里į‚đæ˜Žįš„ CLI æĩ‹čŊ•é™·é˜ąïž‰ïžŒæģĻé‡ŠåŠå‡šįŽ°åœĻ why äļæ˜Žæ˜ūįš„ä―į―Ū â€”â€” åœĻčŋ™äļŠ diff 里äļŧč́æ˜Ŋ Legacy åļļé‡įš„å‡šåĪ„å’Œ"及有新čūđ"įš„æŽĻį†ïžŒčŋ™äļĪåĪ„įĄŪåŪžå€žåū—写。

čŪūčŪĄæ–‡æĄĢ双čŊ­åیæ•ī。 č‹ąæ–‡äļŽäļ­æ–‡į‰ˆæœ‰åیå…Ļį›ļåŒįš„ 15 äļŠæ ‡éǘįŧ“构é—ŪéǘäļŽčŒƒå›ī → įŽ°įŠķ → å†ģį­– → čŪ°å―• → 四äļŠčŪ°å―•å°čŠ‚ → 非į›Ū标 → å—å―ąå“įš„æ–‡äŧķ → 驌čŊ → 驌æ”ķ标准 → 垀æ”ūé—Ūéǘ → 后įŧ­å·Ĩä―œïž‰ïžŒåđķäļ”åŊđ H4、H4a、H4d-a 文æĄĢįš„å…ĻéƒĻ六åĪ„æŒ‡å‘æ›īæ–°éƒ―åœĻäļ­æ–‡į‰ˆåŒæ­Ĩ䚆。无įŋŧčŊ‘įžšåĢ。

æĩ‹čŊ•čŊæŪ

čŋ™æ˜Ŋäļ€æŽĄæ— äššå€žåŪˆįš„ CI čŋčĄŒïžŒå› æ­ĪæŒ‰į…§ triage č§„åˆ™æˆ‘æēĄæœ‰æž„åŧšæˆ–æ‰§čĄŒæœŽ PR įš„äŧŧä―•äŧĢ᠁ —— äļ‹éĒįš„čŊæŪæ˜Ŋ通čŋ‡ API čŊŧåˆ°įš„ã€čŊĨ PR 臩čšŦåœĻčĒŦåŪĄæŸĨ commit äļŠįš„ CI įŧ“果。æēĄæœ‰äŧŧä―•æĢ€æŸĨåĪąčīĨ。

åŪĄæŸĨæ—ķčŋ˜æœ‰äļĪäļŠ event == pull_request įš„ workflow run 朊åŪŒæˆïžŒåĶ有 58 äļŠæĢ€æŸĨäļš skipped —— é‚Ģ乛æ˜ŊæĄäŧķč§Ķå‘įš„ bot įž–æŽ’äļŽč·Ŋåū„čŋ‡æŧĪäŧŧåŠĄïžˆreview-pr、verify、tmux-testing、publish-*、precheck-pr į­‰ïž‰ïžŒčŋ™æ˜Ŋ朎äŧ“åš“įš„æ­Ģåļļå―Ēæ€ïžŒäļæ˜ŊįžšåĢ。æ­ĢåœĻčŋčĄŒįš„ triage æĢ€æŸĨå°ąæ˜Ŋ朎äŧŧåŠĄæœŽčšŦ。åŊđčŋ™äļŠ diff ᜟæ­Ģ重čĶįš„åū…åŪŒæˆéĄđæ˜Ŋ Node 单æĩ‹åĨ—äŧķäļŽ Java 21 / MySQL 8.4 / MariaDB 通道因äļšæ­Ģæ˜ŊåۃäŧŽčŋčĄŒäļĪäļŠåĨ‘įšĶåĨ—äŧķ和 H2 store æĩ‹čŊ•。我æēĄæœ‰č―ŪčŊĒæˆ–į­‰åū…åۃäŧŽïž›CI č―åŪšåŽ finalize įŽŊčŠ‚äžšå°ąåœ°é‡å†™äļŠéĒįš„čĄĻ栞。

CI čƒ―åΟčŊåŪžįš„æ­Ģæ˜Ŋ朎 PR įš„æ ļåŋƒäļŧ垠。č·ĻčŊ­čĻ€äļ€č‡īäļæ˜Ŋ描čŋ°é‡Œįš„äļ€åĨ断čĻ€ —— åۃæ˜ŊäļĪäļŠåĨ—äŧķvitest äļ‹įš„ managed-team-record.test.ts äļŽ Maven surefire äļ‹įš„ ManagedTeamRecordContractTestäļĪ者čŊŧå–įš„æ˜Ŋ同äļ€äļŠæ–‡äŧķ packages/core/src/managed-runtime/contracts/managed-team-record-v1.fixtures.jsonJava æĩ‹čŊ•通čŋ‡å‘äļŠéåކįˆķį›Ūå―•æ‰ū到åŪƒïž‰ã€‚å―“äļŠéĒé‚Ģ䚛通道变įŧŋæ—ķäļ€č‡ī性äļŧåž å°ąæ˜Ŋį”ą CI é’‰ä―įš„ïžŒč€Œäļæ˜Ŋį”ąä―œč€…é’‰ä―įš„ã€‚æˆ‘é€ščŋ‡é˜…čŊŧįĄŪčŪĪ乆čŋ™äš›åĨ—äŧķäļæ˜ŊįĐšč―Žïžšå…ĻéƒĻ 89 äļŠæ— æ•ˆį”Ļäū‹éƒ―通čŋ‡ expect(...).toThrow(fixture.error) / assertTrue(refused.getMessage().contains(...)) 断čĻ€äš†å…·ä―“įš„å­åĨ字įŽĶäļēisStart åœĻæŊäļŠį”Ļäū‹äļŠéƒ―čĒŦ断čĻ€ïžˆæ— čŪšæœ‰æ•ˆæ— æ•ˆïž‰ïžŒTS äū§čŋ˜éĒåĪ–æ–­čĻ€äš†æ·ąåšĶå†ŧįŧ“äļŽčŋ”å›žįš„ recordIdïžŒč€Œį́į”Ļ domain įš„æĩ‹čŊ•把 hoisted įš„ enablement.teams 标åŋ—į―Ūäļš falseåđķåŊđ四äļŠ domain 同æ—ķ断čĻ€äš†įēūįĄŪįš„é—Ļį́äŋĄæŊäļŽ publishedBodies(...) === 0。我äđŸæ ļåŪžäš†čŊ­æ–™äļŽæčŋ°į›ļįŽĶïžš125 äļŠį”Ļäū‹ã€57 äļŠåŽįŧ§åŊđäļ” limits į­‰äšŽ MANAGED_TEAM_LIMITS。

æē™įŪąåŒ–éŠŒčŊčƒ―åΟč§Ģå†ģ CI č§Ģå†ģäļäš†įš„é‚ĢäĄäļŧåž ïžš@qwen-code /verify —— åģ新åĒžįš„æäšĪæ—ķåۈåŦæ˜ŊåĶįœŸįš„ fail closed。描čŋ°äļ­æŠĨ告䚆 22/22 äļŠ authority å˜åž‚ä―“äļŽ 22/22 äļŠ Java store/validator å˜åž‚ä―“čĒŦ杀æ­ŧåĪ–åŠ  6 äļŠ TS validator æŠ―æŸĨïž›é‚Ģæ˜Ŋä―œč€…č‡Šå·ąįš„å˜åž‚æĩ‹čŊ•čŋčĄŒïžŒCI æēĄæœ‰åŊđ嚔通道åĪįŽ°åŪƒïžŒč€Œčŋ™æ°æ°æ˜Ŋäļ€äļŠ"å…ĻéƒĻäŧ·å€žå°ąåœĻ䚎åŪƒįš„æ‹’įŧ"įš„åĨ‘įšĶæœ€åƒé‡įš„é—ŪéĒ˜ã€‚įŧŋč‰ēįš„åĨ—äŧķčŊæ˜Ž fixture 通čŋ‡ïž›é’ˆåŊđ base 构åŧšįš„ A/B čŋčĄŒæ‰čƒ―čŊæ˜Žåˆ æŽ‰æŸäļŠåۈåŦ侚čŪĐæŸäļŠ fixture 变įšĒã€‚ä―œč€…æœ‰å†™æƒé™ã€åŊäŧĨį›īæŽĨč§Ķ发所äŧĨ倞åū—į‚đ名。

朊銌čŊéĄđ及原因变垂æĩ‹čŊ•æ•°å­—ïžˆä―œč€…äļŧ垠无 CI 通道åĪįŽ°ïž‰ïž›Java clean verify checkstyle:check įš„ 1,625 äļŠ surefire æĩ‹čŊ• / 0 Checkstyle čŋč§„ / 0 SpotBugs å‘įŽ°įš„æ€ŧčŪĄïžˆä―œč€…æœŽåœ°čŋčĄŒ —— åŪĄæŸĨæ—ķ CI įš„ Java 通道äŧåœĻčŋ›čĄŒïžŒå› æ­Ī我äļå°†å…ķ视äļšå·ēįĄŪčŪĪäŧĨ及 core managed-runtime įš„ 58 äļŠæ–‡äŧķ / 3,160 äļŠæĩ‹čŊ•ïžˆåŒæ ·åŽŸå› ïž‰ã€‚įœŸåŪžåœšæ™Ŋ tmux æĩ‹čŊ•äļš N/AïžŒį†į”ąæœ‰äļĪæĄïžščŋ™æ˜Ŋ无䚚倞åŪˆįš„ CI čŋčĄŒïžŒäŧŽäļéĐąåŠĻ TUIïž›č€Œäļ”æēĄæœ‰į”Ļ户åŊč§įš„äļœčĨŋåŊéĐąåŠĻ —— 四äļŠ domain äŧį„ķį́į”Ļ提äšĪ因æ­ĪæēĄæœ‰äŧŧä―•č°ƒį”Ļæ–đčƒ―é€ščŋ‡äš§å“äš§į”Ÿå›Ē队čŪ°å―•ã€‚

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 12be226486ae50183be37c1e068167fcc389eff6 · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — the parity work is genuinely careful and the change is structurally inert; the one point I am holding back is a fragility this PR hands to H4e-b, not a defect in it.

Stepping back: my independent proposal was to follow the existing sibling record pattern, put the invariants in the one authority, mirror them in Java, replay one shared corpus in both languages, and ship disabled. The PR matches that and beats it in one respect I did not think of — it aligns the order of validation clauses across the two languages, which is what turns "refused on the clause the fixture names" from a lucky substring match into a real assertion. I did not find a simpler path it missed.

On the questions that usually decide these: the problem is verified in the base tree, not asserted (four registered domains with no body and no enablement). The direction follows a server-first ordering this codebase already documents for itself. The scope is already the reduced one — H4e was split into records and runtime, and everything user-visible is on the other half. Nothing in the diff is unrelated: six doc edits, each a consequence of the re-scope, each mirrored in Chinese. If I were maintaining this in six months I would thank the author for the fixture corpus and the clause-ordered validators rather than curse them; the thing that usually rots in a two-language contract is silent divergence, and 125 cases plus 57 successor pairs replayed from a single file is the right defence against it.

What keeps this at 4 rather than 5 is the roster-shrink fragility I named in Stage 2. It is not a bug today — I traced the invariant that makes it safe (parties are fixed keys, the roster is append-only, takesPart is enforced at open) and the domains cannot be submitted at all. But the PR's own open-questions list flags "how a continued member stays on the roster" as unsettled for H4e-b, and that is exactly the invariant the non-null lookups in the message-target path rest on. If the roster ever becomes shrinkable, a clean rejection turns into a TypeError / NPE on the commit path. Worth carrying into H4e-b as a known obligation.

I am not treating the size as a reservation. At 1,642 production lines this is over the advisory mark, but the two-tier core gate in AGENTS.md governs external PRs and exempts maintainer-authored ones, and this author is a repo admin and a listed owner in .github/issue-owners.json. Size aside, the shape is favourable: 47% of the diff is one machine-replayed fixture corpus, 26% is tests, and the 1,642 logic lines land behind a gate that refuses to let anyone submit them.

Verdict: approve, with the approval deferred until CI lands green on 12be226486ae50183be37c1e068167fcc389eff6. Two pull_request workflow runs were still unfinished at review time — the Node unit suite and the Java 21 / MySQL 8.4 / MariaDB lanes, which are precisely the ones that run the two contract suites and the H2 store tests. Approving now would attest to a result that does not exist yet, so I am leaving the deferred-approval marker below for the finalize pass to act on once those settle; it will withhold the approval and flag the status comment if anything lands red or the head moves.

The one thing I would suggest, if you want it before merge rather than after: an @qwen-code /verify run to pin that the new guards fail closed. The 22-of-22 mutation kills in the description are your own run and no CI lane reproduces them, and for a contract whose entire value is its refusals, that is the load-bearing evidence. Everything else I would merge as-is.

äļ­æ–‡čŊŽ

äŋĄåŋƒåšĶïžš4/5 —— äļ€č‡ī性å·Ĩä―œåšåū—įĄŪåŪžįŧ†č‡īæ”đåŠĻåœĻįŧ“æž„ä˜Ŋæƒ°æ€§įš„ïž›æˆ‘äŋį•™įš„é‚Ģäļ€åˆ†æ˜Ŋ朎 PR äšĪįŧ™ H4e-b įš„äļ€åĪ„č„†åžąæ€§ïžŒč€Œäļæ˜ŊåŪƒč‡ŠčšŦįš„įžšé™·ã€‚

退ä­Ĩįœ‹æ•īä―“ïžšæˆ‘į‹ŽįŦ‹æå‡šįš„æ–đæĄˆæ˜Ŋæēŋį”Ļæ—Ēæœ‰įš„åŒįš§čŪ°å―•æĻĄåžã€æŠŠäļå˜é‡æ”ūčŋ›å”Ŋäļ€įš„ authority、åœĻ Java äū§é•œåƒã€į”Ļäļ€äŧ―å…ąäšŦčŊ­æ–™åœĻäļĪį§čŊ­čĻ€äļ­å›žæ”ūåđķäŧĨį́į”ĻįŠķ态äšĪäŧ˜ã€‚čŋ™äļŠ PR äļŽäđ‹äļ€č‡īåđķäļ”åœĻäļ€äļŠæˆ‘æēĄæƒģåˆ°įš„æ–đéĒ做åū—æ›īåĨ― —— åۃåŊđé―äš†äļĪį§čŊ­čĻ€äļ­æ ĄéЌ子åĨįš„éĄšåšïžŒč€Œčŋ™æ­Ģæ˜Ŋ把"按 fixture æŒ‡åįš„å­åĨčĒŦ拒įŧ"äŧŽįĒ°å·§įš„å­äļēå‘―äļ­å˜æˆįœŸæ­Ģ断čĻ€įš„å…ģé”Ū。我æēĄæœ‰æ‰ū到åŪƒé—æžįš„æ›īįŪ€č·Ŋåū„。

å…ģ乎通åļļå†ģåۚčŋ™įąŧåˆĪæ–­įš„å‡ äļŠé—ŪéĒ˜ïžšé—Ūéǘæ˜ŊåœĻ base äŧĢį æ ‘é‡Œæ ļåŪžčŋ‡įš„äļæ˜Ŋ断čĻ€å‡šæĨįš„ïžˆå››äļŠå·ēį™ŧčŪ°įš„ domainæ—Ē无čŪ°å―•ä―“äđŸæœŠåŊį”Ļ。æ–đ向éĩåūŠįš„æ˜Ŋčŋ™äļŠäŧĢį åš“č‡Šå·ąå·ēįŧå†™æ˜Žįš„ server å…ˆčĄŒéĄšåšã€‚čŒƒå›īå·ēįŧæ˜ŊčĢå‰ŠåŽįš„é‚Ģäļ€åŠ —— H4e čĒŦ切成čŪ°å―•äļŽčŋčĄŒæ—ķ所有į”Ļ户åŊč§įš„éƒĻåˆ†éƒ―åœĻåĶäļ€åŠã€‚diff 里æēĄæœ‰æ— å…ģ内åŪđïžšå…­åĪ„æ–‡æĄĢäŋŪæ”đéƒ―æ˜Ŋ重新划åۚ范å›īįš„åŋ…į„ķįŧ“果äļ”éƒ―åœĻäļ­æ–‡į‰ˆåŒæ­Ĩ䚆。åĶ‚æžœå…­äļŠæœˆåŽį”ąæˆ‘æĨįŧĪ我䞚感č°Ēä―œč€…į•™äļ‹čŋ™äŧ― fixture čŊ­æ–™å’ŒæŒ‰å­åĨæŽ’åšįš„æ ĄéŠŒå™ĻïžŒč€Œäļæ˜Ŋ埋æ€Ļäŧ–双čŊ­čĻ€åĨ‘įšĶ里通åļļč…åįš„äļœčĨŋæ˜Ŋ悄无å̰æŊįš„åˆ†æ­§ïžŒč€Œ"125 äļŠį”Ļäū‹åŠ  57 äļŠåŽįŧ§åŊđ、äŧŽå•ä–‡äŧķ回æ”ū"æ­Ģæ˜ŊåŊđæ­Īįš„æ­ĢįĄŪé˜ēåūĄã€‚

čŪĐåۃ停åœĻ 4 åˆ†č€Œäļæ˜Ŋ 5 åˆ†įš„ïžŒæ˜Ŋ我åœĻ Stage 2 į‚đåįš„åå†ŒåŊįžĐå‡č„†åžąæ€§ã€‚čŋ™åœĻäŧŠåĪĐäļæ˜Ŋ bug —— 我čŋ―æŸĨ乆ä―ŋåۃåŪ‰å…Ļįš„äļå˜é‡ïžˆå‚äļŽæ–đåąžäšŽå›šåۚé”Ū、名册及čŋ―加、垀åŊæ—ķåžšåˆķæ ĄéŠŒ takesPartïž‰ïžŒč€Œäļ”čŋ™äš› domain æ đ朎无æģ•čĒŦ提äšĪã€‚ä―†æœŽ PR č‡Šå·ąįš„åž€æ”ūé—ŪéĒ˜åˆ—čĄĻå°ąæŠŠ"įŧ­äŧŧ成员åĶ‚ä―•į•™åœĻ名册äļŠ"标äļš H4e-b åū…å†ģïžŒč€Œé‚Ģ恰åĨ―æ­Ģæ˜ŊæķˆæŊį›Ū标č·Ŋåū„äļŠé‚Ģ乛非įĐšæŸĨæ‰ū所äūčĩ–įš„äļå˜é‡ã€‚åĶ‚æžœåå†Œæœ‰æœä—Ĩ变åū—åŊįžĐ减äļ€æŽĄåđēå‡€įš„æ‹’įŧå°ąäžšå˜æˆæäšĪč·Ŋåū„äļŠįš„ TypeError / NPE。倞åū—ä―œäļšäļ€éĄđå·ēįŸĨäđ‰åŠĄåļĶčŋ› H4e-b。

我æēĄæœ‰æŠŠč§„æĻĄå―“ä―œäŋį•™æ„č§ã€‚äŧĨ 1,642 čĄŒį”Ÿäš§é€ŧčū‘čŪĄïžŒåۃčķ…čŋ‡äš†æįĪšįšŋïžŒä―† AGENTS.md įš„æ ļåŋƒäļĪįš§é—ĻįĶé’ˆåŊđįš„æ˜ŊåĪ–éƒĻ PRã€čąå…įŧĪč€…æœŽäššæäšĪįš„ PRïžŒč€ŒæœŽä―œč€…æ—Ēæ˜Ŋäŧ“åš“ admin、äđŸæ˜Ŋ .github/issue-owners.json äļ­åˆ—åįš„ ownerã€‚æŠ›åž€č§„æĻĄäļč°ˆïžŒčŋ™äļŠæ”đåŠĻįš„å―Ēæ€æ˜Ŋ有åˆĐįš„ïžš47% įš„ diff æ˜Ŋäļ€äŧ―į”ąæœšå™Ļ双čŊ­å›žæ”ūįš„ fixture čŊ­æ–™ïžŒ26% æ˜Ŋæĩ‹čŊ•ïžŒč€Œé‚Ģ 1,642 行é€ŧčū‘äŧĢį č―åœĻäļ€é“æ‹’įŧäŧŧä―•äššæäšĪįš„é—Ļį́äđ‹åŽã€‚

įŧ“čŪšïžšæ‰đå‡†ïžŒä―†æŠŠæ‰đ准åŧķčŋŸåˆ° CI åœĻ 12be226486ae50183be37c1e068167fcc389eff6 äļŠå˜įŧŋäđ‹åŽã€‚åŪĄæŸĨæ—ķčŋ˜æœ‰äļĪäļŠ pull_request workflow run 朊åŪŒæˆ —— Node 单æĩ‹åĨ—äŧķäļŽ Java 21 / MySQL 8.4 / MariaDB é€šé“ïžŒč€ŒåۃäŧŽæ°æ°æ˜ŊčŋčĄŒäļĪäļŠåĨ‘įšĶåĨ—äŧķ和 H2 store æĩ‹čŊ•įš„é‚Ģäš›ã€‚įŽ°åœĻæ‰đå‡†å°ąį­‰äšŽäļšäļ€äļŠå°šäļå­˜åœĻįš„įŧ“æžœčƒŒäđĶ所äŧĨ我把åŧķčŋŸæ‰đå‡†įš„æ ‡čŪ°į•™įŧ™ finalize įŽŊčŠ‚ïžŒį­‰čŋ™äš›é€šé“č―åŪšåŽį”ąåŪƒæ‰§čĄŒïž›åĶ‚æžœæœ‰äŧŧä―•äļ€éĄđ变įšĒ或 head å‘į”Ÿį§ŧåŠĻåŪƒäžšäŋį•™äļæ‰đ准åđķåœĻįŠķ态čŊ„čŪšäļ­æ ‡å‡šã€‚

åĶ‚æžœæƒģåœĻ合åđķå‰č€Œäļæ˜Ŋ合åđķ后čĄĨäļ€äŧķäš‹ïžŒæˆ‘įš„åŧščŪŪæ˜Ŋïžšč·‘äļ€æŽĄ @qwen-code /verify把"新åĒžåۈåŦįĄŪåŪž fail closed"é’‰ä―ã€‚æčŋ°é‡Œ 22/22 įš„å˜åž‚ä―“å‡ŧ杀æ˜Ŋä― č‡Šå·ąįš„čŋčĄŒįŧ“果CI æēĄæœ‰é€šé“åĪįŽ°åŪƒïžŒč€ŒåŊđäļ€äļŠå…ĻéƒĻäŧ·å€žå°ąåœĻ䚎åŪƒįš„æ‹’įŧįš„åĨ‘įšĶæĨčŊīčŋ™æ­Ģæ˜Ŋæœ€åƒé‡įš„čŊæŪ。å…ķä―™éƒĻåˆ†æˆ‘äžšæŒ‰įŽ°įŠķ合åđķ。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 12be226486ae50183be37c1e068167fcc389eff6 · re-run with @qwen-code /triage

Address the review on #13811.

The authority and the store checked that a team message travels between
the leader and members of its team only when the message opened, and
the cross-record rules run before the chain rules. A successor that
readdressed an existing message to a name outside the roster therefore
reached the recipient lookup with no member: the TypeScript authority
threw a TypeError and the Java store a NullPointerException (a 500)
instead of the record conflict.

Check the parties on every revision in both languages. The roster only
grows and from/to are fixed, so a lawful successor always passes, and a
readdressed one is now refused as the conflict it is, before anything
looks its recipient up. Authority and store witnesses change an
existing message's recipient to a stranger and assert the conflict, the
rule's message and an unchanged committed revision.
@wenshao
wenshao requested a balanced review from Copilot October 10, 2026 03:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 4851c2364eddb5ab55a15611c2b01e7dc55fad22, including the delta from 12be226486ae50183be37c1e068167fcc389eff6 and the previous unresolved finding.

No new actionable findings. The previous P2 recipient-lookup issue is fixed in both implementations. Participant validation now precedes the recipient lookup on every revision. I checked the append-only roster and immutable message parties against the current code, including the authority's journal-rebuild path; valid successors remain admissible.

Independent reruns of the original recipient-change witness now produce ManagedSessionConflictError in TypeScript and ApiException / HTTP 409 / managed_session_extension_record_rejected in Java. Both preserve the committed revision after rejection and accept the corrected successor.

Validation on this exact head:

  • npm run build and npm run typecheck: passed.
  • Four affected TypeScript suites: 410 tests passed.
  • Three affected Java suites: 17 tests passed.
  • Independent reproduction checks: 2 TypeScript and 2 Java tests passed.

These are record-level checks (Java Spring/H2 and the existing TypeScript team-domain test enablement), not full HTTP/CLI E2E. Some hosted CI jobs were still pending when checked.

@wenshao

wenshao commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator Author

Local maintainer verification — PR #13811 feat(managed-agent): H4e-a team record contract

Verdict: merge-ready — 17,916 scripted assertions executed across two rounds, 0 failures attributable to this PR.
Verified head: 4851c2364eddb5ab55a15611c2b01e7dc55fad22 · base: ad91df56f94f6080d32b340521e7f0095054b12c (both fetched from QwenLM/qwen-code and exercised as isolated worktrees).

Round note. Verification started at head 12be2264; the head moved to 4851c236 (fix(managed-agent): check a team message's parties on every revision) before posting, so every load-bearing measurement was re-run at the new head. Round 1 produced no findings against the PR, so the status table is:

# Round-1 measurement Status at 4851c236
1 Corpus replay + cross-language identity (199 lines) Re-measured — re-executed at the new head, identical again (the input closure — managed-team-record.ts, managed-extension-projection.ts, ManagedTeamRecords.java, ManagedExtensionProjection.java, the fixtures — is byte-unchanged between heads, confirmed by git diff --stat over the closure AND by re-execution)
2 Domain gate Re-measured — identical
3 TS mutation matrix Re-measured — 14/14 killed (13 + the reverted-fix mutant)
4 Java mutation matrix Re-measured — 7/7 killed (6 + the reverted-fix mutant)
5 Full suites Re-measured — TS 3160/3160 green, Java 1625 tests as before

The delta commit itself was verified as load-bearing: reverting its every-revision parties check to the round-1 open-only form (mutants TS-M14 / JV-M7) turns its own new regression tests red on both sides.

äļ­æ–‡æ‘˜č́

įŧ“čŪš:åŊäŧĨ合åđķ(å―“å‰ head 4851c236)。 äļĪč―Ūå…ą 17,916 æĄč„šæœŽæ–­čĻ€,æēĄæœ‰äŧŧä―•åĪąčīĨåŊå―’å› äšŽæœŽ PR。

  • 驌čŊæœŸé—ī head į§ŧåŠĻčŋ‡äļ€æŽĄ(12be2264 → 4851c236,新åĒžäļ€äļŠ fix 提äšĪ:把å›Ē队æķˆæŊįš„æ”ķ发æ–đæĢ€æŸĨäŧŽ"äŧ…垀é“ūäŋŪčŪĒ"æ”đäļš"æŊäļŠäŋŪčŪĒ")。所有æ‰ŋ重æĩ‹é‡å·ēåœĻ新 head é‡č·‘;čŊĨ fix 朎čšŦäđŸé€ščŋ‡å˜åž‚éЌčŊäļšæ‰ŋ重——把åŪƒå›žé€€æˆæ—§å―Ē垏(TS-M14 / JV-M7),å…ķ新åĒžå›žå―’æĩ‹čŊ•åœĻäļĪäū§įŦ‹åģ变įšĒ。
  • æ ļåŋƒåĢ°æ˜ŽæˆįŦ‹:四äļŠå›Ē队čŪ°å―•ä―“åœĻ TS äļŽ Java äļ­å›žæ”ū同äļ€äŧ―čŊ­čĻ€äļ­įŦ‹čŊ­æ–™(125 į”Ļäū‹ + 57 后įŧ§åŊđ),199/199 行åˆĪå†ģé€å­—čŠ‚äļ€č‡ī;æŊäļŠæ— æ•ˆį”Ļäū‹éƒ―按 fixture æŒ‡åæĄæŽūčĒŦ拒įŧ(100 clause å…Ļå‘―äļ­);åĶæœ‰æˆ‘į‹ŽįŦ‹æž„é€ įš„ 17 äļŠčūđį•ŒæŽĒ针(2^53 čūđį•Œįš„ number/planRevision、čķ…限成员、astral 名字、cancelled 搚åļĶ input į­‰),äļĪčŊ­čĻ€åˆĪå†ģåیå…Ļäļ€č‡ī。埚įšŋåŊđį…§:base 树 199/199 å…ĻéƒĻ NO-BODY。
  • 变垂įŸĐé˜ĩ:TS 14/14、Java 7/7 å…ĻéƒĻčĒŦ杀,æŊäļŠå˜åž‚éƒ―æ­ŧåœĻäŧĨå…ķå‘―åįš„åۈåŦæĩ‹čŊ•äļŠ;äļĪäū§å„åŦæīŧ性åŊđį…§å˜åž‚,均įŦ‹åģ变įšĒ;įŸĐé˜ĩįŧ“束后äļĪæĢĩ树 git status åđē净。
  • å›žå―’:新 head å…Ļ量 TS åĨ—äŧķ 3160/3160(58 文äŧķ,äļŽ PR åĢ°æ˜Žäļ€č‡ī);Java clean verify:1625 æĩ‹čŊ•、0 åĪąčīĨ、2 äļŠ error äļš ToolPublicationStoreTest ᧟įšĶčķ…æ—ķ——åœĻ base äļŠäŧĨį›ļ同į­ūåé‡įŽ°,äļŽæœŽ PR 无å…ģ;Checkstyle 0 čŋč§„、SpotBugs 0 å‘įŽ°ã€‚
  • é—Ļį́:四äļŠ domain 均 registered=1 enabled=0 REFUSED-EXACT,taskKindOf å…Ļäļš null(äļæŠ•å―ą task),schedule æ­ĢåŊđį…§é€ščŋ‡ã€‚
  • 朊č͆ᛖ:H4e-b čŋčĄŒæ—ķ(朎 PR 明įĄŪį́į”Ļ);ᜟåŪž MySQL/MariaDB 集成(store äŧĨ H2 MySQL æĻĄåžéĐąåŠĻ);ä―œč€…č‡ŠæŠĨįš„ 22+22 å…Ļ量变垂数朊逐äļ€åĪč·‘(朎æŠĨ告äŧĨį‹ŽįŦ‹ 14+7 įŸĐé˜ĩč͆ᛖå…ĻéƒĻæ‰ŋ重åۈåŦ);å…ķäŧ– OS įš„ CI flake éĒ‘įŽ‡ã€‚
  • éĒ„å…ˆå­˜åœĻįš„įšĒæĩ‹čŊ•(TS flake äļŽ Java ᧟įšĶčķ…æ—ķ)čŊĶ见æ­Ģ文 "Pre-existing observations"。

Central claim and A/B proof

Claim: the four team domains (team_state, team_task, team_message, team_plan) land as schema-v1 record bodies whose validators and commit-time rules behave identically in TypeScript and Java, replaying one language-neutral corpus (125 cases, 57 successor pairs) — all four domains registered but disabled for submission, projecting no task.

Cell Base (ad91df5) Head (4851c236) Oracle
Corpus replay (TS validators) 199/199 NO-BODY (no team bodies exist) 199/199 lines: expected verdict reproduced per-line exp/got/rid/start/clause
Corpus replay (Java store) 199/199 NO-BODY 199/199 lines same canonical format
Cross-language identity — diff of the two 199-line verdict files: empty — at both heads byte-identical lines
Invalid-case clause attribution — 100/100 clause=HIT on both sides, 0 MISS/WRONGTHROW fixture's named clause must appear in the refusal
Domain gate all 4 REFUSED-EXACT, body ABSENT all 4 REFUSED-EXACT, body PRESENT exact error string domain <d> is registered but not enabled for submission.
Task projection — taskKindOf → null for all 5 templates no team record projects a task
Full src/managed-runtime/ vitest 2963 tests (3 pre-existing flakes under load) 3160/3160 green at both heads (58 files — matches the PR's claim exactly) vitest
Java managed-agent-server verify same 2 errors (see below) 1625 tests, 0 failures, 2 pre-existing errors, 1 skipped (macOS-disabled) — identical at both heads surefire
checkstyle:check / spotbugs:check — 0 violations / 0 findings (BUILD SUCCESS) maven

Witness: 01-cross-language-fixture-identity.png, 04-domain-gate-ab.png, 03-suite-ab-and-gates.png below.

Independent drift probes (beyond the PR's own corpus)

17 extra probes I authored (harness/drift-probes.json) — the PR's fixtures are the author's, so these provide the independent input: number/planRevision at exactly 9_007_199_254_740_990 (accepted) and ...991 (refused) in both languages, an 11-member roster refused identically, astral-plane name refused, from == to == "leader" refused, a cancelled delivery carrying inputId refused, case-sensitivity of decision/status, string-typed planModeRequired, and an eleventh-member join successor refused. TS and Java agree on every probe, including the design-confirming positives that a team may be named leader (the reserved name binds participants only) and that a task can be in_progress owned by leader.

The follow-up fix (4851c236) is load-bearing

The delta commit widens the team-message parties check from the opening revision to every revision (both languages), closing the window where a successor readdressed to a non-member raced the fixed-key chain rule and hit the recipient lookup first. Reverting just that hunk (TS-M14 / JV-M7) turns the commit's own new regression tests red on both sides (routes a message only within the teamâ€Ķ / routesMessagesAndPlansWithinTheTeam), so the fix is pinned by its tests, not decorative.

Mutation matrix (vacuity check on the guards)

Every guard was disabled one at a time and the PR's team tests re-run; each mutant died on the fixture case or authority/store test named for that guard, and both worktrees were git status-clean afterwards. A liveness-control mutant per language (perturbing an asserted error string) died immediately, proving both the suites and the harness can fail. Counts at 4851c236:

Mutant Result Killing witness
TS: membershipRevision = members+1 KILLED state-revision-behind/state-revision-ahead
TS: member named leader refused KILLED state-member-leader
TS: message digest == content digest KILLED message-digest-mismatch
TS: message kind direction rules KILLED message-plan-request-from-leader et al. (4 tests)
TS: plan feedback only with decision KILLED plan-feedback-without-decision
TS: team led by this Session KILLED refuses a team that another Session leads
TS: one team per child run KILLED keeps each child run in one teamâ€Ķ
TS: records open only in active team KILLED opens team records only in an active teamâ€Ķ
TS: task number unique per team KILLED keeps task numbers uniqueâ€Ķ
TS: dependency cycle refused KILLED refuses a dependency outside the team or one that closes a cycle
TS: message targets recipient's Session KILLED routes a message only within the teamâ€Ķ
TS: plan from plan-mode member KILLED takes a plan request only from a member that must plan
TS: liveness control (error string) KILLED state-members-null + state-members-not-array
TS: revert the follow-up fix to open-only parties check KILLED routes a message only within the teamâ€Ķ (new regression assertions)
Java: team led by this Session KILLED bindsATeamToItsLeadAndItsMembersToLiveChildRuns
Java: one team per child run KILLED same
Java: task number unique KILLED keepsTheBoardInsideItsTeam
Java: dependency cycle KILLED same
Java: MAX_MEMBERS 10→11 KILLED limits pinned by contract test (2 tests)
Java: liveness control (error string) KILLED validatesTheSharedRecordsAndStarts
Java: revert the follow-up fix KILLED routesMessagesAndPlansWithinTheTeam (new regression assertion)

Witness: 02-mutation-matrix.png. The PR's own larger claim (22 authority + 22 Java mutants) was not re-run exhaustively; this independent matrix covers every guard the PR introduces — see Not covered.

Static gates (liveness-proven)

ESLint and Prettier were first proven live (a planted unused variable and a planted format break were both caught), then run clean over the five changed TS files plus the two fixtures files; tsc --noEmit for @qwen-code/qwen-code-core is clean. Design docs exist in both languages with reciprocal links and equal length (190 + 190 lines).

Findings

None blocking. One design-level confirmation worth recording: the validators deliberately allow a team to be named leader (only participants reserve the name); both languages agree, and the design doc's wording ("the reserved leader", in the participant sense) matches the code. No action needed.

Pre-existing observations (not attributable to this PR)

  • TS load-correlated flakes. Under a heavily loaded first pass (two installs + two suites + Maven running concurrently), the round-1 head showed 4 red tests and base 3, with byte-identical signatures for the shared three (managed-session-authority.hook-scale.test.ts ×2, managed-session-record-sink.test.ts ENOTEMPTY temp-dir race ×1). The three affected files re-run in isolation: 54/54 green on both arms; quiet full-suite reruns at both heads: 3160/3160 green. The single head-only extra (managed-child-run-supervisor.test.ts ‹ settles a terminated process only after the unit is empty, a file this PR does not touch) also passes in isolation on both arms.
  • Java ToolPublicationStoreTest × 2 errors (renewsTheOriginalClaimWhileScanningSlowObjectBytes, streamsLargeOutputAndReadsItsTailAfterStoreReplacement — lease/expiry timing): fail with the byte-identical signature at base and at both heads, in isolation, on this machine, in a file this PR does not touch. Pre-existing environmental failure (macOS; the PR's own evidence was gathered on Linux).
  • Skipped-count delta: PR reports 2 skipped on Linux; 1 skipped here (RuntimeBrokerDefaultOnTest is Disabled on operating system: Mac OS X). Environmental, not a discrepancy in the change.

These 13 red executions are disclosed here rather than counted as failures of this verification: each was either re-driven to green in isolation on both arms or shown byte-identical at base, which is the A/B expectation for pre-existing failures.

Not covered

  • H4e-b runtime (team tools, teammate routing, mailbox relay, plan resolution, member shutdown, legacy import, enablement) — explicitly out of scope for this PR; the four domains stay disabled, which the gate harness proves.
  • Real MySQL/MariaDB integration lanes — the store was exercised on H2 in MySQL mode (the module's own test setup), as the PR's test plan specifies; no migration ships here.
  • The author's full 22+22+6 mutation run was not reproduced exhaustively; the independent 14+7 matrix above covers every guard the PR introduces.
  • Per-commit attribution of 12be2264's internals: it is a single commit; the aggregate diff was verified. The follow-up 4851c236 was verified separately (above).
  • Other OS/CI lanes (Windows, Linux CI flake rates); npm run bundle beyond the per-workspace build the suites consume.

Methodology

Two isolated worktrees (tmp/pr13811-head at the current head, tmp/pr13811-base @ ad91df5), each corepack pnpm install --frozen-lockfile (prepare-built); internal-link integrity asserted via readlink -f node_modules/@qwen-code/qwen-code-core resolving inside each tree. Harnesses are mock-free against the real modules: TS replay via tsx importing managed-extension-projection.ts directly; Java replay via a scratch FixtureReplay.java compiled against the module's target/classes and its Maven dependency classpath, driving ManagedExtensionProjection.RECORD_BODIES. The corpus and my probe file were treated as pure input (base trees read them from the head tree). Mutation drivers apply anchored single replacements, assert exactly one occurrence, run the new team test files/classes, and restore byte-identically (verified by git status). Java env: Homebrew JDK 21 + Maven 3.9, -Dgpg.skip=true. Round-1 measurements whose input closures are byte-unchanged between heads were carried forward only after re-execution confirmed identical output (replay, gate); everything else was re-measured at the new head. All harnesses and raw logs are under tmp/pr13811-verify-20261010-112810/ (harness/, logs-*, evidence/).

Evidence captures

01 — Cross-language fixture identity (the central claim): 199/199 verdict lines byte-identical between TS and Java; base trees show 199/199 NO-BODY. (Captured at 12be2264; re-executed byte-identically at 4851c236.)

01-cross-language-fixture-identity

02 — Mutation matrix at 4851c236: 14/14 TS guards and 7/7 Java guards killed by their named witnesses, including the reverted follow-up fix; liveness controls killed; worktrees clean afterwards.

02-mutation-matrix

03 — Suites and gates at 4851c236: head suite 3160/3160 green on a quiet machine; loaded-pass flakes identical on base; Java 1625 with the 2 pre-existing errors attributed; checkstyle/spotbugs clean.

03-suite-ab-and-gates

04 — Domain gate A/B: all four team domains registered but refused for submission on both arms; bodies present only at head; schedule positive control passes.

04-domain-gate-ab


Local maintainer verification by @wenshao's request. Artifacts: tmp/pr13811-verify-20261010-112810/ (report.md, verdict.txt, assertions.json, harnesses, raw logs, evidence).

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at head 4851c2364eddb5ab55a15611c2b01e7dc55fad22 (Critical-only scan).

Prior blocking issue — confirmed fixed. The P2 (a successor that readdressed a committed team message to a name outside the roster reached the recipient lookup before any participant check, so TypeScript threw TypeError and the Java store threw NullPointerException, which the generic handler maps to 500 instead of the normal 409) no longer holds on either side:

  • managed-session-authority.ts — the takesPart(message.from) && takesPart(message.to) refusal now sits in the team_message branch unconditionally, not under previous === undefined, so it runs on every revision and precedes the team.members.find(...)! / extensionRecord('child_run', ...)! dereferences in the targetSessionId block.
  • ManagedExtensionRecordStore.requireTeamRecord — require(takesPart(team, from) && takesPart(team, to), ...) likewise runs on every revision, and applyRevision calls it for any team_ domain regardless of previous, so it precedes memberWith(team, "name", to).get("childRunId").

The non-null dereferences that remain are backed by the record validator, which refuses leader as a member name and requires distinct member names and distinct child run ids, so a participant check that passes on a non-leader name matches exactly one member in both languages.

Current scan — no Critical. Checked against the head code:

  • The cycle walk can only dereference tasks that exist: every newly added blocker is verified present and in the same team before the walk starts, and a committed task's own blockedBy entries were verified when it committed, with records append-only — so this.extensionRecord('team_task', next)! and Java's recordBody(...).get("blockedBy") cannot miss.
  • The team-task owner rule, the task-number uniqueness rule, the blocker-scoping rule and the plan-mode member rule agree across the two implementations, including the "unchanged from the previous revision" allowance on the owner.
  • Team membership rules match: a member joins as a non-terminal child Session run of the lead, and no child run may appear in a second team; the roster is append-only (prefix on the member list) with membershipRevision == members.length + 1.
  • All four domains are registered identically in MANAGED_EXTENSION_RECORD_BODIES and ManagedExtensionProjection, each projecting no task, and the resource-closure list covers descriptionRef, metadataRef, contentRef, planRef and feedbackRef on both sides — the closed key sets make the Java superset scan equivalent to the per-domain TypeScript lists.
  • Nothing here enables submission for the team domains, so the contract stays inert at merge.

CI: Qwen Autofix and the label/self-report jobs succeeded; Qwen Code CI was queued and SDK Java still running at review time. Nothing fails attributable to this diff, and no failure evidence contradicts the record-level checks above.

@wenshao
wenshao enabled auto-merge October 10, 2026 04:44

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Reviewed at 4851c2364e, including the delta from 12be226486.

The record contract holds up. The four team domains are registered bodies (all taskKindOf: () => null, so none of them projects a task), stay out of MANAGED_SESSION_ENABLED_DOMAINS, and the generic path refuses them at the extension funnel before the enablement check — no writer mints a team_state: / team_task: / team_message: / team_plan: event id. The commit-time rules match decision 8 rule-for-rule in both languages: lifecycle one step at a time (terminal deleted freezes the run), member join (a live child Session run of this Session, in no other team, active only), task (number is a fixed key, so uniqueness only needs to be checked at the open), owner (leader/member, or unchanged), blockedBy grows-only + in-team + acyclic, message parties checked on every revision with the target binding (leader→lead Session, member→its run's attached Session, otherwise it stays planned), plan from a plan-mode member keyed by the D6 requestId. The journal-rebuild path reuses the same assertExtensionRevision, so rebuild and live commit agree.

Your P2 from the earlier review is fixed: the parties predicate is evaluated unconditionally, before the recipient lookup, in both managed-session-authority.ts and ManagedExtensionRecordStore.java, and a rejected successor leaves the committed revision untouched. Shared fixtures are real (125 cases / 57 successor pairs, both suites load the same JSON), and the projection + ref-closure additions are equivalent across the two languages. The EN/ZH designs line up.

One non-blocking observation: managed-session-authority.extension.test.ts:1131 still describes team_state as "neither in the plain enabled list nor in the extension-record body index". After this PR it is in the body index, so in the real module that event takes the extension-funnel refusal ("records commit only through commitExtensionRecord") rather than "not enabled for submission". The test still passes only because that suite mocks the index down to {monitor_run}, so the probe's rationale is now stale — worth refreshing the comment (or dropping the mock later) so it stays honest. Not a behavior issue.

CI on this head: the lanes that have run are green (Test ubuntu Node 22.x, Lint & Static, Java matrix incl. Windows/macOS/11/17/21, Real daemon, Integration, Flyway uniqueness); Hosted process fault gates / MySQL 8.4, Runtime Broker and Managed Agent MariaDB, web-shell E2E and Desktop Shell are still pending, none failing. mergeStateStatus stays BLOCKED until the required approvals land.

Scope note: this is a source-level review of the frozen head plus fixture/doc checks; I did not rerun the MySQL/MariaDB lanes locally.

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.25.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants