@sema-agent/sdk 23.1.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +201 -4
  2. package/dist/client.d.ts +6 -1
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +8 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/device/index.d.ts +76 -12
  7. package/dist/device/index.d.ts.map +1 -1
  8. package/dist/device/index.js +90 -31
  9. package/dist/device/index.js.map +1 -1
  10. package/dist/errors.d.ts +318 -6
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +121 -10
  13. package/dist/errors.js.map +1 -1
  14. package/dist/events.d.ts +165 -23
  15. package/dist/events.d.ts.map +1 -1
  16. package/dist/events.js +19 -0
  17. package/dist/events.js.map +1 -1
  18. package/dist/health.d.ts +29 -1
  19. package/dist/health.d.ts.map +1 -1
  20. package/dist/health.js.map +1 -1
  21. package/dist/index.d.ts +38 -12
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +25 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/model-route-env/index.d.ts +129 -0
  26. package/dist/model-route-env/index.d.ts.map +1 -0
  27. package/dist/model-route-env/index.js +131 -0
  28. package/dist/model-route-env/index.js.map +1 -0
  29. package/dist/registry/errors.d.ts +3 -0
  30. package/dist/registry/errors.d.ts.map +1 -1
  31. package/dist/registry/errors.js +3 -2
  32. package/dist/registry/errors.js.map +1 -1
  33. package/dist/registry/me-config.d.ts +1 -0
  34. package/dist/registry/me-config.d.ts.map +1 -1
  35. package/dist/registry/me-config.js +1 -1
  36. package/dist/registry/me-config.js.map +1 -1
  37. package/dist/registry/scope-config-feedback.d.ts +1 -0
  38. package/dist/registry/scope-config-feedback.d.ts.map +1 -1
  39. package/dist/registry/scope-config-feedback.js +1 -1
  40. package/dist/registry/scope-config-feedback.js.map +1 -1
  41. package/dist/resources/adoption.d.ts +8 -2
  42. package/dist/resources/adoption.d.ts.map +1 -1
  43. package/dist/resources/adoption.js.map +1 -1
  44. package/dist/resources/approvals.d.ts +22 -2
  45. package/dist/resources/approvals.d.ts.map +1 -1
  46. package/dist/resources/approvals.js +12 -2
  47. package/dist/resources/approvals.js.map +1 -1
  48. package/dist/resources/elicitations.d.ts +2 -1
  49. package/dist/resources/elicitations.d.ts.map +1 -1
  50. package/dist/resources/elicitations.js.map +1 -1
  51. package/dist/resources/fleet.d.ts +65 -6
  52. package/dist/resources/fleet.d.ts.map +1 -1
  53. package/dist/resources/fleet.js.map +1 -1
  54. package/dist/resources/ops.d.ts +113 -0
  55. package/dist/resources/ops.d.ts.map +1 -1
  56. package/dist/resources/ops.js +10 -0
  57. package/dist/resources/ops.js.map +1 -1
  58. package/dist/resources/principal-inbox.d.ts +62 -0
  59. package/dist/resources/principal-inbox.d.ts.map +1 -0
  60. package/dist/resources/principal-inbox.js +51 -0
  61. package/dist/resources/principal-inbox.js.map +1 -0
  62. package/dist/resources/questions.d.ts +2 -1
  63. package/dist/resources/questions.d.ts.map +1 -1
  64. package/dist/resources/questions.js.map +1 -1
  65. package/dist/resources/session-sync.d.ts +4 -2
  66. package/dist/resources/session-sync.d.ts.map +1 -1
  67. package/dist/resources/session-sync.js +4 -2
  68. package/dist/resources/session-sync.js.map +1 -1
  69. package/dist/resources/tool-approvals.d.ts +201 -22
  70. package/dist/resources/tool-approvals.d.ts.map +1 -1
  71. package/dist/resources/tool-approvals.js +183 -36
  72. package/dist/resources/tool-approvals.js.map +1 -1
  73. package/dist/settings.d.ts +73 -1
  74. package/dist/settings.d.ts.map +1 -1
  75. package/dist/sse.d.ts +8 -1
  76. package/dist/sse.d.ts.map +1 -1
  77. package/dist/sse.js +9 -5
  78. package/dist/sse.js.map +1 -1
  79. package/dist/types.d.ts +475 -113
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/types.js +11 -5
  82. package/dist/types.js.map +1 -1
  83. package/openapi.yaml +1296 -153
  84. package/package.json +6 -2
package/openapi.yaml CHANGED
@@ -423,7 +423,7 @@ paths:
423
423
  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,
424
424
  `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported` / `rewind_snapshot.unresolvable`, the
425
425
  registered `config.*` codes incl. the three roster-bound ones and — server >= 7.116.0 — `config.invalid_checkpoint_store` (400) and
426
- `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: `) and — server >= 7.126.0 — `config.tool_face_control` (400: `allowTools: ["mcp__*"]`, `excludeTools: ["*"]` (and, on server 7.126.0–7.128.x only, a name in `allowTools` / `excludeTools` that no tool of this run answers to — server >= 7.129.0 runs those as written and announces each once with the operator row `runner_config_advisory`), or an `excludeTools` entry that reaches a protocol tool this leg depends on; the engine rejects before the run starts, never silently ignoring the name; `error` is the engine's sentence naming the entry, its list and why), `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
426
+ `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: `) and — server >= 7.126.0 — `config.tool_face_control` (400: `allowTools: ["mcp__*"]`, `excludeTools: ["*"]` (and, on server 7.126.0–7.128.x only, a name in `allowTools` / `excludeTools` that no tool of this run answers to — server >= 7.129.0 runs those as written and announces each once with the operator row `runner_config_advisory`), or an `excludeTools` entry that reaches a protocol tool this leg depends on; the engine rejects before the run starts, never silently ignoring the name; `error` is the engine's sentence naming the entry, its list and why) and — server >= 7.132.0, BREAKING (200 + failed body before) — `config.agent.unknown_tool` (400: a delegated agent's `allowTools` names a near-miss / a retired name with a successor / an impossible spelling, judged by the engine before the run; the intake refusal of `agents[i].denyTools` entries that read as nothing has answered this 400 since 7.131.0) and `config.legacy_tool_name` (400: a stored session-rule row names a tool the engine reads as nothing) and `prompt.assembled_role_refused` (400: `systemPrompt` / `appendSystemPrompt` — `settings.outputStyle` folded in — carries the engine's locked prompt text verbatim; send role text only) and — server >= 7.131.0 — `config.user_attachment_invalid` (400; server >= 7.132.0 the body adds `detail: { path, index? }`), `corrupt_session`; these registered codes answer 400 on all three submit legs without spending the `Idempotency-Key` (async / live legs: 2xx with the same code on the terminal run row); codes outside the table, e.g. `rewind.invalid_spec`, stay
427
427
  200 with a failed body) answers its 4xx / 5xx ERROR BODY only when the whole submission did ZERO WORK (cumulative
428
428
  `stats.turns === 0` AND `stats.tokens === 0` — the common case: nothing billed, not idempotency-cached). When a
429
429
  `verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
@@ -3424,7 +3424,7 @@ paths:
3424
3424
  `synced` (and `synced.count` still counts ONLY the durable queue).
3425
3425
  🔴 THE TWO WORDS DELIBERATELY DO NOT REUSE `pending`/`resolved`: those are keyed by
3426
3426
  `(sessionId, toolCallId)` and decided at `POST /v1/approvals/{sessionId}/decide`, while a live row is
3427
- keyed by `approvalId` and decided at `POST /v1/tool-approvals/{approvalId}/respond`. Sharing one word
3427
+ keyed by `approvalId` and decided at `POST /v1/tool-approvals/{id}/respond`. Sharing one word
3428
3428
  would eventually send a live card to the decide door — a 404 with no wire evidence of the mistake.
3429
3429
  ⚠️ Both live words come from an IN-PROCESS window projection, so their cross-replica reach is exactly
3430
3430
  that of `GET /v1/approvals`'s `livePending` — narrower than the durable pair above. The deployment
@@ -4393,6 +4393,38 @@ paths:
4393
4393
  '404': { description: no pending question for this id (non-owner, answered, expired, or wrong replica) — no existence oracle. }
4394
4394
  '501': { description: AskUserQuestion is not enabled on this worker (ASK_QUESTION_ENABLED). }
4395
4395
 
4396
+ /v1/tool-approvals/{id}:
4397
+ parameters:
4398
+ - $ref: '#/components/parameters/PrincipalHeader'
4399
+ - { name: id, in: path, required: true, schema: { type: string }, description: 'The ask''s `approvalId` (server >= 7.133.0 always equal to its `askId`; an opaque string).' }
4400
+ get:
4401
+ tags: [questions]
4402
+ operationId: toolApprovalRead
4403
+ x-status: live # server >= 7.133.0; SDK toolApprovals.get consumes it
4404
+ summary: Read ONE live-card ask — where is it now (pending, decided, parked or void)? Pure read.
4405
+ description: >
4406
+ server >= 7.133.0 — the by-ask read door. Same root and same gate as POST /v1/tool-approvals/{id}/respond (one `gatedPrincipal`; an owner-gate miss answers the SAME 404 as an unknown
4407
+ id — no existence oracle), and the state is read the way the answer door reads it: a live registration on THIS replica waiting for the ask => `pending` (the body is the `livePending[]` row projection +
4408
+ `state`); otherwise the row is read by id: `DECIDED` => `decided`, `PARKING` / `PARKED` => `parked`, `DENIED` / `VOID` => `void`. A row still `STREAM_PENDING` with no local registration lives on
4409
+ another replica => 404 `tool_approval.not_pending` + `cause: "unknown_or_other_replica"` (the same answer as a late respond); a deployment with no ask ledger answers the same 404 WITHOUT `cause`.
4410
+ Pure read: zero writes, no settlement, the local registration is untouched. Use it to ask "is this card still alive / was it decided by anyone" instead of inferring it from a respond 404.
4411
+ An older server (<= 7.132.x) has no such door: routing-level 404 `not_found.route` — tell it apart from this door's 404 by `errorCode`, never by status.
4412
+ responses:
4413
+ '200':
4414
+ description: The ask's current state.
4415
+ content:
4416
+ application/json:
4417
+ schema: { $ref: '#/components/schemas/ToolApprovalReadBody' }
4418
+ '401': { $ref: '#/components/responses/Unauthorized' }
4419
+ '404':
4420
+ description: >
4421
+ `tool_approval.not_pending` — unknown / not yours (byte-identical) / still pending on another replica (`cause: unknown_or_other_replica`). Past the owner gate the body may carry the additive `cause`
4422
+ (see ToolApprovalNotPendingCause); no ask ledger => no `cause`.
4423
+ content:
4424
+ application/json:
4425
+ schema: { $ref: '#/components/schemas/ToolApprovalNotPendingBody' }
4426
+ '501': { description: 'Live tool-approval HITL not enabled (TOOL_APPROVAL_ENABLED) — `feature.tool_approval_disabled`.' }
4427
+
4396
4428
  /v1/tool-approvals/{id}/respond:
4397
4429
  parameters:
4398
4430
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -5799,6 +5831,70 @@ paths:
5799
5831
  '403': { description: 'Multi-tenant worker: outcomes view is operator-only.' }
5800
5832
  '501': { $ref: '#/components/responses/NotImplemented' }
5801
5833
 
5834
+ /v1/config/catalog:
5835
+ parameters:
5836
+ - $ref: '#/components/parameters/PrincipalHeader'
5837
+ get:
5838
+ tags: [admin]
5839
+ operationId: configCatalog
5840
+ x-status: live # operator lane (capabilities.configCatalog); SDK configCatalog.get consumes it
5841
+ summary: Deployment configuration catalog — every env knob's declaration and this process's effective reading (operator-only).
5842
+ description: >
5843
+ The deployment's configuration catalog (operator lane; probe `capabilities.configCatalog` first). One row per env knob: its declared semantics, which lanes can write it, which lane is in force, the
5844
+ process's effective DISPLAY value and the centre seat (`center`) when the knob is centre-publishable; plus the `retired` tombstone list (env names this process refuses to start on). `Cache-Control: no-store`.
5845
+ Gate: `OPERATOR_PRINCIPALS` — outside the list 403 `auth.operator_only`; empty list on a multi-tenant deployment 501 `capability.operator_principals_required`; `CONFIG_CATALOG_ENABLED=false` 501
5846
+ `feature.config_catalog_disabled`. A server older than 7.53 has no such route (404).
5847
+ `effectiveValue` is a DISPLAY value, never the raw env: `secret` / `opaque` rows report only `{ present, length? }`, a `url` row goes through the display mint (userinfo landed, query values become
5848
+ tokens). Two VERDICT bits on a row are true-only (never `false`; ABSENT = this cell has no such matter): `effectiveValueInvalid` (the effective value falls outside the row's own echo set; `effectiveValue` is then
5849
+ `null`) and, server >= 7.133.0, `effectiveValueWithheld` (a `url` row whose display mint WITHHELD a part — a credential-shaped piece in a query / path segment — so the shown value is not the whole value).
5850
+ responses:
5851
+ '200':
5852
+ description: The catalog.
5853
+ content:
5854
+ application/json:
5855
+ schema: { $ref: '#/components/schemas/ConfigCatalog' }
5856
+ '401': { $ref: '#/components/responses/Unauthorized' }
5857
+ '403': { description: 'The caller is not in OPERATOR_PRINCIPALS (`auth.operator_only`).' }
5858
+ '501': { $ref: '#/components/responses/NotImplemented' }
5859
+
5860
+ /v1/principal-inbox/messages:
5861
+ parameters:
5862
+ - $ref: '#/components/parameters/PrincipalHeader'
5863
+ post:
5864
+ tags: [admin]
5865
+ operationId: principalInboxSend
5866
+ x-status: live # server >= 7.133.0; SDK principalInbox.send consumes it
5867
+ summary: Deliver one letter to another principal's inbox in the same organization (cross-principal delivery; default OFF).
5868
+ description: >
5869
+ server >= 7.133.0 — one letter is appended to the recipient principal's `principal.inbox` ONLY by the organization delivery gate: membership judgement -> mint -> inbox bound -> quota -> append, all inside one gate that answers
5870
+ with a receipt or one of four refusal codes. The SENDER is always your verified identity (`from` is not a body key: sending `from` or any unknown key => 400 `request.body_shape` + `unknownKeys`). DEFAULT OFF: without env
5871
+ `PRINCIPAL_INBOX_ASSISTANT` the route answers 501 `capability.unavailable` (no delivery gate is mounted) — probe `capabilities.principalInboxDelivery` (true <=> this door works). THIS VERSION SHIPS THE DELIVERY HALF ONLY:
5872
+ a delivered letter sits in the recipient's box (at most 50 unconfirmed envelopes) and no read verb exists yet. Judging order: the sender's ATTEMPT bound (every attempt is recorded, succeeded or not, BEFORE membership) ->
5873
+ membership (including the mailbox-key judge) -> mint (a sender that cannot be spelled into the envelope's sender slot answers the same `not_member`, no quota consumed) -> inbox bound -> quota deduction -> append. A refused
5874
+ delivery appends nothing. Not auto-retried by the SDK (every attempt consumes the sender's attempt quota).
5875
+ The four refusal codes: `org_delivery.not_member` 404 (ONE sentence, byte-identical body for: different organization / no such person / directory could not answer / `to` is not a principal shape / the recipient cannot be a
5876
+ mailbox key / the sender cannot be spelled into the envelope — the gate is never an existence probe); `org_delivery.rate_limited` 429 (one sentence for three bounds: 60 attempts per sender per hour — a typo'd recipient costs an
5877
+ attempt —, 10 letters per (sender, recipient) pair per hour, 30 letters per recipient per hour; fixed clock-hour windows, up to double across a boundary; hard-coded, no knob); `org_delivery.inbox_full` 409 (the recipient's box already holds
5878
+ 50 unconfirmed envelopes — judged BEFORE quota so a full box burns none); `org_delivery.recipient_tombstoned` 410 (the recipient's mailbox is in its pre-delete state).
5879
+ requestBody:
5880
+ required: true
5881
+ content:
5882
+ application/json:
5883
+ schema: { $ref: '#/components/schemas/PrincipalInboxMessage' }
5884
+ responses:
5885
+ '200':
5886
+ description: The letter was appended; `receiptId` is the gate's delivery receipt id.
5887
+ content:
5888
+ application/json:
5889
+ schema: { $ref: '#/components/schemas/PrincipalInboxReceipt' }
5890
+ '400': { description: 'Malformed body: `request.field_invalid` (not an object / empty `to` / empty or over-long `content` / non-boolean `urgent`) or `request.body_shape` (unknown key incl. `from`; body carries `unknownKeys`).' }
5891
+ '401': { $ref: '#/components/responses/Unauthorized' }
5892
+ '404': { description: '`org_delivery.not_member` — one byte-identical body for six causes (see the description); the gate is not an existence probe.' }
5893
+ '409': { description: '`org_delivery.inbox_full` — the recipient''s box holds 50 unconfirmed envelopes.' }
5894
+ '410': { description: '`org_delivery.recipient_tombstoned` — the recipient''s mailbox is in its pre-delete state.' }
5895
+ '429': { description: '`org_delivery.rate_limited` — one sentence for the sender-attempt bound, the pair bound and the recipient aggregate bound.' }
5896
+ '501': { description: 'No delivery gate mounted (`PRINCIPAL_INBOX_ASSISTANT` off) — `capability.unavailable`.' }
5897
+
5802
5898
  /v1/diagnostics/wiring:
5803
5899
  parameters:
5804
5900
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -7064,6 +7160,7 @@ components:
7064
7160
  promptSummary: { type: string, description: 'a human-readable prompt DIGEST — NOT the full prompt.' }
7065
7161
  enabled: { type: boolean }
7066
7162
  available: { type: boolean, description: 'whether this deployment can actually RUN the scenario right now — same predicate as the 501 rejection arm on submit; false ⟺ submitting is guaranteed to 501. Independent axis from `enabled` (declared vs runnable).' }
7163
+ attachmentSupplies: { type: array, items: { type: string, enum: [path, content_text, content_image] }, description: 'server >= 7.133.0, always present there: the attachment SUPPLY words this scenario actually carries — a subset of `capabilities.userAttachments.supplies` (`path` only on hands scenarios, where a workspace lives somewhere; a no-hands scenario drops `path`, and a `path` entry is refused by the engine with `config.user_attachment_invalid`). ABSENT = server <= 7.132.x: fall back to `userAttachments.supplies` plus the existing 400 handling.' }
7067
7164
  unavailableReason: { type: string, description: 'machine-readable reason, present ONLY when available:false (absent = no reason). Closed set, currently the single member `git_client_unconfigured`; branch on the key, never match the English prose. Additive-open: treat an unknown word as "unavailable, reason unknown".' }
7068
7165
 
7069
7166
  RunStatus:
@@ -7704,11 +7801,20 @@ components:
7704
7801
  `permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`.
7705
7802
  🔴 ON THIS SUBMIT FACE THE KEY SET IS CLOSED (server >= 7.57.0 for the sub-tree): accepted top-level keys
7706
7803
  are `env` / `hooks` / `model` / `outputStyle` / `permissions` / `skillListingBudgetFraction` /
7707
- `skillListingMaxDescChars` / `ultracode` / `webSearch`, and accepted `permissions.*` keys are
7708
- `allow` / `ask` / `defaultMode` / `deny` / `disableAutoMode`; anything else is 400 `request.body_shape`
7804
+ `skillListingMaxDescChars` / `ultracode` / `webSearch` (server >= 7.133.0 adds the three TIGHTEN-ONLY seats `complianceAdditionalDenies` / `readDenyPatterns` / `writeProtectedExtra`), and accepted `permissions.*` keys are
7805
+ `allow` / `ask` / `defaultMode` / `deny` / `disableAutoMode` (server >= 7.133.0 adds `neverAuto`); anything else is 400 `request.body_shape`
7709
7806
  with the names in the refusal's `unknownKeys` / `unsupportedKeys` arrays (REFUSED, not dropped — a dropped
7710
7807
  key reads as "in effect" to the caller). The object stays OPEN here because the same `SemaSettings`
7711
7808
  schema also serves the local `settings.json` wiring, where deferred CC keys are legal.
7809
+ 🔴 TIGHTEN-ONLY SEATS (server >= 7.133.0; probe `capabilities.taskSettings.tightenOnlySeats`, an absent bit = an older server that answers 400 `request.body_shape` for these four keys): `readDenyPatterns` (entries: a bare
7810
+ pattern string, or `{ pattern, caseSensitive? }`), `writeProtectedExtra` (a bare name — no `/` reads as `segment`, a `/` reads as `segment-run` — or `{ name, kind: basename | segment | segment-run }`),
7811
+ `complianceAdditionalDenies` (`mcp_servers` | `workflows` | `web_fetch` | `org_memory_mount`) and `permissions.neverAuto` (tool names that ALWAYS ask a human). Each seat = the deployment table UNION the request list, ONLY EVER GROWING
7812
+ (the request face has no loosening arm); an empty array is the same as not sent. Bad value => 400 `request.field_invalid` with `field` + `reason` (`over_cap` + `count` + `cap` / `bad_entry` + the first bad `index`).
7813
+ Caps: 1000 entries per seat, EXCEPT `readDenyPatterns`: at most 32 entries, at most 2 `*` per path segment and 16 `*` in the whole table (every star counts, nothing is folded). A `neverAuto` name that can never match any tool => 422.
7814
+ A resume replay of a stored body whose seat went bad => 409 `resume_blocked_by_policy`. A compliance deny is a REFUSAL, not a narrowing: a request denying `web_fetch` is refused whole where the default tool surface still mounts `WebFetch` (the single-user default does).
7815
+ 🔴 `settings.hooks[]` (server >= 7.133.0; probe `capabilities.taskSettings.pluginHooks` / `managedHookGroups` — an absent or false bit = the keys are STRIPPED SILENTLY): group key `source` (closed: `managedPolicy` / `user` / `project` / `local` / `flag` /
7816
+ `managedPlugin:<name>` / `plugin:<name>`; absent = unmanaged; managed groups run in an independent lane after the unmanaged rewrites), command-entry keys `plugin { name, root, dataDir?, options? }` and exec-form `args`; a malformed one =>
7817
+ 400 `request.field_invalid` with `detail: { reason: "wire_key_invalid", key, path }`; the plugin context has one 8192-character budget.
7712
7818
  🔴 `skillListingBudgetFraction` (number in (0,1]) / `skillListingMaxDescChars` (positive integer),
7713
7819
  server >= 7.78.1: the SKILL-LISTING RENDER BUDGET, passed to the engine with ZERO processing
7714
7820
  (no clamping, no conversion, no folding) — ABSENT means the key is not written at all (the server does
@@ -8374,6 +8480,10 @@ components:
8374
8480
  cause (`terminal`), not the eight parallel plane keys — see `TerminalCause`.
8375
8481
  required: [taskId, sessionId, terminal, stats]
8376
8482
  properties:
8483
+ warnings:
8484
+ type: array
8485
+ items: { $ref: '#/components/schemas/EntryProblem' }
8486
+ description: 'server >= 7.133.0, top level of the sync `POST /v1/tasks` 200 body (beside `notice`), present only when non-empty: the accept-time NOTICES for allow-side selector-list entries the engine reads as nothing or that are not in the roster. ABSENT = no notice (older servers too).'
8377
8487
  taskId: { type: string }
8378
8488
  sessionId: { type: string }
8379
8489
  wiringManifest:
@@ -8676,6 +8786,10 @@ components:
8676
8786
  taskId: { type: string }
8677
8787
  sessionId: { type: string }
8678
8788
  status: { $ref: '#/components/schemas/RunStatus' }
8789
+ warnings:
8790
+ type: array
8791
+ items: { $ref: '#/components/schemas/EntryProblem' }
8792
+ description: 'server >= 7.133.0, present only when non-empty: the accept-time NOTICES for allow-side selector-list entries the engine reads as nothing or that are not in the roster (acceptance is unchanged — the engine takes them, only the operator hears afterwards; this makes "this name will not take effect" visible at the call). Same list as the stream `meta` frame''s and the sync 200 body''s. ABSENT = no notice (older servers too).'
8679
8793
 
8680
8794
  SealedKeyCapability:
8681
8795
  type: object
@@ -9657,6 +9771,16 @@ components:
9657
9771
  a child process spawned on this host NEVER inherits a name the deployment declared as its own credential — every `inheritEnv` policy (`"all"` and `"scrub"` alike) subtracts that list first, and an allow list naming one is
9658
9772
  refused. It speaks of INHERITANCE only (the engine process's own environment stays readable to same-user processes on the host; a credential that never enters it remains the root fix).
9659
9773
  ABSENT = an old server (<= 7.128.x) or a word this deployment does not wire: read it as "not promised", not as "absent capability". A shell gates on the word, never on a version number.
9774
+ inherit_env.own_credential_values_withheld:
9775
+ type: boolean
9776
+ description: >-
9777
+ ENGINE CAPABILITY WORD (core `ENGINE_CAPABILITIES`' second word, core 7.52.0; server >= 7.132.0 projects it; the key IS the word, dotted):
9778
+ a child process spawned on this host does NOT inherit a variable whose VALUE equals one of this deployment's credential values (whole-string equality, whatever
9779
+ the variable is called) — a key copied into another variable name is withheld under `inheritEnv: "all"` too. 🔴 A FACT, not a promise — it can be `false`:
9780
+ true <=> every credential variable of the live configuration that has a value in the environment went into the engine's value arm; the engine refuses a value it
9781
+ reads as "not a credential" (blank / a placeholder word / one repeated character / under 8 characters) or "the value of a variable every child process gets"
9782
+ (startup does not refuse; the name arm still withholds) => `false`. A credential resolved only per request is not in the value arm. INHERITANCE only, like the first word.
9783
+ ABSENT = an old server (<= 7.131.x withholds by name only — a same-value copy under another name reaches the child under `"all"`). Gate on `=== true`.
9660
9784
  asyncRuns: { type: boolean }
9661
9785
  artifacts: { type: boolean }
9662
9786
  approvals: { type: boolean }
@@ -9793,6 +9917,11 @@ components:
9793
9917
  store backend deliberately wires no image index (the pool is cloud/fleet-only) ⇒ false there.
9794
9918
  SCOPE: P1 only — `/v1/images/bakes*` (the P2 bake control plane) is a separate dependency and an
9795
9919
  operator/runner-internal face, NOT promised by this bit.
9920
+ principalInboxDelivery:
9921
+ type: boolean
9922
+ description: >-
9923
+ server >= 7.133.0 — the cross-principal delivery door `POST /v1/principal-inbox/messages` is wired (delivery gate present: env `PRINCIPAL_INBOX_ASSISTANT` on and the gate's dependencies in place).
9924
+ True iff that route works; absent gate => the route answers 501 `capability.unavailable`. ABSENT key = server <= 7.132.x (no such route; routing-level 404). Probe this bit; do not trial-by-501.
9796
9925
  sharedMemory:
9797
9926
  type: boolean
9798
9927
  description: >
@@ -9963,6 +10092,12 @@ components:
9963
10092
  lane) or 422 (hosted).
9964
10093
  mcp: { type: boolean, description: "MCP servers can be attached." }
9965
10094
  mcpInjection: { type: boolean, description: "Per-request MCP injection is accepted." }
10095
+ mcpAssertionHeaderSent:
10096
+ type: boolean
10097
+ description: >-
10098
+ server >= 7.133.0 — the SEND seat self-report (a boolean, today ALWAYS `false`): whether `mcpServers[].transport.assertionHeader` (accepted since server 7.128.0, mutually exclusive with `principalHeader`)
10099
+ is actually SENT to the MCP peer. `false` = the declaration is carried but NOT sent (the same switch drives the key stripping, the operator info line and the probe-door refusal; the disclosure sentences leave when it turns true).
10100
+ ABSENT = an older server, which does not send either — treat it as `false`; never rely on the header for peer authentication.
9966
10101
  mcpProbe:
9967
10102
  type: boolean
9968
10103
  description: >
@@ -10002,6 +10137,17 @@ components:
10002
10137
  ⚠️ ABSENT = an older server (<7.87.1) ⇒ cannot tell whether the push face carries the live family;
10003
10138
  do NOT read "no `live_pending` frame arrived yet" as "this deployment never pushes live" — the two
10004
10139
  are indistinguishable on an older server. Fall back to `GET /v1/approvals`'s `livePending` pull face.
10140
+ approvalFrameFamilies:
10141
+ type: array
10142
+ maxItems: 1
10143
+ items: { type: string, enum: [tool_approval, approval_request] }
10144
+ description: >-
10145
+ server >= 7.133.0, ALWAYS present (the projection of the same judgement as `streamApproval`) — which FRAME FAMILY this worker's approval opening frames belong to:
10146
+ a closed-word array with AT MOST ONE member. `["approval_request"]` = the card protocol is on: one ask travels only `approval_request` -> `approval_complete` / `approval_revoke`
10147
+ (the legacy `tool_approval` / `tool_approval_complete` pair is NOT sent — BREAKING vs <= 7.132.x, which sent both families for one ask and relied on `approvalId` de-duplication;
10148
+ remedy on the worker: `STREAM_APPROVAL_ENABLED=false`). `["tool_approval"]` = the card protocol is not on (`STREAM_APPROVAL_ENABLED=false`, no store backend, or no checkpoint store) but the live card leg is.
10149
+ `[]` = per-call approvals are off altogether (`TOOL_APPROVAL_ENABLED=false`): no card is presented. Dispatch frame families off THIS array, never off `streamApproval`.
10150
+ KEY ABSENT (not `[]`) = server <= 7.132.x — there a `streamApproval: true` leg sends BOTH families for one ask.
10005
10151
  streamApproval:
10006
10152
  type: boolean
10007
10153
  description: >
@@ -10056,6 +10202,13 @@ components:
10056
10202
  a server <= 7.118.x returns the archive as written; a 7.119.x server already redacts but does not report the bit. A true
10057
10203
  bit does not mean the read face exists (that is `sessions`; without a session audit face that door answers 501 and this
10058
10204
  bit is still true), nor that the archive holds no credential (the redaction is read-side only).
10205
+ approvalReadRootGrant:
10206
+ type: boolean
10207
+ description: >-
10208
+ server >= 7.133.0, ALWAYS present, a FACT that varies by deployment — whether this deployment accepts the reply-body key `readRootGrant` ("Yes, and add directory": the card's
10209
+ `readRootCandidate.dir` is registered as a read root together with this one allow). True iff the single-user host-lane posture (the same predicate as `projectContext` / the cwd face); false elsewhere.
10210
+ A client shows the option only when the card carries `readRootCandidate` AND this bit is true. On a non-accepting posture a reply carrying `readRootGrant` is refused
10211
+ `400 request.body_shape` (the ask stays unsettled — answer again without the key). ABSENT = server <= 7.132.x = not accepted; do not send the key.
10059
10212
  approvalReach:
10060
10213
  type: boolean
10061
10214
  description: >-
@@ -10168,6 +10321,9 @@ components:
10168
10321
  outputStyle: { type: boolean }
10169
10322
  env: { type: boolean, description: 'TRUE on the single-user host lane (server emits `cwdHonored(config)`). It is NOT hard-coded false any more — the old always-false value predated the per-task shellEnv seam and hid a live capability.' }
10170
10323
  hooks: { type: boolean, description: 'Single-user deployments only (`requirePrincipal !== true`).' }
10324
+ pluginHooks: { type: boolean, description: 'server >= 7.133.0 — the request `settings.hooks[]` PLUGIN-CONTEXT keys (command entry `plugin { name, root, dataDir?, options? }` / exec-form `args`) are accepted; same predicate as `hooks` (single-user true, multi-tenant false). ABSENT or false = the keys are stripped silently — send only on sight of the bit.' }
10325
+ managedHookGroups: { type: boolean, description: 'server >= 7.133.0 — the request `settings.hooks[]` GROUP `source` key (managed lanes `managedPolicy` / `managedPlugin:<name>`) is accepted; same predicate as `hooks`. ABSENT or false = stripped silently (the managed-lane semantics do not apply, it is not a refusal).' }
10326
+ tightenOnlySeats: { type: boolean, description: 'server >= 7.133.0, constant true — the four tighten-only request seats `settings.readDenyPatterns` / `settings.writeProtectedExtra` / `settings.complianceAdditionalDenies` / `settings.permissions.neverAuto` are accepted (each seat = deployment table UNION request list, only ever growing; an empty array is the same as not sent). ABSENT = server <= 7.132.x, which answers 400 `request.body_shape` for those keys — send only on sight of the bit.' }
10171
10327
  settings:
10172
10328
  type: object
10173
10329
  description: >
@@ -10196,6 +10352,7 @@ components:
10196
10352
  properties:
10197
10353
  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.' }
10198
10354
  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).' }
10355
+ callerTrust: { type: string, description: 'server >= 7.133.0 — THIS caller''s trust tier (per caller / per credential, NOT a version bit). Request rule tables that take effect without a person answering (`settings.permissions` buckets; a named-invoked skill''s `allowedTools` / `disallowedTools`) are admitted by it: `allow` loosens the no-ask surface and needs `global`; `deny` / `ask` only tighten and are admitted at any tier. Today `global` (single-user turnkey; multi-tenant with the deployment service credential; multi-tenant sign-in token whose scope role >= the governance write role) or `project` (everything else). Read OPEN: an unknown word is treated as `project` (fail-closed). A request below the floor is refused whole with 400 `request.field_invalid` (`detail.problems[].reason: below_trust_floor`, `detail.callerTrust`). ABSENT = server <= 7.132.x: no per-tier admission.' }
10199
10356
  memoryWrite:
10200
10357
  type: boolean
10201
10358
  description: >
@@ -10258,7 +10415,7 @@ components:
10258
10415
  additionalProperties: true
10259
10416
  properties:
10260
10417
  enabled: { type: boolean, enum: [true] }
10261
- protocolVersion: { type: integer }
10418
+ protocolVersion: { type: integer, description: 'The protocol REVISION this server speaks (the `challenge` frame carries the same value; accepted window [1, 2] on server >= 7.133.0). Server >= 7.133.0 reads 2 (<= 7.132.x read 1). An executor declares the SMALLER of its own highest revision and this value, and takes its outcome-code set from the declared revision (revision 2 added `liveness_no_output`).' }
10262
10419
  maxInflightPerDevice: { type: integer }
10263
10420
  wsPath: { type: string }
10264
10421
  management: { type: boolean, description: 'server >= 7.88.0. `/v1/devices/*` admin verbs present. PRESENT-IFF — absent means a pre-7.88.0 peer (unknown), not false.' }
@@ -10752,9 +10909,9 @@ components:
10752
10909
  server >= 7.65.0 (S-167) — the READ containment rung of this deployment AND WHO SET IT (operator face
10753
10910
  `GET /v1/diagnostics/wiring` -> `readFace`). `face` is the same boot artefact as the tenant-facing
10754
10911
  `capabilities.readFace` (`null` = nothing pinned by this deployment, the engine default is in force).
10755
- `source` is a CLOSED four-word set on the server (exhaustive switch — adding a word is a compile error
10912
+ `source` is a CLOSED five-word set on the server (exhaustive switch — adding a word is a compile error
10756
10913
  there): `env` = this machine's READ_FACE env var (wins over everything) · `center` = the config-center
10757
- readFace domain (restart-to-apply) · `posture` = derived from the single-user turnkey posture
10914
+ readFace domain (restart-to-apply) · `lane` = (server >= 7.133.0) the isolated execution lane's default — with `READ_FACE` absent, e2b / k8s / local-docker mint `open` · `posture` = derived from the single-user turnkey posture
10758
10915
  (REQUIRE_PRINCIPAL unset ⇒ `open`; server >= 7.65.0 behaviour change) · `engine-default` = not pinned.
10759
10916
  `note` is a human-facing pointer for operators. Minimal disclosure: the deny table is never on this face.
10760
10917
  required: [face, source, note]
@@ -10764,7 +10921,7 @@ components:
10764
10921
  enum: [open, roots, null]
10765
10922
  source:
10766
10923
  type: string
10767
- enum: [env, center, posture, engine-default]
10924
+ enum: [env, center, lane, posture, engine-default]
10768
10925
  note:
10769
10926
  type: string
10770
10927
 
@@ -10781,9 +10938,9 @@ components:
10781
10938
  `DURABLE_APPROVAL` ON and a 24h approval window when neither is set, so a default FLIP has to be visible
10782
10939
  on an operator surface or nobody sees it.
10783
10940
 
10784
- `source` is the CLOSED four-word set (exhaustive switch on the server — adding a word is a compile error
10941
+ `source` is the CLOSED five-word set (exhaustive switch on the server — adding a word is a compile error
10785
10942
  there), in PRECEDENCE order: `env` = pinned by this machine's env var (deployment sovereignty; beats any
10786
- published value) · `center` = applied from the config-center (restart-to-apply) · `posture` = derived from
10943
+ published value) · `center` = applied from the config-center (restart-to-apply) · `lane` = (server >= 7.133.0) derived from the isolated execution lane (e2b / k8s / local-docker mint a lane default, e.g. `READ_FACE` -> `open`) · `posture` = derived from
10787
10944
  the deployment shape (single-user turnkey) · `engine-default` = nothing pinned, the built-in default is in
10788
10945
  force. Only `env`/`center` count as "an operator asked for this"; a posture-derived `true` does not.
10789
10946
  `note` is PROSE for a human (per-knob, and on the `posture` arm it is that knob's own fact sentence) —
@@ -10798,7 +10955,7 @@ components:
10798
10955
  description: 'The knob''s effective value. Typed by the referencing site (boolean / integer / …).'
10799
10956
  source:
10800
10957
  type: string
10801
- enum: [env, center, posture, engine-default]
10958
+ enum: [env, center, lane, posture, engine-default]
10802
10959
  note: { type: string }
10803
10960
 
10804
10961
  SkillSpec:
@@ -10892,7 +11049,19 @@ components:
10892
11049
  `principalHeader` (both set => 400 request.body_shape); a non-string value => 400 request.field_invalid.
10893
11050
  7.128.x accepts and carries the declaration but does NOT send an assertion yet (engine seat pending;
10894
11051
  operator line `mcp_assertion_header_not_sent`).
10895
- allowTools: { type: array, items: { type: string } }
11052
+ allowTools:
11053
+ type: array
11054
+ items: { type: string }
11055
+ description: >-
11056
+ Which of this server's tools to MOUNT (bare tool names, as the server lists them). Three sentences (server >= 7.132.0 contract; read off the engine's
11057
+ `intakeListedTools`, passed through verbatim, never checked against the live list): ABSENT = every listed tool mounts; `[]` = NONE mounts (the server is
11058
+ still connected, still probed, still on the `GET /v1/capabilities/mcp` status face) — `[]` MUST NOT be folded into absence (two facts on the wire;
11059
+ do not drop an empty array when serializing); a name the server does not list = SILENTLY NO MATCH (no 400, no warning — a narrowing mismatch mounts
11060
+ less, never more; for "loud on a typo" name the same tool in `toolFaces`, which notices an unknown name as `config.tool_face_undeclared`; `toolAxes`
11061
+ checks shape only). The engine's one loud case: a server without the `tools` capability and a non-empty list ⇒ operator warning
11062
+ `mcp.tools_capability_absent`. The list also gates the server's MCP RESOURCES: absent ⇒ list / read stay open; a list ⇒ open only if it names the
11063
+ reserved `list_resources` / `read_resource` (so `[]` closes them too). Bad shape (not a string array) ⇒ 400 `request.field_invalid` naming
11064
+ `mcpServers[i].allowTools`.
10896
11065
  elicitation: { type: boolean }
10897
11066
  toolAxes:
10898
11067
  type: object
@@ -10908,6 +11077,15 @@ components:
10908
11077
  server >= 7.66.0 — per-tool face overlays keyed by BARE tool name (see McpToolFace). Any malformed
10909
11078
  face drops the whole server.
10910
11079
  additionalProperties: { $ref: '#/components/schemas/McpToolFace' }
11080
+ startupTimeoutMs:
11081
+ type: number
11082
+ description: >-
11083
+ server >= 7.132.0 (an accepted key; core `McpServerSpec.startupTimeoutMs`): THIS server's startup bound in whole milliseconds — the handshake
11084
+ (connect + initialize) and the initial tool listing each carry it. One read order: this member, else the process-level `MCP_TIMEOUT`, else the MCP
11085
+ SDK's own 60 000 ms. Range 1 000 – 2 147 483 647. The value-shape judge lives ONLY in the engine: neither server door (new submit / probe) checks
11086
+ it at intake; a bad value (a string, a fraction, 0, a negative, NaN, `null`, out of range) refuses ONLY THIS server at its dial
11087
+ (`config.mcp_startup_timeout_invalid`; siblings mount) — never clamped, never read as the default. A <= 7.131.x server answers `400
11088
+ request.body_shape` (named in `unknownKeys`).
10911
11089
 
10912
11090
  A2aServerSpec:
10913
11091
  type: object
@@ -11599,6 +11777,24 @@ components:
11599
11777
  `"other"`). This is the "where did those 180 404s actually land" cell. Empty object = zero failures;
11600
11778
  key absent = an older server.
11601
11779
 
11780
+ ModelPlane:
11781
+ type: object
11782
+ additionalProperties: false
11783
+ description: >
11784
+ `GET /v1/models.modelPlane` (server >= 7.133.0; always present; conditional keys are absent, never `null` / empty string) — the single read for "which model table is this engine serving".
11785
+ Two axes. SERVED TABLE: `servedTable` = `env` (the env table minted from MODEL_ID / MODEL_CHEAP_ID — no catalog is in charge: none configured / source unreachable with no last-known-good / the candidate was refused /
11786
+ no enabled entry) | `catalog` (a catalog's model plane landed and replaced the whole env table). CANDIDATE OUTCOME: `catalog` = `refused` (the latest candidate was refused whole — by the candidate judge or the model-plane
11787
+ group; with no previous generation the env table serves, with one the catalog table still serves — who serves is `servedTable`'s answer, NOT merged into this field) | `adopted` (no whole refusal and the catalog table
11788
+ serves; NOT "the latest candidate landed" — that question belongs to the /health generation ledger) | `absent` (no whole refusal and the env table serves). START-UP ROUTE VERDICT: `refusalCode` / `gap` (both ABSENT when the
11789
+ judge passes — the normal state of a running process): `refusalCode` = `config.model_route_endpoint_missing` | `config.gateway_fallback_without_primary`; `gap` = `endpoint_missing` (set MODEL_GATEWAY_BASEURL or the entry's
11790
+ own `baseUrl`) | `lane_credential_missing` (an anthropic-lane choice with neither ANTHROPIC_API_KEY nor ANTHROPIC_AUTH_TOKEN fell into the endpoint-less openai leg). No sentence is carried. Words are server-owned — read OPEN
11791
+ (an unknown word is "unknown", never "passed").
11792
+ required: [servedTable, catalog]
11793
+ properties:
11794
+ servedTable: { type: string, description: '`env` | `catalog` (open on read).' }
11795
+ catalog: { type: string, description: '`absent` | `adopted` | `refused` (open on read).' }
11796
+ refusalCode: { type: string, description: '`config.model_route_endpoint_missing` | `config.gateway_fallback_without_primary` (open on read); ABSENT = the judge passed.' }
11797
+ gap: { type: string, description: '`endpoint_missing` | `lane_credential_missing` (open on read); ABSENT = the judge passed.' }
11602
11798
  ModelsResponse:
11603
11799
  type: object
11604
11800
  description: >
@@ -11646,6 +11842,11 @@ components:
11646
11842
  Upgrade window (as it is): an on-disk LKG written by server <= 7.122.x only recorded the dropped entries that had a name AND were
11647
11843
  enabled; while serving from it with the center still unreachable this key lists only those rows (possibly `[]`) and the first pull after the
11648
11844
  center is reachable fills it in. An unknown `reasonCode` word reads as "dropped, reason unknown".
11845
+ modelPlane:
11846
+ allOf: [{ $ref: '#/components/schemas/ModelPlane' }]
11847
+ description: >-
11848
+ server >= 7.133.0, ALWAYS present there (appended last): which model table this engine SERVES, whether the catalog was adopted, and what the start-up route judge says — read this instead of predicting
11849
+ (the judge's inputs are the serving process's facts; mirroring two of its judgements drifts). ABSENT = an earlier server (<= 7.132.x). Field reading: ModelPlane.
11649
11850
  candidateRejected:
11650
11851
  allOf: [{ $ref: '#/components/schemas/CandidateRejected' }]
11651
11852
  description: >
@@ -11836,12 +12037,16 @@ components:
11836
12037
  x-open-enum: true
11837
12038
  description: >-
11838
12039
  A model row's key<->URL pairing posture (core `RoutePairingStatus`; server >= 7.45). OPEN on read —
11839
- the six words below are today's set, branch on the known ones and keep a `default` arm. NON-SECRET — no
12040
+ the eight words below are today's set (core `RoutePairingStatus`), branch on the known ones and keep a `default` arm. NON-SECRET — no
11840
12041
  URL, no credential.
11841
12042
  `ok:per-model` this model has its own credential · `ok:paired` deployment credential, and the model's URL
11842
12043
  is on the deployment's declared root · `ok:keyless` no credential anywhere (a local unauthenticated
11843
12044
  gateway) · `broken:credential_mismatch` / `broken:credential_missing` a real request WILL be refused by
11844
- the engine (flag it red) · `unknown` could not be adjudicated — render "unknown", NEVER "good" or "bad".
12045
+ the engine (flag it red) · server >= 7.132.0 / engine 7.52.0 adds two `broken:` words, same reading:
12046
+ `broken:credential_origin_mismatch` (this model's own credential is bound to the host it first went to and the row now points at another
12047
+ host — configure a credential for the new host or point it back; the same refusal on a stream is the `engine_notice`
12048
+ `route.credential_origin_mismatch`) and `broken:credential_answer_conflict` (the per-model credential hook answers the key twice — `apiKey`
12049
+ AND the lane's own key header; answer one of them) · `unknown` could not be adjudicated — render "unknown", NEVER "good" or "bad".
11845
12050
  🔴 sdk 23.0.0 BREAKING (server >= 7.129.0 / engine 7.51.0): `ok:unpinned` (a deployment credential on a deployment that declared no root, so the pairing was unverifiable) is RETIRED with no alias —
11846
12051
  on such a deployment the engine now judges the deployment credential `broken:credential_mismatch`. A server <= 7.128.x still sends `ok:unpinned`: it falls in the open tail, render it as
11847
12052
  "pairing not verified", never as good.
@@ -11851,11 +12056,11 @@ components:
11851
12056
 
11852
12057
  ResumeStartupCause:
11853
12058
  type: string
11854
- enum: [environment, session_gone, tool_unavailable, configuration, decision_rejected]
12059
+ enum: [environment, session_gone, transcript_corrupt, tool_unavailable, configuration, decision_rejected]
11855
12060
  description: >-
11856
- WHY a parked resume could not start its approved action — the engine's closed `RESUME_STARTUP_CAUSES` (core 7.51.0), word for word and in order. The body key of the 422 `parked_resume.startup_failed`
12061
+ WHY a parked resume could not start its approved action — the engine's closed `RESUME_STARTUP_CAUSES` (core 7.51.0; six words since core 7.52.0, server >= 7.132.0), word for word and in order. The body key of the 422 `parked_resume.startup_failed`
11857
12062
  (`ErrorResponse.cause`; SDK `ParkedResumeStartupError.startupCause`). Server >= 7.129.0 screens the engine's word against this set before it goes out (a word the engine did not say, or one outside the set => the key is absent).
11858
- `environment` the leg's infrastructure failed (workspace restore, an abort) — the decision is untouched, retrying replays it · `session_gone` the resumed agent's transcript session is missing ·
12063
+ `environment` the leg's infrastructure failed (workspace restore, an abort) — the decision is untouched, retrying replays it · `session_gone` the resumed agent's transcript session is missing · `transcript_corrupt` (core 7.52.0) the transcript is there but damaged (no header its store wrote, a leaf or parent entry missing, a lost tail the checkpoint was cut inside) — it reopens as `session_gone` does, only the receipt now names it ·
11859
12064
  `tool_unavailable` the approved tool is not available to the resumed leg (gone, or replaced by a different contract) — this approval cannot run, decide afresh · `configuration` the deployment's
11860
12065
  configuration refuses the resumed leg; a retry under the same configuration fails the same way · `decision_rejected` the decision does not fit the checkpoint (another gate's answer, an input the constraints
11861
12066
  refuse) — correct it and resubmit. The shared `ErrorResponse.cause` stays an open string in this document because the key is shared by other codes and by servers <= 7.128.x (which relay the engine's word unscreened).
@@ -12863,14 +13068,14 @@ components:
12863
13068
  ts: { type: integer }
12864
13069
  FleetFrame_hook_notice:
12865
13070
  description: >
12866
- Hook observability frame, TWO kinds (both pure observe — never block, resume, or inject anything;
13071
+ Hook observability frame, FOUR kinds (server >= 7.133.0 added the third and fourth; all pure observe — never block, resume, or inject anything;
12867
13072
  visibility fail-CLOSED like bg_notification: the owner keys ownerScope/ownerSessionId gate delivery
12868
13073
  and are STRIPPED before send; wire is FLAT — `send("hook_notice", { type, ...wire, ts })`, the
12869
13074
  server-internal `{notice}` nesting never reaches it). codex R1-F4: modeled as the discriminated union
12870
13075
  it really is — the common `{type, ts}` frame base composed with each kind's payload arm — so a
12871
13076
  generated client gets the SAME per-kind narrowing as the TS type (a second-family frame missing
12872
- hookName/entryType is invalid, not silently accepted). `kind` is CLOSED two-member on the server but
12873
- OPEN ON READ: degrade an unknown future kind to a generic observability row.
13077
+ hookName/entryType is invalid, not silently accepted). `kind` is CLOSED four-member on the server but
13078
+ OPEN ON READ: degrade an unknown future kind to a generic observability row. DISPATCH on `kind`; never infer a call's outcome from any hook_notice frame (read the tool / approval frames).
12874
13079
  oneOf:
12875
13080
  - allOf:
12876
13081
  - $ref: '#/components/schemas/FleetHookNoticeFrameBase'
@@ -12878,10 +13083,16 @@ components:
12878
13083
  - allOf:
12879
13084
  - $ref: '#/components/schemas/FleetHookNoticeFrameBase'
12880
13085
  - $ref: '#/components/schemas/FleetHookNonBlockingFailure'
13086
+ - allOf:
13087
+ - $ref: '#/components/schemas/FleetHookNoticeFrameBase'
13088
+ - $ref: '#/components/schemas/FleetHookManagedUnrun'
13089
+ - allOf:
13090
+ - $ref: '#/components/schemas/FleetHookNoticeFrameBase'
13091
+ - $ref: '#/components/schemas/FleetHookVerdictUntrusted'
12881
13092
 
12882
13093
  FleetHookNoticeFrameBase:
12883
13094
  type: object
12884
- description: 'The hook_notice frame envelope shared by both kinds: the SSE frame discriminant and the server timestamp.'
13095
+ description: 'The hook_notice frame envelope shared by all four kinds: the SSE frame discriminant and the server timestamp.'
12885
13096
  required: [type, ts]
12886
13097
  properties:
12887
13098
  type: { const: hook_notice }
@@ -12930,7 +13141,7 @@ components:
12930
13141
  # #315(server ≥7.43):第二顶层键 `livePending` = streamApproval 窗内的流内 ask(此前对轮询消费端
12931
13142
  # 结构性不可见)。ADDITIVE / tolerate-absent:键缺席=「协调器未装配(本部署无 live 审批面)」或
12932
13143
  # 旧 server;空数组=「有面、此刻无待批」——两义可判别,消费端测 presence。**分数组不混编**:
12933
- # live 行的决议口=POST /v1/tool-approvals/{approvalId}/respond,durable 行=decide 口;数组名即路由判据。
13144
+ # live 行的决议口=POST /v1/tool-approvals/{id}/respond,durable 行=decide 口;数组名即路由判据。
12934
13145
  # 22.0.0:第三、四顶层键此前漏声明 —— 封闭 schema 上一个真键 = 合法信封被严格校验器拒收。
12935
13146
  additionalProperties: false
12936
13147
  required: [pending]
@@ -12985,7 +13196,7 @@ components:
12985
13196
  type: object
12986
13197
  description: >-
12987
13198
  A live (in-stream) pending ask inside its streamApproval window (#315, server ≥7.43). Decide it via
12988
- POST /v1/tool-approvals/{approvalId}/respond — NOT the /decide route (that is the durable rows' door).
13199
+ POST /v1/tool-approvals/{id}/respond — NOT the /decide route (that is the durable rows' door).
12989
13200
  Optional flags are only-if-true (absent is NEVER encoded as false).
12990
13201
  🔴 THE "tool input/args are NOT listed" NARROWING IS GONE (server >= 7.87.0 / S-454 P1). The old reason
12991
13202
  was "details ride the stream frame" — but a SUSPENDED ask never has a stream frame (at mint time it had
@@ -13003,7 +13214,15 @@ components:
13003
13214
  additionalProperties: true
13004
13215
  required: [approvalId, toolName, ts, expiresAtMs]
13005
13216
  properties:
13006
- approvalId: { type: string, description: 'The SAME id the respond endpoint takes (wire uuidv7).' }
13217
+ approvalId: { type: string, description: 'The SAME id the respond endpoint takes. server >= 7.133.0: for an ask that has a ledger row it is ALWAYS the row''s key `askId` (a 64-character lowercase hexadecimal string — an OPAQUE string, never a uuid); on <= 7.132.x a wire uuidv7.' }
13218
+ consumerState:
13219
+ type: string
13220
+ enum: [absent]
13221
+ description: >-
13222
+ server >= 7.133.0, TRUE-ONLY (present as `absent`): no engine leg on THIS replica is waiting for this ask. Present <=> the row was ADOPTED from the ask ledger at start-up (minted by a previous
13223
+ process / another replica, the row still undecided — a "zombie card"): it lists and can be answered (the row becomes decided; the answer ack carries the same bit), but that decision resumes
13224
+ nothing running on this replica — read the run surface to learn whether anything continued. The `live_pending` stream frame and the `pending` arm of GET /v1/tool-approvals/{id} carry it too. After a restart an
13225
+ ask still undecided on the ledger RETURNS to `livePending[]` (it used to vanish and answer 404). Absent = this replica has an engine leg waiting, or an older server.
13007
13226
  toolName: { type: string }
13008
13227
  ts: { type: number, description: 'Registration time (ms epoch).' }
13009
13228
  expiresAtMs: { type: number, description: 'Absolute window deadline — the SAME number minted once for the tool_approval frame (never recomputed).' }
@@ -13028,9 +13247,13 @@ components:
13028
13247
  allOf: [{ $ref: '#/components/schemas/ToolApprovalCardWord' }]
13029
13248
  description: 'server >= 7.126.0 (core 7.49.0 #1414): which card this ask renders on — the engine-minted card word, the SAME value as `frame.approvalCard` (the row reads the frame''s own seat, no second read), same layer and shape as the operator queue row''s `approvalCard`. It REPLACES `contentKind` (server <= 7.125.x derived it from the tool name; deleted at 7.126.0, no alias). ABSENT = the engine minted none. Display / triage only; it NEVER takes part in decision routing.'
13030
13249
  frame:
13031
- allOf: [{ $ref: '#/components/schemas/ToolApprovalFrame' }]
13250
+ oneOf:
13251
+ - $ref: '#/components/schemas/ToolApprovalFrame'
13252
+ - $ref: '#/components/schemas/ApprovalRequestFrame'
13032
13253
  description: >-
13033
- server >= 7.87.0 (S-454 P1; cli L-397) — THE CARD FRAME THIS ASK WOULD HAVE EMITTED. It is the SAME
13254
+ server >= 7.87.0 (S-454 P1; cli L-397) — THE CARD FRAME THIS ASK WOULD HAVE EMITTED. TWO SHAPES, by leg (sdk 25.0.0 declares the union; discriminant
13255
+ `frame.type`): on a leg that speaks the card protocol (`capabilities.approvalFrameFamilies` contains `approval_request`, server >= 7.133.0) it is the
13256
+ `approval_request` frame (card facts under `frame.card`, the ten off-card live facts flat at the top); on a legacy-family leg it is the `tool_approval` frame. It is the SAME
13034
13257
  OBJECT as the server's `PendingApproval.frame` (the one construction site in `askBroadcast`, already
13035
13258
  redacted / byte-capped / window-keys filled), shared by the suspended and the reachable path — so
13036
13259
  this is NOT a second card projection computed for the list, and there is no second redaction surface.
@@ -13250,6 +13473,7 @@ components:
13250
13473
  required: [sessionId, scope, createdAt]
13251
13474
  additionalProperties: true
13252
13475
  properties:
13476
+ checkpointId: { type: string, minLength: 1, maxLength: 128, description: 'server >= 7.133.0: the NON-SECRET stable identity of the pause this row parks on — the join key where the token cannot go; the SAME value as `PausedCause.checkpointId`. Carried on three durable read surfaces (GET /v1/approvals `pending[]` rows, the /v1/approvals/stream `pending` frame, GET /v1/assistant/inbox rows). A row whose value is unreadable or untrusted (non-string / empty / over 128 / contains the token / an old row / an older server) is STILL LISTED with the key ABSENT — read absence as "this row has no joinable identity", never as a required key.' }
13253
13477
  sessionId: { type: string, description: 'The decide handle: POST /v1/approvals/:sessionId/decide.' }
13254
13478
  scope: { type: string, description: 'Principal; "_" = submitted without one.' }
13255
13479
  taskId: { type: ['string', 'null'], description: 'Suspended run holding the session claim (service-pinned) — join key to trace tool-call blocks + context link.' }
@@ -13265,7 +13489,11 @@ components:
13265
13489
  🔴 OPEN SET — deliberately NOT an enum: observed values include `human`, `irreversible_ask`,
13266
13490
  `plan_review`, `needs_review`, `resource_limit`, and core keeps adding. Pinning an enum would make a
13267
13491
  new gate kind "not exist" for generated clients.
13268
- ABSENT on a pre-`gate_kind` legacy row (SQL column NULL) — degrade honestly, never assume `human`.
13492
+ 🔴 ALWAYS PRESENT on server >= 7.132.0 (a wire narrowing): every park stamps it, the one-time migration M21 filled the stored rows that lacked it
13493
+ (a row whose gate names no registered kind is expired), and a SQL row that still carries none is NOT LISTED (counted under
13494
+ `server.approvals.queue-row-gate-kind-missing`; no decide route can bind it) rather than served without the key. On a <= 7.131.x server it is
13495
+ absent on a pre-`gate_kind` legacy SQL row (column NULL). Either way the reading of an absent key is "unreadable — fail closed": never assume
13496
+ `human`, never route it. Deliberately still not in `required` while the supported floor (3.0.0) is below 7.132.0.
13269
13497
  input:
13270
13498
  description: >
13271
13499
  The pending tool call's args (post-hook), REDACTED + size-bounded — for a tool gate the write
@@ -13457,6 +13685,7 @@ components:
13457
13685
  additionalProperties: false
13458
13686
  properties:
13459
13687
  sessionId: { type: string, description: 'The suspended task/session id.' }
13688
+ checkpointId: { type: string, minLength: 1, maxLength: 128, description: 'server >= 7.133.0: the NON-SECRET stable identity of the pause this row parks on — the join key where the token cannot go; the SAME value as `PausedCause.checkpointId`. Carried on three durable read surfaces (GET /v1/approvals `pending[]` rows, the /v1/approvals/stream `pending` frame, GET /v1/assistant/inbox rows). A row whose value is unreadable or untrusted (non-string / empty / over 128 / contains the token / an old row / an older server) is STILL LISTED with the key ABSENT — read absence as "this row has no joinable identity", never as a required key.' }
13460
13689
  scope: { type: string, description: 'Multi-tenant owner scope.' }
13461
13690
  createdAt: { type: number, description: 'Ask creation time (epoch ms).' }
13462
13691
  gateKind:
@@ -13780,6 +14009,17 @@ components:
13780
14009
  ⚠️ The key name is final: `permissionModeAfter` (the build brief once said `nextPermissionMode`,
13781
14010
  which never shipped — zero aliases).
13782
14011
 
14012
+ SessionAgent:
14013
+ type: object
14014
+ additionalProperties: false
14015
+ description: >-
14016
+ The placement-tuple projection of a delegated subagent's transcript (`SessionRecord.agent`, server >= 7.133.0). Each member is independently optional (only what was stored is projected); `{}` is a legal value
14017
+ (the tuple held only a `kind`). `id` = the tuple's `handle` (the durable agent row's `a…` key — the SAME string as the fleet row's `id` and `GET /v1/agents/roster`'s `handle`); `parentSessionId` = the host
14018
+ session that spawned it; `rootSessionId` = the delegation tree's root host session (a nested grandchild anchors to the root).
14019
+ properties:
14020
+ id: { type: string }
14021
+ parentSessionId: { type: string }
14022
+ rootSessionId: { type: string }
13783
14023
  SessionRecord:
13784
14024
  type: object
13785
14025
  description: >
@@ -13812,6 +14052,14 @@ components:
13812
14052
  $ref: '#/components/schemas/SessionWindow'
13813
14053
  promptEpoch:
13814
14054
  $ref: '#/components/schemas/PromptEpoch'
14055
+ agent:
14056
+ allOf: [{ $ref: '#/components/schemas/SessionAgent' }]
14057
+ description: >-
14058
+ server >= 7.133.0, top-level additive key — WHICH delegated subagent's transcript this session is (read by terminal side-chain projections that join a fleet frame's `transcriptId`). Only the stored placement
14059
+ tuple is projected (minted by the engine when a BACKGROUND subagent with a durable agent row creates the session on a store that declares placements; a synchronously delegated subagent's transcript is not in
14060
+ the host store and has no tuple); the three members are each optional. 🔴 READ IT AS "WAS IT STORED", NEVER AS "IS IT A SUBAGENT": key PRESENT = a delegated subagent's transcript (even `{}` when the tuple held only a
14061
+ kind); key ABSENT has three causes — an older server (<= 7.132.x) / an ordinary session / a deployment whose store keeps no placement tuple (the local backend: the copy parked from the private short-TTL store is an
14062
+ ordinary session row). Absence is NOT "not a subagent": fall back to the identity the fleet row (`id`) / `GET /v1/agents/roster` (`handle`) already carry.
13815
14063
  userInputUndrained:
13816
14064
  type: array
13817
14065
  description: >-
@@ -13975,23 +14223,67 @@ components:
13975
14223
  message: { description: 'OPEN — the message payload as stored; shape belongs to the transcript format, not to this envelope. server >= 7.125.0: a `role: "user"` message carries the same present-iff rewind handle `taskId` as on the whole-record read (see `SessionRecord.messages`).' }
13976
14224
 
13977
14225
  PromptEpoch:
13978
- type: object
13979
14226
  description: >
13980
- A session's prompt-epoch pin (server >=1.220, additive on `GET /v1/sessions/:id`). Identifies WHICH
13981
- assembled system-prompt artifact this session is pinned to, so a transcript stays reproducible across
13982
- prompt-pack releases. IDs/digests only — never prompt text. OPEN object.
13983
- required: [epoch, artifactDigest, packId, assemblyApi, activatedBy]
13984
- additionalProperties: true
14227
+ A session's prompt-epoch record on `GET /v1/sessions/:id` (absent = a pre-epoch session — never null). 🔴 server >= 7.132.0 BREAKING (no alias): the value
14228
+ is the engine's `StoredPromptEpoch` reading VERBATIM (the server neither reads nor projects it) — a v3 pin `{ kind: "pin", descriptor }`, or a record of a
14229
+ retired shape recognized by its field names `{ kind: "legacy", centerPinned }` (the engine reads only whether it pinned a center artifact; the next run
14230
+ replaces it with a v3 record). The <= 7.131.x shape `{ epoch, artifactDigest, packId, assemblyApi, activatedBy }` is retired: `epoch` / `activatedBy` live
14231
+ in `descriptor`, `descriptor.identity` takes the identity role (a different digest over different inputs — not comparable), `packId` / `assemblyApi` have
14232
+ no successor. IDs / digests only — never prompt text.
14233
+ oneOf:
14234
+ - $ref: '#/components/schemas/PromptEpochPin'
14235
+ - $ref: '#/components/schemas/PromptEpochLegacy'
14236
+ discriminator:
14237
+ propertyName: kind
14238
+ mapping:
14239
+ pin: '#/components/schemas/PromptEpochPin'
14240
+ legacy: '#/components/schemas/PromptEpochLegacy'
14241
+ PromptEpochPin:
14242
+ type: object
14243
+ additionalProperties: false
14244
+ required: [kind, descriptor]
13985
14245
  properties:
13986
- epoch: { type: integer, description: Monotonic epoch counter within the session. }
13987
- artifactDigest: { type: string, description: 'Content digest of the assembled artifact (`sha256:<64 hex>`).' }
13988
- packId: { type: string, description: 'Prompt-pack identity, e.g. `sema-default@1`.' }
13989
- assemblyApi: { type: integer, description: Assembly-contract version (currently always 1). }
14246
+ kind: { const: pin }
14247
+ descriptor: { $ref: '#/components/schemas/PromptEpochDescriptor' }
14248
+ PromptEpochLegacy:
14249
+ type: object
14250
+ additionalProperties: false
14251
+ required: [kind, centerPinned]
14252
+ properties:
14253
+ kind: { const: legacy }
14254
+ centerPinned: { type: boolean, description: 'whether the retired-shape record pinned a center artifact.' }
14255
+ PromptEpochDescriptor:
14256
+ type: object
14257
+ description: 'A v3 epoch record (core `PromptEpochDescriptor`), persisted as a session entry and restated in every compaction entry.'
14258
+ additionalProperties: false
14259
+ required: [epoch, identity, layers, activatedBy]
14260
+ properties:
14261
+ epoch: { type: integer, description: 'monotonic per-session record counter.' }
14262
+ identity: { type: string, description: 'the run''s prompt identity digest (= the manifest''s `identity.artifact`).' }
14263
+ layers: { $ref: '#/components/schemas/PromptIdentityLayers' }
13990
14264
  activatedBy:
13991
14265
  type: string
13992
- description: >
13993
- Open enum — known values `session_start` | `compaction` | `legacy_migration`; treat unknown values as
13994
- opaque rather than failing closed.
14266
+ enum: [session_start, compaction, code_change, selection_change]
14267
+ description: 'how the record came to be — core `PROMPT_EPOCH_ACTIVATIONS`: `session_start` / `compaction` (legal boundaries), `code_change` (code or input layers moved), `selection_change` (only the selector values moved). CLOSED: the engine''s judge (`normalizePromptEpoch`) checks membership, a record with any other word does not read as a pin.'
14268
+ changed:
14269
+ type: array
14270
+ items: { type: string, enum: [catalog, renderers, deploy, task, published_schema] }
14271
+ description: 'only on a `code_change` record whose changed layers are known — core `PROMPT_EPOCH_CHANGES` (closed, judged by the engine like `activatedBy`).'
14272
+ sourceRevision: { type: string, description: 'opaque publish provenance of the pinned center layer — present only beside `layers.center`; never digested.' }
14273
+ PromptIdentityLayers:
14274
+ type: object
14275
+ description: >
14276
+ The inputs of a run's prompt identity (core `PromptIdentityLayers`; the epoch record's `layers` and the manifest's `identity.layers` are this one shape).
14277
+ Only `center` is pinned (resolved by digest on resume); the rest are what the run computed. Absent `center` / `deploy` / `task` = that layer contributed nothing.
14278
+ additionalProperties: false
14279
+ required: [catalog, renderers, binding]
14280
+ properties:
14281
+ catalog: { type: string }
14282
+ renderers: { type: object, additionalProperties: { type: number }, description: 'renderer id -> version.' }
14283
+ center: { type: string, description: 'the adopted center artifact''s digest.' }
14284
+ deploy: { type: string }
14285
+ task: { type: string }
14286
+ binding: { type: string, description: 'digest of the run''s selector values (the plain form is the manifest''s `selectors`).' }
13995
14287
 
13996
14288
  SessionListPage:
13997
14289
  type: object
@@ -14097,14 +14389,45 @@ components:
14097
14389
  # 封闭(census 批2 第三段,2026-07-30):三个铸造臂(`routes/sessions.ts` 的空面板 / 超时 degraded / 正常)键集并集恰是这 3 键;degraded 只在超时臂铸。
14098
14390
  # sdk 9.4.0:第 4 键 `lastLegMcp`(server ≥7.77.0)—— 同一个铸造点上按调用方裁的可选顶层键,不在
14099
14391
  # 面板那张短 TTL 单飞缓存里(缓存键=场景×principal,与会话无关)。
14100
- required: [asOf, servers]
14392
+ # server >= 7.131.0:第 5 键 `scenarioRefusal`(场景门拒时在场,此时 `servers` 缺席)⇒ `servers` 出 required。
14393
+ # 两臂恰一(server 7.132.0 三只铸造臂):served = `servers`(+ `degraded` 只在超时臂)/ refused = `scenarioRefusal`(无 `servers` / `degraded`)⇒ `oneOf` 判(sdk `McpStatusPanelServed` / `McpStatusPanelRefused`)。
14394
+ # 每一臂自己禁掉对方的键(不靠「另一臂恰好不过」:混合体 `{ servers, scenarioRefusal, degraded }` 曾因 refused 臂被 `degraded` 判掉而恰一臂过)。
14395
+ required: [asOf]
14101
14396
  additionalProperties: false
14397
+ oneOf:
14398
+ - required: [servers]
14399
+ not: { required: [scenarioRefusal] }
14400
+ - required: [scenarioRefusal]
14401
+ not: { anyOf: [{ required: [servers] }, { required: [degraded] }] }
14102
14402
  properties:
14103
14403
  asOf: { type: string }
14104
14404
  servers:
14105
14405
  type: array
14106
14406
  items: { $ref: '#/components/schemas/McpServerStatus' }
14407
+ description: >-
14408
+ The scenario half: what this principal's scenario would mount NOW. server >= 7.131.0: ABSENT only when the scenario gate refused (then
14409
+ `scenarioRefusal` is present) — distinct from `[]` (the gate passed and that scenario configures no MCP). Always present on older servers.
14107
14410
  degraded: { type: boolean }
14411
+ scenarioRefusal:
14412
+ type: object
14413
+ description: >-
14414
+ server >= 7.131.0 — present ONLY when the scenario gate refused (and then `servers` is absent); the panel still answers 200 because the ledger half
14415
+ (`lastLegMcp`) does not depend on the scenario. Exactly one of `servers` / `scenarioRefusal` is present (the `oneOf` above; `degraded` only beside `servers`). Members = the body of the same refusal answered
14416
+ whole-face (503 on `GET /v1/capabilities/mcp` / `GET /v1/sessions/{id}/a2a`), byte-equal (one sentence builder): today `errorCode`
14417
+ `state.scenario_assignment_unknown`, `error` naming the scenario and this deployment's roster, `detail: { scenario, source }` with `source` one of
14418
+ `ruling` (the center assigned this principal a scenario this deployment does not have) / `deployment` (a dangling `DEFAULT_SCENARIO`; sema-server refuses
14419
+ to boot on it, only an embedder's own `resolveSpec` reaches it). A gate that throws something that is not a typed refusal is still a 500.
14420
+ required: [errorCode, error]
14421
+ additionalProperties: true
14422
+ properties:
14423
+ errorCode: { type: string }
14424
+ error: { type: string }
14425
+ detail:
14426
+ type: object
14427
+ required: [scenario, source]
14428
+ properties:
14429
+ scenario: { type: string }
14430
+ source: { type: string, description: '`ruling` / `deployment` today; read open.' }
14108
14431
  lastLegMcp:
14109
14432
  type: object
14110
14433
  description: >
@@ -14636,6 +14959,10 @@ components:
14636
14959
  row (`status` / `instanceId`) — zero extra reads — and a cross-principal caller never reaches these codes
14637
14960
  (the owner gate answers `not_found.run`, same code and text as the unknown arm). Both dispositions are the
14638
14961
  same: stop retrying the handle, read the run's own outcome via GET /v1/runs/{taskId}.
14962
+ SettingsHooksConfigPath:
14963
+ type: array
14964
+ items: { type: [string, integer] }
14965
+ description: 'the coordinate of a `settings.hooks` verdict — segments relative to the request body''s `settings` (caller-text segments echo-bounded). SDK `SettingsHooksConfigPath`.'
14639
14966
  ErrorResponse:
14640
14967
  type: object
14641
14968
  description: >
@@ -14766,7 +15093,9 @@ components:
14766
15093
  unreadable) / `unreachable` (unreachable / 5xx / timeout / bad body / any status other than 200 / 409 / 400 / 401 / 403 —
14767
15094
  center 404 / 429 / 3xx included). On 400 `request.field_invalid` for a `settings.permissions` bucket (server >= 7.106.0 /
14768
15095
  S-904): `over_cap` (more than `cap` entries; beside `count` + `cap`) / `bad_entry` (a non-string or empty item; beside
14769
- `index`). On 400 `rules.inert_entry` (server >= 7.129.0): the engine's `ALLOW_ENTRY_INERT_REASONS` (`tool_name` / `wildcard_all` / `namespace_wildcard` / `content_unsupported` / `grammar` / `near_miss` / `earlier_grammar`). Out-of-vocabulary word ⇒ treat as unknown. (Other codes carry their own `reason` words.)
15096
+ `index`); server >= 7.132.0 widens that group to every tool-name list on the body, the agent definitions and the two host-path lists, and adds
15097
+ `too_long` (a text over its character cap; beside `count` + `cap`) — closed words `over_cap` / `too_long` / `bad_entry` (SDK `RequestFieldRefusalExtra`); server >= 7.133.0 adds `too_large` (a payload over its BYTE cap, only on 413 `request.payload_too_large`: `{ field: "body", reason: "too_large", cap }` — `count` ABSENT when the read stopped at the cap and cannot measure; a request body over 8 MiB is now always that 413) and the `request.invalid_json` reasons `not_json` | `not_object` (400, every JSON-body door; formerly `request.rejected`/`request.body_shape` by door).
15098
+ On 409 refusals of a resume (server >= 7.132.0): the engine's `CheckpointErrorReason` arm (`detail.reason` passed through; see `blockedBy`). On 400 `rules.inert_entry` (server >= 7.129.0): the engine's `ALLOW_ENTRY_INERT_REASONS` (`tool_name` / `wildcard_all` / `namespace_wildcard` / `content_unsupported` / `grammar` / `near_miss` / `earlier_grammar`). Out-of-vocabulary word ⇒ treat as unknown. (Other codes carry their own `reason` words.)
14770
15099
  retryAfterSec:
14771
15100
  type: number
14772
15101
  description: >-
@@ -14782,7 +15111,10 @@ components:
14782
15111
  carry their own `field`: the bucket on `rules.inert_entry`, the offending key on `rules.invalid_shape`.) Resume legs carry the same group on 409 `resume_blocked_by_policy`
14783
15112
  (`blockedBy: "request.field_invalid"`). Also on 409 `checkpoint.config_refused` (server >= 7.113.0): which part of the
14784
15113
  row's configuration was refused — `taskConfig` (change the configuration, then decide the SAME card again) or
14785
- `inheritedGate` (the row's recorded mode seed is bad — the run can only be cancelled). Also on 400 `request.field_invalid` for a
15114
+ `inheritedGate` (the row's recorded mode seed is bad — the run can only be cancelled) and — core 7.53.0 / server >= 7.132.0 — `deps` (the deployment's OWN
15115
+ seat is broken: fix the deployment, then decide the SAME card again). server >= 7.132.0: on 400 `request.field_invalid` cap / bad-entry refusals `field` is
15116
+ the IN-BODY COORDINATE of the capped / bad place (`skills[1].allowedTools`, `agents[0].denyTools[3]`, `agents`, `allowTools`, `additionalDirectories` …) —
15117
+ the same group as `reason` / `count` / `cap` / `index`. Also on 400 `request.field_invalid` for a
14786
15118
  `settings.hooks` tool-event matcher that selects an engine alias but not its canonical name (server >= 7.115.0 / S-1025):
14787
15119
  `field` is always the literal `settings.hooks` (see `detail`). Also on 400 `request.body_shape` (server >= 7.119.0), on the THREE
14788
15120
  tool-approval decision doors (`POST /v1/tool-approvals/{id}/respond`, `POST /v1/tasks/{taskId}/asks/{askId}/decision`,
@@ -14800,33 +15132,163 @@ components:
14800
15132
  `request.field_invalid` for a `settings.hooks` tool-event matcher (server >= 7.115.0 / S-1025; additive, the sentence and the code
14801
15133
  are unchanged): an OBJECT `{ event, alias, canonical }` — `event` = the tool event of the refused group (the first refused one when
14802
15134
  several are), `alias` / `canonical` = the engine catalog's alias and canonical name (closed strings, never the caller's raw bytes),
14803
- beside `field: "settings.hooks"`. On 400 `request.field_invalid` for the two `POST /v1/adoption` principal sentences (adoption):
15135
+ beside `field: "settings.hooks"`; 🔴 server >= 7.126.0 adds `reason` (`dead_alias` / `over_budget` / `invalid_regex`) and server >= 7.132.0 makes EVERY
15136
+ `settings.hooks` refusal carry this object with `path` ALWAYS present (the coordinate of the verdict, a segment array relative to the body's `settings`):
15137
+ the matcher forms (+ `budget_exceeded` with `totalMs`), the config reader's piece codes (+ `key`), `unsupported_entry_type` (+ `type`), the service caps
15138
+ `over_cap` / `too_long` (+ `count` / `cap`) and `unnamed_refusal` — see the hooks branch below and SDK `SettingsHooksRefusalDetail`. On 400
15139
+ `config.user_attachment_invalid` (server >= 7.132.0 / core 7.53.0): an OBJECT `{ path, index? }` — the field the engine names at the head of the sentence,
15140
+ `index` (0-based) only when the sentence names one attachment (SDK `UserAttachmentRefusalExtra`). On 400 `request.field_invalid` for the two `POST /v1/adoption` principal sentences (adoption):
14804
15141
  a human-readable STRING. Other codes (core `checkpoint.*` mirrors; `rules.*` refusals carry no `detail` — their keys are `field` / `entry` / `reason` / `verdict` / `currentName` / `guidance` / `got` at the top level) carry their
14805
15142
  own `detail`; read it by `errorCode`. SDK: `err.extra.detail` (verbatim, unnamed).
14806
15143
  oneOf:
14807
15144
  - type: object
14808
- required: [event, alias, canonical]
15145
+ description: 'the `settings.hooks` refusal detail (beside `field: "settings.hooks"`), one shape per `reason` — branch on `reason`, exhaustively (SDK `SettingsHooksRefusalDetail`). server >= 7.132.0: every hooks refusal carries it and `path` is always present; in-floor older servers: the three matcher shapes without `path` (7.126.0–7.131.x) and the reason-less dead-alias shape (7.115.0–7.125.x).'
15146
+ oneOf:
15147
+ - type: object
15148
+ description: 'matcher: selects an engine alias but not its canonical name (`path` absent on server 7.126.0–7.131.x).'
15149
+ required: [reason, event, alias, canonical]
15150
+ additionalProperties: false
15151
+ properties:
15152
+ reason: { type: string, enum: [dead_alias] }
15153
+ event: { type: string }
15154
+ alias: { type: string }
15155
+ canonical: { type: string }
15156
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15157
+ - type: object
15158
+ description: 'matcher: one regex evaluation over the per-evaluation budget (`path` absent on server 7.126.0–7.131.x).'
15159
+ required: [reason, event, groupIndex, budgetMs]
15160
+ additionalProperties: false
15161
+ properties:
15162
+ reason: { type: string, enum: [over_budget] }
15163
+ event: { type: string }
15164
+ groupIndex: { type: integer }
15165
+ budgetMs: { type: integer }
15166
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15167
+ - type: object
15168
+ description: 'matcher (server >= 7.132.0): the completed regex evaluations of one admission summed over `totalMs`.'
15169
+ required: [reason, event, groupIndex, totalMs, path]
15170
+ additionalProperties: false
15171
+ properties:
15172
+ reason: { type: string, enum: [budget_exceeded] }
15173
+ event: { type: string }
15174
+ groupIndex: { type: integer }
15175
+ totalMs: { type: integer }
15176
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15177
+ - type: object
15178
+ description: 'matcher: the regex does not compile (`path` absent on server 7.126.0–7.131.x).'
15179
+ required: [reason, event, groupIndex]
15180
+ additionalProperties: false
15181
+ properties:
15182
+ reason: { type: string, enum: [invalid_regex] }
15183
+ event: { type: string }
15184
+ groupIndex: { type: integer }
15185
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15186
+ - type: object
15187
+ description: 'the config reader''s classifier code verbatim (server >= 7.132.0; SDK `SETTINGS_HOOKS_PIECE_CODES`), `key` = the key that broke that piece when named.'
15188
+ required: [reason, path]
15189
+ additionalProperties: false
15190
+ properties:
15191
+ reason: { type: string, enum: [guard-outside-hooks, hooks-is-matcher, guard-under-unknown-event, guard-event-not-array, guard-matcher-invalid, guard-entry-invalid, hooks-not-object, unknown-event, event-not-array, matcher-invalid, entry-invalid] }
15192
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15193
+ key: { type: string }
15194
+ - type: object
15195
+ description: 'an entry type this service does not execute (server >= 7.132.0).'
15196
+ required: [reason, path, type]
15197
+ additionalProperties: false
15198
+ properties:
15199
+ reason: { type: string, enum: [unsupported_entry_type] }
15200
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15201
+ type: { type: string }
15202
+ - type: object
15203
+ description: 'a service cap (server >= 7.132.0).'
15204
+ required: [reason, path, count, cap]
15205
+ additionalProperties: false
15206
+ properties:
15207
+ reason: { type: string, enum: [over_cap, too_long] }
15208
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15209
+ count: { type: integer }
15210
+ cap: { type: integer }
15211
+ - type: object
15212
+ description: 'the classifier refused without naming a piece — contract drift, fail-closed (server >= 7.132.0).'
15213
+ required: [reason, path]
15214
+ additionalProperties: false
15215
+ properties:
15216
+ reason: { type: string, enum: [unnamed_refusal] }
15217
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15218
+ - type: object
15219
+ description: 'one of the three wire keys this service reads itself is malformed (server >= 7.133.0): the group `source` / a command entry''s `plugin` / `args`; `key` names it, `path` is its coordinate. The whole request is refused (they used to be stripped silently as unknown keys).'
15220
+ required: [reason, key, path]
15221
+ additionalProperties: false
15222
+ properties:
15223
+ reason: { type: string, enum: [wire_key_invalid] }
15224
+ key: { type: string }
15225
+ path: { $ref: '#/components/schemas/SettingsHooksConfigPath' }
15226
+ - type: object
15227
+ description: 'server 7.115.0–7.125.x: the dead-alias matcher refusal before `reason` existed.'
15228
+ required: [event, alias, canonical]
15229
+ additionalProperties: false
15230
+ properties:
15231
+ event: { type: string }
15232
+ alias: { type: string }
15233
+ canonical: { type: string }
15234
+ - type: object
15235
+ description: '400 `config.user_attachment_invalid` (server >= 7.132.0): the field the engine names; `index` only when one attachment is named.'
15236
+ required: [path]
14809
15237
  additionalProperties: false
14810
15238
  properties:
14811
- event: { type: string }
14812
- alias: { type: string }
14813
- canonical: { type: string }
15239
+ path: { type: string }
15240
+ index: { type: integer }
14814
15241
  - type: object
14815
15242
  required: [scenario, source]
14816
15243
  additionalProperties: false
14817
15244
  properties:
14818
15245
  scenario: { type: string }
14819
15246
  source: { type: string, enum: [ruling, deployment] }
15247
+ - type: object
15248
+ description: >-
15249
+ server >= 7.133.0 — entry problems on a tool-name list / settings-rule refusal (400 `request.field_invalid`, 422): `problems` (at most 1000, each EntryProblem) + `total` (the true count; `total > problems.length` => cut).
15250
+ With `callerTrust` it is the below-trust-floor refusal: the caller's admission tier (`global` | `project`, read open; the same words as `capabilities.settings.permissions.callerTrust`).
15251
+ required: [problems, total]
15252
+ additionalProperties: false
15253
+ properties:
15254
+ problems: { type: array, items: { $ref: '#/components/schemas/EntryProblem' } }
15255
+ total: { type: integer }
15256
+ callerTrust: { type: string }
15257
+ - type: object
15258
+ description: >-
15259
+ server >= 7.133.0 — 400 `request.field_conflict`: the request body contradicts itself. `kind` = which constraint (a CLOSED fourteen-word set, read open: cascade_verify | image_profile | resume_anchor |
15260
+ system_prompt_fold | file_history_restore | skills_disabled | existing_session | scratchpad_root | ask_answer | policy_person_ask | plan_edit | plan_mode_after | bound_call_door | remember_decision);
15261
+ `fields` = the named body coordinates, subject first (at least one; a dynamic index reads `agents[1].skills`); `count` / `cap` only on the fold-cap kind (the folded total and its cap).
15262
+ required: [kind, fields]
15263
+ additionalProperties: false
15264
+ properties:
15265
+ kind: { type: string }
15266
+ fields: { type: array, items: { type: string } }
15267
+ count: { type: integer }
15268
+ cap: { type: integer }
15269
+ - type: object
15270
+ description: '409 `conflict.session_placed` (server >= 7.133.0): the session sync commit targeted a session that holds a delegated agent''s transcript. `placement_kind` is the stored placement kind (today only `subagent`; read open).'
15271
+ required: [placement_kind]
15272
+ additionalProperties: false
15273
+ properties:
15274
+ placement_kind: { type: string }
14820
15275
  - type: string
14821
15276
  count:
14822
15277
  type: integer
14823
- description: 'S-904 `reason: over_cap` only — how many entries the bucket had (server >= 7.106.0). SDK: `err.extra.count`.'
15278
+ description: '`reason: over_cap` / `too_long` / `too_large` — how much was sent (entries / characters / bytes; every capped face since >= 7.132.0). ABSENT on the unmeasured form (a body read that stopped at the cap). SDK: `err.extra.count`.'
14824
15279
  cap:
14825
15280
  type: integer
14826
- description: 'S-904 `reason: over_cap` only — the bucket cap (1000). SDK: `err.extra.cap`.'
15281
+ description: '`reason: over_cap` / `too_long` / `too_large` — the cap (1000 for a permissions bucket; per face otherwise; bytes on a too_large 413). SDK: `err.extra.cap`.'
14827
15282
  index:
14828
15283
  type: integer
14829
- description: 'S-904 `reason: bad_entry` only — the index of the first bad item; ABSENT when the bucket itself is not an array. SDK: `err.extra.index`.'
15284
+ description: '`reason: bad_entry` only — the index of the first bad item; ABSENT when the list itself is not an array. SDK: `err.extra.index`.'
15285
+ wallSource:
15286
+ type: string
15287
+ description: >-
15288
+ server >= 7.132.0, on a 413 refused against the database's limits — WHERE THE WALL'S NUMBER CAME FROM. On `request.payload_too_large` (a submit body over
15289
+ the database's single-row / packet wall): `reported` (the database's own number) / `assumed` (not reported — engine facts not established yet, or that
15290
+ wall's read failed and fell back to the documented default; refusing conservatively). On `blob_too_large_for_sql` (`PUT /v1/sessions/{id}/sync/blobs/{hash}`):
15291
+ `operator` (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`) / `reported` / `assumed`. Two closed sets, one per face (SDK `SqlWallSource` / `BlobSqlCapSource`).
14830
15292
  blockedBy:
14831
15293
  type: string
14832
15294
  description: >-
@@ -14837,11 +15299,19 @@ components:
14837
15299
  `ResumeBlockedByPolicyError.blockedBy` (`undefined` when the key is absent or not a string — never defaulted).
14838
15300
  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)
14839
15301
  ⇒ `blockedBy: "request.field_invalid"`, refused before the claim; the terminal-run steer leg answers it before minting the wake card.
15302
+ server >= 7.132.0 / core 7.52.0: also on an ENGINE-refused resume (the five resume legs, the PARKED redemption seat and the SLA deny sweep share one body):
15303
+ the engine's `detail.code` passes through as `blockedBy` (today only `checkpoint.config_refused` carries it — the closed set is core's
15304
+ `CHECKPOINT_CONFIG_REFUSAL_CODES`, 40 words since core 7.53.0; read it as "the errorCode a fresh run would report for the same bad configuration"),
15305
+ next to `field` and `reason`, absent keys not minted (SDK `ResumeRefusalExtra`).
14840
15306
 
14841
15307
  ResumeLegConflictError:
14842
15308
  description: >
14843
15309
  The 409 body of a RESUME LEG that core refused BEFORE the claim (`POST /v1/approvals/{sessionId}/decide`,
14844
- `POST /v1/sessions/{sessionId}/wake`): `ErrorResponse` plus the one key those 409s may add, `retriable`. The key is declared HERE and
15310
+ `POST /v1/sessions/{sessionId}/wake`): `ErrorResponse` plus the one key those 409s may add, `retriable`. server >= 7.132.0: ONE writer and one
15311
+ rule — on the three kinds of resume refusal (the server's own stored-body replay refusal, the admission-policy fold `resume_blocked_by_policy`, an
15312
+ engine-refused resume) `retriable: true` is present <=> a pending card is still waiting and the run row stays `suspended`; so `resume_blocked_by_policy`
15313
+ now carries it too, and the terminal-run `POST /v1/runs/{id}/steer` refusal before a wake card is minted carries none (the `checkpoint.unsupported_version`
15314
+ allow-reading / mode-reading arms used to claim `true` there). The key is declared HERE and
14845
15315
  not on the shared `ErrorResponse` on purpose (codex r1 [medium], sdk 16.0.0): `ErrorResponse` is the body of every error
14846
15316
  response in this document, and declaring `retriable` there would offer a retry signal on 401 / 404 / 422 / 5xx bodies where the
14847
15317
  server never mints it. (`ErrorResponse` stays open, so a validator cannot REJECT the key elsewhere; the confinement is that only
@@ -14920,6 +15390,8 @@ components:
14920
15390
  - $ref: '#/components/schemas/Event_message_committed'
14921
15391
  # [2373]A-2(2026-08-03,server 5.0.0):compaction_outcome 三腿同源批的 spec 半场(SDK 臂 20867e1 同批)。
14922
15392
  - $ref: '#/components/schemas/Event_compaction_outcome'
15393
+ # server 7.132.0(core 7.53.0 #2008):压缩开始帧,与 compaction_outcome 同一族的三腿同源白名单构造器。
15394
+ - $ref: '#/components/schemas/Event_compaction_started'
14923
15395
  # [2400] ch2(SDK 4.2.0):CB-1 流控帽帧 + TR-7 durable 五臂 —— 两侧同缺,见各 schema 处的注。
14924
15396
  - $ref: '#/components/schemas/Event_error'
14925
15397
  - $ref: '#/components/schemas/Event_question'
@@ -14942,6 +15414,11 @@ components:
14942
15414
  - $ref: '#/components/schemas/Event_tool_approval_complete'
14943
15415
  - $ref: '#/components/schemas/Event_approval_request'
14944
15416
  - $ref: '#/components/schemas/Event_approval_revoke'
15417
+ # server >= 7.133.0 (sdk 25.0.0): the card family's single-card CLOSE frame (`approval_complete`; one frame family per leg — it replaces the legacy
15418
+ # `tool_approval_complete` on a leg where `capabilities.approvalFrameFamilies` = ["approval_request"]) and the LIVE-ONLY `session_titled` frame (a session
15419
+ # title was written; never ledgered, no eventId).
15420
+ - $ref: '#/components/schemas/Event_approval_complete'
15421
+ - $ref: '#/components/schemas/Event_session_titled'
14945
15422
  # sdk 9.1.0: two LIVE-leg arms the service has been emitting while neither this union nor the
14946
15423
  # SDK's declared them — `text_end` since server 7.66.0 (segment boundary; retires the idle-flush
14947
15424
  # heuristic) and `tool_roster_delta` since server 7.70.0 (run-time roster change). A generated client
@@ -14990,6 +15467,7 @@ components:
14990
15467
  needs_review: '#/components/schemas/Event_needs_review'
14991
15468
  message_committed: '#/components/schemas/Event_message_committed'
14992
15469
  compaction_outcome: '#/components/schemas/Event_compaction_outcome'
15470
+ compaction_started: '#/components/schemas/Event_compaction_started'
14993
15471
  error: '#/components/schemas/Event_error'
14994
15472
  question: '#/components/schemas/Event_question'
14995
15473
  question_complete: '#/components/schemas/Event_question_complete'
@@ -15002,6 +15480,8 @@ components:
15002
15480
  tool_approval_complete: '#/components/schemas/Event_tool_approval_complete'
15003
15481
  approval_request: '#/components/schemas/Event_approval_request'
15004
15482
  approval_revoke: '#/components/schemas/Event_approval_revoke'
15483
+ approval_complete: '#/components/schemas/Event_approval_complete'
15484
+ session_titled: '#/components/schemas/Event_session_titled'
15005
15485
  text_end: '#/components/schemas/Event_text_end'
15006
15486
  tool_roster_delta: '#/components/schemas/Event_tool_roster_delta'
15007
15487
  reasoning_end: '#/components/schemas/Event_reasoning_end'
@@ -15031,13 +15511,18 @@ components:
15031
15511
  condition: durable-ledger leg only (no ledger => neither header nor frame). sessionId rides along
15032
15512
  (needed to bind subsequent turns when the server minted a fresh session). Always precedes any content
15033
15513
  frame. NOT an engine event — server-minted, no EventIdentity.
15034
- # 封闭:铸造点 routes/tasks.ts 是逐字写死的三键字面量(sessionId 条件在场)。
15514
+ # 封闭:铸造点 routes/tasks.ts 是逐字写死的三键字面量(sessionId 条件在场);server >= 7.133.0 起第四个条件键 `warnings`(有才发)。
15035
15515
  additionalProperties: false
15036
15516
  required: [type, taskId]
15037
15517
  properties:
15038
15518
  type: { const: meta }
15039
15519
  taskId: { type: string }
15040
15520
  sessionId: { type: string }
15521
+ warnings:
15522
+ type: array
15523
+ items: { $ref: '#/components/schemas/EntryProblem' }
15524
+ description: 'server >= 7.133.0, present only when non-empty: the accept-time NOTICES for entries of the allow-side selector lists (root / agent `allowTools`) the engine reads as nothing or that are not in the roster. Same list as the 202 receipt''s and the sync 200 body''s `warnings`. ABSENT = no notice (older servers too).'
15525
+
15041
15526
  Event_reasoning:
15042
15527
  type: object
15043
15528
  description: durable stream only — turn-aggregated thinking.
@@ -15629,6 +16114,29 @@ components:
15629
16114
  WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (archive read, `GateRecordAskOrigin`) — see the
15630
16115
  note above.
15631
16116
 
16117
+ McpProbeFaceRow:
16118
+ # server 7.133.0: the face''s row = the wiring-manifest row projection + TWO face-only optional keys. The manifest row is CLOSED (so an `allOf` over it would reject the two extra keys), hence a
16119
+ # standalone closed schema whose per-key shapes are `$ref`s into WiringManifestMcpEntry's own properties (one source per shape); the key SET is pinned by test (manifest row keys + the two).
16120
+ type: object
16121
+ description: >
16122
+ One row of the no-run MCP status face (GET /v1/capabilities/mcp and POST /v1/capabilities/mcp/probe): the SAME projection a prepared leg pushes onto `wiring_manifest.mcp[]`, PLUS two optional keys only this
16123
+ face carries (server >= 7.133.0). `toolNames` = the tool names this server will mount (the same probe reading `servers[i].tools`, engine namespace form `mcp__<server>__<tool>`, after the schema gate and the per-server
16124
+ allow-list; the server neither composes nor renames), at most 200 per server, the reading-order prefix. ABSENT = an older server or no `tools` in that server's reading — NEVER invent `[]` (it would read as "this server
16125
+ has no tools"); a present `[]` is real. `toolNamesTruncated` = TRUE-ONLY: the list was cut (more than 200; exactly 200 is NOT cut and the key stays absent, never `false`) — when true `toolCount` is the true count, not
16126
+ `toolNames.length`. A misaligned reading (not an array / count differs from the row count) answers 500 `internal.error` on both doors instead of shipping a list that could be paired wrongly.
16127
+ additionalProperties: false
16128
+ required: [name, status]
16129
+ properties:
16130
+ name: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/name' }
16131
+ status: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/status' }
16132
+ source: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/source' }
16133
+ toolCount: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/toolCount' }
16134
+ errorCode: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/errorCode' }
16135
+ delivered: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/delivered' }
16136
+ httpStatus: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/httpStatus' }
16137
+ liveness: { $ref: '#/components/schemas/WiringManifestMcpEntry/properties/liveness' }
16138
+ toolNames: { type: array, items: { type: string }, maxItems: 200 }
16139
+ toolNamesTruncated: { type: boolean, enum: [true] }
15632
16140
  McpProbeFace:
15633
16141
  type: object
15634
16142
  description: >
@@ -15654,8 +16162,8 @@ components:
15654
16162
  description: 'How long this reading may be reused (seconds; 30s per identity + declaration set server-side). A hit inside the window does NOT count against the rate limit.'
15655
16163
  servers:
15656
16164
  type: array
15657
- items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
15658
- description: 'One row per declaration, SAME ROW SHAPE as the wiring manifest, index-aligned (see this schema''s description).'
16165
+ items: { $ref: '#/components/schemas/McpProbeFaceRow' }
16166
+ description: 'One row per declaration, the wiring-manifest row shape plus (server >= 7.133.0) this face''s two optional keys, index-aligned (see this schema''s description).'
15659
16167
 
15660
16168
  McpLivenessState:
15661
16169
  type: string
@@ -15936,6 +16444,9 @@ components:
15936
16444
  summaryMs: { type: integer }
15937
16445
  persistMs: { type: integer }
15938
16446
  ptlRetries: { type: integer }
16447
+ pass:
16448
+ type: string
16449
+ description: 'server >= 7.132.0 (core 7.53.0 #2008): the id of the compaction pass that LANDED — the same opaque value its `compaction_started.pass` carried. Pair the start with its end on `pass`, not on "the next `compacted`" (legs interleave on one stream). Absent = an older server / a pass that never announced a start.'
15939
16450
  Event_suggestions:
15940
16451
  type: object
15941
16452
  description: >
@@ -15969,7 +16480,7 @@ components:
15969
16480
  type: { const: status }
15970
16481
  # 🔴 A-057.27:6 员(此前只列 4,漏 recovered/gave_up 两个**终局**值 —— 一段重试序列恒恰一终局帧,
15971
16482
  # 两者对渲染方是同一句话「把重试行撤下来」)。闭集声明、开集读。
15972
- phase: { type: string, description: "rate_limited | retrying | reconnecting | circuit_open | recovered | gave_up (closed in core, read as open set)" }
16483
+ phase: { type: string, description: "rate_limited | retrying | reconnecting | circuit_open | recovered | gave_up | waiting_first_token (core's seven words since core 7.3.1 — `waiting_first_token` observes a first-token wait before any failure and carries `elapsedMs` / `timeoutMs`, never a retry countdown; closed in core, read as open set)" }
15973
16484
  detail: { type: string, description: "neutral human hint (NO provider/HTTP detail)" }
15974
16485
  retryInSec: { type: number }
15975
16486
  # 🔴 A-057.26:以下四键 server 真在发(builder brainStatusEventData,三腿共享),而本 schema 是
@@ -15978,7 +16489,7 @@ components:
15978
16489
  attempt: { type: number, description: "1-based index of the attempt that just FAILED (this wait precedes attempt+1)" }
15979
16490
  maxRetries: { type: number, description: "retry budget of the lane in force (refused-connect short lane = 2)" }
15980
16491
  retryInMs: { type: number, description: "same number as retryInSec at ms precision (countdown rendering)" }
15981
- errClass: { type: string, description: "connect_refused | transport | rate_limit | server | http | output_cap — retry-wait frames ONLY; never inferred from phase" }
16492
+ errClass: { type: string, description: "connect_refused | transport | stall | rate_limit | server | http | output_cap (core's seven words since core 7.14.0 — `stall` = this process's own stall watchdog cancelled the request) — retry-wait frames ONLY; never inferred from phase" }
15982
16493
  providerCondition: { type: string, description: "server >= 7.126.0 (core 7.49.0 #1454, passed through verbatim): what the provider said about the account / capacity — core's closed `PROVIDER_CONDITIONS`; a retry frame carries only the three retryable words `throttled | overloaded | unknown` (`quota_exhausted` / `stop_other` do not retry and ride the terminal `detail.providerCondition`). Presence law as `errorStatus`; read as an OPEN set (keep a default arm). ABSENT = not judged, read the same as `unknown` (back off, do not assert quota). Moves with `providerConditionSource`." }
15983
16494
  providerConditionSource: { type: string, description: "server >= 7.126.0 (core 7.49.0 #1454): the evidence the `providerCondition` verdict rests on — `unified_header | provider_code | status`; present exactly when `providerCondition` is; read as an OPEN set." }
15984
16495
  retryAtMs: { type: number, description: "server >= 7.52.0 (core 7.0.0): the WALL-CLOCK end of the wait = `Date.now() + retryInMs` at emit, on the emitting process's clock (not monotonic — never compare it with a local monotonic timer). `retryInMs` stays the authoritative RELATIVE amount; this is the producer's absolute note (a reader several hops away recomputing it would reset the countdown on every replay). Moves with `retryInMs`; absent ⇒ not minted." }
@@ -16101,9 +16612,12 @@ components:
16101
16612
  `message_committed` + stream order). Live emissions PRECEDE the commit and omit `entryId`.
16102
16613
  SERVICE PROJECTION: core's `principal` is deliberately NOT forwarded (the stream is already owner-scoped —
16103
16614
  the frame does not restate the tenant). `issuer` / `actor.id` / `actor.issuer` are host-derived identity
16104
- strings and arrive SECRET-REDACTED. Server >= 7.127.0 does NOT project core 7.50.0's `attachments` member (always absent here)
16105
- and does not accept the engine's request-layer `userAttachments` key: a submit / resume body carrying it is 400
16106
- `request.body_shape` (named in `unknownKeys`).
16615
+ strings and arrive SECRET-REDACTED. REQUEST-LAYER ATTACHMENTS (server >= 7.131.0 / core 7.50.0): the frame of the objective that declared
16616
+ attachments (`carrier: "objective"`, `delivery: "applied"`) carries `attachments` — one item per attachment, in declared order (submit key
16617
+ `TaskRequest.userAttachments`; probe `Capabilities.userAttachments`). It is the ADMISSION outcome, not delivery: each attachment's message lands as its
16618
+ own `message_committed{role:"user"}`. Attachments ride the objective only: a steer door (run / child / workflow child steer, child resume, interrupt
16619
+ bare or with text) carrying `userAttachments` is 400 `request.body_shape` (named in `unsupportedKeys`), and resume / delivery legs do not rebuild it.
16620
+ (7.127.0 – 7.130.x did not accept `userAttachments` and never carried `attachments`; the capability bit is absent there.)
16107
16621
  allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
16108
16622
  additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
16109
16623
  required: [type, inputId]
@@ -16134,6 +16648,23 @@ components:
16134
16648
  hostAsserted: { type: boolean }
16135
16649
  issuer: { type: string }
16136
16650
  entryId: { type: string, description: 'The committed session entry this input became (absent on the live pre-commit emission).' }
16651
+ attachments:
16652
+ type: array
16653
+ description: >-
16654
+ server >= 7.131.0 (core 7.50.0): the per-attachment ADMISSION outcome of the request-layer attachments this objective declared, one item per
16655
+ attachment in declared order; present only on that objective's frame. The server copies each member key only when well-formed (a non-object member is
16656
+ not minted); `ordinal` / `outcome` / `label` are required members of the engine's `HumanInputAttachment` (core 7.53.0), hence `required` here. `ordinal` 1-based; `kind` the engine-recognized kind (absent when unreadable; read open); `outcome` `attached` / `partial` / `unchanged` /
16657
+ `not_attached` (engine words, verbatim — a file the engine may not read is NOT refused: its message still goes out saying why, and its outcome is
16658
+ `not_attached`); `label` the path or name the caller declared, credential-redacted.
16659
+ items:
16660
+ type: object
16661
+ required: [ordinal, outcome, label]
16662
+ additionalProperties: false
16663
+ properties:
16664
+ ordinal: { type: integer }
16665
+ kind: { type: string }
16666
+ outcome: { type: string }
16667
+ label: { type: string }
16137
16668
  eventId: { type: string }
16138
16669
  parentToolCallId: { type: string }
16139
16670
  sourceTaskId: { type: string }
@@ -16313,19 +16844,22 @@ components:
16313
16844
  already owns the single redaction mint point for it). The actionable cause is `errorCode`.
16314
16845
  A row missing `name` or `status` is dropped individually — the other servers'' rows still ship.
16315
16846
  items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
16316
- hooks:
16847
+ mods:
16317
16848
  type: array
16318
16849
  description: >
16319
- core 7.18.0 (#789), server >= 7.78.0 — TENANT-visible: the hook seats THIS leg wired, one row per
16320
- family, in core's declaration order. EFFECTIVE half only.
16321
- 🔴 PRESENT IFF at least one seat is wired — core NEVER emits an empty array, so "the key is here"
16322
- means non-empty. 🔴 ABSENCE READS "this leg wired no hook RECORD of its own", NOT "no hook runs":
16323
- a delegated child with no local record can still be screened (and refused) by an ANCESTOR's
16324
- `preToolUse` through the gate's constraint fold, which this face deliberately does not call a hook
16325
- of this leg. A row missing `family` or `owner` is dropped INDIVIDUALLY (same rule as `mcp[]`); all
16326
- rows dropped => the key is absent. 🔴 EXCLUDED from `configFingerprint` (the record is a per-task
16327
- seat), so two legs with the same fingerprint and different `hooks[]` are NOT in contradiction.
16328
- items: { $ref: '#/components/schemas/WiringManifestHookEntry' }
16850
+ core 7.52.0, server >= 7.132.0 (BREAKING — replaces `hooks[]`, which is gone with no alias) — TENANT-visible: this leg's MODS ADMISSION record, one row
16851
+ per registration of the two seats (deployment, task), in the engine's run order (tier, then deployment before task, then seat order). Two row forms
16852
+ (`WiringManifestModEntry`): a MOUNTED registration `{ name, seat, tier, events, observe?, skipped? }` — `events` / `observe` are the patterns it actually
16853
+ hung (observe-only ones in `observe`), `skipped[]` the judges skipped while it was judged — or a REFUSED one `{ name, seat, tier, refusedBy, reason }`.
16854
+ Registrant strings (`name`, `refusedBy`, `reason`, every `skipped[]` member) go out through the server's one wire-text exit (credential shapes
16855
+ replaced, length-capped) — the same one the `engine_notice` `mods.registration_refused` detail uses, so the same refusal reads byte-equal on both;
16856
+ `seat` / `tier` / `events` / `observe` are engine words passed through verbatim (vocabulary owner core; read open). PRESENT IFF the leg carries a
16857
+ registration — on this server always (it mounts the permission-denied meter on every leg: `server.permission-denied-meter`; `TOOL_TRACE=true` adds
16858
+ `server.tool-trace`, a branded deployment `server.brand-identity`; a single-user request's `settings.hooks` is the task-seat registration
16859
+ `settings.hooks`). 🔴 ABSENCE READS "this leg carries no registration", NOT "no extension judges this leg's calls" (a delegated child can still be
16860
+ screened through an ancestor's chain). A row that reads as neither form is dropped individually; all dropped => key absent. EXCLUDED from
16861
+ `configFingerprint` (the task seat is per task). EFFECTIVE half only.
16862
+ items: { $ref: '#/components/schemas/WiringManifestModEntry' }
16329
16863
  lsp:
16330
16864
  allOf: [{ $ref: '#/components/schemas/WiringManifestLspSeam' }]
16331
16865
  description: >
@@ -16400,6 +16934,12 @@ components:
16400
16934
  (`capabilities.rewindFiles`); only a run the REQUEST word turned off cannot restore itself.
16401
16935
  OPEN on read: the server does not enumerate the word (a present non-string is dropped), a third word passes
16402
16936
  through — treat it as unknown. ABSENT = an older engine / external derivation, never "capturing".
16937
+ permissionMode:
16938
+ allOf: [{ $ref: '#/components/schemas/WiringManifestPermissionMode' }]
16939
+ description: >-
16940
+ server >= 7.133.0 (core 7.53.0; TENANT and OPERATOR faces alike) — this leg's PERMISSION-MODE receipt `{ requested?, effective, foldedBy? }`; the shell renders its mode indicator from `effective`
16941
+ (`foldedBy` present = the requested mode was folded), never from the word it sent. ABSENT = an older engine / server, not an assertion of the default mode. Effective half only (this frame);
16942
+ the same section rides the non-streaming `wiringManifest` receipt and the ledger row. Section shape: WiringManifestPermissionMode.
16403
16943
  instructions:
16404
16944
  allOf: [{ $ref: '#/components/schemas/WiringManifestInstructions' }]
16405
16945
  description: >
@@ -16722,6 +17262,26 @@ components:
16722
17262
  was armed but declined/failed (check the store, check authorisation), `false` = there was no lane at all
16723
17263
  (wire one up). The two recovery verbs differ, so a consumer MUST render them apart — NEVER fold them into
16724
17264
  one "nobody approved it".
17265
+ server 7.132.0 adds three codes to the user whitelist (appendix D.3 38 -> 41; all audience `user` in core's register) and changes one value:
17266
+ `config.session_rules_earlier_grammar` (core 7.53.0) — the session's STORED permission rules hold entries the grammar they were written in reads as nothing
17267
+ (an allow entry admits nothing; a `toolDeny` entry this engine reads as no rule makes those rules REFUSE EVERY CALL until an operator rewrites the row
17268
+ without its rev). `{ bucket, storedGrammar, entries: [{ entry, reader }], rewrite: { by, stampsGrammar }, rulesSessionId, sessionId }` — `entries` every
17269
+ one the engine handed over (the session-stream row passes the generic notice-detail bound: at most the first 32, no truncation mark — the full list is on
17270
+ the ops log line), `entry` the redacted rule text (the same reading as `permission.allowed_tool_not_granted`'s `entries[].entry`), `reader` one of
17271
+ `no_rule` / `named_no_tool`, `rewrite.by` = `operator_write_without_rev` (closed words owned by core, echoed — read open); `rulesSessionId` = whose
17272
+ rules (an ancestor's on a delegated child), `sessionId` = the run that read them. Once per distinct entry set per bucket per rule layer.
17273
+ `mods.registration_refused` (core 7.52.0) — an extension registration was NOT mounted (another registration's `plugin.register` judge refused it); the leg
17274
+ runs without it. `{ name, seat, tier, refusedBy, reason, sessionId }` — `seat` / `tier` engine words (echoed), `reason` the refusing registration's own
17275
+ words (UNTRUSTED-for-display); the same strings read byte-equal with the `wiring_manifest.mods[]` refused row. Unreachable on this server today (it wires
17276
+ no `plugin.register` judge). Once per leg per registration.
17277
+ `route.credential_origin_mismatch` (core 7.52.0) — a request was REFUSED and nothing was sent: this model's own credential is bound to the host it first
17278
+ went to and this request goes to another (the entry's `baseUrl` moved host, the credential stayed; a same-host path move does not count). The request's
17279
+ own failure carries the same code. `{ modelId, boundOrigin, requestOrigin, sessionId, runId }` — both origins in display spelling (scheme + host +
17280
+ port, no userinfo / path), never a credential or a digest of one. Once per run per (modelId, boundOrigin, requestOrigin). It replaces the retired
17281
+ operator code `route.base_url_changed_key_unchanged` (never on this whitelist). `config.credential_not_withheld` is operator-audience — ops log only.
17282
+ Value change: `permission.rule_row_dropped`'s `detail.rows[].rule` is, since core 7.53.0, the rule's original text (the same reading as
17283
+ `allowed_tool_not_granted`'s `entries[].entry`: at most 1 024 code points, longer cut to 1 024 + a truncation mark, CONTROL CHARACTERS RIDE AS DATA —
17284
+ neutralize at render); before it was the engine-neutralized single-line form (at most 201 code points). Same keys.
16725
17285
  `server.model_unreachable` (server-minted, audience `user`; server >= 7.108.0, `taskId` added in server 7.121.0) — a model call on
16726
17286
  this leg ended in error and its last request failed AT CONNECT (VPN switched mid-session / the proxy read at boot went stale / the
16727
17287
  gateway is gone) — not the provider's answer. `{ code, targetHost, proxyInUse?, remedy, sessionId, taskId? }`: `code` one of the
@@ -16963,56 +17523,78 @@ components:
16963
17523
  一律按不可分辨处理,消费方不要猜。
16964
17524
 
16965
17525
  PromptManifest:
16966
- # 新增(2026-07-25 回填批):server ≥1.219 的 prompt-assembly manifest。此前本文件完全没有它(全文 0 次提及),
16967
- # 尽管它同时是 `AgentEvent.prompt_assembled` 臂和 trace 流 `prompt-assembled` 帧的载荷。
16968
17526
  type: object
16969
17527
  description: >
16970
- The prompt-assembly manifest (core's `prompt.assembled` trace contract verbatim). Carries ONLY
16971
- ids/digests/counts — NEVER prompt bodies (privacy boundary). 🔴 Hashes are PROCESS-SALTED: they are NOT
16972
- comparable across worker restarts, so a UI must never diff them cross-process. `constitution`/`blocks`/
16973
- `totalChars` are the v1 face; `sections`/`tools` are the v2 ADDITIVE face (absent on a v1-only engine).
16974
- ⚠️ One task can carry MULTIPLE manifests — a cascade/verify run re-prepares under one taskId and every
16975
- rung's manifest is persisted in emission order.
16976
- required: [constitution, blocks, totalChars]
17528
+ The prompt manifest v3 (server >= 7.132.0 = engine 7.52.0 manifest v3; BREAKING): WHICH prompt this leg ran (`identity` + its selector values `selectors`),
17529
+ which blocks and sections it is made of, and where each section's text came from (`chain`; superseded overlays in `superseded`). IDs / sizes / digests
17530
+ only — NEVER prompt text. One row per prepare (cascade / verify re-prepare under one taskId, rows in order). Three exits carry the SAME stored row
17531
+ verbatim: the ledger replay `prompt_assembled`, the turns `prompt-assembled` block, the trace SSE `prompt-assembled` frame. Members = the engine frame
17532
+ minus its envelope (`kind` / `version` / `taskId` / `ts`) and `snapshot` / `lowering` (server `promptManifestRecordOf`).
17533
+ READING: EVERY member may be absent — an optional member absent means "none", never an empty value; a member present but unreadable is not put on the
17534
+ row (`identity` / `selectors` as whole sentences, sections / blocks / `superseded` / tools one row at a time), so a v3 row without `identity` means the
17535
+ engine's identity did not read (dropped and counted), NOT an old row. An empty list is a statement (`tools: []` = zero tools). Words (band names,
17536
+ activations, source classes, renderer ids, host role / form) are engine vocabulary, passed through verbatim — read open.
17537
+ 🔴 THE ONE READING OF "WHICH PROMPT DID THIS LEG RUN" IS `identity`. BREAKING (no alias): v2's top-level `constitution` is gone (the constitution band
17538
+ is locked and engine-composed; a prompt carrying locked text verbatim is refused before the run, `prompt.assembled_role_refused`); `sections[]` lost
17539
+ `slot` / `carrier` / `cadence` / `cacheClass`, `hash` became `contentHash` (`sha256:` unsalted for published text, `salted:` for text carrying the task's
17540
+ or the run's own inputs) and gained `band` (always) / `variant?` / `renderer?` / `locale` / `chain`; the row gained `identity` / `selectors` / `superseded?`.
17541
+ ⚠️ Rows written by <= 7.131.x keep the v2 shape (the server neither re-reads nor rewrites them) — see PromptManifestRow: a row carrying `constitution`
17542
+ is such an old row. ⚠️ Block `hash` is a PROCESS-SALTED short digest — not comparable across worker restarts.
17543
+ # 不封闭(`additionalProperties: true`):本 schema 是 `Event_prompt_assembled` 的 allOf 目标(帧上多一个 `type`),封闭会把合法帧判成违约(见 Event_reasoning 处的长注)。
17544
+ # 与旧行的互斥由 `not: { required: [constitution] }` 一句写死(v3 行永不带它),`PromptManifestRow` 的 oneOf 因此恰中一臂。
16977
17545
  additionalProperties: true
17546
+ not: { required: [constitution] }
16978
17547
  properties:
16979
- constitution:
16980
- type: string
16981
- description: 'who owned the constitution layer — `core` is the steady state; anything else is worth eyes.'
16982
- enum: [core, replaced, provider-assembled, legacy]
16983
- x-open-enum: true
17548
+ identity: { $ref: '#/components/schemas/PromptManifestIdentity' }
17549
+ selectors:
17550
+ type: object
17551
+ description: 'the run''s selector values (core `PromptManifestSelectors`) — the plain form of `identity.layers.binding`. `binding` = the scenario name (server >= 7.132.0 always writes `TaskSpec.promptBinding`).'
17552
+ additionalProperties: false
17553
+ required: [traits, host, facts]
17554
+ properties:
17555
+ binding: { type: string }
17556
+ traits: { type: array, items: { type: string } }
17557
+ host:
17558
+ type: object
17559
+ additionalProperties: false
17560
+ required: [role, form]
17561
+ properties:
17562
+ role: { type: string, description: 'engine word (`general` / `code` / `coordinator` / `cowork` today); read open.' }
17563
+ form: { type: string, description: 'engine word (`standard` / `minimal` today); read open.' }
17564
+ facts: { type: object, additionalProperties: { type: boolean } }
16984
17565
  blocks:
16985
17566
  type: array
16986
17567
  items:
16987
17568
  type: object
17569
+ additionalProperties: false
16988
17570
  required: [id, chars, hash]
16989
- additionalProperties: true
16990
17571
  properties:
16991
17572
  id: { type: string }
16992
17573
  chars: { type: integer }
16993
- hash: { type: string, description: 'process-salted digest — never compare across restarts.' }
17574
+ hash: { type: string, description: 'process-salted short digest — never compare across restarts.' }
16994
17575
  sections:
16995
17576
  type: array
16996
- description: 'v2 additive face — absent on a v1-only engine.'
17577
+ items: { $ref: '#/components/schemas/PromptManifestSection' }
17578
+ superseded:
17579
+ type: array
17580
+ description: 'every overlay the composition superseded (a later one on the same target won, or it changed nothing this run); absent when none was.'
16997
17581
  items:
16998
17582
  type: object
16999
- required: [id, slot, carrier, cadence, cacheClass, chars, hash]
17000
- additionalProperties: true
17583
+ additionalProperties: false
17584
+ required: [id, source, by, target, supersededBy]
17001
17585
  properties:
17002
17586
  id: { type: string }
17003
- slot: { type: string }
17004
- carrier: { type: string }
17005
- cadence: { type: string }
17006
- cacheClass: { type: string }
17007
- chars: { type: integer }
17008
- hash: { type: string }
17587
+ source: { type: string }
17588
+ by: { type: string }
17589
+ target: { type: string }
17590
+ supersededBy: { type: string, description: 'the winning overlay id, or the literal `(absent this run)`.' }
17009
17591
  tools:
17010
17592
  type: array
17011
- description: 'v2 additive face — the tool contract summary.'
17593
+ description: 'the mounted tools plane — per-tool contract identity, durable-replay aliases, shape digest and full wire-schema digest (initial mount face; deferred activations announce via tools_delta).'
17012
17594
  items:
17013
17595
  type: object
17596
+ additionalProperties: false
17014
17597
  required: [wireName, aliases, contractId, implementationRevision, cardId, shapeDigest, wireSchemaDigest]
17015
- additionalProperties: true
17016
17598
  properties:
17017
17599
  wireName: { type: string }
17018
17600
  aliases: { type: array, items: { type: string } }
@@ -17021,7 +17603,72 @@ components:
17021
17603
  cardId: { type: string }
17022
17604
  shapeDigest: { type: string }
17023
17605
  wireSchemaDigest: { type: string }
17606
+ toolDisclosure:
17607
+ type: object
17608
+ description: 'the resolved deferred-tool disclosure strategy (core `ToolDisclosureManifest`); absent when the leg deferred nothing.'
17609
+ additionalProperties: false
17610
+ required: [deferredTools, strategy, source]
17611
+ properties:
17612
+ deferredTools: { type: integer }
17613
+ strategy: { type: string, description: '`swap` / `static`; read open.' }
17614
+ source: { type: string, description: '`spec` / `env` / `default` / `degraded_no_direct_lane`; read open.' }
17024
17615
  totalChars: { type: integer }
17616
+ PromptManifestIdentity:
17617
+ type: object
17618
+ description: 'the run''s prompt identity as the manifest reports it (core `PromptManifestIdentity`): `artifact` = the identity digest, `layers` = its inputs, `epoch` / `activatedBy` = the session epoch record that names it.'
17619
+ additionalProperties: false
17620
+ required: [artifact, layers, epoch, activatedBy]
17621
+ properties:
17622
+ artifact: { type: string }
17623
+ layers: { $ref: '#/components/schemas/PromptIdentityLayers' }
17624
+ epoch: { type: integer }
17625
+ activatedBy: { type: string, description: 'core `PROMPT_EPOCH_ACTIVATIONS` word; read open.' }
17626
+ PromptManifestSection:
17627
+ type: object
17628
+ description: 'one rendered section (core `PromptManifestSection`).'
17629
+ additionalProperties: false
17630
+ required: [id, band, rank, chars, locale, contentHash, chain]
17631
+ properties:
17632
+ id: { type: string }
17633
+ band: { type: string }
17634
+ rank: { type: number }
17635
+ variant: { type: string }
17636
+ renderer:
17637
+ type: object
17638
+ additionalProperties: false
17639
+ required: [id, version]
17640
+ properties:
17641
+ id: { type: string, description: 'core `RENDERER_IDS` word; read open.' }
17642
+ version: { type: number }
17643
+ chars: { type: integer }
17644
+ locale: { type: string, description: '`canonical` (the catalog''s text) / `overlay` (text an overlay author wrote); read open.' }
17645
+ contentHash: { type: string, description: '`sha256:<hex>` unsalted for published text, `salted:<hex>` for text carrying the task''s or the run''s own inputs.' }
17646
+ chain:
17647
+ type: array
17648
+ items:
17649
+ type: object
17650
+ additionalProperties: false
17651
+ required: [source]
17652
+ properties:
17653
+ source: { type: string, description: '`bundled` or an overlay source class; read open.' }
17654
+ overlay: { type: string }
17655
+ op: { type: string }
17656
+ superseded: { const: true }
17657
+ PromptManifestRetiredRow:
17658
+ type: object
17659
+ description: >
17660
+ A `prompt_assembled` row written by a <= 7.131.x server (the v2 shape: top-level `constitution` + slot-keyed sections). The server neither re-reads nor
17661
+ rewrites it, so a run parked before the upgrade and resumed after carries both shapes in its ledger. The ONE discriminator (owned by the server contract):
17662
+ `constitution` present <=> an old row (old rows always carry it, v3 rows never do). Its members are deliberately NOT typed here (the v2 reading is retired).
17663
+ required: [constitution]
17664
+ additionalProperties: true
17665
+ properties:
17666
+ constitution: { type: string }
17667
+ PromptManifestRow:
17668
+ description: 'One row on the three manifest exits: a v3 row (PromptManifest) or an old row (PromptManifestRetiredRow) — tell them apart by the presence of `constitution`.'
17669
+ oneOf:
17670
+ - $ref: '#/components/schemas/PromptManifest'
17671
+ - $ref: '#/components/schemas/PromptManifestRetiredRow'
17025
17672
 
17026
17673
  Event_file_link:
17027
17674
  type: object
@@ -17053,7 +17700,7 @@ components:
17053
17700
  manifest's own keys sit at the top level next to `type`, they are NOT nested under a `manifest` key).
17054
17701
  Lands AHEAD of the first event of the turn it prepared. ⚠️ MULTIPLE per task on cascade/verify runs.
17055
17702
  allOf:
17056
- - $ref: '#/components/schemas/PromptManifest'
17703
+ - $ref: '#/components/schemas/PromptManifestRow'
17057
17704
  required: [type]
17058
17705
  properties:
17059
17706
  type: { const: prompt_assembled }
@@ -17172,7 +17819,7 @@ components:
17172
17819
  payload carried `type` (the read boundary emits `{type, ...data}`) so runtime discrimination worked,
17173
17820
  but a generated client had nothing to switch on and dropped approval cards. That gap is closed.
17174
17821
  The shell renders `tool_approval` as the three-choice card and dismisses it on `tool_approval_complete`.
17175
- Respond via POST /v1/tool-approvals/{approvalId}/respond.
17822
+ Respond via POST /v1/tool-approvals/{id}/respond.
17176
17823
  required: [type, approvalId]
17177
17824
  additionalProperties: true
17178
17825
  properties:
@@ -17304,15 +17951,16 @@ components:
17304
17951
  (no alias): the allow-layer members that MATCHED this call and could NOT clear this ask, one `{ member, entries, omitted? }` record per
17305
17952
  matching member (see AllowRuleShadow). `member` is one of `hook` / `deployment` / `persisted` / `skill` / `request`; `entries` are the
17306
17953
  entries that member matched, as their source spells them (UNTRUSTED for display like `message`, secret-redacted, <= 512 chars each,
17307
- <= 32 listed; a `hook` record's `entries` is always the empty list); `omitted` counts the entries not listed.
17954
+ <= 32 listed; a `hook` record's `entries` names the answering registration — server >= 7.132.0 / engine 7.52.0 mods: on this server only the
17955
+ request's own `settings.hooks` answers, so `["settings.hooks"]`; it was always the empty list on <= 7.131.x); `omitted` counts the entries not listed.
17308
17956
 
17309
17957
  The explaining sentence ("... matches this call but does not clear the ask — <reason>") is IN `message` (the engine writes it where it builds
17310
17958
  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
17311
17959
  time"): without it the only conclusion a user can draw is "my rule broke".
17312
17960
 
17313
17961
  🔴 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.
17314
- 🔴 `member: "hook"` does not say whose hook (a deployment PreToolUse hook and, on the single-user lane, the request's own `settings.hooks`
17315
- share one member). 🔴 Deliberately SEPARATE from `governanceForced` (that one: "who imposed the gate"; this one: "what happened to YOUR
17962
+ 🔴 `member: "hook"` names the answering registration in `entries` (server >= 7.132.0; the deployment seat's registrations are observers and never
17963
+ answer — on <= 7.131.x the record could not say whose hook). 🔴 Deliberately SEPARATE from `governanceForced` (that one: "who imposed the gate"; this one: "what happened to YOUR
17316
17964
  rule"). What the allow layer can NOT clear: any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` /
17317
17965
  `MANUAL_MODE_SHELL_GATE=always`, write protection, sensitive-path writes, governance, a hook's ask, plan-mode asks. The durable dual is
17318
17966
  `gate.riskDescriptor.allowRuleShadowed` on the parked row (PendingCheckpoint.riskDescriptor). Rolling-upgrade caveat: a card stored by a
@@ -17567,8 +18215,8 @@ components:
17567
18215
 
17568
18216
  # ── design/172 流内审批协议(#151)—— 已随 server 7.3.0 上线;开关 STREAM_APPROVAL_ENABLED 的默认值
17569
18217
  # 分两段:server ≤7.4.0 默认 OFF,≥7.5.0 默认 ON(BREAKING,见 server CHANGELOG)。──
17570
- # 与上面的 ToolApprovalFrame 是**并行加帧,不替换**:开关打开时同一只 ask 出两帧(先 tool_approval,
17571
- # 后 approval_request),两帧同带 `approvalId` ⇒ 消费端按它去重、优先渲染新帧。
18218
+ # 与上面的 ToolApprovalFrame 是两个帧族,**一条腿只走一个**(server >= 7.133.0,capabilities.approvalFrameFamilies):呈卡协议上场的腿上一只 ask 只出本族的帧、
18219
+ # 旧族一帧都不发,消费端不再按 `approvalId` 去重;≤7.132.x 的呈卡腿两族并发(先 tool_approval、后 approval_request,同带 `approvalId`)⇒ 按它去重、优先渲染新帧。
17572
18220
  classifierUnavailable:
17573
18221
  $ref: '#/components/schemas/ClassifierUnavailable'
17574
18222
  description: >-
@@ -17610,6 +18258,14 @@ components:
17610
18258
  (a malformed shape, or a `dir` over 1024 UTF-16 code units ⇒ the key is ABSENT — never truncated,
17611
18259
  because a truncated directory is a DIFFERENT directory and adding it would not clear this ask;
17612
18260
  see the STRING-LENGTH UNIT convention in `info.description` for this `maxLength`'s counting unit).
18261
+ covers:
18262
+ allOf: [{ $ref: '#/components/schemas/ReadRootCover' }]
18263
+ description: >-
18264
+ Optional (engine core 7.22.0 `ReadRootGrantCandidate.covers`; the server carries it through key by key). ABSENT ⇒ `dir` is a DIRECTORY
18265
+ (adding it to the read roots releases what lies under it). `exact` ⇒ `dir` is the path the command NAMED, offered verbatim
18266
+ (a file or one concrete path, not a directory containing it): the consumer ADDS the string itself — expanding it into a
18267
+ pattern grants nothing — and must not render the offer as "add directory". The domain is the single word `exact`
18268
+ (a word outside it makes the server drop the whole candidate). The `approval_request` frame reads this very definition.
17613
18269
  clearsThisAsk:
17614
18270
  type: boolean
17615
18271
  enum: [true]
@@ -17690,8 +18346,23 @@ components:
17690
18346
  approvalId:
17691
18347
  type: string
17692
18348
  description: >
17693
- Bridge to the legacy frame / dedupe key — same value as the parallel tool_approval frame. During the
17694
- transition window POST /v1/tool-approvals/{approvalId}/respond still works.
18349
+ The id of POST /v1/tool-approvals/{id}/respond and GET /v1/tool-approvals/{id}. server >= 7.133.0: ALWAYS EQUAL TO `askId` (the row's key is the ask's one wire
18350
+ identity; a 64-character lowercase hexadecimal string — treat it as an OPAQUE string, never parse it as a uuid); the matching close frame `approval_complete` carries it too.
18351
+ On server <= 7.132.x it is the legacy bridge / dedupe key (a uuid, the same value as the parallel tool_approval frame).
18352
+ # ── server >= 7.133.0: the TEN off-card LIVE facts on the opening frame's top level (one frame family per leg) ──────────────────────────
18353
+ # Same keys, same shapes, same values as on the legacy `tool_approval` frame (each is a `$ref` into ToolApprovalFrame's own property — one
18354
+ # source per shape). Only the frame minted LIVE carries them (and what the durable ledger recorded of that frame): a disconnect-replay frame
18355
+ # (rebuilt from `card_json`) never does. Absence means "this frame does not carry it", not "there is none". See APPROVAL_REQUEST_LIVE_FACT_KEYS (sdk) / the server's twin.
18356
+ mandated: { $ref: '#/components/schemas/ToolApprovalFrame/properties/mandated' }
18357
+ mandate: { $ref: '#/components/schemas/ToolApprovalFrame/properties/mandate' }
18358
+ origin: { $ref: '#/components/schemas/ToolApprovalFrame/properties/origin' }
18359
+ boundGrantDigest: { $ref: '#/components/schemas/ToolApprovalFrame/properties/boundGrantDigest' }
18360
+ boundGrants: { $ref: '#/components/schemas/ToolApprovalFrame/properties/boundGrants' }
18361
+ classifierNoRuling: { $ref: '#/components/schemas/ToolApprovalFrame/properties/classifierNoRuling' }
18362
+ boundGrantAbsence: { $ref: '#/components/schemas/ToolApprovalFrame/properties/boundGrantAbsence' }
18363
+ approvalCard: { $ref: '#/components/schemas/ToolApprovalFrame/properties/approvalCard' }
18364
+ readRootCandidate: { $ref: '#/components/schemas/ToolApprovalFrame/properties/readRootCandidate' }
18365
+ probeReason: { $ref: '#/components/schemas/ToolApprovalFrame/properties/probeReason' }
17695
18366
 
17696
18367
  ApprovalCard:
17697
18368
  type: object
@@ -17756,7 +18427,7 @@ components:
17756
18427
  items: { $ref: '#/components/schemas/AllowRuleShadow' }
17757
18428
  description: >
17758
18429
  ADDITIVE (server >= 7.117.0 / core 7.42.0, BREAKING RENAME of the retired `persistedRuleShadowed`): the allow-layer members that matched this
17759
- call and could not clear this ask. Semantics, the five members, the always-empty `hook` entries, the explaining sentence living in `message`,
18430
+ call and could not clear this ask. Semantics, the five members, the `hook` record naming its registration (server >= 7.132.0; always empty before), the explaining sentence living in `message`,
17760
18431
  the ABSENT != "you have no rule" clause and the separation from `governanceForced` are stated verbatim on ToolApprovalFrame.allowRuleShadowed
17761
18432
  — the live frame, the stored `card_json`, the replayed frame and the `approval_request` card carry the SAME material.
17762
18433
  fromSubagent: { type: boolean, enum: [true], description: 'Present ⇔ the ask comes from a DELEGATED child — the explicit discriminator (a worker cannot forge it).' }
@@ -17954,6 +18625,13 @@ components:
17954
18625
  and a word outside the set all read as ABSENT — a card without this word must not be treated as "approving it grants nothing else"; whether to render "allow for this session" is `rememberRefused`.
17955
18626
  Display metadata, never an adjudication input. CLOSED: the server narrows with the engine's members, anything else reads as absent.
17956
18627
 
18628
+ ReadRootCover:
18629
+ type: string
18630
+ enum: [exact]
18631
+ description: >
18632
+ The closed vocabulary of `readRootCandidate.covers` (engine core 7.22.0 `ReadRootGrantCandidate.covers`; the server's narrow read `readReadRootCandidate` drops the whole
18633
+ candidate for a word outside it). One word today: `exact` — `dir` is the path the command NAMED, not a directory containing it. The SDK exports the same table as
18634
+ `READ_ROOT_COVERS` and its `isApprovalRequestFrameV1` judges `covers` by membership in it; a source-level anchor reads the word off the server's minting site and the engine's type.
17957
18635
  PersistedRuleMandate:
17958
18636
  type: string
17959
18637
  enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved]
@@ -18367,6 +19045,40 @@ components:
18367
19045
  depth: { type: integer, description: 'Delegation depth (a direct child of the host = 1).' }
18368
19046
  agentName: { type: string, description: 'Child agent name. UNTRUSTED — spawning-model free text (server-redacted and length-bounded).' }
18369
19047
 
19048
+ ApprovalCompleteFrame:
19049
+ type: object
19050
+ description: >
19051
+ The CLOSE frame of the card family (`approval_complete`; server >= 7.133.0; SSE, `type` IS the event name; the durable leg appends the same frame, so it replays through
19052
+ GET /v1/runs/:id/events as the `Event_approval_complete` arm). ONE FRAME FAMILY PER LEG: on a leg where the card protocol is on (`capabilities.approvalFrameFamilies`
19053
+ = `["approval_request"]`) one ask travels only `approval_request` -> `approval_complete` / `approval_revoke`; the legacy `tool_approval` / `tool_approval_complete` pair is NOT sent
19054
+ (BREAKING vs <= 7.132.x, which sent both families for one ask; remedy on the worker: `STREAM_APPROVAL_ENABLED=false`).
19055
+ Same meaning as the legacy `tool_approval_complete` (one settle, one verdict: the `outcome` three words, the non-human `cause`, the park-route `parked`); dismiss the card by the opening frame's
19056
+ `askId`. Division of labour with ApprovalRevokeFrame: the revoke frame is BATCH-level (siblings voided by a winner), this one is the SINGLE card's end (answered / window expired / park route / cancelled);
19057
+ one ask can receive both — clearing by `askId` is enough. The gate is "this connection's opening frame was actually delivered". The frame is a notification, the row is the source of truth: a lost
19058
+ frame is compensated by the open-stream preamble (see ApprovalRequestFrame). THIS SCHEMA DESCRIBES v1 ONLY (read it as ApprovalFrameEnvelope first; the SDK's `isApprovalCompleteFrameV1` narrows).
19059
+ required: [type, schemaVersion, askId, taskId, approvalId, outcome, serverNowMs]
19060
+ additionalProperties: true
19061
+ properties:
19062
+ type: { type: string, enum: [approval_complete] }
19063
+ schemaVersion: { type: integer, enum: [1], description: 'This schema describes v1 only (see ApprovalFrameEnvelope for the unknown-version read).' }
19064
+ askId: { type: string, description: 'The durable identity — dismiss the card by it (the same key as the opening frame''s `askId`).' }
19065
+ taskId: { type: string, description: 'wire run id (same as the opening frame).' }
19066
+ approvalId: { type: string, description: 'Always equal to `askId` (same as the opening frame).' }
19067
+ outcome:
19068
+ type: string
19069
+ enum: [allowed, denied, expired]
19070
+ description: 'How the ask settled — the same three words as the legacy frame: `allowed` / `denied` = a decision; `expired` = a non-human ending (the reason is in `cause`).'
19071
+ cause:
19072
+ allOf: [{ $ref: '#/components/schemas/ApprovalSettleCause' }]
19073
+ description: >-
19074
+ The non-human ending''s cause (present only on `outcome: "expired"`; the fourteen words of the approval seat''s ending-cause set, the same word as on the audit settle row). A human answer carries none.
19075
+ ABSENCE IS NOT EVIDENCE.
19076
+ parked:
19077
+ type: boolean
19078
+ enum: [true]
19079
+ description: 'TRUE-ONLY: this ask itself ended on the park route (row moved to PARKING / PARKED; a late answer is reachable). Absent = no evidence — NEVER read as a real denial.'
19080
+ serverNowMs: { type: integer, description: 'Server clock at mint time.' }
19081
+
18370
19082
  ApprovalRevokeFrame:
18371
19083
  type: object
18372
19084
  description: >
@@ -18666,7 +19378,7 @@ components:
18666
19378
  description: >
18667
19379
  server >= 7.126.0 (sdk 21.1.0 `APPROVAL_DECIDE_GATE_KINDS`, verbatim and in order; single owner = server
18668
19380
  `APPROVAL_GATE_KINDS`) — the parked gate kinds the DURABLE decision door `POST /v1/approvals/{sessionId}/decide`
18669
- accepts (the live `POST /v1/tool-approvals/{approvalId}/respond` answers the same card family). A parked row of
19381
+ accepts (the live `POST /v1/tool-approvals/{id}/respond` answers the same card family). A parked row of
18670
19382
  kind `plan_review` goes to the plan-review door instead (`PlanReviewGateKind`); `resource_limit` / `needs_review` /
18671
19383
  `task_done` have no human decision door (resume / terminal). Clients route a pending row by reading this set,
18672
19384
  never by a hand-written copy of it.
@@ -18681,7 +19393,7 @@ components:
18681
19393
  enum: [person, policy]
18682
19394
  description: >
18683
19395
  server >= 7.118.0 (`APPROVAL_DECIDED_BY`, verbatim and in order) — WHO made an approval decision, one key on all three decision
18684
- doors (live `POST /v1/tool-approvals/{approvalId}/respond`, durable `POST /v1/tasks/{taskId}/asks/{askId}/decision`, durable
19396
+ doors (live `POST /v1/tool-approvals/{id}/respond`, durable `POST /v1/tasks/{taskId}/asks/{askId}/decision`, durable
18685
19397
  `POST /v1/approvals/{sessionId}/decide`). `person` = a person decided through the approval channel (ABSENT means this);
18686
19398
  `policy` = this deployment's own policy decided (a rule / posture answering on someone's behalf, a headless lane) — BOTH
18687
19399
  directions: the engine records `tool_end.gate.settlement.kind` = `policy_allowed` / `policy_refused` (`who.party: "policy"`).
@@ -18773,6 +19485,20 @@ components:
18773
19485
  `updatedInputForwarded` this field IS persisted, so an idempotent replay DOES promise byte-identity.
18774
19486
  The 409 `conflict.ask_decided` body echoes the same field — and there it is always the FIRST
18775
19487
  decision's note, not the one the rejected request carried.
19488
+ noteRecorded:
19489
+ type: boolean
19490
+ description: >
19491
+ server >= 7.133.0, ADDITIVE: did the `note` THIS request carried land as the row's text? Present ONLY when the request carried a `note`. `true` <=> the row's `decisionNote` is
19492
+ verbatim the text this request submitted (text that happens to be identical counts even if someone else wrote it first); `false` = not confirmed (the row holds another reason /
19493
+ the store could not be read back). Same shape and meaning as `noteRecorded` on the live respond ack and on its replayed arm (one judgement for the three doors). Carried by the
19494
+ first-decision and idempotent-replay forms; the PARKED redemption leg does not carry it (read it as unknown). ABSENT is not `false`.
19495
+ consumerState:
19496
+ type: string
19497
+ enum: [absent]
19498
+ description: >
19499
+ TRUE-ONLY disclosure (present as `absent`): no engine leg on THIS replica is waiting for this ask — the decision is on the row (an idempotent replay will read it, the audit surface keeps it)
19500
+ but nothing running here resumes because of it (the holder of the suspended promise may be another replica, or this replica restarted and its local window is empty). Read the RUN surface to
19501
+ learn whether anything continued; NEVER take a 200 without this key as proof the run continued (older servers never send it). Not a 409 / 410: the decision DID take effect.
18776
19502
 
18777
19503
  QuestionFrame:
18778
19504
  type: object
@@ -19067,6 +19793,31 @@ components:
19067
19793
  outcome: { type: string }
19068
19794
  trigger: { type: string, description: 'engine-shaped; current emitters send auto | manual | forced' }
19069
19795
  reason: { type: string }
19796
+ pass:
19797
+ type: string
19798
+ description: >-
19799
+ server >= 7.132.0 (core 7.53.0 #2008): the id of the pass this frame CONCLUDES (= its `compaction_started.pass`). PRESENT <=> that pass announced a start,
19800
+ so only on `failed` / `mooted` (a pass that ran its summary and did not land, or was cut while it ran — `mooted` with `reason: "task_ending"` = the task's
19801
+ own end cut the running summary, a form this frame closes since core 7.53.0). `blocked` / `suppressed` / `disabled` / `noop` and a manual request no
19802
+ boundary served carry none (those passes never started).
19803
+ eventId: { type: string }
19804
+ parentToolCallId: { type: string }
19805
+ Event_compaction_started:
19806
+ type: object
19807
+ description: >
19808
+ server >= 7.132.0 (core 7.53.0 #2008) — a compaction pass is UNDER WAY: announced at the pass's commit point (trigger gate passed, a cut point exists,
19809
+ `preCompact` did not block), BEFORE the summary call, so a summary waiting out a provider's retries is no longer silent on the stream. Projected verbatim by
19810
+ ONE whitelist builder (server `compactionStartedEventData`) on the live stream and both durable ledgers (bg / resume; `GET /v1/runs/{id}/events`); the
19811
+ §2.3 trace stream does not carry it. PAIRING: exactly one frame concludes the pass with the SAME `pass` — `compacted` when it lands,
19812
+ `compaction_outcome` (`failed` / `mooted`) when it does not; "compacting" runs from this frame to that one (or the stream's end), elapsed = now − startedAt.
19813
+ A segment boundary, like `compacted` / `compaction_outcome`. Servers <= 7.131.x never send it. Identity: the same TWO keys as `compaction_outcome`.
19814
+ additionalProperties: false # 封闭:铸造点 `compactionStartedEventData` 只投这三键 + 身份两键
19815
+ required: [type, trigger, pass, startedAt]
19816
+ properties:
19817
+ type: { const: compaction_started }
19818
+ trigger: { type: string, description: 'the same vocabulary (and the same value) as the frame that concludes the pass — engine-shaped, read open; today auto | manual | forced.' }
19819
+ pass: { type: string, description: 'the pass identity: an opaque engine-minted id, unique across runs — pair on it alone.' }
19820
+ startedAt: { type: integer, description: 'engine clock, epoch ms, at the moment the pass committed.' }
19070
19821
  eventId: { type: string }
19071
19822
  parentToolCallId: { type: string }
19072
19823
  Event_config_assembled:
@@ -19282,7 +20033,7 @@ components:
19282
20033
  Event_tool_approval:
19283
20034
  description: >
19284
20035
  DURABLE-leg replay of the legacy tool-approval OPEN frame. Same payload as ToolApprovalFrame with the
19285
- discriminant narrowed to `tool_approval`. Settle it via POST /v1/tool-approvals/{approvalId}/respond.
20036
+ discriminant narrowed to `tool_approval`. Settle it via POST /v1/tool-approvals/{id}/respond.
19286
20037
  Anchor the card on `toolCallId` (NOT `approvalId`); render `governanceForced` as a non-bypassable
19287
20038
  "forced by governance" badge. UNTRUSTED for display: `message`/`args` are redacted but model-authored.
19288
20039
  allOf:
@@ -19305,9 +20056,11 @@ components:
19305
20056
  type: { type: string, enum: [tool_approval_complete] }
19306
20057
  Event_approval_request:
19307
20058
  description: >
19308
- The design/172 in-stream approval card as an AgentEvent arm. PARALLEL to `tool_approval`, not a
19309
- replacement: with the protocol on, one ask emits BOTH frames (legacy first) carrying the SAME
19310
- `approvalId` — dedupe on it and prefer this arm. The decision key is `askId`; settle via
20059
+ The design/172 in-stream approval card as an AgentEvent arm. ONE FRAME FAMILY PER LEG (server >= 7.133.0,
20060
+ `capabilities.approvalFrameFamilies`): on a leg where the card protocol is on, one ask emits only this family
20061
+ (`approval_request` -> `approval_complete` / `approval_revoke`) and none of the legacy `tool_approval` family,
20062
+ so there is nothing to deduplicate. On server <= 7.132.x the two families ran side by side (legacy first) carrying
20063
+ the SAME `approvalId` — there, dedupe on it and prefer this arm. The decision key is `askId`; settle via
19311
20064
  POST /v1/tasks/{taskId}/asks/{askId}/decision.
19312
20065
 
19313
20066
  🔴 THIS ARM IS THE OPEN ENVELOPE, NOT `ApprovalRequestFrame`, AND THAT IS DELIBERATE. The v1 frame
@@ -19354,6 +20107,37 @@ components:
19354
20107
  properties:
19355
20108
  type: { type: string, enum: [approval_revoke] }
19356
20109
 
20110
+ Event_approval_complete:
20111
+ description: >
20112
+ The card family's single-card CLOSE frame as an AgentEvent arm (server >= 7.133.0). One frame family per leg: with `capabilities.approvalFrameFamilies` = `["approval_request"]` this frame
20113
+ REPLACES the legacy `tool_approval_complete` (one ask no longer travels two families). Live SSE and the durable ledger replay carry the same shape (same settle, same verdict: `outcome`,
20114
+ non-human `cause`, `parked`).
20115
+ 🔴 THIS ARM IS THE OPEN ENVELOPE, NOT `ApprovalCompleteFrame` — same ruling as Event_approval_request / Event_approval_revoke: narrow with `schemaVersion == 1` (SDK:
20116
+ `isApprovalCompleteFrameV1`) before touching `outcome` / `cause` / `parked`; a frame that does not narrow still dismisses the card by its `askId` (when it has one) — never guess the outcome.
20117
+ 🔴 Consumption discipline: (a) dismiss by the opening frame's `askId`; (b) a ledger-replayed frame is for TIMELINE RENDERING ONLY — the card-set baseline stays the open-stream preamble;
20118
+ (c) NEVER derive the run's fate from this frame (read the run row / checkpoint face); (d) it does not conflict with ApprovalRevokeFrame — one ask can receive both.
20119
+ allOf:
20120
+ - $ref: '#/components/schemas/ApprovalFrameEnvelope'
20121
+ - type: object
20122
+ required: [type]
20123
+ properties:
20124
+ type: { type: string, enum: [approval_complete] }
20125
+ Event_session_titled:
20126
+ description: >
20127
+ server >= 7.133.0 — the session title was WRITTEN (contract §4a-ttl): the title generator wrote the title into the session row and minted this frame on THE RUN'S LIVE STREAM afterwards.
20128
+ 🔴 LIVE-ONLY, NEVER STORED: the session row (`GET /v1/sessions` list row `title`) is the single source of the title; this frame only says "the row just changed". It is not ledgered
20129
+ (GET /v1/runs/:id/events replay never carries it), not part of any replay surface, and has NO `eventId` (no SSE `id:` line, no part in the `Last-Event-ID` cursor). Only a leg holding a live stream
20130
+ receives it (today `POST /v1/tasks/stream`); the sync-JSON leg and the durable background leg structurally cannot. NOT sent when the session already has a title, on a user rename / pin
20131
+ (`PATCH /v1/sessions/{id}` does not go through the generator), with `SESSION_AUTO_TITLE=false`, after `done`, or once the connection is gone. THE FRAME MAY NOT ARRIVE — the title is whatever
20132
+ the session row says: refresh on the frame, read the row on cold start / another window / reconnect. `title` has passed the generator's redaction and cleaning and is the SAME value as the row's
20133
+ (still UNTRUSTED for display — model-authored text).
20134
+ type: object
20135
+ required: [type, sessionId, title]
20136
+ additionalProperties: false
20137
+ properties:
20138
+ type: { type: string, enum: [session_titled] }
20139
+ sessionId: { type: string, description: 'The session whose row was just written.' }
20140
+ title: { type: string, description: 'The title that landed in the session row (redacted and cleaned; bounded by the server''s TITLE_MAX_CHARS).' }
19357
20141
  # ── resources/*.ts schema backfill, batch 2 (2026-07-28) — the 28-item WIRE_PENDING ratchet zeroed.
19358
20142
  # Each schema below is cross-checked against the server source (file:line cited in its description
19359
20143
  # or in the SDK resource JSDoc it mirrors). Wire objects are OPEN sets (additionalProperties: true).
@@ -19378,6 +20162,10 @@ components:
19378
20162
  sessionId: { type: string, description: '`resolved` frames: which pending settled.' }
19379
20163
  toolCallId: { type: string, description: '`resolved` frames: the settled pending''s bound tool-call id (absent when the pending carried none).' }
19380
20164
  count: { type: integer, description: '`synced` frames: how many pendings the connect-time snapshot carried.' }
20165
+ checkpointId: { type: string, description: '`pending` frames only (server >= 7.133.0): the spread PendingCheckpoint row''s `checkpointId` (key absent when the row has no trusted value — read as "no joinable identity").' }
20166
+ consumerState: { type: string, enum: [absent], description: '`live_pending` frames only (server >= 7.133.0): the spread LivePendingRow''s true-only `consumerState` (no engine leg on this replica is waiting for the ask).' }
20167
+ errorCode: { type: string, description: '`error` frames: the machine code (`STREAM_MAX_DURATION` / `draining` / server >= 7.133.0 `approvals.read_unavailable` — the stream STAYS open, do NOT reconnect on it — / `approvals.read_corrupt` — a corrupt row was read, the server closes the stream after the frame).' }
20168
+ message: { type: string, description: '`error` frames: the human sentence (redacted); never match on it.' }
19381
20169
  hasBidiControls:
19382
20170
  type: boolean
19383
20171
  enum: [true]
@@ -19498,20 +20286,24 @@ components:
19498
20286
 
19499
20287
  FleetHookNotice:
19500
20288
  description: >
19501
- Hook observability payload, TWO kinds (service fleet-bus.ts `HookNotice` minus the wire-stripped
20289
+ Hook observability payload, FOUR kinds (server >= 7.133.0 added `hook_managed_unrun` and `hook_verdict_untrusted`; service fleet-bus.ts `HookNotice` minus the wire-stripped
19502
20290
  ownerScope/ownerSessionId — the payload view of FleetFrame_hook_notice; a discriminated union on
19503
20291
  `kind`, mirroring the SDK type verbatim). Both kinds are pure observe — never block, resume, or
19504
20292
  inject. Visibility is fail-CLOSED: a ?session= subscriber only receives notices provably owned by
19505
- that session. `kind` is a CLOSED two-member set on the server (exhaustive-switch discipline) but
20293
+ that session. `kind` is a CLOSED four-member set on the server (exhaustive-switch discipline) but
19506
20294
  OPEN ON READ: degrade an unknown future kind to a generic observability row, never crash.
19507
20295
  oneOf:
19508
20296
  - $ref: '#/components/schemas/FleetHookDecisionUnavailable'
19509
20297
  - $ref: '#/components/schemas/FleetHookNonBlockingFailure'
20298
+ - $ref: '#/components/schemas/FleetHookManagedUnrun'
20299
+ - $ref: '#/components/schemas/FleetHookVerdictUntrusted'
19510
20300
  discriminator:
19511
20301
  propertyName: kind
19512
20302
  mapping:
19513
20303
  hook_decision_unavailable: '#/components/schemas/FleetHookDecisionUnavailable'
19514
20304
  hook_non_blocking_failure: '#/components/schemas/FleetHookNonBlockingFailure'
20305
+ hook_managed_unrun: '#/components/schemas/FleetHookManagedUnrun'
20306
+ hook_verdict_untrusted: '#/components/schemas/FleetHookVerdictUntrusted'
19515
20307
 
19516
20308
  FleetHookDecisionUnavailable:
19517
20309
  type: object
@@ -19546,7 +20338,7 @@ components:
19546
20338
  reason:
19547
20339
  # 🔴 NO `enum` — same open-on-read ruling as kind 1 (SDK type is `... | (string & {})`).
19548
20340
  type: string
19549
- description: 'Machine-branchable failure shape. CLOSED server-side, OPEN ON READ — KNOWN WORDS TODAY: exit_nonzero | timeout | spawn_failed | bad_json.'
20341
+ description: 'Machine-branchable failure shape. CLOSED server-side, OPEN ON READ — KNOWN WORDS TODAY: exit_nonzero | timeout | spawn_failed | bad_json | matcher_over_budget (server >= 7.133.0: the group matcher / the entry `if` name part did not finish evaluating within its 1000 ms budget on this name — NOT a broken hook script but an unevaluable selection condition; the server treats it as selected, the entry still runs; ONE frame per group, `if` one per entry, no exitCode / stderr).'
19550
20342
  hookName: { type: string, description: 'Hook identity (statusMessage, else command/URL/prompt; redacted + bounded, http entries stripped of query/fragment). Always sent.' }
19551
20343
  entryType: { type: string, description: 'command | http | prompt | agent — without it, hookName''s nature is a guess. Closed server-side, open on read. Always sent.' }
19552
20344
  exitCode: { type: integer, description: 'Absent for timeout/spawn_failed (no exit code exists — the server never mints a legit-looking 0).' }
@@ -19554,6 +20346,57 @@ components:
19554
20346
  stderr: { type: string, description: 'Redacted + truncated stderr digest (512-char cap incl. marker). Empty stderr => absent.' }
19555
20347
  detail: { type: string, description: 'Redacted supplement (e.g. where an exit-2 had no venue). Additive / tolerate-absent.' }
19556
20348
 
20349
+ FleetHookManagedUnrun:
20350
+ type: object
20351
+ description: >
20352
+ Kind 3 (server >= 7.133.0): a MANAGED hook did not complete — the group carries a `source`; managed groups (`managedPolicy` / `managedPlugin:<name>`) are the organization policy's guard and run in an independent lane
20353
+ after the unmanaged rewrites, on the final input. A synchronous managed entry that gave neither a legal verdict (exit 0 with no verdict / a closed-set verdict) nor a legal veto (exit 2 / `decision:"block"` /
20354
+ `continue:false`) is "unrun": on `PreToolUse` the conclusion folds to `ask` when nothing vetoed (a person in the loop; unmanaged allows are not trusted; any phase's veto is still the last vote), on other events the
20355
+ conclusion is unchanged — but this frame is ALWAYS sent. One frame per unrun managed entry. The frame does NOT assert how the call ended (read the tool / approval frames).
20356
+ required: [kind, event, reason, source, hookName, entryType]
20357
+ additionalProperties: true
20358
+ properties:
20359
+ kind: { type: string, enum: [hook_managed_unrun] }
20360
+ event: { type: string, description: 'Which hook event. On PreToolUse this entry sets the managed lane: with no veto the call folds to `ask`.' }
20361
+ reason:
20362
+ # NO `enum` — same open-on-read ruling as kinds 1 and 2.
20363
+ type: string
20364
+ description: >-
20365
+ Why it did not complete. CLOSED on the server (thirteen words), OPEN ON READ — KNOWN WORDS TODAY: matcher_invalid | llm_carrier_unavailable | llm_cap | powershell_unsupported | if_unevaluable | budget_exhausted |
20366
+ spawn_failed | timeout | exit_nonzero | bad_json | decision_out_of_set | evidence_unavailable | rewrite_unsettled. `input_truncated` / `matcher_over_budget` are NOT in this kind (they belong to kind 4).
20367
+ source: { type: string, description: 'The group''s source word (`managedPolicy` or `managedPlugin:<name>`; the name is trace only, never a criterion).' }
20368
+ hookName: { type: string, description: 'Hook identity (same rule as kind 2); redacted + bounded.' }
20369
+ entryType: { type: string, description: 'command | http | prompt | agent — closed server-side, open on read.' }
20370
+ toolName: { type: string, description: 'Present on tool events.' }
20371
+ detail: { type: string, description: 'Redacted supplement.' }
20372
+
20373
+ FleetHookVerdictUntrusted:
20374
+ type: object
20375
+ description: >
20376
+ Kind 4 (server >= 7.133.0): a hook's VERDICT IS UNTRUSTED — one rule: an entry that did not get the complete payload, or was pulled in BY SELECTION by an over-budget matcher, has its VETO count (deny / exit 2 /
20377
+ `decision:"block"` / `continue:false` / ask) and its ALLOW and `updatedInput` NOT count. The rule holds on both lanes, so there is one frame; reason, lane and identity are machine fields. Emission condition per lane:
20378
+ managed = the entry ran with an untrusted cause (sent whatever it judged; `PreToolUse` then folds to `ask` when nothing vetoed); unmanaged = it gave an allow or a real rewrite (an identity echo does not count) that was
20379
+ dropped. Today only on `PreToolUse` (the event with an allow word). THE FRAME DOES NOT ASSERT HOW THE CALL ENDED: a managed entry that itself vetoes ends in deny with the frame sent; another trusted allow in the group still
20380
+ lets the call through — read the call's own result. NEVER infer a call outcome from any hook_notice frame.
20381
+ required: [kind, event, reason, lane, hookName, entryType]
20382
+ additionalProperties: true
20383
+ properties:
20384
+ kind: { type: string, enum: [hook_verdict_untrusted] }
20385
+ event: { type: string, description: 'Which hook event (today always PreToolUse).' }
20386
+ reason:
20387
+ # NO `enum` — same open-on-read ruling as kinds 1–3.
20388
+ type: string
20389
+ description: >-
20390
+ Why the verdict is untrusted. CLOSED on the server (two words), OPEN ON READ — KNOWN WORDS TODAY: input_truncated (the entry did not get the full payload: `tool_input` over the cap was replaced by a truncation marker, or a prompt /
20391
+ agent entry''s whole payload text was cut at the same cap) | matcher_over_budget (the group matcher / entry `if` name part ran past its budget and the entry ran BY SELECTION; the same moment also sends a kind-2 frame
20392
+ `matcher_over_budget`).
20393
+ lane: { type: string, description: 'The lane the entry sits in, read off the source table: `managedPolicy` / `managedPlugin:*` => `managed`, everything else and absent => `unmanaged`. Closed (two words) server-side, open on read. Read THIS field, never re-derive from `source`.' }
20394
+ hookName: { type: string, description: 'Hook identity (same rule as kind 2); redacted + bounded.' }
20395
+ entryType: { type: string, description: 'command | http | prompt | agent — closed server-side, open on read.' }
20396
+ toolName: { type: string, description: 'Present on tool events.' }
20397
+ source: { type: string, description: 'The source word the group declared (both lanes give it; ABSENT = the group declared none, so only unmanaged). Trace only, never a criterion.' }
20398
+ detail: { type: string, description: 'Redacted supplement (human-facing; machines read `reason` / `lane`).' }
20399
+
19557
20400
  BakeStatus:
19558
20401
  # 新(census 批2 四段)——server `BakeStatus`(plugins/store-contracts.ts), the COARSE lifecycle
19559
20402
  # bakeView/claimResponse/bakesCreate all key on.
@@ -19874,7 +20717,7 @@ components:
19874
20717
  description: >
19875
20718
  🔴 ONE RULE FOR EVERY "EFFECTIVE HALF ONLY" KEY (sdk 9.7.0, after a codex adversarial-review [high]):
19876
20719
  this schema's only consumer today is `GET /v1/diagnostics/wiring`'s `static` half, and every key marked
19877
- "effective half only" (`leg`, `ask.effective`, `configFingerprint`, `tools`, `hooks`, `lsp`,
20720
+ "effective half only" (`leg`, `ask.effective`, `configFingerprint`, `tools`, `mods`, `lsp`,
19878
20721
  `modelGate`, `autoMode`, `writeProtection`, `readDeny`, `autoConsolidation`, `fileHistory`, `instructions`) is ALWAYS ABSENT on that
19879
20722
  wire ⇒ THEIR ABSENCE HERE CARRIES NO INFORMATION. The per-key absence readings ("absent = not armed",
19880
20723
  "absent = a third state", …) hold for the LIVE FRAME (`Event_wiring_manifest`) and must not be carried
@@ -20020,14 +20863,12 @@ components:
20020
20863
  properties:
20021
20864
  armed: { type: boolean }
20022
20865
  reason: { type: string }
20023
- hooks:
20866
+ mods:
20024
20867
  type: array
20025
20868
  description: >
20026
- core 7.18.0 (#789), server >= 7.78.0 — the hook seats THIS leg wired, one row per family.
20027
- EFFECTIVE half only (the diagnostics endpoint's static half never carries it). Present iff at least
20028
- one seat is wired; absence means "no hook RECORD of this leg", never "no hook runs".
20029
- Full per-key contract: see Event_wiring_manifest.hooks.
20030
- items: { $ref: '#/components/schemas/WiringManifestHookEntry' }
20869
+ core 7.52.0, server >= 7.132.0 (BREAKING — replaces `hooks[]`) — the leg's mods admission record, one row per registration. EFFECTIVE half only (the
20870
+ diagnostics endpoint's static half never carries it). Full per-key contract: see Event_wiring_manifest.mods.
20871
+ items: { $ref: '#/components/schemas/WiringManifestModEntry' }
20031
20872
  lsp:
20032
20873
  allOf: [{ $ref: '#/components/schemas/WiringManifestLspSeam' }]
20033
20874
  description: >
@@ -20065,6 +20906,11 @@ components:
20065
20906
  K-24 (server >= 7.104.0 / S-724) — does this leg capture file history (`on` / `disabled`, OPEN word). EFFECTIVE
20066
20907
  HALF ONLY ⇒ ALWAYS ABSENT ON THIS ENDPOINT; the deployment-level reading is `capabilities.fileHistoryCapture`.
20067
20908
  Full per-key contract: see Event_wiring_manifest.fileHistory.
20909
+ permissionMode:
20910
+ allOf: [{ $ref: '#/components/schemas/WiringManifestPermissionMode' }]
20911
+ description: >-
20912
+ core 7.53.0, server >= 7.133.0 — the leg's permission-mode receipt (section shape WiringManifestPermissionMode). EFFECTIVE HALF ONLY ⇒ this (static) face NEVER carries it, and absence here carries no
20913
+ information; the reading holds only on a live `wiring_manifest` frame.
20068
20914
  instructions:
20069
20915
  allOf: [{ $ref: '#/components/schemas/WiringManifestInstructions' }]
20070
20916
  description: >
@@ -20136,33 +20982,70 @@ components:
20136
20982
  type: array
20137
20983
  description: 'The selected chain member(s), spelled as the chain spells them (relative to the cwd); `[]` = the chain was walked, nothing was selected.'
20138
20984
  items: { type: string, enum: ['CLAUDE.md', '.claude/CLAUDE.md', 'AGENTS.md', '.claude/AGENTS.md'] }
20139
- WiringManifestHookEntry:
20140
- # sdk 9.5.0:与 `WiringManifestMcpEntry` 同一条裁定 —— 同一份行形被 live 帧与静态诊断面两处引用,
20141
- # 内联就是两份会各自漂的镜像,所以一开始就具名。
20985
+ WiringManifestPermissionMode:
20986
+ # server 7.133.0 / core 7.53.0:同一段形被 live 帧(两张脸)、静态诊断面、`POST /v1/tasks` 的 `wiringManifest` 回执与账本行引用 ⇒ 具名单源(`spec-field-drift-gate` 也按「导出的 interface 必有同名 schema」逼出它)。
20987
+ type: object
20988
+ additionalProperties: false
20989
+ description: >
20990
+ The `wiring_manifest.permissionMode` section (core 7.53.0 `PermissionModeReceipt`; server >= 7.133.0 projects it on BOTH faces — tenant and operator — and the same section rides the `wiringManifest`
20991
+ receipt of the `POST /v1/tasks` 200 body and the `/events` ledger row; the server judges SHAPE only: `effective` a non-empty string, `requested` / `foldedBy` absent or non-empty strings, else the whole section
20992
+ is dropped). The engine gives the same three-member receipt on EVERY mode question: `requested` = the mode word the caller declared (ABSENT = declared nothing — a root inherits `default`, a child inherits its
20993
+ parent); `effective` = the mode the leg actually runs under (the request clamped under a parent's ceiling, then folded to `default` where the deployment denies it or the classifier face is absent) — ALWAYS present,
20994
+ the shell renders its mode indicator from it; `foldedBy` = PRESENT IFF `requested` and `effective` differ — `operator_deny` (the deployment / organization ceiling denies the mode) / `face_absent` (`auto` needs
20995
+ a classifier face this leg lacks) / `inherited_ceiling` (a delegated child clamped under its parent's ceiling); read OPEN (a fourth word passes through as "folded, cause unknown"). ABSENT section = an older
20996
+ engine / server (<= 7.132.x), not an assertion of the default mode. This is ONE added key on a closed object: a consumer validating `wiring_manifest` strictly against sdk <= 24.0.0's spec fails the whole frame
20997
+ on 7.133.0+ — upgrade to sdk 25.0.0 or relax unknown keys on this section.
20998
+ required: [effective]
20999
+ properties:
21000
+ requested: { type: string, description: 'The mode word the caller declared (a PermissionMode word today; read open). ABSENT = declared nothing.' }
21001
+ effective: { type: string, description: 'The mode the leg actually runs under (a PermissionMode word today; read open). Always present.' }
21002
+ foldedBy: { type: string, description: 'Present iff `requested` and `effective` differ: `operator_deny` / `face_absent` / `inherited_ceiling` (engine words; read OPEN — an unknown word means "folded, cause unknown").' }
21003
+ WiringManifestModEntry:
21004
+ # server 7.132.0 / core 7.52.0:取代钩子席位行形(删,零别名)。同一份行形被 live 帧与静态诊断面两处引用 ⇒ 具名;两形各自具名(⑤bis 名单逐形对账)。
21005
+ description: >
21006
+ One row of a leg's MODS ADMISSION record (core 7.52.0 `ModAdmissionRecord`; server >= 7.132.0): a registration that was mounted, or one that was refused.
21007
+ Discriminate on presence — a mounted row carries `events`, a refused row carries `refusedBy` + `reason` (never both).
21008
+ oneOf:
21009
+ - $ref: '#/components/schemas/WiringManifestModMounted'
21010
+ - $ref: '#/components/schemas/WiringManifestModRefused'
21011
+ WiringManifestModMounted:
20142
21012
  type: object
20143
21013
  description: >
20144
- One row of a leg's HOOK seat manifest (core 7.18.0 #789): which family, where the seat came from, and
20145
- whether the `preToolUse` row was DECLARED observational.
21014
+ A MOUNTED registration: the patterns it actually hung (`events`), the ones it declared observe-only (`observe`, present iff any), and the judges skipped while it
21015
+ was judged (`skipped`, present iff non-empty — absence and an empty list are both "every judge answered").
20146
21016
  additionalProperties: false
20147
- required: [family, owner]
21017
+ required: [name, seat, tier, events]
20148
21018
  properties:
20149
- family:
20150
- type: string
20151
- description: >
20152
- The hook family. core's closed vocabulary today (`preToolUse` / `postToolUse` / `userPromptSubmit` /
20153
- `stop` / `postToolUseFailure` / `postToolBatch` / `preCompact` / `postCompact` / `stopFailure` /
20154
- `permissionDenied`) but READ AS OPEN: the server passes it through verbatim and does not enumerate,
20155
- so a newly minted family must pass through your default arm.
20156
- owner:
20157
- type: string
20158
- description: "Where the seat came from — core's `SeamProvenance` (`spec` / `deps`). Closed today, read as open."
20159
- observational:
20160
- type: boolean
20161
- enum: [true]
20162
- description: >
20163
- Only on the `preToolUse` row, and only when the deployment DECLARED the face observational: it still
20164
- runs on every call but is not folded into a delegated child's inherited constraints.
20165
- 🔴 NEVER-FALSE — core's words: "Absence means 'not declared', never 'declared false'".
21019
+ name: { type: string, description: 'the registration name (registrant text, through the server''s wire-text exit; UNTRUSTED-for-display).' }
21020
+ seat: { type: string, description: 'which seat it came from — core `MOD_SEATS` (`deployment` / `task`). Engine word, passed through verbatim; read open.' }
21021
+ tier: { type: string, description: 'its tier — core `MOD_TIERS` (`prepend` / `user` / `append` / `builtin` / `core`). Engine word, passed through verbatim; read open.' }
21022
+ events: { type: array, items: { type: string }, description: 'the event patterns it actually hung (engine words, verbatim).' }
21023
+ observe: { type: array, items: { type: string }, description: 'the patterns it hung observe-only (engine words, verbatim); absent = none.' }
21024
+ skipped:
21025
+ type: array
21026
+ description: 'judges the dispatch skipped while this registration was judged (core `ModSkip`); present iff non-empty. An element missing a member is dropped individually.'
21027
+ items:
21028
+ type: object
21029
+ additionalProperties: false
21030
+ required: [registration, cause, reason]
21031
+ properties:
21032
+ registration: { type: string, description: 'the skipped judge''s registration name (registrant text).' }
21033
+ cause: { type: string, description: 'why it did not answer for itself — core `MOD_FAILURE_CAUSES` today (`threw` / `timeout` / `violation` / `reentry`); read open.' }
21034
+ reason: { type: string, description: 'the judge''s own words (registrant text, UNTRUSTED-for-display).' }
21035
+ WiringManifestModRefused:
21036
+ type: object
21037
+ description: >
21038
+ A REFUSED registration: another registration's `plugin.register` judge refused it at admission; the leg runs without it. The same refusal also reaches
21039
+ the stream as the `engine_notice` `mods.registration_refused` (its `detail` strings read byte-equal with this row's). This deployment wires no
21040
+ `plugin.register` judge, so it cannot happen here today.
21041
+ additionalProperties: false
21042
+ required: [name, seat, tier, refusedBy, reason]
21043
+ properties:
21044
+ name: { type: string, description: 'the refused registration (registrant text).' }
21045
+ seat: { type: string, description: 'core `MOD_SEATS` word, verbatim; read open.' }
21046
+ tier: { type: string, description: 'core `MOD_TIERS` word, verbatim; read open.' }
21047
+ refusedBy: { type: string, description: 'the registration whose judge refused it (registrant text).' }
21048
+ reason: { type: string, description: 'the refusing judge''s own words (registrant text, UNTRUSTED-for-display).' }
20166
21049
  WiringManifestLspSeam:
20167
21050
  # sdk 9.5.0:同上 —— live 帧与静态面共用一份段形。
20168
21051
  type: object
@@ -20339,7 +21222,9 @@ components:
20339
21222
  description: >
20340
21223
  server >= 7.60 (S-131) — this deployment's SQL engine posture (engine / version / session isolation
20341
21224
  level / TiDB transaction mode), READ BACK off a live connection rather than echoed from env.
20342
- `null` = no SQL backend. NOT YET MIRRORED KEY-BY-KEY in this SDK generation — read as opaque.
21225
+ `null` = no SQL backend. server >= 7.132.0 adds three keys (additive): `maxRowBytes` / `maxPacketBytes` (the database's single-row / packet
21226
+ wall; `null` = that wall does not exist) and `wallSources` (each wall's provenance, `reported` / `assumed` — what a 413 body's `wallSource` reads).
21227
+ NOT YET MIRRORED KEY-BY-KEY in this SDK generation — read as opaque.
20343
21228
  configApply:
20344
21229
  type: ["object", "null"]
20345
21230
  additionalProperties: true
@@ -20644,10 +21529,218 @@ components:
20644
21529
  remember for this session's category (fs-write tools share ONE category: approving Write covers
20645
21530
  Edit/NotebookEdit; other tools are per-tool) — the CC "don't ask again this session" semantic.
20646
21531
 
21532
+ ConfigCatalog:
21533
+ type: object
21534
+ description: >
21535
+ The 200 body of GET /v1/config/catalog (server `ConfigCatalogWire`) — a PROJECTION, left open on read (the producer ships independently and adds keys additively between SDK releases; the request side has no body).
21536
+ required: [catalogVersion, envLaneTiming, restartSlices, centerDomains, summary, rows, retired]
21537
+ additionalProperties: true
21538
+ properties:
21539
+ catalogVersion: { type: integer, enum: [1] }
21540
+ envLaneTiming: { type: string, description: 'The env lane''s uniform timing (`deploy`: env is immutable at runtime, so changing env = changing the deployment). Open on read.' }
21541
+ restartSlices: { type: array, items: { type: string }, description: 'The restart-slice names a centre write can land in (open on read).' }
21542
+ centerDomains: { type: array, items: { type: string }, description: 'The contract package''s centre-configuration domains.' }
21543
+ summary:
21544
+ type: object
21545
+ required: [total, envHeld, secret, centerManaged]
21546
+ additionalProperties: true
21547
+ properties:
21548
+ total: { type: integer }
21549
+ envHeld: { type: integer }
21550
+ secret: { type: integer }
21551
+ centerManaged: { type: integer }
21552
+ rows: { type: array, items: { $ref: '#/components/schemas/ConfigCatalogRow' } }
21553
+ retired: { type: array, items: { $ref: '#/components/schemas/ConfigCatalogRetiredEnv' }, description: 'Tombstoned env names this process refuses to start on (always present, may be empty). A name is never in `rows` and here at once.' }
21554
+ ConfigCatalogRow:
21555
+ type: object
21556
+ description: >
21557
+ One catalog row (server `ConfigCatalogRow`): a deployment knob's static declaration plus this process's effective reading. OPEN on read (projection). The two VERDICT bits are the true-only family:
21558
+ `effectiveValueInvalid` (the effective value fell outside the row's echo set; `effectiveValue` is `null`) and `effectiveValueWithheld` (server >= 7.133.0) — see the property.
21559
+ required: [name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, effectiveValue, valueClass, danger]
21560
+ additionalProperties: true
21561
+ properties:
21562
+ name: { type: string, description: 'The knob (env) name; the catalog is sorted by it.' }
21563
+ domain: { type: string, description: 'One of the nine configuration groups (store / modelPlane / approval / memory / auth / orchestration / limitsHttp / observability / integrations). Open on read.' }
21564
+ type: { type: string, description: 'Value type: boolean | number | string | enum | csv | json | path | url | secret | opaque. Open on read.' }
21565
+ semantics: { type: string, description: 'Human sentence (true to the read site).' }
21566
+ writeLanes: { type: array, items: { type: string, enum: [env, center] }, description: 'Which lanes can write this knob.' }
21567
+ effectiveLane: { type: string, description: 'The lane actually in force: env | center | default. Open on read.' }
21568
+ envHeld: { type: boolean, description: 'This machine''s env sets it explicitly.' }
21569
+ envTiming: { type: string, description: '`deploy` (changing env = changing the deployment) | `inert` (retired / zero-consumer key). Open on read.' }
21570
+ staticDefault: { type: string }
21571
+ derivedDefaultNote: { type: string }
21572
+ effectiveValue:
21573
+ description: 'This process''s effective DISPLAY value: a scalar / a word list / `null` (unset) / `{ present, length? }` (`secret` / `opaque` rows report presence and length only).'
21574
+ oneOf:
21575
+ - { type: [string, number, boolean, 'null'] }
21576
+ - { type: array, items: { type: string } }
21577
+ - { type: object, required: [present], additionalProperties: true, properties: { present: { type: boolean }, length: { type: integer } } }
21578
+ valueClass: { type: string, description: '`plain` (echoable) | `secret` | `opaque` (the value never goes on the wire). Open on read.' }
21579
+ danger: { type: array, items: { type: string }, description: 'The risk axes of a wrong setting: security | billing | data (open on read).' }
21580
+ center: { $ref: '#/components/schemas/ConfigCatalogRowCenter' }
21581
+ centerDomain: { type: string }
21582
+ centerPath: { type: string }
21583
+ scope: { type: string, description: '`bootstrap-only` = read only at start-up.' }
21584
+ enumValues: { type: array, items: { type: string }, description: 'An `enum` row''s writable word set.' }
21585
+ effectiveValueInvalid:
21586
+ type: boolean
21587
+ enum: [true]
21588
+ description: 'TRUE-ONLY verdict bit: the effective value fell OUTSIDE the row''s own echo set (or a boolean without a declared mapping) — `effectiveValue` is then `null` (echoing an out-of-set value would teach a consumer to write back an illegal word). Enum rows only. ABSENT = this cell has no such matter.'
21589
+ effectiveValueWithheld:
21590
+ type: boolean
21591
+ enum: [true]
21592
+ description: >-
21593
+ server >= 7.133.0, TRUE-ONLY verdict bit on `type: url` rows (present only when true, never `false`): the display mint judged this cell (single value) or any item of it (list) WITHHELD — a part the position rule
21594
+ would have shown (the whole query / a path segment) was NOT shown because it held a credential-shaped piece. The mint owns the verdict; the catalog does not judge again. So the shown value is not the whole value:
21595
+ a catalog page / `sema config show` marks the row ("part of this value is withheld"), and must not read the shown value as "the value is exactly that". ABSENT = nothing withheld (older servers never send it).
21596
+ The standard position rule (userinfo landed, query values become tokens) holds for every row and is NOT a withholding; a value that became a placeholder as a whole means the row's display rule cannot show it
21597
+ (not a hierarchical URL; the two outbound-proxy rows show only scheme://host[:port]) — also not this bit.
21598
+ ConfigCatalogRowCenter:
21599
+ type: object
21600
+ description: 'A catalog row''s centre seat: whether the knob is centre-publishable, who wins, when it takes effect. Present only on rows with a centre seat.'
21601
+ required: [domain, path, precedence, timing, managedNow, effectiveNow]
21602
+ additionalProperties: true
21603
+ properties:
21604
+ domain: { type: string }
21605
+ path: { type: string, description: 'Path in the domain (`a.b` into objects; `xs[].c` into array elements).' }
21606
+ precedence: { type: string, description: '`center-wins` | `env-wins` (open on read).' }
21607
+ timing: { type: string, description: '`hot` | `restart` | `inert` (open on read; not `deploy`).' }
21608
+ restartSlice: { type: string }
21609
+ restartReason: { type: string }
21610
+ managedNow: { type: boolean, description: 'The centre''s latest effective config currently MANAGES this key (no config pipeline => always false).' }
21611
+ effectiveNow: { type: boolean, description: 'The centre write is in force right now (managed AND not held down by an env placeholder).' }
21612
+ ConfigCatalogRetiredEnv:
21613
+ type: object
21614
+ description: 'A tombstoned env name (the contract package''s RETIRED_ENVS wire projection): a name this process refuses to start on. `renamed` carries only `replacement`; `replaced` carries `replacement` + `reason` (how the value semantics changed); `removed` carries only `reason`.'
21615
+ required: [name, kind]
21616
+ additionalProperties: true
21617
+ properties:
21618
+ name: { type: string }
21619
+ kind: { type: string, description: '`renamed` | `replaced` | `removed` (open on read).' }
21620
+ replacement: { type: string }
21621
+ reason: { type: string }
21622
+ EntryProblem:
21623
+ type: object
21624
+ description: >
21625
+ One ENTRY PROBLEM (server >= 7.133.0): a refusal body's `detail.problems[]` element and an accept-time `warnings[]` element share this shape. `code` = the refusal code the problem belongs to (in `warnings[]`: the engine's
21626
+ reading code for that entry); `field` = the coordinate of the list holding the entry in the request body (`settings.permissions.allow`, `skills[i].allowedTools` …); `entry` = the entry string (passed through the server''s
21627
+ redaction mouth — not a guarantee of your original bytes); `reason` = the machine reason — mostly the ENGINE''s word verbatim (why it read the entry as nothing); the server mints two of its own, `not_in_roster` (accept-time
21628
+ warnings only: the name in an allow-side selector list is not in the roster and is no protocol namespace name — the engine accepts it anyway) and `home_unresolvable` (settings-rule 422: a `~/` path rule on a deployment that cannot
21629
+ resolve the execution environment''s home); and `below_trust_floor` (the caller''s trust tier is below the member''s admission floor — see ErrorResponse.detail.callerTrust). Read OPEN.
21630
+ required: [code, field, entry, reason]
21631
+ additionalProperties: false
21632
+ properties:
21633
+ code: { type: string }
21634
+ field: { type: string }
21635
+ entry: { type: string }
21636
+ reason: { type: string }
21637
+ PrincipalInboxMessage:
21638
+ type: object
21639
+ description: 'The request body of POST /v1/principal-inbox/messages: a CLOSED three-key shape (`from` is minted by the server from the verified identity and cannot be sent).'
21640
+ required: [to, content]
21641
+ additionalProperties: false
21642
+ properties:
21643
+ to: { type: string, minLength: 1, description: 'The recipient principal string. Must belong to the sender''s organization (the membership judgement reads the organization directory); a failed judgement is 404 `org_delivery.not_member`, byte-identical to "no such person".' }
21644
+ content: { type: string, minLength: 1, maxLength: 32000, description: 'The letter body (characters; over 32000 => 400 `request.field_invalid`).' }
21645
+ urgent: { type: boolean, description: 'The urgent flag on the envelope (absent = ordinary).' }
21646
+ PrincipalInboxReceipt:
21647
+ type: object
21648
+ description: 'The 200 receipt: the letter was appended. `receiptId` = the gate-minted delivery receipt id (the envelope''s `gateReceiptId` carries the same value).'
21649
+ required: [receiptId]
21650
+ additionalProperties: false
21651
+ properties:
21652
+ receiptId: { type: string }
21653
+ OrgDeliveryRefusalCode:
21654
+ type: string
21655
+ enum: [org_delivery.not_member, org_delivery.rate_limited, org_delivery.inbox_full, org_delivery.recipient_tombstoned]
21656
+ description: >
21657
+ The four cross-principal delivery refusal codes (server >= 7.133.0; the engine's closed `ORG_DELIVERY_REFUSAL_CODES`, one frozen sentence each — machines read the CODE): `org_delivery.not_member` (404),
21658
+ `org_delivery.rate_limited` (429), `org_delivery.inbox_full` (409), `org_delivery.recipient_tombstoned` (410). See POST /v1/principal-inbox/messages.
21659
+ ToolApprovalReadState:
21660
+ type: string
21661
+ enum: [pending, decided, parked, void]
21662
+ description: >
21663
+ `state` of GET /v1/tool-approvals/{id} (server >= 7.133.0), a CLOSED four-word set — read it exhaustively: `pending` = a live registration on this replica is waiting (answer door open);
21664
+ `decided` = the row is DECIDED; `parked` = PARKING / PARKED (a late answer redeems the parked card); `void` = DENIED / VOID (nobody decided it, the answer has nowhere to land).
21665
+ ApprovalsStreamReadErrorCode:
21666
+ type: string
21667
+ enum: [approvals.read_unavailable, approvals.read_corrupt]
21668
+ description: >
21669
+ The `errorCode` of the two READ-FAILURE `error` frames on GET /v1/approvals/stream (server >= 7.133.0). `approvals.read_unavailable` = this tick's approval read failed (the store was momentarily
21670
+ unreadable) — the stream STAYS open and the next tick reads again; it is NOT a terminal frame and NOT a reconnect signal (do not reconnect on it, and do not read it as "nothing pending").
21671
+ `approvals.read_corrupt` = a corrupt row was read — the server closes the stream after the frame; deal with that row before reopening. Branch on the code, never match the message.
21672
+ Older servers (<= 7.132.x) answer a read failure with nothing but heartbeats.
21673
+ ToolApprovalReadProjection:
21674
+ type: object
21675
+ description: >
21676
+ The row projection shared by the three non-`pending` arms of GET /v1/tool-approvals/{id} (server >= 7.133.0): a SUBSET of the `livePending[]` row keys — one reader, one key set. `frame` = the disconnect-replay
21677
+ opening frame (an `approval_request` frame; ABSENT when the card cannot be read back — never invented).
21678
+ required: [approvalId, ts, expiresAtMs]
21679
+ additionalProperties: true
21680
+ properties:
21681
+ approvalId: { type: string }
21682
+ ts: { type: number, description: 'Registration time (ms epoch).' }
21683
+ expiresAtMs: { type: number, description: 'Absolute window deadline (cast once, never recomputed).' }
21684
+ sessionId: { type: string }
21685
+ toolName: { type: string }
21686
+ frame: { $ref: '#/components/schemas/ApprovalRequestFrame' }
21687
+ ToolApprovalReadBody:
21688
+ description: >
21689
+ The 200 body of GET /v1/tool-approvals/{id} (server >= 7.133.0) — a four-arm union discriminated by `state` (closed, read it exhaustively): `pending` = a live registration on this replica is waiting
21690
+ (the `livePending[]` row + `state`; answer door open); `decided` = the row is DECIDED (`decision` / `decidedAtMs` / `decisionNote?`, the same three keys as the late-replay body); `parked` = PARKING / PARKED
21691
+ (a late answer redeems the parked card; `decidePath?` points at the durable decision path); `void` = DENIED / VOID (nobody decided it, the answer has nowhere to land; `cause?` is the same cause word as the
21692
+ late-answer 404's, see ToolApprovalNotPendingCause).
21693
+ oneOf:
21694
+ - $ref: '#/components/schemas/ToolApprovalReadPending'
21695
+ - $ref: '#/components/schemas/ToolApprovalReadDecided'
21696
+ - $ref: '#/components/schemas/ToolApprovalReadParked'
21697
+ - $ref: '#/components/schemas/ToolApprovalReadVoid'
21698
+ discriminator:
21699
+ propertyName: state
21700
+ mapping:
21701
+ pending: '#/components/schemas/ToolApprovalReadPending'
21702
+ decided: '#/components/schemas/ToolApprovalReadDecided'
21703
+ parked: '#/components/schemas/ToolApprovalReadParked'
21704
+ void: '#/components/schemas/ToolApprovalReadVoid'
21705
+ ToolApprovalReadPending:
21706
+ allOf:
21707
+ - $ref: '#/components/schemas/LivePendingRow'
21708
+ - type: object
21709
+ required: [state]
21710
+ properties:
21711
+ state: { type: string, enum: [pending] }
21712
+ ToolApprovalReadDecided:
21713
+ allOf:
21714
+ - $ref: '#/components/schemas/ToolApprovalReadProjection'
21715
+ - type: object
21716
+ required: [state, decision, decidedAtMs]
21717
+ properties:
21718
+ state: { type: string, enum: [decided] }
21719
+ decision: { type: string, enum: [allow, deny] }
21720
+ decidedAtMs: { type: integer }
21721
+ decisionNote: { type: string }
21722
+ ToolApprovalReadParked:
21723
+ allOf:
21724
+ - $ref: '#/components/schemas/ToolApprovalReadProjection'
21725
+ - type: object
21726
+ required: [state]
21727
+ properties:
21728
+ state: { type: string, enum: [parked] }
21729
+ decidePath: { type: string, description: 'The durable decision path for this ask when it can be built.' }
21730
+ ToolApprovalReadVoid:
21731
+ allOf:
21732
+ - $ref: '#/components/schemas/ToolApprovalReadProjection'
21733
+ - type: object
21734
+ required: [state]
21735
+ properties:
21736
+ state: { type: string, enum: [void] }
21737
+ cause:
21738
+ allOf: [{ $ref: '#/components/schemas/AskRowEndingCause' }]
21739
+ description: 'The row''s recorded ending cause (absent for a legacy row with no cause written — never invented).'
20647
21740
  ToolApprovalRespondApplied:
20648
21741
  type: object
20649
21742
  description: >
20650
- The APPLIED arm of the 200 ack of POST /v1/tool-approvals/{approvalId}/respond (server tool-approval.ts — exact
21743
+ The APPLIED arm of the 200 ack of POST /v1/tool-approvals/{id}/respond (server tool-approval.ts — exact
20651
21744
  shape, `decision` echoed): the decision settled a live gate. Owner-gated with a 404 and NO existence oracle: non-owner and
20652
21745
  unknown/settled/expired/wrong-replica ids are indistinguishable. See ToolApprovalRespondAck for the replayed arm.
20653
21746
  # 封闭(census 批2 第三段,2026-07-30):铸造点 `tool-approval.ts` 三恒在键 + 两条件键
@@ -20663,6 +21756,13 @@ components:
20663
21756
  approvalId: { type: string }
20664
21757
  delivery: { type: string, enum: [applied] }
20665
21758
  decision: { $ref: '#/components/schemas/ToolApprovalDecision' }
21759
+ consumerState:
21760
+ type: string
21761
+ enum: [absent]
21762
+ description: >-
21763
+ server >= 7.133.0, TRUE-ONLY: no engine leg on THIS replica was waiting for the ask this answer settled — the same bit, with the same meaning, as `LivePendingRow.consumerState` and the
21764
+ durable decision ack's. Present <=> the answered entry was ADOPTED from the ask ledger at start-up. The decision lands on the row as usual, but nothing running here resumes because of it — read the run
21765
+ surface; do not read the 200 as "the run continued". Absent = a local engine leg was waiting, or an older server.
20666
21766
  rememberApplied:
20667
21767
  type: boolean
20668
21768
  description: >
@@ -20754,7 +21854,7 @@ components:
20754
21854
  ToolApprovalRespondReplayed:
20755
21855
  type: object
20756
21856
  description: >
20757
- The REPLAYED arm of the 200 ack of POST /v1/tool-approvals/{approvalId}/respond: the ask was already DECIDED
21857
+ The REPLAYED arm of the 200 ack of POST /v1/tool-approvals/{id}/respond: the ask was already DECIDED
20758
21858
  (by anyone) when this answer arrived, so the server replays the FIRST decision — `decision` is the row's two-word
20759
21859
  value (`allow_session` was folded to `allow` when it landed), `decidedAtMs` the first decision's clock,
20760
21860
  `decisionNote` only when the row has one, `rememberApplied: false` only when THIS request asked `allow_session`.
@@ -20770,14 +21870,57 @@ components:
20770
21870
  decidedAtMs: { type: integer }
20771
21871
  decisionNote: { type: string }
20772
21872
  rememberApplied: { type: boolean, enum: [false] }
21873
+ noteRecorded: { type: boolean, description: 'server >= 7.133.0 (two doors, one shape): present ONLY when THIS request carried a `note`; `true` <=> the row''s text is verbatim what this request submitted, `false` = not confirmed (another reason is on the row / no store / read-back failed). A late answer''s note never overwrites the first decision''s reason — that is `decisionNote`.' }
21874
+ rulePersisted: { type: boolean, enum: [false], description: 'server >= 7.133.0: present (as `false`) only when this late answer carried `persistRule`: the rule is redeemed on the FIRST decision only, a late answer never saves one.' }
21875
+ ruleRefusal:
21876
+ type: string
21877
+ enum: [rule_lane_unavailable]
21878
+ description: 'with `rulePersisted: false`; on this arm always `rule_lane_unavailable` (the only word the published late-answer path mints).'
21879
+ ToolApprovalRespondParkedRedeemed:
21880
+ type: object
21881
+ description: >
21882
+ The PARKED-REDEEMED arm of the 200 ack of POST /v1/tool-approvals/{id}/respond (server >= 7.133.0; server tool-approval.ts `redeemParkedAsk` /
21883
+ `replayRedeemReceipt`): the card's window had run out and the ask moved to background waiting (PARKED); this late answer — on another replica or after a restart —
21884
+ goes through the EXISTING durable redemption leg (the same CAS as POST /v1/approvals/{sessionId}/decide) instead of the old 404 `tool_approval.not_pending`
21885
+ (`cause: unknown_or_other_replica`). Body = the redemption leg's receipt (ApprovalDecisionResult's keys — `status` / `taskId` / `sessionId` / ... as they come) plus the three keys
21886
+ this door adds: `approvalId` / `askId` (the same 64-hex value) and the discriminant `accepted`. `decision` is the two-word direction on the REPLAY form only (`allow` / `deny`,
21887
+ `allow_session` folded to `allow`); `rememberApplied: false` appears only when this request asked `allow_session` (a parked redemption cannot remember a session grant);
21888
+ `rulePersisted` / `ruleRefusal` / `persistedRule*` are the `persistRule` outcome on the redemption leg (a rule that fails to store NEVER flips the verdict).
21889
+ `idempotent: true` appears only on the REPLAY (the same already-redeemed card answered again returns the first redemption's receipt).
21890
+ CLOSED like its two sibling arms (the census keeps this op's 200 body closed): the key set is ApprovalDecisionResult's own keys (one source per shape, by `$ref`) plus the added ones.
21891
+ required: [approvalId, askId, accepted]
21892
+ additionalProperties: false
21893
+ properties:
21894
+ approvalId: { type: string }
21895
+ askId: { type: string }
21896
+ accepted: { type: string, enum: [parked_redeemed] }
21897
+ status: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/status' }
21898
+ taskId: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/taskId' }
21899
+ sessionId: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/sessionId' }
21900
+ bindingEnforced: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/bindingEnforced' }
21901
+ decision: { type: string, description: 'REPLAY form: `allow` / `deny` (`allow_session` folded to `allow`). FIRST redemption: whatever the redemption leg''s own receipt says (`approve` / `deny` today) — read it open.' }
21902
+ errorCode: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/errorCode' }
21903
+ errorMessage: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/errorMessage' }
21904
+ retriable: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/retriable' }
21905
+ idempotent: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/idempotent' }
21906
+ executionOutcome: { $ref: '#/components/schemas/ApprovalDecisionResult/properties/executionOutcome' }
21907
+ rememberApplied: { type: boolean, enum: [false] }
21908
+ rulePersisted: { $ref: '#/components/schemas/ToolApprovalRespondApplied/properties/rulePersisted' }
21909
+ ruleRefusal: { $ref: '#/components/schemas/ToolApprovalRespondApplied/properties/ruleRefusal' }
21910
+ persistedRule: { $ref: '#/components/schemas/ToolApprovalRespondApplied/properties/persistedRule' }
21911
+ persistedRules: { $ref: '#/components/schemas/ToolApprovalRespondApplied/properties/persistedRules' }
21912
+ persistedRuleAnchors: { $ref: '#/components/schemas/ToolApprovalRespondApplied/properties/persistedRuleAnchors' }
20773
21913
  ToolApprovalRespondAck:
20774
21914
  description: >
20775
- The 200 ack of POST /v1/tool-approvals/{approvalId}/respond — a discriminated union: the decision was APPLIED to a live
21915
+ The 200 ack of POST /v1/tool-approvals/{id}/respond — a discriminated union: the decision was APPLIED to a live
20776
21916
  gate (ToolApprovalRespondApplied, `delivery: "applied"`) or the ask was already decided and the first decision is
20777
- REPLAYED (ToolApprovalRespondReplayed, `idempotent: true`). The discriminant is `idempotent`.
21917
+ REPLAYED (ToolApprovalRespondReplayed, `idempotent: true`), or (server >= 7.133.0, sdk 25.0.0) the answer REDEEMED a PARKED card through the durable redemption
21918
+ leg (ToolApprovalRespondParkedRedeemed, `accepted: "parked_redeemed"`). The discriminants are `delivery` / `idempotent` / `accepted` (a replayed redemption carries
21919
+ BOTH `accepted: "parked_redeemed"` and `idempotent: true` — read `accepted` first).
20778
21920
  oneOf:
20779
21921
  - $ref: '#/components/schemas/ToolApprovalRespondApplied'
20780
21922
  - $ref: '#/components/schemas/ToolApprovalRespondReplayed'
21923
+ - $ref: '#/components/schemas/ToolApprovalRespondParkedRedeemed'
20781
21924
  UsageMetric:
20782
21925
  type: string
20783
21926
  enum: [tasks, tokensIn, tokensOut, costUsd]
@@ -21735,7 +22878,7 @@ components:
21735
22878
 
21736
22879
  AdoptionNotMigratedFace:
21737
22880
  type: string
21738
- enum: [usage-window, cost-quota, rate-limit, approval-exemption-grantor, approval-ask-decision-actor, image-bake-requested-by]
22881
+ enum: [usage-window, cost-quota, rate-limit, approval-exemption-grantor, approval-ask-decision-actor, image-bake-requested-by, permission-rule-approval-history, permission-rule-import-ticket, org-delivery-quota]
21739
22882
  description: >
21740
22883
  FROZEN machine-readable vocabulary of the faces the server deliberately does not migrate. Renaming or
21741
22884
  removing a member is a wire break; adding one is a new behaviour face. Consumers assert POSITIVELY on