@sema-agent/sdk 25.0.0 → 26.0.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.
Files changed (56) hide show
  1. package/README.md +103 -3
  2. package/dist/client.d.ts +0 -4
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +0 -5
  5. package/dist/client.js.map +1 -1
  6. package/dist/errors.d.ts +57 -25
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +40 -35
  9. package/dist/errors.js.map +1 -1
  10. package/dist/events.d.ts +13 -2
  11. package/dist/events.d.ts.map +1 -1
  12. package/dist/events.js.map +1 -1
  13. package/dist/index.d.ts +17 -14
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +6 -3
  16. package/dist/index.js.map +1 -1
  17. package/dist/resources/approvals.d.ts +16 -2
  18. package/dist/resources/approvals.d.ts.map +1 -1
  19. package/dist/resources/approvals.js +7 -2
  20. package/dist/resources/approvals.js.map +1 -1
  21. package/dist/resources/fleet.d.ts +8 -12
  22. package/dist/resources/fleet.d.ts.map +1 -1
  23. package/dist/resources/fleet.js.map +1 -1
  24. package/dist/resources/rules.d.ts +2 -5
  25. package/dist/resources/rules.d.ts.map +1 -1
  26. package/dist/resources/rules.js.map +1 -1
  27. package/dist/resources/session-sync.d.ts +27 -32
  28. package/dist/resources/session-sync.d.ts.map +1 -1
  29. package/dist/resources/session-sync.js +25 -39
  30. package/dist/resources/session-sync.js.map +1 -1
  31. package/dist/resources/tool-approvals.d.ts +60 -99
  32. package/dist/resources/tool-approvals.d.ts.map +1 -1
  33. package/dist/resources/tool-approvals.js +41 -35
  34. package/dist/resources/tool-approvals.js.map +1 -1
  35. package/dist/settings.d.ts +13 -0
  36. package/dist/settings.d.ts.map +1 -1
  37. package/dist/settings.js +0 -24
  38. package/dist/settings.js.map +1 -1
  39. package/dist/sync.d.ts +5 -5
  40. package/dist/sync.d.ts.map +1 -1
  41. package/dist/sync.js +13 -55
  42. package/dist/sync.js.map +1 -1
  43. package/dist/transport.d.ts +2 -0
  44. package/dist/transport.d.ts.map +1 -1
  45. package/dist/transport.js +3 -0
  46. package/dist/transport.js.map +1 -1
  47. package/dist/types.d.ts +132 -110
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/types.js +5 -0
  50. package/dist/types.js.map +1 -1
  51. package/openapi.yaml +237 -532
  52. package/package.json +1 -1
  53. package/dist/resources/workspace.d.ts +0 -95
  54. package/dist/resources/workspace.d.ts.map +0 -1
  55. package/dist/resources/workspace.js +0 -60
  56. package/dist/resources/workspace.js.map +0 -1
package/openapi.yaml CHANGED
@@ -172,7 +172,7 @@ tags:
172
172
  themself said yes"), never operator. GATED AND OFF BY DEFAULT: the whole lane hangs on
173
173
  PERMISSION_RULES_ENABLED (default false) plus a wired rule store — so on a stock deployment BOTH doors
174
174
  answer 501 `capability.rule_store_required`, and so does the single-card half (the ask frame simply
175
- carries no `ruleSuggestions`). Read the observed frame / an actual response, never a version number.
175
+ carries no `ruleOffers`). Read the observed frame / an actual response, never a version number.
176
176
  The SINGLE-CARD half of the same lane is `persistRule` on POST /v1/tool-approvals/{id}/respond.
177
177
  - name: adoption
178
178
  description: >
@@ -2280,38 +2280,16 @@ paths:
2280
2280
  get:
2281
2281
  tags: [workspace]
2282
2282
  operationId: workspaceList
2283
- x-status: retired # S-15/design/381 (server 7.52.0): whole-tree snapshot epoch retired — every ≥7.52 deployment answers 501 capability.workspace_retired.
2284
- summary: 'RETIRED (server 7.52.0, S-15) — snapshot listing; ≥7.52 answers 501 capability.workspace_retired. Live on 1.299–7.51 only.'
2285
- description: >
2286
- 🔴 RETIRED as of `@sema-agent/server` 7.52.0 (S-15/design/381): the E19 whole-tree snapshot store this
2287
- face projected was replaced by per-edited-file rewind HISTORY, which holds only files the agent edited
2288
- — structurally there is no "whole workspace" manifest to browse anymore. The route SHAPE is kept
2289
- server-side as a tombstone: every ≥7.52 deployment answers 501 `capability.workspace_retired` (a named
2290
- refusal, not a 404 that reads like a typo), and `capabilities.workspace` is always false (same
2291
- predicate). There is NO 1:1 replacement path — a future "rewind coverage" browse would be a NEW face
2292
- with tracked-set semantics, not this name wired back. Related live faces: rewind restore rides
2293
- `TaskRequest.restoreFiles` (probe `capabilities.restoreFiles`); per-file history blobs ride
2294
- `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`.
2295
- Historical shape (servers 1.299–7.51): read-only projection over the E19 snapshot store. Rows carry
2296
- `{key, files, bytes?}`; `latest` = the newest snapshot key. Owner-scoped (non-owner → 404, no
2297
- oracle); 501 without the snapshot-store read faces (probe `capabilities.workspace`).
2298
- parameters:
2299
- - in: query
2300
- name: limit
2301
- required: false
2302
- schema: { type: integer, minimum: 1, maximum: 100 }
2283
+ x-status: retired # server 7.52.0: whole-tree snapshot epoch retired; sdk 26.0.0 deleted the verb (support floor 7.52.0).
2284
+ summary: 'RETIRED (server 7.52.0, S-15) — snapshot listing; every supported server (>= 7.52.0) answers 501 capability.workspace_retired.'
2285
+ description: >
2286
+ 🔴 RETIRED as of `@sema-agent/server` 7.52.0 (S-15/design/381): the E19 whole-tree snapshot store this face projected was replaced by per-edited-file
2287
+ rewind HISTORY (only files the agent edited — there is no "whole workspace" manifest to browse). The route SHAPE is kept server-side as a tombstone:
2288
+ every supported server answers 501 `capability.workspace_retired` (a named refusal, not a 404 that reads like a typo), and `capabilities.workspace` is
2289
+ always `false` (same predicate). No 1:1 replacement: rewind restore rides `TaskRequest.restoreFiles` (probe `capabilities.restoreFiles`); per-file
2290
+ history blobs ride `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`. sdk 26.0.0 deleted `client.workspace` (its 200 shapes only existed on 1.299–7.51).
2303
2291
  responses:
2304
- '200':
2305
- description: The snapshot listing.
2306
- content:
2307
- application/json:
2308
- # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts`(信封与行两处)。条件键=信封 `latest`(零快照即省略)、行 `bytes`(blobSizes 面缺席=诚实省略)。
2309
- # 4.3.0:形提成具名 `WorkspaceSnapshotPage`(SDK 同批把返回位的内联字面量也提成了 exported type,
2310
- # 让 spec-resource-schema-gate 罩得住 —— CAPS-OPS-8)。
2311
- schema: { $ref: '#/components/schemas/WorkspaceSnapshotPage' }
2312
- '400': { $ref: '#/components/responses/BadRequest' }
2313
2292
  '401': { $ref: '#/components/responses/Unauthorized' }
2314
- '404': { $ref: '#/components/responses/NotFound' }
2315
2293
  '501': { $ref: '#/components/responses/NotImplemented' }
2316
2294
 
2317
2295
  /v1/sessions/{sessionId}/workspace/{key}/tree:
@@ -2325,38 +2303,19 @@ paths:
2325
2303
  name: key
2326
2304
  required: true
2327
2305
  schema: { type: string }
2328
- description: 'Snapshot key (entryId), or the alias `latest` = the newest snapshot.'
2329
2306
  get:
2330
2307
  tags: [workspace]
2331
2308
  operationId: workspaceTree
2332
- x-status: retired # S-15 (server 7.52.0): retired with the family — see workspaceList's retirement note.
2333
- summary: 'RETIRED (server 7.52.0, S-15) — snapshot file tree; ≥7.52 answers 501 capability.workspace_retired.'
2334
- description: >
2335
- 🔴 RETIRED as of server 7.52.0 — same retirement (and same replacement pointers) as `workspaceList`
2336
- above: every ≥7.52 deployment answers 501 `capability.workspace_retired`; `capabilities.workspace` is
2337
- always false. Historical shape (1.299–7.51): entries carry `{path, hash, size?}` — `hash` is the
2338
- sha256 content-address (cross-snapshot change highlighting, [1894]②); `size` only when the SQL size
2339
- index is available. `limit` 1..2000 default 500, `offset` cursor; `nextOffset` present = more pages.
2340
- parameters:
2341
- - in: query
2342
- name: limit
2343
- required: false
2344
- schema: { type: integer, minimum: 1, maximum: 2000 }
2345
- - in: query
2346
- name: offset
2347
- required: false
2348
- schema: { type: integer, minimum: 0 }
2309
+ x-status: retired # server 7.52.0: whole-tree snapshot epoch retired; sdk 26.0.0 deleted the verb (support floor 7.52.0).
2310
+ summary: 'RETIRED (server 7.52.0, S-15) — snapshot file tree; every supported server (>= 7.52.0) answers 501 capability.workspace_retired.'
2311
+ description: >
2312
+ 🔴 RETIRED as of `@sema-agent/server` 7.52.0 (S-15/design/381): the E19 whole-tree snapshot store this face projected was replaced by per-edited-file
2313
+ rewind HISTORY (only files the agent edited — there is no "whole workspace" manifest to browse). The route SHAPE is kept server-side as a tombstone:
2314
+ every supported server answers 501 `capability.workspace_retired` (a named refusal, not a 404 that reads like a typo), and `capabilities.workspace` is
2315
+ always `false` (same predicate). No 1:1 replacement: rewind restore rides `TaskRequest.restoreFiles` (probe `capabilities.restoreFiles`); per-file
2316
+ history blobs ride `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`. sdk 26.0.0 deleted `client.workspace` (its 200 shapes only existed on 1.299–7.51).
2349
2317
  responses:
2350
- '200':
2351
- description: One page of the tree.
2352
- content:
2353
- application/json:
2354
- # 封闭两层(census 批2 第三段,2026-07-30):铸造点 `routes/sessions.ts`(信封与行两处)。条件键=信封 `nextOffset`(尾页省略)、行 `size`(blobSizes 面缺席省略)。
2355
- # 4.3.0:形提成具名 `WorkspaceTreePage`(理由同 list 腿 —— CAPS-OPS-8)。
2356
- schema: { $ref: '#/components/schemas/WorkspaceTreePage' }
2357
- '400': { $ref: '#/components/responses/BadRequest' }
2358
2318
  '401': { $ref: '#/components/responses/Unauthorized' }
2359
- '404': { $ref: '#/components/responses/NotFound' }
2360
2319
  '501': { $ref: '#/components/responses/NotImplemented' }
2361
2320
 
2362
2321
  /v1/sessions/{sessionId}/workspace/{key}/file:
@@ -2373,46 +2332,16 @@ paths:
2373
2332
  get:
2374
2333
  tags: [workspace]
2375
2334
  operationId: workspaceFile
2376
- x-status: retired # S-15 (server 7.52.0): retired with the family — ≥7.52 the tombstone rejects BEFORE any size check, so the 413 envelope below is unreachable there.
2377
- summary: 'RETIRED (server 7.52.0, S-15) — single-file read; ≥7.52 answers 501 capability.workspace_retired.'
2378
- description: >
2379
- 🔴 RETIRED as of server 7.52.0 — same retirement (and replacement pointers) as `workspaceList` above.
2380
- On ≥7.52 the tombstone 501 fires BEFORE any size check, so the 413 `workspace_file_too_large`
2381
- envelope below is UNREACHABLE there (the SDK keeps its typed WorkspaceFileTooLargeError for
2382
- 1.299–7.51 servers still inside the support range). Historical shape (1.299–7.51): raw bytes with a
2383
- CONSERVATIVE content-type table (html/svg etc. deliberately serve as octet-stream — agent output is
2384
- untrusted; `x-content-type-options: nosniff` always set) + the `x-sema-content-binary` discriminator
2385
- header (first-8KiB NUL sniff, [1894]③). Capped at `capabilities.workspace.maxFileBytes` (default
2386
- 8MiB, WORKSPACE_FILE_MAX_BYTES) — over-cap ⇒ 413 STRUCTURED envelope
2387
- `{errorCode: "workspace_file_too_large", sizeBytes, limit}` ([1894]①; archive was the escape hatch).
2388
- With the SQL size index the 413 fired BEFORE fetching the bytes.
2389
- parameters:
2390
- - in: query
2391
- name: path
2392
- required: true
2393
- schema: { type: string }
2394
- description: The tree's relPath, verbatim.
2335
+ x-status: retired # server 7.52.0: whole-tree snapshot epoch retired; sdk 26.0.0 deleted the verb (support floor 7.52.0).
2336
+ summary: 'RETIRED (server 7.52.0, S-15) — snapshot single file; every supported server (>= 7.52.0) answers 501 capability.workspace_retired.'
2337
+ description: >
2338
+ 🔴 RETIRED as of `@sema-agent/server` 7.52.0 (S-15/design/381): the E19 whole-tree snapshot store this face projected was replaced by per-edited-file
2339
+ rewind HISTORY (only files the agent edited — there is no "whole workspace" manifest to browse). The route SHAPE is kept server-side as a tombstone:
2340
+ every supported server answers 501 `capability.workspace_retired` (a named refusal, not a 404 that reads like a typo), and `capabilities.workspace` is
2341
+ always `false` (same predicate). No 1:1 replacement: rewind restore rides `TaskRequest.restoreFiles` (probe `capabilities.restoreFiles`); per-file
2342
+ history blobs ride `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`. sdk 26.0.0 deleted `client.workspace` (its 200 shapes only existed on 1.299–7.51).
2395
2343
  responses:
2396
- '200':
2397
- description: The file bytes.
2398
- content:
2399
- application/octet-stream:
2400
- schema: { type: string, format: binary }
2401
- '400': { $ref: '#/components/responses/BadRequest' }
2402
2344
  '401': { $ref: '#/components/responses/Unauthorized' }
2403
- '404': { $ref: '#/components/responses/NotFound' }
2404
- '413':
2405
- description: 'Structured over-cap envelope: {error, errorCode: "workspace_file_too_large", sizeBytes, limit}.'
2406
- content:
2407
- application/json:
2408
- schema:
2409
- type: object
2410
- required: [error, errorCode, sizeBytes, limit]
2411
- properties:
2412
- error: { type: string }
2413
- errorCode: { type: string, enum: [workspace_file_too_large] }
2414
- sizeBytes: { type: integer }
2415
- limit: { type: integer }
2416
2345
  '501': { $ref: '#/components/responses/NotImplemented' }
2417
2346
 
2418
2347
  /v1/sessions/{sessionId}/workspace/{key}/archive:
@@ -2429,28 +2358,17 @@ paths:
2429
2358
  get:
2430
2359
  tags: [workspace]
2431
2360
  operationId: workspaceArchive
2432
- x-status: retired # S-15 (server 7.52.0): retired with the family — see workspaceList's retirement note.
2433
- summary: 'RETIRED (server 7.52.0, S-15) — whole-snapshot tar; ≥7.52 answers 501 capability.workspace_retired.'
2434
- description: >
2435
- 🔴 RETIRED as of server 7.52.0 — same retirement (and replacement pointers) as `workspaceList` above:
2436
- every ≥7.52 deployment answers 501 `capability.workspace_retired`. Historical shape (1.299–7.51):
2437
- streamed ustar (`content-disposition: attachment`), entries in stable path order, blob-by-blob with
2438
- backpressure — server memory stays O(1 file). Pre-flight checks BEFORE the headers went out: paths
2439
- not representable in ustar ⇒ 422 naming the first offenders; missing blobs (raced a reap / partial
2440
- import) ⇒ 502 (retryable). A blob vanishing MID-stream destroyed the connection (honest truncation)
2441
- rather than faking a complete archive.
2361
+ x-status: retired # server 7.52.0: whole-tree snapshot epoch retired; sdk 26.0.0 deleted the verb (support floor 7.52.0).
2362
+ summary: 'RETIRED (server 7.52.0, S-15) — snapshot tar archive; every supported server (>= 7.52.0) answers 501 capability.workspace_retired.'
2363
+ description: >
2364
+ 🔴 RETIRED as of `@sema-agent/server` 7.52.0 (S-15/design/381): the E19 whole-tree snapshot store this face projected was replaced by per-edited-file
2365
+ rewind HISTORY (only files the agent edited — there is no "whole workspace" manifest to browse). The route SHAPE is kept server-side as a tombstone:
2366
+ every supported server answers 501 `capability.workspace_retired` (a named refusal, not a 404 that reads like a typo), and `capabilities.workspace` is
2367
+ always `false` (same predicate). No 1:1 replacement: rewind restore rides `TaskRequest.restoreFiles` (probe `capabilities.restoreFiles`); per-file
2368
+ history blobs ride `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`. sdk 26.0.0 deleted `client.workspace` (its 200 shapes only existed on 1.299–7.51).
2442
2369
  responses:
2443
- '200':
2444
- description: The tar stream.
2445
- content:
2446
- application/x-tar:
2447
- schema: { type: string, format: binary }
2448
- '400': { $ref: '#/components/responses/BadRequest' }
2449
2370
  '401': { $ref: '#/components/responses/Unauthorized' }
2450
- '404': { $ref: '#/components/responses/NotFound' }
2451
- '422': { description: 'Snapshot contains paths not representable in ustar (names the first 5).' }
2452
2371
  '501': { $ref: '#/components/responses/NotImplemented' }
2453
- '502': { description: 'Snapshot is missing blobs (raced a reap or a partial import) — retry or pick another snapshot.' }
2454
2372
 
2455
2373
  /v1/sessions/{sessionId}/policy:
2456
2374
  parameters:
@@ -3040,50 +2958,6 @@ paths:
3040
2958
  '404': { $ref: '#/components/responses/NotFound' }
3041
2959
  '501': { $ref: '#/components/responses/NotImplemented' }
3042
2960
 
3043
- /v1/sessions/{sessionId}/sync/snapshots/{key}/blobs/{hash}:
3044
- parameters:
3045
- - $ref: '#/components/parameters/PrincipalHeader'
3046
- - in: path
3047
- name: sessionId
3048
- required: true
3049
- schema: { type: string }
3050
- - in: path
3051
- name: key
3052
- required: true
3053
- schema: { type: string }
3054
- description: The snapshot key (= a `SessionTreeEntry.id`).
3055
- - in: path
3056
- name: hash
3057
- required: true
3058
- schema: { type: string, pattern: '^[0-9a-f]{64}$' }
3059
- description: The content-address (SHA-256, 64 lowercase-hex).
3060
- get:
3061
- tags: [sessions]
3062
- operationId: sessionSyncGetBlob
3063
- x-status: retired # S-15 (server 7.52.0): the E19-era snapshot-scoped PULL arm was REPLACED by GET …/sync/history/blobs/{hash} — ≥7.52 this path 404s (not_found.route).
3064
- summary: 'RETIRED (server 7.52.0, S-15) — snapshot-scoped blob PULL; use GET …/sync/history/blobs/{hash}. Live on ≤7.51 only.'
3065
- description: >
3066
- 🔴 RETIRED as of `@sema-agent/server` 7.52.0 (S-15/design/381): a blob is scoped by the session's
3067
- HISTORY GRAPH now, not by a per-snapshot manifest key, so this arm was replaced by
3068
- `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}` (sessionSyncGetHistoryBlob — same
3069
- octet-stream body, same no-oracle 404 posture, scope check = "referenced by this session's
3070
- exportHistory envelope"). On ≥7.52 this path no longer matches the sync route family and answers 404
3071
- `not_found.route`. SDK: `sessions.sync.getHistoryBlob` (the legacy `getBlob` verb stays, deprecated,
3072
- for ≤7.51 servers inside the support range).
3073
- Historical shape (≤7.51): one content-addressed blob's RAW bytes; the cloud served a hash ONLY if
3074
- THIS session's `:key` manifest referenced it (foreign/unreferenced → 404 `blob not found`). 400 on a
3075
- non-64-hex hash or an undecodable key/hash. Owner-scoped (404).
3076
- responses:
3077
- '200':
3078
- description: The blob bytes.
3079
- content:
3080
- application/octet-stream:
3081
- schema: { type: string, format: binary }
3082
- '400': { $ref: '#/components/responses/BadRequest' }
3083
- '401': { $ref: '#/components/responses/Unauthorized' }
3084
- '404': { $ref: '#/components/responses/NotFound' }
3085
- '501': { $ref: '#/components/responses/NotImplemented' }
3086
-
3087
2961
  /v1/sessions/{sessionId}/sync/history/blobs/{hash}:
3088
2962
  parameters:
3089
2963
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -3109,7 +2983,7 @@ paths:
3109
2983
  → 404 `not_found.blob`, never a blind read; a server whose file-history store has no blob faces
3110
2984
  (e.g. the local file backend) answers the SAME 404 shape (no oracle — "nothing servable" and "not
3111
2985
  referenced" are deliberately indistinguishable). 400 `request.id_invalid` on a non-64-hex hash.
3112
- Owner-scoped (404). Replaces the retired `GET …/sync/snapshots/{key}/blobs/{hash}`.
2986
+ Owner-scoped (404). Replaces the retired snapshot-scoped PULL arm of servers <= 7.51 (below the support floor; sdk 26.0.0 deleted `getBlob` and its path).
3113
2987
  responses:
3114
2988
  '200':
3115
2989
  description: The blob bytes.
@@ -3142,12 +3016,10 @@ paths:
3142
3016
  body). The cloud verifies `sha256(body) === :hash` (the content-address integrity guard) BEFORE storing — a
3143
3017
  mismatch → 400 `blob hash mismatch`. own-or-FRESH gate. Per-blob cap 64 MiB → 413 mid-stream beyond it. A
3144
3018
  backend write failure (e.g. a MinIO non-2xx) → 502 `blob store write failed`. 204 on success (no body). The
3145
- PUSH path is the BARE `blobs/:hash` (the PULL is `history/blobs/:hash` since server 7.52,
3146
- `snapshots/:key/blobs/:hash` before). 🔴 server ≥7.52 (S-15 换店): the blob faces ride the
3019
+ PUSH path is the BARE `blobs/:hash` (the PULL is `history/blobs/:hash`). The blob faces ride the
3147
3020
  file-HISTORY store — a deployment whose store has no blob faces (the local file backend) answers 501
3148
3021
  `capability.snapshot_store_required` ("blob upload requires a durable file-history store") while the
3149
- rest of the sync surface keeps working; on ≤7.51 the same code meant a missing snapshot store. Same
3150
- code, same operator disposition (wire a backend with blob faces).
3022
+ rest of the sync surface keeps working (operator disposition: wire a backend with blob faces).
3151
3023
  requestBody:
3152
3024
  required: true
3153
3025
  content:
@@ -3217,14 +3089,12 @@ paths:
3217
3089
  x-status: live
3218
3090
  summary: PUSH Phase A — classify + stage (mint a stagingId).
3219
3091
  description: >
3220
- 2c session-sync PUSH Phase A — a SMALL metadata body (`entryIds, snapshots, policy, anchors, resolution?`); the
3092
+ 2c session-sync PUSH Phase A — a SMALL metadata body (`entryIds, fileHistory?, policy, anchors, resolution?, logDigest?, title?, pinned?`); the
3221
3093
  entries stream in Phase B. The cloud classifies (§7), pre-checks blob presence (a missing referenced hash →
3222
3094
  422 `missing_blob` — PUT it first), guards the import lease + any active run (409), then mints a stagingId.
3223
- ⚠️ S-15 EPOCH: server ≥7.52's Phase A consumes `{entryIds, fileHistory?, policy, anchors, resolution?,
3224
- logDigest?}` (routes/session-sync.ts) — the `snapshots` key is SILENTLY IGNORED there (an unknown body key); the
3225
- file history rides `fileHistory` (a `FileHistoryExport` envelope, `null` / absent = none) and every blob it references must have been
3226
- `PUT …/sync/blobs/{hash}` first. ≤7.51 servers consume `snapshots` and do not know `fileHistory`. SDK 16.2.0 carries
3227
- `fileHistory` in `pushSessionBundle` / `importSessionBundle` (and fills `snapshots: []` on the pull side when the manifest omits it); a hand-rolled
3095
+ The file history rides `fileHistory` (a `FileHistoryExport` envelope, `null` / absent = none) and every blob it references must have been
3096
+ `PUT …/sync/blobs/{hash}` first (the `snapshots` key of servers <= 7.51 is outside the support range; a supported server ignores it and sdk 26.0.0
3097
+ no longer declares it). The SDK carries `fileHistory` in `pushSessionBundle` / `importSessionBundle`; a hand-rolled
3228
3098
  push must send `fileHistory` itself, and only to a peer whose `capabilities.sessionSyncFileHistory` is not false (server >= 7.116.0 — else the
3229
3099
  commit 422s `missing_blob`).
3230
3100
  `identical` → `{ relation:"identical", basis, payloadVerified }` with NO stagingId (skip Phase B);
@@ -3239,15 +3109,14 @@ paths:
3239
3109
  application/json:
3240
3110
  schema:
3241
3111
  type: object
3242
- required: [entryIds, snapshots, policy, anchors]
3112
+ required: [entryIds, policy, anchors]
3243
3113
  properties:
3244
3114
  entryIds: { type: array, items: { type: string } }
3245
- snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' } }
3246
3115
  fileHistory:
3247
3116
  oneOf:
3248
3117
  - { $ref: '#/components/schemas/FileHistoryExport' }
3249
3118
  - { type: 'null' }
3250
- description: 'server >= 7.52: the file-history envelope (replaces `snapshots`); `null` / absent = none. Every blob it references must already be PUT.'
3119
+ description: 'the file-history envelope; `null` / absent = none. Every blob it references must already be PUT.'
3251
3120
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
3252
3121
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
3253
3122
  title:
@@ -3324,7 +3193,7 @@ paths:
3324
3193
  description: >
3325
3194
  2c session-sync PUSH Phase B — stream the entry log as an NDJSON REQUEST body into the staged shadow id; the
3326
3195
  cloud validates per-line (the streaming invariant gate) then `finish()` (exactly-one-root + leaf-resolvable) →
3327
- commits (the atomic swap) → replays snapshots/policy/anchors. Returns `{ relation }` (the AUTHORITATIVE in-txn
3196
+ commits (the atomic swap) → replays fileHistory/policy/anchors. Returns `{ relation }` (the AUTHORITATIVE in-txn
3328
3197
  re-classify). Re-POSTing the same stagingId is idempotent. Typed errors: a per-entry / structural violation →
3329
3198
  422 `invalid_entries`; a line too large → 413; a commit-time TOCTOU flip to fork/stale → 409 `conflict`
3330
3199
  (SyncConflictError); a refused policy loosen → 403 `loosen_forbidden`; a wrong/foreign stagingId → 404.
@@ -3385,6 +3254,15 @@ paths:
3385
3254
  application/json:
3386
3255
  schema: { $ref: '#/components/schemas/PendingList' }
3387
3256
  '401': { $ref: '#/components/responses/Unauthorized' }
3257
+ '500':
3258
+ description: >-
3259
+ server >= 7.134.0: a queue row whose stored body cannot be read (the store's refusal contract — a retry fails the same way; an operator has to repair
3260
+ or delete that row) answers `500` + `errorCode: "approvals.read_corrupt"` (body `{ error, errorCode }`; the bad row's coordinates stay off the wire) —
3261
+ the same judge and the same code as `GET /v1/approvals/stream`. Do NOT retry it as-is (the SDK's GET retry arm exempts the code). Every other read
3262
+ failure (connection / timeout / store hiccup) is still `500 internal.error`. Older servers answered the corrupt row with `internal.error`.
3263
+ content:
3264
+ application/json:
3265
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3388
3266
 
3389
3267
  /v1/approvals/stream:
3390
3268
  parameters:
@@ -4458,7 +4336,7 @@ paths:
4458
4336
  verbatim 400 texts, the two capability bits to probe first and the `deny` posture are on
4459
4337
  the PersistRuleSelection schema. Allow-family only (on `deny` the whole field is ignored).
4460
4338
 
4461
- ⚠️ If the frame carried neither `ruleOffers` nor `ruleSuggestions`, do not send this key —
4339
+ ⚠️ If the frame carried no `ruleOffers`, do not send this key —
4462
4340
  there is nothing redeemable for THIS card and the answer is necessarily
4463
4341
  `rulePersisted:false` + `rule_lane_unavailable` (the missing material short-circuits BEFORE
4464
4342
  any candidate comparison, so it is never `rule_not_offered`). Absence has several causes —
@@ -5327,39 +5205,6 @@ paths:
5327
5205
  error: { type: string }
5328
5206
  missing: { type: array, items: { type: string } }
5329
5207
 
5330
- /v1/approvals/{approvalId}:
5331
- parameters:
5332
- - $ref: '#/components/parameters/PrincipalHeader'
5333
- - in: path
5334
- name: approvalId
5335
- required: true
5336
- schema: { type: string }
5337
- get:
5338
- tags: [approvals]
5339
- operationId: approvalsGet
5340
- x-status: retired # 5.0.0: legacy approval-store leg deleted — NO deployment serves this route.
5341
- summary: 'RETIRED (server 5.0.0) — by-id approval read; every deployment answers 404. Use GET /v1/approvals.'
5342
- description: >
5343
- 🔴 RETIRED as of `@sema-agent/server` 5.0.0 ([2354]/[2356] A-5): the legacy approval-store polling leg
5344
- that served this route was deleted whole, and the durable checkpoint lane never had a by-id read
5345
- (it can list by scope or look up by capability token — there is no simple-id query). A worker WITH a
5346
- checkpoint store answers 404 `not_found.route`; a worker WITHOUT one never matches the `/v1/approvals*`
5347
- prefix at all. Either way: 404 on every deployment, and that 404 is indistinguishable from "no such
5348
- approval", which is exactly why the SDK stopped calling it (4.0.1 — `approvals.get` now reads
5349
- `GET /v1/approvals` and filters by `sessionId`, one round trip instead of a guaranteed-404 probe plus
5350
- the same list call).
5351
- Kept in the spec as a tombstone rather than deleted: consumers generated against an older spec need to
5352
- see WHY the path went away. Decide via `POST /v1/approvals/{sessionId}/decide`; list via `GET /v1/approvals`.
5353
- Historical shape (below) is left intact for the same reason; it describes no live behavior.
5354
- responses:
5355
- '200':
5356
- description: The approval row (full args projection).
5357
- content:
5358
- application/json:
5359
- schema: { $ref: '#/components/schemas/ApprovalRow' }
5360
- '401': { $ref: '#/components/responses/Unauthorized' }
5361
- '404': { $ref: '#/components/responses/NotFound' }
5362
-
5363
5208
  /v1/memory/export:
5364
5209
  parameters:
5365
5210
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -7801,7 +7646,7 @@ components:
7801
7646
  `permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`.
7802
7647
  🔴 ON THIS SUBMIT FACE THE KEY SET IS CLOSED (server >= 7.57.0 for the sub-tree): accepted top-level keys
7803
7648
  are `env` / `hooks` / `model` / `outputStyle` / `permissions` / `skillListingBudgetFraction` /
7804
- `skillListingMaxDescChars` / `ultracode` / `webSearch` (server >= 7.133.0 adds the three TIGHTEN-ONLY seats `complianceAdditionalDenies` / `readDenyPatterns` / `writeProtectedExtra`), and accepted `permissions.*` keys are
7649
+ `skillListingMaxDescChars` / `ultracode` / `webSearch` (server >= 7.133.0 adds the three TIGHTEN-ONLY seats `complianceAdditionalDenies` / `readDenyPatterns` / `writeProtectedExtra`; server >= 7.134.0 adds `approvalFrameFamilies`), and accepted `permissions.*` keys are
7805
7650
  `allow` / `ask` / `defaultMode` / `deny` / `disableAutoMode` (server >= 7.133.0 adds `neverAuto`); anything else is 400 `request.body_shape`
7806
7651
  with the names in the refusal's `unknownKeys` / `unsupportedKeys` arrays (REFUSED, not dropped — a dropped
7807
7652
  key reads as "in effect" to the caller). The object stays OPEN here because the same `SemaSettings`
@@ -7812,6 +7657,15 @@ components:
7812
7657
  (the request face has no loosening arm); an empty array is the same as not sent. Bad value => 400 `request.field_invalid` with `field` + `reason` (`over_cap` + `count` + `cap` / `bad_entry` + the first bad `index`).
7813
7658
  Caps: 1000 entries per seat, EXCEPT `readDenyPatterns`: at most 32 entries, at most 2 `*` per path segment and 16 `*` in the whole table (every star counts, nothing is folded). A `neverAuto` name that can never match any tool => 422.
7814
7659
  A resume replay of a stored body whose seat went bad => 409 `resume_blocked_by_policy`. A compliance deny is a REFUSAL, not a narrowing: a request denying `web_fetch` is refused whole where the default tool surface still mounts `WebFetch` (the single-user default does).
7660
+ 🔴 `settings.approvalFrameFamilies` (server >= 7.134.0; send it on sight of `capabilities.taskSettings.approvalFrameFamilies === true` — NOT of the top-level
7661
+ `capabilities.approvalFrameFamilies`, which <= 7.133.x already has while answering 400 `request.body_shape` for this key): the approval opening-frame FAMILIES this
7662
+ run's client RENDERS — an array of `tool_approval` / `approval_request`, each once (`[]` reads as absent). Bad value (not an array / a word outside the set / a
7663
+ repeat) => 400 `request.field_invalid` with `field: "settings.approvalFrameFamilies"`, `reason: "bad_entry"` and `index` (absent for a non-array). BEHAVIOR (BREAKING, wire):
7664
+ a run whose declaration does not contain the family this worker presents (`capabilities.approvalFrameFamilies`) has NO live approval channel — no card of either
7665
+ family (no fallback, no double send), no replay in the stream preamble of the sync stream or of `GET /v1/runs/{taskId}/events`; every ask goes to the unattended
7666
+ policy at once: with a durable store (`capabilities.approvals`) it parks per the operator policy (default; answer through `POST /v1/approvals/{sessionId}/decide`)
7667
+ or is refused naming the missing family; with NO durable store the gated tool is refused on the spot and the run finishes (no card, no decide). The declaration
7668
+ rides the persisted body onto resume legs.
7815
7669
  🔴 `settings.hooks[]` (server >= 7.133.0; probe `capabilities.taskSettings.pluginHooks` / `managedHookGroups` — an absent or false bit = the keys are STRIPPED SILENTLY): group key `source` (closed: `managedPolicy` / `user` / `project` / `local` / `flag` /
7816
7670
  `managedPlugin:<name>` / `plugin:<name>`; absent = unmanaged; managed groups run in an independent lane after the unmanaged rewrites), command-entry keys `plugin { name, root, dataDir?, options? }` and exec-form `args`; a malformed one =>
7817
7671
  400 `request.field_invalid` with `detail: { reason: "wire_key_invalid", key, path }`; the plugin context has one 8192-character budget.
@@ -9659,6 +9513,14 @@ components:
9659
9513
  redispatched: { type: array, items: { type: string } }
9660
9514
  collapsed: { type: string }
9661
9515
 
9516
+ ApprovalFrameFamily:
9517
+ type: string
9518
+ enum: [tool_approval, approval_request]
9519
+ description: >-
9520
+ The approval opening-frame FAMILY words (closed; the server's `APPROVAL_FRAME_FAMILIES` verbatim and in order): `tool_approval` = the legacy family
9521
+ (live card `tool_approval` / close `tool_approval_complete`); `approval_request` = the card family (`approval_request` / `approval_complete` /
9522
+ batch revoke `approval_revoke`). ONE vocabulary for two faces: the capability `approvalFrameFamilies` (which family this worker presents) and, server >= 7.134.0,
9523
+ the request key `settings.approvalFrameFamilies` (which families this run's client renders).
9662
9524
  ApprovalDecision:
9663
9525
  type: object
9664
9526
  description: HITL decision (`POST /v1/approvals/:sessionId/decide`).
@@ -9726,30 +9588,17 @@ components:
9726
9588
  refused by the ENGINE before the CAS (409 `resume_outcome_invalid` + `field: "hostDecision"`, the card stays
9727
9589
  pending); outside the closed set ⇒ 400 `request.field_invalid`; any value on a direct-door worker ⇒ 400
9728
9590
  `decided_by_not_in_proof`. An older server (no `approvalDecidedBy` bit) refuses the key (400 `request.body_shape`).
9729
-
9730
- ApprovalRow:
9731
- type: object
9732
- description: >
9733
- The legacy D-lane approval row of GET /v1/approvals/{approvalId} (server `ApprovalRow`,
9734
- plugins/approval-store-sql.ts — the row is `sendJson`'d verbatim, approvals-assistant.ts).
9735
- Present only on deployments running the legacy `approvalStore` leg (no `checkpointStore` wired);
9736
- the durable F4 lane has no by-id GET (only list + decide).
9737
- # 🔴 census 批2 五段:CLOSED — byte-identical to the server row type, no extra keys.
9738
- required: [id, taskId, sessionId, owner, scope, toolName, args, status, reason, decidedBy, createdAt, decidedAt]
9739
- additionalProperties: false
9740
- properties:
9741
- id: { type: string }
9742
- taskId: { type: [string, 'null'] }
9743
- sessionId: { type: [string, 'null'] }
9744
- owner: { type: [string, 'null'] }
9745
- scope: { type: [string, 'null'], description: 'Single-DB fleet scope guard (tenant identity, owner-sourced).' }
9746
- toolName: { type: string }
9747
- args: { description: 'The reviewed tool-call args — full fidelity, not redacted (this IS the review surface).' }
9748
- status: { type: string, enum: [pending, approved, denied, expired] }
9749
- reason: { type: [string, 'null'] }
9750
- decidedBy: { type: [string, 'null'] }
9751
- createdAt: { type: string }
9752
- decidedAt: { type: [string, 'null'] }
9591
+ feedback:
9592
+ type: string
9593
+ description: >-
9594
+ server >= 7.134.0 (core 7.54.0) — what the approving PERSON writes in the card's "and tell the agent what to do next" input: on the resumed
9595
+ leg it reaches the model as the user's own words (unfenced, unbounded — like the reference's accepted-prompt feedback). ONLY on a person's approve
9596
+ (`decision: "approve"`, `decidedBy` absent or `person`): on a deny or a `decidedBy: "policy"` decision ⇒ 400 `request.field_conflict` with
9597
+ `detail.kind: "approval_feedback"` and `detail.fields` `["feedback", "decision"]` / `["feedback", "decidedBy"]` (a denial's words go in `reason`);
9598
+ a non-string ⇒ 400 `request.field_invalid`; any value on a direct-door worker ⇒ 400 `feedback_not_in_proof` (not inside the signed proof); words
9599
+ carrying `<system-reminder>` ⇒ 409 `approval_binding_mismatch` (the engine's sentence; the card stays pending — ask the person to change the words,
9600
+ do not auto-refetch and re-decide on the code). The same door serves parked background-agent cards and workflow child cards; the live
9601
+ `POST /v1/tool-approvals/{id}/respond` does NOT take it. Servers <= 7.133.x refuse the key (400 `request.body_shape`, `unknownKeys: ["feedback"]`).
9753
9602
 
9754
9603
  Capabilities:
9755
9604
  type: object
@@ -10029,25 +9878,12 @@ components:
10029
9878
  leaseClient: { type: boolean, description: 'server >= 7.121.0, a version bit (always true, rides with the `fleet` object): this binary carries the serving-lease CLIENT — when the engine is a fleet member (`FLEET_GENERATION`) and the center declares in its heartbeat reply that it answers renewals (`servingLease`), the engine renews and is governed by the lease. Deliberately NOT named `servingLease` (that is the center''s bit): this says the client exists, not that a lease is in force — read `/health.leaseState` for that. Absent = an older worker.' }
10030
9879
  generationEcho: { type: boolean, description: 'server >= 7.121.0, a version bit (always true, rides with the `fleet` object): this binary echoes env `FLEET_GENERATION` as `generation` on the heartbeat and on `/health` (with `leaseState` / `leaseUntil`), and the drain endpoint `POST /v1/admin/drain` compares the four-part identity including `expectedGeneration`. Absent = an older worker.' }
10031
9880
  workspace:
9881
+ type: boolean
9882
+ enum: [false]
10032
9883
  description: >
10033
- #3 (server 1.299–7.51): workspace-browse capability family — an OBJECT when the E19 snapshot-store
10034
- read faces + a durable session store are wired ({browse, archive, maxFileBytes}); `false`/absent
10035
- otherwise (same predicate as the routes' 501). `maxFileBytes` = the single-file read cap (over-cap
10036
- ⇒ 413 workspace_file_too_large envelope; the archive endpoint is the escape hatch).
10037
- 🔴 RETIRED (server ≥7.52, S-15/design/381): the whole-tree snapshot epoch this face projected was
10038
- replaced by per-edited-file rewind history, so this key is now ALWAYS false and the four
10039
- /v1/sessions/{sessionId}/workspace* routes answer 501 `capability.workspace_retired` (route shape
10040
- kept as a tombstone — a named refusal, not a 404 that reads like a typo). Consumption posture
10041
- unchanged: probe this bit, false ⇒ don't render the panel.
10042
- oneOf:
10043
- - type: boolean
10044
- enum: [false]
10045
- - type: object
10046
- additionalProperties: true
10047
- properties:
10048
- browse: { type: boolean }
10049
- archive: { type: boolean }
10050
- maxFileBytes: { type: integer }
9884
+ The workspace-browse bit — ALWAYS `false` on every supported server (>= 7.52.0: the whole-tree snapshot epoch was replaced by
9885
+ per-edited-file rewind history; the four /v1/sessions/{sessionId}/workspace* routes answer 501 `capability.workspace_retired`, route shape kept as a
9886
+ tombstone). sdk 26.0.0 deleted the object arm `{ browse, archive, maxFileBytes }` (servers 1.299–7.51, below the support floor).
10051
9887
  sessionEvents:
10052
9888
  type: boolean
10053
9889
  description: >
@@ -10140,7 +9976,7 @@ components:
10140
9976
  approvalFrameFamilies:
10141
9977
  type: array
10142
9978
  maxItems: 1
10143
- items: { type: string, enum: [tool_approval, approval_request] }
9979
+ items: { $ref: '#/components/schemas/ApprovalFrameFamily' }
10144
9980
  description: >-
10145
9981
  server >= 7.133.0, ALWAYS present (the projection of the same judgement as `streamApproval`) — which FRAME FAMILY this worker's approval opening frames belong to:
10146
9982
  a closed-word array with AT MOST ONE member. `["approval_request"]` = the card protocol is on: one ask travels only `approval_request` -> `approval_complete` / `approval_revoke`
@@ -10148,6 +9984,9 @@ components:
10148
9984
  remedy on the worker: `STREAM_APPROVAL_ENABLED=false`). `["tool_approval"]` = the card protocol is not on (`STREAM_APPROVAL_ENABLED=false`, no store backend, or no checkpoint store) but the live card leg is.
10149
9985
  `[]` = per-call approvals are off altogether (`TOOL_APPROVAL_ENABLED=false`): no card is presented. Dispatch frame families off THIS array, never off `streamApproval`.
10150
9986
  KEY ABSENT (not `[]`) = server <= 7.132.x — there a `streamApproval: true` leg sends BOTH families for one ask.
9987
+ 🔴 server >= 7.134.0: this bit says which family THIS WORKER presents, no longer that you will receive cards — a run has a live approval channel only when
9988
+ its submit body declared that family in `settings.approvalFrameFamilies` (send it on sight of `taskSettings.approvalFrameFamilies`, not of this bit — <= 7.133.x
9989
+ already had this bit yet answers 400 `request.body_shape` for the key). The value does not change with a declaration. The words are the request key's words.
10151
9990
  streamApproval:
10152
9991
  type: boolean
10153
9992
  description: >
@@ -10324,6 +10163,7 @@ components:
10324
10163
  pluginHooks: { type: boolean, description: 'server >= 7.133.0 — the request `settings.hooks[]` PLUGIN-CONTEXT keys (command entry `plugin { name, root, dataDir?, options? }` / exec-form `args`) are accepted; same predicate as `hooks` (single-user true, multi-tenant false). ABSENT or false = the keys are stripped silently — send only on sight of the bit.' }
10325
10164
  managedHookGroups: { type: boolean, description: 'server >= 7.133.0 — the request `settings.hooks[]` GROUP `source` key (managed lanes `managedPolicy` / `managedPlugin:<name>`) is accepted; same predicate as `hooks`. ABSENT or false = stripped silently (the managed-lane semantics do not apply, it is not a refusal).' }
10326
10165
  tightenOnlySeats: { type: boolean, description: 'server >= 7.133.0, constant true — the four tighten-only request seats `settings.readDenyPatterns` / `settings.writeProtectedExtra` / `settings.complianceAdditionalDenies` / `settings.permissions.neverAuto` are accepted (each seat = deployment table UNION request list, only ever growing; an empty array is the same as not sent). ABSENT = server <= 7.132.x, which answers 400 `request.body_shape` for those keys — send only on sight of the bit.' }
10166
+ approvalFrameFamilies: { type: boolean, description: 'server >= 7.134.0, constant true — the request key `settings.approvalFrameFamilies` (which approval opening-frame families this run''s client renders) is accepted. THIS is the trigger bit for that key, NOT the top-level `approvalFrameFamilies`: servers <= 7.133.x already carry the top-level bit yet answer 400 `request.body_shape` (`unknownKeys: ["settings.approvalFrameFamilies"]`) for the key. ABSENT = do not send it.' }
10327
10167
  settings:
10328
10168
  type: object
10329
10169
  description: >
@@ -11931,7 +11771,8 @@ components:
11931
11771
  # 没声明 ⇒ 键缺席 ⇒ 不再是必有键(此前由 `input` 恒算成布尔,对没声明的条目报 false)。
11932
11772
  # 🔴 server 7.131.0 起 `reasoning` 同样三态:env 车道 `MODEL_REASONING` / `MODEL_VISION` 缺省 true 退役、中心名册条目不写即不声明
11933
11773
  # ⇒ 两键都「未声明 = 键缺席」,`required` 只剩 name / id / provider(sdk 23.1.0;TS 型 `ModelInfo.reasoning?` 早已可选)。
11934
- required: [name, id, provider]
11774
+ # sdk 26.0.0(支持底线 7.52.0):`routePairing` 进 required(≥7.45 恒铸,此前因地板 3.0.0 不进)。
11775
+ required: [name, id, provider, routePairing]
11935
11776
  additionalProperties: false
11936
11777
  properties:
11937
11778
  name: { type: string, description: 'the @mention handle (user-pickable model name).' }
@@ -11955,8 +11796,8 @@ components:
11955
11796
  adjudicated" must stay distinguishable on the wire). Declared here 2026-09-10 alongside `compat`:
11956
11797
  this schema is CLOSED, and the key has been minted unconditionally since 7.45 — so every real
11957
11798
  /v1/models response has been structurally rejected by a strict validator for the whole time (same
11958
- class as `hasBidiControls` on InboxRow). Optional here because the SDK's supported server floor is
11959
- 3.0.0; the discriminant is the PEER'S VERSION, not this key's presence.
11799
+ class as `hasBidiControls` on InboxRow). REQUIRED since sdk 26.0.0 (the support floor 7.52.0 is above 7.45; it
11800
+ was optional only while the floor was 3.0.0).
11960
11801
  compat:
11961
11802
  $ref: '#/components/schemas/ModelCompat'
11962
11803
  description: >-
@@ -12766,19 +12607,6 @@ components:
12766
12607
  startedAt: { type: integer }
12767
12608
  elapsedMs: { type: integer }
12768
12609
  tokens: { type: integer }
12769
- # tokenDir REMOVED (engine ruling 2026-06-28): dead field, 0 producer / 0 consumer — spec caught up 2026-07-26.
12770
- queuedCount:
12771
- type: integer
12772
- description: >-
12773
- 🔴 PHANTOM KEY — NO server version ever emits it (verified against the published 7.87.0 bytes:
12774
- the row type declares it and the fleet route's per-key table forwards it, but NO producer writes it
12775
- — not `publishTask`'s call sites, not core's bg-sink tick, not the durable reconcile leg — and the
12776
- wire contract does not mention it either). The server deleted it as a DEAD KEY (S-459, zero
12777
- behavioural impact since `!== undefined` was always false). It stays declared here only because
12778
- removing an exported field is BREAKING = major-only; it is registered with its deletion condition
12779
- in `test/_keyset-divergence.ts` (`FLEET_TASK_ROW_PHANTOM_KEYS`) and goes with the next major.
12780
- DO NOT read it as "queue depth is already reported" — a permanently absent optional key is a FALSE
12781
- LEAD. Real queue depth needs a NEW key WITH a producer, not this one resurrected.
12782
12610
  awaitingPlanApproval: { type: boolean }
12783
12611
  currentAction: { type: string, description: "The child's most recent tool intent as one human-readable line (UNTRUSTED argument head, redacted server-side)." }
12784
12612
  currentTool:
@@ -12892,6 +12720,13 @@ components:
12892
12720
  failedCount: { type: integer }
12893
12721
  elapsedMs: { type: integer }
12894
12722
  tokens: { type: integer }
12723
+ errorCode:
12724
+ type: string
12725
+ description: >-
12726
+ server >= 7.134.0 — the workflow run's MACHINE code (open set; the same words as the same-named slot on `GET /v1/workflows/{id}`): carried verbatim when the
12727
+ run row has one, the key ABSENT otherwise (never an empty string). A parked workflow run reads `status: "failed"` + `errorCode: "workflow.agent_parked"`
12728
+ (parked, not failed; the `status` vocabulary gains no word). Snapshot rows, `workflow` upsert frames and rows merged from the terminal window share one mint
12729
+ point; a `?session=` subscriber's demoted rows carry it too. ABSENT = an older server, or no code on this row — fall back to `status`.
12895
12730
  FleetFrame:
12896
12731
  description: >
12897
12732
  One decoded frame off GET /v1/fleet/stream (service streamFleet emit set, src/http/routes/fleet.ts). On the
@@ -13105,7 +12940,8 @@ components:
13105
12940
  DISTINCT vocabulary from AgentEvent (the durable log is BLOCK-grained, each frame is a delta to append).
13106
12941
  Resumable (each `id:` = the durable task_event.seq; on 416 the server returns `{retainedFrom}` and the SDK
13107
12942
  resumes from there after a turns full-sync). Known events: meta / block-thinking-delta / block-content-delta
13108
- / tool-call / tool-result / turn / done / error (+ heartbeat keep-alives the SDK swallows). Open set.
12943
+ / tool-call / tool-result / prompt-assembled / cross-boundary-taint (server >= 7.134.0: the device lane's taint row + `seq`, see
12944
+ `Event_cross_boundary_taint`) / turn / done / error (+ heartbeat keep-alives the SDK swallows). Open set.
13109
12945
  NOTE: SSE framing on the wire (event name + JSON data + optional id), not a JSON body.
13110
12946
  required: [event, data]
13111
12947
  additionalProperties: true
@@ -13122,10 +12958,11 @@ components:
13122
12958
  description: >
13123
12959
  契约调和车1 G(2026-08-02;server [2373]C-12, `src/trace/project.ts` + the meta frame at
13124
12960
  `routes/trace-usage.ts`): the frame's own event name, DOUBLE-EMITTED inside `data`. Always
13125
- present on server >= 5.0.0; OPTIONAL here because the SDK's supported floor is server 3.0.0
13126
- (where only the `error` frame carried it). Equal to the SSE `event:` line verbatim — its reason
13127
- to exist is the proxy/relay case that strips the `event:` line and forwards only the data JSON,
13128
- where this is the only key left to dispatch on. Heartbeats (`data: {}`) carry none.
12961
+ present on every named frame of a supported server (>= 7.52.0; minted since server 5.0.0) — the SDK's
12962
+ typed arms declare it REQUIRED since 26.0.0; it is not in this schema's `required` only because the one
12963
+ schema also covers heartbeats (`data: {}`, which carry none and which the SDK swallows). Equal to the SSE
12964
+ `event:` line verbatim — its reason to exist is the proxy/relay case that strips the `event:` line and
12965
+ forwards only the data JSON, where this is the only key left to dispatch on.
13129
12966
  output:
13130
12967
  allOf: [{ $ref: '#/components/schemas/ToolOutput' }]
13131
12968
  description: >
@@ -13528,26 +13365,13 @@ components:
13528
13365
  is RETIRED: the SQL backend rewrote its parked rows once at the 7.117.0 boot to `[{ member: "persisted", entries: [<old value>] }]`, but the
13529
13366
  file backend's pre-upgrade parked rows and run results already on disk (`checkpointGate.riskDescriptor`) keep the old key verbatim
13530
13367
  (text still redacted, no structured disclosure) — a consumer reading those must still tolerate the old string.
13531
- # server 7.16.0 [3684]② — durable 队列行归因两键(与活卡 tool_approval 帧的同名键语义同族、判据不同源;
13532
- # 两条腿对同一只 ask 的在场性可以不一致,两侧都只认在场、都禁把缺席读成否定)。
13533
- ruleSuggestions:
13534
- type: array
13535
- minItems: 1
13536
- items: { $ref: '#/components/schemas/RuleSuggestion' }
13537
- description: >
13538
- server 7.16.0 ([3684]②), ADDITIVE: the rule candidates minted for this ask, replayed off the durable
13539
- row (checkpoint.rule_suggestions). DISPLAY-ONLY on this leg — the durable queue has no persistRule
13540
- redemption; the only redemption path is the LIVE card leg (respond + persistRule). Array order =
13541
- display order; an EXACT entry, when present, is always first. core contract caps candidates at 2;
13542
- the server enforces a tolerance cap of 4 on read-back — lay out for 4. Absent = pre-column row
13543
- (parked before the column existed) OR no candidates; render both as "no candidates", never an error.
13544
13368
  ruleOffers:
13545
13369
  type: array
13546
13370
  minItems: 1
13547
13371
  items: { $ref: '#/components/schemas/RuleOffer' }
13548
13372
  description: >
13549
13373
  server >= 7.46.0 (core 5.58.0, design/375) — the discriminated-union successor of the retired
13550
- `ruleSuggestions` above on the DURABLE queue row (core `PendingAction.ruleOffers`, the same
13374
+ `ruleSuggestions` (servers <= 7.45; sdk 26.0.0 deleted it) on the DURABLE queue row (core `PendingAction.ruleOffers`, the same
13551
13375
  contract as the sync leg's `AskRequest.ruleOffers`). A >= 7.46.0 row carries only the new key;
13552
13376
  pre-5.58 rows fail the new narrow read entry by entry and land as "no supply" (no compatibility
13553
13377
  read — a stale schema means re-ask).
@@ -13772,25 +13596,20 @@ components:
13772
13596
  one-off question. The value comes WHOLE from core (`summarizeCheckpoint` echoes it only when the
13773
13597
  cause is a member of the engine's set), so the server neither recomputes nor re-screens it. See
13774
13598
  ClassifierUnavailable for the open-`cause` rule and the three shapes absence covers.
13775
- RETIRED: server >= 7.72.0 (core 7.14.0 #688) never mints this key on any of the three faces; its
13599
+ RETIRED: server >= 7.72.0 (core 7.14.0) never mints this key on any of the three faces; its
13776
13600
  successor is `disposition.cause` on the gate record (`unavailable` / `parse_error` — a different
13777
- question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0).
13601
+ question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0); the row key retires
13602
+ together with the frame key (floor ≥ 7.106.0, next major); why the frame still declares it: `ToolApprovalFrame.classifierUnavailable`.
13778
13603
  ruleStoreUnreadable:
13779
13604
  type: string
13780
13605
  enum: [store, call]
13781
13606
  description: >-
13782
- server >= 7.72.0 (core 7.14.0 #688 C3), ADDITIVE — WHICH HALF of the rule lane could not be read.
13783
- Present iff `origin === "rule_store_unavailable"`: `store` = the wired rule store could not be read
13784
- (look at the store / network); `call` = the store was read but THIS command could not be read against
13785
- the person's deny/ask rows (look at the command spelling). One `origin` word covers both facts, and
13786
- their recovery verbs live in different places (ops vs prompt). Carried on the live `tool_approval`
13787
- frame and the inbox row; the `card_json` card deliberately does NOT carry it (it travels with `origin`,
13788
- which is not on the card). Absence = `origin` is not that word — NOT an assertion that the rule store
13789
- is healthy.
13790
- RETIRED: server >= 7.106.0 (core 7.34.0 C-R172, BREAKING — wire) never mints this key on either face — an
13791
- unreadable rule store no longer raises a card: that read's rows are SKIPPED by partition and the session
13792
- stream gets the user-facing `engine_notice` `permission.rule_store_unavailable` (`detail.at` = `prepare` /
13793
- `call`). Kept for servers in [7.72.0, 7.106.0) only (sdk K-32); `origin: rule_store_unavailable` itself stays.
13607
+ server >= 7.72.0 (core 7.14.0 #688 C3), ADDITIVE — WHICH HALF of the rule lane could not be read (`store` / `call`).
13608
+ The same key, with the same two words and the same reading, as on the live frame; the authoritative statement of the shape, the presence
13609
+ rule, what the two words mean and the retirement wording is on `ToolApprovalFrame.ruleStoreUnreadable`, and this description points to it.
13610
+ RETIRED: server >= 7.106.0 never mints this key on either face (the frame's description names the successor). Kept for
13611
+ servers in [7.72.0, 7.106.0) only (sdk K-32); the row key retires together with the frame key (floor ≥ 7.106.0, next major);
13612
+ `origin: rule_store_unavailable` itself stays.
13794
13613
 
13795
13614
  InboxList:
13796
13615
  type: object
@@ -14685,26 +14504,6 @@ components:
14685
14504
  parentId: { type: [string, 'null'] }
14686
14505
  type: { type: string, description: Entry discriminator (message / leaf / compaction / …) — opaque to the SDK. }
14687
14506
 
14688
- SyncSnapshot:
14689
- type: object
14690
- description: >
14691
- One E19 file snapshot keyed by a `SessionTreeEntry.id`. `manifest` = `[relPath, blobHash]` tuples; the blob
14692
- BYTES are NOT inlined (they ride the content-addressed blob routes).
14693
- # 封闭(census 批2 第三段,2026-07-30):server 侧类型+铸造点(`session-sync.ts` PULL 出 /
14694
- # `routes/session-sync.ts` PUSH 校验形)都恰是这两键。
14695
- additionalProperties: false
14696
- required: [key, manifest]
14697
- properties:
14698
- key: { type: string }
14699
- manifest:
14700
- type: array
14701
- items:
14702
- type: array
14703
- minItems: 2
14704
- maxItems: 2
14705
- items: { type: string }
14706
- description: A `[relPath, blobHash]` tuple.
14707
-
14708
14507
  SyncAnchor:
14709
14508
  type: object
14710
14509
  description: One E18 resume-at anchor. `owner` is RE-KEYED to the importing principal on a PUSH (§9).
@@ -14732,31 +14531,28 @@ components:
14732
14531
  description: >
14733
14532
  2c session-sync `GET …/sync/manifest` payload (wrapped server-side as `{ manifest }`). The THIN cross-backend
14734
14533
  snapshot: entry IDS (oldest-first, NOT payloads) + the file-history envelope + policy + anchors + leaf.
14735
- 🔴 server >= 7.52 (S-15, design/381; re-read byte for byte against 7.116.0): the key `snapshots` (per-snapshot
14736
- relPath→blobHash, the retired E19 whole-tree snapshot epoch) is NO LONGER SENT — the server's `exportSessionManifest` mints
14737
- `{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` (server >= 7.125.0 adds the session row's two writable bits `title` / `pinned`,
14738
- present-iff the session is a row of the list face — see the two properties) and `fileHistory` (core's versioned
14739
- `FileHistoryExport` envelope; `null` = no history / the source store cannot export) replaces it. <= 7.51 servers still send
14740
- `snapshots` and no `fileHistory`. The blob BYTES never ride the manifest: content-addressed, via the history blob faces
14741
- (`GET …/sync/history/blobs/{hash}` / `PUT …/sync/blobs/{hash}`; probe `capabilities.sessionSyncFileHistory` on BOTH ends).
14742
- The SDK's `manifest()` fills `snapshots: []` when the wire omits it.
14534
+ The server's `exportSessionManifest` mints `{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` (server >= 7.125.0 adds the
14535
+ session row's two writable bits `title` / `pinned`, present-iff the session is a row of the list face — see the two properties). `fileHistory`
14536
+ (core's versioned `FileHistoryExport` envelope; `null` = no history / the source store cannot export) is ALWAYS present on a supported server
14537
+ (>= 7.52.0); the retired E19 whole-tree snapshot key `snapshots` of servers <= 7.51 is below the support floor (sdk 26.0.0 deleted it). The blob BYTES
14538
+ never ride the manifest: content-addressed, via the history blob faces (`GET …/sync/history/blobs/{hash}` / `PUT …/sync/blobs/{hash}`; probe
14539
+ `capabilities.sessionSyncFileHistory` on BOTH ends). The SDK returns the wire verbatim.
14743
14540
  # 封闭(census 批2 第三段,2026-07-30;16.2.0 按 7.116.0 发布字节重核):铸造点 `session-sync.ts`(exportSessionManifest 尾部
14744
14541
  # 字面量)恰是 `{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` 7 键(≥7.52),无 `snapshots`;
14745
14542
  # server ≥7.125.0 另有 `title` / `pinned` 两键(会话行可写位,present-iff 会话是列表面上的行;条件展开,不进 required);
14746
- # ≤7.51 老 server 发 `snapshots`、无 `fileHistory` ⇒ 两键都不进 required(支持底线 3.0.0)。
14543
+ # sdk 26.0.0(支持底线 7.52.0):`fileHistory` 进 required(支持面内恒铸),`snapshots` 删(≤7.51 才发)。
14747
14544
  additionalProperties: false
14748
- required: [sessionId, entryIds, entryCount, leafId, policy, anchors]
14545
+ required: [sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors]
14749
14546
  properties:
14750
14547
  sessionId: { type: string }
14751
14548
  entryIds: { type: array, items: { type: string }, description: Oldest-first, verbatim cross-backend. }
14752
14549
  entryCount: { type: integer, description: '= entryIds.length (the `/sync/entries` trailer count).' }
14753
14550
  leafId: { type: [string, 'null'] }
14754
- snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' }, description: 'server <= 7.51 ONLY (the retired E19 snapshot epoch); server >= 7.52 does not send it — see `fileHistory`.' }
14755
14551
  fileHistory:
14756
14552
  oneOf:
14757
14553
  - { $ref: '#/components/schemas/FileHistoryExport' }
14758
14554
  - { type: 'null' }
14759
- description: 'server >= 7.52: the per-edited-file rewind history envelope; `null` = no history / the source store cannot export; key ABSENT = a <= 7.51 server (that generation sends `snapshots`).'
14555
+ description: 'the per-edited-file rewind history envelope; `null` = no history / the source store cannot export. Always present on a supported server.'
14760
14556
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
14761
14557
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
14762
14558
  title:
@@ -14819,20 +14615,18 @@ components:
14819
14615
  SessionBundle:
14820
14616
  type: object
14821
14617
  description: >
14822
- The portable state of ONE session, ready to replay into another backend. Blobs are NOT inlined (each
14823
- snapshot carries only its `manifest`; the bytes stream through the content-addressed blob routes). The SDK
14824
- assembles this from a manifest + the streamed entries for a PUSH. server >= 7.52: the file history rides `fileHistory`
14825
- (the `FileHistoryExport` envelope) instead of `snapshots`.
14826
- required: [sessionId, entries, snapshots, policy, anchors]
14618
+ The portable state of ONE session, ready to replay into another backend. Blobs are NOT inlined (the file-history
14619
+ envelope carries only blob hashes; the bytes stream through the content-addressed blob routes). The SDK
14620
+ assembles this from a manifest + the streamed entries for a PUSH.
14621
+ required: [sessionId, entries, fileHistory, policy, anchors]
14827
14622
  properties:
14828
14623
  sessionId: { type: string }
14829
14624
  entries: { type: array, items: { $ref: '#/components/schemas/SyncEntry' } }
14830
- snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' } }
14831
14625
  fileHistory:
14832
14626
  oneOf:
14833
14627
  - { $ref: '#/components/schemas/FileHistoryExport' }
14834
14628
  - { type: 'null' }
14835
- description: 'server >= 7.52 file-history envelope (same as `SessionManifest.fileHistory`); key absent = the source is a <= 7.51 server.'
14629
+ description: 'the file-history envelope (same as `SessionManifest.fileHistory`; `null` = none, or the pull did not carry it because the source has no blob faces).'
14836
14630
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
14837
14631
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
14838
14632
 
@@ -15020,8 +14814,9 @@ components:
15020
14814
  <= 7.105.0 answered the synchronous leg 200 + a failed body), `corrupt` (500 — the session rule store
15021
14815
  cannot read its own bytes), `internal.memory_control_plane_corrupt` (500 — the memory control plane refuses
15022
14816
  fail-closed) and `state.scenario_assignment_unknown` (503, server >= 7.119.0 — the center's scenario assignment for the
15023
- principal is dangling; only re-pointing the assignment, or adding the scenario back to the deployment, helps). Every other 5xx
15024
- code keeps the status-based retry.
14817
+ principal is dangling; only re-pointing the assignment, or adding the scenario back to the deployment, helps) and
14818
+ `approvals.read_corrupt` (500, server >= 7.134.0 on `GET /v1/approvals` — a queue row's stored body cannot be read; an operator has to repair or
14819
+ delete the row). Every other 5xx code keeps the status-based retry.
15025
14820
  🔴 `request.host_posture_refused` (422) has TWO triggers (same code, same status — "you wrote it right, this
15026
14821
  deployment will not grant that host posture"): (1) the write-containment face (`permissionMode: bypassPermissions` ⇒
15027
14822
  `writeFace: open`; read `capabilities.taskWriteFaceOpen` first); (2) server >= 7.106.0 / S-895: `inheritEnv: "all"` on a
@@ -15095,7 +14890,7 @@ components:
15095
14890
  S-904): `over_cap` (more than `cap` entries; beside `count` + `cap`) / `bad_entry` (a non-string or empty item; beside
15096
14891
  `index`); server >= 7.132.0 widens that group to every tool-name list on the body, the agent definitions and the two host-path lists, and adds
15097
14892
  `too_long` (a text over its character cap; beside `count` + `cap`) — closed words `over_cap` / `too_long` / `bad_entry` (SDK `RequestFieldRefusalExtra`); server >= 7.133.0 adds `too_large` (a payload over its BYTE cap, only on 413 `request.payload_too_large`: `{ field: "body", reason: "too_large", cap }` — `count` ABSENT when the read stopped at the cap and cannot measure; a request body over 8 MiB is now always that 413) and the `request.invalid_json` reasons `not_json` | `not_object` (400, every JSON-body door; formerly `request.rejected`/`request.body_shape` by door).
15098
- On 409 refusals of a resume (server >= 7.132.0): the engine's `CheckpointErrorReason` arm (`detail.reason` passed through; see `blockedBy`). On 400 `rules.inert_entry` (server >= 7.129.0): the engine's `ALLOW_ENTRY_INERT_REASONS` (`tool_name` / `wildcard_all` / `namespace_wildcard` / `content_unsupported` / `grammar` / `near_miss` / `earlier_grammar`). Out-of-vocabulary word ⇒ treat as unknown. (Other codes carry their own `reason` words.)
14893
+ On 409 refusals of a resume (server >= 7.132.0) and on 422 `parked_resume.startup_failed` (server >= 7.134.0 — the parked background agent's pre-claim failure; absent when the engine gave none): the engine's `CheckpointErrorReason` arm (`detail.reason` passed through; see `blockedBy`). On 409 `conflict.checkpoint_ctx_unsealable` (server >= 7.134.0): why the request's credentials cannot be sealed into, or unsealed from, the stored resume context — a closed five-word set (SDK `CHECKPOINT_CTX_UNSEALABLE_REASONS`); which doors can answer which word is written once, in the SDK documentation of `CHECKPOINT_CTX_UNSEALABLE_REASONS`, not here: `no_key_store` (this server has no sealed-key store) / `key_not_held` (sealed with a key this server does not hold — another replica's, or a replaced sealed-key directory) / `unopenable` (the sealed value is damaged or belongs to another session) / `plaintext` (an older server stored it unsealed) / `unrecognized_seal` (a seal format this version does not know) — the way out depends on the door and the `reason`: `key_not_held` (a resume door) — have the operator restore the sealed-key directory that sealed it and restart that replica (it reads its keys only at startup), then resend the same request; `no_key_store` on the submit door — give the service a writable sealed-key directory (`<data root>/sealed-key/`, read at startup), or resubmit WITHOUT those credentials — resending the same submit body is refused again; `no_key_store` on a resume door — the same way out as `key_not_held`: restore the directory that sealed it and restart, then resend (a fresh empty directory does not help: it holds no such key, so the next read says `key_not_held`); `unopenable` / `plaintext` / `unrecognized_seal` — submit the work as a new task (cancel the still-suspended one first); never auto-retry, never match the sentence. A resume door's refusal decides nothing and starts no resume (the card stays pending; the known exception is a terminal run's steer race arm, which first opens a wake card and, when the second read fails for any of the five reasons, withdraws it and then answers 409 — if the withdrawal fails the server only logs it); a submit door's refusal lands before the task runs (nothing runs; a run row already opened for it is closed as `failed` and its idempotency key released, both best-effort — a failed step is only logged by the server and the answer is still the 409). On 400 `rules.inert_entry` (server >= 7.129.0): the engine's `ALLOW_ENTRY_INERT_REASONS` (`tool_name` / `wildcard_all` / `namespace_wildcard` / `content_unsupported` / `grammar` / `near_miss` / `earlier_grammar`). Out-of-vocabulary word ⇒ treat as unknown. (Other codes carry their own `reason` words.)
15099
14894
  retryAfterSec:
15100
14895
  type: number
15101
14896
  description: >-
@@ -15122,6 +14917,10 @@ components:
15122
14917
  `null` and `""` count), the refusal landed before any decision, and `field` is the literal `settledBy`; the three doors now answer
15123
14918
  the SAME sentence. Every such 400 carries it, even when another defect in the same body was hit first (the sentence then names that
15124
14919
  defect). Branch on the code plus `field`, never on the sentence; a bad body WITHOUT that key carries no `field`. SDK: `err.extra.field`.
14920
+ server >= 7.134.0: on 409 `conflict.checkpoint_ctx_unsealable` `field` is the SUBMIT-BODY PATH of the credential slot that cannot be sealed or unsealed (arrays
14921
+ `[i]`, record members `.key`: `mcpServers[0].transport.headers.Authorization`, `settings.hooks.PreToolUse[0].hooks[0].headers.Authorization`; the key
14922
+ name is not a secret, the value never enters the refusal); on 400 `request.field_invalid` for the request key `settings.approvalFrameFamilies` it is
14923
+ that literal (with `reason: "bad_entry"` and the bad `index`; no `index` for a non-array).
15125
14924
  detail:
15126
14925
  description: >
15127
14926
  🔴 SHAPES differ by code — branch on `field` (or the `errorCode`), never guess from the type of `detail`. On 503
@@ -15256,8 +15055,9 @@ components:
15256
15055
  callerTrust: { type: string }
15257
15056
  - type: object
15258
15057
  description: >-
15259
- server >= 7.133.0 — 400 `request.field_conflict`: the request body contradicts itself. `kind` = which constraint (a CLOSED fourteen-word set, read open: cascade_verify | image_profile | resume_anchor |
15260
- system_prompt_fold | file_history_restore | skills_disabled | existing_session | scratchpad_root | ask_answer | policy_person_ask | plan_edit | plan_mode_after | bound_call_door | remember_decision);
15058
+ server >= 7.133.0 — 400 `request.field_conflict`: the request body contradicts itself. `kind` = which constraint (a CLOSED set — fourteen words, fifteen from server >= 7.134.0 — read open: cascade_verify | image_profile | resume_anchor |
15059
+ system_prompt_fold | file_history_restore | skills_disabled | existing_session | scratchpad_root | ask_answer | policy_person_ask | plan_edit | plan_mode_after | bound_call_door | remember_decision |
15060
+ approval_feedback (server >= 7.134.0: `feedback` on a deny — fields `["feedback", "decision"]` — or on a policy decision — `["feedback", "decidedBy"]`));
15261
15061
  `fields` = the named body coordinates, subject first (at least one; a dynamic index reads `agents[1].skills`); `count` / `cap` only on the fold-cap kind (the folded total and its cap).
15262
15062
  required: [kind, fields]
15263
15063
  additionalProperties: false
@@ -15303,6 +15103,8 @@ components:
15303
15103
  the engine's `detail.code` passes through as `blockedBy` (today only `checkpoint.config_refused` carries it — the closed set is core's
15304
15104
  `CHECKPOINT_CONFIG_REFUSAL_CODES`, 40 words since core 7.53.0; read it as "the errorCode a fresh run would report for the same bad configuration"),
15305
15105
  next to `field` and `reason`, absent keys not minted (SDK `ResumeRefusalExtra`).
15106
+ server >= 7.134.0 / core 7.54.0: the SAME three keys on 422 `parked_resume.startup_failed` (a parked background agent's leg refused before the claim — the
15107
+ engine now hands the failure's code / field / reason to the redemption receipt; absent when the engine gave none; <= 7.133.x carried only `cause` there).
15306
15108
 
15307
15109
  ResumeLegConflictError:
15308
15110
  description: >
@@ -15386,6 +15188,9 @@ components:
15386
15188
  # 🔴 census 轴一(2026-07-30)补的 4 臂:server 今天就真发,spec/SDK 两侧此前同缺(详见各 schema 处的注)。
15387
15189
  - $ref: '#/components/schemas/Event_context_usage'
15388
15190
  - $ref: '#/components/schemas/Event_config_assembled'
15191
+ # server >= 7.134.0 (core 7.54.0): the device lane's cross-boundary taint row — DURABLE only (the `GET /v1/runs/{taskId}/events` ledger replay), never
15192
+ # on the live stream and never on the `GET /v1/runs/{taskId}` poll object; the trace SSE twin is the `cross-boundary-taint` frame.
15193
+ - $ref: '#/components/schemas/Event_cross_boundary_taint'
15389
15194
  - $ref: '#/components/schemas/Event_needs_review'
15390
15195
  - $ref: '#/components/schemas/Event_message_committed'
15391
15196
  # [2373]A-2(2026-08-03,server 5.0.0):compaction_outcome 三腿同源批的 spec 半场(SDK 臂 20867e1 同批)。
@@ -15464,6 +15269,7 @@ components:
15464
15269
  failed: '#/components/schemas/Event_failed'
15465
15270
  context_usage: '#/components/schemas/Event_context_usage'
15466
15271
  config_assembled: '#/components/schemas/Event_config_assembled'
15272
+ cross_boundary_taint: '#/components/schemas/Event_cross_boundary_taint'
15467
15273
  needs_review: '#/components/schemas/Event_needs_review'
15468
15274
  message_committed: '#/components/schemas/Event_message_committed'
15469
15275
  compaction_outcome: '#/components/schemas/Event_compaction_outcome'
@@ -15690,10 +15496,10 @@ components:
15690
15496
  # 每只闭集 = 引擎今天的词 ∪ 引擎退役词表里那一集。这五张 enum 与引擎 `RETIRED_MEMBERS` 逐集逐词同(由 `server-7129-wire` 对拍);卡面 / 队列行 / 请求面的同名闭集只取今天的词。
15691
15497
  GateRecordAskClass:
15692
15498
  type: string
15693
- enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary, write_protection_unresolved, governance_unavailable]
15499
+ enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, cross_boundary, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary, write_protection_unresolved, governance_unavailable]
15694
15500
  description: >-
15695
15501
  The class word on a GATE RECORD (`tool_end.gate.disposition.askClass` / `.classes` on the mode-refusal arm, `ModeRelease.askClass` / `.classes`) — server >= 7.129.0 reads the record as an ARCHIVE:
15696
- today's `AskClass` set (twenty-five words) PLUS the words the engine retired (`write_protection_unresolved`, retired in core 7.49.0 — renamed `target_unresolved`; `governance_unavailable`,
15502
+ today's `AskClass` set (twenty-six words; `cross_boundary` from server >= 7.134.0) PLUS the words the engine retired (`write_protection_unresolved`, retired in core 7.49.0 — renamed `target_unresolved`; `governance_unavailable`,
15697
15503
  retired in core 7.51.0 — merged into `rule_unreadable`). A record the previous engine generation legally wrote is replayed byte for byte (a 7.50.x mode refusal can carry `governance_unavailable`);
15698
15504
  a word NO engine generation ever minted (a case variant, the empty string, a future word) withholds the whole `gate` and counts `server.trace.gate-outcome-defective`.
15699
15505
  Render a known archive word normally and an unknown one generically; never drop the record because the word is not in today's set.
@@ -15711,9 +15517,9 @@ components:
15711
15517
  OMITTED `reason` for the two retired words (`released` was still sent); from 7.129.0 they arrive verbatim on the replay faces. Same reading as `GateRecordAskClass`.
15712
15518
  GateRecordPersistedRuleMandate:
15713
15519
  type: string
15714
- enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved, write_protection_unresolved]
15520
+ enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved, cross_boundary_taint, write_protection_unresolved]
15715
15521
  description: >-
15716
- `released.mandate` on a GATE RECORD (archive read, server >= 7.129.0): today's six `PersistedRuleMandate` words PLUS the retired `write_protection_unresolved` (core 7.49.0 rename). Same reading as `GateRecordAskClass`.
15522
+ `released.mandate` on a GATE RECORD (archive read, server >= 7.129.0): today's seven `PersistedRuleMandate` words (`cross_boundary_taint` from server >= 7.134.0) PLUS the retired `write_protection_unresolved` (core 7.49.0 rename). Same reading as `GateRecordAskClass`.
15717
15523
  GateRecordAskOrigin:
15718
15524
  type: string
15719
15525
  enum: [content_question, plan_review, ancestor_marked, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy, unresolvable]
@@ -15944,9 +15750,10 @@ components:
15944
15750
  `UNATTENDED_PERMISSION_MODE=dontAsk`).
15945
15751
  AskClass:
15946
15752
  type: string
15947
- enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
15753
+ enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, cross_boundary, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
15948
15754
  description: >
15949
- core `ASK_CLASSES` (TWENTY-FIVE words as of core 7.51.0 / server >= 7.129.0: `governance_unavailable` was merged into `rule_unreadable`, no alias — sdk 23.0.0; a stored card / parked row written by server <= 7.128.x is rewritten once by the server at boot. The gate record reads the ARCHIVE form, see `GateRecordAskClass`. History: twenty-two words as of core 7.40.0, twenty-three as of core 7.41.0, twenty-four as of core 7.43.0, twenty-five as of core 7.45.0, twenty-six as of core 7.49.0 — 7.34.0 minted twenty; `unverifiable_shell_text` joined in 7.49.0 #1505 and rides server >= 7.126.0, where the
15755
+ core `ASK_CLASSES` (TWENTY-SIX words as of core 7.54.0 / server >= 7.134.0: `cross_boundary` joined after `requires_user_interaction` — the device lane's
15756
+ cross-boundary confirmation, see below; TWENTY-FIVE as of core 7.51.0 / server >= 7.129.0: `governance_unavailable` was merged into `rule_unreadable`, no alias — sdk 23.0.0; a stored card / parked row written by server <= 7.128.x is rewritten once by the server at boot. The gate record reads the ARCHIVE form, see `GateRecordAskClass`. History: twenty-two words as of core 7.40.0, twenty-three as of core 7.41.0, twenty-four as of core 7.43.0, twenty-five as of core 7.45.0, twenty-six as of core 7.49.0 — 7.34.0 minted twenty; `unverifiable_shell_text` joined in 7.49.0 #1505 and rides server >= 7.126.0, where the
15950
15757
  unresolved-write-target class was also RENAMED to `target_unresolved` (#955; zero alias — the previous spelling is in the SDK README's 22.0.0 migration table); `shell_floor` joined in 7.39.0 #1260 and rides
15951
15758
  server >= 7.113.0; `unstated` joined in 7.40.0 and rides server >= 7.115.0; `requires_user_interaction` joined in 7.41.0 and rides
15952
15759
  server >= 7.116.0; `classifier_no_ruling` joined in 7.43.0 #1353 and rides server >= 7.118.0; `dangerous_delete` joined in 7.45.0 and rides
@@ -17164,6 +16971,16 @@ components:
17164
16971
  pathTarget: { $ref: '#/components/schemas/McpToolPathTarget' }
17165
16972
  ruleFace: { $ref: '#/components/schemas/McpToolRuleFace' }
17166
16973
  renderHints: { $ref: '#/components/schemas/McpToolRenderHints' }
16974
+ boundaryLegs:
16975
+ description: >-
16976
+ core 7.54.0 / server >= 7.134.0 (a REQUIRED engine member: every row of `wiring_manifest.tools.entries[]` and of a `tool_roster_delta` frame's
16977
+ `delta.roster.entries[]` carries it) — the LEGS a call of this tool travels (core `BOUNDARY_LEGS`, in that order: `env` = inside the execution
16978
+ environment / on the device, `outside_read` = a read beyond it, `outside_write` = a write beyond it), or `per_call` when its dispatch gives the legs
16979
+ per call (the engine's own definitions and the protocol tools state them; every other row follows its `effect`). The device lane's launch gate reads
16980
+ it. ABSENT = an older server (<= 7.133.x).
16981
+ oneOf:
16982
+ - { type: string, const: per_call }
16983
+ - { type: array, items: { type: string, enum: [env, outside_read, outside_write] } }
17167
16984
  McpToolPathTarget:
17168
16985
  type: object
17169
16986
  required: [param, access]
@@ -17865,44 +17682,6 @@ components:
17865
17682
  governance shell gate sits at the "classify" tier, a shell ask may come from the classifier or from
17866
17683
  another gate; the server leaves the key absent rather than guessing). Never render absence as
17867
17684
  "this gate can be bypassed by a posture".
17868
- ruleSuggestions:
17869
- type: array
17870
- minItems: 1 # 空数组不可能出现:server 的合取项②要求 `req.ruleSuggestions` **非空**,否则整键省略(缺席 = 本卡无此选项)
17871
- # 刻意**不**声明 maxItems:server 的 `.max(4)` 是**卡** schema(`ApprovalCardSchema`)上的约束,这条
17872
- # live 帧腿没有它。在这里抄一个上界 = 声明一条 server 并不执行的约束(照 spec 生成的严格客户端会
17873
- # 拒收一条合法帧)。数量上界的真源是引擎:core 5.22.0 的 `suggestRulesForCommand` **至多铸一条,且恒
17874
- # `match: "exact"`**(不写死上界是给将来的 miner 留位,不是暗示今天会有更多)。
17875
- items: { $ref: '#/components/schemas/RuleSuggestion' }
17876
- description: >
17877
- server #154 车二 (core 5.18.0, design/179), ADDITIVE, "tool_approval" only: the rule candidates
17878
- the ENGINE minted for this ask. Render them as the "don't ask again" options and echo the chosen
17879
- one back as `persistRule.rule` on POST /v1/tool-approvals/{id}/respond.
17880
-
17881
- 🔴 PRESENCE IS THE PROMISE: the key is present ONLY on a deployment that actually has a
17882
- permission-rule store wired (the same object behind the engine's `permissionRules.storeWired`
17883
- manifest bit). A deployment without one OMITS it rather than offering an option with nowhere to
17884
- redeem — an unredeemable "don't ask again" is a wire lie, worse than absence.
17885
-
17886
- 🔴 THE CONVERSE DOES NOT HOLD: absence means "THIS CARD has no redeemable candidate", and three
17887
- causes produce it — no rule store wired, the engine minted no candidate for this command (compound /
17888
- redirect / substitution), or the command bytes were unreadable. One frame cannot tell them apart, so
17889
- never diagnose the deployment's rule lane from a single absence, and never synthesize a candidate.
17890
-
17891
- 📏 HOW MANY, TODAY: core 5.22.0's miner (`suggestRulesForCommand`) emits AT MOST ONE candidate and
17892
- always `match: "exact"` — no shipped producer mints the broader prefix option. `prefix` stays in the
17893
- closed vocabulary because the wire validator accepts it and a later miner may emit it; render what
17894
- arrives, but do NOT build a UI that assumes a narrow/broad PAIR is on offer.
17895
-
17896
- 🔴 The values are the engine's VERBATIM (closed `match` vocabulary + engine-minted text); the
17897
- server neither re-mints nor re-orders them. A hand-built rule string is refused at redemption
17898
- (`ruleRefusal: "rule_not_offered"`) — by construction, so a click on a harmless command cannot be
17899
- turned into a rule for something else.
17900
-
17901
- ⚠️ REDEMPTION SCOPE: the key also rides the durable card replay, but its only redemption channel is
17902
- the LIVE respond leg keyed by this frame's `approvalId` — an in-process map on ONE replica. Same
17903
- replica ⇒ redeemable; after a restart / on another replica that `approvalId` 404s and the person
17904
- must use the durable decide leg, which carries NO rule field in v1 (its body is strict — an extra
17905
- `persistRule` is a loud 400, not a silent drop).
17906
17685
  ruleOffers:
17907
17686
  type: array
17908
17687
  minItems: 1
@@ -17910,8 +17689,8 @@ components:
17910
17689
  description: >
17911
17690
  server >= 7.46.0 (core 5.58.0, design/375), "tool_approval" only — the ENGINE-minted "don't ask
17912
17691
  again" OPTIONS for this ask, as a DISCRIMINATED UNION (`single` | `batch`). It is the BREAKING
17913
- REPLACEMENT of `ruleSuggestions` above: a >= 7.46.0 engine mints the old key nowhere, there is no
17914
- alias, and the two keys are declared side by side only so one type surface covers both generations.
17692
+ REPLACEMENT of the triple-shaped `ruleSuggestions` of servers <= 7.45: a >= 7.46.0 engine mints the old key nowhere and there is no
17693
+ alias (sdk 26.0.0 deleted the old key with the support floor 7.52.0).
17915
17694
 
17916
17695
  🔴 READ `kind` TO TELL THE ARMS APART, never a positional assumption. ORDER IS CONTRACT: a
17917
17696
  whole-string exact `single` is index 0 when present and a `batch` is always last; core's own
@@ -17926,23 +17705,15 @@ components:
17926
17705
  suppress its persistence actions entirely (core's normative clause) — a compacted index means the
17927
17706
  k-th option the person clicked is not the k-th rule the server redeems.
17928
17707
 
17929
- 🔴 PRESENCE IS THE PROMISE (same as `ruleSuggestions`): present only where a permission-rule store
17708
+ 🔴 PRESENCE IS THE PROMISE: present only where a permission-rule store
17930
17709
  is really wired. The converse does not hold — a wired deployment still omits it for mandated /
17931
17710
  requiresRealApproval / allow_outranked / ask_rule_matched / hook-produced / inherited-unresolved / ancestor-resolved /
17932
17711
  anonymous asks. Absence reads as "THIS CARD has no such option", never as "this deployment has no
17933
17712
  rule lane" (that is `capabilities.permissionRules`) and never as "your rule broke" (that is
17934
- `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x).
17713
+ `allowRuleShadowed`).
17935
17714
 
17936
17715
  ⚠️ REDEMPTION SCOPE is the live respond leg keyed by this frame's `approvalId` only; a replayed
17937
17716
  offer on another replica is not a promise that it is still redeemable.
17938
- persistedRuleShadowed:
17939
- type: string
17940
- description: >-
17941
- server >= 7.13.0 (core 5.25.0 #144), ADDITIVE, "tool_approval" only — this ask MATCHED one of the caller's persisted allow
17942
- rules and that rule COULD NOT silence it; the value is the rule's VERBATIM text (secret-redacted + length-bounded; UNTRUSTED
17943
- for display, like `message`). Absent != "you have no rule"; deliberately separate from `governanceForced`.
17944
- RETIRED: server >= 7.117.0 (core 7.42.0, BREAKING rename, no alias) never mints this key on any face; its successor is
17945
- `allowRuleShadowed` (all five allow-layer members, not just the rule store). Kept for servers in [7.13.0, 7.117.0) only (sdk 16.3.0).
17946
17717
  allowRuleShadowed:
17947
17718
  type: array
17948
17719
  items: { $ref: '#/components/schemas/AllowRuleShadow' }
@@ -18220,17 +17991,18 @@ components:
18220
17991
  classifierUnavailable:
18221
17992
  $ref: '#/components/schemas/ClassifierUnavailable'
18222
17993
  description: >-
18223
- server >= 7.69.0 (core 7.10.0 #616), ADDITIVE, present only when the classifier was consulted and
17994
+ server >= 7.69.0 (core 7.10.0), ADDITIVE, present only when the classifier was consulted and
18224
17995
  could not run — WHY this ask reached a human. See ClassifierUnavailable for the open-`cause` rule and
18225
17996
  the three shapes absence covers.
18226
- RETIRED: server >= 7.72.0 (core 7.14.0 #688) never mints this key on any of the three faces; its
17997
+ RETIRED: server >= 7.72.0 (core 7.14.0) never mints this key on any of the three faces; its
18227
17998
  successor is `disposition.cause` on the gate record (`unavailable` / `parse_error` — a different
18228
- question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0).
17999
+ question, so an old switch does not port). Kept on the frame because servers 7.69–7.71 are inside the supported range and still
18000
+ send it, and the closed inbox row declares the same key (frame and row read the same); it goes with the next major once the support floor is >= 7.106.0.
18229
18001
  ruleStoreUnreadable:
18230
18002
  type: string
18231
18003
  enum: [store, call]
18232
18004
  description: >-
18233
- server >= 7.72.0 (core 7.14.0 #688 C3), ADDITIVE — WHICH HALF of the rule lane could not be read.
18005
+ server >= 7.72.0 (core 7.14.0), ADDITIVE — WHICH HALF of the rule lane could not be read.
18234
18006
  Present iff `origin === "rule_store_unavailable"`: `store` = the wired rule store could not be read
18235
18007
  (look at the store / network); `call` = the store was read but THIS command could not be read against
18236
18008
  the person's deny/ask rows (look at the command spelling). One `origin` word covers both facts, and
@@ -18238,10 +18010,11 @@ components:
18238
18010
  frame and the inbox row; the `card_json` card deliberately does NOT carry it (it travels with `origin`,
18239
18011
  which is not on the card). Absence = `origin` is not that word — NOT an assertion that the rule store
18240
18012
  is healthy.
18241
- RETIRED: server >= 7.106.0 (core 7.34.0 C-R172, BREAKING — wire) never mints this key on either face — an
18242
- unreadable rule store no longer raises a card: that read's rows are SKIPPED by partition and the session
18243
- stream gets the user-facing `engine_notice` `permission.rule_store_unavailable` (`detail.at` = `prepare` /
18244
- `call`). Kept for servers in [7.72.0, 7.106.0) only (sdk K-32); `origin: rule_store_unavailable` itself stays.
18013
+ RETIRED: server >= 7.106.0 (core 7.34.0, BREAKING — wire) never mints this key on either face — an
18014
+ unreadable rule store no longer raises a card; the signal moved to the session stream's user-facing
18015
+ `engine_notice` `permission.rule_store_unavailable` (`detail.at` = `prepare` / `call`; what the run does while the rules cannot be read is
18016
+ that notice's own text). Kept on the frame because servers 7.72–7.105 are inside the supported range and still send it, and the closed inbox row
18017
+ declares the same key; it goes with the next major once the support floor is >= 7.106.0 (`origin: rule_store_unavailable` itself stays).
18245
18018
  readRootCandidate:
18246
18019
  type: object
18247
18020
  required: [dir, clearsThisAsk]
@@ -18395,33 +18168,15 @@ components:
18395
18168
  posture. Semantics, the ABSENT != false clause and the discrimination boundary are stated verbatim on
18396
18169
  ToolApprovalFrame.governanceForced. It sits at the card's top level rather than inside `risk` because
18397
18170
  `risk` describes the ENGINE's judgement of the call, while this key says WHO imposed the gate.
18398
- ruleSuggestions:
18399
- type: array
18400
- minItems: 1 # 同帧腿:空数组不可能出现(素材同源),缺席才是「本卡无此选项」
18401
- maxItems: 4 # 卡形上**真有**这条上界(server `ApprovalCardSchema` 的 `.max(4)`);live 帧腿没有,故只写在这里
18402
- items: { $ref: '#/components/schemas/RuleSuggestion' }
18403
- description: >
18404
- ADDITIVE (#154 车二 / core 5.18.0 design/179): the rule candidates the ENGINE minted for this ask —
18405
- the card's "don't ask again" options; echo the chosen one back as `persistRule.rule`. Semantics,
18406
- the PRESENCE-IS-THE-PROMISE clause (present only where a rule store is wired) and the redemption
18407
- scope are stated verbatim on ToolApprovalFrame.ruleSuggestions — the live frame, the stored
18408
- `card_json` and the replayed frame carry the SAME material.
18409
18171
  ruleOffers:
18410
18172
  type: array
18411
18173
  minItems: 1
18412
18174
  items: { $ref: '#/components/schemas/RuleOffer' }
18413
18175
  description: >
18414
- server >= 7.46.0 (core 5.58.0, design/375) — the discriminated-union replacement of
18415
- `ruleSuggestions` above. Same material as the live frame and the stored `card_json` (the server
18176
+ server >= 7.46.0 (core 5.58.0, design/375) — the discriminated-union replacement of the retired
18177
+ `ruleSuggestions` (servers <= 7.45; sdk 26.0.0 deleted it). Same material as the live frame and the stored `card_json` (the server
18416
18178
  projects all three through one `copyRuleOffer`), so semantics, order-is-contract, presence-is-the-
18417
18179
  promise and the per-leg index rules are stated verbatim on ToolApprovalFrame.ruleOffers.
18418
- persistedRuleShadowed:
18419
- type: string
18420
- description: >-
18421
- server >= 7.13.0 (core 5.25.0 #144), ADDITIVE — the persisted rule this ask matched but which could not silence it; frame and
18422
- card carry the SAME material (see ToolApprovalFrame.persistedRuleShadowed).
18423
- RETIRED: server >= 7.117.0 (core 7.42.0) never mints this key on any face (its strict card schema even refuses a stored card
18424
- carrying it); its successor is `allowRuleShadowed`. Kept for servers in [7.13.0, 7.117.0) only (sdk 16.3.0).
18425
18180
  allowRuleShadowed:
18426
18181
  type: array
18427
18182
  items: { $ref: '#/components/schemas/AllowRuleShadow' }
@@ -18456,35 +18211,13 @@ components:
18456
18211
  classifierUnavailable:
18457
18212
  $ref: '#/components/schemas/ClassifierUnavailable'
18458
18213
  description: >-
18459
- server >= 7.69.0 (core 7.10.0 #616), ADDITIVE — WHY this ask reached a human. Frame and card carry
18460
- the SAME value (one narrow-read function server-side). See ClassifierUnavailable for the open-`cause`
18461
- rule and the three shapes absence covers.
18462
- RETIRED: server >= 7.72.0 (core 7.14.0 #688) never mints this key on any of the three faces; its
18214
+ (minted by servers [7.69.0, 7.72.0), core 7.10.0 #616) WHY this ask reached a human. See ClassifierUnavailable for the open-`cause` rule and the three
18215
+ shapes absence covers.
18216
+ RETIRED: server >= 7.72.0 (core 7.14.0) never mints this key on any of the three faces; its
18463
18217
  successor is `disposition.cause` on the gate record (`unavailable` / `parse_error` — a different
18464
- question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0).
18465
-
18466
- RuleSuggestion:
18467
- type: object
18468
- description: >
18469
- One ENGINE-minted rule candidate (#154 车二 / core 5.18.0 `RuleSuggestion`) — what a single click on
18470
- "don't ask again" would persist.
18471
-
18472
- 🔴 THE TEXT COMES FROM THE ENGINE, NOT THE SHELL: the server derives it from the adjudicated command's
18473
- own bytes; a client may only echo one of these back verbatim (`persistRule.rule`). Reporting text that
18474
- is not among the candidates is refused (`ruleRefusal: "rule_not_offered"`) rather than guessed at, so
18475
- "click on a harmless command, get a rule for something else" is not spellable on this path.
18476
- # 封闭:core `RuleSuggestion` 恰三键(rule/match/command),server 逐字透传不重铸。
18477
- additionalProperties: false
18478
- required: [rule, match, command]
18479
- properties:
18480
- rule: { type: string, maxLength: 512, description: 'The canonical rule text to echo back on redemption (server MAX_RULE_TEXT_CHARS = 512).' }
18481
- match:
18482
- type: string
18483
- enum: [exact, prefix]
18484
- description: '`exact` = this one command only (`Bash(git status)`); `prefix` = word-boundary prefix (`Bash(git status:*)`). Closed vocabulary — an unknown value is not a candidate this contract describes; do not render it as redeemable.'
18485
- command: { type: string, maxLength: 512, description: 'The command pattern inside the rule, for a shell that would rather not re-parse `rule`.' }
18218
+ question, so an old switch does not port). It stays declared on the CARD because the pinned server's card schema (`ApprovalCardSchema`, server 7.134.0) still accepts it: a still-pending `card_json` row a
18219
+ 7.69–7.71 server wrote replays verbatim onto `approval_request.card` / the replay frame. (The FRAME's same-named key stays declared too; why, and until when: `ToolApprovalFrame.classifierUnavailable`.)
18486
18220
 
18487
- # ─── S-139(sdk 8.3.0):对 server 7.60.0 整体重对账带进来的四张型面 ───────────────────────
18488
18221
  ClassifierUnavailable:
18489
18222
  # 8.9.0:三面(tool_approval 帧 / ApprovalCard / InboxRow)同形同值 ⇒ 具名单源,内联三份会各自漂。
18490
18223
  type: object
@@ -18599,7 +18332,7 @@ components:
18599
18332
  `ApprovalCard.ruleOffersAbsence`; server >= 7.55.0 / core #490 fix ②, S-15). The engine names which
18600
18333
  door is shut: `mandated` = this ask must be confirmed every time (🔴 this arm must NOT point the user
18601
18334
  at writing a rule — a rule would not silence it, and saying so sends them into a loop); `allow_outranked` = an
18602
- allow-layer member matched this call but the ask sits at a higher tier, so another allow rule cannot clear it (the machine-readable twin of `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x);
18335
+ allow-layer member matched this call but the ask sits at a higher tier, so another allow rule cannot clear it (the machine-readable twin of `allowRuleShadowed`);
18603
18336
  `ask_rule_matched` = an explicit `ask` rule matched this call (same frame `origin: ask_rule`): it keeps asking, an allow rule would not silence it either; a card from the deployment name-list gate
18604
18337
  `APPROVAL_REQUIRE` / the supervisor route that reports an explicit ask-rule hit lands here too; `lane_cannot_speak` = a rule COULD clear this ask but the lane cannot speak this time (the engine could mint no coverable form for this command).
18605
18338
  🔴 sdk 23.0.0 BREAKING (server >= 7.129.0 / engine 7.51.0, `RULE_OFFERS_ABSENCES`): the former `shadowed` meant two things and is split into the middle two words, no alias; a card / parked row that a server <= 7.128.x
@@ -18634,10 +18367,12 @@ components:
18634
18367
  `READ_ROOT_COVERS` and its `isApprovalRequestFrameV1` judges `covers` by membership in it; a source-level anchor reads the word off the server's minting site and the engine's type.
18635
18368
  PersistedRuleMandate:
18636
18369
  type: string
18637
- enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved]
18370
+ enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved, cross_boundary_taint]
18638
18371
  description: >
18639
18372
  WHICH WORD a mandated ask stands on (server >= 7.102.0 / S-721 / core 7.32.0 #1092) — core's
18640
- `PERSISTED_RULE_MANDATES` six words, transcribed verbatim and in order (core 7.32.1
18373
+ `PERSISTED_RULE_MANDATES` (seven words since core 7.54.0 / server >= 7.134.0: `cross_boundary_taint` — the run executes on a device outside the deployment's
18374
+ trust boundary and the deployment is multi-tenant, so an org-side write, or after an off-target read a call bound for the device, is confirmed by the deployment's
18375
+ approver per call; no standing allow rule and no automatic lane answers it; core puts it at the HEAD of the mandate ladder, the array keeps core's constant order), transcribed verbatim and in order (core 7.32.1
18641
18376
  `dist/core/ask-origin.d.ts:349`; the vocabulary's owner is core; the sixth word is `target_unresolved` from core 7.49.0 #955 / server >= 7.126.0 —
18642
18377
  server 7.122.0–7.125.x sent the previous spelling, which reads as out of set here, see the SDK README's 22.0.0 migration table). Rides FOUR faces with one value: the
18643
18378
  `tool_approval` frame, the `livePending` row top level, the operator queue row (`GET /v1/approvals`
@@ -18691,9 +18426,9 @@ components:
18691
18426
  RuleOfferMatch:
18692
18427
  type: string
18693
18428
  description: >
18694
- The MATCH FORM of a persisted rule — the server's `PERSISTED_RULE_MATCHES`, verbatim and in order (the vocabulary
18695
- is OWNED BY CORE; the server only transcribes it and pins set equality, so core adding a member turns
18696
- the server red at compile time). ONE vocabulary for two faces: a card's rule offer (`RuleOffer.match`, the
18429
+ The MATCH FORM of a persisted rule — core's `PERSISTED_RULE_MATCHES`, verbatim and in order (the vocabulary is OWNED BY CORE: a package-root
18430
+ value since core 7.54.0, which server >= 7.134.0 reads directly instead of keeping its own transcription; earlier servers transcribed it and pinned set
18431
+ equality). ONE vocabulary for two faces: a card's rule offer (`RuleOffer.match`, the
18697
18432
  server's card schema validates it with this very enum) and a persisted rule row (`GET /v1/rules`).
18698
18433
 
18699
18434
  `exact` = this one command only (`Bash(git status)`). `prefix` = the historical colon-star
@@ -18896,8 +18631,7 @@ components:
18896
18631
  maxLength: 512
18897
18632
  description: >
18898
18633
  With `edited` absent or false this is the CANDIDATE ARM: echo back, VERBATIM, the `rule` of the
18899
- chosen `kind: "single"` entry of the frame's `ruleOffers` (or, against a <= 7.45 server, of
18900
- `ruleSuggestions`). Do not hand-build it, re-case it or append `:*` — the server locates it by
18634
+ chosen `kind: "single"` entry of the frame's `ruleOffers`. Do not hand-build it, re-case it or append `:*` — the server locates it by
18901
18635
  text and refuses otherwise (`rule_not_offered`). With `edited: true` this is the FREE-TEXT ARM
18902
18636
  and the string is whatever the person typed.
18903
18637
  edited:
@@ -19835,6 +19569,38 @@ components:
19835
19569
  fields: {}
19836
19570
  overrideReasons: {}
19837
19571
  ts: {}
19572
+ CrossBoundaryTaintRow:
19573
+ type: object
19574
+ description: >
19575
+ server >= 7.134.0 / core 7.54.0 — the device lane's CROSS-BOUNDARY TAINT row: the engine's `permission.cross_boundary_taint` frame minus its envelope
19576
+ (`kind` / `version` / `taskId` / `ts`). Metadata only. Emitted ONLY on a tree that executes on a target device (one row when an armed tree prepares a
19577
+ leg — `prepare`, one on a durable resume leg — `resume`, one when this leg first lets an off-target read into the tree — `observed`, with that call);
19578
+ the host lane never emits it. Two exits, one row: the ledger event `cross_boundary_taint` on `GET /v1/runs/{taskId}/events` (`Event_cross_boundary_taint`)
19579
+ and the trace SSE `cross-boundary-taint` frame on `GET /v1/tasks/{taskId}/stream` (the row + `seq`). The `GET /v1/runs/{taskId}` poll object carries NO
19580
+ such key.
19581
+ # 不封闭(`additionalProperties: true`):本 schema 是 `Event_cross_boundary_taint` 的 allOf 目标(帧上多一个 `type`),封闭会把合法帧判成违约(同 PromptManifestRow)。
19582
+ additionalProperties: true
19583
+ required: [runId, phase, taint, governed]
19584
+ properties:
19585
+ runId: { type: string, description: 'the leg''s run id.' }
19586
+ phase: { type: string, enum: [prepare, resume, observed], description: 'core `permission.cross_boundary_taint` `phase` (engine closed set).' }
19587
+ taint:
19588
+ type: array
19589
+ description: 'The tree''s taint bits after the event — core `CROSS_BOUNDARY_TAINT_BITS` (`targetExecutionArmed` = the tree is armed to execute on the target; `offTargetReadObserved` = off-target read content was let into the tree).'
19590
+ items: { type: string, enum: [targetExecutionArmed, offTargetReadObserved] }
19591
+ governed: { type: boolean, description: 'the tree has run under the `multi_tenant` posture since it armed (derived from `REQUIRE_PRINCIPAL`).' }
19592
+ toolName: { type: string, description: '`observed` only: the call that let the off-target read in.' }
19593
+ toolCallId: { type: string, description: '`observed` only: that call''s id.' }
19594
+ Event_cross_boundary_taint:
19595
+ description: >
19596
+ DURABLE only (server >= 7.134.0 / core 7.54.0) — the ledger event of a `CrossBoundaryTaintRow` on `GET /v1/runs/{taskId}/events` (SSE `id:` = the row's
19597
+ seq). Never on the live stream, never on the `GET /v1/runs/{taskId}` poll object; an older server drops the frame on the spot.
19598
+ allOf:
19599
+ - $ref: '#/components/schemas/CrossBoundaryTaintRow'
19600
+ - type: object
19601
+ required: [type]
19602
+ properties:
19603
+ type: { const: cross_boundary_taint }
19838
19604
  Event_needs_review:
19839
19605
  type: object
19840
19606
  description: >
@@ -21690,6 +21456,13 @@ components:
21690
21456
  (the `livePending[]` row + `state`; answer door open); `decided` = the row is DECIDED (`decision` / `decidedAtMs` / `decisionNote?`, the same three keys as the late-replay body); `parked` = PARKING / PARKED
21691
21457
  (a late answer redeems the parked card; `decidePath?` points at the durable decision path); `void` = DENIED / VOID (nobody decided it, the answer has nowhere to land; `cause?` is the same cause word as the
21692
21458
  late-answer 404's, see ToolApprovalNotPendingCause).
21459
+ 🔴 server >= 7.134.0: a PARKED row passes the gate judge (the same one the answer door's 409 `approval_stale` reads) — the gate still open ⇒ `parked`
21460
+ (+ `decidePath`; ONLY this state renders "go decide"); the gate settled by a person / policy ⇒ `decided` (no `decidePath`); the gate expired (a cancel expires it
21461
+ first) / superseded / settled by the host SLA sweep, the window or nobody-to-answer ⇒ `void` WITHOUT `cause`; the checkpoint row gone, or settled without a
21462
+ readable direction / time ⇒ 404 `tool_approval.not_pending` WITHOUT `cause`; the gate unreadable (store failure) ⇒ falls back to `parked`. Known boundary (re-read
21463
+ to settle it): a parked row's `decided` can return to `parked` when the resume leg fails before the approved action starts (the engine reopens the same gate), and
21464
+ a cancelled resume reads `void` once the server collects the reopened gate (`parked` in between, or `void` for an instant during the reopen).
21465
+ Older servers answered all of these with `parked` + `decidePath`.
21693
21466
  oneOf:
21694
21467
  - $ref: '#/components/schemas/ToolApprovalReadPending'
21695
21468
  - $ref: '#/components/schemas/ToolApprovalReadDecided'
@@ -22083,74 +21856,6 @@ components:
22083
21856
  delivery: { type: string, const: applied, description: 'Literal "applied" on today''s server.' }
22084
21857
  marker: { type: string, description: 'Engine-minted delivery marker.' }
22085
21858
 
22086
- WorkspaceSnapshotPage:
22087
- # 4.3.0(CAPS-OPS-8):把 GET …/workspace 的 200 信封提成具名 schema。SDK 侧此前是 `list()` 返回位上的
22088
- # **内联闭合字面量**,两道 spec 门(field-drift 只扫 types.ts / resource-schema 只扫 resources 的 exported
22089
- # type)对方法返回位的内联形都是盲的 —— 于是它漏了这里一直标为 required 的 `sessionId`,CI 全绿。
22090
- type: object
22091
- description: >
22092
- GET /v1/sessions/{sessionId}/workspace 的 200 信封(server src/http/routes/sessions.ts)。
22093
- ⚠️ `total` = **本页行数**(`snapshots.length`),不是快照总数,且这条腿没有任何分页游标 —— 与同族
22094
- `WorkspaceTreePage.total`(真·全量)**同名反义**。`latest` 与展示行同源(防 ghost key)。
22095
- additionalProperties: false
22096
- required: [sessionId, snapshots, total]
22097
- properties:
22098
- sessionId: { type: string }
22099
- snapshots:
22100
- type: array
22101
- items: { $ref: '#/components/schemas/WorkspaceSnapshotRow' }
22102
- latest: { type: string, description: 'Newest snapshot key; absent when the session has none.' }
22103
- total: { type: integer, description: 'ROWS ON THIS PAGE — not the snapshot count.' }
22104
-
22105
- WorkspaceTreePage:
22106
- # 4.3.0(CAPS-OPS-8):同上,tree 腿的 200 信封。
22107
- type: object
22108
- description: >
22109
- GET /v1/sessions/{sessionId}/workspace/{key}/tree 的 200 信封(server src/http/routes/sessions.ts)。
22110
- `total` 在这条腿上是**全量**(`all.length`)—— 与 `WorkspaceSnapshotPage.total` 语义相反。
22111
- `key` 回的是解析后的真实键(请求可传 `latest` 别名)。
22112
- additionalProperties: false
22113
- required: [sessionId, key, entries, total]
22114
- properties:
22115
- sessionId: { type: string }
22116
- key: { type: string, description: 'The RESOLVED snapshot key (a `latest` alias in the request comes back resolved).' }
22117
- entries:
22118
- type: array
22119
- items: { $ref: '#/components/schemas/WorkspaceTreeEntry' }
22120
- total: { type: integer, description: 'FULL entry count (not the page size).' }
22121
- nextOffset: { type: integer, description: 'Present ⇒ more pages; send it back as ?offset=.' }
22122
-
22123
- WorkspaceSnapshotRow:
22124
- type: object
22125
- description: >
22126
- One snapshot row of GET /v1/sessions/{sessionId}/workspace (envelope `{sessionId, snapshots,
22127
- latest?, total}`; server src/http/routes/sessions.ts — newest first, uuidv7 keys). E19 snapshot store's
22128
- read-only projection: each row is the freeze at a turn boundary, NOT a live workspace.
22129
- required: [key, files]
22130
- additionalProperties: true
22131
- properties:
22132
- key: { type: string, description: 'Snapshot key (uuidv7 entry id; "latest" is an accepted alias on the sub-routes).' }
22133
- files: { type: integer, description: 'Manifest row count.' }
22134
- bytes: { type: integer, description: 'Total snapshot bytes — present only when the store has a sizes face (SQL backends); the local store honestly omits it ([1894]② additive).' }
22135
-
22136
- WorkspaceTreeEntry:
22137
- type: object
22138
- description: >
22139
- One file entry of GET /v1/sessions/{sessionId}/workspace/{key}/tree (envelope `{sessionId, key,
22140
- entries, total, nextOffset?}`; server src/http/routes/sessions.ts — stable path order, offset paging).
22141
- required: [path, hash]
22142
- additionalProperties: true
22143
- properties:
22144
- path: { type: string, description: 'The relPath — pass VERBATIM as ?path= on the file sub-route.' }
22145
- hash: { type: string, description: 'Content address (sha256) — enables cross-snapshot change highlighting ([1894]②).' }
22146
- size: { type: integer, description: 'Present only when the store has a sizes face (SQL backends).' }
22147
-
22148
- # ═══════════════════════════════════════════════════════════════════════════════════════════════
22149
- # #154 车二(design/179 §7)—— 持久权限规则的 CC settings 导入面。形按 server 真码 + core 原形亲读
22150
- # (`src/http/routes/rules.ts` / `src/rules-consent.ts`;preview/result 两形是 core `ImportPreview` /
22151
- # `ImportResult` 的原样透传)。
22152
- # ═══════════════════════════════════════════════════════════════════════════════════════════════
22153
-
22154
21859
  CcImportSettingsLayer:
22155
21860
  type: string
22156
21861
  enum: [userSettings, projectSettings, localSettings]