@sema-agent/sdk 6.6.1 → 6.7.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 +68 -0
- package/dist/errors.d.ts +105 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +191 -5
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +94 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/resources/tool-approvals.d.ts +262 -1
- package/dist/resources/tool-approvals.d.ts.map +1 -1
- package/dist/resources/tool-approvals.js +139 -1
- package/dist/resources/tool-approvals.js.map +1 -1
- package/dist/settings.d.ts +30 -7
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +1 -1
- package/dist/types.d.ts +52 -9
- package/dist/types.d.ts.map +1 -1
- package/openapi.yaml +573 -9
- package/package.json +2 -2
package/openapi.yaml
CHANGED
|
@@ -515,6 +515,15 @@ paths:
|
|
|
515
515
|
errorCode "steering.not_running" — the run is terminal, is running on ANOTHER replica
|
|
516
516
|
(cross-replica live-steer is not wired), or the durable park lost its CAS (the checkpoint
|
|
517
517
|
resolved/expired in between). NOT the ordinary suspended case — that one parks and 202s.
|
|
518
|
+
errorCode "steering.queue_full" (server >=7.3.0 / core 5.14.0) — the run IS parked, but the
|
|
519
|
+
steering queue on its checkpoint is full. The queue is bounded and FAIL-LOUD: the engine never
|
|
520
|
+
evicts an already-accepted instruction to make room. Opposite handling from not_running: the
|
|
521
|
+
text is still valid — let the run resume (which drains the queue) or reduce concurrent steers,
|
|
522
|
+
then resend.
|
|
523
|
+
errorCode "steering.duplicate_input_id" (server >=7.3.0 / core 5.14.0) — the Idempotency-Key you
|
|
524
|
+
sent is already parked with DIFFERENT steering content (same key + same content is an idempotent
|
|
525
|
+
no-op and 202s). The engine refuses rather than silently picking a side. Reissue with a fresh
|
|
526
|
+
key — this is a new instruction, not a retry.
|
|
518
527
|
content:
|
|
519
528
|
application/json:
|
|
520
529
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -2800,6 +2809,97 @@ paths:
|
|
|
2800
2809
|
'404': { description: Unknown/settled/expired/foreign approval (no existence oracle). }
|
|
2801
2810
|
'501': { description: Live tool-approval HITL not enabled (TOOL_APPROVAL_ENABLED). }
|
|
2802
2811
|
|
|
2812
|
+
/v1/tasks/{taskId}/asks/{askId}/decision:
|
|
2813
|
+
parameters:
|
|
2814
|
+
- $ref: '#/components/parameters/PrincipalHeader'
|
|
2815
|
+
- $ref: '#/components/parameters/TaskIdPath'
|
|
2816
|
+
- { name: askId, in: path, required: true, schema: { type: string }, description: 'From ApprovalRequestFrame.askId (the durable identity, NOT approvalId).' }
|
|
2817
|
+
post:
|
|
2818
|
+
tags: [questions]
|
|
2819
|
+
operationId: askDecision
|
|
2820
|
+
x-status: planned # design/172 §3.2 (#151 车4) — UNRELEASED; ships with server 7.3.0, gated OFF by STREAM_APPROVAL_ENABLED. SDK toolApprovals.decideAsk() 消费。
|
|
2821
|
+
summary: Settle an IN-STREAM approval card (design/172 streaming approval protocol).
|
|
2822
|
+
description: >
|
|
2823
|
+
The in-stream sibling of POST /v1/tool-approvals/{id}/respond: the approver is live on the SSE stream, so
|
|
2824
|
+
the ask does a stream round-trip and the run never parks. Keyed on `askId` (the DURABLE identity derived
|
|
2825
|
+
from a five-tuple), so the decision is accepted by ANY replica (the CAS lands in the persistent store).
|
|
2826
|
+
|
|
2827
|
+
Decision is a TWO-word set (`approve`/`deny`) — `allow_session` is a live-bridge-only semantic and is NOT
|
|
2828
|
+
on this endpoint. `actor` accepts ONLY `label`: `id`/`verified` are always server-minted and an actor
|
|
2829
|
+
NEVER participates in the permission decision (design/171 §5.1).
|
|
2830
|
+
|
|
2831
|
+
Response doctrine (design/172 §3.2, four rules): ① same `idempotencyKey` retried → the ORIGINAL 200
|
|
2832
|
+
replayed (no second CAS); ② already DECIDED with a DIFFERENT decision → 409 `conflict.ask_decided`
|
|
2833
|
+
carrying the first decision echo (`decision`/`decidedAtMs`/`actor`; `note` is NEVER echoed across
|
|
2834
|
+
permission boundaries); ③ PARKED/DENIED/VOID → 410 `gone.ask_parked` (with the gate reconciliation
|
|
2835
|
+
coordinates `{sessionId, gateBoundCallId, gateBoundInputHash}` — NEVER the checkpoint token) or
|
|
2836
|
+
`gone.ask`; ④ PARKING → 425 `parking.ask_retry` — retrying fetches the FINAL ROUTING INFO, it does NOT
|
|
2837
|
+
retry acceptance of the decision (the window is already closed).
|
|
2838
|
+
|
|
2839
|
+
Attribution split: DENIED is produced ONLY by routing failure (`routing_failure_fail_closed`); an
|
|
2840
|
+
operator refusal is DECIDED(decision=deny). A timeout NEVER produces a deny.
|
|
2841
|
+
requestBody:
|
|
2842
|
+
required: true
|
|
2843
|
+
content:
|
|
2844
|
+
application/json:
|
|
2845
|
+
schema: { $ref: '#/components/schemas/AskDecisionBody' }
|
|
2846
|
+
responses:
|
|
2847
|
+
'200':
|
|
2848
|
+
description: The decision was accepted (or replayed for the same idempotencyKey).
|
|
2849
|
+
content:
|
|
2850
|
+
application/json:
|
|
2851
|
+
schema: { $ref: '#/components/schemas/AskDecisionAck' }
|
|
2852
|
+
'400':
|
|
2853
|
+
description: 'Malformed body — `request.invalid_json` (unparseable) or `request.body_shape` (schema reject, e.g. an `actor` carrying anything but `label`).'
|
|
2854
|
+
content:
|
|
2855
|
+
application/json:
|
|
2856
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
2857
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2858
|
+
'404':
|
|
2859
|
+
description: >
|
|
2860
|
+
Unknown askId / taskId mismatch / non-owner — ONE shape, NO existence oracle (`not_found.ask`).
|
|
2861
|
+
NOT the cross-leg case: a park+resume mints a NEW askId, and the OLD row survives in its terminal
|
|
2862
|
+
state, so deciding against the old askId gets that row's terminal answer (200 idempotent replay /
|
|
2863
|
+
409 first-decision echo / 410) — the "no consent is ever reused across legs" guarantee comes from
|
|
2864
|
+
the new leg having its own independent row, not from the old one 404-ing.
|
|
2865
|
+
content:
|
|
2866
|
+
application/json:
|
|
2867
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
2868
|
+
'409':
|
|
2869
|
+
description: >
|
|
2870
|
+
Already settled with a DIFFERENT decision (`conflict.ask_decided`, first-decision echo in the body),
|
|
2871
|
+
or the idempotencyKey belongs to another ask (`conflict.ask_idempotency`).
|
|
2872
|
+
content:
|
|
2873
|
+
application/json:
|
|
2874
|
+
schema: { $ref: '#/components/schemas/AskDecidedConflictBody' }
|
|
2875
|
+
'410':
|
|
2876
|
+
description: >
|
|
2877
|
+
The window is closed and the outcome is already cast: `gone.ask_parked` (gate coordinates in the
|
|
2878
|
+
body — route to the durable gate flow) or `gone.ask` (`{state, deniedReason?}`). The two legs carry
|
|
2879
|
+
DISJOINT bodies.
|
|
2880
|
+
content:
|
|
2881
|
+
application/json:
|
|
2882
|
+
schema: { $ref: '#/components/schemas/AskGoneBody' }
|
|
2883
|
+
'425':
|
|
2884
|
+
description: >
|
|
2885
|
+
PARKING — transferring to the delivery plane. `parking.ask_retry`; the retry fetches FINAL ROUTING
|
|
2886
|
+
INFO, never acceptance. The body carries `retryAfterSec` and NO gate coordinates (they are not
|
|
2887
|
+
written until bind).
|
|
2888
|
+
headers:
|
|
2889
|
+
Retry-After:
|
|
2890
|
+
schema: { type: integer }
|
|
2891
|
+
description: 'Seconds to wait before polling again for the final routing info. Paired with the body''s `retryAfterSec`.'
|
|
2892
|
+
content:
|
|
2893
|
+
application/json:
|
|
2894
|
+
schema: { $ref: '#/components/schemas/AskParkingBody' }
|
|
2895
|
+
'501':
|
|
2896
|
+
description: >
|
|
2897
|
+
In-stream approval protocol not on this worker (`feature.approval_ask_disabled`) — no ask store, or
|
|
2898
|
+
STREAM_APPROVAL_ENABLED is off. Probe the capability, do not trial-by-501.
|
|
2899
|
+
content:
|
|
2900
|
+
application/json:
|
|
2901
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
2902
|
+
|
|
2803
2903
|
/v1/side-query:
|
|
2804
2904
|
parameters:
|
|
2805
2905
|
- $ref: '#/components/parameters/PrincipalHeader'
|
|
@@ -4173,14 +4273,30 @@ components:
|
|
|
4173
4273
|
type: string
|
|
4174
4274
|
enum: [default, plan, acceptEdits, bypassPermissions, auto]
|
|
4175
4275
|
description: >
|
|
4176
|
-
CC permission-mode INTENT
|
|
4177
|
-
|
|
4276
|
+
CC-parity permission-mode INTENT — the five values THIS wire's closed submit-gate accepts (post-[816]/
|
|
4277
|
+
[820]/[822]; not a claim these are CC's/sema-cli's complete mode vocabulary, which is wider, e.g.
|
|
4278
|
+
`dontAsk`). The frontend (TUI shell / web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
|
|
4178
4279
|
`plan` ⇒ mount present_plan (core enablePlanMode) + read-only hands (CC EnterPlanMode research);
|
|
4179
|
-
`default` adds the manual ask gate
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4280
|
+
`default` adds the manual ask gate on non-exempt writes (scratchpad dirs / remembered session
|
|
4281
|
+
exemptions still auto-allow — not literally every Write/Edit); the server emits the SAME gate-policy
|
|
4282
|
+
for `auto` as for `default` — sending `auto` is NOT itself what arms core's auto-mode classifier; that
|
|
4283
|
+
classifier is armed purely by the principal's entitlement (`runtimeCaps.autoMode===true` plus a
|
|
4284
|
+
deployment `deps.autoMode` wiring), independent of which mode word the request sends: an entitled
|
|
4285
|
+
principal's `default` request is screened too, and a non-entitled principal's `auto` request behaves
|
|
4286
|
+
as plain `default`; `acceptEdits` adds the SAME gate narrowed to auto-allow inside the working
|
|
4287
|
+
directory — but only when the service has a trusted working directory to scope it to; on a sandbox
|
|
4288
|
+
execution lane with none wired, `acceptEdits` degrades to asking on every non-exempt write, same
|
|
4289
|
+
as `default`; `bypassPermissions` adds no mode-derived gate at all. None of the five can ever subtract
|
|
4290
|
+
from the deployment/operator policy (deny-wins). Omitting this field is NOT itself "no mode-derived
|
|
4291
|
+
gate" — this field wins over a `settings.permissions.defaultMode` bundle when both are sent, but when
|
|
4292
|
+
THIS field is omitted the bundled mode (if any) still supplies one; only when neither carrier supplies
|
|
4293
|
+
a mode is there no mode-derived gate (the same posture as `bypassPermissions`). Sending this field is
|
|
4294
|
+
also NOT the same as leaving it unset — an explicit `default` actively installs the ask gate. A fresh
|
|
4295
|
+
submit with an unknown string is rejected fail-loud with 400 (sibling of `promptProfile`); the old
|
|
4296
|
+
silent most-asking-mode fallback no longer applies to fresh submits — a lenient version of that
|
|
4297
|
+
fallback survives only internally, for RESUME replay of a pre-gate persisted
|
|
4298
|
+
body. Carrying the intent (not pre-interpreted core fields) = one interpretation across frontends +
|
|
4299
|
+
service-owned governance.
|
|
4184
4300
|
attachmentIds:
|
|
4185
4301
|
type: array
|
|
4186
4302
|
maxItems: 16
|
|
@@ -4259,6 +4375,17 @@ components:
|
|
|
4259
4375
|
type: string
|
|
4260
4376
|
enum: [simple, classic]
|
|
4261
4377
|
description: Which prompt-assembly profile to use.
|
|
4378
|
+
toolMaterializeStrategy:
|
|
4379
|
+
type: string
|
|
4380
|
+
enum: [static, swap]
|
|
4381
|
+
description: >
|
|
4382
|
+
server >=7.3.0 (core 5.14.0, board [2856]2) — how a DEFERRED tool (see `deferTools`) supplies its
|
|
4383
|
+
schema AFTER ToolSearch activates it. `static` keeps the placeholder (activation only flips
|
|
4384
|
+
callability; the advertised schema stays the empty object); `swap` advertises the REAL schema on the
|
|
4385
|
+
next request. A provider whose decoding is CONSTRAINED by the advertised schema loops UNBOUNDED under
|
|
4386
|
+
`static` (every round emits `{}` and the next round still advertises an empty schema), which is why
|
|
4387
|
+
this is a per-task knob. OMIT to inherit the engine chain (`spec ?? env ?? static`) — sending an
|
|
4388
|
+
explicit value takes that decision away from the deployment env lane. Unknown value => 400 fail-loud.
|
|
4262
4389
|
clientContext:
|
|
4263
4390
|
type: object
|
|
4264
4391
|
description: Non-authoritative client hints (never trusted for authz).
|
|
@@ -4276,8 +4403,11 @@ components:
|
|
|
4276
4403
|
description: >
|
|
4277
4404
|
TOC `settings.json` contract carried to the worker (CC-parity v1: permissions/hooks/env/model/
|
|
4278
4405
|
outputStyle). The SDK owns the schema in `src/settings.ts` (`SemaSettings`) — it is deliberately NOT
|
|
4279
|
-
duplicated here as a component
|
|
4280
|
-
|
|
4406
|
+
duplicated here as a component to keep ONE schema authority instead of two drifting copies. The
|
|
4407
|
+
service ACTIVELY interprets this bundle (task-settings.ts: permissions + permissions.defaultMode +
|
|
4408
|
+
model + outputStyle + env shipped in the 1.26.0 v1 scope; hooks was deferred at that point and wired
|
|
4409
|
+
in a later batch — both are live and interpreted today, not a raw uninterpreted passthrough);
|
|
4410
|
+
`permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`. OPEN object.
|
|
4281
4411
|
additionalProperties: true
|
|
4282
4412
|
cwd: { type: string, description: Working directory for the execution env. }
|
|
4283
4413
|
selfOrchestration: { type: boolean, description: Permit S8 self-orchestration (run_workflow) inside this task. }
|
|
@@ -7010,6 +7140,9 @@ components:
|
|
|
7010
7140
|
- $ref: '#/components/schemas/Event_elicitation'
|
|
7011
7141
|
- $ref: '#/components/schemas/Event_elicitation_complete'
|
|
7012
7142
|
- $ref: '#/components/schemas/Event_workflow_complete'
|
|
7143
|
+
# [2854] core 5.14.0 TaskEvent 16->18 (server 7.3.0 pickup): both new arms, projected.
|
|
7144
|
+
- $ref: '#/components/schemas/Event_human_input'
|
|
7145
|
+
- $ref: '#/components/schemas/Event_wiring_manifest'
|
|
7013
7146
|
discriminator:
|
|
7014
7147
|
propertyName: type
|
|
7015
7148
|
mapping:
|
|
@@ -7046,6 +7179,8 @@ components:
|
|
|
7046
7179
|
elicitation: '#/components/schemas/Event_elicitation'
|
|
7047
7180
|
elicitation_complete: '#/components/schemas/Event_elicitation_complete'
|
|
7048
7181
|
workflow_complete: '#/components/schemas/Event_workflow_complete'
|
|
7182
|
+
human_input: '#/components/schemas/Event_human_input'
|
|
7183
|
+
wiring_manifest: '#/components/schemas/Event_wiring_manifest'
|
|
7049
7184
|
|
|
7050
7185
|
# design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
|
|
7051
7186
|
# the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
|
|
@@ -7321,6 +7456,13 @@ components:
|
|
|
7321
7456
|
properties:
|
|
7322
7457
|
type: { const: task_progress }
|
|
7323
7458
|
taskId: { type: string, description: 'The subagent task id (the delegated sub-run).' }
|
|
7459
|
+
taskType:
|
|
7460
|
+
type: string
|
|
7461
|
+
description: >
|
|
7462
|
+
server >=7.3.0 (core 5.14.0, board [2854] six-car batch item 2) — the DELEGATION KIND discriminator
|
|
7463
|
+
(`background_agent` | `workflow`), so a consumer routes a progress frame without inferring the kind
|
|
7464
|
+
from the id shape. ABSENT on a synchronous child's frames (absence means "no fleet row" — tolerate
|
|
7465
|
+
it). Open on read.
|
|
7324
7466
|
usage:
|
|
7325
7467
|
type: object
|
|
7326
7468
|
description: 'Cumulative usage as of this turn boundary (closed 3-field set, byte-aligned to core).'
|
|
@@ -7339,6 +7481,121 @@ components:
|
|
|
7339
7481
|
parentToolCallId: { type: string }
|
|
7340
7482
|
sourceTaskId: { type: string }
|
|
7341
7483
|
bgAgentId: { type: string }
|
|
7484
|
+
Event_human_input:
|
|
7485
|
+
type: object
|
|
7486
|
+
description: >
|
|
7487
|
+
server >=7.3.0 (core 5.14.0 design/171) — HUMAN INPUT LIFECYCLE: who fed this run what, and whether it
|
|
7488
|
+
landed. One frame per carrier (objective / live steer / nextTurn / parked-steer resume / wake). It carries
|
|
7489
|
+
NO body text: this is the attribution + delivery ledger, not the message (join content through
|
|
7490
|
+
`message_committed` + stream order). Live emissions PRECEDE the commit and omit `entryId`.
|
|
7491
|
+
SERVICE PROJECTION: core's `principal` is deliberately NOT forwarded (the stream is already owner-scoped —
|
|
7492
|
+
the frame does not restate the tenant). `issuer` / `actor.id` / `actor.issuer` are host-derived identity
|
|
7493
|
+
strings and arrive SECRET-REDACTED.
|
|
7494
|
+
allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
|
|
7495
|
+
additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
|
|
7496
|
+
required: [type, inputId]
|
|
7497
|
+
properties:
|
|
7498
|
+
type: { const: human_input }
|
|
7499
|
+
inputId: { type: string, description: 'Stable per-input id (also the durable steering queue idempotency key).' }
|
|
7500
|
+
sessionSeq: { type: integer, description: 'Leg-scoped monotone sequence of human inputs.' }
|
|
7501
|
+
carrier: { type: string, description: 'Which carrier delivered it (steer / objective / a source word). Open on read.' }
|
|
7502
|
+
source: { type: string, description: 'objective | steer | next_turn | wake | external | system. Open on read.' }
|
|
7503
|
+
delivery: { type: string, description: 'applied | queued | parked_for_wake. Open on read.' }
|
|
7504
|
+
issuer: { type: string, description: 'Who asserted the identity (host-derived; REDACTED).' }
|
|
7505
|
+
actor:
|
|
7506
|
+
type: object
|
|
7507
|
+
description: >
|
|
7508
|
+
Speaker attribution — NEVER authority. `hostAsserted:false` means the host did NOT derive the identity
|
|
7509
|
+
from ingress credentials; render it as unverified (core's own `[from ...]` renderer appends
|
|
7510
|
+
" (unverified)" on exactly this bit). `id`/`issuer` arrive REDACTED.
|
|
7511
|
+
additionalProperties: false
|
|
7512
|
+
properties:
|
|
7513
|
+
id: { type: string }
|
|
7514
|
+
hostAsserted: { type: boolean }
|
|
7515
|
+
issuer: { type: string }
|
|
7516
|
+
entryId: { type: string, description: 'The committed session entry this input became (absent on the live pre-commit emission).' }
|
|
7517
|
+
eventId: { type: string }
|
|
7518
|
+
parentToolCallId: { type: string }
|
|
7519
|
+
sourceTaskId: { type: string }
|
|
7520
|
+
bgAgentId: { type: string }
|
|
7521
|
+
Event_wiring_manifest:
|
|
7522
|
+
type: object
|
|
7523
|
+
description: >
|
|
7524
|
+
server >=7.3.0 (core 5.14.0 design/173) — ASSEMBLY SELF-EVIDENCE: what this leg actually wired, emitted at
|
|
7525
|
+
prepare. Root / child / resume legs each emit their OWN manifest (a delegated child's rides the CHILD's
|
|
7526
|
+
stream by design). SERVICE PROJECTION: the `governance` section core stamps `audience:"operator"` is
|
|
7527
|
+
STRIPPED by the server before this frame reaches a tenant stream (core contract: "no projection => do not
|
|
7528
|
+
disclose") — it is deliberately absent from this schema. `configFingerprint` is ALSO stripped for the same
|
|
7529
|
+
reason: core hashes the whole manifest (governance included) unsalted, and governance is four booleans —
|
|
7530
|
+
sixteen combinations a tenant could brute-force against everything else it already receives, recovering the
|
|
7531
|
+
section that was removed. Both come back on the operator-scoped projection, not here.
|
|
7532
|
+
allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
|
|
7533
|
+
additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
|
|
7534
|
+
required: [type, manifest]
|
|
7535
|
+
properties:
|
|
7536
|
+
type: { const: wiring_manifest }
|
|
7537
|
+
manifest:
|
|
7538
|
+
type: object
|
|
7539
|
+
additionalProperties: false
|
|
7540
|
+
properties:
|
|
7541
|
+
schemaVersion: { type: integer }
|
|
7542
|
+
leg:
|
|
7543
|
+
type: object
|
|
7544
|
+
additionalProperties: false
|
|
7545
|
+
properties: { kind: { type: string, description: 'root | child | resume. Open on read.' } }
|
|
7546
|
+
ask:
|
|
7547
|
+
type: object
|
|
7548
|
+
additionalProperties: false
|
|
7549
|
+
description: 'Approval seam: how it is wired, where it came from, and what it effectively resolves to.'
|
|
7550
|
+
properties:
|
|
7551
|
+
form: { type: string }
|
|
7552
|
+
provenance: { type: string, description: 'spec | deps. Open on read.' }
|
|
7553
|
+
effective: { type: string }
|
|
7554
|
+
question:
|
|
7555
|
+
type: object
|
|
7556
|
+
additionalProperties: false
|
|
7557
|
+
description: >
|
|
7558
|
+
Question channel — three-valued (wired / absent / stripped_bg_lane, the last meaning the engine
|
|
7559
|
+
stripped a bg child per-request face). `interactiveToolsWithoutDeliveryFace` is the composition-lie flag.
|
|
7560
|
+
properties:
|
|
7561
|
+
wired: { type: string }
|
|
7562
|
+
provenance: { type: string }
|
|
7563
|
+
interactiveToolsWithoutDeliveryFace: { type: boolean }
|
|
7564
|
+
interaction:
|
|
7565
|
+
type: object
|
|
7566
|
+
additionalProperties: false
|
|
7567
|
+
properties: { posture: { type: string, description: 'interactive | headless | absent. Open on read.' } }
|
|
7568
|
+
elicit:
|
|
7569
|
+
type: object
|
|
7570
|
+
additionalProperties: false
|
|
7571
|
+
properties:
|
|
7572
|
+
seamWired: { type: boolean }
|
|
7573
|
+
serversOptedIn: { type: integer }
|
|
7574
|
+
parkLane:
|
|
7575
|
+
type: object
|
|
7576
|
+
additionalProperties: false
|
|
7577
|
+
description: >
|
|
7578
|
+
Durable park lane: capability vs EFFECTIVE policy. `effective` is THREE-VALUED — `"unresolved"`
|
|
7579
|
+
means the engine has not decided yet; never fold it to false.
|
|
7580
|
+
properties:
|
|
7581
|
+
capable: { type: boolean }
|
|
7582
|
+
effective: { oneOf: [{ type: boolean }, { type: string }] }
|
|
7583
|
+
reasons: { type: array, items: { type: string } }
|
|
7584
|
+
checkpointDurability: { type: string }
|
|
7585
|
+
session:
|
|
7586
|
+
type: object
|
|
7587
|
+
additionalProperties: false
|
|
7588
|
+
properties: { store: { type: string } }
|
|
7589
|
+
fleet:
|
|
7590
|
+
type: object
|
|
7591
|
+
additionalProperties: false
|
|
7592
|
+
properties:
|
|
7593
|
+
backgroundAgentStore: { type: boolean }
|
|
7594
|
+
hostChildEventSink: { type: boolean }
|
|
7595
|
+
eventId: { type: string }
|
|
7596
|
+
parentToolCallId: { type: string }
|
|
7597
|
+
sourceTaskId: { type: string }
|
|
7598
|
+
bgAgentId: { type: string }
|
|
7342
7599
|
Event_workspace_changed:
|
|
7343
7600
|
type: object
|
|
7344
7601
|
description: >
|
|
@@ -7705,6 +7962,313 @@ components:
|
|
|
7705
7962
|
enum: [allowed, denied, expired]
|
|
7706
7963
|
description: '"tool_approval_complete" only. `expired` = TTL/abort/disconnect — a fail-closed DENY the shell should render as such, not as "still pending".'
|
|
7707
7964
|
|
|
7965
|
+
# ── design/172 流内审批协议(#151;UNRELEASED,候 server 7.3.0 + STREAM_APPROVAL_ENABLED,默认 OFF)──
|
|
7966
|
+
# 与上面的 ToolApprovalFrame 是**并行加帧,不替换**:开关打开时同一只 ask 出两帧(先 tool_approval,
|
|
7967
|
+
# 后 approval_request),两帧同带 `approvalId` ⇒ 消费端按它去重、优先渲染新帧。
|
|
7968
|
+
ApprovalRequestFrame:
|
|
7969
|
+
type: object
|
|
7970
|
+
description: >
|
|
7971
|
+
IN-STREAM approval card (SSE, `type` IS the event name; design/172 §3.1). NOT an AgentEvent arm — it
|
|
7972
|
+
interleaves on the live token stream like tool_approval/question/elicitation. Settle it via
|
|
7973
|
+
POST /v1/tasks/{taskId}/asks/{askId}/decision.
|
|
7974
|
+
|
|
7975
|
+
WINDOW: `expiresAtMs` is cast ONCE and never recomputed; `expiresInMs` = max(0, expiresAtMs - serverNowMs)
|
|
7976
|
+
and is therefore MONOTONICALLY DECREASING across replays — a reconnect NEVER renews the window.
|
|
7977
|
+
|
|
7978
|
+
REPLAY: on a new connection the server replays every STREAM_PENDING card as a PREAMBLE, and that preamble
|
|
7979
|
+
is the shell's FULL RECONCILIATION BASELINE — any local card not in it must be dropped (revoke frames can
|
|
7980
|
+
be lost across a process boundary; the preamble is the structural compensation). Historical approval
|
|
7981
|
+
frames replayed from the durable events tail are for TIMELINE RENDERING ONLY, not a card-set baseline
|
|
7982
|
+
(their `expiresInMs` is the value frozen when the frame was minted). Preamble frames carry no SSE `id:`
|
|
7983
|
+
and take no part in the Last-Event-ID cursor.
|
|
7984
|
+
|
|
7985
|
+
UNKNOWN SHAPES ARE NEVER AUTO-DENIED: an unknown schemaVersion/kind or an unrenderable card gets a
|
|
7986
|
+
GENERIC card (safety metadata + manual approve/deny); if it cannot be shown, keep the frame and tell the
|
|
7987
|
+
operator, letting the window fall through to park. Folding "I don't understand it" into a refusal is the
|
|
7988
|
+
deadlock this protocol exists to remove.
|
|
7989
|
+
|
|
7990
|
+
NOT EVERY ASK HAS A CARD: the non-streaming POST /v1/tasks (no delivery plane) and council/team subtask
|
|
7991
|
+
legs produce none; an adhoc (non-durable) sync stream's taskId is one-shot and has no replay handle; a
|
|
7992
|
+
background child's ask arriving after the host run ended is NOT thrown at the dead stream — it parks onto
|
|
7993
|
+
the durable gate (the existing inbox surface).
|
|
7994
|
+
required: [type, schemaVersion, askId, taskId, kind, card, expiresInMs, expiresAtMs, serverNowMs, approvalId]
|
|
7995
|
+
additionalProperties: true
|
|
7996
|
+
properties:
|
|
7997
|
+
type: { type: string, enum: [approval_request] }
|
|
7998
|
+
schemaVersion:
|
|
7999
|
+
type: integer
|
|
8000
|
+
enum: [1]
|
|
8001
|
+
description: >
|
|
8002
|
+
Frame shape version. THIS SCHEMA DESCRIBES v1 ONLY — a frame carrying any other version is not this
|
|
8003
|
+
shape; read it as ApprovalFrameEnvelope and render the generic card. NEVER auto-deny.
|
|
8004
|
+
askId:
|
|
8005
|
+
type: string
|
|
8006
|
+
description: >
|
|
8007
|
+
The DURABLE identity (derived from the five-tuple sourceTaskId/runId/toolCallId/legKey/
|
|
8008
|
+
parentToolCallId). Dismissal, decision and reconciliation all key on THIS. Deduplicate by it (the
|
|
8009
|
+
same frame may be delivered repeatedly). VOID AFTER A PARK and never reused across legs — a resume
|
|
8010
|
+
re-asks with a NEW askId, so "the thing you just approved may be asked again" is normal and the
|
|
8011
|
+
shell must explain it.
|
|
8012
|
+
taskId: { type: string, description: 'wire run id — the {taskId} segment of the decision path.' }
|
|
8013
|
+
kind:
|
|
8014
|
+
type: string
|
|
8015
|
+
enum: [permission]
|
|
8016
|
+
description: >
|
|
8017
|
+
v1 closed set. `content` is a reserved slot, not implemented (design/172 §8③). A frame with any
|
|
8018
|
+
other kind is not this shape — read it as ApprovalFrameEnvelope and render the generic card.
|
|
8019
|
+
card: { $ref: '#/components/schemas/ApprovalCard' }
|
|
8020
|
+
expiresInMs: { type: integer, description: 'Remaining budget = max(0, expiresAtMs - serverNowMs). Monotonically decreasing across replays; the window is never renewed.' }
|
|
8021
|
+
expiresAtMs: { type: integer, description: 'Absolute server-clock deadline. Cast ONCE, never recomputed.' }
|
|
8022
|
+
serverNowMs: { type: integer, description: 'Server clock at mint time — the shell corrects its local clock skew with it.' }
|
|
8023
|
+
approvalId:
|
|
8024
|
+
type: string
|
|
8025
|
+
description: >
|
|
8026
|
+
Bridge to the legacy frame / dedupe key — same value as the parallel tool_approval frame. During the
|
|
8027
|
+
transition window POST /v1/tool-approvals/{approvalId}/respond still works.
|
|
8028
|
+
|
|
8029
|
+
ApprovalCard:
|
|
8030
|
+
type: object
|
|
8031
|
+
description: >
|
|
8032
|
+
The card payload — a NEUTRAL PROJECTION (design/172 §6.7: the server neither restates nor rewrites; the
|
|
8033
|
+
shell owns rendering). UNTRUSTED FOR DISPLAY: `message`/`args`/`sourceAgentName`/`delegation.agentName`
|
|
8034
|
+
are server-redacted and length-bounded but remain model/tool-authored bytes — render only, never re-feed
|
|
8035
|
+
to a model. `args` has a 16 KiB BYTE cap (UTF-8 bytes, not characters); over the cap it is omitted with
|
|
8036
|
+
`argsOmitted: true` and the shell must render an elision stub, NOT conclude "no arguments".
|
|
8037
|
+
required: [toolName, message, risk]
|
|
8038
|
+
additionalProperties: true
|
|
8039
|
+
properties:
|
|
8040
|
+
toolName: { type: string, description: 'Canonical core tool name.' }
|
|
8041
|
+
message: { type: string, description: 'The engine ask text, secret-redacted. UNTRUSTED for display.' }
|
|
8042
|
+
args: { description: 'The pending call''s args, redacted + byte-bounded. Absent (with argsOmitted) when over the cap or unserializable. UNTRUSTED for display.' }
|
|
8043
|
+
argsOmitted: { type: boolean, enum: [true], description: 'Present (true) only when args exceeded the byte cap. Never false.' }
|
|
8044
|
+
toolCallId: { type: string, description: 'The engine''s tool-call id (the `call_…` on the assistant message''s tool_use block) — same domain as ToolApprovalFrame.toolCallId.' }
|
|
8045
|
+
risk: { $ref: '#/components/schemas/ApprovalRiskAxes' }
|
|
8046
|
+
fromSubagent: { type: boolean, enum: [true], description: 'Present ⇔ the ask comes from a DELEGATED child — the explicit discriminator (a worker cannot forge it).' }
|
|
8047
|
+
sourceTaskId: { type: string, description: 'The child''s core session id — row-join value + fallback discriminator.' }
|
|
8048
|
+
sourceAgentName: { type: string, description: 'The child agent''s display name, redacted. UNTRUSTED.' }
|
|
8049
|
+
delegation: { $ref: '#/components/schemas/ApprovalCardDelegation' }
|
|
8050
|
+
|
|
8051
|
+
ApprovalRiskAxes:
|
|
8052
|
+
type: object
|
|
8053
|
+
description: >
|
|
8054
|
+
The two risk axes plus the coarse safety marker (design/172 §3.1; SDK ApprovalRiskAxes).
|
|
8055
|
+
|
|
8056
|
+
🔴 THREE-STATE, NOT TWO: true / false / ABSENT = the engine did not annotate it. The shell must render
|
|
8057
|
+
absence as "not annotated" and MUST NOT fold it to false — rendering "unknown" as "safe" is exactly the
|
|
8058
|
+
failure this contract forbids. The axes are optional because core exposes them additively on AskRequest,
|
|
8059
|
+
so an older engine/server omits them entirely.
|
|
8060
|
+
|
|
8061
|
+
`requiresRealApproval` is the ONE marker core always carries ("this is a safety-class ask" — the gate
|
|
8062
|
+
that refuses blanket-allow). It is NOT a substitute for, nor the conjunction of, the two axes: folding
|
|
8063
|
+
"deletes data" and "sends data out" into a single boolean erases the basis for the human decision.
|
|
8064
|
+
required: [requiresRealApproval]
|
|
8065
|
+
additionalProperties: true
|
|
8066
|
+
properties:
|
|
8067
|
+
irreversible: { type: boolean, description: 'The call is irreversible. ABSENT = not annotated — do not read as false.' }
|
|
8068
|
+
egress: { type: boolean, description: 'The call sends data across a boundary. ABSENT = not annotated — do not read as false.' }
|
|
8069
|
+
requiresRealApproval: { type: boolean, description: 'Coarse safety-class marker (core AskRequest.requiresRealApproval). Always present.' }
|
|
8070
|
+
|
|
8071
|
+
ApprovalCardDelegation:
|
|
8072
|
+
type: object
|
|
8073
|
+
description: 'Delegation provenance for a child ask (present only alongside fromSubagent).'
|
|
8074
|
+
required: [parentToolCallId, depth]
|
|
8075
|
+
additionalProperties: true
|
|
8076
|
+
properties:
|
|
8077
|
+
parentToolCallId: { type: string, description: 'The host-side delegating tool-call id — the card''s attribution anchor.' }
|
|
8078
|
+
depth: { type: integer, description: 'Delegation depth (a direct child of the host = 1).' }
|
|
8079
|
+
agentName: { type: string, description: 'Child agent name. UNTRUSTED — spawning-model free text (server-redacted and length-bounded).' }
|
|
8080
|
+
|
|
8081
|
+
ApprovalRevokeFrame:
|
|
8082
|
+
type: object
|
|
8083
|
+
description: >
|
|
8084
|
+
BATCH-LEVEL card revocation (SSE; design/172 §3.3). A batch = the blast radius of ONE run-level abort
|
|
8085
|
+
(every in-flight tool call of that run plus its synchronous delegation subtree). On receipt the shell
|
|
8086
|
+
clears that batch's local cards (whitelist-pick by `askIds`) and waits for the new askIds a resume mints.
|
|
8087
|
+
|
|
8088
|
+
Siblings already DECIDED are NOT in `askIds` (an accepted decision is not affected by revocation).
|
|
8089
|
+
Revoke frames CAN BE LOST (frames produced by the reconciler / orphan legs have no seq to allocate and
|
|
8090
|
+
ride the live plane only) — the structural compensation is the open-stream preamble baseline (see
|
|
8091
|
+
ApprovalRequestFrame), not a redelivery of this frame.
|
|
8092
|
+
required: [type, schemaVersion, batchId, askIds, reason, serverNowMs]
|
|
8093
|
+
additionalProperties: true
|
|
8094
|
+
properties:
|
|
8095
|
+
type: { type: string, enum: [approval_revoke] }
|
|
8096
|
+
schemaVersion: { type: integer, enum: [1], description: 'This schema describes v1 only (see ApprovalFrameEnvelope for the unknown-version read).' }
|
|
8097
|
+
batchId: { type: string }
|
|
8098
|
+
askIds: { type: array, items: { type: string }, description: 'The asks revoked (moved to VOID) by this frame.' }
|
|
8099
|
+
reason:
|
|
8100
|
+
type: string
|
|
8101
|
+
description: >
|
|
8102
|
+
`superseded_by_park` = a sibling''s window expired and the whole batch degraded to the durable park;
|
|
8103
|
+
`aborted` = the run was cancelled. Read as OPEN: an unknown reason is handled as a generic revoke
|
|
8104
|
+
(clear the cards), never specially.
|
|
8105
|
+
serverNowMs: { type: integer }
|
|
8106
|
+
|
|
8107
|
+
AskDecidedConflictBody:
|
|
8108
|
+
allOf:
|
|
8109
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
8110
|
+
- type: object
|
|
8111
|
+
properties:
|
|
8112
|
+
decision:
|
|
8113
|
+
allOf: [{ $ref: '#/components/schemas/ApprovalAskDecision' }]
|
|
8114
|
+
description: 'FIRST-DECISION ECHO (`conflict.ask_decided` only): the decision that was accepted.'
|
|
8115
|
+
decidedAtMs: { type: integer, description: 'When the first decision was durably accepted.' }
|
|
8116
|
+
actor:
|
|
8117
|
+
allOf: [{ $ref: '#/components/schemas/ActorAssertion' }]
|
|
8118
|
+
description: 'Who decided. Omitted when the persisted actor fails its schema check (car4 §12-D).'
|
|
8119
|
+
description: >
|
|
8120
|
+
409 body. `conflict.ask_decided` carries the FIRST-DECISION ECHO; `conflict.ask_idempotency` carries the
|
|
8121
|
+
bare `errorCode` (the key belongs to a different ask — change the key, not the ask).
|
|
8122
|
+
🔴 `note` is NEVER echoed: the echo is bounded by the requester's visible permissions (design/172 §3.2).
|
|
8123
|
+
Retrying the same decision can never succeed — single-winner CAS means the outcome is already cast.
|
|
8124
|
+
|
|
8125
|
+
AskGoneBody:
|
|
8126
|
+
allOf:
|
|
8127
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
8128
|
+
- type: object
|
|
8129
|
+
properties:
|
|
8130
|
+
sessionId: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 1; drive the existing durable-gate flow with it.' }
|
|
8131
|
+
gateBoundCallId: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 2.' }
|
|
8132
|
+
gateBoundInputHash: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 3.' }
|
|
8133
|
+
state: { type: string, enum: [DENIED, VOID], description: 'gone.ask ONLY — the terminal state.' }
|
|
8134
|
+
deniedReason:
|
|
8135
|
+
type: string
|
|
8136
|
+
description: >
|
|
8137
|
+
gone.ask ONLY — attribution, GROUPED BY `state`. `DENIED` carries the single-member deny
|
|
8138
|
+
vocabulary `routing_failure_fail_closed`; `VOID` carries the separate (and growing) void
|
|
8139
|
+
vocabulary, e.g. `adhoc_leg_no_durable_domain`. The two vocabularies never mix.
|
|
8140
|
+
description: >
|
|
8141
|
+
410 body. THE TWO LEGS ARE DISJOINT: `gone.ask_parked` carries exactly the three gate coordinates,
|
|
8142
|
+
`gone.ask` carries exactly `{state, deniedReason?}`. Routing off field presence across the legs is how a
|
|
8143
|
+
consumer ends up driving the wrong durable gate.
|
|
8144
|
+
🔴 The checkpoint token is NEVER here: the resume credential is not externally disclosed. Use
|
|
8145
|
+
`sessionId`.
|
|
8146
|
+
🔴 ATTRIBUTION SPLIT: `DENIED` is produced ONLY by routing failure. An operator refusal is
|
|
8147
|
+
`DECIDED(decision="deny")` and reaches the caller as a 200 or as the 409 first-decision echo — never as
|
|
8148
|
+
`DENIED`. A timeout never produces a deny.
|
|
8149
|
+
|
|
8150
|
+
AskParkingBody:
|
|
8151
|
+
allOf:
|
|
8152
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
8153
|
+
- type: object
|
|
8154
|
+
properties:
|
|
8155
|
+
retryAfterSec: { type: integer, description: 'Suggested lower bound before polling again; paired with the Retry-After header.' }
|
|
8156
|
+
description: >
|
|
8157
|
+
425 body (`parking.ask_retry`). 🔴 NOT "retry may succeed": the window is already closed and the decision
|
|
8158
|
+
can never be accepted. The retry fetches the FINAL ROUTING INFO (poll until the 410 hands over the gate
|
|
8159
|
+
coordinates). Carries NO gate coordinates — they are not written until the batch binds.
|
|
8160
|
+
|
|
8161
|
+
ApprovalFrameEnvelope:
|
|
8162
|
+
type: object
|
|
8163
|
+
description: >
|
|
8164
|
+
The UNKNOWN-SHAPE read of either approval frame — the minimal common shape on the wire, and the typed
|
|
8165
|
+
counterpart of design/172's "unknown shapes are never auto-denied" clause.
|
|
8166
|
+
|
|
8167
|
+
ApprovalRequestFrame and ApprovalRevokeFrame describe **v1 only** (`schemaVersion: 1`, plus
|
|
8168
|
+
`kind: permission` on the request frame). Leaving those discriminants open would let a schema
|
|
8169
|
+
simultaneously say "any version may arrive" and keep promising the complete v1 `card` — which is exactly
|
|
8170
|
+
how a consumer ends up dereferencing `card.risk.requiresRealApproval` on a frame that never promised it.
|
|
8171
|
+
Read the frame as this envelope, narrow on the discriminants, and fall back to the generic card when it
|
|
8172
|
+
does not narrow.
|
|
8173
|
+
required: [type, schemaVersion]
|
|
8174
|
+
additionalProperties: true
|
|
8175
|
+
properties:
|
|
8176
|
+
type: { type: string, description: 'Echo of the SSE event name. Open: a third frame family may appear.' }
|
|
8177
|
+
schemaVersion: { type: integer, description: 'Frame shape version. Anything but 1 means this package does not describe the payload.' }
|
|
8178
|
+
|
|
8179
|
+
ApprovalAskDecision:
|
|
8180
|
+
type: string
|
|
8181
|
+
enum: [approve, deny]
|
|
8182
|
+
description: >
|
|
8183
|
+
The decision on an IN-STREAM approval card (design/172 §3.2). A TWO-word set — deliberately NOT the
|
|
8184
|
+
three-word ToolApprovalDecision: `allow_session` ("remember for this session") is a live-bridge-only
|
|
8185
|
+
semantic and does not exist on POST /v1/tasks/{taskId}/asks/{askId}/decision.
|
|
8186
|
+
|
|
8187
|
+
ActorAssertion:
|
|
8188
|
+
type: object
|
|
8189
|
+
description: >
|
|
8190
|
+
Decision-maker identity assertion (design/171 §5.1; the cross-repo source of truth is
|
|
8191
|
+
`@sema-agent/registry-core` ActorAssertionWire — the SDK holds a value copy).
|
|
8192
|
+
|
|
8193
|
+
NORMATIVE, not a style note: an ACTOR NEVER PARTICIPATES IN THE PERMISSION DECISION — authority always
|
|
8194
|
+
belongs to the principal; this shape exists only for audit/echo/presentation. `id` is ALWAYS server-minted
|
|
8195
|
+
(= the verified principal; an auth-off deployment mints the `"_"` sentinel), and a wire requester may
|
|
8196
|
+
submit ONLY `label`.
|
|
8197
|
+
required: [id]
|
|
8198
|
+
additionalProperties: false # registry-core's ActorAssertionWire is .strict()
|
|
8199
|
+
properties:
|
|
8200
|
+
id: { type: string, minLength: 1, maxLength: 256, description: 'Server-minted principal ("_" sentinel when auth is off). Never submittable by the requester.' }
|
|
8201
|
+
label: { type: string, maxLength: 256, description: 'Unverified display text — the ONLY field a requester may submit (server redacts + bounds it). UNTRUSTED.' }
|
|
8202
|
+
verified: { type: boolean, description: 'Whether the server minted this id under an authenticated context (auth-off ⇒ false, honestly). Never submittable.' }
|
|
8203
|
+
via:
|
|
8204
|
+
type: string
|
|
8205
|
+
enum: [owner, operator]
|
|
8206
|
+
description: >
|
|
8207
|
+
Audit axis for which authorization path accepted the decision: `owner` (row owner) or `operator`
|
|
8208
|
+
(explicit operator seat). CLOSED — verbatim-equal to the owning schema
|
|
8209
|
+
(registry-core ActorAssertionWire). Widening it here without widening the owner is copy drift.
|
|
8210
|
+
|
|
8211
|
+
AskDecisionBody:
|
|
8212
|
+
type: object
|
|
8213
|
+
description: 'Request body of POST /v1/tasks/{taskId}/asks/{askId}/decision (design/172 §3.2).'
|
|
8214
|
+
required: [decision]
|
|
8215
|
+
additionalProperties: false
|
|
8216
|
+
properties:
|
|
8217
|
+
decision: { $ref: '#/components/schemas/ApprovalAskDecision' }
|
|
8218
|
+
updatedInput:
|
|
8219
|
+
description: >
|
|
8220
|
+
Operator-edited args (WHOLE REPLACEMENT, not a patch). Valid on the approve arm only (leniently
|
|
8221
|
+
ignored on deny). Passed through verbatim: core re-runs PreToolUse + the full policy chain on the
|
|
8222
|
+
edit; the server interprets nothing. Whether it was forwarded shows up as AskDecisionAck
|
|
8223
|
+
.updatedInputForwarded.
|
|
8224
|
+
note:
|
|
8225
|
+
type: string
|
|
8226
|
+
maxLength: 2048
|
|
8227
|
+
description: >
|
|
8228
|
+
Audit note. NEVER echoed back on the 409 first-decision echo (the echo is bounded by the requester's
|
|
8229
|
+
visible permissions).
|
|
8230
|
+
idempotencyKey:
|
|
8231
|
+
type: string
|
|
8232
|
+
minLength: 1
|
|
8233
|
+
maxLength: 255
|
|
8234
|
+
description: >
|
|
8235
|
+
Decision idempotency key (<= 255). The same key retried replays the ORIGINAL 200 (no second CAS);
|
|
8236
|
+
the same key against a DIFFERENT askId is a 409 `conflict.ask_idempotency` — deliberately fail-loud,
|
|
8237
|
+
because replaying across asks would both mask a caller key-management bug and violate design/172''s
|
|
8238
|
+
"never reuse an old consent" clause.
|
|
8239
|
+
actor:
|
|
8240
|
+
type: object
|
|
8241
|
+
description: 'Display name of the decision-maker. ONLY `label` is accepted — id/verified are always server-minted; any extra key is a 400 request.body_shape.'
|
|
8242
|
+
required: [label]
|
|
8243
|
+
additionalProperties: false
|
|
8244
|
+
properties:
|
|
8245
|
+
label: { type: string, maxLength: 256 }
|
|
8246
|
+
|
|
8247
|
+
AskDecisionAck:
|
|
8248
|
+
type: object
|
|
8249
|
+
description: 'The 200 ack of POST /v1/tasks/{taskId}/asks/{askId}/decision (design/172 §3.2 rule ①; also the replay body for an idempotent retry).'
|
|
8250
|
+
required: [askId, taskId, decision, decidedAtMs]
|
|
8251
|
+
additionalProperties: false # server-composed envelope (car4 §6), not an engine passthrough — closed on purpose
|
|
8252
|
+
properties:
|
|
8253
|
+
askId: { type: string }
|
|
8254
|
+
taskId: { type: string }
|
|
8255
|
+
decision: { $ref: '#/components/schemas/ApprovalAskDecision' }
|
|
8256
|
+
decidedAtMs: { type: integer, description: 'Server instant at which the decision was durably accepted.' }
|
|
8257
|
+
actor:
|
|
8258
|
+
allOf: [{ $ref: '#/components/schemas/ActorAssertion' }]
|
|
8259
|
+
description: >
|
|
8260
|
+
OPTIONAL: when the persisted decision_actor fails its schema check the server OMITS this field
|
|
8261
|
+
rather than answering 500 (car4 §12-D). Never dereference `actor.id` without a presence check.
|
|
8262
|
+
updatedInputForwarded:
|
|
8263
|
+
type: boolean
|
|
8264
|
+
enum: [true]
|
|
8265
|
+
description: >
|
|
8266
|
+
Present (true) when the approve carried `updatedInput` AND a live in-process entry was actually
|
|
8267
|
+
settled with it (the server forwarded the edited args to the engine). 🔴 TRUE OR ABSENT, NEVER FALSE
|
|
8268
|
+
— so "no live entry (pure durable / another replica)" and "no edit sent" are indistinguishable on the
|
|
8269
|
+
wire. An idempotent replay does NOT promise byte-identity on this field: it is a function of THIS
|
|
8270
|
+
replica's live state and is not persisted.
|
|
8271
|
+
|
|
7708
8272
|
QuestionFrame:
|
|
7709
8273
|
type: object
|
|
7710
8274
|
description: >
|