@sema-agent/sdk 6.7.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml CHANGED
@@ -56,8 +56,17 @@ openapi: 3.1.0
56
56
  # live — implemented & verified server-side; in the contract-test assertion set.
57
57
  # draft — verb exists but the response shape is not fully pinned; co-design pending (do not over-assert).
58
58
  # planned — NOT yet implemented server-side; documented for SDK/producer alignment; EXCLUDED from
59
- # contract-test assertions until it ships.
60
- # gated — implemented but off by a server deploy flag (e.g. leader); not on the default product path.
59
+ # contract-test assertions until it ships. ⚠️ It is a claim about the SERVER, and it EXPIRES:
60
+ # once the face ships, move it to `live`/`gated` the same day. Two gates now police that —
61
+ # `spec-path-drift-gate` (planned ∩ paths the SDK actually calls must be empty) and
62
+ # `test/live/route-existence.test.ts` (a planned path must have NO handler in the pinned
63
+ # server artifact). Both exist because `POST /v1/tasks/{taskId}/asks/{askId}/decision` kept
64
+ # this label for two server releases after it went live.
65
+ # gated — implemented but off by a server deploy flag (e.g. leader, STREAM_APPROVAL_ENABLED); answers
66
+ # 501 where the flag/deps are unmet. Says nothing about the flag's DEFAULT — that can differ
67
+ # per server version, so read the matching `capabilities` bit, never this label.
68
+ # retired — the face was DELETED server-side; the entry is a tombstone kept so consumers generated from
69
+ # an older spec can find out where it went. No deployment serves it.
61
70
  # ─────────────────────────────────────────────────────────────────────────────
62
71
 
63
72
  info:
@@ -907,7 +916,7 @@ paths:
907
916
  responses:
908
917
  '200':
909
918
  # 🔴 census 批2 第三段(2026-07-30)spec 谎修正:这条 200 此前只有一句 description、**零 content
910
- # 声明**(机器视角=「无 body」),而 server 真发 JSON(`routes/runs.ts:1155`,与 output 面同一个
919
+ # 声明**(机器视角=「无 body」),而 server 真发 JSON(`routes/runs.ts`,与 output 面同一个
911
920
  # sendJson,含 g14 的条件 `cursorSemantics`)。补接同一个 schema。
912
921
  description: 'Stopped — same projection shape as the output verb.'
913
922
  content:
@@ -1077,8 +1086,8 @@ paths:
1077
1086
  schema:
1078
1087
  type: object
1079
1088
  # 封闭(census 批2 第二段):三个 200 铸造点都是单键字面量 —— `{deleted:false}` 两处
1080
- # (未知会话 / 外租户,`routes/sessions.ts:491,495`)+ purgeSession 的 `{deleted}` 返回
1081
- # (`http/server.ts:255` 的签名就是这一个键;它的另一臂 `{active}` 走 409)。
1089
+ # (未知会话 / 外租户,`routes/sessions.ts`)+ purgeSession 的 `{deleted}` 返回
1090
+ # (`http/server.ts` 的签名就是这一个键;它的另一臂 `{active}` 走 409)。
1082
1091
  additionalProperties: false
1083
1092
  required: [deleted]
1084
1093
  properties:
@@ -1115,7 +1124,7 @@ paths:
1115
1124
  application/json:
1116
1125
  schema:
1117
1126
  type: object
1118
- # 封闭:铸造点 `routes/sessions.ts:469` 是单键字面量 `{ sessionId: newId }`。
1127
+ # 封闭:铸造点 `routes/sessions.ts` 是单键字面量 `{ sessionId: newId }`。
1119
1128
  additionalProperties: false
1120
1129
  required: [sessionId]
1121
1130
  properties:
@@ -1157,7 +1166,7 @@ paths:
1157
1166
  application/json:
1158
1167
  schema:
1159
1168
  type: object
1160
- # 封闭:铸造点 `routes/sessions.ts:212` 是两键字面量;版本号本身走 ETag 响应头,不进体。
1169
+ # 封闭:铸造点 `routes/sessions.ts` 是两键字面量;版本号本身走 ETag 响应头,不进体。
1161
1170
  additionalProperties: false
1162
1171
  required: [sessionId, leafId]
1163
1172
  properties:
@@ -1295,7 +1304,7 @@ paths:
1295
1304
  description: Delivered onto the live stream.
1296
1305
  content:
1297
1306
  application/json:
1298
- # 封闭(census 批2 第三段,2026-07-30):live 臂铸造点 `routes/notify-wake.ts:98` 是两键字面量。
1307
+ # 封闭(census 批2 第三段,2026-07-30):live 臂铸造点 `routes/notify-wake.ts` 是两键字面量。
1299
1308
  # 4.3.0:形提成具名 `SessionNotifyResult` 的 live 臂(SDK 同批给了判别联合返回型)。
1300
1309
  schema:
1301
1310
  type: object
@@ -1308,7 +1317,7 @@ paths:
1308
1317
  description: No live stream — parked in the session inbox, drained on the next stream open.
1309
1318
  content:
1310
1319
  application/json:
1311
- # 封闭 + note 补 required:parked 臂铸造点 `routes/notify-wake.ts:116` 是三键字面量,note 无条件。
1320
+ # 封闭 + note 补 required:parked 臂铸造点 `routes/notify-wake.ts` 是三键字面量,note 无条件。
1312
1321
  schema:
1313
1322
  type: object
1314
1323
  additionalProperties: false
@@ -1352,7 +1361,7 @@ paths:
1352
1361
  responses:
1353
1362
  '200':
1354
1363
  # 🔴 census 批2 第三段(2026-07-30)spec 谎修正:此前这里写的是 **202** + required [taskId,…]。
1355
- # 亲读真码:wake 的成功腿是 `driveResumeIntoRunLog` 的返回(`server.ts:2289` 与取消臂 `:2240`),
1364
+ # 亲读真码:wake 的成功腿是 `driveResumeIntoRunLog` 的返回(`server.ts`,成功腿与取消臂两处铸造点),
1356
1365
  # **恒为 200**(wake 是同步驱动到终局/再挂起,不是 accepted 回执),且 `taskId` 有条件
1357
1366
  # (无活跃行即省略——ApprovalDecisionResult 同一铸造点的同一注)。旧 202+required taskId 双谎。
1358
1367
  description: Woken — the resumed leg ran to its next settle (terminal, re-suspended, or needs_review).
@@ -1405,8 +1414,7 @@ paths:
1405
1414
  description: The snapshot listing.
1406
1415
  content:
1407
1416
  application/json:
1408
- # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts:624-629`(信封)与
1409
- # `:620`(行)。条件键=信封 `latest`(零快照即省略)、行 `bytes`(blobSizes 面缺席=诚实省略)。
1417
+ # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts`(信封与行两处)。条件键=信封 `latest`(零快照即省略)、行 `bytes`(blobSizes 面缺席=诚实省略)。
1410
1418
  # 4.3.0:形提成具名 `WorkspaceSnapshotPage`(SDK 同批把返回位的内联字面量也提成了 exported type,
1411
1419
  # 让 spec-resource-schema-gate 罩得住 —— CAPS-OPS-8)。
1412
1420
  schema: { $ref: '#/components/schemas/WorkspaceSnapshotPage' }
@@ -1450,8 +1458,7 @@ paths:
1450
1458
  description: One page of the tree.
1451
1459
  content:
1452
1460
  application/json:
1453
- # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts:651-657`(信封)与
1454
- # `:654`(行)。条件键=信封 `nextOffset`(尾页省略)、行 `size`(blobSizes 面缺席省略)。
1461
+ # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts`(信封与行两处)。条件键=信封 `nextOffset`(尾页省略)、行 `size`(blobSizes 面缺席省略)。
1455
1462
  # 4.3.0:形提成具名 `WorkspaceTreePage`(理由同 list 腿 —— CAPS-OPS-8)。
1456
1463
  schema: { $ref: '#/components/schemas/WorkspaceTreePage' }
1457
1464
  '400': { $ref: '#/components/responses/BadRequest' }
@@ -1571,7 +1578,7 @@ paths:
1571
1578
  application/json:
1572
1579
  schema:
1573
1580
  type: object
1574
- # 封闭:铸造点 `routes/sessions.ts:540` 是单键字面量。BC-3 保证 `rules` 恒非 null
1581
+ # 封闭:铸造点 `routes/sessions.ts` 是单键字面量。BC-3 保证 `rules` 恒非 null
1575
1582
  # (无策略的已属会话回 `{rev:0}` 空记录,绝不回 `{rules:null}`)。
1576
1583
  additionalProperties: false
1577
1584
  required: [rules]
@@ -1612,7 +1619,7 @@ paths:
1612
1619
  application/json:
1613
1620
  schema:
1614
1621
  type: object
1615
- # 封闭:铸造点 `routes/sessions.ts:558` 是单键字面量 `{ rules: stored }`。
1622
+ # 封闭:铸造点 `routes/sessions.ts` 是单键字面量 `{ rules: stored }`。
1616
1623
  additionalProperties: false
1617
1624
  required: [rules]
1618
1625
  properties:
@@ -1656,8 +1663,11 @@ paths:
1656
1663
 
1657
1664
  # ── 2c session-sync (P1d) — the cloud as a SYNC PEER ──────────────────────────────────────────────────────────
1658
1665
  # The local shell's local backend ⇄ this cloud service. PULL a session's whole state out, or PUSH the local
1659
- # peer's in. Gate the whole surface off `capabilities.sessionSync` (= durable backend + entry export + a
1660
- # file-snapshot store). Owner-gated like fork/delete (§9): a PULL 404s a non-owner; a PUSH is own-or-FRESH.
1666
+ # peer's in. Gate the whole surface off `capabilities.sessionSync` — a FOUR-way conjunction (durable backend
1667
+ # AND entry export AND import STAGING AND a file-snapshot store). The staging conjunct is not decorative: a
1668
+ # three-conjunct predicate was disproven in the field ([2024]) when a warm-cache wrapper store forwarded the
1669
+ # other three and dropped the staging family, so caps said true while PUSH Phase A 501'd forever.
1670
+ # Owner-gated like fork/delete (§9): a PULL 404s a non-owner; a PUSH is own-or-FRESH.
1661
1671
  /v1/sessions/{sessionId}/sync/manifest:
1662
1672
  parameters:
1663
1673
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -1675,7 +1685,7 @@ paths:
1675
1685
  `/sync/entries`) + per-snapshot relPath→blobHash + policy + anchors + leaf. The local peer feeds `entryIds`
1676
1686
  to the §7 classifier to decide fast-forward/fork BEFORE pulling the entry stream + only the blob hashes it
1677
1687
  lacks. Owner-scoped (non-owner → 404, no oracle); 400 on an id that is not uuid-SHAPED — 🔴 the sync family's
1678
- gate is `isUuidShape` (ANY uuid version), NOT `isUuidV7`: server `routes/session-sync.ts:135-138` deliberately
1688
+ gate is `isUuidShape` (ANY uuid version), NOT `isUuidV7`: server `routes/session-sync.ts` deliberately
1679
1689
  keeps a v4-keyed legacy shell session pushable (it was creatable via the lenient submit face), while
1680
1690
  crafted-id classes stay shut out. `fork`/`delete`/`policy` DO use `isUuidV7` — only this family is lenient;
1681
1691
  501 without a durable session store.
@@ -1686,7 +1696,7 @@ paths:
1686
1696
  application/json:
1687
1697
  schema:
1688
1698
  type: object
1689
- # 封闭(census 批2 第三段,2026-07-30):铸造点 `routes/session-sync.ts:165` 是单键字面量。
1699
+ # 封闭(census 批2 第三段,2026-07-30):铸造点 `routes/session-sync.ts` 是单键字面量。
1690
1700
  additionalProperties: false
1691
1701
  required: [manifest]
1692
1702
  properties:
@@ -1889,7 +1899,7 @@ paths:
1889
1899
  description: >
1890
1900
  🔴 OPTIONAL content digest of the SOURCE's whole entry log (core `sessionLogDigest`, scheme
1891
1901
  `sema-log-v3`; this leg's core floor is ^1.416.0). Server producer/consumer:
1892
- `routes/session-sync.ts:295,310,357-366`. This is THE key that makes `ImportStaged.payloadVerified:true`
1902
+ `routes/session-sync.ts`. This is THE key that makes `ImportStaged.payloadVerified:true`
1893
1903
  reachable — until 4.3.0 the response schema promised a `payloadVerified`/`entry-ids+digest` pair
1894
1904
  that the request side had no way to trigger. Rationale: `identical` is classified from entry-id
1895
1905
  SETS only, and an entry id is a uuidv7 (NOT content-addressed), so "same ids, different payloads"
@@ -2086,7 +2096,7 @@ paths:
2086
2096
  Owner-gate / unknown session (no existence oracle), OR — [2400] CB-2, declared 2026-08-02 — the core
2087
2097
  code `checkpoint.not_found`: the pending checkpoint this decide addressed is gone. It is the ONE
2088
2098
  member of the mirrored `checkpoint.*` family the server sends as a 404 rather than a 409
2089
- (`src/http/server.ts:2199`: `e.code === "checkpoint.not_found" ? 404 : 409`), so the SDK maps it to
2099
+ (`src/http/server.ts`: `e.code === "checkpoint.not_found" ? 404 : 409`), so the SDK maps it to
2090
2100
  NotFoundError while the rest of the family maps to ConflictError. Client action: refetch the inbox —
2091
2101
  do not retry this decide.
2092
2102
  content:
@@ -2101,14 +2111,14 @@ paths:
2101
2111
  ConflictError vs ApprovalBindingMismatchError. binding_mismatch = SAFETY signal: refetch + re-present
2102
2112
  to the human, NEVER auto-retry.
2103
2113
  FOURTH member ([2400] CB-2, declared 2026-08-02): `gate_not_tool_approval` — the suspension this
2104
- sessionId is parked on is NOT a tool-approval gate (`src/http/server.ts:1581` rejects pre-CAS when the
2114
+ sessionId is parked on is NOT a tool-approval gate (`src/http/server.ts` rejects pre-CAS when the
2105
2115
  gate kind is neither `human` nor `irreversible_ask`: nothing is consumed, no model leg burns). It is
2106
2116
  the THIRD member of the wrong-door guard family alongside `gate_not_resumable` (§4b) and
2107
2117
  `gate_not_plan_review` (§4c) → SDK `GateNotToolApprovalError`. Client action: route by the row's
2108
2118
  `gateKind` — `plan_review` → `POST /v1/assistant/tasks/{taskId}/plan_review`, `resource_limit` →
2109
2119
  `POST /v1/assistant/tasks/{taskId}/resume`. Retrying the same body never succeeds.
2110
2120
  FIFTH group ([2400] CB-2): the core `checkpoint.*` family the server mirrors verbatim
2111
- (`src/http/server.ts:2199-2206`) — `checkpoint.gate_mismatch` / `checkpoint.reopen_failed` /
2121
+ (`src/http/server.ts`) — `checkpoint.gate_mismatch` / `checkpoint.reopen_failed` /
2112
2122
  `checkpoint.reopen_revote` / `checkpoint.already_exists` / `checkpoint.resume_aborted` /
2113
2123
  `checkpoint.unsupported_version` all arrive as 409 and fall on the SDK's `checkpoint.` PREFIX family
2114
2124
  (→ ConflictError, `.errorCode` carried verbatim; an OPEN set, so a future core code degrades with
@@ -2246,7 +2256,7 @@ paths:
2246
2256
  application/json:
2247
2257
  schema:
2248
2258
  type: object
2249
- # 封闭:铸造点 `routes/approvals-assistant.ts:146` 的 200 臂是单键字面量
2259
+ # 封闭:铸造点 `routes/approvals-assistant.ts` 的 200 臂是单键字面量
2250
2260
  # `{ revoked: true }`(404 臂是另一个字面量,走 NotFound 信封)。
2251
2261
  additionalProperties: false
2252
2262
  required: [revoked]
@@ -2475,7 +2485,7 @@ paths:
2475
2485
  application/json:
2476
2486
  schema:
2477
2487
  type: object
2478
- # 🔴 封闭 + 两处 spec 谎修正(census 批2 第三段,2026-07-30),铸造点 `routes/trace-usage.ts:61`:
2488
+ # 🔴 封闭 + 两处 spec 谎修正(census 批2 第三段,2026-07-30),铸造点 `routes/trace-usage.ts`:
2479
2489
  # ① 顶层信封 server 真发 `{sinceSec, rows}` —— `sinceSec`(生效的窗口秒数,默认 86400)
2480
2490
  # 此前整键不在 spec 里;
2481
2491
  # ② 行 `source` 三个 store 实现(sql/pg/local 的 sourceSummary)签名都是 `string | null`
@@ -2495,7 +2505,7 @@ paths:
2495
2505
  status: { type: string }
2496
2506
  count: { type: integer }
2497
2507
  '401': { $ref: '#/components/responses/Unauthorized' }
2498
- '501': { $ref: '#/components/responses/NotImplemented' } # 无 durable run store(capability.run_store_required)——`routes/trace-usage.ts:56`,此前漏登记
2508
+ '501': { $ref: '#/components/responses/NotImplemented' } # 无 durable run store(capability.run_store_required)——`routes/trace-usage.ts`,此前漏登记
2499
2509
 
2500
2510
  /v1/tasks/{taskId}/artifacts:
2501
2511
  parameters:
@@ -2536,7 +2546,7 @@ paths:
2536
2546
  # 🔴 修正(2026-07-25):此处原先 $ref 到 `AgentEvent`,是真错 —— 而且本文件自己就矛盾:专门为这条路写的
2537
2547
  # `TraceStreamEvent` schema 的描述里明写「A DISTINCT vocabulary from AgentEvent」,却没有任何地方引用它
2538
2548
  # (它是本文件里的一个孤儿 schema)。按 server 真码两侧取证后确定 TraceStreamEvent 才是对的:
2539
- # `streamTaskTrace` 每帧走 `mapTraceEvent`(`trace/project.ts:530`),发的是 SSE **event 名**
2549
+ # `streamTaskTrace` 每帧走 `mapTraceEvent`(`trace/project.ts`),发的是 SSE **event 名**
2540
2550
  # `block-thinking-delta` / `block-content-delta` / `tool-call` / `tool-result` / `turn` /
2541
2551
  # `prompt-assembled` / `done` / `error`,帧里**没有** `type` 字段;SDK 的 `trace.stream()` 注释逐字同款。
2542
2552
  # 照旧声明生成的客户端会去 switch 一个不存在的 `type` ⇒ 这条流路对它完全不可用。
@@ -2680,7 +2690,7 @@ paths:
2680
2690
  schema:
2681
2691
  type: object
2682
2692
  # 🔴 封闭 + `defaultModel` 补登记(census 批2 第三段,2026-07-30):铸造点
2683
- # `routes/capabilities.ts:322` 三键字面量。`defaultModel`([865]③ id/name 撕裂消解,server
2693
+ # `routes/capabilities.ts` 三键字面量。`defaultModel`([865]③ id/name 撕裂消解,server
2684
2694
  # 已发多版)SDK `models.list()` 的返回类型早就带,**spec 是唯一没登记的一侧** —— 生成式
2685
2695
  # 消费方对它全瞎。`name` 条件(目录无命中即省略),`id` 恒在。
2686
2696
  additionalProperties: false
@@ -2817,7 +2827,7 @@ paths:
2817
2827
  post:
2818
2828
  tags: [questions]
2819
2829
  operationId: askDecision
2820
- x-status: planned # design/172 §3.2 (#151 车4) — UNRELEASED; ships with server 7.3.0, gated OFF by STREAM_APPROVAL_ENABLED. SDK toolApprovals.decideAsk() 消费。
2830
+ x-status: gated # design/172 §3.2 (#151 车4) — SHIPPED in server 7.3.0 (7.3.0/7.4.0 both on npm); off-by-flag (STREAM_APPROVAL_ENABLED, default OFF ≤7.4.0 / ON ≥7.5.0) ⇒ 501 feature.approval_ask_disabled when the five-way gate is unmet. SDK toolApprovals.decideAsk() 消费。
2821
2831
  summary: Settle an IN-STREAM approval card (design/172 streaming approval protocol).
2822
2832
  description: >
2823
2833
  The in-stream sibling of POST /v1/tool-approvals/{id}/respond: the approver is live on the SSE stream, so
@@ -2895,7 +2905,8 @@ paths:
2895
2905
  '501':
2896
2906
  description: >
2897
2907
  In-stream approval protocol not on this worker (`feature.approval_ask_disabled`) — no ask store, or
2898
- STREAM_APPROVAL_ENABLED is off. Probe the capability, do not trial-by-501.
2908
+ STREAM_APPROVAL_ENABLED is off. Probe `capabilities.streamApproval` (same predicate as this gate),
2909
+ do not trial-by-501.
2899
2910
  content:
2900
2911
  application/json:
2901
2912
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3122,7 +3133,7 @@ paths:
3122
3133
  POST-MERGE FULL row — replace by `row.id`, NOT a sparse patch) + `event: task_remove`/`event:
3123
3134
  workflow_remove` `{id, ts}` departures.
3124
3135
  🔴 Keep-alive is a REAL NAMED FRAME — `event: heartbeat` + `data: {}` every 15 s
3125
- (`src/http/routes/fleet.ts:223`), NOT an SSE `: hb` comment (the旁注 says why: a per-frame-parsing BFF
3136
+ (`src/http/routes/fleet.ts`), NOT an SSE `: hb` comment (the旁注 says why: a per-frame-parsing BFF
3126
3137
  drops comments, so downstream saw a zero-frame window). Do NOT implement the reader as "skip lines
3127
3138
  starting with `:`" — swallow the heartbeat BY NAME. (Corrected 2026-08-02; the `: hb` shape belongs to
3128
3139
  `GET /v1/sessions/{sessionId}/events` alone, and beats at a tunable ~25 s.)
@@ -3278,7 +3289,7 @@ paths:
3278
3289
  summary: 'Newest PUBLISHED entry for a profile → { image }.'
3279
3290
  responses:
3280
3291
  # 🔴 census 批2 四段:this arm was a bare open `{}` (no `image` key even declared) — the handler's real
3281
- # body is `{ image: entry }` (images.ts:478-484), entry = the SAME ImageIndexEntry as the list route.
3292
+ # body is `{ image: entry }` (images.ts), entry = the SAME ImageIndexEntry as the list route.
3282
3293
  '200': { description: '{ image: entry }.', content: { application/json: { schema: { type: object, required: [image], additionalProperties: false, properties: { image: { $ref: '#/components/schemas/ImageIndexEntry' } } } } } }
3283
3294
  '404': { $ref: '#/components/responses/NotFound' }
3284
3295
 
@@ -3292,7 +3303,7 @@ paths:
3292
3303
  x-status: live
3293
3304
  summary: 'Entry by immutable digest → { image }.'
3294
3305
  responses:
3295
- # 🔴 census 批2 四段:same open-`{}` gap as imagesByProfile — real body `{ image: entry }` (images.ts:466-472).
3306
+ # 🔴 census 批2 四段:same open-`{}` gap as imagesByProfile — real body `{ image: entry }` (images.ts).
3296
3307
  '200': { description: '{ image: entry }.', content: { application/json: { schema: { type: object, required: [image], additionalProperties: false, properties: { image: { $ref: '#/components/schemas/ImageIndexEntry' } } } } } }
3297
3308
  '404': { $ref: '#/components/responses/NotFound' }
3298
3309
 
@@ -3567,7 +3578,7 @@ paths:
3567
3578
  schema:
3568
3579
  type: object
3569
3580
  # 🔴 census 批2 五段:CLOSED — exact literal `{ scope, exportedAt, entries }`
3570
- # (memory-policy.ts:58), no fourth key.
3581
+ # (memory-policy.ts), no fourth key.
3571
3582
  required: [scope, exportedAt, entries]
3572
3583
  additionalProperties: false
3573
3584
  properties:
@@ -3648,7 +3659,7 @@ paths:
3648
3659
  schema:
3649
3660
  type: object
3650
3661
  # 🔴 census 批2 五段:path↔schema 脱钩修(same class as the earlier BakeClaim/usage-triplet
3651
- # finds) — `OutcomeRow` already existed named+correct (observability.ts:51's exact
3662
+ # finds) — `OutcomeRow` already existed named+correct (observability.ts's exact
3652
3663
  # `{ outcomes: rows }`) but this path never `$ref`'d it, declaring a bare open `{}` instead.
3653
3664
  required: [outcomes]
3654
3665
  additionalProperties: false
@@ -3657,6 +3668,42 @@ paths:
3657
3668
  '403': { description: 'Multi-tenant worker: outcomes view is operator-only.' }
3658
3669
  '501': { $ref: '#/components/responses/NotImplemented' }
3659
3670
 
3671
+ /v1/diagnostics/wiring:
3672
+ parameters:
3673
+ - $ref: '#/components/parameters/PrincipalHeader'
3674
+ get:
3675
+ tags: [diagnostics]
3676
+ operationId: wiringDiagnostics
3677
+ x-status: live # server >=7.4.0 (#154) — assembly self-evidence read face.
3678
+ summary: What this worker actually wired (operator-only).
3679
+ description: >
3680
+ #154 — one screen that answers "what is this worker actually wired to". `static` is core's
3681
+ STATIC-half wiring manifest (`describeStaticWiring`): a DEPLOYMENT-level fact, independent of any
3682
+ leg, and it carries the `governance` section verbatim because **this face is that section's
3683
+ operator audience** (on a tenant-readable live stream the server strips it — core's contract is
3684
+ "no projection => do not disclose"). `serverGates` is the server's own assembly predicates,
3685
+ including WHY the in-stream approval protocol is or is not on (the first unsatisfied conjunct of
3686
+ the same five-way predicate that gates `capabilities.streamApproval` and the decision endpoint's
3687
+ 501 — so this page can never disagree with what a consumer hits).
3688
+ OPERATOR-ONLY. The gate is `OPERATOR_PRINCIPALS` NON-EMPTY **OR** REQUIRE_PRINCIPAL: if either
3689
+ holds, the caller must be a principal listed in OPERATOR_PRINCIPALS (403 `auth.operator_only`
3690
+ otherwise) — an EMPTY operator list means NOBODY is an operator, never everybody. Note that
3691
+ "REQUIRE_PRINCIPAL is off" does NOT mean open: a deployment that lists operators still 403s an
3692
+ anonymous or non-listed caller. The only open posture is an empty operator list on a
3693
+ non-multi-tenant worker (single-user turnkey — the sole user IS the operator).
3694
+ Deliberately carries **no per-leg history**: the governance section exists only in the live
3695
+ frame's operator projection — the durable ledger always stores the stripped form, because at
3696
+ write time it cannot know who will read it back.
3697
+ responses:
3698
+ '200':
3699
+ description: This worker's static wiring + the server's own gate readings.
3700
+ content:
3701
+ application/json:
3702
+ schema: { $ref: '#/components/schemas/WiringDiagnostics' }
3703
+ '401': { $ref: '#/components/responses/Unauthorized' }
3704
+ '403': { description: 'Multi-tenant worker: wiring diagnostics are operator-only (`auth.operator_only`).' }
3705
+ '404': { description: 'This worker serves no wiring diagnostics (older server, or a process not assembled by the composition root).' }
3706
+
3660
3707
  /v1/sendfile-links:
3661
3708
  parameters:
3662
3709
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -3782,7 +3829,7 @@ paths:
3782
3829
  description: 'Injected. Body `{ runId, label, status: "running", delivery: "applied", marker }`.'
3783
3830
  # 🔴 census 批2 五段:spec-谎修——this 200 declared a bare `description` with NO `content`/`schema`
3784
3831
  # at all (classify() reads it as "no-json", invisible to both the open- and closed-op ledgers)
3785
- # while the server genuinely sends a JSON body (workflows.ts:279). The named schema
3832
+ # while the server genuinely sends a JSON body (workflows.ts). The named schema
3786
3833
  # `WorkflowAgentSteerReceipt` already existed (path↔schema decoupling, same class as the earlier
3787
3834
  # BakeClaim/usage-triplet finds) — wiring it up here, not inventing a new one.
3788
3835
  content:
@@ -3826,7 +3873,7 @@ paths:
3826
3873
  content:
3827
3874
  application/json:
3828
3875
  # 🔴 census 批2 五段:CLOSED against the real emitter — `sendJson(res, 200, { model,
3829
- # ...deps.metrics.summarize() })` (server.ts:794) is a fixed key set (server observability/
3876
+ # ...deps.metrics.summarize() })` (server.ts) is a fixed key set (server observability/
3830
3877
  # metrics.ts `MetricsSummary`), not an ops free-for-all. Left `x-status: draft` / `x-sdk: none`
3831
3878
  # untouched (still deliberately outside the SDK's wrapped surface) — closing the SHAPE and
3832
3879
  # keeping it un-wrapped are independent axes.
@@ -4017,8 +4064,8 @@ components:
4017
4064
  `Capabilities.scenarios` instead. `source`/`builtin` = builtin vs center-config overlay; `toolset` names
4018
4065
  the tool bundle while `tools` are the resolved tool names; `promptSummary` is a human-readable prompt
4019
4066
  DIGEST (never the full prompt); `enabled` = a center scenario can be declared-but-disabled.
4020
- # 封闭(census 批2 第三段,2026-07-30):路由 `routes/capabilities.ts:38` 原样发 `deps.scenarioDetails[name]`,
4021
- # 其类型 `ScenarioDetail`(capabilities/scenarios.ts:353)恰是这 8 个必填键;builtin 工厂 `mk`(:379)与
4067
+ # 封闭(census 批2 第三段,2026-07-30):路由 `routes/capabilities.ts` 原样发 `deps.scenarioDetails[name]`,
4068
+ # 其类型 `ScenarioDetail`(capabilities/scenarios.ts)恰是这 8 个必填键;builtin 工厂 `mk` 与
4022
4069
  # center overlay 都按该接口铸,无第 9 键来源。
4023
4070
  required: [name, source, builtin, summary, toolset, tools, promptSummary, enabled]
4024
4071
  additionalProperties: false
@@ -4163,7 +4210,7 @@ components:
4163
4210
  type: string
4164
4211
  description: >-
4165
4212
  [2400] TR-16 (declared 2026-08-02) — CALLER-MINTED uuidv7 task id: the DURABLE second tier of
4166
- idempotency on POST /v1/runs (`src/http/routes/runs.ts:196-217`). The `Idempotency-Key` header's
4213
+ idempotency on POST /v1/runs (`src/http/routes/runs.ts`). The `Idempotency-Key` header's
4167
4214
  cache is in-memory and PER-POD, so a dispatch gateway that re-routes a retry to ANOTHER instance
4168
4215
  after a network error would start a SECOND run; THIS replay reads the durable run store, so it
4169
4216
  survives the re-route and a restart — it is the prerequisite for gateway failover. A hit replays the
@@ -4353,7 +4400,7 @@ components:
4353
4400
  description: Quality-gate cascade (cheap→strong). Mutually exclusive with `verify`. Not on the stream.
4354
4401
  suggestNextPrompts:
4355
4402
  description: >
4356
- E12 (shell-host; service spec-fields.ts:9 / runs.ts:392, SHIPPED) — opt-in post-completion "what to ask
4403
+ E12 (shell-host; service spec-fields.ts / runs.ts, SHIPPED) — opt-in post-completion "what to ask
4357
4404
  next" suggestions. After a COMPLETED run, core runs an LLM pass and the service appends a `suggestions`
4358
4405
  event to the durable run-events tail. `true` = core defaults; the object form tunes count / generating
4359
4406
  role. 🔴 Mutually exclusive with `verify`/`cascade` (those return a result, not a streamed run → 400).
@@ -4384,8 +4431,12 @@ components:
4384
4431
  callability; the advertised schema stays the empty object); `swap` advertises the REAL schema on the
4385
4432
  next request. A provider whose decoding is CONSTRAINED by the advertised schema loops UNBOUNDED under
4386
4433
  `static` (every round emits `{}` and the next round still advertises an empty schema), which is why
4387
- this is a per-task knob. OMIT to inherit the engine chain (`spec ?? env ?? static`) — sending an
4388
- explicit value takes that decision away from the deployment env lane. Unknown value => 400 fail-loud.
4434
+ this is a per-task knob. OMIT to inherit the engine chain,
4435
+ `spec ?? env(SEMA_TOOL_MATERIALIZE_STRATEGY) ?? "swap"` — sending an explicit value takes that
4436
+ decision away from the deployment env lane. 🔴 The chain-tail default is **`swap`** (this text used
4437
+ to say `static`): it defaulted to `static` for exactly one core release window and core 5.15.0
4438
+ (BREAKING) put it back to `swap`; the server floor is core >= 5.16.0, so every supported deployment
4439
+ resolves an omitted value to `swap`. Unknown value => 400 fail-loud.
4389
4440
  clientContext:
4390
4441
  type: object
4391
4442
  description: Non-authoritative client hints (never trusted for authz).
@@ -4428,7 +4479,7 @@ components:
4428
4479
  type: boolean
4429
4480
  description: >
4430
4481
  🔴 PER-TASK opt-in for forwarding subagent CONTENT onto the parent's event stream (server
4431
- `src/http/wire-types.ts:157`, consumed at `boot/resolve-spec.ts:557` — read STRICTLY as `=== true`).
4482
+ `src/http/wire-types.ts`, consumed at `boot/resolve-spec.ts` — read STRICTLY as `=== true`).
4432
4483
  ⚠️ The capability bit of the same name says "this body key is ACCEPTED", NOT "forwarding is on":
4433
4484
  it is hard-coded true, while forwarding itself is OFF unless this key is sent. Without it the parent
4434
4485
  stream carries progress-only frames and a subagent-transcript view stays empty forever.
@@ -4439,11 +4490,11 @@ components:
4439
4490
  - type: object
4440
4491
  additionalProperties: false
4441
4492
  properties:
4442
- ttlMs: { type: integer, description: 'Retention window; server CLAMPS to ≤ 24 h (task-workflow.ts:77-83).' }
4493
+ ttlMs: { type: integer, description: 'Retention window; server CLAMPS to ≤ 24 h (task-workflow.ts).' }
4443
4494
  max: { type: integer, description: 'Retained session count; server CLAMPS to ≤ 64.' }
4444
4495
  description: >
4445
4496
  🔴 PER-TASK opt-in to RETAIN finished subagent sessions so they can be resumed (server
4446
- `src/http/wire-types.ts:169`, normalized at `boot/resolve-spec.ts:564`). This is the OTHER HALF of
4497
+ `src/http/wire-types.ts`, normalized at `boot/resolve-spec.ts`). This is the OTHER HALF of
4447
4498
  `POST /v1/runs/{runId}/subagents/{handle}/resume`: the capability bit `subagentResume` can be true and
4448
4499
  the route mounted, and the call still 409s `resume.retain_off` — retention is per-run and OFF by default.
4449
4500
  An over-large ask is CLAMPED, not rejected. (Declared 2026-08-02; the error code and the capability bit
@@ -4535,7 +4586,7 @@ components:
4535
4586
  # 🔴 **刻意不封闭**(census 轴一裁定,2026-07-30)。此前是「没写」= JSON Schema 默认开放,分不清
4536
4587
  # 「决定了要开」还是「忘了」;现在显式写成 true,让这条决定可读、可复审。理由:server 对引擎
4537
4588
  # `TaskResult` 是**整体透传**(runs.ts `withModelUsage` 只做 `{...result, stats:{...}}` 的加法),
4538
- # 而 SDK 的 `TaskResult`/`TaskStats` 两侧都带 `[key: string]: unknown` 开集(types.ts:119)——把
4589
+ # 而 SDK 的 `TaskResult`/`TaskStats` 两侧都带 `[key: string]: unknown` 开集(types.ts)——把
4539
4590
  # 这里改成 false 会让 spec 与本仓自己的类型面互相打脸,并且把「引擎加字段」判成 wire 违约(它不是)。
4540
4591
  # 收紧这一面的正解不是 additionalProperties,是把引擎真发的键**逐个登记**(字段级 drift 门的活)。
4541
4592
  additionalProperties: true
@@ -4647,7 +4698,7 @@ components:
4647
4698
 
4648
4699
  RunReceipt:
4649
4700
  type: object
4650
- # 封闭(census 轴一,2026-07-30):producer 铸造点 routes/runs.ts:180 与 idem 重放臂各自逐字写死
4701
+ # 封闭(census 轴一,2026-07-30):producer 铸造点 routes/runs.ts 与 idem 重放臂各自逐字写死
4651
4702
  # `{taskId, sessionId, status}` —— 三键全在场、无第四键 ⇒ required 已完整,additionalProperties 收紧到
4652
4703
  # false 后「server 多发一个 spec 没登记的键」当场红(此前 ①档只校已声明键的类型,多键完全不可见)。
4653
4704
  additionalProperties: false
@@ -4664,7 +4715,7 @@ components:
4664
4715
  Run state (`GET /v1/runs/:id`) — live shape per the pinned wire contract Drift 2: `result` is the NESTED TaskResult
4665
4716
  object (output text in result.result, stats in result.stats; NO top-level stats), and the error text
4666
4717
  field is `error` (not errorMessage; errorCode is floated up from result.errorCode).
4667
- # 封闭(census 轴一,2026-07-30):铸造点 routes/runs.ts:337-351 是一个逐键写死的字面量,恰好 10 键
4718
+ # 封闭(census 轴一,2026-07-30):铸造点 routes/runs.ts 是一个逐键写死的字面量,恰好 10 键
4668
4719
  # (taskId/sessionId/status/result/supervisorCost/suggestions/errorCode/error/jobId/source),与本
4669
4720
  # properties 一一对上;只有前三键无条件在场(其余走 `?? undefined` ⇒ JSON 丢键)⇒ required 维持 3 键。
4670
4721
  additionalProperties: false
@@ -4728,7 +4779,7 @@ components:
4728
4779
  description: >
4729
4780
  Synchronous ack for `POST /v1/runs/:id/cancel`. `status` is "cancelling" (accepted) or a terminal status
4730
4781
  (idempotent no-op). NOTE: "cancelling" is NOT a RunStatus enum member — it only appears here.
4731
- # 封闭(census 轴一,2026-07-30):cancel 面的 7 个 sendJson 铸造点(routes/runs.ts:417/436/480/483/
4782
+ # 封闭(census 轴一,2026-07-30):cancel 面的 7 个 sendJson 铸造点(routes/runs.ts
4732
4783
  # 495/505/512)键集的并集恰好 = {taskId, status, note?, errorCode?};taskId/status 无条件在场。
4733
4784
  additionalProperties: false
4734
4785
  required: [taskId, status]
@@ -4829,7 +4880,7 @@ components:
4829
4880
  securityContext: { type: object, additionalProperties: true, description: 'k8s-shaped, genuinely arbitrary — Record<string,unknown> server-side.' }
4830
4881
 
4831
4882
  ImageSelectResult:
4832
- # 新增(2026-07-25 回填批)。🔴 census 批2 四段:closed against the real handler (images.ts:407-415) — ALL
4883
+ # 新增(2026-07-25 回填批)。🔴 census 批2 四段:closed against the real handler (images.ts) — ALL
4833
4884
  # seven keys are unconditionally assigned every 200 (required tightened from none); `manifestSha` can be
4834
4885
  # null (ImageIndexEntry.manifestSha is `string | null`, this was typed non-nullable — a spec lie); and
4835
4886
  # `podContract`/`capabilities` now share the SAME closed schemas as ImageIndexEntry (no more drift/no
@@ -4906,7 +4957,7 @@ components:
4906
4957
  description: >
4907
4958
  Turn trace envelope (`GET /v1/tasks/:id/turns`). Key is `turns` (resource-named). `retainedFrom` marks
4908
4959
  the lowest retained seq. Turn ITEM shape is draft (co-design with artifacts, Q7).
4909
- # 封闭 + required 补全(census 轴一,2026-07-30):铸造点 routes/trace-usage.ts:233
4960
+ # 封闭 + required 补全(census 轴一,2026-07-30):铸造点 routes/trace-usage.ts
4910
4961
  # `{ turns, ...(nextCursor?{nextCursor}:{}) , retainedFrom }` —— `retainedFrom` 是**无条件**键
4911
4962
  # (`RunStore.retainedFrom(): Promise<number>`,四个实现都返回数字),此前只 required 了 `turns`,
4912
4963
  # 于是「server 少发 retainedFrom」这种断页判据丢失对本门不可见。
@@ -4922,7 +4973,7 @@ components:
4922
4973
  TaskList:
4923
4974
  type: object
4924
4975
  description: Paginated run summaries (`GET /v1/tasks`). Envelope key is `tasks` (resource-named, NOT `items`).
4925
- # 封闭(census 轴一,2026-07-30):信封铸造点 routes/trace-usage.ts:201 只有 tasks + 条件 nextCursor。
4976
+ # 封闭(census 轴一,2026-07-30):信封铸造点 routes/trace-usage.ts 只有 tasks + 条件 nextCursor。
4926
4977
  # 行本身(`TaskSummary`)保持开集 —— 它的 TS 侧有索引签名(引擎透传),那份「开」是真的。
4927
4978
  additionalProperties: false
4928
4979
  required: [tasks]
@@ -4983,7 +5034,7 @@ components:
4983
5034
 
4984
5035
  ArtifactList:
4985
5036
  type: object
4986
- # 封闭(census 轴一,2026-07-30):铸造点 routes/trace-usage.ts:214 只有 artifacts 一键(无分页)。
5037
+ # 封闭(census 轴一,2026-07-30):铸造点 routes/trace-usage.ts 只有 artifacts 一键(无分页)。
4987
5038
  # 行本身(`Artifact`)保持开集(TS 侧索引签名)。
4988
5039
  additionalProperties: false
4989
5040
  required: [artifacts]
@@ -4995,7 +5046,7 @@ components:
4995
5046
  LeaderReceipt:
4996
5047
  type: object
4997
5048
  description: >
4998
- 202 receipt of POST /v1/leader (server leader/endpoint.ts:130 — exact literal
5049
+ 202 receipt of POST /v1/leader (server leader/endpoint.ts — exact literal
4999
5050
  `{ leaderRunId, status: "running" }`; the POST ack is always the "running" value, the other
5000
5051
  `status` enum members only ever appear on the GET twin below).
5001
5052
  # 🔴 census 批2 五段:CLOSED — the POST handler's literal never carries a third key.
@@ -5008,12 +5059,12 @@ components:
5008
5059
  LeaderRecord:
5009
5060
  type: object
5010
5061
  description: >
5011
- 200 body of GET /v1/leader/{leaderRunId} (server leader/endpoint.ts:142-150 — exact literal
5062
+ 200 body of GET /v1/leader/{leaderRunId} (server leader/endpoint.ts — exact literal
5012
5063
  `{ id, status, ...(result?{result}:{}), ...(error?{error}:{}) }`).
5013
5064
  # 🔴 census 批2 五段:两处修——① CLOSED(result/error 是 OMIT-when-absent 的可选键,不是额外键);
5014
5065
  # ② `status` 的第四值 `needs_human`(LEADER-REPAIRLOOP-INTEGRATION §5/§10.6 的第三终态,
5015
5066
  # `statusForLeaderResult` 在 candidate_only/needs_human_oracle/conflict 三个 repairTerminal 上产出)
5016
- # 此前 SPEC LIES BY OMISSION——TS 侧 `LeaderRecord.status` 早就带这个第四值(types.ts:1607,亲验
5067
+ # 此前 SPEC LIES BY OMISSION——TS 侧 `LeaderRecord.status` 早就带这个第四值(types.ts,亲验
5017
5068
  # 已在场),只有 spec 的 enum 一直缺它,是个只有场景真触发才会现形的假红洞(一个真实合法帧被判违约)。
5018
5069
  required: [id, status]
5019
5070
  additionalProperties: false
@@ -5076,7 +5127,7 @@ components:
5076
5127
  type: object
5077
5128
  description: >
5078
5129
  The legacy D-lane approval row of GET /v1/approvals/{approvalId} (server `ApprovalRow`,
5079
- plugins/approval-store-sql.ts:43 — the row is `sendJson`'d verbatim, approvals-assistant.ts:668).
5130
+ plugins/approval-store-sql.ts — the row is `sendJson`'d verbatim, approvals-assistant.ts).
5080
5131
  Present only on deployments running the legacy `approvalStore` leg (no `checkpointStore` wired);
5081
5132
  the durable F4 lane has no by-id GET (only list + decide).
5082
5133
  # 🔴 census 批2 五段:CLOSED — byte-identical to the server row type, no extra keys.
@@ -5130,7 +5181,7 @@ components:
5130
5181
  legacy verbs in 1.0.0; the live face is `client.memory.exportScope` / `.sync`
5131
5182
  (`GET /v1/memory/export`, `POST /v1/memory/sync/{scope}`), which this flag does NOT gate.
5132
5183
  Kept on the wire so an older shell that still reads it honestly hides the dead affordances.
5133
- workflows: { type: boolean, description: "S8 self-orchestration workflow routes are mounted." }
5184
+ workflows: { type: boolean, description: "ENGINE-CAN: this worker's engine can orchestrate S8 self-orchestration workflows (server: `workflowsCapable ?? Boolean(workflowRunStore)`). The MOUNTING of the durable /v1/workflows read routes is the separate `workflowsList` bit below; the two coincide in today's wiring but are declared as orthogonal axes." }
5134
5185
  workflowsList: { type: boolean, description: "`GET /v1/workflows` list face (separate from the per-run reads)." }
5135
5186
  resumeAt: { type: boolean, description: "E18 resume-at-message anchors are resolvable." }
5136
5187
  rewindFiles: { type: boolean, description: "E19 working-tree snapshot/restore is wired." }
@@ -5141,7 +5192,7 @@ components:
5141
5192
  sessionSearch: { type: boolean, description: "Server-side session search." }
5142
5193
  sessionFork: { type: boolean, description: "Session fork verb." }
5143
5194
  sessionDelete: { type: boolean, description: "Session delete verb." }
5144
- sessionInit: { type: boolean, description: "Session pre-initialization verb." }
5195
+ sessionInit: { type: boolean, description: "`GET /v1/sessions/{sessionId}/init` startup bundle (E11 shell-host contract) is available. Pure config assembly (model + mode), always served — the server hard-codes true. NOTE: this advertises an ENDPOINT, not a `session_init` stream frame; no such frame exists on any leg." }
5145
5196
  sessionPolicy: { type: boolean, description: "E6 per-session operator-tightened tool rules are durable." }
5146
5197
  usage: { type: boolean, description: "QUOTA face wired (server costQuota dep — cost/token budget enforcement + per-principal accounting). NOT an analytics-availability probe ([1871] B15): per-turn analytics (turn_end.usage, model_usage frames) are engine-side and unconditional, and GET /v1/usage always answers 200 (with `enabled: false` when the quota face is off). Gate quota UI on this key; never gate analytics on it." }
5147
5198
  fleet:
@@ -5182,7 +5233,7 @@ components:
5182
5233
  sessionEvents:
5183
5234
  type: boolean
5184
5235
  description: >
5185
- 🔴 契约调和车1 H② (2026-08-02): server HAS been emitting this key (`src/http/routes/capabilities.ts:70`)
5236
+ 🔴 契约调和车1 H② (2026-08-02): server HAS been emitting this key (the `sessionEvents` bit in `src/http/routes/capabilities.ts`)
5186
5237
  while both this spec and the SDK type were missing it. Gates `GET /v1/sessions/{sessionId}/events`
5187
5238
  (the session-level SSE head subscription). Predicate is a THREE-way AND: a durable session-watch seam
5188
5239
  plus a session store exposing BOTH `getLeafId` and `ownerOf` — the route's 501 uses the same
@@ -5213,11 +5264,36 @@ components:
5213
5264
  mcpElicitation: { type: boolean, description: "MCP elicitation round-trips are supported." }
5214
5265
  askUserQuestion: { type: boolean, description: "AskUserQuestion gate is available (durable when approvals are)." }
5215
5266
  toolApproval: { type: boolean, description: "Tool-approval gating is active." }
5267
+ streamApproval:
5268
+ type: boolean
5269
+ description: >
5270
+ design/172 IN-STREAM approval protocol is on this worker (server >=7.3.0, the `streamApproval` bit in
5271
+ `src/http/routes/capabilities.ts` — `resolveStreamApprovalGate(...).active`). TRUE ⇒ the live
5272
+ stream may carry `approval_request` / `approval_revoke` frames and `POST
5273
+ /v1/tasks/{taskId}/asks/{askId}/decision` settles them. FALSE/absent ⇒ that endpoint answers **501
5274
+ `feature.approval_ask_disabled`** on every call and the two frames never appear — the SAME predicate
5275
+ gates both, so "says yes ⟺ the route works". Probe this bit and hide the face; do NOT trial-by-501.
5276
+ The predicate is a FIVE-way conjunction (`resolveStreamApprovalGate` in `src/tool-approval.ts`): the live approval coordinator
5277
+ is wired (TOOL_APPROVAL_ENABLED) AND `STREAM_APPROVAL_ENABLED` AND a store backend is present AND
5278
+ `backend.kind != "local"` (the ask ledger must be DURABLE — an in-memory ledger loses accepted
5279
+ decisions on restart) AND the park facility is present (checkpoint store + DURABLE_APPROVAL, the
5280
+ degradation target when a window expires; without it the outcome would be a fail-closed deny, worse
5281
+ than today's live card). The deployment default for `STREAM_APPROVAL_ENABLED` reads in TWO
5282
+ SEGMENTS: on server **<= 7.4.0 it is default OFF**, so on virtually every deployment of those
5283
+ versions this bit is `false` and the legacy `tool_approval` live-card leg is the only approval face;
5284
+ on server **>= 7.5.0 it is default ON** (published to npm 2026-08-07, disclosed as BREAKING in the
5285
+ server CHANGELOG together with the ask window default 60s -> 300s), so a deployment meeting the
5286
+ other four conjuncts serves the frames and the decision endpoint out of the box. BOTH segments are
5287
+ live deployment realities — the first is not a historical footnote, it still describes every running
5288
+ 7.3.x/7.4.x install, and a consumer that reads only one of them is wrong about half the fleet.
5289
+ `STREAM_APPROVAL_ENABLED=false` is the clean revert key. Do NOT infer from the version number or
5290
+ from this prose — READ THIS BIT; it is the conjunction's actual value. Orthogonal to `toolApproval`
5291
+ (the live-card leg) and to `approvals` (the durable checkpoint leg) — all three can differ.
5216
5292
  promptSuggestions: { type: boolean, description: "Follow-up prompt suggestions are generated." }
5217
5293
  modelUsage:
5218
5294
  type: boolean
5219
5295
  description: >
5220
- Per-model usage echo is available (server `src/http/routes/capabilities.ts:197`
5296
+ Per-model usage echo is available (server: the `modelUsage` bit in `src/http/routes/capabilities.ts`,
5221
5297
  `Boolean(runStore && modelUsage)` — BOTH the durable run store and the tracker that feeds it;
5222
5298
  absent either, a consumer only gets aggregate cost).
5223
5299
  🔴 CORRECTED (契约调和车1 H③, 2026-08-02): this bit does NOT gate a read route — the route this
@@ -5233,21 +5309,25 @@ components:
5233
5309
  s3PublicEndpoint:
5234
5310
  type: ["string", "null"]
5235
5311
  description: >
5236
- Public base URL for artifact/snapshot links, when the deployment exposes one. `null` = not configured
5237
- (links are then worker-relative). One of only two NON-boolean capability values the server emits.
5312
+ SendUserFile's S3 public-link base (`config.sendUserFile.publicEndpoint`), when the deployment
5313
+ configures one. `null` = not configured. 🔴 CORRECTED: it has NOTHING to do with artifact/snapshot
5314
+ links, and there is no "links are then worker-relative" fallback — an unconfigured endpoint makes
5315
+ SendUserFile fail loudly and the sibling `sendUserFile` bit report false. A NON-boolean capability
5316
+ value (there are several — see `taskSettings`, `fleet`, `workspace`, `version`, `scenarios`).
5238
5317
  taskSettings:
5239
5318
  type: object
5240
5319
  description: >
5241
5320
  Which `settings.json` sub-faces this worker honours (CC-parity v1). OPEN object — unknown keys may
5242
- appear. The other non-boolean capability value.
5321
+ appear. One of the NON-boolean capability values (not "the other one": `fleet`, `workspace`,
5322
+ `s3PublicEndpoint`, `version` and `scenarios` are non-boolean too — read each key's own shape).
5243
5323
  additionalProperties: true
5244
5324
  properties:
5245
5325
  permissions: { type: boolean }
5246
5326
  permissionMode: { type: boolean }
5247
5327
  model: { type: boolean }
5248
5328
  outputStyle: { type: boolean }
5249
- env: { type: boolean }
5250
- hooks: { type: boolean }
5329
+ env: { type: boolean, description: 'TRUE on the single-user host lane (server emits `cwdHonored(config)`). It is NOT hard-coded false any more — the old always-false value predated the per-task shellEnv seam and hid a live capability.' }
5330
+ hooks: { type: boolean, description: 'Single-user deployments only (`requirePrincipal !== true`).' }
5251
5331
  memoryWrite:
5252
5332
  type: boolean
5253
5333
  description: >
@@ -5333,7 +5413,7 @@ components:
5333
5413
  description: >
5334
5414
  core `MemoryEntry` (@sema-agent/core memory-engine/types.d.ts) — one memory-engine (DB twins)
5335
5415
  note. Used verbatim by both GET /v1/memory/export's `entries` and POST /v1/memory/sync/{scope}'s
5336
- `serverEntries` (server never re-shapes it — memory-policy.ts:57 / memory-sync.ts:79 pass the core
5416
+ `serverEntries` (server never re-shapes it — memory-policy.ts / memory-sync.ts pass the core
5337
5417
  array straight to `sendJson`).
5338
5418
  required: [id, slug, frontmatter, body, rev, scope]
5339
5419
  additionalProperties: false
@@ -5348,8 +5428,8 @@ components:
5348
5428
  MemorySyncResponse:
5349
5429
  type: object
5350
5430
  description: >
5351
- 200 body of POST /v1/memory/sync/{scope} — core `MemorySyncResponse` (server memory-sync.ts:74-86),
5352
- `sendJson`'d verbatim (memory-policy.ts:107). `pullTruncated` is OMIT-when-absent (present+`true`
5431
+ 200 body of POST /v1/memory/sync/{scope} — core `MemorySyncResponse` (server memory-sync.ts),
5432
+ `sendJson`'d verbatim (memory-policy.ts). `pullTruncated` is OMIT-when-absent (present+`true`
5353
5433
  only when `serverEntries` was cut by `?pull.limit`).
5354
5434
  required: [applied, conflicts, serverEntries, serverDeletes, cursor]
5355
5435
  additionalProperties: false
@@ -5402,7 +5482,7 @@ components:
5402
5482
  MetricsSummaryOps:
5403
5483
  type: object
5404
5484
  description: >
5405
- 200 body of GET /metrics/summary — `{ model, ...MetricsSummary }` (server.ts:794; server
5485
+ 200 body of GET /metrics/summary — `{ model, ...MetricsSummary }` (server.ts; server
5406
5486
  observability/metrics.ts `MetricsSummary` interface + `summarize()`, all keys unconditional).
5407
5487
  Ops/observability face (`x-sdk: none` on the path) — closed here for census fidelity, but this
5408
5488
  does NOT change its SDK-wrapping status.
@@ -5453,7 +5533,7 @@ components:
5453
5533
  A non-secret `@model` catalog entry (GET /v1/models; service src/http/routes/capabilities.ts). `name` = the `@handle` the
5454
5534
  user types in the objective body to pick this model per-task. 🔴 NON-SECRET — name/capabilities only, never
5455
5535
  baseUrl/apiKey/headers (the service strips them).
5456
- # 🔴 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):铸造点 `routes/capabilities.ts:295-313`
5536
+ # 🔴 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):铸造点 `routes/capabilities.ts`
5457
5537
  # 是逐键字面量投影(白名单,不是透传——「open set」旧注不成立,新键要先过这道 spec)。core Model 类型
5458
5538
  # 里 id/provider/reasoning 都是必填、`vision` 由 `input` 恒算 ⇒ 五键无条件;contextWindow/maxOutputTokens
5459
5539
  # 0/缺省即省略,supportedEffortLevels 仅 reasoning 模型。
@@ -5493,7 +5573,7 @@ components:
5493
5573
 
5494
5574
  ElicitRespondAck:
5495
5575
  type: object
5496
- description: The 200 ack from a successful elicitation respond (service elicitation.ts:267).
5576
+ description: The 200 ack from a successful elicitation respond (service elicitation.ts).
5497
5577
  # 封闭(census 批2 第三段,2026-07-30):铸造点是三键字面量,三键全部无条件。
5498
5578
  additionalProperties: false
5499
5579
  required: [elicitationId, delivery, action]
@@ -5505,7 +5585,7 @@ components:
5505
5585
  QuestionAnswer:
5506
5586
  type: object
5507
5587
  description: >
5508
- The shell's answer to a live `question` frame (= core QuestionAnswer, ask-question.ts:69). The service validates
5588
+ The shell's answer to a live `question` frame (= core QuestionAnswer, ask-question.ts). The service validates
5509
5589
  only this OUTER shape (`answers[]` of `{header, selected:string[], note?}`); core owns the semantic fence
5510
5590
  (`selected ⊆ the offered options`, `note` wrapped in an untrusted-DATA fence).
5511
5591
  required: [answers]
@@ -5527,7 +5607,7 @@ components:
5527
5607
 
5528
5608
  QuestionRespondAck:
5529
5609
  type: object
5530
- description: The 200 ack from a successful question respond (service question.ts:226).
5610
+ description: The 200 ack from a successful question respond (service question.ts).
5531
5611
  # 封闭(census 批2 第三段,2026-07-30):铸造点是两键字面量。
5532
5612
  additionalProperties: false
5533
5613
  required: [questionId, delivery]
@@ -5542,7 +5622,7 @@ components:
5542
5622
  the REQUEST principal. When no quota window is configured → enabled=false + ONLY windowSec/maxTask*
5543
5623
  (the amount/limit fields are ABSENT) — render must tolerate the gap.
5544
5624
  # 🔴 封闭 + required 补铸造点(census 批2 第二段,2026-07-30)。两条腿都是逐字字面量
5545
- # (`routes/observability.ts:68` 无配额臂 4 键 / `:72` 有配额臂 11 键),11 个属性就是并集。
5625
+ # (`routes/observability.ts` —— 无配额臂 4 键 / 有配额臂 11 键),11 个属性就是并集。
5546
5626
  # required 从 `[enabled]` 抬到四键:`windowSec`/`maxTaskCostUsd`/`maxTaskTokens` 在**两条腿上都**
5547
5627
  # 无条件发送(config 直读),只列 `enabled` 是把恒在键写成了可缺 —— 消费方因此得写永远为真的判空。
5548
5628
  required: [enabled, windowSec, maxTaskCostUsd, maxTaskTokens]
@@ -5568,7 +5648,7 @@ components:
5568
5648
  admission + approval enforcement is 100% server-side). Top level CLOSED; the two pass-through
5569
5649
  collections (`commandPolicy` rows, `approvalRequire` entries) stay permissive — they are operator config
5570
5650
  echoed verbatim, not a service-minted shape.
5571
- # 🔴 封闭 + required(census 批2 第二段,2026-07-30):铸造点 `routes/memory-policy.ts:131` 是一个
5651
+ # 🔴 封闭 + required(census 批2 第二段,2026-07-30):铸造点 `routes/memory-policy.ts` 是一个
5572
5652
  # 四键字面量,四个键**全部无条件**发送(`autonomy` 走 `?? null`,`commandPolicy` 走 `?? []`)——
5573
5653
  # 此前 required 是空的,于是「一个丢了三个键的响应」也能干干净净通过。
5574
5654
  required: [autonomy, commandPolicy, approvalRequire, limits]
@@ -5592,7 +5672,7 @@ components:
5592
5672
  type: object
5593
5673
  description: >
5594
5674
  Effective ceilings (single-task + principal-level + quota window). CLOSED (the literal at
5595
- `routes/memory-policy.ts:135` has exactly these four keys); the two `maxPrincipal*`/`costQuota*`
5675
+ `routes/memory-policy.ts` has exactly these four keys); the two `maxPrincipal*`/`costQuota*`
5596
5676
  entries stay OUT of `required` — they are optional config and are omitted when unset.
5597
5677
  additionalProperties: false
5598
5678
  required: [maxTaskCostUsd, maxTaskTokens]
@@ -5615,7 +5695,7 @@ components:
5615
5695
  GET /v1/workflows list row = core summarizeWorkflowRun projection (owner-scoped; scope = the creating
5616
5696
  principal). All times are epoch MS.
5617
5697
  🔴 census 批2 四段(2026-07-30):CLOSED against core's real `summarizeWorkflowRun` (sema-core
5618
- src/core/workflow-run-store.ts:79-97) — `originatingSessionId`/`agentFailures` were emitted on the
5698
+ src/core/workflow-run-store.ts) — `originatingSessionId`/`agentFailures` were emitted on the
5619
5699
  wire (both conditional-spread, present only when defined) but absent from this schema (two-side same-gap).
5620
5700
  required: [id, scope, status, phaseCount, agentCount, tokens, startedAt, createdAt]
5621
5701
  additionalProperties: false
@@ -5645,7 +5725,7 @@ components:
5645
5725
  🔴 `arg` is a SHORT primary-arg SUMMARY (command→name, path→basename, url→origin+path), secret-scrubbed
5646
5726
  and truncated (~80 code points) by core. It exists for the monitor's `Read(path)` / `Bash(grep …)`
5647
5727
  display — it is NOT the full args. RENDER it; NEVER re-feed it to a model.
5648
- 🔴 census 批2 四段:CLOSED against core's real `ToolActivity` (sema-core src/core/types.ts:2332-2348) —
5728
+ 🔴 census 批2 四段:CLOSED against core's real `ToolActivity` (sema-core src/core/types.ts) —
5649
5729
  `at` (the [843]④a epoch-ms stamp, carried verbatim by both the start and end beat) was on the wire
5650
5730
  but absent from this schema.
5651
5731
  required: [phase, toolCallId, toolName]
@@ -5661,7 +5741,7 @@ components:
5661
5741
  WorkflowAgentRow:
5662
5742
  # 新增(2026-07-25 回填批)—— 取代 `WorkflowRun.agents` 的 `items: {}`(零形状)。
5663
5743
  # 🔴 census 批2 四段:CLOSED against the REAL wire projection — server's `summarizeWorkflowDetail`
5664
- # (src/http/routes/workflows.ts:358-394), not core's raw `WorkflowAgentRun`. The two differ: the server
5744
+ # (src/http/routes/workflows.ts), not core's raw `WorkflowAgentRun`. The two differ: the server
5665
5745
  # ADDS `displayStatus`/`callKey`/`groupId`/`lastActivityAt`/`durationMs`/`queuedAt`/`startedAt`/`endedAt`/
5666
5746
  # `replayed` (all present on the wire but previously undeclared — same-side gap, no server change needed)
5667
5747
  # and DROPS core's `errorCode`/`errorMessage`/`attempts`/`lastAttemptReason`/`sessionId` (never projected
@@ -5698,7 +5778,7 @@ components:
5698
5778
 
5699
5779
  WorkflowPhaseProgress:
5700
5780
  # 新(census 批2 四段):此前 `WorkflowRun.phases` 是 `items: {}`(零形状)。真形 = server
5701
- # `summarizeWorkflowDetail`'s phases.map projection (workflows.ts:335-346) — a DERIVED progress view,
5781
+ # `summarizeWorkflowDetail`'s phases.map projection (workflows.ts) — a DERIVED progress view,
5702
5782
  # not core's raw `WorkflowPhase` (drops `detail`/`model`/(phase-level) `agentFailures`; adds `done`/`total`).
5703
5783
  type: object
5704
5784
  description: One phase's progress in a workflow's detail (server-derived — done/total of the agents grouped under it).
@@ -5715,7 +5795,7 @@ components:
5715
5795
 
5716
5796
  WorkflowGroupNode:
5717
5797
  # 新(census 批2 四段):`WorkflowRun.groups` was undeclared entirely. Real shape = server's rebuilt nested
5718
- # tree (workflows.ts:399-441, GroupNode) — recursive, acyclic (a dangling/cyclic parentGroupId is grafted
5798
+ # tree (workflows.ts, GroupNode) — recursive, acyclic (a dangling/cyclic parentGroupId is grafted
5719
5799
  # onto the root by the server, so this shape never actually cycles on the wire).
5720
5800
  type: object
5721
5801
  description: One node of the workflow's nested `ctx.workflow` group tree (rebuilt server-side from `parentGroupId`).
@@ -5733,7 +5813,7 @@ components:
5733
5813
 
5734
5814
  WorkflowRunStats:
5735
5815
  # 新(census 批2 四段):此前 inline `stats` 只钉了 `tokens`/`nested.tokens` 两键;真形 = core
5736
- # `WorkflowRunStats`(sema-core src/orchestration/workflow-types.ts:105-110)——own/nested 各自还有
5816
+ # `WorkflowRunStats`(sema-core src/orchestration/workflow-types.ts)——own/nested 各自还有
5737
5817
  # `turns`/`costMicroUsd`,nested 另有 `tasks`。四键(own turns/costMicroUsd + nested 两键)此前两侧同缺。
5738
5818
  type: object
5739
5819
  description: 'Cumulative workflow usage. `own` (top-level) and `nested` (delegated sub-agents) are kept SEPARATE (R-5) — total spend = tokens + nested.tokens.'
@@ -5755,7 +5835,7 @@ components:
5755
5835
 
5756
5836
  WorkflowRun:
5757
5837
  # 🔴 census 批2 四段:CLOSED against the REAL wire projection — server's `summarizeWorkflowDetail`
5758
- # (src/http/routes/workflows.ts:317-475), which is a DERIVED view over core's `WorkflowRun`, not the raw
5838
+ # (src/http/routes/workflows.ts), which is a DERIVED view over core's `WorkflowRun`, not the raw
5759
5839
  # record. This schema previously mirrored (a stale subset of) the core type; the actual wire adds
5760
5840
  # `durationMs`/`unphased`/`groups` and drops core's `sourceTaskId`/`originatingSessionId`/`effectiveArgs`/
5761
5841
  # `resultFull`/`completionId`/`resume`/`journalSkips` (never projected by this route — genuinely absent
@@ -5878,7 +5958,7 @@ components:
5878
5958
  SessionNotifyResult:
5879
5959
  # 4.3.0(SM-9):把 notify 的两态提成**具名** schema —— 此前只有 path 内联的两个匿名 200/202 形,
5880
5960
  # SDK 侧的返回型则是裸 `Record<string, unknown>`,`delivery` 这条承重语义(已送达 vs 排队)在类型面
5881
- # 整个丢失。铸造点 `routes/notify-wake.ts:98`(live)/ `:116`(parked),两臂都是字面量。
5961
+ # 整个丢失。铸造点 `routes/notify-wake.ts`(live / parked 两臂),都是字面量。
5882
5962
  description: >
5883
5963
  POST /v1/sessions/{sessionId}/notify 的判别联合。分支 `delivery`:`live` = 会话有活流、通知已送达
5884
5964
  (HTTP 200);`parked` = 无活流,排队到下次开流才 drain(HTTP 202)。消费方不得把两者都渲染成"已通知"。
@@ -5929,7 +6009,7 @@ components:
5929
6009
  nextOffset: { type: integer, description: 'Pagination cursor; present only when the page is exactly `limit` rows.' }
5930
6010
  WorkflowJournalEntry:
5931
6011
  # 新(census 批2 四段):GET /v1/workflows/:id/journal row, the non-truncated arm (server `projectResult`,
5932
- # workflows.ts:127-134) — one agent()-call's cached TaskResult, bounded + redacted.
6012
+ # workflows.ts) — one agent()-call's cached TaskResult, bounded + redacted.
5933
6013
  type: object
5934
6014
  description: One journal entry — a completed agent() call's projected TaskResult (bounded + redacted).
5935
6015
  required: [callKey, ordinal, status]
@@ -5944,7 +6024,7 @@ components:
5944
6024
  turns: { type: integer, description: 'present only when the result carried stats.' }
5945
6025
 
5946
6026
  WorkflowJournalEntryTruncated:
5947
- # 新(census 批2 四段):the truncated arm (workflows.ts:141-142/159-162) — an honest stub for a row whose
6027
+ # 新(census 批2 四段):the truncated arm (workflows.ts) — an honest stub for a row whose
5948
6028
  # stored TaskResult exceeded the per-row 64KiB gate (SQL stores) or byte length (file/in-memory
5949
6029
  # fallback): the server never pulls the oversized payload into process memory.
5950
6030
  type: object
@@ -5961,17 +6041,17 @@ components:
5961
6041
  type: object
5962
6042
  description: >
5963
6043
  One frame of GET /v1/workflows/:id/stream (streamWorkflowRun). FIRST frame is `event:meta` with
5964
- `data: {type:"meta", version, runId}` (`src/http/routes/workflows.ts:487` — the `type` is double-emitted
6044
+ `data: {type:"meta", version, runId}` (`src/http/routes/workflows.ts` — the `type` is double-emitted
5965
6045
  on purpose: a proxy that forwards only `data:` lines would otherwise leave a `data.type`-dispatching
5966
6046
  consumer unable to recognise the opener). Subsequent frames carry a core WorkflowEvent verbatim (its
5967
6047
  `type` is the SSE event name). NOT resumable (replica-local, in-process; no id/Last-Event-ID).
5968
6048
  `data` is permissive (core-internal, evolves).
5969
6049
  🔴 There IS exactly one TERMINAL frame and it means FAILURE: `event: error` with
5970
6050
  `data: {type:"error", errorCode:"workflow.stream_error", message:"workflow stream error"}`
5971
- (`routes/workflows.ts:517`). Branch on `errorCode` — NEVER anchor on `message`; the errorCode was added
6051
+ (`routes/workflows.ts`). Branch on `errorCode` — NEVER anchor on `message`; the errorCode was added
5972
6052
  precisely because consumers were otherwise forced to anchor prose or to conflate "the stream broke" with
5973
6053
  "the stream ended normally", i.e. to report a FAILED workflow as finished. (Declared 2026-08-02.)
5974
- Keep-alive is a REAL NAMED FRAME `event: heartbeat` / `data: {}` @15 s (`:496`), not an SSE comment.
6054
+ Keep-alive is a REAL NAMED FRAME `event: heartbeat` / `data: {}` @15 s (`routes/workflows.ts`), not an SSE comment.
5975
6055
  NOTE: on the wire this is SSE framing (event name + JSON data), not a JSON body — schema describes one
5976
6056
  decoded frame.
5977
6057
  required: [event, data]
@@ -5984,7 +6064,7 @@ components:
5984
6064
  FleetTaskStatus:
5985
6065
  type: string
5986
6066
  description: >
5987
- The fleet vocabulary the shell renders (service FleetTaskStatus, fleet-bus.ts:27-36). The service maps its
6067
+ The fleet vocabulary the shell renders (service FleetTaskStatus, fleet-bus.ts). The service maps its
5988
6068
  run/workflow status onto this NEUTRAL set: `awaiting approval` = a needs-review/plan-approval park,
5989
6069
  `waiting` = a durable suspend. CLOSED on the wire; render OPEN (branch known, fall back generically).
5990
6070
  enum: [queued, running, waiting, stopping, 'awaiting approval', idle, completed, failed, killed]
@@ -5992,7 +6072,7 @@ components:
5992
6072
  type: object
5993
6073
  description: 'Upload receipt (POST /v1/attachments 201). `name` is the SANITIZED basename the server will materialize under `attachments/`.'
5994
6074
  # 🔴 census 批2 五段(2026-07-30):CLOSED against the real emitter — `sendJson(res, 201, { id, name,
5995
- # mime, sha256, sizeBytes })` (server attachments.ts:90) is an EXACT 5-key object literal, never a
6075
+ # mime, sha256, sizeBytes })` (server attachments.ts) is an EXACT 5-key object literal, never a
5996
6076
  # superset.
5997
6077
  required: [id, name, mime, sha256, sizeBytes]
5998
6078
  additionalProperties: false
@@ -6005,7 +6085,7 @@ components:
6005
6085
  FleetTaskRow:
6006
6086
  type: object
6007
6087
  description: >
6008
- One active task row (service FleetTaskRow fleet-bus.ts:40-54) — MINUS the wire-STRIPPED `scope` (a
6088
+ One active task row (service FleetTaskRow fleet-bus.ts) — MINUS the wire-STRIPPED `scope` (a
6009
6089
  tenant-isolation field the service removes AFTER the owner-gate; never rendered, never on this wire). A
6010
6090
  `task` frame carries the POST-MERGE FULL row → replace by `id`, NOT a sparse patch. A subagent child row
6011
6091
  sets `parentId` (E2 parentToolCallId) + an id of `"<runId> <taskId>"`.
@@ -6070,14 +6150,14 @@ components:
6070
6150
  FleetWorkflowRow:
6071
6151
  type: object
6072
6152
  description: >
6073
- One workflow row (service FleetWorkflowRow fleet-bus.ts:57-69) — MINUS the wire-stripped `scope`. `status`
6153
+ One workflow row (service FleetWorkflowRow fleet-bus.ts) — MINUS the wire-stripped `scope`. `status`
6074
6154
  is the NEUTRAL workflow run status (running|completed|failed), open on read.
6075
6155
  required: [id, name, status]
6076
6156
  additionalProperties: true
6077
6157
  properties:
6078
6158
  # 🔴 FW-6(2026-08-02):这里此前有一个 `sourceLane` —— **幻影键,已删**。`sourceLane` 只在
6079
- # `FleetTaskRow`(`src/fleet/fleet-bus.ts:146`)上,`FleetWorkflowRow`(`:150-170`)全文没有它,
6080
- # `publishWorkflow`(`:338-342`)也从不写它;SDK 侧同样没有(SDK 是对的那一边)。
6159
+ # `FleetTaskRow`(`src/fleet/fleet-bus.ts`)上,`FleetWorkflowRow` 全文没有它,
6160
+ # 同文件的 `publishWorkflow` 也从不写它;SDK 侧同样没有(SDK 是对的那一边)。
6081
6161
  # 按它 codegen 的第三方会得到一个永远 undefined 的 `workflowRow.sourceLane`,并照 FleetTaskRow 那条
6082
6162
  # 「run-leg composite rows only」的描述写出一个**永不触发**的 workflow 行去重分支。
6083
6163
  id: { type: string }
@@ -6086,7 +6166,7 @@ components:
6086
6166
  status: { type: string, description: 'Neutral workflow run status (running|completed|failed; open).' }
6087
6167
  doneCount: { type: integer }
6088
6168
  # 🔴 FW-6(2026-08-02):补上一个 server 真发、SDK 真声明、只有 spec 漏了的键。
6089
- # `src/fleet/fleet-bus.ts:167` 声明,`src/orchestration/workflow-notify-journal.ts:511` 真发。
6169
+ # `src/fleet/fleet-bus.ts` 声明,`src/orchestration/workflow-notify-journal.ts` 真发。
6090
6170
  startedCount: { type: integer, description: 'Agents actually STARTED (vs `totalCount` = planned, queued included). CC 2.1.220 的 `⚠ Large workflow` 规模告警用它当分母 —— 漏了它那条告警根本渲染不出来。' }
6091
6171
  totalCount: { type: integer }
6092
6172
  failedCount: { type: integer }
@@ -6124,7 +6204,7 @@ components:
6124
6204
  hook_notice: '#/components/schemas/FleetFrame_hook_notice'
6125
6205
  FleetFrame_meta:
6126
6206
  type: object
6127
- # 🔴 FW-10(2026-08-02):此前只声明三键,而铸造点 `src/http/routes/fleet.ts:79` **无条件**发五键 ——
6207
+ # 🔴 FW-10(2026-08-02):此前只声明三键,而铸造点 `src/http/routes/fleet.ts` **无条件**发五键 ——
6128
6208
  # `sessionScoped` / `bgNotifyFailClosed` 不在任何条件分支里。这两个键正是「server 契约方复审 F1
6129
6209
  # (行为错·最高)」的当事键:它们曾被一次白名单重构静默剥掉,导致 cli 的让位臂全程死代码。
6130
6210
  # SDK 侧已上锁(`test/fleet.test.ts` 有专门的透传回归钉),spec 侧一直空白 —— 按 spec 生成的客户端
@@ -6279,8 +6359,8 @@ components:
6279
6359
  type:
6280
6360
  type: string
6281
6361
  description: >
6282
- 契约调和车1 G(2026-08-02;server [2373]C-12, `src/trace/project.ts:763` + the meta frame at
6283
- `routes/trace-usage.ts:258`): the frame's own event name, DOUBLE-EMITTED inside `data`. Always
6362
+ 契约调和车1 G(2026-08-02;server [2373]C-12, `src/trace/project.ts` + the meta frame at
6363
+ `routes/trace-usage.ts`): the frame's own event name, DOUBLE-EMITTED inside `data`. Always
6284
6364
  present on server >= 5.0.0; OPTIONAL here because the SDK's supported floor is server 3.0.0
6285
6365
  (where only the `error` frame carried it). Equal to the SSE `event:` line verbatim — its reason
6286
6366
  to exist is the proxy/relay case that strips the `event:` line and forwards only the data JSON,
@@ -6289,7 +6369,7 @@ components:
6289
6369
  PendingList:
6290
6370
  type: object
6291
6371
  description: Envelope for pending HITL checkpoints (key `pending`, NOT a bare array — the pinned wire contract).
6292
- # 封闭(census 轴一,2026-07-30):三个铸造点(routes/approvals-assistant.ts:89/620/624)都只发
6372
+ # 封闭(census 轴一,2026-07-30):三个铸造点(routes/approvals-assistant.ts)都只发
6293
6373
  # `{ pending }`。行本身(`PendingCheckpoint`)保持开集(TS 侧索引签名)。
6294
6374
  additionalProperties: false
6295
6375
  required: [pending]
@@ -6302,7 +6382,7 @@ components:
6302
6382
  type: object
6303
6383
  description: Envelope for a session's approval exemptions (; key `exemptions`, NOT a bare array).
6304
6384
  required: [exemptions]
6305
- # 封闭:铸造点 `routes/approvals-assistant.ts:138` 是单键字面量。
6385
+ # 封闭:铸造点 `routes/approvals-assistant.ts` 是单键字面量。
6306
6386
  additionalProperties: false
6307
6387
  properties:
6308
6388
  exemptions:
@@ -6318,7 +6398,7 @@ components:
6318
6398
  · the RESUMED-TASK shape (`http/server.ts` driveResumeIntoRunLog return) — `{taskId?, sessionId, status,
6319
6399
  errorCode?, errorMessage?, retriable?}`, plus `rememberApplied` when the request carried
6320
6400
  `remember:"session"` AND the worker has an exemption store;
6321
- · the PARKED-BACKGROUND-AGENT shape (`parked-decide.ts:272`, server >=1.267) — `{taskId, status:"resuming",
6401
+ · the PARKED-BACKGROUND-AGENT shape (`parked-decide.ts`, server >=1.267) — `{taskId, status:"resuming",
6322
6402
  decision}`, an ACCEPTED (not completed) receipt; the revive drives asynchronously.
6323
6403
  `status` is the only key BOTH shapes guarantee: `taskId` rides `getActiveTaskId` on the resumed leg (omitted
6324
6404
  when there is no active row) and `sessionId` is absent from the parked shape.
@@ -6360,7 +6440,7 @@ components:
6360
6440
  MEMORY only — deny/neverAuto always outrank it; it never loosens the session-policy layer.
6361
6441
  required: [toolName, grantedBy, createdAt]
6362
6442
  # 封闭:三个 store 实现(sql/pg/local)的 `list()` 都逐字铸这三键
6363
- # (`plugins/approval-exemption-store.ts:67`),行不是原样透传的 DB 行。
6443
+ # (`plugins/approval-exemption-store.ts`),行不是原样透传的 DB 行。
6364
6444
  additionalProperties: false
6365
6445
  properties:
6366
6446
  toolName: { type: string, description: Canonical tool name the exemption covers. }
@@ -6375,7 +6455,7 @@ components:
6375
6455
  The `GET /v1/approvals` operator queue row (true shape). NEVER includes a capability token.
6376
6456
  ⚠️ `createdAt`/`deadline` are EPOCH MILLISECONDS (numbers), not ISO strings.
6377
6457
  🔴 真矛盾撞获+纠正(2026-07-25,core [1640] 审计撞获 + server 复核确认 src/http/routes/approvals-assistant.ts→
6378
- listPending() 真实实现 `src/plugins/tidb-checkpoint-store.ts:54-79`):这份 schema 此前声称
6458
+ listPending() 真实实现 `src/plugins/tidb-checkpoint-store.ts`):这份 schema 此前声称
6379
6459
  "MIRRORS core CheckpointSummary,顶层 gateKind/severity,NEVER a riskDescriptor object"——
6380
6460
  与真实实现完全相反。真实的 `PendingCheckpoint`(service 自定义类型,不是 core 的
6381
6461
  `CheckpointSummary`)**没有 token 字段**(store 层注释:"deliberately NO token")、**没有
@@ -6504,7 +6584,7 @@ components:
6504
6584
  type: object
6505
6585
  description: 'ASSISTANT-WIRE-CONTRACT §2 — `GET /v1/assistant/inbox` envelope. Already severity-sorted by core.'
6506
6586
  required: [inbox]
6507
- # 封闭:铸造点 `routes/approvals-assistant.ts:210` 是单键字面量(行本身 InboxRow 已封闭)。
6587
+ # 封闭:铸造点 `routes/approvals-assistant.ts` 是单键字面量(行本身 InboxRow 已封闭)。
6508
6588
  additionalProperties: false
6509
6589
  properties:
6510
6590
  inbox:
@@ -6608,7 +6688,7 @@ components:
6608
6688
  ASSISTANT-WIRE-CONTRACT §3 — `GET /v1/assistant/tasks` envelope. Triage-ordered by core (needs-attention
6609
6689
  first, then severity DESC, then spend DESC). Empty `{tasks:[]}` when no TiDB run store.
6610
6690
  required: [tasks]
6611
- # 封闭:两个铸造点(`routes/approvals-assistant.ts:224` 的无 run-store 空臂 + `:273`)都是单键字面量。
6691
+ # 封闭:两个铸造点(`routes/approvals-assistant.ts` 的无 run-store 空臂 + 正常臂)都是单键字面量。
6612
6692
  additionalProperties: false
6613
6693
  properties:
6614
6694
  tasks:
@@ -6699,12 +6779,12 @@ components:
6699
6779
  SessionSummary:
6700
6780
  type: object
6701
6781
  description: >
6702
- E16 (shell-host sessionPicker; service SessionSummary tidb-run-store.ts:42) — a `GET /v1/sessions` list row
6782
+ E16 (shell-host sessionPicker; service SessionSummary tidb-run-store.ts) — a `GET /v1/sessions` list row
6703
6783
  aggregating a session's run ledger (last activity + most-recent objective preview + status). owner-scoped.
6704
6784
  All timestamps ISO. `objectivePreview` is service-redacted + truncated (genuinely `null` when no run).
6705
6785
  required: [sessionId, owner, lastActivityAt, firstActivityAt, runCount, objectivePreview, lastStatus]
6706
- # 🔴 封闭(census 批2 第二段,2026-07-30)。铸造点=`security.ts:22 SessionListItem`,9 键逐字对上;
6707
- # 路由 `routes/sessions-list.ts:148` 把 store 行**原样**放进 `sessions[]`(零投影),所以「多键」
6786
+ # 🔴 封闭(census 批2 第二段,2026-07-30)。铸造点=`security.ts SessionListItem`,9 键逐字对上;
6787
+ # 路由 `routes/sessions-list.ts` 把 store 行**原样**放进 `sessions[]`(零投影),所以「多键」
6708
6788
  # 只能来自 store 实现自己加字段 —— 那正是要抓的漂。`lastRunId`/`title` 刻意**不进 required**:
6709
6789
  # 两者由 store 侧 SQL 投影(legacy `task_run` 聚合腿的 lister 不一定发),required 会把一条
6710
6790
  # 合法的退化面响应判红。
@@ -6778,7 +6858,7 @@ components:
6778
6858
  type: object
6779
6859
  description: Keyset page envelope for `GET /v1/sessions`. `nextCursor` absent = last page.
6780
6860
  required: [sessions]
6781
- # 封闭:铸造点 `routes/sessions-list.ts:148` 是逐字两键字面量(`nextCursor` 条件在场)。
6861
+ # 封闭:铸造点 `routes/sessions-list.ts` 是逐字两键字面量(`nextCursor` 条件在场)。
6782
6862
  additionalProperties: false
6783
6863
  properties:
6784
6864
  sessions:
@@ -6789,7 +6869,7 @@ components:
6789
6869
  SessionPermissionRules:
6790
6870
  type: object
6791
6871
  description: >
6792
- E6 (shell-host permissionMode; core SessionPermissionRules session-policy-store.ts:23) — operator-tightened
6872
+ E6 (shell-host permissionMode; core SessionPermissionRules session-policy-store.ts) — operator-tightened
6793
6873
  per-session tool-permission rules (core reads them at prepare-time, SUBTRACT-only). 5 optional `string[]`
6794
6874
  fields. tighten-only invariant is core-enforced (a loosen needs an operator).
6795
6875
  properties:
@@ -6813,7 +6893,7 @@ components:
6813
6893
  # SessionPermissionRules + 内联的 `rev`)声明的键不在它作用域内 ⇒ 只写 `false` 会把合法的
6814
6894
  # `toolAllow`/`rev` 判成违约。镜像的一致性由 spec.test.ts 的通用「封闭 allOf 臂必须镜像被引
6815
6895
  # schema 全部键」门看守(以后往 SessionPermissionRules 加键,忘了同步镜像就当场红)。
6816
- # 为什么值得封闭:PUT 腿 `routes/sessions.ts:551` 只挑这 5 个已知字段落店,GET 腿回的是 store
6896
+ # 为什么值得封闭:PUT 腿 `routes/sessions.ts` 只挑这 5 个已知字段落店,GET 腿回的是 store
6817
6897
  # 原样 —— 「store 多回一个键」正是本门要抓的那类漂(消费方按 spec 生成的类型会漏掉它)。
6818
6898
  additionalProperties: false
6819
6899
  properties:
@@ -6827,10 +6907,10 @@ components:
6827
6907
  McpServerStatus:
6828
6908
  type: object
6829
6909
  description: >
6830
- E9 (shell-host mcpStatus; core McpServerStatus core/mcp.ts:76) — one MCP server's status at materialization
6910
+ E9 (shell-host mcpStatus; core McpServerStatus core/mcp.ts) — one MCP server's status at materialization
6831
6911
  time. `status` is connected | failed (core never emits disabled). `error` (failed only) is service-redacted
6832
6912
  + length-bounded.
6833
- # 封闭三层(census 批2 第三段,2026-07-30):行铸造点 `routes/sessions.ts:157-165` 逐键条件展开这 5 键;
6913
+ # 封闭三层(census 批2 第三段,2026-07-30):行铸造点 `routes/sessions.ts` 逐键条件展开这 5 键;
6834
6914
  # `serverInfo` 不是透传——core 自己投影成两键字面量(core dist mcp.js:986 `{name, version}`)。
6835
6915
  required: [name, status]
6836
6916
  additionalProperties: false
@@ -6852,8 +6932,7 @@ components:
6852
6932
  description: >
6853
6933
  `GET /v1/sessions/:id/mcp` envelope. `asOf` = THIS materialization moment (ISO). `degraded:true` (servers
6854
6934
  empty) ⇒ materialize timed out/failed (NOT "no MCP"). No MCP configured ⇒ `servers:[]` without `degraded`.
6855
- # 封闭(census 批2 第三段,2026-07-30):三个铸造臂(`routes/sessions.ts:134` 空面板 / `:152` 超时
6856
- # degraded / `:155-166` 正常)键集并集恰是这 3 键;degraded 只在超时臂铸。
6935
+ # 封闭(census 批2 第三段,2026-07-30):三个铸造臂(`routes/sessions.ts` 的空面板 / 超时 degraded / 正常)键集并集恰是这 3 键;degraded 只在超时臂铸。
6857
6936
  required: [asOf, servers]
6858
6937
  additionalProperties: false
6859
6938
  properties:
@@ -6882,8 +6961,8 @@ components:
6882
6961
  description: >
6883
6962
  One E19 file snapshot keyed by a `SessionTreeEntry.id`. `manifest` = `[relPath, blobHash]` tuples; the blob
6884
6963
  BYTES are NOT inlined (they ride the content-addressed blob routes).
6885
- # 封闭(census 批2 第三段,2026-07-30):server 侧类型+铸造点(`session-sync.ts:206` PULL 出 /
6886
- # `routes/session-sync.ts:53` PUSH 校验形)都恰是这两键。
6964
+ # 封闭(census 批2 第三段,2026-07-30):server 侧类型+铸造点(`session-sync.ts` PULL 出 /
6965
+ # `routes/session-sync.ts` PUSH 校验形)都恰是这两键。
6887
6966
  additionalProperties: false
6888
6967
  required: [key, manifest]
6889
6968
  properties:
@@ -6900,7 +6979,7 @@ components:
6900
6979
  SyncAnchor:
6901
6980
  type: object
6902
6981
  description: One E18 resume-at anchor. `owner` is RE-KEYED to the importing principal on a PUSH (§9).
6903
- # 封闭(census 批2 第三段,2026-07-30):server 侧行类型 `session-sync.ts:52` 恰是这三键(owner 可 null 不可缺)。
6982
+ # 封闭(census 批2 第三段,2026-07-30):server 侧行类型 `session-sync.ts` 恰是这三键(owner 可 null 不可缺)。
6904
6983
  additionalProperties: false
6905
6984
  required: [eventId, entryId, owner]
6906
6985
  properties:
@@ -6911,7 +6990,7 @@ components:
6911
6990
  SessionRulesRecord:
6912
6991
  type: object
6913
6992
  description: A (principal, rules) policy record — one row across ALL principals (E6 `listBySession`). Replayed verbatim on import (the cloud applies the tighten-only gate).
6914
- # 封闭(census 批2 第三段,2026-07-30):core `SessionRulesRecord`(session-policy-store.d.ts:19)恰是
6993
+ # 封闭(census 批2 第三段,2026-07-30):core `SessionRulesRecord`(session-policy-store.d.ts)恰是
6915
6994
  # 这两键;`principal: undefined`(会话默认规则)在 JSON 里=整键省略。
6916
6995
  additionalProperties: false
6917
6996
  required: [rules]
@@ -6924,7 +7003,7 @@ components:
6924
7003
  description: >
6925
7004
  2c session-sync `GET …/sync/manifest` payload (wrapped server-side as `{ manifest }`). The THIN cross-backend
6926
7005
  snapshot: entry IDS (oldest-first, NOT payloads) + per-snapshot relPath→blobHash + policy + anchors + leaf.
6927
- # 封闭(census 批2 第三段,2026-07-30):铸造点 `session-sync.ts:226`(exportSessionManifest 尾部
7006
+ # 封闭(census 批2 第三段,2026-07-30):铸造点 `session-sync.ts`(exportSessionManifest 尾部
6928
7007
  # 字面量)恰是这 7 键,全部无条件(leafId 缺席位=null 不省略)。
6929
7008
  additionalProperties: false
6930
7009
  required: [sessionId, entryIds, entryCount, leafId, snapshots, policy, anchors]
@@ -6956,7 +7035,7 @@ components:
6956
7035
  §7 — how a SOURCE log relates to a DESTINATION log, decided over the entry-ID SETS. A discriminated union on
6957
7036
  `relation`. `fresh`/`identical` carry nothing else; `fast_forward` the appended tail; `stale` the dst entries
6958
7037
  the src lacks; `fork` the common ancestor + each side's exclusive ids.
6959
- # 五臂全封闭(census 批2 第三段,2026-07-30):铸造点=分类器 `session-sync-kernel.ts:126-160`,
7038
+ # 五臂全封闭(census 批2 第三段,2026-07-30):铸造点=分类器 `session-sync-kernel.ts`,
6960
7039
  # 五个 return 全是字面量,臂形与 core 导出的 `SyncRelation` 判别联合逐键一致。
6961
7040
  oneOf:
6962
7041
  - type: object
@@ -7001,8 +7080,8 @@ components:
7001
7080
  description: >
7002
7081
  `POST …/sync/import` Phase-A result. `identical` → no `stagingId` (skip Phase B); `fresh`/`fast_forward` →
7003
7082
  `{ stagingId, relation }`. `relation` is the BARE classifier tag (not the full object).
7004
- # 封闭(census 批2 第三段,2026-07-30):两个铸造臂——identical 臂 `routes/session-sync.ts:385-389`
7005
- # (relation+basis+payloadVerified,无 stagingId)/ staged 臂 `:472`(stagingId+relation)。
7083
+ # 封闭(census 批2 第三段,2026-07-30):两个铸造臂——identical 臂 `routes/session-sync.ts`
7084
+ # (relation+basis+payloadVerified,无 stagingId)/ 同文件的 staged 臂(stagingId+relation)。
7006
7085
  additionalProperties: false
7007
7086
  required: [relation]
7008
7087
  properties:
@@ -7013,7 +7092,7 @@ components:
7013
7092
  type: string
7014
7093
  # 🔴 census 批2 第三段(2026-07-30)spec 谎修正:`entry-ids+digest` 不是"将来值"——1.277.0 落
7015
7094
  # digest 半场的**同一车**里铸造点就是 `basis: payloadVerified ? "entry-ids+digest" : "entry-ids"`
7016
- # (routes/session-sync.ts:387),旧注却把它写成 x-open-enum 的假想例。补进闭集;x-open-enum 保留
7095
+ # (routes/session-sync.ts),旧注却把它写成 x-open-enum 的假想例。补进闭集;x-open-enum 保留
7017
7096
  # (真正的将来值仍按未知值降级)。
7018
7097
  enum: [entry-ids, entry-ids+digest]
7019
7098
  x-open-enum: true
@@ -7035,7 +7114,7 @@ components:
7035
7114
  ImportCommitted:
7036
7115
  type: object
7037
7116
  description: '`POST …/sync/import/:stagingId/entries` (Phase B) commit result — the AUTHORITATIVE in-txn re-classify tag.'
7038
- # 封闭(census 批2 第三段,2026-07-30):铸造点 `routes/session-sync.ts:608`(`{ relation: committed.relation }`)单键字面量。
7117
+ # 封闭(census 批2 第三段,2026-07-30):铸造点 `routes/session-sync.ts`(`{ relation: committed.relation }`)单键字面量。
7039
7118
  additionalProperties: false
7040
7119
  required: [relation]
7041
7120
  properties:
@@ -7074,10 +7153,13 @@ components:
7074
7153
  `quota_exhausted` (429 — the E4 fleet quota-LEASE budget, distinct from the cost quota).
7075
7154
  NOTE: budget (`budget.precall`/`budget.exceeded`) and `cancelled` are RESULT-level codes on
7076
7155
  TaskResult/RunRecord, NOT HTTP errors (pinned wire contract).
7077
- 🔴 STABLE PREFIX FAMILIES (wire-contract Appendix A, criterion 3) — nine prefixes are a stable
7156
+ 🔴 STABLE PREFIX FAMILIES (wire-contract Appendix A, criterion 3) — ELEVEN prefixes are a stable
7078
7157
  COARSE branch surface, so falling back on the prefix (unknown code → look at its prefix → only then
7079
- at the status) is always safe: `auth.` `request.` `not_found.` `conflict.` `limit.` `capability.`
7080
- `feature.` `internal.` `state.`. The SDK maps each family to a typed class, so a code this SDK has
7158
+ at the status) is always safe: `auth.` `request.` `not_found.` `conflict.` `gone.` `parking.`
7159
+ `limit.` `capability.` `feature.` `internal.` `state.`. (`gone.` = 410, "the channel closed, switch
7160
+ channels"; `parking.` = 425, "intermediate state — you retry to learn the landing, not to re-submit".
7161
+ Both families, and both status codes, first reached the wire with server 7.3.0's in-stream approval
7162
+ decision endpoint.) The SDK maps each family to a typed class, so a code this SDK has
7081
7163
  never seen still degrades INFORMATIVELY (e.g. a future `capability.xyz_required` already lands on
7082
7164
  CapabilityUnavailableError). Family examples not named elsewhere in this document:
7083
7165
  `request.payload_too_large` (413 — a field or the whole body is over the server cap; shorten and
@@ -7093,11 +7175,18 @@ components:
7093
7175
  description: On a 409 — the task currently holding the session lock (see the Conflict response note).
7094
7176
 
7095
7177
  # ── Streaming event taxonomy (the UX 命脉). Each SSE `data:` line is one JSON-serialized AgentEvent. ──
7096
- # TWO vocabularies (the pinned wire contract Drift 3):
7178
+ # TWO vocabularies (the pinned wire contract Drift 3). ⚠️ The two lines below are the SHAPE-DEFINING
7179
+ # contrast (per-token deltas vs turn-aggregated), NOT an enumeration of either leg's arm set — the union
7180
+ # under `AgentEvent` below is the authoritative list, and each `Event_*` schema states its own legs.
7097
7181
  # live (POST /v1/tasks/stream): text_delta / reasoning_delta (delta = string fragment, core-native)
7098
- # + tool_start/tool_end/turn_end/compacted/done
7182
+ # + the lifecycle/tool/observation arms + the out-of-band NAMED frames
7183
+ # (question / elicitation / tool_approval / approval_request), which
7184
+ # interleave on the same connection but are NOT AgentEvent arms
7099
7185
  # durable (GET /v1/runs/:id/events): text / reasoning (turn-AGGREGATED, no deltas)
7100
- # + tool_start/tool_end/turn_end/compacted/suspended/done/failed
7186
+ # + the same lifecycle/tool arms + the durable-only appends
7187
+ # (prompt_assembled / config_assembled / model_usage / suggestions /
7188
+ # needs_review / task_notification / diagnostics / workflow_complete)
7189
+ # + suspended/failed
7101
7190
  AgentEvent:
7102
7191
  oneOf:
7103
7192
  - $ref: '#/components/schemas/Event_meta'
@@ -7143,6 +7232,16 @@ components:
7143
7232
  # [2854] core 5.14.0 TaskEvent 16->18 (server 7.3.0 pickup): both new arms, projected.
7144
7233
  - $ref: '#/components/schemas/Event_human_input'
7145
7234
  - $ref: '#/components/schemas/Event_wiring_manifest'
7235
+ # 🔴 #185a (2026-08-08): the three APPROVAL arms of the DURABLE leg. They were replayed by
7236
+ # GET /v1/runs/:id/events all along (the server appends them and the read boundary re-emits
7237
+ # `{type, ...data}`) but neither this union nor the SDK's declared them, so a generated client
7238
+ # had no arm to switch on and dropped approval cards. Each arm is `allOf: [<the frame schema>, the
7239
+ # narrowed discriminant]` — the key set stays owned by ToolApprovalFrame / ApprovalRequestFrame,
7240
+ # so a frame gaining a key (7.5.0's governanceForced) needs no edit here.
7241
+ # `approval_revoke` is deliberately ABSENT: it is live-only and never appended.
7242
+ - $ref: '#/components/schemas/Event_tool_approval'
7243
+ - $ref: '#/components/schemas/Event_tool_approval_complete'
7244
+ - $ref: '#/components/schemas/Event_approval_request'
7146
7245
  discriminator:
7147
7246
  propertyName: type
7148
7247
  mapping:
@@ -7181,15 +7280,18 @@ components:
7181
7280
  workflow_complete: '#/components/schemas/Event_workflow_complete'
7182
7281
  human_input: '#/components/schemas/Event_human_input'
7183
7282
  wiring_manifest: '#/components/schemas/Event_wiring_manifest'
7283
+ tool_approval: '#/components/schemas/Event_tool_approval'
7284
+ tool_approval_complete: '#/components/schemas/Event_tool_approval_complete'
7285
+ approval_request: '#/components/schemas/Event_approval_request'
7184
7286
 
7185
7287
  # design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
7186
7288
  # the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
7187
- # `text` re-stamps the turn's first text_delta eventId, runs.ts:250); parentToolCallId = sub-agent attribution.
7289
+ # `text` re-stamps the turn's first text_delta eventId, runs.ts); parentToolCallId = sub-agent attribution.
7188
7290
  EventIdentity:
7189
7291
  type: object
7190
7292
  # 🔴 census 轴一(2026-07-30)亲读铸造点补的两键:LIVE 腿的白名单化 catch-all
7191
- # (server `routes/tasks.ts:640-648`,text_delta / turn_end / message_committed / context_usage 四臂)
7192
- # 传的是**四个** identity 键,而 durable 腿的 `identityFields`(trace/project.ts:101)只传前两个
7293
+ # (server `routes/tasks.ts`,text_delta / turn_end / message_committed / context_usage 四臂)
7294
+ # 传的是**四个** identity 键,而 durable 腿的 `identityFields`(trace/project.ts)只传前两个
7193
7295
  # ——「两腿两策」是那段代码自己写下的口径。此前 spec 只声明两键 ⇒ 在封闭这些臂时会把真键判成违约。
7194
7296
  # 声明面取两腿并集(逐臂再收窄不值当),缺席即缺席。
7195
7297
  properties:
@@ -7205,7 +7307,7 @@ components:
7205
7307
  condition: durable-ledger leg only (no ledger => neither header nor frame). sessionId rides along
7206
7308
  (needed to bind subsequent turns when the server minted a fresh session). Always precedes any content
7207
7309
  frame. NOT an engine event — server-minted, no EventIdentity.
7208
- # 封闭:铸造点 routes/tasks.ts:122 是逐字写死的三键字面量(sessionId 条件在场)。
7310
+ # 封闭:铸造点 routes/tasks.ts 是逐字写死的三键字面量(sessionId 条件在场)。
7209
7311
  additionalProperties: false
7210
7312
  required: [type, taskId]
7211
7313
  properties:
@@ -7220,7 +7322,7 @@ components:
7220
7322
  # `additionalProperties` 只认**同一个 subschema** 的 `properties`,`allOf` 分支($ref EventIdentity)
7221
7323
  # 声明的键不在它的作用域内 —— 只写 `additionalProperties: false` 会把合法的 eventId 判成违约
7222
7324
  # (draft-07 的 ajv 也不支持能跨 allOf 结算的 `unevaluatedProperties`,写了会被静默忽略 = 假绿)。
7223
- # 镜像取两腿并集(超集,宁可漏抓不可假红);`$ref` 保留(spec.test.ts:92 的 §E2 门要它)。
7325
+ # 镜像取两腿并集(超集,宁可漏抓不可假红);`$ref` 保留(spec.test.ts 的 §E2 门要它)。
7224
7326
  # 镜像与 EventIdentity 的一致性由 spec.test.ts 的「封闭臂必须镜像全部 identity 键」门看守。
7225
7327
  additionalProperties: false
7226
7328
  required: [type, text]
@@ -7279,7 +7381,7 @@ components:
7279
7381
  type: { const: tool_start }
7280
7382
  toolCallId: { type: string }
7281
7383
  toolName: { type: string }
7282
- # 🔴 census 轴一(2026-07-30)回填:server 白名单 builder `toolStartEventData`(trace/project.ts:112)
7384
+ # 🔴 census 轴一(2026-07-30)回填:server 白名单 builder `toolStartEventData`(trace/project.ts)
7283
7385
  # 真发 `label`(core 展示名,与 tool_end 同待遇),spec 此前漏声明 —— 封闭前必须先补,否则真帧变假红。
7284
7386
  label: { type: string, description: 'core 展示名(缺席 ⇒ 用 toolName 渲染)。' }
7285
7387
  args: {}
@@ -7301,7 +7403,7 @@ components:
7301
7403
  type: { const: tool_end }
7302
7404
  toolCallId: { type: string }
7303
7405
  toolName: { type: string }
7304
- # 🔴 census 轴一(2026-07-30)回填:`toolEndEventData`(trace/project.ts:125)真发 `label` 与
7406
+ # 🔴 census 轴一(2026-07-30)回填:`toolEndEventData`(trace/project.ts)真发 `label` 与
7305
7407
  # `structured`,spec 此前两者都漏 —— SDK 的 events.ts 早已声明,漂移只在 spec 这一侧。
7306
7408
  label: { type: string, description: 'core 展示名(同 tool_start.label)。' }
7307
7409
  structured: {} # core 1.203 CC 卡片明细;与 output 同一 UNTRUSTED RAW 待遇(已 redactDeep),形状开放
@@ -7315,7 +7417,7 @@ components:
7315
7417
  bgAgentId: { type: string }
7316
7418
  Event_turn_end:
7317
7419
  type: object
7318
- # 🔴 census 轴一(2026-07-30):`turn_end` 走 LIVE 腿的白名单 catch-all(routes/tasks.ts:646),该分支
7420
+ # 🔴 census 轴一(2026-07-30):`turn_end` 走 LIVE 腿的白名单 catch-all(routes/tasks.ts),该分支
7319
7421
  # 对四个 identity 键做条件 spread —— 即「core 若真在 turn_end 上盖了 identity,它会上 wire」。
7320
7422
  # 处置:**不** allOf EventIdentity(契约面继续不承诺生命周期臂带 identity,spec.test.ts §E2 那条钉
7321
7423
  # 保持有效),但把四键作为本地可选属性声明出来,好让下面的封闭对那条真实代码路径不产生假红。
@@ -7344,7 +7446,7 @@ components:
7344
7446
  cacheReadTokens: { type: integer }
7345
7447
  cacheWriteTokens: { type: integer }
7346
7448
  costMicroUsd: { type: integer }
7347
- # 🔴 census 轴一(2026-07-30)回填:`turnEndEventData`(trace/project.ts:449)是 2026-07-25 才补的
7449
+ # 🔴 census 轴一(2026-07-30)回填:`turnEndEventData`(trace/project.ts)是 2026-07-25 才补的
7348
7450
  # builder,它带上来的这两键 spec 从未声明过 —— 而它们恰恰是**诚实缺席**语义的载体:
7349
7451
  # `usageMissing` 缺了,「没有 usage」会被读成「用量是 0」而不是「未知」。
7350
7452
  usageMissing: { const: true, description: '本轮用量不可信/缺失的诚实标记。缺席 ⇒ usage 可信。' }
@@ -7356,14 +7458,14 @@ components:
7356
7458
  Event_compacted:
7357
7459
  type: object
7358
7460
  # 🔴 **刻意不封闭**(SDK events.ts 的 compacted 臂带 `[k: string]: unknown`),但 census 轴一
7359
- # (2026-07-30)把 `compactedEventData`(trace/project.ts:340-407)真发的键**全部登记**了一遍 ——
7461
+ # (2026-07-30)把 `compactedEventData`(trace/project.ts)真发的键**全部登记**了一遍 ——
7360
7462
  # 此前 spec 只有 tokensBefore 一键,连 fixture 天天在喂的 `trigger` 都不在声明面上。
7361
7463
  additionalProperties: true
7362
7464
  required: [type]
7363
7465
  properties:
7364
7466
  type: { const: compacted }
7365
7467
  tokensBefore: { type: integer }
7366
- trigger: { type: string, description: 'auto(逼近窗口上限)| manual(/compact)。缺席按 auto 渲染。' }
7468
+ trigger: { type: string, description: '开集(engine-shaped,别当闭合枚举):`auto`(逼近窗口上限)/ `manual`(`POST /v1/runs/:id/compact`,发在该 run 自己的流上)/ `forced`(core ≥5.16.0 的 prompt-too-long 恢复腿)。缺席按 auto 渲染;未知值也按 auto 渲染,别崩。' }
7367
7469
  preserved_segment:
7368
7470
  type: object
7369
7471
  required: [firstKeptEntryId]
@@ -7398,7 +7500,7 @@ components:
7398
7500
  Event_suggestions:
7399
7501
  type: object
7400
7502
  description: >
7401
- E12 (shell-host; service runs.ts:392 + src/http/routes/runs.ts, opt-in via `TaskRequest.suggestNextPrompts`) —
7503
+ E12 (shell-host; service runs.ts + src/http/routes/runs.ts, opt-in via `TaskRequest.suggestNextPrompts`) —
7402
7504
  post-completion "what to ask next" prompt suggestions. Appended to the DURABLE run-events tail AFTER a
7403
7505
  `done` with `status:"completed"` (core's fire-and-forget LLM pass). 🔴 UNTRUSTED model text, service-
7404
7506
  redacted, UI-ONLY — render as clickable next-prompt chips; NEVER re-feed to a model. Empty/absent ⇒ none.
@@ -7444,7 +7546,7 @@ components:
7444
7546
  CLIENT wire」已是 doc-rot,SDK events.ts 早已改口):server **确实**在三条腿上转发本臂
7445
7547
  (`taskProgressEventData`,bg runs.ts append / resume append / 同步 live SSE),此外才是 fleet 子行。
7446
7548
  allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
7447
- # 🔴 census 轴一(2026-07-30)三处纠正,依据=亲读 `taskProgressEventData`(trace/project.ts:146-166):
7549
+ # 🔴 census 轴一(2026-07-30)三处纠正,依据=亲读 `taskProgressEventData`(trace/project.ts):
7448
7550
  # ① required 过严 —— builder 里 `usage`/`status` 都是**条件 spread**,只有 `taskId` 无条件在场;
7449
7551
  # spec 却把它们列进 required ⇒ 一个没带 usage 的真 tick 会被判违约(方向反了的假红)。
7450
7552
  # ② `status` 写成 `const: running` 太窄 —— core [1414]#3 的 settle 终态 tick("completed"/"failed")
@@ -7523,75 +7625,92 @@ components:
7523
7625
  description: >
7524
7626
  server >=7.3.0 (core 5.14.0 design/173) — ASSEMBLY SELF-EVIDENCE: what this leg actually wired, emitted at
7525
7627
  prepare. Root / child / resume legs each emit their OWN manifest (a delegated child's rides the CHILD's
7526
- stream by design). SERVICE PROJECTION: the `governance` section core stamps `audience:"operator"` is
7527
- STRIPPED by the server before this frame reaches a tenant stream (core contract: "no projection => do not
7528
- disclose") — it is deliberately absent from this schema. `configFingerprint` is ALSO stripped for the same
7529
- reason: core hashes the whole manifest (governance included) unsalted, and governance is four booleans —
7530
- sixteen combinations a tenant could brute-force against everything else it already receives, recovering the
7531
- section that was removed. Both come back on the operator-scoped projection, not here.
7628
+ stream by design).
7629
+ 🔴 FLAT: every section sits at the TOP LEVEL of the frame — there is NO `manifest` wrapper. (Declared
7630
+ nested through 6.7.0; the wire never had it. The server projects this arm through the same shared
7631
+ whitelist builder as every other arm and SPREADS the result into the frame, on all three legs — live SSE,
7632
+ durable ledger row, resume replay. Corrected 2026-08-07 against a real capture.)
7633
+ SERVICE PROJECTION, TWO FACES (server >=7.4.0, #154): the `governance` section core stamps
7634
+ `audience:"operator"`, and `configFingerprint`, are STRIPPED on a tenant stream (core contract: "no
7635
+ projection => do not disclose") and PRESENT on an operator-scoped one. The fingerprint travels with the
7636
+ section because core hashes the whole manifest (governance included) unsalted, and governance is four
7637
+ booleans — sixteen combinations a tenant could brute-force against everything else it already receives,
7638
+ recovering the section that was removed. The face is chosen per CONNECTION from the caller identity (an
7639
+ explicitly listed OPERATOR_PRINCIPALS member; an EMPTY list means NOBODY). Only the LIVE stream forks —
7640
+ the durable ledger always stores the stripped form, so the deployment-level truth is read at
7641
+ GET /v1/diagnostics/wiring.
7532
7642
  allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
7533
7643
  additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
7534
- required: [type, manifest]
7644
+ required: [type]
7535
7645
  properties:
7536
7646
  type: { const: wiring_manifest }
7537
- manifest:
7647
+ schemaVersion: { type: integer }
7648
+ leg:
7538
7649
  type: object
7539
7650
  additionalProperties: false
7651
+ properties: { kind: { type: string, description: 'root | child | resume. Open on read.' } }
7652
+ ask:
7653
+ type: object
7654
+ additionalProperties: false
7655
+ description: 'Approval seam: how it is wired, where it came from, and what it effectively resolves to.'
7540
7656
  properties:
7541
- schemaVersion: { type: integer }
7542
- leg:
7543
- type: object
7544
- additionalProperties: false
7545
- properties: { kind: { type: string, description: 'root | child | resume. Open on read.' } }
7546
- ask:
7547
- type: object
7548
- additionalProperties: false
7549
- description: 'Approval seam: how it is wired, where it came from, and what it effectively resolves to.'
7550
- properties:
7551
- form: { type: string }
7552
- provenance: { type: string, description: 'spec | deps. Open on read.' }
7553
- effective: { type: string }
7554
- question:
7555
- type: object
7556
- additionalProperties: false
7557
- description: >
7558
- Question channel — three-valued (wired / absent / stripped_bg_lane, the last meaning the engine
7559
- stripped a bg child per-request face). `interactiveToolsWithoutDeliveryFace` is the composition-lie flag.
7560
- properties:
7561
- wired: { type: string }
7562
- provenance: { type: string }
7563
- interactiveToolsWithoutDeliveryFace: { type: boolean }
7564
- interaction:
7565
- type: object
7566
- additionalProperties: false
7567
- properties: { posture: { type: string, description: 'interactive | headless | absent. Open on read.' } }
7568
- elicit:
7569
- type: object
7570
- additionalProperties: false
7571
- properties:
7572
- seamWired: { type: boolean }
7573
- serversOptedIn: { type: integer }
7574
- parkLane:
7575
- type: object
7576
- additionalProperties: false
7577
- description: >
7578
- Durable park lane: capability vs EFFECTIVE policy. `effective` is THREE-VALUED — `"unresolved"`
7579
- means the engine has not decided yet; never fold it to false.
7580
- properties:
7581
- capable: { type: boolean }
7582
- effective: { oneOf: [{ type: boolean }, { type: string }] }
7583
- reasons: { type: array, items: { type: string } }
7584
- checkpointDurability: { type: string }
7585
- session:
7586
- type: object
7587
- additionalProperties: false
7588
- properties: { store: { type: string } }
7589
- fleet:
7590
- type: object
7591
- additionalProperties: false
7592
- properties:
7593
- backgroundAgentStore: { type: boolean }
7594
- hostChildEventSink: { type: boolean }
7657
+ form: { type: string }
7658
+ provenance: { type: string, description: 'spec | deps. Open on read.' }
7659
+ effective: { type: string }
7660
+ question:
7661
+ type: object
7662
+ additionalProperties: false
7663
+ description: >
7664
+ Question channel — three-valued (wired / absent / stripped_bg_lane, the last meaning the engine
7665
+ stripped a bg child per-request face). `interactiveToolsWithoutDeliveryFace` is the composition-lie flag.
7666
+ properties:
7667
+ wired: { type: string }
7668
+ provenance: { type: string }
7669
+ interactiveToolsWithoutDeliveryFace: { type: boolean }
7670
+ interaction:
7671
+ type: object
7672
+ additionalProperties: false
7673
+ properties: { posture: { type: string, description: 'interactive | headless | absent. Open on read.' } }
7674
+ elicit:
7675
+ type: object
7676
+ additionalProperties: false
7677
+ properties:
7678
+ seamWired: { type: boolean }
7679
+ serversOptedIn: { type: integer }
7680
+ parkLane:
7681
+ type: object
7682
+ additionalProperties: false
7683
+ description: >
7684
+ Durable park lane: capability vs EFFECTIVE policy. `effective` is THREE-VALUED — `"unresolved"`
7685
+ means the engine has not decided yet; never fold it to false.
7686
+ properties:
7687
+ capable: { type: boolean }
7688
+ effective: { oneOf: [{ type: boolean }, { type: string }] }
7689
+ reasons: { type: array, items: { type: string } }
7690
+ checkpointDurability: { type: string }
7691
+ session:
7692
+ type: object
7693
+ additionalProperties: false
7694
+ properties: { store: { type: string } }
7695
+ fleet:
7696
+ type: object
7697
+ additionalProperties: false
7698
+ properties:
7699
+ backgroundAgentStore: { type: boolean }
7700
+ hostChildEventSink: { type: boolean }
7701
+ governance:
7702
+ type: object
7703
+ additionalProperties: false
7704
+ description: 'OPERATOR face only — presence of the four deployment governance faces. Absent on every tenant stream.'
7705
+ properties:
7706
+ audience: { type: string, enum: [operator] }
7707
+ lockedConfig: { type: boolean }
7708
+ compliance: { type: boolean }
7709
+ memoryAdmission: { type: boolean }
7710
+ retention: { type: boolean }
7711
+ configFingerprint:
7712
+ type: string
7713
+ description: 'OPERATOR face only — unsalted sha256 prefix (16 hex) over this leg whole manifest.'
7595
7714
  eventId: { type: string }
7596
7715
  parentToolCallId: { type: string }
7597
7716
  sourceTaskId: { type: string }
@@ -7669,8 +7788,8 @@ components:
7669
7788
  Event_suspended:
7670
7789
  type: object
7671
7790
  description: HITL — the run paused on an F4 gate or AskUserQuestion. Resolve via approvals.decide, then resume.
7672
- # 封闭(census 轴一,2026-07-30)。两个铸造点都是写死的字面量:server.ts:2147 `{gate}`、
7673
- # server.ts:2161 `{gate:null, reopened}` —— 后者的 `reopened` 此前 spec 完全没有,是本次亲读补上的。
7791
+ # 封闭(census 轴一,2026-07-30)。两个铸造点都是写死的字面量:server.ts `{gate}`、
7792
+ # server.ts `{gate:null, reopened}` —— 后者的 `reopened` 此前 spec 完全没有,是本次亲读补上的。
7674
7793
  additionalProperties: false
7675
7794
  required: [type]
7676
7795
  properties:
@@ -7683,7 +7802,7 @@ components:
7683
7802
  reopened:
7684
7803
  type: ['string', 'null']
7685
7804
  description: >
7686
- server.ts:2161 —— 一条 resume 腿把 run 重新挂回 suspended 时,带上「为什么重开」的 errorCode
7805
+ server.ts —— 一条 resume 腿把 run 重新挂回 suspended 时,带上「为什么重开」的 errorCode
7687
7806
  (无则显式 null)。只出现在这条重开臂上;普通挂起帧不带此键。
7688
7807
  ModelUsageDelta:
7689
7808
  # 新增(2026-07-25 回填批)。SDK 的 facade 审计把这个形状命名了一次(此前是在两个使用点各写一遍的匿名内联形:
@@ -7825,7 +7944,7 @@ components:
7825
7944
  description: >
7826
7945
  Per-model usage delta (live-verified on canary). `usage` is keyed BY MODEL NAME — same source as
7827
7946
  `done.result.stats.modelUsage` (lets a multi-model / role-routed run attribute spend per model).
7828
- # 封闭:铸造点 trace/project.ts:27 `append("model_usage", { usage: delta })` 只有这一键
7947
+ # 封闭:铸造点 trace/project.ts `append("model_usage", { usage: delta })` 只有这一键
7829
7948
  #(map 的**值**仍是开集 ModelUsageDelta —— 那层的「开」是真的,不动)。
7830
7949
  additionalProperties: false
7831
7950
  required: [type, usage]
@@ -7925,9 +8044,16 @@ components:
7925
8044
  ToolApprovalFrame:
7926
8045
  type: object
7927
8046
  description: >
7928
- LIVE tool-approval frame (SSE, `type` IS the event name). NOT an AgentEvent arm — it interleaves on the
7929
- run's stream when TOOL_APPROVAL_ENABLED. The shell renders `tool_approval` as the three-choice card and
7930
- dismisses it on `tool_approval_complete`. Respond via POST /v1/tool-approvals/{approvalId}/respond.
8047
+ Tool-approval frame (SSE, `type` IS the event name). On the LIVE stream it interleaves out-of-band when
8048
+ TOOL_APPROVAL_ENABLED and is NOT an AgentEvent arm there (the live leg dispatches by SSE `event:` name).
8049
+ On the BACKGROUND/durable leg the server appends it to the events tail so it also replays through
8050
+ GET /v1/runs/:id/events — and there it IS a declared arm as of #185a, via `Event_tool_approval` /
8051
+ `Event_tool_approval_complete` (each = THIS schema plus the narrowed discriminant, so the keys live
8052
+ here only). Before #185a the durable response was typed solely as `AgentEvent` with no such arm: the
8053
+ payload carried `type` (the read boundary emits `{type, ...data}`) so runtime discrimination worked,
8054
+ but a generated client had nothing to switch on and dropped approval cards. That gap is closed.
8055
+ The shell renders `tool_approval` as the three-choice card and dismisses it on `tool_approval_complete`.
8056
+ Respond via POST /v1/tool-approvals/{approvalId}/respond.
7931
8057
  required: [type, approvalId]
7932
8058
  additionalProperties: true
7933
8059
  properties:
@@ -7949,6 +8075,25 @@ components:
7949
8075
  args:
7950
8076
  description: '"tool_approval" only: the call''s args, secret-redacted. UNTRUSTED for display. Absent (with argsOmitted) when over the byte cap or unserializable.'
7951
8077
  argsOmitted: { type: boolean, enum: [true] }
8078
+ governanceForced:
8079
+ type: boolean
8080
+ enum: [true]
8081
+ description: >
8082
+ "tool_approval" only, ADDITIVE, two-stage presence: ABSENT on server <= 7.4.0; from server >= 7.5.0
8083
+ ([2942]/[2943]) present with value `true` when this ask's gate comes from the OPERATOR GOVERNANCE
8084
+ layer (the deployment-side AUTONOMY / commandPolicy / MANUAL_MODE_SHELL_GATE / SENSITIVE_WRITE_PATTERNS
8085
+ knobs) rather than from a model default gate or the request's own client permission posture.
8086
+ The only authority on presence is an observed frame, never a version string.
8087
+
8088
+ Use it to render a "forced by governance" badge: such a gate CANNOT be removed by a client posture,
8089
+ so the shell must present it as non-bypassable instead of pointing the user at `permissionMode`.
8090
+
8091
+ 🔴 ABSENT != false (same discipline as the risk axes): the key is present ONLY when true. Absence
8092
+ means "no evidence of a governance origin" — it covers both genuinely non-governance asks (e.g. a tool
8093
+ listed in the deployment's APPROVAL_REQUIRE) AND shapes the server cannot discriminate (e.g. when the
8094
+ governance shell gate sits at the "classify" tier, a shell ask may come from the classifier or from
8095
+ another gate; the server leaves the key absent rather than guessing). Never render absence as
8096
+ "this gate can be bypassed by a posture".
7952
8097
  fromSubagent:
7953
8098
  type: boolean
7954
8099
  enum: [true]
@@ -7957,20 +8102,36 @@ components:
7957
8102
  type: string
7958
8103
  description: 'Child''s core session id (same id domain as task_progress.taskId) — row-join value + fallback discriminator against a server predating fromSubagent.'
7959
8104
  sourceAgentName: { type: string, description: 'Display name of the child agent, redacted. UNTRUSTED.' }
8105
+ delegation:
8106
+ type: object
8107
+ description: >
8108
+ core >= 5.9.0 W1 ([2535]), "tool_approval" only, ADDITIVE: the ask's DELEGATION provenance chain
8109
+ (the innermost grandchild frame wins). Present on the same door as `fromSubagent`. Read-only display
8110
+ enrichment; `agentName` is UNTRUSTED for display like `sourceAgentName`.
8111
+ additionalProperties: true
8112
+ required: [parentToolCallId, depth]
8113
+ properties:
8114
+ parentToolCallId: { type: string }
8115
+ depth: { type: integer }
8116
+ agentName: { type: string }
7960
8117
  outcome:
7961
8118
  type: string
7962
8119
  enum: [allowed, denied, expired]
7963
- description: '"tool_approval_complete" only. `expired` = TTL/abort/disconnect — a fail-closed DENY the shell should render as such, not as "still pending".'
8120
+ description: '"tool_approval_complete" only. Cosmetic dismiss reason. `expired` = TTL/abort/disconnect; what the ENGINE received for it DEPENDS on the deployment: where the design/172 ask ledger is on (server >= 7.3.0 under STREAM_APPROVAL_ENABLED) the server''s expireAsk CAS re-adjudicates the outcome to "unavailable" and the run PARKS; without that ledger the historical fail-closed DENY stands. Never derive the run''s fate from this frame.'
7964
8121
 
7965
- # ── design/172 流内审批协议(#151;UNRELEASED,候 server 7.3.0 + STREAM_APPROVAL_ENABLED,默认 OFF)──
8122
+ # ── design/172 流内审批协议(#151)—— 已随 server 7.3.0 上线;开关 STREAM_APPROVAL_ENABLED 的默认值
8123
+ # 分两段:server ≤7.4.0 默认 OFF,≥7.5.0 默认 ON(BREAKING,见 server CHANGELOG)。──
7966
8124
  # 与上面的 ToolApprovalFrame 是**并行加帧,不替换**:开关打开时同一只 ask 出两帧(先 tool_approval,
7967
8125
  # 后 approval_request),两帧同带 `approvalId` ⇒ 消费端按它去重、优先渲染新帧。
7968
8126
  ApprovalRequestFrame:
7969
8127
  type: object
7970
8128
  description: >
7971
- IN-STREAM approval card (SSE, `type` IS the event name; design/172 §3.1). NOT an AgentEvent arm — it
7972
- interleaves on the live token stream like tool_approval/question/elicitation. Settle it via
7973
- POST /v1/tasks/{taskId}/asks/{askId}/decision.
8129
+ IN-STREAM approval card (SSE, `type` IS the event name; design/172 §3.1). TWO LEGS: on the LIVE token
8130
+ stream it is an out-of-band named frame (NOT an AgentEvent arm) interleaved like
8131
+ tool_approval/question/elicitation; on the DURABLE leg the server appends it, so the same frame replays
8132
+ through GET /v1/runs/:id/events — and there it IS registered, as the `Event_approval_request` arm
8133
+ (#185a; that arm allOf-refs THIS schema, so the keys are never restated).
8134
+ Settle it via POST /v1/tasks/{taskId}/asks/{askId}/decision.
7974
8135
 
7975
8136
  WINDOW: `expiresAtMs` is cast ONCE and never recomputed; `expiresInMs` = max(0, expiresAtMs - serverNowMs)
7976
8137
  and is therefore MONOTONICALLY DECREASING across replays — a reconnect NEVER renews the window.
@@ -8043,6 +8204,15 @@ components:
8043
8204
  argsOmitted: { type: boolean, enum: [true], description: 'Present (true) only when args exceeded the byte cap. Never false.' }
8044
8205
  toolCallId: { type: string, description: 'The engine''s tool-call id (the `call_…` on the assistant message''s tool_use block) — same domain as ToolApprovalFrame.toolCallId.' }
8045
8206
  risk: { $ref: '#/components/schemas/ApprovalRiskAxes' }
8207
+ governanceForced:
8208
+ type: boolean
8209
+ enum: [true]
8210
+ description: >
8211
+ ADDITIVE, two-stage presence (ABSENT on server <= 7.4.0; present with `true` from server >= 7.5.0):
8212
+ this ask's gate comes from the OPERATOR GOVERNANCE layer and cannot be removed by a client permission
8213
+ posture. Semantics, the ABSENT != false clause and the discrimination boundary are stated verbatim on
8214
+ ToolApprovalFrame.governanceForced. It sits at the card's top level rather than inside `risk` because
8215
+ `risk` describes the ENGINE's judgement of the call, while this key says WHO imposed the gate.
8046
8216
  fromSubagent: { type: boolean, enum: [true], description: 'Present ⇔ the ask comes from a DELEGATED child — the explicit discriminator (a worker cannot forge it).' }
8047
8217
  sourceTaskId: { type: string, description: 'The child''s core session id — row-join value + fallback discriminator.' }
8048
8218
  sourceAgentName: { type: string, description: 'The child agent''s display name, redacted. UNTRUSTED.' }
@@ -8272,8 +8442,14 @@ components:
8272
8442
  QuestionFrame:
8273
8443
  type: object
8274
8444
  description: >
8275
- LIVE AskUserQuestion frame (SSE, `type` IS the event name). NOT an AgentEvent arm. Respond via
8276
- POST /v1/questions/{questionId}/respond; unanswered asks fall back to the headless default.
8445
+ AskUserQuestion frame (SSE, `type` IS the event name). TWO LEGS: on the LIVE stream it is an out-of-band
8446
+ named frame (NOT an AgentEvent arm); on the DURABLE leg the server persists it, so the same frame replays
8447
+ through GET /v1/runs/:id/events — and there it IS registered, as the closed `Event_question` /
8448
+ `Event_question_complete` arms (keep the key sets mirrored; a gate enforces it).
8449
+ Respond via POST /v1/questions/{questionId}/respond. 🔴 An UNANSWERED ask does NOT "fall back to the headless
8450
+ default" (server 7.4.0 / #166): the deployment reports `unavailable` ("nobody was reachable") and CORE
8451
+ picks the landing — a DURABLE leg PARKS a checkpoint for an operator to answer later, a non-durable leg
8452
+ continues on the `declined_unavailable` synthetic-continuation card. The run never hangs either way.
8277
8453
  required: [type, questionId]
8278
8454
  additionalProperties: true
8279
8455
  properties:
@@ -8282,11 +8458,14 @@ components:
8282
8458
  questions:
8283
8459
  type: array
8284
8460
  items: { $ref: '#/components/schemas/AskQuestion' }
8285
- description: '"question" only: the model''s structured questions, secret-redacted. UNTRUSTED — never re-feed to a model. Absent on an over-cap payload (the ask then headless-defaults and the shell never renders it).'
8461
+ description: '"question" only: the model''s structured questions, secret-redacted. UNTRUSTED — never re-feed to a model. Absent on an over-cap payload — and then NO frame is emitted at all: the ask reports `unavailable` (see this schema''s description).'
8286
8462
  outcome:
8287
8463
  type: string
8288
8464
  enum: [answered, unanswered]
8289
- description: '"question_complete" only: `answered` = a human responded; `unanswered` = ttl/abort/throttle released it (the model got the headless default). Cosmetic dismiss reason.'
8465
+ description: '"question_complete" only: `answered` = a human responded; `unanswered` = ttl/abort/throttle released it and core was told NOBODY WAS REACHABLE (`unavailable`) — on a durable deployment that PARKS rather than handing the model a default. Cosmetic dismiss reason only; never derive the run''s fate from it.'
8466
+ serverNowMs:
8467
+ type: integer
8468
+ description: 'server >= 7.3.0, ADDITIVE: the server-signed emit timestamp. A server-minted side-frame has no core-stream eventId to carry, so this is its own time coordinate. Absent on older servers.'
8290
8469
 
8291
8470
  AskQuestion:
8292
8471
  type: object
@@ -8313,8 +8492,11 @@ components:
8313
8492
  ElicitationFrame:
8314
8493
  type: object
8315
8494
  description: >
8316
- LIVE MCP elicitation frame (SSE, `type` IS the event name). NOT an AgentEvent arm. Respond via
8317
- POST /v1/elicitations/{elicitationId}/respond.
8495
+ MCP elicitation frame (SSE, `type` IS the event name). TWO LEGS: on the LIVE stream it is an
8496
+ out-of-band named frame (NOT an AgentEvent arm); on the DURABLE leg the server persists it, so the same
8497
+ frame replays through GET /v1/runs/:id/events — and there it IS registered, as the closed
8498
+ `Event_elicitation` / `Event_elicitation_complete` arms (keep the key sets mirrored; a gate enforces it).
8499
+ Respond via POST /v1/elicitations/{elicitationId}/respond.
8318
8500
  required: [type, elicitationId, mcpServerName]
8319
8501
  additionalProperties: true
8320
8502
  properties:
@@ -8331,6 +8513,9 @@ components:
8331
8513
  type: string
8332
8514
  enum: [accept, decline, cancel]
8333
8515
  description: '"elicitation_complete" only: how it resolved (dialog dismiss reason).'
8516
+ serverNowMs:
8517
+ type: integer
8518
+ description: 'server >= 7.3.0, ADDITIVE: the server-signed emit timestamp. A server-minted side-frame carries no core-stream `eventId`, so this is its own time coordinate. Absent on older servers.'
8334
8519
 
8335
8520
  Event_steering_injected:
8336
8521
  type: object
@@ -8355,7 +8540,7 @@ components:
8355
8540
  2026-08-02) — do NOT assume the full TaskResult:
8356
8541
  · `TaskResult` — the normal terminal (output string at `.result`, stats at `.stats`), service Drift 4;
8357
8542
  · `ActiveRunConflictDoneResult` — the SSE lane's REJECTION terminal: when the session already has an
8358
- active run, the submit's 409 is encoded as this done frame (`src/http/routes/tasks.ts:234`), and it
8543
+ active run, the submit's 409 is encoded as this done frame (`src/http/routes/tasks.ts`), and it
8359
8544
  carries NO `taskId` / `sessionId` / `stats`.
8360
8545
  Narrow on the presence of `stats` (required on TaskResult, absent from the conflict shape), or on
8361
8546
  `errorCode === "conflict.session_active_run"`. `status` does NOT discriminate — both shapes carry it.
@@ -8370,17 +8555,17 @@ components:
8370
8555
  replay:
8371
8556
  type: boolean
8372
8557
  description: >
8373
- 契约调和车1 F(2026-08-02;server `src/http/routes/tasks.ts:71` 与 `:919` 两处铸造点)— `true`
8558
+ 契约调和车1 F(2026-08-02;server `src/http/routes/tasks.ts` 的两处铸造点)— `true`
8374
8559
  marks the IDEMPOTENT-REPLAY leg: this terminal was NOT produced by a live run of your submit. Either
8375
- the `Idempotency-Key` hit the in-flight/settled cache (:71), or your call was deduplicated into a
8376
- concurrent identical submit and received no live events at all (:919). The `result` is the original
8560
+ the `Idempotency-Key` hit the in-flight/settled cache, or your call was deduplicated into a
8561
+ concurrent identical submit and received no live events at all. The `result` is the original
8377
8562
  run's terminal, verbatim. ABSENT on every live leg (never `false`) — a UI can use it to say "replayed"
8378
8563
  instead of implying the work just ran twice.
8379
8564
 
8380
8565
  ActiveRunConflictDoneResult:
8381
8566
  type: object
8382
8567
  description: >
8383
- The SSE lane's REJECTION `done.result` (server `src/http/active-run-conflict.ts:64` `toDoneFrameResult`,
8568
+ The SSE lane's REJECTION `done.result` (server `src/http/active-run-conflict.ts` `toDoneFrameResult`,
8384
8569
  server >= 3.21). Same material as the 409 `conflict.session_active_run` body, minus the token-free
8385
8570
  addressing rule's exceptions: a checkpoint token NEVER appears on the wire. It is NOT a TaskResult —
8386
8571
  there is no run to report on, so `taskId`/`sessionId`/`stats` are structurally absent.
@@ -8427,11 +8612,11 @@ components:
8427
8612
 
8428
8613
  # ══════════════════════════════════════════════════════════════════════════════════════════════
8429
8614
  # 🔴 census 轴一(2026-07-30)补的**四个未登记臂**。它们不是「将来会有」——**今天就在 wire 上**:
8430
- # · `context_usage` LIVE(routes/tasks.ts:653)+ DURABLE(trace/ledger-sink.ts:144 / server.ts:2115)
8431
- # · `config_assembled` DURABLE(trace/project.ts:43)
8432
- # · `needs_review` DURABLE(http/server.ts:2148)
8433
- # · `message_committed` LIVE(routes/tasks.ts:650)
8434
- # durable 腿的读边界(routes/runs.ts:99 `formatEvent`)对**每一条**落库事件原样下发 `{type, ...data}`
8615
+ # · `context_usage` LIVE(routes/tasks.ts)+ DURABLE(trace/ledger-sink.ts / server.ts)
8616
+ # · `config_assembled` DURABLE(trace/project.ts)
8617
+ # · `needs_review` DURABLE(http/server.ts)
8618
+ # · `message_committed` LIVE(routes/tasks.ts)
8619
+ # durable 腿的读边界(routes/runs.ts `formatEvent`)对**每一条**落库事件原样下发 `{type, ...data}`
8435
8620
  # (唯一归一是 brain_status→status),所以「本仓 append 了什么」= 「wire 上会出现什么」。而本文件的
8436
8621
  # `AgentEvent` 是闭集 oneOf ⇒ 一个真实的 context_usage 帧对着 spec 校验**当场零臂命中**。
8437
8622
  # ⚠️ 元教训:`spec-union-arm-gate` 比的是 spec ↔ SDK 两侧的臂集,这四个臂**两侧同缺**,所以那道门
@@ -8440,7 +8625,7 @@ components:
8440
8625
  Event_context_usage:
8441
8626
  type: object
8442
8627
  description: >
8443
- core 1.414 first-class arm, whitelisted by `contextUsageEventData` (server trace/project.ts:431).
8628
+ core 1.414 first-class arm, whitelisted by `contextUsageEventData` (server trace/project.ts).
8444
8629
  🔴 Two engine-stated conventions, pass them through, never re-derive: (a) `usedTokens > compactAtTokens`
8445
8630
  IS the predicate the engine itself feeds `shouldCompact`; (b) `windowTokens` is the AUTOCOMPACT window,
8446
8631
  NOT the model's context size (rendering it as "model context" gives the user a wrong denominator).
@@ -8454,7 +8639,7 @@ components:
8454
8639
  usedTokens: { type: integer }
8455
8640
  windowTokens: { type: integer, description: 'autocompact 窗,不是模型上下文大小。' }
8456
8641
  compactAtTokens: { type: integer }
8457
- # LIVE 腿(routes/tasks.ts:653)在这条臂上挂 identity;durable 腿不挂。同 turn_end 的处置:
8642
+ # LIVE 腿(routes/tasks.ts)在这条臂上挂 identity;durable 腿不挂。同 turn_end 的处置:
8458
8643
  # 本地可选声明,不 allOf(不把 identity 承诺进契约面)。
8459
8644
  eventId: { type: string }
8460
8645
  parentToolCallId: { type: string }
@@ -8464,7 +8649,7 @@ components:
8464
8649
  type: object
8465
8650
  description: >
8466
8651
  Compaction result frame ([2373]A-2, server 5.0.0 three-leg parity batch: live SSE + durable bg append +
8467
- durable resume append all share one builder, `compactionOutcomeEventData`, server trace/project.ts:375 —
8652
+ durable resume append all share one builder, `compactionOutcomeEventData`, server trace/project.ts —
8468
8653
  replays no longer drop it). `outcome`/`trigger` are engine-shaped passthrough strings coerced via
8469
8654
  String(); `reason` is optional, secret-redacted. Identity: TWO keys only (`eventId`,
8470
8655
  `parentToolCallId`) — this arm is NOT on the live-leg four-key whitelist that `context_usage` sits on
@@ -8483,7 +8668,7 @@ components:
8483
8668
  Event_config_assembled:
8484
8669
  type: object
8485
8670
  description: >
8486
- DURABLE only ([1301]③, server trace/project.ts:43) — the per-turn companion record of `prompt_assembled`:
8671
+ DURABLE only ([1301]③, server trace/project.ts) — the per-turn companion record of `prompt_assembled`:
8487
8672
  which config catalog version produced this turn's effective fields, and why anything was overridden.
8488
8673
  Bounded frame: the builder DROPS the whole record over 32 KiB rather than shipping a truncated half-shape.
8489
8674
  `fields` / `overrideReasons` are engine/registry-shaped passthrough — treat as opaque display data.
@@ -8498,7 +8683,7 @@ components:
8498
8683
  Event_needs_review:
8499
8684
  type: object
8500
8685
  description: >
8501
- DURABLE only (design/80 D-B, server http/server.ts:2148) — a RESUMED plan_review leg got re-gated into
8686
+ DURABLE only (design/80 D-B, server http/server.ts) — a RESUMED plan_review leg got re-gated into
8502
8687
  another review pause. Same payload shape as `suspended`: the gate verbatim, or an explicit `null`.
8503
8688
  NOTE `needs_review` is BOTH a `RunStatus` terminal and a `CheckpointGate.kind`; this arm is the third
8504
8689
  use of the name — the stream frame announcing that pause.
@@ -8514,7 +8699,7 @@ components:
8514
8699
  Event_message_committed:
8515
8700
  type: object
8516
8701
  description: >
8517
- LIVE only (server routes/tasks.ts:650, whitelisted in the same catch-all as text_delta/turn_end/
8702
+ LIVE only (server routes/tasks.ts, whitelisted in the same catch-all as text_delta/turn_end/
8518
8703
  context_usage) — a session-history entry was committed. `entryId` is the durable entry handle the
8519
8704
  R8 rewind anchor is keyed on ("rewind to the prompt" targets a `role:"user"` entry).
8520
8705
  additionalProperties: false
@@ -8537,8 +8722,8 @@ components:
8537
8722
  Event_error:
8538
8723
  type: object
8539
8724
  description: >
8540
- STREAM-CONTROL frame, NOT a run failure (server src/http/sse-log.ts:86; the trace leg emits the same
8541
- shape at routes/trace-usage.ts:294). The server writes it as a NAMED frame (`event: error`) whose payload
8725
+ STREAM-CONTROL frame, NOT a run failure (server src/http/sse-log.ts; the trace leg emits the same
8726
+ shape at routes/trace-usage.ts). The server writes it as a NAMED frame (`event: error`) whose payload
8542
8727
  echoes `type`, so both an `event:`-dispatching client and a payload-dispatching one see it.
8543
8728
  🔴 Its arrival means the RUN IS STILL ALIVE — the 15-minute stream cap fired. The correct handling is to
8544
8729
  RECONNECT with `Last-Event-ID` and keep reading (the durable leg stamps `id:` on every frame); rendering
@@ -8554,12 +8739,15 @@ components:
8554
8739
  Event_question:
8555
8740
  type: object
8556
8741
  description: >
8557
- AskUserQuestion OPEN frame (server src/question.ts:38 `QuestionFrame`). The live leg dispatches it by SSE
8558
- event name (routes/tasks.ts:677, the same out-of-band channel as `elicitation`/`tool_approval`); the
8559
- DURABLE leg persists it via `append(type, rest)` (src/runs.ts:498), so the same frame also replays on
8742
+ AskUserQuestion OPEN frame (server src/question.ts `QuestionFrame`). The live leg dispatches it by SSE
8743
+ event name (routes/tasks.ts, the same out-of-band channel as `elicitation`/`tool_approval`); the
8744
+ DURABLE leg persists it via `append(type, rest)` (src/runs.ts), so the same frame also replays on
8560
8745
  GET /v1/runs/:id/events — that half is what this schema registers.
8561
8746
  🔴 UNTRUSTED-for-display: `questions` is secret-redacted, render only, NEVER re-feed to a model.
8562
- `questions` absent ⇒ the payload exceeded the 16 KiB cap and was dropped (the ask headless-defaults).
8747
+ `questions` absent ⇒ the payload exceeded the 16 KiB cap and was dropped — and then NO frame is emitted
8748
+ at all: the ask reports `unavailable` (server 7.4.0 / #166), which on a DURABLE deployment PARKS a
8749
+ checkpoint for an operator and on a non-durable one continues via the `declined_unavailable`
8750
+ synthetic-continuation card. It does NOT "headless-default".
8563
8751
  additionalProperties: false
8564
8752
  required: [type, questionId]
8565
8753
  properties:
@@ -8568,22 +8756,38 @@ components:
8568
8756
  questions:
8569
8757
  type: array
8570
8758
  items: { $ref: '#/components/schemas/AskQuestion' }
8759
+ serverNowMs:
8760
+ type: integer
8761
+ description: >
8762
+ server >= 7.3.0, ADDITIVE: the server-signed emit timestamp. A server-minted side-frame carries no
8763
+ core-stream `eventId`, so this is its own time coordinate. 🔴 It MUST be declared here: this schema
8764
+ is `additionalProperties: false`, so omitting a key the server really sends makes a strict validator
8765
+ reject a legitimate frame. Absent on servers < 7.3.0.
8571
8766
  Event_question_complete:
8572
8767
  type: object
8573
8768
  description: >
8574
8769
  AskUserQuestion CLOSE frame (dialog-dismiss signal). `answered` = a human responded; `unanswered` =
8575
- ttl / abort / throttle released it and the model got the headless default. Cosmetic — it does not change
8576
- the run's status.
8770
+ ttl / abort / throttle released it and core was told NOBODY WAS REACHABLE (`unavailable`) — on a durable
8771
+ deployment that PARKS rather than handing the model a default (server 7.4.0 / #166; the old "the model
8772
+ got the headless default" reading is wrong). Cosmetic dismiss reason — it does not itself change the
8773
+ run's status, and the run's fate must never be derived from it.
8577
8774
  additionalProperties: false
8578
8775
  required: [type, questionId]
8579
8776
  properties:
8580
8777
  type: { const: question_complete }
8581
8778
  questionId: { type: string }
8582
8779
  outcome: { type: string, enum: [answered, unanswered] }
8780
+ serverNowMs:
8781
+ type: integer
8782
+ description: >
8783
+ server >= 7.3.0, ADDITIVE: the server-signed emit timestamp. A server-minted side-frame carries no
8784
+ core-stream `eventId`, so this is its own time coordinate. 🔴 It MUST be declared here: this schema
8785
+ is `additionalProperties: false`, so omitting a key the server really sends makes a strict validator
8786
+ reject a legitimate frame. Absent on servers < 7.3.0.
8583
8787
  Event_elicitation:
8584
8788
  type: object
8585
8789
  description: >
8586
- Inbound-MCP elicitation OPEN frame (server src/elicitation.ts:42 `ElicitationFrame`). Same two legs as
8790
+ Inbound-MCP elicitation OPEN frame (server src/elicitation.ts `ElicitationFrame`). Same two legs as
8587
8791
  `question`: live dispatches by event name, the durable leg persists + replays it.
8588
8792
  🔴 UNTRUSTED: `message` is fenced + secret-redacted; `requestedSchema` is an OPAQUE passthrough (never
8589
8793
  interpreted/validated) and must still be fenced on display. v1 is always `mode: "form"` (url-mode is
@@ -8597,6 +8801,13 @@ components:
8597
8801
  message: { type: string }
8598
8802
  requestedSchema: {}
8599
8803
  mode: { type: string, enum: [form] }
8804
+ serverNowMs:
8805
+ type: integer
8806
+ description: >
8807
+ server >= 7.3.0, ADDITIVE: the server-signed emit timestamp. A server-minted side-frame carries no
8808
+ core-stream `eventId`, so this is its own time coordinate. 🔴 It MUST be declared here: this schema
8809
+ is `additionalProperties: false`, so omitting a key the server really sends makes a strict validator
8810
+ reject a legitimate frame. Absent on servers < 7.3.0.
8600
8811
  Event_elicitation_complete:
8601
8812
  type: object
8602
8813
  description: 'Inbound-MCP elicitation CLOSE frame; `action` is how it settled (the dialog-dismiss reason).'
@@ -8607,11 +8818,18 @@ components:
8607
8818
  elicitationId: { type: string }
8608
8819
  mcpServerName: { type: string }
8609
8820
  action: { type: string, enum: [accept, decline, cancel] }
8821
+ serverNowMs:
8822
+ type: integer
8823
+ description: >
8824
+ server >= 7.3.0, ADDITIVE: the server-signed emit timestamp. A server-minted side-frame carries no
8825
+ core-stream `eventId`, so this is its own time coordinate. 🔴 It MUST be declared here: this schema
8826
+ is `additionalProperties: false`, so omitting a key the server really sends makes a strict validator
8827
+ reject a legitimate frame. Absent on servers < 7.3.0.
8610
8828
  Event_workflow_complete:
8611
8829
  type: object
8612
8830
  description: >
8613
8831
  Out-of-band delivery of an async workflow's completion (server
8614
- src/orchestration/workflow-completion-inbox.ts:522). Delivery is STREAM-OPEN driven: the session's inbox
8832
+ src/orchestration/workflow-completion-inbox.ts). Delivery is STREAM-OPEN driven: the session's inbox
8615
8833
  is drained on its NEXT leg — the sync leg writes a live frame, the bg/resume legs `append` it as a durable
8616
8834
  event the tailing client replays.
8617
8835
  🔴 AT-LEAST-ONCE (two-phase: the entry is acked only after a successful emit, and two legs racing the same
@@ -8625,6 +8843,67 @@ components:
8625
8843
  status: { type: string }
8626
8844
  summary: { type: string }
8627
8845
 
8846
+ # ── 🔴 #185a (2026-08-08): the DURABLE leg's three APPROVAL arms ────────────────────────────────
8847
+ # Shape rule for all three: `allOf: [<frame schema>, {the narrowed discriminant}]`. The keys are NOT
8848
+ # restated here — ToolApprovalFrame / ApprovalRequestFrame stay their single owner, so an additive
8849
+ # frame key (7.5.0's `governanceForced`) lands on the arm for free. The arms stay OPEN
8850
+ # (the frame schemas are `additionalProperties: true`), which is also why they need no local mirror
8851
+ # of the allOf branch keys (that duty only binds closed schemas).
8852
+ # Presence is CONDITIONAL and differs per family — an arm being declared says the payload's SHAPE,
8853
+ # never that the frame will arrive: `tool_approval*` needs TOOL_APPROVAL_ENABLED, `approval_request`
8854
+ # needs the design/172 protocol (server >= 7.3.0; default OFF <= 7.4.0 / ON >= 7.5.0, and the only
8855
+ # authority is the measured `capabilities.streamApproval` bit, never a version string).
8856
+ Event_tool_approval:
8857
+ description: >
8858
+ DURABLE-leg replay of the legacy tool-approval OPEN frame. Same payload as ToolApprovalFrame with the
8859
+ discriminant narrowed to `tool_approval`. Settle it via POST /v1/tool-approvals/{approvalId}/respond.
8860
+ Anchor the card on `toolCallId` (NOT `approvalId`); render `governanceForced` as a non-bypassable
8861
+ "forced by governance" badge. UNTRUSTED for display: `message`/`args` are redacted but model-authored.
8862
+ allOf:
8863
+ - $ref: '#/components/schemas/ToolApprovalFrame'
8864
+ - type: object
8865
+ required: [type]
8866
+ properties:
8867
+ type: { type: string, enum: [tool_approval] }
8868
+ Event_tool_approval_complete:
8869
+ description: >
8870
+ DURABLE-leg replay of the legacy tool-approval CLOSE frame (ToolApprovalFrame with the discriminant
8871
+ narrowed to `tool_approval_complete`). `outcome` is a COSMETIC dismiss reason: for `expired`, a
8872
+ deployment carrying the design/172 ask ledger re-adjudicates to "unavailable" and the run PARKS, while
8873
+ one without it keeps the historical fail-closed DENY. NEVER derive the run's fate from this frame.
8874
+ allOf:
8875
+ - $ref: '#/components/schemas/ToolApprovalFrame'
8876
+ - type: object
8877
+ required: [type]
8878
+ properties:
8879
+ type: { type: string, enum: [tool_approval_complete] }
8880
+ Event_approval_request:
8881
+ description: >
8882
+ The design/172 in-stream approval card as an AgentEvent arm. PARALLEL to `tool_approval`, not a
8883
+ replacement: with the protocol on, one ask emits BOTH frames (legacy first) carrying the SAME
8884
+ `approvalId` — dedupe on it and prefer this arm. The decision key is `askId`; settle via
8885
+ POST /v1/tasks/{taskId}/asks/{askId}/decision.
8886
+
8887
+ 🔴 THIS ARM IS THE OPEN ENVELOPE, NOT `ApprovalRequestFrame`, AND THAT IS DELIBERATE. The v1 frame
8888
+ schema pins `schemaVersion: 1` and `kind: permission`, but a stream carries whatever the server sends:
8889
+ binding the arm to v1 would (a) make a generated client REJECT a legitimate future-version frame at the
8890
+ `oneOf`, and (b) tell a TypeScript/codegen consumer that `card` and its risk axes are guaranteed present
8891
+ on a frame that never validated as v1 — the exact bypass of the "unknown shapes get a GENERIC card,
8892
+ NEVER auto-deny" rule stated on ApprovalRequestFrame and ApprovalFrameEnvelope.
8893
+ ⇒ Read this arm as the envelope, THEN check `schemaVersion == 1 && kind == "permission"` (SDK:
8894
+ `isApprovalRequestFrameV1`) before touching `card`/`askId`/the window keys — the validated v1 payload is
8895
+ described by `ApprovalRequestFrame`. If it does not validate, render the generic card.
8896
+
8897
+ 🔴 A frame replayed from the durable events tail is for TIMELINE RENDERING ONLY, never a card-set
8898
+ baseline: its `expiresInMs` is frozen at mint time, and the full reconciliation baseline is the
8899
+ open-stream preamble.
8900
+ allOf:
8901
+ - $ref: '#/components/schemas/ApprovalFrameEnvelope'
8902
+ - type: object
8903
+ required: [type]
8904
+ properties:
8905
+ type: { type: string, enum: [approval_request] }
8906
+
8628
8907
  # ── resources/*.ts schema backfill, batch 2 (2026-07-28) — the 28-item WIRE_PENDING ratchet zeroed.
8629
8908
  # Each schema below is cross-checked against the server source (file:line cited in its description
8630
8909
  # or in the SDK resource JSDoc it mirrors). Wire objects are OPEN sets (additionalProperties: true).
@@ -8655,7 +8934,7 @@ components:
8655
8934
  description: >
8656
8935
  The 200 body of POST /v1/approvals/{sessionId}/decide when the pending checkpoint belongs to a
8657
8936
  PARKED background agent (server 1.267, ASSISTANT-WIRE-CONTRACT §4a parked variant; server
8658
- parked-decide.ts:272 — exact literal shape). ACCEPTANCE semantics: the revive drives
8937
+ parked-decide.ts — exact literal shape). ACCEPTANCE semantics: the revive drives
8659
8938
  ASYNCHRONOUSLY — 200 means accepted, not completed; a failed drive honestly re-parks the row and
8660
8939
  the pending re-appears on the list. Task-level suspends keep the legacy resumed shape —
8661
8940
  discriminate by `status === "resuming"`.
@@ -8755,19 +9034,19 @@ components:
8755
9034
  detail: { type: string, description: 'Redacted supplement (e.g. why skipped). Additive / tolerate-absent.' }
8756
9035
 
8757
9036
  BakeStatus:
8758
- # 新(census 批2 四段)——server `BakeStatus`(plugins/store-contracts.ts:83), the COARSE lifecycle
9037
+ # 新(census 批2 四段)——server `BakeStatus`(plugins/store-contracts.ts), the COARSE lifecycle
8759
9038
  # bakeView/claimResponse/bakesCreate all key on.
8760
9039
  type: string
8761
9040
  enum: [queued, running, done, failed]
8762
9041
 
8763
9042
  BakeState:
8764
- # 新(census 批2 四段)——server `BakeState`(store-contracts.ts:85-93), build.sh's finer 8-value state;
9043
+ # 新(census 批2 四段)——server `BakeState`(store-contracts.ts), build.sh's finer 8-value state;
8765
9044
  # null before the runner's first `state` line lands.
8766
9045
  type: [string, 'null']
8767
9046
  enum: [PENDING, BUILDING, PUSHING, VERIFYING, REGISTERING, COMPLETE, FAILED, CANCELLED, null]
8768
9047
 
8769
9048
  BakeErrorCode:
8770
- # 新(census 批2 四段)——server `BakeErrorCode`(store-contracts.ts:96), the closed structured terminal
9049
+ # 新(census 批2 四段)——server `BakeErrorCode`(store-contracts.ts), the closed structured terminal
8771
9050
  # code set (§P2.14 #2); null = success or an uncategorized failure.
8772
9051
  type: [string, 'null']
8773
9052
  enum: [band-conflict, duplicate, disk, timeout, cancelled, null]
@@ -8803,7 +9082,7 @@ components:
8803
9082
 
8804
9083
  BakeRecordView:
8805
9084
  # 新(census 批2 四段)——GET /v1/images/bakes/{bakeId}'s `{ bake }` envelope. Exact key set = server
8806
- # `bakeView()` (images.ts:527-551): the durable BakeRecord MINUS the runner-internal secret/lease fields
9085
+ # `bakeView()` (images.ts): the durable BakeRecord MINUS the runner-internal secret/lease fields
8807
9086
  # (ingestSecret/runnerId/leaseUntil/cancelRequested/argv — an ops surface, not the claim credential).
8808
9087
  type: object
8809
9088
  description: The operator poll view of one bake (server bakeView projection over the durable BakeRecord).
@@ -8836,7 +9115,7 @@ components:
8836
9115
 
8837
9116
  BakeSubmitAck:
8838
9117
  # 新(census 批2 四段)——POST /v1/images/bakes 202 body. All three code paths (attach-to-in-flight,
8839
- # atomic-admission-created, dryRun) produce this SAME shape (images.ts:193/206/213/217).
9118
+ # atomic-admission-created, dryRun) produce this SAME shape (images.ts).
8840
9119
  type: object
8841
9120
  description: Acknowledgement of a submitted/attached-to bake.
8842
9121
  required: [bakeId, eventsUrl, status, state]
@@ -8848,7 +9127,7 @@ components:
8848
9127
  state: { $ref: '#/components/schemas/BakeState' }
8849
9128
 
8850
9129
  BakeCancelAck:
8851
- # 新(census 批2 四段)——POST /v1/images/bakes/{bakeId}/cancel 202 body (images.ts:239-241). `note` is
9130
+ # 新(census 批2 四段)——POST /v1/images/bakes/{bakeId}/cancel 202 body (images.ts). `note` is
8852
9131
  # ALWAYS present (a ternary VALUE, not a conditional key) — the spec previously implied it was optional.
8853
9132
  type: object
8854
9133
  description: Cooperative-cancel acknowledgement (a durable flag; the runner kills the build at its next heartbeat).
@@ -8861,7 +9140,7 @@ components:
8861
9140
 
8862
9141
  BakeIngestAck:
8863
9142
  # 新(census 批2 四段)——POST /v1/images/bakes/{bakeId}/ingest 200 body. 🔴 FOUR distinct shapes share
8864
- # this one status code (images.ts:302-334): a heartbeat ack carries no `seq`; a non-terminal frame ack
9143
+ # this one status code (images.ts): a heartbeat ack carries no `seq`; a non-terminal frame ack
8865
9144
  # carries `seq` but no `terminal`; a terminal `done` carries `terminal`+`indexId` (success) OR
8866
9145
  # `terminal`+`needsFlip` (the bounded-retry-exhausted non-throw path, still 200 — the runner re-POSTs the
8867
9146
  # idempotent done to re-drive the flip). `cancelRequested`+`leaseValid` ride on every non-heartbeat shape
@@ -8922,6 +9201,107 @@ components:
8922
9201
  passRate: { type: number }
8923
9202
  meanCostMicroUsd: { type: number }
8924
9203
 
9204
+ WiringManifest:
9205
+ type: object
9206
+ description: >
9207
+ core's assembly self-evidence manifest (core >=5.14.0 `WiringManifest`). TWO HALVES, ONE shape (a
9208
+ consumer must line the two faces up, so they are not split into separate types) — but which keys
9209
+ belong to which half is DETERMINATE, not "usually": `leg`, `ask.effective` and `configFingerprint`
9210
+ are EFFECTIVE-half only (the live `wiring_manifest` event) and the STATIC half
9211
+ (GET /v1/diagnostics/wiring) NEVER mints them; every other section, plus `governance`, is always
9212
+ present on the static half. Fields stay optional because core may add or drop sections and that
9213
+ must not hard-break a client — read by half, and never wait on a key the half never sends.
9214
+ 🔴 `governance` and `configFingerprint` appear ONLY on an operator-scoped face. A tenant stream
9215
+ gets neither — and NOT just the section: core hashes the WHOLE manifest unsalted and governance is
9216
+ four booleans, so the fingerprint alone would let a tenant brute-force sixteen combinations against
9217
+ everything else it already has and recover the section that was removed.
9218
+ additionalProperties: true
9219
+ properties:
9220
+ schemaVersion: { type: integer }
9221
+ leg:
9222
+ type: object
9223
+ description: 'Which leg emitted it (effective half only; the static half has no leg identity).'
9224
+ properties:
9225
+ kind: { type: string, enum: [root, child, resume] }
9226
+ ask:
9227
+ type: object
9228
+ description: 'Approval seam: wired form, which seat it came from, and (effective half) what it adjudicates to.'
9229
+ properties:
9230
+ form: { type: string, enum: [callback, allow, deny, absent] }
9231
+ provenance: { type: string, enum: [spec, deps] }
9232
+ effective: { type: string, enum: [human_reachable, auto_allow, auto_deny, park_only, unresolved] }
9233
+ question:
9234
+ type: object
9235
+ description: 'Question channel; `interactiveToolsWithoutDeliveryFace` is the composition-lie flag (interactive tools mounted with no delivery face).'
9236
+ properties:
9237
+ wired: { type: string, enum: [wired, absent, stripped_bg_lane] }
9238
+ provenance: { type: string, enum: [spec, deps] }
9239
+ interactiveToolsWithoutDeliveryFace: { type: boolean, enum: [true] }
9240
+ interaction:
9241
+ type: object
9242
+ properties:
9243
+ posture: { type: string, enum: [interactive, headless, absent] }
9244
+ elicit:
9245
+ type: object
9246
+ properties:
9247
+ seamWired: { type: boolean }
9248
+ serversOptedIn: { type: integer }
9249
+ parkLane:
9250
+ type: object
9251
+ description: 'Durable park lane. `effective` is THREE-VALUED — "unresolved" means the engine has not decided yet; never fold it to false.'
9252
+ properties:
9253
+ capable: { type: boolean }
9254
+ effective: { oneOf: [{ type: boolean }, { type: string, enum: [unresolved] }] }
9255
+ reasons: { type: array, items: { type: string } }
9256
+ checkpointDurability: { type: string, enum: [declared_durable, process_local] }
9257
+ session:
9258
+ type: object
9259
+ properties:
9260
+ store: { type: string, enum: [declared_durable, process_local] }
9261
+ fleet:
9262
+ type: object
9263
+ properties:
9264
+ backgroundAgentStore: { type: boolean }
9265
+ hostChildEventSink: { type: boolean }
9266
+ governance:
9267
+ type: object
9268
+ description: 'OPERATOR-AUDIENCE section (presence of the four deployment governance faces). Absent on every tenant-readable face.'
9269
+ properties:
9270
+ audience: { type: string, enum: [operator] }
9271
+ lockedConfig: { type: boolean }
9272
+ compliance: { type: boolean }
9273
+ memoryAdmission: { type: boolean }
9274
+ retention: { type: boolean }
9275
+ configFingerprint:
9276
+ type: string
9277
+ description: 'Unsalted sha256 prefix (16 hex) over the whole manifest. Effective half + operator face only.'
9278
+
9279
+ ServerWiringGates:
9280
+ type: object
9281
+ description: >
9282
+ The server's OWN assembly predicates (the half core's manifest does not cover).
9283
+ required: [streamApproval, durableApproval, checkpointStore]
9284
+ additionalProperties: false
9285
+ properties:
9286
+ streamApproval:
9287
+ type: string
9288
+ description: >
9289
+ design/172 in-stream approval protocol: "active", else the FIRST unsatisfied conjunct of the
9290
+ same five-way predicate that gates `capabilities.streamApproval` and the decision endpoint's
9291
+ 501 — so the diagnostics page and the consumer-facing behavior cannot disagree.
9292
+ enum: [active, no_tool_approval, protocol_disabled, no_backend, volatile_ask_ledger, no_park_facility]
9293
+ durableApproval: { type: boolean, description: 'DURABLE_APPROVAL is on.' }
9294
+ checkpointStore: { type: boolean, description: 'A checkpoint store is wired (= the park facility exists).' }
9295
+
9296
+ WiringDiagnostics:
9297
+ type: object
9298
+ description: 'GET /v1/diagnostics/wiring response (operator-only). No per-leg history by design.'
9299
+ required: [static, serverGates]
9300
+ additionalProperties: false
9301
+ properties:
9302
+ static: { $ref: '#/components/schemas/WiringManifest' }
9303
+ serverGates: { $ref: '#/components/schemas/ServerWiringGates' }
9304
+
8925
9305
  SendfileLinkRow:
8926
9306
  type: object
8927
9307
  description: >
@@ -8929,7 +9309,7 @@ components:
8929
9309
  nextBefore?}`; server src/http/routes/sessions-list.ts — exact projection). A principal caller is PINNED to its
8930
9310
  own scope (?scope= ignored); a fleet credential may pass ?scope= (default = the single-user
8931
9311
  sentinel "_"). Page with `before` = a previous page's `nextBefore` (uuidv7; malformed → 400).
8932
- # 🔴 census 批2 五段:CLOSED — exact projection (sessions-list.ts:94-105); taskId/sessionId are the
9312
+ # 🔴 census 批2 五段:CLOSED — exact projection (sessions-list.ts); taskId/sessionId are the
8933
9313
  # only OMIT-when-absent keys, no others.
8934
9314
  required: [id, filename, size, ttlSec, track, bucket, key, createdAt]
8935
9315
  additionalProperties: false
@@ -8950,7 +9330,7 @@ components:
8950
9330
  enum: [now, next, later]
8951
9331
  description: >-
8952
9332
  [2400] TR-4 — per-message queue priority on POST /v1/runs/{taskId}/steer. ACCEPTED + ENUM-VALIDATED
8953
- FAIL-LOUD (`routes/runs.ts:603`; anything else is 400 `request.field_invalid`) and echoed back on the
9333
+ FAIL-LOUD (`routes/runs.ts`; anything else is 400 `request.field_invalid`) and echoed back on the
8954
9334
  receipt, but ADVISORY today: core's steer is a single-slot last-writer-wins with no per-message priority
8955
9335
  or addressable drop, so a faithful priority queue (and DELETE /v1/runs/{taskId}/queue/{messageId}) waits
8956
9336
  on a core seam.
@@ -8960,16 +9340,16 @@ components:
8960
9340
  description: >
8961
9341
  [2400] TR-4 — the POST /v1/runs/{taskId}/steer receipt, DISCRIMINATED on `delivery` (three server mint
8962
9342
  points meaning three different things; branch on this, NOT on the status code):
8963
- `applied` (200, `routes/runs.ts:648`) — injected into the run LIVE on this replica, drains at the next
9343
+ `applied` (200, `routes/runs.ts`) — injected into the run LIVE on this replica, drains at the next
8964
9344
  turn boundary (`status:"running"`);
8965
- `queued` (202, `:641`) — the run is durably SUSPENDED, so the steer was PARKED on its pending checkpoint
9345
+ `queued` (202) — the run is durably SUSPENDED, so the steer was PARKED on its pending checkpoint
8966
9346
  and is injected on resume (`status:"suspended"`);
8967
- `parked_for_wake` (202, `:734`) — the run already ENDED; the server minted a `task_done` checkpoint and
9347
+ `parked_for_wake` (202) — the run already ENDED; the server minted a `task_done` checkpoint and
8968
9348
  parked the message on it, delivered ONLY by POST /v1/sessions/{sessionId}/wake — nothing happens until
8969
9349
  the session is woken.
8970
9350
  `messageId` is server-minted and stable across all three (dedup / correlation handle).
8971
9351
  required: [taskId, status, delivery, messageId]
8972
- # 封闭:三个铸造点是逐字面量拼出来的对象(routes/runs.ts:641/648/734),不是引擎透传形。
9352
+ # 封闭:三个铸造点是逐字面量拼出来的对象(routes/runs.ts),不是引擎透传形。
8973
9353
  additionalProperties: false
8974
9354
  properties:
8975
9355
  taskId: { type: string }
@@ -9003,8 +9383,7 @@ components:
9003
9383
  TaskOutput-tool text (the child's final assistant body once terminal) — UNTRUSTED model output,
9004
9384
  same posture as a run `result`. `output` = the engine registry's UnifiedTaskOutput projection
9005
9385
  passed through VERBATIM.
9006
- # 封闭(census 批2 第三段,2026-07-30):两个铸造点(`routes/runs.ts:934` 窄面 4 键 / `:1155` generic
9007
- # 面 4 键 + 条件 `...g14`)顶层键集就是这 5 个。`output` 子对象**保持开**——它是 registry 投影的
9386
+ # 封闭(census 批2 第三段,2026-07-30):两个铸造点(`routes/runs.ts` 的窄面 4 键 / generic 面 4 键 + 条件 `...g14`)顶层键集就是这 5 个。`output` 子对象**保持开**——它是 registry 投影的
9008
9387
  # verbatim 透传(deps 签名 `details: unknown`),不是 server 铸的形。
9009
9388
  required: [taskId, target, content, output]
9010
9389
  additionalProperties: false
@@ -9043,7 +9422,7 @@ components:
9043
9422
  exact literal: `status:"running"`, `delivery:"accepted"`). ACCEPTANCE, not an outcome: compaction
9044
9423
  runs at the next turn boundary and a `compacted{trigger:"manual"}` event rides the run stream ONLY
9045
9424
  if anything was summarized (mooted/failed → no event).
9046
- # 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):唯一铸造点 `routes/runs.ts:793` 是
9425
+ # 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):唯一铸造点 `routes/runs.ts` 是
9047
9426
  # 四键字面量,四键**全部无条件**——此前 required 只列 taskId,把恒在键写成了可缺。
9048
9427
  required: [taskId, status, delivery, note]
9049
9428
  additionalProperties: false
@@ -9060,7 +9439,7 @@ components:
9060
9439
  `toolCallId` echoed, `delivery:"requested"`). Fire-and-forget: acceptance only — the
9061
9440
  authoritative outcome is the tool call's own `tool_end.structured {detached:true, task_id}`;
9062
9441
  a non-detachable env makes the request a fail-safe no-op.
9063
- # 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):唯一铸造点 `routes/runs.ts:848` 是
9442
+ # 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):唯一铸造点 `routes/runs.ts` 是
9064
9443
  # 四键字面量,四键全部无条件(toolCallId=请求回显,body 校验过必在)。
9065
9444
  required: [taskId, toolCallId, delivery, note]
9066
9445
  additionalProperties: false
@@ -9148,10 +9527,10 @@ components:
9148
9527
  ToolApprovalRespondAck:
9149
9528
  type: object
9150
9529
  description: >
9151
- The 200 ack of POST /v1/tool-approvals/{approvalId}/respond (server tool-approval.ts:521 — exact
9530
+ The 200 ack of POST /v1/tool-approvals/{approvalId}/respond (server tool-approval.ts — exact
9152
9531
  shape, `decision` echoed). Owner-gated with a 404 and NO existence oracle: non-owner and
9153
9532
  unknown/settled/expired/wrong-replica ids are indistinguishable.
9154
- # 封闭(census 批2 第三段,2026-07-30):铸造点 `tool-approval.ts:521` 三恒在键 + 两条件键
9533
+ # 封闭(census 批2 第三段,2026-07-30):铸造点 `tool-approval.ts` 三恒在键 + 两条件键
9155
9534
  # (rememberApplied/updatedInputForwarded 的条件展开就在同一行字面量里),键集恰是这 5 个。
9156
9535
  required: [approvalId, delivery, decision]
9157
9536
  additionalProperties: false
@@ -9175,25 +9554,25 @@ components:
9175
9554
  UsageMetric:
9176
9555
  type: string
9177
9556
  enum: [tasks, tokensIn, tokensOut, costUsd]
9178
- description: 'The ?metric= axis of GET /v1/usage/series (server usage-analytics.ts:67). Default costUsd.'
9557
+ description: 'The ?metric= axis of GET /v1/usage/series (server usage-analytics.ts). Default costUsd.'
9179
9558
 
9180
9559
  UsageGranularity:
9181
9560
  type: string
9182
9561
  enum: [hour, day]
9183
- description: 'The ?granularity= axis of GET /v1/usage/series (UTC buckets; server usage-analytics.ts:68). Default day.'
9562
+ description: 'The ?granularity= axis of GET /v1/usage/series (UTC buckets; server usage-analytics.ts). Default day.'
9184
9563
 
9185
9564
  UsageDimension:
9186
9565
  type: string
9187
9566
  enum: [principal, model]
9188
9567
  description: >
9189
- The ?dimension= axis of GET /v1/usage/breakdown (server usage-analytics.ts:85). Default principal.
9568
+ The ?dimension= axis of GET /v1/usage/breakdown (server usage-analytics.ts). Default principal.
9190
9569
  A JWT principal querying dimension=principal only ever sees its own bucket (owner is enforced
9191
9570
  upstream — the semantics hold naturally, no leak surface).
9192
9571
 
9193
9572
  UsageTotals:
9194
9573
  type: object
9195
9574
  description: >
9196
- Window totals (server usage-analytics.ts:31 UsageTotals). tokensIn = Σ(inputTokens +
9575
+ Window totals (server usage-analytics.ts UsageTotals). tokensIn = Σ(inputTokens +
9197
9576
  cacheReadTokens + cacheWriteTokens) — the billing view (cache hits count); tokensOut = Σ
9198
9577
  outputTokens; costUsd = Σ costMicroUsd / 1e6 (authoritative micro-USD ledger, not an estimate).
9199
9578
  required: [tasks, tokensIn, tokensOut, costUsd, estimated]
@@ -9228,9 +9607,9 @@ components:
9228
9607
  allOf:
9229
9608
  - $ref: '#/components/schemas/UsageWindowBase'
9230
9609
  - $ref: '#/components/schemas/UsageTotals'
9231
- # 🔴 封闭 + 本地镜像(census 批2 第二段,2026-07-30)。铸造点 `routes/trace-usage.ts:141` 逐字是
9610
+ # 🔴 封闭 + 本地镜像(census 批2 第二段,2026-07-30)。铸造点 `routes/trace-usage.ts` 逐字是
9232
9611
  # `{ ...base, ...usageSummary(scan.rows) }`,两半都是本仓自己的字面量:`base` = from/to/truncated?
9233
- # (`:139`),`usageSummary` = `finish(UsageTotals)` 的五键(`usage-analytics.ts:98`)。
9612
+ # (`usage-analytics.ts`),`usageSummary` = `finish(UsageTotals)` 的五键(`usage-analytics.ts`)。
9234
9613
  # 镜像的理由与 Event_reasoning 处那条长注逐字同一条(`additionalProperties` 不结算 allOf 分支);
9235
9614
  # 一致性由 spec.test.ts 的通用「封闭 allOf 臂必须镜像被引 schema 全部键」门看守。
9236
9615
  additionalProperties: false
@@ -9260,13 +9639,13 @@ components:
9260
9639
  type: array
9261
9640
  items:
9262
9641
  type: object
9263
- # 封闭:桶行铸造点 `usage-analytics.ts:118` 是两键字面量 `{ t, v }`。
9642
+ # 封闭:桶行铸造点 `usage-analytics.ts` 是两键字面量 `{ t, v }`。
9264
9643
  additionalProperties: false
9265
9644
  required: [t, v]
9266
9645
  properties:
9267
9646
  t: { type: string, description: 'ISO-8601 bucket start (UTC).' }
9268
9647
  v: { type: number }
9269
- # 封闭 + 本地镜像:铸造点 `routes/trace-usage.ts:149` = `{ ...base, metric, granularity, series }`。见 UsageSummary 处的注。
9648
+ # 封闭 + 本地镜像:铸造点 `routes/trace-usage.ts` = `{ ...base, metric, granularity, series }`。见 UsageSummary 处的注。
9270
9649
  additionalProperties: false
9271
9650
  properties:
9272
9651
  from: { type: string }
@@ -9298,7 +9677,7 @@ components:
9298
9677
  properties:
9299
9678
  key: { type: string, description: 'The bucket key (a principal or a model id).' }
9300
9679
  - $ref: '#/components/schemas/UsageTotals'
9301
- # 封闭 + 本地镜像:桶行铸造点 `usage-analytics.ts:126` 是 `{ key, ...UsageTotals }`。
9680
+ # 封闭 + 本地镜像:桶行铸造点 `usage-analytics.ts` 是 `{ key, ...UsageTotals }`。
9302
9681
  additionalProperties: false
9303
9682
  properties:
9304
9683
  key: { type: string }
@@ -9307,7 +9686,7 @@ components:
9307
9686
  tokensOut: { type: number }
9308
9687
  costUsd: { type: number }
9309
9688
  estimated: { type: boolean }
9310
- # 封闭 + 本地镜像:铸造点 `routes/trace-usage.ts:157` = `{ ...base, dimension, breakdown }`。见 UsageSummary 处的注。
9689
+ # 封闭 + 本地镜像:铸造点 `routes/trace-usage.ts` = `{ ...base, dimension, breakdown }`。见 UsageSummary 处的注。
9311
9690
  additionalProperties: false
9312
9691
  properties:
9313
9692
  from: { type: string }
@@ -9323,7 +9702,7 @@ components:
9323
9702
  exact literal: `status:"running"`, `delivery:"applied"`, `marker` = the engine-minted delivery
9324
9703
  marker; NO `note`, unlike the run-subagent verb).
9325
9704
  # 🔴 census 批2 五段:CLOSED — all 5 keys are unconditional in the literal
9326
- # `{ runId: wfRunId, label, status: "running", delivery: "applied", marker }` (workflows.ts:279);
9705
+ # `{ runId: wfRunId, label, status: "running", delivery: "applied", marker }` (workflows.ts);
9327
9706
  # this schema previously existed but no path `$ref`'d it (see the path fix above).
9328
9707
  required: [runId, label, status, delivery, marker]
9329
9708
  additionalProperties: false
@@ -9340,7 +9719,7 @@ components:
9340
9719
  # type)对方法返回位的内联形都是盲的 —— 于是它漏了这里一直标为 required 的 `sessionId`,CI 全绿。
9341
9720
  type: object
9342
9721
  description: >
9343
- GET /v1/sessions/{sessionId}/workspace 的 200 信封(server src/http/routes/sessions.ts:624-629)。
9722
+ GET /v1/sessions/{sessionId}/workspace 的 200 信封(server src/http/routes/sessions.ts)。
9344
9723
  ⚠️ `total` = **本页行数**(`snapshots.length`),不是快照总数,且这条腿没有任何分页游标 —— 与同族
9345
9724
  `WorkspaceTreePage.total`(真·全量)**同名反义**。`latest` 与展示行同源(防 ghost key)。
9346
9725
  additionalProperties: false
@@ -9357,7 +9736,7 @@ components:
9357
9736
  # 4.3.0(CAPS-OPS-8):同上,tree 腿的 200 信封。
9358
9737
  type: object
9359
9738
  description: >
9360
- GET /v1/sessions/{sessionId}/workspace/{key}/tree 的 200 信封(server src/http/routes/sessions.ts:651-657)。
9739
+ GET /v1/sessions/{sessionId}/workspace/{key}/tree 的 200 信封(server src/http/routes/sessions.ts)。
9361
9740
  `total` 在这条腿上是**全量**(`all.length`)—— 与 `WorkspaceSnapshotPage.total` 语义相反。
9362
9741
  `key` 回的是解析后的真实键(请求可传 `latest` 别名)。
9363
9742
  additionalProperties: false