@sema-agent/sdk 11.2.1 → 12.0.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.
Files changed (51) hide show
  1. package/README.md +102 -0
  2. package/dist/control-types.d.ts +12 -10
  3. package/dist/control-types.d.ts.map +1 -1
  4. package/dist/control-types.js +1 -1
  5. package/dist/control-types.js.map +1 -1
  6. package/dist/errors.d.ts +9 -1
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +11 -3
  9. package/dist/errors.js.map +1 -1
  10. package/dist/events.d.ts +10 -0
  11. package/dist/events.d.ts.map +1 -1
  12. package/dist/events.js.map +1 -1
  13. package/dist/health.d.ts +118 -6
  14. package/dist/health.d.ts.map +1 -1
  15. package/dist/health.js +51 -1
  16. package/dist/health.js.map +1 -1
  17. package/dist/index.d.ts +13 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +6 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/registry/auth.d.ts +36 -16
  22. package/dist/registry/auth.d.ts.map +1 -1
  23. package/dist/registry/auth.js +17 -4
  24. package/dist/registry/auth.js.map +1 -1
  25. package/dist/registry/errors.d.ts +6 -5
  26. package/dist/registry/errors.d.ts.map +1 -1
  27. package/dist/registry/errors.js +6 -5
  28. package/dist/registry/errors.js.map +1 -1
  29. package/dist/registry/index.d.ts +3 -3
  30. package/dist/registry/index.js +3 -3
  31. package/dist/registry/scopes-effective.d.ts +34 -6
  32. package/dist/registry/scopes-effective.d.ts.map +1 -1
  33. package/dist/registry/scopes-effective.js +2 -2
  34. package/dist/registry/scopes-effective.js.map +1 -1
  35. package/dist/resources/approvals.d.ts +41 -0
  36. package/dist/resources/approvals.d.ts.map +1 -1
  37. package/dist/resources/approvals.js.map +1 -1
  38. package/dist/resources/sessions.d.ts +40 -1
  39. package/dist/resources/sessions.d.ts.map +1 -1
  40. package/dist/resources/sessions.js +48 -0
  41. package/dist/resources/sessions.js.map +1 -1
  42. package/dist/resources/tool-approvals.d.ts +102 -14
  43. package/dist/resources/tool-approvals.d.ts.map +1 -1
  44. package/dist/resources/tool-approvals.js +49 -1
  45. package/dist/resources/tool-approvals.js.map +1 -1
  46. package/dist/types.d.ts +180 -13
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js.map +1 -1
  49. package/openapi.yaml +463 -14
  50. package/package.json +2 -2
  51. package/registry-openapi.yaml +6 -5
package/openapi.yaml CHANGED
@@ -2322,6 +2322,106 @@ paths:
2322
2322
  '422': { $ref: '#/components/responses/UnprocessableEntity' }
2323
2323
  '501': { $ref: '#/components/responses/NotImplemented' }
2324
2324
 
2325
+ /v1/sessions/{sessionId}/background:
2326
+ parameters:
2327
+ - $ref: '#/components/parameters/PrincipalHeader'
2328
+ - in: path
2329
+ name: sessionId
2330
+ required: true
2331
+ schema: { type: string }
2332
+ get:
2333
+ tags: [sessions]
2334
+ operationId: sessionsBackgroundList
2335
+ x-status: live # S-745 (server 7.102.0; core 7.32.0 #1107); SDK sessions.background.
2336
+ summary: The session's background tasks (the exit sheet's "keeps running / will stop" material).
2337
+ description: >
2338
+ S-745 (server >= 7.102.0; core 7.32.0 #1107; contract §16 "embedded-shape contract"; mint
2339
+ `src/http/routes/background.ts`). 200 `{ rows }` — each row is core `TaskRegistry.list()`'s row projected
2340
+ KEY BY KEY by an explicit whitelist (`task_id` renamed `id`; `toolUseId` / `updatedAt` deliberately
2341
+ stripped). Only THIS session's rows: the access tuple is the session's own `{owner: sid, sessionId: sid,
2342
+ scope}` and visibility is core's fail-closed `canAccess` (the session's session-resident shells, the run row
2343
+ whose task id is the session id, workflows this session started, background agents rooted at this
2344
+ session). `retained` rides ONLY when true (core's retain predicate: env / task lane / row mark) — present =
2345
+ "keeps running" on the exit sheet; absent = an `includeRetained:false` stop WILL stop it. `description` is
2346
+ the model's description, falling back to the launch command text (content family: redacted, cut at 500
2347
+ chars). At most 500 rows (core `list()`'s own cap). 🔴 IN-PROCESS: only rows on THIS replica.
2348
+ Zero model work: not billable, not refused by drain. Gates (same family as `mcp/reconnect`): 501
2349
+ `capability.session_ownership_required` (no `sessionStorage.ownerOf`) · 404 `not_found.session` — unknown
2350
+ session AND someone else's session answer the SAME code and SAME message (no existence oracle); an EXPLICIT
2351
+ operator may read any session · 400 `request.path_malformed`. Probe with `capabilities.background.listFace`;
2352
+ ABSENT key = an older server, where this path is 404 `not_found.route` (same status, different code).
2353
+ responses:
2354
+ '200':
2355
+ description: The session's background rows (read the row schema for the `retained` / `status` readings).
2356
+ content:
2357
+ application/json:
2358
+ schema: { $ref: '#/components/schemas/SessionBackgroundList' }
2359
+ '400': { $ref: '#/components/responses/BadRequest' }
2360
+ '401': { $ref: '#/components/responses/Unauthorized' }
2361
+ '404': { $ref: '#/components/responses/NotFound' }
2362
+ '501': { $ref: '#/components/responses/NotImplemented' }
2363
+
2364
+ /v1/sessions/{sessionId}/background/stop:
2365
+ parameters:
2366
+ - $ref: '#/components/parameters/PrincipalHeader'
2367
+ - in: path
2368
+ name: sessionId
2369
+ required: true
2370
+ schema: { type: string }
2371
+ post:
2372
+ tags: [sessions]
2373
+ operationId: sessionsBackgroundStop
2374
+ x-status: live # S-745 (server 7.102.0; core 7.32.0 #1107); SDK sessions.backgroundStop.
2375
+ summary: Stop the session's background tasks — soft, then one grace, then a synchronous hard kill; receipts are complete before 200.
2376
+ description: >
2377
+ S-745 (server >= 7.102.0; contract §16; mint `src/http/routes/background.ts`). The SAME session-level
2378
+ convergence function as the `DELETE /v1/sessions/{sessionId}` cascade: SOFT (core's session anchor SIGTERMs
2379
+ and settles the session's background agents / workflows; every still-running shell row the hard face will
2380
+ revisit gets a per-row soft stop — with `includeRetained:true` the retained live services receive SIGTERM
2381
+ here too) → GRACE (the hard face waits until every row it will revisit is past "its soft-stop instant +
2382
+ `HOST_BG_KILL_GRACE_MS`", default 1000 ms; nothing to wait for ⇒ no wait) → HARD (core
2383
+ `killAllBackgroundShellsNow` — a synchronous process-group SIGKILL; a shell ignoring SIGTERM does not
2384
+ survive it). 🔴 THE RECEIPTS ARE COMPLETE BEFORE THE 200: with background rows the answer is one grace
2385
+ slower — budget the shell's exit wait in seconds.
2386
+ Body `{ includeRetained }` is REQUIRED with NO default (core's rule: the caller states which kind of stop
2387
+ this is): `true` = also stop the explicitly retained live services (the shell's "Exit and stop tasks");
2388
+ `false` = keep them (they come back as `retained_skipped`; `sema kill`'s session-level reap). The body
2389
+ gate is STRICT — absent / non-boolean / an extra key ⇒ 400 `request.body_shape`.
2390
+ Receipts carry SHELL rows only (`background_bash` / `monitor`); `outcome` is core's four words passed
2391
+ through VERBATIM — `killed` (a live process group was reaped now) · `already_gone` (already exited, or
2392
+ already hard-killed — incl. exiting within the grace) · `retained_skipped` (kept by
2393
+ `includeRetained:false`) · `no_pgid` (NO synchronous hard kill landed: remote / sandbox lanes have no
2394
+ host-local process group — e2b / k8s·kata / local-docker / ssh / device / adb always answer it — or win32 /
2395
+ a refused signal; core already sent an ordinary kill ⇒ "MAY STILL BE RUNNING", neither a failure nor ever
2396
+ "stopped").
2397
+ 🔴 CREDENTIAL-GATED (server >= 7.102.0 as released): on a worker with no service credential
2398
+ (`SERVICE_AUTH_TOKEN(S)`) and without `ALLOW_UNAUTHED_WRITES`, this path answers 503
2399
+ `auth.service_token_required` BEFORE routing (same code and sentence as every other mutating door) — the
2400
+ principal header is self-asserted there, and stopping background processes is irreversible.
2401
+ `capabilities.background.exitFaces` does NOT include "credentials ready" (the same family gap as the
2402
+ `memoryCompliance` row) — branch on that code. Other gates as the list path: 501
2403
+ `capability.session_ownership_required` · 404 `not_found.session` (unknown and foreign sessions alike) · 400
2404
+ `request.path_malformed`; this path is rate-limited (429). NOT called by run cancel (Esc): session-resident
2405
+ background shells live across turns (CC parity). A delegated helper's shells hang on the helper's own
2406
+ session and are out of this door's reach. Probe with `capabilities.background.exitFaces`.
2407
+ requestBody:
2408
+ required: true
2409
+ content:
2410
+ application/json:
2411
+ schema: { $ref: '#/components/schemas/SessionBackgroundStopRequest' }
2412
+ responses:
2413
+ '200':
2414
+ description: The receipts (shell rows only). Read `no_pgid` as "may still be running".
2415
+ content:
2416
+ application/json:
2417
+ schema: { $ref: '#/components/schemas/SessionBackgroundStopResult' }
2418
+ '400': { $ref: '#/components/responses/BadRequest' }
2419
+ '401': { $ref: '#/components/responses/Unauthorized' }
2420
+ '404': { $ref: '#/components/responses/NotFound' }
2421
+ '429': { $ref: '#/components/responses/RateLimited' }
2422
+ '501': { $ref: '#/components/responses/NotImplemented' }
2423
+ '503': { $ref: '#/components/responses/Unauthorized' } # `auth.service_token_required` — the credential-gated write door (no service credential and no ALLOW_UNAUTHED_WRITES), before routing.
2424
+
2325
2425
  /v1/sessions/{sessionId}/memory-status:
2326
2426
  parameters:
2327
2427
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -3031,6 +3131,15 @@ paths:
3031
3131
  (REOPENED) and re-appears on `/v1/approvals`. SDK → ParkedResumeStartupError. Client action:
3032
3132
  re-fetch the pending list and decide again, fixing what was rejected. (Before 5.12.0 this shape
3033
3133
  collapsed into the 409 `decide.revive_rejected`.)
3134
+ 🔴 core 7.32.0 (#1110) ADDS A CAUSE (server >= 7.102.0; contract appendix A.1, MIGRATION 7.102.0): an
3135
+ `approve` on a child parked on an INHERITED tool (a tool family the child inherits from the scenario
3136
+ layer — the task tools, WebFetch …) whose redemption cannot hand over the inherited seat ⇒ core refuses
3137
+ BEFORE consuming the approval: the approval stays PENDING and a `deny` still settles it. From server 7.102.0
3138
+ a ONE-LEVEL row (its root session = the parent session, and that root session STILL belongs to this card's
3139
+ tenant) is redeemed with the seat re-resolved from the root session, so it no longer lands here; a DEEPER
3140
+ (grandchild) row, or one whose root session was deleted / changed owner, still does (core #1112 pending) —
3141
+ render it as "this card can only be denied / wait for a server upgrade". Not a regression: 7.31.x children
3142
+ never had those tools.
3034
3143
  content:
3035
3144
  application/json:
3036
3145
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3808,6 +3917,32 @@ paths:
3808
3917
  substitution), the command bytes were unreadable, or this ask belongs to one of the seven
3809
3918
  families that never get offers — so do NOT diagnose the deployment's rule lane from one
3810
3919
  frame or from that one refusal.
3920
+ settledBy:
3921
+ type: string
3922
+ enum: [policy]
3923
+ description: >-
3924
+ server #1016 (>= 7.93.3, core 7.26.2), ADDITIVE — the SETTLEMENT SOURCE of this answer.
3925
+ `"policy"` is a MACHINE REFUSAL, NEVER THE USER'S WORDS: the deployment's own policy answered
3926
+ this card, not a person. ABSENT = today's shape (read as a person's answer).
3927
+
3928
+ Same word, same source and the same three-question rationale as the durable leg's key
3929
+ (`POST /v1/approvals/{sessionId}/decide`, ApprovalDecision.settledBy) — see that schema for
3930
+ the full account. `decision:"deny"` ONLY: an allow-family decision carrying this key is a
3931
+ contradiction, refused BEFORE THE DECISION (400 `request.body_shape`, the same sentence as
3932
+ the durable leg) rather than after — the engine's refusal cannot travel back up this HTTP
3933
+ leg, and by then this door's own side effects (the session allow-all grant, a persisted
3934
+ rule, a DECIDED row that replays as a plain approval) have already landed. Because the
3935
+ refusal precedes the decision, the ask is NOT settled: the card is still open and the same
3936
+ `approvalId` can be answered again. The PARKED late-acceptance arm reads the same key.
3937
+
3938
+ SHAPE, not word: a non-string is a 400. A word core does NOT know travels UNTOUCHED — this
3939
+ door still answers 200 (its 200 only ever meant "your answer was delivered") and the ENGINE
3940
+ refuses it fail-closed, settling as `approver_contract`, NEVER as `policy_refused`: a
3941
+ misspelled word must not be rendered as "the deployment's policy refused".
3942
+
3943
+ No capability bit: an older server ignores unknown keys and answers 200, so the refusal is
3944
+ still read as a person's there. Judge by server version, or read back whether
3945
+ `tool_end.gate.settlement.kind` ever shows `policy_refused`.
3811
3946
  note:
3812
3947
  type: string
3813
3948
  maxLength: 2048
@@ -3828,6 +3963,23 @@ paths:
3828
3963
  NOT strict (hand-written parser, unknown keys are ignored), so an older server answers 200 and
3829
3964
  records nothing. Bit absent = old server, do not offer the field. Bit present ⇒ read the
3830
3965
  per-call truth from the ack's `noteRecorded`.
3966
+ readRootGrant:
3967
+ type: string
3968
+ minLength: 1
3969
+ description: >-
3970
+ S-720 (server >= 7.102.0 / core 7.32.0 #1093), ADDITIVE — "Yes, and add directory": the frame's
3971
+ `readRootCandidate.dir` sent back BYTE FOR BYTE (a STRING — never normalised, cut or composed; not an
3972
+ object). Beside `allow` it also adds that directory to THIS RUN's read directories (read faces only).
3973
+ 🔴 `decision:"allow"` ONLY — on `allow_session` / `deny` the server refuses BEFORE the decision with 400
3974
+ `request.body_shape` (the card stays open; answer the same `approvalId` again). SHAPE gate: a non-empty
3975
+ string of at most core's `READ_ROOT_CANDIDATE_DIR_MAX`, else the same 400 (an over-long value is refused
3976
+ whole, never cut — a cut directory is another directory). The server judges SHAPE, never VALUE: whether
3977
+ it equals the card's string is the ENGINE's call, and the verdict arrives only as an `engine_notice` —
3978
+ `approval.read_root_granted` (the run's read roots really widened) or `approval.read_root_grant_rejected`
3979
+ (nothing changed; the decision stands). The 200 never says which. Reach: THIS RUN only (the ask row has no
3980
+ such column, so replay / failover / durable resume do not carry it) and THIS DOOR only (the durable
3981
+ `/decide` and the in-stream `…/asks/{askId}/decision` doors do not read it). An older server ignores the
3982
+ unknown key and answers 200 with a plain allow (no capability bit yet) — trust the notice.
3831
3983
  responses:
3832
3984
  '200':
3833
3985
  description: Ack (`decision` echoed).
@@ -6532,15 +6684,27 @@ components:
6532
6684
  rewindFiles:
6533
6685
  type: boolean
6534
6686
  description: >
6535
- CC Rewind parity (E18). On a NORMAL turn ⇒ CAPTURE a working-tree snapshot at turn end (per-turn, by leaf;
6536
- required so later rewinds have something to restore). On a resumeAt turn ⇒ RESTORE the tree to resumeAt's
6537
- snapshot ("both"). Needs a fileSnapshotStore wired (capabilities.rewindFiles).
6687
+ RETIRED SPELLING (the whole-tree snapshot epoch; S-15 / design/381, server >= 7.52 — note corrected in sdk
6688
+ 11.4.0, the same stale-note family as `rewindFilesTo`, board [8437] U3). Server `src/http/wire-types.ts`: still
6689
+ accepted on the wire — its capture sense (true, no resumeAt) is a tolerated no-op with a deprecation disclosure;
6690
+ its restore sense (true + resumeAt) is core's TYPED migration rejection naming `restoreFiles` (restore semantics
6691
+ really changed: whole tree ⇒ tracked set; the server never remaps it silently). Send `restoreFiles` (probe
6692
+ `capabilities.restoreFiles`); this key stays declared only for <= 7.51 servers.
6538
6693
  rewindFilesTo:
6539
6694
  type: string
6540
6695
  description: >
6541
- CC Rewind "code"-only (core 1.166.0 TaskSpec.rewindFilesTo). Restore the working tree to this user-message
6542
- entryId's snapshot WITHOUT forking the conversation (no setLeafId). Use instead of resumeAt+rewindFiles when
6543
- the user wants only files reverted. Target = a user-message SessionTreeEntry.id.
6696
+ CC Rewind "code"-only (core 1.166.0 TaskSpec.rewindFilesTo; server "R8"). Converge the files this session
6697
+ edited (the TRACKED set) to the history boundary at a prior USER message WITHOUT forking the conversation (the
6698
+ session leaf is untouched — only the files move). Use instead of resumeAt+restoreFiles when the user wants only
6699
+ files reverted. 🔴 The value is a HANDLE, not an entry id (corrected in sdk 11.4.0, board [8437] U3): the SAME
6700
+ kind of handle as `resumeAt`, resolved through the SAME anchor store — typically the `taskId` from that turn's
6701
+ 202 receipt (the server keys the user-message anchor by the run's taskId). Server `src/boot/resolve-spec.ts:686-697`:
6702
+ `resumeAnchorStore.resolve(sessionId, handle, principal)` → entry id; unknown handle ⇒ 404
6703
+ `rewind_files_to.unknown_event`; no session ⇒ 422 `rewind_files_to.no_session`; no anchor store / no getLeafId ⇒
6704
+ 501. ⚠️ The 501 is NOT the capability's whole predicate: `capabilities.rewindFilesTo` also requires the
6705
+ file-history store (capture face); without it the server does not 501 — core refuses in the run with
6706
+ `rewind.store_unconfigured` — so probe the bit first. Honored ONLY when `resumeAt` is absent. One-shot at submit
6707
+ (resume legs strip it, server >= 7.72.0).
6544
6708
  restoreFiles:
6545
6709
  type: boolean
6546
6710
  description: >-
@@ -7931,6 +8095,40 @@ components:
7931
8095
  not inside the HMAC payload, so accepting it unsigned would be tamperable); 501 without an exemption
7932
8096
  store. On the parked-background-agent leg the grant lands on the child's ROOT/HOST session (the whole
7933
8097
  session tree shares it). The 200 body echoes `rememberApplied` when a store is wired.
8098
+ settledBy:
8099
+ type: string
8100
+ enum: [policy]
8101
+ description: >-
8102
+ server #1016 (>= 7.93.3, core 7.26.2), ADDITIVE — WHO made this decision. `"policy"` is a MACHINE
8103
+ REFUSAL, NEVER THE USER''S WORDS: the deployment''s own policy answered this card (a headless lane —
8104
+ CI, a background batch, a shell refusing by a standing deployment posture). ABSENT = today''s shape
8105
+ (`person`), so an unrevised caller is byte-for-byte unchanged.
8106
+
8107
+ WHY IT EXISTS: without the word, a policy refusal is byte-identical to "a person pressed No", and the
8108
+ model reads "the user doesn''t want to proceed; STOP and wait for the user to tell you how to proceed"
8109
+ — a sentence about a person, in a run where none is coming. With it the engine mints a POLICY sentence
8110
+ instead, this request''s `reason` travels as the POLICY''s words (not as the user''s guidance), and the
8111
+ settlement is recorded as `policy_refused` (`who.party: "none"`).
8112
+
8113
+ CONSTRAINTS: `decision:"deny"` only — only a person approves an action that EXECUTES, so
8114
+ `{decision:"approve", settledBy:"policy"}` is a contradiction, refused by the SERVER before the
8115
+ decision (400 `request.field_conflict`, the SAME SENTENCE as the live door) with the engine''s own
8116
+ pre-CAS refusal (`checkpoint.invalid_outcome`) behind it as depth; the server does NOT silently drop
8117
+ the key. It is therefore MUTUALLY EXCLUSIVE with `remember` (which is approve-only) — one body
8118
+ cannot carry both. The vocabulary''s owner is still core: the server accepts the KEY and relays the
8119
+ VALUE verbatim, so a word core does not know is refused by the engine, never by a server-side
8120
+ second judge of the vocabulary.
8121
+ REFUSED on a direct-door worker (400 `settled_by_not_in_proof` — not inside the HMAC payload, same
8122
+ rule as `remember` / `updatedInput` / `checkpointToken`); a non-direct-door deployment is unchanged.
8123
+
8124
+ SHAPE, not word: a non-string is 400 `request.field_invalid` (same family as `reason must be a
8125
+ string`). A word core does NOT know travels UNTOUCHED and is refused BY THE ENGINE pre-CAS:
8126
+ 409 + `errorCode: "resume_outcome_invalid"` + `field: "hostDecision"` + `retriable: true`, and the
8127
+ card STAYS PENDING (re-decide the same coordinates with a corrected body). NOTE this family no
8128
+ longer answers `approval_binding_mismatch`: from server 7.93.3 the `policy_ask` gate splits
8129
+ `invalid_outcome` by `field`, and the non-binding seats (`hostDecision`, `approver`) answer
8130
+ `resume_outcome_invalid` — "you approved the wrong action" is a sentence about a different fault.
8131
+ The binding seats and the field-absent shape are byte-for-byte unchanged.
7934
8132
 
7935
8133
  ApprovalRow:
7936
8134
  type: object
@@ -8127,7 +8325,16 @@ components:
8127
8325
  with no explicit allowlist); ABSENT = an older server (degrade to not teaching, not validating).
8128
8326
  resumeAt: { type: boolean, description: "E18 resume-at-message anchors are resolvable." }
8129
8327
  rewindFiles: { type: boolean, description: "E19 working-tree snapshot/restore is wired." }
8130
- rewindFilesTo: { type: boolean, description: "Restore to a SPECIFIC entry id (not just the latest snapshot)." }
8328
+ rewindFilesTo:
8329
+ type: boolean
8330
+ description: >-
8331
+ Code-only rewind (`TaskRequest.rewindFilesTo`) is wired — mint `routes/capabilities.ts:839` =
8332
+ `Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && deps.sessionStorage?.getLeafId)`: it needs the
8333
+ file-history store (to restore) AND the anchor map (to resolve the target HANDLE). Corrected in sdk 11.4.0
8334
+ (board [8437] U3): the store is the FILE-HISTORY store (not the pre-S-15 whole-tree snapshot store), and since
8335
+ server 7.97.0 (S-589) that seat is the CAPTURE face = backend AND the `FILE_HISTORY_CAPTURE` knob — a deployment
8336
+ that turns capture off reports this bit false together with `rewindFiles` / `restoreFiles` / `sessionSync`
8337
+ (and `fileHistoryCapture: "off"`).
8131
8338
  restoreFiles:
8132
8339
  type: boolean
8133
8340
  description: >-
@@ -8691,6 +8898,70 @@ components:
8691
8898
  properties:
8692
8899
  provider: { type: string, description: 'host | e2b | k8s | ssh | adb | local-docker | device — typed from the server''s RemoteExecProvider; `REMOTE_EXEC` unset ⇒ `host`.' }
8693
8900
  toolsOnThisHost: { type: boolean, description: 'The tools run on the process that answered this request (server-absolute paths mean something to the model''s tools).' }
8901
+ taskWriteFaceOpen:
8902
+ type: boolean
8903
+ description: >
8904
+ server >= 7.95.0 (S-591; value meaning refined at 7.96.0 / S-613; mint `routes/capabilities.ts:653` =
8905
+ `taskWriteFaceRefusal(deps.config) === undefined && autonomyOverrides(deps.config.autonomy).writeFace !== "roots"`)
8906
+ — DISCLOSURE BIT OF THE WRITE-CONTAINMENT FACE: does this deployment honour the task-level "open the write
8907
+ fence" declaration (`permissionMode:"bypassPermissions"` ⇒ `TaskSpec.writeFace:"open"`, writes outside the
8908
+ workspace roots)? `false` has two forms: (1) the hosted-posture gate refuses ⇒ sending that mode gets
8909
+ `422 request.host_posture_refused`; (2) the deployment runs `AUTONOMY=read-only|plan` ⇒ the request is
8910
+ accepted but the write face is clamped back to roots — the declaration silently does nothing. Three states
8911
+ are folded into one bit on purpose: the caller has exactly one decision (send that key or not); tier and
8912
+ evidence live in the refusal body and the operator's `GET /v1/diagnostics/wiring`. It speaks ONLY for the
8913
+ write-containment cell — the other two columns of `bypassPermissions` are unaffected.
8914
+ ABSENT = an older worker (<= 7.94) ⇒ read as `true` (contract appendix B.1: nothing refused it back then).
8915
+ taskApproverPosture:
8916
+ type: boolean
8917
+ description: >
8918
+ server >= 7.95.0 (S-591 leg ③; mint `routes/capabilities.ts:659` = `taskApproverPostureHonored(deps.config)`,
8919
+ the SAME predicate as the real consumer) — DISCLOSURE BIT OF `approverPosture`: does this deployment honour
8920
+ the submit body's host-posture statement ("this run's approval cards are auto-answered by the host
8921
+ itself")? `false` ⇒ the key is IGNORED (the request is still 202-accepted; the server logs
8922
+ `task_approver_posture_ignored`) — deliberately NOT refused: the resume legs replay the persisted body
8923
+ verbatim and the caller cannot remove it, so a refusal would brick a parked task forever. Same reach as
8924
+ the hosted-gate half of `taskWriteFaceOpen` (hosted posture ⇒ both false); `AUTONOMY=read-only|plan`
8925
+ only clears `taskWriteFaceOpen`, never this bit.
8926
+ ABSENT = an older worker (<= 7.94) ⇒ read as `true` (contract appendix B.1: it passed through back then).
8927
+ fileHistoryCapture:
8928
+ type: string
8929
+ enum: ['off', on-always]
8930
+ description: >
8931
+ server >= 7.97.0 (S-589; mint `routes/capabilities.ts:847` = `fileHistoryCaptureOf(deps.fileHistoryStore)`,
8932
+ closed word set owned by the server's `src/file-history-capture.ts` `FILE_HISTORY_CAPTURE_WORD_SET`) —
8933
+ CAN FILE-HISTORY CAPTURE BE TURNED OFF. Derived from the ASSEMBLY RESULT (capture face = backend AND the
8934
+ `FILE_HISTORY_CAPTURE` deployment knob), NOT an echo of the knob — a deployment without the backend is
8935
+ `off` too; the SAME judgement as `rewindFiles` / `restoreFiles` / `rewindFilesTo` / `sessionSync`.
8936
+ `off` = this deployment captures no first-touch snapshots and has no rewind / restore / code-only rewind /
8937
+ session-sync ⇒ render neither a "turn capture off" switch nor a rewind entry. `on-always` = capturing, and
8938
+ it CANNOT be turned off per session (core's first-touch capture is process-wide) ⇒ render one read-only true
8939
+ sentence, never a switch that cannot switch.
8940
+ 🔴 A third word `on-optional` ("this session may skip capture") is DELIBERATELY UNDECLARED by the server (it
8941
+ waits for a per-session no-capture seat in core); when it lands it is an additive server change and this
8942
+ enum grows with it. Keep a default arm (unknown word ⇒ treat as unknown, render no switch). Turning capture
8943
+ off does NOT delete what was already captured.
8944
+ ABSENT = an older worker (<= 7.96) ⇒ fall back to `rewindFiles` (`true` ⇒ `on-always`, `false` ⇒ `off`).
8945
+ background:
8946
+ type: object
8947
+ additionalProperties: true
8948
+ description: >
8949
+ server >= 7.102.0 (S-745 / core 7.32.0 #1107; mint `routes/capabilities.ts:308` = `{ listFace:
8950
+ sessionBackgroundFacesAvailable(deps), exitFaces: sessionBackgroundFacesAvailable(deps) }`) — presence of the
8951
+ session background-task doors: `listFace` = `GET /v1/sessions/{sessionId}/background`, `exitFaces` =
8952
+ `POST /v1/sessions/{sessionId}/background/stop` (contract §16). Both bits use the SAME predicate as the two
8953
+ doors' 501 (the session-ownership face `sessionStorage.ownerOf`), so "says yes but 501s" is structurally
8954
+ impossible; without it both bits are explicitly `false` and both doors answer 501
8955
+ `capability.session_ownership_required`. ENDPOINT-GATE FORM: `false` ⇒ do not call. True only promises the
8956
+ endpoint — not that this replica holds any background row of that session (the registry is in-process).
8957
+ ⚠️ `exitFaces` does NOT cover "credentials ready": on a worker with no service credential and without
8958
+ `ALLOW_UNAUTHED_WRITES` the bit is true while the stop door answers 503 `auth.service_token_required` —
8959
+ branch on that code (the same family gap as `memoryCompliance`). ABSENT = an older server (both doors 404)
8960
+ ⇒ the shell's exit dialog offers only "Stay / Exit", never "Exit and stop tasks".
8961
+ required: [listFace, exitFaces]
8962
+ properties:
8963
+ listFace: { type: boolean }
8964
+ exitFaces: { type: boolean }
8694
8965
  WriteProtectionCapability:
8695
8966
  type: object
8696
8967
  additionalProperties: false
@@ -10746,6 +11017,13 @@ components:
10746
11017
  sessionId: { type: string }
10747
11018
  requiresRealApproval: { type: boolean, enum: [true] }
10748
11019
  governanceForced: { type: boolean, enum: [true] }
11020
+ mandated: { type: boolean, enum: [true], description: 'server >= 7.101.0 (T4①): the mandated bit, same layer and shape as the two flags above (present only when true); the SAME value as `frame.mandated` (read once when the entry is minted).' }
11021
+ mandate:
11022
+ allOf: [{ $ref: '#/components/schemas/PersistedRuleMandate' }]
11023
+ description: 'server >= 7.102.0 (S-721): which word the mandated bit stands on — same layer and shape as `mandated`, the SAME value as `frame.mandate`. Absence never names a word.'
11024
+ origin:
11025
+ allOf: [{ $ref: '#/components/schemas/AskOrigin' }]
11026
+ description: 'server >= 7.102.0 (S-755 / cli L-695): WHO raised this ask, read once together with `frame.origin` when the entry is minted. The durable `pending[]` rows of the same response carry it top-level too — one reading for both row families. OPEN set (see AskOrigin).'
10749
11027
  fromSubagent: { type: boolean, enum: [true], description: 'The ask originates from a delegated subagent.' }
10750
11028
  originTaskId: { type: string, description: 'S-52 C2 (server ≥7.52, A-075.87): the delegated child taskId (= the child''s core runSourceTaskId, uuid) — correlates this live ask to the child run row. Minted by the SAME conditional spread as `fromSubagent`: the two keys are always both present or both absent (三键恒等亲证在案).' }
10751
11029
  contentKind: { type: string, description: 'S-62 (server >= 7.6x): content-question kind — `content_ask` when the gated tool is AskUserQuestion (the SAME word as the two durable read faces and core''s summarize face). Absent = an ordinary tool ask. Display/triage only; it NEVER takes part in decision routing.' }
@@ -11054,6 +11332,27 @@ components:
11054
11332
  presence may differ between the two legs for one ask. Derived from hot-reloadable governance knobs —
11055
11333
  long subscribers follow the stream's re-emitted frames. Absence ≠ "not governance-forced"; it means
11056
11334
  "no governance-origin evidence". Never written as false.
11335
+ # ── park-row facts (server `src/approval-card.ts` `ParkRowFacts` — the five facts core mints on the parked
11336
+ # `PendingAction.tool_approval` arm, projected by `parkRowFacts`, the SAME projection on both stores and on the
11337
+ # `/stream` `pending` frame). DISPLAY / TRIAGE projection, never a permission input; each present only when
11338
+ # true / a word. sdk 11.4.0 declares all five (the first three were already on the wire since 7.100.0 / 7.101.0).
11339
+ requiresRealApproval:
11340
+ type: boolean
11341
+ enum: [true]
11342
+ description: 'server >= 7.100.0 (S-700): this parked ask can only be cleared by a REAL HUMAN (core park-row twin seat, strict `=== true`). Absent = not marked, never "clearable without a person".'
11343
+ mandated:
11344
+ type: boolean
11345
+ enum: [true]
11346
+ description: 'server >= 7.101.0 (S-663 part 2 / core 7.31.0 #1079): the durable twin of the mandated bit (no rule can clear this ask). Independent of `requiresRealApproval` (which implies it) and `ruleOffersAbsence` — projected bit by bit, never inferred.'
11347
+ mandate:
11348
+ allOf: [{ $ref: '#/components/schemas/PersistedRuleMandate' }]
11349
+ description: 'server >= 7.102.0 (S-721): which word the mandated bit stands on — the park-row twin seat read through `readClosedWordSeat(…, "mandate")` (core `isPersistedRuleMandate`); an out-of-set word reads as absent. Answers the same as the inbox row for the same row.'
11350
+ origin:
11351
+ allOf: [{ $ref: '#/components/schemas/AskOrigin' }]
11352
+ description: 'server >= 7.102.0 (S-755 / cli L-695 / C-R111): WHO raised this ask — the park-row twin seat `PendingAction.tool_approval.origin` read through `readClosedWordSeat(…, "origin")` (core `isAskOrigin`). Same word as the live card frame and the inbox row. Absent = an old row (parked before core 7.5.0) or an out-of-set word — NOT "policy".'
11353
+ ruleOffersAbsence:
11354
+ allOf: [{ $ref: '#/components/schemas/RuleOffersAbsence' }]
11355
+ description: 'server >= 7.100.0 (S-700): WHY the park row offers no rule — the same narrow read and closed three-word set as the live card frame and `card_json`.'
11057
11356
  hasBidiControls:
11058
11357
  type: boolean
11059
11358
  enum: [true]
@@ -11160,6 +11459,20 @@ components:
11160
11459
  🔴 NEVER minted as `false` (the OMIT contract this row's other flags follow); absence is "not marked",
11161
11460
  never "confirmed clearable without a person". The value comes WHOLE from core (`summarizeCheckpoint`
11162
11461
  projects the park row's own bit) — the server neither recomputes nor redacts it.
11462
+ mandated:
11463
+ type: boolean
11464
+ enum: [true]
11465
+ description: >-
11466
+ server >= 7.101.0 (S-663 part 2 / core 7.31.0 #1079), ADDITIVE, present ONLY when true: the durable twin of
11467
+ the mandated bit (core `summarizeCheckpoint` screens the park row's `PendingAction.tool_approval.mandated`
11468
+ to a literal `true`; the server mints the key on strict `=== true`). Declared in sdk 11.4.0 — this schema is
11469
+ CLOSED, so before that a strict validator rejected every row that carried it.
11470
+ mandate:
11471
+ allOf: [{ $ref: '#/components/schemas/PersistedRuleMandate' }]
11472
+ description: >-
11473
+ server >= 7.102.0 (S-721 / core 7.32.0 #1092): which word the mandated bit stands on (core
11474
+ `CheckpointSummary.mandate`, already screened by core's `isPersistedRuleMandate`; the server does not screen
11475
+ twice). Answers the same as the operator queue row for the same park row.
11163
11476
  denialLimitFallback:
11164
11477
  $ref: '#/components/schemas/DenialLimitFallback'
11165
11478
  description: >-
@@ -11789,6 +12102,73 @@ components:
11789
12102
  budgetMs: { type: integer }
11790
12103
  error: { type: string }
11791
12104
 
12105
+ SessionBackgroundRow:
12106
+ type: object
12107
+ description: >
12108
+ One row of `GET /v1/sessions/{sessionId}/background` (S-745, server >= 7.102.0; mint
12109
+ `src/http/routes/background.ts` `SessionBackgroundRow`) = core `TaskRegistry.list()`'s row projected key by
12110
+ key (`task_id` renamed `id`; `toolUseId` / `updatedAt` stripped). The three word sets are OWNED BY CORE
12111
+ (7.32.1 `SemaTaskType` / `SemaTaskStatus`), transcribed verbatim. 🔴 IN-PROCESS: only rows on this replica.
12112
+ Response projection: declared key by key AND open (additive keys between SDK releases must not make a legal
12113
+ row unreadable — the LivePendingRow ruling).
12114
+ additionalProperties: true
12115
+ required: [id, type, status, createdAt, sessionScoped]
12116
+ properties:
12117
+ id: { type: string, description: 'core `task_id` (renamed). A receipt''s `id` is the same value.' }
12118
+ description: { type: string, description: 'The model''s description, falling back to the launch command text (core keeps no separate raw command). Content family: redacted, cut at 500 characters. UNTRUSTED for display.' }
12119
+ type: { type: string, enum: [background_bash, workflow, background_agent, monitor], description: 'core `SemaTaskType`.' }
12120
+ status: { type: string, enum: [pending, running, parked, completed, failed, killed, cancelled], description: 'core `SemaTaskStatus` (`parked` = a background agent suspended on a pending approval — neither live nor terminal).' }
12121
+ createdAt: { type: integer, description: 'Registration time (epoch ms).' }
12122
+ sessionScoped: { type: boolean, description: '`true` = session-resident (lives across turns); `false` = run-scoped.' }
12123
+ retained:
12124
+ type: boolean
12125
+ enum: [true]
12126
+ description: >-
12127
+ core's retain predicate (env declaration / task lane / row mark — any ⇒ kept) — present ONLY when true,
12128
+ never minted as false, and only ever on a shell row (`background_bash` / `monitor`; core `list()` row
12129
+ doc). Present = "keeps running" on the exit sheet; absent = an `includeRetained:false` stop WILL stop it.
12130
+ SessionBackgroundList:
12131
+ type: object
12132
+ description: '`GET /v1/sessions/{sessionId}/background` 200 body (contract §16). An idle session answers `{ rows: [] }`.'
12133
+ additionalProperties: true
12134
+ required: [rows]
12135
+ properties:
12136
+ rows:
12137
+ type: array
12138
+ items: { $ref: '#/components/schemas/SessionBackgroundRow' }
12139
+ SessionBackgroundStopRequest:
12140
+ type: object
12141
+ description: >
12142
+ `POST /v1/sessions/{sessionId}/background/stop` body — server `StopBodySchema = z.object({ includeRetained:
12143
+ z.boolean() }).strict()`: EXACTLY this key, REQUIRED, NO default. `true` = also stop explicitly retained live
12144
+ services ("Exit and stop tasks"); `false` = keep them (`retained_skipped`). Absent / non-boolean / an extra
12145
+ key ⇒ 400 `request.body_shape`.
12146
+ additionalProperties: false
12147
+ required: [includeRetained]
12148
+ properties:
12149
+ includeRetained: { type: boolean }
12150
+ SessionBackgroundReceipt:
12151
+ type: object
12152
+ description: >
12153
+ One receipt of the stop door — SHELL rows only (`background_bash` / `monitor`). `outcome` = core 7.32.1
12154
+ `BackgroundShellHardKillReceipt.outcome` (`BackgroundHardKillOutcome` ∪ `retained_skipped`), passed through
12155
+ VERBATIM: `killed` / `already_gone` (both = "this row is reaped") · `retained_skipped` (kept, process alive) ·
12156
+ `no_pgid` (no synchronous hard kill landed — MAY STILL BE RUNNING; never render it as stopped).
12157
+ additionalProperties: true
12158
+ required: [id, outcome]
12159
+ properties:
12160
+ id: { type: string, description: 'The row id (core taskId).' }
12161
+ outcome: { type: string, enum: [killed, already_gone, retained_skipped, no_pgid] }
12162
+ SessionBackgroundStopResult:
12163
+ type: object
12164
+ description: '`POST /v1/sessions/{sessionId}/background/stop` 200 body. The receipts are COMPLETE before the 200 (soft → grace → hard is awaited).'
12165
+ additionalProperties: true
12166
+ required: [receipts]
12167
+ properties:
12168
+ receipts:
12169
+ type: array
12170
+ items: { $ref: '#/components/schemas/SessionBackgroundReceipt' }
12171
+
11792
12172
  # ── 2c session-sync (P1d) — the cloud-as-a-SYNC-PEER schemas ──────────────────────────────────────────────
11793
12173
  SyncEntry:
11794
12174
  type: object
@@ -12479,12 +12859,18 @@ components:
12479
12859
  the ROUTING question "nobody reachable" and no park took the ask) · `approver_error` (it threw) ·
12480
12860
  `approver_contract` (it answered outside its contract) · `presentation_failed` (args/edit could not be
12481
12861
  safely presented or adopted) · `blanket_allow_refused` (a blanket `onAsk:"allow"` met a
12482
- `requiresRealApproval` ask) · `task_aborted` (the wait''s abort signal ended it).
12862
+ `requiresRealApproval` ask) · `policy_refused` (server >= 7.93.3 / core 7.26.2, #1016 — THE
12863
+ DEPLOYMENT''S OWN POLICY refused: the approval channel reported that a MACHINE, not a person, ended
12864
+ this wait. Distinct from `human_refused` because the party differs and the model is told a different
12865
+ thing — a person''s "no" is the user''s direction, a policy''s is a capability of this run; distinct
12866
+ from `no_approver` because a channel WAS wired and answered. This family carries NO `note` seat: the
12867
+ note seat belongs to the two human words, whose ABSENCE is a control-flow fact) · `task_aborted`
12868
+ (the wait''s abort signal ended it).
12483
12869
  required: [kind, who, when]
12484
12870
  properties:
12485
12871
  kind:
12486
12872
  type: string
12487
- enum: [no_approver, approver_unavailable, approver_error, approver_contract, presentation_failed, blanket_allow_refused, task_aborted]
12873
+ enum: [no_approver, approver_unavailable, approver_error, approver_contract, presentation_failed, blanket_allow_refused, policy_refused, task_aborted]
12488
12874
  who:
12489
12875
  type: object
12490
12876
  additionalProperties: false
@@ -13585,6 +13971,17 @@ components:
13585
13971
  spec-resolver, which now passes the notice seat). Consequence for consumers that assert on ledger
13586
13972
  CONTENTS: a resume leg's ledger MAY now carry one extra `engine_notice` row it did not before — assert
13587
13973
  "may appear", never an exact row count or order (and consume idempotently: reconnect replay shows it again).
13974
+ `approval.read_root_granted` / `approval.read_root_grant_rejected` (server >= 7.102.0 / S-720 / core 7.32.0
13975
+ #1093) — the ENGINE's verdict on a `readRootGrant` answer (POST /v1/tool-approvals/{id}/respond), at most one
13976
+ frame per answered ask. GRANTED = this run's read directories really include it now (core: the ONLY evidence the
13977
+ run widened; read faces only, this run only) — `{dir, covers?, root, toolName, toolCallId, sessionId, runId,
13978
+ taskId?}`, `root` = the canonical spelling the engine holds. REJECTED = nothing changed and the decision stands
13979
+ (say so plainly) — `{reason, offered?, requested?, toolName, toolCallId, sessionId, runId, taskId?}`, `reason` one of
13980
+ core's eight words (`on_refusal` / `not_a_string` / `no_candidate` / `mismatch` / `unresolvable` /
13981
+ `resolves_elsewhere` / `admits_artifact_store` — the directory contains, lies inside or is the local artifact
13982
+ store; unreachable on a server 7.102.0 deployment, which wires no artifact host — / `no_read_roots`), passed
13983
+ through verbatim by the server (echo-only, not enumerated) — read it as an OPEN set. `toolCallId` joins to the
13984
+ `tool_approval` frame.
13588
13985
  `delegation.ask_unresolvable` (server >= 7.69.0 / core 7.10.0 #648) — an `ask` reached a FINAL DENY with
13589
13986
  NOBODY having ruled on it: the approver consulted for the call answered `unavailable` and no durable park
13590
13987
  caught it afterwards. The deny itself is unchanged (`tool_end.gate.settlement.kind:"approver_unavailable"`
@@ -14258,8 +14655,18 @@ components:
14258
14655
  description: >
14259
14656
  (server >= 7.93.0, core 7.26.0 #997) This card was MANDATED — a read-station / rule-lane forced ask (a rule can
14260
14657
  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).
14658
+ absent = an ordinary ask. ⚠️ CORRECTED (sdk 11.4.0): the durable parked row DOES carry a twin bit since core
14659
+ 7.31.0 #1079 (`PendingAction.tool_approval.mandated`), projected since server 7.101.0 onto the operator queue
14660
+ row / the `/stream` `pending` frame / the inbox row (S-663 part 2) and the `livePending` row top level (T4①)
14661
+ — four faces, one bit. `card_json` does not carry it (`ApprovalCardSchema` is `.strict()`).
14662
+ mandate:
14663
+ allOf: [{ $ref: '#/components/schemas/PersistedRuleMandate' }]
14664
+ description: >-
14665
+ server >= 7.102.0 (S-721 / core 7.32.0 #1092), "tool_approval" only, ADDITIVE — which word the `mandated`
14666
+ bit stands on (server `src/tool-approval.ts:841`, read through `readClosedWordSeat(req, "mandate")` =
14667
+ core's `isPersistedRuleMandate` screen). Present only when the WHOLE cause of `mandated:true` is that one
14668
+ word; absence never names a word and is not "not mandated". Same value on the `livePending` row, the
14669
+ operator queue row and the inbox row; NOT on `card_json` / `approval_request`.
14263
14670
  denialLimitFallback: { $ref: '#/components/schemas/DenialLimitFallback' }
14264
14671
  parked:
14265
14672
  type: boolean
@@ -14561,6 +14968,13 @@ components:
14561
14968
 
14562
14969
  The SAME type is the `origin` member of `GateOutcome` on `tool_end` — one vocabulary, two faces.
14563
14970
 
14971
+ server >= 7.102.0 (S-755 / cli L-695 / C-R111): the word now also rides the `livePending` row top level
14972
+ (read once with the frame's) and the operator queue row (`GET /v1/approvals` `pending[]` + the `/stream`
14973
+ `pending` frame — the park-row twin seat, screened by core's `isAskOrigin`), besides the frame and the inbox
14974
+ row — five faces, one word. `card_json` / the `approval_request` frame still do not carry it (the card is
14975
+ `.strict()`). The eleven known words are exported at runtime as the SDK's `ASK_ORIGINS` (core 7.32.1
14976
+ `ASK_ORIGINS`, verbatim and in order) — known words, NOT an enforcement table.
14977
+
14564
14978
  RuleOffersAbsence:
14565
14979
  type: string
14566
14980
  enum: [mandated, shadowed, lane_cannot_speak]
@@ -14578,6 +14992,26 @@ components:
14578
14992
  server-side narrow read, so both faces carry identical values; a word outside the set is treated as
14579
14993
  malformed and the key is not minted at all (the server never passes an unknown word through).
14580
14994
 
14995
+ PersistedRuleMandate:
14996
+ type: string
14997
+ enum: [operator_always, tool_marks, probe_mandate, probe_unanswered, write_protection, write_protection_unresolved]
14998
+ description: >
14999
+ WHICH WORD a mandated ask stands on (server >= 7.102.0 / S-721 / core 7.32.0 #1092) — core's
15000
+ `PERSISTED_RULE_MANDATES` six words, transcribed verbatim and in order (core 7.32.1
15001
+ `dist/core/ask-origin.d.ts:349`; the vocabulary's owner is core). Rides FOUR faces with one value: the
15002
+ `tool_approval` frame, the `livePending` row top level, the operator queue row (`GET /v1/approvals`
15003
+ `pending[]` + the `/v1/approvals/stream` `pending` frame) and the inbox row. NOT on `card_json` / the
15004
+ `approval_request` frame (live or reconnect replay — `ApprovalCardSchema` is `.strict()`), same as `mandated`.
15005
+ 🔴 A TRUE CLOSED SET, deliberately unlike `AskOrigin`: on every face the server screens it through core's
15006
+ `isPersistedRuleMandate` (`approval-card.ts` `readClosedWordSeat`; the inbox face is screened by core's own
15007
+ `summarizeCheckpoint`) and an out-of-set word reads as ABSENT, never on the wire.
15008
+ 🔴 ABSENCE NEVER NAMES A WORD and is NOT "not mandated": another door (governance / hook / ancestor) or more
15009
+ than one reason ⇒ absent; whether the ask is mandated is `mandated`. For an ENGINE-MINTED value, present ⇒
15010
+ `mandated` is true (core: present ⇔ that word is the bit's WHOLE reason); the converse does not hold. ⚠️ On the
15011
+ durable faces (queue row / inbox row) the server projects the two seats INDEPENDENTLY (it does not bind `mandate`
15012
+ to `mandated`, `approval-card.ts` `readClosedWordSeat`), so a hand-edited or generation-skewed row may carry one
15013
+ without the other — read `mandated` for the bit, never infer it from this word. Echo-only — the server never
15014
+ branches on the word.
14581
15015
  DenialLimitKind:
14582
15016
  type: string
14583
15017
  enum: [consecutive, total]
@@ -15133,14 +15567,15 @@ components:
15133
15567
  type: object
15134
15568
  description: >
15135
15569
  Decision-maker identity assertion (design/171 §5.1; the cross-repo source of truth is
15136
- `@sema-agent/registry-core` ActorAssertionWire — the SDK holds a value copy).
15570
+ `@sema-agent/settings-schema` ActorAssertionWire — the SDK holds a value copy; the package was named
15571
+ `@sema-agent/registry-core` before 1.0.0).
15137
15572
 
15138
15573
  NORMATIVE, not a style note: an ACTOR NEVER PARTICIPATES IN THE PERMISSION DECISION — authority always
15139
15574
  belongs to the principal; this shape exists only for audit/echo/presentation. `id` is ALWAYS server-minted
15140
15575
  (= the verified principal; an auth-off deployment mints the `"_"` sentinel), and a wire requester may
15141
15576
  submit ONLY `label`.
15142
15577
  required: [id]
15143
- additionalProperties: false # registry-core's ActorAssertionWire is .strict()
15578
+ additionalProperties: false # settings-schema's ActorAssertionWire is .strict()
15144
15579
  properties:
15145
15580
  id: { type: string, minLength: 1, maxLength: 256, description: 'Server-minted principal ("_" sentinel when auth is off). Never submittable by the requester.' }
15146
15581
  label: { type: string, maxLength: 256, description: 'Unverified display text — the ONLY field a requester may submit (server redacts + bounds it). UNTRUSTED.' }
@@ -15151,7 +15586,7 @@ components:
15151
15586
  description: >
15152
15587
  Audit axis for which authorization path accepted the decision: `owner` (row owner) or `operator`
15153
15588
  (explicit operator seat). CLOSED — verbatim-equal to the owning schema
15154
- (registry-core ActorAssertionWire). Widening it here without widening the owner is copy drift.
15589
+ (settings-schema ActorAssertionWire). Widening it here without widening the owner is copy drift.
15155
15590
 
15156
15591
  AskDecisionBody:
15157
15592
  type: object
@@ -15800,6 +16235,20 @@ components:
15800
16235
  SAME projection as the `GET /v1/approvals` row (`projectPendingForWire`), declared here explicitly so
15801
16236
  a stream consumer sees it typed instead of through the open index signature. Present ONLY when true;
15802
16237
  absence = "not detected", never "confirmed clean" (see PendingCheckpoint.hasBidiControls).
16238
+ # sdk 11.4.0 (che-OV adversarial review [low] F5): the `pending` frame spreads the PendingCheckpoint row and the
16239
+ # `live_pending` frame (server >= 7.87.0) spreads the LivePendingRow — so the rows' park-row facts / mandate bit /
16240
+ # origin word ride these frames with the same names and values. Declared for the same reason as hasBidiControls.
16241
+ requiresRealApproval: { type: boolean, enum: [true], description: '`pending` / `live_pending` frames — see PendingCheckpoint / LivePendingRow.requiresRealApproval.' }
16242
+ mandated: { type: boolean, enum: [true], description: '`pending` / `live_pending` frames (server >= 7.101.0) — see the row schemas.' }
16243
+ mandate:
16244
+ allOf: [{ $ref: '#/components/schemas/PersistedRuleMandate' }]
16245
+ description: '`pending` / `live_pending` frames (server >= 7.102.0 / S-721) — see PendingCheckpoint.mandate / LivePendingRow.mandate.'
16246
+ origin:
16247
+ allOf: [{ $ref: '#/components/schemas/AskOrigin' }]
16248
+ description: '`pending` / `live_pending` frames (server >= 7.102.0 / S-755) — see PendingCheckpoint.origin / LivePendingRow.origin. OPEN set.'
16249
+ ruleOffersAbsence:
16250
+ allOf: [{ $ref: '#/components/schemas/RuleOffersAbsence' }]
16251
+ description: '`pending` frames only (server >= 7.100.0 / S-700) — see PendingCheckpoint.ruleOffersAbsence.'
15803
16252
 
15804
16253
  ParkedDecideAccepted:
15805
16254
  type: object