@sema-agent/sdk 8.2.0 โ†’ 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml CHANGED
@@ -1453,7 +1453,7 @@ paths:
1453
1453
  required: [sessionId, leafId]
1454
1454
  properties:
1455
1455
  sessionId: { type: string }
1456
- leafId: { type: string, nullable: true, description: "null = session exists but has no entries yet." }
1456
+ leafId: { type: ["string", "null"], description: "null = session exists but has no entries yet." }
1457
1457
  '304': { description: 'Leaf unchanged since the presented ETag (no body).' }
1458
1458
  '401': { $ref: '#/components/responses/Unauthorized' }
1459
1459
  '404': { $ref: '#/components/responses/NotFound' }
@@ -5633,6 +5633,121 @@ components:
5633
5633
  properties:
5634
5634
  clampTolerance: { type: number }
5635
5635
 
5636
+ TerminalCause:
5637
+ description: >
5638
+ ๐Ÿ”ด BREAKING (server >= 7.64.0, core 7.6.0 design/390 D-8) โ€” WHY the run ended, as ONE tagged cause
5639
+ and the ONLY terminal record a `TaskResult` carries. It REPLACES the eight parallel plane keys of
5640
+ 7.63.0 and earlier (`status` / `errorCode` / `errorMessage` / `blockedReason` / `checkpointToken` /
5641
+ `checkpointId` / `checkpointGate` / `workspaceRestoreMode`), all of which are GONE from this schema โ€”
5642
+ no optional compatibility twin ships, because two spellings of one fact on one wire is two writers on
5643
+ one semantic surface.
5644
+ TRIAGE ON `terminal.kind`. The legacy five-word `status` (and the seven flat words) is a DERIVATION:
5645
+ call the engine's `terminalProjection(terminal)` โ€” the ONE cause->plane derivation โ€” never hand-roll it.
5646
+ Which pause reads as `suspended` and which as `needs_review` is decided by the engine's pause registry,
5647
+ not by this service.
5648
+ ๐Ÿ”ด The service's OWN columns and envelopes are UNCHANGED and still speak the five words: the `status` /
5649
+ `errorCode` columns on `GET /v1/runs/:id`, the task-list row `status`, the `/decide` 200 body's
5650
+ `status` / `errorCode` / `errorMessage`, and the fleet row's terminal word. Only their data source moved
5651
+ (projected once from the cause).
5652
+ oneOf:
5653
+ - $ref: '#/components/schemas/TerminalCause_completed'
5654
+ - $ref: '#/components/schemas/TerminalCause_failed'
5655
+ - $ref: '#/components/schemas/TerminalCause_blocked'
5656
+ - $ref: '#/components/schemas/PausedCause'
5657
+ discriminator:
5658
+ propertyName: kind
5659
+ mapping:
5660
+ completed: '#/components/schemas/TerminalCause_completed'
5661
+ failed: '#/components/schemas/TerminalCause_failed'
5662
+ blocked: '#/components/schemas/TerminalCause_blocked'
5663
+ paused: '#/components/schemas/PausedCause'
5664
+
5665
+ TerminalCause_completed:
5666
+ type: object
5667
+ additionalProperties: false
5668
+ description: 'The run finished (a person''s clean halt included โ€” see `TaskResult.haltedByUser`).'
5669
+ required: [kind]
5670
+ properties:
5671
+ kind: { const: completed }
5672
+
5673
+ TerminalCause_failed:
5674
+ type: object
5675
+ additionalProperties: false
5676
+ description: 'A limit, a provider failure, an abort, an invalid outputโ€ฆ'
5677
+ required: [kind]
5678
+ properties:
5679
+ kind: { const: failed }
5680
+ code:
5681
+ type: string
5682
+ description: >
5683
+ The machine code this failure carried โ€” the seat the retired flat `errorCode` occupied, now
5684
+ STRUCTURALLY owned by the failure arm ("a code belongs to a failure" is the shape, not a
5685
+ cross-field convention). OPEN SET, dotted namespaces so a caller can prefix-match a whole class:
5686
+ `limits.max_tokens_exceeded` / `limits.max_cost_exceeded` / `limits.max_turns_exceeded` /
5687
+ `limits.max_walltime_exceeded` / `config.*` / `resume_at.*`; brain codes (`auth` / `network` /
5688
+ `rate_limit` / โ€ฆ) and `conflict` stay FLAT. Two EXTERNAL stop causes are deliberately NOT
5689
+ `limits.*` (retrying them needs a new environment / a freed window, not a smaller budget):
5690
+ `env.lifetime_expired` and `usage.window_exhausted`.
5691
+ Governance codes arrive here too, and WHICH leg remaps one to an HTTP status differs per code โ€”
5692
+ do not generalize from one to the other: (a) `memory.admission_denied` /
5693
+ `memory.admission_required` are remapped to 403/503 ONLY on the synchronous `POST /v1/tasks`;
5694
+ on `POST /v1/runs` -> `GET /v1/runs/{taskId}` and on the SSE `done` frame they ride a 200 result.
5695
+ (b) `usage.window_exhausted` is a 429 only when the PRE-ADMISSION gate catches it; caught in the
5696
+ race after admission it is NOT remapped on any leg, including the synchronous 200.
5697
+ A NESTED orchestrator that met a durable pause it cannot drive stamps `unexpected.suspended` /
5698
+ `unexpected.needs_review` and carries that pause on `nestedPause`.
5699
+ Consumers MUST branch with a `default` arm. Absent = the failure never had a code.
5700
+ message: { type: string, description: 'Human-readable failure text. Absent when there was none.' }
5701
+ nestedPause:
5702
+ allOf: [{ $ref: '#/components/schemas/PausedCause' }]
5703
+ description: >
5704
+ The durable pause this failure STANDS IN FOR โ€” set ONLY at a nested hard boundary (verify /
5705
+ cascade): the nested leg paused on a durable gate the orchestrator cannot drive, so at ITS
5706
+ boundary the pause is reported as a failure while the pause's own cause travels here verbatim.
5707
+ ๐Ÿ”ด On the wire it carries NO `token` โ€” the same withholding rule as the top-level `paused` arm
5708
+ (see `PausedCause`); `gate` / `checkpointId` are present as usual.
5709
+ Absent on every failure the engine itself assembles.
5710
+
5711
+ TerminalCause_blocked:
5712
+ type: object
5713
+ additionalProperties: false
5714
+ description: 'The agent could not finish and said why (a governance verdict is NOT a failure).'
5715
+ required: [kind, reason]
5716
+ properties:
5717
+ kind: { const: blocked }
5718
+ reason: { type: string, description: 'Why the agent could not finish.' }
5719
+
5720
+ PausedCause:
5721
+ type: object
5722
+ additionalProperties: false
5723
+ description: >
5724
+ A durable pause committed a checkpoint and the run is resumable.
5725
+ ๐Ÿ”ด THE `token` SEAT IS NEVER ON THE WIRE. It is the resume CAPABILITY (token-as-auth) and never leaves
5726
+ the service โ€” the SAME discipline the retired top-level `checkpointToken` had, only the credential
5727
+ changed address (`src/terminal.ts` `stripResumeToken` removes it on both reachable paths: this arm and
5728
+ `TerminalCause_failed.nestedPause`). To act on this pause call
5729
+ `POST /v1/approvals/{sessionId}/decide` and reconcile by `checkpointId`.
5730
+ required: [kind, gate]
5731
+ properties:
5732
+ kind: { const: paused }
5733
+ gate:
5734
+ allOf: [{ $ref: '#/components/schemas/CheckpointGate' }]
5735
+ description: 'WHICH pause โ€” who/what must resume it. Same word as the park row''s `gate.kind`.'
5736
+ checkpointId:
5737
+ type: string
5738
+ description: >
5739
+ The pause's NON-SECRET stable identity โ€” the display/correlation key to log or render where the
5740
+ token must not travel. Absent on pre-identity checkpoints.
5741
+ restoreMode:
5742
+ type: string
5743
+ enum: [snapshot, park_only]
5744
+ description: >
5745
+ HOW the paused task's remote workspace comes back, present whenever the pause captured one.
5746
+ `snapshot` = the VM was suspended into a snapshot (provider billing typically stops).
5747
+ `park_only` = the env declared itself non-suspendable (an SSH host / an ADB device) so NOTHING was
5748
+ paused: the machine keeps running and keeps costing. ABSENT for a process-local pause โ€” a
5749
+ scheduler that assumes "paused => idle and free" must be able to see the difference.
5750
+
5636
5751
  TaskResult:
5637
5752
  type: object
5638
5753
  # ๐Ÿ”ด **ๅˆปๆ„ไธๅฐ้—ญ**(census ่ฝดไธ€่ฃๅฎš,2026-07-30)ใ€‚ๆญคๅ‰ๆ˜ฏใ€Œๆฒกๅ†™ใ€= JSON Schema ้ป˜่ฎคๅผ€ๆ”พ,ๅˆ†ไธๆธ…
@@ -5642,12 +5757,21 @@ components:
5642
5757
  # ่ฟ™้‡Œๆ”นๆˆ false ไผš่ฎฉ spec ไธŽๆœฌไป“่‡ชๅทฑ็š„็ฑปๅž‹้ขไบ’็›ธๆ‰“่„ธ,ๅนถไธ”ๆŠŠใ€Œๅผ•ๆ“ŽๅŠ ๅญ—ๆฎตใ€ๅˆคๆˆ wire ่ฟ็บฆ(ๅฎƒไธๆ˜ฏ)ใ€‚
5643
5758
  # ๆ”ถ็ดง่ฟ™ไธ€้ข็š„ๆญฃ่งฃไธๆ˜ฏ additionalProperties,ๆ˜ฏๆŠŠๅผ•ๆ“Ž็œŸๅ‘็š„้”ฎ**้€ไธช็™ป่ฎฐ**(ๅญ—ๆฎต็บง drift ้—จ็š„ๆดป)ใ€‚
5644
5759
  additionalProperties: true
5645
- description: Synchronous task result (`POST /v1/tasks`).
5646
- required: [taskId, sessionId, status, stats]
5760
+ description: >
5761
+ Synchronous task result (`POST /v1/tasks`). ๐Ÿ”ด BREAKING at server 7.64.0: the terminal is ONE tagged
5762
+ cause (`terminal`), not the eight parallel plane keys โ€” see `TerminalCause`.
5763
+ required: [taskId, sessionId, terminal, stats]
5647
5764
  properties:
5648
5765
  taskId: { type: string }
5649
5766
  sessionId: { type: string }
5650
- status: { $ref: '#/components/schemas/RunStatus' }
5767
+ terminal:
5768
+ allOf: [{ $ref: '#/components/schemas/TerminalCause' }]
5769
+ description: >
5770
+ ๐Ÿ”ด server >= 7.64.0 โ€” the ONE terminal record. Branch on `terminal.kind`. The eight retired plane
5771
+ keys (`status` / `errorCode` / `errorMessage` / `blockedReason` / `checkpointToken` /
5772
+ `checkpointId` / `checkpointGate` / `workspaceRestoreMode`) are GONE from this schema and this
5773
+ SDK ships NO compatibility projection: a consumer that still needs the five-word `status` calls
5774
+ the engine's `terminalProjection(terminal)` itself.
5651
5775
  result: { type: string }
5652
5776
  activeTaskId:
5653
5777
  type: [string, 'null']
@@ -5678,20 +5802,13 @@ components:
5678
5802
  salvagedOutput:
5679
5803
  type: string
5680
5804
  description: Partial output preserved when the run could not complete normally (best-effort salvage).
5681
- blockedReason:
5682
- type: string
5683
- description: Why a `blocked` status was reached (human-readable).
5684
- checkpointGate:
5685
- description: >
5686
- The gate kind that produced `checkpointToken` when the run suspended. Deliberately UNTYPED here โ€”
5687
- the shape is core's and still evolving; treat it as opaque and branch on `status`/`errorCode` instead.
5688
5805
  toolCallId:
5689
5806
  type: string
5690
5807
  description: >
5691
5808
  [4913] (server 7.41+) โ€” the PENDING tool call's id when this result is a durable park
5692
- (`status` suspended/needs_review with a `tool_approval` pending action); same key and meaning as the
5809
+ (`terminal.kind === "paused"` with a `tool_approval` pending action); same key and meaning as the
5693
5810
  `tool_approval` frame's `toolCallId` (server >= 1.307). ABSENT (key omitted, never null) on a
5694
- tool-less park (resource_limit / plan_review / task_done), on every terminal result, and when the
5811
+ tool-less park (resource_limit / plan_review / task_done), on every non-paused cause, and when the
5695
5812
  server-side checkpoint read failed โ€” consumers test presence.
5696
5813
  degraded:
5697
5814
  type: object
@@ -5716,21 +5833,6 @@ components:
5716
5833
  description: >
5717
5834
  The task's structured (schema-constrained) output when one was requested. Shape is caller-defined,
5718
5835
  so this is intentionally untyped.
5719
- errorCode:
5720
- type: string
5721
- description: >
5722
- Result-level terminal code (NOT an HTTP error). Includes `budget.precall` / `budget.exceeded`
5723
- (cost gate, non-retryable) โ€” branch on this, not on a 4xx (pinned wire contract).
5724
- Governance codes also arrive HERE, and WHICH leg renders them as an HTTP status differs per code โ€”
5725
- do not generalize from one to the other:
5726
- (a) `memory.admission_denied` / `memory.admission_required` (server >=7.0.0) are remapped to 403/503
5727
- ONLY on the synchronous `POST /v1/tasks`; on `POST /v1/runs` -> `GET /v1/runs/{taskId}` and on the
5728
- SSE `done` frame they ride a 200 result (those legs have no second chance at a status line).
5729
- (b) `usage.window_exhausted` is a 429 only when the PRE-ADMISSION gate catches it (all three submit
5730
- endpoints). When the window is exhausted in the race after admission, the engine's refusal is NOT
5731
- remapped on any leg โ€” it lands in the result, including a 200 from the synchronous `POST /v1/tasks`.
5732
- Branch on this field, never on the status alone.
5733
- errorMessage: { type: string }
5734
5836
  retryAfterMs:
5735
5837
  type: integer
5736
5838
  description: >
@@ -5751,6 +5853,51 @@ components:
5751
5853
  rounds: { type: integer }
5752
5854
  findings: { type: array, items: { type: string } }
5753
5855
 
5856
+ LegacyPlaneTaskResult:
5857
+ type: object
5858
+ additionalProperties: true
5859
+ description: >
5860
+ ๐Ÿ”ด THE SHAPE THAT IS ON DISK โ€” a task result written by server 7.63.0 or earlier, in the retired FLAT
5861
+ plane form. It appears ONLY on the faces that REPLAY PERSISTED BYTES, and only on a deployment that was
5862
+ upgraded across 7.64.0: `GET /v1/runs/:id` -> `result` (the service passes the stored blob through
5863
+ verbatim, redaction only โ€” `src/http/routes/runs.ts`) and the durable-ledger replay of the `done` frame.
5864
+ NOTHING WRITES IT ANY MORE: the write path mints exactly one shape (`TaskResult`, with `terminal`).
5865
+ ๐Ÿ”ด THIS IS NOT A COMPATIBILITY TWIN. The rule against "two spellings of one fact on one wire" is about
5866
+ WRITERS: two producers of one semantic surface. Here there is ONE writer and a READ face that admits the
5867
+ bytes a now-retired writer actually left on disk โ€” the same distinction the service draws for its own
5868
+ two-generation reader (`persistedPlaneOf`, whose header records the two reproducible regressions that
5869
+ deleting it caused: a 500 on every historical `GET /v1/runs/:id`, and unredacted credentials replayed
5870
+ from old ledger rows).
5871
+ NARROWING: `"terminal" in result` โ€” present = the current cause form, absent = this shape. Do NOT narrow
5872
+ on `status` alone: the SSE conflict envelope (`ActiveRunConflictDoneResult`) also carries `status`.
5873
+ ๐Ÿ”ด `terminal` IS REFUSED HERE, and that clause is load-bearing rather than decorative: the two
5874
+ generations are MUTUALLY EXCLUSIVE on disk (7.63.0 wrote `status` and never `terminal`; 7.64.0 writes
5875
+ `terminal` and never `status`), and without the refusal a MALFORMED current result โ€” `terminal: null`,
5876
+ an unknown cause kind, a `paused` cause that still carries its `token` โ€” would fail the `TaskResult`
5877
+ arm and then be LAUNDERED through this one, so the read faces would silently lose every negative
5878
+ control `TaskResult` enforces while a consumer narrowing on `"terminal" in result` walks straight into
5879
+ it. The documented discriminant and the enforced one are the same clause.
5880
+ not: { required: [terminal] }
5881
+ required: [taskId, sessionId, status, stats]
5882
+ properties:
5883
+ taskId: { type: string }
5884
+ sessionId: { type: string }
5885
+ status: { $ref: '#/components/schemas/RunStatus' }
5886
+ result: { type: string }
5887
+ stats: { $ref: '#/components/schemas/TaskStats' }
5888
+ errorCode: { type: string, description: 'The retired flat terminal code. Its successor is `TerminalCause.failed.code`.' }
5889
+ errorMessage: { type: string, description: 'The retired flat failure text. Successor: `TerminalCause.failed.message`.' }
5890
+ blockedReason: { type: string, description: 'The retired flat governance reason. Successor: `TerminalCause.blocked.reason`.' }
5891
+ checkpointGate:
5892
+ description: >
5893
+ The retired flat pause gate โ€” UNTYPED here on purpose, because these are bytes an older generation
5894
+ wrote and this contract does not re-litigate their shape. Successor: `TerminalCause.paused.gate`
5895
+ (typed as `CheckpointGate`).
5896
+ salvagedOutput: { type: string }
5897
+ model: { type: string }
5898
+ toolCallId: { type: string }
5899
+ retryAfterMs: { type: integer }
5900
+
5754
5901
  RunReceipt:
5755
5902
  type: object
5756
5903
  # ๅฐ้—ญ(census ่ฝดไธ€,2026-07-30):producer ้“ธ้€ ็‚น routes/runs.ts ไธŽ idem ้‡ๆ”พ่‡‚ๅ„่‡ช้€ๅญ—ๅ†™ๆญป
@@ -5769,7 +5916,12 @@ components:
5769
5916
  description: >
5770
5917
  Run state (`GET /v1/runs/:id`) โ€” live shape per the pinned wire contract Drift 2: `result` is the NESTED TaskResult
5771
5918
  object (output text in result.result, stats in result.stats; NO top-level stats), and the error text
5772
- field is `error` (not errorMessage; errorCode is floated up from result.errorCode).
5919
+ field is `error` (not errorMessage).
5920
+ ๐Ÿ”ด server >= 7.64.0 โ€” this ROW's own `status` / `errorCode` columns are UNCHANGED and still speak the
5921
+ five words; only their SOURCE moved. The nested `result` is the engine result itself and therefore
5922
+ changed shape with it (`result.terminal`, no plane keys): the row's `errorCode` column is now projected
5923
+ from `result.terminal` (`src/http/routes/runs.ts` -> `persistedPlaneOf`, which also reads the flat bytes
5924
+ 7.63.0 and earlier wrote to disk), NOT copied from a `result.errorCode` that no longer exists.
5773
5925
  # ๅฐ้—ญ(census ่ฝดไธ€,2026-07-30;A-075.21 ๅ›žๅกซๅŽ 12 ้”ฎ):้“ธ้€ ็‚น routes/runs.ts ๆ˜ฏไธ€ไธช้€้”ฎๅ†™ๆญป็š„
5774
5926
  # ๅญ—้ข้‡(taskId/sessionId/status/msSinceLastActivity/crossSliceUsage/result/supervisorCost/
5775
5927
  # suggestions/errorCode/error/jobId/source),ไธŽๆœฌ properties ไธ€ไธ€ๅฏนไธŠ;ๅชๆœ‰ taskId/sessionId/status
@@ -5806,10 +5958,21 @@ components:
5806
5958
  spentMicroUsd: { type: integer, description: 'Integer micro-USD; absent on unpriced deployments (unknown, not 0).' }
5807
5959
  maxSlices: { type: integer }
5808
5960
  sliceCount: { type: integer }
5809
- result: { $ref: '#/components/schemas/TaskResult' }
5961
+ result:
5962
+ oneOf:
5963
+ - $ref: '#/components/schemas/TaskResult'
5964
+ - $ref: '#/components/schemas/LegacyPlaneTaskResult'
5965
+ description: >
5966
+ ๐Ÿ”ด A READ-OF-DISK face, so it carries TWO generations of bytes (see `LegacyPlaneTaskResult`): the
5967
+ current cause form on anything server >= 7.64.0 wrote, and the retired flat form on rows written
5968
+ before the upgrade. The service passes the stored blob through verbatim (redaction only) and does
5969
+ NOT back-fill `terminal`. Narrow on `"terminal" in result`.
5810
5970
  errorCode:
5811
5971
  type: string
5812
- description: Result-level terminal code (e.g. `budget.exceeded`, `cancelled`); NOT an HTTP error.
5972
+ description: >
5973
+ Result-level terminal code (e.g. `budget.exceeded`, `cancelled`); NOT an HTTP error. This ROW COLUMN
5974
+ is unchanged at 7.64.0 โ€” only its source moved (projected from `result.terminal`, or read straight
5975
+ off a legacy flat blob).
5813
5976
  error: { type: string }
5814
5977
  jobId:
5815
5978
  type: string
@@ -6080,20 +6243,29 @@ components:
6080
6243
  input: { description: tool-call โ€” redacted args. }
6081
6244
  callId: { type: string }
6082
6245
  isError: { type: boolean }
6083
- # ๐Ÿ”ด core >=5.18.1 (#187) โ€”โ€” ไธŽ `Event_tool_end.settledBy` **ๅŒไธ€ไธชๅ€ผ**:server ็š„ๅ…ฑไบซๆŒ‘้”ฎๅ™จ
6084
- # `toolResultFieldsOf`(src/trace/project.ts)ๅŒๆ—ถๅ–‚ turns ้ขไธŽ trace SSE ้ข,่€Œๅ†™ไพง
6085
- # `toolEndEventData` ๅ–‚ durable ่ดฆๆœฌ โ€”โ€” ไธ‰้ขไธ€ไธช็œŸๆบ,ๆ‰€ไปฅ่ฟ™้‡Œ็š„่ฏ่กจไธŽ้‚ฃ่พน้€ๅญ—็›ธๅŒใ€‚
6086
- settledBy:
6246
+ # ๐Ÿ”ด BREAKING (server >=7.64.0, core 7.6.0 S6-A) โ€”โ€” ไธŽ `Event_tool_end.gate` **ๅŒไธ€ไธชๅ€ผ**:server ็š„
6247
+ # ๅ…ฑไบซๆŒ‘้”ฎๅ™จ `toolResultFieldsOf`(src/trace/project.ts)ๅŒๆ—ถๅ–‚ turns ้ขไธŽ trace SSE ้ข,่€Œๅ†™ไพง
6248
+ # `toolEndEventData` ๅ–‚ durable ่ดฆๆœฌ โ€”โ€” ไธ‰้ขไธ€ไธช็œŸๆบใ€‚้€€ๅฝน็š„ `settledBy` ไธŽๅฎƒ็š„ไธ‰ๅ…„ๅผŸ
6249
+ # (`resolution`/`autoDenied`/`approver`)ๅœจ่ฟ™ไธ‰้ขไธŠไธ€ๅนถๅˆ ้™คใ€‚
6250
+ gate:
6251
+ allOf: [{ $ref: '#/components/schemas/GateOutcome' }]
6252
+ description: >
6253
+ tool-result โ€” the WHOLE record of this call''s gate pass (see `GateOutcome`). The READ leg
6254
+ screens it INDEPENDENTLY of the write leg (a ledger row can come from any engine generation or
6255
+ a third-party producer), so a defective record is withheld here too. Absence is not a fact
6256
+ about the call.
6257
+ delivered:
6258
+ allOf: [{ $ref: '#/components/schemas/McpDelivered' }]
6259
+ description: 'tool-result โ€” MCP delivery verdict; read with `errorCode`. Absent on every non-MCP-failure result.'
6260
+ errorCode:
6087
6261
  type: string
6088
- enum: [human, timeout, aborted]
6089
6262
  description: >
6090
- tool-result โ€” HOW this call was settled when it went through an approval: `human` = somebody
6091
- actually answered; `timeout` = the approval window elapsed; `aborted` = abort / unclonable
6092
- arguments / out-of-contract. Present ONLY on the settled call.
6093
- ABSENCE CARRIES NO SEMANTICS (an older worker, or a posture arm that deliberately leaves it
6094
- unset โ€” the two are indistinguishable), so absent is neither "human" nor "no approval
6095
- happened". The read leg re-validates the closed set independently of the write leg, because a
6096
- ledger row can come from any engine generation.
6263
+ tool-result โ€” the machine code for WHY this call failed, verbatim from any tool''s
6264
+ `ToolResult.details.code` (OPEN set; the MCP family within it is core''s closed
6265
+ `McpFailureKind` word list since 7.6.0). Same key and semantics as `Event_tool_end.errorCode`.
6266
+ gatedCallId:
6267
+ type: string
6268
+ description: 'tool-result โ€” WHICH tool call a durable park is holding (core >= 5.55.0); absent on a tool-less park.'
6097
6269
  tokens:
6098
6270
  type: object
6099
6271
  properties:
@@ -6684,6 +6856,293 @@ components:
6684
6856
  and the history PULL answers the no-oracle 404 `not_found.blob`, while the REST of the sync
6685
6857
  surface keeps working.
6686
6858
 
6859
+ # โ”€โ”€โ”€ S-139(sdk 8.3.0):ๅฏน server 7.60.0 ็š„ๆ•ดไฝ“้‡ๅฏน่ดฆ่กฅ้ฝ็š„ 19 ไฝ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
6860
+ # ่กŒๅท = server 7.60.0 ๆ ‘็š„ `src/http/routes/capabilities.ts`ใ€‚่ฏญไน‰ไธŽ่ฐ“่ฏ็š„ๅ…จๆ–‡ๅœจ
6861
+ # `packages/sdk/src/types.ts` ็š„ `Capabilities` ๅŒๅๆˆๅ‘˜ไธŠ(้€ๆกไบฒ่ฏป server ๆบ็ ่ฝฌ่ฟฐ);
6862
+ # ่ฟ™้‡Œๅช็•™ codegen ้œ€่ฆ็š„ๅฝขไธŽไธ€ๅฅๅฎšๆ€งใ€‚ๆœบๆขฐๅฏน่ดฆ้—จ = `test/server-keyset-parity.test.ts`ใ€‚
6863
+ deviceExecutor:
6864
+ description: >
6865
+ device lane (capabilities.ts:162). Object OR `false` โ€” same tri-state family as `fleet`.
6866
+ Predicate = `deps.deviceHub` present, byte-identical to the `GET /v1/device/ws` upgrade
6867
+ handler's mount condition. `wsPath` is deliberately on the wire (hard-coding it downstream
6868
+ would turn a path change into a cross-repo breaking change). Device COUNT / online state are
6869
+ deliberately NOT advertised (that is the `/v1/devices/*` admin face).
6870
+ oneOf:
6871
+ - type: boolean
6872
+ enum: [false]
6873
+ - type: object
6874
+ required: [enabled, protocolVersion, maxInflightPerDevice, wsPath]
6875
+ additionalProperties: true
6876
+ properties:
6877
+ enabled: { type: boolean, enum: [true] }
6878
+ protocolVersion: { type: integer }
6879
+ maxInflightPerDevice: { type: integer }
6880
+ wsPath: { type: string }
6881
+ memoryEngine:
6882
+ description: >
6883
+ Memory ENGINE posture (capabilities.ts:210 = `projectMemoryEngineCapability`). Object = the
6884
+ memory face is really lit, `false` = dark. NOT the same thing as `memory`/`memoryWrite` (those
6885
+ advertise the RETIRED MemoryStore HTTP verbs and stay false forever) โ€” the engine has NO HTTP
6886
+ verbs, so a true here does NOT mean there are memory read/write endpoints to call.
6887
+ `vectorMode: null` = this leg has no rung indicator (the `file` engine), NOT "the rung is null".
6888
+ oneOf:
6889
+ - type: boolean
6890
+ enum: [false]
6891
+ - type: object
6892
+ required: [backend, vectorMode]
6893
+ additionalProperties: true
6894
+ properties:
6895
+ backend: { type: string, enum: [file, pg, tidb] }
6896
+ # ๐Ÿ”ด openapi **3.1**:ๅฏ็ฉบ็”จ `type: [T, "null"]`,`nullable` ไธๆ˜ฏ 3.1 ๅ…ณ้”ฎๅญ—(ไผš่ขซ้™้ป˜ๅฟฝ็•ฅ)ใ€‚
6897
+ # `null` ไนŸๅฟ…้กป่ฟ› enum,ๅฆๅˆ™็œŸๆ ก้ชŒๅ™จๆŠŠ file ๅผ•ๆ“Ž็š„ๅธธๆ€็ญ”ๅˆค้žๆณ•ใ€‚
6898
+ vectorMode: { type: ["string", "null"], enum: [native, portable, lexical, null] }
6899
+ sql:
6900
+ type: ["object", "null"]
6901
+ additionalProperties: true
6902
+ description: >
6903
+ This deployment's SQL transaction read semantics (capabilities.ts:216 =
6904
+ `projectSqlEngineCapability`), read back AFTER connection initialization set it โ€” same source as
6905
+ the operator `GET /v1/diagnostics/wiring` `sqlEngine` section. `null` = NO SQL backend here
6906
+ (env-only worker / local file backend), NOT "could not read it". `txnMode: null` = this engine
6907
+ has no such indicator, NOT "optimistic".
6908
+ required: [engine, isolation, txnMode]
6909
+ properties:
6910
+ engine: { type: string, enum: [tidb, innodb, pg] }
6911
+ isolation: { type: string }
6912
+ txnMode: { type: ["string", "null"], enum: [pessimistic, null] }
6913
+ sessionBackgroundable:
6914
+ type: boolean
6915
+ description: >
6916
+ The REMOTE lane can detach (capabilities.ts:233 = `Boolean(deps.runStore)`): `POST
6917
+ /v1/tasks/stream` with `x-detach-on-disconnect: true` survives client disconnect; come back via
6918
+ the `X-Task-Id` response header and `GET /v1/runs/{id}` + `/events`. Deployment half only โ€” the
6919
+ per-request half (caller must pass `sessionId`) is not in this bit. Scope is the REMOTE lane;
6920
+ never repurpose it to disable local backgrounding.
6921
+ runMemoryCaptureOptOut:
6922
+ type: boolean
6923
+ description: >
6924
+ `POST /v1/runs/{id}/memory/capture-optout` mid-run flip verb is present (capabilities.ts:237 =
6925
+ `Boolean(deps.runStore)`). Absent โ‡’ do not render the affordance. True only promises the
6926
+ ENDPOINT exists, not that THIS run is live (non-live โ‡’ 409 with a signposting body).
6927
+ memoryBundle:
6928
+ type: boolean
6929
+ description: >
6930
+ Governance carry-out bundle pair `POST /v1/memory/{export,import}` (operator lane) โ€”
6931
+ capabilities.ts:261, two conjuncts: both engine seams wired AND `operatorPrincipals` NON-EMPTY
6932
+ (an empty roster makes those routes 403 for every identity). True does not promise the backend
6933
+ has the bundle composite face (core then refuses loudly with `memory.export_incomplete`).
6934
+ memoryCompliance:
6935
+ type: boolean
6936
+ description: >
6937
+ Provenance / erasure compliance pair `GET /v1/memory/entries/{entryId}/provenance` and
6938
+ `POST /v1/memory/erase` (operator lane) โ€” capabilities.ts:273, same two-conjunct family as
6939
+ `memoryBundle`. Deliberately NOT the same source as `memoryBundle` (a deployment can have the
6940
+ bundle composite face without control-plane ownership, and vice versa).
6941
+ memoryOrigin:
6942
+ type: boolean
6943
+ description: >
6944
+ External-origin marking face (three routes under `/v1/memory/origin/*`, operator lane) โ€”
6945
+ capabilities.ts:287, same two-conjunct family. Same predicate as `memoryCompliance` TODAY but a
6946
+ deliberately separate bit (two products; a deployment may want only one). True does not promise
6947
+ the `GET โ€ฆ/external` answer is complete โ€” there is no scope enumeration face, so an empty answer
6948
+ must never be read as "the store is clean".
6949
+ memoryConsolidationDriver:
6950
+ type: boolean
6951
+ description: >
6952
+ Consolidation valve pair (`POST /v1/admin/memory/consolidation/run`, `GET
6953
+ /v1/admin/memory/consolidation`, operator lane) โ€” capabilities.ts:301. FALSE โ‡’ those routes are
6954
+ **404** (whole domain unmounted), NOT 501: do not branch on 501 here. Contrast
6955
+ `memoryOptOutGrant`, whose false arm IS a 501.
6956
+ memoryOptOutGrant:
6957
+ type: boolean
6958
+ description: >
6959
+ memory-capture opt-out GRANT table admin (four routes under `/v1/admin/memory-optout`,
6960
+ operator-only) โ€” capabilities.ts:307. FALSE โ‡’ **501** `capability.memory_optout_grant_required`
6961
+ (change the deployment shape), as opposed to `memoryConsolidationDriver`'s 404 (turn the knob on).
6962
+ permissionModeAuto:
6963
+ type: object
6964
+ additionalProperties: true
6965
+ description: >
6966
+ Default-state disclosure for `permissionMode: "auto"` (capabilities.ts:463; adjudicated by
6967
+ `src/auto-mode-face.ts`). The parts are deliberately NOT folded into one boolean โ€” "this binary
6968
+ does not know auto" and "it knows auto but this box has no entitlement source" would collapse to
6969
+ the same false, and a shell must do different things for those. `reason` is present ONLY when
6970
+ `armed` is false; read its vocabulary as an OPEN set (server already grew it from four words to
6971
+ six). `model` is omitted when the classifier route cannot be resolved (the server logs the
6972
+ engine's refusal rather than 500-ing the read face). `?permissionMode=<five words>` folds the
6973
+ caller's INTENT into the verdict; anything outside the five words is a 400
6974
+ `request.field_invalid`.
6975
+ # ๐Ÿ”ด required ๅชๆœ‰**ไธ‰ไฝ**:ๆญฆ่ฃ…ไธ‰้”ฎ(intentArming / armed / reason)ๆ˜ฏ server >= 7.57.0 (S-80)
6976
+ # ๆ‰ๆœ‰็š„,่€ŒๆœฌๅŒ…็š„ๆ”ฏๆŒๅœฐๆฟๆ˜ฏ 3.0.0 => every 7.x <= 7.56.0 server is inside the promised face and
6977
+ # emits ONLY these three (verified verbatim against server tags v7.50.0 / v7.56.0). Requiring the
6978
+ # arming triple would make a SUPPORTED server's legitimate response invalid.
6979
+ required: [accepted, classifierSeat, entitlementSource]
6980
+ properties:
6981
+ accepted: { type: boolean, enum: [true] }
6982
+ classifierSeat: { type: boolean }
6983
+ entitlementSource: { type: boolean }
6984
+ intentArming: { type: boolean, description: 'server >= 7.57.0 only. ABSENT together with `armed`/`reason` on <= 7.56.0.' }
6985
+ armed:
6986
+ type: boolean
6987
+ description: >
6988
+ server >= 7.57.0 only. Read as THREE states, not two: true = will arm; false = will not, and
6989
+ `reason` is then present; ABSENT = an older server that does not report arming at all โ€” never
6990
+ fold that absence into false (it would render a 7.50 box that IS arming as "auto == default").
6991
+ reason:
6992
+ type: string
6993
+ enum: [mode_not_auto, deployment_incapable, org_denied, local_denied, settings_denied, resolver_fault]
6994
+ model: { type: string }
6995
+ readFace:
6996
+ type: ["string", "null"]
6997
+ enum: [open, roots, null]
6998
+ description: >
6999
+ The EFFECTIVE READ containment rung this deployment explicitly declared (capabilities.ts:484 =
7000
+ `deps.config.readFace ?? null`; env `READ_FACE` or config-center's `readFace.face` โ€” same config
7001
+ key, so this bit does not distinguish org-pushed from local). `null` = this deployment pinned
7002
+ NOTHING and the engine default takes over; the server deliberately does NOT fold `null` into
7003
+ `roots` (copying an upstream default onto the wire becomes a lie the day core changes it).
7004
+ Minimal disclosure โ€” the deny table itself is never on the capability face. There is NO
7005
+ per-request `readFace` field on TaskRequest.
7006
+ callerCwd:
7007
+ type: boolean
7008
+ description: >
7009
+ Whether a caller-supplied `cwd` is actually HONORED (capabilities.ts:541 =
7010
+ `cwdHonored(config) || deviceCwdHonored(config)`) โ€” single-user `host` lane OR the `device` lane.
7011
+ Byte-identical predicate to the submit-side gate in `boot/resolve-spec.ts`. Prefer this bit;
7012
+ when absent (older worker) fall back to `projectContext` โ€” that is the verbatim pre-bit behavior.
7013
+ a2a:
7014
+ type: boolean
7015
+ description: >
7016
+ The A2A CLIENT read face `GET /v1/sessions/{id}/a2a` exists (capabilities.ts:555, unconditional
7017
+ `true` โ€” like `mcp`): with no peers configured it returns an honest empty panel, so there is no
7018
+ 501 path to gate on. Deliberately a DIFFERENT predicate from `a2aInjection`.
7019
+ a2aInjection:
7020
+ type: boolean
7021
+ description: >
7022
+ This deployment HONORS a caller-supplied `body.a2aPeers` (capabilities.ts:556 =
7023
+ `a2aInjectionHonored(config)`; three vetoes owned by `task-a2a.ts`: single-user AND `a2a`
7024
+ unlocked AND compliance permits). FALSE is a FIELD gate, not a 4xx: the request still 200s and
7025
+ the field is warned + ignored โ€” EXCEPT under a config lock, where submit refuses 400
7026
+ `config.locked_key`.
7027
+ a2aServe:
7028
+ type: boolean
7029
+ description: >
7030
+ Server-as-peer knob (capabilities.ts:564 = `config.a2aServe !== undefined`): true โŸบ
7031
+ `GET /.well-known/agent-card.json` and `POST /v1/a2a` exist; false โŸบ both 404. Scope is ROUTE
7032
+ EXISTENCE, not "every method runs" โ€” `message/send` / `tasks/get` additionally need a durable run
7033
+ store (without it they answer a named JSON-RPC `-32004`).
7034
+ agentRoster:
7035
+ type: boolean
7036
+ description: >
7037
+ `GET /v1/agents/roster` background-agent roster read face (capabilities.ts:640 =
7038
+ `Boolean(deps.backgroundAgentStore)`, byte-identical to that route's 501 gate). Scope is the
7039
+ ENUMERATION face (content-free projection); subagent OUTPUT bodies go through
7040
+ `/v1/runs/{id}/subagents/{handle}/output`, gated by `subagentOutput`.
7041
+ retention:
7042
+ type: ["object", "null"]
7043
+ additionalProperties: true
7044
+ description: >
7045
+ Managed-retention deployment facts (capabilities.ts:654). `null` = the sweep lane is off
7046
+ (default). `mode: audit-only` = it runs and judges but calls NO destructive method; `enforce` =
7047
+ it really deletes. The predicate looks only at config because a box with `intervalSec > 0` and no
7048
+ real executor CANNOT BOOT (`boot/retention-lane.ts` refuses at startup).
7049
+ required: [mode, maxAgeDays]
7050
+ properties:
7051
+ mode: { type: string, enum: [audit-only, enforce] }
7052
+ maxAgeDays: { type: integer }
7053
+ workflowsGate:
7054
+ type: object
7055
+ additionalProperties: true
7056
+ description: >
7057
+ Self-orchestration denial disclosure (capabilities.ts:739). The two parts are deliberately not
7058
+ folded: `engineCan` = core's `workflowsCapability`; `denial` = the DEPLOYMENT-level admission
7059
+ refusal (closed set, today's only member `entitlement_resolver_absent`), `null` = this layer does
7060
+ not refuse. Non-null means the engine is there but this deployment can grant it to NO principal
7061
+ (half-configured) โ€” signpost the OPERATOR, do not tell the user to retry. `denial: null` does NOT
7062
+ mean "you are authorized" (that is per-principal). `workflows` is the PRODUCT of the two factors.
7063
+ required: [engineCan, denial]
7064
+ properties:
7065
+ engineCan: { type: boolean }
7066
+ denial:
7067
+ type: ["string", "null"]
7068
+ enum: [entitlement_resolver_absent, null]
7069
+ writeProtection:
7070
+ oneOf:
7071
+ - $ref: '#/components/schemas/WriteProtectionCapability'
7072
+ - type: 'null'
7073
+ description: >
7074
+ server >= 7.63.0 (S-138) โ€” the WRITE-PROTECTION TABLE's posture on this deployment (see
7075
+ `WriteProtectionCapability`). `null` = this process cannot say; an ABSENT key means an OLDER worker,
7076
+ and "no key" is NOT "no table" โ€” an absent seat on the engine side is precisely the DEFAULT TABLE
7077
+ BEING IN PLACE.
7078
+ WriteProtectionCapability:
7079
+ type: object
7080
+ additionalProperties: false
7081
+ description: >
7082
+ server >= 7.63.0 (S-138) โ€” the WRITE-PROTECTION TABLE's posture on this deployment. The engine's
7083
+ literal name table downgrades a locatable write (Write / Edit / NotebookEdit) that lands on a table
7084
+ row from `allow` to `ask`, so a shell can tell in advance that those paths will raise a card.
7085
+ ๐Ÿ”ด THE THREE ARE DELIBERATELY NOT FOLDED INTO ONE BOOLEAN: folded, "the engine's default table is
7086
+ in place" and "an operator swapped in a table of their own" both read `true`, and those two say
7087
+ different things to an operator.
7088
+ ๐Ÿ”ด MINIMUM DISCLOSURE: the per-row names/kinds are NOT here โ€” a deployment-authored row can carry
7089
+ an internal path name, and listing them tells anyone who wants around them which names are NOT
7090
+ protected. Rows live on the operator face `GET /v1/diagnostics/wiring` -> `writeProtection.rows`.
7091
+ ๐Ÿ”ด A PARALLEL MECHANISM, NOT THE SAME ONE: `SENSITIVE_WRITE_PATTERNS` (pattern-shaped write deny)
7092
+ is reported separately and must never be folded into this bit โ€” the two have different fixes.
7093
+ The three-state reading of the SEAT itself lives at the reference point (`Capabilities.writeProtection`):
7094
+ `null` = this process cannot say; key absent = an older worker (and "no key" is NOT "no table").
7095
+ required: [armed, rows, replaced]
7096
+ properties:
7097
+ armed: { type: boolean, description: 'The effective table is non-empty => the engine really will raise a card on a row hit.' }
7098
+ rows: { type: integer, description: 'How many rows the effective table has (contents stay on the operator face).' }
7099
+ replaced: { type: boolean, description: 'The whole-table replacement knob was written => the default table is no longer in place as-is (an explicit EMPTY table counts โ€” "the operator turned it off" is not "nothing was touched").' }
7100
+
7101
+
7102
+ WriteProtectedRow:
7103
+ type: object
7104
+ additionalProperties: false
7105
+ description: >
7106
+ One row of the write-protection table (core `WriteProtectedRow`): a LITERAL name plus how it matches.
7107
+ The `name` is also the row's stable identity โ€” the string a refusal / ask message cites.
7108
+ required: [name, kind]
7109
+ properties:
7110
+ name: { type: string }
7111
+ kind:
7112
+ type: string
7113
+ enum: [basename, segment, segment-run]
7114
+ description: >
7115
+ `basename` = the target's LAST segment equals the row name ยท `segment` = ANY path segment equals it ยท
7116
+ `segment-run` = a CONSECUTIVE run of segments equals the row's `/`-separated segments.
7117
+
7118
+ WriteProtectionPosture:
7119
+ type: object
7120
+ additionalProperties: false
7121
+ description: >
7122
+ server >= 7.63.0 (S-138) โ€” the OPERATOR face of the write-protection table (`GET /v1/diagnostics/wiring`
7123
+ -> `writeProtection`). Same boot artefact as the tenant-facing `capabilities.writeProtection`, so the two
7124
+ faces cannot tell different stories; this one adds the ROW CONTENTS, which the tenant face withholds.
7125
+ required: [rows, source]
7126
+ properties:
7127
+ rows:
7128
+ type: array
7129
+ items: { $ref: '#/components/schemas/WriteProtectedRow' }
7130
+ description: 'The effective table, verbatim from the engine''s own compile (the service recomputes nothing). Empty = an explicitly empty table.'
7131
+ source:
7132
+ type: string
7133
+ enum: [default, extra, replace, off]
7134
+ description: >
7135
+ `default` = neither knob was written (the engine's default table is in place; the service does NOT
7136
+ copy that table โ€” the engine owns it) ยท `extra` = default plus additions ยท `replace` = whole-table
7137
+ replacement ยท `off` = replaced with an EMPTY table (legal, and loud).
7138
+ droppedDefaultRows:
7139
+ type: array
7140
+ items: { type: string }
7141
+ description: >
7142
+ The DEFAULT row names a whole-table replacement dropped. Present only on the replacement family
7143
+ (`extra` / `default` cannot structurally drop a row). Row identity is judged by the ENGINE's own
7144
+ fold rule, so a case-variant spelling is not reported as a loss.
7145
+
6687
7146
  SkillSpec:
6688
7147
  type: object
6689
7148
  description: A per-request skill (passed as an object; core-native TaskSpec.skills shape).
@@ -9056,6 +9515,214 @@ components:
9056
9515
  parentToolCallId: { type: string }
9057
9516
  sourceTaskId: { type: string }
9058
9517
  bgAgentId: { type: string }
9518
+ DeniedBy:
9519
+ type: string
9520
+ description: >
9521
+ WHO REFUSED a call โ€” the LAYER whose verdict is the deny (core 7.6.0 `DENIED_BY_VALUES`, 8 words,
9522
+ CLOSED). The other question a deny raises โ€” who ASKED โ€” is answered by `AskOrigin`; the two used to
9523
+ share one seven-word list in which `classifier` meant both.
9524
+ `policy` = the deployment `ToolPolicy` denied (directly, or re-checking an approved edit), or the
9525
+ approval-edit chain hit its round cap ยท `hook` = a PreToolUse hook denied, threw, or never answered ยท
9526
+ `org` = an organization policy rule denied ยท `classifier` = the auto-mode classifier denied directly ยท
9527
+ `plan_mode` = plan mode's read-only block on a write tool ยท `compliance` = the compliance call-time
9528
+ lock ยท `write_protection` = an approved edit was rewritten onto a write-protected path no approval
9529
+ covers ยท `ask_resolution` = the ask's own settlement IS the refusal (detail on `GateOutcome.settlement`).
9530
+ ๐Ÿ”ด CLOSED ON THIS WIRE, and deliberately not marked open: the engine's invariant screen
9531
+ (`screenGateOutcome`) treats an out-of-set `deniedBy` as a DEFECT, and the service withholds a defective
9532
+ record WHOLE (report + withhold). A word outside this set therefore cannot reach a consumer โ€” it arrives
9533
+ as the `gate` key being ABSENT, never as an unfamiliar word. Same rule for `Settlement.kind` and for
9534
+ `GateOutcome.origin`.
9535
+ enum: [policy, hook, org, classifier, plan_mode, compliance, write_protection, ask_resolution]
9536
+
9537
+ Settlement:
9538
+ description: >
9539
+ HOW the wait an ask was in ENDED โ€” a DISCRIMINATED union on `kind` (core 7.6.0 `SETTLEMENT_KINDS`,
9540
+ 12 words, CLOSED). It is deliberately NOT a free product of `kind` x `who`: a free product can express
9541
+ "human_allowed, ended by the engine's window" โ€” a structurally complete record that contradicts itself.
9542
+ `who` says WHICH PARTY ended it and its shape is fixed BY the word; `when` is epoch ms written at the
9543
+ settlement site; a person's refusal carries the decider's own `note` when one was attached.
9544
+ ๐Ÿ”ด Single mint: every kind is composed by the ENGINE at the site whose wait ended. A host never mints a
9545
+ word โ€” it reports FACTS (a synchronous approver's `human`/`timeout`, a durable decide's
9546
+ `decidedBy: "person" | "sla_timeout"`) and core turns them into the word.
9547
+ Server-side projection note: `who.approver` and `note` are host-derived / decider-authored text and are
9548
+ secret-redacted (and `note` length-bounded) before they reach this wire.
9549
+ oneOf:
9550
+ - $ref: '#/components/schemas/Settlement_human'
9551
+ - $ref: '#/components/schemas/Settlement_approval_window_expired'
9552
+ - $ref: '#/components/schemas/Settlement_denial_limit_window_expired'
9553
+ - $ref: '#/components/schemas/Settlement_park_sla_expired'
9554
+ - $ref: '#/components/schemas/Settlement_fail_closed'
9555
+
9556
+ Settlement_human:
9557
+ type: object
9558
+ additionalProperties: false
9559
+ description: 'A person (or the approver acting for one) decided. `human_refused` may carry the decider''s own note.'
9560
+ required: [kind, who, when]
9561
+ properties:
9562
+ kind: { type: string, enum: [human_allowed, human_refused] }
9563
+ who:
9564
+ type: object
9565
+ additionalProperties: false
9566
+ required: [party]
9567
+ properties:
9568
+ party: { const: person }
9569
+ approver: { type: string, description: 'The attribution the approval channel reported. Absent = the channel reported no name โ€” NEVER read as "nobody approved".' }
9570
+ when: { type: integer, format: int64, description: 'Epoch ms, written at the settlement site.' }
9571
+ note: { type: string, description: 'The refusing decider''s own words, when one was attached. A refusal WITHOUT a note is a bare "no".' }
9572
+
9573
+ Settlement_approval_window_expired:
9574
+ type: object
9575
+ additionalProperties: false
9576
+ description: 'An approval window elapsed with no answer โ€” the engine''s own approval factory window, or a synchronous host approver reporting its own window elapsed.'
9577
+ required: [kind, who, when]
9578
+ properties:
9579
+ kind: { const: approval_window_expired }
9580
+ who:
9581
+ oneOf:
9582
+ - type: object
9583
+ additionalProperties: false
9584
+ required: [party, window]
9585
+ properties:
9586
+ party: { const: engine }
9587
+ window: { const: approval_factory }
9588
+ - type: object
9589
+ additionalProperties: false
9590
+ required: [party]
9591
+ properties:
9592
+ party: { const: host }
9593
+ when: { type: integer, format: int64 }
9594
+
9595
+ Settlement_denial_limit_window_expired:
9596
+ type: object
9597
+ additionalProperties: false
9598
+ description: 'The classifier denial-limit fallback''s auto-deny window (core''s own timer over the synchronous leg) elapsed with no answer.'
9599
+ required: [kind, who, when]
9600
+ properties:
9601
+ kind: { const: denial_limit_window_expired }
9602
+ who:
9603
+ type: object
9604
+ additionalProperties: false
9605
+ required: [party, window]
9606
+ properties:
9607
+ party: { const: engine }
9608
+ window: { const: denial_limit }
9609
+ when: { type: integer, format: int64 }
9610
+
9611
+ Settlement_park_sla_expired:
9612
+ type: object
9613
+ additionalProperties: false
9614
+ description: >
9615
+ A DURABLE park''s SLA deadline passed and the host''s sweep resolved it as a deny. A store
9616
+ `expire`/`reap` produces no `tool_end` and therefore no settlement.
9617
+ required: [kind, who, when]
9618
+ properties:
9619
+ kind: { const: park_sla_expired }
9620
+ who:
9621
+ type: object
9622
+ additionalProperties: false
9623
+ required: [party]
9624
+ properties:
9625
+ party: { const: host }
9626
+ approver: { type: string }
9627
+ when: { type: integer, format: int64 }
9628
+
9629
+ Settlement_fail_closed:
9630
+ type: object
9631
+ additionalProperties: false
9632
+ description: >
9633
+ The fail-closed family โ€” nobody ended the wait, the situation did. `no_approver` (headless: no approver
9634
+ or question face is wired, or the deny posture string) ยท `approver_unavailable` (the approver answered
9635
+ the ROUTING question "nobody reachable" and no park took the ask) ยท `approver_error` (it threw) ยท
9636
+ `approver_contract` (it answered outside its contract) ยท `presentation_failed` (args/edit could not be
9637
+ safely presented or adopted) ยท `blanket_allow_refused` (a blanket `onAsk:"allow"` met a
9638
+ `requiresRealApproval` ask) ยท `task_aborted` (the wait''s abort signal ended it).
9639
+ required: [kind, who, when]
9640
+ properties:
9641
+ kind:
9642
+ type: string
9643
+ enum: [no_approver, approver_unavailable, approver_error, approver_contract, presentation_failed, blanket_allow_refused, task_aborted]
9644
+ who:
9645
+ type: object
9646
+ additionalProperties: false
9647
+ required: [party]
9648
+ properties:
9649
+ party: { const: none }
9650
+ when: { type: integer, format: int64 }
9651
+
9652
+ GateDisposition:
9653
+ description: 'The final disposition of one tool-gate pass. A deny NAMES the layer that refused.'
9654
+ oneOf:
9655
+ - type: object
9656
+ additionalProperties: false
9657
+ required: [kind]
9658
+ properties:
9659
+ kind: { const: allowed }
9660
+ - type: object
9661
+ additionalProperties: false
9662
+ required: [kind, deniedBy]
9663
+ properties:
9664
+ kind: { const: denied }
9665
+ deniedBy: { $ref: '#/components/schemas/DeniedBy' }
9666
+
9667
+ GateOutcome:
9668
+ type: object
9669
+ additionalProperties: false
9670
+ description: >
9671
+ ๐Ÿ”ด BREAKING (server >= 7.64.0, core 7.6.0 design/390 S6-A) โ€” the WHOLE record of ONE tool-gate pass.
9672
+ It REPLACES the four orthogonal words of 7.63.0 and earlier (`settledBy` / `resolution` / `autoDenied` /
9673
+ `approver`, all four DELETED with no alias): their pairing rules lived only in comments, and consumers
9674
+ had to infer semantics from ABSENCE ("a `timeout` with no `resolution` means a durable park expired").
9675
+ Minted ONCE by the engine (the gate''s exit; the decide lane for a durable park) and projected without
9676
+ re-derivation onto three faces โ€” the `permissionDenied` observer payload, this `tool_end` frame, and the
9677
+ durable row''s resolved outcome โ€” so the three cannot tell different stories.
9678
+ The three members are orthogonal but bound by four engine invariants: (I1) `settlement` present <=>
9679
+ `origin` present โ€” they describe the SAME ask; (I2) `deniedBy === "ask_resolution"` => `settlement`
9680
+ present AND a refusal kind; (I3) a `human_allowed` settlement beside a `denied` disposition means a
9681
+ LATER re-check vetoed a person''s approval, so `deniedBy` is one of the veto layers (`policy` / `hook` /
9682
+ `org` / `write_protection`) and the approver stays on the settlement; (I4) an `allowed` disposition has
9683
+ `settlement` absent or `human_allowed`.
9684
+ ๐Ÿ”ด NOTHING here is read out of an ABSENCE: an ordinary allow with no ask is
9685
+ `{disposition:{kind:"allowed"}}`, a direct policy deny is `{disposition:{kind:"denied",deniedBy:"policy"}}`,
9686
+ and neither carries a settlement because neither settled one.
9687
+ ๐Ÿ”ด The SERVICE withholds a record that fails the engine''s invariant screen (report + withhold, counter
9688
+ `server.trace.gate-outcome-defective`) โ€” so "a gate should have been here but the key is absent" has a
9689
+ fourth cause besides the three below: that record was defective. The four are indistinguishable on the
9690
+ wire; a consumer falls back to `isError` + the message text.
9691
+ required: [disposition]
9692
+ properties:
9693
+ disposition: { $ref: '#/components/schemas/GateDisposition' }
9694
+ settlement:
9695
+ allOf: [{ $ref: '#/components/schemas/Settlement' }]
9696
+ description: 'Present only when THIS pass settled an ask. Same-present/same-absent as `origin` (I1).'
9697
+ origin:
9698
+ allOf:
9699
+ - { $ref: '#/components/schemas/AskOrigin' }
9700
+ # ๐Ÿ”ด ้—ญ้›†ๆ‰งๆณ•**ๅชๅœจ่ฟ™ไธ€้ข**:ๅผ•ๆ“Ž็š„ `screenGateOutcome` ๅฏน `origin` ๅˆคๆˆๅ‘˜,ๅ‡บ้›†ๅณ่ฎฐๅฝ•็ผบ้™ท,่€Œ
9701
+ # ๆœๅŠกๅฏน็ผบ้™ท่ฎฐๅฝ•ๆ˜ฏๆ•ดๆกไธไธŠๅธง โ‡’ ่ฏ่กจๅค–็š„ๅ€ผๅœจ่ฟ™ๆก wire ไธŠๅˆฐไธไบ†ๆถˆ่ดน็ซฏใ€‚ๅฎกๆ‰นๅธง้‚ฃไธ€้ขไธๅˆคๆˆๅ‘˜,
9702
+ # ๆ‰€ไปฅ `AskOrigin` ๆœฌไฝ“ไฟๆŒ็œŸๅผ€(่ง่ฏฅ schema ็š„้•ฟๆณจ)ใ€‚่ฟ™ๅผ  enum ๆ˜ฏๆœฌ spec ้‡Œ่ฏฅ่ฏ่กจ็š„**ๅ”ฏไธ€**
9703
+ # ๆ‰งๆณ•็‚น;core ๅŠ ่ฏๆ—ถๆ”น่ฟ™ไธ€ๅค„ใ€‚
9704
+ - enum: [content_question, unresolvable, org_unavailable, org_rule, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
9705
+ description: >
9706
+ WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (and only here) โ€” see the
9707
+ enum note above.
9708
+
9709
+ McpDelivered:
9710
+ type: string
9711
+ description: >
9712
+ Whether a failed MCP request REACHED the server (core 7.6.0 `MCP_DELIVERY_VERDICTS`, CLOSED).
9713
+ `yes` = the server answered (a `protocol` rejection proves the exchange happened) ยท `no` = provably
9714
+ never sent (a connect-phase errno, a spawn failure, a malformed declaration, a server already known
9715
+ dead) โ€” safe to retry, nothing executed ยท `unknown` = the client stopped waiting or lost the pipe
9716
+ mid-exchange, so the request MAY have executed and a write-capable tool''s side effects must be
9717
+ verified before a retry.
9718
+ ๐Ÿ”ด READ IT WITH `errorCode`, never alone: the SAME `connection_closed` is directly retryable at `no`
9719
+ and must be investigated at `unknown` โ€” which is exactly why the verdict is its own field and not
9720
+ folded into the class.
9721
+ โš ๏ธ `no` says the CALLER''S TOOL CALL was not sent; it does NOT promise the server received zero bytes
9722
+ (an elicitation during the dial may already have crossed the connection) โ€” core''s contract, passed
9723
+ through verbatim.
9724
+ enum: [yes, no, unknown]
9725
+
9059
9726
  Event_tool_end:
9060
9727
  type: object
9061
9728
  description: >
@@ -9063,7 +9730,19 @@ components:
9063
9730
  (TextContent|ImageContent)[] block array) and degrades to a single TRUNCATED string over the core size cap
9064
9731
  (`truncated: true`). Absent โ‡’ the tool produced no body. ๐Ÿ”ด UNTRUSTED RAW + observability-only: the service
9065
9732
  has redactDeep'd + size-bounded it; a consumer MAY further bound, MUST never re-feed it to a model.
9066
- allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
9733
+ allOf:
9734
+ - { $ref: '#/components/schemas/EventIdentity' }
9735
+ # ๐Ÿ”ด ็Žฐๅฝนๅ†™ๅฃไธŽ้€€ๅฝนๅ››่ฏ**ไบ’ๆ–ฅ**(S-170 codex r2 [medium] ้ชŒ็œŸๅŽ่กฅ):้€€ๅฝนๅ››่ฏๅชๅ‡บ็Žฐๅœจ <=7.63.0 ๅ†™ไธ‹็š„
9736
+ # durable ่กŒไธŠ,่€Œ้‚ฃ็ง่กŒ็ป“ๆž„ไธŠ**ๆฒกๆœ‰** `gate`(7.63.0 ๅŽ‹ๆ นไธ้“ธๅฎƒ)ใ€‚ๆ‰€ไปฅใ€Œ`gate` ไธŽๅ››่ฏๅŒๅธงใ€ๅœจ
9737
+ # ไปปไฝ•ไธ€ไปฃ็š„็œŸๅญ—่Š‚้‡Œ้ƒฝไธๅญ˜ๅœจ โ€”โ€” ๅฎƒๅชๅฏ่ƒฝๆ˜ฏ็Žฐๅฝนๅ†™ๅฃๅ›žๅ้€€ๅฝน้”ฎ,ๆˆ–ไธ€ๆก่‡ช็›ธ็Ÿ›็›พ็š„ๅˆๆˆ่ฎฐๅฝ•ใ€‚
9738
+ # ไธๆ‹’ๅฎƒ,ใ€Œๅ››่ฏๅชๅœจๅ›žๆ”พ้‡Œใ€่ฟ™ๅฅ่ฏๅฐฑๆฒกๆœ‰ไปปไฝ•ๆœบๅ™จๅœจๅฎˆ(ๆ ‡่ฎฐ้”ฎ `x-legacy-replay-only` ๆ˜ฏ็ป™ไบบๅ’Œ
9739
+ # codegen ่ฏป็š„,ไธๆ‰งๆณ•)ใ€‚
9740
+ - not:
9741
+ anyOf:
9742
+ - { required: [gate, settledBy] }
9743
+ - { required: [gate, resolution] }
9744
+ - { required: [gate, autoDenied] }
9745
+ - { required: [gate, approver] }
9067
9746
  additionalProperties: false # ๅฐ้—ญ + identity ๆœฌๅœฐ้•œๅƒ:่ง Event_reasoning ๅค„็š„้•ฟๆณจ
9068
9747
  required: [type, toolCallId, toolName, isError]
9069
9748
  properties:
@@ -9078,19 +9757,60 @@ components:
9078
9757
  output: {} # NON-UNIFORM: string | (TextContent|ImageContent)[] | (truncated) string. Absent โ‡’ no body.
9079
9758
  truncated: { type: boolean }
9080
9759
  totalChars: { type: integer, description: 'core >=1.442 (RB-210): honest ORIGINAL size when truncated โ€” sum of each block''s true size (text=chars, image/document=base64 bytes), no JSON-wrapping overhead. Absent on older cores / untruncated results.' } # true โ‡’ output was size-bounded to a truncated string.
9081
- # ๐Ÿ”ด core >=5.18.1 (#187). Closed set, mirrored here as `enum` (the vocabulary's owner is the engine).
9082
- # ABSENCE CARRIES NO SEMANTICS: it is either an older caller, or a posture arm (headless auto-deny /
9083
- # blanket onAsk) that deliberately leaves it unset โ€” the two are indistinguishable, so absent is neither
9084
- # "human" nor "no approval happened". Read the run row / checkpoint face to judge a run's fate.
9760
+ # ๐Ÿ”ด BREAKING (server >=7.64.0, core 7.6.0 S6-A): `settledBy` / `resolution` / `autoDenied` / `approver`
9761
+ # are RETIRED and replaced by the ONE `gate` record below. NO 7.64.0+ writer mints them, and the TS type
9762
+ # does NOT carry them โ€” nobody should code against a dead vocabulary.
9763
+ # ๐Ÿ”ด They are still DECLARED here, and only for one reason: this arm is `additionalProperties: false`, and
9764
+ # the durable-ledger replay leg (`GET /v1/runs/:id/events`) passes a stored `tool_end` row through
9765
+ # VERBATIM (the service's `redactLedgerEventData` returns the row unchanged for this type). Server 7.63.0
9766
+ # and earlier really did write all four into that ledger, so on a deployment upgraded across 7.64.0 those
9767
+ # bytes reach a consumer โ€” and without a declaration that is not "four fields we cannot read", it is the
9768
+ # WHOLE FRAME judged invalid (the same failure family as `permissionRules` and `errorCode` before it).
9769
+ # Same doctrine as `LegacyPlaneTaskResult`: the write face mints ONE shape; a face that replays persisted
9770
+ # bytes admits what the retired writer actually left on disk. `x-legacy-replay-only: true` marks them so a
9771
+ # code generator / a reader can tell them apart from live keys at a glance.
9085
9772
  settledBy:
9086
9773
  type: string
9087
9774
  enum: [human, timeout, aborted]
9775
+ x-legacy-replay-only: true
9088
9776
  description: >
9089
- core >=5.18.1 (#187) โ€” HOW this call was settled when it went through an approval:
9090
- `human` = somebody actually answered; `timeout` = the approval window elapsed;
9091
- `aborted` = abort / unclonable arguments / out-of-contract. Present ONLY on the settled call.
9092
- Absent = an older caller OR a posture arm that does not fill it โ€” do NOT infer a semantic
9093
- from the missing key.
9777
+ RETIRED at server 7.64.0 (core 7.6.0 S6-A) โ€” superseded by `gate.settlement`. Appears ONLY when a
9778
+ durable ledger row written by server <= 7.63.0 is replayed. Never minted by a current writer; not on
9779
+ the SDK type. Do not branch on it.
9780
+ resolution:
9781
+ type: string
9782
+ x-legacy-replay-only: true
9783
+ description: 'RETIRED at 7.64.0 โ€” superseded by `gate.settlement.kind`. Replay-only, see `settledBy`.'
9784
+ autoDenied:
9785
+ const: true
9786
+ x-legacy-replay-only: true
9787
+ description: 'RETIRED at 7.64.0 โ€” superseded by `gate.disposition` + `gate.settlement`. Replay-only, see `settledBy`.'
9788
+ approver:
9789
+ type: string
9790
+ x-legacy-replay-only: true
9791
+ description: 'RETIRED at 7.64.0 โ€” superseded by `gate.settlement.who.approver`. Replay-only, see `settledBy`.'
9792
+ gate:
9793
+ allOf: [{ $ref: '#/components/schemas/GateOutcome' }]
9794
+ description: >
9795
+ ๐Ÿ”ด server >= 7.64.0 โ€” the WHOLE record of the gate pass this call went through: who allowed it or
9796
+ which LAYER refused it (`disposition`), how the wait it consumed ended (`settlement`, carrying the
9797
+ refusing person and their own note), and who ASKED (`origin`).
9798
+ ๐Ÿ”ด ABSENCE IS NOT A FACT ABOUT THIS CALL: the gate never saw it (an unarmed gate, a deferred
9799
+ re-send, an orphan picked up by reconcile) โ€” or the record was defective and the service withheld
9800
+ it (see `GateOutcome`). Never read a semantic out of the missing key.
9801
+ delivered:
9802
+ allOf: [{ $ref: '#/components/schemas/McpDelivered' }]
9803
+ description: >
9804
+ core >= 7.6.0 (S6-B) โ€” whether THIS failed MCP request reached the server. Present only on an MCP
9805
+ failure: every successful call and every non-MCP failure omits it, and the absence carries NO
9806
+ semantics. Read it WITH `errorCode` (see `McpDelivered`).
9807
+ gatedCallId:
9808
+ type: string
9809
+ description: >
9810
+ core >= 5.55.0 (#333) โ€” WHICH tool call a durable park is holding, minted by the engine from the
9811
+ committed checkpoint''s pending action (a tool may not self-report it). Present only on a park
9812
+ frame; absent on a park that holds no tool call (resource_limit / plan_review / task_done) โ€”
9813
+ ABSENT, never guessed.
9094
9814
  # ๐Ÿ”ด core >=5.9.0 (W3 / [2535]). OPEN SET โ€” deliberately a bare `type: string`, NOT an enum.
9095
9815
  # This arm is `additionalProperties: false`, and the server's `toolEndEventData` has been emitting
9096
9816
  # `errorCode` since 5.9.0, so omitting it made every legitimate frame carrying one fail strict
@@ -9105,6 +9825,12 @@ components:
9105
9825
  when `isError` is true. OPEN SET: a verbatim passthrough of any tool's `ToolResult.details.code`.
9106
9826
  The engine's own codes (`gate.parked`, `tool.not_found`) are only two examples; the fs tools
9107
9827
  already emit `path_not_in_root` / `readonly_out_of_root`, and self-registered tools may add more.
9828
+ ๐Ÿ”ด core >= 7.6.0 โ€” the MCP family within this open set is now the CLOSED `McpFailureKind` word list
9829
+ (`connect_refused` / `connection_failed` / `connection_closed` / `http_status` / `not_mcp_response` /
9830
+ `spawn_failed` / `timeout` / `protocol` / `invalid_config` / `unknown`); the retired
9831
+ `MCP_FAILURE_CODES` spellings โ€” including `network` and the `http_<status>` encoding โ€” are gone,
9832
+ and an HTTP status now rides the structured `httpStatus` seat on `wiring_manifest.mcp[]` instead of
9833
+ being baked into the code. The set as a WHOLE stays open (a tool owns its own codes).
9108
9834
  Consumers MUST branch with a `default` arm and MUST NOT exhaustively switch on known values.
9109
9835
  The service enforces only a length bound (>128 chars = malformed, the whole key is DROPPED rather
9110
9836
  than truncated โ€” truncating would mint a code core never sent). NOTE: this is a DIFFERENT
@@ -9469,6 +10195,80 @@ components:
9469
10195
  renders "don't ask again" without reading this bit is advertising an outcome it cannot promise.
9470
10196
  ๐Ÿ”ด Same three-way reading as `syncWired`: present-and-false = concept known, not armed here;
9471
10197
  absent = the worker never sent the key. Honestly `false` on server 7.12.0.
10198
+ modelGate:
10199
+ type: object
10200
+ additionalProperties: false
10201
+ description: >
10202
+ core >= 7.3.x (design/385 ็‰‡2b), server >= 7.58 โ€” TENANT-visible: which tools the tool-MODEL gate
10203
+ removed from THIS session and how to get them back. All three keys are engine-derived (`class` = the
10204
+ merged gate table''s class row key, `removed` = wire tool names, `restore` = the engine''s own
10205
+ recovery line), so the service passes them verbatim.
10206
+ ๐Ÿ”ด ALL-OR-NOTHING: the service mints the section only when all three keys are present and
10207
+ well-formed โ€” half a section would read as "these are the only tools that were removed".
10208
+ ABSENT = this run had nothing gated (the service never mints an empty section or `removed: []`).
10209
+ required: [class, removed, restore]
10210
+ properties:
10211
+ class: { type: string }
10212
+ removed: { type: array, items: { type: string } }
10213
+ restore: { type: string }
10214
+ autoMode:
10215
+ type: object
10216
+ additionalProperties: false
10217
+ description: >
10218
+ core >= 7.3.1 (#529), server >= 7.59 โ€” TENANT-visible: did AUTO mode actually arm on THIS leg, and
10219
+ if not, which arm did it stop at. Sibling of `GET /v1/capabilities.permissionModeAuto`, which
10220
+ answers the BEFORE question ("would a submit right now arm?"); this section answers the AFTER one
10221
+ ("did the leg that really ran arm?").
10222
+ ๐Ÿ”ด `reason` is core''s closed word list passed through VERBATIM and is deliberately NOT normalised
10223
+ onto the service''s own six-word `PermissionModeAutoUnarmedReason` set: the two tables answer
10224
+ different questions at different granularity (core''s single `denied` covers the service''s
10225
+ `org_denied` + `local_denied`, and a settings-level kill-switch folds into core''s `no_intent`,
10226
+ not `denied`, because it rewrites the MODE before the intent seat is ever written). To tell those
10227
+ apart, read `/v1/capabilities`.
10228
+ ๐Ÿ”ด ABSENCE IS NOT "not applicable" (core''s own words: it means an older mint or an external
10229
+ derivation) โ€” never fold it to `armed: false`. A self-contradicting pair (`armed` disagreeing with
10230
+ `reason === "armed"`, core''s own in-section invariant) makes the service withhold the WHOLE section.
10231
+ required: [armed, reason]
10232
+ properties:
10233
+ armed: { type: boolean }
10234
+ reason: { type: string, description: 'core''s word (`armed` / `no_intent` / `no_face` / `denied` / `resolver_fault` / `latch_open` today). Open on read โ€” branch known, keep a default arm.' }
10235
+ mcp:
10236
+ type: array
10237
+ description: >
10238
+ core >= 7.5.0 (#562), server >= 7.60 โ€” TENANT-visible: one row per MCP server THIS leg declared โ€”
10239
+ did it connect, if not which failure class, and how many tools it mounted.
10240
+ ๐Ÿ”ด AN EMPTY ARRAY IS NOT ABSENCE: `[]` means "this leg declared no servers"; a missing key means an
10241
+ older mint or an external derivation. Never fold one into the other.
10242
+ ๐Ÿ”ด The engine''s `error` free text is DELIBERATELY NOT PROJECTED (it is remote-author text, and core
10243
+ already owns the single redaction mint point for it). The actionable cause is `errorCode`.
10244
+ A row missing `name` or `status` is dropped individually โ€” the other servers'' rows still ship.
10245
+ items:
10246
+ type: object
10247
+ additionalProperties: false
10248
+ required: [name, status]
10249
+ properties:
10250
+ name: { type: string, description: 'The declared server name.' }
10251
+ status: { type: string, description: 'Three-word engine set (connected / failed / โ€ฆ). Open on read.' }
10252
+ source: { type: string, description: 'The declarer''s own layer label, echoed verbatim by core (single-line, length-capped).' }
10253
+ toolCount: { type: integer, description: 'Tools mounted. ABSENT rather than 0 โ€” a minted 0 would read as "connected with no tools".' }
10254
+ errorCode:
10255
+ type: string
10256
+ description: >
10257
+ core >= 7.6.0 โ€” the `McpFailureKind` word (`connect_refused` / `connection_failed` /
10258
+ `connection_closed` / `http_status` / `not_mcp_response` / `spawn_failed` / `timeout` /
10259
+ `protocol` / `invalid_config` / `unknown`). The retired 7.5.0 spellings โ€” `network` and the
10260
+ `http_<status>` encoding โ€” are GONE; an HTTP status now rides `httpStatus`.
10261
+ Deliberately NOT enumerated here: the vocabulary''s single owner is the engine, and mirroring
10262
+ it would swallow a newly minted word as a violation. Switch with a `default` arm.
10263
+ delivered:
10264
+ allOf: [{ $ref: '#/components/schemas/McpDelivered' }]
10265
+ description: 'core >= 7.6.0 (S6-B) โ€” did the request reach that server. Read WITH `errorCode`.'
10266
+ httpStatus:
10267
+ type: integer
10268
+ description: >
10269
+ core >= 7.6.0 (S6-B) โ€” the status the endpoint answered with; rides ONLY with
10270
+ `errorCode === "http_status"` (401/403 = re-authorize, 5xx = that end is down). ABSENT rather
10271
+ than 0.
9472
10272
  governance:
9473
10273
  type: object
9474
10274
  additionalProperties: false
@@ -9501,6 +10301,16 @@ components:
9501
10301
  `memory.harvest_quarantined` `{count, moved, escalated, reason?}` โ€” `moved` and `escalated` MUST NOT be
9502
10302
  subtracted from each other (an in-place tombstone counts as both) ยท `memory.delegation_static_mark_waived`
9503
10303
  `{reason, subagentType?, sessionId?}`.
10304
+ `config.durable_gate_unavailable` โ€” a per-principal `forceDurableGate` entitlement took effect on a leg
10305
+ with NO checkpoint store, so every ask on that leg settles on the LIVE chain (answered in-stream where a
10306
+ live approval / question face exists, fail-closed denied where none does) and NO durable approval record
10307
+ is written. `{sessionId, runId, principal?, cause, liveApprover, liveQuestionFace}`.
10308
+ ๐Ÿ”ด `cause` is a CLOSED two-word set: `no_deployment_store` (this deployment wired no store) and
10309
+ `task_store_disabled` (`TaskSpec.checkpointStore: "disabled"` โ€” the per-run off switch).
10310
+ โš ๏ธ server >= 7.64.0 / core 7.6.0 (D-9) RENAMED that second word from `task_store_null`, and the off
10311
+ switch itself became the WORD `"disabled"` instead of `null` (a string is not nullish, so the ordinary
10312
+ coalesce is the whole resolution rule and no reader has to remember a null test). A consumer still
10313
+ matching `task_store_null` matches nothing.
9504
10314
  A notice whose `detail.sessionId` is absent is HONESTLY NOT DELIVERED (the server never guesses a session),
9505
10315
  so the frame's absence does NOT mean "did not happen" โ€” the operator-facing structured log always has the
9506
10316
  full family. `detail` is already redacted + size-bounded by the server; still treat it as external text.
@@ -9553,14 +10363,28 @@ components:
9553
10363
  kind:
9554
10364
  type: string
9555
10365
  description: >
10366
+ The SIX known kinds (`server src/http/wire-gate.ts` `WIRE_GATE_KINDS`), listed for codegen and
10367
+ autocomplete โ€” NOT a closed set, and deliberately NOT an `enum` (see the note below):
9556
10368
  `human` โ€” plain HITL approval (budget-auto-approvable). `irreversible_ask` โ€” design/37
9557
10369
  irreversibility / design/70 egress tighten; NOT budgetable (load-bearing safety gate).
9558
10370
  `resource_limit` โ€” design/74 cost/token/time slice boundary; resolve `{decision:"continue"}`.
9559
10371
  `needs_review` โ€” design/76 dry-run/shadow โ†’ durable `needs_review` TERMINAL (post-prediction
9560
10372
  review, disjoint from the approval family). `task_done` โ€” design/38 Path A background sub-task
9561
- handle (door B never sees it).
9562
- enum: [human, irreversible_ask, resource_limit, needs_review, task_done]
10373
+ handle (door B never sees it). `plan_review` โ€” [2400] TR-8 / design/80 D-B PRE-ACTION plan
10374
+ review, resolved at `POST /v1/assistant/tasks/:id/plan_review` (3-state), never at `/decide`.
10375
+ An unknown future kind is VALID on the wire: branch on `kind` and render it generically.
9563
10376
  x-open-enum: true # do not close this union โ€” an unknown future kind is VALID on the wire
10377
+ # ๐Ÿ”ด ้กถๅฑ‚ `enum` **ๅทฒๅˆ **(S-170,codex r1 [high] ้ชŒ็œŸๅŽไฟฎ)โ€”โ€” ๅฎƒไธŽๆœฌ schema ่‡ชๅทฑๅฃฐๆ˜Ž็š„ๅผ€้›†่ฏญไน‰
10378
+ # ็›ดๆŽฅ็Ÿ›็›พ,่€Œ็Ÿ›็›พ็š„้‚ฃไธ€ๅคดๅœจๆ‰งๆณ•:
10379
+ # โ‘  ้‚ฃๅผ ่กจๅœๅœจ**ไบ”**่ฏ,ๆผไบ† `plan_review` โ€”โ€” ๅฎƒ่‡ช [2400] TR-8 ่ตทๅฐฑๆ˜ฏ**็Žฐๅฝน**้—จ(SDK ็š„
10380
+ # `CheckpointGate.kind` ไธŽ server ็š„ `WIRE_GATE_KINDS` ๅ…ญ่ฏ้ƒฝๆœ‰ๅฎƒ),ไธ‹้ข catch-all ่‡‚็š„ๆณจ
10381
+ # ็”š่‡ณ่ฟ˜ๆŠŠๅฎƒๅฝ“ๆˆใ€Œๆœชๆฅ็š„ๆœช็Ÿฅ kindใ€ไธพไพ‹ใ€‚ไธ€ๆฌก plan_review park ็š„ๆ•ดๆกๅ“ๅบ”ๅ› ๆญค่ขซๅˆค่ฟ็บฆใ€‚
10382
+ # โ‘ก ๅฐฑ็ฎ—่กฅ้ฝ็ฌฌๅ…ญ่ฏ,้กถๅฑ‚ `enum` ไปๆ˜ฏ**ๅˆๅ–**็š„:`x-open-enum` ๅชๆ˜ฏๆ„ๅ›พๆ ‡่ฎฐ,ๆ ‡ๅ‡† JSON Schema
10383
+ # ็š„ `enum` ็…งๆ ทๆ‰งๆณ• โ‡’ core ๅŠ ็ฌฌไธƒไธช้—จ็š„ๅฝ“ๅคฉ,ๆฏไธ€ๆก้‚ฃ็ฑป park ๅ“ๅบ”ๅ…จ็บฟ่ฟ็บฆใ€‚
10384
+ # โ‡’ **kind ็š„ๅˆๆณ•ๆ€งๅช็”ฑไธ‹้ข็š„ `oneOf` ไธ€ๅค„ๅ†ณๅฎš**(ๅ…ทๅ่‡‚็ฎกๅทฒ็Ÿฅ่ฏ็š„ๅฝข็Šถ,catch-all ็ฎกๆœช็Ÿฅ่ฏ),
10385
+ # ่ง„ๅˆ™ไปŽไธคๅค„ๅ˜ไธ€ๅค„ใ€‚ๅทฒ็Ÿฅ่ฏ่กจ็•™ๅœจๆœฌๆ่ฟฐ้‡Œ็ป™ codegen / ่‡ชๅŠจ่กฅๅ…จ่ฏป,ไธๅ†ๆ˜ฏ็ฌฌไบŒ้“ๆ‰งๆณ•็‚นใ€‚
10386
+ # (ๆœฌ่ฝฆๆŠŠ `terminal.paused.gate` ๆŒ‡ๅ‘ๆœฌ schema ไน‹ๅŽ,่ฟ™ๆกๆ—ขๅญ˜็ผบ้™ท็š„ๅฐ„็จ‹ไปŽ `suspended` ๅธงๆ‰ฉๅˆฐไบ†
10387
+ # ๆฏไธ€ๆก park ็ปˆๅฑ€ โ€”โ€” ๆ‰€ไปฅๅœจๆœฌ่ฝฆ้‡Œไฟฎ,่€Œไธๆ˜ฏ็•™็ป™ไธ‹ไธ€่ฝฆใ€‚)
9564
10388
  reason: { type: string, description: 'present on human / irreversible_ask / resource_limit / needs_review (not task_done).' }
9565
10389
  toolName: { type: string, description: 'present on human / irreversible_ask.' }
9566
10390
  oneOf:
@@ -9579,12 +10403,16 @@ components:
9579
10403
  - type: object # design/38 Path A background sub-task handle (door B never sees it)
9580
10404
  required: [kind]
9581
10405
  properties: { kind: { const: task_done } }
9582
- - type: object # OPEN SET โ€” unknown future kind (e.g. design/80 D-B plan_review); render generically, never crash
10406
+ - type: object # [2400] TR-8 / design/80 D-B โ€” PRE-ACTION plan review; resolved at POST /v1/assistant/tasks/:id/plan_review
10407
+ required: [kind, reason]
10408
+ properties: { kind: { const: plan_review } }
10409
+ - type: object # OPEN SET โ€” a future kind this contract predates; render generically, never crash
9583
10410
  required: [kind]
9584
- # ๐Ÿ”ด the pinned wire contract: exclude the 5 KNOWN kinds so a known kind matches ONLY its const member above
10411
+ # ๐Ÿ”ด the pinned wire contract: exclude the KNOWN kinds so a known kind matches ONLY its const member above
9585
10412
  # (not also this fallback) โ†’ keeps `oneOf` unambiguous AND preserves each kind's shape validation.
9586
- # This fallback matches ONLY a future kind this contract predates. Add new known kinds to the enum.
9587
- properties: { kind: { type: string, not: { enum: [human, irreversible_ask, resource_limit, needs_review, task_done] } } }
10413
+ # This fallback matches ONLY a future kind this contract predates. Add new known kinds to the enum
10414
+ # AND to this exclusion list (S-170: `plan_review` was a KNOWN kind sitting in neither).
10415
+ properties: { kind: { type: string, not: { enum: [human, irreversible_ask, resource_limit, needs_review, task_done, plan_review] } } }
9588
10416
 
9589
10417
  Event_suspended:
9590
10418
  type: object
@@ -10089,6 +10917,53 @@ components:
10089
10917
  parentToolCallId: { type: string }
10090
10918
  depth: { type: integer }
10091
10919
  agentName: { type: string }
10920
+ # โ”€โ”€โ”€ S-139(sdk 8.3.0):ๅฏน server 7.60.0 ็š„ๆ•ดไฝ“้‡ๅฏน่ดฆ่กฅ้ฝ็š„ 9 ไฝ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
10921
+ # ้•ฟ description ็š„ๅ•ไธ€็œŸๆบๅœจ `packages/sdk/src/resources/tool-approvals.ts` ็š„ๅŒๅๆˆๅ‘˜ไธŠ;
10922
+ # ่ฟ™้‡Œๅช็•™ codegen ้œ€่ฆ็š„ๅฝขไธŽ็กฌๆกๆฌพไธ€ๅฅใ€‚ๆœบๆขฐๅฏน่ดฆ้—จ = `test/server-keyset-parity.test.ts`ใ€‚
10923
+ requiresRealApproval:
10924
+ type: boolean
10925
+ enum: [true]
10926
+ description: >
10927
+ server >= 7.30.0 (core 5.37, #283), "tool_approval" only, ADDITIVE: the SAFETY-class provenance
10928
+ bit core stamps at the gate. Present (true) or ABSENT โ€” never encoded as false. Deliberately a
10929
+ DIFFERENT contract from the card's `risk.requiresRealApproval` (which is an always-present
10930
+ boolean): the card form needs the durable ask store, and this key is what lets a LIVE-ONLY
10931
+ deployment read the bit at all. Present โ‡’ every automatic allowance (remembered rules,
10932
+ bypassPermissions posture) stands down.
10933
+ origin: { $ref: '#/components/schemas/AskOrigin' }
10934
+ inputHasBidi:
10935
+ type: boolean
10936
+ enum: [true]
10937
+ description: >
10938
+ server >= 7.45.0 (E-14), "tool_approval" only, ADDITIVE: the TO-BE-EXECUTED input (`args`)
10939
+ contains Unicode bidi control characters โ€” what the eye reads may not be the byte order that
10940
+ runs. DISCLOSURE bit, bytes unchanged (sanitising would change the bytes about to execute, which
10941
+ is WORSE than not disclosing); rendering is the shell's. Present (true) or ABSENT; absence !=
10942
+ "verified clean". Computed BEFORE the byte cap, so it can ride a frame whose `args` were omitted.
10943
+ expiresAtMs:
10944
+ type: integer
10945
+ description: >
10946
+ server >= 7.34.0 (#288), "tool_approval" only, ADDITIVE: window triple (with `expiresInMs` and
10947
+ `serverNowMs`, same names/semantics as on ApprovalRequestFrame) so the LEGACY frame family can
10948
+ render a countdown too. `expiresAtMs` = this ask's absolute deadline on the server clock. The
10949
+ three keys are BORN AND ABSENT TOGETHER โ€” any frame that gets emitted carries all three.
10950
+ expiresInMs:
10951
+ type: integer
10952
+ description: 'Window triple (see expiresAtMs): max(0, expiresAtMs - serverNowMs), computed at mint time.'
10953
+ serverNowMs:
10954
+ type: integer
10955
+ description: 'Window triple (see expiresAtMs): the server clock at FRAME MINT time โ€” the countdown anchor; use it to correct local clock skew.'
10956
+ ruleOffersAbsence: { $ref: '#/components/schemas/RuleOffersAbsence' }
10957
+ denialLimitFallback: { $ref: '#/components/schemas/DenialLimitFallback' }
10958
+ parked:
10959
+ type: boolean
10960
+ enum: [true]
10961
+ description: >
10962
+ server >= 7.42.0 (#329), "tool_approval_complete" ONLY, ADDITIVE: the explicit discriminator for
10963
+ the PARK sense of `outcome: "expired"` (which is one word with three landings). Present โ‡” THIS ask
10964
+ settled through the park route โ‡’ render "moved to the background queue", not "the card died".
10965
+ ๐Ÿ”ด ABSENCE MUST NOT BE READ AS "really denied": a store-less deployment, a deny policy, and a
10966
+ sibling VOIDed in a batch all emit no such key โ€” absence only means "no park evidence".
10092
10967
  outcome:
10093
10968
  type: string
10094
10969
  enum: [allowed, denied, expired]
@@ -10226,6 +11101,10 @@ components:
10226
11101
  probeCause: { type: object, additionalProperties: true, description: 'server >= 7.17.0: structured reversibility-probe tightening cause โ€” same shape and semantics as ToolApprovalFrame.probeCause ({code, roots:{shown,total}, further?:{shown,total}}); frame and card carry the SAME value (one narrow-read function server-side). See that key for the full contract.' }
10227
11102
  ruleEvidence: { type: object, additionalProperties: true, description: 'server >= 7.23.0: rule-provenance evidence โ€” same shape and semantics as ToolApprovalFrame.ruleEvidence (each member a value or a NAMED absence word; display/reconciliation metadata, never adjudication input). Frame and card carry the SAME value. See that key for the full contract.' }
10228
11103
  delegation: { $ref: '#/components/schemas/ApprovalCardDelegation' }
11104
+ # S-139(sdk 8.3.0):ๅŒ A-057.โ‘ฃ ็š„็†็”ฑ โ€”โ€” server ๆ—ฉ้šๅกๆŠ•ๅ‡บ(ๅธงไธŽๅกๅŒไธ€ๅช็ช„่ฏปๅ‡ฝๆ•ฐ โ‡’ ๅŒๅ€ผ),
11105
+ # ไฝ†ๆœฌ schema ๆญคๅ‰ๅชๅœจๅธงไธŠๅ…ฌ็คบ่ฟ‡ใ€‚้•ฟ description ็š„ๅ•ไธ€็œŸๆบๅœจ ToolApprovalFrame ็š„ๅŒๅ้”ฎใ€‚
11106
+ ruleOffersAbsence: { $ref: '#/components/schemas/RuleOffersAbsence' }
11107
+ denialLimitFallback: { $ref: '#/components/schemas/DenialLimitFallback' }
10229
11108
 
10230
11109
  RuleSuggestion:
10231
11110
  type: object
@@ -10248,6 +11127,87 @@ components:
10248
11127
  description: '`exact` = this one command only (`Bash(git status)`); `prefix` = word-boundary prefix (`Bash(git status:*)`). Closed vocabulary โ€” an unknown value is not a candidate this contract describes; do not render it as redeemable.'
10249
11128
  command: { type: string, maxLength: 512, description: 'The command pattern inside the rule, for a shell that would rather not re-parse `rule`.' }
10250
11129
 
11130
+ # โ”€โ”€โ”€ S-139(sdk 8.3.0):ๅฏน server 7.60.0 ๆ•ดไฝ“้‡ๅฏน่ดฆๅธฆ่ฟ›ๆฅ็š„ๅ››ๅผ ๅž‹้ข โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
11131
+ AskOrigin:
11132
+ type: string
11133
+ x-open-enum: true
11134
+ # ๐Ÿ”ด ้กถๅฑ‚ `enum` **ไธๆ”พ่ฟ™้‡Œ**(S-170 codex r2 [medium] ้ชŒ็œŸๅŽไฟฎ,ไธŽๆœฌ่ฝฆๅฏน `CheckpointGate` ็š„ๅค„็ฝฎๅŒๆฌพ):
11135
+ # ่ฏ่กจๅฑžไธปๆ˜ฏๅผ•ๆ“Žใ€่€Œไธ”ๅฎƒ**็œŸ็š„้•ฟ่ฟ‡**(core 7.6.0 ไปŽ 8 ่ฏๅŠ ๅˆฐ 10 ่ฏ),่€Œ `tool_approval` ๅธง่ฟ™ไธ€้ข
11136
+ # server ๅชๅˆค้ž็ฉบไธฒใ€ไธๅˆคๆˆๅ‘˜(`src/tool-approval.ts` ๅธง้“ธ็‚น)โ‡’ ็•™ไธ€ไธช `enum` ๅœจ่ฟ™้‡Œ,core ๅŠ ่ฏ็š„
11137
+ # ๅฝ“ๅคฉ,ไธ€ไธช**ๅˆๆณ•**็š„ๅฎกๆ‰นๅกๅฐฑไผš่ขซไธฅๆ ผๆถˆ่ดน็ซฏๆ‹’ๆ”ถใ€‚`x-open-enum` ๅชๆ˜ฏๆ„ๅ›พๆ ‡่ฎฐ,ไธ่งฃ้™ค enum ็š„ๆ‰งๆณ• โ€”โ€”
11138
+ # ่ฟ™ๆญฃๆ˜ฏๆœฌ่ฝฆๅˆšไปŽ `CheckpointGate` ๆ‹†ๆމ็š„้‚ฃไธชๅ‡ๅผ€้›†,ไธๅœจ่ฟ™้‡Œๆขไธชๅœฐๆ–น้‡็Šฏใ€‚
11139
+ # ้—ญ้›†**ๆ‰งๆณ•**ๅชๅฑžไบŽ**็ญ›่ฟ‡็š„**้‚ฃไธ€้ข(`GateOutcome.origin`:ๅผ•ๆ“Ž็š„ `screenGateOutcome` ๅˆคๆˆๅ‘˜,
11140
+ # ๅ‡บ้›†ๅณๆ•ดๆกไธไธŠๅธง),ๆ‰€ไปฅ้‚ฃๅผ  enum ่ฝๅœจ**้‚ฃไธชๅผ•็”จ็‚น**,ๆฐไธ€ๅค„ใ€‚ๅทฒ็Ÿฅๅ่ฏ่งไธ‹้ข็š„ๆ่ฟฐใ€‚
11141
+ description: >
11142
+ WHO raised an ask (`ToolApprovalFrame.origin`; server >= 7.57.0 / core 7.5.0, S-125โ‘ข/#564) โ€” core's
11143
+ `ASK_ORIGINS`, ENGINE-STAMPED at the gate (core's words: "engine-stamped at the gate, never a policy's
11144
+ claim"), transcribed verbatim by the server, which deliberately mints no second eligibility table of
11145
+ its own.
11146
+
11147
+ ๐Ÿ”ด READ AS AN OPEN SET. The vocabulary's sole owner is core and it has grown before; branch with a
11148
+ default arm and treat an unknown word as "unknown origin", never as "no origin".
11149
+ ๐Ÿ”ด NOT interchangeable with `governanceForced`: this says which KIND of authority asked, that one says
11150
+ whether THIS DEPLOYMENT's ops governance layer is the gate โ€” `origin: policy` covers any deployment
11151
+ ToolPolicy's ask, so a governance-produced ask and an ordinary one look identical in this one word.
11152
+
11153
+ ๐Ÿ”ด core 7.6.0 ADDED TWO WORDS (the set was eight through core 7.5.x): `shell_gate_tighten` = the gate's
11154
+ own irreversibility tighten raised the ask over a tier the COARSE `shellGate` doctrine installed (a
11155
+ shell command under `shellGate: "classify"` / `"always"`), and `safety_tighten` = the gate's own
11156
+ post-fold tightens over an EXPLICIT fact of the call (an egress mark, an irreversibility mark, a
11157
+ peer-message referral in auto mode, a write onto the write-protected table). They are separate words
11158
+ precisely so a deny observer or an approval card can say WHICH engine layer raised the question โ€” one
11159
+ word could not. Both are classifier-eligible (arming auto mode is the deployment's explicit choice to
11160
+ let the classifier resolve those classes).
11161
+
11162
+ The SAME type is the `origin` member of `GateOutcome` on `tool_end` โ€” one vocabulary, two faces.
11163
+
11164
+ RuleOffersAbsence:
11165
+ type: string
11166
+ enum: [mandated, shadowed, lane_cannot_speak]
11167
+ description: >
11168
+ WHY `ruleOffers` is absent while the rule lane IS present (`ToolApprovalFrame.ruleOffersAbsence` and
11169
+ `ApprovalCard.ruleOffersAbsence`; server >= 7.55.0 / core #490 fix โ‘ก, S-15). The engine names which
11170
+ door is shut: `mandated` = this ask must be confirmed every time (๐Ÿ”ด this arm must NOT point the user
11171
+ at writing a rule โ€” a rule would not silence it, and saying so sends them into a loop); `shadowed` = a
11172
+ rule matched but could not clear it (the machine-readable twin of `persistedRuleShadowed`);
11173
+ `lane_cannot_speak` = the engine could mint no coverable form for this command.
11174
+
11175
+ Engine-side MUTUALLY EXCLUSIVE with `ruleOffers`. ๐Ÿ”ด ABSENCE IS NOT AN ASSERTION โ€” it covers both
11176
+ "there are offers" and the three structural doors, and one frame cannot tell them apart. ADVISORY
11177
+ display metadata, NEVER adjudication input. The frame and the stored `card_json` go through the SAME
11178
+ server-side narrow read, so both faces carry identical values; a word outside the set is treated as
11179
+ malformed and the key is not minted at all (the server never passes an unknown word through).
11180
+
11181
+ DenialLimitKind:
11182
+ type: string
11183
+ enum: [consecutive, total]
11184
+ description: 'Which bound tripped in a `DenialLimitFallback` โ€” the consecutive-refusal limit or the cumulative one.'
11185
+
11186
+ DenialLimitFallback:
11187
+ type: object
11188
+ additionalProperties: false
11189
+ required: [consecutive, total, limit, autoDenyAfterMs]
11190
+ description: >
11191
+ The auto-mode classifier's DENIAL-LIMIT FALLBACK card (`ToolApprovalFrame.denialLimitFallback` and
11192
+ `ApprovalCard.denialLimitFallback`; server >= 7.57.0 / core 7.4.0 #548, S-114): the call that hit the
11193
+ consecutive (default 3) or cumulative (default 20) refusal limit is no longer silently denied โ€” it
11194
+ becomes a card a HUMAN must decide. The same mint also stamps `requiresRealApproval`; the two are twins.
11195
+
11196
+ ๐Ÿ”ด ALL FOUR MEMBERS ARE REQUIRED: core's shape has no optional member, so "one missing" is a bad value
11197
+ rather than an older core, and the server drops the whole key instead of half-minting a card whose
11198
+ counts would be misread.
11199
+ ๐Ÿ”ด `autoDenyAfterMs` (ms; `0` = not armed โ€” the deployment turned the knob off, or a TOTAL-tier card
11200
+ waits for a person) is for RENDERING THE COUNTDOWN ONLY. Never start a second timer from it: the window
11201
+ is executed by the ENGINE, and two overlapping windows are worse than the original defect and silent.
11202
+ ๐Ÿ”ด ABSENCE IS NOT AN ASSERTION โ€” the vast majority of asks are not fallback cards.
11203
+ โš ๏ธ The durable (parked) leg does NOT carry this key today; the card's copy rides `card_json`, which is
11204
+ a different leg from the parked row's `pendingAction`.
11205
+ properties:
11206
+ consecutive: { type: integer, description: 'Consecutive refusals at the moment the limit tripped.' }
11207
+ total: { type: integer, description: 'Cumulative refusals at the moment the limit tripped.' }
11208
+ limit: { $ref: '#/components/schemas/DenialLimitKind' }
11209
+ autoDenyAfterMs: { type: integer, description: 'This card''s own auto-deny window in ms; 0 = not armed. Countdown rendering only.' }
11210
+
10251
11211
  RuleOfferMatch:
10252
11212
  type: string
10253
11213
  description: >
@@ -10928,8 +11888,15 @@ components:
10928
11888
  ยท `ActiveRunConflictDoneResult` โ€” the SSE lane's REJECTION terminal: when the session already has an
10929
11889
  active run, the submit's 409 is encoded as this done frame (`src/http/routes/tasks.ts`), and it
10930
11890
  carries NO `taskId` / `sessionId` / `stats`.
10931
- Narrow on the presence of `stats` (required on TaskResult, absent from the conflict shape), or on
10932
- `errorCode === "conflict.session_active_run"`. `status` does NOT discriminate โ€” both shapes carry it.
11891
+ Narrow in TWO steps: `stats` present (an engine result) vs absent (the conflict envelope) โ€” or match
11892
+ `errorCode === "conflict.session_active_run"` โ€” and then, among the engine results, `"terminal" in result`
11893
+ (the current cause form) vs absent (`LegacyPlaneTaskResult`, the flat bytes an older generation left in
11894
+ the durable ledger; a REPLAYED frame on an upgraded deployment can be that shape).
11895
+ ๐Ÿ”ด server >= 7.64.0 โ€” the two arms are now told apart by their KEYS, not by a shared word: the normal
11896
+ terminal carries `terminal` (the tagged cause) and NO `status`; the conflict shape carries the flat
11897
+ `status: "failed"` + `errorCode` because it is the SERVICE's own rejection envelope, not an engine
11898
+ result, and it did not change. `status` therefore appears on exactly one arm โ€” but keep narrowing on
11899
+ `stats` / `errorCode`, which are the discriminants the service pins.
10933
11900
  additionalProperties: false # ๅฐ้—ญ:ๅธงๆœฌ่บซๅชๆœ‰ {type,result,replay?};`result` ๅ†…้ƒจๆŒ‰่‡‚ๅ„่‡ช่ฃๅฎš(TaskResult ๅผ€้›†,ๅ†ฒ็ชๅฝขๅฐ้—ญ)
10934
11901
  required: [type, result]
10935
11902
  properties:
@@ -10938,6 +11905,7 @@ components:
10938
11905
  oneOf:
10939
11906
  - $ref: '#/components/schemas/TaskResult'
10940
11907
  - $ref: '#/components/schemas/ActiveRunConflictDoneResult'
11908
+ - $ref: '#/components/schemas/LegacyPlaneTaskResult'
10941
11909
  replay:
10942
11910
  type: boolean
10943
11911
  description: >
@@ -11819,7 +12787,9 @@ components:
11819
12787
  core's assembly self-evidence manifest (core >=5.14.0 `WiringManifest`). TWO HALVES, ONE shape (a
11820
12788
  consumer must line the two faces up, so they are not split into separate types) โ€” but which keys
11821
12789
  belong to which half is DETERMINATE, not "usually": `leg`, `ask.effective` and `configFingerprint`
11822
- are EFFECTIVE-half only (the live `wiring_manifest` event) and the STATIC half
12790
+ are EFFECTIVE-half only (the live `wiring_manifest` event) โ€” as are the three per-leg sections the
12791
+ event schema declares (`modelGate`, `autoMode`, `mcp`), which describe what THIS leg did and have no
12792
+ static counterpart โ€” and the STATIC half
11823
12793
  (GET /v1/diagnostics/wiring) NEVER mints them; every other section, plus `governance`, is always
11824
12794
  present on the static half. Fields stay optional because core may add or drop sections and that
11825
12795
  must not hard-break a client โ€” read by half, and never wait on a key the half never sends.
@@ -11907,12 +12877,52 @@ components:
11907
12877
 
11908
12878
  WiringDiagnostics:
11909
12879
  type: object
11910
- description: 'GET /v1/diagnostics/wiring response (operator-only). No per-leg history by design.'
12880
+ description: >
12881
+ GET /v1/diagnostics/wiring response (operator-only). No per-leg history by design.
12882
+ ๐Ÿ”ด Every posture key here answers "what can THIS PROCESS say", so its absent form is an explicit
12883
+ `null` โ€” never an empty shell that would read as a real answer. `null` = this process cannot say
12884
+ (it was not assembled by the composition root, or the facility is not wired).
11911
12885
  required: [static, serverGates]
11912
12886
  additionalProperties: false
11913
12887
  properties:
11914
12888
  static: { $ref: '#/components/schemas/WiringManifest' }
11915
12889
  serverGates: { $ref: '#/components/schemas/ServerWiringGates' }
12890
+ writeProtection:
12891
+ oneOf:
12892
+ - $ref: '#/components/schemas/WriteProtectionPosture'
12893
+ - type: 'null'
12894
+ description: >
12895
+ server >= 7.63.0 (S-138) โ€” the write-protection table, WITH row contents (operator face only; the
12896
+ tenant face `capabilities.writeProtection` carries counts alone). Same boot artefact as that bit.
12897
+ memoryPosture:
12898
+ type: ["object", "null"]
12899
+ additionalProperties: true
12900
+ description: >
12901
+ server >= 7.30 (#252) โ€” the memory plane's posture (backend / vector mode / lit-or-dark and why /
12902
+ embedder identity). Facts that used to live only in the startup log, which is neither queryable nor
12903
+ aggregatable across replicas. NOT YET MIRRORED KEY-BY-KEY in this SDK generation: declared here so a
12904
+ strict consumer stops rejecting a real operator response, read it as opaque.
12905
+ sqlEngine:
12906
+ type: ["object", "null"]
12907
+ additionalProperties: true
12908
+ description: >
12909
+ server >= 7.60 (S-131) โ€” this deployment's SQL engine posture (engine / version / session isolation
12910
+ level / TiDB transaction mode), READ BACK off a live connection rather than echoed from env.
12911
+ `null` = no SQL backend. NOT YET MIRRORED KEY-BY-KEY in this SDK generation โ€” read as opaque.
12912
+ configApply:
12913
+ type: ["object", "null"]
12914
+ additionalProperties: true
12915
+ description: >
12916
+ server >= 7.5x (#322) โ€” the config generation ledger's operator face (per-group appliedVersion,
12917
+ deferredKeys with their reason, and a SANITISED lastRejected: key names and verdict text only,
12918
+ never values). The three keys on `/health` are a narrow projection of this same reading.
12919
+ NOT YET MIRRORED KEY-BY-KEY in this SDK generation โ€” read as opaque.
12920
+ mcpRevocations:
12921
+ type: ["object", "null"]
12922
+ additionalProperties: true
12923
+ description: >
12924
+ server >= 7.5x (#324, design/338) โ€” the mid-turn MCP revocation list. `null` = this process has no
12925
+ config pipeline (it cannot say). NOT YET MIRRORED KEY-BY-KEY in this SDK generation โ€” read as opaque.
11916
12926
 
11917
12927
  SendfileLinkRow:
11918
12928
  type: object