cortexdb-mcp 0.8.0__tar.gz → 0.10.0__tar.gz

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 (34) hide show
  1. cortexdb_mcp-0.10.0/COVERAGE.md +221 -0
  2. cortexdb_mcp-0.10.0/PKG-INFO +612 -0
  3. cortexdb_mcp-0.10.0/README.md +595 -0
  4. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/cortexdb_mcp/api.py +10 -2
  5. cortexdb_mcp-0.10.0/cortexdb_mcp/check_call.py +668 -0
  6. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/cortexdb_mcp/config.py +228 -5
  7. cortexdb_mcp-0.10.0/cortexdb_mcp/errors.py +769 -0
  8. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/cortexdb_mcp/insights.py +21 -2
  9. cortexdb_mcp-0.10.0/cortexdb_mcp/render.py +1050 -0
  10. cortexdb_mcp-0.10.0/cortexdb_mcp/server.py +3711 -0
  11. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/pyproject.toml +1 -1
  12. cortexdb_mcp-0.10.0/tests/live/live_stdio.py +829 -0
  13. cortexdb_mcp-0.10.0/tests/test_alignment.py +656 -0
  14. cortexdb_mcp-0.10.0/tests/test_audit.py +348 -0
  15. cortexdb_mcp-0.10.0/tests/test_auth.py +469 -0
  16. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/tests/test_check_call.py +56 -0
  17. cortexdb_mcp-0.10.0/tests/test_corrections.py +341 -0
  18. cortexdb_mcp-0.10.0/tests/test_derivation_policy.py +338 -0
  19. cortexdb_mcp-0.10.0/tests/test_memory_why.py +420 -0
  20. cortexdb_mcp-0.10.0/tests/test_new_tools.py +632 -0
  21. cortexdb_mcp-0.10.0/tests/test_review.py +174 -0
  22. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/tests/test_server.py +53 -3
  23. cortexdb_mcp-0.10.0/tests/test_stdio.py +214 -0
  24. cortexdb_mcp-0.8.0/PKG-INFO +0 -301
  25. cortexdb_mcp-0.8.0/README.md +0 -284
  26. cortexdb_mcp-0.8.0/cortexdb_mcp/check_call.py +0 -266
  27. cortexdb_mcp-0.8.0/cortexdb_mcp/render.py +0 -204
  28. cortexdb_mcp-0.8.0/cortexdb_mcp/server.py +0 -1882
  29. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/Dockerfile +0 -0
  30. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/cortexdb_mcp/__init__.py +0 -0
  31. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/cortexdb_mcp/__main__.py +0 -0
  32. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/tests/__init__.py +0 -0
  33. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/tests/test_insights.py +0 -0
  34. {cortexdb_mcp-0.8.0 → cortexdb_mcp-0.10.0}/tests/test_integration.py +0 -0
@@ -0,0 +1,221 @@
1
+ # cortexdb-mcp coverage of CortexDB 0.14.0
2
+
3
+ This file records how `cortexdb-mcp` (the stdio MCP server in this directory) covers the CortexDB 0.14.0 API: the
4
+ audit of every tool that existed before 0.14.0, and what each finding changed. The spec, in order of authority:
5
+ the server code (the `sharedcontext` branch with lanes p5g, p5h and p5j merged), `docs/schemas/openapi.json`,
6
+ `docs/shared_memory/IDENTITY_AND_API_CONTRACT.md` with `docs/API_REFERENCE_V1.md`, and the release-notes drafts
7
+ `docs/shared_memory/PHASE{0..5}_RELEASE_NOTES_DRAFT.md`.
8
+
9
+ The server's own HTTP MCP endpoint (`POST /mcp`, `crates/cortex-api/src/v1/mcp.rs`, `CORTEX_MCP=on`) is a separate
10
+ surface with tools of its own. The stdio server keeps today's tools, output shapes and default profile (design
11
+ §13.8, §14.3); the tools it adds follow `/mcp`'s rule, read by default and writes opt-in.
12
+
13
+ ## What 0.14.0 breaks, and what it did to the tools
14
+
15
+ | 0.14.0 change | Tools it reached | What happened before 0.9.0 | Now |
16
+ |---|---|---|---|
17
+ | **Strict forget and erasure selectors**: a selector field a route does not apply is 422 `unknown_selector_field`, a misspelt layer `unknown_layer`, and the erasure routes refuse the key selectors | `memory_forget`, `memory_delete`, `memory_bulk_delete`, `forget_preview` | Their bodies were valid (only `about_subject`, `memory_ids`, `cascade`, `reason`, `confirm_all`, `from_preview_id`). The same class of mistake was open one level up: FastMCP drops an argument a tool does not take, so `memory_forget(reason, memory_ids=[…])` dropped the ids and forgot the whole scope's derived records | Every body is checked against the strict rules in the tests (`test_audit.py::test_the_bodies_pass_the_strict_selector`). Every tool refuses an argument it does not take, naming it and listing the ones it does (`additionalProperties: false` in each schema), as the server refuses a field it does not apply and `/mcp` refuses one on its destructive tools |
18
+ | **`/v1/forget` cascades**: a forget that names no cascade runs `derived_only`, which deletes derived records only; a whole-scope forget under it deletes no memory and says so in `warnings` | `memory_forget`, `memory_bulk_delete` | `memory_forget` said a call without `query` "deletes everything under the scope"; `memory_bulk_delete` said it deletes "multiple memories" and reported `0 event(s) removed`. The server's `warnings` were dropped | The descriptions say what is deleted (derived records) and what is KEPT (the events), and point to `memory_delete` and `forget_by_key` for memories. Every forget answer lists its derived counts, `requested` and `matched`, its audit id and the server's warnings |
19
+ | **Total confinement**: a token that carries `scopes` holds only the families its entries name (403 `POLICY_DENIED`, `details.link: "confinement"`, `details.capability`) | every tool | A refusal surfaced as `CortexDB returned HTTP 403: {raw json}` | Every refusal is a typed `CortexDBError`: code, retriable, request id, and for a confinement refusal the capability and the `scopes` entry to mint (`<capability>:<scope>`, `<scope>/*`, `<capability>:*`), with the view hint for `scope.read.holistic` and `scope.read.descend`. A signup token names its 18 families, so every tool still works inside the signup user's own subtrees |
20
+ | **Break-glass**: on `cloud_shared_saas` the operator key's data reads (and its in-band writes) are 403 `BREAK_GLASS_REQUIRED` without `X-Cortex-Break-Glass` | every read tool, `memory_forget`/`memory_bulk_delete`/`forget_preview` by query, the insights engine | Nothing could send the header; the insights engine answered "no insights" to the refusal | `CORTEXDB_BREAK_GLASS_REASON`, `_TICKET` and `_NOTE` send it, validated as the server validates it; `BREAK_GLASS_REQUIRED` says to use a person's token or set the two settings. A forget or preview by ids or keys needs none (it reads nothing first) |
21
+ | **`tree.display_name` refused** in version-1 registry writes (422 `INVALID_SCOPE_TREE`) | none: no tool registers scopes | `cortexdb_check_call` did not know `POST /v1/scopes` or `PUT /v1/scopes/tree` | `cortexdb_check_call` flags `tree.display_name` there and points to `PUT /v1/directory/{id}` |
22
+ | **Org trees' views**: at a shared org-tree scope (org, team, group, compartment) a walking view is 422 `VIEW_NOT_ALLOWED_AT_SCOPE`, and a recall naming no view reads granular | `memory_search`, `get_context`, `advanced_search` | They always sent `view` (the default holistic by name), so every search at a team scope was refused | `view` is sent only when `CORTEXDB_VIEW` is set; otherwise the server picks (holistic elsewhere, as before, granular at a shared scope) |
23
+ | **Agent writes redirected** (DB-28): a non-human chain's write whose read record is unknown, tainted or carries a private atom lands in its writer's private space, `redirected_to` | `memory_store`, `entity_link`, `belief_declare` | The receipt's `redirected_to` was dropped: the agent was told it stored at the scope it asked for | The answer names where it landed |
24
+ | **Per-person and chain tokens**: a minted token needs `X-Cortex-Actor` equal to its `sub`; an issuers-file token in `prefix`, `hash` or `directory` mode goes alone | every tool | A minted token without `CORTEXDB_ACTOR` was 401 `ACTOR_MISMATCH` on every call | `X-Cortex-Actor` is read from a PASETO or JWT bearer whose `sub` is an actor id, left out for an IdP subject, and `CORTEXDB_ACTOR=-` sends none |
25
+
26
+ ## The audit, tool by tool
27
+
28
+ "Request" is the body or query the tool sends, checked against the route's handler; "output" the fields it reads;
29
+ "errors" what the agent sees on a refusal. Every tool's errors are the typed errors above, so the column names only
30
+ what is particular to the tool.
31
+
32
+ | Tool | Route | Request | Output | Errors | Changed in 0.9.0 |
33
+ |---|---|---|---|---|---|
34
+ | `cortexdb_check_call` | none (a static contract) | — | — | — | Knows the strict forget body and selector (`unknown_selector_field`, `unknown_layer`, unknown top-level fields), the key selectors and their bounds, the erasure routes' refusals (`key_selector_not_supported`, `UNSUPPORTED_SELECTOR`, `confirm` without `subject`), `tree.display_name`, and the new routes (access check and explain, rollups, publish, notices, the directory) |
35
+ | `memory_store` | `POST /v1/experience` | ok | dropped `redirected_to`, `replayed_from_idempotency`, `warnings` | `RUN_REQUIRED`, `ATOMS_REDIRECT_REFUSED`, confinement | names a redirect, a replay and the server's warnings |
36
+ | `memory_status` | `GET /v1/experience/status` | ok | ignored the server's `resend` and `note`: claimed "re-storing is safe" for every not-found answer, where by event id the server answers `check_first` (erased, forgotten or not visible: do not re-send); said "indexing in progress" for a cancelled write | — | renders `resend` and `note`; takes `tenant_id` (`?scope=`, the alignment pass below) |
37
+ | `memory_search`, `get_context`, `advanced_search` | `POST /v1/recall` | `view` always sent (see above) | ok | `VIEW_NOT_ALLOWED_AT_SCOPE`, confinement of `scope.read.holistic` | `view` only when set |
38
+ | `memory_forget` | `POST /v1/forget` | ok | dropped `warnings`; the description was wrong (see above) | `BREAK_GLASS_REQUIRED` for a query under `deny` | accurate description, warnings, counts |
39
+ | `forget_preview` | `POST /v1/forget/preview` | ok | told the agent to execute a `memory_ids` preview with `memory_forget`, which takes no ids | — | names the tool that sends exactly the previewed selector (`memory_delete` for one id, which now takes `from_preview_id`) and shows `requested`/`matched` |
40
+ | `memory_list` | `GET /v1/events` | sent `offset` as a numeric `cursor`, which the final server refuses (422 `INVALID_CURSOR`: its cursor is `le1.`, newest first, bound to the query) | dropped `next_cursor` | `INVALID_CURSOR` | pages by `cursor` (`offset` is gone) and ends a page with `More: cursor=`; a type is filtered by the server (`labels`), the default type here |
41
+ | `memory_get` | `GET /v1/events/{id}` | ignored `tenant_id`, the exact scope `?scope=` confines the read to, which a cluster routes a by-id read by (a break-glass by-id read without it was 503 `NOTICE_UNAVAILABLE` `scope_unnamed` until the final 0.14.0 server, which decides it by the row's own scope) | ok | `NOTICE_UNAVAILABLE` | sends `tenant_id` as `?scope=` (the event's exact scope) when given |
42
+ | `memory_delete` | `POST /v1/forget` (`memory_ids`) | ok | dropped `matched` and `warnings` (an id stored elsewhere answers `matched: 0`) | — | counts, `matched`, warnings; takes `from_preview_id` |
43
+ | `memory_bulk_delete` | `POST /v1/forget` (`about_subject`), `/v1/forget/preview` | ok | reported events only, always 0 | — | derived counts and warnings; accurate description |
44
+ | `entity_list`, `entity_get`, `entity_edges` | `GET /v1/facts` | ok | ok | — | none |
45
+ | `entity_link` | `POST /v1/experience` | ok | dropped `redirected_to` | as `memory_store` | names a redirect |
46
+ | `belief_declare` | `POST /v1/beliefs` | ok (strictness unchanged; `provenance` is optional) | dropped `redirected_to` | `RUN_REQUIRED`, `FORMAT_UPGRADE_PENDING` (atoms below format 10) | names a redirect |
47
+ | `list_conflicts`, `resolve_conflict`, `claim_history` | `GET /v1/conflicts`, `POST /v1/conflicts/{id}/resolve`, `GET /v1/claims/history` | ok | ok | — | `resolve_conflict` takes `tenant_id` (`?scope=`, the alignment pass below) |
48
+ | `health_check` | `GET /v1/auth/whoami` | ok | dropped `chain` (0.14.0: an agent's chain token names its links) | — | shows `chain` |
49
+ | `get_usage` | `GET /v1/auth/whoami` | ok | dropped `chain` | raised a raw `httpx` error | typed errors; shows the chain |
50
+ | `get_insights`, `cortexdb://insights` | `GET /v1/events`, `/v1/facts`, `/v1/beliefs` | sent only the bearer and the actor | ok | every failure was swallowed: a refused read (`BREAK_GLASS_REQUIRED`, confinement) answered "no notable insights" | reads with the tools' headers; names a layer it could not read, and fails when it read none |
51
+ | `code_explore`, `code_impact`, `code_inventory`, `code_graph` | `/v1/code/*` | ok | ok | `CODE_PLANE_DISABLED`; code reads are D-rows routes, so the operator key needs break-glass under `deny` | none |
52
+ | resources `cortexdb://health`, `cortexdb://episodes` | `GET /v1/auth/whoami`, `GET /v1/events` | ok | ok | — | none |
53
+ | the insights API (`cortexdb_mcp.api`) | as `get_insights` | sent no `X-Cortex-Actor`, so a minted token was 401 | ok | — | the tools' headers |
54
+
55
+ ## The tools 0.9.0 adds
56
+
57
+ Profiles (`--tool-profile`, or `CORTEXDB_TOOL_PROFILE`): `all`, the default, is every tool but the opt-in writes,
58
+ so an existing install keeps its tools and gains the read tools below; `write` adds `publish` and `forget_by_key`;
59
+ `read` lists only the tools that write no memory (`/mcp`'s default); `code` the four code tools. Every tool carries
60
+ MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`), as `/mcp`'s do.
61
+
62
+ | Tool | Profile | Route | What it answers |
63
+ |---|---|---|---|
64
+ | `access_check` | all, read, write | `POST /v1/access/check` | Can a principal (the token's own chain by default, or another: `principal`, `agent`, `app`, `chain_scopes`) read, write or own a scope, or read a memory: ALLOWED or DENIED with the deciding link and each link's path, the delivered role, the label cap, conditions and directory names |
65
+ | `access_explain` | all, read, write | `POST /v1/access/explain` | Why: the home's decision link by link, one decision per atom (a redacted one says so), a derived memory's visible supports, the label against the cap |
66
+ | `rollups` | all, read, write | `GET /v1/rollups`, `/v1/rollups/{id}`, `/v1/rollups/totals` | A unit's rollups at its `aud:roll` home (a unit's path is completed), by kind or topic, one by id with its citations, or the merged typed totals over a month range |
67
+ | `notices` | all, read, write | `GET /v1/access/notices?mine=true` | The caller's own unacknowledged notices at its root: who read what, when and why (a grant, a break-glass read, a sign-in account change), with directory names |
68
+ | `acknowledge_notices` | all, read, write | `POST /v1/access/notices/ack` | Acknowledges notices by `seqs` or a `through` position (changes no memory; a stale position acknowledges nothing and says so) |
69
+ | `directory_lookup` | all, read, write | `GET /v1/directory`, `GET /v1/directory/{id}` | A name or email prefix search, or one entry, with only the fields the caller may read |
70
+ | `publish` | write | `POST /v1/publish` | A person (directly, or through an admitted application whose token says `client: person`) copies an event or a rollup into a home they write; the answer names the copy and its original, and with `consistency` a token to read it back |
71
+ | `forget_by_key` | write | `POST /v1/forget`, `/v1/forget/preview` (`selector.idempotency_keys`, `selector.capture_keys`) | The read-free forget, with its preview and `from_preview_id` |
72
+
73
+ New fields on existing tools, each sent only when used, so a call without them sends the 0.8.0 request (but for `belief_declare`, which sends a fresh `idempotency_key` when none is given, as `memory_store` does):
74
+
75
+ | Tool | Fields |
76
+ |---|---|
77
+ | `memory_search` | `extra_scopes` (at most 40), `coverage`, `at_least` (a consistency token) |
78
+ | `get_context` | the same, and `merged_beliefs` (`beliefs: "merged"`) and `app_mounts` (`mounts: "default"`) |
79
+ | `advanced_search` | `coverage`, `at_least` |
80
+ | `memory_list`, `memory_get` | `at_least` (`?at_least=`) |
81
+ | `memory_store` | `idempotency_key`, `provenance`, `consistency` (`?consistency=1`, the answer's token) |
82
+ | `belief_declare` | `provenance`, `idempotency_key` |
83
+
84
+ ## CortexDB 0.14.0's route and field families
85
+
86
+ What this server covers of each family the 0.14.0 clients cover, and why the rest is not here. The stdio server is an
87
+ agent's tool surface, not an SDK: an operator's or an identity provider's route has no tool.
88
+
89
+ | Family | Covered | By, or why not |
90
+ |---|---|---|
91
+ | Access API: `check`, `explain` | yes | `access_check`, `access_explain` |
92
+ | Access API: `limits`, `lookup/scopes`, `lookup/principals`, `enrich` | no | sizing and planning inputs for an application or an Owner; `access_check` answers an agent's question about a given scope |
93
+ | AuthZEN (`/access/v1/*`, `/.well-known/authzen-configuration`) | no | a policy decision point's interface for policy engines |
94
+ | Change sets (`/v1/access/changes*`) | no | registry administration (CortexOS's compiler, an Owner) |
95
+ | Watch | no | the root's Owner's and the operator's feed |
96
+ | Notices and their acknowledgement | yes | `notices`, `acknowledge_notices` |
97
+ | Restores (`/v1/access/{records,restore/ack}`, `/v1/admin/access/restore`) | no | an access source's or the operator's acknowledgement; a pending root's refusal (`ACCESS_RESTORE_PENDING`) is mapped |
98
+ | Source containers (`rehome`, `watermarks`, `accounts/resolve`) | no | a connector's work; a source's watermark reaches `coverage` |
99
+ | Applications (`/v1/apps/{id}`) | no | administration; `app_mounts` reads an application's mounts |
100
+ | Directory (`/v1/directory[/{id}]`) | reads | `directory_lookup`; writes are an Owner's or the operator's |
101
+ | SCIM (`/scim/v2/*`) | no | an identity provider's interface; a SCIM error body is parsed if one reaches a tool |
102
+ | Rollups (`/v1/rollups`, `/totals`, `/{id}`) | yes | `rollups`; `/v1/admin/rollups/status` is the operator's |
103
+ | Publish (`POST /v1/publish`) | yes | `publish` (write profile) |
104
+ | Erasures (`/v1/erasures/{person,tenant}`, `subject: {email}`) | no | the operator's GDPR jobs; `cortexdb_check_call` knows their selector rules |
105
+ | The read-free forget (`/v1/forget` key selectors) | yes | `forget_by_key` (write profile) |
106
+ | Admin (`/v1/admin/access/*`, `/v1/admin/auth/reload`) | no | operator routes |
107
+ | Read fields: `extra_scopes` (40), `coverage`, `beliefs: "merged"`, `mounts: "default"`, `consistency`, `purpose` | yes | the recall tools (above); `purpose` on the access tools too |
108
+ | Write fields: `provenance`, `?consistency=1` | yes | `memory_store`, `belief_declare` |
109
+ | Consistency tokens: `consistency_token` in answers, `?at_least=` on GETs | yes | shown when returned; `memory_list`, `memory_get` |
110
+ | Tokens: confinement, lifetime ceilings, `act`, issuers-file tokens | yes | any bearer in `CORTEXDB_API_KEY`, `X-Cortex-Actor` from the token, typed refusals |
111
+ | The operator key's break-glass | yes | `CORTEXDB_BREAK_GLASS_*`; `BREAK_GLASS_REQUIRED` and a malformed header's `INVALID_HEADER` mapped |
112
+ | The subject-token shadow | yes | `CORTEXDB_SUBJECT_TOKEN` |
113
+ | New error codes, retriable ones marked | yes | `CortexDBError`: the envelope's code, `retriable` and `Retry-After`, with what to do for each code a caller can act on |
114
+ | Format 10 arming (`FORMAT_UPGRADE_PENDING` on an unarmed node) | yes | mapped by `details.reason` |
115
+
116
+ ## The alignment pass: the final 0.14.0 server
117
+
118
+ What changed on the server after 0.9.0 was first built (`sharedcontext` 8b8bfb6d), and what the tools do about it.
119
+
120
+ | Server change | Tools | Now |
121
+ |---|---|---|
122
+ | `GET /v1/events` pages with a newest-first cursor (`le1.`) bound to its query; any other cursor is 422 `INVALID_CURSOR` | `memory_list` | `cursor` replaces `offset`; each page ends with `More: cursor=<next>`; `episode_type` is the server's `labels` filter, so the cursor pages the filtered listing |
123
+ | Codes: 400 `INVALID_HEADER` for any header that does not parse, the break-glass header's included (`details {part, field, reason}`); 410 `ROLLUP_SUPERSEDED` (`details.current`); 422 `act_not_delegable` (and the token's 401 `INVALID_TOKEN_CLAIM`); 422 `field_not_applied`; 504 `DB_TIMEOUT`; 500 `INTERNAL_ERROR` (was `INTERNAL`); 500 `SCOPE_STORAGE_MISCONFIGURED`; 503 `WAL_OUTCOME_UNKNOWN` (was `SCOPE_MUTATION_UNKNOWN`); 405 `METHOD_NOT_ALLOWED`; every envelope's `request_id` | every tool | each says what to do: the setting behind the header, the current rollup to read or publish, the employee's own chain, the fields to drop, to narrow a timed-out call; `memory_store`'s retriable refusal names the idempotency key to retry with, so a write whose outcome is unknown is replayed, not stored twice. `IDEMPOTENCY_CONFLICT` and `CLUSTER_ROUTE_UNSUPPORTED` are mapped too. `cortexdb_check_call` flags a `field_not_applied` forget. The client keeps no table of statuses or retriable flags: it reads each envelope's |
124
+ | `read_purpose_v1`: a read declares `purpose` (`[a-z0-9_-]{1,64}`) on `/v1/recall`, `/recall/stream`, `/answer`, `/compose` and `POST /v1/access/check`/`explain`; a roster path whose `when` names it holds only then, and every decision line records it | `memory_search`, `get_context`, `advanced_search`, `access_check`, `access_explain` | `purpose` on each, default `CORTEXDB_PURPOSE`; checked against the grammar before anything is sent (the setting's error names it); a call naming none sends the request it sent before. The layer GETs (`memory_list`, `memory_get`, the entity tools) take no purpose on the server, so none is sent there. `cortexdb_check_call` checks the grammar |
125
+ | `derivation_use_rules_v1` (CortexDB 0.15.0): a read names its `session` on `/v1/recall`, `/recall/stream`, `/answer` and `/compose`; where a read-time use rule is in force, memory from other sessions or older than its window is withheld and `warnings` says `use_rule_withheld: <n> events from earlier sessions` | `memory_search`, `get_context`, `advanced_search` | `session` on each, per call (no setting: one server serves many conversations); checked before anything is sent (1 to 256 bytes with no control character); a call naming none sends the request it sent before. The layer GETs take none on the server. `cortexdb_check_call` checks the grammar |
126
+ | Forget and its preview gain `events_under_legal_hold` and `derived_under_legal_hold` (facts, beliefs, episodes, understanding): what a legal hold keeps, counted in no deleted field | `memory_forget`, `memory_delete`, `memory_bulk_delete` (and its dry run), `forget_preview`, `forget_by_key` (and its preview) | each answer says what the hold kept ("Kept under a legal hold", a preview's "Would keep"): the events, erased when the hold is released, and the derived records by layer; nothing when it kept nothing |
127
+ | The by-id reads take `?scope=` (a row elsewhere answers 404), and a cluster routes every by-id route by it, refusing one without (501 `CLUSTER_ROUTE_UNSUPPORTED`); `include_vectors=true` returns the embeddings | `memory_get`, `memory_status`, `resolve_conflict` | `memory_get` sent it already; `memory_status` (the exact scope the write went to) and `resolve_conflict` (the scope the conflict was listed at) take `tenant_id` and send it as `?scope=`, only when given, so a single node's call is unchanged. `rollups` by id and `directory_lookup` by id send their home and root already. No tool has a by-id fact, belief, concept or lifecycle read. `include_vectors` is not sent: an embedding is no text an agent reads, and it would fill the tool's answer |
128
+ | `POST /v1/beliefs` takes `idempotency_key` (at most 256 bytes): the same key at the same scope answers the first declaration's belief, another declaration under it is 409 `IDEMPOTENCY_CONFLICT` | `belief_declare` | takes `idempotency_key`, and sends a fresh one when none is given (as `memory_store` does), so a retriable refusal names the key to retry with and a declaration whose outcome is unknown is never made twice; a server before 0.14.0 ignores the field |
129
+ | Publish: a person may publish through an application the tenant admitted (`client: person`); refused are an agent's `act`, a run, a service subject and an `app` token with no `client`. `?consistency=1` returns `consistency_token`; a superseded rollup is 410 `ROLLUP_SUPERSEDED` | `publish` | the description and the `PUBLISH_PERSON_REQUIRED` hint no longer list "an application" among the refused, and say which chains are; `consistency` sends `?consistency=1` and shows the token; the 410 names the build to publish instead |
130
+ | `GET /v1/rollups/totals` takes a bound of exactly four digits, a hyphen and a month 01 to 12 (p5o item 9: `2026-+1` used to pass), else 422 `INVALID_QUERY` | `rollups` (`totals`) | the client's check is the server's: ASCII digits only, the whole value (a trailing newline or another script's digits no longer pass it) |
131
+ | The final catalog (API reference §16): `NON_HUMAN_WRITE_UNSUPPORTED` retired, `RATE_LIMITED` answered by no route (`SUBTREE_RATE_LIMITED`, `RECALL_OVERLOADED`), `NOTICE_UNAVAILABLE`'s reasons `moving`, `limit`, `arming_disabled`, `members_behind`, `refused` (`scope_unnamed` gone: a by-id read's notice is its row's region's), and a failed write's `resend: no` | every tool; `memory_get`, `memory_status` | the retired codes' hints are gone and `RECALL_OVERLOADED` has one; `NOTICE_UNAVAILABLE` reads the new reasons; `memory_get` no longer says a break-glass read needs `tenant_id`; `memory_status` says a failed write is never re-sent (it said `memory_store` re-stores it under a new key) |
132
+ | p5w: under `deny` the operator key's change-set activation is in-band: 403 `BREAK_GLASS_REQUIRED` (`details.class: "in_band"`) without the header, 503 `NOTICE_UNAVAILABLE`, 503 `REGISTRY_STALE`; the preview gains `in_band` and `in_band_parts` | none previews or activates a change set; the operator key's tools meet `in_band` on a write into a private region or a shared scope | the `BREAK_GLASS_REQUIRED` hint says what the class refused (in-band work, or data reads); `REGISTRY_STALE` is retriable and read as the envelope says |
133
+ | Codes no tool can meet | none | `ERASURE_NOT_CANCELLABLE`, `COHORT_UNSUPPORTED`, `SCIM_UNSUPPORTED`, `TENANT_SETTINGS_UNAVAILABLE`, the mint's `scopes_exceed_minter`, `caps_exceed_minter` and `deployment_mint_requires_operator`, `BLOB_STORE_ERROR`, and p5o's admin codes (`PERSIST_FAILED`, `ICE_FORK_UNSUPPORTED`, the repairs' `UNSUPPORTED_MEDIA_TYPE`, `CROSS_SHARD_REQUEST`): no tool cancels an erasure, asks a cohort, speaks SCIM, mints or calls an admin route; any of them still renders as a typed error |
134
+
135
+ ## CortexDB 0.15.0: how memories were made (`derivation_provenance_v1`)
136
+
137
+ What N10's step 0 added to the server (`next`; `docs/GOVERNED_MEMORY_FORMATION.md` §18.11, §18.12, §18.16, §20.5),
138
+ and what this server does with it. The tool ships in 0.10.0.
139
+ §20.5's other tools wait for their routes, except `derivation_policy`, whose route has merged since (below).
140
+
141
+ | Family | Covered | By, or why not |
142
+ |---|---|---|
143
+ | `include=derivation` on `GET /v1/facts/{id}` and `GET /v1/beliefs/why` | yes | `memory_why` for a fact or a belief id: the verdict, the models, the prompt and gate versions, the policy, and the rule, topic, `derived_by`, pinned, confirmed and unsynced when set |
144
+ | `include=derivation` on `GET /v1/facts`, `/v1/facts/timeline`, `/v1/beliefs` | no | the listing tools (`entity_list`, `entity_get`, `entity_edges`, `get_insights`) render claims; `memory_why` reads one record's derivation by id, which is what an agent asks about |
145
+ | `GET /v1/derivation/decisions` | yes | `memory_why` for a memory id (at `tenant_id`, else the configured scope), and for each of a fact's first five source memories (at the fact's own scope); a source whose decision fails is named and the answer goes on; a fact without `derivation` (an older server) asks for none |
146
+ | The status route's `extraction` block (`GET /v1/derivation/status`) | no tool | a sync's or a connector's poll after a bulk load; `cortexdb_check_call` knows the route and says what the block reports |
147
+ | `directive_not_enforced:<name>` in a write's ack and an import's job | nothing to do | `memory_store`, `entity_link` and `belief_declare` send no directive, so their acks carry none; a warning any ack carries is rendered (`Warning: ...`) |
148
+ | `enrich_kept` in a change set's staging and preview | no | no tool stages or previews a change set |
149
+ | The capability `derivation_provenance_v1` | yes | `memory_why` says so when a fact or belief carries no `derivation` (an older server ignores `include=`, and 404s the decisions route) |
150
+ | The decisions route's refusals | yes | 404 `NOT_FOUND`, one answer for an absent event, one under another scope, a cancelled or a forgotten one, and for a server before 0.15.0: the hint names `tenant_id` and the version; 503 `FORMAT_UPGRADE_PENDING` (a shard below format 10) by `details.reason`; 400 `INVALID_QUERY` for a parameter besides `scope` and `event_id`, which `cortexdb_check_call` catches before the call |
151
+
152
+ Its wording follows the server's own HTTP `/mcp`, whose `memory_why` answers the same reads: a `null` verdict is "no
153
+ derivation stamp (written directly, or made before 0.15.0)", an outcome "learned (N accepted)", "derived, nothing
154
+ admitted", "not derived yet", "held for review (N held)", "skipped: <reason>", "failed, a retry is queued" or
155
+ "failed, dead-lettered", and what was rejected "rejected by rule: <rule> <count>", never its text.
156
+
157
+ ## CortexDB 0.15.0: governed memory formation (`derivation_policy_v1`, `derivation_feed_v1`, `derived_by_v1`)
158
+
159
+ N10's policy routes as merged (`docs/GOVERNED_MEMORY_FORMATION.md` §7.2-§7.4, §13.1, §13.3, §18.1-§18.6, §18.15,
160
+ §18.16, §18.19, §20.5). The stdio server adds the one read tool §20.5 names for them, `derivation_policy`, in every
161
+ profile but `code`; the corrections and review tools (`memory_about`, `review_list`, `memory_correct`,
162
+ `review_decide`, `correction_undo`) are in the two sections below.
163
+
164
+ | Family | Covered | By, or why not |
165
+ |---|---|---|
166
+ | `GET /v1/derivation/policy` | yes | `derivation_policy(scope=None, include_texts=False)`: the hash (`dp1:none`: no policy), whether anything is learned at the scope and if not why (the ceiling: enrichment off, the enrichment allowlist, the scope's `enrich`, a reference root; or the mode `off`, or no stage), the mode and who set it, each restriction in words with who set it (the policy at a scope, through the org chart, or the subject settings) and the consents live now, the steering (texts by id and size, the model that runs when none is named), custom topics, predicates (cardinality, conflict, writers, requires), the values set without effect and why, the versions; below apply format 11 it says no policy can be activated yet. `include_texts` sends `include=texts` (an Owner with `derivation.administer.scope`) and adds each text in full. A 403 is the typed error; an older server's 404 says it predates the capability |
167
+ | `GET /v1/derivation/templates`, `/taxonomy` | no | static catalogues for whoever writes a policy (`cortexdb derivation templates`, `taxonomy`, the console's editor), not a question an assistant answers for a user |
168
+ | Derive records in change sets, their preview's `derivation` section | no | no tool stages, previews or activates a change set (an assistant does not rewrite an org's policy); `cortexdb derivation stage` does |
169
+ | `PUT`, `GET`, `DELETE /v1/derivation/settings` | no | a person's own restrictions and consents are given or withdrawn deliberately, by the application or the person (`cortexdb derivation settings`); `derivation_policy` shows their effect (`settings.*` restrictions, the mode, the consents live) |
170
+ | `GET /v1/derivation/texts`, `POST /v1/derivation/texts/erase` | no | an Owner's administration (`cortexdb derivation texts`); `derivation_policy` names the texts in force, and with `include_texts` shows them |
171
+ | `GET /v1/derivation/feed` | no | a worker's long-running read with a cursor of its own, not an assistant's (`cortexdb derivation feed`, the SDK's worker) |
172
+ | `derived_by` on typed triples and declared beliefs | no | `memory_store` writes a message and `belief_declare` a statement of the agent's own; neither is a write-back of a pipeline. `memory_why` shows a stamp a fact carries (`derived by: <name>@<version>`) |
173
+ | Their refusals | hints | `DERIVATION_POLICY_INVALID` (by reason, with the field and record), `DERIVATION_POLICY_WIDENS` (the rule and the ancestor), `DERIVATION_MODEL_NOT_ALLOWED` (the allowed models), `DERIVATION_TEXT_INVALID`, `FEED_CURSOR_INVALID` and `DERIVED_BY_INVALID` (by reason), and the 404 of a derivation route on a server without it. Only `derivation_policy`'s 403 and 404 can reach a tool today; the rest render for a tool that later writes these routes |
174
+ | `cortexdb_check_call` | yes | knows `GET /v1/derivation/policy` (`scope` required; `include` takes `texts` only) and says where a policy and a person's settings are written |
175
+
176
+ ## CortexDB 0.15.0: corrections (`corrections_v1`)
177
+
178
+ N10 M3 (`docs/GOVERNED_MEMORY_FORMATION.md` §10, §18.7, §18.8, §18.17, §20.5): how a wrong memory is fixed. The tools
179
+ §20.5 names for it; the review tools (`review_list`, `review_decide`) are in the next section.
180
+
181
+ | Family | Covered | By, or why not |
182
+ |---|---|---|
183
+ | `GET /v1/memory/about` | yes | `memory_about(scope=None, include_corrections=False, limit=50, cursor=None)`, a read tool in every profile but `code`: the counts, each fact and belief with its id, validity, pin and first three sources (event, date, snippet), the corrections when asked for (said not to be served below format 11), the next cursor |
184
+ | `POST /v1/corrections` | yes | `memory_correct(action, fact_id, value, valid_from, bind, reason, subject_principal, scope, idempotency_key)`, opt-in (`write`), destructive (a reject deletes facts): `target {fact_id}`; every rule of the body checked before sending; a fresh `mcp-` key unless given, which a retriable refusal names for the retry; the answer names the claim, the replacement, each effect, and the `correction_undo` call; a replay says nothing changed |
185
+ | A correction's `target.claim` and a confirm's `valid` window | no | an agent names the fact `memory_about` or `memory_why` gave it; a claim stated as words and a conflict split's window are the console's and the SDK's |
186
+ | `GET /v1/corrections`, `GET /v1/corrections/{id}` | no tool | `memory_about` with `include_corrections` lists a scope's corrections for an assistant; the full records and their snapshots are `cortexdb corrections list` and `show`'s; `cortexdb_check_call` knows the list route |
187
+ | `DELETE /v1/corrections/{id}` | yes | `correction_undo(correction_id, scope=None)`, opt-in (`write`), destructive and idempotent: the facts restored, the pin removed, the replacement's memory erased |
188
+ | A conflict's resolution as corrections (`correction_ids`, §18.20) | yes | `resolve_conflict` names the corrections a resolution was proposed as (format 11), which `memory_about` with `include_corrections` lists |
189
+ | A wrong memory is a correction, not a forget | yes | `memory_correct`'s and `memory_forget`'s descriptions and the server instructions say so; `cortexdb_check_call` corrects `/v1/correct`, `/v1/correction`, `/v1/fix`, `/v1/memories/correct` and `/v1/facts/correct` to `POST /v1/corrections`, checks its body (its fields, exactly one target, `bind` with `reject`, `value` with `replace`, `valid` with `confirm`, the bounds), and the forget route's note points there |
190
+ | Their refusals | hints | `CORRECTION_TARGET_UNKNOWN` (the claim, and that a replace of it is allowed), `CORRECTION_CONFLICT` (the open correction; undo it only if the user wants this one), `PROVENANCE_ERASED` on the corrections route, the 404 of a fact or a correction (who may undo), a server without the routes, `IDEMPOTENCY_CONFLICT` and `WAL_OUTCOME_UNKNOWN` as for every write |
191
+
192
+ ## CortexDB 0.15.0: review before commit (`derivation_review_v1`)
193
+
194
+ N10 M6 (`docs/GOVERNED_MEMORY_FORMATION.md` §7.7, §11, §18.9, §18.10, §20.5): the candidates a derivation policy held
195
+ for review before learning them, and their decisions.
196
+
197
+ | Family | Covered | By, or why not |
198
+ |---|---|---|
199
+ | `GET /v1/review` | yes | `review_list(scope=None, topic=None, rule=None, descend=False, limit=50, cursor=None)`, a read tool in every profile but `code`: how many are open, each candidate's held id, what it states, its rule and topic, when it lapses, its source (event, date, snippet, or not here to quote), the next cursor |
200
+ | `POST /v1/review/decisions` | yes | `review_decide(decision, held_ids, bind=None, reason=None, scope=None)`, opt-in (`write`), destructive (a rejection erases the candidate and suppresses its claim) and idempotent (a decision sent again answers `REVIEW_ITEM_DECIDED`): one decision kind for 1 to 100 ids, every rule checked before sending (ids `hld_` and 32 hex, each once; `bind` `claim` or `subject_predicate`, with reject only; reason 512 bytes); each result said: the fact an approval made, the correction a rejection left with its `correction_undo` call, a refused one's code and whether to call again |
201
+ | Mixed decisions in one request (some approved, some rejected) | no | an assistant decides one kind per call; the SDK's `review_decide` and the console send mixed batches |
202
+ | `include=held` on `GET /v1/memory/about` | no | `review_list` is the queue an assistant reads; `memory_about` keeps to what is known |
203
+ | Their refusals | hints | `POLICY_DENIED` on the review routes (who reviews: a Writer of the scope or a principal `review.reviewers` names), a server without the routes (`derivation_review_v1`); `FORMAT_UPGRADE_PENDING` as for every format-11 route; a refused decision is its result's text, never an error |
204
+ | `cortexdb_check_call` | yes | knows `GET /v1/review` (`scope` required) and `POST /v1/review/decisions` (`scope`, `decisions`; what a decision holds) |
205
+
206
+ ## Server behaviour noted, not changed here
207
+
208
+ - Fixed on the final server: `GET /v1/experience/status` answered `resend: "new_key"` for a `failed` write, which
209
+ the contract (`INDEXING_FAILED`, §8 O6) says never to re-send; it answers `no` now, and since p5v 17 its note says
210
+ never to re-send it, at any age, under any key (the key replays the failure; the operator re-includes the stored
211
+ event in place, and a copy under a new key would store the memory twice). `memory_status` and the
212
+ `INDEXING_FAILED` hint say the same. The reference's §11.2 no longer says the forget selector ignores a field it
213
+ does not know.
214
+ - Fixed in p5v, after the live run of this pass found them: a forget whose every named event a legal hold kept
215
+ answered `matched: 0` and the "matched no records ... check the ids" warning (p5v 1: `matched` counts what a hold
216
+ kept, at most `requested`, and the warning fires only when nothing matched); a read refused for want of its
217
+ purpose answered as if the caller held no role (p5v 2: 403 `POLICY_DENIED`, `details.reason: "purpose_required"`
218
+ and `details.purpose`, which the hint names); the reference said a redirected write is found by key only without
219
+ `scope` (p5v 3: by either, as the code does). A format hold's `NOTICE_UNAVAILABLE` keeps its own reason (p5v 12):
220
+ `reserved_path_in_use` is not retriable, and the hint says the operator clears it.
221
+ - Tenant settings carry `deployment_mode` (p5v 13); no tool reads them.