Skip to content

Commit 12d6c41

Browse files
authored
Merge branch 'main' into w2-cwd-change
2 parents c6a35b6 + 2007925 commit 12d6c41

30 files changed

Lines changed: 1498 additions & 226 deletions

‎docs/design/2026-09-27-managed-extension-authority.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ The generated WebShell types gain the two task routes, the task schemas and the
128128
- the record bodies, task states and outbox states, as constants;
129129
- 7 task ID cases;
130130
- 50 run start and 32 Monitor start cases;
131-
- 45 single-revision views and 11 run histories, with their outbox membership;
131+
- 46 single-revision views and 12 run histories, with their outbox membership;
132132
- 2 Monitor chains that both sides commit through their authority or store, and 12 chains they must refuse: one reuses the command that opened another record, and three attach a Runtime again under the same or an older generation, or without an unknown outcome;
133-
- 10 Broker cases, one per execution state of the Broker's ledger, each with the wire status the Broker reports for it and the execution the Harness reads from that, and 8 wire-status cases.
133+
- 10 Broker cases, one per execution state of the Broker's ledger, each with the wire status the Broker reports for it and the execution the Harness reads from that.
134134

135-
A Python labeler written from this document, independent of both languages and kept outside the repository as for H0b, produced the labels. `managed-extension-projection.test.ts` and `ManagedExtensionProjectionContractTest` both replay the task ID, start, view and history cases. Java maps each Broker case's state; TypeScript maps its wire status and the wire-status cases, and checks that the two readings differ only for `DISPATCHING`, as decision 10 says; and the Broker's `ManagedExtensionExecutionContractTest` checks that the Broker reports those wire statuses. The authority suite, `ManagedExtensionRecordStoreTest` and `ManagedAgentMySqlIT` commit the chains.
135+
A Python labeler written from this document, independent of both languages and kept outside the repository as for H0b, produced the labels. `managed-extension-projection.test.ts` and `ManagedExtensionProjectionContractTest` both replay the task ID, start, view and history cases. Java maps each Broker case's state; TypeScript maps its wire status, and checks that the two readings differ only for `DISPATCHING`, as decision 10 says; and the Broker's `ManagedExtensionExecutionContractTest` checks that the Broker reports those wire statuses. The authority suite, `ManagedExtensionRecordStoreTest` and `ManagedAgentMySqlIT` commit the chains.
136136

137137
`managed-extension-journal-v1.fixtures.json` holds the requests that the TypeScript authority sent through its HTTP store for a Session with two Monitor revisions, the second with a notification input and its wake. The HTTP store test fails when the writer's output changes, and rewrites the file when run with `QWEN_WRITE_GOLDEN=1`. `ManagedSessionStoreIntegrationTest` sends the same requests to the Java store, which must accept every one and project the same task.
138138

@@ -173,7 +173,7 @@ A Python labeler written from this document, independent of both languages and k
173173
4. **Logical and physical start.** H0b lets a run stay `admitted` while its execution is already `running_attached`, so such a task shows `pending` with the Runtime state `ready` and no start time. Tightening that rule is a change to the H0b contract.
174174
5. **Replayed domain records.** `commitDomainRecord` publishes a new body before it detects a replayed command, and returns that body's reference instead of the committed one. `commitExtensionRecord` checks for the replay first; the older method is left for a separate fix.
175175
6. **Notification wakes.** A revision that commits a notification input also commits its `wake.requested`, but nothing consumes the wake yet. The hosted Session path refuses to reopen a Session while an accepted input has no `turn.settled` (`hosted_turn_recovery_required`), so H3 must run or settle such inputs before it enables a domain that notifies.
176-
7. **Bodies added later.** The Java store materializes only the bodies it knows and passes the `domain.committed` events of other domains through, as before H0c. A slice that adds a body must reach the server before any writer commits the domain, or backfill the rows from the journal when the server gains the body. Otherwise the first revision that such a server sees for a record is not a start, so it is refused and the writer stops. H0c's only body, `monitor_run`, ships on both sides and stays disabled.
176+
7. **Bodies added later.** The Java store materializes only the bodies it knows and passes the `domain.committed` events of other domains through, as before H0c. The enabled domains that commit their records as envelopes are listed as `MANAGED_SESSION_ENVELOPE_DOMAINS`, and a body never registers for one of them: the bodies module refuses the collision when it loads, and a reopened authority skips their pre-registration envelopes — records no closed body could ever parse — instead of dying on them. Whichever slice registers the next body owes these four checks: the list, the tripwire, the skip and the key disjointness the skip counts on — the envelope's three keys, `operationId`, `revision` and `previousRecordRef`, must stay out of every body's closed key set, or reopening would skip the body's own committed revisions. A body that ships with its domain from the start, as the H1 and H2 records did, needs no such move. Registering one for a domain that already commits through `commitExtensionRecord` still means reaching the server before any writer commits it, or backfilling its rows from the journal. H0c's only body, `monitor_run`, ships on both sides and stays disabled.
177177

178178
## Follow-up work
179179

‎docs/design/2026-09-27-managed-extension-authority.zh-CN.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ OpenAPI 版本升为 `1.19.0`,排在持久 Session 生命周期(#12881)的
128128
- 记录正文、任务状态与 outbox 状态,作为常量;
129129
- 7 个任务 ID 用例;
130130
- 50 个运行起始用例与 32 个 Monitor 起始用例;
131-
- 45 个单修订视图与 11 段运行历史,附带各自的 outbox 归属;
131+
- 46 个单修订视图与 12 段运行历史,附带各自的 outbox 归属;
132132
- 2 条 Monitor 修订链,两侧分别通过各自的 authority 或存储提交;另有 12 条两侧都必须拒绝的修订链:其中一条复用了已开启另一条记录的命令,另有三条在相同或更旧的代数下、或未经未知结果就重新挂接 Runtime;
133-
- 10 个 Broker 用例,Broker 台账的每个执行状态各一个,每个都附带 Broker 为它上报的线上状态以及 Harness 据此读出的执行状态;另有 8 个线上状态执行用例。
133+
- 10 个 Broker 用例,Broker 台账的每个执行状态各一个,每个都附带 Broker 为它上报的线上状态以及 Harness 据此读出的执行状态。
134134

135-
标注由一个依据本文编写、独立于两种语言的 Python 实现给出,它与 H0b 一样保存在仓库之外。`managed-extension-projection.test.ts` 与 `ManagedExtensionProjectionContractTest` 都回放任务 ID、起始、视图与历史用例。Java 映射每个 Broker 用例的状态;TypeScript 映射它的线上状态以及线上状态用例,并检查两种读法只在 `DISPATCHING` 上不同,与决策 10 一致;Broker 的 `ManagedExtensionExecutionContractTest` 则检查 Broker 确实上报这些线上状态。authority 测试套件、`ManagedExtensionRecordStoreTest` 与 `ManagedAgentMySqlIT` 提交修订链。
135+
标注由一个依据本文编写、独立于两种语言的 Python 实现给出,它与 H0b 一样保存在仓库之外。`managed-extension-projection.test.ts` 与 `ManagedExtensionProjectionContractTest` 都回放任务 ID、起始、视图与历史用例。Java 映射每个 Broker 用例的状态;TypeScript 映射它的线上状态,并检查两种读法只在 `DISPATCHING` 上不同,与决策 10 一致;Broker 的 `ManagedExtensionExecutionContractTest` 则检查 Broker 确实上报这些线上状态。authority 测试套件、`ManagedExtensionRecordStoreTest` 与 `ManagedAgentMySqlIT` 提交修订链。
136136

137137
`managed-extension-journal-v1.fixtures.json` 保存了 TypeScript authority 通过其 HTTP 存储发出的请求,对应一个含两条 Monitor 修订的 Session,其中第二条带有通知输入及其唤醒。写入端的输出一旦变化,HTTP 存储测试就会失败;以 `QWEN_WRITE_GOLDEN=1` 运行时会重写该文件。`ManagedSessionStoreIntegrationTest` 把同样的请求发给 Java 存储,后者必须全部接受并投影出相同的任务。
138138

@@ -173,7 +173,7 @@ OpenAPI 版本升为 `1.19.0`,排在持久 Session 生命周期(#12881)的
173173
4. **逻辑启动与物理启动。** H0b 允许运行在执行已处于 `running_attached` 时仍停在 `admitted`,此时任务显示为 `pending`,Runtime 状态为 `ready`,且没有启动时间。收紧这条规则属于对 H0b 契约的修改。
174174
5. **重放的 domain 记录。** `commitDomainRecord` 会在发现命令是重放之前就发布新的正文,并返回这个正文的引用,而不是已提交的那个。`commitExtensionRecord` 先检查重放;旧方法留待单独修复。
175175
6. **通知唤醒。** 提交通知输入的修订也会提交对应的 `wake.requested`,但目前还没有任何消费方。托管 Session 路径在已接受的输入缺少 `turn.settled` 时拒绝重新打开 Session(`hosted_turn_recovery_required`),因此 H3 在开放会发出通知的 domain 之前,必须先运行或结算这类输入。
176-
7. **后续新增的正文。** Java 存储只物化它认识的正文,其他 domain 的 `domain.committed` 事件则与 H0c 之前一样直接放行。新增正文的切片必须先让服务端上线,再让任何写入者提交该 domain;否则就要在服务端获得该正文时从日志回填这些行。不然,这样的服务端看到某条记录的第一条修订并不是起始修订,会拒绝它,写入者随之停止。H0c 唯一的正文 `monitor_run` 两侧同时具备,且仍未开放。
176+
7. **后续新增的正文。** Java 存储只物化它认识的正文,其他 domain 的 `domain.committed` 事件则与 H0c 之前一样直接放行。以信封路径提交记录的已开放 domain 列在 `MANAGED_SESSION_ENVELOPE_DOMAINS` 中,而正文绝不注册给它们:正文模块加载时会拒绝这种冲突,重开的 authority 则会跳过它们的注册前信封——任何封闭正文都无法解析的记录——而不是因此拒绝打开。注册下一个正文的切片承担这四项检查:这张清单、这道绊线、这一次跳过,以及跳过所依赖的键不相交性——它识别的三个信封键 `operationId`、`revision` 与 `previousRecordRef` 必须始终落在每一个正文的封闭键集之外,否则重开会跳过该正文自己已提交的修订。正文若是与自身 domain 同时发布的,正如 H1 与 H2 的记录,就无需这些迁移。若要为已经通过 `commitExtensionRecord` 提交的 domain 注册正文,仍然必须先让服务端上线,再让任何写入者提交它,或者从日志回填它的行。H0c 唯一的正文 `monitor_run` 两侧同时具备,且仍未开放。
177177

178178
## 后续工作
179179

‎integration-tests/cli/qwen-serve-routes.test.ts‎

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -783,27 +783,34 @@ describe('qwen serve — POST /session validation + concurrent coalescing', () =
783783
});
784784

785785
it('honors and reserves a normalized caller-supplied session ID', async () => {
786-
const requestedId = '550E8400-E29B-41D4-A716-446655440000';
786+
// Fresh id per attempt: vitest `retry` re-enters this body against the
787+
// same long-lived daemon, and a fixed id turns one timed-out create into
788+
// deterministic 409 session_id_conflict failures on every retry — the
789+
// session a failed attempt left live (or abandoned-but-later-registered)
790+
// still owns the reservation the retry's first POST collides with. The
791+
// ACP_INITIALIZE_TIMEOUT_MS note above names the load class.
792+
const requestedId = randomUUID().toUpperCase();
787793
const normalizedId = requestedId.toLowerCase();
788-
const created = await fetch(`${base}/session`, {
789-
method: 'POST',
790-
headers: {
791-
Authorization: `Bearer ${TOKEN}`,
792-
'content-type': 'application/json',
793-
},
794-
body: JSON.stringify({
795-
cwd: REPO_ROOT,
796-
sessionId: requestedId,
797-
sessionScope: 'single',
798-
}),
799-
});
800-
expect(created.status).toBe(200);
801-
await expect(created.json()).resolves.toMatchObject({
802-
sessionId: normalizedId,
803-
attached: false,
804-
});
805-
806794
try {
795+
const created = await fetch(`${base}/session`, {
796+
method: 'POST',
797+
headers: {
798+
Authorization: `Bearer ${TOKEN}`,
799+
'content-type': 'application/json',
800+
},
801+
body: JSON.stringify({
802+
cwd: REPO_ROOT,
803+
sessionId: requestedId,
804+
sessionScope: 'single',
805+
}),
806+
});
807+
const createdBody = await created.json();
808+
expect(created.status, JSON.stringify(createdBody)).toBe(200);
809+
expect(createdBody).toMatchObject({
810+
sessionId: normalizedId,
811+
attached: false,
812+
});
813+
807814
let conflict: unknown;
808815
try {
809816
await client.createOrAttachSession({

‎packages/cli/src/serve/hosted-harness-session.test.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3381,13 +3381,16 @@ describe('Hosted Harness no-tool session', () => {
33813381
payloadDigest: `sha256:${createHash('sha256').update(JSON.stringify(prompt)).digest('hex')}`,
33823382
})
33833383
.expect(202);
3384-
await vi.waitFor(async () => {
3385-
const status = await headers(
3386-
supertest(server).get(`/session/${SESSION_ID}/status`),
3387-
).set('X-Qwen-Client-Id', created.body.clientId as string);
3388-
expect(status.body.hasActivePrompt).toBe(false);
3389-
expect(status.body.recoveryBlocked).toBe(false);
3390-
});
3384+
await vi.waitFor(
3385+
async () => {
3386+
const status = await headers(
3387+
supertest(server).get(`/session/${SESSION_ID}/status`),
3388+
).set('X-Qwen-Client-Id', created.body.clientId as string);
3389+
expect(status.body.hasActivePrompt).toBe(false);
3390+
expect(status.body.recoveryBlocked).toBe(false);
3391+
},
3392+
{ timeout: 10_000 },
3393+
);
33913394
await headers(supertest(server).delete(`/session/${SESSION_ID}`)).set(
33923395
'X-Qwen-Client-Id',
33933396
created.body.clientId as string,

‎packages/core/src/managed-runtime/contracts/managed-extension-journal-v1.fixtures.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"contractVersion": 1,
23
"sessionKey": {
34
"tenantId": "tenant-a",
45
"workspaceId": "workspace-a",

0 commit comments

Comments
 (0)