neotoma 0.21.5 → 0.22.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/README.md +20 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +162 -14
- package/dist/actions.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +203 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/developer/cli_agent_instructions.md +2 -0
- package/dist/docs/developer/cli_reference.md +9 -0
- package/dist/docs/developer/mcp/instructions.md +8 -0
- package/dist/docs/infrastructure/deployment.md +6 -4
- package/dist/docs/legal/site_terms_of_use.md +2 -1
- package/dist/docs/reference/error_codes.md +95 -0
- package/dist/docs/security/advisories/2026-05-11-inspector-auth-bypass.md +1 -1
- package/dist/docs/security/advisories/README.md +3 -3
- package/dist/docs/security/threat_model.md +1 -1
- package/dist/docs/subsystems/issues.md +1 -1
- package/dist/docs/subsystems/sandbox_deployment.md +22 -16
- package/dist/docs/testing/automated_test_catalog.md +13 -6
- package/dist/inspector/assets/{access_policies-B0S0qvu_.js → access_policies-SizWwSjn.js} +1 -1
- package/dist/inspector/assets/{agent_badge-BkdvFESY.js → agent_badge-Cn1SlcFX.js} +1 -1
- package/dist/inspector/assets/{agent_detail-DP0gQW2Q.js → agent_detail-BG5KDNtE.js} +1 -1
- package/dist/inspector/assets/{agent_filter-COKVxYFV.js → agent_filter-BAONVePH.js} +1 -1
- package/dist/inspector/assets/{agent_grant_detail-Dx9XnxQF.js → agent_grant_detail-UxL02Gdg.js} +1 -1
- package/dist/inspector/assets/{agent_grant_form-c_cKOS1t.js → agent_grant_form-CjQdyLL1.js} +1 -1
- package/dist/inspector/assets/{agent_grants-2kUpLoVa.js → agent_grants-B_h0fn0F.js} +1 -1
- package/dist/inspector/assets/{agents-CGacc5go.js → agents-DIH5cs7p.js} +1 -1
- package/dist/inspector/assets/{api_not_configured_state-D3-Y7f4P.js → api_not_configured_state-zajjLcVz.js} +1 -1
- package/dist/inspector/assets/{attribution_card-DHSwBDvm.js → attribution_card-DdyUbizz.js} +1 -1
- package/dist/inspector/assets/{attribution_summary-BDb76qdA.js → attribution_summary-IHhspwd0.js} +1 -1
- package/dist/inspector/assets/{bundles-BrZCSKdE.js → bundles-DN7cQAQe.js} +1 -1
- package/dist/inspector/assets/{card-HLZ7P9gv.js → card-DfiBnyJz.js} +1 -1
- package/dist/inspector/assets/{checkbox-X0LhUk54.js → checkbox-Dhb2Rqtd.js} +1 -1
- package/dist/inspector/assets/{compliance-B-vHY0Mj.js → compliance-Bf_6GcUj.js} +1 -1
- package/dist/inspector/assets/{confirm-dialog-BZhGXjGp.js → confirm-dialog-Bw3EJsrJ.js} +1 -1
- package/dist/inspector/assets/{conversation_common-CqwsnXcN.js → conversation_common-CFRyZ7p2.js} +1 -1
- package/dist/inspector/assets/{conversation_detail-BmH4sCju.js → conversation_detail-Bg19Nxid.js} +1 -1
- package/dist/inspector/assets/{copy_id_button-CDok7EQV.js → copy_id_button-CnakyAUV.js} +1 -1
- package/dist/inspector/assets/{copyable_code_block-j3QB0aQg.js → copyable_code_block-Cf7BV5XI.js} +1 -1
- package/dist/inspector/assets/{corrections-BnjNBaly.js → corrections-DLiH5564.js} +1 -1
- package/dist/inspector/assets/{dashboard-BulD_nUh.js → dashboard-CLnmOktO.js} +1 -1
- package/dist/inspector/assets/{data-table-B-mxpOia.js → data-table-DSAodRMR.js} +1 -1
- package/dist/inspector/assets/{design-DnQ02ply.js → design-BAz4q7Iq.js} +1 -1
- package/dist/inspector/assets/{dialog-CsNugp51.js → dialog-BsZ5EdEE.js} +1 -1
- package/dist/inspector/assets/{docs-BSpGmuZ2.js → docs-lZtdsNZN.js} +1 -1
- package/dist/inspector/assets/{docs_page_layout-Ej9dnRro.js → docs_page_layout-BTLx30w6.js} +1 -1
- package/dist/inspector/assets/{embed_graph-Dbg37Xuy.js → embed_graph-Cqu_rKJi.js} +1 -1
- package/dist/inspector/assets/{empty_state-Y44G8LC3.js → empty_state-D4eAv7uQ.js} +1 -1
- package/dist/inspector/assets/{entities-DkBCXibK.js → entities-DJ9KuPrY.js} +1 -1
- package/dist/inspector/assets/{entity_correct-CSa6U1Nk.js → entity_correct-CjMlwcWd.js} +1 -1
- package/dist/inspector/assets/{entity_history-CxmrR_9X.js → entity_history-aDxms2rn.js} +1 -1
- package/dist/inspector/assets/{entity_history_sections-DEWV31Ot.js → entity_history_sections-B58KGFzd.js} +1 -1
- package/dist/inspector/assets/{entity_link-oDivxz85.js → entity_link-BlqV2ig2.js} +1 -1
- package/dist/inspector/assets/{entity_relationships_by_type-DY0Ld1rw.js → entity_relationships_by_type-BnCbbao4.js} +1 -1
- package/dist/inspector/assets/{entity_segment_page-WT6IOAC6.js → entity_segment_page-CbQjUSLk.js} +1 -1
- package/dist/inspector/assets/{entity_table_column_toggle-CBgWmzQl.js → entity_table_column_toggle-DEr55C5I.js} +1 -1
- package/dist/inspector/assets/{entity_timeline-Dq38UOgV.js → entity_timeline-CT95_E8j.js} +1 -1
- package/dist/inspector/assets/{entity_types-CN-WOauu.js → entity_types-BOD6hz75.js} +1 -1
- package/dist/inspector/assets/{faq-SK87sez6.js → faq-CmTO_NOt.js} +1 -1
- package/dist/inspector/assets/{graph_auto_fit-BMOrV_D2.js → graph_auto_fit-DG06bEkf.js} +1 -1
- package/dist/inspector/assets/{graph_explorer-DEu_WyZ2.js → graph_explorer-dhssxyh6.js} +1 -1
- package/dist/inspector/assets/{home-kfu3wSXt.js → home-C1-SpHes.js} +1 -1
- package/dist/inspector/assets/{index-afk3dCXk.js → index-BwcgXq7C.js} +1 -1
- package/dist/inspector/assets/{index-D6jp2NN2.js → index-D7WGgcjs.js} +2 -2
- package/dist/inspector/assets/{index-CnTUFeBn.js → index-DaLi8rY1.js} +1 -1
- package/dist/inspector/assets/{interpretations-DbtcIPBS.js → interpretations-BOTRs9zO.js} +1 -1
- package/dist/inspector/assets/interpretations-C1f56O6G.js +1 -0
- package/dist/inspector/assets/{issue_author_attribution-C_Uvk4F5.js → issue_author_attribution-DUDerjDR.js} +1 -1
- package/dist/inspector/assets/{issue_detail-Ct1G2hyw.js → issue_detail-B-1sOpD9.js} +1 -1
- package/dist/inspector/assets/{issues-CUiar649.js → issues-B2mxuRox.js} +1 -1
- package/dist/inspector/assets/{json_viewer-DPC1DsaT.js → json_viewer-CvBI_AVO.js} +1 -1
- package/dist/inspector/assets/{label-BQ8-xKUC.js → label-B_MDF1Qm.js} +1 -1
- package/dist/inspector/assets/{live_relative_time-D1kEqQ4i.js → live_relative_time-Cod2SFqP.js} +1 -1
- package/dist/inspector/assets/{mobile_filter_popover-Bvj6GSAo.js → mobile_filter_popover-D-AfmJEd.js} +1 -1
- package/dist/inspector/assets/{not_found-0bIlvlSX.js → not_found-W1xm1Bpn.js} +1 -1
- package/dist/inspector/assets/{oauth_callback-BWXd9Wge.js → oauth_callback--oUBxVdh.js} +1 -1
- package/dist/inspector/assets/{oauth_signin-CZO_QVYB.js → oauth_signin-CbkMUE89.js} +1 -1
- package/dist/inspector/assets/{observations-CikFMfEg.js → observations-CPsDsA62.js} +1 -1
- package/dist/inspector/assets/{page_shell-BosAA9Zi.js → page_shell-DE3y5lgp.js} +1 -1
- package/dist/inspector/assets/{pagination-9hSWjJSM.js → pagination-CHnUCq28.js} +1 -1
- package/dist/inspector/assets/{peer_detail-BkQDnYWW.js → peer_detail-B704bYU_.js} +1 -1
- package/dist/inspector/assets/{peers-BCBT6Ylr.js → peers-l__mD88k.js} +1 -1
- package/dist/inspector/assets/{pin_primitive_button-DDwdBrpd.js → pin_primitive_button-wyB6QUdt.js} +1 -1
- package/dist/inspector/assets/{recent_activity-D8ZzUMMQ.js → recent_activity-CDsDl96P.js} +1 -1
- package/dist/inspector/assets/{recent_conversations-DCgvsODD.js → recent_conversations-AljoEjIJ.js} +1 -1
- package/dist/inspector/assets/{recent_conversations-Drio7WK-.js → recent_conversations-VL5wKo8t.js} +1 -1
- package/dist/inspector/assets/{recent_records_feed-BtPgzGjQ.js → recent_records_feed-StdxJqbO.js} +1 -1
- package/dist/inspector/assets/record_activity-BFZ8OHZk.js +1 -0
- package/dist/inspector/assets/{relationship_detail-Bib84abm.js → relationship_detail-BRwUMgL4.js} +1 -1
- package/dist/inspector/assets/{relationship_panel_groups-DLZtyWmB.js → relationship_panel_groups-Yc8ecUfZ.js} +1 -1
- package/dist/inspector/assets/{relationships-C6AMAVnt.js → relationships-BulEoxTG.js} +1 -1
- package/dist/inspector/assets/{relationships-CbmaLg1w.js → relationships-DmV58gWH.js} +1 -1
- package/dist/inspector/assets/{sandbox-CDn_AfpW.js → sandbox-CyKCUqMo.js} +1 -1
- package/dist/inspector/assets/{schema_detail-rdHCdMiO.js → schema_detail-B-OSbQxD.js} +1 -1
- package/dist/inspector/assets/{schemas-BGFS1NIU.js → schemas-BH5NPYiv.js} +1 -1
- package/dist/inspector/assets/{search-D0mISNIp.js → search-8_d9cddJ.js} +1 -1
- package/dist/inspector/assets/{segmented_control-Dh4asumQ.js → segmented_control-Dbs8GQ7U.js} +1 -1
- package/dist/inspector/assets/{select-CGgVNBJP.js → select-Ca8Vi4-P.js} +1 -1
- package/dist/inspector/assets/{settings-WrB7DH64.js → settings-Ciqn2fJY.js} +1 -1
- package/dist/inspector/assets/{source_content-CdneU5cC.js → source_content-DOlpoUVc.js} +1 -1
- package/dist/inspector/assets/{source_content_open_button-DdwQmqei.js → source_content_open_button-BtFZ3Gu9.js} +1 -1
- package/dist/inspector/assets/{source_detail-BGV8BTSk.js → source_detail-8Dz_iJtK.js} +1 -1
- package/dist/inspector/assets/{source_link-3OE0LIxN.js → source_link-CGGiFzQc.js} +1 -1
- package/dist/inspector/assets/{sources-MQzjTQPU.js → sources-B29h_Vtk.js} +1 -1
- package/dist/inspector/assets/{subscriptions-DSSlZgxQ.js → subscriptions-DuZVEAlj.js} +1 -1
- package/dist/inspector/assets/{switch-CE4bL5sC.js → switch-D472BSa5.js} +1 -1
- package/dist/inspector/assets/{tabs-ZrN5u0Y9.js → tabs-CNCi1aCG.js} +1 -1
- package/dist/inspector/assets/{textarea-CBYe0AAK.js → textarea-DsTTUsEK.js} +1 -1
- package/dist/inspector/assets/{timeline-Ad0NUrF_.js → timeline-CAZLGn0X.js} +1 -1
- package/dist/inspector/assets/{timeline-uLYAndU3.js → timeline-Dnlu40yX.js} +1 -1
- package/dist/inspector/assets/{timeline_event_detail-BfgHJ0XR.js → timeline_event_detail-C7preATf.js} +1 -1
- package/dist/inspector/assets/{turn_detail-CUBCiCcq.js → turn_detail-DLzSm996.js} +1 -1
- package/dist/inspector/assets/{turns-B1PAnWxV.js → turns-BAMqn17p.js} +1 -1
- package/dist/inspector/assets/{use_agents-Kr_qThpq.js → use_agents-c0bZzrii.js} +1 -1
- package/dist/inspector/assets/{use_entities-zE9adlzL.js → use_entities-x5BPBdKo.js} +1 -1
- package/dist/inspector/assets/use_interpretations-BR4Kbo1i.js +1 -0
- package/dist/inspector/assets/{use_mutations-D4WoExI4.js → use_mutations-C284FI3l.js} +1 -1
- package/dist/inspector/assets/{use_peers-k2UDs1Pi.js → use_peers-DXFhfeuf.js} +1 -1
- package/dist/inspector/assets/{use_relationships-DVVjD-SH.js → use_relationships-CXktmg7M.js} +1 -1
- package/dist/inspector/assets/{use_stats-Cmjbd4x1.js → use_stats-DG4ySbul.js} +1 -1
- package/dist/inspector/assets/{use_timeline-De1q_UCR.js → use_timeline-CXc_N-XZ.js} +1 -1
- package/dist/inspector/assets/{use_turns-B4g1lTlJ.js → use_turns-D8vym3df.js} +1 -1
- package/dist/inspector/assets/{world_time_event_timeline-CLue1xiu.js → world_time_event_timeline-DwiwRhnM.js} +1 -1
- package/dist/inspector/index.html +1 -1
- package/dist/mcp_instruction_doc.d.ts +22 -0
- package/dist/mcp_instruction_doc.d.ts.map +1 -1
- package/dist/mcp_instruction_doc.js +26 -0
- package/dist/mcp_instruction_doc.js.map +1 -1
- package/dist/repositories/sqlite/sqlite_client.d.ts +14 -0
- package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
- package/dist/repositories/sqlite/sqlite_client.js +46 -1
- package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
- package/dist/scripts/reset_sandbox.js +3 -2
- package/dist/scripts/reset_sandbox.js.map +1 -1
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +103 -2
- package/dist/server.js.map +1 -1
- package/dist/services/correction.d.ts.map +1 -1
- package/dist/services/correction.js +20 -0
- package/dist/services/correction.js.map +1 -1
- package/dist/services/google_oidc.d.ts +12 -1
- package/dist/services/google_oidc.d.ts.map +1 -1
- package/dist/services/google_oidc.js +19 -2
- package/dist/services/google_oidc.js.map +1 -1
- package/dist/services/instance_policy.d.ts +284 -0
- package/dist/services/instance_policy.d.ts.map +1 -0
- package/dist/services/instance_policy.js +581 -0
- package/dist/services/instance_policy.js.map +1 -0
- package/dist/services/issues/issue_operations.d.ts.map +1 -1
- package/dist/services/issues/issue_operations.js +5 -0
- package/dist/services/issues/issue_operations.js.map +1 -1
- package/dist/services/issues/neotoma_client.d.ts +5 -0
- package/dist/services/issues/neotoma_client.d.ts.map +1 -1
- package/dist/services/issues/neotoma_client.js +15 -0
- package/dist/services/issues/neotoma_client.js.map +1 -1
- package/dist/services/root_landing/harness_snippets.d.ts.map +1 -1
- package/dist/services/root_landing/harness_snippets.js +2 -1
- package/dist/services/root_landing/harness_snippets.js.map +1 -1
- package/dist/services/sandbox_mode.d.ts.map +1 -1
- package/dist/services/sandbox_mode.js +3 -4
- package/dist/services/sandbox_mode.js.map +1 -1
- package/dist/services/schema_definitions.d.ts.map +1 -1
- package/dist/services/schema_definitions.js +56 -0
- package/dist/services/schema_definitions.js.map +1 -1
- package/dist/services/schema_registry.d.ts +69 -0
- package/dist/services/schema_registry.d.ts.map +1 -1
- package/dist/services/schema_registry.js +7 -0
- package/dist/services/schema_registry.js.map +1 -1
- package/dist/shared/action_schemas.d.ts +2 -2
- package/dist/shared/api_client.d.ts +8 -0
- package/dist/shared/api_client.d.ts.map +1 -1
- package/dist/shared/api_client.js +13 -4
- package/dist/shared/api_client.js.map +1 -1
- package/dist/shared/contract_mappings.d.ts.map +1 -1
- package/dist/shared/contract_mappings.js +10 -0
- package/dist/shared/contract_mappings.js.map +1 -1
- package/dist/shared/openapi_types.d.ts +320 -2
- package/dist/shared/openapi_types.d.ts.map +1 -1
- package/dist/shared/sandbox_terms_content.d.ts +6 -3
- package/dist/shared/sandbox_terms_content.d.ts.map +1 -1
- package/dist/shared/sandbox_terms_content.js +10 -5
- package/dist/shared/sandbox_terms_content.js.map +1 -1
- package/dist/tool_definitions.d.ts +1 -1
- package/dist/tool_definitions.d.ts.map +1 -1
- package/dist/tool_definitions.js +6 -0
- package/dist/tool_definitions.js.map +1 -1
- package/docs/developer/mcp/instructions.md +8 -0
- package/openapi.yaml +323 -1
- package/package.json +1 -1
- package/dist/inspector/assets/interpretations-8w33Il_Q.js +0 -1
- package/dist/inspector/assets/record_activity-OPIQqbxs.js +0 -1
- 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
|
-
|
|
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
|
|
package/dist/actions.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;
|
|
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
|
-
|
|
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
|
|
755
|
-
|
|
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
|
-
//
|
|
776
|
+
// An unreadable policy degrades to the global instructions rather than
|
|
777
|
+
// failing the request.
|
|
766
778
|
}
|
|
767
|
-
res.
|
|
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
|
|
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
|
});
|