@sema-agent/sdk 22.0.3 → 23.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.
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: a name in `allowTools` / `excludeTools` that no tool of this run answers to, `allowTools: ["mcp__*"]`, `excludeTools: ["*"]`, 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), `corrupt_session`; 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
@@ -2511,7 +2511,8 @@ paths:
2511
2511
  description: >
2512
2512
  E6 — write the per-session rules. 🔴 TIGHTEN-ONLY for a normal principal: a loosen (remove a deny / widen
2513
2513
  an allowlist or allowDirs) → 403 `loosen_forbidden` (operator-only). `expectedRev` = optimistic-concurrency
2514
- CAS; a stale rev → 409 `conflict`. Each rule field must be `string[]` → malformed body 400. ownerless
2514
+ CAS; a stale rev → 409 `conflict`. Each rule field must be `string[]` and the body carries only the three buckets (`toolAllow` / `toolDeny` / `allowDirs`; any other
2515
+ key, including the retired `commandAllow` / `commandDeny`, → 400 `rules.invalid_shape` on server >= 7.129.0) → malformed body 400. ownerless
2515
2516
  session → 409; non-owner → 404. 501 without a session-policy store.
2516
2517
  requestBody:
2517
2518
  required: true
@@ -2521,7 +2522,22 @@ paths:
2521
2522
  type: object
2522
2523
  required: [rules]
2523
2524
  properties:
2524
- rules: { $ref: '#/components/schemas/SessionPermissionRules' }
2525
+ # 🔴 请求体里的 `rules` 收闭(server >= 7.129.0:写口的判官拒一切不是桶的键,含退役命令桶 ⇒ 400 `rules.invalid_shape`)。
2526
+ # `SessionPermissionRules` 本体保持开放(响应 / 同步面的 `StoredSessionRules` 经 allOf 引它并再加 `rev` / `gen`,本体收闭会把合法的 `rev` 判成违约);
2527
+ # 所以收闭落在**这一个引用点**:allOf + 本地镜像三桶 + `additionalProperties:false`(spec.test 的通用「封闭 allOf 必须镜像分支键」门看守镜像完整)。
2528
+ rules:
2529
+ allOf: [{ $ref: '#/components/schemas/SessionPermissionRules' }]
2530
+ additionalProperties: false
2531
+ properties:
2532
+ toolAllow: { type: array, items: { type: string } }
2533
+ toolDeny: { type: array, items: { type: string } }
2534
+ allowDirs: { type: array, items: { type: string } }
2535
+ # server >= 7.129.0 (engine 7.51.2): a stored body echoed back (GET → edit → PUT) may carry its `rev` and `grammar`;
2536
+ # the engine reads the entries in THAT grammar (`rev` without `grammar` = grammar 0 ⇒ a rule-spelled allow entry
2537
+ # answers `400 rules.inert_entry` reason `earlier_grammar`; the way out is to send the three buckets alone — the
2538
+ # write judge stamps the current grammar). `grammar` newer than the engine's ⇒ `400 rules.invalid_shape`.
2539
+ rev: { type: integer, minimum: 0 }
2540
+ grammar: { type: integer, minimum: 0 }
2525
2541
  expectedRev: { type: integer, minimum: 0, description: CAS — reject if the stored rev differs. }
2526
2542
  responses:
2527
2543
  '200':
@@ -2537,18 +2553,22 @@ paths:
2537
2553
  rules: { $ref: '#/components/schemas/StoredSessionRules' }
2538
2554
  '400':
2539
2555
  description: >-
2540
- 400 — the rules body is refused BEFORE anything is written (not one byte reaches the store). Branch on `errorCode`, never on the sentence.
2541
- `rules.legacy_tool_name` (server >= 7.105.0; the `rules.*` family, the SDK lands it on `BadRequestError`): a tool-name bucket (`toolAllow` / `toolDeny`) entry
2542
- that would be written but could never run — an engine-retired name, a `__` name without a protocol prefix, or a protocol name whose peer segment the
2543
- engine cannot mint. Body keys (top level; the SDK reads them from `err.extra`): `field` (which bucket), `name` (which entry), `verdict`
2544
- (`retired` / `unprefixed_namespace`, server >= 7.116.0; `unmintable_segment`, server >= 7.118.0), `currentName` (server >= 7.116.0; present when
2545
- there is a 1:1 successor), `guidance` (server >= 7.119.0; the engine judge's wording of the fix, byte for byte — carried by `retired` and
2546
- `unmintable_segment`, absent for `unprefixed_namespace`). 🔴 server >= 7.123.0 also lands a protocol name with an EMPTY TOOL SEGMENT
2547
- (`mcp__<server>__`, `a2a__<peer>__`) here, as `verdict: "unmintable_segment"`; server 7.116.0–7.122.x answered `400 request.field_invalid`
2548
- for it — a caller that branched on that code for this shape now branches on `rules.legacy_tool_name` (the body's `verdict` tells the shapes apart).
2549
- `request.field_invalid`: a bucket that is not a list of non-empty strings (a bare string, `null`, a non-string member) and — behavior change
2550
- in server >= 7.123.0 — an EMPTY or BLANK (whitespace-only) member, which earlier servers took in (200); the sentence is the engine's own,
2551
- match on the code. Also `request.id_invalid` (malformed session id) and a malformed body.
2556
+ 400 — the rules body is refused BEFORE anything is written (not one byte reaches the store; the store's own refusal uses the same code and body). Branch on `errorCode`, never on the sentence.
2557
+ The `rules.*` family (the SDK lands both codes on `BadRequestError`); the judge is the ENGINE's write-door judge, one reader for the whole body (server >= 7.129.0, engine 7.51.0):
2558
+ `rules.inert_entry`: a rule entry in a bucket (`toolAllow` / `toolDeny`) that the engine reads as NOTHING — written down it would deny / allow nothing. Body keys (top level; the SDK reads them from `err.extra`):
2559
+ `field` (which bucket), `entry` (which entry, the original value), `reason` (the engine's `ALLOW_ENTRY_INERT_REASONS`, six words: `tool_name` no tool answers to the name · `wildcard_all` the bare `*` ·
2560
+ `namespace_wildcard` `mcp__*`-style wildcard naming no server · `content_unsupported` the tool has no rule form like that, e.g. `Monitor(touch:*)` · `grammar` the grammar fails, e.g. an empty string or `Bash(` ·
2561
+ `near_miss` a near-miss of a valid form), `verdict` (optional; only on `tool_name`: `retired` an engine-retired name / `unprefixed_namespace` a `__` name without a protocol prefix / `unmintable_segment` a protocol
2562
+ name whose peer segment or tool segment the engine cannot mint, or whose tool segment is EMPTY — `mcp__<server>__`, `a2a__<peer>__`), `currentName` (optional; present when there is a 1:1 successor, e.g. `bash` => `Bash`), `guidance` (optional; the engine judge's wording
2563
+ of the fix, byte for byte — carried by `retired` and `unmintable_segment`). The sentence is the engine's own: match on the code. Rewrite that entry and write again.
2564
+ `rules.invalid_shape`: the rules body itself has the wrong shape — not an object (`rules` absent, `null`, an array, a scalar), a key that is not a bucket (including the RETIRED `commandAllow` / `commandDeny`:
2565
+ never silently dropped, a dropped deny is enforced by nobody), a bucket that is not a list of strings, an `allowDirs` member that is empty / blank. Body keys: `field` (which key; the empty string when the
2566
+ body as a whole is not an object), `got` (the TYPE of that value, e.g. `a string` / `not a rule bucket` — the value is never echoed); `error` is the engine's sentence.
2567
+ The command rule's new spelling is an entry: `commandDeny: ["rm"]` => `toolDeny: ["Bash(rm:*)"]` (denies the command name `rm`; it does not deny a path-spelled `/bin/rm ...` nor a command line that does not read as one command
2568
+ name — the old bucket denied both; to deny those too deny the whole shell, `toolDeny: ["Bash"]`); `commandAllow` has no counterpart.
2569
+ 🔴 BREAKING (server 7.129.0): server 7.105.0–7.128.x answered this family with `rules.legacy_tool_name` (entry in `name`, no `reason`) — that code is retired with no alias; a caller that branched on it branches on the two codes
2570
+ above (the `rules.` prefix test still holds). On server <= 7.128.x the shape problems answered `400 request.field_invalid` (`rules` not an object: `request.body_shape`) and a body with a command bucket was accepted.
2571
+ Also `request.id_invalid` (malformed session id).
2552
2572
  content:
2553
2573
  application/json:
2554
2574
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3261,15 +3281,12 @@ paths:
3261
3281
  schema: { $ref: '#/components/schemas/ImportStaged' }
3262
3282
  '400':
3263
3283
  description: >-
3264
- 400 — malformed input (the `policy` face is judged by the same write-door reader as `PUT /v1/sessions/{sessionId}/policy`, BEFORE any staging
3265
- is created, so a refused push leaves nothing behind). Branch on `errorCode`, never on the sentence. `rules.legacy_tool_name` (server >= 7.105.0):
3266
- a record's tool-name bucket (`toolAllow` / `toolDeny`) holds a name that could never run — an engine-retired name, a `__` name without a protocol prefix,
3267
- or a protocol name whose peer segment the engine cannot mint. Body keys (top level; the SDK reads them from `err.extra`): `field`, `name`, `verdict`
3268
- (`retired` / `unprefixed_namespace`, server >= 7.116.0; `unmintable_segment`, server >= 7.118.0), `currentName` (server >= 7.116.0, when there is a 1:1 successor),
3269
- `guidance` (server >= 7.119.0, the engine judge's wording; absent for `unprefixed_namespace`). 🔴 server >= 7.123.0 also lands a protocol name with an EMPTY TOOL
3270
- SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) here (`verdict: "unmintable_segment"`); server 7.116.0–7.122.x answered `400 request.field_invalid` for it.
3271
- `request.field_invalid`: a bucket that is not a list of non-empty strings and — behavior change in server >= 7.123.0 — an EMPTY or BLANK member, which earlier
3272
- servers took in (the sentence carries the `policy: [i].rules: ` position prefix and then the engine's own sentence; match on the code).
3284
+ 400 — malformed input (the `policy` face is judged by the same engine write-door judge as `PUT /v1/sessions/{sessionId}/policy`, BEFORE any staging is created, so a refused push leaves nothing behind).
3285
+ Branch on `errorCode`, never on the sentence. `rules.inert_entry` / `rules.invalid_shape` (server >= 7.129.0, engine 7.51.0; the `rules.*` family, same code and same body as the policy door — read the bucket list, the `reason` words
3286
+ (`tool_name` / `wildcard_all` / `namespace_wildcard` / `content_unsupported` / `grammar` / `near_miss`) and the body keys `field` / `entry` / `reason` / `verdict` / `currentName` / `guidance` / `got` there):
3287
+ a record's rule entry the engine reads as nothing (e.g. a protocol name with an EMPTY TOOL SEGMENT — `mcp__<server>__`, `a2a__<peer>__` — has `reason` `tool_name` and `verdict` `unmintable_segment`), or a rules body of the wrong shape (including the retired `commandAllow` / `commandDeny` buckets). The sentence carries the record position prefix `policy: [i].rules: ` and then the
3288
+ engine's own sentence; match on the code. `rules.legacy_tool_name` is retired with no alias (server 7.105.0–7.128.x sent it) — it is replaced by `rules.inert_entry` (its `name` key is now `entry`).
3289
+ On server <= 7.128.x the shape problems answered `400 request.field_invalid` and a record with a command bucket was staged.
3273
3290
  content:
3274
3291
  application/json:
3275
3292
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3646,12 +3663,13 @@ paths:
3646
3663
  render it as "this card can only be denied / wait for a server upgrade". Not a regression: 7.31.x children
3647
3664
  never had those tools.
3648
3665
  🔴 THE TWO CAUSES ARE TOLD APART BY `cause` (server S-818, riding core #1161; ADDITIVE, optional): the body may
3649
- carry `cause` — core's CAUSE WORD for this startup failure, relayed VERBATIM from the engine receipt's `details`
3650
- (no rewrite, no default; absent when the engine did not say, i.e. every server before S-818 and every core before
3651
- #1161). The closed set is core's (#1161 exports it as a constant, named in core 7.34.1's release notes); this
3652
- spec copies no word list, so treat it as an OPEN string with a default arm. Absent ⇒ "cannot tell": offer both
3653
- ways out. SDK → `ParkedResumeStartupError.startupCause` (`undefined` when absent or not a string; the wire key stays
3654
- `cause` — the SDK does not put it on the standard `Error.cause`, which means a chained error).
3666
+ carry `cause` — core's CAUSE WORD for this startup failure, relayed from the engine receipt's `details`
3667
+ (no default; absent when the engine did not say, i.e. every server before S-818 and every core before
3668
+ #1161). The closed set is core's (#1161; core 7.51.0 exports it as `RESUME_STARTUP_CAUSES`); since server >= 7.129.0
3669
+ the key is one of those five words (`ResumeStartupCause`: `environment` / `session_gone` / `tool_unavailable` / `configuration` / `decision_rejected`)
3670
+ — the server screens it, a word outside the set is dropped; servers <= 7.128.x relay the word unscreened. Absent ⇒ "cannot tell": offer both
3671
+ ways out. SDK → `ParkedResumeStartupError.startupCause` (23.0.0: `ResumeStartupCause`; `undefined` when absent, not a string, or outside the five words — the raw value stays at `err.extra.cause`;
3672
+ the wire key stays `cause` — the SDK does not put it on the standard `Error.cause`, which means a chained error).
3655
3673
  content:
3656
3674
  application/json:
3657
3675
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -7144,7 +7162,7 @@ components:
7144
7162
  name: { type: string, description: 'the `subagent_type` the model passes to Task. Non-empty, ≤64 chars, UNIQUE per request.' }
7145
7163
  whenToUse: { type: string, description: 'when the model should delegate to this agent (shown in the Task tool''s agent list). ≤4096 chars.' }
7146
7164
  whenToUseLean: { type: string, description: 'compact variant of `whenToUse` for lean tool listings. ≤4096 chars.' }
7147
- allowTools: { type: array, items: { type: string }, description: 'allowlist of tool names. Omit ⇒ inherit ALL tools. server >= 7.126.0: the names are judged by the ENGINE, one selector shared with the root run''s `allowTools` / `excludeTools`; a name no tool answers to => the run is rejected with the terminal code `config.agent.unknown_tool` (not an HTTP code).' }
7165
+ allowTools: { type: array, items: { type: string }, description: 'allowlist of tool names. Omit ⇒ inherit ALL tools. server >= 7.126.0: the names are judged by the ENGINE, one selector shared with the root run''s `allowTools` / `excludeTools`; a name no tool answers to => the run is rejected with the terminal code `config.agent.unknown_tool` (not an HTTP code) on server 7.126.0–7.128.x, and RUNS AS WRITTEN on server >= 7.129.0 (the entry is kept, an operator row `runner_config_advisory` announces it once; `denyTools: ["bash"]` does not deny `Bash`).' }
7148
7166
  denyTools: { type: array, items: { type: string }, description: 'denylist (SUBTRACTIVE — applied after `allowTools`). server >= 7.126.0: same engine-judged selector as `allowTools`; `Tool(content)` => the whole tool.' }
7149
7167
  skills:
7150
7168
  type: array
@@ -7667,7 +7685,7 @@ components:
7667
7685
  🔴 server >= 7.118.0: the NAME layer of the allow side moved to the engine — a retired name, an unprefixed short name or an unmintable protocol name is ACCEPTED here (no 422) and the run opens with an
7668
7686
  `engine_notice` `permission.allowed_tool_not_granted` (`entries[].reason: tool_name`); the `deny` / `ask` buckets still refuse that family with a whole-request 422. 🔴 server >= 7.123.0 (core 7.48.0) also accepts a
7669
7687
  protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) in this bucket (server 7.118.0–7.122.x refused it on every table). One name, three outcomes:
7670
- allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name` (`verdict: unmintable_segment`).
7688
+ allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.inert_entry` (server >= 7.129.0; `reason: tool_name`, `verdict: unmintable_segment`; 7.123.0–7.128.x answered a retired code, named in the 400 of `PUT /v1/sessions/{sessionId}/policy`).
7671
7689
  🔴 THE 422 SENTENCE (server >= 7.117.0): `error` starts with `request settings cannot be applied as written: ` (<= 7.116.x: a different sentence that said tighten-only — all three
7672
7690
  buckets share this exit, and the old head misled a caller writing a tightening rule and lied about the allow bucket). Do NOT dispatch on the sentence: it carries no code of its own, so
7673
7691
  `errorCode` is the status-derived coarse code `request.unprocessable`.
@@ -7805,8 +7823,9 @@ components:
7805
7823
  items: { type: string }
7806
7824
  description: >
7807
7825
  Tool names to withhold from this task's roster. server >= 7.126.0 (engine 7.49.0, contract G.9e): read with the SAME grammar as `allowTools`; a name that
7808
- NO tool answers to rejects the run before it starts with `config.tool_face_control` (sync leg 400, no token burned; streamed / background legs: a `failed` terminal with
7809
- the same code) instead of being silently ignored, and `"*"` is rejected on this list too (unmount-everything is only `excludeAllTools`).
7826
+ NO tool answers to rejects the run before it starts with `config.tool_face_control` on server 7.126.0–7.128.x (sync leg 400, no token burned; streamed / background legs: a `failed` terminal with
7827
+ the same code); server >= 7.129.0 RUNS it as written (the entry is kept and takes effect if a tool of that name is mounted later; an operator row `runner_config_advisory` announces it once, with the spelling to write —
7828
+ names are read verbatim, so `excludeTools: ["bash"]` does not withhold `Bash`). `"*"` is rejected on this list too (unmount-everything is only `excludeAllTools`).
7810
7829
  allowTools:
7811
7830
  type: array
7812
7831
  items: { type: string }
@@ -9586,6 +9605,13 @@ components:
9586
9605
  additionalProperties: true
9587
9606
  properties:
9588
9607
  sideQuery: { type: boolean, description: 'POST /v1/side-query one-shot brain-routed Q&A face is wired (probe before use, no trial-by-501).' }
9608
+ inherit_env.own_credentials_withheld:
9609
+ type: boolean
9610
+ description: >-
9611
+ ENGINE CAPABILITY WORD (core `ENGINE_CAPABILITIES`, core 7.51.1; the key IS the word, dotted, value always `true` when present; sdk 23.0.0 `ENGINE_CAPABILITY_WORDS` derives the typed key from the engine's list):
9612
+ 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
9613
+ 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).
9614
+ 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.
9589
9615
  asyncRuns: { type: boolean }
9590
9616
  artifacts: { type: boolean }
9591
9617
  approvals: { type: boolean }
@@ -10089,7 +10115,7 @@ components:
10089
10115
  name / unprefixed short name / unmintable protocol server segment => 422, at most 1000 entries => 400) — never the content.
10090
10116
  🔴 server >= 7.118.0: the allow side's NAME layer moved to the engine (retired / short / unmintable names are accepted there and announced with `engine_notice` `permission.allowed_tool_not_granted`; `deny` / `ask` still 422);
10091
10117
  server >= 7.123.0 also accepts a protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`) on the allow side (server 7.118.0–7.122.x refused it on every table):
10092
- allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name`.
10118
+ allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.inert_entry` (server >= 7.129.0; 7.123.0–7.128.x answered a retired code, named in the 400 of `PUT /v1/sessions/{sessionId}/policy`).
10093
10119
  🔴 ABSENT `allowLayer` with `allowGrant` present = a 7.115.0–7.116.x server: allow is a STATEMENT (it does not clear the engine's own write gate / shell checks; a content form
10094
10120
  is a whole-request 422). ABSENT KEY altogether (no `settings`) = an older server (<= 7.114.x), which read `allow` as a WHITELIST (everything off the list refused) — do not put
10095
10121
  anything in the allow bucket there. NOT the same bit as `taskSettings.permissions` (unchanged, still a boolean: "this server accepts the `settings.permissions` face"). The
@@ -10749,7 +10775,7 @@ components:
10749
10775
  `agents[].skills[]` judge identically). Probe `capabilities.skillToolLists`.
10750
10776
  🔴 The name-layer 400s above hold for server 7.116.x–7.117.x only. server >= 7.118.0: the allow side's name layer moved to the engine — a retired name / unprefixed short name / unmintable protocol name is ACCEPTED here and the run opens with an
10751
10777
  `engine_notice` `permission.allowed_tool_not_granted` (`entries[].reason: tool_name`); server >= 7.123.0 also accepts a protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) here (server 7.118.0–7.122.x refused it).
10752
- One name, three outcomes: allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name` (`disallowedTools` below is a deny side).
10778
+ One name, three outcomes: allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.inert_entry` (server >= 7.129.0; 7.123.0–7.128.x answered a retired code, named in the 400 of `PUT /v1/sessions/{sessionId}/policy`; `disallowedTools` below is a deny side).
10753
10779
  disallowedTools:
10754
10780
  type: array
10755
10781
  items: { type: string }
@@ -10765,7 +10791,7 @@ components:
10765
10791
  409 `resume_blocked_by_policy` (`blockedBy: "request.field_invalid"`, same sentence) BEFORE the claim — row and card untouched; a terminal run's `POST /v1/runs/{taskId}/steer`
10766
10792
  answers it before minting the wake card. Why: engine 7.41 trimmed whitespace (`"Bash "` was a working deny), engine 7.42 reads verbatim (reads as nothing). The allow side does not refuse.
10767
10793
  🔴 A protocol name with an EMPTY TOOL SEGMENT (`mcp__<server>__`, `a2a__<peer>__`) is refused here (server >= 7.123.0: `error` is the engine's entry-reading sentence, `... reads as nothing (tool_name) ...`), while `allowedTools` accepts it from 7.123.0 —
10768
- one name, three outcomes: allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.legacy_tool_name`.
10794
+ one name, three outcomes: allow side accepts + notice, deny / ask side refuses, session-rule write doors answer `400 rules.inert_entry` (server >= 7.129.0; 7.123.0–7.128.x answered a retired code, named in the 400 of `PUT /v1/sessions/{sessionId}/policy`).
10769
10795
 
10770
10796
  McpServerSpec:
10771
10797
  type: object
@@ -11735,22 +11761,35 @@ components:
11735
11761
  # 把 `AskOrigin` 改真开集的同一条判据)。core 的 `routePairingStatus` 把 `verdict.posture` **直接内插**
11736
11762
  # 进 `ok:${…}`,server 的铸点与 core 的这只函数**全路径没有一处运行期成员判定** —— 一只自带
11737
11763
  # `adjudicateRoute` 的 brain 供什么词就上什么词。写一条会执法的 enum = 假闭集,会把一台合法 worker
11738
- # 的真响应判违约。已知七词写在 description 里(消费端 switch 认已知词 + 一条 default 臂)。
11764
+ # 的真响应判违约。已知六词写在 description 里(消费端 switch 认已知词 + 一条 default 臂)。
11739
11765
  type: string
11740
11766
  x-open-enum: true
11741
11767
  description: >-
11742
11768
  A model row's key<->URL pairing posture (core `RoutePairingStatus`; server >= 7.45). OPEN on read —
11743
- the seven words below are today's set, branch on the known ones and keep a `default` arm. NON-SECRET — no
11769
+ the six words below are today's set, branch on the known ones and keep a `default` arm. NON-SECRET — no
11744
11770
  URL, no credential.
11745
11771
  `ok:per-model` this model has its own credential · `ok:paired` deployment credential, and the model's URL
11746
- is on the deployment's declared root · `ok:unpinned` deployment credential but the deployment declared no
11747
- root, so the pairing is UNVERIFIABLE · `ok:keyless` no credential anywhere (a local unauthenticated
11772
+ is on the deployment's declared root · `ok:keyless` no credential anywhere (a local unauthenticated
11748
11773
  gateway) · `broken:credential_mismatch` / `broken:credential_missing` a real request WILL be refused by
11749
11774
  the engine (flag it red) · `unknown` could not be adjudicated — render "unknown", NEVER "good" or "bad".
11775
+ 🔴 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 —
11776
+ 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
11777
+ "pairing not verified", never as good.
11750
11778
  The verdict's frame of reference is the gateway root as of BRAIN CONSTRUCTION, so a live re-pointing of
11751
11779
  the deployment root only shows up after a restart (both this reading and the real request judge from the
11752
11780
  same snapshot).
11753
11781
 
11782
+ ResumeStartupCause:
11783
+ type: string
11784
+ enum: [environment, session_gone, tool_unavailable, configuration, decision_rejected]
11785
+ description: >-
11786
+ 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`
11787
+ (`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).
11788
+ `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 ·
11789
+ `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
11790
+ 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
11791
+ 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).
11792
+
11754
11793
  ElicitResponse:
11755
11794
  type: object
11756
11795
  description: >
@@ -12716,6 +12755,7 @@ components:
12716
12755
  seq: { type: integer, description: 'Stop-cycle counter (revive second cycle = higher seq); absent on older engines.' }
12717
12756
  status: { type: string, enum: [completed, failed, killed] }
12718
12757
  summary: { type: string }
12758
+ result: { type: string, description: "server >= 7.129.0 (K-86), the notification frame ONLY (row frames never carry it): the background child's report, the same string the parent model reads in the completion frame (the engine redacts it and truncates it once at the frame's cap; the server redacts again at its own wire boundary, like `summary`). ABSENT = no report. Additive / tolerate-absent." }
12719
12759
  scope: { type: string, enum: [task, session], description: 'Background LIFETIME scope (design/129), NOT the tenant scope.' }
12720
12760
  parentTaskId: { type: string }
12721
12761
  stoppedBy: { type: string, description: 'Verbatim open enum (who/what stopped a killed child).' }
@@ -13264,7 +13304,10 @@ components:
13264
13304
  description: 'server >= 7.126.0 (core 7.49.0 #1414): which card this row renders on — the engine-minted `gate.approvalCard` (closed eight-word set; the park-row twin seat reads the same word core''s `summarizeCheckpoint` projects onto `CheckpointSummary.approvalCard`; an out-of-set word reads as absent and is counted), the same word as the live frame / `livePending` row. It REPLACES `contentKind` (deleted at 7.126.0, no alias). ABSENT = the engine minted none: a row parked before engine 7.49.0 (core never derives a card from the tool name: no card = not a question, so an old question row carries no card word and is swept by the SLA at its deadline), a non-approval gate, or an older engine. Display / triage only; NEVER part of resume / gate / CAS; the inbox row does not carry it.'
13265
13305
  ruleOffersAbsence:
13266
13306
  allOf: [{ $ref: '#/components/schemas/RuleOffersAbsence' }]
13267
- description: 'server >= 7.100.0 (S-700): WHY the park row offers no rule — the same narrow read and closed three-word set as the live card frame and `card_json`.'
13307
+ description: 'server >= 7.100.0 (S-700): WHY the park row offers no rule — the same narrow read and closed set (four words from server >= 7.129.0) as the live card frame and `card_json`.'
13308
+ boundGrantAbsence:
13309
+ allOf: [{ $ref: '#/components/schemas/BoundGrantAbsence' }]
13310
+ description: 'server >= 7.129.0 (core 7.51.0 #1688; park-row fact eight): why this parked card has no grant-binding digest — the park-row twin seat `PendingAction.tool_approval.boundGrantAbsence`, the same narrow read and the same name / value as the live frame and the inbox row. ABSENT on this face = a skill card (it carries the digest; the digest itself is not on this face) / a row parked before engine 7.51.0 (says neither — the inbox face projects `unreadable` for the same row) / a word outside the set / an old server — NOT `not_bindable`.'
13268
13311
  askClass:
13269
13312
  allOf: [{ $ref: '#/components/schemas/AskClass' }]
13270
13313
  description: 'server >= 7.109.0 (S-934 ② / core 7.37.0 #1214): the park-row twin seat `PendingAction.tool_approval.askClass`, read through `readAskClassPair` — the SAME narrow read as the live frame and the card. Present together with `classes` (`classes[0] === askClass`); the basis a durable approver answers by. Absent = an old row (parked before core 7.37.0; resume does not backfill) or an unreadable pair — NOT "no class". The inbox row (InboxRow) does not carry it.'
@@ -13411,6 +13454,9 @@ components:
13411
13454
  boundGrants:
13412
13455
  allOf: [{ $ref: '#/components/schemas/BoundSkillGrants' }]
13413
13456
  description: 'server >= 7.118.0 — the value `boundGrantDigest` hashes, published ONLY when it hashes to it (the engine judges; a pair that disagrees reads as absent). DEPLOYMENT TEXT: render with contextual escaping.'
13457
+ boundGrantAbsence:
13458
+ allOf: [{ $ref: '#/components/schemas/BoundGrantAbsence' }]
13459
+ description: 'server >= 7.129.0 (core 7.51.0 #1688) — why this card has no `boundGrantDigest` (exactly one of the two; the engine''s summary is total over tool-approval rows — a row parked before engine 7.51.0, which says neither, reads `unreadable` here). Same name and value on the live frame and the operator queue row.'
13414
13460
  classifierNoRuling:
13415
13461
  allOf: [{ $ref: '#/components/schemas/AutoModeFallbackCause' }]
13416
13462
  description: 'server >= 7.118.0 — only on a parked `classifier_no_ruling` card: why the auto-mode classifier did not rule.'
@@ -13893,14 +13939,18 @@ components:
13893
13939
  type: object
13894
13940
  description: >
13895
13941
  E6 (shell-host permissionMode; core SessionPermissionRules session-policy-store.ts) — operator-tightened
13896
- per-session tool-permission rules (core reads them at prepare-time, SUBTRACT-only). 5 optional `string[]`
13897
- fields. tighten-only invariant is core-enforced (a loosen needs an operator).
13898
- properties:
13899
- toolAllow: { type: array, items: { type: string }, description: Narrowing allowlist (only these tools allowed). }
13900
- toolDeny: { type: array, items: { type: string }, description: Always-denied tool names (deny wins). }
13942
+ per-session tool-permission rules (core reads them at prepare-time, SUBTRACT-only). THREE optional `string[]`
13943
+ buckets. tighten-only invariant is core-enforced (a loosen needs an operator).
13944
+ 🔴 server >= 7.129.0 (engine 7.51.0): ONE grammar. `toolAllow` / `toolDeny` hold rule ENTRIES — a tool name, `mcp__<server>`,
13945
+ or a rule in the tool's own grammar such as `Bash(git push:*)` / `Edit(src/**)`; `toolDeny: ["Bash(rm:*)"]` denies the command name `rm` in any segment of a connected command line.
13946
+ The command buckets `commandAllow` / `commandDeny` are RETIRED (sdk 23.0.0 deletes them, no alias): writing one answers `400 rules.invalid_shape` (never silently ignored — an ignored
13947
+ deny is a deny nobody enforces); `commandDeny: ["rm"]` becomes `toolDeny: ["Bash(rm:*)"]` (which does not deny a path-spelled `/bin/rm ...` nor a command line that does not read as one command name — deny the whole
13948
+ shell with `toolDeny: ["Bash"]` for those); `commandAllow` has no counterpart. A stored SQL row (written by server <= 7.128.x) that still holds a command bucket reads back as `500 corrupt`
13949
+ on this deployment (the operator rewrites that row to the new grammar or deletes the session). An entry the engine reads as nothing answers `400 rules.inert_entry`.
13950
+ properties:
13951
+ toolAllow: { type: array, items: { type: string }, description: 'Narrowing allowlist (only these entries allowed) — rule entries, see the schema description.' }
13952
+ toolDeny: { type: array, items: { type: string }, description: 'Always-denied entries (deny wins) — a tool name, `mcp__<server>`, or a rule such as `Bash(rm:*)`.' }
13901
13953
  allowDirs: { type: array, items: { type: string }, description: Write tools confined to these dirs; bash denied while set. }
13902
- commandAllow: { type: array, items: { type: string }, description: "Only these bash command names (argv[0]) allowed." }
13903
- commandDeny: { type: array, items: { type: string }, description: "Always-denied bash command names (argv[0])." }
13904
13954
 
13905
13955
  StoredSessionRules:
13906
13956
  description: '{@link SessionPermissionRules} + the monotonic `rev` the store stamps (the OCC key).'
@@ -13917,22 +13967,32 @@ components:
13917
13967
  server >= 7.92.0 (engine 7.25.0): the session's rule LINEAGE generation. An erase
13918
13968
  (`deleteBySession`) ends the lineage — the next write carries gen+1 — so a reader treats a
13919
13969
  different gen as a new lineage, never a continuation, regardless of rev. Absent on older servers.
13970
+ grammar:
13971
+ type: integer
13972
+ minimum: 0
13973
+ description: >
13974
+ server >= 7.129.0 (engine 7.51.2): the rule GRAMMAR the row's entries are read in, stamped by the
13975
+ engine's write judge on every row written through it (every row this server writes carries `1`).
13976
+ Absent = a row stored before the stamp (grammar 0: an allow entry spelled as a rule, `Bash(rm:*)`,
13977
+ named no tool and admitted nothing — the engine keeps reading such a row that way and announces it
13978
+ once; the one-time migration rewrites stored rows and stamps them). Echoing a stored body back into
13979
+ PUT with its `rev` but no `grammar` is read as grammar 0 (`400 rules.inert_entry`, reason
13980
+ `earlier_grammar`); a `grammar` newer than the engine's answers `400 rules.invalid_shape` (field `grammar`).
13920
13981
  # 🔴 封闭 + 本地镜像(census 批2 第二段,2026-07-30)。同 Event_reasoning 处那条长注的病:
13921
13982
  # `additionalProperties` 只结算**同一个 subschema** 的 `properties`,`allOf` 分支($ref
13922
13983
  # SessionPermissionRules + 内联的 `rev`)声明的键不在它作用域内 ⇒ 只写 `false` 会把合法的
13923
13984
  # `toolAllow`/`rev` 判成违约。镜像的一致性由 spec.test.ts 的通用「封闭 allOf 臂必须镜像被引
13924
13985
  # schema 全部键」门看守(以后往 SessionPermissionRules 加键,忘了同步镜像就当场红)。
13925
- # 为什么值得封闭:PUT 腿 `routes/sessions.ts` 只挑这 5 个已知字段落店,GET 腿回的是 store
13926
- # 原样 —— 「store 多回一个键」正是本门要抓的那类漂(消费方按 spec 生成的类型会漏掉它)。
13986
+ # 为什么值得封闭:GET 腿回的是 store 原样 —— 「store 多回一个键」正是本门要抓的那类漂(消费方按 spec 生成的类型会漏掉它)。
13987
+ # server >= 7.129.0 规则体只剩三只桶(命令桶退役,带命令桶的 SQL 存量行读回 500,不会出现在 200 体上)。
13927
13988
  additionalProperties: false
13928
13989
  properties:
13929
13990
  toolAllow: { type: array, items: { type: string } }
13930
13991
  toolDeny: { type: array, items: { type: string } }
13931
13992
  allowDirs: { type: array, items: { type: string } }
13932
- commandAllow: { type: array, items: { type: string } }
13933
- commandDeny: { type: array, items: { type: string } }
13934
13993
  rev: { type: integer }
13935
13994
  gen: { type: integer }
13995
+ grammar: { type: integer }
13936
13996
 
13937
13997
  McpServerStatus:
13938
13998
  type: object
@@ -14616,10 +14676,10 @@ components:
14616
14676
  type: string
14617
14677
  description: >-
14618
14678
  server S-818 (riding core #1161), on `parked_resume.startup_failed` (422) ONLY: core's CAUSE WORD for the parked
14619
- resume's startup failure, relayed verbatim (never rewritten, never defaulted; absent when the engine did not say).
14620
- An OPEN string here — the closed set is core's (#1161 exports it; the constant's name is in core 7.34.1's release
14621
- notes). Absent ⇒ cannot tell which of the two causes (see the `/v1/approvals/{sessionId}/decide` 422 text).
14622
- SDK reads it as `ParkedResumeStartupError.startupCause`.
14679
+ resume's startup failure, relayed (never defaulted; absent when the engine did not say). From server >= 7.129.0 it is
14680
+ one of the engine's closed `RESUME_STARTUP_CAUSES` (core 7.51.0; schema `ResumeStartupCause` — the server screens it, a word outside the set is dropped);
14681
+ server <= 7.128.x relays the engine's word unscreened (an OPEN string — hence the plain `string` here). Absent ⇒ cannot tell which cause (see the
14682
+ `/v1/approvals/{sessionId}/decide` 422 text). SDK reads it as `ParkedResumeStartupError.startupCause` (23.0.0: the five-word `ResumeStartupCause`; a word outside it reads as `undefined`, the raw value stays at `err.extra.cause`).
14623
14683
  remaining:
14624
14684
  type: array
14625
14685
  items: { type: string }
@@ -14636,7 +14696,7 @@ components:
14636
14696
  unreadable) / `unreachable` (unreachable / 5xx / timeout / bad body / any status other than 200 / 409 / 400 / 401 / 403 —
14637
14697
  center 404 / 429 / 3xx included). On 400 `request.field_invalid` for a `settings.permissions` bucket (server >= 7.106.0 /
14638
14698
  S-904): `over_cap` (more than `cap` entries; beside `count` + `cap`) / `bad_entry` (a non-string or empty item; beside
14639
- `index`). Out-of-vocabulary word ⇒ treat as unknown. (Other codes carry their own `reason` words, e.g. `rules.*`.)
14699
+ `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`). Out-of-vocabulary word ⇒ treat as unknown. (Other codes carry their own `reason` words.)
14640
14700
  retryAfterSec:
14641
14701
  type: number
14642
14702
  description: >-
@@ -14648,8 +14708,8 @@ components:
14648
14708
  type: string
14649
14709
  description: >-
14650
14710
  On 400 `request.field_invalid` for a `settings.permissions` bucket (server >= 7.106.0 / S-904): which bucket —
14651
- `settings.permissions.allow` | `settings.permissions.deny` | `settings.permissions.ask`. (`rules.*` content refusals
14652
- carry their own `field`.) Resume legs carry the same group on 409 `resume_blocked_by_policy`
14711
+ `settings.permissions.allow` | `settings.permissions.deny` | `settings.permissions.ask`. (`rules.*` refusals
14712
+ 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`
14653
14713
  (`blockedBy: "request.field_invalid"`). Also on 409 `checkpoint.config_refused` (server >= 7.113.0): which part of the
14654
14714
  row's configuration was refused — `taskConfig` (change the configuration, then decide the SAME card again) or
14655
14715
  `inheritedGate` (the row's recorded mode seed is bad — the run can only be cancelled). Also on 400 `request.field_invalid` for a
@@ -14671,7 +14731,7 @@ components:
14671
14731
  are unchanged): an OBJECT `{ event, alias, canonical }` — `event` = the tool event of the refused group (the first refused one when
14672
14732
  several are), `alias` / `canonical` = the engine catalog's alias and canonical name (closed strings, never the caller's raw bytes),
14673
14733
  beside `field: "settings.hooks"`. On 400 `request.field_invalid` for the two `POST /v1/adoption` principal sentences (adoption):
14674
- a human-readable STRING. Other codes (`rules.*` content refusals: `reason` + human `detail`; core `checkpoint.*` mirrors) carry their
14734
+ 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
14675
14735
  own `detail`; read it by `errorCode`. SDK: `err.extra.detail` (verbatim, unnamed).
14676
14736
  oneOf:
14677
14737
  - type: object
@@ -15053,9 +15113,8 @@ components:
15053
15113
  persisted rules). It is the personal-store sibling of `org`: same VETO family (it can overturn an approval
15054
15114
  a person already gave, and it re-judges an edited command), which is why it is neither `policy` nor
15055
15115
  `ask_resolution` · `classifier` = the auto-mode classifier denied directly ·
15056
- ⚰️ `plan_mode` = RETIRED WORD (server <= 7.105.0 / core <= 7.33.x): plan mode's read-only block on a write tool —
15057
- core 7.34.0 design/1111 folded it into `mode`, server 7.106.0 no longer emits it; kept because servers inside this SDK's
15058
- support floor still send it (delete when the floor passes 7.106.0) · `mode` (server >= 7.106.0 / core 7.34.0) = the
15116
+ (⚰️ `plan_mode` — server <= 7.105.0 / core <= 7.33.x: plan mode's read-only block on a write tool, folded into `mode` by core 7.34.0 design/1111 — is NOT in this set since sdk 23.0.0: it is an
15117
+ ARCHIVE word of the gate record, see `GateRecordDeniedBy`) · `mode` (server >= 7.106.0 / core 7.34.0) = the
15059
15118
  permission MODE refused a surviving ask on its own account — `dontAsk` turns an ask nobody can answer into a refusal,
15060
15119
  `bypassPermissions` refuses a write whose protection could not be judged; the deny arm then carries `mode` / `askClass` /
15061
15120
  `classes` (core invariant I8) · `compliance` = the compliance call-time
@@ -15064,11 +15123,48 @@ components:
15064
15123
  `read_boundary` = the shell READ STATION refused (core 7.25.0 #945): a deny-listed read before any approval,
15065
15124
  or an approved rewrite re-judged onto a deny row (beside `settlement.kind: "human_allowed"`); may veto, carries no `cause`.
15066
15125
  🔴 CLOSED ON THIS WIRE, and deliberately not marked open: the engine's invariant screen
15067
- (`screenGateOutcome`) treats an out-of-set `deniedBy` as a DEFECT, and the service withholds a defective
15068
- record WHOLE (report + withhold). A word outside this set therefore cannot reach a consumer — it arrives
15069
- as the `gate` key being ABSENT, never as an unfamiliar word. Same rule for `Settlement.kind` and for
15070
- `GateOutcome.origin`.
15071
- enum: [policy, hook, org, persisted_rule, classifier, plan_mode, compliance, write_protection, ask_resolution, read_boundary, mode]
15126
+ (`screenGateOutcome`) treats a word that NO engine generation ever minted as a DEFECT, and the service withholds a defective
15127
+ record WHOLE (report + withhold). Since server >= 7.129.0 (engine 7.51.0) the screen reads the gate record as an ARCHIVE:
15128
+ today's words PLUS the engine's retired words (`RETIRED_MEMBERS`) — `GateRecordDeniedBy` is that archive set; this schema is today's set
15129
+ (the request / card faces). Same rule for `Settlement.kind` and for `GateOutcome.origin`.
15130
+ enum: [policy, hook, org, persisted_rule, classifier, compliance, write_protection, ask_resolution, read_boundary, mode]
15131
+
15132
+
15133
+ # ── sdk 23.0.0 · server >= 7.129.0 (engine 7.51.0 `RETIRED_MEMBERS`): the GATE RECORD's archive sets ──────────────────────────────────────────
15134
+ # 门记录({@link GateOutcome}:`tool_end.gate`、turns / trace 回放、`GET /v1/runs/:id/events` 的账本裸回放)只过引擎的 `screenGateOutcome` 一次,缺省按**档案**读:
15135
+ # 每只闭集 = 引擎今天的词 ∪ 引擎退役词表里那一集。这五张 enum 与引擎 `RETIRED_MEMBERS` 逐集逐词同(由 `server-7129-wire` 对拍);卡面 / 队列行 / 请求面的同名闭集只取今天的词。
15136
+ GateRecordAskClass:
15137
+ type: string
15138
+ enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary, write_protection_unresolved, governance_unavailable]
15139
+ description: >-
15140
+ The class word on a GATE RECORD (`tool_end.gate.disposition.askClass` / `.classes` on the mode-refusal arm, `ModeRelease.askClass` / `.classes`) — server >= 7.129.0 reads the record as an ARCHIVE:
15141
+ today's `AskClass` set (twenty-five words) PLUS the words the engine retired (`write_protection_unresolved`, retired in core 7.49.0 — renamed `target_unresolved`; `governance_unavailable`,
15142
+ retired in core 7.51.0 — merged into `rule_unreadable`). A record the previous engine generation legally wrote is replayed byte for byte (a 7.50.x mode refusal can carry `governance_unavailable`);
15143
+ a word NO engine generation ever minted (a case variant, the empty string, a future word) withholds the whole `gate` and counts `server.trace.gate-outcome-defective`.
15144
+ Render a known archive word normally and an unknown one generically; never drop the record because the word is not in today's set.
15145
+ GateRecordDeniedBy:
15146
+ type: string
15147
+ enum: [policy, hook, org, persisted_rule, classifier, compliance, write_protection, ask_resolution, read_boundary, mode, plan_mode]
15148
+ description: >-
15149
+ `deniedBy` on a GATE RECORD (archive read, server >= 7.129.0): today's `DeniedBy` set PLUS the retired `plan_mode` (core 7.34.0 folded it into `mode`). Same reading as `GateRecordAskClass`.
15150
+ GateRecordModeReleaseReason:
15151
+ type: string
15152
+ enum: [target_unresolved, write_protection_unresolved, rule_unreadable]
15153
+ description: >-
15154
+ `released.reason` on a GATE RECORD (archive read, server >= 7.129.0): today's one word `target_unresolved` (core `MODE_RELEASE_REASONS`) PLUS the retired `write_protection_unresolved`
15155
+ (server 7.122.0–7.125.x spelling, retired in core 7.49.0) and `rule_unreadable` (server <= 7.126.x: the release went over an unreadable rule; retired in core 7.50.0). Server 7.127.0–7.128.x
15156
+ OMITTED `reason` for the two retired words (`released` was still sent); from 7.129.0 they arrive verbatim on the replay faces. Same reading as `GateRecordAskClass`.
15157
+ GateRecordPersistedRuleMandate:
15158
+ type: string
15159
+ enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved, write_protection_unresolved]
15160
+ description: >-
15161
+ `released.mandate` on a GATE RECORD (archive read, server >= 7.129.0): today's six `PersistedRuleMandate` words PLUS the retired `write_protection_unresolved` (core 7.49.0 rename). Same reading as `GateRecordAskClass`.
15162
+ GateRecordAskOrigin:
15163
+ type: string
15164
+ enum: [content_question, plan_review, ancestor_marked, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy, unresolvable]
15165
+ description: >-
15166
+ `origin` on a GATE RECORD (`GateOutcome.origin`, `ModeRelease.origin`; archive read, server >= 7.129.0): core's twelve `ASK_ORIGINS` PLUS the retired `unresolvable` (core 7.14.0 renamed it `ancestor_marked`).
15167
+ 🔴 CLOSED on this face — the engine judges membership here — unlike the OPEN `AskOrigin` of the approval-card faces (the server only checks a non-empty string there). Same reading as `GateRecordAskClass`.
15072
15168
 
15073
15169
  Settlement:
15074
15170
  description: >
@@ -15247,7 +15343,7 @@ components:
15247
15343
  required: [kind, deniedBy]
15248
15344
  properties:
15249
15345
  kind: { const: denied }
15250
- deniedBy: { $ref: '#/components/schemas/DeniedBy' }
15346
+ deniedBy: { $ref: '#/components/schemas/GateRecordDeniedBy' }
15251
15347
  classifier:
15252
15348
  allOf: [{ $ref: '#/components/schemas/GateClassifierAttribution' }]
15253
15349
  description: >
@@ -15275,12 +15371,12 @@ components:
15275
15371
  that refused. `dontAsk` turns an ask nobody can answer into a refusal; `bypassPermissions` refuses a write whose
15276
15372
  protection could not be judged. `plan` refuses nothing here (its questions ask).
15277
15373
  askClass:
15278
- allOf: [{ $ref: '#/components/schemas/AskClass' }]
15279
- description: 'Beside `mode`: the refused ask''s disclosed class word.'
15374
+ allOf: [{ $ref: '#/components/schemas/GateRecordAskClass' }]
15375
+ description: 'Beside `mode`: the refused ask''s disclosed class word (gate-record archive read — a 7.50.x mode refusal can carry the retired `governance_unavailable`).'
15280
15376
  classes:
15281
15377
  type: array
15282
- items: { $ref: '#/components/schemas/AskClass' }
15283
- description: 'Beside `mode`: the whole folded class set.'
15378
+ items: { $ref: '#/components/schemas/GateRecordAskClass' }
15379
+ description: 'Beside `mode`: the whole folded class set (archive read, see `GateRecordAskClass`).'
15284
15380
 
15285
15381
  PermissionMode:
15286
15382
  type: string
@@ -15293,9 +15389,9 @@ components:
15293
15389
  `UNATTENDED_PERMISSION_MODE=dontAsk`).
15294
15390
  AskClass:
15295
15391
  type: string
15296
- enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
15392
+ enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, unverifiable_shell_text, transcript_write, org_rule, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, target_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
15297
15393
  description: >
15298
- core `ASK_CLASSES` (twenty-two words as of core 7.40.0, twenty-three as of core 7.41.0, twenty-four as of core 7.43.0, twenty-five as of core 7.45.0, twenty-six as of core 7.49.0 — 7.34.0 minted twenty; `unverifiable_shell_text` joined in 7.49.0 #1505 and rides server >= 7.126.0, where the
15394
+ core `ASK_CLASSES` (TWENTY-FIVE words as of core 7.51.0 / server >= 7.129.0: `governance_unavailable` was merged into `rule_unreadable`, no alias — sdk 23.0.0; a stored card / parked row written by server <= 7.128.x is rewritten once by the server at boot. The gate record reads the ARCHIVE form, see `GateRecordAskClass`. History: twenty-two words as of core 7.40.0, twenty-three as of core 7.41.0, twenty-four as of core 7.43.0, twenty-five as of core 7.45.0, twenty-six as of core 7.49.0 — 7.34.0 minted twenty; `unverifiable_shell_text` joined in 7.49.0 #1505 and rides server >= 7.126.0, where the
15299
15395
  unresolved-write-target class was also RENAMED to `target_unresolved` (#955; zero alias — the previous spelling is in the SDK README's 22.0.0 migration table); `shell_floor` joined in 7.39.0 #1260 and rides
15300
15396
  server >= 7.113.0; `unstated` joined in 7.40.0 and rides server >= 7.115.0; `requires_user_interaction` joined in 7.41.0 and rides
15301
15397
  server >= 7.116.0; `classifier_no_ruling` joined in 7.43.0 #1353 and rides server >= 7.118.0; `dangerous_delete` joined in 7.45.0 and rides
@@ -15307,6 +15403,7 @@ components:
15307
15403
  it: no mode (`bypassPermissions` included), no allow (request `settings.permissions.allow`, operator `APPROVAL_GRANT`, a stored allow row, a
15308
15404
  skill's tool list, a hook's allow) and no `auto` classifier does; the frame carries `requiresRealApproval: true`, nobody answering means the
15309
15405
  call does not run (the unattended leg refuses), and it is bypass-IMMUNE (not a releasable class).
15406
+ `rule_unreadable` (core 7.51.0 / server >= 7.129.0 also covers the organization snapshot that could not be read — the former `governance_unavailable`) = a rule that may govern this call could not be READ (the organization's snapshot, a partition of the person's own rule store, or the rule this call was read against); bypass-immune.
15310
15407
  `unverifiable_shell_text` (core 7.49.0 #1505, server >= 7.126.0) = the engine CANNOT READ the text this shell will execute — the shell is fed
15311
15408
  text the command does not spell (a program's output, a substitution, a file's content, an argv a stream fills in: `bash -c "$CMD"`,
15312
15409
  `eval "$(…)"`, `xargs sh -c …`; shell text that does not parse or is nested too deep lands here too). Its arbitration row is
@@ -15334,7 +15431,7 @@ components:
15334
15431
  `shell_floor` = the operator's `shellGate: "always"` floor on a shell-running seat (the question the `operator_always` mandate
15335
15432
  word discloses), a class of its own so a set that holds another forced question still holds it; like `operator_forced` it is
15336
15433
  bypass-IMMUNE (not releasable).
15337
- CLOSED on the gate record (screened with core `isAskClass`). `bypassPermissions` releases only the RELEASABLE classes (core
15434
+ CLOSED: the card / queue-row / request faces carry exactly today's twenty-five words; the GATE RECORD (`tool_end.gate` and the replay faces) carries today's words PLUS the engine's retired words (`GateRecordAskClass`). `bypassPermissions` releases only the RELEASABLE classes (core
15338
15435
  `releasableAskClassesFor(face)`). Server >= 7.127.0 (core 7.50.0): nine on every face (`transcript_write` / `probe` / `write_protection` /
15339
15436
  `tool_marks_mcp` / `tool_marks_a2a` / `artifact_shared_read` / `mode` / `unstated` / `ordinary`) plus a tenth on the SHELL face only,
15340
15437
  `target_unresolved` (shell 10, other faces 9; on the file tools that class is refused, `deniedBy: mode`); `rule_unreadable` is no longer released
@@ -15353,30 +15450,29 @@ components:
15353
15450
  `permission.ask_released_by_mode` per run.
15354
15451
  properties:
15355
15452
  mode: { $ref: '#/components/schemas/PermissionMode' }
15356
- askClass: { $ref: '#/components/schemas/AskClass' }
15357
- classes: { type: array, items: { $ref: '#/components/schemas/AskClass' } }
15453
+ askClass: { $ref: '#/components/schemas/GateRecordAskClass' }
15454
+ classes: { type: array, items: { $ref: '#/components/schemas/GateRecordAskClass' } }
15358
15455
  origin:
15359
- type: string
15360
- enum: [content_question, plan_review, ancestor_marked, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
15361
- description: 'The origin word the card would have shown (core `ASK_ORIGINS`; screened closed on this face, same list as `GateOutcome.origin`).'
15362
- mandate: { $ref: '#/components/schemas/PersistedRuleMandate' }
15456
+ allOf: [{ $ref: '#/components/schemas/GateRecordAskOrigin' }]
15457
+ description: 'The origin word the card would have shown (core `ASK_ORIGINS`, archive read — screened closed on this face, same set as `GateOutcome.origin`).'
15458
+ mandate:
15459
+ allOf: [{ $ref: '#/components/schemas/GateRecordPersistedRuleMandate' }]
15460
+ description: 'The mandate word the card would have shown (today''s six `PersistedRuleMandate` words plus the retired spelling, archive read).'
15363
15461
  reason:
15364
- type: string
15365
- enum: [target_unresolved]
15462
+ allOf: [{ $ref: '#/components/schemas/GateRecordModeReleaseReason' }]
15366
15463
  description: >
15367
15464
  WHAT the release went over — one word: the FIRST member (in core `MODE_RELEASE_REASONS` array order) that the ask's class
15368
- set holds; ABSENT when the set holds none. CLOSED set of ONE word — the server screens it with core `isModeReleaseReason`, so a word
15369
- outside it withholds the whole `gate` from the frame.
15465
+ set holds; ABSENT when the set holds none. Today's set has ONE word, `target_unresolved` (core `MODE_RELEASE_REASONS`); the GATE RECORD reads the ARCHIVE form
15466
+ (server >= 7.129.0, `GateRecordModeReleaseReason`), so the engine's two retired words can also arrive verbatim on the replay faces.
15370
15467
  `target_unresolved` = the release went over a write whose landing place could not be determined (a link on the way could not be examined,
15371
15468
  or the environment could not resolve the target), so whether it lands on a guarded path is unknown. It is released on the SHELL
15372
15469
  face only: a caller-supplied tool whose wire name is exactly `Bash` or `Monitor` and which declares a write-path target,
15373
15470
  under `bypassPermissions`; on the file tools the same unresolved write is still refused (`deniedBy: mode`). The engine's
15374
15471
  own `Bash` / `Monitor` cannot mint this class and the server mounts no such tool — it comes from a caller-embedded tool of
15375
15472
  those names.
15376
- History (this SDK's type and this enum hold the one word from 22.0.0; a word an older server sends reads as OUT OF SET and is not rewritten):
15377
- server 7.122.0–7.125.x (core 7.47.0–7.48.x) spelled this member differently (the SDK README's 22.0.0 migration table); server <= 7.126.x also
15378
- had `rule_unreadable` (the release went over a rule this call could not be read against) — core 7.50.0 / server >= 7.127.0 releases an
15379
- unreadable rule under no mode, so that member left (`rule_unreadable` stays an `AskClass`). Before server 7.122.0 the set was `rule_unreadable` alone.
15473
+ History (sdk 22.0.0 narrowed this to one word; sdk 23.0.0 reads the archive form because server >= 7.129.0 replays the engine's retired words verbatim):
15474
+ the two retired words are the server 7.122.0–7.125.x spelling of today's word and, on server <= 7.126.x, `rule_unreadable` (the release went over a rule this call
15475
+ could not be read against) — core 7.50.0 / server >= 7.127.0 releases an unreadable rule under no mode (`rule_unreadable` stays an `AskClass`). Before server 7.122.0 the set was `rule_unreadable` alone.
15380
15476
 
15381
15477
  GateClassifierAttribution:
15382
15478
  # sdk 9.5.0:具名 —— 同一份形挂在 `GateDisposition` 的两只臂上,内联会立刻是两份会各自漂的镜像。
@@ -15455,16 +15551,13 @@ components:
15455
15551
  allOf: [{ $ref: '#/components/schemas/Settlement' }]
15456
15552
  description: 'Present only when THIS pass settled an ask. Same-present/same-absent as `origin` (I1).'
15457
15553
  origin:
15458
- allOf:
15459
- - { $ref: '#/components/schemas/AskOrigin' }
15460
- # 🔴 闭集执法**只在这一面**:引擎的 `screenGateOutcome` 对 `origin` 判成员,出集即记录缺陷,而
15461
- # 服务对缺陷记录是整条不上帧 ⇒ 词表外的值在这条 wire 上到不了消费端。审批帧那一面不判成员,
15462
- # 所以 `AskOrigin` 本体保持真开(见该 schema 的长注)。这张 enum 是本 spec 里该词表的**唯一**
15463
- # 执法点;core 加词时改这一处。
15464
- - enum: [content_question, plan_review, ancestor_marked, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
15554
+ allOf: [{ $ref: '#/components/schemas/GateRecordAskOrigin' }]
15555
+ # 🔴 闭集**执法**只在门记录这一面:引擎的 `screenGateOutcome` 对 `origin` 判成员(server >= 7.129.0 按**档案**读:今天的十二词 ∪ 退役词 `unresolvable`),
15556
+ # 任何一代引擎都没铸过的词即记录缺陷,服务对缺陷记录是整条不上帧。审批帧那一面不判成员,所以 `AskOrigin` 本体保持真开(见该 schema 的长注)。
15557
+ # 闭集执法点 = `GateRecordAskOrigin` 这一张 enum(门记录的两个引用点 `GateOutcome.origin` / `ModeRelease.origin` 共用);core 加词时改这一处。
15465
15558
  description: >
15466
- WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (and only here) — see the
15467
- enum note above.
15559
+ WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (archive read, `GateRecordAskOrigin`) — see the
15560
+ note above.
15468
15561
 
15469
15562
  McpProbeFace:
15470
15563
  type: object
@@ -16593,8 +16686,9 @@ components:
16593
16686
  `toolCallId` joins the `tool_approval` card / `tool_end`; `sessionId` is conditional. Not a block; not counted by
16594
16687
  `classifier.denial_limit`. · `config.permission_mode_unavailable` — the requested mode is NOT the mode this leg runs,
16595
16688
  `{ requested, effective, by, sessionId, runId, childSessionId? }`, `requested` / `effective` = core's six mode words, `by` = core
16596
- `PERMISSION_MODE_FOLD_REASONS` (`operator_deny` / `face_absent` / `inherited_ceiling` — the last is ALSO minted onto the PARENT
16597
- session's stream with `childSessionId`); the first two arms always have `effective: default`; the server-side
16689
+ `PERMISSION_MODE_FOLD_REASONS` (`operator_deny` / `face_absent` / `inherited_ceiling`); from core 7.51.0 / server >= 7.129.0 a child leg's fold on ANY of the three arms is ALSO minted onto the PARENT
16690
+ session's stream (`detail.sessionId` = the parent, `childSessionId` = the child; dedupe by `childSessionId`) and `requested` reads as "the word that was folded" (a child that requested nothing: the word it inherited);
16691
+ the first two arms always have `effective: default`; the server-side
16598
16692
  `settings.permissions.disableAutoMode` fold happens before the engine and mints no frame. · `permission.ask_released_by_mode`
16599
16693
  — `bypassPermissions` released an ask nobody answered, `{ mode, askClass, origin?, mandate?, count, firstToolCallId, sessionId, runId }`,
16600
16694
  first time per (mode, askClass) per run (`count` is 1); the per-call record is `tool_end.gate.disposition.released`. ·
@@ -16619,6 +16713,12 @@ components:
16619
16713
  switch itself became the WORD `"disabled"` instead of `null` (a string is not nullish, so the ordinary
16620
16714
  coalesce is the whole resolution rule and no reader has to remember a null test). A consumer still
16621
16715
  matching `task_store_null` matches nothing.
16716
+ server 7.129.0 (the user-visible whitelist grows 37 -> 38 codes; contract appendix D.3): `permission.entry_mode_clamped` (SERVER-minted, audience `user`, at most one frame per submission) — the operator's
16717
+ entry floor `AUTONOMY=plan` folded the requested permission mode (top-level `permissionMode` or `settings.permissions.defaultMode`, whichever took effect) back to `plan`, so this run starts in a different mode
16718
+ (before, the fold was undisclosed). `detail` = `{ requested, effective, ceiling, sessionId }` — `requested` the folded word, `effective` the word the run starts in, `ceiling` the floor that folded it, all
16719
+ three from core's six `PERMISSION_MODES`, passed through verbatim; there is no `runId` (the fold happens at the admission door, before the engine mints a run identity — attribute the frame by the stream / ledger it lands on).
16720
+ Sent on the two fresh legs (`POST /v1/tasks/stream` / `POST /v1/runs`) at the open beat, before any model output; NOT sent when there is no entry floor, no word was declared, the requested word is not looser than the floor,
16721
+ on the resume family, or on the synchronous non-streaming `POST /v1/tasks` (that leg has no notice port). It is a different fact from `config.permission_mode_unavailable` (the engine's own fold), which cannot see this one.
16622
16722
  server 7.126.0 (the user-visible whitelist grows 35 -> 37 codes; contract appendix D.3): `task.tool_turn_incomplete` gains an optional `detail.outcome` (core 7.49.1) —
16623
16723
  `{cause, count, toolCallIds?, outcome?, sessionId, runId}`, `outcome` in {`terminal` (the dropped calls ended the run with `model_output_invalid`), `reissued` (the model was asked to reissue)}, present only when
16624
16724
  the turn's own message can say it, ABSENT = not said; two NEW codes (core 7.49.0, audience `user`, zero body text): `route.finish_reason_unknown` `{finishReason, sessionId, runId}` — the provider ended the turn
@@ -16746,7 +16846,13 @@ components:
16746
16846
  type: ['string', 'null']
16747
16847
  description: >
16748
16848
  server.ts —— 一条 resume 腿把 run 重新挂回 suspended 时,带上「为什么重开」的 errorCode
16749
- (无则显式 null)。只出现在这条重开臂上;普通挂起帧不带此键。
16849
+ (无则显式 null)。只出现在这条重开臂上;普通挂起帧不带此键。server >= 7.129.0:重开与否只按店里那一行判(不再按失败码的三码名单),所以名单外的失败码也会出现在这里;
16850
+ `plan_review` 的重开落 `needs_review`(同形)。
16851
+ toolCallId:
16852
+ type: string
16853
+ description: >
16854
+ The tool call the run is parked on (a tool-approval gate; ABSENT for a tool-less park / when it could not be read — never `null`). Same value as the `tool_approval` card's `toolCallId`. The server's park ledger row has
16855
+ always carried it (sdk 23.0.0 declares it; this arm is closed, so a strict validator rejected a real replay row before).
16750
16856
  ModelUsageDelta:
16751
16857
  # 新增(2026-07-25 回填批)。SDK 的 facade 审计把这个形状命名了一次(此前是在两个使用点各写一遍的匿名内联形:
16752
16858
  # `AgentEvent` 的 `model_usage` 臂 与 `done.result.stats.modelUsage` 的值类型,同源同形)。
@@ -17105,7 +17211,7 @@ components:
17105
17211
 
17106
17212
  🔴 PRESENCE IS THE PROMISE (same as `ruleSuggestions`): present only where a permission-rule store
17107
17213
  is really wired. The converse does not hold — a wired deployment still omits it for mandated /
17108
- requiresRealApproval / shadowed / hook-produced / inherited-unresolved / ancestor-resolved /
17214
+ requiresRealApproval / allow_outranked / ask_rule_matched / hook-produced / inherited-unresolved / ancestor-resolved /
17109
17215
  anonymous asks. Absence reads as "THIS CARD has no such option", never as "this deployment has no
17110
17216
  rule lane" (that is `capabilities.permissionRules`) and never as "your rule broke" (that is
17111
17217
  `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x).
@@ -17358,6 +17464,9 @@ components:
17358
17464
  boundGrants:
17359
17465
  allOf: [{ $ref: '#/components/schemas/BoundSkillGrants' }]
17360
17466
  description: 'server >= 7.118.0, "tool_approval" only — the value `boundGrantDigest` hashes; present iff it is. DEPLOYMENT TEXT: render with contextual escaping.'
17467
+ boundGrantAbsence:
17468
+ allOf: [{ $ref: '#/components/schemas/BoundGrantAbsence' }]
17469
+ description: 'server >= 7.129.0 (core 7.51.0 #1688), "tool_approval" only — why this card has no `boundGrantDigest` (exactly one of the two). Same name and value on the inbox row, the operator queue row and the `pending` stream frame; passed through verbatim.'
17361
17470
  classifierNoRuling:
17362
17471
  allOf: [{ $ref: '#/components/schemas/AutoModeFallbackCause' }]
17363
17472
  description: >-
@@ -17743,21 +17852,38 @@ components:
17743
17852
 
17744
17853
  RuleOffersAbsence:
17745
17854
  type: string
17746
- enum: [mandated, shadowed, lane_cannot_speak]
17855
+ enum: [mandated, allow_outranked, ask_rule_matched, lane_cannot_speak]
17747
17856
  description: >
17748
17857
  WHY `ruleOffers` is absent while the rule lane IS present (`ToolApprovalFrame.ruleOffersAbsence` and
17749
17858
  `ApprovalCard.ruleOffersAbsence`; server >= 7.55.0 / core #490 fix ②, S-15). The engine names which
17750
17859
  door is shut: `mandated` = this ask must be confirmed every time (🔴 this arm must NOT point the user
17751
- at writing a rule — a rule would not silence it, and saying so sends them into a loop); `shadowed` = a
17752
- an allow-layer member matched but could not clear it (the machine-readable twin of `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x);
17753
- `lane_cannot_speak` = the engine could mint no coverable form for this command.
17860
+ at writing a rule — a rule would not silence it, and saying so sends them into a loop); `allow_outranked` = an
17861
+ allow-layer member matched this call but the ask sits at a higher tier, so another allow rule cannot clear it (the machine-readable twin of `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x);
17862
+ `ask_rule_matched` = an explicit `ask` rule matched this call (same frame `origin: ask_rule`): it keeps asking, an allow rule would not silence it either; a card from the deployment name-list gate
17863
+ `APPROVAL_REQUIRE` / the supervisor route that reports an explicit ask-rule hit lands here too; `lane_cannot_speak` = a rule COULD clear this ask but the lane cannot speak this time (the engine could mint no coverable form for this command).
17864
+ 🔴 sdk 23.0.0 BREAKING (server >= 7.129.0 / engine 7.51.0, `RULE_OFFERS_ABSENCES`): the former `shadowed` meant two things and is split into the middle two words, no alias; a card / parked row that a server <= 7.128.x
17865
+ stored with `shadowed` is rewritten once by the server at boot (the key is dropped — an absent reason is legal).
17754
17866
 
17755
17867
  Engine-side MUTUALLY EXCLUSIVE with `ruleOffers`. 🔴 ABSENCE IS NOT AN ASSERTION — it covers both
17756
- "there are offers" and the three structural doors, and one frame cannot tell them apart. ADVISORY
17868
+ "there are offers" and the structural doors, and one frame cannot tell them apart. ADVISORY
17757
17869
  display metadata, NEVER adjudication input. The frame and the stored `card_json` go through the SAME
17758
- server-side narrow read, so both faces carry identical values; a word outside the set is treated as
17870
+ server-side narrow read, so both faces carry identical values; a word outside the four is treated as
17759
17871
  malformed and the key is not minted at all (the server never passes an unknown word through).
17760
17872
 
17873
+ BoundGrantAbsence:
17874
+ type: string
17875
+ enum: [not_bindable, unreadable]
17876
+ description: >
17877
+ WHY a tool-approval card has NO grant-binding digest (`ToolApprovalFrame.boundGrantDigest`) — the engine's closed `BOUND_GRANT_ABSENCES` (core 7.51.0 #1688; server >= 7.129.0). The engine mints
17878
+ EXACTLY ONE of the digest and this word on every card. `not_bindable` = this ask is not an activation of the engine's `Skill` tool, so it binds no grant: approving it grants nothing else, and "allow for this
17879
+ session" can be remembered per tool. `unreadable` = the card binds a grant but what it binds could not be read (a stored row whose digest is not a string, or says neither — a row parked before engine 7.51.0
17880
+ reads as this on the inbox face); on a live card the engine always supplies the digest, so this word shows up only where STORED rows are read.
17881
+ Four carriers, same name and same value: the live `tool_approval` frame (and `livePending[].frame`), the inbox row, the operator queue row and the `/v1/approvals/stream` `pending` frame; `card_json` and the
17882
+ presented-card frame do not carry it.
17883
+ 🔴 ABSENCE IS NOT `not_bindable`: a skill card carries the digest (exactly one of the two), and an old server (<= 7.128.x projects it on none of the faces), a row parked before engine 7.51.0 on the queue face (it says neither),
17884
+ 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`.
17885
+ Display metadata, never an adjudication input. CLOSED: the server narrows with the engine's members, anything else reads as absent.
17886
+
17761
17887
  PersistedRuleMandate:
17762
17888
  type: string
17763
17889
  enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, target_unresolved]
@@ -18892,7 +19018,7 @@ components:
18892
19018
  type: object
18893
19019
  description: >
18894
19020
  DURABLE only (design/80 D-B, server http/server.ts) — a RESUMED plan_review leg got re-gated into
18895
- another review pause. Same payload shape as `suspended`: the gate verbatim, or an explicit `null`.
19021
+ another review pause. Same payload shape as `suspended`: the gate verbatim, or an explicit `null`; plus `toolCallId?` / `reopened?` (sdk 23.0.0 declares both; the arm is closed).
18896
19022
  NOTE `needs_review` is BOTH a `RunStatus` terminal and a `CheckpointGate.kind`; this arm is the third
18897
19023
  use of the name — the stream frame announcing that pause.
18898
19024
  additionalProperties: false
@@ -18904,6 +19030,14 @@ components:
18904
19030
  oneOf:
18905
19031
  - $ref: '#/components/schemas/CheckpointGate'
18906
19032
  - type: 'null'
19033
+ reopened:
19034
+ type: ['string', 'null']
19035
+ description: >
19036
+ server >= 7.129.0 — same reading as `suspended.reopened`: a resume leg failed BEFORE the approved step started and the `plan_review` gate went back to `needs_review`; the errorCode of that failure (explicit null when it had none).
19037
+ (Before 7.129.0 a `plan_review` reopen of the three listed codes was recorded as `suspended`.) Ordinary frames do not carry the key.
19038
+ toolCallId:
19039
+ type: string
19040
+ description: 'Same as `suspended.toolCallId` (ABSENT when the park has no tool call; never `null`).'
18907
19041
  Event_message_committed:
18908
19042
  type: object
18909
19043
  description: >
@@ -19199,6 +19333,9 @@ components:
19199
19333
  ruleOffersAbsence:
19200
19334
  allOf: [{ $ref: '#/components/schemas/RuleOffersAbsence' }]
19201
19335
  description: '`pending` frames only (server >= 7.100.0 / S-700) — see PendingCheckpoint.ruleOffersAbsence.'
19336
+ boundGrantAbsence:
19337
+ allOf: [{ $ref: '#/components/schemas/BoundGrantAbsence' }]
19338
+ description: '`pending` frames only (server >= 7.129.0 / core 7.51.0 #1688) — see PendingCheckpoint.boundGrantAbsence. A `live_pending` frame carries it only inside its `frame`.'
19202
19339
  askClass:
19203
19340
  allOf: [{ $ref: '#/components/schemas/AskClass' }]
19204
19341
  description: '`pending` frames only (server >= 7.109.0 / S-934 ②) — see PendingCheckpoint.askClass. A `live_pending` frame carries the pair only inside its `frame`.'
@@ -20437,12 +20574,12 @@ components:
20437
20574
  remember for this session's category (fs-write tools share ONE category: approving Write covers
20438
20575
  Edit/NotebookEdit; other tools are per-tool) — the CC "don't ask again this session" semantic.
20439
20576
 
20440
- ToolApprovalRespondAck:
20577
+ ToolApprovalRespondApplied:
20441
20578
  type: object
20442
20579
  description: >
20443
- The 200 ack of POST /v1/tool-approvals/{approvalId}/respond (server tool-approval.ts — exact
20444
- shape, `decision` echoed). Owner-gated with a 404 and NO existence oracle: non-owner and
20445
- unknown/settled/expired/wrong-replica ids are indistinguishable.
20580
+ The APPLIED arm of the 200 ack of POST /v1/tool-approvals/{approvalId}/respond (server tool-approval.ts — exact
20581
+ shape, `decision` echoed): the decision settled a live gate. Owner-gated with a 404 and NO existence oracle: non-owner and
20582
+ unknown/settled/expired/wrong-replica ids are indistinguishable. See ToolApprovalRespondAck for the replayed arm.
20446
20583
  # 封闭(census 批2 第三段,2026-07-30):铸造点 `tool-approval.ts` 三恒在键 + 两条件键
20447
20584
  # (rememberApplied/updatedInputForwarded 的条件展开就在同一行字面量里)。
20448
20585
  # #154 车二(2026-08-10):`persistRuleAfterDecision` 的 `withFlag()` 在同一个体上再盖两个条件键
@@ -20544,6 +20681,33 @@ components:
20544
20681
  server ignores the unknown key and still answers 200, so "this build does not know `note`" is only
20545
20682
  distinguishable via `capabilities.approvalDecisionNote` — probe that bit before sending `note`.
20546
20683
 
20684
+ ToolApprovalRespondReplayed:
20685
+ type: object
20686
+ description: >
20687
+ The REPLAYED arm of the 200 ack of POST /v1/tool-approvals/{approvalId}/respond: the ask was already DECIDED
20688
+ (by anyone) when this answer arrived, so the server replays the FIRST decision — `decision` is the row's two-word
20689
+ value (`allow_session` was folded to `allow` when it landed), `decidedAtMs` the first decision's clock,
20690
+ `decisionNote` only when the row has one, `rememberApplied: false` only when THIS request asked `allow_session`.
20691
+ No `delivery` key: nothing was delivered. A reversed late answer is the same replay — not a 409, nothing re-judged.
20692
+ (server src/tool-approval.ts `replayDecidedAsk`; sdk 23.0.0 declares it, the shape predates it.)
20693
+ required: [approvalId, askId, idempotent, decision, decidedAtMs]
20694
+ additionalProperties: false
20695
+ properties:
20696
+ approvalId: { type: string }
20697
+ askId: { type: string }
20698
+ idempotent: { type: boolean, enum: [true] }
20699
+ decision: { type: string, enum: [allow, deny] }
20700
+ decidedAtMs: { type: integer }
20701
+ decisionNote: { type: string }
20702
+ rememberApplied: { type: boolean, enum: [false] }
20703
+ ToolApprovalRespondAck:
20704
+ description: >
20705
+ The 200 ack of POST /v1/tool-approvals/{approvalId}/respond — a discriminated union: the decision was APPLIED to a live
20706
+ gate (ToolApprovalRespondApplied, `delivery: "applied"`) or the ask was already decided and the first decision is
20707
+ REPLAYED (ToolApprovalRespondReplayed, `idempotent: true`). The discriminant is `idempotent`.
20708
+ oneOf:
20709
+ - $ref: '#/components/schemas/ToolApprovalRespondApplied'
20710
+ - $ref: '#/components/schemas/ToolApprovalRespondReplayed'
20547
20711
  UsageMetric:
20548
20712
  type: string
20549
20713
  enum: [tasks, tokensIn, tokensOut, costUsd]