@sema-agent/sdk 17.0.0 → 17.2.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
@@ -5804,7 +5804,7 @@ paths:
5804
5804
  content:
5805
5805
  application/json:
5806
5806
  schema: { $ref: '#/components/schemas/CcImportPrepareResult' }
5807
- '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — not {layers:[{layer,path,root,content}]}, or an empty `root` (an empty prefix would silently promote a project rule to global)
5807
+ '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — not {layers:[{layer,path,root,content}]}, or a layer `root` the server does not accept (server >= 7.120.0: not a complete absolute path — see RuleScope; sentence "scope must be 'global' or 'project:<root>' with an absolute root", no ticket minted; <= 7.119.x: an empty `root`, answered with the body-shape sentence)
5808
5808
  '401': { $ref: '#/components/responses/Unauthorized' }
5809
5809
  '413': { $ref: '#/components/responses/PayloadTooLarge' } # request.payload_too_large — more than 200 candidates across all submitted layers; SHORTEN and resend (retrying the same payload cannot help)
5810
5810
  '429': { $ref: '#/components/responses/RateLimited' }
@@ -5931,8 +5931,8 @@ paths:
5931
5931
  required: false
5932
5932
  schema: { type: string, minLength: 1 }
5933
5933
  description: >
5934
- Filter to one scope, spelled `global` or `project:<root>` with a NON-EMPTY root. Unreadable ⇒ 400
5935
- `request.query_invalid` (never silently unfiltered). Matching is done on the CANONICAL form, so an
5934
+ Filter to one scope, spelled `global` or `project:<root>` (which roots are accepted: see RuleScope). Unreadable
5935
+ or a root the server does not accept ⇒ 400 `request.query_invalid` (never silently unfiltered). Matching is done on the CANONICAL form, so an
5936
5936
  equivalent-but-differently-spelled root filters to an empty page.
5937
5937
  - name: limit
5938
5938
  in: query
@@ -6015,7 +6015,7 @@ paths:
6015
6015
  content:
6016
6016
  application/json:
6017
6017
  schema: { $ref: '#/components/schemas/RuleWriteResult' }
6018
- '400': { $ref: '#/components/responses/BadRequest' } # request.field_invalid — `behavior: "allow"` (whole-sentence refusal, NO `reason` key), or a content test refused it (closed `reason` + human `detail`: rule_rejected / scope_bidi_controls / scope_control_chars / row_inconsistent / …, the SAME table the local-import preview answers with). request.body_shape — body is not {behavior, rule, scope, principal?}, or `scope` is not `global`/`project:<non-empty root>` (same sentence as DELETE)
6018
+ '400': { $ref: '#/components/responses/BadRequest' } # request.field_invalid — `behavior: "allow"` (whole-sentence refusal, NO `reason` key), or a content test refused it (closed `reason` + human `detail`: rule_rejected / scope_bidi_controls / scope_control_chars / row_inconsistent / …, the SAME table the local-import preview answers with). request.body_shape — body is not {behavior, rule, scope, principal?}, or `scope` is not `global`/`project:<root>` with a root the server accepts (see RuleScope; same sentence as DELETE)
6019
6019
  '401': { $ref: '#/components/responses/Unauthorized' }
6020
6020
  '403': { $ref: '#/components/responses/Forbidden' } # auth.operator_only — writing another principal's rules without explicit OPERATOR_PRINCIPALS membership
6021
6021
  '429': { $ref: '#/components/responses/RateLimited' }
@@ -6085,7 +6085,7 @@ paths:
6085
6085
  content:
6086
6086
  application/json:
6087
6087
  schema: { $ref: '#/components/schemas/RuleRevokeResult' }
6088
- '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — body is not {behavior, rule, scope, principal?} (server >= 7.67.0: `behavior` is REQUIRED and has no default), or `scope` is not `global`/`project:<non-empty root>`
6088
+ '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — body is not {behavior, rule, scope, principal?} (server >= 7.67.0: `behavior` is REQUIRED and has no default), or `scope` is not `global`/`project:<root>` with a root the server accepts (see RuleScope)
6089
6089
  '401': { $ref: '#/components/responses/Unauthorized' }
6090
6090
  '403': { $ref: '#/components/responses/Forbidden' } # auth.operator_only — revoking another principal's rule without explicit OPERATOR_PRINCIPALS membership
6091
6091
  '429': { $ref: '#/components/responses/RateLimited' }
@@ -6339,7 +6339,7 @@ paths:
6339
6339
  content:
6340
6340
  application/json:
6341
6341
  schema: { $ref: '#/components/schemas/DeviceEnrollResult' }
6342
- '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape (unknown key / missing required); request.field_invalid (platformOs outside the v1 closed set `darwin|linux` — a third value is never silently accepted; a field over its byte column — a silently truncated value is a DIFFERENT value)
6342
+ '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape (unknown key / missing required); request.field_invalid (platformOs outside the v1 closed set `darwin|linux` — a third value is never silently accepted; a field over its byte column — a silently truncated value is a DIFFERENT value; server >= 7.120.0: `workspaceRoot` not an absolute POSIX path — the token is not consumed)
6343
6343
  '401': { $ref: '#/components/responses/Unauthorized' }
6344
6344
  '403':
6345
6345
  description: >
@@ -6598,6 +6598,13 @@ components:
6598
6598
  declared exceeds the roster bound (name 128, contract id / revision 64, `modelGate` 64); the caller cannot route
6599
6599
  around it and a verbatim retry never changes it (DETERMINISTIC — the SDK does not auto-retry these codes; they land
6600
6600
  on ServiceStateError via the 503 tail, with no wait hint); only the deployment fixing its declaration helps.
6601
+ Server >= 7.119.0 adds `state.scenario_assignment_unknown` to this slot — the center's scenario assignment for the principal names a
6602
+ scenario this deployment does not have; refused before any side effect or billing, zero run, body `detail: { scenario, source }`, NO
6603
+ `Retry-After` (resending before the assignment is re-pointed — or the scenario added back to the deployment — is pointless; then retry
6604
+ verbatim, the serving replica reads the new assignment after its own cache window, one minute by default). The caller did nothing wrong:
6605
+ an explicit unknown `scenario` in the body is still 400 `scenario_unknown`. DETERMINISTIC for the SDK's retry arm (see
6606
+ `ErrorResponse.errorCode`); lands on ServiceStateError. On a resume leg the same refusal folds into 409 `resume_blocked_by_policy`
6607
+ (`blockedBy: state.scenario_assignment_unknown`).
6601
6608
  Which of them carry a wait hint is stated ONCE, under `headers.Retry-After` below (kept in one place so
6602
6609
  the two statements cannot drift apart).
6603
6610
  headers:
@@ -7118,7 +7125,7 @@ components:
7118
7125
  key on every submit of that session). ⚠️ A server < 7.104.0 answers 400 `request.body_shape` (unknown key).
7119
7126
  permissionMode:
7120
7127
  type: string
7121
- enum: [default, plan, acceptEdits, bypassPermissions, auto]
7128
+ enum: [default, plan, acceptEdits, bypassPermissions, auto, dontAsk]
7122
7129
  description: >
7123
7130
  🔴 K-32 — server >= 7.106.0 (S-877 / core 7.34.0 design/1111 "one permission mode"), READ THIS FIRST; the long text
7124
7131
  below describes <= 7.105.0. The effective mode passes through VERBATIM to the engine (`TaskSpec.permissionMode`); the
@@ -7131,13 +7138,16 @@ components:
7131
7138
  `config.permission_mode_unavailable`. `bypassPermissions` RELEASES the ten releasable ask classes (server >= 7.115.0: the tenth is `unstated`, a server-tool call nobody stated)
7132
7139
  and DISCLOSES each (`tool_end.gate.disposition.released` + one `engine_notice` `permission.ask_released_by_mode` per (mode, class) per run);
7133
7140
  immune classes (deployment-authored, ask / org rules, hooks, ancestors, questions …) still ask; a write whose protection
7134
- cannot be judged is refused (`deniedBy: mode`). Request vocabulary: FIVE words — `dontAsk` on a fresh submit is 400
7135
- `request.field_invalid` (S-902). The three sentences below — "omitting both carriers means no mode-derived gate", "`auto`
7141
+ cannot be judged is refused (`deniedBy: mode`). Request vocabulary: SIX words (server >= 7.119.0) — `dontAsk`, the engine's
7142
+ "auto-refuse" mode, is accepted and passes through verbatim: the ENGINE refuses the calls that would ask (no card, no parked row,
7143
+ `tool_end.gate.disposition = { kind: denied, deniedBy: mode, mode: dontAsk, … }`), the run ends normally, and it is never looser
7144
+ than `default`. One exception: a PreToolUse hook's OWN ask, when no other class in that ask's set backs it, is still handed to a person
7145
+ (the engine's `arbitrateByMode`: the `hook` class on a row without `hookAskFloor`). Send it only when `capabilities.permissionModes` carries it: that bit absent = server <= 7.118.x, whose request
7146
+ vocabulary is FIVE words — `dontAsk` on a fresh submit is 400 `request.field_invalid` there. The three sentences below — "omitting both carriers means no mode-derived gate", "`auto`
7136
7147
  is armed by the principal's entitlement independent of the mode word", "`bypassPermissions` adds no mode-derived gate" —
7137
7148
  hold for <= 7.105.0 only.
7138
- CC-parity permission-mode INTENT — the five values THIS wire's closed submit-gate accepts (post-[816]/
7139
- [820]/[822]; not a claim these are CC's/sema-cli's complete mode vocabulary, which is wider, e.g.
7140
- `dontAsk`). The frontend (TUI shell / web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
7149
+ CC-parity permission-mode INTENT — the values THIS wire's closed submit-gate accepts (the first five on server <= 7.118.x,
7150
+ plus `dontAsk` from 7.119.0 = the engine's whole mode vocabulary, `capabilities.permissionModes`). The frontend (TUI shell / web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
7141
7151
  `plan` ⇒ mount present_plan (core enablePlanMode) + read-only hands (CC EnterPlanMode research);
7142
7152
  `default` adds the manual ask gate on non-exempt writes (scratchpad dirs / remembered session
7143
7153
  exemptions still auto-allow — not literally every Write/Edit); the server emits the SAME gate-policy
@@ -7148,7 +7158,7 @@ components:
7148
7158
  as plain `default`; `acceptEdits` adds the SAME gate narrowed to auto-allow inside the working
7149
7159
  directory — but only when the service has a trusted working directory to scope it to; on a sandbox
7150
7160
  execution lane with none wired, `acceptEdits` degrades to asking on every non-exempt write, same
7151
- as `default`; `bypassPermissions` adds no mode-derived gate at all. None of the five can ever subtract
7161
+ as `default`; `bypassPermissions` adds no mode-derived gate at all. None of them can ever subtract
7152
7162
  from the deployment/operator policy (deny-wins). Omitting this field is NOT itself "no mode-derived
7153
7163
  gate" — this field wins over a `settings.permissions.defaultMode` bundle when both are sent, but when
7154
7164
  THIS field is omitted the bundled mode (if any) still supplies one; only when neither carrier supplies
@@ -8114,18 +8124,21 @@ components:
8114
8124
  enum: [mysql, pg, local, memory]
8115
8125
  description: >
8116
8126
  The ASSEMBLED backend (same word table; nothing assembled = `memory`). `declared != backend` is legal:
8117
- `DB_BACKEND=pg` + `SESSION_BACKEND=memory` (deliberately non-durable, no `degradedReason`) / an `auto` degrade
8118
- (`db_unreachable`) / a default-derived local store that could not be built (`declared: local`, `backend: memory`,
8119
- `local_unwritable`).
8127
+ `DB_BACKEND=pg` + `SESSION_BACKEND=memory` (deliberately non-durable, no `degradedReason`) / a default-derived local
8128
+ store that could not be built (`declared: local`, `backend: memory`, `local_unwritable`). Server >= 7.119.0: a configured
8129
+ SQL store that cannot open is retried inside the wait window and then the boot is REFUSED — it never degrades to memory
8130
+ (server <= 7.118.x degraded it silently under `SESSION_BACKEND=auto`, with the RETIRED word `degradedReason: db_unreachable`).
8120
8131
  durable: { type: boolean, description: '`backend !== "memory"`.' }
8121
8132
  degradedReason:
8122
8133
  type: string
8123
8134
  enum: [db_unreachable, local_unwritable]
8124
8135
  description: >
8125
- PRESENT-IFF the degrade arm ran; closed two words by backend family (server `STORE_DEGRADED_REASONS`):
8126
- `db_unreachable` = the SQL family could not open under `SESSION_BACKEND=auto` (connect or DDL, not told apart);
8136
+ PRESENT-IFF the degrade arm ran; the server's own closed set (`STORE_DEGRADED_REASONS`) is ONE word since server >= 7.119.0:
8127
8137
  `local_unwritable` = the default-derived local file store could not be built (mkdir / lock / permission / read-only
8128
- mount / disk full). Absent = not degraded.
8138
+ mount / disk full). ⚰️ RETIRED word `db_unreachable`: the SQL family could not open under `SESSION_BACKEND=auto` (connect or DDL, not told
8139
+ apart) — a server <= 7.118.x (inside the support floor) still sends it; a server >= 7.119.0 NEVER does (a configured SQL store that
8140
+ cannot open is retried, then the boot is refused — the SQL family has no degraded state). Kept in this enum so a valid response from an
8141
+ older server still validates; it is deleted once the support floor rises past 7.118.x. Absent = not degraded.
8129
8142
  sql:
8130
8143
  allOf: [{ $ref: '#/components/schemas/SqlEngineCapability' }]
8131
8144
  description: >
@@ -9349,11 +9362,32 @@ components:
9349
9362
  server >= 7.118.0, hardcoded true (a version bit): the three approval decision doors read `decidedBy` (ApprovalDecidedBy).
9350
9363
  Send it only when this is present; absent = an older server (the durable doors and `plan_review` 400 it, the live
9351
9364
  `respond` drops it silently and answers 200).
9365
+ sessionReadRedaction:
9366
+ type: boolean
9367
+ description: >-
9368
+ server >= 7.120.0, hardcoded true (a predicate-class bit, same family as `approvalDecidedBy`): the session HISTORY read
9369
+ (`GET /v1/sessions/:id`, the `messages` string leaves, every fetch form) passes the same redaction as the live and ledger
9370
+ frames (see SessionRecord). Branch on the bit, not on the server version. Bit absent is NOT `false` and NOT "unredacted":
9371
+ a server <= 7.118.x returns the archive as written; a 7.119.x server already redacts but does not report the bit. A true
9372
+ bit does not mean the read face exists (that is `sessions`; without a session audit face that door answers 501 and this
9373
+ bit is still true), nor that the archive holds no credential (the redaction is read-side only).
9352
9374
  approvalReach:
9353
9375
  type: boolean
9354
9376
  description: >-
9355
9377
  server >= 7.118.0, hardcoded true (same word as the request key): TaskRequest.approvalReach is understood. Absent = an
9356
9378
  older server — do not send the key.
9379
+ permissionModes:
9380
+ type: array
9381
+ items: { type: string }
9382
+ description: >-
9383
+ server >= 7.119.0 — the permission-mode words this worker's REQUEST face accepts: the engine's `PERMISSION_MODES` whole set, engine order
9384
+ (today six: `default`, `acceptEdits`, `plan`, `auto`, `bypassPermissions`, `dontAsk`). The top-level `TaskRequest.permissionMode`,
9385
+ `settings.permissions.defaultMode` and `GET /v1/capabilities?permissionMode=` read ONE closed set: every word in the bit is accepted by
9386
+ all three, a word outside it is 400 `request.field_invalid` whose sentence lists the same words. Send `dontAsk` only when this bit
9387
+ carries it; ABSENT = server <= 7.118.x (five words, `dontAsk` is a 400). OPEN set: the bit forwards the engine's vocabulary
9388
+ verbatim, so a future engine word shows up here before this document knows it — decide by the bit, do not filter it through the
9389
+ `PermissionMode` schema. It says "this server accepts the word", not "the word takes effect on this submit" (`auto` still needs a
9390
+ classifier seat and an entitlement — see `permissionModeAuto`).
9357
9391
  respondFreeFormRules:
9358
9392
  type: boolean
9359
9393
  description: >
@@ -9618,8 +9652,9 @@ components:
9618
9652
  the same false, and a shell must do different things for those. `reason` is present ONLY when
9619
9653
  `armed` is false; read its vocabulary as an OPEN set (server already grew it from four words to
9620
9654
  six). `model` is omitted when the classifier route cannot be resolved (the server logs the
9621
- engine's refusal rather than 500-ing the read face). `?permissionMode=<five words>` folds the
9622
- caller's INTENT into the verdict; anything outside the five words is a 400
9655
+ engine's refusal rather than 500-ing the read face). `?permissionMode=<mode word>` folds the
9656
+ caller's INTENT into the verdict (server >= 7.119.0 takes the six words of `capabilities.permissionModes` — `dontAsk` answers
9657
+ `armed: false` / `reason: mode_not_auto`; server <= 7.118.x takes five); anything outside the server's words is a 400
9623
9658
  `request.field_invalid`.
9624
9659
  # 🔴 required 只有**三位**:武装两键(armed / reason)是 server >= 7.57.0 (S-80)
9625
9660
  # 才有的,而本包的支持地板是 3.0.0 => every 7.x <= 7.56.0 server is inside the promised face and
@@ -12756,6 +12791,12 @@ components:
12756
12791
  description: >
12757
12792
  Session audit (`GET /v1/sessions/:id`) — true shape. `messages` = current model context
12758
12793
  (older history folded into a summary message); can be large.
12794
+ Server >= 7.119.0 (wire VALUE change, shape unchanged): string leaves inside `messages` (assistant text / reasoning, tool arguments,
12795
+ tool results, shell output, compaction summaries) pass the SAME redaction as the live and ledger frames — a credential-shaped span reads
12796
+ `«redacted:…»`. Key set, count and segment order are verbatim; a history with no credential-shaped span is byte-identical; the archive itself is
12797
+ untouched (the redaction is read-side only), and `?blobCap=` truncation runs AFTER redaction. A caller that rebuilds a model context from this
12798
+ read gets the placeholder, not the original string. Server >= 7.120.0 reports this with the capability bit `sessionReadRedaction`
12799
+ (a 7.119.x server redacts but does not report it).
12759
12800
  # 🔴 **暂不封闭**(census 轴一裁定,2026-07-30):本体是 core `StoredSession.buildContext()` 的投影
12760
12801
  # (server `audit.ts` auditFromStorage),再叠 server 自己的两条 additive 后处理(windowMessages /
12761
12802
  # truncateMessageBlobs)与路由层追加的 owner —— 键集由「core 投影 × 查询参数」共同决定,不是一个
@@ -13574,8 +13615,10 @@ components:
13574
13615
  7.104.0 / S-271 — a deployment-declared tool exceeds the roster bound), `config.tool_schema_bound` (503, server >=
13575
13616
  7.106.0 / core 7.35.0 #765 — a tool's `parameters` schema exceeds the roster row's key bound; "same family, same tier";
13576
13617
  <= 7.105.0 answered the synchronous leg 200 + a failed body), `corrupt` (500 — the session rule store
13577
- cannot read its own bytes) and `internal.memory_control_plane_corrupt` (500 — the memory control plane refuses
13578
- fail-closed). Every other 5xx code keeps the status-based retry.
13618
+ cannot read its own bytes), `internal.memory_control_plane_corrupt` (500 — the memory control plane refuses
13619
+ fail-closed) and `state.scenario_assignment_unknown` (503, server >= 7.119.0 — the center's scenario assignment for the
13620
+ principal is dangling; only re-pointing the assignment, or adding the scenario back to the deployment, helps). Every other 5xx
13621
+ code keeps the status-based retry.
13579
13622
  🔴 `request.host_posture_refused` (422) has TWO triggers (same code, same status — "you wrote it right, this
13580
13623
  deployment will not grant that host posture"): (1) the write-containment face (`permissionMode: bypassPermissions` ⇒
13581
13624
  `writeFace: open`; read `capabilities.taskWriteFaceOpen` first); (2) server >= 7.106.0 / S-895: `inheritEnv: "all"` on a
@@ -13657,10 +13700,19 @@ components:
13657
13700
  row's configuration was refused — `taskConfig` (change the configuration, then decide the SAME card again) or
13658
13701
  `inheritedGate` (the row's recorded mode seed is bad — the run can only be cancelled). Also on 400 `request.field_invalid` for a
13659
13702
  `settings.hooks` tool-event matcher that selects an engine alias but not its canonical name (server >= 7.115.0 / S-1025):
13660
- `field` is always the literal `settings.hooks` (see `detail`). SDK: `err.extra.field`.
13703
+ `field` is always the literal `settings.hooks` (see `detail`). Also on 400 `request.body_shape` (server >= 7.119.0), on the THREE
13704
+ tool-approval decision doors (`POST /v1/tool-approvals/{id}/respond`, `POST /v1/tasks/{taskId}/asks/{askId}/decision`,
13705
+ `POST /v1/approvals/{sessionId}/decide`) and ONLY there: the body carried the retired key `settledBy` (any value but `undefined` —
13706
+ `null` and `""` count), the refusal landed before any decision, and `field` is the literal `settledBy`; the three doors now answer
13707
+ the SAME sentence. Every such 400 carries it, even when another defect in the same body was hit first (the sentence then names that
13708
+ defect). Branch on the code plus `field`, never on the sentence; a bad body WITHOUT that key carries no `field`. SDK: `err.extra.field`.
13661
13709
  detail:
13662
13710
  description: >
13663
- 🔴 TWO SHAPES under one code — branch on `field` (or the `errorCode`), never guess from the type of `detail`. On 400
13711
+ 🔴 SHAPES differ by code — branch on `field` (or the `errorCode`), never guess from the type of `detail`. On 503
13712
+ `state.scenario_assignment_unknown` (server >= 7.119.0): an OBJECT `{ scenario, source }` — `scenario` = the dangling scenario name,
13713
+ `source` ∈ `ruling` (the center's assignment for this principal names a scenario this deployment does not have) / `deployment`
13714
+ (the deployment default `DEFAULT_SCENARIO` is dangling — the server refuses to BOOT on that, so a request reaches it only on an
13715
+ embedder that assembles `resolveSpec` itself). On 400
13664
13716
  `request.field_invalid` for a `settings.hooks` tool-event matcher (server >= 7.115.0 / S-1025; additive, the sentence and the code
13665
13717
  are unchanged): an OBJECT `{ event, alias, canonical }` — `event` = the tool event of the refused group (the first refused one when
13666
13718
  several are), `alias` / `canonical` = the engine catalog's alias and canonical name (closed strings, never the caller's raw bytes),
@@ -13675,6 +13727,12 @@ components:
13675
13727
  event: { type: string }
13676
13728
  alias: { type: string }
13677
13729
  canonical: { type: string }
13730
+ - type: object
13731
+ required: [scenario, source]
13732
+ additionalProperties: false
13733
+ properties:
13734
+ scenario: { type: string }
13735
+ source: { type: string, enum: [ruling, deployment] }
13678
13736
  - type: string
13679
13737
  count:
13680
13738
  type: integer
@@ -14266,16 +14324,24 @@ components:
14266
14324
  enum: [default, acceptEdits, plan, auto, bypassPermissions, dontAsk]
14267
14325
  description: >
14268
14326
  core 7.34.0 `PERMISSION_MODES` (six words). CLOSED on the gate record (the server screens it with core `isPermissionMode`;
14269
- an out-of-set word withholds the whole `gate`). ⚠️ The REQUEST face (`TaskRequest.permissionMode`) takes FIVE on server
14270
- 7.106.0 — `dontAsk` on a fresh submit is 400 `request.field_invalid` (S-902) — yet `dontAsk` can still appear here (e.g. an
14271
- unattended leg run under `UNATTENDED_PERMISSION_MODE=dontAsk`).
14327
+ an out-of-set word withholds the whole `gate`). The REQUEST face (`TaskRequest.permissionMode`) takes the same six words from
14328
+ server 7.119.0 (`capabilities.permissionModes`); on server <= 7.118.x it takes FIVE — `dontAsk` on a fresh submit is 400
14329
+ `request.field_invalid` there — yet `dontAsk` can still appear here on those versions (e.g. an unattended leg run under
14330
+ `UNATTENDED_PERMISSION_MODE=dontAsk`).
14272
14331
  AskClass:
14273
14332
  type: string
14274
- enum: [content_question, hook, ancestor, unverifiable_delete, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, write_protection_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
14333
+ enum: [content_question, hook, ancestor, dangerous_delete, unverifiable_delete, transcript_write, org_rule, governance_unavailable, rule_unreadable, ask_rule, denial_limit_fallback, classifier_no_ruling, requires_user_interaction, operator_forced, shell_floor, write_protection_unresolved, probe, write_protection, tool_marks_mcp, tool_marks_a2a, policy_rra, artifact_shared_read, mode, unstated, ordinary]
14275
14334
  description: >
14276
- 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 — 7.34.0 minted twenty; `shell_floor` joined in 7.39.0 #1260 and rides
14335
+ 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 — 7.34.0 minted twenty; `shell_floor` joined in 7.39.0 #1260 and rides
14277
14336
  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
14278
- server >= 7.116.0; `classifier_no_ruling` joined in 7.43.0 #1353 and rides server >= 7.118.0) — which CLASS an ask falls in (the columns of the mode × class arbitration table).
14337
+ 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
14338
+ server >= 7.119.0) — which CLASS an ask falls in (the columns of the mode × class arbitration table).
14339
+ `dangerous_delete` = a DELETE OF A PROTECTED PLACE: an `rm` / `rmdir` whose target is the filesystem root, a top-level directory, the home
14340
+ directory, the task root / working directory / an added working directory or an ancestor of one (including `rm -rf *` at the task root and
14341
+ `rm -rf ~/*`), or a shell handed a script that is only assembled at run time (`bash -c "$CMD"`, `eval "$(…)"`). ONLY A PERSON'S ANSWER clears
14342
+ it: no mode (`bypassPermissions` included), no allow (request `settings.permissions.allow`, operator `APPROVAL_GRANT`, a stored allow row, a
14343
+ skill's tool list, a hook's allow) and no `auto` classifier does; the frame carries `requiresRealApproval: true`, nobody answering means the
14344
+ call does not run (the unattended leg refuses), and it is bypass-IMMUNE (not one of the ten releasable classes).
14279
14345
  `classifier_no_ruling` = the auto-mode classifier did not rule (it could not run — `error` / `timeout` — or replied with no verdict —
14280
14346
  `parse_error`), so a person decides: the denial-limit fallback's row (bypass-immune, no allow rule clears it, the classifier is not asked
14281
14347
  again); the live frame and the inbox row also carry the cause as `classifierNoRuling`.
@@ -19531,6 +19597,20 @@ components:
19531
19597
  description: >
19532
19598
  Where a persisted rule applies (core `RuleScope`). `project.root` is the directory prefix that decides
19533
19599
  which cwds the rule is live under.
19600
+
19601
+ 🔴 WHICH ROOTS ARE ACCEPTED — the one statement every other mention in this file points to (the cc-import
19602
+ layer `root`, and the `project:<root>` discriminant string on write / revoke / the list filter):
19603
+ server >= 7.120.0 accepts only a COMPLETE ABSOLUTE PATH — POSIX `/…`, a Windows drive root `X:\…` / `X:/…`,
19604
+ or a UNC root `\\host\…` (the engine's durable-scope predicate; every server entry calls it rather than
19605
+ keeping a copy). A relative root (`proj`, `./x`, `~/x`) or a drive without a root (`C:`, `C:x`) is a 400 whose
19606
+ `error` is exactly "scope must be 'global' or 'project:<root>' with an absolute root" — code
19607
+ `request.body_shape` on write / revoke / cc-import prepare, `request.query_invalid` on the list filter.
19608
+ Server <= 7.119.x checks only that the root is non-empty and keeps relative roots as written; such rows take
19609
+ no part in any check on 7.120.0 — SQL backends delete them with a one-time migration at boot, while on the
19610
+ file (`local`) backend they stay in the store, are absent from the listing, cannot be revoked, and each run
19611
+ gets an `engine_notice` `permission.rule_row_dropped` naming them. Rewrite them with the project's full path.
19612
+ An EMPTY root is refused on every version (an empty prefix contains every cwd). The root's truthfulness is
19613
+ never validated — only its spelling.
19534
19614
  oneOf:
19535
19615
  - type: object
19536
19616
  additionalProperties: false
@@ -19542,7 +19622,7 @@ components:
19542
19622
  required: [kind, root]
19543
19623
  properties:
19544
19624
  kind: { type: string, enum: [project] }
19545
- root: { type: string, minLength: 1, description: 'The canonical directory this rule is scoped to. NEVER empty — an empty prefix contains EVERY cwd, which would silently promote a project rule to a global one.' }
19625
+ root: { type: string, minLength: 1, description: 'The canonical directory this rule is scoped to. NEVER empty — an empty prefix contains EVERY cwd, which would silently promote a project rule to a global one. Server >= 7.120.0: a complete absolute path (see the RuleScope description).' }
19546
19626
 
19547
19627
  RuleBehavior:
19548
19628
  type: string
@@ -19618,9 +19698,12 @@ components:
19618
19698
  cloud deployment has no access to the user's filesystem — that is the deployment shape, not a bypass:
19619
19699
  what is read is still only the allow bucket.
19620
19700
 
19621
- 🔴 `root` MUST be non-empty (an empty prefix contains every cwd — see RuleScope). Its TRUTHFULNESS is
19622
- deliberately NOT validated: the server has no such tree to check against, and pretending to validate
19623
- would be a lie. It is the user's own label for "which cwds should this rule be live under".
19701
+ 🔴 `root` follows the RuleScope rule (server >= 7.120.0: a complete absolute path; otherwise 400
19702
+ `request.body_shape` with "scope must be 'global' or 'project:<root>' with an absolute root" and no ticket
19703
+ minted). It applies to ALL layers alike — a `userSettings` layer's root never becomes a scope, but it is the
19704
+ same field under the same check. On <= 7.119.x only an empty root is refused (with the body-shape sentence).
19705
+ Its TRUTHFULNESS is deliberately NOT validated: the server has no such tree to check against, and pretending
19706
+ to validate would be a lie. It is the user's own label for "which cwds should this rule be live under".
19624
19707
  # 封闭:server 侧 zod 是 `.strict()`(把 `content` 拼成 `contents` 的客户端应当场 400,而不是拿到
19625
19708
  # 一份「零候选」的预览去纳闷)。
19626
19709
  additionalProperties: false
@@ -19817,7 +19900,7 @@ components:
19817
19900
  allOf: [{ $ref: '#/components/schemas/RuleBehavior' }]
19818
19901
  description: 'Which of the three this row is. Part of the row''s IDENTITY — pass it back VERBATIM to revoke, or the delete aims at another behavior''s same-text row.'
19819
19902
  rule: { type: string, description: 'Canonical rule text — `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). Pass back VERBATIM to revoke.' }
19820
- scope: { type: string, description: 'Scope discriminant string: `global`, or `project:<root>` with a NON-EMPTY root. Pass back VERBATIM to revoke.' }
19903
+ scope: { type: string, description: 'Scope discriminant string: `global`, or `project:<root>` (which roots: see RuleScope — on server >= 7.120.0 every listed project row has a complete absolute root; an older server may list relative ones). Pass back VERBATIM to revoke.' }
19821
19904
  tool:
19822
19905
  type: string
19823
19906
  # 🔴 只钉非空,**不借**输入面那条 `maxLength: 190`(server 的 `LocalImportRowSchema` 帽的是**收进来的**
@@ -19918,7 +20001,7 @@ components:
19918
20001
  type: string
19919
20002
  minLength: 1
19920
20003
  maxLength: 4104
19921
- description: 'The scope discriminant string — `global` or `project:<root>` with a NON-EMPTY root (an empty root would match every cwd). Unreadable ⇒ 400 `request.body_shape`, never guessed into `global`. Cap = "project:".length + server MAX_CWD_CHARS (8+4096), same source as RuleRevokeRequest.'
20004
+ description: 'The scope discriminant string — `global` or `project:<root>` (which roots are accepted: see RuleScope; an empty root would match every cwd). Unreadable, or a root the server does not accept ⇒ 400 `request.body_shape`, never guessed into `global`. Cap = "project:".length + server MAX_CWD_CHARS (8+4096), same source as RuleRevokeRequest.'
19922
20005
  principal: { type: string, minLength: 1, maxLength: 190, description: 'OPERATOR-ONLY cross-tenant write. Absent (or your own principal) = write your own rule. Gated exactly like the revoke half.' }
19923
20006
 
19924
20007
  RuleWriteResult:
@@ -20082,7 +20165,7 @@ components:
20082
20165
  at an `allow` that got defaulted onto the same-text `deny` deletes a refusal that was meant to stay,
20083
20166
  and hands the caller a 200. core's `sameRuleIdentity` names this exact failure.
20084
20167
  rule: { type: string, minLength: 1, maxLength: 1024, description: 'The canonical rule text, VERBATIM as `RuleListResult.rules[].rule` gave it.' }
20085
- scope: { type: string, minLength: 1, maxLength: 4104, description: 'The scope discriminant string — `global` or `project:<root>` with a NON-EMPTY root (an empty root would match every cwd). VERBATIM as the listing gave it. Cap = "project:".length + server MAX_CWD_CHARS (8+4096) — the 1088 previously written here was the same stale independent copy the server itself fixed (rules.ts MAX_RULE_SCOPE_CHARS 顶注), found by the A-335 anchor sweep.' }
20168
+ scope: { type: string, minLength: 1, maxLength: 4104, description: 'The scope discriminant string — `global` or `project:<root>` (which roots are accepted: see RuleScope; an empty root would match every cwd; a root the server does not accept ⇒ 400 `request.body_shape`). VERBATIM as the listing gave it. Cap = "project:".length + server MAX_CWD_CHARS (8+4096) — the 1088 previously written here was the same stale independent copy the server itself fixed (rules.ts MAX_RULE_SCOPE_CHARS 顶注), found by the A-335 anchor sweep.' }
20086
20169
  principal: { type: string, minLength: 1, maxLength: 190, description: 'OPERATOR-ONLY cross-tenant revoke. Absent (or your own principal) = revoke your own rule.' }
20087
20170
 
20088
20171
  RemovalLiveness:
@@ -20427,7 +20510,7 @@ components:
20427
20510
  description: 'The v1 CLOSED SET. A third value (e.g. `win32`) ⇒ 400 request.field_invalid — never silently accepted.'
20428
20511
  platformArch: { type: string, maxLength: 32, description: 'At most 32 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound, see the unit convention).' }
20429
20512
  displayName: { type: string, maxLength: 255, description: 'Echoed back by `GET /v1/devices` as `name`, and HONESTLY ABSENT there when empty. At most 255 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound).' }
20430
- workspaceRoot: { type: string, minLength: 1, maxLength: 1024, description: 'The device-side root every instruction resolves against. Required and non-empty. At most 1024 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound) ⇒ over it 400 request.field_invalid (a silently truncated value is a DIFFERENT value).' }
20513
+ workspaceRoot: { type: string, minLength: 1, maxLength: 1024, description: 'The device-side root every instruction resolves against (and the `cwd` of `exec` / `execStream` when neither the session nor its delegation parents registered a working directory). Server >= 7.120.0: an ABSOLUTE POSIX path — empty, relative (`work`, `./w`) or `~/…` ⇒ 400 request.field_invalid, `error` starting "workspaceRoot must be an absolute POSIX path", the token NOT consumed (resolve the root on the device first). Server <= 7.119.x: only non-empty is checked, and a device registered with a relative root has its sessions refused when 7.120.0 builds their execution environment — re-enroll with an absolute root (new `deviceId`; revoke the old one). At most 1024 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound) ⇒ over it 400 request.field_invalid (a silently truncated value is a DIFFERENT value).' }
20431
20514
 
20432
20515
  DeviceEnrollResult:
20433
20516
  type: object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "17.0.0",
3
+ "version": "17.2.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",