@sema-agent/sdk 16.2.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml CHANGED
@@ -417,7 +417,7 @@ paths:
417
417
  prepare-time refusal code that HAS a status in the server's per-code triage table (`resume_at.*` — `resume_at.not_found` is 404, the rest 422 —, governance codes,
418
418
  `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported` / `rewind_snapshot.unresolvable`, the
419
419
  registered `config.*` codes incl. the three roster-bound ones and — server >= 7.116.0 — `config.invalid_checkpoint_store` (400) and
420
- `config.user_invoked_skill_unknown` (400: top-level `userInvokedSkills` named a skill this run does not carry), `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
420
+ `config.user_invoked_skill_unknown` (400: top-level `userInvokedSkills` named a skill this run does not carry) and — server >= 7.117.0 — `config.image_invalid` (400: an inline image failed the engine's byte admission; `error` starts with `TaskSpec.images: `), `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
421
421
  200 with a failed body) answers its 4xx / 5xx ERROR BODY only when the whole submission did ZERO WORK (cumulative
422
422
  `stats.turns === 0` AND `stats.tokens === 0` — the common case: nothing billed, not idempotency-cached). When a
423
423
  `verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
@@ -886,6 +886,13 @@ paths:
886
886
  ⚠️ Against a server 7.19.0–7.22.0 the OLD caveat applies verbatim: only the parked legs dedupe —
887
887
  a lost 200 retried with the same key injects TWICE with byte-identical receipts; treat a lost 200
888
888
  there as "outcome unknown", never "safe to retry".
889
+ 🔴 server >= 7.117.0 — TWO MORE 409 answers on a TERMINAL run (the `parked_for_wake` leg), refused BEFORE the wake card is minted
890
+ (no card is minted; the row is untouched; on 7.116.x this was a 202 `parked_for_wake` followed by a refusal at `/wake`, i.e. a card that could
891
+ never be redeemed): when the stored submit body of this session is one this version no longer replays — errorCode
892
+ `checkpoint.unsupported_version` (`retriable: true` = the card is still pending / cancellable, NOT "a retry will succeed"; the stored body carries
893
+ `settings.permissions.allow` under an earlier reading — list or statement era) or errorCode `resume_blocked_by_policy` (`blockedBy: "request.field_invalid"`;
894
+ the stored skill `disallowedTools` carries an entry the engine now reads as nothing). Both answers are word-for-word the ones `POST /v1/sessions/{sessionId}/wake`
895
+ gives for the same body. Way out: cancel the task and resubmit as a new one (with the entry fixed). The SDK does not retry them (409 is not in the retry set).
889
896
  content:
890
897
  application/json:
891
898
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -7187,6 +7194,13 @@ components:
7187
7194
  description: 'core 5.11.0: extra READ-ONLY absolute host paths — read fence only (classify read-boundary auto-allow + read_file/grep containment); writes to the same dirs are still refused. Same shape/400 rules as additionalDirectories; servers <6.6.0 silently ignore (harmless additive).'
7188
7195
  images:
7189
7196
  type: array
7197
+ description: >
7198
+ Image input: `{ data, mimeType }` (inline base64) or `{ url }`. 🔴 INLINE-ARM ADMISSION (server >= 7.117.0 / core 7.42.0): `mimeType` must be one of `image/png` /
7199
+ `image/jpeg` / `image/gif` / `image/webp` (the API's type word, not a file extension), the decoded bytes must carry the DECLARED type's file signature, and the decoded size must
7200
+ not exceed 5 MiB — otherwise the engine refuses BEFORE the first model call (after the UserPromptSubmit hook; not one model token spent) with 400 `config.image_invalid`, whose
7201
+ `error` starts with `TaskSpec.images: `. Before 7.117.0 such an image was sent as-is to the model provider and the whole first request failed there (no code to branch on).
7202
+ A base64 string longer than 5 242 880 characters (the model API's 5 MB image cap; the server reads the engine's exported constant) is NOT this code: the submit gate refuses it first
7203
+ with 413 `request.payload_too_large` (zero model calls, no submit hook, no run row). The `{ url }` arm is unchanged. The model must accept image input (`ModelInfo.vision`; absent = unknown, send anyway).
7190
7204
  items:
7191
7205
  oneOf:
7192
7206
  - type: object
@@ -7330,9 +7344,20 @@ components:
7330
7344
  the delivery lane, so raising it only helps a model whose declared window is below 200 000 tokens.
7331
7345
  The engine's own twin code `config.skill_listing_budget_invalid` (400) is NOT reachable through this
7332
7346
  submit leg today — it belongs to callers that build a `TaskSpec` directly.
7333
- 🔴 `permissions.allow` IS A STATEMENT, NOT A WHITELIST (server >= 7.115.0 / S-654; probe
7334
- `capabilities.settings.permissions.allowGrant === true` — an absent key is an older server that reads the bucket as a
7335
- list, so put nothing in it there). The bucket is compiled by the SAME rule compiler as `deny` / `ask`: a hit is a
7347
+ 🔴 `permissions.allow` — THE READING DEPENDS ON THE SERVER GENERATION (probe `capabilities.settings.permissions`; an absent bit = the older generation).
7348
+ server >= 7.117.0 (`allowLayer === true`, core 7.42.0): the WHOLE list goes to the engine's allow layer and a listed call is NOT ASKED — the engine's ordinary and
7349
+ "nobody stated it" questions are cleared, built-in write gates and shell checks included (like the reference implementation's `permissions.allow`); content forms
7350
+ (`Bash(git:*)`, `Read(//dir/**)`) are accepted; entries the engine reads as nothing (`*`, write-path forms such as `Edit(src/**)`, domain forms, names with whitespace) are NOT
7351
+ refused — the run opens with an `engine_notice` `permission.allowed_tool_not_granted`. Any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` /
7352
+ `MANUAL_MODE_SHELL_GATE=always`, write protection, governance, a hook's ask and plan-mode asks still ask (a matching `request` member then shows as `allowRuleShadowed` on the
7353
+ card). The submit gate judges only the shape (array of strings, at most 1000 entries => 400) and the NAMES (the same judge and the same sentence as `deny` / `ask`: a retired name,
7354
+ an unprefixed short name, or a protocol name whose server segment cannot be minted / is missing / is a wildcard (`mcp__*`) is a whole-request 422) — never the content.
7355
+ 🔴 THE 422 SENTENCE (server >= 7.117.0): `error` starts with `request settings cannot be applied as written: ` (<= 7.116.x: a different sentence that said tighten-only — all three
7356
+ buckets share this exit, and the old head misled a caller writing a tightening rule and lied about the allow bucket). Do NOT dispatch on the sentence: it carries no code of its own, so
7357
+ `errorCode` is the status-derived coarse code `request.unprocessable`.
7358
+ The skill lists (`skills[].allowedTools`) read exactly like this bucket from 7.117.0 on.
7359
+ Server 7.115.0–7.116.x (`allowGrant` without `allowLayer`): `permissions.allow` IS A STATEMENT, NOT A WHITELIST — an absent `allowGrant` is a server <= 7.114.x that reads the bucket
7360
+ as a list, so put nothing in it there. The bucket is compiled by the SAME rule compiler as `deny` / `ask`: a hit is a
7336
7361
  reasoned allow = "somebody stated it" (it answers an MCP / A2A server-tool call nobody stated; without it an
7337
7362
  ask-class mode raises a card, and with no approval surface the call is refused); a miss states nothing and the call is
7338
7363
  judged as usual — a tool off the list is NOT refused any more (<= 7.114.x refused it). It does NOT skip the question
@@ -9414,22 +9439,28 @@ components:
9414
9439
  settings:
9415
9440
  type: object
9416
9441
  description: >
9417
- 🔴 server >= 7.115.0 (S-654), ALWAYS present: `{ permissions: { allowGrant: true } }` — the VERSION bit for "which reading does this
9418
- server give `settings.permissions.allow` on a task submit". `permissions.allowGrant === true` = the `allow` bucket is compiled into
9419
- ALLOW RULES: a hit gives "somebody stated it" (so an MCP / A2A server-tool call nobody stated no longer raises a card) — it is NOT
9420
- "skip the question" and NOT a whitelist (a tool off the list is no longer refused; the engine's own write gate / shell checks still ask
9421
- on built-in tools, deny / ask still win; a bad-shape or content-form name is a whole-request 422).
9422
- 🔴 ABSENT KEY = an older server (<= 7.114.x), which read `allow` as a WHITELIST (everything off the list refused) — do not put anything
9423
- in the allow bucket there. NOT the same bit as `taskSettings.permissions` (unchanged, still a boolean: "this server accepts the
9424
- `settings.permissions` face"). A version bit: constant `true`, never varies with the deployment; the deployment-level "who stated what"
9425
- (operator env `APPROVAL_GRANT`) is on the operator diagnostics face (`serverGates.approvalGrant`), not here.
9442
+ 🔴 server >= 7.115.0, ALWAYS present: `{ permissions: { allowGrant: true } }`; server >= 7.117.0 adds `allowLayer: true` to `permissions` — the VERSION bits for
9443
+ "which reading does this server give `settings.permissions.allow` on a task submit" (both constant `true`, never vary with the deployment: the single-user
9444
+ and `REQUIRE_PRINCIPAL=true` forms carry the same value). Read three levels: `settings` -> `permissions` -> <bit>; an absent / non-boolean bit = a server without it.
9445
+ `permissions.allowGrant === true` (>= 7.115.0) = the `allow` bucket is read as allow RULES, not as a list (a tool off the list is no longer refused).
9446
+ `permissions.allowLayer === true` (>= 7.117.0, core 7.42.0) = the server hands the WHOLE `allow` list to the engine's allow layer: a listed call is NOT ASKED (the engine's
9447
+ ordinary and "nobody stated it" questions are cleared — built-in write gates / shell checks included, like the reference implementation's `permissions.allow`); content forms
9448
+ (`Bash(git:*)`, `Read(//dir/**)`) are accepted; entries the engine reads as nothing (`*`, write-path / domain forms, names with whitespace) are NOT refused — the run opens with an
9449
+ `engine_notice` `permission.allowed_tool_not_granted`; any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / shell floor / write protection /
9450
+ governance, a hook's ask and plan-mode asks still ask (a matching member then shows as `allowRuleShadowed` on the card). The submit gate judges only the shape and the NAMES (retired
9451
+ name / unprefixed short name / unmintable protocol server segment => 422, at most 1000 entries => 400) — never the content.
9452
+ 🔴 ABSENT `allowLayer` with `allowGrant` present = a 7.115.0–7.116.x server: allow is a STATEMENT (it does not clear the engine's own write gate / shell checks; a content form
9453
+ is a whole-request 422). ABSENT KEY altogether (no `settings`) = an older server (<= 7.114.x), which read `allow` as a WHITELIST (everything off the list refused) — do not put
9454
+ anything in the allow bucket there. NOT the same bit as `taskSettings.permissions` (unchanged, still a boolean: "this server accepts the `settings.permissions` face"). The
9455
+ deployment-level "who stated what" (operator env `APPROVAL_GRANT`) is on the operator diagnostics face (`serverGates.approvalGrant`), not here.
9426
9456
  additionalProperties: true
9427
9457
  properties:
9428
9458
  permissions:
9429
9459
  type: object
9430
9460
  additionalProperties: true
9431
9461
  properties:
9432
- allowGrant: { type: boolean, description: 'Constant `true` on server >= 7.115.0: `settings.permissions.allow` is read as allow RULES (a statement), not as a list. The whole `settings` key being absent = an older server.' }
9462
+ allowGrant: { type: boolean, description: 'Constant `true` on server >= 7.115.0: `settings.permissions.allow` is read as allow RULES, not as a list. The whole `settings` key being absent = an older server.' }
9463
+ allowLayer: { type: boolean, description: 'Constant `true` on server >= 7.117.0 (core 7.42.0): the whole `settings.permissions.allow` list goes to the engine allow layer (listed calls are not asked; content forms accepted; entries read as nothing get an `engine_notice` `permission.allowed_tool_not_granted`). Absent = a server without it (7.115.0–7.116.x: allow is a statement; older: a list).' }
9433
9464
  memoryWrite:
9434
9465
  type: boolean
9435
9466
  description: >
@@ -10014,8 +10045,9 @@ components:
10014
10045
  🔴 server >= 7.116.0 (core 7.41.0): a skill carries two TOOL LISTS, `allowedTools` / `disallowedTools` — the
10015
10046
  run-scoped ALLOW-RULE and DENY-RULE source of the skill once it is activated. NOT a fence (<= 7.115.x the engine read
10016
10047
  the skill's tool list as "only these"): unlisted tools are judged as usual, listed ones are not asked about for the rest
10017
- of the run — which makes it WIDER than `settings.permissions.allow` (that one stays a statement and does not
10018
- release the engine's own write gate / shell ordinary asks on built-in tools). Probe `capabilities.skillToolLists`
10048
+ of the run. On server 7.116.x that made it WIDER than `settings.permissions.allow` (then a statement that did not
10049
+ release the engine's own write gate / shell ordinary asks on built-in tools); server >= 7.117.0 reads the two request-level
10050
+ allow sources the SAME way (see `capabilities.settings.permissions.allowLayer`). Probe `capabilities.skillToolLists`
10019
10051
  (absent = an old server that accepts the keys on a top-level skill and silently ignores them). The skill's `manifest`
10020
10052
  key is RETIRED: any value on a top-level skill is a 400 `request.field_invalid` ("skill.manifest was retired in core
10021
10053
  7.41.0 — …"; rewrite `manifest.allowTools` to `allowedTools`; `allowPaths` has no replacement), and on a delegated
@@ -10032,21 +10064,28 @@ components:
10032
10064
  server >= 7.116.0. The skill's run-scoped ALLOW-RULE source (permission-rule spellings: a tool name,
10033
10065
  `mcp__<server>` / `mcp__<server>__*`; `*`, write paths and domain forms grant nothing). Once the skill is activated
10034
10066
  (named in top-level `userInvokedSkills` — no `Skill` card — or activated by the model and the card approved) the
10035
- listed tools are not asked about for the rest of the run: it clears the engine's ordinary and "nobody stated it" asks,
10036
- so it is WIDER than `settings.permissions.allow`; deny / ask rules, `APPROVAL_REQUIRE`, `APPROVAL_NEVER_AUTO`, the shell
10037
- floor, write protection and plan-mode asks are not cleared. NOT a fence: unlisted tools are judged as usual. A non-string-array
10038
- is a 400 `request.field_invalid`; a protocol name whose server segment cannot be minted / is missing / is a wildcard
10039
- (`mcp__*`) is a 400 too (top-level `skills[]` and `agents[].skills[]` judge identically). Probe `capabilities.skillToolLists`.
10067
+ listed tools are not asked about for the rest of the run: it clears the engine's ordinary and "nobody stated it" asks;
10068
+ deny / ask rules, `APPROVAL_REQUIRE`, `APPROVAL_NEVER_AUTO`, the shell floor, write protection and plan-mode asks are not cleared.
10069
+ NOT a fence: unlisted tools are judged as usual. Versus `settings.permissions.allow`: on server 7.116.x this list was WIDER (that bucket
10070
+ was then only a statement); server >= 7.117.0 reads the two the SAME way, entry for entry (entries the engine reads as nothing get an
10071
+ `engine_notice` `permission.allowed_tool_not_granted`). A non-string-array is a 400 `request.field_invalid`; a protocol name whose server
10072
+ segment cannot be minted / is missing / is a wildcard (`mcp__*`) is a 400 too, and from 7.117.0 so is a retired name / unprefixed short
10073
+ name (the name layer is judged by the same judge as the allow bucket) and a list of more than 1000 entries (top-level `skills[]` and
10074
+ `agents[].skills[]` judge identically). Probe `capabilities.skillToolLists`.
10040
10075
  disallowedTools:
10041
10076
  type: array
10042
10077
  items: { type: string }
10043
10078
  description: >
10044
10079
  server >= 7.116.0. The skill's run-scoped DENY-RULE source (same grammar / gates / probe as `allowedTools`): once the skill
10045
10080
  is activated these tools are refused for the run (no execution, no card; `tool_end.gate.disposition` is
10046
- `{ kind: "denied", deniedBy: "policy" }`). `*` and domain forms (`WebFetch(domain:…)`) read as "none" on this side too —
10047
- accepted with 2xx and only an operator-audience notice `skills.allowed_tool_not_granted`; write-path forms and the rest compile
10048
- into deny rows (as the server contract states it for the engine's `compileSkillGrant`; the SDK did not re-run the engine on the deny side's write-path forms —
10049
- the engine's own notice text lists "a write-path or domain entry" among entries that "grant (or refuse) nothing").
10081
+ `{ kind: "denied", deniedBy: "policy" }`); write-path forms and command rules compile into deny rows too.
10082
+ 🔴 server >= 7.117.0: an entry the engine reads as nothing (`*`, a domain form `WebFetch(domain:…)`, a protocol name with a wildcard, a name with
10083
+ whitespace or malformed) is refused AT SUBMIT — 400 `request.field_invalid`, `error` = `skill.disallowedTools entry "<entry>" reads as nothing (<reason>): <how to write it>`
10084
+ (delegated level: prefix `agents[<i>]: skills[<j>]: `), top-level `skills[]` and `agents[].skills[]` alike — because an entry that looks like a refusal but refuses nothing is
10085
+ worse than none (<= 7.116.x accepted it with only an operator-audience notice that was not sent to the session stream; that code is now `permission.allowed_tool_not_granted`,
10086
+ user audience, on the session stream). 🔴 The resume legs judge the stored body the same way: a parked task whose stored skill `disallowedTools` carries such an entry answers
10087
+ 409 `resume_blocked_by_policy` (`blockedBy: "request.field_invalid"`, same sentence) BEFORE the claim — row and card untouched; a terminal run's `POST /v1/runs/{taskId}/steer`
10088
+ answers it before minting the wake card. Why: engine 7.41 trimmed whitespace (`"Bash "` was a working deny), engine 7.42 reads verbatim (reads as nothing). The allow side does not refuse.
10050
10089
 
10051
10090
  McpServerSpec:
10052
10091
  type: object
@@ -10788,16 +10827,17 @@ components:
10788
10827
  baseUrl/apiKey/headers (the service strips them).
10789
10828
  # 🔴 封闭 + required 补铸造点(census 批2 第三段,2026-07-30):铸造点 `routes/capabilities.ts`
10790
10829
  # 是逐键字面量投影(白名单,不是透传——「open set」旧注不成立,新键要先过这道 spec)。core Model 类型
10791
- # 里 id/provider/reasoning 都是必填、`vision` 由 `input` 恒算 ⇒ 五键无条件;contextWindow/maxOutputTokens
10792
- # 0/缺省即省略,supportedEffortLevels 仅 reasoning 模型。
10793
- required: [name, id, provider, reasoning, vision]
10830
+ # 里 id/provider/reasoning 都是必填 ⇒ 四键无条件;contextWindow/maxOutputTokens 0/缺省即省略,
10831
+ # supportedEffortLevels 仅 reasoning 模型。🔴 server 7.117.0 起 `vision` 是三态(收 / 不收 / 没声明 `input`),
10832
+ # 没声明 ⇒ 键缺席 ⇒ 不再是必有键(此前由 `input` 恒算成布尔,对没声明的条目报 false)。
10833
+ required: [name, id, provider, reasoning]
10794
10834
  additionalProperties: false
10795
10835
  properties:
10796
10836
  name: { type: string, description: 'the @mention handle (user-pickable model name).' }
10797
10837
  id: { type: string, description: 'underlying model id (display, e.g. deepseek-v4-pro).' }
10798
10838
  provider: { type: string }
10799
10839
  reasoning: { type: boolean, description: 'a reasoning-tier model (picker can badge it).' }
10800
- vision: { type: boolean, description: 'accepts image input.' }
10840
+ vision: { type: boolean, description: 'Whether this catalog row accepts image input — THREE states (server >= 7.117.0; before it a row that declared no `input` reported `false`): `true` = the row declares `input` and it contains "image"; `false` = declares `input` without it; ABSENT = the row declared no `input` (a hand-written catalog entry; the env lane and the config-center lane always declare it). ABSENT means UNKNOWN, not false: treat it as "send the image anyway and tell the user", do not read it as `false` and block the image locally — the server hands the image to the model for that same row at run time. Only an explicit `false` justifies "this model does not take images".' }
10801
10841
  contextWindow: { type: integer, description: 'E4 (SHIPPED) — context-window token DENOMINATOR for the %-used gauge; omitted when unknown.' }
10802
10842
  maxOutputTokens: { type: integer, description: 'E4 (SHIPPED) — single-call max output tokens; omitted when unknown.' }
10803
10843
  supportedEffortLevels:
@@ -12260,6 +12300,16 @@ components:
12260
12300
  toolName: { type: string }
12261
12301
  summary: { type: string }
12262
12302
  touchedPaths: {}
12303
+ allowRuleShadowed:
12304
+ type: array
12305
+ items: { $ref: '#/components/schemas/AllowRuleShadow' }
12306
+ description: >
12307
+ server >= 7.117.0 (core 7.42.0): the DURABLE dual of `ToolApprovalFrame.allowRuleShadowed` — the allow-layer members that matched this call
12308
+ and could not clear its ask (same shape and semantics; entries are secret-redacted again at the server's read boundary). Passed through whole
12309
+ on `GET /v1/approvals` and `/v1/approvals/stream`. Absent != "you have no rule". 🔴 The old key `shadowedRule` (a string, server <= 7.116.x)
12310
+ is RETIRED: the SQL backend rewrote its parked rows once at the 7.117.0 boot to `[{ member: "persisted", entries: [<old value>] }]`, but the
12311
+ file backend's pre-upgrade parked rows and run results already on disk (`checkpointGate.riskDescriptor`) keep the old key verbatim
12312
+ (text still redacted, no structured disclosure) — a consumer reading those must still tolerate the old string.
12263
12313
  # server 7.16.0 [3684]② — durable 队列行归因两键(与活卡 tool_approval 帧的同名键语义同族、判据不同源;
12264
12314
  # 两条腿对同一只 ask 的在场性可以不一致,两侧都只认在场、都禁把缺席读成否定)。
12265
12315
  ruleSuggestions:
@@ -13628,6 +13678,8 @@ components:
13628
13678
  `not_found.resource` / `request.unprocessable` / `capability.unavailable` …). ALWAYS PRESENT on server >= 7.104.0
13629
13679
  (S-607); before that it was absent when the original refusal carried no code. SDK:
13630
13680
  `ResumeBlockedByPolicyError.blockedBy` (`undefined` when the key is absent or not a string — never defaulted).
13681
+ server >= 7.117.0 adds a third fold source: a stored skill `disallowedTools` entry the engine now reads as nothing (the submit leg's 400 `request.field_invalid` sentence)
13682
+ ⇒ `blockedBy: "request.field_invalid"`, refused before the claim; the terminal-run steer leg answers it before minting the wake card.
13631
13683
 
13632
13684
  ResumeLegConflictError:
13633
13685
  description: >
@@ -15905,29 +15957,45 @@ components:
15905
15957
  requiresRealApproval / shadowed / hook-produced / inherited-unresolved / ancestor-resolved /
15906
15958
  anonymous asks. Absence reads as "THIS CARD has no such option", never as "this deployment has no
15907
15959
  rule lane" (that is `capabilities.permissionRules`) and never as "your rule broke" (that is
15908
- `persistedRuleShadowed`).
15960
+ `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x).
15909
15961
 
15910
15962
  ⚠️ REDEMPTION SCOPE is the live respond leg keyed by this frame's `approvalId` only; a replayed
15911
15963
  offer on another replica is not a promise that it is still redeemable.
15912
15964
  persistedRuleShadowed:
15913
15965
  type: string
15966
+ deprecated: true
15914
15967
  description: >
15915
- server >= 7.13.0 (core 5.25.0 #144), ADDITIVE, "tool_approval" only: this ask MATCHED one of the
15968
+ 🔴 RETIRED by server 7.117.0 (core 7.42.0) — replaced by `allowRuleShadowed`, no alias. A server >= 7.117.0 never mints this key
15969
+ (its strict card schema even refuses a stored card carrying it); only a server <= 7.116.x still does, and the SDK keeps the declaration
15970
+ for those servers (the supported floor is far below 7.117.0). Slated for deletion in the next major.
15971
+
15972
+ For those older servers (server >= 7.13.0 / core 5.25.0 #144, ADDITIVE, "tool_approval" only): this ask MATCHED one of the
15916
15973
  caller's persisted allow rules and that rule COULD NOT silence it. The value is the shadowed rule's
15917
15974
  VERBATIM text (secret-redacted + length-bounded). UNTRUSTED for display, same treatment as `message`.
15918
-
15919
- It documents a BEHAVIOUR NARROWING ("Allow rules silence the CLASSIFIER's questions, never a MANDATED
15920
- one"): under MANUAL_MODE_SHELL_GATE=always, for tools carrying an egress/irreversible mark, and under
15921
- the governance gates, asks a rule used to silence now REAPPEAR. Render an explanatory line ("your rule
15922
- is still there — this call must be confirmed each time"); without it the only conclusion a user can
15923
- draw is "my rule broke", while the rule sits fine in the store.
15924
-
15925
- 🔴 ABSENT != "you have no rule": present only when a rule matched AND could not clear the ask. Most
15926
- asks have no match at all, and matched-and-cleared ones never become asks (they were silenced — no
15927
- card exists). 🔴 Deliberately SEPARATE from `governanceForced` and not derivable from it: that key
15928
- answers "who imposed the gate", this one answers "what happened to YOUR rule"; governance is only one
15929
- of three un-silenceable sources (doctrine `always` and a tool's own mark carry no governance badge).
15930
- The durable dual is `gate.riskDescriptor.shadowedRule` on the parked row.
15975
+ 🔴 ABSENT != "you have no rule": present only when a rule matched AND could not clear the ask. 🔴 Deliberately SEPARATE
15976
+ from `governanceForced` and not derivable from it: that key answers "who imposed the gate", this one answers "what
15977
+ happened to YOUR rule". The durable dual on those servers is `gate.riskDescriptor.shadowedRule` on the parked row.
15978
+ allowRuleShadowed:
15979
+ type: array
15980
+ items: { $ref: '#/components/schemas/AllowRuleShadow' }
15981
+ description: >
15982
+ server >= 7.117.0 (core 7.42.0), ADDITIVE, "tool_approval" only — BREAKING RENAME, replacing the retired string `persistedRuleShadowed`
15983
+ (no alias): the allow-layer members that MATCHED this call and could NOT clear this ask, one `{ member, entries, omitted? }` record per
15984
+ matching member (see AllowRuleShadow). `member` is one of `hook` / `deployment` / `persisted` / `skill` / `request`; `entries` are the
15985
+ entries that member matched, as their source spells them (UNTRUSTED for display like `message`, secret-redacted, <= 512 chars each,
15986
+ <= 32 listed; a `hook` record's `entries` is always the empty list); `omitted` counts the entries not listed.
15987
+
15988
+ The explaining sentence ("... matches this call but does not clear the ask — <reason>") is IN `message` (the engine writes it where it builds
15989
+ the card); this key is the structured twin of the same fact. Render a line from it ("your rule matched, but this call must be confirmed each
15990
+ time"): without it the only conclusion a user can draw is "my rule broke".
15991
+
15992
+ 🔴 ABSENT != "you have no rule": present only when a member matched AND could not clear the ask; a match that cleared never becomes an ask.
15993
+ 🔴 `member: "hook"` does not say whose hook (a deployment PreToolUse hook and, on the single-user lane, the request's own `settings.hooks`
15994
+ share one member). 🔴 Deliberately SEPARATE from `governanceForced` (that one: "who imposed the gate"; this one: "what happened to YOUR
15995
+ rule"). What the allow layer can NOT clear: any layer's deny / ask rule, operator `APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` /
15996
+ `MANUAL_MODE_SHELL_GATE=always`, write protection, sensitive-path writes, governance, a hook's ask, plan-mode asks. The durable dual is
15997
+ `gate.riskDescriptor.allowRuleShadowed` on the parked row (PendingCheckpoint.riskDescriptor). Rolling-upgrade caveat: a card stored by a
15998
+ <= 7.116.x replica (`card_json` carrying `persistedRuleShadowed`) cannot be read by a >= 7.117.0 replica — the replay skips it.
15931
15999
  probeCause:
15932
16000
  type: object
15933
16001
  description: >
@@ -16320,12 +16388,21 @@ components:
16320
16388
  promise and the per-leg index rules are stated verbatim on ToolApprovalFrame.ruleOffers.
16321
16389
  persistedRuleShadowed:
16322
16390
  type: string
16391
+ deprecated: true
16392
+ description: >
16393
+ 🔴 RETIRED by server 7.117.0 (core 7.42.0) — replaced by `allowRuleShadowed`, no alias (a server >= 7.117.0 never mints it; the strict card
16394
+ schema refuses a stored card carrying it). Only a server <= 7.116.x still does; slated for deletion in the next major. For those servers
16395
+ (ADDITIVE, server >= 7.13.0 / core 5.25.0 #144): the VERBATIM text of the persisted rule this ask matched but which could not silence it
16396
+ (secret-redacted + length-bounded). UNTRUSTED for display. Semantics, the ABSENT != "you have no rule" clause and the deliberate separation
16397
+ from `governanceForced` are stated verbatim on ToolApprovalFrame.persistedRuleShadowed.
16398
+ allowRuleShadowed:
16399
+ type: array
16400
+ items: { $ref: '#/components/schemas/AllowRuleShadow' }
16323
16401
  description: >
16324
- ADDITIVE (server >= 7.13.0 / core 5.25.0 #144): the VERBATIM text of the persisted rule this ask
16325
- matched but which could not silence it (secret-redacted + length-bounded). UNTRUSTED for display.
16326
- Semantics, the ABSENT != "you have no rule" clause and the deliberate separation from
16327
- `governanceForced` are stated verbatim on ToolApprovalFrame.persistedRuleShadowed — the live frame,
16328
- the stored `card_json` and the replayed frame carry the SAME material.
16402
+ ADDITIVE (server >= 7.117.0 / core 7.42.0, BREAKING RENAME of the retired `persistedRuleShadowed`): the allow-layer members that matched this
16403
+ call and could not clear this ask. Semantics, the five members, the always-empty `hook` entries, the explaining sentence living in `message`,
16404
+ the ABSENT != "you have no rule" clause and the separation from `governanceForced` are stated verbatim on ToolApprovalFrame.allowRuleShadowed
16405
+ — the live frame, the stored `card_json`, the replayed frame and the `approval_request` card carry the SAME material.
16329
16406
  fromSubagent: { type: boolean, enum: [true], description: 'Present ⇔ the ask comes from a DELEGATED child — the explicit discriminator (a worker cannot forge it).' }
16330
16407
  sourceTaskId: { type: string, description: 'The child''s core session id — row-join value + fallback discriminator.' }
16331
16408
  sourceAgentName: { type: string, description: 'The child agent''s display name, redacted. UNTRUSTED.' }
@@ -16450,6 +16527,37 @@ components:
16450
16527
  `.strict()`). The eleven known words are exported at runtime as the SDK's `ASK_ORIGINS` (core 7.32.1
16451
16528
  `ASK_ORIGINS`, verbatim and in order) — known words, NOT an enforcement table.
16452
16529
 
16530
+ AllowRuleMember:
16531
+ type: string
16532
+ enum: [hook, deployment, persisted, skill, request]
16533
+ description: >
16534
+ The closed five-word vocabulary of `AllowRuleShadow.member` (server >= 7.117.0 / core 7.42.0 `ALLOW_RULE_MEMBERS`, verbatim and in the order the engine reads the members). The SDK exports
16535
+ it at runtime as `ALLOW_RULE_MEMBERS`. Owner = core; read it as an open string with a default arm (core may add a member and the server follows).
16536
+
16537
+ AllowRuleShadow:
16538
+ type: object
16539
+ additionalProperties: false
16540
+ required: [member, entries]
16541
+ description: >
16542
+ One allow-layer DISCLOSURE record (`ToolApprovalFrame.allowRuleShadowed` / `ApprovalCard.allowRuleShadowed` / the durable
16543
+ `PendingCheckpoint.riskDescriptor.allowRuleShadowed`; server >= 7.117.0 / core 7.42.0 `AllowRuleShadow`): one member of the allow layer that
16544
+ MATCHED the call and could NOT clear its ask. At most one record per member, at most five per ask. The vocabulary owner is core; the server
16545
+ drops the whole key on a member outside the five words, so a >= 7.117.0 server only ever sends these five — still read `member` as an open
16546
+ string with a default arm (core may add members and the server follows).
16547
+ properties:
16548
+ member:
16549
+ $ref: '#/components/schemas/AllowRuleMember'
16550
+ description: >
16551
+ Which allow-layer member matched, in the order the engine reads them: `hook` = a PreToolUse hook's explicit `allow` (a deployment hook or,
16552
+ on the single-user lane, the request's own `settings.hooks` — the key cannot say which) · `deployment` = the operator's `APPROVAL_GRANT` ·
16553
+ `persisted` = a person's stored allow rules · `skill` = an activated skill's `allowedTools` · `request` = the request's `settings.permissions.allow`.
16554
+ entries:
16555
+ type: array
16556
+ maxItems: 32
16557
+ items: { type: string, maxLength: 512 }
16558
+ description: 'The entries that member matched, as their source spells them (UNTRUSTED for display, secret-redacted, <= 512 chars each, <= 32 listed). Always the empty list for `hook` — its word is about the call, not about an entry.'
16559
+ omitted: { type: integer, minimum: 1, description: 'How many matched entries were not listed (beyond the cap). Absent = none omitted.' }
16560
+
16453
16561
  RuleOffersAbsence:
16454
16562
  type: string
16455
16563
  enum: [mandated, shadowed, lane_cannot_speak]
@@ -16458,7 +16566,7 @@ components:
16458
16566
  `ApprovalCard.ruleOffersAbsence`; server >= 7.55.0 / core #490 fix ②, S-15). The engine names which
16459
16567
  door is shut: `mandated` = this ask must be confirmed every time (🔴 this arm must NOT point the user
16460
16568
  at writing a rule — a rule would not silence it, and saying so sends them into a loop); `shadowed` = a
16461
- rule matched but could not clear it (the machine-readable twin of `persistedRuleShadowed`);
16569
+ an allow-layer member matched but could not clear it (the machine-readable twin of `allowRuleShadowed`; `persistedRuleShadowed` on a server <= 7.116.x);
16462
16570
  `lane_cannot_speak` = the engine could mint no coverable form for this command.
16463
16571
 
16464
16572
  Engine-side MUTUALLY EXCLUSIVE with `ruleOffers`. 🔴 ABSENCE IS NOT AN ASSERTION — it covers both
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "16.2.0",
3
+ "version": "16.3.0",
4
4
  "description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",