@sema-agent/sdk 11.1.0 → 11.2.1

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
@@ -62,6 +62,14 @@ openapi: 3.1.0
62
62
  # `test/live/route-existence.test.ts` (a planned path must have NO handler in the pinned
63
63
  # server artifact). Both exist because `POST /v1/tasks/{taskId}/asks/{askId}/decision` kept
64
64
  # this label for two server releases after it went live.
65
+ # ahead — implemented server-side, but in a version NEWER THAN THE PINNED ARTIFACT this repo's contract
66
+ # suite runs against (the SDK ships the verb in the same train — "the SDK is never behind the
67
+ # server"). DISTINCT FROM `planned`, which claims the server has NOT implemented it: an `ahead`
68
+ # face is real, has a named server version, and HAS a verb here calling it. Two gates police the
69
+ # pair so neither word can hide the other — `test/live/route-existence.test.ts` (an `ahead` path
70
+ # must have NO handler in the pinned artifact; the day it appears, flip this label to `live`/
71
+ # `gated`) and `spec-path-drift-gate` (every `ahead` path must be one the SDK actually calls —
72
+ # an `ahead` nobody calls is a mislabeled `planned`). It EXPIRES the same way `planned` does.
65
73
  # gated — implemented but off by a server deploy flag (e.g. leader, STREAM_APPROVAL_ENABLED); answers
66
74
  # 501 where the flag/deps are unmet. Says nothing about the flag's DEFAULT — that can differ
67
75
  # per server version, so read the matching `capabilities` bit, never this label.
@@ -230,6 +238,170 @@ paths:
230
238
  application/json:
231
239
  schema: { $ref: '#/components/schemas/ErrorResponse' }
232
240
 
241
+ # ── S-481(server >= 7.93.0;core 7.26.0 `probeMcpServers`)—— 无 run 可读的逐台 MCP 状态面,两条口一套词表 ──
242
+ /v1/capabilities/mcp:
243
+ parameters:
244
+ - $ref: '#/components/parameters/PrincipalHeader'
245
+ get:
246
+ tags: [metrics]
247
+ operationId: mcpCapabilities
248
+ x-status: live # server 7.93.0 routes/capabilities.ts `capability-mcp`(钉版 7.93.0 产物在;11.2.0 时曾标 ahead);SDK client.mcpCapabilities() 消费。
249
+ summary: Per-server MCP status for THIS deployment's own declared servers — dialed now, with no run.
250
+ description: >
251
+ What this deployment's OWN configured MCP servers answer RIGHT NOW, one row per declaration. It exists
252
+ because a session that never started a run had no face at all: `capabilities.mcp` is a single boolean, so a
253
+ shell's one-shot command had to dial and invent its own words — and then disagreed with the interactive
254
+ lane about the same server (a command line reporting Failed while that server's tools worked in a session).
255
+
256
+ 🔴 THE ROWS ARE NOT MINTED HERE. `servers[]` is the engine's `probeMcpServers` entries through the SAME
257
+ projection a prepared leg pushes onto `wiring_manifest.mcp[]` — same mint point, same failure vocabulary.
258
+ So a row here and a row in that run are the same KIND of thing, not two things that look alike; every
259
+ clause of `WiringManifestMcpEntry` holds verbatim, including the deliberate ABSENCE of the engine's
260
+ `error` free text (the actionable cause is `errorCode`).
261
+
262
+ 🔴 VISIBLE SET = THIS DEPLOYMENT'S DEFAULT SCENARIO (the same filter `resolveSpec` uses), NOT "what that
263
+ run would actually mount": on a deployment whose scenarios carry different MCP labels, or that decides
264
+ scenarios per principal, the two differ. There is no `?scenario=` on this face today — ask a run's own
265
+ manifest for another scenario. Per-principal authorization is decided at the MCP server by the injected
266
+ `principalHeader`, not filtered here.
267
+
268
+ 🔴 ZERO DECLARATIONS = `200` with `servers: []` (an honest empty face — NOT a 404, NOT a 501). Probing is a
269
+ REAL DIAL, so the face is rate-limited per caller identity (12/60s per replica) with a 30s reuse window
270
+ (`ttlSec`; a cache hit does not count against the limit and returns the ORIGINAL `probedAt`). Gate on
271
+ `capabilities.mcpProbe` — absent means an older worker where this path does not exist (404
272
+ `not_found.route`), not "this deployment has no MCP".
273
+ responses:
274
+ '200':
275
+ description: 'The per-server readings (see `McpProbeFace`); an empty `servers` array = this deployment declared none.'
276
+ content:
277
+ application/json:
278
+ schema: { $ref: '#/components/schemas/McpProbeFace' }
279
+ '401':
280
+ description: >
281
+ THREE codes land here and they do NOT share one typed class, so branch on `errorCode`:
282
+ `auth.unauthorized` (the worker-wide service-credential gate, before this route),
283
+ `auth.principal_required` (the principal header is absent while `REQUIRE_PRINCIPAL` is on — same gate as
284
+ `GET /v1/sessions/{id}/mcp`), and `principal_unverified` (the header WAS sent and failed verification —
285
+ a DIFFERENT action: fix the token/signing, not the header). A consumer that only handles the `auth.*`
286
+ family silently misses the third.
287
+ content:
288
+ application/json:
289
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
290
+ '429': { $ref: '#/components/responses/RateLimited' } # limit.rate_exceeded — 12/60s per identity, OR the replica-wide in-flight cap (32, ACROSS identities: one caller can be refused because of another). Retry-After and the body's `retryAfterSec` are the same value; the body's absence means no header either
291
+ '500':
292
+ description: >
293
+ errorCode `internal.error` — the engine refused THIS DEPLOYMENT'S declaration SET as a whole (the one
294
+ documented cause: two servers would mount under the same tool-namespace prefix, refused BEFORE any
295
+ transport is touched). 🔴 WHOEVER WROTE THE DECLARATIONS OWNS THE ERROR: on this door they are the
296
+ deployment's, so this machine's configuration is broken and a 5xx is the honest direction — folding it
297
+ into a 4xx would report an operator's mistake as the caller's. The cause is NOT echoed (it comes from
298
+ the declaration set itself); it is in this worker's logs. Retrying does not change it.
299
+ content:
300
+ application/json:
301
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
302
+ '503':
303
+ description: >
304
+ errorCode `state.mcp_probe_incomplete` — the WHOLE walk did not settle within this face's bound
305
+ (derived from the declaration count: `ceil(n / 8) x 10s + 4s`, so 1 server = 14s and 32 = 44s). 🔴 THIS
306
+ FACE HAS NO `degraded` ARM ON PURPOSE: a probe that cannot answer must not return a normal-looking
307
+ empty list, which is exactly the false answer it exists to remove. A retry MAY succeed. ⚠️ The
308
+ abandoned dial can outlive the request (the engine's dial does not own cancellation).
309
+ headers:
310
+ Retry-After: { schema: { type: integer }, description: 'Suggested wait; a HINT, not a promise.' }
311
+ content:
312
+ application/json:
313
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
314
+
315
+ /v1/capabilities/mcp/probe:
316
+ parameters:
317
+ - $ref: '#/components/parameters/PrincipalHeader'
318
+ post:
319
+ tags: [metrics]
320
+ operationId: mcpProbe
321
+ x-status: gated # server 7.93.0 routes/capabilities.ts `capability-mcp-probe`(钉版 7.93.0 产物在;11.2.0 时曾标 ahead);门 = mcpInjectionHonored(多租户 501 腿)。SDK client.probeMcp() 消费。
322
+ summary: Probe the CALLER'S OWN MCP declarations (the shell's .mcp.json) — same body shape as a submission.
323
+ description: >
324
+ The caller-declared twin of `mcpCapabilities`: it dials the servers in the request body and answers with
325
+ the SAME body shape, so one consumer rendering reads both faces.
326
+
327
+ 🔴 THE GATE IS `capabilities.mcpInjection` (the SAME predicate the task-submission leg uses): a
328
+ multi-tenant worker, an administratively locked `mcp` config key, or a compliance posture that denies
329
+ `mcp_servers` each refuse the whole call with 501 `capability.mcp_injection_required`. ⚠️ DELIBERATELY
330
+ UNLIKE the submission leg, which IGNORES an unhonored `mcpServers` (plus a user-facing notice) — a probe
331
+ asks "can I use these", so the answer must be loud.
332
+
333
+ 🔴 THE BODY IS CLOSED AND A BAD ENTRY REFUSES THE WHOLE LIST. Top level accepts `mcpServers` only; entry
334
+ keys and both `transport` key sets are closed (the vocabulary is pinned to the engine's `McpServerSpec`
335
+ in both directions). An out-of-vocabulary key is 400 `request.body_shape` with `unknownKeys` naming the
336
+ KEY PATHS verbatim (`mcpServers[0].transport.header`); a bad value (a non-http(s) url, a `transport.kind`
337
+ outside the closed set, a `contentOrigin` outside its vocabulary, more than 32 declarations) is 400
338
+ `request.field_invalid`. ⚠️ Again unlike the submission leg, which names and DROPS a bad entry while
339
+ mounting its siblings: quietly removing the problem entry and returning a good-looking list is the false
340
+ answer this face exists to remove. A refusal may echo a url, always through the display-safe mint point
341
+ (no userinfo on the wire); a SUCCESS body carries no url at all (the row shape has no `url`/`headers`/`env`).
342
+
343
+ Rate limit, reuse window and the in-flight cap are shared with `mcpCapabilities` — see that operation.
344
+ requestBody:
345
+ required: true
346
+ content:
347
+ application/json:
348
+ schema:
349
+ type: object
350
+ description: >
351
+ The declaration list to probe — the SAME shape a task submits (`TaskRequest.mcpServers`), which is
352
+ why the key has that name and the element is the very same schema. Closed: any other top-level key
353
+ is a 400 `request.body_shape`.
354
+ required: [mcpServers]
355
+ additionalProperties: false
356
+ properties:
357
+ mcpServers:
358
+ type: array
359
+ maxItems: 32
360
+ items: { $ref: '#/components/schemas/McpServerSpec' }
361
+ description: 'At most 32; index-aligned with `McpProbeFace.servers`.'
362
+ responses:
363
+ '200':
364
+ description: 'The per-server readings — the SAME body as `mcpCapabilities` (see `McpProbeFace`).'
365
+ content:
366
+ application/json:
367
+ schema: { $ref: '#/components/schemas/McpProbeFace' }
368
+ '400': { $ref: '#/components/responses/BadRequest' } # request.body_shape (closed-key refusal; body carries `unknownKeys` = the offending KEY PATHS) / request.field_invalid (bad value, over the 32-declaration ceiling, or the engine refused the declaration SET — two servers under one tool-namespace prefix; that cause is NOT echoed) / request.rejected (the body is not parseable JSON — the status-derived coarse code from the body READER, which runs before the closed-key door, so it is NOT `request.body_shape`)
369
+ '401':
370
+ description: >
371
+ Same three codes as `mcpCapabilities`, same reason to branch on `errorCode`: `auth.unauthorized`
372
+ (service-credential gate), `auth.principal_required` (header absent under `REQUIRE_PRINCIPAL`), and
373
+ `principal_unverified` (header sent, verification failed — fix the token, not the header). They do not
374
+ share one typed class.
375
+ content:
376
+ application/json:
377
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
378
+ '413':
379
+ description: >
380
+ errorCode `request.payload_too_large` — the request body is over this worker's cap. It comes from the
381
+ body READER (before this route's closed-key door), so it carries no `unknownKeys`. Shorten the
382
+ declaration list and resend; resending the same bytes never succeeds.
383
+ content:
384
+ application/json:
385
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
386
+ '429': { $ref: '#/components/responses/RateLimited' } # limit.rate_exceeded — per-identity window OR the replica-wide in-flight cap; see mcpCapabilities
387
+ '501':
388
+ description: >
389
+ errorCode `capability.mcp_injection_required` — this deployment does not honor caller-supplied MCP
390
+ declarations (multi-tenant, or `mcp` is a locked config key, or the compliance posture denies
391
+ `mcp_servers`; the refusal names which). Read `capabilities.mcpInjection` BEFORE asking — do not
392
+ trial-by-501. `mcpCapabilities` (this deployment's OWN declared servers) stays available either way.
393
+ content:
394
+ application/json:
395
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
396
+ '503':
397
+ description: >
398
+ errorCode `state.mcp_probe_incomplete` — identical to `mcpCapabilities`'s 503 (see there).
399
+ headers:
400
+ Retry-After: { schema: { type: integer }, description: 'Suggested wait; a HINT, not a promise.' }
401
+ content:
402
+ application/json:
403
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
404
+
233
405
  /v1/tasks:
234
406
  parameters:
235
407
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -6722,6 +6894,18 @@ components:
6722
6894
  ⚠️ SCOPE: the A2A inbound face (`message/send`) does NOT carry this key — that leg's submit body is
6723
6895
  rebuilt server-side from the protocol message as a CLOSED four-field set, so putting the key in an
6724
6896
  A2A message is INERT and does not error. An A2A peer that wants a zero-tool run routes by skill.
6897
+ approverPosture:
6898
+ type: string
6899
+ enum: [bypass]
6900
+ description: >
6901
+ (server >= 7.93.0, core 7.26.0 #1000; client-core CC-116 assembly seat) The HOST's declaration of its
6902
+ APPROVER POSTURE for this run — "I answer these approval cards myself". ONE word (`bypass`); the closed set is
6903
+ owned by the engine (`ApproverPosture`), this schema only mirrors the wire spelling. 🔴 It authorizes NOTHING and
6904
+ is NOT a gate: the engine only renders it to the model, no verdict leg reads it. Not the same thing as
6905
+ `permissionMode: bypassPermissions` (that picks the GATE's shape and can only tighten); the two must not be derived
6906
+ from each other. The server passes it through verbatim with zero judgement; an unknown word is refused LOUDLY by
6907
+ the engine (`config.*` family). Servers <= 7.92.2 treat it as an unknown top-level key (400 `request.body_shape`,
6908
+ listed in `unknownKeys`) — trial-by-400 is the probe path.
6725
6909
  deferTools:
6726
6910
  type: array
6727
6911
  items: { type: string }
@@ -8047,6 +8231,20 @@ components:
8047
8231
  projectContext: { type: boolean, description: "Project-context injection is wired." }
8048
8232
  mcp: { type: boolean, description: "MCP servers can be attached." }
8049
8233
  mcpInjection: { type: boolean, description: "Per-request MCP injection is accepted." }
8234
+ mcpProbe:
8235
+ type: boolean
8236
+ description: >
8237
+ server >= 7.93.0 (S-481) — the no-run per-server MCP status face is present: `GET /v1/capabilities/mcp`
8238
+ (this deployment's OWN declared servers) and `POST /v1/capabilities/mcp/probe` (the CALLER'S
8239
+ declarations). ALWAYS TRUE where it exists, for the same reason as `mcp`: the GET has no 501 arm
8240
+ (zero declarations answer an honest empty face), so "says yes" iff "the route works" is structural.
8241
+ PRESENT-IFF carries the version: ABSENT = an older worker with no such face (fall back to the
8242
+ session replay leg `McpStatusPanel.lastLegMcp`, or show the human the real cause row).
8243
+ 🔴 TWO BITS, TWO QUESTIONS — DO NOT MERGE: whether the CALLER-declared door works is answered by
8244
+ `mcpInjection` (the same `mcpInjectionHonored` predicate IS that route's gate; multi-tenant, a
8245
+ locked `mcp` key, or a compliance posture denying `mcp_servers` each yield 501
8246
+ `capability.mcp_injection_required`). So `mcpProbe: true` beside `mcpInjection: false` is the
8247
+ NORMAL multi-tenant reading, not a contradiction.
8050
8248
  mcpElicitation: { type: boolean, description: "MCP elicitation round-trips are supported." }
8051
8249
  askUserQuestion: { type: boolean, description: "AskUserQuestion gate is available (durable when approvals are)." }
8052
8250
  toolApproval: { type: boolean, description: "Tool-approval gating is active." }
@@ -12423,6 +12621,74 @@ components:
12423
12621
  WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (and only here) — see the
12424
12622
  enum note above.
12425
12623
 
12624
+ McpProbeFace:
12625
+ type: object
12626
+ description: >
12627
+ The body of the no-run per-server MCP status face — BYTE-FOR-BYTE THE SAME for `mcpCapabilities` (GET) and
12628
+ `mcpProbe` (POST), so one consumer rendering reads both (S-481, server >= 7.93.0).
12629
+
12630
+ 🔴 THE ROWS ARE NOT MINTED BY THIS FACE: `servers[]` is `WiringManifestMcpEntry` — the engine's
12631
+ `probeMcpServers` entries through the SAME projection a prepared leg pushes onto `wiring_manifest.mcp[]`.
12632
+ 🔴 `servers[]` IS INDEX-ALIGNED with the declaration list (same order, same length; engine contract
12633
+ `mcp.probe.index_aligned`): a failed, timed-out or never-dialed server STILL HOLDS ITS POSITION. Pair it
12634
+ with your own declaration list BY INDEX, never by `name` — names are not guaranteed unique.
12635
+ 🔴 NO `degraded` ARM, DELIBERATELY (unlike `McpStatusPanel.degraded`): a probe that cannot answer returns a
12636
+ LOUD 503 `state.mcp_probe_incomplete`, never a normal-looking empty list. ⚠️ `servers: []` is a different
12637
+ thing entirely — an honest empty face (this deployment declared nothing).
12638
+ required: [probedAt, ttlSec, servers]
12639
+ additionalProperties: false
12640
+ properties:
12641
+ probedAt:
12642
+ type: string
12643
+ description: 'WHEN this reading is from (ISO). 🔴 A cache hit returns the ORIGINAL instant — a reused answer pretending to be fresh is a false fact.'
12644
+ ttlSec:
12645
+ type: integer
12646
+ description: 'How long this reading may be reused (seconds; 30s per identity + declaration set server-side). A hit inside the window does NOT count against the rate limit.'
12647
+ servers:
12648
+ type: array
12649
+ items: { $ref: '#/components/schemas/WiringManifestMcpEntry' }
12650
+ description: 'One row per declaration, SAME ROW SHAPE as the wiring manifest, index-aligned (see this schema''s description).'
12651
+
12652
+ McpLivenessState:
12653
+ type: string
12654
+ enum: [reachable, unreachable, unknown]
12655
+ description: >
12656
+ The engine's CLOSED three-word answer to ONE question, "can this client complete an exchange with this
12657
+ server" (core `MCP_LIVENESS_STATES`; the vocabulary's single owner is the engine, mirrored here the same way
12658
+ `McpDelivered` is): `reachable` = an exchange COMPLETED (handshake + list, a successful re-dial, or the
12659
+ server itself answered — an MCP-level rejection IS an answer, so it proves reach even though the call
12660
+ failed); `unreachable` = an exchange was attempted and LOST at the transport or the clock (refused, reset,
12661
+ closed, unspawnable, timed out); `unknown` = an exchange was attempted and what came back DOES NOT ANSWER
12662
+ the question (a status some gateway wrote, a non-MCP payload, a thrown value with no structure this table
12663
+ reads).
12664
+ 🔴 ABSENCE OF THE WHOLE `liveness` RECORD is a FOURTH reading and is NOT one of these words — see
12665
+ `WiringManifestMcpEntry.liveness`. Do not fold an absent record into `unknown`.
12666
+
12667
+ McpServerLiveness:
12668
+ type: object
12669
+ description: >
12670
+ ONE liveness OBSERVATION about one MCP server (core 7.24.3 #874 `McpServerLiveness`). It says what the
12671
+ engine last LEARNED and WHEN — never what is true now, and NEVER a poll: the engine makes no probe of its
12672
+ own, so every record is a by-product of work the leg already did (the dial, a re-dial, a transport close).
12673
+ 🔴 `state` + `observedAt` ARE BOTH LOAD-BEARING — together they are one sentence (what was seen, and when);
12674
+ the server's projection drops the WHOLE record when either half fails its shape check, so a record that
12675
+ arrives has both.
12676
+ 🔴 `errorCode` RIDES `unreachable` ONLY: on `unknown` it is deliberately absent, because the whole point of
12677
+ that word is that the failure did NOT decide reachability — carrying a failure class there invites a
12678
+ consumer to key a reconnect action off evidence that says nothing about reach.
12679
+ required: [state, observedAt]
12680
+ additionalProperties: false
12681
+ properties:
12682
+ state:
12683
+ allOf: [{ $ref: '#/components/schemas/McpLivenessState' }]
12684
+ description: 'WHAT the engine last learned (the closed three-word table — see `McpLivenessState`).'
12685
+ observedAt: { type: integer, description: 'When that observation happened (epoch ms).' }
12686
+ errorCode:
12687
+ type: string
12688
+ description: >
12689
+ The failure class that decided `unreachable` (the engine's `McpFailureKind` word — deliberately NOT
12690
+ enumerated here; the vocabulary's owner is the engine). Absent on the other two words.
12691
+
12426
12692
  McpDelivered:
12427
12693
  type: string
12428
12694
  description: >
@@ -13986,6 +14252,14 @@ components:
13986
14252
  type: integer
13987
14253
  description: 'Window triple (see expiresAtMs): the server clock at FRAME MINT time — the countdown anchor; use it to correct local clock skew.'
13988
14254
  ruleOffersAbsence: { $ref: '#/components/schemas/RuleOffersAbsence' }
14255
+ mandated:
14256
+ type: boolean
14257
+ enum: [true]
14258
+ description: >
14259
+ (server >= 7.93.0, core 7.26.0 #997) This card was MANDATED — a read-station / rule-lane forced ask (a rule can
14260
+ never clear it; an approver's yes cannot release a deny-listed read). Present only when true, literal `true`;
14261
+ absent = an ordinary ask. The durable parked row carries no twin bit: read `ruleOffersAbsence: "mandated"` there
14262
+ (the same fact on the durable face).
13989
14263
  denialLimitFallback: { $ref: '#/components/schemas/DenialLimitFallback' }
13990
14264
  parked:
13991
14265
  type: boolean
@@ -16289,6 +16563,18 @@ components:
16289
16563
  core >= 7.6.0 (S6-B) — the status the endpoint answered with; rides ONLY with
16290
16564
  `errorCode === "http_status"` (401/403 = re-authorize, 5xx = that end is down). ABSENT rather
16291
16565
  than 0.
16566
+ liveness:
16567
+ allOf: [{ $ref: '#/components/schemas/McpServerLiveness' }]
16568
+ description: >
16569
+ core >= 7.24.3 (#874), server >= 7.91.2 — the engine's LAST OBSERVATION of reachability.
16570
+ 🔴 SEPARATE FROM `status`: `status` is the verdict at dial time (the engine freezes it), this is what
16571
+ the connection layer last LEARNED — so `failed` beside `reachable` on one row is a REAL row (the server
16572
+ answered the handshake and answered with a protocol error = alive and misconfigured).
16573
+ 🔴 ABSENCE IS NOT `unknown`: absent = NO USABLE OBSERVATION EXISTS (the leg never reached this server,
16574
+ the declaration was never dialed, an older mint, or the record failed its shape check), while `unknown`
16575
+ is the engine's STRONGER claim (it looked and the answer does not decide reach). Do not infer "was it
16576
+ dialed" from absence. Declared here as of sdk 11.2.0 — the key had been on the wire since server 7.91.2
16577
+ while this row schema stayed closed, so a strict consumer was rejecting a LEGITIMATE response.
16292
16578
 
16293
16579
  ServerWiringGates:
16294
16580
  type: object
@@ -17424,9 +17710,19 @@ components:
17424
17710
  current state with `GET /v1/rules` instead.
17425
17711
 
17426
17712
 
17427
- `unknown` — DOES NOT OCCUR ON THIS DOOR (a read-back that cannot be taken is a 5xx: a write whose
17428
- outcome cannot be told does not answer 200). That is why each arm narrows the shared three-word table
17429
- to its own single word with an `enum`; the table itself is NOT forked.
17713
+ `unknown` — REACHABLE SINCE server 7.93.0: the write LANDED (the engine confirmed the add and handed
17714
+ back a real `rev`) and THE READ-BACK THAT WOULD ANSWER LIVENESS COULD NOT BE TAKEN (the store did not
17715
+ answer). `rule` is ABSENT. 🔴 SAME DISPOSITION AS `no` — DO NOT RETRY (a resend only answers `no-op`);
17716
+ reconcile with `GET /v1/rules`. Up to 7.92.2 this shape answered 500 `internal.error`, byte-identical
17717
+ to the one a PRE-write store failure produced — two opposite dispositions under one answer, which is
17718
+ exactly what these keys exist to separate. ⚠️ A store failure BEFORE the write is a different answer:
17719
+ 503 `state.rule_write_failed` with `committed: "no"` (provably nothing landed ⇒ resending the same body
17720
+ is safe). Same store outage, opposite handling, decided by WHICH SIDE of the write it happened on.
17721
+
17722
+
17723
+ ⇒ THE TWO ROWLESS WORDS SHARE ONE ARM (`RuleWriteResult_rowless`): identical key set, identical
17724
+ disposition; the WORD is what distinguishes "observed gone" from "nobody looked". The shared
17725
+ three-word table is narrowed per arm with an `enum` and is NEVER forked.
17430
17726
 
17431
17727
 
17432
17728
  🔴 READ `stillLive` FIRST, THEN `rule`. The `rule` KEY LIVES ONLY ON THE `yes` ARM — it is not "present
@@ -17441,22 +17737,28 @@ components:
17441
17737
  passed through verbatim). A shell that must span both generations reads an ABSENT `stillLive` AS `yes`
17442
17738
  itself — that is its own judgement about its own deployment, not something this door said.
17443
17739
  oneOf:
17444
- - $ref: '#/components/schemas/RuleWriteResult_yes'
17445
- - $ref: '#/components/schemas/RuleWriteResult_no'
17740
+ - $ref: '#/components/schemas/RuleWriteResult_live'
17741
+ - $ref: '#/components/schemas/RuleWriteResult_rowless'
17446
17742
  # 🔴 两支是**命名 schema**,不是内联形(codex 对抗复审 [medium],验真后修):OAS 3.1 的
17447
17743
  # discriminator 明文**不考虑内联 schema**,而 mapping 指回父联合就等于「选不出臂」——
17448
17744
  # 照它生成/解码的消费方拿不到分支,而普通 oneOf 校验照样绿,坏形因此不可见。
17745
+ # 🔴 **两支按「有没有活行可端」命名**(sdk 11.2.0 改名,codegen 面的名字变了、wire 逐字节不变):
17746
+ # `unknown` 自 server 7.93.0 起可达且与 `no` 同支,一个叫 `_no` 的臂承载两个词就是「名字与它所含的
17747
+ # 一半相反」——本仓明令禁止的那一形(server 侧 `indeterminate`→`failed` 改名同一条理由)。
17748
+ # mapping 必须给**每个**词一个键:少一个词,照 spec 解码的消费端对那个词选不出臂。
17449
17749
  discriminator:
17450
17750
  propertyName: stillLive
17451
17751
  mapping:
17452
- "yes": '#/components/schemas/RuleWriteResult_yes'
17453
- "no": '#/components/schemas/RuleWriteResult_no'
17752
+ "yes": '#/components/schemas/RuleWriteResult_live'
17753
+ "no": '#/components/schemas/RuleWriteResult_rowless'
17754
+ "unknown": '#/components/schemas/RuleWriteResult_rowless'
17454
17755
 
17455
- RuleWriteResult_yes:
17756
+ RuleWriteResult_live:
17456
17757
  description: >
17457
17758
  The LIVE-ROW arm of `rulesWrite`'s 200 (the ordinary outcome): the read-back saw the rule alive, so
17458
17759
  there is a row to hand over. THE ONLY ARM THAT CARRIES `rule`. Never returned on its own — read it
17459
- through `RuleWriteResult`.
17760
+ through `RuleWriteResult`. (Named `RuleWriteResult_yes` up to SDK 11.1.0 — renamed when its sibling
17761
+ started carrying two words; the wire is byte-identical, only codegen names change.)
17460
17762
  type: object
17461
17763
  additionalProperties: false
17462
17764
  required: [status, rev, stillLive, rule]
@@ -17475,7 +17777,7 @@ components:
17475
17777
  stillLive:
17476
17778
  allOf: [{ $ref: '#/components/schemas/RemovalLiveness' }]
17477
17779
  enum: ["yes"]
17478
- description: 'The read-back SAW IT ALIVE — the ordinary outcome, and the only arm that carries `rule`. Same key, same word table as `rulesRevoke`; this door''s value range is narrower (`unknown` is unreachable here), never a second table.'
17780
+ description: 'The read-back SAW IT ALIVE — the ordinary outcome, and the only arm that carries `rule`. Same key, same word table as `rulesRevoke`; since server 7.93.0 the same VALUE RANGE as well (all three words are reachable on this door too), and never a second table.'
17479
17781
  rule:
17480
17782
  allOf: [{ $ref: '#/components/schemas/PersistedRule' }]
17481
17783
  description: >
@@ -17491,12 +17793,16 @@ components:
17491
17793
  a human answered a card, `starter` = a deployment preset, `imported-cc` = a real import) — this
17492
17794
  call wrote nothing, so it rewrites nothing either.
17493
17795
 
17494
- RuleWriteResult_no:
17796
+ RuleWriteResult_rowless:
17495
17797
  description: >
17496
- The ROW-IS-GONE arm of `rulesWrite`'s 200: the write LANDED (`rev` is real) and a concurrent revoke
17497
- reached the row before the read-back. `rule` is ABSENT here BY CONSTRUCTION, and 🔴 RETRYING would
17498
- re-erect a tightening someone just revoked. Never returned on its own — read it through
17499
- `RuleWriteResult`.
17798
+ The NO-LIVE-ROW arm of `rulesWrite`'s 200 — TWO WORDS, ONE KEY SET, ONE DISPOSITION (named
17799
+ `RuleWriteResult_no` up to SDK 11.1.0; renamed at 11.2.0 when `unknown` became reachable, because an arm
17800
+ called `_no` that also carries `unknown` is a name that contradicts half of what it holds).
17801
+ The write LANDED (`rev` is real) and either a concurrent revoke reached the row before the read-back
17802
+ (`no`), or the read-back itself could not be taken (`unknown`, server >= 7.93.0). `rule` is ABSENT here
17803
+ BY CONSTRUCTION, and 🔴 RETRYING IS WRONG FOR BOTH WORDS (it re-erects a tightening someone just
17804
+ revoked, or simply answers `no-op`) — reconcile with `GET /v1/rules`. Never returned on its own — read
17805
+ it through `RuleWriteResult`.
17500
17806
  type: object
17501
17807
  additionalProperties: false
17502
17808
  required: [status, rev, stillLive]
@@ -17511,12 +17817,18 @@ components:
17511
17817
  rev: { type: integer, description: 'The rule bucket''s OCC revision after the call. REAL on this arm — the write landed; only the row''s liveness went the other way.' }
17512
17818
  stillLive:
17513
17819
  allOf: [{ $ref: '#/components/schemas/RemovalLiveness' }]
17514
- enum: ["no"]
17515
- description: >
17516
- The read-back OBSERVED IT GONE: a legitimate concurrent revoke (yours, or an operator''s) landed
17517
- between the engine minting the dot and this leg reading the row back. 🔴 NOT an `indeterminate`
17518
- and NOT a 503: retrying here re-erects a tightening someone just revoked. `rule` is ABSENT on
17519
- this arm by construction.
17820
+ enum: ["no", unknown]
17821
+ description: >
17822
+ WHICH KIND OF "no live row" this is. `no` = the read-back OBSERVED IT GONE: a legitimate concurrent
17823
+ revoke (yours, or an operator''s) landed between the engine minting the dot and this leg reading the
17824
+ row back. `unknown` = THE READ-BACK ITSELF COULD NOT BE TAKEN (the store did not answer; reachable
17825
+ since server 7.93.0, a 500 before that) — nobody observed the liveness, which is a WEAKER statement
17826
+ than `no` and must not be rendered as "gone".
17827
+ 🔴 NEITHER IS an `indeterminate` and NEITHER IS a 503: the engine answered "it wrote" and handed
17828
+ back a real `rev` on both. Retrying is wrong for both (re-erecting a just-revoked tightening, or
17829
+ simply getting `no-op`) — reconcile with `GET /v1/rules`. `rule` is ABSENT on this arm by
17830
+ construction. ⚠️ Do not confuse `unknown` here with 503 `committed: "no"`: that is the SAME store
17831
+ failing BEFORE the write, where nothing landed and resending is safe.
17520
17832
 
17521
17833
  RuleRevokeRequest:
17522
17834
  type: object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "11.1.0",
3
+ "version": "11.2.1",
4
4
  "description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",