@sema-agent/sdk 9.8.0 → 9.9.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
@@ -4517,6 +4517,359 @@ paths:
4517
4517
  '422': { description: 'errorCode "memory_sync_invalid_body" — the request failed the typed shape gate.' }
4518
4518
  '501': { $ref: '#/components/responses/NotImplemented' }
4519
4519
 
4520
+ /v1/memory/entries/{entryId}/provenance:
4521
+ parameters:
4522
+ - $ref: '#/components/parameters/PrincipalHeader'
4523
+ - in: path
4524
+ name: entryId
4525
+ required: true
4526
+ schema: { type: string }
4527
+ description: 'The memory entry id, percent-encoded. A malformed escape → 400 `request.id_invalid`.'
4528
+ get:
4529
+ tags: [memory]
4530
+ operationId: memoryEntryProvenance
4531
+ x-status: live # design/316 item ③ (server >= 7.46.0); route is always mounted and answers 501 itself. SDK memory.provenance().
4532
+ summary: One entry's provenance account (operator lane).
4533
+ description: >
4534
+ design/316 item ③ — the machine-readable answer to "who wrote this in, what state is it in, has it been
4535
+ moved" for an auditor holding an entry id. AUDIENCE IS OPERATOR / COMPLIANCE, not a shell knob: the
4536
+ owner-facing self-service faces are `GET /v1/memory/export` and `POST /v1/sessions/{id}/memory/erase`.
4537
+
4538
+ GATE ORDER: identity (401 `auth.principal_required`) → authorization (403 `auth.operator_only`) →
4539
+ capability (501 `capability.memory_engine_required`) → shape (400). AUTHORIZATION BEFORE CAPABILITY, on
4540
+ purpose: a caller who can reach nothing must not be able to read the deployment's shape off it.
4541
+ Capability bit: `memoryCompliance` — mounted AND `OPERATOR_PRINCIPALS` non-empty. It is NOT a synonym of
4542
+ `memoryBundle` (that one asks about the backend's v2-c composite face; this one asks whether the backend
4543
+ OWNS THE ENGINE CONTROL PLANE, `controlPlaneRoot` — the file memory backend does, pg/tidb do not).
4544
+ ⚠️ The bit does NOT include "credentials ready": this route sits behind the deployment-wide rewrite gate,
4545
+ so a worker with no service credential (and without an explicit `ALLOW_UNAUTHED_WRITES`) answers 503
4546
+ `auth.service_token_required` BEFORE routing while the bit still reads `true`.
4547
+
4548
+ The 200 body is core's `EntryProvenanceAccount` ITSELF — not wrapped, not projected, not padded. The
4549
+ account form is an OPEN discriminated shape, so writing a whitelist in the middle would restate a
4550
+ structure that drifts with core. This is DELIBERATELY THE OPPOSITE of the import report (an explicit
4551
+ whitelist there): the test is "is a new key silently reaching the wire dangerous?" — in a disposition
4552
+ list, yes; in a STATEMENT OF FACT, withholding a field is the dangerous half.
4553
+ responses:
4554
+ '200':
4555
+ description: The entry's assembled provenance account (core's, verbatim).
4556
+ content:
4557
+ application/json:
4558
+ schema: { $ref: '#/components/schemas/EntryProvenanceAccount' }
4559
+ '400': { description: 'errorCode "request.id_invalid" — the entry id could not be decoded.' }
4560
+ '401': { $ref: '#/components/responses/Unauthorized' }
4561
+ '403': { description: 'errorCode "auth.operator_only" — provenance and erasure are operator-only deployment actions.' }
4562
+ '500':
4563
+ description: >
4564
+ errorCode "internal.memory_control_plane_corrupt" — core's FAIL-CLOSED refusal: the control plane
4565
+ cannot be read soundly and it will not assemble an answer over an account it cannot vouch for.
4566
+ core's diagnostic text rides VERBATIM (it names which ledger is damaged) — show it to the operator in
4567
+ full. 🔴 The code is TWO-MEANINGED and core carries NO discriminator for it today, so DO NOT sniff the
4568
+ message to tell them apart: (1) BEFORE the operation — nothing happened, repair/rebuild the ledger;
4569
+ (2) AFTER it landed (ERASE ONLY) — the deletions and their evidence anchor ARE COMMITTED and only the
4570
+ residue enumeration failed, so the action is to replay with the SAME `requestId`. Never read (2) as
4571
+ "nothing was deleted". ("internal.error" is the unclassified 500; its text is not echoed.)
4572
+ content:
4573
+ application/json:
4574
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4575
+ '501': { description: 'errorCode "capability.memory_engine_required" — this deployment has no memory engine whose backend owns the engine control plane. Change the deployment shape; retrying will not help.' }
4576
+
4577
+ /v1/memory/erase:
4578
+ parameters:
4579
+ - $ref: '#/components/parameters/PrincipalHeader'
4580
+ post:
4581
+ tags: [memory]
4582
+ operationId: memoryErase
4583
+ x-status: live # design/316 item ③ (server >= 7.46.0). SDK memory.erase().
4584
+ summary: Explicitly authorized compliance erasure (operator lane).
4585
+ description: >
4586
+ design/316 item ③ — the deliverable a compliance caller archives. Gate order, capability bit and the
4587
+ rewrite-gate caveat are VERBATIM those of `GET /v1/memory/entries/{entryId}/provenance`.
4588
+
4589
+ The request body is handed to the engine AS SENT: the server adds, drops and rewrites nothing. In
4590
+ particular `allowUnevidenced` is NEVER INJECTED — the degraded lane is a HUMAN authorization statement,
4591
+ and a middle layer ticking it is that layer signing for the caller. The selector's whole judgment
4592
+ (exactly one of `ids`/`scope`/`sessionId`, de-duplication, types) belongs to core, so a
4593
+ form-right-meaning-wrong body such as `{"ids": [...], "scope": "..."}` reaches the engine and comes back
4594
+ 400 `config.memory_erasure_request`.
4595
+
4596
+ 🔴 IDEMPOTENCY HAS TWO LEGS WITH DIFFERENT CONTRACTS — read `evidenceCapability` BEFORE deciding whether a
4597
+ retry is safe. `"journal"`: `requestId` is the idempotency identity; a resend READS BACK the ids pinned by
4598
+ the anchor row and never re-resolves, and reusing an id under a DIFFERENT selector is refused loudly (409
4599
+ `memory.erasure_selector_mismatch`). `"none"` (the explicit `allowUnevidenced` degradation): core's words
4600
+ are "same requestId = a NEW request (re-resolved — replay convergence is not promised)" — A RESEND IS A
4601
+ SECOND REAL DELETE. 🔴 DO NOT put an automatic retry policy on this endpoint.
4602
+
4603
+ ⚠️ THE MASS-DELETION FUSE DOES NOT APPLY: a whole-scope / whole-session selector is LEGAL here (the fuse
4604
+ guards "files quietly vanished during harvest"; an explicitly authorized erasure vouches for itself by
4605
+ enumerating every id in the attestation). `residuals.propagation` is core's self-declared boundary:
4606
+ deletion is a THIS-STORE fact, remote-peer convergence is the sync deployment's half to prove.
4607
+ requestBody:
4608
+ required: true
4609
+ content:
4610
+ application/json:
4611
+ schema: { $ref: '#/components/schemas/MemoryErasureRequest' }
4612
+ responses:
4613
+ '200':
4614
+ description: The erasure attestation (core's, verbatim).
4615
+ content:
4616
+ application/json:
4617
+ schema: { $ref: '#/components/schemas/MemoryErasureAttestation' }
4618
+ '400':
4619
+ description: >
4620
+ Three codes, three different operator actions — deliberately not folded into one.
4621
+ "request.request_id_required": `requestId` missing / empty / non-string. It is THIS request's
4622
+ idempotency identity and the server never mints one for you, so a RETRY MUST CARRY THE SAME ID (a lost
4623
+ ack must not open a second real delete). "request.body_shape": the top-level JSON shape is wrong, or a
4624
+ key that rewrites an object's prototype was smuggled in as a selector key. "config.memory_erasure_request":
4625
+ core's own verdict — the shape passed, the SELECTOR does not (not exactly one of ids/scope/sessionId,
4626
+ empty/duplicated ids, …).
4627
+ content:
4628
+ application/json:
4629
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4630
+ '401': { $ref: '#/components/responses/Unauthorized' }
4631
+ '403': { description: 'errorCode "auth.operator_only" — provenance and erasure are operator-only deployment actions.' }
4632
+ '409':
4633
+ description: >
4634
+ Three core-minted codes, passed through verbatim; branch on `errorCode` (the status cannot tell them
4635
+ apart). "memory.erasure_evidence_unavailable": this backend has NO evidence face and the caller did not
4636
+ opt into the degraded lane — deliberately NOT 501, because the caller holds a real choice
4637
+ (`allowUnevidenced: true`) and a 501 would send someone off to change a deployment that is fine.
4638
+ "memory.erasure_selector_mismatch": this `requestId` was first executed under a DIFFERENT selector —
4639
+ mint a new id. "memory.erasure_census_incomplete": the store-wide projection census could not be read in
4640
+ full, so the attestation refuses to claim a clean sweep — core states nothing was deleted and nothing
4641
+ recorded; repair the filesystem fault and retry.
4642
+ content:
4643
+ application/json:
4644
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4645
+ '500':
4646
+ description: 'errorCode "internal.memory_control_plane_corrupt" — see the same response on `GET /v1/memory/entries/{entryId}/provenance`; meaning (2) of that two-meaninged code (deletions COMMITTED, residue enumeration failed) occurs on THIS endpoint only.'
4647
+ content:
4648
+ application/json:
4649
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4650
+ '501': { description: 'errorCode "capability.memory_engine_required" — this deployment has no memory engine whose backend owns the engine control plane.' }
4651
+
4652
+ /v1/memory/origin/external:
4653
+ parameters:
4654
+ - $ref: '#/components/parameters/PrincipalHeader'
4655
+ - in: query
4656
+ name: scopes
4657
+ required: true
4658
+ schema: { type: string }
4659
+ description: >
4660
+ One or more memory scope keys. Comma-separated (`?scopes=a,b`) and/or REPEATED (`?scopes=a&scopes=b`) —
4661
+ both forms are merged in full, then trimmed per segment, emptied segments dropped, de-duplicated in
4662
+ order. Scope keys carry `:` / `@` / `/`, so every key is percent-encoded before the separators are
4663
+ joined. ⚠️ A scope key containing a LITERAL COMMA is not expressible on this query (the server decodes
4664
+ before it splits).
4665
+ - in: query
4666
+ name: originAware
4667
+ required: false
4668
+ schema: { type: string, enum: ['1'] }
4669
+ description: >
4670
+ design/383 S-6 / S-50 §3 (F3) — the ORIGIN FENCE capability declaration. The server has no per-client
4671
+ version visibility, so an absent flag is read as "an old client", and an old client's strict/strip parse
4672
+ SILENTLY DROPS the `origin` marker — flattening a "known external" entry into unmarked text that the
4673
+ consumer then quotes or writes back as clean content, laundering the provenance away.
4674
+ 🔴 EVERY row of THIS face carries a marker, so WITHOUT `originAware=1` the whole list is withheld and the
4675
+ response carries `withheldOriginTagged: <n>` instead (minted only when n > 0). That is the fence working,
4676
+ not a bug: an operator tool must declare it knows the origin key family before it can see them.
4677
+ Only the literal `1` is accepted (no loose parsing of `true`/`yes`).
4678
+ get:
4679
+ tags: [memory]
4680
+ operationId: memoryOriginExternal
4681
+ x-status: live # design/316 item ② (server >= 7.46.0). SDK memory.originExternal() (which always declares originAware=1).
4682
+ summary: Every externally-marked entry in the named scopes, with its provenance account (operator lane).
4683
+ description: >
4684
+ design/316 item ② — a memory entry may carry an EXTERNAL-ORIGIN marker: its content came from material the
4685
+ model READ (a web page, a third-party doc, someone else's repo) rather than from first-hand facts in the
4686
+ session. The marker does not mean "poisoned", it means "VERIFY BEFORE USE", and it rides along into every
4687
+ mount. Gate order, capability caveat and the rewrite gate are verbatim those of item ③; the bit here is
4688
+ `memoryOrigin` (same predicate as `memoryCompliance` TODAY, but deliberately a SECOND bit — they are two
4689
+ product faces and a deployment may one day open only one of them; do not cache them as one).
4690
+
4691
+ 🔴 THE ANSWER COVERS ONLY THE SCOPES YOU NAMED, AND AN EMPTY RESULT MUST NEVER BE READ AS "THE STORE IS
4692
+ CLEAN". This server generation has no scope-enumeration read face, so scope names are supplied by the
4693
+ caller and INCOMPLETENESS IS SILENT (an empty array and "these scopes are clean" are the same bytes on the
4694
+ wire). An auditing script must guarantee its own list is complete (the keys are the same ones
4695
+ `GET /v1/memory/export?scope=` takes). The echoed `scopes` is THE SET ACTUALLY AUDITED after
4696
+ normalization — it is returned so the coverage itself is observable rather than inferred from what the
4697
+ caller remembers sending.
4698
+
4699
+ Each row's `provenance` IS the `EntryProvenanceAccount` of item ③ (same discriminated form, same
4700
+ pass-through discipline). An entry that is also CHALLENGED is still listed here — the exclusion rides
4701
+ inside the provenance account, and a human must SEE it in order to adjudicate it.
4702
+ responses:
4703
+ '200':
4704
+ description: The audited scopes (normalized, echoed) and one row per marked entry.
4705
+ content:
4706
+ application/json:
4707
+ schema: { $ref: '#/components/schemas/MemoryOriginExternalResult' }
4708
+ '400': { description: 'errorCode "request.query_invalid" — `scopes` is absent or normalizes to an empty list. The refusal text also names where scope keys come from and repeats that an empty answer is not a clean store.' }
4709
+ '401': { $ref: '#/components/responses/Unauthorized' }
4710
+ '403': { description: 'errorCode "auth.operator_only" — origin marking and clearance are operator-only deployment actions.' }
4711
+ '500':
4712
+ description: 'errorCode "internal.memory_control_plane_corrupt" (meaning (1) only on this read face) or "internal.error" (unclassified, text not echoed).'
4713
+ content:
4714
+ application/json:
4715
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4716
+ '501': { description: 'errorCode "capability.memory_engine_required" — no memory engine whose backend owns the engine control plane; the clearance ledger and its write-ahead custody rows live in that plane.' }
4717
+
4718
+ /v1/memory/origin/clearances:
4719
+ parameters:
4720
+ - $ref: '#/components/parameters/PrincipalHeader'
4721
+ get:
4722
+ tags: [memory]
4723
+ operationId: memoryOriginClearances
4724
+ x-status: live # design/316 item ② (server >= 7.46.0). SDK memory.originClearances().
4725
+ summary: The origin-clearance audit ledger (operator lane, explicit whitelist projection).
4726
+ description: >
4727
+ design/316 item ② — who un-marked what, when, why, and whether the clearance finished. Gate order and
4728
+ capability bit are those of `GET /v1/memory/origin/external`.
4729
+
4730
+ Each row is an EXPLICIT WHITELIST PROJECTION of core's `OriginClearanceRow`, which is DELIBERATELY THE
4731
+ OPPOSITE posture from the two pass-through accounts above — the test is the same one ("is a new key
4732
+ silently reaching the wire dangerous?") and the answer flips, because this is a CUSTODY face rather than a
4733
+ statement of fact.
4734
+ 🔴 `entryText` — the full text of the cleared entry — NEVER reaches the wire, but its presence is disclosed
4735
+ honestly as `custodyBytes`. That field is the CRASH-RECOVERY SEAT: if the process dies between the
4736
+ tombstone batch and the re-record batch, the row holds the only copy of that memory; the recovery is TO
4737
+ CALL `POST /v1/memory/origin/entries/{entryId}/clear` AGAIN (the engine replays from the row), not to read
4738
+ the bytes out and paste them back. Putting a whole memory's body on an audit LIST face would attach a full
4739
+ text export to every "let me look at the clearance records". So it is stripped — and STRIPPING IS
4740
+ DISCLOSED: a `pending` row with `custodyBytes > 0` means the recovery seat is still holding a memory and A
4741
+ HUMAN MUST ACT.
4742
+ 🔴 THE WHITELIST IS DEEP: the nested `origin` is projected key by key too, because the ledger read returns
4743
+ the RAW ON-DISK OBJECT (core validates it without rewriting it), so version skew, a future core key, or
4744
+ core's own documented BY-HAND recovery path could otherwise ride an extra member straight through — one
4745
+ `origin.entryText` would bypass the single most load-bearing exclusion on this face. Members outside the
4746
+ documented three are never serialized; their KEY NAMES are disclosed under `originUnknownKeys` (names
4747
+ only, never values).
4748
+ 🔴 Attribution on this ledger is SELF-DECLARED: `requestId` is filled in by the caller and recorded
4749
+ verbatim, so any operator who can reach the clear valve can book an irreversible clearance under someone
4750
+ else's name. The deployment-side counterpart is a server log line (`memory_origin_cleared`) carrying the
4751
+ VERIFIED principal beside the CLAIMED requestId and the `clearanceId` to join on — DO NOT draw a compliance
4752
+ conclusion from the ledger alone.
4753
+ responses:
4754
+ '200':
4755
+ description: The clearance ledger (whitelist projection, newest-first not guaranteed).
4756
+ content:
4757
+ application/json:
4758
+ schema: { $ref: '#/components/schemas/MemoryOriginClearancesResult' }
4759
+ '401': { $ref: '#/components/responses/Unauthorized' }
4760
+ '403': { description: 'errorCode "auth.operator_only" — origin marking and clearance are operator-only deployment actions.' }
4761
+ '500':
4762
+ description: 'errorCode "internal.memory_control_plane_corrupt" (the ledger cannot be read soundly) or "internal.error".'
4763
+ content:
4764
+ application/json:
4765
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4766
+ '501': { description: 'errorCode "capability.memory_engine_required" — no memory engine whose backend owns the engine control plane.' }
4767
+
4768
+ /v1/memory/origin/entries/{entryId}/clear:
4769
+ parameters:
4770
+ - $ref: '#/components/parameters/PrincipalHeader'
4771
+ - in: path
4772
+ name: entryId
4773
+ required: true
4774
+ schema: { type: string }
4775
+ description: 'The memory entry id, percent-encoded. A malformed escape → 400 `request.id_invalid`.'
4776
+ post:
4777
+ tags: [memory]
4778
+ operationId: memoryOriginClear
4779
+ x-status: live # design/316 item ② (server >= 7.46.0). SDK memory.originClear().
4780
+ summary: The audited UN-MARK valve (operator lane).
4781
+ description: >
4782
+ design/316 item ② — this vouches AWAY the disclosure "this memory came from outside, verify before use",
4783
+ ON BEHALF OF THE HOST. The signer must be the deployment's operator, never a tenant. Gate order and
4784
+ capability bit are those of `GET /v1/memory/origin/external`.
4785
+
4786
+ The clear rides the backend immutability law's ONE legal exit: a write-ahead clearance row (custody = the
4787
+ full entry text) → a COMMITTED tombstone (CAS on the judged rev) → a later-batch re-record of THE SAME ID
4788
+ with the marker stripped. The same id is on purpose: links, usage accounts and the clearance join all keep
4789
+ their key. Every failure leaves the custody row standing, and CALLING THE SAME ENDPOINT AGAIN IS THE
4790
+ RESUME (idempotent).
4791
+
4792
+ 🔴 `requestId` IS THE AUDIT ATTRIBUTION, NOT AN IDEMPOTENCY KEY — the opposite of the same-named field on
4793
+ `POST /v1/memory/erase`. Here it answers "WHO cleared this", recorded verbatim on the row and on the
4794
+ terminal event; passing a NEW id when resuming a pending row is legal and common (the later resumer is a
4795
+ different person). No length cap (the body-size limit bounds it).
4796
+ 🔴 `reason` is shape-checked only (must be a string); WHETHER AN EMPTY REASON IS ACCEPTABLE IS THE ENGINE'S
4797
+ JUDGMENT (422 `memory.origin_clear_invalid`) — restating a `min(1)` in the HTTP layer would be a second
4798
+ source of truth and would permanently keep a code core deliberately minted off the wire.
4799
+ 🔴 THE 200 CARRIES NO "THIS WAS A RESUME" DISCRIMINATOR, and the resume leg DOES NOT LOOK AT YOUR `reason`:
4800
+ the engine resumes the existing pending row, the ledger keeps the OPENER's reason and requestId, and only
4801
+ the terminal EVENT carries yours. So operator B, who resent after a 409 `memory.origin_clear_pending` as
4802
+ instructed and got a 200, may have completed operator A's row. ⇒ AFTER A 200, READ
4803
+ `GET /v1/memory/origin/clearances` BACK and check `reason` / `requestId`; `clearanceId` is that row's key.
4804
+ requestBody:
4805
+ required: true
4806
+ content:
4807
+ application/json:
4808
+ schema: { $ref: '#/components/schemas/MemoryOriginClearRequest' }
4809
+ responses:
4810
+ '200':
4811
+ description: The clearance receipt.
4812
+ content:
4813
+ application/json:
4814
+ schema: { $ref: '#/components/schemas/MemoryOriginClearReceipt' }
4815
+ '400':
4816
+ description: >
4817
+ "request.id_invalid" (the entry id could not be decoded) / "request.request_id_required" (`requestId`
4818
+ missing / empty / non-string — mint a stable id and resend; unlike the erase face, a retry here MAY
4819
+ carry a new one) / "request.body_shape" (the top-level JSON shape is wrong).
4820
+ content:
4821
+ application/json:
4822
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4823
+ '401': { $ref: '#/components/responses/Unauthorized' }
4824
+ '403': { description: 'errorCode "auth.operator_only" — origin marking and clearance are operator-only deployment actions.' }
4825
+ '409':
4826
+ description: >
4827
+ Six core-minted codes, passed through verbatim; BRANCH ON `errorCode` — the six operator actions all
4828
+ differ and folding them into one cell would make people guess.
4829
+ "memory.origin_clear_unknown" — TWO MEANINGS: (1) no committed entry under this id (check the id);
4830
+ (2) a RESUMED clearance row whose entry is gone while this clearance never recorded a tombstone, so
4831
+ core refuses to resurrect a deletion it did not make and the custody bytes stay on the terminal row
4832
+ for a human. Deliberately NOT 404: (2) is not "wrong address", and a generic retry arm would read it
4833
+ as one.
4834
+ "memory.origin_clear_not_marked" — the entry is there but was never marked; use
4835
+ `GET /v1/memory/origin/external` to see who really is.
4836
+ "memory.origin_clear_challenged" — the entry is challenged/latched. THE CLEAR VALVE IS NOT A CHALLENGE
4837
+ EXIT: adjudicate the challenge first (laundering an exclusion through a weaker credential is exactly
4838
+ what this refusal stops).
4839
+ "memory.origin_clear_conflict" — the judgment basis moved since the row was opened (rev changed,
4840
+ projection slug changed, unadopted on-disk changes, tombstone leg refused). Action: call again to
4841
+ re-judge; BYTES ON BOTH SIDES ARE UNCHANGED.
4842
+ "memory.origin_clear_failed" — STOPPED HALFWAY AND RESUMABLE (the re-record leg was refused, or the
4843
+ custody bytes failed their self-check). The row stays `pending` with the bytes in it ⇒ deliberately NOT
4844
+ a 500: the server did not break. Action: fix, then call the same endpoint again to recover. ⚠️ Do not
4845
+ read it as "nothing happened" — the tombstone may ALREADY be committed, and that row's `custodyBytes`
4846
+ on the clearances ledger is the notice.
4847
+ "memory.origin_clear_pending" — there is ALREADY a pending row on this entry (core's words: the pending
4848
+ row is a resume seat, not a queue). This is CONCURRENCY, not a fault ⇒ resend, and the next call walks
4849
+ into the resume leg. ⚠️ See the 200's note: that resume completes SOMEONE ELSE'S row.
4850
+ content:
4851
+ application/json:
4852
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4853
+ '422':
4854
+ description: >
4855
+ Two core-minted SEMANTIC refusals (the body is valid JSON and the shape is right; what cannot be
4856
+ produced is MEANING). "memory.origin_clear_unattributed" — an empty `requestId` (core's words: refused,
4857
+ never defaulted; a clearance is the host signing for a foreign memory and a signature needs a name).
4858
+ ⚠️ Through this route it is UNREACHABLE today (the route's own shape gate answers 400
4859
+ `request.request_id_required` first); it is documented because core owns the refusal, and the day that
4860
+ gate is loosened it must land as a 422 rather than as "the server broke".
4861
+ "memory.origin_clear_invalid" — an empty `reason`; THIS one really does reach the wire, because the
4862
+ HTTP layer deliberately does not restate core's `min(1)`.
4863
+ content:
4864
+ application/json:
4865
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4866
+ '500':
4867
+ description: 'errorCode "internal.memory_control_plane_corrupt" or "internal.error" (unclassified, text not echoed).'
4868
+ content:
4869
+ application/json:
4870
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
4871
+ '501': { description: 'errorCode "capability.memory_engine_required" — no memory engine whose backend owns the engine control plane.' }
4872
+
4520
4873
  /v1/outcomes:
4521
4874
  parameters:
4522
4875
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -6257,6 +6610,30 @@ components:
6257
6610
  type: array
6258
6611
  items: { type: string }
6259
6612
  description: Tool names to withhold from this task's roster.
6613
+ excludeAllTools:
6614
+ type: boolean
6615
+ enum: [true]
6616
+ description: >
6617
+ (server >= 7.90.0, contract G.9b / acceptance G-40) UNMOUNT THE WHOLE TOOL SURFACE for this run —
6618
+ caller tools, scaffolding, hands (Bash/Edit/Write…), MCP, A2A and skills are all left off, the model
6619
+ has nothing to call, and the outcome is necessarily plain text. On the wire: the tail's first
6620
+ `wiring_manifest` has `tools.count === 0` with an empty `entries` (disclosed ONCE, one frame per leg),
6621
+ and the stream carries zero `tool_start` / `tool_end` / `tool_disclosure` frames.
6622
+ 🔴 DELIBERATELY NOT FOLDED INTO `excludeTools`: `"*"` is a LEGAL caller tool name, so "unmount
6623
+ everything" cannot be spelled `excludeTools:["*"]` without collapsing it with "unmount the one tool
6624
+ called `*`". Both keys may ride together and each takes effect — no conflict, no 400.
6625
+ 🔴 `true` IS THE ONLY VALUE THAT MEANS ANYTHING: the server writes only a literal `true` onto the
6626
+ engine's TaskSpec, because sending `false` merely restates a default the ENGINE owns (absent /
6627
+ `false` ⇒ roster unchanged). A non-boolean is a 400 `request.field_invalid` on the synchronous submit
6628
+ leg; the DURABLE resume leg does not reject it (replaying a stored body — a 4xx there would brick an
6629
+ already-parked run) and degrades it to "key absent".
6630
+ 🔴 PROBED BY THE KEY CLOSED SET, NOT BY A CAPABILITY BIT: a server that has not wired it (<= 7.89.x)
6631
+ treats it as an unknown top-level key — 400 `request.body_shape` naming it verbatim under
6632
+ `unknownKeys`, so trial-by-400 IS the probe (one key, one probe path; a second one is deliberately
6633
+ not grown).
6634
+ ⚠️ SCOPE: the A2A inbound face (`message/send`) does NOT carry this key — that leg's submit body is
6635
+ rebuilt server-side from the protocol message as a CLOSED four-field set, so putting the key in an
6636
+ A2A message is INERT and does not error. An A2A peer that wants a zero-tool run routes by skill.
6260
6637
  deferTools:
6261
6638
  type: array
6262
6639
  items: { type: string }
@@ -8319,6 +8696,436 @@ components:
8319
8696
  updatedAtMs: { type: integer }
8320
8697
  pullTruncated: { type: boolean, enum: [true] }
8321
8698
 
8699
+ MemoryTransferEvidence:
8700
+ type: object
8701
+ description: >
8702
+ core `TransferEvidence` — one row of the public `transfers.jsonl` audit face, as an OPEN discriminated
8703
+ form: the three base keys are the contract, everything else is variant payload.
8704
+ 🔴 CONSUMER OBLIGATION: tolerate unknown `channel` values (report / skip / display raw) — NEVER an
8705
+ exhaustive `switch` that throws. The channel vocabulary is set by whichever binary wrote the chain, and a
8706
+ chain written by a newer engine must still read here.
8707
+ required: [ev, channel, at]
8708
+ additionalProperties: true
8709
+ properties:
8710
+ ev: { type: string, description: 'Unique event id (the evidence chain''s append-idempotency key).' }
8711
+ channel: { type: string, description: 'The event kind — OPEN ON PURPOSE (see the consumer obligation above).' }
8712
+ at: { type: integer, description: 'ms epoch of the event.' }
8713
+
8714
+ MemoryCommittedBinding:
8715
+ description: >
8716
+ core `CommittedBinding` — the committed-account BINDING half of one ledger row, as ONE tagged definition
8717
+ (never a look-alike copy: an untagged `{scope, slug}` beside `{state: "unbound"}` can be satisfied by a
8718
+ single hybrid object under both readings). `"unbound"` is a LEGAL transitional state (a v1→v2 migration
8719
+ row whose projection could not be derived), never a refusal.
8720
+ oneOf:
8721
+ - type: object
8722
+ required: [state, scope, slug]
8723
+ additionalProperties: true
8724
+ properties:
8725
+ state: { type: string, enum: [bound] }
8726
+ scope: { type: string }
8727
+ slug: { type: string }
8728
+ at: { type: integer }
8729
+ prev:
8730
+ type: object
8731
+ required: [scope, slug, at]
8732
+ additionalProperties: true
8733
+ properties:
8734
+ scope: { type: string }
8735
+ slug: { type: string }
8736
+ at: { type: integer }
8737
+ - type: object
8738
+ required: [state]
8739
+ additionalProperties: true
8740
+ properties:
8741
+ state: { type: string, enum: [unbound] }
8742
+
8743
+ MemoryProvenanceBinding:
8744
+ description: >
8745
+ `EntryProvenanceAccount.binding` — FOUR states without collapse: `bound` / `unbound` (the shared tagged
8746
+ {@link MemoryCommittedBinding}), `absent` (the account has NO row for this id) and `unknown` (the backend
8747
+ has no audit-snapshot capability).
8748
+ 🔴 `unknown` ("cannot judge") MUST NEVER be folded into `absent` ("does not exist") — the two lead to
8749
+ opposite compliance conclusions.
8750
+ oneOf:
8751
+ - { $ref: '#/components/schemas/MemoryCommittedBinding' }
8752
+ - type: object
8753
+ required: [state]
8754
+ additionalProperties: true
8755
+ properties:
8756
+ state: { type: string, enum: [absent] }
8757
+ - type: object
8758
+ required: [state, reason]
8759
+ additionalProperties: true
8760
+ properties:
8761
+ state: { type: string, enum: [unknown] }
8762
+ reason: { type: string, enum: [audit-snapshot-capability-absent] }
8763
+
8764
+ EntryProvenanceAccount:
8765
+ type: object
8766
+ description: >
8767
+ 200 body of GET /v1/memory/entries/{entryId}/provenance — core `EntryProvenanceAccount` ITSELF (the server
8768
+ neither wraps, projects nor pads it). An ASSEMBLY, not a second account: every field is read from an
8769
+ authority that already exists (lineage / challenges / pollution = the engine's control plane;
8770
+ binding / content / custody = the backend's committed account + evidence chain).
8771
+ 🔴 OPEN on purpose, like every other 2xx body of this family — the enforcement is ASYMMETRIC: a REQUEST
8772
+ body is policed by the server's own strict gate (declaring it closed is an honest description), while a
8773
+ RESPONSE body has no enforcer at all, so a consumer that closes one only turns "the producer shipped
8774
+ first" into "this legal response failed validation". Tolerate new keys here; never reject on them.
8775
+ 🔴 `v` is a version envelope: a consumer reading `v > 1` must REFUSE, never reinterpret.
8776
+ required: [v, id, binding, contributors, contentState, custody]
8777
+ additionalProperties: true
8778
+ properties:
8779
+ v: { type: integer, enum: [1] }
8780
+ id: { type: string }
8781
+ binding: { $ref: '#/components/schemas/MemoryProvenanceBinding' }
8782
+ contributors:
8783
+ type: array
8784
+ description: >
8785
+ The committed lineage BY-ENTRY projection, each row joined with its session's pollution record where
8786
+ one exists. PENDING (unsettled) lineage rows are never contributions — they surface as the
8787
+ `lineage_pending` exclusion instead.
8788
+ items:
8789
+ type: object
8790
+ required: [sessionId, lastRev]
8791
+ additionalProperties: true
8792
+ properties:
8793
+ sessionId: { type: string }
8794
+ lastRev: { type: string }
8795
+ lastAt: { type: integer }
8796
+ polluted:
8797
+ type: object
8798
+ required: [at, reason]
8799
+ additionalProperties: true
8800
+ properties:
8801
+ at: { type: integer }
8802
+ reason: { type: string }
8803
+ exclusion:
8804
+ type: object
8805
+ description: "The model-visible read faces' withholding state for this id, when any."
8806
+ required: [code]
8807
+ additionalProperties: true
8808
+ properties:
8809
+ code: { type: string, enum: [challenged, lineage_pending] }
8810
+ generation: { type: integer }
8811
+ at: { type: integer }
8812
+ contentState:
8813
+ type: string
8814
+ enum: [present, unavailable, capability-absent, absent]
8815
+ description: >
8816
+ The content half, LINKED to `binding` by construction: `absent` ⇔ binding `absent`;
8817
+ `capability-absent` ⇔ binding `unknown`; a row answers `present` (committed content servable — only
8818
+ then may `ingest` ride) or `unavailable` ("cannot read it" stays distinct from "does not exist").
8819
+ ingest:
8820
+ type: object
8821
+ description: "The typed repo-file ingest provenance carried by the committed content's frontmatter (with the un-whitewashable `trust` marker). Present only with `contentState: present`."
8822
+ required: [kind, path, contentHash, ingestedAt]
8823
+ additionalProperties: true
8824
+ properties:
8825
+ kind: { type: string, enum: [repo_file] }
8826
+ path: { type: string }
8827
+ contentHash: { type: string }
8828
+ ingestedAt: { type: integer }
8829
+ trust: { type: string, enum: [untrusted] }
8830
+ custody:
8831
+ type: object
8832
+ description: >
8833
+ The per-id evidence-chain read. `damaged` = the chain's integrity is impeached while a sound reading
8834
+ still answers (torn tail / evidence loss / ev-identity contradiction; `reason` carries the detail when
8835
+ one is known). `capability-absent` = the backend has no custody face — `events` is then empty and is
8836
+ NEVER fabricated.
8837
+ required: [state, events]
8838
+ additionalProperties: true
8839
+ properties:
8840
+ state: { type: string, enum: [complete, damaged, capability-absent] }
8841
+ events: { type: array, items: { $ref: '#/components/schemas/MemoryTransferEvidence' } }
8842
+ reason: { type: string }
8843
+
8844
+ MemoryErasureRequest:
8845
+ type: object
8846
+ description: >
8847
+ Request body of POST /v1/memory/erase.
8848
+ 🔴 `requestId` is THIS request's IDEMPOTENCY IDENTITY and neither the server nor the engine ever mints one
8849
+ — a retry MUST carry the SAME id (a lost ack must not open a second real delete). Contrast
8850
+ `MemoryOriginClearRequest.requestId`, which is an AUDIT ATTRIBUTION and may legitimately change on a resume.
8851
+ 🔴 `allowUnevidenced` is NEVER INJECTED by the server: the degraded lane is a human authorization statement.
8852
+ required: [requestId, select]
8853
+ additionalProperties: false
8854
+ properties:
8855
+ requestId: { type: string, minLength: 1 }
8856
+ select:
8857
+ type: object
8858
+ description: >
8859
+ The erasure selector. The documented forms are `{ids: string[]}` / `{scope: string}` /
8860
+ `{sessionId: string}`, EXACTLY ONE of them. Declared OPEN on purpose: the rule's single owner is core
8861
+ (`erasureRequestInvalid`) and the HTTP layer validates only the top-level structure — restating the
8862
+ field table here would be a second source of truth, and would pre-reject a fourth form core may grow.
8863
+ A form-right-meaning-wrong selector is answered 400 `config.memory_erasure_request` by the engine.
8864
+ additionalProperties: true
8865
+ allowUnevidenced:
8866
+ type: boolean
8867
+ description: "Opt-in to the evidence-capability-ABSENT degradation lane. Default = a loud refusal (409 `memory.erasure_evidence_unavailable`)."
8868
+
8869
+ MemoryErasedBinding:
8870
+ description: "core `ErasedBinding` — one erased row's coordinates (the snapshot's tagged binding law, without the account-trace extras)."
8871
+ oneOf:
8872
+ - type: object
8873
+ required: [state, scope, slug]
8874
+ additionalProperties: true
8875
+ properties:
8876
+ state: { type: string, enum: [bound] }
8877
+ scope: { type: string }
8878
+ slug: { type: string }
8879
+ - type: object
8880
+ required: [state]
8881
+ additionalProperties: true
8882
+ properties:
8883
+ state: { type: string, enum: [unbound] }
8884
+
8885
+ MemoryErasureAttestation:
8886
+ type: object
8887
+ description: >
8888
+ 200 body of POST /v1/memory/erase — core `MemoryErasureAttestation` ITSELF, the deliverable a compliance
8889
+ caller archives. Every claim is black-box re-checkable.
8890
+ 🔴 READ `evidenceCapability` BEFORE DECIDING WHETHER A RETRY IS SAFE — the two legs have different
8891
+ contracts (see the endpoint description). OPEN for the same reason as `EntryProvenanceAccount`.
8892
+ required: [v, requestId, at, status, evidenceCapability, custodyState, select, selectHash, resolvedIds, erased, notFound, conflicts, residuals]
8893
+ additionalProperties: true
8894
+ properties:
8895
+ v: { type: integer, enum: [1], description: 'Version envelope — a consumer reading `v > 1` must refuse, never reinterpret.' }
8896
+ requestId: { type: string }
8897
+ at: { type: integer }
8898
+ status:
8899
+ type: string
8900
+ enum: [complete, partial]
8901
+ description: '`complete` ⇔ conflicts empty ∧ every pinned id ∈ erased ∪ erasedPreviously ∪ (notFound with `custodyState === "complete"`). Everything else is `partial` (a replay converges it).'
8902
+ evidenceCapability:
8903
+ type: string
8904
+ enum: [journal, none]
8905
+ description: '`journal` = the File evidence path; `none` = the explicit `allowUnevidenced` degradation lane. ALWAYS PRESENT — a silent downgrade is the forbidden shape.'
8906
+ custodyState:
8907
+ type: string
8908
+ enum: [complete, damaged, capability-absent]
8909
+ description: "The evidence chain's integrity as read at assembly. `damaged` ⇒ every notFound row carries `historyUnknown` and `erasedPreviously` is never produced."
8910
+ select: { type: object, additionalProperties: true, description: 'The selector, echoed verbatim (same open form as the request).' }
8911
+ selectHash: { type: string, description: "sha256 hex of the selector's canonical JSON — equals the anchor row's." }
8912
+ resolvedIds: { type: array, items: { type: string }, description: "The pinned id set (= the anchor row's ids; a replay reads it back, never re-resolves)." }
8913
+ erased:
8914
+ type: array
8915
+ items:
8916
+ type: object
8917
+ required: [id, rev, binding, projectionsRemoved, sessions]
8918
+ additionalProperties: true
8919
+ properties:
8920
+ id: { type: string }
8921
+ rev: { type: string }
8922
+ binding: { $ref: '#/components/schemas/MemoryErasedBinding' }
8923
+ evidenceEv: { type: string, description: "The delete evidence row's `ev` — absent in `none` mode." }
8924
+ projectionsRemoved: { type: integer, description: 'Distinct census-confirmed projection files this transaction physically deleted (the sweep arm can answer > 1).' }
8925
+ sessions: { type: array, items: { type: string }, description: 'Contributing sessions (`none` mode answers []). A replay after a crash carries the chain''s set — an honest downgrade, never a fabricated rebuild.' }
8926
+ notFound:
8927
+ type: array
8928
+ description: >
8929
+ No row at judgment time and not claimable as erased under THIS request. Covers never-existed /
8930
+ organically deleted / deleted by another request — the chain can distinguish them, the attestation does
8931
+ not pretend to. `historyUnknown` ⇔ `custodyState !== "complete"`.
8932
+ items:
8933
+ type: object
8934
+ required: [id]
8935
+ additionalProperties: true
8936
+ properties:
8937
+ id: { type: string }
8938
+ historyUnknown: { type: boolean, enum: [true] }
8939
+ erasedPreviously:
8940
+ type: array
8941
+ description: 'Chain-read rows: an origin-ABSENT delete row carrying THIS requestId. Present only when non-empty; never produced in `none` mode or over a damaged chain.'
8942
+ items:
8943
+ type: object
8944
+ required: [id, ev, at]
8945
+ additionalProperties: true
8946
+ properties:
8947
+ id: { type: string }
8948
+ ev: { type: string }
8949
+ at: { type: integer }
8950
+ from:
8951
+ type: object
8952
+ required: [scope, slug]
8953
+ additionalProperties: true
8954
+ properties:
8955
+ scope: { type: string }
8956
+ slug: { type: string }
8957
+ conflicts:
8958
+ type: array
8959
+ description: 'Per-id PLANNING-stage faults (io/plan errors under the lock — there is no CAS loser inside a single lock span). Non-fatal; a replay converges them.'
8960
+ items:
8961
+ type: object
8962
+ required: [id, reason]
8963
+ additionalProperties: true
8964
+ properties:
8965
+ id: { type: string }
8966
+ reason: { type: string }
8967
+ residuals:
8968
+ type: object
8969
+ required: [quarantineHits, quarantineOpaque, propagation]
8970
+ additionalProperties: true
8971
+ properties:
8972
+ quarantineHits: { type: array, items: { type: string }, description: 'Quarantine files whose frontmatter id is in the pinned set (PRESERVE-NOT-DELETE: the attestation enumerates, an explicit second action clears).' }
8973
+ quarantineOpaque: { type: integer, description: 'Quarantine files that could not be attributed (fragments / non-entry shapes) — the honest BOUND of the enumeration.' }
8974
+ indexUncleared: { type: array, items: { type: string }, description: 'Derived MEMORY.md index files that may STILL carry a line naming an erased entry (the erase-time sweep could not read or rewrite them). Present only when non-empty.' }
8975
+ propagation:
8976
+ type: string
8977
+ enum: [local-store-only]
8978
+ description: 'Propagation boundary, SELF-DECLARED ALWAYS: deletion is a THIS-STORE fact; remote-peer convergence is the sync deployment''s half to prove.'
8979
+
8980
+ MemoryEntryOriginMark:
8981
+ type: object
8982
+ description: >
8983
+ core `MemoryEntryOrigin` — one entry's EXTERNAL-ORIGIN marker: the content came from material the model
8984
+ READ, not from first-hand facts in the session. The marker means "verify before use", not "poisoned".
8985
+ 🔴 `cause` MAY BE ABSENT AND IS NEVER GUESSED: an entry marked before the cause vocabulary existed is
8986
+ honestly without one.
8987
+ required: [taint, at]
8988
+ additionalProperties: true
8989
+ properties:
8990
+ taint: { type: string, enum: [external] }
8991
+ cause: { type: string, enum: [observed, derived, static, unattributed] }
8992
+ at: { type: integer }
8993
+
8994
+ MemoryOriginExternalEntry:
8995
+ type: object
8996
+ description: 'One row of GET /v1/memory/origin/external: the marker plus the FULL provenance account. Open (statement-of-fact posture).'
8997
+ required: [id, slug, scope, origin, provenance]
8998
+ additionalProperties: true
8999
+ properties:
9000
+ id: { type: string }
9001
+ slug: { type: string }
9002
+ scope: { type: string }
9003
+ origin: { $ref: '#/components/schemas/MemoryEntryOriginMark' }
9004
+ provenance:
9005
+ allOf: [{ $ref: '#/components/schemas/EntryProvenanceAccount' }]
9006
+ description: 'IS the `EntryProvenanceAccount` of GET /v1/memory/entries/{entryId}/provenance — same discriminated form, same pass-through discipline. A CHALLENGED marked entry is still listed; the exclusion rides in here.'
9007
+
9008
+ MemoryOriginExternalResult:
9009
+ type: object
9010
+ description: >
9011
+ 200 body of GET /v1/memory/origin/external.
9012
+ 🔴 THE ANSWER COVERS ONLY THE SCOPES YOU NAMED, AND AN EMPTY RESULT IS NEVER "THE STORE IS CLEAN" — this
9013
+ server generation has no scope-enumeration read face, so incompleteness is SILENT. `scopes` echoes the set
9014
+ ACTUALLY AUDITED (after trimming / dropping empty segments / de-duplicating in order) so the coverage is
9015
+ observable rather than inferred.
9016
+ ⚠️ `withheldOriginTagged` (an integer, minted only when > 0) rides the OPEN tail when the request did NOT
9017
+ declare `originAware=1`: every row of this face is marked, so the fence withholds them all. A caller that
9018
+ declares the flag (the SDK always does) never sees the key.
9019
+ required: [scopes, entries]
9020
+ additionalProperties: true
9021
+ properties:
9022
+ scopes: { type: array, items: { type: string } }
9023
+ entries: { type: array, items: { $ref: '#/components/schemas/MemoryOriginExternalEntry' } }
9024
+
9025
+ MemoryOriginClearanceEvent:
9026
+ type: object
9027
+ description: 'One TERMINAL event of a clearance (who / when / to / detail is the whole of the concept "event"). `detail` absent ⇒ the KEY is absent — on an audit face "no detail written" and "the detail is empty" are not the same thing.'
9028
+ required: [eventId, at, to, requestId]
9029
+ additionalProperties: true
9030
+ properties:
9031
+ eventId: { type: string }
9032
+ at: { type: integer }
9033
+ to: { type: string, enum: [done, failed] }
9034
+ requestId: { type: string, description: "THIS event's resolver (the opener on the normal path; a later resumer on a crash path)." }
9035
+ detail: { type: string, description: 'Mechanical detail (landing slug, refusal reason) — engine-composed, bounded by the writer.' }
9036
+
9037
+ MemoryOriginClearanceRow:
9038
+ type: object
9039
+ description: >
9040
+ One row of GET /v1/memory/origin/clearances — an EXPLICIT WHITELIST PROJECTION of core's
9041
+ `OriginClearanceRow`, guarded on the server side by a compile-time set-difference gate.
9042
+ 🔴 core's `entryText` (the cleared entry's full body) is STRIPPED BY THE SERVER and its presence disclosed
9043
+ as `custodyBytes`; the nested `origin` is projected KEY BY KEY, with any member outside the documented
9044
+ three disclosed by NAME ONLY under `originUnknownKeys`.
9045
+ ⚠️ THAT IS THE SERVER'S RESTRAINT, NOT A CLIENT-SIDE STRICTNESS — this schema stays OPEN like every 2xx
9046
+ body of the family. The server decides WHAT TO SEND; a consumer schema decides WHAT TO TOLERATE, and
9047
+ closing the latter only breaks on version skew. The whitelist is written down so a reader knows what the
9048
+ server promises, and `entryText` is simply NOT DECLARED so nobody reads a promise that was never made.
9049
+ required: [clearanceId, entryId, scope, slug, baseRev, origin, requestId, reason, at, status, events, custodyBytes]
9050
+ additionalProperties: true
9051
+ properties:
9052
+ clearanceId: { type: string }
9053
+ entryId: { type: string, description: 'The cleared entry. A clearance KEEPS THE SAME ID, so this key joins the clearance to the entry across its whole life.' }
9054
+ scope: { type: string }
9055
+ slug: { type: string }
9056
+ baseRev: { type: string, description: "The committed rev the clearance was judged against (the tombstone's CAS anchor)." }
9057
+ origin:
9058
+ allOf: [{ $ref: '#/components/schemas/MemoryEntryOriginMark' }]
9059
+ description: 'THE MARKER THAT WAS CLEARED, deep-projected to the documented three keys (audit: what exactly did the host vouch away).'
9060
+ originUnknownKeys:
9061
+ type: array
9062
+ items: { type: string }
9063
+ description: "Present only when the on-disk row's `origin` carried members outside the documented three — KEY NAMES ONLY, values never reach the wire. A normal row does not have this key."
9064
+ requestId:
9065
+ type: string
9066
+ description: >
9067
+ WHO cleared it — the mandatory audit attribution, never defaulted by core.
9068
+ 🔴 IT IS SELF-DECLARED, NOT VERIFIED: any operator who can reach the valve can book a clearance under
9069
+ another name. The deployment-side counterpart is the server log line `memory_origin_cleared` (verified
9070
+ principal + claimed requestId + clearanceId to join on).
9071
+ reason: { type: string, description: "WHY — the host's stated ground, never defaulted (free text)." }
9072
+ at: { type: integer, description: 'When the row was opened.' }
9073
+ status: { type: string, enum: [pending, done, failed] }
9074
+ tombstonedAt: { type: integer, description: "This clearance's tombstone commit moment (SENT ONLY WHEN PRESENT). A row without it refuses conservatively on the resume leg." }
9075
+ events: { type: array, items: { $ref: '#/components/schemas/MemoryOriginClearanceEvent' } }
9076
+ custodyBytes:
9077
+ type: integer
9078
+ description: >
9079
+ The custody text's UTF-8 BYTE COUNT (not a JS string length — that counts UTF-16 code units and would
9080
+ under-report a CJK entry by up to two thirds, and this number is the only evidence of how much the
9081
+ recovery seat still holds). `0` = this row carries no bytes; `> 0` on a `pending` row = the crash
9082
+ recovery seat still holds a memory's body AND A HUMAN MUST ACT (call the clear endpoint again).
9083
+
9084
+ MemoryOriginClearancesResult:
9085
+ type: object
9086
+ description: '200 body of GET /v1/memory/origin/clearances.'
9087
+ required: [clearances]
9088
+ additionalProperties: true
9089
+ properties:
9090
+ clearances: { type: array, items: { $ref: '#/components/schemas/MemoryOriginClearanceRow' } }
9091
+
9092
+ MemoryOriginClearRequest:
9093
+ type: object
9094
+ description: >
9095
+ Request body of POST /v1/memory/origin/entries/{entryId}/clear.
9096
+ 🔴 `requestId` is the AUDIT ATTRIBUTION, not an idempotency key (the opposite of the same-named field on
9097
+ POST /v1/memory/erase): passing a NEW id when resuming a pending row is legal and common. No length cap.
9098
+ 🔴 `reason` is shape-checked only; whether an EMPTY reason is acceptable is the ENGINE's judgment
9099
+ (422 `memory.origin_clear_invalid`).
9100
+ required: [requestId, reason]
9101
+ additionalProperties: false
9102
+ properties:
9103
+ requestId: { type: string, minLength: 1 }
9104
+ reason: { type: string }
9105
+
9106
+ MemoryOriginClearReceipt:
9107
+ type: object
9108
+ description: >
9109
+ 200 body of POST /v1/memory/origin/entries/{entryId}/clear.
9110
+ 🔴 `origin` IS FORWARDED VERBATIM AND MAY CARRY MEMBERS OUTSIDE THE DOCUMENTED THREE. On a freshly opened
9111
+ row it is core's narrow rebuild (`committedOriginOf` mints exactly taint / cause? / at); on the RESUME leg
9112
+ core answers with the ledger row's ORIGINAL object (`clearEntryOrigin` finds the pending row and returns
9113
+ `origin: row.origin`), so version skew, a future core key, or core's own documented by-hand recovery can
9114
+ ride along — on a clearance that ALREADY COMMITTED SUCCESSFULLY. Only the clearances LIST face
9115
+ deep-projects it, which is why this receipt and that row are not the same shape.
9116
+ 🔴 THERE IS NO "THIS WAS A RESUME" DISCRIMINATOR HERE, and the resume leg ignores your `reason` — read
9117
+ GET /v1/memory/origin/clearances back and check `reason` / `requestId` before treating a 200 as "my ground
9118
+ was recorded". `clearanceId` is that row's key.
9119
+ required: [entryId, clearanceId, origin, landedSlug]
9120
+ additionalProperties: true
9121
+ properties:
9122
+ entryId: { type: string }
9123
+ clearanceId: { type: string }
9124
+ origin:
9125
+ allOf: [{ $ref: '#/components/schemas/MemoryEntryOriginMark' }]
9126
+ description: 'The marker that was just cleared, verbatim.'
9127
+ landedSlug: { type: string, description: "The re-recorded entry's landing slug." }
9128
+
8322
9129
  MetricsSummaryOps:
8323
9130
  type: object
8324
9131
  description: >
@@ -16707,9 +17514,9 @@ components:
16707
17514
  type: string
16708
17515
  enum: [darwin, linux]
16709
17516
  description: 'The v1 CLOSED SET. A third value (e.g. `win32`) ⇒ 400 request.field_invalid — never silently accepted.'
16710
- platformArch: { type: string }
16711
- displayName: { type: string, description: 'Echoed back by `GET /v1/devices` as `name`, and HONESTLY ABSENT there when empty.' }
16712
- workspaceRoot: { type: string, minLength: 1, description: 'The device-side root every instruction resolves against. Required and non-empty. Over its byte column ⇒ 400 (a silently truncated value is a DIFFERENT value).' }
17517
+ platformArch: { type: string, maxLength: 32, description: 'At most 32 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound, see the unit convention).' }
17518
+ displayName: { type: string, maxLength: 255, description: 'Echoed back by `GET /v1/devices` as `name`, and HONESTLY ABSENT there when empty. At most 255 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound).' }
17519
+ workspaceRoot: { type: string, minLength: 1, maxLength: 1024, description: 'The device-side root every instruction resolves against. Required and non-empty. At most 1024 **UTF-8 bytes** (producer gates byte length; `maxLength` is the looser character bound) ⇒ over it 400 request.field_invalid (a silently truncated value is a DIFFERENT value).' }
16713
17520
 
16714
17521
  DeviceEnrollResult:
16715
17522
  type: object
@@ -16730,7 +17537,7 @@ components:
16730
17537
  description: 'The `POST /v1/devices/{deviceId}/revoke` body (closed shape; the only accepted key is `reason`). Absent body is folded to `{}` server-side.'
16731
17538
  additionalProperties: false
16732
17539
  properties:
16733
- reason: { type: string, maxLength: 512, description: 'At most 512 BYTES; over it ⇒ 400 request.field_invalid — a silently truncated audit reason is a DIFFERENT reason.' }
17540
+ reason: { type: string, maxLength: 512, description: 'At most 512 **UTF-8 bytes** (the producer gates `Buffer.byteLength(reason, "utf8")`, NOT `.length` — this is the one family where the file''s unit convention''s explicit-UTF-8 carve-out applies); `maxLength: 512` is the deliberately LOOSER character bound (a multi-byte value can pass this schema and still get 400 request.field_invalid naming the field — loud, never a client-side false refusal). A silently truncated audit reason is a DIFFERENT reason.' }
16734
17541
 
16735
17542
  DeviceRevokeResult:
16736
17543
  type: object