@sema-agent/sdk 6.13.0 → 6.14.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/README.md +96 -0
- package/dist/events.d.ts +18 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/rules.d.ts +214 -8
- package/dist/resources/rules.d.ts.map +1 -1
- package/dist/resources/rules.js +80 -0
- package/dist/resources/rules.js.map +1 -1
- package/dist/resources/tool-approvals.d.ts +8 -2
- package/dist/resources/tool-approvals.d.ts.map +1 -1
- package/dist/resources/tool-approvals.js.map +1 -1
- package/dist/types.d.ts +59 -2
- package/dist/types.d.ts.map +1 -1
- package/openapi.yaml +401 -3
- package/package.json +1 -1
package/openapi.yaml
CHANGED
|
@@ -4084,6 +4084,140 @@ paths:
|
|
|
4084
4084
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
4085
4085
|
'501': { $ref: '#/components/responses/NotImplemented' } # capability.rule_store_required
|
|
4086
4086
|
|
|
4087
|
+
# ── design/203 §2(server 7.12.0)—— 持久权限规则的**撤销面**两口。与上面的导入两口共用部署谓词
|
|
4088
|
+
# (PERMISSION_RULES_ENABLED + 规则店装配,缺一 ⇒ 501),但多一条 operator 越权域:`principal` 让一个
|
|
4089
|
+
# 列了名的 operator 读或收回**任一租户**的规则。方向决定授权 —— 收回是 TIGHTENING(core removePersistedRule
|
|
4090
|
+
# 头注逐字:a host may narrow on a user's behalf, it may not widen),而回收权限正是撤销面的本义。
|
|
4091
|
+
# 🔴 路径**没有 :id 段**:一条规则的身份就是它的 (rule, scope) 内容对,服务端不铸行 id,所以删是按内容删。
|
|
4092
|
+
/v1/rules:
|
|
4093
|
+
parameters:
|
|
4094
|
+
- $ref: '#/components/parameters/PrincipalHeader'
|
|
4095
|
+
get:
|
|
4096
|
+
tags: [rules]
|
|
4097
|
+
operationId: rulesList
|
|
4098
|
+
x-status: gated # server routes/rules.ts handleRuleList;同 cc-import 的部署谓词。SDK rules.list() 消费。
|
|
4099
|
+
summary: List the rules that are LIVE under a principal (keyset-paged, cursor bound to the revision).
|
|
4100
|
+
description: >
|
|
4101
|
+
List the persisted allow rules currently live under one principal (tombstones already folded).
|
|
4102
|
+
Ordering is deterministic — `(scope, rule)` lexicographic — because keyset paging's whole premise is
|
|
4103
|
+
that the same data comes back in the same order every time. The order is decided by the server, NOT
|
|
4104
|
+
by the store (core's `list()` promises none).
|
|
4105
|
+
|
|
4106
|
+
🔴 THE CURSOR IS BOUND TO `(rev, principal, scope)` AND A MISMATCH IS REFUSED, NOT RESET. A rule set
|
|
4107
|
+
is read WHOLE, so there is no tearing WITHIN a page — tearing happens BETWEEN pages, when somebody
|
|
4108
|
+
adds or removes a rule after page 1 and the keyset start now lands in a different set (silently
|
|
4109
|
+
dropped or duplicated rows). Sending a cursor minted at another revision / principal / scope filter
|
|
4110
|
+
gets a 400 (`request.query_invalid`, body carries the current `rev`); restart the listing without a
|
|
4111
|
+
cursor. Silently starting over would let a client read page 2 as "continuing page 1" and hand a
|
|
4112
|
+
governance surface a list that is both short and duplicated, with nobody the wiser.
|
|
4113
|
+
|
|
4114
|
+
`scope` FILTER: given but unreadable ⇒ 400, never "treated as unfiltered" — that would silently turn
|
|
4115
|
+
"show me only this project" into a full listing the caller would accept as the project's list.
|
|
4116
|
+
`limit` is CLAMPED (not refused) into 1..200, default 50 — a mistyped paging knob must not make a
|
|
4117
|
+
governance page unopenable.
|
|
4118
|
+
|
|
4119
|
+
OPERATOR OVERRIDE: `principal` reads ANOTHER tenant's rules and is `OPERATOR_PRINCIPALS`-gated
|
|
4120
|
+
(an EMPTY list means NOBODY — never inferred from the service token, which is a shared deployment
|
|
4121
|
+
credential, not a person). Omitted, or equal to your own principal, is the ordinary self-read and
|
|
4122
|
+
needs no operator standing.
|
|
4123
|
+
parameters:
|
|
4124
|
+
- name: principal
|
|
4125
|
+
in: query
|
|
4126
|
+
required: false
|
|
4127
|
+
schema: { type: string, minLength: 1, maxLength: 190 }
|
|
4128
|
+
description: >
|
|
4129
|
+
OPERATOR-ONLY cross-tenant read. Absent (or equal to the caller's own principal) = read your own
|
|
4130
|
+
rules. Anything else requires explicit OPERATOR_PRINCIPALS membership ⇒ otherwise 403
|
|
4131
|
+
`auth.operator_only`. Rule text contains command shapes, so third-party reads are an information
|
|
4132
|
+
disclosure and are deliberately not opened to non-operators.
|
|
4133
|
+
- name: scope
|
|
4134
|
+
in: query
|
|
4135
|
+
required: false
|
|
4136
|
+
schema: { type: string, minLength: 1 }
|
|
4137
|
+
description: >
|
|
4138
|
+
Filter to one scope, spelled `global` or `project:<root>` with a NON-EMPTY root. Unreadable ⇒ 400
|
|
4139
|
+
`request.query_invalid` (never silently unfiltered). Matching is done on the CANONICAL form, so an
|
|
4140
|
+
equivalent-but-differently-spelled root filters to an empty page.
|
|
4141
|
+
- name: limit
|
|
4142
|
+
in: query
|
|
4143
|
+
required: false
|
|
4144
|
+
schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
|
|
4145
|
+
description: 'Page size. CLAMPED into 1..200 (non-numeric / out-of-range is clamped, not refused); default 50.'
|
|
4146
|
+
- name: cursor
|
|
4147
|
+
in: query
|
|
4148
|
+
required: false
|
|
4149
|
+
schema: { type: string }
|
|
4150
|
+
description: >
|
|
4151
|
+
The `nextCursor` from the previous page, VERBATIM (base64url, opaque — do not parse or mint one).
|
|
4152
|
+
It is bound to the revision, the principal and the scope filter it was minted on; any mismatch is
|
|
4153
|
+
a 400 `request.query_invalid` carrying the current `rev`. Restart the listing without a cursor.
|
|
4154
|
+
responses:
|
|
4155
|
+
'200':
|
|
4156
|
+
description: One page of live rules plus the store revision the page was read at.
|
|
4157
|
+
content:
|
|
4158
|
+
application/json:
|
|
4159
|
+
schema: { $ref: '#/components/schemas/RuleListResult' }
|
|
4160
|
+
'400': { $ref: '#/components/responses/BadRequest' } # request.query_invalid — unreadable `scope`, unreadable `cursor`, or a cursor minted at a different (rev, principal, scope). Body carries `rev` on the revision-mismatch arm.
|
|
4161
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
4162
|
+
'403': { $ref: '#/components/responses/Forbidden' } # auth.operator_only — reading another principal's rules without explicit OPERATOR_PRINCIPALS membership (an EMPTY list means nobody)
|
|
4163
|
+
'429': { $ref: '#/components/responses/RateLimited' }
|
|
4164
|
+
'501': { $ref: '#/components/responses/NotImplemented' } # capability.rule_store_required — this deployment has no permission-rule store wired
|
|
4165
|
+
delete:
|
|
4166
|
+
tags: [rules]
|
|
4167
|
+
operationId: rulesRevoke
|
|
4168
|
+
x-status: gated # server routes/rules.ts handleRuleRevoke;同 cc-import 的部署谓词。SDK rules.revoke() 消费。
|
|
4169
|
+
summary: Revoke a persisted rule BY CONTENT — `(rule, scope)`, with no id in the path.
|
|
4170
|
+
description: >
|
|
4171
|
+
Revoke one persisted allow rule identified by its CONTENT pair `(rule, scope)`. There is no `:id`
|
|
4172
|
+
path segment on purpose: a rule's identity IS that pair, the server mints no row id, and the engine's
|
|
4173
|
+
`removePersistedRule` primitive (which this endpoint always goes through) works in observed-remove
|
|
4174
|
+
form over the add dots.
|
|
4175
|
+
|
|
4176
|
+
🔴 IDEMPOTENT, AND DELIBERATELY NOT A 404. Revoking twice, or revoking something that was never
|
|
4177
|
+
there, is `200 {status:"no-op"}` — a "did this rule ever exist?" 404 would be an existence oracle
|
|
4178
|
+
(a CROSS-TENANT one under the operator override), and deletion has no business answering that
|
|
4179
|
+
question. Read `status` to learn whether you removed anything.
|
|
4180
|
+
|
|
4181
|
+
🔴 `stillLive` IS LOAD-BEARING ON BOTH ARMS. `true` means the tombstone landed but the rule is
|
|
4182
|
+
STILL live under add-wins — a fresh approval was recorded during the call. Folding that into a plain
|
|
4183
|
+
200 would let a client read an INCOMPLETE revocation as complete, which is precisely what a
|
|
4184
|
+
governance surface must never get wrong. The `no-op` arm carries it for the same reason: the engine
|
|
4185
|
+
answers `no-op` off its initial snapshot without a read-back, so an approval landing in that window
|
|
4186
|
+
leaves the rule alive while the verb says "nothing to remove". Both arms therefore ship the same key
|
|
4187
|
+
set and one consumer branch reads both.
|
|
4188
|
+
|
|
4189
|
+
OPERATOR OVERRIDE: `principal` revokes on ANOTHER tenant's behalf, gated exactly like the read half
|
|
4190
|
+
(explicit `OPERATOR_PRINCIPALS`; empty list = nobody). The direction is what makes the override
|
|
4191
|
+
legitimate — revoking is TIGHTENING — but the identity test does not relax because of it.
|
|
4192
|
+
requestBody:
|
|
4193
|
+
required: true
|
|
4194
|
+
content:
|
|
4195
|
+
application/json:
|
|
4196
|
+
schema: { $ref: '#/components/schemas/RuleRevokeRequest' }
|
|
4197
|
+
responses:
|
|
4198
|
+
'200':
|
|
4199
|
+
description: >
|
|
4200
|
+
The revocation outcome. `removed` = a tombstone was written; `no-op` = nothing matched (idempotent
|
|
4201
|
+
repeat, or it was never there). BOTH arms carry `stillLive` — read it before telling a human the
|
|
4202
|
+
rule is gone.
|
|
4203
|
+
content:
|
|
4204
|
+
application/json:
|
|
4205
|
+
schema: { $ref: '#/components/schemas/RuleRevokeResult' }
|
|
4206
|
+
'400': { $ref: '#/components/responses/BadRequest' } # request.body_shape — body is not {rule, scope, principal?}, or `scope` is not `global`/`project:<non-empty root>`
|
|
4207
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
4208
|
+
'403': { $ref: '#/components/responses/Forbidden' } # auth.operator_only — revoking another principal's rule without explicit OPERATOR_PRINCIPALS membership
|
|
4209
|
+
'429': { $ref: '#/components/responses/RateLimited' }
|
|
4210
|
+
'503':
|
|
4211
|
+
description: >
|
|
4212
|
+
errorCode `state.rule_remove_failed` — the engine could not complete the removal right now. Core's
|
|
4213
|
+
contract for this arm is "a read-back confirmed nothing was written, OR the read-back itself failed
|
|
4214
|
+
and the outcome is undetermined" — both are RETRY-CAN-CHANGE-IT, which is why it is a 503 and not a
|
|
4215
|
+
500. Retry, then reconcile with `GET /v1/rules`.
|
|
4216
|
+
content:
|
|
4217
|
+
application/json:
|
|
4218
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
4219
|
+
'501': { $ref: '#/components/responses/NotImplemented' } # capability.rule_store_required
|
|
4220
|
+
|
|
4087
4221
|
# ── design/183 —— 身份收编面(form b:纯身份重绑)。operator-only 的一次性部署级动作,零模型工作。 ──
|
|
4088
4222
|
/v1/adoption:
|
|
4089
4223
|
parameters:
|
|
@@ -4820,6 +4954,35 @@ components:
|
|
|
4820
4954
|
items: { $ref: '#/components/schemas/TaskAgentDefinition' }
|
|
4821
4955
|
description: Per-task sub-agent definitions (roster additions scoped to this task only).
|
|
4822
4956
|
interactiveTools: { type: boolean, description: Mount interactive (prompting) tools. }
|
|
4957
|
+
oneShot:
|
|
4958
|
+
type: boolean
|
|
4959
|
+
description: >
|
|
4960
|
+
core >=5.23.0 `TaskSpec.oneShot`, server >=7.12.0 — declare that THIS SUBMISSION is one-shot: no
|
|
4961
|
+
later turn exists in which an asynchronous background notification could land. The archetype is a
|
|
4962
|
+
headless `sema -p` whose process exits when the turn ends.
|
|
4963
|
+
|
|
4964
|
+
🔴 IT GRANTS NOTHING AND GUARANTEES NOTHING — core consumes it as GUIDANCE ONLY. What changes is
|
|
4965
|
+
the wording the engine gives the MODEL for `RunWorkflow` / delegation receipts: "end your turn, you
|
|
4966
|
+
will be notified" (correct only when a next turn exists) becomes "block-wait via
|
|
4967
|
+
`TaskOutput({block:true})`". Omitting it on a headless call is a known way to LOSE background
|
|
4968
|
+
results — that is why the key exists — but sending it is **best-effort**, not a completion
|
|
4969
|
+
contract: the model may ignore the instruction or run out of budget, and some legs have no poll
|
|
4970
|
+
path at all. If your caller REQUIRES the results, keep your own wait/reconcile step; do not treat
|
|
4971
|
+
`oneShot: true` as one. There is no tenancy gate because there is no capability to gate.
|
|
4972
|
+
|
|
4973
|
+
🔴 PROBE `capabilities.oneShot` BEFORE SENDING IT: the task body is an OPEN set (the server does
|
|
4974
|
+
not refuse unknown keys), so a worker that does not yet consume this field accepts `oneShot: true`,
|
|
4975
|
+
answers 200, and still tells the model to end its turn and await a notification the process will
|
|
4976
|
+
never receive — no 400, no warning. The capability bit (server >=7.12.0) is what distinguishes the
|
|
4977
|
+
two; ABSENT means the key is being ignored. Note the bit being PRESENT does not retire the
|
|
4978
|
+
caller-side wait step: it proves the key is consumed, not that the results arrive.
|
|
4979
|
+
|
|
4980
|
+
PER-REQUEST on purpose: "does this submission expect to be continued" is a property of the
|
|
4981
|
+
SUBMISSION, not of the connection it arrived on. Sibling of `interactiveTools` (the same `-p`
|
|
4982
|
+
posture) and passed through identically — a boolean rides, ABSENT ⇒ the key is omitted and core's
|
|
4983
|
+
default (interactive) applies. Anything that is neither absent nor a boolean is a fail-loud 400
|
|
4984
|
+
`request.field_invalid` ("oneShot must be a boolean"), NOT a silent coercion. Rides the persisted
|
|
4985
|
+
body onto resume legs.
|
|
4823
4986
|
retainBackgroundProcesses: { type: boolean, description: Keep background processes alive past the turn. }
|
|
4824
4987
|
forwardSubagentEvents:
|
|
4825
4988
|
type: boolean
|
|
@@ -5294,6 +5457,20 @@ components:
|
|
|
5294
5457
|
input: { description: tool-call — redacted args. }
|
|
5295
5458
|
callId: { type: string }
|
|
5296
5459
|
isError: { type: boolean }
|
|
5460
|
+
# 🔴 core >=5.18.1 (#187) —— 与 `Event_tool_end.settledBy` **同一个值**:server 的共享挑键器
|
|
5461
|
+
# `toolResultFieldsOf`(src/trace/project.ts)同时喂 turns 面与 trace SSE 面,而写侧
|
|
5462
|
+
# `toolEndEventData` 喂 durable 账本 —— 三面一个真源,所以这里的词表与那边逐字相同。
|
|
5463
|
+
settledBy:
|
|
5464
|
+
type: string
|
|
5465
|
+
enum: [human, timeout, aborted]
|
|
5466
|
+
description: >
|
|
5467
|
+
tool-result — HOW this call was settled when it went through an approval: `human` = somebody
|
|
5468
|
+
actually answered; `timeout` = the approval window elapsed; `aborted` = abort / unclonable
|
|
5469
|
+
arguments / out-of-contract. Present ONLY on the settled call.
|
|
5470
|
+
ABSENCE CARRIES NO SEMANTICS (an older worker, or a posture arm that deliberately leaves it
|
|
5471
|
+
unset — the two are indistinguishable), so absent is neither "human" nor "no approval
|
|
5472
|
+
happened". The read leg re-validates the closed set independently of the write leg, because a
|
|
5473
|
+
ledger row can come from any engine generation.
|
|
5297
5474
|
tokens:
|
|
5298
5475
|
type: object
|
|
5299
5476
|
properties:
|
|
@@ -5558,9 +5735,60 @@ components:
|
|
|
5558
5735
|
permissionRules:
|
|
5559
5736
|
type: boolean
|
|
5560
5737
|
description: >
|
|
5561
|
-
The permission-rule lane
|
|
5562
|
-
|
|
5563
|
-
|
|
5738
|
+
The permission-rule lane is available: /v1/rules/cc-import/* , the respond persistRule redemption
|
|
5739
|
+
arm, and (from server 7.12.0) the GET/DELETE /v1/rules revocation face. Server predicate =
|
|
5740
|
+
PERMISSION_RULES_ENABLED AND a wired rule store — the SAME predicate behind every one of those
|
|
5741
|
+
endpoints' 501s, so "says yes" means all of the ones this server has actually work.
|
|
5742
|
+
🔴 THE KNOB'S DEFAULT READS IN TWO SEGMENTS: default OFF (opt-in) on server <= 7.11.0, default ON
|
|
5743
|
+
(opt-out) from 7.12.0, where the revocation face — the prerequisite that kept it opt-in — landed.
|
|
5744
|
+
So "the operator said nothing" means OPPOSITE things across that boundary; only an explicit
|
|
5745
|
+
`false` disables it. Read THIS BIT; never infer the lane's state from a knob default or a
|
|
5746
|
+
version number.
|
|
5747
|
+
permissionRulesRevoke:
|
|
5748
|
+
type: boolean
|
|
5749
|
+
description: >
|
|
5750
|
+
server >=7.12.0 — the rule REVOCATION face (GET + DELETE /v1/rules) is ROUTED on this worker.
|
|
5751
|
+
Same deps predicate as `permissionRules`; what carries the extra information is this key's mere
|
|
5752
|
+
PRESENCE. 🔴 That is the whole point of it being a second bit rather than a tightening of the
|
|
5753
|
+
first: on a <=7.11.0 worker `permissionRules` already answers true while these two routes 404, so
|
|
5754
|
+
that bit cannot distinguish "the lane is off" from "this build predates the face", and its
|
|
5755
|
+
published wording ("all four endpoints work") is already untrue there — you can add a bit, you
|
|
5756
|
+
cannot retroactively change one. ABSENT => an older worker: expect 404 `not_found.route` from
|
|
5757
|
+
`rules.list` / `rules.revoke` and hide the governance affordance. PRESENT => the routes exist
|
|
5758
|
+
(whether they then answer depends on `permissionRules`, as always).
|
|
5759
|
+
modeShellGateTranslation:
|
|
5760
|
+
type: boolean
|
|
5761
|
+
description: >
|
|
5762
|
+
design/201 §3 (server >=7.12.0) — this binary CONTAINS the `permissionMode` -> `spec.shellGate`
|
|
5763
|
+
translation table (bypassPermissions => off; auto/default/acceptEdits/plan => classify; no stated
|
|
5764
|
+
mode => the key is not written). HARD-CODED true and landed in the SAME COMMIT as the table, so
|
|
5765
|
+
"says yes <=> the translation is really there" is structural, not deps-derived — no missing
|
|
5766
|
+
dependency can make it false.
|
|
5767
|
+
🔴 THE PROBE MUST BE THIS BIT ON THE SERVER YOU ARE TALKING TO, NEVER YOUR OWN SHELL VERSION. A
|
|
5768
|
+
shell drops its unconditional `MANUAL_MODE_SHELL_GATE` env injection on the strength of this bit;
|
|
5769
|
+
keyed off the shell's own version instead, the three real deployments — new shell against an old
|
|
5770
|
+
server, a replica rolled back to an older binary, a mixed-version fleet — each open a window where
|
|
5771
|
+
the shell has stopped injecting and the server cannot yet translate. Absent/false ⇒ keep injecting.
|
|
5772
|
+
oneShot:
|
|
5773
|
+
type: boolean
|
|
5774
|
+
description: >
|
|
5775
|
+
server >=7.12.0 — this worker actually CONSUMES `TaskRequest.oneShot` (projects it into `TaskSpec`
|
|
5776
|
+
when it is a boolean, omits the key when absent, and 400s a non-boolean). Hard-coded true: the
|
|
5777
|
+
consumption depends on no optional facility. 🔴 PROBE IT BEFORE RELYING ON THE FIELD: the task
|
|
5778
|
+
request body is an OPEN set, so a worker that does not consume the key still accepts it and
|
|
5779
|
+
answers 200 — a headless caller would declare itself one-shot, never receive core's block-wait
|
|
5780
|
+
guidance, and lose its background results with nothing on the wire to reveal it. ABSENT => the key
|
|
5781
|
+
is being ignored; keep your own wait/reconcile step. (That step remains good practice even when
|
|
5782
|
+
the bit is present: the field is guidance to the model, not a completion contract.)
|
|
5783
|
+
outcomeLedger:
|
|
5784
|
+
type: boolean
|
|
5785
|
+
description: >
|
|
5786
|
+
server >=7.12.0 — the `GET /v1/outcomes` mechanical-signal read aggregate (design/73 §7.2) is
|
|
5787
|
+
available. Predicate mirrors the route's refusal arms exactly: a QUERYABLE ledger is wired (a
|
|
5788
|
+
file-only sink writes JSONL and has no query face ⇒ 501), AND on a multi-tenant deployment the
|
|
5789
|
+
operator list is non-empty (single-user has no such gate — the only user IS the operator).
|
|
5790
|
+
Added because this face previously had a 501 but NO capability bit, leaving consumers to
|
|
5791
|
+
trial-by-501 — the exact pattern this capability surface exists to remove.
|
|
5564
5792
|
sharedMemory:
|
|
5565
5793
|
type: boolean
|
|
5566
5794
|
description: >
|
|
@@ -8134,8 +8362,35 @@ components:
|
|
|
8134
8362
|
redeem it. The engine ALWAYS emits the section, so `false` is a REAL reading ("no rule lane on this
|
|
8135
8363
|
worker"), NOT "this engine is too old". The section is optional HERE only because this spec's support
|
|
8136
8364
|
floor is server 3.0.0, which predates it; absent = the worker never sent it.
|
|
8365
|
+
|
|
8366
|
+
🔴 `syncWired` / `orgGoverned` (core >=5.23.0 design/182 §9/§7, server >=7.12.0) sit in THIS
|
|
8367
|
+
section — the engine deliberately did not put them in the operator-only `governance` section, and
|
|
8368
|
+
the server projects all three bits on both faces. THEY WERE THE REASON THIS SECTION HAD TO BE
|
|
8369
|
+
REDECLARED: this arm is `additionalProperties: false`, so a strict consumer validating a real
|
|
8370
|
+
frame against the 6.13.0 spec did not "miss two fields" — the WHOLE SECTION failed validation and
|
|
8371
|
+
the diagnostics surface went unreadable. Same failure family as `errorCode` on `Event_tool_end`.
|
|
8137
8372
|
properties:
|
|
8138
8373
|
storeWired: { type: boolean }
|
|
8374
|
+
syncWired:
|
|
8375
|
+
type: boolean
|
|
8376
|
+
description: >
|
|
8377
|
+
core >=5.23.0 (design/182 §9) — this worker merges persisted rules into the SAME consent trust
|
|
8378
|
+
domain as its cross-device / transport / server-side peers. Read with `storeWired` to answer
|
|
8379
|
+
"how far does this 'don't ask again' actually travel?": the same click means something wider
|
|
8380
|
+
here than on a worker that keeps its rules local.
|
|
8381
|
+
🔴 PRESENT-AND-FALSE IS A REAL READING — "this engine has the concept, this deployment has not
|
|
8382
|
+
wired it" — NOT "too old to know". Absent (the whole key missing) is the only "did not send it"
|
|
8383
|
+
signal. On server 7.12.0 this is honestly `false` everywhere: the seam exists in core, the
|
|
8384
|
+
server has not wired a supplier yet.
|
|
8385
|
+
orgGoverned:
|
|
8386
|
+
type: boolean
|
|
8387
|
+
description: >
|
|
8388
|
+
core >=5.23.0 (design/182 §7) — an ORGANIZATION governance layer is armed over this rule lane.
|
|
8389
|
+
Load-bearing consequence: that layer is FAIL-CLOSED, so when the org snapshot cannot be read,
|
|
8390
|
+
EVERY allow is escalated into a real human approval rather than quietly honored. A shell that
|
|
8391
|
+
renders "don't ask again" without reading this bit is advertising an outcome it cannot promise.
|
|
8392
|
+
🔴 Same three-way reading as `syncWired`: present-and-false = concept known, not armed here;
|
|
8393
|
+
absent = the worker never sent the key. Honestly `false` on server 7.12.0.
|
|
8139
8394
|
governance:
|
|
8140
8395
|
type: object
|
|
8141
8396
|
additionalProperties: false
|
|
@@ -8753,6 +9008,16 @@ components:
|
|
|
8753
9008
|
`rule_not_offered` — the echoed text is not among THIS ask's candidates (a stale card, or someone
|
|
8754
9009
|
trying to mint a rule of their own).
|
|
8755
9010
|
`rule_store_error` — the rule store wobbled. The adjudication is unaffected.
|
|
9011
|
+
`rule_governance_forced` (server >=7.12.0, #204) — the gate on THIS ask came from the deployment's
|
|
9012
|
+
operator governance layer, so the ask never entered the rule lane at all.
|
|
9013
|
+
🔴 SPLIT FROM `rule_lane_unavailable` ON PURPOSE: that one is "one of four conjuncts failed" (no rule
|
|
9014
|
+
store, no candidate, unreadable command bytes, no verified owner — two of which are per-CARD limits),
|
|
9015
|
+
this one is "the lane is perfectly healthy, only THIS ask is operator-governed". Folding them would
|
|
9016
|
+
erase a real distinction on the card.
|
|
9017
|
+
⚠️ NEITHER IS A DEPLOYMENT-LEVEL VERDICT. Every member of this enum describes ONE ask's outcome; the
|
|
9018
|
+
note on `rule_lane_unavailable` above already says never to disable the feature globally on it, and
|
|
9019
|
+
the same holds here. The one correct source for "does this worker serve rules at all" is
|
|
9020
|
+
`capabilities.permissionRules`. Keep offering "don't ask again" on the next ask; just not on this one.
|
|
8756
9021
|
enum:
|
|
8757
9022
|
- no-candidates
|
|
8758
9023
|
- unknown-candidate
|
|
@@ -8762,6 +9027,7 @@ components:
|
|
|
8762
9027
|
- rule_input_edited
|
|
8763
9028
|
- rule_not_offered
|
|
8764
9029
|
- rule_store_error
|
|
9030
|
+
- rule_governance_forced
|
|
8765
9031
|
|
|
8766
9032
|
ApprovalRiskAxes:
|
|
8767
9033
|
type: object
|
|
@@ -10520,6 +10786,138 @@ components:
|
|
|
10520
10786
|
properties:
|
|
10521
10787
|
result: { $ref: '#/components/schemas/CcImportResult' }
|
|
10522
10788
|
|
|
10789
|
+
# ── design/203 §2 撤销面(server 7.12.0)—— 列举行 + 两口的 200 体 ────────────────────────────────
|
|
10790
|
+
# 行形逐字段照 server `src/rules-consent.ts` 的 `PersistedRuleWireRow`(它自己逐字段照 core 的
|
|
10791
|
+
# `PersistedAllowRule`,只把 `scope` 换成判别式串)。
|
|
10792
|
+
|
|
10793
|
+
RuleDot:
|
|
10794
|
+
type: object
|
|
10795
|
+
description: >
|
|
10796
|
+
The immutable causal identity of ONE add — a replica identity plus a monotonic counter (core
|
|
10797
|
+
`RuleDot`). Compared by IDENTITY, never by causal order: single dots minted on different replicas
|
|
10798
|
+
have no order to compare. Treat it as an opaque pair; it is not a timestamp and not a sort key.
|
|
10799
|
+
additionalProperties: false
|
|
10800
|
+
required: [actor, counter]
|
|
10801
|
+
properties:
|
|
10802
|
+
actor: { type: string, description: 'The replica identity that minted this add.' }
|
|
10803
|
+
counter: { type: integer, description: 'That replica''s monotonic counter at mint time. Monotonic PER ACTOR only — never comparable across actors.' }
|
|
10804
|
+
|
|
10805
|
+
RuleAddOrigin:
|
|
10806
|
+
type: string
|
|
10807
|
+
description: >
|
|
10808
|
+
Where ONE add came from (core `RuleAddOrigin`). Stored PER ADD and never folded across dots — a
|
|
10809
|
+
folded provenance could not follow a dot that is later deleted on its own.
|
|
10810
|
+
`user` = minted from an approval card ("don't ask again"); `imported-cc` = redeemed through the
|
|
10811
|
+
CC-settings import lane; `starter` = a deployment-seeded rule.
|
|
10812
|
+
Closed vocabulary on the wire; read it with a default arm anyway (the owner is the engine).
|
|
10813
|
+
enum: [user, imported-cc, starter]
|
|
10814
|
+
|
|
10815
|
+
RuleAdd:
|
|
10816
|
+
type: object
|
|
10817
|
+
description: >
|
|
10818
|
+
ONE add of one logical rule, carrying its own dot and its own provenance (core `RuleAdd`). A rule is
|
|
10819
|
+
LIVE iff at least one of its adds survives the tombstones, which is why this is a SET and not a
|
|
10820
|
+
scalar: the same `(rule, scope)` can be approved several times, from different origins, at different
|
|
10821
|
+
moments. Folding it to a single "addedAt" would make "did I import this or click it?" unanswerable on
|
|
10822
|
+
a governance surface — which is the one place it matters.
|
|
10823
|
+
additionalProperties: false
|
|
10824
|
+
required: [dot, origin, createdAt]
|
|
10825
|
+
properties:
|
|
10826
|
+
dot: { $ref: '#/components/schemas/RuleDot' }
|
|
10827
|
+
origin: { $ref: '#/components/schemas/RuleAddOrigin' }
|
|
10828
|
+
createdAt: { type: string, description: 'When this ADD was recorded (engine-stamped ISO-8601). It dates the add, NOT the rule — a rule with several adds has several of these.' }
|
|
10829
|
+
|
|
10830
|
+
PersistedRule:
|
|
10831
|
+
type: object
|
|
10832
|
+
description: >
|
|
10833
|
+
One LIVE persisted allow rule as `GET /v1/rules` puts it on the wire (server `PersistedRuleWireRow`).
|
|
10834
|
+
Tombstones are already folded — every row here is live at `RuleListResult.rev`.
|
|
10835
|
+
🔴 `scope` is the DISCRIMINANT STRING (`global` / `project:<root>`), NOT the `RuleScope` object the
|
|
10836
|
+
import lane speaks: this is the exact byte sequence the revoke body wants back, so echo it VERBATIM
|
|
10837
|
+
rather than re-serializing a parsed form (an equivalent-but-differently-spelled root matches nothing).
|
|
10838
|
+
The rule lane is ALLOW-only; deny/ask rules are the tightening direction and never appear here.
|
|
10839
|
+
additionalProperties: false
|
|
10840
|
+
required: [rule, scope, tool, match, command, adds]
|
|
10841
|
+
properties:
|
|
10842
|
+
rule: { type: string, description: 'Canonical rule text — `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). Pass back VERBATIM to revoke.' }
|
|
10843
|
+
scope: { type: string, description: 'Scope discriminant string: `global`, or `project:<root>` with a NON-EMPTY root. Pass back VERBATIM to revoke.' }
|
|
10844
|
+
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.' }
|
|
10845
|
+
match:
|
|
10846
|
+
type: string
|
|
10847
|
+
enum: [exact, prefix]
|
|
10848
|
+
description: '`exact` = this one command only; `prefix` = word-boundary prefix. (core reserves a `wildcard` form for v2 that no current version produces.)'
|
|
10849
|
+
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`.' }
|
|
10850
|
+
adds:
|
|
10851
|
+
type: array
|
|
10852
|
+
description: 'Every LIVE add of this rule (see RuleAdd — a real set, deliberately not folded).'
|
|
10853
|
+
items: { $ref: '#/components/schemas/RuleAdd' }
|
|
10854
|
+
|
|
10855
|
+
RuleListResult:
|
|
10856
|
+
type: object
|
|
10857
|
+
description: >
|
|
10858
|
+
The 200 body of `rulesList` — one keyset page plus the revision it was read at.
|
|
10859
|
+
`nextCursor` ABSENT = this was the last page (there is no empty-string sentinel). `rev` is the rule
|
|
10860
|
+
bucket's OCC revision; the cursor is bound to it, so a page fetched with a stale cursor is REFUSED
|
|
10861
|
+
rather than silently restarted.
|
|
10862
|
+
additionalProperties: false
|
|
10863
|
+
required: [rules, rev]
|
|
10864
|
+
properties:
|
|
10865
|
+
rules:
|
|
10866
|
+
type: array
|
|
10867
|
+
description: 'Live rules, ordered `(scope, rule)` lexicographic — a server-decided deterministic order, since keyset paging depends on it.'
|
|
10868
|
+
items: { $ref: '#/components/schemas/PersistedRule' }
|
|
10869
|
+
rev: { type: integer, description: 'The rule bucket''s OCC revision at read time. Bucket-LOCAL: two tenants each holding one rule both read `rev: 1`, so it is not a global clock and not comparable across principals.' }
|
|
10870
|
+
nextCursor: { type: string, description: 'Opaque base64url cursor for the next page. ABSENT ⇒ no further pages. Pass back VERBATIM; do not parse or mint one.' }
|
|
10871
|
+
|
|
10872
|
+
RuleRevokeRequest:
|
|
10873
|
+
type: object
|
|
10874
|
+
description: >
|
|
10875
|
+
The `DELETE /v1/rules` body. A rule is addressed BY CONTENT — there is no id anywhere in this face.
|
|
10876
|
+
Echo `rule` and `scope` VERBATIM from the listing: both are compared as opaque bytes, so an
|
|
10877
|
+
equivalent-but-differently-spelled scope root simply matches nothing (and answers a cheerful `no-op`).
|
|
10878
|
+
# 封闭:server 侧 zod 是 `.strict()` —— 把 `scope` 拼成 `scopes` 的客户端应当场知道,而不是
|
|
10879
|
+
# 拿到一个「删掉了 global 那条」的意外结果。
|
|
10880
|
+
additionalProperties: false
|
|
10881
|
+
required: [rule, scope]
|
|
10882
|
+
properties:
|
|
10883
|
+
rule: { type: string, minLength: 1, maxLength: 1024, description: 'The canonical rule text, VERBATIM as `RuleListResult.rules[].rule` gave it.' }
|
|
10884
|
+
scope: { type: string, minLength: 1, maxLength: 1088, 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.' }
|
|
10885
|
+
principal: { type: string, minLength: 1, maxLength: 190, description: 'OPERATOR-ONLY cross-tenant revoke. Absent (or your own principal) = revoke your own rule.' }
|
|
10886
|
+
|
|
10887
|
+
RuleRevokeResult:
|
|
10888
|
+
type: object
|
|
10889
|
+
description: >
|
|
10890
|
+
The 200 body of `rulesRevoke`. `removed` = a tombstone was written; `no-op` = nothing matched (an
|
|
10891
|
+
idempotent repeat, or it was never there — deliberately NOT a 404, which would be an existence
|
|
10892
|
+
oracle). BOTH arms carry `stillLive`, and the key set is identical on purpose so one consumer branch
|
|
10893
|
+
reads both.
|
|
10894
|
+
additionalProperties: false
|
|
10895
|
+
required: [status, rev, stillLive]
|
|
10896
|
+
properties:
|
|
10897
|
+
status:
|
|
10898
|
+
type: string
|
|
10899
|
+
enum: [removed, "no-op"]
|
|
10900
|
+
description: 'Whether this call wrote a tombstone. `no-op` says nothing about whether the rule EVER existed — do not render it as "not found".'
|
|
10901
|
+
rev: { type: integer, description: 'The rule bucket''s OCC revision after the operation.' }
|
|
10902
|
+
stillLive:
|
|
10903
|
+
type: boolean
|
|
10904
|
+
description: >
|
|
10905
|
+
🔴 `true` = the rule is STILL LIVE despite this call — under add-wins, an approval recorded during
|
|
10906
|
+
the operation keeps it alive. NEVER tell a human "the rule is gone" without reading this. It rides
|
|
10907
|
+
the `no-op` arm too, because the engine answers `no-op` off its initial snapshot with no read-back,
|
|
10908
|
+
so a concurrent approval leaves the rule alive while the verb reports nothing to remove.
|
|
10909
|
+
|
|
10910
|
+
🔴 THE TWO TRUTH VALUES ARE NOT EQUALLY STRONG, AND THE ASYMMETRY IS IN THE ENGINE, NOT HERE.
|
|
10911
|
+
`true` is positive evidence: something was observed alive. `false` is only "no evidence it
|
|
10912
|
+
survived" — on the `removed` arm the engine performs the confirming read-back inside a `catch`
|
|
10913
|
+
that swallows a failure and reports `false` (core `removePersistedRule`), so a degraded store
|
|
10914
|
+
yields a byte-identical `{status:"removed", stillLive:false}` to a genuinely verified removal.
|
|
10915
|
+
The `no-op` arm does NOT share that hole: the server does that read-back itself and lets a failure
|
|
10916
|
+
surface as a 500 rather than invent an answer. So: act on `true`; when it MATTERS that a rule is
|
|
10917
|
+
really gone (an operator revoking a standing allow), confirm with `GET /v1/rules` instead of
|
|
10918
|
+
treating `false` as proof. Registered upstream — an indeterminate outcome should be its own state
|
|
10919
|
+
rather than folding into `false`.
|
|
10920
|
+
|
|
10523
10921
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
10524
10922
|
# design/183 —— 身份收编(form b:纯身份重绑)。形逐字段照 server `src/adoption/wire.ts` 的 zod
|
|
10525
10923
|
# (那份 schema 自陈是「跨仓 wire 契约,下游按字段名逐字钉围栏」)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.14.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",
|