neotoma 0.21.5 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +20 -2
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +162 -14
  4. package/dist/actions.js.map +1 -1
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +203 -6
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/docs/developer/cli_agent_instructions.md +2 -0
  9. package/dist/docs/developer/cli_reference.md +9 -0
  10. package/dist/docs/developer/mcp/instructions.md +8 -0
  11. package/dist/docs/infrastructure/deployment.md +6 -4
  12. package/dist/docs/legal/site_terms_of_use.md +2 -1
  13. package/dist/docs/reference/error_codes.md +95 -0
  14. package/dist/docs/security/advisories/2026-05-11-inspector-auth-bypass.md +1 -1
  15. package/dist/docs/security/advisories/2026-08-26-jwt-bearer-unverified-fallback-auth-bypass.md +59 -0
  16. package/dist/docs/security/advisories/README.md +4 -3
  17. package/dist/docs/security/practices.md +5 -1
  18. package/dist/docs/security/threat_model.md +1 -1
  19. package/dist/docs/subsystems/issues.md +1 -1
  20. package/dist/docs/subsystems/sandbox_deployment.md +22 -16
  21. package/dist/docs/testing/automated_test_catalog.md +13 -6
  22. package/dist/inspector/assets/{access_policies-B0S0qvu_.js → access_policies-SizWwSjn.js} +1 -1
  23. package/dist/inspector/assets/{agent_badge-BkdvFESY.js → agent_badge-Cn1SlcFX.js} +1 -1
  24. package/dist/inspector/assets/{agent_detail-DP0gQW2Q.js → agent_detail-BG5KDNtE.js} +1 -1
  25. package/dist/inspector/assets/{agent_filter-COKVxYFV.js → agent_filter-BAONVePH.js} +1 -1
  26. package/dist/inspector/assets/{agent_grant_detail-Dx9XnxQF.js → agent_grant_detail-UxL02Gdg.js} +1 -1
  27. package/dist/inspector/assets/{agent_grant_form-c_cKOS1t.js → agent_grant_form-CjQdyLL1.js} +1 -1
  28. package/dist/inspector/assets/{agent_grants-2kUpLoVa.js → agent_grants-B_h0fn0F.js} +1 -1
  29. package/dist/inspector/assets/{agents-CGacc5go.js → agents-DIH5cs7p.js} +1 -1
  30. package/dist/inspector/assets/{api_not_configured_state-D3-Y7f4P.js → api_not_configured_state-zajjLcVz.js} +1 -1
  31. package/dist/inspector/assets/{attribution_card-DHSwBDvm.js → attribution_card-DdyUbizz.js} +1 -1
  32. package/dist/inspector/assets/{attribution_summary-BDb76qdA.js → attribution_summary-IHhspwd0.js} +1 -1
  33. package/dist/inspector/assets/{bundles-BrZCSKdE.js → bundles-DN7cQAQe.js} +1 -1
  34. package/dist/inspector/assets/{card-HLZ7P9gv.js → card-DfiBnyJz.js} +1 -1
  35. package/dist/inspector/assets/{checkbox-X0LhUk54.js → checkbox-Dhb2Rqtd.js} +1 -1
  36. package/dist/inspector/assets/{compliance-B-vHY0Mj.js → compliance-Bf_6GcUj.js} +1 -1
  37. package/dist/inspector/assets/{confirm-dialog-BZhGXjGp.js → confirm-dialog-Bw3EJsrJ.js} +1 -1
  38. package/dist/inspector/assets/{conversation_common-CqwsnXcN.js → conversation_common-CFRyZ7p2.js} +1 -1
  39. package/dist/inspector/assets/{conversation_detail-BmH4sCju.js → conversation_detail-Bg19Nxid.js} +1 -1
  40. package/dist/inspector/assets/{copy_id_button-CDok7EQV.js → copy_id_button-CnakyAUV.js} +1 -1
  41. package/dist/inspector/assets/{copyable_code_block-j3QB0aQg.js → copyable_code_block-Cf7BV5XI.js} +1 -1
  42. package/dist/inspector/assets/{corrections-BnjNBaly.js → corrections-DLiH5564.js} +1 -1
  43. package/dist/inspector/assets/{dashboard-BulD_nUh.js → dashboard-CLnmOktO.js} +1 -1
  44. package/dist/inspector/assets/{data-table-B-mxpOia.js → data-table-DSAodRMR.js} +1 -1
  45. package/dist/inspector/assets/{design-DnQ02ply.js → design-BAz4q7Iq.js} +1 -1
  46. package/dist/inspector/assets/{dialog-CsNugp51.js → dialog-BsZ5EdEE.js} +1 -1
  47. package/dist/inspector/assets/{docs-BSpGmuZ2.js → docs-lZtdsNZN.js} +1 -1
  48. package/dist/inspector/assets/{docs_page_layout-Ej9dnRro.js → docs_page_layout-BTLx30w6.js} +1 -1
  49. package/dist/inspector/assets/{embed_graph-Dbg37Xuy.js → embed_graph-Cqu_rKJi.js} +1 -1
  50. package/dist/inspector/assets/{empty_state-Y44G8LC3.js → empty_state-D4eAv7uQ.js} +1 -1
  51. package/dist/inspector/assets/{entities-DkBCXibK.js → entities-DJ9KuPrY.js} +1 -1
  52. package/dist/inspector/assets/{entity_correct-CSa6U1Nk.js → entity_correct-CjMlwcWd.js} +1 -1
  53. package/dist/inspector/assets/{entity_history-CxmrR_9X.js → entity_history-aDxms2rn.js} +1 -1
  54. package/dist/inspector/assets/{entity_history_sections-DEWV31Ot.js → entity_history_sections-B58KGFzd.js} +1 -1
  55. package/dist/inspector/assets/{entity_link-oDivxz85.js → entity_link-BlqV2ig2.js} +1 -1
  56. package/dist/inspector/assets/{entity_relationships_by_type-DY0Ld1rw.js → entity_relationships_by_type-BnCbbao4.js} +1 -1
  57. package/dist/inspector/assets/{entity_segment_page-WT6IOAC6.js → entity_segment_page-CbQjUSLk.js} +1 -1
  58. package/dist/inspector/assets/{entity_table_column_toggle-CBgWmzQl.js → entity_table_column_toggle-DEr55C5I.js} +1 -1
  59. package/dist/inspector/assets/{entity_timeline-Dq38UOgV.js → entity_timeline-CT95_E8j.js} +1 -1
  60. package/dist/inspector/assets/{entity_types-CN-WOauu.js → entity_types-BOD6hz75.js} +1 -1
  61. package/dist/inspector/assets/{faq-SK87sez6.js → faq-CmTO_NOt.js} +1 -1
  62. package/dist/inspector/assets/{graph_auto_fit-BMOrV_D2.js → graph_auto_fit-DG06bEkf.js} +1 -1
  63. package/dist/inspector/assets/{graph_explorer-DEu_WyZ2.js → graph_explorer-dhssxyh6.js} +1 -1
  64. package/dist/inspector/assets/{home-kfu3wSXt.js → home-C1-SpHes.js} +1 -1
  65. package/dist/inspector/assets/{index-afk3dCXk.js → index-BwcgXq7C.js} +1 -1
  66. package/dist/inspector/assets/{index-D6jp2NN2.js → index-D7WGgcjs.js} +2 -2
  67. package/dist/inspector/assets/{index-CnTUFeBn.js → index-DaLi8rY1.js} +1 -1
  68. package/dist/inspector/assets/{interpretations-DbtcIPBS.js → interpretations-BOTRs9zO.js} +1 -1
  69. package/dist/inspector/assets/interpretations-C1f56O6G.js +1 -0
  70. package/dist/inspector/assets/{issue_author_attribution-C_Uvk4F5.js → issue_author_attribution-DUDerjDR.js} +1 -1
  71. package/dist/inspector/assets/{issue_detail-Ct1G2hyw.js → issue_detail-B-1sOpD9.js} +1 -1
  72. package/dist/inspector/assets/{issues-CUiar649.js → issues-B2mxuRox.js} +1 -1
  73. package/dist/inspector/assets/{json_viewer-DPC1DsaT.js → json_viewer-CvBI_AVO.js} +1 -1
  74. package/dist/inspector/assets/{label-BQ8-xKUC.js → label-B_MDF1Qm.js} +1 -1
  75. package/dist/inspector/assets/{live_relative_time-D1kEqQ4i.js → live_relative_time-Cod2SFqP.js} +1 -1
  76. package/dist/inspector/assets/{mobile_filter_popover-Bvj6GSAo.js → mobile_filter_popover-D-AfmJEd.js} +1 -1
  77. package/dist/inspector/assets/{not_found-0bIlvlSX.js → not_found-W1xm1Bpn.js} +1 -1
  78. package/dist/inspector/assets/{oauth_callback-BWXd9Wge.js → oauth_callback--oUBxVdh.js} +1 -1
  79. package/dist/inspector/assets/{oauth_signin-CZO_QVYB.js → oauth_signin-CbkMUE89.js} +1 -1
  80. package/dist/inspector/assets/{observations-CikFMfEg.js → observations-CPsDsA62.js} +1 -1
  81. package/dist/inspector/assets/{page_shell-BosAA9Zi.js → page_shell-DE3y5lgp.js} +1 -1
  82. package/dist/inspector/assets/{pagination-9hSWjJSM.js → pagination-CHnUCq28.js} +1 -1
  83. package/dist/inspector/assets/{peer_detail-BkQDnYWW.js → peer_detail-B704bYU_.js} +1 -1
  84. package/dist/inspector/assets/{peers-BCBT6Ylr.js → peers-l__mD88k.js} +1 -1
  85. package/dist/inspector/assets/{pin_primitive_button-DDwdBrpd.js → pin_primitive_button-wyB6QUdt.js} +1 -1
  86. package/dist/inspector/assets/{recent_activity-D8ZzUMMQ.js → recent_activity-CDsDl96P.js} +1 -1
  87. package/dist/inspector/assets/{recent_conversations-DCgvsODD.js → recent_conversations-AljoEjIJ.js} +1 -1
  88. package/dist/inspector/assets/{recent_conversations-Drio7WK-.js → recent_conversations-VL5wKo8t.js} +1 -1
  89. package/dist/inspector/assets/{recent_records_feed-BtPgzGjQ.js → recent_records_feed-StdxJqbO.js} +1 -1
  90. package/dist/inspector/assets/record_activity-BFZ8OHZk.js +1 -0
  91. package/dist/inspector/assets/{relationship_detail-Bib84abm.js → relationship_detail-BRwUMgL4.js} +1 -1
  92. package/dist/inspector/assets/{relationship_panel_groups-DLZtyWmB.js → relationship_panel_groups-Yc8ecUfZ.js} +1 -1
  93. package/dist/inspector/assets/{relationships-C6AMAVnt.js → relationships-BulEoxTG.js} +1 -1
  94. package/dist/inspector/assets/{relationships-CbmaLg1w.js → relationships-DmV58gWH.js} +1 -1
  95. package/dist/inspector/assets/{sandbox-CDn_AfpW.js → sandbox-CyKCUqMo.js} +1 -1
  96. package/dist/inspector/assets/{schema_detail-rdHCdMiO.js → schema_detail-B-OSbQxD.js} +1 -1
  97. package/dist/inspector/assets/{schemas-BGFS1NIU.js → schemas-BH5NPYiv.js} +1 -1
  98. package/dist/inspector/assets/{search-D0mISNIp.js → search-8_d9cddJ.js} +1 -1
  99. package/dist/inspector/assets/{segmented_control-Dh4asumQ.js → segmented_control-Dbs8GQ7U.js} +1 -1
  100. package/dist/inspector/assets/{select-CGgVNBJP.js → select-Ca8Vi4-P.js} +1 -1
  101. package/dist/inspector/assets/{settings-WrB7DH64.js → settings-Ciqn2fJY.js} +1 -1
  102. package/dist/inspector/assets/{source_content-CdneU5cC.js → source_content-DOlpoUVc.js} +1 -1
  103. package/dist/inspector/assets/{source_content_open_button-DdwQmqei.js → source_content_open_button-BtFZ3Gu9.js} +1 -1
  104. package/dist/inspector/assets/{source_detail-BGV8BTSk.js → source_detail-8Dz_iJtK.js} +1 -1
  105. package/dist/inspector/assets/{source_link-3OE0LIxN.js → source_link-CGGiFzQc.js} +1 -1
  106. package/dist/inspector/assets/{sources-MQzjTQPU.js → sources-B29h_Vtk.js} +1 -1
  107. package/dist/inspector/assets/{subscriptions-DSSlZgxQ.js → subscriptions-DuZVEAlj.js} +1 -1
  108. package/dist/inspector/assets/{switch-CE4bL5sC.js → switch-D472BSa5.js} +1 -1
  109. package/dist/inspector/assets/{tabs-ZrN5u0Y9.js → tabs-CNCi1aCG.js} +1 -1
  110. package/dist/inspector/assets/{textarea-CBYe0AAK.js → textarea-DsTTUsEK.js} +1 -1
  111. package/dist/inspector/assets/{timeline-Ad0NUrF_.js → timeline-CAZLGn0X.js} +1 -1
  112. package/dist/inspector/assets/{timeline-uLYAndU3.js → timeline-Dnlu40yX.js} +1 -1
  113. package/dist/inspector/assets/{timeline_event_detail-BfgHJ0XR.js → timeline_event_detail-C7preATf.js} +1 -1
  114. package/dist/inspector/assets/{turn_detail-CUBCiCcq.js → turn_detail-DLzSm996.js} +1 -1
  115. package/dist/inspector/assets/{turns-B1PAnWxV.js → turns-BAMqn17p.js} +1 -1
  116. package/dist/inspector/assets/{use_agents-Kr_qThpq.js → use_agents-c0bZzrii.js} +1 -1
  117. package/dist/inspector/assets/{use_entities-zE9adlzL.js → use_entities-x5BPBdKo.js} +1 -1
  118. package/dist/inspector/assets/use_interpretations-BR4Kbo1i.js +1 -0
  119. package/dist/inspector/assets/{use_mutations-D4WoExI4.js → use_mutations-C284FI3l.js} +1 -1
  120. package/dist/inspector/assets/{use_peers-k2UDs1Pi.js → use_peers-DXFhfeuf.js} +1 -1
  121. package/dist/inspector/assets/{use_relationships-DVVjD-SH.js → use_relationships-CXktmg7M.js} +1 -1
  122. package/dist/inspector/assets/{use_stats-Cmjbd4x1.js → use_stats-DG4ySbul.js} +1 -1
  123. package/dist/inspector/assets/{use_timeline-De1q_UCR.js → use_timeline-CXc_N-XZ.js} +1 -1
  124. package/dist/inspector/assets/{use_turns-B4g1lTlJ.js → use_turns-D8vym3df.js} +1 -1
  125. package/dist/inspector/assets/{world_time_event_timeline-CLue1xiu.js → world_time_event_timeline-DwiwRhnM.js} +1 -1
  126. package/dist/inspector/index.html +1 -1
  127. package/dist/mcp_instruction_doc.d.ts +22 -0
  128. package/dist/mcp_instruction_doc.d.ts.map +1 -1
  129. package/dist/mcp_instruction_doc.js +26 -0
  130. package/dist/mcp_instruction_doc.js.map +1 -1
  131. package/dist/repositories/sqlite/sqlite_client.d.ts +14 -0
  132. package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
  133. package/dist/repositories/sqlite/sqlite_client.js +46 -1
  134. package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
  135. package/dist/scripts/reset_sandbox.js +3 -2
  136. package/dist/scripts/reset_sandbox.js.map +1 -1
  137. package/dist/server.d.ts +14 -0
  138. package/dist/server.d.ts.map +1 -1
  139. package/dist/server.js +103 -2
  140. package/dist/server.js.map +1 -1
  141. package/dist/services/correction.d.ts.map +1 -1
  142. package/dist/services/correction.js +20 -0
  143. package/dist/services/correction.js.map +1 -1
  144. package/dist/services/google_oidc.d.ts +12 -1
  145. package/dist/services/google_oidc.d.ts.map +1 -1
  146. package/dist/services/google_oidc.js +19 -2
  147. package/dist/services/google_oidc.js.map +1 -1
  148. package/dist/services/instance_policy.d.ts +284 -0
  149. package/dist/services/instance_policy.d.ts.map +1 -0
  150. package/dist/services/instance_policy.js +581 -0
  151. package/dist/services/instance_policy.js.map +1 -0
  152. package/dist/services/issues/issue_operations.d.ts.map +1 -1
  153. package/dist/services/issues/issue_operations.js +5 -0
  154. package/dist/services/issues/issue_operations.js.map +1 -1
  155. package/dist/services/issues/neotoma_client.d.ts +5 -0
  156. package/dist/services/issues/neotoma_client.d.ts.map +1 -1
  157. package/dist/services/issues/neotoma_client.js +15 -0
  158. package/dist/services/issues/neotoma_client.js.map +1 -1
  159. package/dist/services/mcp_auth.d.ts +19 -3
  160. package/dist/services/mcp_auth.d.ts.map +1 -1
  161. package/dist/services/mcp_auth.js +23 -33
  162. package/dist/services/mcp_auth.js.map +1 -1
  163. package/dist/services/root_landing/harness_snippets.d.ts.map +1 -1
  164. package/dist/services/root_landing/harness_snippets.js +2 -1
  165. package/dist/services/root_landing/harness_snippets.js.map +1 -1
  166. package/dist/services/sandbox_mode.d.ts.map +1 -1
  167. package/dist/services/sandbox_mode.js +3 -4
  168. package/dist/services/sandbox_mode.js.map +1 -1
  169. package/dist/services/schema_definitions.d.ts.map +1 -1
  170. package/dist/services/schema_definitions.js +56 -0
  171. package/dist/services/schema_definitions.js.map +1 -1
  172. package/dist/services/schema_registry.d.ts +69 -0
  173. package/dist/services/schema_registry.d.ts.map +1 -1
  174. package/dist/services/schema_registry.js +7 -0
  175. package/dist/services/schema_registry.js.map +1 -1
  176. package/dist/shared/action_schemas.d.ts +2 -2
  177. package/dist/shared/api_client.d.ts +8 -0
  178. package/dist/shared/api_client.d.ts.map +1 -1
  179. package/dist/shared/api_client.js +13 -4
  180. package/dist/shared/api_client.js.map +1 -1
  181. package/dist/shared/contract_mappings.d.ts.map +1 -1
  182. package/dist/shared/contract_mappings.js +10 -0
  183. package/dist/shared/contract_mappings.js.map +1 -1
  184. package/dist/shared/openapi_types.d.ts +320 -2
  185. package/dist/shared/openapi_types.d.ts.map +1 -1
  186. package/dist/shared/sandbox_terms_content.d.ts +6 -3
  187. package/dist/shared/sandbox_terms_content.d.ts.map +1 -1
  188. package/dist/shared/sandbox_terms_content.js +10 -5
  189. package/dist/shared/sandbox_terms_content.js.map +1 -1
  190. package/dist/tool_definitions.d.ts +1 -1
  191. package/dist/tool_definitions.d.ts.map +1 -1
  192. package/dist/tool_definitions.js +6 -0
  193. package/dist/tool_definitions.js.map +1 -1
  194. package/docs/developer/mcp/instructions.md +8 -0
  195. package/openapi.yaml +323 -1
  196. package/package.json +1 -1
  197. package/dist/inspector/assets/interpretations-8w33Il_Q.js +0 -1
  198. package/dist/inspector/assets/record_activity-OPIQqbxs.js +0 -1
  199. package/dist/inspector/assets/use_interpretations-CD0-ZgOr.js +0 -1
package/README.md CHANGED
@@ -1,11 +1,29 @@
1
1
  # Neotoma
2
2
 
3
- Neotoma is a deterministic, versioned state layer for AI agents. It ingests sources into immutable observations and computes reproducible entity snapshots with full provenance, exposed over an MCP server, a REST API, a CLI, and a bundled web Inspector.
3
+ **Agents cannot own work they cannot verify.**
4
+
5
+ You cannot delegate real work to agents you cannot hold to account. Accountability has two halves: what was true, and who was authorized. **Neotoma is the system of record for AI agents** — it answers what was true. Your agents can produce a value; they cannot tell you its version, its source, or whether it is still current. Neotoma versions every fact, attributes it to a source, and replays it.
6
+
7
+ What ships today is the foundation: a deterministic, versioned state layer that ingests sources into immutable observations and computes reproducible entity snapshots with full provenance, exposed over an MCP server, a REST API, a CLI, and a bundled web Inspector. See [Vision and execution status](#vision-and-execution-status) for exactly where the implementation stands against the vision.
4
8
 
5
9
  It is built for developers building and operating AI agents who need a persistent, deterministic, auditable memory layer shared across tools. The same engine serves two further groups: individuals running a personal cross-assistant memory on a single-user install, and operators running a shared, multi-user instance with agent access control. Agents store structured records (contacts, tasks, transactions, decisions, events, and any other type) once and read them back across every tool and session, and the same observations always reduce to the same state.
6
10
 
7
11
  **[neotoma.io](https://neotoma.io)** · **[Install](https://neotoma.io/install)** · **[Documentation](https://neotoma.io/docs)** (also served in-app at `/docs`)
8
12
 
13
+ ## Vision and execution status
14
+
15
+ As agent use expands, reality fragments across chat histories, agent memories, files, databases, tickets, and application state. The failure is not retrieval — it is that humans and agents can no longer answer: *which assertion is current? which source introduced it? was it observed, inferred, or decided? who had authority to correct it? which agents relied on the previous version? is there legitimate disagreement?* Neotoma's long-term role is **collective epistemic coherence**: not "better semantic memory," but deterministic, governed, multi-party state that humans and agents maintain together — attributed, correctable, disagreement-aware, portable, and independent of any one model or application.
16
+
17
+ | Vision phase | What it means | Status |
18
+ | --- | --- | --- |
19
+ | **P0 — Deterministic single-writer state** | Append-only observations, hash-derived IDs, deterministic reduction, field-level provenance, corrections-win, replayable history | ✅ **Shipped and stable** — the core guarantee, in daily use |
20
+ | **P1 — Multi-user state foundations** | Human and agent identities, attributed writes, tenant isolation, access controls, shared instances | 🟡 **Partially shipped**: hosted multi-user mode with `user_id` tenancy, per-agent identity (AAuth, optional hardware attestation), capability grants, guest tokens, peer federation. Not yet: organizations/workspaces, membership lifecycle, invitations, offboarding |
21
+ | **P2 — Authority over state** | Domain ownership, correction rights, supersession, policy ownership, temporary grants, disclosure logs | 🟡 **Mechanisms exist** (corrections always win, `SUPERSEDES` relationships, per-operation access controls); authority *semantics* — who may correct what, scoped by domain — are future work |
22
+ | **P3 — Multi-principal semantics** | Competing interpretations, disputed claims, official positions, consensus vs unresolved disagreement, field-level policies, historical validity | 🔜 Planned — interpretations are first-class today; dispute/official-position semantics are not |
23
+ | **P4 — Organizational intelligence substrate** | Non-developer governance UI, "why does the system believe this?" explanations, impact analysis, authority-graph views, enterprise administration | 🔭 Vision — the Inspector's provenance and audit views are the seed |
24
+
25
+ [Ateles](https://github.com/markmhendrickson/ateles) is the other half. Accountability has two halves: **Neotoma answers what was true** (truth, memory, provenance); **Ateles answers who was authorized** (initiative, delegation, approval) — against Neotoma state. Neither is sufficient alone. Memory without authority tells you what happened but not whether it was permitted; authority without memory tells you who may act but not against what state.
26
+
9
27
  ## What Neotoma is
10
28
 
11
29
  Neotoma is a state layer, not a chat memory or a vector cache. It records immutable observations from your sources, resolves them into entities, and computes a current snapshot for each entity by reducing its observations in a deterministic order. Nothing is overwritten. Corrections and reinterpretations add new observations; the prior history stays intact and replayable.
@@ -187,7 +205,7 @@ Full profile: [ICP from functionality](docs/icp/icp_from_functionality.md).
187
205
 
188
206
  **Version:** v0.17.0 · **License:** MIT · **Storage:** local-only (SQLite + local files).
189
207
 
190
- Neotoma is in developer preview and used daily in real agent workflows. The core guarantees (deterministic state, versioned history, append-only log, full provenance, same contract across CLI and MCP) are stable. Schemas, extraction across versions, long-term replay compatibility, and backward compatibility are not yet guaranteed. Expect breaking changes.
208
+ Neotoma is in developer preview and used daily in real agent workflows. The core guarantees (deterministic state, versioned history, append-only log, full provenance, same contract across CLI and MCP) are stable — phase P0 of the [vision roadmap](#vision-and-execution-status), with P1 partially shipped in hosted mode. Schemas, extraction across versions, long-term replay compatibility, and backward compatibility are not yet guaranteed. Expect breaking changes.
191
209
 
192
210
  ## Development
193
211
 
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
package/dist/actions.js CHANGED
@@ -23,6 +23,7 @@ import { buildSessionInfo, normalizeSessionOrigin } from "./services/session_inf
23
23
  import { AttributionPolicyError, enforceAttributionPolicy } from "./services/attribution_policy.js";
24
24
  import { OverridePolicyViolationError } from "./services/override_validation.js";
25
25
  import { CursorError } from "./services/entity_cursor.js";
26
+ import { StorePolicyUnavailableError } from "./services/instance_policy.js";
26
27
  import { AgentCapabilityError, contextFromAgentIdentity, enforceAgentCapability, } from "./services/agent_capabilities.js";
27
28
  import { assertGuestWriteAllowed, AccessPolicyError, } from "./services/access_policy.js";
28
29
  import { hashGuestAccessToken } from "./services/guest_access_token.js";
@@ -748,23 +749,34 @@ app.get("/server-info", (req, res) => {
748
749
  endpoints: buildEndpointsMap(mode),
749
750
  });
750
751
  });
751
- app.get("/mcp-interaction-instructions", (_req, res) => {
752
- const instructionsPath = path.join(config.projectRoot, "docs", "developer", "mcp", "instructions.md");
752
+ app.get("/mcp-interaction-instructions", async (_req, res) => {
753
+ // Uses the shared extractor rather than an inlined fence regex, and appends
754
+ // the instance policy through the shared composer (#1974), so this surface
755
+ // cannot drift from the MCP handshake and the CLI.
756
+ const { readMcpInstructionsMarkdown, extractFirstFencedCodeBlock, resolveNeotomaPackageRoot, composeClientInstructions, } = await import("./mcp_instruction_doc.js");
757
+ let text = null;
758
+ for (const root of [config.projectRoot, resolveNeotomaPackageRoot()]) {
759
+ const raw = readMcpInstructionsMarkdown(root);
760
+ if (!raw)
761
+ continue;
762
+ text = extractFirstFencedCodeBlock(raw);
763
+ if (text)
764
+ break;
765
+ }
766
+ if (!text) {
767
+ res.status(404).json({ error: "instructions_not_found" });
768
+ return;
769
+ }
770
+ let policySection = "";
753
771
  try {
754
- const raw = fs.readFileSync(instructionsPath, "utf-8");
755
- const match = raw.match(/```\s*\n?([\s\S]*?)```/);
756
- if (match && match[1]) {
757
- const text = match[1].trim();
758
- if (text) {
759
- res.type("text/plain").send(text);
760
- return;
761
- }
762
- }
772
+ const { getInstancePolicy, renderInstancePolicyInstructions } = await import("./services/instance_policy.js");
773
+ policySection = renderInstancePolicyInstructions(await getInstancePolicy());
763
774
  }
764
775
  catch {
765
- // fall through to 404
776
+ // An unreadable policy degrades to the global instructions rather than
777
+ // failing the request.
766
778
  }
767
- res.status(404).json({ error: "instructions_not_found" });
779
+ res.type("text/plain").send(composeClientInstructions(text, policySection));
768
780
  });
769
781
  // ============================================================================
770
782
  // MCP StreamableHTTP Endpoint (OAuth-enabled MCP transport)
@@ -2419,7 +2431,17 @@ app.get("/mcp/oauth/google/callback", async (req, res) => {
2419
2431
  // their isolated per-email one — so the whole allowlisted team, plus the
2420
2432
  // bearer/MCP identity that owns the data, share one populated graph. Unset
2421
2433
  // → isolated per-email behavior (unchanged). See getSharedGraphUserId.
2422
- const resolvedUserId = getSharedGraphUserId() ?? perEmailUser.id;
2434
+ const sharedGraphUserId = getSharedGraphUserId();
2435
+ // A configured-but-rejected value means NEOTOMA_SHARED_GRAPH_USER_ID is set
2436
+ // to something unusable (malformed, or the nil UUID). The signer silently
2437
+ // falls back to their isolated per-email graph, which looks like an empty
2438
+ // team graph rather than an error — so say so once per sign-in.
2439
+ if (!sharedGraphUserId && (process.env.NEOTOMA_SHARED_GRAPH_USER_ID || "").trim()) {
2440
+ logWarn("SharedGraphUserIdRejected", req, {
2441
+ reason: "not a usable UUID (malformed or nil); falling back to isolated per-email graph",
2442
+ });
2443
+ }
2444
+ const resolvedUserId = sharedGraphUserId ?? perEmailUser.id;
2423
2445
  // Mark this browser session as Google-verified and bound to the resolved
2424
2446
  // user_id, exactly the same OAuth key-session cookie the private-key/
2425
2447
  // mnemonic/bearer path sets. `/mcp/oauth/authorize` -> `/mcp/oauth/
@@ -3618,6 +3640,21 @@ function handleApiError(req, res, error, defaultMessage, errorCode = "DB_QUERY_F
3618
3640
  .status(error.statusCode)
3619
3641
  .json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelope()));
3620
3642
  }
3643
+ if (error instanceof StorePolicyUnavailableError) {
3644
+ // 503, not 400: the write was refused because the instance could not read
3645
+ // its own policy, which is a service-side fault the caller can retry — not
3646
+ // a statement about the payload. A 400 here would tell an agent its data
3647
+ // was wrong and invite it to narrow what it stores in response to an
3648
+ // outage. Logged at error level because an enforcing instance that cannot
3649
+ // read its policy is refusing every write.
3650
+ logError(logContext || "StorePolicyUnavailable", req, error, {
3651
+ code: error.code,
3652
+ cause: error.cause_message,
3653
+ });
3654
+ return res
3655
+ .status(503)
3656
+ .json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelope()));
3657
+ }
3621
3658
  if (error instanceof IssueValidationError) {
3622
3659
  logWarn(logContext || "IssueValidationError", req, {
3623
3660
  code: error.code,
@@ -4298,6 +4335,28 @@ app.get("/schemas", async (req, res) => {
4298
4335
  }
4299
4336
  });
4300
4337
  // GET /api/schemas/:entity_type - Get specific schema (FU-XXX)
4338
+ // GET /instance-policy — describe this instance's data policy (#1974/#1975).
4339
+ //
4340
+ // Deliberately takes no user_id or instance identifier: the policy is
4341
+ // instance-wide, so every authenticated caller sees the same record and there
4342
+ // is no parameter through which one caller could address another scope. Auth is
4343
+ // still required — the policy shape reveals what kind of data the instance
4344
+ // handles.
4345
+ app.get("/instance-policy", async (req, res) => {
4346
+ try {
4347
+ await getAuthenticatedUserId(req, undefined);
4348
+ const { getInstancePolicyResult } = await import("./services/instance_policy.js");
4349
+ const result = await getInstancePolicyResult();
4350
+ // Explicit null, never 404 and never {} — "no policy configured" must not
4351
+ // be mistakable for "denies everything". entity_id rides alongside so a
4352
+ // remote client (CLI `instance-policy set`) can resolve the id needed to
4353
+ // `correct` an existing policy without a local database connection.
4354
+ return res.json({ policy: result.policy ?? null, entity_id: result.entity_id ?? null });
4355
+ }
4356
+ catch (error) {
4357
+ return handleApiError(req, res, error, "Failed to read instance policy", "DB_QUERY_FAILED", "APIError:instance-policy");
4358
+ }
4359
+ });
4301
4360
  app.get("/schemas/:entity_type", async (req, res) => {
4302
4361
  try {
4303
4362
  const entityType = decodeURIComponent(req.params.entity_type);
@@ -5854,6 +5913,37 @@ export async function storeStructuredForApi(params) {
5854
5913
  entities: existingEntities,
5855
5914
  };
5856
5915
  }
5916
+ // Instance store-policy enforcement (#1975).
5917
+ //
5918
+ // Placed before `storeRawContent` — not merely before the observation writes
5919
+ // — because that call persists the `sources` row. A store call is not wrapped
5920
+ // in a transaction, so a denial raised after it would leave an orphaned
5921
+ // source row behind for a request that returned "rejected, 0 persisted".
5922
+ // For a data-protection control, an error that says rejected while data sits
5923
+ // in the database is the worst possible failure mode.
5924
+ //
5925
+ // Evaluates the raw request entities rather than the post-resolution
5926
+ // `resolved` array (computed further down): the policy predicates read
5927
+ // `entity_type` and field presence, both available here, and resolution runs
5928
+ // after the source write.
5929
+ {
5930
+ const { assertStorePolicyAllows } = await import("./services/instance_policy.js");
5931
+ const { schemaRegistry: policySchemaRegistry } = await import("./services/schema_registry.js");
5932
+ const policyCandidates = entities.map((entityData) => {
5933
+ const raw = (entityData ?? {});
5934
+ const candidateFields = { ...raw };
5935
+ delete candidateFields.entity_type;
5936
+ delete candidateFields.type;
5937
+ return {
5938
+ entity_type: raw.entity_type || raw.type || "generic",
5939
+ fields: candidateFields,
5940
+ };
5941
+ });
5942
+ await assertStorePolicyAllows(policyCandidates, async (entityType) => {
5943
+ const entry = await policySchemaRegistry.loadActiveSchema(entityType, userId);
5944
+ return entry?.schema_definition ?? null;
5945
+ });
5946
+ }
5857
5947
  const { createObservation } = await import("./services/observation_storage.js");
5858
5948
  const jsonContent = incomingJsonContent;
5859
5949
  const filenameForStorage = originalFilename?.trim() || undefined;
@@ -6761,6 +6851,15 @@ export async function storeStructuredForApi(params) {
6761
6851
  : {}),
6762
6852
  };
6763
6853
  }
6854
+ // KNOWN SCOPE BOUNDARY (release-preparation finding, v0.22.0): this raw-file
6855
+ // path does NOT call assertStorePolicyAllows / the instance-policy gate.
6856
+ // Unlike storeStructuredForApi/storeStructuredInternal/createCorrection (see
6857
+ // tests/unit/instance_policy_write_path_coverage.test.ts), raw file uploads
6858
+ // never construct a typed entity with an entity_type, so the policy's
6859
+ // out_of_scope_entity_types / max_sensitivity_class gates have no evaluable
6860
+ // target here today. An instance with enforcement: "enforced" still accepts
6861
+ // arbitrary raw file content through this path. See the "Known scope
6862
+ // boundary" section in src/services/instance_policy.ts's docblock.
6764
6863
  async function storeUnstructuredForApi(params) {
6765
6864
  const { fileContent, fileBuffer, filePath, mimeType, idempotencyKey, originalFilename, sourceType, userId, storageMode = "inline", } = params;
6766
6865
  const resolvedFileBuffer = fileBuffer ?? (fileContent !== undefined ? Buffer.from(fileContent, "base64") : undefined);
@@ -6962,6 +7061,45 @@ async function handleStorePost(req, res) {
6962
7061
  logWarn("IdempotencyCollision:store", req, { message });
6963
7062
  return sendError(res, 409, "ERR_IDEMPOTENCY_COLLISION", message);
6964
7063
  }
7064
+ if (error && typeof error === "object" && errCode === "ERR_STORE_POLICY_DENIED") {
7065
+ const err = error;
7066
+ // Log counts and reason codes only. The denied entities' field values are
7067
+ // exactly the data the policy refused to hold, so they must not reach the
7068
+ // log either (guardrails MUST NOT 11).
7069
+ logWarn("StorePolicyDenied:store", req, {
7070
+ denied_count: err.denied?.length ?? 0,
7071
+ reason_codes: [...new Set((err.denied ?? []).map((d) => d.reason_code))].sort(),
7072
+ });
7073
+ return res.status(400).json({
7074
+ error: {
7075
+ code: "ERR_STORE_POLICY_DENIED",
7076
+ message: err.message,
7077
+ denied: err.denied ?? [],
7078
+ },
7079
+ });
7080
+ }
7081
+ if (error instanceof StorePolicyUnavailableError) {
7082
+ // MUST sit beside its DENIED sibling, not fall through to the generic
7083
+ // 500 below. `/correct` gets 503 from the shared `handleApiError`, but
7084
+ // this handler catches first and never reaches it — so without this
7085
+ // branch REST `/store` answered 500 DB_QUERY_FAILED while MCP, /correct,
7086
+ // the docs, and the declared envelope all promised 503 retryable:true.
7087
+ //
7088
+ // The status is the entire contract here. 500 reads as "this request
7089
+ // broke the server" and a well-behaved agent responds by changing the
7090
+ // payload; 503 + retryable:true reads as "ask again unchanged". Getting
7091
+ // it wrong makes an agent narrow what it stores because the instance
7092
+ // could not read its own policy — the failure this split exists to
7093
+ // prevent, arriving through the surface it was built for.
7094
+ logError("StorePolicyUnavailable:store", req, error, {
7095
+ code: error.code,
7096
+ cause: error.cause_message,
7097
+ });
7098
+ // Nested under `error` to match the DENIED sibling directly above, so a
7099
+ // client reading `error.code` finds both policy outcomes in one place.
7100
+ // `toErrorEnvelope()` already carries code + message + retryable + hint.
7101
+ return res.status(503).json({ error: error.toErrorEnvelope() });
7102
+ }
6965
7103
  if (error && typeof error === "object" && errCode === "ERR_STORE_RESOLUTION_FAILED") {
6966
7104
  const err = error;
6967
7105
  logWarn("StoreResolutionError:store", req, {
@@ -8873,6 +9011,16 @@ app.post("/correct", async (req, res) => {
8873
9011
  .status(400)
8874
9012
  .json(buildErrorEnvelope(error.code, error.message, { entity_type: error.entityType }));
8875
9013
  }
9014
+ // Instance store-policy denial (#1975) — same envelope as /store, so a
9015
+ // caller handling one handles both.
9016
+ const { StorePolicyDeniedError } = await import("./services/instance_policy.js");
9017
+ if (error instanceof StorePolicyDeniedError) {
9018
+ logWarn("StorePolicyDenied:correct", req, {
9019
+ denied_count: error.denied.length,
9020
+ reason_codes: [...new Set(error.denied.map((d) => d.reason_code))].sort(),
9021
+ });
9022
+ return res.status(400).json({ error: error.toErrorEnvelope() });
9023
+ }
8876
9024
  return handleApiError(req, res, error, "Failed to create correction", "DB_QUERY_FAILED", "APIError:correct");
8877
9025
  }
8878
9026
  });