@sema-agent/sdk 6.6.1 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml CHANGED
@@ -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,98 @@ 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 `capabilities.streamApproval` (same predicate as this gate),
2899
+ do not trial-by-501.
2900
+ content:
2901
+ application/json:
2902
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
2903
+
2803
2904
  /v1/side-query:
2804
2905
  parameters:
2805
2906
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -3557,6 +3658,42 @@ paths:
3557
3658
  '403': { description: 'Multi-tenant worker: outcomes view is operator-only.' }
3558
3659
  '501': { $ref: '#/components/responses/NotImplemented' }
3559
3660
 
3661
+ /v1/diagnostics/wiring:
3662
+ parameters:
3663
+ - $ref: '#/components/parameters/PrincipalHeader'
3664
+ get:
3665
+ tags: [diagnostics]
3666
+ operationId: wiringDiagnostics
3667
+ x-status: live # server >=7.4.0 (#154) — assembly self-evidence read face.
3668
+ summary: What this worker actually wired (operator-only).
3669
+ description: >
3670
+ #154 — one screen that answers "what is this worker actually wired to". `static` is core's
3671
+ STATIC-half wiring manifest (`describeStaticWiring`): a DEPLOYMENT-level fact, independent of any
3672
+ leg, and it carries the `governance` section verbatim because **this face is that section's
3673
+ operator audience** (on a tenant-readable live stream the server strips it — core's contract is
3674
+ "no projection => do not disclose"). `serverGates` is the server's own assembly predicates,
3675
+ including WHY the in-stream approval protocol is or is not on (the first unsatisfied conjunct of
3676
+ the same five-way predicate that gates `capabilities.streamApproval` and the decision endpoint's
3677
+ 501 — so this page can never disagree with what a consumer hits).
3678
+ OPERATOR-ONLY. The gate is `OPERATOR_PRINCIPALS` NON-EMPTY **OR** REQUIRE_PRINCIPAL: if either
3679
+ holds, the caller must be a principal listed in OPERATOR_PRINCIPALS (403 `auth.operator_only`
3680
+ otherwise) — an EMPTY operator list means NOBODY is an operator, never everybody. Note that
3681
+ "REQUIRE_PRINCIPAL is off" does NOT mean open: a deployment that lists operators still 403s an
3682
+ anonymous or non-listed caller. The only open posture is an empty operator list on a
3683
+ non-multi-tenant worker (single-user turnkey — the sole user IS the operator).
3684
+ Deliberately carries **no per-leg history**: the governance section exists only in the live
3685
+ frame's operator projection — the durable ledger always stores the stripped form, because at
3686
+ write time it cannot know who will read it back.
3687
+ responses:
3688
+ '200':
3689
+ description: This worker's static wiring + the server's own gate readings.
3690
+ content:
3691
+ application/json:
3692
+ schema: { $ref: '#/components/schemas/WiringDiagnostics' }
3693
+ '401': { $ref: '#/components/responses/Unauthorized' }
3694
+ '403': { description: 'Multi-tenant worker: wiring diagnostics are operator-only (`auth.operator_only`).' }
3695
+ '404': { description: 'This worker serves no wiring diagnostics (older server, or a process not assembled by the composition root).' }
3696
+
3560
3697
  /v1/sendfile-links:
3561
3698
  parameters:
3562
3699
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -4173,14 +4310,30 @@ components:
4173
4310
  type: string
4174
4311
  enum: [default, plan, acceptEdits, bypassPermissions, auto]
4175
4312
  description: >
4176
- CC permission-mode INTENT (the five CC modes verbatim, post-[816]/[820]/[822]). The frontend (TUI shell /
4177
- web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
4313
+ CC-parity permission-mode INTENT — the five values THIS wire's closed submit-gate accepts (post-[816]/
4314
+ [820]/[822]; not a claim these are CC's/sema-cli's complete mode vocabulary, which is wider, e.g.
4315
+ `dontAsk`). The frontend (TUI shell / web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
4178
4316
  `plan` ⇒ mount present_plan (core enablePlanMode) + read-only hands (CC EnterPlanMode research);
4179
- `default` adds the manual ask gate; `auto` = same ask gate with core's auto-mode classifier screening
4180
- asks upstream (entitlement-gated in core); `acceptEdits`/`bypassPermissions` are honored as
4181
- "add less/no mode-derived gating" — they can never subtract from the deployment/operator policy
4182
- (deny-wins). An unknown string coerces to `default` (the most-asking mode). Carrying the intent
4183
- (not pre-interpreted core fields) = one interpretation across frontends + service-owned governance.
4317
+ `default` adds the manual ask gate on non-exempt writes (scratchpad dirs / remembered session
4318
+ exemptions still auto-allow — not literally every Write/Edit); the server emits the SAME gate-policy
4319
+ for `auto` as for `default` — sending `auto` is NOT itself what arms core's auto-mode classifier; that
4320
+ classifier is armed purely by the principal's entitlement (`runtimeCaps.autoMode===true` plus a
4321
+ deployment `deps.autoMode` wiring), independent of which mode word the request sends: an entitled
4322
+ principal's `default` request is screened too, and a non-entitled principal's `auto` request behaves
4323
+ as plain `default`; `acceptEdits` adds the SAME gate narrowed to auto-allow inside the working
4324
+ directory — but only when the service has a trusted working directory to scope it to; on a sandbox
4325
+ execution lane with none wired, `acceptEdits` degrades to asking on every non-exempt write, same
4326
+ as `default`; `bypassPermissions` adds no mode-derived gate at all. None of the five can ever subtract
4327
+ from the deployment/operator policy (deny-wins). Omitting this field is NOT itself "no mode-derived
4328
+ gate" — this field wins over a `settings.permissions.defaultMode` bundle when both are sent, but when
4329
+ THIS field is omitted the bundled mode (if any) still supplies one; only when neither carrier supplies
4330
+ a mode is there no mode-derived gate (the same posture as `bypassPermissions`). Sending this field is
4331
+ also NOT the same as leaving it unset — an explicit `default` actively installs the ask gate. A fresh
4332
+ submit with an unknown string is rejected fail-loud with 400 (sibling of `promptProfile`); the old
4333
+ silent most-asking-mode fallback no longer applies to fresh submits — a lenient version of that
4334
+ fallback survives only internally, for RESUME replay of a pre-gate persisted
4335
+ body. Carrying the intent (not pre-interpreted core fields) = one interpretation across frontends +
4336
+ service-owned governance.
4184
4337
  attachmentIds:
4185
4338
  type: array
4186
4339
  maxItems: 16
@@ -4259,6 +4412,17 @@ components:
4259
4412
  type: string
4260
4413
  enum: [simple, classic]
4261
4414
  description: Which prompt-assembly profile to use.
4415
+ toolMaterializeStrategy:
4416
+ type: string
4417
+ enum: [static, swap]
4418
+ description: >
4419
+ server >=7.3.0 (core 5.14.0, board [2856]2) — how a DEFERRED tool (see `deferTools`) supplies its
4420
+ schema AFTER ToolSearch activates it. `static` keeps the placeholder (activation only flips
4421
+ callability; the advertised schema stays the empty object); `swap` advertises the REAL schema on the
4422
+ next request. A provider whose decoding is CONSTRAINED by the advertised schema loops UNBOUNDED under
4423
+ `static` (every round emits `{}` and the next round still advertises an empty schema), which is why
4424
+ this is a per-task knob. OMIT to inherit the engine chain (`spec ?? env ?? static`) — sending an
4425
+ explicit value takes that decision away from the deployment env lane. Unknown value => 400 fail-loud.
4262
4426
  clientContext:
4263
4427
  type: object
4264
4428
  description: Non-authoritative client hints (never trusted for authz).
@@ -4276,8 +4440,11 @@ components:
4276
4440
  description: >
4277
4441
  TOC `settings.json` contract carried to the worker (CC-parity v1: permissions/hooks/env/model/
4278
4442
  outputStyle). The SDK owns the schema in `src/settings.ts` (`SemaSettings`) — it is deliberately NOT
4279
- duplicated here as a component: the service passes it through to core rather than interpreting it, so
4280
- the TS type is the single authority. OPEN object.
4443
+ duplicated here as a component to keep ONE schema authority instead of two drifting copies. The
4444
+ service ACTIVELY interprets this bundle (task-settings.ts: permissions + permissions.defaultMode +
4445
+ model + outputStyle + env shipped in the 1.26.0 v1 scope; hooks was deferred at that point and wired
4446
+ in a later batch — both are live and interpreted today, not a raw uninterpreted passthrough);
4447
+ `permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`. OPEN object.
4281
4448
  additionalProperties: true
4282
4449
  cwd: { type: string, description: Working directory for the execution env. }
4283
4450
  selfOrchestration: { type: boolean, description: Permit S8 self-orchestration (run_workflow) inside this task. }
@@ -5083,6 +5250,24 @@ components:
5083
5250
  mcpElicitation: { type: boolean, description: "MCP elicitation round-trips are supported." }
5084
5251
  askUserQuestion: { type: boolean, description: "AskUserQuestion gate is available (durable when approvals are)." }
5085
5252
  toolApproval: { type: boolean, description: "Tool-approval gating is active." }
5253
+ streamApproval:
5254
+ type: boolean
5255
+ description: >
5256
+ design/172 IN-STREAM approval protocol is on this worker (server >=7.3.0,
5257
+ `src/http/routes/capabilities.ts:233` — `resolveStreamApprovalGate(...).active`). TRUE ⇒ the live
5258
+ stream may carry `approval_request` / `approval_revoke` frames and `POST
5259
+ /v1/tasks/{taskId}/asks/{askId}/decision` settles them. FALSE/absent ⇒ that endpoint answers **501
5260
+ `feature.approval_ask_disabled`** on every call and the two frames never appear — the SAME predicate
5261
+ gates both, so "says yes ⟺ the route works". Probe this bit and hide the face; do NOT trial-by-501.
5262
+ The predicate is a FIVE-way conjunction (`resolveStreamApprovalGate` in `src/tool-approval.ts`): the live approval coordinator
5263
+ is wired (TOOL_APPROVAL_ENABLED) AND `STREAM_APPROVAL_ENABLED` AND a store backend is present AND
5264
+ `backend.kind != "local"` (the ask ledger must be DURABLE — an in-memory ledger loses accepted
5265
+ decisions on restart) AND the park facility is present (checkpoint store + DURABLE_APPROVAL, the
5266
+ degradation target when a window expires; without it the outcome would be a fail-closed deny, worse
5267
+ than today's live card). `STREAM_APPROVAL_ENABLED` is **default OFF**, so on virtually every
5268
+ deployment today this bit is `false` and the legacy `tool_approval` live-card leg is the only
5269
+ approval face. Orthogonal to `toolApproval` (the live-card leg) and to `approvals` (the durable
5270
+ checkpoint leg) — all three can differ.
5086
5271
  promptSuggestions: { type: boolean, description: "Follow-up prompt suggestions are generated." }
5087
5272
  modelUsage:
5088
5273
  type: boolean
@@ -7010,6 +7195,9 @@ components:
7010
7195
  - $ref: '#/components/schemas/Event_elicitation'
7011
7196
  - $ref: '#/components/schemas/Event_elicitation_complete'
7012
7197
  - $ref: '#/components/schemas/Event_workflow_complete'
7198
+ # [2854] core 5.14.0 TaskEvent 16->18 (server 7.3.0 pickup): both new arms, projected.
7199
+ - $ref: '#/components/schemas/Event_human_input'
7200
+ - $ref: '#/components/schemas/Event_wiring_manifest'
7013
7201
  discriminator:
7014
7202
  propertyName: type
7015
7203
  mapping:
@@ -7046,6 +7234,8 @@ components:
7046
7234
  elicitation: '#/components/schemas/Event_elicitation'
7047
7235
  elicitation_complete: '#/components/schemas/Event_elicitation_complete'
7048
7236
  workflow_complete: '#/components/schemas/Event_workflow_complete'
7237
+ human_input: '#/components/schemas/Event_human_input'
7238
+ wiring_manifest: '#/components/schemas/Event_wiring_manifest'
7049
7239
 
7050
7240
  # design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
7051
7241
  # the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
@@ -7321,6 +7511,13 @@ components:
7321
7511
  properties:
7322
7512
  type: { const: task_progress }
7323
7513
  taskId: { type: string, description: 'The subagent task id (the delegated sub-run).' }
7514
+ taskType:
7515
+ type: string
7516
+ description: >
7517
+ server >=7.3.0 (core 5.14.0, board [2854] six-car batch item 2) — the DELEGATION KIND discriminator
7518
+ (`background_agent` | `workflow`), so a consumer routes a progress frame without inferring the kind
7519
+ from the id shape. ABSENT on a synchronous child's frames (absence means "no fleet row" — tolerate
7520
+ it). Open on read.
7324
7521
  usage:
7325
7522
  type: object
7326
7523
  description: 'Cumulative usage as of this turn boundary (closed 3-field set, byte-aligned to core).'
@@ -7339,6 +7536,138 @@ components:
7339
7536
  parentToolCallId: { type: string }
7340
7537
  sourceTaskId: { type: string }
7341
7538
  bgAgentId: { type: string }
7539
+ Event_human_input:
7540
+ type: object
7541
+ description: >
7542
+ server >=7.3.0 (core 5.14.0 design/171) — HUMAN INPUT LIFECYCLE: who fed this run what, and whether it
7543
+ landed. One frame per carrier (objective / live steer / nextTurn / parked-steer resume / wake). It carries
7544
+ NO body text: this is the attribution + delivery ledger, not the message (join content through
7545
+ `message_committed` + stream order). Live emissions PRECEDE the commit and omit `entryId`.
7546
+ SERVICE PROJECTION: core's `principal` is deliberately NOT forwarded (the stream is already owner-scoped —
7547
+ the frame does not restate the tenant). `issuer` / `actor.id` / `actor.issuer` are host-derived identity
7548
+ strings and arrive SECRET-REDACTED.
7549
+ allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
7550
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
7551
+ required: [type, inputId]
7552
+ properties:
7553
+ type: { const: human_input }
7554
+ inputId: { type: string, description: 'Stable per-input id (also the durable steering queue idempotency key).' }
7555
+ sessionSeq: { type: integer, description: 'Leg-scoped monotone sequence of human inputs.' }
7556
+ carrier: { type: string, description: 'Which carrier delivered it (steer / objective / a source word). Open on read.' }
7557
+ source: { type: string, description: 'objective | steer | next_turn | wake | external | system. Open on read.' }
7558
+ delivery: { type: string, description: 'applied | queued | parked_for_wake. Open on read.' }
7559
+ issuer: { type: string, description: 'Who asserted the identity (host-derived; REDACTED).' }
7560
+ actor:
7561
+ type: object
7562
+ description: >
7563
+ Speaker attribution — NEVER authority. `hostAsserted:false` means the host did NOT derive the identity
7564
+ from ingress credentials; render it as unverified (core's own `[from ...]` renderer appends
7565
+ " (unverified)" on exactly this bit). `id`/`issuer` arrive REDACTED.
7566
+ additionalProperties: false
7567
+ properties:
7568
+ id: { type: string }
7569
+ hostAsserted: { type: boolean }
7570
+ issuer: { type: string }
7571
+ entryId: { type: string, description: 'The committed session entry this input became (absent on the live pre-commit emission).' }
7572
+ eventId: { type: string }
7573
+ parentToolCallId: { type: string }
7574
+ sourceTaskId: { type: string }
7575
+ bgAgentId: { type: string }
7576
+ Event_wiring_manifest:
7577
+ type: object
7578
+ description: >
7579
+ server >=7.3.0 (core 5.14.0 design/173) — ASSEMBLY SELF-EVIDENCE: what this leg actually wired, emitted at
7580
+ prepare. Root / child / resume legs each emit their OWN manifest (a delegated child's rides the CHILD's
7581
+ stream by design).
7582
+ 🔴 FLAT: every section sits at the TOP LEVEL of the frame — there is NO `manifest` wrapper. (Declared
7583
+ nested through 6.7.0; the wire never had it. The server projects this arm through the same shared
7584
+ whitelist builder as every other arm and SPREADS the result into the frame, on all three legs — live SSE,
7585
+ durable ledger row, resume replay. Corrected 2026-08-07 against a real capture.)
7586
+ SERVICE PROJECTION, TWO FACES (server >=7.4.0, #154): the `governance` section core stamps
7587
+ `audience:"operator"`, and `configFingerprint`, are STRIPPED on a tenant stream (core contract: "no
7588
+ projection => do not disclose") and PRESENT on an operator-scoped one. The fingerprint travels with the
7589
+ section because core hashes the whole manifest (governance included) unsalted, and governance is four
7590
+ booleans — sixteen combinations a tenant could brute-force against everything else it already receives,
7591
+ recovering the section that was removed. The face is chosen per CONNECTION from the caller identity (an
7592
+ explicitly listed OPERATOR_PRINCIPALS member; an EMPTY list means NOBODY). Only the LIVE stream forks —
7593
+ the durable ledger always stores the stripped form, so the deployment-level truth is read at
7594
+ GET /v1/diagnostics/wiring.
7595
+ allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
7596
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
7597
+ required: [type]
7598
+ properties:
7599
+ type: { const: wiring_manifest }
7600
+ schemaVersion: { type: integer }
7601
+ leg:
7602
+ type: object
7603
+ additionalProperties: false
7604
+ properties: { kind: { type: string, description: 'root | child | resume. Open on read.' } }
7605
+ ask:
7606
+ type: object
7607
+ additionalProperties: false
7608
+ description: 'Approval seam: how it is wired, where it came from, and what it effectively resolves to.'
7609
+ properties:
7610
+ form: { type: string }
7611
+ provenance: { type: string, description: 'spec | deps. Open on read.' }
7612
+ effective: { type: string }
7613
+ question:
7614
+ type: object
7615
+ additionalProperties: false
7616
+ description: >
7617
+ Question channel — three-valued (wired / absent / stripped_bg_lane, the last meaning the engine
7618
+ stripped a bg child per-request face). `interactiveToolsWithoutDeliveryFace` is the composition-lie flag.
7619
+ properties:
7620
+ wired: { type: string }
7621
+ provenance: { type: string }
7622
+ interactiveToolsWithoutDeliveryFace: { type: boolean }
7623
+ interaction:
7624
+ type: object
7625
+ additionalProperties: false
7626
+ properties: { posture: { type: string, description: 'interactive | headless | absent. Open on read.' } }
7627
+ elicit:
7628
+ type: object
7629
+ additionalProperties: false
7630
+ properties:
7631
+ seamWired: { type: boolean }
7632
+ serversOptedIn: { type: integer }
7633
+ parkLane:
7634
+ type: object
7635
+ additionalProperties: false
7636
+ description: >
7637
+ Durable park lane: capability vs EFFECTIVE policy. `effective` is THREE-VALUED — `"unresolved"`
7638
+ means the engine has not decided yet; never fold it to false.
7639
+ properties:
7640
+ capable: { type: boolean }
7641
+ effective: { oneOf: [{ type: boolean }, { type: string }] }
7642
+ reasons: { type: array, items: { type: string } }
7643
+ checkpointDurability: { type: string }
7644
+ session:
7645
+ type: object
7646
+ additionalProperties: false
7647
+ properties: { store: { type: string } }
7648
+ fleet:
7649
+ type: object
7650
+ additionalProperties: false
7651
+ properties:
7652
+ backgroundAgentStore: { type: boolean }
7653
+ hostChildEventSink: { type: boolean }
7654
+ governance:
7655
+ type: object
7656
+ additionalProperties: false
7657
+ description: 'OPERATOR face only — presence of the four deployment governance faces. Absent on every tenant stream.'
7658
+ properties:
7659
+ audience: { type: string, enum: [operator] }
7660
+ lockedConfig: { type: boolean }
7661
+ compliance: { type: boolean }
7662
+ memoryAdmission: { type: boolean }
7663
+ retention: { type: boolean }
7664
+ configFingerprint:
7665
+ type: string
7666
+ description: 'OPERATOR face only — unsalted sha256 prefix (16 hex) over this leg whole manifest.'
7667
+ eventId: { type: string }
7668
+ parentToolCallId: { type: string }
7669
+ sourceTaskId: { type: string }
7670
+ bgAgentId: { type: string }
7342
7671
  Event_workspace_changed:
7343
7672
  type: object
7344
7673
  description: >
@@ -7705,6 +8034,313 @@ components:
7705
8034
  enum: [allowed, denied, expired]
7706
8035
  description: '"tool_approval_complete" only. `expired` = TTL/abort/disconnect — a fail-closed DENY the shell should render as such, not as "still pending".'
7707
8036
 
8037
+ # ── design/172 流内审批协议(#151;UNRELEASED,候 server 7.3.0 + STREAM_APPROVAL_ENABLED,默认 OFF)──
8038
+ # 与上面的 ToolApprovalFrame 是**并行加帧,不替换**:开关打开时同一只 ask 出两帧(先 tool_approval,
8039
+ # 后 approval_request),两帧同带 `approvalId` ⇒ 消费端按它去重、优先渲染新帧。
8040
+ ApprovalRequestFrame:
8041
+ type: object
8042
+ description: >
8043
+ IN-STREAM approval card (SSE, `type` IS the event name; design/172 §3.1). NOT an AgentEvent arm — it
8044
+ interleaves on the live token stream like tool_approval/question/elicitation. Settle it via
8045
+ POST /v1/tasks/{taskId}/asks/{askId}/decision.
8046
+
8047
+ WINDOW: `expiresAtMs` is cast ONCE and never recomputed; `expiresInMs` = max(0, expiresAtMs - serverNowMs)
8048
+ and is therefore MONOTONICALLY DECREASING across replays — a reconnect NEVER renews the window.
8049
+
8050
+ REPLAY: on a new connection the server replays every STREAM_PENDING card as a PREAMBLE, and that preamble
8051
+ is the shell's FULL RECONCILIATION BASELINE — any local card not in it must be dropped (revoke frames can
8052
+ be lost across a process boundary; the preamble is the structural compensation). Historical approval
8053
+ frames replayed from the durable events tail are for TIMELINE RENDERING ONLY, not a card-set baseline
8054
+ (their `expiresInMs` is the value frozen when the frame was minted). Preamble frames carry no SSE `id:`
8055
+ and take no part in the Last-Event-ID cursor.
8056
+
8057
+ UNKNOWN SHAPES ARE NEVER AUTO-DENIED: an unknown schemaVersion/kind or an unrenderable card gets a
8058
+ GENERIC card (safety metadata + manual approve/deny); if it cannot be shown, keep the frame and tell the
8059
+ operator, letting the window fall through to park. Folding "I don't understand it" into a refusal is the
8060
+ deadlock this protocol exists to remove.
8061
+
8062
+ NOT EVERY ASK HAS A CARD: the non-streaming POST /v1/tasks (no delivery plane) and council/team subtask
8063
+ legs produce none; an adhoc (non-durable) sync stream's taskId is one-shot and has no replay handle; a
8064
+ background child's ask arriving after the host run ended is NOT thrown at the dead stream — it parks onto
8065
+ the durable gate (the existing inbox surface).
8066
+ required: [type, schemaVersion, askId, taskId, kind, card, expiresInMs, expiresAtMs, serverNowMs, approvalId]
8067
+ additionalProperties: true
8068
+ properties:
8069
+ type: { type: string, enum: [approval_request] }
8070
+ schemaVersion:
8071
+ type: integer
8072
+ enum: [1]
8073
+ description: >
8074
+ Frame shape version. THIS SCHEMA DESCRIBES v1 ONLY — a frame carrying any other version is not this
8075
+ shape; read it as ApprovalFrameEnvelope and render the generic card. NEVER auto-deny.
8076
+ askId:
8077
+ type: string
8078
+ description: >
8079
+ The DURABLE identity (derived from the five-tuple sourceTaskId/runId/toolCallId/legKey/
8080
+ parentToolCallId). Dismissal, decision and reconciliation all key on THIS. Deduplicate by it (the
8081
+ same frame may be delivered repeatedly). VOID AFTER A PARK and never reused across legs — a resume
8082
+ re-asks with a NEW askId, so "the thing you just approved may be asked again" is normal and the
8083
+ shell must explain it.
8084
+ taskId: { type: string, description: 'wire run id — the {taskId} segment of the decision path.' }
8085
+ kind:
8086
+ type: string
8087
+ enum: [permission]
8088
+ description: >
8089
+ v1 closed set. `content` is a reserved slot, not implemented (design/172 §8③). A frame with any
8090
+ other kind is not this shape — read it as ApprovalFrameEnvelope and render the generic card.
8091
+ card: { $ref: '#/components/schemas/ApprovalCard' }
8092
+ expiresInMs: { type: integer, description: 'Remaining budget = max(0, expiresAtMs - serverNowMs). Monotonically decreasing across replays; the window is never renewed.' }
8093
+ expiresAtMs: { type: integer, description: 'Absolute server-clock deadline. Cast ONCE, never recomputed.' }
8094
+ serverNowMs: { type: integer, description: 'Server clock at mint time — the shell corrects its local clock skew with it.' }
8095
+ approvalId:
8096
+ type: string
8097
+ description: >
8098
+ Bridge to the legacy frame / dedupe key — same value as the parallel tool_approval frame. During the
8099
+ transition window POST /v1/tool-approvals/{approvalId}/respond still works.
8100
+
8101
+ ApprovalCard:
8102
+ type: object
8103
+ description: >
8104
+ The card payload — a NEUTRAL PROJECTION (design/172 §6.7: the server neither restates nor rewrites; the
8105
+ shell owns rendering). UNTRUSTED FOR DISPLAY: `message`/`args`/`sourceAgentName`/`delegation.agentName`
8106
+ are server-redacted and length-bounded but remain model/tool-authored bytes — render only, never re-feed
8107
+ to a model. `args` has a 16 KiB BYTE cap (UTF-8 bytes, not characters); over the cap it is omitted with
8108
+ `argsOmitted: true` and the shell must render an elision stub, NOT conclude "no arguments".
8109
+ required: [toolName, message, risk]
8110
+ additionalProperties: true
8111
+ properties:
8112
+ toolName: { type: string, description: 'Canonical core tool name.' }
8113
+ message: { type: string, description: 'The engine ask text, secret-redacted. UNTRUSTED for display.' }
8114
+ args: { description: 'The pending call''s args, redacted + byte-bounded. Absent (with argsOmitted) when over the cap or unserializable. UNTRUSTED for display.' }
8115
+ argsOmitted: { type: boolean, enum: [true], description: 'Present (true) only when args exceeded the byte cap. Never false.' }
8116
+ 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.' }
8117
+ risk: { $ref: '#/components/schemas/ApprovalRiskAxes' }
8118
+ fromSubagent: { type: boolean, enum: [true], description: 'Present ⇔ the ask comes from a DELEGATED child — the explicit discriminator (a worker cannot forge it).' }
8119
+ sourceTaskId: { type: string, description: 'The child''s core session id — row-join value + fallback discriminator.' }
8120
+ sourceAgentName: { type: string, description: 'The child agent''s display name, redacted. UNTRUSTED.' }
8121
+ delegation: { $ref: '#/components/schemas/ApprovalCardDelegation' }
8122
+
8123
+ ApprovalRiskAxes:
8124
+ type: object
8125
+ description: >
8126
+ The two risk axes plus the coarse safety marker (design/172 §3.1; SDK ApprovalRiskAxes).
8127
+
8128
+ 🔴 THREE-STATE, NOT TWO: true / false / ABSENT = the engine did not annotate it. The shell must render
8129
+ absence as "not annotated" and MUST NOT fold it to false — rendering "unknown" as "safe" is exactly the
8130
+ failure this contract forbids. The axes are optional because core exposes them additively on AskRequest,
8131
+ so an older engine/server omits them entirely.
8132
+
8133
+ `requiresRealApproval` is the ONE marker core always carries ("this is a safety-class ask" — the gate
8134
+ that refuses blanket-allow). It is NOT a substitute for, nor the conjunction of, the two axes: folding
8135
+ "deletes data" and "sends data out" into a single boolean erases the basis for the human decision.
8136
+ required: [requiresRealApproval]
8137
+ additionalProperties: true
8138
+ properties:
8139
+ irreversible: { type: boolean, description: 'The call is irreversible. ABSENT = not annotated — do not read as false.' }
8140
+ egress: { type: boolean, description: 'The call sends data across a boundary. ABSENT = not annotated — do not read as false.' }
8141
+ requiresRealApproval: { type: boolean, description: 'Coarse safety-class marker (core AskRequest.requiresRealApproval). Always present.' }
8142
+
8143
+ ApprovalCardDelegation:
8144
+ type: object
8145
+ description: 'Delegation provenance for a child ask (present only alongside fromSubagent).'
8146
+ required: [parentToolCallId, depth]
8147
+ additionalProperties: true
8148
+ properties:
8149
+ parentToolCallId: { type: string, description: 'The host-side delegating tool-call id — the card''s attribution anchor.' }
8150
+ depth: { type: integer, description: 'Delegation depth (a direct child of the host = 1).' }
8151
+ agentName: { type: string, description: 'Child agent name. UNTRUSTED — spawning-model free text (server-redacted and length-bounded).' }
8152
+
8153
+ ApprovalRevokeFrame:
8154
+ type: object
8155
+ description: >
8156
+ BATCH-LEVEL card revocation (SSE; design/172 §3.3). A batch = the blast radius of ONE run-level abort
8157
+ (every in-flight tool call of that run plus its synchronous delegation subtree). On receipt the shell
8158
+ clears that batch's local cards (whitelist-pick by `askIds`) and waits for the new askIds a resume mints.
8159
+
8160
+ Siblings already DECIDED are NOT in `askIds` (an accepted decision is not affected by revocation).
8161
+ Revoke frames CAN BE LOST (frames produced by the reconciler / orphan legs have no seq to allocate and
8162
+ ride the live plane only) — the structural compensation is the open-stream preamble baseline (see
8163
+ ApprovalRequestFrame), not a redelivery of this frame.
8164
+ required: [type, schemaVersion, batchId, askIds, reason, serverNowMs]
8165
+ additionalProperties: true
8166
+ properties:
8167
+ type: { type: string, enum: [approval_revoke] }
8168
+ schemaVersion: { type: integer, enum: [1], description: 'This schema describes v1 only (see ApprovalFrameEnvelope for the unknown-version read).' }
8169
+ batchId: { type: string }
8170
+ askIds: { type: array, items: { type: string }, description: 'The asks revoked (moved to VOID) by this frame.' }
8171
+ reason:
8172
+ type: string
8173
+ description: >
8174
+ `superseded_by_park` = a sibling''s window expired and the whole batch degraded to the durable park;
8175
+ `aborted` = the run was cancelled. Read as OPEN: an unknown reason is handled as a generic revoke
8176
+ (clear the cards), never specially.
8177
+ serverNowMs: { type: integer }
8178
+
8179
+ AskDecidedConflictBody:
8180
+ allOf:
8181
+ - $ref: '#/components/schemas/ErrorResponse'
8182
+ - type: object
8183
+ properties:
8184
+ decision:
8185
+ allOf: [{ $ref: '#/components/schemas/ApprovalAskDecision' }]
8186
+ description: 'FIRST-DECISION ECHO (`conflict.ask_decided` only): the decision that was accepted.'
8187
+ decidedAtMs: { type: integer, description: 'When the first decision was durably accepted.' }
8188
+ actor:
8189
+ allOf: [{ $ref: '#/components/schemas/ActorAssertion' }]
8190
+ description: 'Who decided. Omitted when the persisted actor fails its schema check (car4 §12-D).'
8191
+ description: >
8192
+ 409 body. `conflict.ask_decided` carries the FIRST-DECISION ECHO; `conflict.ask_idempotency` carries the
8193
+ bare `errorCode` (the key belongs to a different ask — change the key, not the ask).
8194
+ 🔴 `note` is NEVER echoed: the echo is bounded by the requester's visible permissions (design/172 §3.2).
8195
+ Retrying the same decision can never succeed — single-winner CAS means the outcome is already cast.
8196
+
8197
+ AskGoneBody:
8198
+ allOf:
8199
+ - $ref: '#/components/schemas/ErrorResponse'
8200
+ - type: object
8201
+ properties:
8202
+ sessionId: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 1; drive the existing durable-gate flow with it.' }
8203
+ gateBoundCallId: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 2.' }
8204
+ gateBoundInputHash: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 3.' }
8205
+ state: { type: string, enum: [DENIED, VOID], description: 'gone.ask ONLY — the terminal state.' }
8206
+ deniedReason:
8207
+ type: string
8208
+ description: >
8209
+ gone.ask ONLY — attribution, GROUPED BY `state`. `DENIED` carries the single-member deny
8210
+ vocabulary `routing_failure_fail_closed`; `VOID` carries the separate (and growing) void
8211
+ vocabulary, e.g. `adhoc_leg_no_durable_domain`. The two vocabularies never mix.
8212
+ description: >
8213
+ 410 body. THE TWO LEGS ARE DISJOINT: `gone.ask_parked` carries exactly the three gate coordinates,
8214
+ `gone.ask` carries exactly `{state, deniedReason?}`. Routing off field presence across the legs is how a
8215
+ consumer ends up driving the wrong durable gate.
8216
+ 🔴 The checkpoint token is NEVER here: the resume credential is not externally disclosed. Use
8217
+ `sessionId`.
8218
+ 🔴 ATTRIBUTION SPLIT: `DENIED` is produced ONLY by routing failure. An operator refusal is
8219
+ `DECIDED(decision="deny")` and reaches the caller as a 200 or as the 409 first-decision echo — never as
8220
+ `DENIED`. A timeout never produces a deny.
8221
+
8222
+ AskParkingBody:
8223
+ allOf:
8224
+ - $ref: '#/components/schemas/ErrorResponse'
8225
+ - type: object
8226
+ properties:
8227
+ retryAfterSec: { type: integer, description: 'Suggested lower bound before polling again; paired with the Retry-After header.' }
8228
+ description: >
8229
+ 425 body (`parking.ask_retry`). 🔴 NOT "retry may succeed": the window is already closed and the decision
8230
+ can never be accepted. The retry fetches the FINAL ROUTING INFO (poll until the 410 hands over the gate
8231
+ coordinates). Carries NO gate coordinates — they are not written until the batch binds.
8232
+
8233
+ ApprovalFrameEnvelope:
8234
+ type: object
8235
+ description: >
8236
+ The UNKNOWN-SHAPE read of either approval frame — the minimal common shape on the wire, and the typed
8237
+ counterpart of design/172's "unknown shapes are never auto-denied" clause.
8238
+
8239
+ ApprovalRequestFrame and ApprovalRevokeFrame describe **v1 only** (`schemaVersion: 1`, plus
8240
+ `kind: permission` on the request frame). Leaving those discriminants open would let a schema
8241
+ simultaneously say "any version may arrive" and keep promising the complete v1 `card` — which is exactly
8242
+ how a consumer ends up dereferencing `card.risk.requiresRealApproval` on a frame that never promised it.
8243
+ Read the frame as this envelope, narrow on the discriminants, and fall back to the generic card when it
8244
+ does not narrow.
8245
+ required: [type, schemaVersion]
8246
+ additionalProperties: true
8247
+ properties:
8248
+ type: { type: string, description: 'Echo of the SSE event name. Open: a third frame family may appear.' }
8249
+ schemaVersion: { type: integer, description: 'Frame shape version. Anything but 1 means this package does not describe the payload.' }
8250
+
8251
+ ApprovalAskDecision:
8252
+ type: string
8253
+ enum: [approve, deny]
8254
+ description: >
8255
+ The decision on an IN-STREAM approval card (design/172 §3.2). A TWO-word set — deliberately NOT the
8256
+ three-word ToolApprovalDecision: `allow_session` ("remember for this session") is a live-bridge-only
8257
+ semantic and does not exist on POST /v1/tasks/{taskId}/asks/{askId}/decision.
8258
+
8259
+ ActorAssertion:
8260
+ type: object
8261
+ description: >
8262
+ Decision-maker identity assertion (design/171 §5.1; the cross-repo source of truth is
8263
+ `@sema-agent/registry-core` ActorAssertionWire — the SDK holds a value copy).
8264
+
8265
+ NORMATIVE, not a style note: an ACTOR NEVER PARTICIPATES IN THE PERMISSION DECISION — authority always
8266
+ belongs to the principal; this shape exists only for audit/echo/presentation. `id` is ALWAYS server-minted
8267
+ (= the verified principal; an auth-off deployment mints the `"_"` sentinel), and a wire requester may
8268
+ submit ONLY `label`.
8269
+ required: [id]
8270
+ additionalProperties: false # registry-core's ActorAssertionWire is .strict()
8271
+ properties:
8272
+ id: { type: string, minLength: 1, maxLength: 256, description: 'Server-minted principal ("_" sentinel when auth is off). Never submittable by the requester.' }
8273
+ label: { type: string, maxLength: 256, description: 'Unverified display text — the ONLY field a requester may submit (server redacts + bounds it). UNTRUSTED.' }
8274
+ verified: { type: boolean, description: 'Whether the server minted this id under an authenticated context (auth-off ⇒ false, honestly). Never submittable.' }
8275
+ via:
8276
+ type: string
8277
+ enum: [owner, operator]
8278
+ description: >
8279
+ Audit axis for which authorization path accepted the decision: `owner` (row owner) or `operator`
8280
+ (explicit operator seat). CLOSED — verbatim-equal to the owning schema
8281
+ (registry-core ActorAssertionWire). Widening it here without widening the owner is copy drift.
8282
+
8283
+ AskDecisionBody:
8284
+ type: object
8285
+ description: 'Request body of POST /v1/tasks/{taskId}/asks/{askId}/decision (design/172 §3.2).'
8286
+ required: [decision]
8287
+ additionalProperties: false
8288
+ properties:
8289
+ decision: { $ref: '#/components/schemas/ApprovalAskDecision' }
8290
+ updatedInput:
8291
+ description: >
8292
+ Operator-edited args (WHOLE REPLACEMENT, not a patch). Valid on the approve arm only (leniently
8293
+ ignored on deny). Passed through verbatim: core re-runs PreToolUse + the full policy chain on the
8294
+ edit; the server interprets nothing. Whether it was forwarded shows up as AskDecisionAck
8295
+ .updatedInputForwarded.
8296
+ note:
8297
+ type: string
8298
+ maxLength: 2048
8299
+ description: >
8300
+ Audit note. NEVER echoed back on the 409 first-decision echo (the echo is bounded by the requester's
8301
+ visible permissions).
8302
+ idempotencyKey:
8303
+ type: string
8304
+ minLength: 1
8305
+ maxLength: 255
8306
+ description: >
8307
+ Decision idempotency key (<= 255). The same key retried replays the ORIGINAL 200 (no second CAS);
8308
+ the same key against a DIFFERENT askId is a 409 `conflict.ask_idempotency` — deliberately fail-loud,
8309
+ because replaying across asks would both mask a caller key-management bug and violate design/172''s
8310
+ "never reuse an old consent" clause.
8311
+ actor:
8312
+ type: object
8313
+ 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.'
8314
+ required: [label]
8315
+ additionalProperties: false
8316
+ properties:
8317
+ label: { type: string, maxLength: 256 }
8318
+
8319
+ AskDecisionAck:
8320
+ type: object
8321
+ 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).'
8322
+ required: [askId, taskId, decision, decidedAtMs]
8323
+ additionalProperties: false # server-composed envelope (car4 §6), not an engine passthrough — closed on purpose
8324
+ properties:
8325
+ askId: { type: string }
8326
+ taskId: { type: string }
8327
+ decision: { $ref: '#/components/schemas/ApprovalAskDecision' }
8328
+ decidedAtMs: { type: integer, description: 'Server instant at which the decision was durably accepted.' }
8329
+ actor:
8330
+ allOf: [{ $ref: '#/components/schemas/ActorAssertion' }]
8331
+ description: >
8332
+ OPTIONAL: when the persisted decision_actor fails its schema check the server OMITS this field
8333
+ rather than answering 500 (car4 §12-D). Never dereference `actor.id` without a presence check.
8334
+ updatedInputForwarded:
8335
+ type: boolean
8336
+ enum: [true]
8337
+ description: >
8338
+ Present (true) when the approve carried `updatedInput` AND a live in-process entry was actually
8339
+ settled with it (the server forwarded the edited args to the engine). 🔴 TRUE OR ABSENT, NEVER FALSE
8340
+ — so "no live entry (pure durable / another replica)" and "no edit sent" are indistinguishable on the
8341
+ wire. An idempotent replay does NOT promise byte-identity on this field: it is a function of THIS
8342
+ replica's live state and is not persisted.
8343
+
7708
8344
  QuestionFrame:
7709
8345
  type: object
7710
8346
  description: >
@@ -8358,6 +8994,107 @@ components:
8358
8994
  passRate: { type: number }
8359
8995
  meanCostMicroUsd: { type: number }
8360
8996
 
8997
+ WiringManifest:
8998
+ type: object
8999
+ description: >
9000
+ core's assembly self-evidence manifest (core >=5.14.0 `WiringManifest`). TWO HALVES, ONE shape (a
9001
+ consumer must line the two faces up, so they are not split into separate types) — but which keys
9002
+ belong to which half is DETERMINATE, not "usually": `leg`, `ask.effective` and `configFingerprint`
9003
+ are EFFECTIVE-half only (the live `wiring_manifest` event) and the STATIC half
9004
+ (GET /v1/diagnostics/wiring) NEVER mints them; every other section, plus `governance`, is always
9005
+ present on the static half. Fields stay optional because core may add or drop sections and that
9006
+ must not hard-break a client — read by half, and never wait on a key the half never sends.
9007
+ 🔴 `governance` and `configFingerprint` appear ONLY on an operator-scoped face. A tenant stream
9008
+ gets neither — and NOT just the section: core hashes the WHOLE manifest unsalted and governance is
9009
+ four booleans, so the fingerprint alone would let a tenant brute-force sixteen combinations against
9010
+ everything else it already has and recover the section that was removed.
9011
+ additionalProperties: true
9012
+ properties:
9013
+ schemaVersion: { type: integer }
9014
+ leg:
9015
+ type: object
9016
+ description: 'Which leg emitted it (effective half only; the static half has no leg identity).'
9017
+ properties:
9018
+ kind: { type: string, enum: [root, child, resume] }
9019
+ ask:
9020
+ type: object
9021
+ description: 'Approval seam: wired form, which seat it came from, and (effective half) what it adjudicates to.'
9022
+ properties:
9023
+ form: { type: string, enum: [callback, allow, deny, absent] }
9024
+ provenance: { type: string, enum: [spec, deps] }
9025
+ effective: { type: string, enum: [human_reachable, auto_allow, auto_deny, park_only, unresolved] }
9026
+ question:
9027
+ type: object
9028
+ description: 'Question channel; `interactiveToolsWithoutDeliveryFace` is the composition-lie flag (interactive tools mounted with no delivery face).'
9029
+ properties:
9030
+ wired: { type: string, enum: [wired, absent, stripped_bg_lane] }
9031
+ provenance: { type: string, enum: [spec, deps] }
9032
+ interactiveToolsWithoutDeliveryFace: { type: boolean, enum: [true] }
9033
+ interaction:
9034
+ type: object
9035
+ properties:
9036
+ posture: { type: string, enum: [interactive, headless, absent] }
9037
+ elicit:
9038
+ type: object
9039
+ properties:
9040
+ seamWired: { type: boolean }
9041
+ serversOptedIn: { type: integer }
9042
+ parkLane:
9043
+ type: object
9044
+ description: 'Durable park lane. `effective` is THREE-VALUED — "unresolved" means the engine has not decided yet; never fold it to false.'
9045
+ properties:
9046
+ capable: { type: boolean }
9047
+ effective: { oneOf: [{ type: boolean }, { type: string, enum: [unresolved] }] }
9048
+ reasons: { type: array, items: { type: string } }
9049
+ checkpointDurability: { type: string, enum: [declared_durable, process_local] }
9050
+ session:
9051
+ type: object
9052
+ properties:
9053
+ store: { type: string, enum: [declared_durable, process_local] }
9054
+ fleet:
9055
+ type: object
9056
+ properties:
9057
+ backgroundAgentStore: { type: boolean }
9058
+ hostChildEventSink: { type: boolean }
9059
+ governance:
9060
+ type: object
9061
+ description: 'OPERATOR-AUDIENCE section (presence of the four deployment governance faces). Absent on every tenant-readable face.'
9062
+ properties:
9063
+ audience: { type: string, enum: [operator] }
9064
+ lockedConfig: { type: boolean }
9065
+ compliance: { type: boolean }
9066
+ memoryAdmission: { type: boolean }
9067
+ retention: { type: boolean }
9068
+ configFingerprint:
9069
+ type: string
9070
+ description: 'Unsalted sha256 prefix (16 hex) over the whole manifest. Effective half + operator face only.'
9071
+
9072
+ ServerWiringGates:
9073
+ type: object
9074
+ description: >
9075
+ The server's OWN assembly predicates (the half core's manifest does not cover).
9076
+ required: [streamApproval, durableApproval, checkpointStore]
9077
+ additionalProperties: false
9078
+ properties:
9079
+ streamApproval:
9080
+ type: string
9081
+ description: >
9082
+ design/172 in-stream approval protocol: "active", else the FIRST unsatisfied conjunct of the
9083
+ same five-way predicate that gates `capabilities.streamApproval` and the decision endpoint's
9084
+ 501 — so the diagnostics page and the consumer-facing behavior cannot disagree.
9085
+ enum: [active, no_tool_approval, protocol_disabled, no_backend, volatile_ask_ledger, no_park_facility]
9086
+ durableApproval: { type: boolean, description: 'DURABLE_APPROVAL is on.' }
9087
+ checkpointStore: { type: boolean, description: 'A checkpoint store is wired (= the park facility exists).' }
9088
+
9089
+ WiringDiagnostics:
9090
+ type: object
9091
+ description: 'GET /v1/diagnostics/wiring response (operator-only). No per-leg history by design.'
9092
+ required: [static, serverGates]
9093
+ additionalProperties: false
9094
+ properties:
9095
+ static: { $ref: '#/components/schemas/WiringManifest' }
9096
+ serverGates: { $ref: '#/components/schemas/ServerWiringGates' }
9097
+
8361
9098
  SendfileLinkRow:
8362
9099
  type: object
8363
9100
  description: >