@sema-agent/sdk 16.1.0 โ†’ 16.3.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 (74) hide show
  1. package/README.md +111 -1
  2. package/dist/errors.d.ts +52 -12
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +31 -5
  5. package/dist/errors.js.map +1 -1
  6. package/dist/events.d.ts +8 -0
  7. package/dist/events.d.ts.map +1 -1
  8. package/dist/events.js.map +1 -1
  9. package/dist/index.d.ts +7 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/resources/adoption.d.ts +2 -1
  14. package/dist/resources/adoption.d.ts.map +1 -1
  15. package/dist/resources/adoption.js +2 -1
  16. package/dist/resources/adoption.js.map +1 -1
  17. package/dist/resources/approvals.d.ts +10 -3
  18. package/dist/resources/approvals.d.ts.map +1 -1
  19. package/dist/resources/approvals.js +11 -3
  20. package/dist/resources/approvals.js.map +1 -1
  21. package/dist/resources/assistant.d.ts +4 -2
  22. package/dist/resources/assistant.d.ts.map +1 -1
  23. package/dist/resources/assistant.js.map +1 -1
  24. package/dist/resources/leader.d.ts +5 -4
  25. package/dist/resources/leader.d.ts.map +1 -1
  26. package/dist/resources/leader.js +5 -4
  27. package/dist/resources/leader.js.map +1 -1
  28. package/dist/resources/memory.d.ts +6 -2
  29. package/dist/resources/memory.d.ts.map +1 -1
  30. package/dist/resources/memory.js +3 -1
  31. package/dist/resources/memory.js.map +1 -1
  32. package/dist/resources/ops.d.ts +3 -0
  33. package/dist/resources/ops.d.ts.map +1 -1
  34. package/dist/resources/ops.js +3 -0
  35. package/dist/resources/ops.js.map +1 -1
  36. package/dist/resources/rules.d.ts +4 -3
  37. package/dist/resources/rules.d.ts.map +1 -1
  38. package/dist/resources/rules.js +3 -3
  39. package/dist/resources/rules.js.map +1 -1
  40. package/dist/resources/runs.d.ts +26 -6
  41. package/dist/resources/runs.d.ts.map +1 -1
  42. package/dist/resources/runs.js +9 -2
  43. package/dist/resources/runs.js.map +1 -1
  44. package/dist/resources/session-sync.d.ts +11 -7
  45. package/dist/resources/session-sync.d.ts.map +1 -1
  46. package/dist/resources/session-sync.js +9 -6
  47. package/dist/resources/session-sync.js.map +1 -1
  48. package/dist/resources/sessions.d.ts +9 -3
  49. package/dist/resources/sessions.d.ts.map +1 -1
  50. package/dist/resources/sessions.js +9 -3
  51. package/dist/resources/sessions.js.map +1 -1
  52. package/dist/resources/tasks.d.ts +13 -1
  53. package/dist/resources/tasks.d.ts.map +1 -1
  54. package/dist/resources/tasks.js +13 -1
  55. package/dist/resources/tasks.js.map +1 -1
  56. package/dist/resources/tool-approvals.d.ts +83 -12
  57. package/dist/resources/tool-approvals.d.ts.map +1 -1
  58. package/dist/resources/tool-approvals.js +37 -1
  59. package/dist/resources/tool-approvals.js.map +1 -1
  60. package/dist/resources/workflows.d.ts +5 -2
  61. package/dist/resources/workflows.d.ts.map +1 -1
  62. package/dist/resources/workflows.js +5 -2
  63. package/dist/resources/workflows.js.map +1 -1
  64. package/dist/settings.d.ts +22 -8
  65. package/dist/settings.d.ts.map +1 -1
  66. package/dist/sync.d.ts +19 -2
  67. package/dist/sync.d.ts.map +1 -1
  68. package/dist/sync.js +88 -2
  69. package/dist/sync.js.map +1 -1
  70. package/dist/types.d.ts +195 -26
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js.map +1 -1
  73. package/openapi.yaml +464 -101
  74. package/package.json +1 -1
package/openapi.yaml CHANGED
@@ -416,7 +416,8 @@ paths:
416
416
  ๐Ÿ”ด PREPARE-TIME REFUSALS ARE TRIAGED ON WORK DONE (server >= 7.104.0 / S-271, one rule for the whole table): a
417
417
  prepare-time refusal code that HAS a status in the server's per-code triage table (`resume_at.*` โ€” `resume_at.not_found` is 404, the rest 422 โ€”, governance codes,
418
418
  `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported` / `rewind_snapshot.unresolvable`, the
419
- registered `config.*` codes incl. the three roster-bound ones, `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
419
+ registered `config.*` codes incl. the three roster-bound ones and โ€” server >= 7.116.0 โ€” `config.invalid_checkpoint_store` (400) and
420
+ `config.user_invoked_skill_unknown` (400: top-level `userInvokedSkills` named a skill this run does not carry) and โ€” server >= 7.117.0 โ€” `config.image_invalid` (400: an inline image failed the engine's byte admission; `error` starts with `TaskSpec.images: `), `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
420
421
  200 with a failed body) answers its 4xx / 5xx ERROR BODY only when the whole submission did ZERO WORK (cumulative
421
422
  `stats.turns === 0` AND `stats.tokens === 0` โ€” the common case: nothing billed, not idempotency-cached). When a
422
423
  `verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
@@ -885,6 +886,13 @@ paths:
885
886
  โš ๏ธ Against a server 7.19.0โ€“7.22.0 the OLD caveat applies verbatim: only the parked legs dedupe โ€”
886
887
  a lost 200 retried with the same key injects TWICE with byte-identical receipts; treat a lost 200
887
888
  there as "outcome unknown", never "safe to retry".
889
+ ๐Ÿ”ด server >= 7.117.0 โ€” TWO MORE 409 answers on a TERMINAL run (the `parked_for_wake` leg), refused BEFORE the wake card is minted
890
+ (no card is minted; the row is untouched; on 7.116.x this was a 202 `parked_for_wake` followed by a refusal at `/wake`, i.e. a card that could
891
+ never be redeemed): when the stored submit body of this session is one this version no longer replays โ€” errorCode
892
+ `checkpoint.unsupported_version` (`retriable: true` = the card is still pending / cancellable, NOT "a retry will succeed"; the stored body carries
893
+ `settings.permissions.allow` under an earlier reading โ€” list or statement era) or errorCode `resume_blocked_by_policy` (`blockedBy: "request.field_invalid"`;
894
+ the stored skill `disallowedTools` carries an entry the engine now reads as nothing). Both answers are word-for-word the ones `POST /v1/sessions/{sessionId}/wake`
895
+ gives for the same body. Way out: cancel the task and resubmit as a new one (with the entry fixed). The SDK does not retry them (409 is not in the retry set).
888
896
  content:
889
897
  application/json:
890
898
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -960,7 +968,8 @@ paths:
960
968
  un-started ones settle as interrupted pairs, in-flight tools are NOT collateral. TWO shapes,
961
969
  discriminated by `text` presence.
962
970
  WITH text = cut + steer: the in-flight turn is cut and the text goes to the FRONT of the queue โ€” the
963
- `"now"` tier is the VERB's semantics, not a body field (the steer op's `priority` stays advisory) โ€”
971
+ `"now"` tier is the VERB's semantics, not a body field (the steer op's own `priority` is a separate
972
+ input: it takes effect only on a server whose `Capabilities.fleet.steerPriority` reads `"engine"`) โ€”
964
973
  and the run CONTINUES. 202 `{taskId, status, delivery:"queued-immediate", messageId, note}`;
965
974
  `messageId` โ‰ก the engine inputId, and a real cut is proven ONLY by a `task.turn_interrupted` event on
966
975
  the run stream whose `detail.inputId` matches it (core: no false interrupt claims โ€” every real cut
@@ -2419,9 +2428,10 @@ paths:
2419
2428
  written. DETERMINISTIC: a retry cannot change it (the SDK does not auto-retry this code; it arrives as a bare `APIError`
2420
2429
  with `errorCode: "corrupt"`); the repair is the operator's โ€” fix the file or delete it (deleting = an explicit "no
2421
2430
  rules"). For a damaged rules row `error` names the session, the damage grade and both exits, with NO on-disk path.
2422
- โš ๏ธ The same code also comes from the write path's high-water-mark sidecar (`.hwm.json`) being unreadable, and that one
2423
- sentence STILL names the sidecar's on-disk path (server 7.114.0 discloses it as a known gap: core #1307 / #1309, core
2424
- 7.41.0). Also `internal.error` (unclassified).
2431
+ โš ๏ธ The same code also comes from the write path's high-water-mark sidecar (`.hwm.json`) being unreadable: the sentence names the
2432
+ object, the damage and both exits (fix the mark file, or delete it โ€” deleting gives up the number it was holding). server >= 7.116.0
2433
+ does NOT name the on-disk path any more (the location goes to the operator log `file_store_corrupt_read` only); server <= 7.115.x
2434
+ still did. Also `internal.error` (unclassified).
2425
2435
  content:
2426
2436
  application/json:
2427
2437
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3039,14 +3049,13 @@ paths:
3039
3049
  2c session-sync PUSH Phase A โ€” a SMALL metadata body (`entryIds, snapshots, policy, anchors, resolution?`); the
3040
3050
  entries stream in Phase B. The cloud classifies (ยง7), pre-checks blob presence (a missing referenced hash โ†’
3041
3051
  422 `missing_blob` โ€” PUT it first), guards the import lease + any active run (409), then mints a stagingId.
3042
- โš ๏ธ S-15 EPOCH DEBT (registered 2026-08-30, codex adversarial review; migration is a SEPARATE batch):
3043
- server โ‰ฅ7.52's Phase A actually consumes `{entryIds, fileHistory?, policy, anchors, resolution?,
3044
- logDigest?}` (routes/session-sync.ts) โ€” the `snapshots` key below is SILENTLY IGNORED there (an
3045
- unknown body key), so on โ‰ฅ7.52 a push lands entries/policy/anchors and the session itself but NO
3046
- file snapshots/history; the snapshot-epoch shape below is what โ‰ค7.51 servers consume. The full
3047
- migration (SessionManifest/SessionBundle/push-pull orchestration + `fileHistory: FileHistoryExport |
3048
- null` + epoch negotiation) is tracked as its own debt โ€” this op's schema deliberately stays the
3049
- โ‰ค7.51 truth until that batch lands rather than half-changing.
3052
+ โš ๏ธ S-15 EPOCH: server โ‰ฅ7.52's Phase A consumes `{entryIds, fileHistory?, policy, anchors, resolution?,
3053
+ logDigest?}` (routes/session-sync.ts) โ€” the `snapshots` key is SILENTLY IGNORED there (an unknown body key); the
3054
+ file history rides `fileHistory` (a `FileHistoryExport` envelope, `null` / absent = none) and every blob it references must have been
3055
+ `PUT โ€ฆ/sync/blobs/{hash}` first. โ‰ค7.51 servers consume `snapshots` and do not know `fileHistory`. SDK 16.2.0 carries
3056
+ `fileHistory` in `pushSessionBundle` / `importSessionBundle` (and fills `snapshots: []` on the pull side when the manifest omits it); a hand-rolled
3057
+ push must send `fileHistory` itself, and only to a peer whose `capabilities.sessionSyncFileHistory` is not false (server >= 7.116.0 โ€” else the
3058
+ commit 422s `missing_blob`).
3050
3059
  `identical` โ†’ `{ relation:"identical", basis, payloadVerified }` with NO stagingId (skip Phase B);
3051
3060
  `basis` is `"entry-ids+digest"` when the caller supplied a comparable digest and it matched, else
3052
3061
  `"entry-ids"` (id-set equality only) โ€” see the `ImportStaged` schema, which has carried these two
@@ -3063,6 +3072,11 @@ paths:
3063
3072
  properties:
3064
3073
  entryIds: { type: array, items: { type: string } }
3065
3074
  snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' } }
3075
+ fileHistory:
3076
+ oneOf:
3077
+ - { $ref: '#/components/schemas/FileHistoryExport' }
3078
+ - { type: 'null' }
3079
+ description: 'server >= 7.52: the file-history envelope (replaces `snapshots`); `null` / absent = none. Every blob it references must already be PUT.'
3066
3080
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
3067
3081
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
3068
3082
  resolution: { type: string, enum: [overwrite-dst], description: Override a fork/stale loss-of-history conflict. }
@@ -4639,10 +4653,11 @@ paths:
4639
4653
  `LEADER_ENABLED` (default OFF) + `REMOTE_EXEC=e2b`. Mode is a SERVER deploy flag, not a client choice:
4640
4654
  `LEADER_FANOUT_ENABLED=true` (default) lets the router fan out to N isolated workers (= the value-HOLD
4641
4655
  correctness bet); `=false` pins a single worker. Either way it runs the E2B+git pipeline. Door B's brain
4642
- is the strong single agent via `/v1/tasks`+`/v1/runs`+`scenario`. When disabled (`LEADER_ENABLED` off)
4643
- the route is simply NOT REGISTERED โ†’ the generic **404** `not_found.route`, not 501.
4644
- (Corrected 2026-07-31: "When disabled โ†’ 501" was never true โ€” no code path emits 501 here; a consumer
4645
- branching on 501 to detect "leader unavailable" never fires.)
4656
+ is the strong single agent via `/v1/tasks`+`/v1/runs`+`scenario`. When there is no leader face on the deployment
4657
+ (`LEADER_ENABLED` off) the answer depends on the server: server >= 7.116.0 mounts the route unconditionally and answers
4658
+ **501 `capability.leader_endpoint_required`** (new code; under multi-tenant the identity gate's 401 still comes first);
4659
+ <= 7.115.x did not register the route at all โ†’ the generic **404** `not_found.route`. Detect "leader unavailable" by
4660
+ `errorCode` (or the `capabilities.leader` bit, which reads the same fact as the gate), not by the status alone.
4646
4661
 
4647
4662
 
4648
4663
  ๐Ÿ”ด IDEMPOTENCY (server >= 7.24.0): this POST now consumes the `Idempotency-Key` header โ€” the header is
@@ -4783,7 +4798,8 @@ paths:
4783
4798
  description: >-
4784
4799
  (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate (not the bare
4785
4800
  isOperator, whose empty-OPERATOR_PRINCIPALS "true-for-all" would make this index-mutating door
4786
- world-writable).
4801
+ world-writable). With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501
4802
+ `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization).
4787
4803
  content:
4788
4804
  application/json:
4789
4805
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -4837,7 +4853,7 @@ paths:
4837
4853
  '400': { $ref: '#/components/responses/BadRequest' }
4838
4854
  '401': { $ref: '#/components/responses/Unauthorized' }
4839
4855
  '403':
4840
- description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate (ยงP2.4a).
4856
+ description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate (ยงP2.4a). With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).
4841
4857
  content:
4842
4858
  application/json:
4843
4859
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -4891,7 +4907,7 @@ paths:
4891
4907
  responses:
4892
4908
  '200': { description: '{ bake }.', content: { application/json: { schema: { type: object, required: [bake], additionalProperties: false, properties: { bake: { $ref: '#/components/schemas/BakeRecordView' } } } } } }
4893
4909
  '403':
4894
- description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate.
4910
+ description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).
4895
4911
  content:
4896
4912
  application/json:
4897
4913
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -4909,7 +4925,7 @@ paths:
4909
4925
  responses:
4910
4926
  '202': { description: '{ bakeId, cancelRequested, note }.', content: { application/json: { schema: { $ref: '#/components/schemas/BakeCancelAck' } } } }
4911
4927
  '403':
4912
- description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate.
4928
+ description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).
4913
4929
  content:
4914
4930
  application/json:
4915
4931
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -4971,7 +4987,7 @@ paths:
4971
4987
  responses:
4972
4988
  '200': { description: 'SSE stream.', content: { text/event-stream: { schema: { type: string } } } }
4973
4989
  '403':
4974
- description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate (this summary previously did not mention the restriction at all).
4990
+ description: (declared 2026-07-31) errorCode "auth.operator_only" โ€” EXPLICIT operator gate (this summary previously did not mention the restriction at all). With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).
4975
4991
  content:
4976
4992
  application/json:
4977
4993
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -5183,7 +5199,7 @@ paths:
5183
5199
  schema: { $ref: '#/components/schemas/EntryProvenanceAccount' }
5184
5200
  '400': { description: 'errorCode "request.id_invalid" โ€” the entry id could not be decoded.' }
5185
5201
  '401': { $ref: '#/components/responses/Unauthorized' }
5186
- '403': { description: 'errorCode "auth.operator_only" โ€” provenance and erasure are operator-only deployment actions.' }
5202
+ '403': { description: 'errorCode "auth.operator_only" โ€” provenance and erasure are operator-only deployment actions. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).' }
5187
5203
  '500':
5188
5204
  description: >
5189
5205
  errorCode "internal.memory_control_plane_corrupt" โ€” core's FAIL-CLOSED refusal: the control plane
@@ -5253,7 +5269,7 @@ paths:
5253
5269
  application/json:
5254
5270
  schema: { $ref: '#/components/schemas/ErrorResponse' }
5255
5271
  '401': { $ref: '#/components/responses/Unauthorized' }
5256
- '403': { description: 'errorCode "auth.operator_only" โ€” provenance and erasure are operator-only deployment actions.' }
5272
+ '403': { description: 'errorCode "auth.operator_only" โ€” provenance and erasure are operator-only deployment actions. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).' }
5257
5273
  '409':
5258
5274
  description: >
5259
5275
  Three core-minted codes, passed through verbatim; branch on `errorCode` (the status cannot tell them
@@ -5332,7 +5348,7 @@ paths:
5332
5348
  schema: { $ref: '#/components/schemas/MemoryOriginExternalResult' }
5333
5349
  '400': { description: 'errorCode "request.query_invalid" โ€” `scopes` is absent or normalizes to an empty list. The refusal text also names where scope keys come from and repeats that an empty answer is not a clean store.' }
5334
5350
  '401': { $ref: '#/components/responses/Unauthorized' }
5335
- '403': { description: 'errorCode "auth.operator_only" โ€” origin marking and clearance are operator-only deployment actions.' }
5351
+ '403': { description: 'errorCode "auth.operator_only" โ€” origin marking and clearance are operator-only deployment actions. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).' }
5336
5352
  '500':
5337
5353
  description: 'errorCode "internal.memory_control_plane_corrupt" (meaning (1) only on this read face) or "internal.error" (unclassified, text not echoed).'
5338
5354
  content:
@@ -5382,7 +5398,7 @@ paths:
5382
5398
  application/json:
5383
5399
  schema: { $ref: '#/components/schemas/MemoryOriginClearancesResult' }
5384
5400
  '401': { $ref: '#/components/responses/Unauthorized' }
5385
- '403': { description: 'errorCode "auth.operator_only" โ€” origin marking and clearance are operator-only deployment actions.' }
5401
+ '403': { description: 'errorCode "auth.operator_only" โ€” origin marking and clearance are operator-only deployment actions. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).' }
5386
5402
  '500':
5387
5403
  description: 'errorCode "internal.memory_control_plane_corrupt" (the ledger cannot be read soundly) or "internal.error".'
5388
5404
  content:
@@ -5446,7 +5462,7 @@ paths:
5446
5462
  application/json:
5447
5463
  schema: { $ref: '#/components/schemas/ErrorResponse' }
5448
5464
  '401': { $ref: '#/components/responses/Unauthorized' }
5449
- '403': { description: 'errorCode "auth.operator_only" โ€” origin marking and clearance are operator-only deployment actions.' }
5465
+ '403': { description: 'errorCode "auth.operator_only" โ€” origin marking and clearance are operator-only deployment actions. With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).' }
5450
5466
  '409':
5451
5467
  description: >
5452
5468
  Six core-minted codes, passed through verbatim; BRANCH ON `errorCode` โ€” the six operator actions all
@@ -5569,7 +5585,7 @@ paths:
5569
5585
  application/json:
5570
5586
  schema: { $ref: '#/components/schemas/WiringDiagnostics' }
5571
5587
  '401': { $ref: '#/components/responses/Unauthorized' }
5572
- '403': { description: 'Multi-tenant worker: wiring diagnostics are operator-only (`auth.operator_only`).' }
5588
+ '403': { description: 'Multi-tenant worker: wiring diagnostics are operator-only (`auth.operator_only`). With an EMPTY `OPERATOR_PRINCIPALS` the answer is instead 501 `capability.operator_principals_required` (server >= 7.116.0; any identity, ahead of authorization โ€” branch on `errorCode`, not the status).' }
5573
5589
  '404': { description: 'This worker serves no wiring diagnostics (older server, or a process not assembled by the composition root).' }
5574
5590
 
5575
5591
  /v1/sendfile-links:
@@ -5899,7 +5915,9 @@ paths:
5899
5915
  description: >
5900
5916
  OPERATOR-ONLY cross-tenant read. Absent (or equal to the caller's own principal) = read your own
5901
5917
  rules. Anything else requires explicit OPERATOR_PRINCIPALS membership โ‡’ otherwise 403
5902
- `auth.operator_only`. Rule text contains command shapes, so third-party reads are an information
5918
+ `auth.operator_only` (server >= 7.116.0: with an EMPTY list the answer is 501
5919
+ `capability.operator_principals_required` instead โ€” any identity, ahead of authorization; your own rules never
5920
+ go through the operator gate). Rule text contains command shapes, so third-party reads are an information
5903
5921
  disclosure and are deliberately not opened to non-operators.
5904
5922
  - name: scope
5905
5923
  in: query
@@ -6644,7 +6662,14 @@ components:
6644
6662
  application/json:
6645
6663
  schema: { $ref: '#/components/schemas/QuotaError' }
6646
6664
  NotImplemented:
6647
- description: 501 โ€” capability not available (e.g. no TiDB run store; leader gated off; `wake` unsupported).
6665
+ description: >
6666
+ 501 โ€” "this deployment cannot answer this" (e.g. no TiDB run store; `wake` unsupported). server >= 7.116.0: HTTP has exactly ONE status for
6667
+ "unavailable on this deployment" โ€” 501 โ€” and the CAUSE is the code: `capability.*` = change the deployment shape / wire the facility
6668
+ (incl. the new `capability.leader_endpoint_required` for `/v1/leader*` and `capability.shared_memory_required` for `/v1/shared-memory/*`),
6669
+ `feature.*` = turn a knob on (incl. the new `feature.memory_consolidation_disabled` and `feature.config_catalog_disabled`), and
6670
+ `capability.operator_principals_required` = configure `OPERATOR_PRINCIPALS` (every operator-only endpoint answers it, for any identity,
6671
+ when the list is empty; a non-empty list the caller is not on is still 403 `auth.operator_only`). Branch on `errorCode`, not the status
6672
+ (these families answered 404 / 403 on <= 7.115.x). The one registered exception is `a2aServe`'s two routes (404, anti-fingerprint).
6648
6673
  content:
6649
6674
  application/json:
6650
6675
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -6824,7 +6849,18 @@ components:
6824
6849
  name: { type: string }
6825
6850
  description: { type: string }
6826
6851
  content: { type: string }
6827
- manifest: { type: object, additionalProperties: true }
6852
+ manifest:
6853
+ type: object
6854
+ additionalProperties: true
6855
+ deprecated: true
6856
+ description: >
6857
+ RETIRED โ€” server >= 7.116.0 rejects the WHOLE skill when this key is present on a delegated agent's skill (unknown key:
6858
+ `agent.skills must be โ€ฆ`; a top-level skill answers 400 `request.field_invalid` "skill.manifest was retired in core 7.41.0 โ€” โ€ฆ").
6859
+ core 7.41.0 retired the skill manifest (hard break, no alias). Rewrite `manifest.allowTools` to the skill's
6860
+ `allowedTools` (an allow-rule source, not a fence); `manifest.allowPaths` has no replacement (use the session rules'
6861
+ `allowDirs`). Older servers (<= 7.115.x) still accept it; the key stays declared here until the next major.
6862
+ allowedTools: { type: array, items: { type: string }, description: 'server >= 7.116.0 โ€” the skill''s allow-rule source (see `SkillSpec.allowedTools`); <= 7.115.x rejects the key (unknown key).' }
6863
+ disallowedTools: { type: array, items: { type: string }, description: 'server >= 7.116.0 โ€” the skill''s deny-rule source (see `SkillSpec.disallowedTools`); <= 7.115.x rejects the key (unknown key).' }
6828
6864
  files:
6829
6865
  type: array
6830
6866
  items:
@@ -6933,6 +6969,18 @@ components:
6933
6969
  LISTING RENDERER, not to this gate: turn it down with `settings.skillListingBudgetFraction` /
6934
6970
  `settings.skillListingMaxDescChars`.
6935
6971
  items: { $ref: '#/components/schemas/SkillSpec' }
6972
+ userInvokedSkills:
6973
+ type: array
6974
+ items: { type: string, minLength: 1 }
6975
+ description: >
6976
+ server >= 7.116.0 (core 7.41.0 #1303). Skills the HUMAN called by name (the shell parses the typed `/name`; by `SkillSpec.name`).
6977
+ Their `allowedTools` / `disallowedTools` take effect from the start of the run and NO `Skill` card is raised (the human naming
6978
+ it is the statement); it governs this one run only (a steer does not end it; the next submit does not carry it). Every name must be
6979
+ a skill the run carries (scenario factory โˆช this request's `skills`), else the engine refuses to start: 400
6980
+ `config.user_invoked_skill_unknown` (sync leg `POST /v1/tasks`; the async / live-stream legs answer 2xx and the same code lands on the
6981
+ terminal run row; `disableSkills: true` makes any name this code). A bad value shape (bare string / empty-string member / non-string /
6982
+ object / null) is a 400 `request.field_invalid`. Does not deliver the skill body. PROBE `capabilities.userInvokedSkills` (constant true);
6983
+ absent = an older server that answers 400 `request.body_shape` listing the key in `unknownKeys` โ€” do not send it.
6936
6984
  reasoningEffort:
6937
6985
  type: string
6938
6986
  enum: [off, minimal, low, medium, high, xhigh, max]
@@ -7146,6 +7194,13 @@ components:
7146
7194
  description: 'core 5.11.0: extra READ-ONLY absolute host paths โ€” read fence only (classify read-boundary auto-allow + read_file/grep containment); writes to the same dirs are still refused. Same shape/400 rules as additionalDirectories; servers <6.6.0 silently ignore (harmless additive).'
7147
7195
  images:
7148
7196
  type: array
7197
+ description: >
7198
+ Image input: `{ data, mimeType }` (inline base64) or `{ url }`. ๐Ÿ”ด INLINE-ARM ADMISSION (server >= 7.117.0 / core 7.42.0): `mimeType` must be one of `image/png` /
7199
+ `image/jpeg` / `image/gif` / `image/webp` (the API's type word, not a file extension), the decoded bytes must carry the DECLARED type's file signature, and the decoded size must
7200
+ 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
7201
+ `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).
7202
+ 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
7203
+ 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).
7149
7204
  items:
7150
7205
  oneOf:
7151
7206
  - type: object
@@ -7289,9 +7344,20 @@ components:
7289
7344
  the delivery lane, so raising it only helps a model whose declared window is below 200 000 tokens.
7290
7345
  The engine's own twin code `config.skill_listing_budget_invalid` (400) is NOT reachable through this
7291
7346
  submit leg today โ€” it belongs to callers that build a `TaskSpec` directly.
7292
- ๐Ÿ”ด `permissions.allow` IS A STATEMENT, NOT A WHITELIST (server >= 7.115.0 / S-654; probe
7293
- `capabilities.settings.permissions.allowGrant === true` โ€” an absent key is an older server that reads the bucket as a
7294
- list, so put nothing in it there). The bucket is compiled by the SAME rule compiler as `deny` / `ask`: a hit is a
7347
+ ๐Ÿ”ด `permissions.allow` โ€” THE READING DEPENDS ON THE SERVER GENERATION (probe `capabilities.settings.permissions`; an absent bit = the older generation).
7348
+ server >= 7.117.0 (`allowLayer === true`, core 7.42.0): the WHOLE list goes to the engine's allow layer and a listed call is NOT ASKED โ€” the engine's ordinary and
7349
+ "nobody stated it" questions are cleared, built-in write gates and shell checks included (like the reference implementation's `permissions.allow`); content forms
7350
+ (`Bash(git:*)`, `Read(//dir/**)`) are accepted; entries the engine reads as nothing (`*`, write-path forms such as `Edit(src/**)`, domain forms, names with whitespace) are NOT
7351
+ refused โ€” the run opens with an `engine_notice` `permission.allowed_tool_not_granted`. Any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` /
7352
+ `MANUAL_MODE_SHELL_GATE=always`, write protection, governance, a hook's ask and plan-mode asks still ask (a matching `request` member then shows as `allowRuleShadowed` on the
7353
+ card). The submit gate judges only the shape (array of strings, at most 1000 entries => 400) and the NAMES (the same judge and the same sentence as `deny` / `ask`: a retired name,
7354
+ an unprefixed short name, or a protocol name whose server segment cannot be minted / is missing / is a wildcard (`mcp__*`) is a whole-request 422) โ€” never the content.
7355
+ ๐Ÿ”ด THE 422 SENTENCE (server >= 7.117.0): `error` starts with `request settings cannot be applied as written: ` (<= 7.116.x: a different sentence that said tighten-only โ€” all three
7356
+ buckets share this exit, and the old head misled a caller writing a tightening rule and lied about the allow bucket). Do NOT dispatch on the sentence: it carries no code of its own, so
7357
+ `errorCode` is the status-derived coarse code `request.unprocessable`.
7358
+ The skill lists (`skills[].allowedTools`) read exactly like this bucket from 7.117.0 on.
7359
+ Server 7.115.0โ€“7.116.x (`allowGrant` without `allowLayer`): `permissions.allow` IS A STATEMENT, NOT A WHITELIST โ€” an absent `allowGrant` is a server <= 7.114.x that reads the bucket
7360
+ as a list, so put nothing in it there. The bucket is compiled by the SAME rule compiler as `deny` / `ask`: a hit is a
7295
7361
  reasoned allow = "somebody stated it" (it answers an MCP / A2A server-tool call nobody stated; without it an
7296
7362
  ask-class mode raises a card, and with no approval surface the call is refused); a miss states nothing and the call is
7297
7363
  judged as usual โ€” a tool off the list is NOT refused any more (<= 7.114.x refused it). It does NOT skip the question
@@ -7300,8 +7366,11 @@ components:
7300
7366
  for that). For "only these tools" use the real knobs: the session rule `toolAllow`
7301
7367
  (`PUT /v1/sessions/{sessionId}/policy`), per-request `excludeTools` / `excludeAllTools`, or the deployment's scenario
7302
7368
  tool surface. Entry forms: a tool-name rule (exact name, `mcp__<server>__*`, `a2a__<peer>__*`; `Bash(*)` is accepted
7303
- and compiled as a whole-tool rule); a retired name, a short name with no protocol prefix, a missing / two-segment
7304
- MCP name (`mcp__figma` -> write `mcp__figma__*`), a backslash, more than 512 characters, or a CONTENT form
7369
+ and compiled as a whole-tool rule; server >= 7.116.0 also accepts the two-segment covering form `mcp__<server>` / `a2a__<peer>`,
7370
+ read as the whole server โ€” same meaning as `mcp__<server>__*`); a retired name, a short name with no protocol prefix, a missing-segment
7371
+ name (`mcp__` / `mcp__<server>__`), a protocol name whose server segment the engine cannot have minted (e.g. `mcp__github.com__*` โ€”
7372
+ write `mcp__github_com__*`; server >= 7.116.0 refuses it with a 422 instead of accepting a rule that can never match), a backslash,
7373
+ more than 512 characters, or a CONTENT form
7305
7374
  (`Bash(npm:*)`, `Read(./x)`, `WebFetch(domain:...)`) is a whole-request 422 (permanent content-form allows live in the
7306
7375
  persistent rule store: `/v1/rules/cc-import`, or answer yes on a card). The same applies to a PreToolUse hook: an
7307
7376
  EXPLICIT `permissionDecision: "allow"` states it (and binds only the arguments the hook saw); a hook that only adds
@@ -8218,6 +8287,16 @@ components:
8218
8287
  is unchanged at 7.64.0 โ€” only its source moved (projected from `result.terminal`, or read straight
8219
8288
  off a legacy flat blob). โš ๏ธ K-24 / S-173 (server >= 7.104.0): on a `needs_review` PARK row it is core's canonical
8220
8289
  `review.pending` (absent before) โ€” never read "errorCode present" as "failed"; read `status` first.
8290
+ ๐Ÿ”ด server >= 7.116.0 (S-1099) adds three values for the run store's own terminal verdicts, which used to live only in the `error`
8291
+ sentence: `run.stalled` (heartbeat-lost reap โ€” and the read-time fold that precedes the reap; sentence `run stalled (instance lost?)`),
8292
+ `run.orphaned` (local-lane restart / session-occupancy takeover reclaim; sentence `run orphaned by engine restart`) and
8293
+ `approval.expired` (the expiry reap of a durably parked approval; the column already carried it, the read face did not project it;
8294
+ sentence `approval expired before decision`). None of the three means the request was wrong โ€” resending the same request is
8295
+ pointless, only a new submission makes a new run. READ `status` FIRST (`failed`), then branch on the code. Open set (a string; always keep a
8296
+ default arm). ABSENCE: rows written by <= 7.115.x carry only the sentence; a one-time startup migration backfills the code
8297
+ (log `one_time_migrations_applied` `holderDeathCodeRowsBackfilled`) and a new replica reads such a row as the code during the upgrade window,
8298
+ but the migration has a window (30 days after the release, then the write-back and the read half are deleted) โ€” so
8299
+ "no `errorCode`" never proves "not a holder death": rows migrated late or after the window stay sentence-only.
8221
8300
  error: { type: string }
8222
8301
  jobId:
8223
8302
  type: string
@@ -9029,7 +9108,8 @@ components:
9029
9108
  server >=7.12.0 โ€” the `GET /v1/outcomes` mechanical-signal read aggregate (design/73 ยง7.2) is
9030
9109
  available. Predicate mirrors the route's refusal arms exactly: a QUERYABLE ledger is wired (a
9031
9110
  file-only sink writes JSONL and has no query face โ‡’ 501), AND on a multi-tenant deployment the
9032
- operator list is non-empty (single-user has no such gate โ€” the only user IS the operator).
9111
+ operator list is non-empty (single-user has no such gate โ€” the only user IS the operator); with an empty
9112
+ list the multi-tenant route answers 501 `capability.operator_principals_required` (server >= 7.116.0; 403 before).
9033
9113
  Added because this face previously had a 501 but NO capability bit, leaving consumers to
9034
9114
  trial-by-501 โ€” the exact pattern this capability surface exists to remove.
9035
9115
  images:
@@ -9053,7 +9133,17 @@ components:
9053
9133
  principalโ†’org membership fold present), so true โŸบ the face actually works โ€” a deployment with
9054
9134
  the store but no membership authority deliberately reports false (and does not mount the routes).
9055
9135
  workflows: { type: boolean, description: "ENGINE-CAN: this worker's engine can orchestrate S8 self-orchestration workflows (server: `workflowsCapable ?? Boolean(workflowRunStore)`). The MOUNTING of the durable /v1/workflows read routes is the separate `workflowsList` bit below; the two coincide in today's wiring but are declared as orthogonal axes." }
9056
- workflowsList: { type: boolean, description: "`GET /v1/workflows` list face (separate from the per-run reads)." }
9136
+ workflowsList: { type: boolean, description: "`GET /v1/workflows` list / detail / stream faces (the workflow run store is wired; separate from the per-run reads). server >= 7.116.0: the journal face `GET /v1/workflows/{id}/journal` is advertised by the new bit `workflowsJournal`, not by this one (value unchanged)." }
9137
+ workflowsJournal:
9138
+ type: boolean
9139
+ description: >
9140
+ server >= 7.116.0 (predicate class "facility": the workflow run store AND the journal store). Advertises
9141
+ `GET /v1/workflows/{id}/journal`. On a deployment with the run store but no journal store (`DB_BACKEND=memory`,
9142
+ multi-tenant bare boot, SQL coordinates not fully configured) with self-orchestration on, `workflowsList` is true
9143
+ and this is false; the journal face then answers 501 `capability.workflow_store_required` (any id, ahead of the
9144
+ per-row owner check; `capability.self_orchestration_required` when the run store is absent). A journal entry point reads THIS bit, not
9145
+ `workflowsList`. ABSENT = an older server (<= 7.115.x): fall back to `workflowsList` (the old behaviour โ€” the
9146
+ bit could be true while the journal face always answers 501).
9057
9147
  workflowModels:
9058
9148
  type: array
9059
9149
  items: { type: string }
@@ -9096,8 +9186,8 @@ components:
9096
9186
  (DESIGN-278 ยง5 S2 โ€” deployment config self-description) is mounted on this DEPLOYMENT. TWO-term
9097
9187
  conjunction, written against the route's own refusal arms: โ‘  the `CONFIG_CATALOG_ENABLED` knob is
9098
9188
  on (default on; a compliance deployment that turns it off makes the path a 404 โ€” the valve-family
9099
- precedent) AND โ‘ก the operator principal list is NON-EMPTY (with an empty list the route 403s every
9100
- identity). ๐Ÿ”ด DEPLOYMENT-LEVEL AVAILABILITY, NOT THE CALLER'S AUTHORIZATION: the caller's own
9189
+ precedent; server >= 7.116.0 answers 501 `feature.config_catalog_disabled` there, 404 before) AND โ‘ก the operator principal list is NON-EMPTY (with an empty list the route refuses every
9190
+ identity โ€” 501 `capability.operator_principals_required` on server >= 7.116.0, 403 before). ๐Ÿ”ด DEPLOYMENT-LEVEL AVAILABILITY, NOT THE CALLER'S AUTHORIZATION: the caller's own
9101
9191
  identity is NOT in the predicate โ€” the route additionally requires the CURRENT principal to be an
9102
9192
  explicit operator (`routes/config-catalog.ts` `explicitOperatorOk`, else 403 `auth.operator_only`),
9103
9193
  so a non-operator reads `true` and still gets 403. Show/invoke the catalog only for an identity you
@@ -9117,8 +9207,15 @@ components:
9117
9207
  `false`/absent otherwise (same predicate as the route's 501). `resume: "snapshot"` is a semantic
9118
9208
  advertisement: the bus is latest-state (not an event log) and every connection opens with a full
9119
9209
  snapshot frame, so reconnect = re-snapshot (deliberately no last-event-id cursor); transient
9120
- bg_notification frames re-deliver via the durable completion inbox. `steerPriority: "advisory"` =
9121
- steer body.priority is accepted + enum-validated but a no-op until the core queue seam (E15).
9210
+ bg_notification frames re-deliver via the durable completion inbox. `steerPriority` (a string, open
9211
+ set) says whether the steer body's `priority` takes effect on THIS server: `steerPriority: "advisory"`
9212
+ (server <=7.115.x) = accepted + enum-validated + echoed but a no-op (nothing is handed to the engine);
9213
+ `steerPriority: "engine"` (server >=7.116.0) = `priority` (`now` / `next` / `later`) is handed to the
9214
+ engine as the injection tier, and the engine owns what each tier means โ€” `now` cuts the in-flight turn
9215
+ IMMEDIATELY (the running tool receives an abort, an approval card pending on this replica is revoked; one `engine_notice`
9216
+ `task.turn_interrupted` frame is the evidence), `later` is delivered after the turn the run would have stopped on, no `priority` =
9217
+ `next`. Read this bit before offering a `priority` choice; an absent
9218
+ or unknown value means "do not assume it takes effect", never `"engine"`.
9122
9219
  oneOf:
9123
9220
  - type: boolean
9124
9221
  enum: [false]
@@ -9342,22 +9439,28 @@ components:
9342
9439
  settings:
9343
9440
  type: object
9344
9441
  description: >
9345
- ๐Ÿ”ด server >= 7.115.0 (S-654), ALWAYS present: `{ permissions: { allowGrant: true } }` โ€” the VERSION bit for "which reading does this
9346
- server give `settings.permissions.allow` on a task submit". `permissions.allowGrant === true` = the `allow` bucket is compiled into
9347
- ALLOW RULES: a hit gives "somebody stated it" (so an MCP / A2A server-tool call nobody stated no longer raises a card) โ€” it is NOT
9348
- "skip the question" and NOT a whitelist (a tool off the list is no longer refused; the engine's own write gate / shell checks still ask
9349
- on built-in tools, deny / ask still win; a bad-shape or content-form name is a whole-request 422).
9350
- ๐Ÿ”ด ABSENT KEY = an older server (<= 7.114.x), which read `allow` as a WHITELIST (everything off the list refused) โ€” do not put anything
9351
- in the allow bucket there. NOT the same bit as `taskSettings.permissions` (unchanged, still a boolean: "this server accepts the
9352
- `settings.permissions` face"). A version bit: constant `true`, never varies with the deployment; the deployment-level "who stated what"
9353
- (operator env `APPROVAL_GRANT`) is on the operator diagnostics face (`serverGates.approvalGrant`), not here.
9442
+ ๐Ÿ”ด server >= 7.115.0, ALWAYS present: `{ permissions: { allowGrant: true } }`; server >= 7.117.0 adds `allowLayer: true` to `permissions` โ€” the VERSION bits for
9443
+ "which reading does this server give `settings.permissions.allow` on a task submit" (both constant `true`, never vary with the deployment: the single-user
9444
+ and `REQUIRE_PRINCIPAL=true` forms carry the same value). Read three levels: `settings` -> `permissions` -> <bit>; an absent / non-boolean bit = a server without it.
9445
+ `permissions.allowGrant === true` (>= 7.115.0) = the `allow` bucket is read as allow RULES, not as a list (a tool off the list is no longer refused).
9446
+ `permissions.allowLayer === true` (>= 7.117.0, core 7.42.0) = the server hands the WHOLE `allow` list to the engine's allow layer: a listed call is NOT ASKED (the engine's
9447
+ ordinary and "nobody stated it" questions are cleared โ€” built-in write gates / shell checks included, like the reference implementation's `permissions.allow`); content forms
9448
+ (`Bash(git:*)`, `Read(//dir/**)`) are accepted; entries the engine reads as nothing (`*`, write-path / domain forms, names with whitespace) are NOT refused โ€” the run opens with an
9449
+ `engine_notice` `permission.allowed_tool_not_granted`; any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / shell floor / write protection /
9450
+ governance, a hook's ask and plan-mode asks still ask (a matching member then shows as `allowRuleShadowed` on the card). The submit gate judges only the shape and the NAMES (retired
9451
+ name / unprefixed short name / unmintable protocol server segment => 422, at most 1000 entries => 400) โ€” never the content.
9452
+ ๐Ÿ”ด ABSENT `allowLayer` with `allowGrant` present = a 7.115.0โ€“7.116.x server: allow is a STATEMENT (it does not clear the engine's own write gate / shell checks; a content form
9453
+ is a whole-request 422). ABSENT KEY altogether (no `settings`) = an older server (<= 7.114.x), which read `allow` as a WHITELIST (everything off the list refused) โ€” do not put
9454
+ anything in the allow bucket there. NOT the same bit as `taskSettings.permissions` (unchanged, still a boolean: "this server accepts the `settings.permissions` face"). The
9455
+ deployment-level "who stated what" (operator env `APPROVAL_GRANT`) is on the operator diagnostics face (`serverGates.approvalGrant`), not here.
9354
9456
  additionalProperties: true
9355
9457
  properties:
9356
9458
  permissions:
9357
9459
  type: object
9358
9460
  additionalProperties: true
9359
9461
  properties:
9360
- allowGrant: { type: boolean, description: 'Constant `true` on server >= 7.115.0: `settings.permissions.allow` is read as allow RULES (a statement), not as a list. The whole `settings` key being absent = an older server.' }
9462
+ allowGrant: { type: boolean, description: 'Constant `true` on server >= 7.115.0: `settings.permissions.allow` is read as allow RULES, not as a list. The whole `settings` key being absent = an older server.' }
9463
+ allowLayer: { type: boolean, description: 'Constant `true` on server >= 7.117.0 (core 7.42.0): the whole `settings.permissions.allow` list goes to the engine allow layer (listed calls are not asked; content forms accepted; entries read as nothing get an `engine_notice` `permission.allowed_tool_not_granted`). Absent = a server without it (7.115.0โ€“7.116.x: allow is a statement; older: a list).' }
9361
9464
  memoryWrite:
9362
9465
  type: boolean
9363
9466
  description: >
@@ -9373,9 +9476,25 @@ components:
9373
9476
  S-15 ๆขๅบ—; โ‰ค7.51 the last conjunct was the file-snapshot store). Gate the whole sync surface off
9374
9477
  this. โš ๏ธ The two BLOB faces (PUT โ€ฆ/sync/blobs/{hash}, GET โ€ฆ/sync/history/blobs/{hash})
9375
9478
  additionally need the store's blob faces (`syncBlobFaces` โ€” the SQL/cloud backends; the local
9376
- file backend has none): without them the PUT answers 501 `capability.snapshot_store_required`
9377
- and the history PULL answers the no-oracle 404 `not_found.blob`, while the REST of the sync
9378
- surface keeps working.
9479
+ file backend has none): without them BOTH answer 501 `capability.snapshot_store_required`
9480
+ (server >= 7.116.0; the history PULL answered a no-oracle 404 `not_found.blob` there on <= 7.115.x),
9481
+ while the REST of the sync surface keeps working. Since server >= 7.116.0 the two blob faces are
9482
+ advertised by their own bit `sessionSyncFileHistory`; this bit speaks for the conversation half
9483
+ (its predicate additionally requires a session-policy store that can purge, `deleteBySession` โ€”
9484
+ a deployment whose embedder-supplied store lacks it answers the whole family 501
9485
+ `capability.policy_purge_required` and this bit is false).
9486
+ sessionSyncFileHistory:
9487
+ type: boolean
9488
+ description: >
9489
+ server >= 7.116.0 (predicate class "facility": `sessionSync` AND the file-history store has blob transfer faces
9490
+ โ€” SQL backends only; `DB_BACKEND=local` is always false). Advertises exactly the two file-history blob faces
9491
+ `PUT /v1/sessions/{sessionId}/sync/blobs/{hash}` and `GET /v1/sessions/{sessionId}/sync/history/blobs/{hash}`.
9492
+ READ IT ON BOTH ENDS of a sync: if either is false, sync the conversation only โ€” do not push `fileHistory`, do
9493
+ not PUT blobs (a `fileHistory` pushed to a peer without the blob faces 422s `missing_blob` after commit). When
9494
+ false the two routes answer 501 `capability.snapshot_store_required` (any session id, ahead of the owner check).
9495
+ A blob that the session history graph does not reference / whose bytes are gone stays a 404 `not_found.blob`.
9496
+ ABSENT = an older server (<= 7.115.x): the bit cannot be read there (a `DB_BACKEND=local` deployment with
9497
+ `sessionSync: true` answered PUT blob 501 on every call), so try as before.
9379
9498
 
9380
9499
  # โ”€โ”€โ”€ S-139(sdk 8.3.0):ๅฏน server 7.60.0 ็š„ๆ•ดไฝ“้‡ๅฏน่ดฆ่กฅ้ฝ็š„ 19 ไฝ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
9381
9500
  # ่กŒๅท = server 7.60.0 ๆ ‘็š„ `src/http/routes/capabilities.ts`ใ€‚่ฏญไน‰ไธŽ่ฐ“่ฏ็š„ๅ…จๆ–‡ๅœจ
@@ -9468,7 +9587,8 @@ components:
9468
9587
  description: >
9469
9588
  Governance carry-out bundle pair `POST /v1/memory/{export,import}` (operator lane) โ€”
9470
9589
  capabilities.ts:261, two conjuncts: both engine seams wired AND `operatorPrincipals` NON-EMPTY
9471
- (an empty roster makes those routes 403 for every identity). True does not promise the backend
9590
+ (an empty roster makes those routes refuse every identity โ€” 501 `capability.operator_principals_required` on
9591
+ server >= 7.116.0, 403 before). True does not promise the backend
9472
9592
  has the bundle composite face (core then refuses loudly with `memory.export_incomplete`).
9473
9593
  memoryCompliance:
9474
9594
  type: boolean
@@ -9570,10 +9690,13 @@ components:
9570
9690
  a2aServe:
9571
9691
  type: boolean
9572
9692
  description: >
9573
- Server-as-peer knob (capabilities.ts:564 = `config.a2aServe !== undefined`): true โŸบ
9574
- `GET /.well-known/agent-card.json` and `POST /v1/a2a` exist; false โŸบ both 404. Scope is ROUTE
9575
- EXISTENCE, not "every method runs" โ€” `message/send` / `tasks/get` additionally need a durable run
9576
- store (without it they answer a named JSON-RPC `-32004`).
9693
+ Server-as-peer: true โŸบ `GET /.well-known/agent-card.json` and `POST /v1/a2a` exist; false โŸบ both 404
9694
+ (`not_found.route`, anti-fingerprint โ€” the one registered exception to "unavailable = 501"). server >= 7.116.0
9695
+ predicate (class "facility + config"): the `A2A_SERVE_ENABLED` knob AND a durable run store; <= 7.115.x looked at the knob
9696
+ only (`config.a2aServe !== undefined`). ๐Ÿ”ด value change: with the knob on but NO durable run store
9697
+ (`DB_BACKEND=memory`, multi-tenant bare boot) this bit is now false and the card and the RPC both answer 404 (7.115.x:
9698
+ true, card 200, every `message/send` / `tasks/get` a JSON-RPC `-32004`); the startup log carries a warn
9699
+ `a2a_serve_not_served`. The old "the server deliberately does not fold the run store into this bit" note is retired.
9577
9700
  agentRoster:
9578
9701
  type: boolean
9579
9702
  description: >
@@ -9768,6 +9891,23 @@ components:
9768
9891
  answers the request key with a 400 `request.body_shape` naming it under `unknownKeys` โ€” do not send the key
9769
9892
  there and tell the user the factory skills stay installed. True only says "this server knows the key", not
9770
9893
  whether the deployment has any factory skills.
9894
+ userInvokedSkills:
9895
+ type: boolean
9896
+ enum: [true]
9897
+ description: >
9898
+ server >= 7.116.0 (constant true; no predicate, no tenant gate) โ€” the ADVANCE READING for the request key
9899
+ `TaskRequest.userInvokedSkills` (skills the human named; effective from the start of the run, no `Skill` card);
9900
+ the bit and the key are the SAME word. ABSENT = an older server (<= 7.115.x), which answers the key with a 400
9901
+ `request.body_shape` naming it under `unknownKeys` โ€” do not send it there.
9902
+ skillToolLists:
9903
+ type: boolean
9904
+ enum: [true]
9905
+ description: >
9906
+ server >= 7.116.0 (constant true) โ€” the skill tool lists `allowedTools` / `disallowedTools` TAKE EFFECT (allow-rule /
9907
+ deny-rule source; see `SkillSpec.allowedTools`). ABSENT = an older server (<= 7.115.x): on a top-level skill the two
9908
+ lists are accepted but the engine does not read them โ€” they are silently inert and there is NO 400 to probe, so do not
9909
+ expect them to stop a tool (fence with deny rules instead); on a delegated agent's skill they are an unknown key that
9910
+ rejects the whole skill.
9771
9911
  WriteProtectionCapability:
9772
9912
  type: object
9773
9913
  additionalProperties: false
@@ -9902,11 +10042,50 @@ components:
9902
10042
  engine refuses the skill outright at load time, so the HTTP face answers first where the caller can read it).
9903
10043
  The bound is 1 048 576 **UTF-16 code units** (`MAX_SKILL_CONTENT_CHARS`); see the STRING-LENGTH UNIT
9904
10044
  convention in `info.description` for why this `maxLength` states that number verbatim.
10045
+ ๐Ÿ”ด server >= 7.116.0 (core 7.41.0): a skill carries two TOOL LISTS, `allowedTools` / `disallowedTools` โ€” the
10046
+ run-scoped ALLOW-RULE and DENY-RULE source of the skill once it is activated. NOT a fence (<= 7.115.x the engine read
10047
+ the skill's tool list as "only these"): unlisted tools are judged as usual, listed ones are not asked about for the rest
10048
+ of the run. On server 7.116.x that made it WIDER than `settings.permissions.allow` (then a statement that did not
10049
+ release the engine's own write gate / shell ordinary asks on built-in tools); server >= 7.117.0 reads the two request-level
10050
+ allow sources the SAME way (see `capabilities.settings.permissions.allowLayer`). Probe `capabilities.skillToolLists`
10051
+ (absent = an old server that accepts the keys on a top-level skill and silently ignores them). The skill's `manifest`
10052
+ key is RETIRED: any value on a top-level skill is a 400 `request.field_invalid` ("skill.manifest was retired in core
10053
+ 7.41.0 โ€” โ€ฆ"; rewrite `manifest.allowTools` to `allowedTools`; `allowPaths` has no replacement), and on a delegated
10054
+ agent's skill (`agents[].skills[]`) it is an unknown key that rejects the whole skill.
9905
10055
  required: [name, description, content]
9906
10056
  properties:
9907
10057
  name: { type: string, minLength: 1 }
9908
10058
  description: { type: string }
9909
10059
  content: { type: string, minLength: 1, maxLength: 1048576 }
10060
+ allowedTools:
10061
+ type: array
10062
+ items: { type: string }
10063
+ description: >
10064
+ server >= 7.116.0. The skill's run-scoped ALLOW-RULE source (permission-rule spellings: a tool name,
10065
+ `mcp__<server>` / `mcp__<server>__*`; `*`, write paths and domain forms grant nothing). Once the skill is activated
10066
+ (named in top-level `userInvokedSkills` โ€” no `Skill` card โ€” or activated by the model and the card approved) the
10067
+ listed tools are not asked about for the rest of the run: it clears the engine's ordinary and "nobody stated it" asks;
10068
+ deny / ask rules, `APPROVAL_REQUIRE`, `APPROVAL_NEVER_AUTO`, the shell floor, write protection and plan-mode asks are not cleared.
10069
+ NOT a fence: unlisted tools are judged as usual. Versus `settings.permissions.allow`: on server 7.116.x this list was WIDER (that bucket
10070
+ was then only a statement); server >= 7.117.0 reads the two the SAME way, entry for entry (entries the engine reads as nothing get an
10071
+ `engine_notice` `permission.allowed_tool_not_granted`). A non-string-array is a 400 `request.field_invalid`; a protocol name whose server
10072
+ segment cannot be minted / is missing / is a wildcard (`mcp__*`) is a 400 too, and from 7.117.0 so is a retired name / unprefixed short
10073
+ name (the name layer is judged by the same judge as the allow bucket) and a list of more than 1000 entries (top-level `skills[]` and
10074
+ `agents[].skills[]` judge identically). Probe `capabilities.skillToolLists`.
10075
+ disallowedTools:
10076
+ type: array
10077
+ items: { type: string }
10078
+ description: >
10079
+ server >= 7.116.0. The skill's run-scoped DENY-RULE source (same grammar / gates / probe as `allowedTools`): once the skill
10080
+ is activated these tools are refused for the run (no execution, no card; `tool_end.gate.disposition` is
10081
+ `{ kind: "denied", deniedBy: "policy" }`); write-path forms and command rules compile into deny rows too.
10082
+ ๐Ÿ”ด server >= 7.117.0: an entry the engine reads as nothing (`*`, a domain form `WebFetch(domain:โ€ฆ)`, a protocol name with a wildcard, a name with
10083
+ whitespace or malformed) is refused AT SUBMIT โ€” 400 `request.field_invalid`, `error` = `skill.disallowedTools entry "<entry>" reads as nothing (<reason>): <how to write it>`
10084
+ (delegated level: prefix `agents[<i>]: skills[<j>]: `), top-level `skills[]` and `agents[].skills[]` alike โ€” because an entry that looks like a refusal but refuses nothing is
10085
+ worse than none (<= 7.116.x accepted it with only an operator-audience notice that was not sent to the session stream; that code is now `permission.allowed_tool_not_granted`,
10086
+ user audience, on the session stream). ๐Ÿ”ด The resume legs judge the stored body the same way: a parked task whose stored skill `disallowedTools` carries such an entry answers
10087
+ 409 `resume_blocked_by_policy` (`blockedBy: "request.field_invalid"`, same sentence) BEFORE the claim โ€” row and card untouched; a terminal run's `POST /v1/runs/{taskId}/steer`
10088
+ answers it before minting the wake card. Why: engine 7.41 trimmed whitespace (`"Bash "` was a working deny), engine 7.42 reads verbatim (reads as nothing). The allow side does not refuse.
9910
10089
 
9911
10090
  McpServerSpec:
9912
10091
  type: object
@@ -10648,16 +10827,17 @@ components:
10648
10827
  baseUrl/apiKey/headers (the service strips them).
10649
10828
  # ๐Ÿ”ด ๅฐ้—ญ + required ่กฅ้“ธ้€ ็‚น(census ๆ‰น2 ็ฌฌไธ‰ๆฎต,2026-07-30):้“ธ้€ ็‚น `routes/capabilities.ts`
10650
10829
  # ๆ˜ฏ้€้”ฎๅญ—้ข้‡ๆŠ•ๅฝฑ(็™ฝๅๅ•,ไธๆ˜ฏ้€ไผ โ€”โ€”ใ€Œopen setใ€ๆ—งๆณจไธๆˆ็ซ‹,ๆ–ฐ้”ฎ่ฆๅ…ˆ่ฟ‡่ฟ™้“ spec)ใ€‚core Model ็ฑปๅž‹
10651
- # ้‡Œ id/provider/reasoning ้ƒฝๆ˜ฏๅฟ…ๅกซใ€`vision` ็”ฑ `input` ๆ’็ฎ— โ‡’ ไบ”้”ฎๆ— ๆกไปถ;contextWindow/maxOutputTokens
10652
- # 0/็ผบ็œๅณ็œ็•ฅ,supportedEffortLevels ไป… reasoning ๆจกๅž‹ใ€‚
10653
- required: [name, id, provider, reasoning, vision]
10830
+ # ้‡Œ id/provider/reasoning ้ƒฝๆ˜ฏๅฟ…ๅกซ โ‡’ ๅ››้”ฎๆ— ๆกไปถ;contextWindow/maxOutputTokens 0/็ผบ็œๅณ็œ็•ฅ,
10831
+ # supportedEffortLevels ไป… reasoning ๆจกๅž‹ใ€‚๐Ÿ”ด server 7.117.0 ่ตท `vision` ๆ˜ฏไธ‰ๆ€(ๆ”ถ / ไธๆ”ถ / ๆฒกๅฃฐๆ˜Ž `input`),
10832
+ # ๆฒกๅฃฐๆ˜Ž โ‡’ ้”ฎ็ผบๅธญ โ‡’ ไธๅ†ๆ˜ฏๅฟ…ๆœ‰้”ฎ(ๆญคๅ‰็”ฑ `input` ๆ’็ฎ—ๆˆๅธƒๅฐ”,ๅฏนๆฒกๅฃฐๆ˜Ž็š„ๆก็›ฎๆŠฅ false)ใ€‚
10833
+ required: [name, id, provider, reasoning]
10654
10834
  additionalProperties: false
10655
10835
  properties:
10656
10836
  name: { type: string, description: 'the @mention handle (user-pickable model name).' }
10657
10837
  id: { type: string, description: 'underlying model id (display, e.g. deepseek-v4-pro).' }
10658
10838
  provider: { type: string }
10659
10839
  reasoning: { type: boolean, description: 'a reasoning-tier model (picker can badge it).' }
10660
- vision: { type: boolean, description: 'accepts image input.' }
10840
+ vision: { type: boolean, description: 'Whether this catalog row accepts image input โ€” THREE states (server >= 7.117.0; before it a row that declared no `input` reported `false`): `true` = the row declares `input` and it contains "image"; `false` = declares `input` without it; ABSENT = the row declared no `input` (a hand-written catalog entry; the env lane and the config-center lane always declare it). ABSENT means UNKNOWN, not false: treat it as "send the image anyway and tell the user", do not read it as `false` and block the image locally โ€” the server hands the image to the model for that same row at run time. Only an explicit `false` justifies "this model does not take images".' }
10661
10841
  contextWindow: { type: integer, description: 'E4 (SHIPPED) โ€” context-window token DENOMINATOR for the %-used gauge; omitted when unknown.' }
10662
10842
  maxOutputTokens: { type: integer, description: 'E4 (SHIPPED) โ€” single-call max output tokens; omitted when unknown.' }
10663
10843
  supportedEffortLevels:
@@ -11841,6 +12021,11 @@ components:
11841
12021
  expiresAtMs: { type: number, description: 'Absolute window deadline โ€” the SAME number minted once for the tool_approval frame (never recomputed).' }
11842
12022
  sessionId: { type: string }
11843
12023
  requiresRealApproval: { type: boolean, enum: [true] }
12024
+ rememberRefused:
12025
+ type: boolean
12026
+ enum: [true]
12027
+ description: >-
12028
+ server >= 7.116.0, ADDITIVE, present ONLY when true (never `false`): the "remember for this session" answer (live `allow_session` / durable `/decide` `remember:"session"`) will NOT be remembered on this card โ€” the decision is accepted and applies once, the receipt's `rememberApplied` is `false` (existing key, not a new one), later cards of the same kind still ask. A shell reads this to decide whether to render the "allow for the session" choice instead of inferring it from `requiresRealApproval` / `mandated` / `governanceForced`. One bit for four reasons: the operator-governance gate / a human must approve each time / a mandated card / the answer would also grant more than this one call (new in 7.116.0: approving an engine `Skill` card activates the skill and frees its listed tools for the run, which the card cannot show, so it is not remembered). ABSENT = remember works on this card, OR an older server (<= 7.115.x never carries the key). Same bit on the live frame, the `approval_request` card (= `card_json`, incl. the reconnect replay), the `livePending` row, and the `/v1/approvals` `pending[]` row (+ their `/stream` frames); NOT on inbox rows.
11844
12029
  governanceForced: { type: boolean, enum: [true] }
11845
12030
  mandated: { type: boolean, enum: [true], description: 'server >= 7.101.0 (T4โ‘ ): the mandated bit, same layer and shape as the two flags above (present only when true); the SAME value as `frame.mandated` (read once when the entry is minted).' }
11846
12031
  mandate:
@@ -12003,6 +12188,9 @@ components:
12003
12188
  Present โ‡” the request carried `remember:"session"` and the worker has an exemption store. `true` = the
12004
12189
  per-session per-tool exemption landed; `false` = the grant failed (logged) or the gate carried no
12005
12190
  rememberable toolName โ€” the decision itself still stands.
12191
+ ๐Ÿ”ด server >= 7.116.0: ALWAYS `false` on a card that carries `rememberRefused: true` (an engine `Skill` card etc. โ€” the
12192
+ answer would also grant more than this one call): the decision is accepted and applies once, the exemption store gets
12193
+ zero writes (existing key, not a new one). Ordinary cards keep `true` = landed.
12006
12194
 
12007
12195
  ApprovalExemption:
12008
12196
  type: object
@@ -12112,6 +12300,16 @@ components:
12112
12300
  toolName: { type: string }
12113
12301
  summary: { type: string }
12114
12302
  touchedPaths: {}
12303
+ allowRuleShadowed:
12304
+ type: array
12305
+ items: { $ref: '#/components/schemas/AllowRuleShadow' }
12306
+ description: >
12307
+ server >= 7.117.0 (core 7.42.0): the DURABLE dual of `ToolApprovalFrame.allowRuleShadowed` โ€” the allow-layer members that matched this call
12308
+ and could not clear its ask (same shape and semantics; entries are secret-redacted again at the server's read boundary). Passed through whole
12309
+ on `GET /v1/approvals` and `/v1/approvals/stream`. Absent != "you have no rule". ๐Ÿ”ด The old key `shadowedRule` (a string, server <= 7.116.x)
12310
+ is RETIRED: the SQL backend rewrote its parked rows once at the 7.117.0 boot to `[{ member: "persisted", entries: [<old value>] }]`, but the
12311
+ file backend's pre-upgrade parked rows and run results already on disk (`checkpointGate.riskDescriptor`) keep the old key verbatim
12312
+ (text still redacted, no structured disclosure) โ€” a consumer reading those must still tolerate the old string.
12115
12313
  # server 7.16.0 [3684]โ‘ก โ€” durable ้˜Ÿๅˆ—่กŒๅฝ’ๅ› ไธค้”ฎ(ไธŽๆดปๅก tool_approval ๅธง็š„ๅŒๅ้”ฎ่ฏญไน‰ๅŒๆ—ใ€ๅˆคๆฎไธๅŒๆบ;
12116
12314
  # ไธคๆก่…ฟๅฏนๅŒไธ€ๅช ask ็š„ๅœจๅœบๆ€งๅฏไปฅไธไธ€่‡ด,ไธคไพง้ƒฝๅช่ฎคๅœจๅœบใ€้ƒฝ็ฆๆŠŠ็ผบๅธญ่ฏปๆˆๅฆๅฎš)ใ€‚
12117
12315
  ruleSuggestions:
@@ -12146,6 +12344,11 @@ components:
12146
12344
  ๐Ÿ”ด DISPLAY / TRIAGE ONLY, and ABSENT MEANS THERE REALLY IS NO SUPPLY (rule lane unarmed, the
12147
12345
  command yields no rule, no rule could silence this ask, or an old row) โ€” never "the projection
12148
12346
  dropped it", which is precisely the bug this key exists to close. Never `null`, never `[]`.
12347
+ rememberRefused:
12348
+ type: boolean
12349
+ enum: [true]
12350
+ description: >-
12351
+ server >= 7.116.0, ADDITIVE, present ONLY when true (never `false`): the "remember for this session" answer (live `allow_session` / durable `/decide` `remember:"session"`) will NOT be remembered on this card โ€” the decision is accepted and applies once, the receipt's `rememberApplied` is `false` (existing key, not a new one), later cards of the same kind still ask. A shell reads this to decide whether to render the "allow for the session" choice instead of inferring it from `requiresRealApproval` / `mandated` / `governanceForced`. One bit for four reasons: the operator-governance gate / a human must approve each time / a mandated card / the answer would also grant more than this one call (new in 7.116.0: approving an engine `Skill` card activates the skill and frees its listed tools for the run, which the card cannot show, so it is not remembered). ABSENT = remember works on this card, OR an older server (<= 7.115.x never carries the key). Same bit on the live frame, the `approval_request` card (= `card_json`, incl. the reconnect replay), the `livePending` row, and the `/v1/approvals` `pending[]` row (+ their `/stream` frames); NOT on inbox rows.
12149
12352
  governanceForced:
12150
12353
  type: boolean
12151
12354
  enum: [true]
@@ -13098,31 +13301,97 @@ components:
13098
13301
  type: object
13099
13302
  description: >
13100
13303
  2c session-sync `GET โ€ฆ/sync/manifest` payload (wrapped server-side as `{ manifest }`). The THIN cross-backend
13101
- snapshot: entry IDS (oldest-first, NOT payloads) + per-snapshot relPathโ†’blobHash + policy + anchors + leaf.
13102
- # ๅฐ้—ญ(census ๆ‰น2 ็ฌฌไธ‰ๆฎต,2026-07-30):้“ธ้€ ็‚น `session-sync.ts`(exportSessionManifest ๅฐพ้ƒจ
13103
- # ๅญ—้ข้‡)ๆฐๆ˜ฏ่ฟ™ 7 ้”ฎ,ๅ…จ้ƒจๆ— ๆกไปถ(leafId ็ผบๅธญไฝ=null ไธ็œ็•ฅ)ใ€‚
13304
+ snapshot: entry IDS (oldest-first, NOT payloads) + the file-history envelope + policy + anchors + leaf.
13305
+ ๐Ÿ”ด server >= 7.52 (S-15, design/381; re-read byte for byte against 7.116.0): the key `snapshots` (per-snapshot
13306
+ relPathโ†’blobHash, the retired E19 whole-tree snapshot epoch) is NO LONGER SENT โ€” the server's `exportSessionManifest` mints
13307
+ `{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` and `fileHistory` (core's versioned
13308
+ `FileHistoryExport` envelope; `null` = no history / the source store cannot export) replaces it. <= 7.51 servers still send
13309
+ `snapshots` and no `fileHistory`. The blob BYTES never ride the manifest: content-addressed, via the history blob faces
13310
+ (`GET โ€ฆ/sync/history/blobs/{hash}` / `PUT โ€ฆ/sync/blobs/{hash}`; probe `capabilities.sessionSyncFileHistory` on BOTH ends).
13311
+ The SDK's `manifest()` fills `snapshots: []` when the wire omits it.
13312
+ # ๅฐ้—ญ(census ๆ‰น2 ็ฌฌไธ‰ๆฎต,2026-07-30;16.2.0 ๆŒ‰ 7.116.0 ๅ‘ๅธƒๅญ—่Š‚้‡ๆ ธ):้“ธ้€ ็‚น `session-sync.ts`(exportSessionManifest ๅฐพ้ƒจ
13313
+ # ๅญ—้ข้‡)ๆฐๆ˜ฏ `{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` 7 ้”ฎ(โ‰ฅ7.52),ๆ—  `snapshots`;
13314
+ # โ‰ค7.51 ่€ server ๅ‘ `snapshots`ใ€ๆ—  `fileHistory` โ‡’ ไธค้”ฎ้ƒฝไธ่ฟ› required(ๆ”ฏๆŒๅบ•็บฟ 3.0.0)ใ€‚
13104
13315
  additionalProperties: false
13105
- required: [sessionId, entryIds, entryCount, leafId, snapshots, policy, anchors]
13316
+ required: [sessionId, entryIds, entryCount, leafId, policy, anchors]
13106
13317
  properties:
13107
13318
  sessionId: { type: string }
13108
13319
  entryIds: { type: array, items: { type: string }, description: Oldest-first, verbatim cross-backend. }
13109
13320
  entryCount: { type: integer, description: '= entryIds.length (the `/sync/entries` trailer count).' }
13110
13321
  leafId: { type: [string, 'null'] }
13111
- snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' } }
13322
+ snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' }, description: 'server <= 7.51 ONLY (the retired E19 snapshot epoch); server >= 7.52 does not send it โ€” see `fileHistory`.' }
13323
+ fileHistory:
13324
+ oneOf:
13325
+ - { $ref: '#/components/schemas/FileHistoryExport' }
13326
+ - { type: 'null' }
13327
+ description: 'server >= 7.52: the per-edited-file rewind history envelope; `null` = no history / the source store cannot export; key ABSENT = a <= 7.51 server (that generation sends `snapshots`).'
13112
13328
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
13113
13329
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
13114
13330
 
13331
+ FileHistoryExport:
13332
+ type: object
13333
+ description: >
13334
+ Mirror of core's `FileHistoryExport` (`formatVersion: "file-history/1"`) โ€” the session-sync envelope for the per-edited-file rewind history graph
13335
+ (server >= 7.52; replaces the retired E19 `SyncSnapshot`). Blob bytes are NOT inside: they are content-addressed and travel through the history blob faces.
13336
+ The SDK reads only `tracked[].versions[].blobHash` (which blobs to move) and passes everything else through verbatim โ€” the server's import door judges
13337
+ `formatVersion` (refuse-not-coerce) and the closed `keyKind` / `keyFamily` sets, so they are plain strings here.
13338
+ required: [formatVersion, tracked, boundaries]
13339
+ properties:
13340
+ formatVersion: { type: string, description: 'today only `file-history/1`; the server refuses any other value.' }
13341
+ tracked:
13342
+ type: array
13343
+ items:
13344
+ type: object
13345
+ required: [relOrAbsPath, keyKind, keyFamily, baseAvailable, versions]
13346
+ properties:
13347
+ relOrAbsPath: { type: string }
13348
+ keyKind: { type: string }
13349
+ keyFamily: { type: string }
13350
+ baseAvailable: { type: boolean }
13351
+ versions:
13352
+ type: array
13353
+ items:
13354
+ type: object
13355
+ required: [version, blobHash, backupTime]
13356
+ properties:
13357
+ version: { type: integer }
13358
+ blobHash: { type: [string, 'null'], description: '`null` = the "did not exist" marker (no blob to move).' }
13359
+ mode: { type: integer }
13360
+ parentIdent: { type: string }
13361
+ backupTime: { type: string }
13362
+ boundaries:
13363
+ type: array
13364
+ items:
13365
+ type: object
13366
+ required: [entryId, files]
13367
+ properties:
13368
+ entryId: { type: string }
13369
+ files:
13370
+ type: array
13371
+ items:
13372
+ type: object
13373
+ required: [relOrAbsPath, version]
13374
+ properties:
13375
+ relOrAbsPath: { type: string }
13376
+ version: { type: integer }
13377
+
13115
13378
  SessionBundle:
13116
13379
  type: object
13117
13380
  description: >
13118
13381
  The portable state of ONE session, ready to replay into another backend. Blobs are NOT inlined (each
13119
13382
  snapshot carries only its `manifest`; the bytes stream through the content-addressed blob routes). The SDK
13120
- assembles this from a manifest + the streamed entries for a PUSH.
13383
+ assembles this from a manifest + the streamed entries for a PUSH. server >= 7.52: the file history rides `fileHistory`
13384
+ (the `FileHistoryExport` envelope) instead of `snapshots`.
13121
13385
  required: [sessionId, entries, snapshots, policy, anchors]
13122
13386
  properties:
13123
13387
  sessionId: { type: string }
13124
13388
  entries: { type: array, items: { $ref: '#/components/schemas/SyncEntry' } }
13125
13389
  snapshots: { type: array, items: { $ref: '#/components/schemas/SyncSnapshot' } }
13390
+ fileHistory:
13391
+ oneOf:
13392
+ - { $ref: '#/components/schemas/FileHistoryExport' }
13393
+ - { type: 'null' }
13394
+ description: 'server >= 7.52 file-history envelope (same as `SessionManifest.fileHistory`); key absent = the source is a <= 7.51 server.'
13126
13395
  policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
13127
13396
  anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
13128
13397
 
@@ -13409,6 +13678,8 @@ components:
13409
13678
  `not_found.resource` / `request.unprocessable` / `capability.unavailable` โ€ฆ). ALWAYS PRESENT on server >= 7.104.0
13410
13679
  (S-607); before that it was absent when the original refusal carried no code. SDK:
13411
13680
  `ResumeBlockedByPolicyError.blockedBy` (`undefined` when the key is absent or not a string โ€” never defaulted).
13681
+ server >= 7.117.0 adds a third fold source: a stored skill `disallowedTools` entry the engine now reads as nothing (the submit leg's 400 `request.field_invalid` sentence)
13682
+ โ‡’ `blockedBy: "request.field_invalid"`, refused before the claim; the terminal-run steer leg answers it before minting the wake card.
13412
13683
 
13413
13684
  ResumeLegConflictError:
13414
13685
  description: >
@@ -13985,10 +14256,17 @@ components:
13985
14256
  unattended leg run under `UNATTENDED_PERMISSION_MODE=dontAsk`).
13986
14257
  AskClass:
13987
14258
  type: string
13988
- enum: [content_question, hook, ancestor, unverifiable_delete, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, operator_forced, shell_floor, write_protection_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
13989
- description: >
13990
- core `ASK_CLASSES` (twenty-two words as of core 7.40.0 โ€” 7.34.0 minted twenty; `shell_floor` joined in 7.39.0 #1260 and rides
13991
- server >= 7.113.0; `unstated` joined in 7.40.0 and rides server >= 7.115.0) โ€” which CLASS an ask falls in (the columns of the mode ร— class arbitration table).
14259
+ enum: [content_question, hook, ancestor, unverifiable_delete, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, requires_user_interaction, operator_forced, shell_floor, write_protection_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
14260
+ description: >
14261
+ core `ASK_CLASSES` (twenty-two words as of core 7.40.0, twenty-three as of core 7.41.0 โ€” 7.34.0 minted twenty; `shell_floor` joined in 7.39.0 #1260 and rides
14262
+ server >= 7.113.0; `unstated` joined in 7.40.0 and rides server >= 7.115.0; `requires_user_interaction` joined in 7.41.0 and rides
14263
+ server >= 7.116.0) โ€” which CLASS an ask falls in (the columns of the mode ร— class arbitration table).
14264
+ `requires_user_interaction` = the MCP server declared, in the tool's advertisement, `_meta["anthropic/requiresUserInteraction"] === true`
14265
+ (only the boolean `true`): EVERY call of that tool asks the human, in EVERY mode including `bypassPermissions` (it is not one of the ten
14266
+ RELEASABLE classes), and only the human's answer clears it โ€” `settings.permissions.allow`, operator `APPROVAL_GRANT`, a skill's tool list +
14267
+ `userInvokedSkills` and a hook's explicit allow all fail to; with no approval seat the call is refused (the server sees nothing). The frame
14268
+ also carries `requiresRealApproval: true` / `mandated: true` (`origin: "safety_tighten"`). A client that renders cards by exhaustive `askClass`
14269
+ adds this one word.
13992
14270
  `unstated` = NOBODY STATED IT: in an ask-class mode (`default` / `acceptEdits` / `auto` / `plan`) a call to an MCP server tool or A2A skill
13993
14271
  (a seat that states no permission of its own) reached the end of the fold with no layer โ€” rule / hook / approver / classifier / mode
13994
14272
  release โ€” having said "this time is fine"; the engine raises a card with `askClass: "unstated"` (sentence `The agent requested permission to use <tool>,
@@ -15589,6 +15867,11 @@ components:
15589
15867
  args:
15590
15868
  description: '"tool_approval" only: the call''s args, secret-redacted. UNTRUSTED for display. Absent (with argsOmitted) when over the byte cap or unserializable.'
15591
15869
  argsOmitted: { type: boolean, enum: [true] }
15870
+ rememberRefused:
15871
+ type: boolean
15872
+ enum: [true]
15873
+ description: >-
15874
+ server >= 7.116.0, ADDITIVE, present ONLY when true (never `false`): the "remember for this session" answer (live `allow_session` / durable `/decide` `remember:"session"`) will NOT be remembered on this card โ€” the decision is accepted and applies once, the receipt's `rememberApplied` is `false` (existing key, not a new one), later cards of the same kind still ask. A shell reads this to decide whether to render the "allow for the session" choice instead of inferring it from `requiresRealApproval` / `mandated` / `governanceForced`. One bit for four reasons: the operator-governance gate / a human must approve each time / a mandated card / the answer would also grant more than this one call (new in 7.116.0: approving an engine `Skill` card activates the skill and frees its listed tools for the run, which the card cannot show, so it is not remembered). ABSENT = remember works on this card, OR an older server (<= 7.115.x never carries the key). Same bit on the live frame, the `approval_request` card (= `card_json`, incl. the reconnect replay), the `livePending` row, and the `/v1/approvals` `pending[]` row (+ their `/stream` frames); NOT on inbox rows.
15592
15875
  governanceForced:
15593
15876
  type: boolean
15594
15877
  enum: [true]
@@ -15674,29 +15957,45 @@ components:
15674
15957
  requiresRealApproval / shadowed / hook-produced / inherited-unresolved / ancestor-resolved /
15675
15958
  anonymous asks. Absence reads as "THIS CARD has no such option", never as "this deployment has no
15676
15959
  rule lane" (that is `capabilities.permissionRules`) and never as "your rule broke" (that is
15677
- `persistedRuleShadowed`).
15960
+ `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x).
15678
15961
 
15679
15962
  โš ๏ธ REDEMPTION SCOPE is the live respond leg keyed by this frame's `approvalId` only; a replayed
15680
15963
  offer on another replica is not a promise that it is still redeemable.
15681
15964
  persistedRuleShadowed:
15682
15965
  type: string
15966
+ deprecated: true
15683
15967
  description: >
15684
- server >= 7.13.0 (core 5.25.0 #144), ADDITIVE, "tool_approval" only: this ask MATCHED one of the
15968
+ ๐Ÿ”ด RETIRED by server 7.117.0 (core 7.42.0) โ€” replaced by `allowRuleShadowed`, no alias. A server >= 7.117.0 never mints this key
15969
+ (its strict card schema even refuses a stored card carrying it); only a server <= 7.116.x still does, and the SDK keeps the declaration
15970
+ for those servers (the supported floor is far below 7.117.0). Slated for deletion in the next major.
15971
+
15972
+ For those older servers (server >= 7.13.0 / core 5.25.0 #144, ADDITIVE, "tool_approval" only): this ask MATCHED one of the
15685
15973
  caller's persisted allow rules and that rule COULD NOT silence it. The value is the shadowed rule's
15686
15974
  VERBATIM text (secret-redacted + length-bounded). UNTRUSTED for display, same treatment as `message`.
15687
-
15688
- It documents a BEHAVIOUR NARROWING ("Allow rules silence the CLASSIFIER's questions, never a MANDATED
15689
- one"): under MANUAL_MODE_SHELL_GATE=always, for tools carrying an egress/irreversible mark, and under
15690
- the governance gates, asks a rule used to silence now REAPPEAR. Render an explanatory line ("your rule
15691
- is still there โ€” this call must be confirmed each time"); without it the only conclusion a user can
15692
- draw is "my rule broke", while the rule sits fine in the store.
15693
-
15694
- ๐Ÿ”ด ABSENT != "you have no rule": present only when a rule matched AND could not clear the ask. Most
15695
- asks have no match at all, and matched-and-cleared ones never become asks (they were silenced โ€” no
15696
- card exists). ๐Ÿ”ด Deliberately SEPARATE from `governanceForced` and not derivable from it: that key
15697
- answers "who imposed the gate", this one answers "what happened to YOUR rule"; governance is only one
15698
- of three un-silenceable sources (doctrine `always` and a tool's own mark carry no governance badge).
15699
- The durable dual is `gate.riskDescriptor.shadowedRule` on the parked row.
15975
+ ๐Ÿ”ด ABSENT != "you have no rule": present only when a rule matched AND could not clear the ask. ๐Ÿ”ด Deliberately SEPARATE
15976
+ from `governanceForced` and not derivable from it: that key answers "who imposed the gate", this one answers "what
15977
+ happened to YOUR rule". The durable dual on those servers is `gate.riskDescriptor.shadowedRule` on the parked row.
15978
+ allowRuleShadowed:
15979
+ type: array
15980
+ items: { $ref: '#/components/schemas/AllowRuleShadow' }
15981
+ description: >
15982
+ server >= 7.117.0 (core 7.42.0), ADDITIVE, "tool_approval" only โ€” BREAKING RENAME, replacing the retired string `persistedRuleShadowed`
15983
+ (no alias): the allow-layer members that MATCHED this call and could NOT clear this ask, one `{ member, entries, omitted? }` record per
15984
+ matching member (see AllowRuleShadow). `member` is one of `hook` / `deployment` / `persisted` / `skill` / `request`; `entries` are the
15985
+ entries that member matched, as their source spells them (UNTRUSTED for display like `message`, secret-redacted, <= 512 chars each,
15986
+ <= 32 listed; a `hook` record's `entries` is always the empty list); `omitted` counts the entries not listed.
15987
+
15988
+ The explaining sentence ("... matches this call but does not clear the ask โ€” <reason>") is IN `message` (the engine writes it where it builds
15989
+ the card); this key is the structured twin of the same fact. Render a line from it ("your rule matched, but this call must be confirmed each
15990
+ time"): without it the only conclusion a user can draw is "my rule broke".
15991
+
15992
+ ๐Ÿ”ด ABSENT != "you have no rule": present only when a member matched AND could not clear the ask; a match that cleared never becomes an ask.
15993
+ ๐Ÿ”ด `member: "hook"` does not say whose hook (a deployment PreToolUse hook and, on the single-user lane, the request's own `settings.hooks`
15994
+ share one member). ๐Ÿ”ด Deliberately SEPARATE from `governanceForced` (that one: "who imposed the gate"; this one: "what happened to YOUR
15995
+ rule"). What the allow layer can NOT clear: any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` /
15996
+ `MANUAL_MODE_SHELL_GATE=always`, write protection, sensitive-path writes, governance, a hook's ask, plan-mode asks. The durable dual is
15997
+ `gate.riskDescriptor.allowRuleShadowed` on the parked row (PendingCheckpoint.riskDescriptor). Rolling-upgrade caveat: a card stored by a
15998
+ <= 7.116.x replica (`card_json` carrying `persistedRuleShadowed`) cannot be read by a >= 7.117.0 replica โ€” the replay skips it.
15700
15999
  probeCause:
15701
16000
  type: object
15702
16001
  description: >
@@ -16053,6 +16352,11 @@ components:
16053
16352
  argsOmitted: { type: boolean, enum: [true], description: 'Present (true) only when args exceeded the byte cap. Never false.' }
16054
16353
  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.' }
16055
16354
  risk: { $ref: '#/components/schemas/ApprovalRiskAxes' }
16355
+ rememberRefused:
16356
+ type: boolean
16357
+ enum: [true]
16358
+ description: >-
16359
+ server >= 7.116.0, ADDITIVE, present ONLY when true (never `false`): the "remember for this session" answer (live `allow_session` / durable `/decide` `remember:"session"`) will NOT be remembered on this card โ€” the decision is accepted and applies once, the receipt's `rememberApplied` is `false` (existing key, not a new one), later cards of the same kind still ask. A shell reads this to decide whether to render the "allow for the session" choice instead of inferring it from `requiresRealApproval` / `mandated` / `governanceForced`. One bit for four reasons: the operator-governance gate / a human must approve each time / a mandated card / the answer would also grant more than this one call (new in 7.116.0: approving an engine `Skill` card activates the skill and frees its listed tools for the run, which the card cannot show, so it is not remembered). ABSENT = remember works on this card, OR an older server (<= 7.115.x never carries the key). Same bit on the live frame, the `approval_request` card (= `card_json`, incl. the reconnect replay), the `livePending` row, and the `/v1/approvals` `pending[]` row (+ their `/stream` frames); NOT on inbox rows.
16056
16360
  governanceForced:
16057
16361
  type: boolean
16058
16362
  enum: [true]
@@ -16084,12 +16388,21 @@ components:
16084
16388
  promise and the per-leg index rules are stated verbatim on ToolApprovalFrame.ruleOffers.
16085
16389
  persistedRuleShadowed:
16086
16390
  type: string
16391
+ deprecated: true
16392
+ description: >
16393
+ ๐Ÿ”ด RETIRED by server 7.117.0 (core 7.42.0) โ€” replaced by `allowRuleShadowed`, no alias (a server >= 7.117.0 never mints it; the strict card
16394
+ schema refuses a stored card carrying it). Only a server <= 7.116.x still does; slated for deletion in the next major. For those servers
16395
+ (ADDITIVE, server >= 7.13.0 / core 5.25.0 #144): the VERBATIM text of the persisted rule this ask matched but which could not silence it
16396
+ (secret-redacted + length-bounded). UNTRUSTED for display. Semantics, the ABSENT != "you have no rule" clause and the deliberate separation
16397
+ from `governanceForced` are stated verbatim on ToolApprovalFrame.persistedRuleShadowed.
16398
+ allowRuleShadowed:
16399
+ type: array
16400
+ items: { $ref: '#/components/schemas/AllowRuleShadow' }
16087
16401
  description: >
16088
- ADDITIVE (server >= 7.13.0 / core 5.25.0 #144): the VERBATIM text of the persisted rule this ask
16089
- matched but which could not silence it (secret-redacted + length-bounded). UNTRUSTED for display.
16090
- Semantics, the ABSENT != "you have no rule" clause and the deliberate separation from
16091
- `governanceForced` are stated verbatim on ToolApprovalFrame.persistedRuleShadowed โ€” the live frame,
16092
- the stored `card_json` and the replayed frame carry the SAME material.
16402
+ ADDITIVE (server >= 7.117.0 / core 7.42.0, BREAKING RENAME of the retired `persistedRuleShadowed`): the allow-layer members that matched this
16403
+ call and could not clear this ask. Semantics, the five members, the always-empty `hook` entries, the explaining sentence living in `message`,
16404
+ the ABSENT != "you have no rule" clause and the separation from `governanceForced` are stated verbatim on ToolApprovalFrame.allowRuleShadowed
16405
+ โ€” the live frame, the stored `card_json`, the replayed frame and the `approval_request` card carry the SAME material.
16093
16406
  fromSubagent: { type: boolean, enum: [true], description: 'Present โ‡” the ask comes from a DELEGATED child โ€” the explicit discriminator (a worker cannot forge it).' }
16094
16407
  sourceTaskId: { type: string, description: 'The child''s core session id โ€” row-join value + fallback discriminator.' }
16095
16408
  sourceAgentName: { type: string, description: 'The child agent''s display name, redacted. UNTRUSTED.' }
@@ -16214,6 +16527,37 @@ components:
16214
16527
  `.strict()`). The eleven known words are exported at runtime as the SDK's `ASK_ORIGINS` (core 7.32.1
16215
16528
  `ASK_ORIGINS`, verbatim and in order) โ€” known words, NOT an enforcement table.
16216
16529
 
16530
+ AllowRuleMember:
16531
+ type: string
16532
+ enum: [hook, deployment, persisted, skill, request]
16533
+ description: >
16534
+ 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
16535
+ 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).
16536
+
16537
+ AllowRuleShadow:
16538
+ type: object
16539
+ additionalProperties: false
16540
+ required: [member, entries]
16541
+ description: >
16542
+ One allow-layer DISCLOSURE record (`ToolApprovalFrame.allowRuleShadowed` / `ApprovalCard.allowRuleShadowed` / the durable
16543
+ `PendingCheckpoint.riskDescriptor.allowRuleShadowed`; server >= 7.117.0 / core 7.42.0 `AllowRuleShadow`): one member of the allow layer that
16544
+ MATCHED the call and could NOT clear its ask. At most one record per member, at most five per ask. The vocabulary owner is core; the server
16545
+ drops the whole key on a member outside the five words, so a >= 7.117.0 server only ever sends these five โ€” still read `member` as an open
16546
+ string with a default arm (core may add members and the server follows).
16547
+ properties:
16548
+ member:
16549
+ $ref: '#/components/schemas/AllowRuleMember'
16550
+ description: >
16551
+ Which allow-layer member matched, in the order the engine reads them: `hook` = a PreToolUse hook's explicit `allow` (a deployment hook or,
16552
+ on the single-user lane, the request's own `settings.hooks` โ€” the key cannot say which) ยท `deployment` = the operator's `APPROVAL_GRANT` ยท
16553
+ `persisted` = a person's stored allow rules ยท `skill` = an activated skill's `allowedTools` ยท `request` = the request's `settings.permissions.allow`.
16554
+ entries:
16555
+ type: array
16556
+ maxItems: 32
16557
+ items: { type: string, maxLength: 512 }
16558
+ description: 'The entries that member matched, as their source spells them (UNTRUSTED for display, secret-redacted, <= 512 chars each, <= 32 listed). Always the empty list for `hook` โ€” its word is about the call, not about an entry.'
16559
+ omitted: { type: integer, minimum: 1, description: 'How many matched entries were not listed (beyond the cap). Absent = none omitted.' }
16560
+
16217
16561
  RuleOffersAbsence:
16218
16562
  type: string
16219
16563
  enum: [mandated, shadowed, lane_cannot_speak]
@@ -16222,7 +16566,7 @@ components:
16222
16566
  `ApprovalCard.ruleOffersAbsence`; server >= 7.55.0 / core #490 fix โ‘ก, S-15). The engine names which
16223
16567
  door is shut: `mandated` = this ask must be confirmed every time (๐Ÿ”ด this arm must NOT point the user
16224
16568
  at writing a rule โ€” a rule would not silence it, and saying so sends them into a loop); `shadowed` = a
16225
- rule matched but could not clear it (the machine-readable twin of `persistedRuleShadowed`);
16569
+ an allow-layer member matched but could not clear it (the machine-readable twin of `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x);
16226
16570
  `lane_cannot_speak` = the engine could mint no coverable form for this command.
16227
16571
 
16228
16572
  Engine-side MUTUALLY EXCLUSIVE with `ruleOffers`. ๐Ÿ”ด ABSENCE IS NOT AN ASSERTION โ€” it covers both
@@ -16707,16 +17051,27 @@ components:
16707
17051
  sessionId: { type: string, description: 'gone.ask_parked ONLY โ€” gate reconciliation coordinate 1; drive the existing durable-gate flow with it.' }
16708
17052
  gateBoundCallId: { type: string, description: 'gone.ask_parked ONLY โ€” gate reconciliation coordinate 2.' }
16709
17053
  gateBoundInputHash: { type: string, description: 'gone.ask_parked ONLY โ€” gate reconciliation coordinate 3.' }
16710
- state: { type: string, enum: [DENIED, VOID], description: 'gone.ask ONLY โ€” the terminal state.' }
17054
+ state: { type: string, enum: [DENIED, VOID], description: 'gone.ask ONLY โ€” the terminal state. server >= 7.116.0 also mints `VOID` for "the process that held the ask is gone" (local lane restart; see `deniedReason`).' }
16711
17055
  deniedReason:
16712
17056
  type: string
16713
17057
  description: >
16714
17058
  gone.ask ONLY โ€” attribution, GROUPED BY `state`. `DENIED` carries the single-member deny
16715
17059
  vocabulary `routing_failure_fail_closed`; `VOID` carries the separate (and growing) void
16716
- vocabulary, e.g. `adhoc_leg_no_durable_domain`. The two vocabularies never mix.
17060
+ vocabulary, seven words today: `run_cancelled`, `batch_aborted`, `batch_bound_elsewhere`,
17061
+ `adhoc_leg_no_durable_domain`, `orphan_ttl_exceeded` and โ€” new in server 7.116.0 (local lane) โ€”
17062
+ `crashed_before_park` (the process that held the ask crashed / was killed) and `shutdown_before_park`
17063
+ (an orderly shutdown could not settle the card inside its grace window; a fault shape, hard to build black-box).
17064
+ The last two are the SAME word as `GET /v1/approvals` `crashConverged[].cause`; the other six were already on the wire
17065
+ (7.3.0 / 7.55.0 / 7.108.0) and are only registered in the server's self-minted word table by 7.116.0.
17066
+ ABSENT = the row carries no attribution. The two vocabularies never mix; the VOID one is open (read an unknown word as "the ask ended without
17067
+ a human decision, reason unknown").
16717
17068
  description: >
16718
17069
  410 body. THE TWO LEGS ARE DISJOINT: `gone.ask_parked` carries exactly the three gate coordinates,
16719
- `gone.ask` carries exactly `{state, deniedReason?}`. Routing off field presence across the legs is how a
17070
+ `gone.ask` carries exactly `{state, deniedReason?}` (no `consumerState`).
17071
+ ๐Ÿ”ด server >= 7.116.0 (local lane, `DB_BACKEND=local`): after a crash / kill and a restart on the same data root, answering the PREVIOUS
17072
+ life's still-open card is now `410 gone.ask` with `state: "VOID"` and `deniedReason` `crashed_before_park` / `shutdown_before_park` โ€”
17073
+ <= 7.115.x answered `200` + `consumerState: "absent"` (the run had long been reclaimed; nothing executed). An ask that was already decided
17074
+ (decided, then the process died mid-execution) is unchanged: `409 conflict.ask_decided`. SQL backends are unchanged in 7.116.0. Routing off field presence across the legs is how a
16720
17075
  consumer ends up driving the wrong durable gate.
16721
17076
  ๐Ÿ”ด The checkpoint token is NEVER here: the resume credential is not externally disclosed. Use
16722
17077
  `sessionId`.
@@ -18495,9 +18850,15 @@ components:
18495
18850
  description: >-
18496
18851
  [2400] TR-4 โ€” per-message queue priority on POST /v1/runs/{taskId}/steer. ACCEPTED + ENUM-VALIDATED
18497
18852
  FAIL-LOUD (`routes/runs.ts`; anything else is 400 `request.field_invalid`) and echoed back on the
18498
- receipt, but ADVISORY today: core's steer is a single-slot last-writer-wins with no per-message priority
18499
- or addressable drop, so a faithful priority queue (and DELETE /v1/runs/{taskId}/queue/{messageId}) waits
18500
- on a core seam.
18853
+ receipt. WHETHER IT TAKES EFFECT is the server's call โ€” read `Capabilities.fleet.steerPriority` first:
18854
+ `"engine"` (server >=7.116.0) = the tier is handed to the engine on every delivery leg (`now` cuts the in-flight turn
18855
+ IMMEDIATELY โ€” the running tool receives an abort, an approval card pending on this replica is revoked, one `engine_notice`
18856
+ `task.turn_interrupted` frame is the evidence; `next` โ‰ก no `priority`; `later` is delivered after the turn the run would have stopped on; parked
18857
+ legs store the tier and deliver on the resume's / wake's first turn; replaying the same `Idempotency-Key` + text with a different effective
18858
+ tier is 409 `steering.duplicate_input_id`); `"advisory"` (server <=7.115.x) = accepted and echoed but NOT applied โ€”
18859
+ core's steer was a single-slot last-writer-wins with no per-message priority or addressable drop, so a
18860
+ faithful priority queue (and DELETE /v1/runs/{taskId}/queue/{messageId}) waited on a core seam. An absent
18861
+ or unknown capability value means "do not assume it is applied".
18501
18862
 
18502
18863
  SteerReceipt:
18503
18864
  type: object
@@ -18774,6 +19135,8 @@ components:
18774
19135
  server 1.239 ([1392]โ‘ก): on allow_session, an honest echo of whether the grant was REALLY
18775
19136
  recorded (true = landed in the bridge store; false = no sessionId, could not record โ€” do not
18776
19137
  render "session-wide allowed"). Omitted on non-allow_session decisions and on older servers.
19138
+ ๐Ÿ”ด server >= 7.116.0: ALWAYS `false` on a card that carries `rememberRefused: true` (an engine `Skill` card etc.) โ€” the decision
19139
+ applies once and nothing is remembered (existing key, not a new one).
18777
19140
  updatedInputForwarded:
18778
19141
  type: boolean
18779
19142
  description: >