@sema-agent/sdk 8.6.0 → 8.8.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
@@ -4620,10 +4620,18 @@ paths:
4620
4620
  x-status: gated # server routes/rules.ts handleRuleList;同 cc-import 的部署谓词。SDK rules.list() 消费。
4621
4621
  summary: List the rules that are LIVE under a principal (keyset-paged, cursor bound to the revision).
4622
4622
  description: >
4623
- List the persisted allow rules currently live under one principal (tombstones already folded).
4624
- Ordering is deterministic — `(scope, rule)` lexicographic — because keyset paging's whole premise is
4625
- that the same data comes back in the same order every time. The order is decided by the server, NOT
4626
- by the store (core's `list()` promises none).
4623
+ List the persisted rules currently live under one principal (tombstones already folded) — all THREE
4624
+ behaviors from server >= 7.67.0, not the allow bucket alone.
4625
+ Ordering is deterministic — `(scope, rule, behavior)` lexicographic from server >= 7.67.0, `(scope, rule)`
4626
+ before it — because keyset paging's whole premise is that the same data comes back in the same order every
4627
+ time. The order is decided by the server, NOT by the store (core's `list()` promises none).
4628
+
4629
+ 🔴 THE CURSOR CARRIES A FORMAT GENERATION, BUMPED TO v2 IN 7.67.0 (the sort key gained `behavior`, since a
4630
+ same-text deny/allow pair collided under the old two-part key and the strict `>` silently dropped the
4631
+ sibling row at a page boundary). During a rolling upgrade or rollback the two generations REFUSE each
4632
+ other's cursors with a 400 rather than each walking its own key shape — a governance listing going wrong
4633
+ under an all-200 conversation is far more expensive than one re-listing. The cursor stays OPAQUE and the
4634
+ handling is unchanged: echo it back verbatim, and on a 400 drop it and re-list from the top.
4627
4635
 
4628
4636
  🔴 THE CURSOR IS BOUND TO `(rev, principal, scope)` AND A MISMATCH IS REFUSED, NOT RESET. A rule set
4629
4637
  is read WHOLE, so there is no tearing WITHIN a page — tearing happens BETWEEN pages, when somebody
@@ -4688,12 +4696,16 @@ paths:
4688
4696
  tags: [rules]
4689
4697
  operationId: rulesRevoke
4690
4698
  x-status: gated # server routes/rules.ts handleRuleRevoke;同 cc-import 的部署谓词。SDK rules.revoke() 消费。
4691
- summary: Revoke a persisted rule BY CONTENT — `(rule, scope)`, with no id in the path.
4699
+ summary: Revoke a persisted rule BY CONTENT — `(behavior, rule, scope)`, with no id in the path.
4692
4700
  description: >
4693
- Revoke one persisted allow rule identified by its CONTENT pair `(rule, scope)`. There is no `:id`
4694
- path segment on purpose: a rule's identity IS that pair, the server mints no row id, and the engine's
4695
- `removePersistedRule` primitive (which this endpoint always goes through) works in observed-remove
4696
- form over the add dots.
4701
+ Revoke one persisted rule identified by its CONTENT triple `(behavior, rule, scope)` — a pair before
4702
+ server 7.67.0. There is no `:id` path segment on purpose: a rule's identity IS that triple, the server
4703
+ mints no row id, and the engine's `removePersistedRule` primitive (which this endpoint always goes
4704
+ through) works in observed-remove form over the add dots.
4705
+
4706
+ 🔴 `behavior` IS REQUIRED AND HAS NO DEFAULT (server >= 7.67.0; omitting it is a 400
4707
+ `request.body_shape`). Defaulting it would let a revoke aimed at an `allow` delete the same-text `deny` —
4708
+ a refusal the operator meant to keep — while answering 200.
4697
4709
 
4698
4710
  🔴 IDEMPOTENT, AND DELIBERATELY NOT A 404. Revoking twice, or revoking something that was never
4699
4711
  there, is `200 {status:"no-op"}` — a "did this rule ever exist?" 404 would be an existence oracle
@@ -4725,7 +4737,7 @@ paths:
4725
4737
  content:
4726
4738
  application/json:
4727
4739
  schema: { $ref: '#/components/schemas/RuleRevokeResult' }
4728
- '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — body is not {rule, scope, principal?}, or `scope` is not `global`/`project:<non-empty root>`
4740
+ '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>`
4729
4741
  '401': { $ref: '#/components/responses/Unauthorized' }
4730
4742
  '403': { $ref: '#/components/responses/Forbidden' } # auth.operator_only — revoking another principal's rule without explicit OPERATOR_PRINCIPALS membership
4731
4743
  '429': { $ref: '#/components/responses/RateLimited' }
@@ -7169,6 +7181,39 @@ components:
7169
7181
  note:
7170
7182
  type: string
7171
7183
 
7184
+ PostureKnobReading:
7185
+ type: object
7186
+ additionalProperties: false
7187
+ description: >
7188
+ server >= 7.67.0 (S-178) — ONE deployment knob's reading: its value, WHO SET IT, and one operator-facing
7189
+ pointer. Same shape as `ReadFacePosture`, whose value key is called `face` instead of `value`; both exist,
7190
+ neither replaces the other.
7191
+
7192
+ 🔴 WHY NOT A BARE VALUE: a bare boolean or number cannot answer "why is THIS machine on this setting, and
7193
+ how do I pin it back". From 7.67.0 a single-user turnkey worker (REQUIRE_PRINCIPAL unset) derives
7194
+ `DURABLE_APPROVAL` ON and a 24h approval window when neither is set, so a default FLIP has to be visible
7195
+ on an operator surface or nobody sees it.
7196
+
7197
+ `source` is the CLOSED four-word set (exhaustive switch on the server — adding a word is a compile error
7198
+ there), in PRECEDENCE order: `env` = pinned by this machine's env var (deployment sovereignty; beats any
7199
+ published value) · `center` = applied from the config-center (restart-to-apply) · `posture` = derived from
7200
+ the deployment shape (single-user turnkey) · `engine-default` = nothing pinned, the built-in default is in
7201
+ force. Only `env`/`center` count as "an operator asked for this"; a posture-derived `true` does not.
7202
+ `note` is PROSE for a human (per-knob, and on the `posture` arm it is that knob's own fact sentence) —
7203
+ classify on `source`, never by matching `note`.
7204
+
7205
+ 🔴 `value` IS NARROWED AT THE REFERENCE POINT, not here: the reading is one shape over many knobs, and a
7206
+ second copy of the record per value type is how the three keys drift apart. Each `serverGates` row pins
7207
+ its own `value` type beside the `$ref`.
7208
+ required: [value, source, note]
7209
+ properties:
7210
+ value:
7211
+ description: 'The knob''s effective value. Typed by the referencing site (boolean / integer / …).'
7212
+ source:
7213
+ type: string
7214
+ enum: [env, center, posture, engine-default]
7215
+ note: { type: string }
7216
+
7172
7217
  SkillSpec:
7173
7218
  type: object
7174
7219
  description: A per-request skill (passed as an object; core-native TaskSpec.skills shape).
@@ -9568,12 +9613,16 @@ components:
9568
9613
  DeniedBy:
9569
9614
  type: string
9570
9615
  description: >
9571
- WHO REFUSED a call — the LAYER whose verdict is the deny (core 7.6.0 `DENIED_BY_VALUES`, 8 words,
9572
- CLOSED). The other question a deny raises — who ASKED — is answered by `AskOrigin`; the two used to
9573
- share one seven-word list in which `classifier` meant both.
9616
+ WHO REFUSED a call — the LAYER whose verdict is the deny (core `DENIED_BY_VALUES`; 8 words in 7.6.0,
9617
+ NINE from core 7.9.0 / server >= 7.67.0, CLOSED). The other question a deny raises — who ASKED — is
9618
+ answered by `AskOrigin`; the two used to share one seven-word list in which `classifier` meant both.
9574
9619
  `policy` = the deployment `ToolPolicy` denied (directly, or re-checking an approved edit), or the
9575
9620
  approval-edit chain hit its round cap · `hook` = a PreToolUse hook denied, threw, or never answered ·
9576
- `org` = an organization policy rule denied · `classifier` = the auto-mode classifier denied directly ·
9621
+ `org` = an organization policy rule denied · `persisted_rule` = the person's OWN standing `deny` row
9622
+ denied (the deny list of their settings, imported — reachable from core 7.9.0 #625's three-behavior
9623
+ persisted rules). It is the personal-store sibling of `org`: same VETO family (it can overturn an approval
9624
+ a person already gave, and it re-judges an edited command), which is why it is neither `policy` nor
9625
+ `ask_resolution` · `classifier` = the auto-mode classifier denied directly ·
9577
9626
  `plan_mode` = plan mode's read-only block on a write tool · `compliance` = the compliance call-time
9578
9627
  lock · `write_protection` = an approved edit was rewritten onto a write-protected path no approval
9579
9628
  covers · `ask_resolution` = the ask's own settlement IS the refusal (detail on `GateOutcome.settlement`).
@@ -9582,7 +9631,7 @@ components:
9582
9631
  record WHOLE (report + withhold). A word outside this set therefore cannot reach a consumer — it arrives
9583
9632
  as the `gate` key being ABSENT, never as an unfamiliar word. Same rule for `Settlement.kind` and for
9584
9633
  `GateOutcome.origin`.
9585
- enum: [policy, hook, org, classifier, plan_mode, compliance, write_protection, ask_resolution]
9634
+ enum: [policy, hook, org, persisted_rule, classifier, plan_mode, compliance, write_protection, ask_resolution]
9586
9635
 
9587
9636
  Settlement:
9588
9637
  description: >
@@ -9751,7 +9800,7 @@ components:
9751
9800
  # 服务对缺陷记录是整条不上帧 ⇒ 词表外的值在这条 wire 上到不了消费端。审批帧那一面不判成员,
9752
9801
  # 所以 `AskOrigin` 本体保持真开(见该 schema 的长注)。这张 enum 是本 spec 里该词表的**唯一**
9753
9802
  # 执法点;core 加词时改这一处。
9754
- - enum: [content_question, unresolvable, org_unavailable, org_rule, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
9803
+ - enum: [content_question, unresolvable, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
9755
9804
  description: >
9756
9805
  WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (and only here) — see the
9757
9806
  enum note above.
@@ -10397,6 +10446,23 @@ components:
10397
10446
  access: { type: string, enum: [read, create, edit] }
10398
10447
  aliases: { type: array, items: { type: string } }
10399
10448
  skillScopeEligible: { type: boolean, enum: [true] }
10449
+ base:
10450
+ type: string
10451
+ enum: [cwd, root]
10452
+ description: >
10453
+ core >= 7.9.1 (#635) — which base a RELATIVE path resolves against: the live cwd or the task root.
10454
+ Only present when declared; `base:"root"` is legal on read faces only (the engine refuses it elsewhere).
10455
+ absent:
10456
+ type: string
10457
+ enum: [none, base]
10458
+ description: >
10459
+ core >= 7.9.1 (#635) — what the tool targets when the param is ABSENT: nothing (`none`) or the base
10460
+ directory (`base`, read faces only — e.g. `Grep` with no path searches the cwd).
10461
+ patternParam:
10462
+ type: string
10463
+ description: >
10464
+ core >= 7.9.1 (#635) — the input key that carries a glob PATTERN (e.g. `Glob.pattern`); an absolute
10465
+ pattern there is the effective target. Must name a top-level input property.
10400
10466
  McpToolRenderHints:
10401
10467
  type: object
10402
10468
  additionalProperties: false
@@ -11276,7 +11342,7 @@ components:
11276
11342
  # 当天,一个**合法**的审批卡就会被严格消费端拒收。`x-open-enum` 只是意图标记,不解除 enum 的执法 ——
11277
11343
  # 这正是本车刚从 `CheckpointGate` 拆掉的那个假开集,不在这里换个地方重犯。
11278
11344
  # 闭集**执法**只属于**筛过的**那一面(`GateOutcome.origin`:引擎的 `screenGateOutcome` 判成员,
11279
- # 出集即整条不上帧),所以那张 enum 落在**那个引用点**,恰一处。已知十词见下面的描述。
11345
+ # 出集即整条不上帧),所以那张 enum 落在**那个引用点**,恰一处。已知十一词见下面的描述。
11280
11346
  description: >
11281
11347
  WHO raised an ask (`ToolApprovalFrame.origin`; server >= 7.57.0 / core 7.5.0, S-125③/#564) — core's
11282
11348
  `ASK_ORIGINS`, ENGINE-STAMPED at the gate (core's words: "engine-stamped at the gate, never a policy's
@@ -11298,6 +11364,11 @@ components:
11298
11364
  word could not. Both are classifier-eligible (arming auto mode is the deployment's explicit choice to
11299
11365
  let the classifier resolve those classes).
11300
11366
 
11367
+ 🔴 core 7.9.0 ADDS AN ELEVENTH WORD (server >= 7.67.0): `rule_store_unavailable` = the gate's own
11368
+ PERSISTED-RULE lane could not be READ for this call, so its deny/ask rows are unenforceable and the gate
11369
+ fails closed to a person. It is the personal-store sibling of `org_unavailable` (a governance source that
11370
+ cannot be read means ASK, never a silent allow).
11371
+
11301
11372
  The SAME type is the `origin` member of `GateOutcome` on `tool_end` — one vocabulary, two faces.
11302
11373
 
11303
11374
  RuleOffersAbsence:
@@ -13005,8 +13076,18 @@ components:
13005
13076
  ServerWiringGates:
13006
13077
  type: object
13007
13078
  description: >
13008
- The server's OWN assembly predicates (the half core's manifest does not cover).
13009
- required: [streamApproval, durableApproval, checkpointStore]
13079
+ The server's OWN assembly predicates (the half core's manifest does not cover) — server
13080
+ `routes/diagnostics.ts` `buildServerWiringGates`.
13081
+
13082
+ 🔴 BREAKING (server >= 7.67.0 / S-178): the three POSTURE-FAMILY knobs are now `{value, source, note}`
13083
+ readings instead of bare values, and two of those rows are NEW. `streamApproval` is a GATE reading (a
13084
+ different axis: is this leg live) and `checkpointStore` is a presence bit — neither is a knob, so neither
13085
+ changed by one byte. Migration: `serverGates.durableApproval` -> `serverGates.durableApproval.value`.
13086
+
13087
+ ⚠️ THIS SCHEMA DESCRIBES ONE GENERATION (same treatment as `TaskResult.terminal` in SDK 8.4.0): against a
13088
+ worker < 7.67.0 `durableApproval` is still a bare boolean on the wire and the other two keys are absent.
13089
+ The discriminator is the PEER'S VERSION, not the presence of these keys.
13090
+ required: [streamApproval, durableApproval, streamAskWindowMs, sessionAutoTitle, checkpointStore]
13010
13091
  additionalProperties: false
13011
13092
  properties:
13012
13093
  streamApproval:
@@ -13016,7 +13097,39 @@ components:
13016
13097
  same five-way predicate that gates `capabilities.streamApproval` and the decision endpoint's
13017
13098
  501 — so the diagnostics page and the consumer-facing behavior cannot disagree.
13018
13099
  enum: [active, no_tool_approval, protocol_disabled, no_backend, volatile_ask_ledger, no_park_facility]
13019
- durableApproval: { type: boolean, description: 'DURABLE_APPROVAL is on.' }
13100
+ durableApproval:
13101
+ allOf:
13102
+ - { $ref: '#/components/schemas/PostureKnobReading' }
13103
+ - properties: { value: { type: boolean } }
13104
+ description: >
13105
+ `DURABLE_APPROVAL` (the durable-checkpoint gate's master switch) AND who set it. 🔴 On a single-user
13106
+ turnkey worker (REQUIRE_PRINCIPAL unset) the ABSENT default is now ON — conjoined with a durable store
13107
+ actually being present, so a `DB_BACKEND=memory` worker still mints nothing. A multi-tenant worker's
13108
+ absent default is unchanged (off). Pin it back with `DURABLE_APPROVAL=false` on the machine
13109
+ (`source: "env"` always wins).
13110
+ streamAskWindowMs:
13111
+ allOf:
13112
+ - { $ref: '#/components/schemas/PostureKnobReading' }
13113
+ # 🔴 `number`,不是 `integer`:server 的 `parseNumOrFail`(config.ts)只判 `Number.isFinite`,
13114
+ # 于是 `STREAM_ASK_WINDOW_MS=300000.5` 是一个**能启动**的部署,而这个值原样上诊断面 ——
13115
+ # 写 `integer` 会把一台合法 worker 的真实响应判违约。下界 0 是**真有**的拒启门(负窗拒启)。
13116
+ - properties: { value: { type: number, minimum: 0 } }
13117
+ description: >
13118
+ The approval window in milliseconds AND who set it. 🔴 On a single-user turnkey worker the absent
13119
+ default moved from 5 minutes to 24 HOURS (a person stepping away for ten minutes should not come back
13120
+ to a run that settled itself unattended); multi-tenant is unchanged at 300000. Pin it back with
13121
+ `STREAM_ASK_WINDOW_MS=300000`.
13122
+ ⚠️ POSTURE ONLY FILLS AN EMPTY SEAT: where the derived 24h would collide with the existing
13123
+ cross-knob refuse-to-start inequality (`WINDOW + ADHOC_GRACE < ORPHAN_TTL`), posture YIELDS back to the
13124
+ engine default 300000 and reports `source: "engine-default"` rather than refusing to boot a deployment
13125
+ that changed nothing.
13126
+ sessionAutoTitle:
13127
+ allOf:
13128
+ - { $ref: '#/components/schemas/PostureKnobReading' }
13129
+ - properties: { value: { type: boolean } }
13130
+ description: >
13131
+ `SESSION_AUTO_TITLE` AND who set it. Its default is the SAME on every deployment shape (true), so this
13132
+ knob has NO posture arm — an unset one reads `source: "engine-default"` and says so in `note`.
13020
13133
  checkpointStore: { type: boolean, description: 'A checkpoint store is wired (= the park facility exists).' }
13021
13134
 
13022
13135
  WiringDiagnostics:
@@ -13689,12 +13802,32 @@ components:
13689
13802
  kind: { type: string, enum: [project] }
13690
13803
  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.' }
13691
13804
 
13805
+ RuleBehavior:
13806
+ type: string
13807
+ description: >
13808
+ WHICH OF THE THREE a persisted rule is (core `RULE_BEHAVIORS`, closed; listed in PRECEDENCE order, so
13809
+ when rules of more than one behavior speak for one call the earliest member decides).
13810
+
13811
+ 🔴 A RULE'S IDENTITY IS THE TRIPLE (behavior, text, scope) — core `sameRuleIdentity`; server >= 7.67.0 /
13812
+ core 7.9.0 #625. The same text under two behaviors is TWO DIFFERENT ROWS, which is why the revoke body
13813
+ requires this field and deliberately gives it NO default: a delete aimed at an `allow` that got defaulted
13814
+ onto the same-text `deny` removes a refusal the operator meant to keep, and answers 200.
13815
+ `allow` = the standing form of one recorded human approval ("don't ask again" on a card); `deny` / `ask` =
13816
+ the standing forms of "never run this" and "ask me every time", the same content-form rules the engine
13817
+ reads out of a settings file's deny/ask lists. ONE grammar, ONE canonical spelling and ONE matcher family
13818
+ serve all three — the behavior is a FIELD BESIDE the text, never part of it.
13819
+ enum: [deny, ask, allow]
13820
+
13692
13821
  RuleCandidate:
13693
13822
  type: object
13694
- description: 'One candidate rule: the canonical text plus the scope it would land in (core `RuleCandidate`).'
13823
+ description: >
13824
+ One candidate rule: WHICH BEHAVIOR it is, the canonical text, and the scope it would land in (core
13825
+ `RuleCandidate`). `behavior` is new in server >= 7.67.0 / core 7.9.0 #625 — a card mints `allow`
13826
+ candidates only (a card is one person's yes), while the settings import now carries all three lists.
13695
13827
  additionalProperties: false
13696
- required: [rule, scope]
13828
+ required: [behavior, rule, scope]
13697
13829
  properties:
13830
+ behavior: { $ref: '#/components/schemas/RuleBehavior' }
13698
13831
  rule: { type: string, maxLength: 512 }
13699
13832
  scope: { $ref: '#/components/schemas/RuleScope' }
13700
13833
 
@@ -13764,8 +13897,14 @@ components:
13764
13897
  `uncovered` is a TWO-KEY RECORD, not a list: the two layers this version does not read are named in the
13765
13898
  TYPE, so an empty list, a missing member or a duplicate is not expressible. A preview that reported only
13766
13899
  the layers it read would be claiming a completeness it does not have.
13900
+
13901
+ 🔴 server >= 7.67.0 / core 7.9.0 #625 — THE DENY AND ASK LISTS ARE IMPORTED TOO. Until then only the allow
13902
+ bucket was read and deny/ask entries were counted into an `uncovered.denyAskBuckets` seat that said "left
13903
+ in place"; now every candidate carries its own `behavior` and that seat is GONE — an entry that did not
13904
+ become a candidate is in `skipped` with its reason. A consumer rendering `uncovered.denyAskBuckets` reads
13905
+ `candidates[].behavior` instead.
13767
13906
  additionalProperties: false
13768
- required: [candidates, skipped, layers, uncovered]
13907
+ required: [candidates, skipped, translated, layers, uncovered]
13769
13908
  properties:
13770
13909
  candidates:
13771
13910
  type: array
@@ -13775,6 +13914,25 @@ components:
13775
13914
  type: array
13776
13915
  items: { $ref: '#/components/schemas/CcImportSkippedRule' }
13777
13916
  description: 'Entries that did not become candidates. `reason` is PROSE, not a code — see the schema.'
13917
+ translated:
13918
+ type: array
13919
+ description: >
13920
+ ALWAYS EMPTY in this version — a compatibility seat core keeps on the wire shape (core
13921
+ `ImportPreview.translated`, verbatim). It once carried entries whose MATCH FORM was rewritten on the
13922
+ way in (the other product's space-star suggestion form translated to this lane's colon-star). That
13923
+ translation is retired: space-star is now a first-class match form of this lane's own grammar, so an
13924
+ entry's match form imports as the file states it and nothing is ever pushed here. A reader rendering a
13925
+ "rewritten spellings" column from this seat renders an empty column, correctly.
13926
+ ⚠️ Declared here from SDK 8.8.0 on: core has always emitted the key, so a closed schema without it
13927
+ judged every real 200 body a violation (a staleness fix, not a behavior change).
13928
+ items:
13929
+ type: object
13930
+ additionalProperties: false
13931
+ required: [from, to, scope]
13932
+ properties:
13933
+ from: { type: string }
13934
+ to: { type: string }
13935
+ scope: { $ref: '#/components/schemas/RuleScope' }
13778
13936
  layers:
13779
13937
  type: array
13780
13938
  items: { $ref: '#/components/schemas/CcImportLayerReport' }
@@ -13814,9 +13972,12 @@ components:
13814
13972
  indeterminate, release the claim and answer 503 `state.rule_import_retry` (server rules-consent.ts) —
13815
13973
  so this schema honestly describes the two-value `status` only.
13816
13974
  additionalProperties: false
13817
- required: [candidateIndex, rule, scope, status, alreadyRedeemed, dot]
13975
+ required: [candidateIndex, behavior, rule, scope, status, alreadyRedeemed, dot]
13818
13976
  properties:
13819
13977
  candidateIndex: { type: integer, description: 'Index into the prepare preview''s candidate set (candidate space).' }
13978
+ behavior:
13979
+ allOf: [{ $ref: '#/components/schemas/RuleBehavior' }]
13980
+ description: 'server >= 7.67.0 / core 7.9.0 #625 — which of the three the landed row is (same value as the same-index candidate in the preview; one leg of the identity triple).'
13820
13981
  rule: { type: string, description: 'Canonical rule text.' }
13821
13982
  scope: { $ref: '#/components/schemas/RuleScope' }
13822
13983
  status:
@@ -13898,27 +14059,71 @@ components:
13898
14059
  PersistedRule:
13899
14060
  type: object
13900
14061
  description: >
13901
- One LIVE persisted allow rule as `GET /v1/rules` puts it on the wire (server `PersistedRuleWireRow`).
14062
+ One LIVE persisted rule as `GET /v1/rules` puts it on the wire (server `PersistedRuleWireRow`).
13902
14063
  Tombstones are already folded — every row here is live at `RuleListResult.rev`.
13903
14064
  🔴 `scope` is the DISCRIMINANT STRING (`global` / `project:<root>`), NOT the `RuleScope` object the
13904
14065
  import lane speaks: this is the exact byte sequence the revoke body wants back, so echo it VERBATIM
13905
14066
  rather than re-serializing a parsed form (an equivalent-but-differently-spelled root matches nothing).
13906
- The rule lane is ALLOW-only; deny/ask rules are the tightening direction and never appear here.
14067
+
14068
+ 🔴 THREE BEHAVIORS, NOT ALLOW-ONLY (server >= 7.67.0 / core 7.9.0 #625). Until 7.66.0 this schema said
14069
+ "the rule lane is ALLOW-only; deny/ask rules are the tightening direction and never appear here" — that
14070
+ sentence is now false, and `behavior` is the leg of the identity triple a revoke has to echo back.
13907
14071
  additionalProperties: false
13908
- required: [rule, scope, tool, match, command, adds]
14072
+ required: [behavior, rule, scope, tool, match, command, adds, source, status]
13909
14073
  properties:
14074
+ behavior:
14075
+ allOf: [{ $ref: '#/components/schemas/RuleBehavior' }]
14076
+ 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.'
13910
14077
  rule: { type: string, description: 'Canonical rule text — `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). Pass back VERBATIM to revoke.' }
13911
14078
  scope: { type: string, description: 'Scope discriminant string: `global`, or `project:<root>` with a NON-EMPTY root. Pass back VERBATIM to revoke.' }
13912
- tool: { type: string, enum: [Bash], description: 'The one tool the v1 rule lane speaks for. The field exists so a later version can widen without a shape change — read it, do not assume it.' }
14079
+ tool:
14080
+ type: string
14081
+ # 🔴 只钉非空,**不借**输入面那条 `maxLength: 190`(server 的 `LocalImportRowSchema` 帽的是**收进来的**
14082
+ # 行;列举面出去的行不过那道尺)。借一条别的面的约束来判这一面,迟早把一条合法行判成违约。
14083
+ minLength: 1
14084
+ description: >
14085
+ Which tool this row speaks for. 🔴 NOT A CLOSED SET from server >= 7.67.0 / core 7.9.0 #625 (the
14086
+ `enum: [Bash]` written here through SDK 8.7.0 is gone): the tool set is DERIVED from the engine
14087
+ catalogue's path-target declarations — `Bash` is the command grammar, any tool declaring a
14088
+ `pathTarget` (`Read` / `Write` / `Edit` / `Glob` / `Grep` / `NotebookEdit`, …) is the path grammar.
14089
+ The test is "does the engine's rule grammar speak for this name", and that roster lives in the
14090
+ engine's catalogue, not on this wire. READ IT; do not switch over a closed list. A name the grammar
14091
+ does not know is still a 400 on the input faces.
13913
14092
  match:
13914
14093
  type: string
13915
- enum: [exact, prefix]
13916
- description: '`exact` = this one command only; `prefix` = word-boundary prefix. (core reserves a `wildcard` form for v2 that no current version produces.)'
14094
+ enum: [exact, prefix, wildcard, subpath, path]
14095
+ description: >
14096
+ The match form (core `PersistedRuleMatch`, five members). `exact` = this one command only ·
14097
+ `prefix` = word-boundary prefix (the historical colon-star spelling, and the only spelling for a
14098
+ compound prefix) · `wildcard` = the space-star form, the SAME predicate as `prefix` on a single
14099
+ command body · `subpath` = the directory (read-containment) form, which never admits a command ·
14100
+ `path` = NEW in server >= 7.67.0, the path-family pattern grammar for deny/ask/allow rules (`//abs`,
14101
+ `~/`, root-relative, cwd-relative; `*` within a segment, `**` across segments).
14102
+ SDK 8.7.0 and earlier declared only the first two — a consumer rendering this cell off a closed list
14103
+ must add the other three.
13917
14104
  command: { type: string, description: 'What the matcher actually compares against, parsed out of `rule` at mint time and stored so no consumer re-parses. Trust this + `match`, not your own parse of `rule`.' }
13918
14105
  adds:
13919
14106
  type: array
13920
14107
  description: 'Every LIVE add of this rule (see RuleAdd — a real set, deliberately not folded).'
13921
14108
  items: { $ref: '#/components/schemas/RuleAdd' }
14109
+ source:
14110
+ type: string
14111
+ enum: [user, project, session]
14112
+ description: >
14113
+ Which source this row came from (server >= 7.57.0 / core 7.5.0 design/389). DERIVED FROM `scope`
14114
+ (`global`<->`user`, `project`<->`project`, `session`<->`session`) — the row never carries a second
14115
+ source byte that could drift from its scope. With only the durable partition wired today the reachable
14116
+ values are `user | project`.
14117
+ ⚠️ The IMPORT faces do NOT accept this key (nor `status`): POSTing a listing row straight back to
14118
+ local-import is a 400. Declared here from SDK 8.8.0 on — the server has emitted it since 7.57.0, so a
14119
+ closed schema without it judged every real listing row a violation.
14120
+ status:
14121
+ type: string
14122
+ enum: [live, shadowed-by-org]
14123
+ description: >
14124
+ The row's STANDING (server >= 7.57.0 / design/389): `live`, or `shadowed-by-org` when an org deny
14125
+ covers its command pattern. With no org partition wired today it is always `live` — branch on BOTH
14126
+ values now rather than optimising the constant away. Same staleness note as `source`.
13922
14127
 
13923
14128
  RuleListResult:
13924
14129
  type: object
@@ -13946,8 +14151,15 @@ components:
13946
14151
  # 封闭:server 侧 zod 是 `.strict()` —— 把 `scope` 拼成 `scopes` 的客户端应当场知道,而不是
13947
14152
  # 拿到一个「删掉了 global 那条」的意外结果。
13948
14153
  additionalProperties: false
13949
- required: [rule, scope]
14154
+ required: [behavior, rule, scope]
13950
14155
  properties:
14156
+ behavior:
14157
+ allOf: [{ $ref: '#/components/schemas/RuleBehavior' }]
14158
+ description: >
14159
+ 🔴 REQUIRED from server >= 7.67.0 (omit it and the body is a 400 `request.body_shape`) — WHICH
14160
+ BEHAVIOR's row to revoke, VERBATIM as the listing gave it. 🔴 DELIBERATELY NO DEFAULT: a revoke aimed
14161
+ at an `allow` that got defaulted onto the same-text `deny` deletes a refusal that was meant to stay,
14162
+ and hands the caller a 200. core's `sameRuleIdentity` names this exact failure.
13951
14163
  rule: { type: string, minLength: 1, maxLength: 1024, description: 'The canonical rule text, VERBATIM as `RuleListResult.rules[].rule` gave it.' }
13952
14164
  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.' }
13953
14165
  principal: { type: string, minLength: 1, maxLength: 190, description: 'OPERATOR-ONLY cross-tenant revoke. Absent (or your own principal) = revoke your own rule.' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "8.6.0",
3
+ "version": "8.8.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",