@sema-agent/sdk 26.0.0 → 27.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 (99) hide show
  1. package/README.md +129 -1
  2. package/dist/abort.d.ts +25 -0
  3. package/dist/abort.d.ts.map +1 -0
  4. package/dist/abort.js +42 -0
  5. package/dist/abort.js.map +1 -0
  6. package/dist/client.d.ts +8 -3
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +5 -4
  9. package/dist/client.js.map +1 -1
  10. package/dist/control-client.d.ts +4 -2
  11. package/dist/control-client.d.ts.map +1 -1
  12. package/dist/control-client.js +3 -3
  13. package/dist/control-client.js.map +1 -1
  14. package/dist/errors.d.ts +80 -8
  15. package/dist/errors.d.ts.map +1 -1
  16. package/dist/errors.js +32 -6
  17. package/dist/errors.js.map +1 -1
  18. package/dist/events.d.ts +31 -2
  19. package/dist/events.d.ts.map +1 -1
  20. package/dist/events.js.map +1 -1
  21. package/dist/index.d.ts +12 -4
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/model-route-env/index.d.ts +11 -4
  26. package/dist/model-route-env/index.d.ts.map +1 -1
  27. package/dist/model-route-env/index.js +52 -3
  28. package/dist/model-route-env/index.js.map +1 -1
  29. package/dist/registry/auth.d.ts.map +1 -1
  30. package/dist/registry/auth.js +23 -13
  31. package/dist/registry/auth.js.map +1 -1
  32. package/dist/registry/client.d.ts +9 -1
  33. package/dist/registry/client.d.ts.map +1 -1
  34. package/dist/registry/client.js +17 -2
  35. package/dist/registry/client.js.map +1 -1
  36. package/dist/registry/read-body.d.ts +2 -0
  37. package/dist/registry/read-body.d.ts.map +1 -0
  38. package/dist/registry/read-body.js +24 -0
  39. package/dist/registry/read-body.js.map +1 -0
  40. package/dist/registry/scopes-effective.d.ts.map +1 -1
  41. package/dist/registry/scopes-effective.js +4 -2
  42. package/dist/registry/scopes-effective.js.map +1 -1
  43. package/dist/resources/approvals.d.ts +27 -10
  44. package/dist/resources/approvals.d.ts.map +1 -1
  45. package/dist/resources/approvals.js +5 -4
  46. package/dist/resources/approvals.js.map +1 -1
  47. package/dist/resources/attachments.d.ts +2 -1
  48. package/dist/resources/attachments.d.ts.map +1 -1
  49. package/dist/resources/attachments.js +3 -9
  50. package/dist/resources/attachments.js.map +1 -1
  51. package/dist/resources/fleet.d.ts +4 -36
  52. package/dist/resources/fleet.d.ts.map +1 -1
  53. package/dist/resources/fleet.js +40 -1
  54. package/dist/resources/fleet.js.map +1 -1
  55. package/dist/resources/ops.d.ts +2 -1
  56. package/dist/resources/ops.d.ts.map +1 -1
  57. package/dist/resources/ops.js +4 -6
  58. package/dist/resources/ops.js.map +1 -1
  59. package/dist/resources/rules.d.ts +1 -1
  60. package/dist/resources/runs.d.ts +4 -1
  61. package/dist/resources/runs.d.ts.map +1 -1
  62. package/dist/resources/runs.js +4 -1
  63. package/dist/resources/runs.js.map +1 -1
  64. package/dist/resources/session-sync.d.ts +20 -5
  65. package/dist/resources/session-sync.d.ts.map +1 -1
  66. package/dist/resources/session-sync.js +16 -10
  67. package/dist/resources/session-sync.js.map +1 -1
  68. package/dist/resources/sessions.d.ts +14 -6
  69. package/dist/resources/sessions.d.ts.map +1 -1
  70. package/dist/resources/sessions.js +15 -15
  71. package/dist/resources/sessions.js.map +1 -1
  72. package/dist/resources/side-query.d.ts +8 -0
  73. package/dist/resources/side-query.d.ts.map +1 -1
  74. package/dist/resources/side-query.js.map +1 -1
  75. package/dist/resources/tasks.d.ts +2 -0
  76. package/dist/resources/tasks.d.ts.map +1 -1
  77. package/dist/resources/tasks.js +2 -0
  78. package/dist/resources/tasks.js.map +1 -1
  79. package/dist/resources/tool-approvals.d.ts +61 -8
  80. package/dist/resources/tool-approvals.d.ts.map +1 -1
  81. package/dist/resources/tool-approvals.js +32 -9
  82. package/dist/resources/tool-approvals.js.map +1 -1
  83. package/dist/settings.d.ts +1 -1
  84. package/dist/sse.d.ts.map +1 -1
  85. package/dist/sse.js +4 -4
  86. package/dist/sse.js.map +1 -1
  87. package/dist/sync.d.ts.map +1 -1
  88. package/dist/sync.js +2 -0
  89. package/dist/sync.js.map +1 -1
  90. package/dist/transport.d.ts +66 -23
  91. package/dist/transport.d.ts.map +1 -1
  92. package/dist/transport.js +129 -27
  93. package/dist/transport.js.map +1 -1
  94. package/dist/types.d.ts +134 -27
  95. package/dist/types.d.ts.map +1 -1
  96. package/dist/types.js +3 -0
  97. package/dist/types.js.map +1 -1
  98. package/openapi.yaml +418 -54
  99. package/package.json +2 -3
package/openapi.yaml CHANGED
@@ -2428,7 +2428,7 @@ paths:
2428
2428
  summary: Write the per-session tool-permission rules (tighten-only).
2429
2429
  description: >
2430
2430
  E6 — write the per-session rules. 🔴 TIGHTEN-ONLY for a normal principal: a loosen (remove a deny / widen
2431
- an allowlist or allowDirs) → 403 `loosen_forbidden` (operator-only). `expectedRev` = optimistic-concurrency
2431
+ an allowlist or allowDirs) → 403 `loosen_forbidden` (operator-only). 🔴 server >= 7.135.0 (engine 7.55.0): the tighten gate is a gate on the row's OPERATOR PART (`operatorRules`), not on the whole row — a non-operator (the session's owner) can take back what NON-operator writes added, but not what an operator wrote (moving anything inside the operator part, even an entry both sides wrote, answers 403 `loosen_forbidden`; the row stays). <= 7.134.x: a non-operator write was subtract-only against the whole row. Read the CODE (the sentence changed: `a non-operator session-rule write may not loosen what an operator wrote; refused: …` vs `… may only tighten …`) — never the sentence. `expectedRev` = optimistic-concurrency
2432
2432
  CAS; a stale rev → 409 `conflict`. Each rule field must be `string[]` and the body carries only the three buckets (`toolAllow` / `toolDeny` / `allowDirs`; any other
2433
2433
  key, including the retired `commandAllow` / `commandDeny`, → 400 `rules.invalid_shape` on server >= 7.129.0) → malformed body 400. ownerless
2434
2434
  session → 409; non-owner → 404. 501 without a session-policy store.
@@ -2456,6 +2456,9 @@ paths:
2456
2456
  # write judge stamps the current grammar). `grammar` newer than the engine's ⇒ `400 rules.invalid_shape`.
2457
2457
  rev: { type: integer, minimum: 0 }
2458
2458
  grammar: { type: integer, minimum: 0 }
2459
+ # server >= 7.135.0 (engine 7.55.0): a stored body echoed back (GET → edit → PUT) may also carry its `operatorRules`; on THIS door the echoed part is ALWAYS ignored — the route reads the value through the write reader
2460
+ # (its product carries no `rev`) and the store judges it again: over an existing row the STORED row's operator part is kept, over no row an empty stamped part is minted. Do not rely on an echo to move the operator line — send the new value without `operatorRules`.
2461
+ operatorRules: { $ref: '#/components/schemas/SessionRulesOperatorPart' }
2459
2462
  expectedRev: { type: integer, minimum: 0, description: CAS — reject if the stored rev differs. }
2460
2463
  responses:
2461
2464
  '200':
@@ -2492,7 +2495,8 @@ paths:
2492
2495
  schema: { $ref: '#/components/schemas/ErrorResponse' }
2493
2496
  '401': { $ref: '#/components/responses/Unauthorized' }
2494
2497
  '403':
2495
- description: 403 `loosen_forbidden` — a non-operator write tried to relax the rules (tighten-only).
2498
+ description: >-
2499
+ 403 `loosen_forbidden` — a non-operator write tried to relax what an OPERATOR wrote (server >= 7.135.0; <= 7.134.x: relax the rules at all, tighten-only). Branch on the code, never the sentence (it changed with 7.135.0).
2496
2500
  content:
2497
2501
  application/json:
2498
2502
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -2581,7 +2585,7 @@ paths:
2581
2585
  narrow it, switch with a default arm; `httpStatus` 401 is a re-authorize door); `reason` is HUMAN TEXT
2582
2586
  (server-redacted, length-bounded) and must NOT be parsed.
2583
2587
  Gates, the same family as steer / capture-optout: 501 `capability.run_store_required` (no durable run
2584
- ledger) · 501 `capability.session_ownership_required` (no `sessionStorage.ownerOf`) · 400
2588
+ ledger) · 501 `capability.session_ownership_required` (no `sessionStorage.headOf`; <= 7.134.x: `ownerOf`) · 400
2585
2589
  `request.path_malformed` (bad percent-encoding in the id) · 404 `not_found.session` — unknown session
2586
2590
  AND someone else's session answer the SAME code and SAME message (deliberate: no existence oracle); an
2587
2591
  EXPLICIT operator may re-dial any tenant's session · 422 `steering.invalid_content` · 409
@@ -2647,7 +2651,7 @@ paths:
2647
2651
  the model's description, falling back to the launch command text (content family: redacted, cut at 500
2648
2652
  chars). At most 500 rows (core `list()`'s own cap). 🔴 IN-PROCESS: only rows on THIS replica.
2649
2653
  Zero model work: not billable, not refused by drain. Gates (same family as `mcp/reconnect`): 501
2650
- `capability.session_ownership_required` (no `sessionStorage.ownerOf`) · 404 `not_found.session` — unknown
2654
+ `capability.session_ownership_required` (no `sessionStorage.headOf`; <= 7.134.x: `ownerOf`) · 404 `not_found.session` — unknown
2651
2655
  session AND someone else's session answer the SAME code and SAME message (no existence oracle); an EXPLICIT
2652
2656
  operator may read any session · 400 `request.path_malformed`. Probe with `capabilities.background.listFace`;
2653
2657
  ABSENT key = an older server, where this path is 404 `not_found.route` (same status, different code).
@@ -2920,6 +2924,15 @@ paths:
2920
2924
  '401': { $ref: '#/components/responses/Unauthorized' }
2921
2925
  '404': { $ref: '#/components/responses/NotFound' }
2922
2926
  '501': { $ref: '#/components/responses/NotImplemented' }
2927
+ '503':
2928
+ description: >-
2929
+ server >= 7.135.0: `503 state.file_history_unreadable` — this session's file history cannot be read WHOLE (a boundary torn / unreadable, the tracked set unreadable); nothing is exported this time. 🔴 It is NOT "no history":
2930
+ with nothing to export the manifest still answers `200` with `fileHistory: null`. May carry `permanence` (`transient` / `terminal`; absent = the store did not judge — see `ErrorResponse.permanence`). Older servers read an
2931
+ unreadable history as "none" (manifest 200 / blob 404).
2932
+ Server >= 7.136.0: the SAME code with `permanence: "transient"` also answers a database / store connection failure on this leg (the entry-id set, policy, anchors, session row, the history read — the code's `file_history` is its origin; the action reads `permanence`); before 7.136.0 that fell to a generic `500 internal.error`. The SDK retries this 503 up to `maxRetries` times BEFORE the first byte (the same retrier on all three pull legs), except `permanence: "terminal"` (the store judged retrying useless until it is repaired).
2933
+ content:
2934
+ application/json:
2935
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2923
2936
 
2924
2937
  /v1/sessions/{sessionId}/sync/entries:
2925
2938
  parameters:
@@ -2957,6 +2970,13 @@ paths:
2957
2970
  '401': { $ref: '#/components/responses/Unauthorized' }
2958
2971
  '404': { $ref: '#/components/responses/NotFound' }
2959
2972
  '501': { $ref: '#/components/responses/NotImplemented' }
2973
+ '503':
2974
+ description: >-
2975
+ server >= 7.136.0: `503 state.file_history_unreadable` + `permanence: "transient"` — a database / store connection failure BEFORE the response head is sent (nothing is streamed; before 7.136.0 a generic `500 internal.error`). The code's `file_history` is its origin; the action reads `permanence` (see `ErrorResponse.permanence`).
2976
+ A disconnect AFTER the 200 head can only truncate the stream — no `{"__sync":"end"}` trailer — and is never a 5xx. The SDK retries the 503 up to `maxRetries` times BEFORE the first byte (the same retrier on all three pull legs), except `permanence: "terminal"` (the store judged retrying useless until it is repaired); a truncated body is not retried (resume with `afterSeq`).
2977
+ content:
2978
+ application/json:
2979
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2960
2980
 
2961
2981
  /v1/sessions/{sessionId}/sync/history/blobs/{hash}:
2962
2982
  parameters:
@@ -2994,6 +3014,15 @@ paths:
2994
3014
  '401': { $ref: '#/components/responses/Unauthorized' }
2995
3015
  '404': { $ref: '#/components/responses/NotFound' }
2996
3016
  '501': { $ref: '#/components/responses/NotImplemented' }
3017
+ '503':
3018
+ description: >-
3019
+ server >= 7.135.0: `503 state.file_history_unreadable` — the history cannot be read WHOLE, so the server cannot tell whether this hash is one the session references — it does NOT answer "no such blob" (that is `404 not_found.blob`) and gives no bytes. 🔴 It is NOT "no history":
3020
+ with nothing to export the manifest still answers `200` with `fileHistory: null`. May carry `permanence` (`transient` / `terminal`; absent = the store did not judge — see `ErrorResponse.permanence`). Older servers read an
3021
+ unreadable history as "none" (manifest 200 / blob 404).
3022
+ Server >= 7.136.0: the SAME code with `permanence: "transient"` also answers a database / store connection failure on this leg (the history read or the blob bytes read — the code's `file_history` is its origin; the action reads `permanence`); before 7.136.0 that fell to a generic `500 internal.error`. The SDK retries this 503 up to `maxRetries` times BEFORE the first byte (the same retrier on all three pull legs), except `permanence: "terminal"` (the store judged retrying useless until it is repaired).
3023
+ content:
3024
+ application/json:
3025
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2997
3026
 
2998
3027
  /v1/sessions/{sessionId}/sync/blobs/{hash}:
2999
3028
  parameters:
@@ -3091,12 +3120,15 @@ paths:
3091
3120
  description: >
3092
3121
  2c session-sync PUSH Phase A — a SMALL metadata body (`entryIds, fileHistory?, policy, anchors, resolution?, logDigest?, title?, pinned?`); the
3093
3122
  entries stream in Phase B. The cloud classifies (§7), pre-checks blob presence (a missing referenced hash →
3094
- 422 `missing_blob` — PUT it first), guards the import lease + any active run (409), then mints a stagingId.
3123
+ 422 `missing_blob` — PUT it first; server >= 7.135.0 answers it in this FIRST phase — zero staging, zero commit, ahead of the `identical` replay arm — and
3124
+ a deployment whose `capabilities.sessionSyncFileHistory` is false, e.g. `DB_BACKEND=local`, takes a `fileHistory` that references blobs as a first-phase 422 too: the conversation does not land either; <= 7.134.x answered it after the conversation
3125
+ had committed), guards the import lease + any active run (409), then mints a stagingId. 🔴 server >= 7.135.0: a file-history replay fault (the replay arms of `identical` re-push and of the commit) answers `422 adopt_failed`
3126
+ (+ optional `permanence`, see `ErrorResponse.permanence`; <= 7.134.x: `unsupported_format`) — distinct from `missing_blob`.
3095
3127
  The file history rides `fileHistory` (a `FileHistoryExport` envelope, `null` / absent = none) and every blob it references must have been
3096
3128
  `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
3129
  no longer declares it). The SDK carries `fileHistory` in `pushSessionBundle` / `importSessionBundle`; a hand-rolled
3098
- push must send `fileHistory` itself, and only to a peer whose `capabilities.sessionSyncFileHistory` is not false (server >= 7.116.0 — else the
3099
- commit 422s `missing_blob`).
3130
+ push must send `fileHistory` itself, and only to a peer whose `capabilities.sessionSyncFileHistory` is not false (server >= 7.116.0 — else
3131
+ `422 missing_blob` in the first phase from server >= 7.135.0; after the commit on <= 7.134.x).
3100
3132
  `identical` → `{ relation:"identical", basis, payloadVerified }` with NO stagingId (skip Phase B);
3101
3133
  `basis` is `"entry-ids+digest"` when the caller supplied a comparable digest and it matched, else
3102
3134
  `"entry-ids"` (id-set equality only) — see the `ImportStaged` schema, which has carried these two
@@ -3169,7 +3201,12 @@ paths:
3169
3201
  content:
3170
3202
  application/json:
3171
3203
  schema: { $ref: '#/components/schemas/SyncConflict' }
3172
- '422': { $ref: '#/components/responses/UnprocessableEntity' }
3204
+ '422':
3205
+ description: >-
3206
+ 422 — `missing_blob` (first phase from server >= 7.135.0), `restore_failed`, and (server >= 7.135.0) `adopt_failed` (+ optional `permanence`) for the file-history replay; `invalid_entries` etc. as before. Branch on `errorCode`.
3207
+ content:
3208
+ application/json:
3209
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3173
3210
  '501': { $ref: '#/components/responses/NotImplemented' }
3174
3211
  '503': { $ref: '#/components/responses/Unauthorized' } # (declared 2026-08-01) E6 兄弟守卫:无 service token + 未开 ALLOW_UNAUTHED_WRITES 时,这条 import 提交腿与 `PUT …/policy` 写的是同一个 policy store ⇒ 同门同码 `auth.service_token_required`(server 3.15.0 起)
3175
3212
 
@@ -3211,7 +3248,8 @@ paths:
3211
3248
  '400': { $ref: '#/components/responses/BadRequest' }
3212
3249
  '401': { $ref: '#/components/responses/Unauthorized' }
3213
3250
  '403':
3214
- description: 403 `loosen_forbidden` — a post-commit policy replay tried to relax the rules (tighten-only).
3251
+ description: >-
3252
+ 403 `loosen_forbidden` — a post-commit policy replay tried to relax what an operator wrote (server >= 7.135.0; <= 7.134.x: tighten-only against the whole row). A non-operator replay keeps each record's own operator part; an operator replay makes the imported row the operator's.
3215
3253
  content:
3216
3254
  application/json:
3217
3255
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3259,7 +3297,14 @@ paths:
3259
3297
  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
3298
  or delete that row) answers `500` + `errorCode: "approvals.read_corrupt"` (body `{ error, errorCode }`; the bad row's coordinates stay off the wire) —
3261
3299
  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`.
3300
+ failure (connection / timeout / store hiccup) is `503 approvals.read_unavailable` from server >= 7.135.0 (below); servers <= 7.134.x answered it `500 internal.error`. Older servers answered the corrupt row with `internal.error`.
3301
+ content:
3302
+ application/json:
3303
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3304
+ '503':
3305
+ description: >-
3306
+ server >= 7.135.0: `503 approvals.read_unavailable` — the approvals queue could not be read RIGHT NOW (the store hiccuped; transient — the SDK retries it by status). Until a read succeeds, an answer here does NOT mean nothing is pending.
3307
+ Body `{ error, errorCode }`. Older servers answered a read that could not complete with the global fallback `500 internal.error`.
3263
3308
  content:
3264
3309
  application/json:
3265
3310
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3339,7 +3384,10 @@ paths:
3339
3384
  schema: { $ref: '#/components/schemas/ApprovalDecision' }
3340
3385
  responses:
3341
3386
  '200':
3342
- description: Decision accepted; the run resumes.
3387
+ description: >-
3388
+ Decision accepted. Usually the run (or the parked background agent) resumes; on the two parked-agent forms that can never revive,
3389
+ a DENY (server >= 7.136.0) is answered `{ taskId, status: "failed", decision: "deny" }` instead — the card is voided and nothing resumes
3390
+ (see `ParkedDecideAccepted`, `ApprovalDecisionResult`).
3343
3391
  content:
3344
3392
  application/json:
3345
3393
  schema: { $ref: '#/components/schemas/ApprovalDecisionResult' }
@@ -3514,6 +3562,10 @@ paths:
3514
3562
  the claim and rolls back when it is gone or different) — a 7.104.0 session delete retires such cards, so this form
3515
3563
  mostly meets cards left by a pre-7.104.0 delete; `decide.row_unrevivable` (DecideError) now also answers when the
3516
3564
  parked row carries no root-session anchor (refused before the claim, nothing consumed).
3565
+ server >= 7.136.0 — those two never-revive forms (a parked child whose row carries no agent name; a row with no root-session anchor) keep answering APPROVE with this 409 (the sentence now ends
3566
+ by pointing at deny), but DENY answers `200 { taskId, status: "failed", decision: "deny" }`: the card is voided under its own scope CAS and leaves the pending queue and the
3567
+ `/v1/approvals/stream` snapshot, the child ends `failed`, the gated tool never runs, no `remember` is granted; a lost void CAS is the existing 409 `decide.gate_moved`. Branch on
3568
+ `status` / `errorCode`, not on the sentence.
3517
3569
  content:
3518
3570
  application/json:
3519
3571
  schema: { $ref: '#/components/schemas/ResumeLegConflictError' }
@@ -3707,6 +3759,20 @@ paths:
3707
3759
  application/json:
3708
3760
  schema: { $ref: '#/components/schemas/InboxList' }
3709
3761
  '401': { $ref: '#/components/responses/Unauthorized' }
3762
+ '500':
3763
+ description: >-
3764
+ server >= 7.135.0: `500 approvals.read_corrupt` — a queue row's stored body cannot be read (the store's refusal contract: a retry fails the same way; an operator has to repair or delete that row) — the SAME judge and the same code as `GET /v1/approvals`
3765
+ and `GET /v1/approvals/stream`. Do NOT retry it as-is (the SDK's retry arm exempts the code). Every other read failure is `503 approvals.read_unavailable` (below). Older servers: `500 internal.error`.
3766
+ content:
3767
+ application/json:
3768
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3769
+ '503':
3770
+ description: >-
3771
+ server >= 7.135.0: `503 approvals.read_unavailable` — the approvals queue could not be read RIGHT NOW (the store hiccuped; transient — the SDK retries it by status). Until a read succeeds, an answer here does NOT mean nothing is pending.
3772
+ Body `{ error, errorCode }`. Older servers answered a read that could not complete with the global fallback `500 internal.error`.
3773
+ content:
3774
+ application/json:
3775
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3710
3776
 
3711
3777
  /v1/assistant/tasks:
3712
3778
  parameters:
@@ -3728,6 +3794,20 @@ paths:
3728
3794
  application/json:
3729
3795
  schema: { $ref: '#/components/schemas/AssistantTaskList' }
3730
3796
  '401': { $ref: '#/components/responses/Unauthorized' }
3797
+ '500':
3798
+ description: >-
3799
+ server >= 7.135.0: `500 approvals.read_corrupt` — a queue row's stored body cannot be read (the store's refusal contract: a retry fails the same way; an operator has to repair or delete that row) — the SAME judge and the same code as `GET /v1/approvals`
3800
+ and `GET /v1/approvals/stream`. Do NOT retry it as-is (the SDK's retry arm exempts the code). Every other read failure is `503 approvals.read_unavailable` (below). Older servers: `500 internal.error`.
3801
+ content:
3802
+ application/json:
3803
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3804
+ '503':
3805
+ description: >-
3806
+ server >= 7.135.0: `503 approvals.read_unavailable` — the approvals queue could not be read RIGHT NOW (the store hiccuped; transient — the SDK retries it by status). Until a read succeeds, an answer here does NOT mean nothing is pending.
3807
+ Body `{ error, errorCode }`. Older servers answered a read that could not complete with the global fallback `500 internal.error`.
3808
+ content:
3809
+ application/json:
3810
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
3731
3811
 
3732
3812
  /v1/assistant/tasks/{taskId}/resume:
3733
3813
  parameters:
@@ -5921,6 +6001,14 @@ paths:
5921
6001
  schema: { $ref: '#/components/schemas/WorkflowJournalPage' }
5922
6002
  '401': { $ref: '#/components/responses/Unauthorized' }
5923
6003
  '404': { $ref: '#/components/responses/NotFound' }
6004
+ '500':
6005
+ description: >-
6006
+ server >= 7.135.0 (core 7.55.0): `500 workflow.journal_incompatible` — a journal row the engine's judge rejects (a parked row with no checkpoint token, a parked row whose terminal is not paused, a row that does not read as an object — hand-edited / another writer /
6007
+ a newer format). The judgment covers the WHOLE journal, on every backend, on every page (a bad row on another page, or one over 64 KiB, answers the same); the body carries `ordinal` (the first bad row, ascending; `-1` = the refusal names no entry). The run's resume is refused by the engine
6008
+ with the same code. Repair that row from the retained copy — NEVER by deleting it (a missing entry re-runs that step live). A retry changes nothing (the SDK's retry arm exempts the code). <= 7.134.x read such rows back as `200`.
6009
+ content:
6010
+ application/json:
6011
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
5924
6012
  '501': { $ref: '#/components/responses/NotImplemented' }
5925
6013
 
5926
6014
  /v1/workflows/{workflowRunId}/agents/{label}/steer:
@@ -7006,8 +7094,32 @@ components:
7006
7094
  enabled: { type: boolean }
7007
7095
  available: { type: boolean, description: 'whether this deployment can actually RUN the scenario right now — same predicate as the 501 rejection arm on submit; false ⟺ submitting is guaranteed to 501. Independent axis from `enabled` (declared vs runnable).' }
7008
7096
  attachmentSupplies: { type: array, items: { type: string, enum: [path, content_text, content_image] }, description: 'server >= 7.133.0, always present there: the attachment SUPPLY words this scenario actually carries — a subset of `capabilities.userAttachments.supplies` (`path` only on hands scenarios, where a workspace lives somewhere; a no-hands scenario drops `path`, and a `path` entry is refused by the engine with `config.user_attachment_invalid`). ABSENT = server <= 7.132.x: fall back to `userAttachments.supplies` plus the existing 400 handling.' }
7097
+ skills:
7098
+ type: array
7099
+ items: { $ref: '#/components/schemas/ScenarioSkill' }
7100
+ description: >-
7101
+ server >= 7.135.0, always present there (`[]` when none): the deployment SKILLS this scenario's run mounts (the deployment skills directory UNION what the config source hands over, filtered by scenario; a request's own `skills` are not
7102
+ included) — the same single filter that feeds `TaskSpec`, same group, same order. A client's `/skill-name` completion reads it; naming one goes through `userInvokedSkills`. Entries carry no body and none of a skill's tool lists. On the card
7103
+ but not in the run: `disableSkills: true` (nothing mounted) and a skill whose body exceeds the engine's load cap (naming it answers `config.user_invoked_skill_unknown`). ABSENT = an older server: the list cannot be read —
7104
+ NOT "no skills" (that is `[]`). Bit: `capabilities.scenarioSkills`.
7009
7105
  unavailableReason: { type: string, description: 'machine-readable reason, present ONLY when available:false (absent = no reason). Closed set, currently the single member `git_client_unconfigured`; branch on the key, never match the English prose. Additive-open: treat an unknown word as "unavailable, reason unknown".' }
7010
7106
 
7107
+ ScenarioSkill:
7108
+ type: object
7109
+ description: One row of `ScenarioDetail.skills` (server >= 7.135.0). No body, no tool lists.
7110
+ additionalProperties: false
7111
+ required: [name, description, source]
7112
+ properties:
7113
+ name: { type: string }
7114
+ description: { type: string }
7115
+ source: { $ref: '#/components/schemas/ScenarioSkillSource' }
7116
+ ScenarioSkillSource:
7117
+ type: string
7118
+ description: >
7119
+ Where a scenario skill came from (server >= 7.135.0; the server's self-minted word table `scenarioSkillSource`, closed two words): `factory` = the deployment's skills directory (`SKILLS_DIR`);
7120
+ `center` = the config source (the center / the local `config.d` skills list, or a config-declared plugin) — a skill that overrides a same-named factory one by name is also `center`.
7121
+ enum: [factory, center]
7122
+
7011
7123
  RunStatus:
7012
7124
  type: string
7013
7125
  enum: [running, completed, failed, suspended, needs_review, blocked]
@@ -7279,10 +7391,16 @@ components:
7279
7391
  `egress:true` + `effect:"write"` (core's ruling); `toolAxes` is the caller's explicit override face.
7280
7392
  🔴 A malformed entry drops the WHOLE peer (named in a warn), never "drop the bad key and keep the
7281
7393
  peer": `allowSkills` is a NARROWING field whose absence means "no restriction", so a half-applied
7282
- entry would turn a narrowing intent into a widening. The request itself is not rejected.
7283
- The center/config baseline wins on a name clash (the peer name IS the tool namespace segment). Over
7284
- the 32-entry cap the surplus entries are DROPPED and named too — same disposition as a malformed
7285
- entry, not a 400.
7394
+ entry would turn a narrowing intent into a widening.
7395
+ 🔴 server >= 7.136.0 — on a FRESH submit (the three submit doors) a badly written declaration is REFUSED, not dropped, and no longer answers a 200
7396
+ that reads as "in effect": a list that is not an array / an entry that is not an object / a key value the judge cannot read as usable
7397
+ (`url` not an http(s) string, `headers` values that are not strings, `allowSkills` not a string array …) ⇒ `400 request.field_invalid` with the sentence only
7398
+ (no `field` / `reason`); a key outside the peer vocabulary (a mis-spelled `allowSkill` …) ⇒ `400 request.body_shape` + `unknownKeys` (full coordinates, e.g.
7399
+ `a2aPeers[0].allowSkill`); a `url` / `cardUrl` that carries a credential ⇒ `400 request.field_invalid` WITH `field` / `reason` (`url_userinfo` / `url_credential_param`);
7400
+ more than 32 entries ⇒ `400 request.field_invalid` WITH `field: "a2aPeers"` / `reason: "over_cap"` / `count` / `cap`. Branch on `errorCode` / `unknownKeys` / `field`, never on the sentence.
7401
+ The durable-resume REPLAY leg (a stored body re-read on resume) does NOT refuse: it keeps the reader's degradation — a bad entry is not mounted and is named in an operator warn, the request is not 400.
7402
+ Servers <= 7.135.x: a malformed entry drops the whole peer (warn), the request is not rejected, and over the cap the surplus is dropped (7.135.0 already answers 400 `over_cap`).
7403
+ The center/config baseline wins on a name clash (the peer name IS the tool namespace segment).
7286
7404
  items: { $ref: '#/components/schemas/A2aServerSpec' }
7287
7405
  resumeAt:
7288
7406
  type: string
@@ -7290,7 +7408,7 @@ components:
7290
7408
  CC Rewind parity (E18). Fork the conversation FROM this user-message SessionTreeEntry.id (non-destructive
7291
7409
  branch via setLeafId). With `restoreFiles: true` ⇒ "both" (also restore the working tree — the retired spelling
7292
7410
  `rewindFiles` still rides the wire but its restore sense is core's typed migration rejection naming `restoreFiles`). Alone ⇒
7293
- "conversation". HONORED only where the deployment wired a resume-anchor store + getLeafId (capabilities.resumeAt);
7411
+ "conversation". HONORED only where the deployment wired a resume-anchor store + the session head read `headOf` (<= 7.134.x: `getLeafId`) (capabilities.resumeAt);
7294
7412
  unknown anchor ⇒ 4xx, capability-off ⇒ 501. Cannot combine with a durable resume.
7295
7413
  resumeAtMode:
7296
7414
  type: string
@@ -7324,7 +7442,7 @@ components:
7324
7442
  kind of handle as `resumeAt`, resolved through the SAME anchor store — typically the `taskId` from that turn's
7325
7443
  202 receipt (the server keys the user-message anchor by the run's taskId). Server `src/boot/resolve-spec.ts:686-697`:
7326
7444
  `resumeAnchorStore.resolve(sessionId, handle, principal)` → entry id; unknown handle ⇒ 404
7327
- `rewind_files_to.unknown_event`; no session ⇒ 422 `rewind_files_to.no_session`; no anchor store / no getLeafId ⇒
7445
+ `rewind_files_to.unknown_event`; no session ⇒ 422 `rewind_files_to.no_session`; no anchor store / no session head read (`headOf`; <= 7.134.x: `getLeafId`) ⇒
7328
7446
  501. ⚠️ The 501 is NOT the capability's whole predicate: `capabilities.rewindFilesTo` also requires the
7329
7447
  file-history store; without it the server does not 501 — core refuses at prepare with
7330
7448
  `rewind.store_unconfigured` (server >= 7.104.0 / S-724: a 409 error body on the synchronous `POST /v1/tasks` when
@@ -7501,7 +7619,7 @@ components:
7501
7619
  not exceed 5 MiB — otherwise the engine refuses BEFORE the first model call (after the UserPromptSubmit hook; not one model token spent) with 400 `config.image_invalid`, whose
7502
7620
  `error` starts with `TaskSpec.images: `. Before 7.117.0 such an image was sent as-is to the model provider and the whole first request failed there (no code to branch on).
7503
7621
  A base64 string longer than 5 242 880 characters (the model API's 5 MB image cap; the server reads the engine's exported constant) is NOT this code: the submit gate refuses it first
7504
- with 413 `request.payload_too_large` (zero model calls, no submit hook, no run row). The `{ url }` arm is unchanged. The model must accept image input (`ModelInfo.vision`; absent = unknown, send anyway).
7622
+ with 413 `request.payload_too_large` (zero model calls, no submit hook, no run row). The `{ url }` arm: from server >= 7.136.0 a URL that carries a credential (`user:pass@host`, or a credential-named query / fragment parameter) is refused on a FRESH submit with 400 `request.field_invalid` + `field: "images[i].url"` + `reason` `url_userinfo` / `url_credential_param` (the sixth seat of the URL-credential judge; send the bytes inline as `{ data, mimeType }` instead — an inline image never lands in a URL); the durable-resume replay of a stored body is not judged. Otherwise unchanged (the server has long recognised the type by signature). The model must accept image input (`ModelInfo.vision`; absent = unknown, send anyway).
7505
7623
  items:
7506
7624
  oneOf:
7507
7625
  - type: object
@@ -7920,6 +8038,15 @@ components:
7920
8038
  combination); `taskWriteFaceOpen` is NOT its reading. Residual (server S-908): the binding is keyed by SESSION and written
7921
8039
  after the run is claimed (a refused request writes nothing); on deployments without a run store two concurrently accepted
7922
8040
  runs on one session are last-writer-wins. Delegated children's shells keep the default scrub.
8041
+ projectMemory:
8042
+ type: string
8043
+ enum: [disabled]
8044
+ description: >
8045
+ server >= 7.135.0 (engine 7.55.0 `TaskSpec.projectMemory`, passed through verbatim): this ONE run does not read the deployment's project context — the engine does not call the deployment's project-context seat, so the project layer,
8046
+ the declared instruction files and the few things they feed are all absent (the engine's own git-status lane, the memory engine, skills / hooks / MCP are unaffected; delegated / background / fork / workflow children inherit it from
8047
+ the parent). The only word is `disabled`; the key ABSENT = call the seat. The leg's `wiring_manifest.projectMemory` reads `disabled` and the server-minted `instructions` section is absent. A deployment without the seat still accepts it
8048
+ (the manifest answers `absent`). Anything else (`off`, a boolean, `null`) ⇒ a fresh submit answers 400 `request.field_invalid`; a resume replay judges the same and folds into 409 `resume_blocked_by_policy`
8049
+ (`blockedBy: request.field_invalid`). Read `capabilities.projectMemory` beforehand: older servers answer 400 `request.body_shape` (`unknownKeys: ["projectMemory"]`).
7923
8050
  attachments:
7924
8051
  type: object
7925
8052
  description: >
@@ -9596,8 +9723,8 @@ components:
9596
9723
  (`decision: "approve"`, `decidedBy` absent or `person`): on a deny or a `decidedBy: "policy"` decision ⇒ 400 `request.field_conflict` with
9597
9724
  `detail.kind: "approval_feedback"` and `detail.fields` `["feedback", "decision"]` / `["feedback", "decidedBy"]` (a denial's words go in `reason`);
9598
9725
  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
9726
+ carrying `<system-reminder>` ⇒ server >= 7.135.0 (engine 7.55.0): 409 `resume_outcome_invalid` + `field: "feedback"` + `retriable: true` (the engine's sentence naming `feedback`; refused before the CAS, the card stays pending — ask the person to change
9727
+ or drop the words, do not auto-refetch and re-decide on the code); server 7.134.x: 409 `approval_binding_mismatch` (no `field`). The same door serves parked background-agent cards and workflow child cards; the live
9601
9728
  `POST /v1/tool-approvals/{id}/respond` does NOT take it. Servers <= 7.133.x refuse the key (400 `request.body_shape`, `unknownKeys: ["feedback"]`).
9602
9729
 
9603
9730
  Capabilities:
@@ -9807,7 +9934,7 @@ components:
9807
9934
  type: boolean
9808
9935
  description: >-
9809
9936
  Code-only rewind (`TaskRequest.rewindFilesTo`) is wired — mint `routes/capabilities.ts:839` =
9810
- `Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && deps.sessionStorage?.getLeafId)`: it needs the
9937
+ `Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && headReadOf(deps.sessionStorage))` (<= 7.134.x last conjunct: `deps.sessionStorage?.getLeafId`): it needs the
9811
9938
  file-history store (to restore) AND the anchor map (to resolve the target HANDLE). Corrected in sdk 11.4.0
9812
9939
  (board [8437] U3): the store is the FILE-HISTORY store (not the pre-S-15 whole-tree snapshot store).
9813
9940
  🔴 K-24: on server 7.97.0–7.103.x that seat was the CAPTURE face (backend AND the `FILE_HISTORY_CAPTURE` knob), so a
@@ -9889,8 +10016,9 @@ components:
9889
10016
  description: >
9890
10017
  🔴 契约调和车1 H② (2026-08-02): server HAS been emitting this key (the `sessionEvents` bit in `src/http/routes/capabilities.ts`)
9891
10018
  while both this spec and the SDK type were missing it. Gates `GET /v1/sessions/{sessionId}/events`
9892
- (the session-level SSE head subscription). Predicate is a THREE-way AND: a durable session-watch seam
9893
- plus a session store exposing BOTH `getLeafId` and `ownerOf` — the route's 501 uses the same
10019
+ (the session-level SSE head subscription). Predicate (server >= 7.135.0) is a TWO-way AND: a durable session-watch seam
10020
+ plus a session store exposing the session head read `headOf` (<= 7.134.x it was a THREE-way AND with BOTH `getLeafId` and `ownerOf`; those two
10021
+ methods left the store contract in 7.135.0, so `DB_BACKEND=memory` now answers `true`, was `false`) — the route's 501 uses the same
9894
10022
  predicate, so "says yes <=> route works" holds here. False/absent => fall back to polling
9895
10023
  `GET /v1/sessions/{sessionId}/head`. NOTE (server [2373]A-5): no consumer is wired to it yet — it is
9896
10024
  a prepared slot, correct to gate on, wrong to read as "widely consumed".
@@ -10222,11 +10350,29 @@ components:
10222
10350
  — SQL backends only; `DB_BACKEND=local` is always false). Advertises exactly the two file-history blob faces
10223
10351
  `PUT /v1/sessions/{sessionId}/sync/blobs/{hash}` and `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`.
10224
10352
  READ IT ON BOTH ENDS of a sync: if either is false, sync the conversation only — do not push `fileHistory`, do
10225
- not PUT blobs (a `fileHistory` pushed to a peer without the blob faces 422s `missing_blob` after commit). When
10353
+ not PUT blobs (a `fileHistory` pushed to a peer without the blob faces 422s `missing_blob` in the FIRST phase — zero staging, zero commit, the conversation does not land either — from server >= 7.135.0; <= 7.134.x answered it in the second phase, after the conversation committed). When
10226
10354
  false the two routes answer 501 `capability.snapshot_store_required` (any session id, ahead of the owner check).
10227
10355
  A blob that the session history graph does not reference / whose bytes are gone stays a 404 `not_found.blob`.
10228
10356
  ABSENT = an older server (<= 7.115.x): the bit cannot be read there (a `DB_BACKEND=local` deployment with
10229
10357
  `sessionSync: true` answered PUT blob 501 on every call), so try as before.
10358
+ sessionRulesNamespaceForms:
10359
+ type: boolean
10360
+ description: >
10361
+ server >= 7.135.0 (predicate class "always true", read per request from the engine's protocol table): the session-rule write faces (`PUT /v1/sessions/{id}/policy`, the `policy` face of `POST /v1/sessions/{id}/sync/import`)
10362
+ accept — in `toolAllow` / `toolDeny` — the three COVERING FORMS of every protocol namespace the engine knows (today `mcp__` and `a2a__`): the whole peer (`mcp__srv`), a tool-segment wildcard (`mcp__srv__*`)
10363
+ and a tool-segment glob (`mcp__srv__get_*`); the engine's matcher reads each as ONE GROUP of tools. A client may stop refusing those three forms losslessly on this bit alone (never on a version guess).
10364
+ The bit says NOTHING about other wildcards: `*`, partial peer-segment wildcards (`mcp__git*`) and a trailing-separator `mcp__srv__` are still refused by the write face (`400 rules.inert_entry`); the whole-protocol
10365
+ name `mcp__*` is accepted only in `toolDeny`. Orthogonal to `sessionPolicy` (does the deployment have a session-rules write face at all): read `sessionPolicy` AND this bit. ABSENT (older server) and `false` ⇒ read as "do not widen".
10366
+ projectMemory:
10367
+ type: boolean
10368
+ description: >
10369
+ server >= 7.135.0 (always true): the top-level request key `projectMemory: "disabled"` is accepted. The read face hangs on no facility — a deployment without a project-context seat still accepts it
10370
+ (the manifest answers `"absent"`). ABSENT = an older server, whose key-closure gate answers `400 request.body_shape` (`unknownKeys` names it): send the key on sight of the bit only.
10371
+ scenarioSkills:
10372
+ type: boolean
10373
+ description: >
10374
+ server >= 7.135.0 (always true): `GET /v1/capabilities/scenarios/{name}` carries `skills[]` (`ScenarioDetail.skills`). ABSENT = an older server: the card has no list — do NOT read that as "this scenario has no skills"
10375
+ (that is `skills: []`).
10230
10376
 
10231
10377
  # ─── S-139(sdk 8.3.0):对 server 7.60.0 的整体重对账补齐的 19 位 ───────────────────────
10232
10378
  # 行号 = server 7.60.0 树的 `src/http/routes/capabilities.ts`。语义与谓词的全文在
@@ -10513,7 +10659,7 @@ components:
10513
10659
  type: boolean
10514
10660
  description: >
10515
10661
  server >= 7.85.0 (S-381 / core 7.22.0 #857; mint `routes/capabilities.ts:264` =
10516
- `Boolean(deps.runStore) && Boolean(deps.sessionStorage?.ownerOf)`) — presence of
10662
+ `Boolean(deps.runStore) && Boolean(deps.sessionStorage?.ownerOf)`; server >= 7.135.0 the last conjunct is `headReadOf(deps.sessionStorage) !== undefined`) — presence of
10517
10663
  `POST /v1/sessions/{sessionId}/mcp/reconnect`. FACILITY DEPENDENCY, two conjuncts: a durable run
10518
10664
  ledger (the verb reaches the live stream through the run row) AND the session-ownership face (the
10519
10665
  inbound owner gate is fail-closed; without it the whole door is 501). Both 501s derive from the SAME
@@ -10634,7 +10780,7 @@ components:
10634
10780
  sessionBackgroundFacesAvailable(deps), exitFaces: sessionBackgroundFacesAvailable(deps) }`) — presence of the
10635
10781
  session background-task doors: `listFace` = `GET /v1/sessions/{sessionId}/background`, `exitFaces` =
10636
10782
  `POST /v1/sessions/{sessionId}/background/stop` (contract §16). Both bits use the SAME predicate as the two
10637
- doors' 501 (the session-ownership face `sessionStorage.ownerOf`), so "says yes but 501s" is structurally
10783
+ doors' 501 (the session-ownership face: `sessionStorage.headOf` on server >= 7.135.0, `ownerOf` on <= 7.134.x), so "says yes but 501s" is structurally
10638
10784
  impossible; without it both bits are explicitly `false` and both doors answer 501
10639
10785
  `capability.session_ownership_required`. ENDPOINT-GATE FORM: `false` ⇒ do not call. True only promises the
10640
10786
  endpoint — not that this replica holds any background row of that session (the registry is in-process).
@@ -11685,7 +11831,7 @@ components:
11685
11831
  modelPlane:
11686
11832
  allOf: [{ $ref: '#/components/schemas/ModelPlane' }]
11687
11833
  description: >-
11688
- server >= 7.133.0, ALWAYS present there (appended last): which model table this engine SERVES, whether the catalog was adopted, and what the start-up route judge says — read this instead of predicting
11834
+ server >= 7.133.0, ALWAYS present there (appended last on 7.133.0–7.134.x; from server >= 7.135.0 the last key is `aliases` and this one precedes it — key order is not a contract, read by name): which model table this engine SERVES, whether the catalog was adopted, and what the start-up route judge says — read this instead of predicting
11689
11835
  (the judge's inputs are the serving process's facts; mirroring two of its judgements drifts). ABSENT = an earlier server (<= 7.132.x). Field reading: ModelPlane.
11690
11836
  candidateRejected:
11691
11837
  allOf: [{ $ref: '#/components/schemas/CandidateRejected' }]
@@ -11697,6 +11843,25 @@ components:
11697
11843
  configuration is clean" (it is also an earlier server): render the real cause only when the key is present; keep the old behaviour when absent.
11698
11844
  Same read as `models`: key present => this `models` is the fallback generation (none of the candidate's models are in it). No capability bit
11699
11845
  (the endpoint is always there; the presence of the key is the presence rule).
11846
+ aliases:
11847
+ type: array
11848
+ items: { $ref: '#/components/schemas/ModelAlias' }
11849
+ description: >
11850
+ server >= 7.135.0, ALWAYS present there (appended LAST; `[]` when none): the model WORDS besides `models[].name` that the server's acceptance gate takes (tier words / `default` / a catalog entry's id spelling, ...).
11851
+ ACCEPTANCE RULE (one rule; a model-word judge reads this key, never infers from a row's id): a model word is submittable IFF its name is in `models[].name` UNION `aliases[].name` AND its row's `atMentionable !== false`
11852
+ (`atMentionable` absent = no allowlist verdict, not "not mentionable"). `aliases` deliberately does not say which row a word lands on. ABSENT = an earlier server (<= 7.134.x): tier words / `default` stay "cannot tell" —
11853
+ do not read the absence as "no aliases". Known boundary (not introduced by this version): a catalog entry literally named `default` does not appear in `models[]`.
11854
+
11855
+ ModelAlias:
11856
+ type: object
11857
+ additionalProperties: false
11858
+ required: [name]
11859
+ description: >
11860
+ One row of `ModelsResponse.aliases` (server >= 7.135.0): a submittable model word that is not a `models[]` row. `name` = the word (never equal to a `models[].name`); `atMentionable` = the SAME projection as on a `models[]`
11861
+ row, present ONLY when the deployment configured an at-mention allowlist (the allowlist verdict for the catalog entry the word resolves to); absent = no allowlist verdict, NOT "not mentionable".
11862
+ properties:
11863
+ name: { type: string }
11864
+ atMentionable: { type: boolean }
11700
11865
 
11701
11866
  CandidateRejected:
11702
11867
  type: object
@@ -12077,6 +12242,8 @@ components:
12077
12242
  name: { type: string, description: "The workflow script's declared `meta.name`." }
12078
12243
  description: { type: string, description: "The workflow script's declared `meta.description` (one-liner)." }
12079
12244
  agentFailures: { type: integer, description: 'Count of agent-runs that ended failed — present only when > 0 (a "completed, with failures" flag without an N+1 get).' }
12245
+ parked: { type: boolean, enum: [true], description: 'server >= 7.135.0 (core 7.55.0 `runParkedOf`) — present, always the literal `true`, ONLY while the run is `running` AND at least one of its agents is parked at an approval gate; otherwise the key is ABSENT (never `false`). Read it as "the run is waiting on a person", not "failed". Older servers never send it.' }
12246
+ errorCode: { type: string, description: 'The run row''s machine code (open set; e.g. `workflow.agent_parked` for a drained parked terminal) — present only when the row has one. (Declared here in 26.1.0: the engine summary has emitted it since before 7.134.0.)' }
12080
12247
  currentPhase: { type: string, description: 'Title of the phase currently executing (absent once terminal).' }
12081
12248
  status: { $ref: '#/components/schemas/WorkflowRunStatus' }
12082
12249
  phaseCount: { type: integer, description: 'Phases recorded.' }
@@ -12255,6 +12422,8 @@ components:
12255
12422
  description: { type: string, description: "The workflow script's declared `meta.description` (redacted, one-liner)." }
12256
12423
  status: { $ref: '#/components/schemas/WorkflowRunStatus' }
12257
12424
  agentFailures: { type: integer, description: 'run-level count of agents whose status ended failed — stamped at run end, only when > 0.' }
12425
+ parked: { type: boolean, enum: [true], description: 'server >= 7.135.0 (core 7.55.0 `runParkedOf`) — present, always the literal `true`, ONLY while the run is `running` AND at least one of its agents is parked at an approval gate; otherwise the key is ABSENT (never `false`). Read it as "the run is waiting on a person", not "failed". Older servers never send it.' }
12426
+ errorCode: { type: string, description: 'The run row''s machine code (open set; the same words as the same-named slot on the fleet row, e.g. `workflow.agent_parked` for a drained parked terminal) — present only when the row has one. (Declared here in 26.1.0: the detail projection has forwarded it since before 7.134.0.)' }
12258
12427
  stats: { $ref: '#/components/schemas/WorkflowRunStats' }
12259
12428
  startedAt: { type: integer }
12260
12429
  endedAt: { type: integer }
@@ -12727,6 +12896,11 @@ components:
12727
12896
  run row has one, the key ABSENT otherwise (never an empty string). A parked workflow run reads `status: "failed"` + `errorCode: "workflow.agent_parked"`
12728
12897
  (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
12898
  point; a `?session=` subscriber's demoted rows carry it too. ABSENT = an older server, or no code on this row — fall back to `status`.
12899
+ parked:
12900
+ type: boolean
12901
+ enum: [true]
12902
+ description: >-
12903
+ server >= 7.135.0 (core 7.55.0 `runParkedOf`) — present, always the literal `true`, ONLY while the run is `running` AND at least one of its agents is parked at an approval gate; otherwise the key is ABSENT (never `false`). Read it as "the run is waiting on a person", not "failed". Older servers never send it. On a fleet row the workflow-bus row is now replaced WHOLE on every `workflow` upsert (server >= 7.135.0; no longer merged by `id` into the old row), so a key that left is gone from the row.
12730
12904
  FleetFrame:
12731
12905
  description: >
12732
12906
  One decoded frame off GET /v1/fleet/stream (service streamFleet emit set, src/http/routes/fleet.ts). On the
@@ -13147,7 +13321,9 @@ components:
13147
13321
  errorCode?, errorMessage?, retriable?}`, plus `rememberApplied` when the request carried
13148
13322
  `remember:"session"` AND the worker has an exemption store;
13149
13323
  · the PARKED-BACKGROUND-AGENT shape (`parked-decide.ts`, server >=1.267) — `{taskId, status:"resuming",
13150
- decision}`, an ACCEPTED (not completed) receipt; the revive drives asynchronously.
13324
+ decision}`, an ACCEPTED (not completed) receipt; the revive drives asynchronously. Since server >= 7.136.0 this
13325
+ shape has a SECOND ARM, the VOIDED receipt `{taskId, status:"failed", decision:"deny"}` — a DENY on a parked row that can
13326
+ never revive (no agent name / no root-session anchor) voids the card; nothing resumes (see `ParkedDecideAccepted`).
13151
13327
  A FOURTH shape, the #368 IDEMPOTENT REPLAY (server >= 7.4x; declared 2026-09-08 — it shipped without
13152
13328
  ever reaching this schema): `{sessionId, idempotent:true, decision, bindingEnforced:true,
13153
13329
  executionOutcome?}` — it carries NO `status` and NO `taskId` (nothing was accepted this time). So no key
@@ -13208,7 +13384,7 @@ components:
13208
13384
  description: >
13209
13385
  OPEN string — do NOT narrow. Task-level acceptance (server >= 7.37): the literal `resuming` (= accepted,
13210
13386
  resume in flight — follow the run stream). Terminal leg: the run's resulting status (`completed`/`failed`/
13211
- `suspended`/`needs_review`). Parked leg: the literal `resuming` (= accepted, revive in flight).
13387
+ `suspended`/`needs_review`). Parked leg: the literal `resuming` (= accepted, revive in flight) — or, server >= 7.136.0, the literal `failed` on the VOIDED receipt (a deny that closed a card which could never revive; nothing resumes).
13212
13388
  decision:
13213
13389
  type: string
13214
13390
  enum: [approve, deny]
@@ -13429,7 +13605,14 @@ components:
13429
13605
  description: 'server >= 7.100.0 (S-700): WHY the park row offers no rule — the same narrow read and closed set (four words from server >= 7.129.0) as the live card frame and `card_json`.'
13430
13606
  boundGrantAbsence:
13431
13607
  allOf: [{ $ref: '#/components/schemas/BoundGrantAbsence' }]
13432
- description: 'server >= 7.129.0 (core 7.51.0 #1688; park-row fact eight): why this parked card has no grant-binding digest — the park-row twin seat `PendingAction.tool_approval.boundGrantAbsence`, the same narrow read and the same name / value as the live frame and the inbox row. ABSENT on this face = a skill card (it carries the digest; the digest itself is not on this face) / a row parked before engine 7.51.0 (says neither — the inbox face projects `unreadable` for the same row) / a word outside the set / an old server — NOT `not_bindable`.'
13608
+ description: 'server >= 7.129.0 (core 7.51.0 #1688; park-row fact eight): why this parked card has no grant-binding digest — the park-row twin seat `PendingAction.tool_approval.boundGrantAbsence`, the same narrow read and the same name / value as the live frame and the inbox row. ABSENT on this face = a skill card (it carries the digest) / a row parked before engine 7.51.0 (says neither — the inbox face projects `unreadable` for the same row) / a word outside the set / an old server — NOT `not_bindable`.'
13609
+ boundGrantDigest: { type: string, description: 'server >= 7.136.0 — a parked `Skill` card''s grant-binding digest; the same value as the inbox row''s and the live frame''s `boundGrantDigest` (one projection feeds this operator queue row, the `/v1/approvals/stream` `pending` frame and the inbox row — byte-equal for the same parked row). Present only on a skill card; absent on an ordinary card. `card_json` does not carry it. Server <= 7.135.x: not on this face.' }
13610
+ boundGrants:
13611
+ allOf: [{ $ref: '#/components/schemas/BoundSkillGrants' }]
13612
+ description: 'server >= 7.136.0 — the value `boundGrantDigest` hashes, published ONLY when it hashes to it (digest only when they disagree; no digest, no list). DEPLOYMENT TEXT (skill names and list entries are written by the deployer; the server neither redacts nor escapes them): render with contextual escaping.'
13613
+ classifierNoRuling:
13614
+ allOf: [{ $ref: '#/components/schemas/AutoModeFallbackCause' }]
13615
+ description: 'server >= 7.136.0 — only on a parked `classifier_no_ruling` card: why the auto-mode classifier did not rule (closed set, echoed only when a member). Same value as the inbox row''s and the live frame''s.'
13433
13616
  askClass:
13434
13617
  allOf: [{ $ref: '#/components/schemas/AskClass' }]
13435
13618
  description: 'server >= 7.109.0 (S-934 ② / core 7.37.0 #1214): the park-row twin seat `PendingAction.tool_approval.askClass`, read through `readAskClassPair` — the SAME narrow read as the live frame and the card. Present together with `classes` (`classes[0] === askClass`); the basis a durable approver answers by. Absent = an old row (parked before core 7.37.0; resume does not backfill) or an unreadable pair — NOT "no class". The inbox row (InboxRow) does not carry it.'
@@ -13583,7 +13766,7 @@ components:
13583
13766
  classifierNoRuling:
13584
13767
  allOf: [{ $ref: '#/components/schemas/AutoModeFallbackCause' }]
13585
13768
  description: 'server >= 7.118.0 — only on a parked `classifier_no_ruling` card: why the auto-mode classifier did not rule.'
13586
- objective: { type: ['string', 'null'], description: 'task objective, null if unavailable.' }
13769
+ objective: { type: ['string', 'null'], description: 'task objective, null if unavailable. server >= 7.136.0: the run row''s `objectivePreview` (redacted when written, about 120 characters — the same column as `GET /v1/assistant/tasks`; looked up through the gate''s session: its active run, else its latest run; a delegated child''s gate — whose session has no run of its own — falls back to the run that spawned it, via the parked agent row''s `parentTaskId`, hop by hop, at most 8 hops) — null when no preview could be read (a deployment without a run ledger is one cause; a failed read of the row, a delegation chain that breaks or exceeds 8 hops, or a run row without a preview also answer null — the key does not say which). Before 7.136.0 this was the objective ORIGINAL stored in the parked context (not truncated, not redacted); a consumer that used it as the full objective should know that server 7.136.0 has no full-objective read face (`GET /v1/runs/:id` carries neither `objective` nor `objectivePreview`) — this key IS the preview; a full read face comes with a later server version and this package locksteps then (no substitute path is declared here).' }
13587
13770
  input:
13588
13771
  description: >
13589
13772
  REDACTED tool-args preview for display (null on gates without one, e.g. plan_review). Untyped by
@@ -14001,7 +14184,9 @@ components:
14001
14184
  non-object is `400 request.body_shape`. `title`: a string renames (1..120 CHARACTERS counted as Unicode characters = the storage column width, no leading /
14002
14185
  trailing whitespace, stored verbatim — not rewritten, not redacted); `null` CLEARS it back to the automatic title (the next submit lets the auto-titler fill
14003
14186
  it again). `""` is NOT a clear — it is `400 request.body_shape`. A string of the wrong form / a non-string-non-null `title` / a non-boolean `pinned` is
14004
- `400 request.field_invalid`.
14187
+ `400 request.field_invalid`; from server >= 7.136.0 a title made only of invisible characters (zero-width, bidi controls, soft hyphen, C1 controls — empty after the cross-session peer directory's own
14188
+ usable-name normalization) is `400 request.field_invalid` too (`title must contain at least one visible character`; the session-sync 2c import's `title` reads the same rule). Such titles written before
14189
+ 7.136.0 are not migrated.
14005
14190
  additionalProperties: false
14006
14191
  minProperties: 1
14007
14192
  properties:
@@ -14148,12 +14333,20 @@ components:
14148
14333
  server >= 7.92.0 (engine 7.25.0): the session's rule LINEAGE generation. An erase
14149
14334
  (`deleteBySession`) ends the lineage — the next write carries gen+1 — so a reader treats a
14150
14335
  different gen as a new lineage, never a continuation, regardless of rev. Absent on older servers.
14336
+ operatorRules:
14337
+ allOf: [{ $ref: '#/components/schemas/SessionRulesOperatorPart' }]
14338
+ description: >
14339
+ server >= 7.135.0 (engine 7.55.0): the row's OPERATOR PART — the buckets (and their own `grammar` stamp) the last OPERATOR write set; the line a non-operator write may not loosen (`403 loosen_forbidden` on a move inside it).
14340
+ ABSENT = the whole row is the operator's (an operator write; every row stored before 7.135.0, read as before the upgrade: the owner can take nothing back). Minted by the engine's write judge alone, stored whole and read back verbatim; also on the 2c export `policy[].rules`.
14341
+ A non-operator write over an EMPTY row stamps an empty part (stamp only) — the owner can then take back what the owner added. A part ECHOED back is read per door: on `PUT /v1/sessions/{sessionId}/policy` it is ALWAYS ignored (the stored row's part is kept over an existing row; an empty stamped part is minted over none).
14342
+ On the sync-import replay (`POST …/sync/import`, `policy[]` records, which carry `rev`): over an existing row the judge keeps the STORED row's part and ignores the echo; over NO row, a non-operator record that looks like a stored row (carries `rev` >= 1)
14343
+ has its own part adopted (from then on that part IS the row's operator line: taking it back answers 403 `loosen_forbidden`), otherwise an empty stamped part is minted; an operator write never carries a part.
14151
14344
  grammar:
14152
14345
  type: integer
14153
14346
  minimum: 0
14154
14347
  description: >
14155
14348
  server >= 7.129.0 (engine 7.51.2): the rule GRAMMAR the row's entries are read in, stamped by the
14156
- engine's write judge on every row written through it (every row this server writes carries `1`).
14349
+ engine's write judge on every row written through it (server >= 7.135.0 writes `4` — the engine's `SESSION_RULES_GRAMMAR`; 7.129.0 wrote `1`).
14157
14350
  Absent = a row stored before the stamp (grammar 0: an allow entry spelled as a rule, `Bash(rm:*)`,
14158
14351
  named no tool and admitted nothing — the engine keeps reading such a row that way and announces it
14159
14352
  once; the one-time migration rewrites stored rows and stamps them). Echoing a stored body back into
@@ -14174,6 +14367,18 @@ components:
14174
14367
  rev: { type: integer }
14175
14368
  gen: { type: integer }
14176
14369
  grammar: { type: integer }
14370
+ operatorRules: { $ref: '#/components/schemas/SessionRulesOperatorPart' }
14371
+
14372
+ SessionRulesOperatorPart:
14373
+ type: object
14374
+ description: '`StoredSessionRules.operatorRules` (server >= 7.135.0): the three buckets plus their own grammar stamp. The `grammar` is always present (the write judge stamps it).'
14375
+ additionalProperties: false
14376
+ required: [grammar]
14377
+ properties:
14378
+ toolAllow: { type: array, items: { type: string } }
14379
+ toolDeny: { type: array, items: { type: string } }
14380
+ allowDirs: { type: array, items: { type: string } }
14381
+ grammar: { type: integer, minimum: 0 }
14177
14382
 
14178
14383
  McpServerStatus:
14179
14384
  type: object
@@ -14816,7 +15021,18 @@ components:
14816
15021
  fail-closed) and `state.scenario_assignment_unknown` (503, server >= 7.119.0 — the center's scenario assignment for the
14817
15022
  principal is dangling; only re-pointing the assignment, or adding the scenario back to the deployment, helps) and
14818
15023
  `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.
15024
+ delete the row; server >= 7.135.0 the same judge answers `GET /v1/assistant/inbox` and `GET /v1/assistant/tasks` too) and `workflow.journal_incompatible`
15025
+ (500, server >= 7.135.0 on `GET /v1/workflows/{id}/journal` — a journal row the engine's judge rejects; the body carries `ordinal`; repair the row from the retained copy, never by deleting it).
15026
+ Every other 5xx code keeps the status-based retry.
15027
+ 🔴 server >= 7.135.0 read-failure and store-fault codes (all additive; branch on the code, never the sentence): `503 approvals.read_unavailable` on `GET /v1/approvals`, `/v1/assistant/inbox` and
15028
+ `/v1/assistant/tasks` (the queue cannot be read RIGHT NOW — transient, retried by status; until a read succeeds an answer does NOT mean nothing is pending; it moved from the SSE-only table into the status-bearing catalog, status `-` → 503) and, on
15029
+ the same three faces, `500 approvals.read_corrupt` (above); `503 state.file_history_unreadable` on the session-sync pull faces (`GET /v1/sessions/{id}/sync/manifest`, `GET /v1/sessions/{id}/sync/history/blobs/{hash}` — the file history
15030
+ cannot be read WHOLE; it is NOT "no history", which stays `200` with `fileHistory: null`); `422 adopt_failed` on `POST /v1/sessions/{id}/sync/import` (the file-history replay hit a store fault; 7.134.x answered `unsupported_format`;
15031
+ NOT a missing blob — that is `422 missing_blob`, now answered in the FIRST phase). The two store-fault codes may carry `permanence` (`transient` = nothing changed, retry the same shape later / `terminal` = repair the store first; absent = the store did not judge).
15032
+ `422 request.unprocessable` now also answers a deny / ask rule whose PARAMETER does not belong to the tool (the sentence carries the engine code `unsupported.param_unknown`, the same word in `detail.problems[].reason`; a stored body that
15033
+ resumes ⇒ `409 resume_blocked_by_policy` with `blockedBy: request.unprocessable`; 7.134.x accepted such a rule, and it never matched). `400 request.body_shape` also names an unread key inside a `settings.hooks` group / entry in
15034
+ `unknownKeys` as a FULL coordinate (`settings.hooks.PreToolUse.0.hooks.0.onFailure`) on a fresh submit.
15035
+ `409 resume_outcome_invalid` + `field: "feedback"` (`retriable: true`) now answers a release `feedback` that carries an engine `<system-reminder>` tag (refused before the CAS, the card stays pending; fix or drop the words and decide again — it was `409 approval_binding_mismatch` without `field` on 7.134.x). A `feedback` on a deny / policy decision stays `400 request.field_conflict` (`detail.kind: approval_feedback`).
14820
15036
  🔴 `request.host_posture_refused` (422) has TWO triggers (same code, same status — "you wrote it right, this
14821
15037
  deployment will not grant that host posture"): (1) the write-containment face (`permissionMode: bypassPermissions` ⇒
14822
15038
  `writeFace: open`; read `capabilities.taskWriteFaceOpen` first); (2) server >= 7.106.0 / S-895: `inheritEnv: "all"` on a
@@ -14880,10 +15096,23 @@ components:
14880
15096
  the ids of the background children rooted at this session that are still alive (count = length) — the SAME
14881
15097
  strings as the `handle` on `GET /v1/agents/roster` rows. Nothing was deleted; wait for them to finish (or stop
14882
15098
  them where they run) and retry the delete verbatim. SDK: `err.extra.remaining` on the ConflictError.
15099
+ permanence:
15100
+ type: string
15101
+ enum: [transient, terminal]
15102
+ description: >-
15103
+ server >= 7.135.0, on `503 state.file_history_unreadable` and the file-history store's fault answers (`422 adopt_failed`; server >= 7.136.0 also `restore_failed` and the like, and the session-sync pull legs' database-disconnect `503`, where `transient` is judged for connection-class faults): the file-history store's own judgment of the fault (the engine's `GovernanceRetryClass`) —
15104
+ `transient` = nothing was changed, retrying the same shape later may succeed; `terminal` = repair the store first. ABSENT = the store did not judge; not terminal — retried by status like any retryable 5xx (the SDK does so automatically on the pull legs, up to `maxRetries`); still red ⇒ look at the store. SDK: `err.extra.permanence`.
15105
+ ordinal:
15106
+ type: integer
15107
+ description: >-
15108
+ server >= 7.135.0, on `500 workflow.journal_incompatible`: the journal ordinal of the FIRST bad row (ascending); `-1` = the refusal names no entry. SDK: `err.extra.ordinal`.
14883
15109
  reason:
14884
15110
  type: string
14885
15111
  description: >-
14886
- A per-code closed word — read it by `errorCode`, never across codes. On 503 `fleet.lease_unavailable` (server >=
15112
+ A per-code closed word — read it by `errorCode`, never across codes. On 400 `request.field_invalid` for a URL that carries a credential (server >= 7.135.0): `url_userinfo` (`user:pass@host`) / `url_credential_param` (a credential-named
15113
+ query / fragment parameter — the names the service's log redactor treats as credentials), beside `field` = the body coordinate (`mcpServers[i].transport.url`, `a2aPeers[i].url`, `a2aPeers[i].cardUrl`,
15114
+ `settings.hooks.<event>[g].hooks[h].url` — the FULL coordinate, not `field: "settings.hooks"` + `detail` — and `settings.webSearch.endpoint`; a `settings.webSearch.endpoint` with userinfo keeps its sentence and gains `field` / `reason`);
15115
+ `a2aPeers` over 32 entries answers `over_cap` + `count` + `cap` on `field: "a2aPeers"`. Server >= 7.136.0: the same URL-credential words on a SIXTH seat, `images[i].url` (`field` = `images[i].url`, an `images` `{ url }` arm — fresh submits only); `mcpServers` over 32 entries answers `over_cap` + `count` + `cap` on `field: "mcpServers"` (the same body as `a2aPeers`; the probe door `POST /v1/capabilities/mcp/probe` answers the same sentence and keys — before 7.136.0 a sentence without machine keys); a malformed `a2aPeers` list / entry / key value carries NO `reason` (sentence only), a key outside the peer vocabulary is `request.body_shape` + `unknownKeys`. On 503 `fleet.lease_unavailable` (server >=
14887
15116
  7.106.0 / S-866 ③): `auth-rejected` (the center refused this engine's credential, or the credential file is
14888
15117
  unreadable) / `unreachable` (unreachable / 5xx / timeout / bad body / any status other than 200 / 409 / 400 / 401 / 403 —
14889
15118
  center 404 / 429 / 3xx included). On 400 `request.field_invalid` for a `settings.permissions` bucket (server >= 7.106.0 /
@@ -14921,6 +15150,10 @@ components:
14921
15150
  `[i]`, record members `.key`: `mcpServers[0].transport.headers.Authorization`, `settings.hooks.PreToolUse[0].hooks[0].headers.Authorization`; the key
14922
15151
  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
15152
  that literal (with `reason: "bad_entry"` and the bad `index`; no `index` for a non-array).
15153
+ server >= 7.135.0: on 409 `conflict.checkpoint_ctx_unsealable` with `reason: "unrecognized_seal"` the arm of the stored credential-slot GENERATION newer than this server's (or not a positive integer) reads `field: "credentialSlotGeneration"` — not an in-body coordinate.
15154
+ server >= 7.136.0: the credential-slot set gains four seats (generation 3) — `mcpServers[i].transport.command` / `mcpServers[i].transport.args` (stdio transport) and `settings.hooks.<event>[g].hooks[h].command` / `.args` (command hook); `args` is sealed as ONE value (the whole array); on 409 `conflict.checkpoint_ctx_unsealable` `field` names that coordinate (which doors can answer which `reason`, and how the refusal arrives, is written once — in the SDK documentation of `CHECKPOINT_CTX_UNSEALABLE_REASONS` — not repeated here).
15155
+ server >= 7.136.0: on 400 `request.field_invalid` for a URL that carries a credential, `field` also names `images[i].url` (the sixth seat); on `mcpServers` over 32 entries `field` is `mcpServers` (beside `reason: "over_cap"` / `count` / `cap`); a key outside the `a2aPeers` entry vocabulary is `400 request.body_shape` whose `unknownKeys` carry the full coordinates (`a2aPeers[0].allowSkill`).
15156
+ server >= 7.135.0: on `a2aPeers` over 32 entries `field` is `a2aPeers` (beside `reason: "over_cap"` / `count` / `cap`).
14924
15157
  detail:
14925
15158
  description: >
14926
15159
  🔴 SHAPES differ by code — branch on `field` (or the `errorCode`), never guess from the type of `detail`. On 503
@@ -15699,6 +15932,12 @@ components:
15699
15932
  K-32 (server >= 7.106.0 / core 7.34.0 design/1111) — `bypassPermissions` RELEASED an ask nobody answered (see
15700
15933
  `ModeRelease`). Absent = this allow was not a mode release (a plain allow / a person's yes / a rule), or an older
15701
15934
  server (<= 7.105.0 stripped it).
15935
+ clearedBy:
15936
+ allOf: [{ $ref: '#/components/schemas/AllowRuleClearance' }]
15937
+ description: >
15938
+ server >= 7.135.0 (core 7.55.0) — the allow layer's account of a call it let run WITHOUT asking: which member's allow rule cleared a standing ask that nobody answered
15939
+ (`AllowRuleClearance`). Present exactly when an allow member cleared the ask; absent on a call nothing asked about, a mode's release or acceptance, the read-only arm's,
15940
+ the classifier's or the sandbox's allow, and a person's yes. Never present together with `released`. Older servers stripped it.
15702
15941
  - type: object
15703
15942
  additionalProperties: false
15704
15943
  required: [kind, deniedBy]
@@ -16156,6 +16395,10 @@ components:
16156
16395
  `MCP_FAILURE_CODES` spellings — including `network` and the `http_<status>` encoding — are gone,
16157
16396
  and an HTTP status now rides the structured `httpStatus` seat on `wiring_manifest.mcp[]` instead of
16158
16397
  being baked into the code. The set as a WHOLE stays open (a tool owns its own codes).
16398
+ 🔴 core 7.55.0 / server >= 7.135.0 — two MCP words minted BEFORE the call is sent (`delivered: "no"`; the server has no mint point, they pass through verbatim): after the engine redials a Streamable HTTP
16399
+ server that LOST its session, `mcp.tool_declaration_changed` (the server now declares the tool differently — safety axis / read-only reading / the "a person must answer" mark / task support — or no longer lists it) and
16400
+ `mcp.tool_listing_unreached` (the post-redial listing walk stopped before reaching the tool, so its new declaration cannot be proved). Every later call to that tool is refused the same way until a listing of the connection
16401
+ reaches it — the way out is `POST /v1/sessions/{id}/mcp/reconnect` (or the engine's refresh); do not resend as-is. The lost-session failure itself stays `http_status` + `httpStatus: 404` + `delivered: "unknown"`.
16159
16402
  Consumers MUST branch with a `default` arm and MUST NOT exhaustively switch on known values.
16160
16403
  The service enforces only a length bound (>128 chars = malformed, the whole key is DROPPED rather
16161
16404
  than truncated — truncating would mint a code core never sent). NOTE: this is a DIFFERENT
@@ -16505,6 +16748,21 @@ components:
16505
16748
  type: object
16506
16749
  additionalProperties: false
16507
16750
  properties: { kind: { type: string, description: 'root | child | resume. Open on read.' } }
16751
+ sessionRules:
16752
+ type: object
16753
+ additionalProperties: false
16754
+ required: [childrenReadLive]
16755
+ description: >
16756
+ server >= 7.135.0 (engine 7.55.0; effective half only, both faces): whether the session-rules layer is handed to the delegated legs. `childrenReadLive: true` = this leg wired the session-rules layer (the deployment has a session-rules
16757
+ store and the task carries a session id) and handed the session's key to every leg it delegates (sync / background / fork / workflow / resume / observer side-car, any depth) — a rule written into the session NOW governs an
16758
+ already-running child's NEXT call; `false` = no rules layer, nothing handed out. The section ABSENT = an older engine (say nothing; do not read it as `false`). An unreadable section (not `{ childrenReadLive: <boolean> }`) is dropped.
16759
+ properties:
16760
+ childrenReadLive: { type: boolean }
16761
+ projectMemory:
16762
+ type: string
16763
+ description: >
16764
+ server >= 7.135.0 (engine 7.55.0; effective half only, both faces): did this leg call the deployment's project-context seat — `on` = it did; `disabled` = the seat is wired but the request's `projectMemory: "disabled"` turned it off;
16765
+ `absent` = the deployment wired no seat (whatever the request said). On `disabled` / `absent` legs the server-minted `instructions` section is ABSENT. Open on read (a new engine word passes through); ABSENT = an older engine.
16508
16766
  ask:
16509
16767
  type: object
16510
16768
  additionalProperties: false
@@ -17096,6 +17354,10 @@ components:
17096
17354
  own failure carries the same code. `{ modelId, boundOrigin, requestOrigin, sessionId, runId }` — both origins in display spelling (scheme + host +
17097
17355
  port, no userinfo / path), never a credential or a digest of one. Once per run per (modelId, boundOrigin, requestOrigin). It replaces the retired
17098
17356
  operator code `route.base_url_changed_key_unchanged` (never on this whitelist). `config.credential_not_withheld` is operator-audience — ops log only.
17357
+ `advisor.not_attached` (server >= 7.135.0 / core 7.55.0; D.3 is now 42 codes) — the request's advisor model did NOT attach (the engine's pairing judge refuses the (advisor, main) pair).
17358
+ `{ advisorModel, mainModel, reason, sessionId, runId? }` — the two model names are the catalog id / the reference the request wrote (display spelling); `reason` is an engine word (`unknown_model` / `advisor_unranked` /
17359
+ `weakest_tier` / `main_unranked` / `pairing` / `hold_unknown`; the server echoes it — read OPEN). Unreachable on this server today: it does not accept `advisorModel` (the request body does not take it, and the
17360
+ `advisor` section is not projected on either `wiring_manifest` face).
17099
17361
  Value change: `permission.rule_row_dropped`'s `detail.rows[].rule` is, since core 7.53.0, the rule's original text (the same reading as
17100
17362
  `allowed_tool_not_granted`'s `entries[].entry`: at most 1 024 code points, longer cut to 1 024 + a truncation mark, CONTROL CHARACTERS RIDE AS DATA —
17101
17363
  neutralize at render); before it was the engine-neutralized single-line form (at most 201 code points). Same keys.
@@ -17875,7 +18137,7 @@ components:
17875
18137
  second card word `contentKind` that server <= 7.125.x derived from the tool name (deleted at 7.126.0, no alias: a consumer reading `contentKind === "content_ask"` reads
17876
18138
  `approvalCard === "question"`). ABSENT = the engine minted none (a host-built ask / an older engine / a non-approval gate) — never `generic` by default. Display / triage only; it NEVER
17877
18139
  takes part in resume / gate / CAS. The same word rides the `livePending` row top level (= this frame's own value), the operator queue row and the `/v1/approvals/stream` `pending` /
17878
- `live_pending` frames; the `card_json` card face and the `approval_request` frame's `card` do NOT carry it (`ApprovalCard` is `.strict()`).
18140
+ `live_pending` frames; from server >= 7.136.0 the `card_json` card face and the `approval_request` frame's `card` (live mint and reconnect replay alike) carry the same word too (`ApprovalCard.approvalCard`: one read, three faces — before 7.136.0 the card did not carry it, so a replayed frame had none and a reconnecting shell fell back to the generic card).
17879
18141
  inputHasBidi:
17880
18142
  type: boolean
17881
18143
  enum: [true]
@@ -18154,6 +18416,9 @@ components:
18154
18416
  argsOmitted: { type: boolean, enum: [true], description: 'Present (true) only when args exceeded the byte cap. Never false.' }
18155
18417
  toolCallId: { type: string, description: 'The engine''s tool-call id (the `call_…` on the assistant message''s tool_use block) — same domain as ToolApprovalFrame.toolCallId.' }
18156
18418
  risk: { $ref: '#/components/schemas/ApprovalRiskAxes' }
18419
+ approvalCard:
18420
+ allOf: [{ $ref: '#/components/schemas/ToolApprovalCardWord' }]
18421
+ description: 'server >= 7.136.0, ADDITIVE: the engine-minted card word — the SAME word, read once, as the live frame''s top-level `ToolApprovalFrame.approvalCard`; it now rides the live-minted `approval_request` frame''s `card`, the stored `card_json` envelope and the reconnect-replay frame''s `card` (before 7.136.0 the card did not carry it, so a replayed frame had none). ABSENT = the engine minted none / an out-of-set word (the server narrows it again when it mints the card) / a card stored before 7.136.0 — never `generic` by default. Display / triage only; it NEVER takes part in resume / gate / CAS. The stored envelope is strict: a 7.135.x replica cannot read a row carrying this key (replay skips it, the idempotent re-entry goes to the durable park) — roll the fleet in one pass.'
18157
18422
  rememberRefused:
18158
18423
  type: boolean
18159
18424
  enum: [true]
@@ -18300,6 +18565,27 @@ components:
18300
18565
  The closed five-word vocabulary of `AllowRuleShadow.member` (server >= 7.117.0 / core 7.42.0 `ALLOW_RULE_MEMBERS`, verbatim and in the order the engine reads the members). The SDK exports
18301
18566
  it at runtime as `ALLOW_RULE_MEMBERS`. Owner = core; read it as an open string with a default arm (core may add a member and the server follows).
18302
18567
 
18568
+ AllowRuleClearance:
18569
+ type: object
18570
+ additionalProperties: false
18571
+ required: [member, entries]
18572
+ description: >
18573
+ `tool_end.gate.disposition.clearedBy` (server >= 7.135.0 / core 7.55.0 `AllowRuleClearance`): the allow-layer member whose rule CLEARED a standing ask (the pair of `AllowRuleShadow`, which is
18574
+ "matched and could NOT clear"). Same `member` / `entries` / `omitted` shape (minted and capped by the same seat), plus `positions` for a LIST member (`request`, `deployment`): the index of every element of the
18575
+ list the run received that is one of those entries (a string listed twice has both indices), so a host reads which line of its own declaration spoke even where the shown text is capped.
18576
+ properties:
18577
+ member: { $ref: '#/components/schemas/AllowRuleMember' }
18578
+ entries:
18579
+ type: array
18580
+ maxItems: 32
18581
+ items: { type: string, maxLength: 512 }
18582
+ description: 'The entries that member stood on (UNTRUSTED for display, secret-redacted, <= 512 chars each, <= 32 listed).'
18583
+ omitted: { type: integer, minimum: 1, description: 'How many entries were not listed (beyond the cap). Absent = none omitted.' }
18584
+ positions:
18585
+ type: array
18586
+ items: { type: integer, minimum: 0 }
18587
+ description: 'LIST members only: the indices, in the list this run received, of the elements that are those entries. Absent for a non-list member.'
18588
+
18303
18589
  AllowRuleShadow:
18304
18590
  type: object
18305
18591
  additionalProperties: false
@@ -18431,7 +18717,7 @@ components:
18431
18717
  equality). ONE vocabulary for two faces: a card's rule offer (`RuleOffer.match`, the
18432
18718
  server's card schema validates it with this very enum) and a persisted rule row (`GET /v1/rules`).
18433
18719
 
18434
- `exact` = this one command only (`Bash(git status)`). `prefix` = the historical colon-star
18720
+ `exact` = this one command only (`Bash(git status)`). `pattern` (server >= 7.135.0 / core 7.55.0) = the MID-STAR command form (`Bash(git * main)`) — the fourth Bash command form a card edit may persist; the persisted `command` is the written pattern itself. `prefix` = the historical colon-star
18435
18721
  word-boundary prefix (`Bash(git status:*)`) and the ONLY spelling for a compound prefix form; it stays
18436
18722
  a permanent compatibility read. `wildcard` = the trailing space-star form (`Bash(npm run *)`), the
18437
18723
  same predicate as `prefix` on a single command body. `subpath` = the `Read(//abs-dir/**)` directory form,
@@ -18445,11 +18731,11 @@ components:
18445
18731
  always refuses these two words (no network grant is minted through a card). A replica rolled back to 7.125.x reads such a
18446
18732
  row as a damaged row.
18447
18733
 
18448
- 🔴 NINE MEMBERS, closed: the server validates the member on both faces. A consumer type narrower than the
18734
+ 🔴 TEN MEMBERS, closed (server >= 7.135.0 / core 7.55.0 added `pattern`, between `wildcard` and `subpath` — the engine's order): the server validates the member on both faces. A consumer type narrower than the
18449
18735
  mint point is a lie — it lets a `switch` claim exhaustiveness the wire does not honour (SDK <= 16 declared
18450
18736
  four here, so a `path` offer and every 7.118.0 skill-card offer were dropped as malformed; SDK <= 21 declared
18451
18737
  seven, so every domain grant row on `GET /v1/rules` was dropped the same way).
18452
- enum: [exact, prefix, wildcard, subpath, path, name, name_prefix, host, subdomain]
18738
+ enum: [exact, prefix, wildcard, pattern, subpath, path, name, name_prefix, host, subdomain]
18453
18739
 
18454
18740
  RuleOfferUncoveredReason:
18455
18741
  type: string
@@ -19785,6 +20071,12 @@ components:
19785
20071
  runId: { type: string }
19786
20072
  status: { type: string }
19787
20073
  summary: { type: string }
20074
+ completionId:
20075
+ type: string
20076
+ description: >
20077
+ server >= 7.135.0 — the idempotency anchor of this workflow completion: the SAME id rides every notification path of one completion (this frame / `task_notification` / fleet `bg_notification` / inbox replay) and
20078
+ is the dedup key ACROSS paths (the same value domain as `task_notification.completionId`). ABSENT when the engine gave none (an orphan the service terminated itself) — dedup on `runId` as before. The read faces
20079
+ (`GET /v1/workflows/{id}` etc.) deliberately never carry it. Older servers' re-delivered frames have no such key.
19788
20080
 
19789
20081
  # ── 🔴 #185a (2026-08-08): the DURABLE leg's three APPROVAL arms ────────────────────────────────
19790
20082
  # Shape rule for all three: `allOf: [<frame schema>, {the narrowed discriminant}]`. The keys are NOT
@@ -19960,6 +20252,13 @@ components:
19960
20252
  boundGrantAbsence:
19961
20253
  allOf: [{ $ref: '#/components/schemas/BoundGrantAbsence' }]
19962
20254
  description: '`pending` frames only (server >= 7.129.0 / core 7.51.0 #1688) — see PendingCheckpoint.boundGrantAbsence. A `live_pending` frame carries it only inside its `frame`.'
20255
+ boundGrantDigest: { type: string, description: '`pending` frames only (server >= 7.136.0) — see PendingCheckpoint.boundGrantDigest. A `live_pending` frame carries it only inside its `frame`.' }
20256
+ boundGrants:
20257
+ allOf: [{ $ref: '#/components/schemas/BoundSkillGrants' }]
20258
+ description: '`pending` frames only (server >= 7.136.0) — see PendingCheckpoint.boundGrants. DEPLOYMENT TEXT: render with contextual escaping.'
20259
+ classifierNoRuling:
20260
+ allOf: [{ $ref: '#/components/schemas/AutoModeFallbackCause' }]
20261
+ description: '`pending` frames only (server >= 7.136.0) — see PendingCheckpoint.classifierNoRuling.'
19963
20262
  askClass:
19964
20263
  allOf: [{ $ref: '#/components/schemas/AskClass' }]
19965
20264
  description: '`pending` frames only (server >= 7.109.0 / S-934 ②) — see PendingCheckpoint.askClass. A `live_pending` frame carries the pair only inside its `frame`.'
@@ -19970,20 +20269,35 @@ components:
19970
20269
  description: '`pending` frames only (server >= 7.109.0) — see PendingCheckpoint.classes.'
19971
20270
 
19972
20271
  ParkedDecideAccepted:
19973
- type: object
19974
20272
  description: >
19975
20273
  The 200 body of POST /v1/approvals/{sessionId}/decide when the pending checkpoint belongs to a
19976
20274
  PARKED background agent (server 1.267, ASSISTANT-WIRE-CONTRACT §4a parked variant; server
19977
- parked-decide.ts — exact literal shape). ACCEPTANCE semantics: the revive drives
19978
- ASYNCHRONOUSLY — 200 means accepted, not completed; a failed drive honestly re-parks the row and
19979
- the pending re-appears on the list. Task-level suspends keep the legacy resumed shape —
19980
- discriminate by `status === "resuming"`.
19981
- required: [taskId, status, decision]
19982
- additionalProperties: true
19983
- properties:
19984
- taskId: { type: string, description: 'The background agent handle (a* domain).' }
19985
- status: { type: string, enum: [resuming] }
19986
- decision: { type: string, enum: [approve, deny] }
20275
+ parked-decide.ts — exact literal shapes). TWO ARMS, read by `status` (the SDK type `ParkedDecideAccepted` is their union):
20276
+ (1) `status: "resuming"` — the ACCEPTANCE receipt. The revive drives ASYNCHRONOUSLY — 200 means accepted,
20277
+ not completed; a failed drive honestly re-parks the row and the pending re-appears on the list.
20278
+ (2) `status: "failed"` (server >= 7.136.0; `decision` is always `deny`) — the VOIDED receipt: on the two forms that can
20279
+ never revive (a parked child whose row carries no agent name — an unnamed `Agent` call — or no root-session anchor)
20280
+ a DENY voids the card under its own scope CAS; the card leaves the pending queue and the `/v1/approvals/stream` snapshot,
20281
+ the child ends `failed` (the engine's reconcile), the gated tool never runs, no `remember` is granted — and NOTHING is
20282
+ resuming. Do not wait for a revive that never comes. APPROVE on those forms stays `409 decide.row_unrevivable`; a lost
20283
+ void CAS is the existing `409 decide.gate_moved`.
20284
+ Task-level suspends answer their own shapes (see `ApprovalDecisionResult`): discriminate a parked receipt by `decision`
20285
+ present and `sessionId` absent, then read `status`.
20286
+ oneOf:
20287
+ - type: object
20288
+ required: [taskId, status, decision]
20289
+ additionalProperties: true
20290
+ properties:
20291
+ taskId: { type: string, description: 'The background agent handle (a* domain).' }
20292
+ status: { type: string, enum: [resuming] }
20293
+ decision: { type: string, enum: [approve, deny] }
20294
+ - type: object
20295
+ required: [taskId, status, decision]
20296
+ additionalProperties: true
20297
+ properties:
20298
+ taskId: { type: string, description: 'The background agent handle (a* domain).' }
20299
+ status: { type: string, enum: [failed] }
20300
+ decision: { type: string, enum: [deny] }
19987
20301
 
19988
20302
  FleetMeta:
19989
20303
  type: object
@@ -20484,7 +20798,7 @@ components:
20484
20798
  🔴 ONE RULE FOR EVERY "EFFECTIVE HALF ONLY" KEY (sdk 9.7.0, after a codex adversarial-review [high]):
20485
20799
  this schema's only consumer today is `GET /v1/diagnostics/wiring`'s `static` half, and every key marked
20486
20800
  "effective half only" (`leg`, `ask.effective`, `configFingerprint`, `tools`, `mods`, `lsp`,
20487
- `modelGate`, `autoMode`, `writeProtection`, `readDeny`, `autoConsolidation`, `fileHistory`, `instructions`) is ALWAYS ABSENT on that
20801
+ `modelGate`, `autoMode`, `writeProtection`, `readDeny`, `autoConsolidation`, `fileHistory`, `instructions`, `sessionRules`, `projectMemory`) is ALWAYS ABSENT on that
20488
20802
  wire ⇒ THEIR ABSENCE HERE CARRIES NO INFORMATION. The per-key absence readings ("absent = not armed",
20489
20803
  "absent = a third state", …) hold for the LIVE FRAME (`Event_wiring_manifest`) and must not be carried
20490
20804
  over to this response. For the facts behind those keys: a tenant reads
@@ -20497,7 +20811,7 @@ components:
20497
20811
  are EFFECTIVE-half only (the live `wiring_manifest` event) — as are the four per-leg sections
20498
20812
  `modelGate`, `autoMode`, `mcp` and `tools`, which describe what THIS leg did and have no static
20499
20813
  counterpart — and the STATIC half
20500
- (GET /v1/diagnostics/wiring) NEVER mints them; every other section, plus `governance`, is always
20814
+ (GET /v1/diagnostics/wiring) NEVER mints them; every other section NOT in the effective-half-only list above, plus `governance`, is always
20501
20815
  present on the static half. Fields stay optional because core may add or drop sections and that
20502
20816
  must not hard-break a client — read by half, and never wait on a key the half never sends.
20503
20817
  🔴 EVERY section is declared HERE, each saying which half mints it (sdk 8.9.0). Before that, `tools`
@@ -20518,6 +20832,18 @@ components:
20518
20832
  description: 'Which leg emitted it (effective half only; the static half has no leg identity).'
20519
20833
  properties:
20520
20834
  kind: { type: string, enum: [root, child, resume] }
20835
+ sessionRules:
20836
+ type: object
20837
+ required: [childrenReadLive]
20838
+ description: >
20839
+ server >= 7.135.0 (engine 7.55.0; effective half only — the static half never mints it): whether the session-rules layer is handed to the delegated legs. Same reading as on the live frame (`Event_wiring_manifest.sessionRules`):
20840
+ `childrenReadLive: true` = a rule written into the session NOW governs an already-running child's NEXT call; `false` = no rules layer; the section ABSENT = an older engine, or this half.
20841
+ properties:
20842
+ childrenReadLive: { type: boolean }
20843
+ projectMemory:
20844
+ type: string
20845
+ description: >
20846
+ server >= 7.135.0 (engine 7.55.0; effective half only): did the leg call the deployment's project-context seat — `on` / `disabled` (the request's `projectMemory: "disabled"`) / `absent` (no seat wired). Open on read; ABSENT = an older engine, or this half.
20521
20847
  ask:
20522
20848
  type: object
20523
20849
  description: 'Approval seam: wired form, which seat it came from, and (effective half) what it adjudicates to.'
@@ -21285,6 +21611,19 @@ components:
21285
21611
  usageMissing: { type: boolean, enum: [true] }
21286
21612
  stopReason: { type: string }
21287
21613
  errorMessage: { type: string, description: 'A post-stream error (contract: not thrown as HTTP).' }
21614
+ errorCode:
21615
+ type: string
21616
+ description: >-
21617
+ server >= 7.135.0 (core 7.55.0, passed through verbatim; failure only): the engine's brain error code of the failed reply (open on read; today `auth` / `invalid_request` / `rate_limit` / `server` / `network` /
21618
+ `http` / `stream_torn` / `refusal` / `length_empty`). ABSENT on success and on older servers.
21619
+ errorKind:
21620
+ type: string
21621
+ enum: [input_too_long, degenerate, length_empty]
21622
+ description: 'server >= 7.135.0 (failure only): the STRUCTURAL failure kind the reply carried, forwarded as is — `input_too_long` (the provider refused the request as over its window), `degenerate`, `length_empty`. Absent when the reply carried none.'
21623
+ inputTooLongRuledOut:
21624
+ type: boolean
21625
+ enum: [true]
21626
+ description: 'server >= 7.135.0 (failure only): present (always `true`) when the failure was a provider refusal whose minter RULED OUT input-too-long as the explanation (core `AssistantMessage.inputTooLongRuledOut`, passed through). It speaks ONLY to that negative: it does not say the status is 400 / 422 and does not say a verbatim resend repeats the failure (the engine also rules input length out for statuses such as 401 / 429 / 500) — triage on `errorCode`. Absent = no such judgment.'
21288
21627
 
21289
21628
  ToolApprovalDecision:
21290
21629
  type: string
@@ -21463,6 +21802,8 @@ components:
21463
21802
  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
21803
  a cancelled resume reads `void` once the server collects the reopened gate (`parked` in between, or `void` for an instant during the reopen).
21465
21804
  Older servers answered all of these with `parked` + `decidePath`.
21805
+ 🔴 server >= 7.135.0: a gate that was DECIDED and then REOPENED by the engine reads `parked` WITH `reopened` (`ToolApprovalReopened`: the engine's reopen reason + the earlier decision's
21806
+ `decision` / `decidedAtMs` / `decisionNote?`). `reopened` ABSENT = the read cannot say it was reopened (not "confirmed never reopened"). Older servers: always absent.
21466
21807
  oneOf:
21467
21808
  - $ref: '#/components/schemas/ToolApprovalReadPending'
21468
21809
  - $ref: '#/components/schemas/ToolApprovalReadDecided'
@@ -21500,6 +21841,29 @@ components:
21500
21841
  properties:
21501
21842
  state: { type: string, enum: [parked] }
21502
21843
  decidePath: { type: string, description: 'The durable decision path for this ask when it can be built.' }
21844
+ reopened:
21845
+ allOf: [{ $ref: '#/components/schemas/ToolApprovalReopened' }]
21846
+ description: 'server >= 7.135.0: present when the gate was decided and then reopened by the engine (the resume leg failed before the approved action started). Absent = the read cannot say it was reopened.'
21847
+ ToolApprovalReopenReason:
21848
+ type: string
21849
+ description: >
21850
+ `ToolApprovalReopened.reason` (server >= 7.135.0) — the engine's `REOPEN_REASONS`, verbatim and in order (engine-owned; the server echoes a word only when it is in this
21851
+ set, otherwise `reopened` is omitted entirely): `env_failed` (the environment / session side failed) · `tool_unavailable` (the approved tool is not there now — decide again) ·
21852
+ `constraint_rejected` (the decision input was rejected — decide again) · `config_refused` (the configuration was refused). Read open on the consumer side: an unknown word is "reopened, reason unknown".
21853
+ enum: [env_failed, tool_unavailable, constraint_rejected, config_refused]
21854
+ ToolApprovalReopened:
21855
+ type: object
21856
+ description: >
21857
+ `ToolApprovalReadParked.reopened` (server >= 7.135.0): evidence that a DECIDED gate was reopened by the engine. `reason` = the engine's reopen reason; `decision` / `decidedAtMs` / `decisionNote?` = the
21858
+ decision made BEFORE the reopen (the same three keys as a `decided` body). An unreadable earlier decision ⇒ the three keys are absent TOGETHER (only `reason` stays; `decisionNote` may always be absent).
21859
+ Single-instant read: if the row is decided and reopened between the two reads the server re-judges (three rounds in a row ⇒ falls back to plain `parked`).
21860
+ additionalProperties: false
21861
+ required: [reason]
21862
+ properties:
21863
+ reason: { $ref: '#/components/schemas/ToolApprovalReopenReason' }
21864
+ decision: { type: string, enum: [allow, deny] }
21865
+ decidedAtMs: { type: integer }
21866
+ decisionNote: { type: string }
21503
21867
  ToolApprovalReadVoid:
21504
21868
  allOf:
21505
21869
  - $ref: '#/components/schemas/ToolApprovalReadProjection'
@@ -22217,8 +22581,8 @@ components:
22217
22581
  match:
22218
22582
  allOf: [{ $ref: '#/components/schemas/RuleOfferMatch' }]
22219
22583
  description: >
22220
- The match form — the same seven-member vocabulary as a card's rule offer (`RuleOfferMatch`, the server's
22221
- `PERSISTED_RULE_MATCHES`): `exact` / `prefix` / `wildcard` / `subpath` / `path`, and from server >= 7.118.0
22584
+ The match form — the same ten-member vocabulary as a card's rule offer (`RuleOfferMatch`, the server's
22585
+ `PERSISTED_RULE_MATCHES`): `exact` / `prefix` / `wildcard` / `pattern` (server >= 7.135.0: the mid-star command form) / `subpath` / `path`, and from server >= 7.118.0
22222
22586
  `name` / `name_prefix` (a `Skill(<name>)` / `Skill(<prefix>:*)` rule).
22223
22587
  command: { type: string, description: 'What the matcher actually compares against, parsed out of `rule` at mint time and stored so no consumer re-parses. Trust this + `match`, not your own parse of `rule`.' }
22224
22588
  adds: