@sema-agent/sdk 6.13.0 → 6.15.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/errors.d.ts +5 -8
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- 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 +8 -1
- 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 +90 -10
- package/dist/types.d.ts.map +1 -1
- package/openapi.yaml +447 -15
- 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
|
|
@@ -4958,15 +5121,10 @@ components:
|
|
|
4958
5121
|
Status of the occupying run (`running` | `suspended` | `needs_review`) on a session-active-run
|
|
4959
5122
|
rejection. Splits the exit families: running => steer/cancel; parked => decide (see `pendingGate`) / cancel.
|
|
4960
5123
|
pendingGate:
|
|
4961
|
-
|
|
4962
|
-
required: [kind, decidePath]
|
|
4963
|
-
additionalProperties: false
|
|
5124
|
+
allOf: [{ $ref: '#/components/schemas/PendingGateMaterial' }]
|
|
4964
5125
|
description: >
|
|
4965
5126
|
Present when the occupying run is parked on a pending durable gate: `decidePath` is THE ONE correct
|
|
4966
5127
|
resume entry for its `kind` (gate-kind routed; sessionId-addressed — checkpoint tokens never appear on the wire).
|
|
4967
|
-
properties:
|
|
4968
|
-
kind: { type: string }
|
|
4969
|
-
decidePath: { type: string }
|
|
4970
5128
|
model:
|
|
4971
5129
|
type: string
|
|
4972
5130
|
description: >
|
|
@@ -5294,6 +5452,20 @@ components:
|
|
|
5294
5452
|
input: { description: tool-call — redacted args. }
|
|
5295
5453
|
callId: { type: string }
|
|
5296
5454
|
isError: { type: boolean }
|
|
5455
|
+
# 🔴 core >=5.18.1 (#187) —— 与 `Event_tool_end.settledBy` **同一个值**:server 的共享挑键器
|
|
5456
|
+
# `toolResultFieldsOf`(src/trace/project.ts)同时喂 turns 面与 trace SSE 面,而写侧
|
|
5457
|
+
# `toolEndEventData` 喂 durable 账本 —— 三面一个真源,所以这里的词表与那边逐字相同。
|
|
5458
|
+
settledBy:
|
|
5459
|
+
type: string
|
|
5460
|
+
enum: [human, timeout, aborted]
|
|
5461
|
+
description: >
|
|
5462
|
+
tool-result — HOW this call was settled when it went through an approval: `human` = somebody
|
|
5463
|
+
actually answered; `timeout` = the approval window elapsed; `aborted` = abort / unclonable
|
|
5464
|
+
arguments / out-of-contract. Present ONLY on the settled call.
|
|
5465
|
+
ABSENCE CARRIES NO SEMANTICS (an older worker, or a posture arm that deliberately leaves it
|
|
5466
|
+
unset — the two are indistinguishable), so absent is neither "human" nor "no approval
|
|
5467
|
+
happened". The read leg re-validates the closed set independently of the write leg, because a
|
|
5468
|
+
ledger row can come from any engine generation.
|
|
5297
5469
|
tokens:
|
|
5298
5470
|
type: object
|
|
5299
5471
|
properties:
|
|
@@ -5558,9 +5730,73 @@ components:
|
|
|
5558
5730
|
permissionRules:
|
|
5559
5731
|
type: boolean
|
|
5560
5732
|
description: >
|
|
5561
|
-
The permission-rule lane
|
|
5562
|
-
|
|
5563
|
-
|
|
5733
|
+
The permission-rule lane is available: /v1/rules/cc-import/* , the respond persistRule redemption
|
|
5734
|
+
arm, and (from server 7.12.0) the GET/DELETE /v1/rules revocation face. Server predicate =
|
|
5735
|
+
PERMISSION_RULES_ENABLED AND a wired rule store — the SAME predicate behind every one of those
|
|
5736
|
+
endpoints' 501s, so "says yes" means all of the ones this server has actually work.
|
|
5737
|
+
🔴 THE KNOB'S DEFAULT READS IN TWO SEGMENTS: default OFF (opt-in) on server <= 7.11.0, default ON
|
|
5738
|
+
(opt-out) from 7.12.0, where the revocation face — the prerequisite that kept it opt-in — landed.
|
|
5739
|
+
So "the operator said nothing" means OPPOSITE things across that boundary; only an explicit
|
|
5740
|
+
`false` disables it. Read THIS BIT; never infer the lane's state from a knob default or a
|
|
5741
|
+
version number.
|
|
5742
|
+
permissionRulesRevoke:
|
|
5743
|
+
type: boolean
|
|
5744
|
+
description: >
|
|
5745
|
+
server >=7.12.0 — the rule REVOCATION face (GET + DELETE /v1/rules) is ROUTED on this worker.
|
|
5746
|
+
Same deps predicate as `permissionRules`; what carries the extra information is this key's mere
|
|
5747
|
+
PRESENCE. 🔴 That is the whole point of it being a second bit rather than a tightening of the
|
|
5748
|
+
first: on a <=7.11.0 worker `permissionRules` already answers true while these two routes 404, so
|
|
5749
|
+
that bit cannot distinguish "the lane is off" from "this build predates the face", and its
|
|
5750
|
+
published wording ("all four endpoints work") is already untrue there — you can add a bit, you
|
|
5751
|
+
cannot retroactively change one. ABSENT => an older worker: expect 404 `not_found.route` from
|
|
5752
|
+
`rules.list` / `rules.revoke` and hide the governance affordance. PRESENT => the routes exist
|
|
5753
|
+
(whether they then answer depends on `permissionRules`, as always).
|
|
5754
|
+
modeShellGateTranslation:
|
|
5755
|
+
type: boolean
|
|
5756
|
+
description: >
|
|
5757
|
+
design/201 §3 (server >=7.12.0) — this binary CONTAINS the `permissionMode` -> `spec.shellGate`
|
|
5758
|
+
translation table (bypassPermissions => off; auto/default/acceptEdits/plan => classify; no stated
|
|
5759
|
+
mode => the key is not written). HARD-CODED true and landed in the SAME COMMIT as the table, so
|
|
5760
|
+
"says yes <=> the translation is really there" is structural, not deps-derived — no missing
|
|
5761
|
+
dependency can make it false.
|
|
5762
|
+
🔴 THE PROBE MUST BE THIS BIT ON THE SERVER YOU ARE TALKING TO, NEVER YOUR OWN SHELL VERSION. A
|
|
5763
|
+
shell drops its unconditional `MANUAL_MODE_SHELL_GATE` env injection on the strength of this bit;
|
|
5764
|
+
keyed off the shell's own version instead, the three real deployments — new shell against an old
|
|
5765
|
+
server, a replica rolled back to an older binary, a mixed-version fleet — each open a window where
|
|
5766
|
+
the shell has stopped injecting and the server cannot yet translate. Absent/false ⇒ keep injecting.
|
|
5767
|
+
oneShot:
|
|
5768
|
+
type: boolean
|
|
5769
|
+
description: >
|
|
5770
|
+
server >=7.12.0 — this worker actually CONSUMES `TaskRequest.oneShot` (projects it into `TaskSpec`
|
|
5771
|
+
when it is a boolean, omits the key when absent, and 400s a non-boolean). Hard-coded true: the
|
|
5772
|
+
consumption depends on no optional facility. 🔴 PROBE IT BEFORE RELYING ON THE FIELD: the task
|
|
5773
|
+
request body is an OPEN set, so a worker that does not consume the key still accepts it and
|
|
5774
|
+
answers 200 — a headless caller would declare itself one-shot, never receive core's block-wait
|
|
5775
|
+
guidance, and lose its background results with nothing on the wire to reveal it. ABSENT => the key
|
|
5776
|
+
is being ignored; keep your own wait/reconcile step. (That step remains good practice even when
|
|
5777
|
+
the bit is present: the field is guidance to the model, not a completion contract.)
|
|
5778
|
+
outcomeLedger:
|
|
5779
|
+
type: boolean
|
|
5780
|
+
description: >
|
|
5781
|
+
server >=7.12.0 — the `GET /v1/outcomes` mechanical-signal read aggregate (design/73 §7.2) is
|
|
5782
|
+
available. Predicate mirrors the route's refusal arms exactly: a QUERYABLE ledger is wired (a
|
|
5783
|
+
file-only sink writes JSONL and has no query face ⇒ 501), AND on a multi-tenant deployment the
|
|
5784
|
+
operator list is non-empty (single-user has no such gate — the only user IS the operator).
|
|
5785
|
+
Added because this face previously had a 501 but NO capability bit, leaving consumers to
|
|
5786
|
+
trial-by-501 — the exact pattern this capability surface exists to remove.
|
|
5787
|
+
images:
|
|
5788
|
+
type: boolean
|
|
5789
|
+
description: >
|
|
5790
|
+
server >=7.12 — the sandbox-image-pool P1 face (`GET /v1/images`, `/v1/images/{profile}`,
|
|
5791
|
+
`/v1/images/digests/{digest}`, `POST /v1/images/select`) is available. The predicate is verbatim
|
|
5792
|
+
the domain's mount condition (a wired image index), so true ⟺ the catalog reads work. When it is
|
|
5793
|
+
absent the whole domain is unmounted and those paths answer 501
|
|
5794
|
+
`capability.image_index_required`; BEFORE this bit existed they fell through to the global
|
|
5795
|
+
404 `not_found.route` — the same code as a misspelled path, which left consumers structurally
|
|
5796
|
+
unable to tell "this deployment has no image pool" from "I typed the URL wrong". The `local`
|
|
5797
|
+
store backend deliberately wires no image index (the pool is cloud/fleet-only) ⇒ false there.
|
|
5798
|
+
SCOPE: P1 only — `/v1/images/bakes*` (the P2 bake control plane) is a separate dependency and an
|
|
5799
|
+
operator/runner-internal face, NOT promised by this bit.
|
|
5564
5800
|
sharedMemory:
|
|
5565
5801
|
type: boolean
|
|
5566
5802
|
description: >
|
|
@@ -6838,6 +7074,37 @@ components:
|
|
|
6838
7074
|
description: Principal that granted it; null = ownerless/anonymous grant.
|
|
6839
7075
|
createdAt: { type: number, description: Epoch MS of the FIRST grant (re-grant refreshes grantedBy only). }
|
|
6840
7076
|
|
|
7077
|
+
PendingGateMaterial:
|
|
7078
|
+
type: object
|
|
7079
|
+
required: [kind, decidePath]
|
|
7080
|
+
# 🔴 **刻意开放**(#220):`governanceForced` 这次就是从一个 `additionalProperties: false` 的形上
|
|
7081
|
+
# additive 长出来的 —— 封闭的形让「server 加一个只在为真时在场的键」变成对严格校验消费端的破坏性
|
|
7082
|
+
# 变更。出路格(kind/decidePath)靠 `required` 守住,新出身格靠登记守住。
|
|
7083
|
+
additionalProperties: true
|
|
7084
|
+
description: >
|
|
7085
|
+
Pending-gate material on a `conflict.session_active_run` rejection (shared by the 409 body and the SSE
|
|
7086
|
+
`done` frame's reject shape). `kind`/`decidePath` = the EXIT (where to decide: THE ONE correct resume
|
|
7087
|
+
entry for that gate kind — sessionId-addressed; checkpoint tokens never appear on the wire).
|
|
7088
|
+
`governanceForced` = the ORIGIN.
|
|
7089
|
+
properties:
|
|
7090
|
+
kind: { type: string }
|
|
7091
|
+
decidePath: { type: string }
|
|
7092
|
+
governanceForced:
|
|
7093
|
+
const: true
|
|
7094
|
+
description: >
|
|
7095
|
+
server >=7.12 (#220) — ADDITIVE, present ONLY when true (the server never writes `false`). The
|
|
7096
|
+
gate was mandated by the deployment's runtime GOVERNANCE layer (`AUTONOMY` / `MANUAL_MODE_SHELL_GATE`).
|
|
7097
|
+
Derived as a CONJUNCTION: the persisted row's `gate.riskDescriptor.shellGateDoctrine === "always"`
|
|
7098
|
+
AND that deployment's governance layer itself mandating `always` (the doctrine alone is not
|
|
7099
|
+
sufficient — a supervisor-routing posture produces it too). Same family + same discipline as the
|
|
7100
|
+
live card frame's `governanceForced`.
|
|
7101
|
+
🔴 ABSENCE IS NOT A DENIAL: it means "no evidence of a governance origin" and also covers the
|
|
7102
|
+
shapes where the row cannot tell (e.g. the `classify` doctrine, where an ask may equally come
|
|
7103
|
+
from another gate). Render a "governance-mandated" badge on PRESENCE only.
|
|
7104
|
+
🔴 TRIAGE HINT, NOT A VERDICT: the deployment half of the conjunction is read at RESPONSE time
|
|
7105
|
+
while the row was minted earlier, so a hot governance change can make an old row's attribution
|
|
7106
|
+
drift in either direction. It feeds no gate/decision path — display only.
|
|
7107
|
+
|
|
6841
7108
|
PendingCheckpoint:
|
|
6842
7109
|
type: object
|
|
6843
7110
|
description: >
|
|
@@ -8134,8 +8401,35 @@ components:
|
|
|
8134
8401
|
redeem it. The engine ALWAYS emits the section, so `false` is a REAL reading ("no rule lane on this
|
|
8135
8402
|
worker"), NOT "this engine is too old". The section is optional HERE only because this spec's support
|
|
8136
8403
|
floor is server 3.0.0, which predates it; absent = the worker never sent it.
|
|
8404
|
+
|
|
8405
|
+
🔴 `syncWired` / `orgGoverned` (core >=5.23.0 design/182 §9/§7, server >=7.12.0) sit in THIS
|
|
8406
|
+
section — the engine deliberately did not put them in the operator-only `governance` section, and
|
|
8407
|
+
the server projects all three bits on both faces. THEY WERE THE REASON THIS SECTION HAD TO BE
|
|
8408
|
+
REDECLARED: this arm is `additionalProperties: false`, so a strict consumer validating a real
|
|
8409
|
+
frame against the 6.13.0 spec did not "miss two fields" — the WHOLE SECTION failed validation and
|
|
8410
|
+
the diagnostics surface went unreadable. Same failure family as `errorCode` on `Event_tool_end`.
|
|
8137
8411
|
properties:
|
|
8138
8412
|
storeWired: { type: boolean }
|
|
8413
|
+
syncWired:
|
|
8414
|
+
type: boolean
|
|
8415
|
+
description: >
|
|
8416
|
+
core >=5.23.0 (design/182 §9) — this worker merges persisted rules into the SAME consent trust
|
|
8417
|
+
domain as its cross-device / transport / server-side peers. Read with `storeWired` to answer
|
|
8418
|
+
"how far does this 'don't ask again' actually travel?": the same click means something wider
|
|
8419
|
+
here than on a worker that keeps its rules local.
|
|
8420
|
+
🔴 PRESENT-AND-FALSE IS A REAL READING — "this engine has the concept, this deployment has not
|
|
8421
|
+
wired it" — NOT "too old to know". Absent (the whole key missing) is the only "did not send it"
|
|
8422
|
+
signal. On server 7.12.0 this is honestly `false` everywhere: the seam exists in core, the
|
|
8423
|
+
server has not wired a supplier yet.
|
|
8424
|
+
orgGoverned:
|
|
8425
|
+
type: boolean
|
|
8426
|
+
description: >
|
|
8427
|
+
core >=5.23.0 (design/182 §7) — an ORGANIZATION governance layer is armed over this rule lane.
|
|
8428
|
+
Load-bearing consequence: that layer is FAIL-CLOSED, so when the org snapshot cannot be read,
|
|
8429
|
+
EVERY allow is escalated into a real human approval rather than quietly honored. A shell that
|
|
8430
|
+
renders "don't ask again" without reading this bit is advertising an outcome it cannot promise.
|
|
8431
|
+
🔴 Same three-way reading as `syncWired`: present-and-false = concept known, not armed here;
|
|
8432
|
+
absent = the worker never sent the key. Honestly `false` on server 7.12.0.
|
|
8139
8433
|
governance:
|
|
8140
8434
|
type: object
|
|
8141
8435
|
additionalProperties: false
|
|
@@ -8753,6 +9047,16 @@ components:
|
|
|
8753
9047
|
`rule_not_offered` — the echoed text is not among THIS ask's candidates (a stale card, or someone
|
|
8754
9048
|
trying to mint a rule of their own).
|
|
8755
9049
|
`rule_store_error` — the rule store wobbled. The adjudication is unaffected.
|
|
9050
|
+
`rule_governance_forced` (server >=7.12.0, #204) — the gate on THIS ask came from the deployment's
|
|
9051
|
+
operator governance layer, so the ask never entered the rule lane at all.
|
|
9052
|
+
🔴 SPLIT FROM `rule_lane_unavailable` ON PURPOSE: that one is "one of four conjuncts failed" (no rule
|
|
9053
|
+
store, no candidate, unreadable command bytes, no verified owner — two of which are per-CARD limits),
|
|
9054
|
+
this one is "the lane is perfectly healthy, only THIS ask is operator-governed". Folding them would
|
|
9055
|
+
erase a real distinction on the card.
|
|
9056
|
+
⚠️ NEITHER IS A DEPLOYMENT-LEVEL VERDICT. Every member of this enum describes ONE ask's outcome; the
|
|
9057
|
+
note on `rule_lane_unavailable` above already says never to disable the feature globally on it, and
|
|
9058
|
+
the same holds here. The one correct source for "does this worker serve rules at all" is
|
|
9059
|
+
`capabilities.permissionRules`. Keep offering "don't ask again" on the next ask; just not on this one.
|
|
8756
9060
|
enum:
|
|
8757
9061
|
- no-candidates
|
|
8758
9062
|
- unknown-candidate
|
|
@@ -8762,6 +9066,7 @@ components:
|
|
|
8762
9066
|
- rule_input_edited
|
|
8763
9067
|
- rule_not_offered
|
|
8764
9068
|
- rule_store_error
|
|
9069
|
+
- rule_governance_forced
|
|
8765
9070
|
|
|
8766
9071
|
ApprovalRiskAxes:
|
|
8767
9072
|
type: object
|
|
@@ -9134,15 +9439,10 @@ components:
|
|
|
9134
9439
|
type: string
|
|
9135
9440
|
description: '`running` | `suspended` | `needs_review` — best-effort (absent when the store lookup degraded).'
|
|
9136
9441
|
pendingGate:
|
|
9137
|
-
|
|
9138
|
-
required: [kind, decidePath]
|
|
9139
|
-
additionalProperties: false
|
|
9442
|
+
allOf: [{ $ref: '#/components/schemas/PendingGateMaterial' }]
|
|
9140
9443
|
description: >
|
|
9141
9444
|
Present when the occupying run is parked on a pending durable gate: `decidePath` is THE ONE correct
|
|
9142
9445
|
resume entry for its `kind` (gate-kind routed; sessionId-addressed).
|
|
9143
|
-
properties:
|
|
9144
|
-
kind: { type: string }
|
|
9145
|
-
decidePath: { type: string }
|
|
9146
9446
|
Event_failed:
|
|
9147
9447
|
type: object
|
|
9148
9448
|
# 封闭 + 回填 `activeTaskId`(census 轴一,2026-07-30):[1833] G10 —— session 已有活跃 run 的冲突
|
|
@@ -10520,6 +10820,138 @@ components:
|
|
|
10520
10820
|
properties:
|
|
10521
10821
|
result: { $ref: '#/components/schemas/CcImportResult' }
|
|
10522
10822
|
|
|
10823
|
+
# ── design/203 §2 撤销面(server 7.12.0)—— 列举行 + 两口的 200 体 ────────────────────────────────
|
|
10824
|
+
# 行形逐字段照 server `src/rules-consent.ts` 的 `PersistedRuleWireRow`(它自己逐字段照 core 的
|
|
10825
|
+
# `PersistedAllowRule`,只把 `scope` 换成判别式串)。
|
|
10826
|
+
|
|
10827
|
+
RuleDot:
|
|
10828
|
+
type: object
|
|
10829
|
+
description: >
|
|
10830
|
+
The immutable causal identity of ONE add — a replica identity plus a monotonic counter (core
|
|
10831
|
+
`RuleDot`). Compared by IDENTITY, never by causal order: single dots minted on different replicas
|
|
10832
|
+
have no order to compare. Treat it as an opaque pair; it is not a timestamp and not a sort key.
|
|
10833
|
+
additionalProperties: false
|
|
10834
|
+
required: [actor, counter]
|
|
10835
|
+
properties:
|
|
10836
|
+
actor: { type: string, description: 'The replica identity that minted this add.' }
|
|
10837
|
+
counter: { type: integer, description: 'That replica''s monotonic counter at mint time. Monotonic PER ACTOR only — never comparable across actors.' }
|
|
10838
|
+
|
|
10839
|
+
RuleAddOrigin:
|
|
10840
|
+
type: string
|
|
10841
|
+
description: >
|
|
10842
|
+
Where ONE add came from (core `RuleAddOrigin`). Stored PER ADD and never folded across dots — a
|
|
10843
|
+
folded provenance could not follow a dot that is later deleted on its own.
|
|
10844
|
+
`user` = minted from an approval card ("don't ask again"); `imported-cc` = redeemed through the
|
|
10845
|
+
CC-settings import lane; `starter` = a deployment-seeded rule.
|
|
10846
|
+
Closed vocabulary on the wire; read it with a default arm anyway (the owner is the engine).
|
|
10847
|
+
enum: [user, imported-cc, starter]
|
|
10848
|
+
|
|
10849
|
+
RuleAdd:
|
|
10850
|
+
type: object
|
|
10851
|
+
description: >
|
|
10852
|
+
ONE add of one logical rule, carrying its own dot and its own provenance (core `RuleAdd`). A rule is
|
|
10853
|
+
LIVE iff at least one of its adds survives the tombstones, which is why this is a SET and not a
|
|
10854
|
+
scalar: the same `(rule, scope)` can be approved several times, from different origins, at different
|
|
10855
|
+
moments. Folding it to a single "addedAt" would make "did I import this or click it?" unanswerable on
|
|
10856
|
+
a governance surface — which is the one place it matters.
|
|
10857
|
+
additionalProperties: false
|
|
10858
|
+
required: [dot, origin, createdAt]
|
|
10859
|
+
properties:
|
|
10860
|
+
dot: { $ref: '#/components/schemas/RuleDot' }
|
|
10861
|
+
origin: { $ref: '#/components/schemas/RuleAddOrigin' }
|
|
10862
|
+
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.' }
|
|
10863
|
+
|
|
10864
|
+
PersistedRule:
|
|
10865
|
+
type: object
|
|
10866
|
+
description: >
|
|
10867
|
+
One LIVE persisted allow rule as `GET /v1/rules` puts it on the wire (server `PersistedRuleWireRow`).
|
|
10868
|
+
Tombstones are already folded — every row here is live at `RuleListResult.rev`.
|
|
10869
|
+
🔴 `scope` is the DISCRIMINANT STRING (`global` / `project:<root>`), NOT the `RuleScope` object the
|
|
10870
|
+
import lane speaks: this is the exact byte sequence the revoke body wants back, so echo it VERBATIM
|
|
10871
|
+
rather than re-serializing a parsed form (an equivalent-but-differently-spelled root matches nothing).
|
|
10872
|
+
The rule lane is ALLOW-only; deny/ask rules are the tightening direction and never appear here.
|
|
10873
|
+
additionalProperties: false
|
|
10874
|
+
required: [rule, scope, tool, match, command, adds]
|
|
10875
|
+
properties:
|
|
10876
|
+
rule: { type: string, description: 'Canonical rule text — `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). Pass back VERBATIM to revoke.' }
|
|
10877
|
+
scope: { type: string, description: 'Scope discriminant string: `global`, or `project:<root>` with a NON-EMPTY root. Pass back VERBATIM to revoke.' }
|
|
10878
|
+
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.' }
|
|
10879
|
+
match:
|
|
10880
|
+
type: string
|
|
10881
|
+
enum: [exact, prefix]
|
|
10882
|
+
description: '`exact` = this one command only; `prefix` = word-boundary prefix. (core reserves a `wildcard` form for v2 that no current version produces.)'
|
|
10883
|
+
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`.' }
|
|
10884
|
+
adds:
|
|
10885
|
+
type: array
|
|
10886
|
+
description: 'Every LIVE add of this rule (see RuleAdd — a real set, deliberately not folded).'
|
|
10887
|
+
items: { $ref: '#/components/schemas/RuleAdd' }
|
|
10888
|
+
|
|
10889
|
+
RuleListResult:
|
|
10890
|
+
type: object
|
|
10891
|
+
description: >
|
|
10892
|
+
The 200 body of `rulesList` — one keyset page plus the revision it was read at.
|
|
10893
|
+
`nextCursor` ABSENT = this was the last page (there is no empty-string sentinel). `rev` is the rule
|
|
10894
|
+
bucket's OCC revision; the cursor is bound to it, so a page fetched with a stale cursor is REFUSED
|
|
10895
|
+
rather than silently restarted.
|
|
10896
|
+
additionalProperties: false
|
|
10897
|
+
required: [rules, rev]
|
|
10898
|
+
properties:
|
|
10899
|
+
rules:
|
|
10900
|
+
type: array
|
|
10901
|
+
description: 'Live rules, ordered `(scope, rule)` lexicographic — a server-decided deterministic order, since keyset paging depends on it.'
|
|
10902
|
+
items: { $ref: '#/components/schemas/PersistedRule' }
|
|
10903
|
+
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.' }
|
|
10904
|
+
nextCursor: { type: string, description: 'Opaque base64url cursor for the next page. ABSENT ⇒ no further pages. Pass back VERBATIM; do not parse or mint one.' }
|
|
10905
|
+
|
|
10906
|
+
RuleRevokeRequest:
|
|
10907
|
+
type: object
|
|
10908
|
+
description: >
|
|
10909
|
+
The `DELETE /v1/rules` body. A rule is addressed BY CONTENT — there is no id anywhere in this face.
|
|
10910
|
+
Echo `rule` and `scope` VERBATIM from the listing: both are compared as opaque bytes, so an
|
|
10911
|
+
equivalent-but-differently-spelled scope root simply matches nothing (and answers a cheerful `no-op`).
|
|
10912
|
+
# 封闭:server 侧 zod 是 `.strict()` —— 把 `scope` 拼成 `scopes` 的客户端应当场知道,而不是
|
|
10913
|
+
# 拿到一个「删掉了 global 那条」的意外结果。
|
|
10914
|
+
additionalProperties: false
|
|
10915
|
+
required: [rule, scope]
|
|
10916
|
+
properties:
|
|
10917
|
+
rule: { type: string, minLength: 1, maxLength: 1024, description: 'The canonical rule text, VERBATIM as `RuleListResult.rules[].rule` gave it.' }
|
|
10918
|
+
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.' }
|
|
10919
|
+
principal: { type: string, minLength: 1, maxLength: 190, description: 'OPERATOR-ONLY cross-tenant revoke. Absent (or your own principal) = revoke your own rule.' }
|
|
10920
|
+
|
|
10921
|
+
RuleRevokeResult:
|
|
10922
|
+
type: object
|
|
10923
|
+
description: >
|
|
10924
|
+
The 200 body of `rulesRevoke`. `removed` = a tombstone was written; `no-op` = nothing matched (an
|
|
10925
|
+
idempotent repeat, or it was never there — deliberately NOT a 404, which would be an existence
|
|
10926
|
+
oracle). BOTH arms carry `stillLive`, and the key set is identical on purpose so one consumer branch
|
|
10927
|
+
reads both.
|
|
10928
|
+
additionalProperties: false
|
|
10929
|
+
required: [status, rev, stillLive]
|
|
10930
|
+
properties:
|
|
10931
|
+
status:
|
|
10932
|
+
type: string
|
|
10933
|
+
enum: [removed, "no-op"]
|
|
10934
|
+
description: 'Whether this call wrote a tombstone. `no-op` says nothing about whether the rule EVER existed — do not render it as "not found".'
|
|
10935
|
+
rev: { type: integer, description: 'The rule bucket''s OCC revision after the operation.' }
|
|
10936
|
+
stillLive:
|
|
10937
|
+
type: boolean
|
|
10938
|
+
description: >
|
|
10939
|
+
🔴 `true` = the rule is STILL LIVE despite this call — under add-wins, an approval recorded during
|
|
10940
|
+
the operation keeps it alive. NEVER tell a human "the rule is gone" without reading this. It rides
|
|
10941
|
+
the `no-op` arm too, because the engine answers `no-op` off its initial snapshot with no read-back,
|
|
10942
|
+
so a concurrent approval leaves the rule alive while the verb reports nothing to remove.
|
|
10943
|
+
|
|
10944
|
+
🔴 THE TWO TRUTH VALUES ARE NOT EQUALLY STRONG, AND THE ASYMMETRY IS IN THE ENGINE, NOT HERE.
|
|
10945
|
+
`true` is positive evidence: something was observed alive. `false` is only "no evidence it
|
|
10946
|
+
survived" — on the `removed` arm the engine performs the confirming read-back inside a `catch`
|
|
10947
|
+
that swallows a failure and reports `false` (core `removePersistedRule`), so a degraded store
|
|
10948
|
+
yields a byte-identical `{status:"removed", stillLive:false}` to a genuinely verified removal.
|
|
10949
|
+
The `no-op` arm does NOT share that hole: the server does that read-back itself and lets a failure
|
|
10950
|
+
surface as a 500 rather than invent an answer. So: act on `true`; when it MATTERS that a rule is
|
|
10951
|
+
really gone (an operator revoking a standing allow), confirm with `GET /v1/rules` instead of
|
|
10952
|
+
treating `false` as proof. Registered upstream — an indeterminate outcome should be its own state
|
|
10953
|
+
rather than folding into `false`.
|
|
10954
|
+
|
|
10523
10955
|
# ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
10524
10956
|
# design/183 —— 身份收编(form b:纯身份重绑)。形逐字段照 server `src/adoption/wire.ts` 的 zod
|
|
10525
10957
|
# (那份 schema 自陈是「跨仓 wire 契约,下游按字段名逐字钉围栏」)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.15.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",
|