neotoma 0.26.0 → 0.27.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 +3 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +165 -27
- package/dist/actions.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/architecture/architecture.md +1 -1
- package/dist/docs/developer/cli_agent_instructions.md +3 -1
- package/dist/docs/developer/cli_reference.md +4 -2
- package/dist/docs/developer/mcp/instructions.md +8 -2
- package/dist/docs/developer/mcp_codex_remote_setup.md +61 -0
- package/dist/docs/developer/mcp_continue_setup.md +9 -17
- package/dist/docs/developer/mcp_letta_setup.md +5 -2
- package/dist/docs/developer/mcp_openclaw_setup.md +5 -3
- package/dist/docs/developer/mcp_overview.md +39 -19
- package/dist/docs/developer/mcp_vscode_setup.md +10 -23
- package/dist/docs/developer/mcp_windsurf_setup.md +9 -25
- package/dist/docs/developer/seo_analytics_verification.md +12 -10
- package/dist/docs/developer/site_launch_readiness.md +56 -0
- package/dist/docs/developer/tunnels.md +1 -1
- package/dist/docs/infrastructure/deployment.md +1 -1
- package/dist/docs/integrations/es/matrix.md +38 -0
- package/dist/docs/integrations/matrix.md +26 -71
- package/dist/docs/legal/site_privacy_notice.md +30 -8
- package/dist/docs/site/site_doc_manifest.yaml +7 -4
- package/dist/docs/specs/MCP_SPEC.md +7 -2
- package/dist/docs/subsystems/agent_capabilities.md +105 -4
- package/dist/docs/subsystems/auth.md +45 -0
- package/dist/docs/subsystems/conflict_resolution.md +18 -4
- package/dist/docs/subsystems/errors.md +1 -0
- package/dist/docs/subsystems/observation_architecture.md +2 -1
- package/dist/docs/subsystems/peer_sync.md +146 -24
- package/dist/docs/subsystems/reducer.md +130 -10
- package/dist/docs/testing/automated_test_catalog.md +84 -24
- package/dist/inspector/assets/{access_policies-CJOlV60d.js → access_policies-CxCjBA7D.js} +1 -1
- package/dist/inspector/assets/{agent_badge-BnBqn8kt.js → agent_badge-DC5_shpm.js} +1 -1
- package/dist/inspector/assets/{agent_detail-wP0ccjhX.js → agent_detail-BvbCiS93.js} +1 -1
- package/dist/inspector/assets/{agent_filter-Bp2xA131.js → agent_filter-B0yydBrx.js} +1 -1
- package/dist/inspector/assets/{agent_grant_detail-HIGr5dq_.js → agent_grant_detail-3kKIKzX6.js} +1 -1
- package/dist/inspector/assets/agent_grant_form-B6q5pZjP.js +1 -0
- package/dist/inspector/assets/{agent_grants-CTKkadE5.js → agent_grants-B6nfERyL.js} +1 -1
- package/dist/inspector/assets/{agents-CPwJLNJN.js → agents-ClGGY_ZM.js} +1 -1
- package/dist/inspector/assets/{api_not_configured_state-ivqIcs2c.js → api_not_configured_state-CJQ8_tf0.js} +1 -1
- package/dist/inspector/assets/{attribution_card-5ZAqRz6K.js → attribution_card-Bksgj8a3.js} +1 -1
- package/dist/inspector/assets/{attribution_summary-D_MHFv7e.js → attribution_summary-D4MXp_WI.js} +1 -1
- package/dist/inspector/assets/{bundles-QfMciBj9.js → bundles-ycjhJZOr.js} +1 -1
- package/dist/inspector/assets/{card-1rkzdi9W.js → card-4DFCisoq.js} +1 -1
- package/dist/inspector/assets/{checkbox-CO5dXSZf.js → checkbox-V-L1QbzC.js} +1 -1
- package/dist/inspector/assets/{compliance-OTGEtY7Y.js → compliance-DKt8M8nA.js} +1 -1
- package/dist/inspector/assets/{confirm-dialog-D0cZ5vck.js → confirm-dialog-DYa8Vbqn.js} +1 -1
- package/dist/inspector/assets/{conversation_common-cSILaHXq.js → conversation_common-BTYeAMA9.js} +1 -1
- package/dist/inspector/assets/{conversation_detail-DT-lGapN.js → conversation_detail-CW7GDcOq.js} +1 -1
- package/dist/inspector/assets/{copy_id_button-Dz0JDn4l.js → copy_id_button-ByAabH9A.js} +1 -1
- package/dist/inspector/assets/{copyable_code_block-B2f99g8Q.js → copyable_code_block-Bspyx1dk.js} +1 -1
- package/dist/inspector/assets/{corrections-KvxGvvLV.js → corrections-CTmXBVDZ.js} +1 -1
- package/dist/inspector/assets/{dashboard-BudvW21X.js → dashboard-C4yk5lEb.js} +1 -1
- package/dist/inspector/assets/{data-table-d9EMxowf.js → data-table-BWQDz2No.js} +1 -1
- package/dist/inspector/assets/{design-DyiQbEeP.js → design-DSwT0Hqn.js} +1 -1
- package/dist/inspector/assets/{dialog-BmW1Iw8o.js → dialog-D-_2M8D7.js} +1 -1
- package/dist/inspector/assets/{docs-CT3IYX0E.js → docs-BcccV5pq.js} +1 -1
- package/dist/inspector/assets/{docs_page_layout-CosNjUZa.js → docs_page_layout-D2iWK6hY.js} +1 -1
- package/dist/inspector/assets/{embed_graph-Dk1-zVnz.js → embed_graph-BjqqvjAi.js} +1 -1
- package/dist/inspector/assets/{empty_state-B8Ot2oGB.js → empty_state-DWHf3FCX.js} +1 -1
- package/dist/inspector/assets/{entities-Dt5Ts5UN.js → entities-DrG0hlC7.js} +1 -1
- package/dist/inspector/assets/{entity_correct-D2PHEpGY.js → entity_correct-Bpg1q7T4.js} +1 -1
- package/dist/inspector/assets/{entity_history-DHgNythA.js → entity_history-CJ62iQZu.js} +1 -1
- package/dist/inspector/assets/{entity_history_sections-Cnxblw-Z.js → entity_history_sections-BDd9Wiiu.js} +1 -1
- package/dist/inspector/assets/{entity_link-DWf1VD1n.js → entity_link-Bb0aaVN-.js} +1 -1
- package/dist/inspector/assets/{entity_relationships_by_type-B0-TLC5o.js → entity_relationships_by_type-CJyxm8EQ.js} +1 -1
- package/dist/inspector/assets/{entity_segment_page-CzZ0VhKm.js → entity_segment_page-q21vDqLO.js} +1 -1
- package/dist/inspector/assets/{entity_table_column_toggle-C73aErGE.js → entity_table_column_toggle-DN8xVqKP.js} +1 -1
- package/dist/inspector/assets/{entity_timeline-SAq5qvB5.js → entity_timeline-D8glraS3.js} +1 -1
- package/dist/inspector/assets/{entity_types-DEcDvyO0.js → entity_types-Cers3ITY.js} +1 -1
- package/dist/inspector/assets/{faq-DazWIFdv.js → faq-Bd-NrqaV.js} +1 -1
- package/dist/inspector/assets/{graph_auto_fit-CSzD7yNh.js → graph_auto_fit-BYwV350M.js} +1 -1
- package/dist/inspector/assets/{graph_explorer-BEXXiMQ1.js → graph_explorer-LCTji9_2.js} +1 -1
- package/dist/inspector/assets/{home-3AZtmjKp.js → home-EisPy9sH.js} +1 -1
- package/dist/inspector/assets/{index-fc4sMprn.js → index-B6DF8LVA.js} +2 -2
- package/dist/inspector/assets/{index-Dt_q_A1j.js → index-Cb4wreu4.js} +1 -1
- package/dist/inspector/assets/{index-Cii0RALL.js → index-gMZogIZg.js} +1 -1
- package/dist/inspector/assets/interpretations-BGaVaB-X.js +1 -0
- package/dist/inspector/assets/{interpretations-DtzoiGuK.js → interpretations-Cy0Bpx62.js} +1 -1
- package/dist/inspector/assets/{issue_author_attribution-DtmFaluD.js → issue_author_attribution-DcQIow4F.js} +1 -1
- package/dist/inspector/assets/{issue_detail-ChHiz9I_.js → issue_detail-DTjIEgh0.js} +1 -1
- package/dist/inspector/assets/{issues-BoxR2nB2.js → issues-Dphlhv_W.js} +1 -1
- package/dist/inspector/assets/{json_viewer-KAFp_j-8.js → json_viewer-BM4kmWFP.js} +1 -1
- package/dist/inspector/assets/{label-Dn0ARjYl.js → label-hdmBvRvv.js} +1 -1
- package/dist/inspector/assets/{live_relative_time-gOIFOOLR.js → live_relative_time-CYFW3IVl.js} +1 -1
- package/dist/inspector/assets/{mobile_filter_popover-2ezU-mlZ.js → mobile_filter_popover-KCC5eTUH.js} +1 -1
- package/dist/inspector/assets/{not_found-DwX7ikQC.js → not_found-NNkEa9Mr.js} +1 -1
- package/dist/inspector/assets/{oauth_callback-BDGLOyTN.js → oauth_callback-Vg8huIE7.js} +1 -1
- package/dist/inspector/assets/{oauth_signin-CGcCtq-V.js → oauth_signin-DgBwjyyR.js} +1 -1
- package/dist/inspector/assets/{observations-DvcUGpHm.js → observations-DtOGIRUt.js} +1 -1
- package/dist/inspector/assets/{page_shell-DiwchdUC.js → page_shell-ekehM_7R.js} +1 -1
- package/dist/inspector/assets/{pagination-BGIUHQkm.js → pagination-DqHnb2pk.js} +1 -1
- package/dist/inspector/assets/{peer_detail-DeD1sTeJ.js → peer_detail-Gkxcd75E.js} +1 -1
- package/dist/inspector/assets/{peers-CXe47m55.js → peers-CbWw6lyc.js} +1 -1
- package/dist/inspector/assets/{pin_primitive_button-C37Ha0Bt.js → pin_primitive_button-B4r-7dqY.js} +1 -1
- package/dist/inspector/assets/{recent_activity-CzQjt4ym.js → recent_activity-C6PVuidG.js} +1 -1
- package/dist/inspector/assets/{recent_conversations-BnUl4h4t.js → recent_conversations-CIdElm4H.js} +1 -1
- package/dist/inspector/assets/{recent_conversations-BFYZTxUJ.js → recent_conversations-D0yaACqZ.js} +1 -1
- package/dist/inspector/assets/{recent_records_feed-y1n0RuMa.js → recent_records_feed-CIXUCAzn.js} +1 -1
- package/dist/inspector/assets/record_activity-BjhZYy74.js +1 -0
- package/dist/inspector/assets/{relationship_detail-ic83ZCwG.js → relationship_detail-Bpefk_R5.js} +1 -1
- package/dist/inspector/assets/{relationship_panel_groups-CI1fZIU_.js → relationship_panel_groups-DDXpEtW5.js} +1 -1
- package/dist/inspector/assets/{relationships-C7iSdgXG.js → relationships-B3QrncvI.js} +1 -1
- package/dist/inspector/assets/{relationships-C1JrEl85.js → relationships-M99riRdd.js} +1 -1
- package/dist/inspector/assets/{sandbox-Dst-WJhB.js → sandbox-DucoYAes.js} +1 -1
- package/dist/inspector/assets/{schema_detail-C7_JSWAw.js → schema_detail-CN_3OF1B.js} +1 -1
- package/dist/inspector/assets/{schemas-BgrIpRVJ.js → schemas-rRFQFZKI.js} +1 -1
- package/dist/inspector/assets/{search-BUmT32dy.js → search-CtnCyQ8D.js} +1 -1
- package/dist/inspector/assets/{segmented_control-t1pBImFE.js → segmented_control-DSHm0DuI.js} +1 -1
- package/dist/inspector/assets/{select-DU7Jwzuy.js → select-DhzhhG8n.js} +1 -1
- package/dist/inspector/assets/{settings-BAPHY6RY.js → settings-B7cfSG_W.js} +1 -1
- package/dist/inspector/assets/{source_content-CIhKrGsl.js → source_content-Bhwy7rhS.js} +1 -1
- package/dist/inspector/assets/{source_content_open_button-Erw2ErgG.js → source_content_open_button-C6vUIgFc.js} +1 -1
- package/dist/inspector/assets/{source_detail-C0XOo0Uu.js → source_detail-C1JXqtvA.js} +1 -1
- package/dist/inspector/assets/{source_link-CkcRMEO3.js → source_link-BUk8ACVx.js} +1 -1
- package/dist/inspector/assets/{sources-C0JBEINb.js → sources-NTyTD2hT.js} +1 -1
- package/dist/inspector/assets/{subscriptions-DxU4kHGt.js → subscriptions-CNA06mS4.js} +1 -1
- package/dist/inspector/assets/{switch-BhKadp29.js → switch-DLHM_uAC.js} +1 -1
- package/dist/inspector/assets/{tabs-d81XObYb.js → tabs-D2_FUlH4.js} +1 -1
- package/dist/inspector/assets/{textarea-DTiKLH24.js → textarea-DS0t72g6.js} +1 -1
- package/dist/inspector/assets/{timeline-BEyKQpER.js → timeline-CVhW-34e.js} +1 -1
- package/dist/inspector/assets/{timeline-98QXcGln.js → timeline-CZehuTIb.js} +1 -1
- package/dist/inspector/assets/{timeline_event_detail-DzGzqtrx.js → timeline_event_detail-KYTlaJ99.js} +1 -1
- package/dist/inspector/assets/{turn_detail-CDP_FEPT.js → turn_detail-DN4VR_ut.js} +1 -1
- package/dist/inspector/assets/{turns-DLmr1Iue.js → turns-Bq8p8bUB.js} +1 -1
- package/dist/inspector/assets/{use_agents-D3rvlr5v.js → use_agents-GVOD0Ky2.js} +1 -1
- package/dist/inspector/assets/{use_entities-CVikeUOv.js → use_entities-Buvb5BTb.js} +1 -1
- package/dist/inspector/assets/use_interpretations-DDEREnBJ.js +1 -0
- package/dist/inspector/assets/{use_mutations-BhwSLXcJ.js → use_mutations-DbLUsoYs.js} +1 -1
- package/dist/inspector/assets/{use_peers-QTm1Z_BV.js → use_peers-DyQGh7km.js} +1 -1
- package/dist/inspector/assets/{use_relationships-DtymxAI9.js → use_relationships-djzl2cEw.js} +1 -1
- package/dist/inspector/assets/{use_stats-BW1Z-dI2.js → use_stats-DvaDtejF.js} +1 -1
- package/dist/inspector/assets/{use_timeline-DND87Sof.js → use_timeline-BL3A-Im3.js} +1 -1
- package/dist/inspector/assets/{use_turns-Dfb6Q8Ru.js → use_turns-DEvDeYgu.js} +1 -1
- package/dist/inspector/assets/{world_time_event_timeline-DxzejNrJ.js → world_time_event_timeline-DjPxlzYn.js} +1 -1
- package/dist/inspector/index.html +1 -1
- package/dist/reducers/observation_reducer.d.ts +7 -0
- package/dist/reducers/observation_reducer.d.ts.map +1 -1
- package/dist/reducers/observation_reducer.js +60 -7
- package/dist/reducers/observation_reducer.js.map +1 -1
- package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
- package/dist/repositories/sqlite/sqlite_client.js +30 -0
- package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +157 -18
- package/dist/server.js.map +1 -1
- package/dist/services/agent_capabilities.d.ts +25 -1
- package/dist/services/agent_capabilities.d.ts.map +1 -1
- package/dist/services/agent_capabilities.js +5 -8
- package/dist/services/agent_capabilities.js.map +1 -1
- package/dist/services/agent_grants.d.ts.map +1 -1
- package/dist/services/agent_grants.js +7 -0
- package/dist/services/agent_grants.js.map +1 -1
- package/dist/services/batch_correction.d.ts +2 -1
- package/dist/services/batch_correction.d.ts.map +1 -1
- package/dist/services/batch_correction.js +29 -1
- package/dist/services/batch_correction.js.map +1 -1
- package/dist/services/bearer_slots.d.ts +109 -0
- package/dist/services/bearer_slots.d.ts.map +1 -0
- package/dist/services/bearer_slots.js +275 -0
- package/dist/services/bearer_slots.js.map +1 -0
- package/dist/services/bundles/activation.d.ts +4 -2
- package/dist/services/bundles/activation.d.ts.map +1 -1
- package/dist/services/bundles/activation.js +13 -9
- package/dist/services/bundles/activation.js.map +1 -1
- package/dist/services/correction.d.ts.map +1 -1
- package/dist/services/correction.js +14 -2
- package/dist/services/correction.js.map +1 -1
- package/dist/services/correction_identity.d.ts +63 -0
- package/dist/services/correction_identity.d.ts.map +1 -0
- package/dist/services/correction_identity.js +57 -0
- package/dist/services/correction_identity.js.map +1 -0
- package/dist/services/entity_lifecycle_storage.d.ts.map +1 -1
- package/dist/services/entity_lifecycle_storage.js +17 -4
- package/dist/services/entity_lifecycle_storage.js.map +1 -1
- package/dist/services/entity_population_certificate.d.ts +2 -0
- package/dist/services/entity_population_certificate.d.ts.map +1 -0
- package/dist/services/entity_population_certificate.js +63 -0
- package/dist/services/entity_population_certificate.js.map +1 -0
- package/dist/services/entity_queries.d.ts.map +1 -1
- package/dist/services/entity_queries.js +82 -41
- package/dist/services/entity_queries.js.map +1 -1
- package/dist/services/interpretation.d.ts +12 -0
- package/dist/services/interpretation.d.ts.map +1 -1
- package/dist/services/interpretation.js +88 -29
- package/dist/services/interpretation.js.map +1 -1
- package/dist/services/mutation_capability.d.ts +105 -0
- package/dist/services/mutation_capability.d.ts.map +1 -0
- package/dist/services/mutation_capability.js +262 -0
- package/dist/services/mutation_capability.js.map +1 -0
- package/dist/services/net/private_host_guard.d.ts +6 -2
- package/dist/services/net/private_host_guard.d.ts.map +1 -1
- package/dist/services/net/private_host_guard.js +14 -3
- package/dist/services/net/private_host_guard.js.map +1 -1
- package/dist/services/oauth_key_gate.d.ts.map +1 -1
- package/dist/services/oauth_key_gate.js +5 -0
- package/dist/services/oauth_key_gate.js.map +1 -1
- package/dist/services/observation_storage.d.ts.map +1 -1
- package/dist/services/observation_storage.js +4 -0
- package/dist/services/observation_storage.js.map +1 -1
- package/dist/services/rendered_page/copy_controls.d.ts +8 -0
- package/dist/services/rendered_page/copy_controls.d.ts.map +1 -0
- package/dist/services/rendered_page/copy_controls.js +122 -0
- package/dist/services/rendered_page/copy_controls.js.map +1 -0
- package/dist/services/rendered_page/html_template.d.ts.map +1 -1
- package/dist/services/rendered_page/html_template.js +9 -0
- package/dist/services/rendered_page/html_template.js.map +1 -1
- package/dist/services/root_landing/harness_snippets.d.ts.map +1 -1
- package/dist/services/root_landing/harness_snippets.js +23 -49
- package/dist/services/root_landing/harness_snippets.js.map +1 -1
- package/dist/services/root_landing/index.d.ts +1 -6
- package/dist/services/root_landing/index.d.ts.map +1 -1
- package/dist/services/root_landing/index.js +2 -16
- package/dist/services/root_landing/index.js.map +1 -1
- package/dist/services/root_landing/site_nav.js +1 -1
- package/dist/services/root_landing/site_nav.js.map +1 -1
- package/dist/services/store_admission.d.ts +26 -0
- package/dist/services/store_admission.d.ts.map +1 -1
- package/dist/services/store_admission.js +74 -6
- package/dist/services/store_admission.js.map +1 -1
- package/dist/services/sync/conflict_resolver.d.ts +39 -5
- package/dist/services/sync/conflict_resolver.d.ts.map +1 -1
- package/dist/services/sync/conflict_resolver.js +250 -18
- package/dist/services/sync/conflict_resolver.js.map +1 -1
- package/dist/services/sync/full_sync.d.ts +12 -0
- package/dist/services/sync/full_sync.d.ts.map +1 -1
- package/dist/services/sync/full_sync.js +78 -9
- package/dist/services/sync/full_sync.js.map +1 -1
- package/dist/services/sync/peer_ops.d.ts +8 -0
- package/dist/services/sync/peer_ops.d.ts.map +1 -1
- package/dist/services/sync/peer_ops.js +26 -0
- package/dist/services/sync/peer_ops.js.map +1 -1
- package/dist/services/sync/remote_snapshot_admission.d.ts +144 -0
- package/dist/services/sync/remote_snapshot_admission.d.ts.map +1 -0
- package/dist/services/sync/remote_snapshot_admission.js +248 -0
- package/dist/services/sync/remote_snapshot_admission.js.map +1 -0
- package/dist/services/sync/seed_peer_schema.d.ts.map +1 -1
- package/dist/services/sync/seed_peer_schema.js +8 -0
- package/dist/services/sync/seed_peer_schema.js.map +1 -1
- package/dist/services/sync/sync_webhook_inbound.d.ts.map +1 -1
- package/dist/services/sync/sync_webhook_inbound.js +37 -9
- package/dist/services/sync/sync_webhook_inbound.js.map +1 -1
- package/dist/services/sync/sync_webhook_outbound.d.ts +8 -0
- package/dist/services/sync/sync_webhook_outbound.d.ts.map +1 -1
- package/dist/services/sync/sync_webhook_outbound.js +74 -0
- package/dist/services/sync/sync_webhook_outbound.js.map +1 -1
- package/dist/shared/action_handlers/entity_handlers.d.ts.map +1 -1
- package/dist/shared/action_handlers/entity_handlers.js +3 -20
- package/dist/shared/action_handlers/entity_handlers.js.map +1 -1
- package/dist/shared/action_schemas.d.ts +12 -0
- package/dist/shared/action_schemas.d.ts.map +1 -1
- package/dist/shared/action_schemas.js +8 -2
- package/dist/shared/action_schemas.js.map +1 -1
- package/dist/shared/harness_connections.d.ts +37 -0
- package/dist/shared/harness_connections.d.ts.map +1 -0
- package/dist/shared/harness_connections.js +339 -0
- package/dist/shared/harness_connections.js.map +1 -0
- package/dist/shared/http_content_negotiation.d.ts +7 -0
- package/dist/shared/http_content_negotiation.d.ts.map +1 -0
- package/dist/shared/http_content_negotiation.js +17 -0
- package/dist/shared/http_content_negotiation.js.map +1 -0
- package/dist/shared/openapi_types.d.ts +333 -9
- package/dist/shared/openapi_types.d.ts.map +1 -1
- package/dist/tool_definitions.d.ts +6 -0
- package/dist/tool_definitions.d.ts.map +1 -1
- package/dist/tool_definitions.js +12 -5
- package/dist/tool_definitions.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/developer/mcp/instructions.md +8 -2
- package/openapi.yaml +269 -7
- package/package.json +5 -5
- package/dist/inspector/assets/agent_grant_form-BoNBvzEf.js +0 -1
- package/dist/inspector/assets/interpretations-CjR2csNL.js +0 -1
- package/dist/inspector/assets/record_activity-kXm-jtQ6.js +0 -1
- package/dist/inspector/assets/use_interpretations-CCoQ6ihs.js +0 -1
package/README.md
CHANGED
|
@@ -61,13 +61,13 @@ graph LR
|
|
|
61
61
|
|
|
62
62
|
## What you can do with it
|
|
63
63
|
|
|
64
|
-
Neotoma v0.
|
|
64
|
+
Neotoma v0.27.0 exposes 67 MCP tools and 112 REST paths in its OpenAPI spec. They cover largely the same capabilities, but the MCP server, the REST API, and the CLI are separate implementations, and some checks and attribution differ between them (see Known limitations). The capability surface includes:
|
|
65
65
|
|
|
66
66
|
**Ingest and store.** Store structured records or raw files in one call. File ingestion extracts text from PDF (with a first-page image fallback), CSV (with adaptive chunking for large files), Parquet, JSON, and plain text; images and audio are stored as raw sources. A committed, keyed structured store started on v0.26.0 that stops partway finishes its missing observations on a same-key retry; a store carrying an interpretation returns `ERR_STORE_WRITE_INCOMPLETE`, and keys left over from earlier versions behave differently over MCP and HTTP (see Known limitations). See Known limitations.
|
|
67
67
|
|
|
68
68
|
**Resolve, retrieve, and search.** Look up entities by identifier (name, email, and similar), resolve identity from multiple signals with confidence scoring, list observations, retrieve a field's provenance chain, traverse the relationship graph N hops, and pull a full graph neighborhood. When an embedding key is configured, semantic vector search runs over entity snapshots (stored locally in sqlite-vec); keyword filtering works without it.
|
|
69
69
|
|
|
70
|
-
**Correct and evolve.** Submit corrections as
|
|
70
|
+
**Correct and evolve.** Submit corrections as observations marked as corrections, never edits: a correction wins in the snapshot until a later correction for the same field replaces it, on array fields too, and the earlier observation stays readable. Schemas are inferred from your data, recommended from recurring unknown fields, and can be auto-enhanced or updated incrementally with versioning. New entity types work without any code change.
|
|
71
71
|
|
|
72
72
|
**Relate and sequence.** Create typed relationships (for example `PART_OF`, `DEPENDS_ON`, `REFERS_TO`, `DUPLICATE_OF`), query timeline events across types and date ranges, and read an entity's observation history in a deterministic order.
|
|
73
73
|
|
|
@@ -111,7 +111,7 @@ neotoma init
|
|
|
111
111
|
neotoma setup --tool claude-code
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
In v0.
|
|
114
|
+
In v0.27.0, `neotoma setup --tool claude-code` writes the MCP entries and more:
|
|
115
115
|
|
|
116
116
|
- Two MCP entries in the project's `.mcp.json`: `neotoma-dev` (development profile, `neotoma.db`) and `neotoma` (production profile, `neotoma.prod.db`). They use separate databases, so an agent on one entry does not see what an agent on the other stored.
|
|
117
117
|
- CLI instruction files for Claude Code, Cursor, and Codex in the project: `.claude/rules/neotoma_cli.mdc`, `.cursor/rules/neotoma_cli.mdc`, and `.codex/neotoma_cli.md`.
|
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,
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|
package/dist/actions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { preflightStructuredStoreAdmission, } from "./services/store_admission.js";
|
|
1
|
+
import { assertCurrentAgentMayStoreEntityTypes, assertStoreEntityTypesNamed, InvalidEntityTypeError, preflightStructuredStoreAdmission, } from "./services/store_admission.js";
|
|
2
2
|
import express from "express";
|
|
3
3
|
import cookieParser from "cookie-parser";
|
|
4
4
|
import cors from "cors";
|
|
@@ -38,6 +38,7 @@ import { CursorError } from "./services/entity_cursor.js";
|
|
|
38
38
|
import { assertNoShadowedRoutes } from "./services/route_shadowing.js";
|
|
39
39
|
import { StorePolicyDeniedError, StorePolicyUnavailableError } from "./services/instance_policy.js";
|
|
40
40
|
import { AgentCapabilityError, contextFromAgentIdentity, enforceAgentCapability, } from "./services/agent_capabilities.js";
|
|
41
|
+
import { ISSUE_THREAD_ENTITY_TYPES, assertCurrentAgentMay, assertCurrentAgentMayLink, assertCurrentAgentMayOnEntities, assetEntityTypeForMimeType, } from "./services/mutation_capability.js";
|
|
41
42
|
import { AccessPolicyError } from "./services/access_policy.js";
|
|
42
43
|
import { hashGuestAccessToken } from "./services/guest_access_token.js";
|
|
43
44
|
import { subscriptionWithinGuestScope } from "./services/subscriptions/guest_scope.js";
|
|
@@ -73,6 +74,7 @@ import { getSandboxTermsResponse } from "./services/sandbox/terms.js";
|
|
|
73
74
|
import { resolveSandboxReportTransport } from "./services/sandbox/transport.js";
|
|
74
75
|
import { getDb } from "./repositories/db/connection.js";
|
|
75
76
|
import { getMcpAuthToken } from "./crypto/mcp_auth_token.js";
|
|
77
|
+
import { announceBearerHandoverStatus, matchConfiguredBearer, recordPreviousBearerUse, } from "./services/bearer_slots.js";
|
|
76
78
|
import { isOauthKeyCredentialValid, normalizeOauthNextPath, OAuthKeySessionStore, } from "./services/oauth_key_gate.js";
|
|
77
79
|
import { buildGoogleAuthorizeUrl, createGoogleSigninNonce, consumeGoogleSigninNonce, exchangeGoogleAuthorizationCode, getSharedGraphUserId, GoogleIdTokenError, isGoogleSigninEnabled, verifyGoogleIdToken, } from "./services/google_oidc.js";
|
|
78
80
|
import { AnalyzeSchemaCandidatesRequestSchema, AuditUndeclaredFragmentsRequestSchema, CorrectEntityRequestSchema, PatchArrayItemRequestSchema, CreateInterpretationRequestSchema, CreateRelationshipsRequestSchema, CreateRelationshipRequestSchema, DeleteEntityRequestSchema, DeleteRelationshipRequestSchema, IssuesBulkEntityIdsRequestSchema, IssuesAddMessageRequestSchema, IssuesGetStatusRequestSchema, IssuesSubmitRequestSchema, IssuesSyncRequestSchema, EntitiesQueryRequestSchema, EntitySnapshotRequestSchema, FieldProvenanceRequestSchema, GetSchemaRecommendationsRequestSchema, ListObservationsRequestSchema, ListRelationshipsRequestSchema, MergeEntitiesRequestSchema, SplitEntityRequestSchema, ObservationsQueryRequestSchema, RegisterSchemaRequestSchema, RelationshipSnapshotRequestSchema, RestoreEntityRequestSchema, RestoreRelationshipRequestSchema, RetrieveEntityByIdentifierSchema, RetrieveGraphNeighborhoodSchema, RetrieveRelatedEntitiesSchema, RELATIONSHIP_ENTITY_ID_FORMAT_HINT, RELATIONSHIP_ENTITY_ID_FORMAT_ISSUE_CODE, ObservationSourceSchema, StoreRequestSchema, UpdateSchemaIncrementalRequestSchema, } from "./shared/action_schemas.js";
|
|
@@ -1805,7 +1807,20 @@ app.all("/mcp", async (req, res) => {
|
|
|
1805
1807
|
}
|
|
1806
1808
|
if (authHeader?.startsWith("Bearer ") && process.env.NEOTOMA_BEARER_TOKEN) {
|
|
1807
1809
|
const token = authHeader.slice(7).trim();
|
|
1808
|
-
|
|
1810
|
+
// Either configured slot (primary, or the retiring bearer while its
|
|
1811
|
+
// handover window is open) takes the SAME branch: same principal,
|
|
1812
|
+
// same grants. See src/services/bearer_slots.ts.
|
|
1813
|
+
const slot = matchConfiguredBearer(token);
|
|
1814
|
+
if (slot) {
|
|
1815
|
+
if (slot === "previous") {
|
|
1816
|
+
recordPreviousBearerUse({
|
|
1817
|
+
surface: "mcp",
|
|
1818
|
+
method: req.method,
|
|
1819
|
+
path: req.path,
|
|
1820
|
+
userAgent: req.headers["user-agent"],
|
|
1821
|
+
clientName: req.headers["x-client-name"],
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1809
1824
|
setGateConnectionIdHeader(req, "dev-local");
|
|
1810
1825
|
connectionIdHeader = "dev-local";
|
|
1811
1826
|
bearerValidated = true;
|
|
@@ -3825,10 +3840,22 @@ app.use(async (req, res, next) => {
|
|
|
3825
3840
|
}
|
|
3826
3841
|
if (headerAuth.startsWith("Bearer ") && process.env.NEOTOMA_BEARER_TOKEN) {
|
|
3827
3842
|
const token = headerAuth.slice(7).trim();
|
|
3828
|
-
|
|
3843
|
+
// Either configured slot takes the SAME branch: same principal, same
|
|
3844
|
+
// grants. See src/services/bearer_slots.ts.
|
|
3845
|
+
const slot = matchConfiguredBearer(token);
|
|
3846
|
+
if (slot) {
|
|
3847
|
+
if (slot === "previous") {
|
|
3848
|
+
recordPreviousBearerUse({
|
|
3849
|
+
surface: "rest",
|
|
3850
|
+
method: req.method,
|
|
3851
|
+
path: req.path,
|
|
3852
|
+
userAgent: req.headers["user-agent"],
|
|
3853
|
+
clientName: req.headers["x-client-name"],
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3829
3856
|
const devUser = await ensureLocalDevUser();
|
|
3830
3857
|
stampUserPrincipal(req, devUser.id);
|
|
3831
|
-
logger.info(`[Auth] ${req.method} ${req.path} auth_method
|
|
3858
|
+
logger.info(`[Auth] ${req.method} ${req.path} auth_method=${slot === "previous" ? "bearer_env_previous" : "bearer_env"} user_id=${devUser.id}`);
|
|
3832
3859
|
return next();
|
|
3833
3860
|
}
|
|
3834
3861
|
}
|
|
@@ -4594,26 +4621,10 @@ app.get("/entities/:id/html", async (req, res) => {
|
|
|
4594
4621
|
if (etag)
|
|
4595
4622
|
res.setHeader("ETag", etag);
|
|
4596
4623
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
4597
|
-
//
|
|
4598
|
-
//
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
// unauthenticated). Rendered pages are static CONTENT, never applications:
|
|
4602
|
-
// they never need to run script. Override the global policy for THIS response
|
|
4603
|
-
// with a strict, self-contained CSP that blocks all script execution (inline
|
|
4604
|
-
// and external) and sandboxes the document, neutralizing stored XSS regardless
|
|
4605
|
-
// of html_body contents. Inline <style> from the template is still allowed.
|
|
4606
|
-
res.setHeader("Content-Security-Policy", [
|
|
4607
|
-
"default-src 'none'",
|
|
4608
|
-
"script-src 'none'",
|
|
4609
|
-
"style-src 'unsafe-inline'",
|
|
4610
|
-
"img-src 'self' data:",
|
|
4611
|
-
"font-src 'self' data:",
|
|
4612
|
-
"base-uri 'none'",
|
|
4613
|
-
"form-action 'none'",
|
|
4614
|
-
"frame-ancestors 'self'",
|
|
4615
|
-
"sandbox allow-same-origin",
|
|
4616
|
-
].join("; "));
|
|
4624
|
+
// Author scripts stay blocked. Opted-in copy blocks permit only the fixed
|
|
4625
|
+
// renderer-owned helper, confined to an opaque-origin sandbox.
|
|
4626
|
+
const { renderedPageContentSecurityPolicy } = await import("./services/rendered_page/copy_controls.js");
|
|
4627
|
+
res.setHeader("Content-Security-Policy", renderedPageContentSecurityPolicy(htmlBody));
|
|
4617
4628
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
4618
4629
|
return res.send(html);
|
|
4619
4630
|
}
|
|
@@ -4671,6 +4682,10 @@ app.post("/entities/:id/batch_correct", async (req, res) => {
|
|
|
4671
4682
|
if (error instanceof Error && error.message.includes("Not authenticated")) {
|
|
4672
4683
|
return sendError(res, 401, "AUTH_REQUIRED", error.message);
|
|
4673
4684
|
}
|
|
4685
|
+
// Capability denial: same 403 `capability_denied` envelope as /correct.
|
|
4686
|
+
if (error instanceof AgentCapabilityError) {
|
|
4687
|
+
return handleApiError(req, res, error, "Failed to apply batch correction", "DB_QUERY_FAILED", "AgentCapabilityRejection:entity_batch_correct");
|
|
4688
|
+
}
|
|
4674
4689
|
logError("APIError:entity_batch_correct", req, error);
|
|
4675
4690
|
const message = error instanceof Error ? error.message : "Failed to apply batch correction";
|
|
4676
4691
|
return sendError(res, 500, "DB_QUERY_FAILED", message);
|
|
@@ -6097,6 +6112,10 @@ app.post("/interpretations/create", async (req, res) => {
|
|
|
6097
6112
|
}
|
|
6098
6113
|
try {
|
|
6099
6114
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
6115
|
+
// Same per-type `store` capability scope `/store` applies, before any
|
|
6116
|
+
// source lookup or write. No-op without an agent identity.
|
|
6117
|
+
const { assertInterpretationStoreCapability, runInterpretation } = await import("./services/interpretation.js");
|
|
6118
|
+
await assertInterpretationStoreCapability(userId, parsed.data.entities);
|
|
6100
6119
|
const { data: source, error: sourceError } = await db
|
|
6101
6120
|
.from("sources")
|
|
6102
6121
|
.select("id")
|
|
@@ -6108,7 +6127,6 @@ app.post("/interpretations/create", async (req, res) => {
|
|
|
6108
6127
|
if (!source) {
|
|
6109
6128
|
return sendError(res, 404, "SOURCE_NOT_FOUND", "Source not found for authenticated user");
|
|
6110
6129
|
}
|
|
6111
|
-
const { runInterpretation } = await import("./services/interpretation.js");
|
|
6112
6130
|
const interpretationResult = await runInterpretation({
|
|
6113
6131
|
userId,
|
|
6114
6132
|
sourceId: parsed.data.source_id,
|
|
@@ -6190,6 +6208,14 @@ app.post("/interpretations/create", async (req, res) => {
|
|
|
6190
6208
|
});
|
|
6191
6209
|
}
|
|
6192
6210
|
catch (error) {
|
|
6211
|
+
// Capability denial: same 403 `capability_denied` envelope as `/store`.
|
|
6212
|
+
if (error instanceof AgentCapabilityError) {
|
|
6213
|
+
return handleApiError(req, res, error, "Failed to create interpretation", "INTERPRETATION_CREATE_FAILED", "AgentCapabilityRejection:interpretations_create");
|
|
6214
|
+
}
|
|
6215
|
+
if (error instanceof InvalidEntityTypeError) {
|
|
6216
|
+
logWarn("InvalidEntityTypeError:interpretations_create", req, { code: error.code });
|
|
6217
|
+
return sendError(res, 400, error.code, error.message, { hint: error.hint });
|
|
6218
|
+
}
|
|
6193
6219
|
logError("APIError:interpretations_create", req, error);
|
|
6194
6220
|
const message = error instanceof Error ? error.message : "Failed to create interpretation";
|
|
6195
6221
|
return sendError(res, 500, "INTERPRETATION_CREATE_FAILED", message);
|
|
@@ -6302,6 +6328,9 @@ app.post("/observations/create", async (req, res) => {
|
|
|
6302
6328
|
.status(403)
|
|
6303
6329
|
.json(buildErrorEnvelope("FORBIDDEN", "user_id does not match authenticated user."));
|
|
6304
6330
|
}
|
|
6331
|
+
// Same per-type `store` capability scope `/store` applies, before the
|
|
6332
|
+
// entity is resolved or created. No-op without an agent identity.
|
|
6333
|
+
assertCurrentAgentMayStoreEntityTypes([entity_type]);
|
|
6305
6334
|
const { resolveEntity } = await import("./services/entity_resolution.js");
|
|
6306
6335
|
const { createObservation } = await import("./services/observation_storage.js");
|
|
6307
6336
|
const { getSnapshot, recomputeSnapshot } = await import("./services/snapshot_computation.js");
|
|
@@ -6333,6 +6362,10 @@ app.post("/observations/create", async (req, res) => {
|
|
|
6333
6362
|
});
|
|
6334
6363
|
}
|
|
6335
6364
|
catch (error) {
|
|
6365
|
+
// Capability denial: same 403 `capability_denied` envelope as `/store`.
|
|
6366
|
+
if (error instanceof AgentCapabilityError) {
|
|
6367
|
+
return handleApiError(req, res, error, "Failed to create observation", "DB_QUERY_FAILED", "AgentCapabilityRejection:observations_create");
|
|
6368
|
+
}
|
|
6336
6369
|
logError("APIError:observations_create", req, error);
|
|
6337
6370
|
const message = error instanceof Error ? error.message : "Failed to create observation";
|
|
6338
6371
|
return res.status(500).json(buildErrorEnvelope("DB_QUERY_FAILED", message));
|
|
@@ -7619,6 +7652,9 @@ export async function storeUnstructuredForApi(params) {
|
|
|
7619
7652
|
observations_created: 0,
|
|
7620
7653
|
};
|
|
7621
7654
|
}
|
|
7655
|
+
// Agent grants: an upload is recorded under its asset type (the type the
|
|
7656
|
+
// MCP leg writes for the same bytes). Checked before the source is stored.
|
|
7657
|
+
assertCurrentAgentMay("store", [assetEntityTypeForMimeType(mimeType)], { guestCapable: true });
|
|
7622
7658
|
if (storageMode === "reference") {
|
|
7623
7659
|
if (!filePath) {
|
|
7624
7660
|
throw new Error("filePath is required for reference storage mode");
|
|
@@ -7693,9 +7729,24 @@ async function handleStorePost(req, res) {
|
|
|
7693
7729
|
const { assertConditionalStoreRequest } = await import("./services/store_condition_request.js");
|
|
7694
7730
|
assertConditionalStoreRequest(parsed.data);
|
|
7695
7731
|
const hasEntities = Boolean(parsed.data.entities?.length);
|
|
7732
|
+
// Every entity must name its type as a non-empty string, for every caller
|
|
7733
|
+
// kind, before either leg of the request writes anything.
|
|
7734
|
+
if (hasEntities)
|
|
7735
|
+
assertStoreEntityTypesNamed(parsed.data.entities);
|
|
7696
7736
|
const hasFileContent = Boolean(parsed.data.file_content && parsed.data.mime_type);
|
|
7697
7737
|
const hasFilePath = Boolean(parsed.data.file_path);
|
|
7698
7738
|
const hasUnstructured = hasFileContent || hasFilePath;
|
|
7739
|
+
// Agent grants on the file leg, before either leg writes, so a combined
|
|
7740
|
+
// entities-and-file call refused on the file writes no entities either.
|
|
7741
|
+
if (hasUnstructured && parsed.data.commit !== false) {
|
|
7742
|
+
const uploadMime = parsed.data.mime_type ||
|
|
7743
|
+
(parsed.data.file_path
|
|
7744
|
+
? getMimeTypeFromExtension(path.extname(parsed.data.file_path).toLowerCase())
|
|
7745
|
+
: undefined);
|
|
7746
|
+
assertCurrentAgentMay("store", [assetEntityTypeForMimeType(uploadMime)], {
|
|
7747
|
+
guestCapable: true,
|
|
7748
|
+
});
|
|
7749
|
+
}
|
|
7699
7750
|
if (hasEntities && hasUnstructured && parsed.data.commit !== false) {
|
|
7700
7751
|
const { claimCombinedStoreKeys } = await import("./services/store_combined_admission.js");
|
|
7701
7752
|
await claimCombinedStoreKeys({
|
|
@@ -7863,6 +7914,10 @@ async function handleStorePost(req, res) {
|
|
|
7863
7914
|
logWarn("AgentGrantPinConflict:store", req, { code: error.code });
|
|
7864
7915
|
return sendError(res, 409, error.code, error.message, { field: error.field });
|
|
7865
7916
|
}
|
|
7917
|
+
if (error instanceof InvalidEntityTypeError) {
|
|
7918
|
+
logWarn("InvalidEntityTypeError:store", req, { code: error.code });
|
|
7919
|
+
return sendError(res, 400, error.code, error.message, { hint: error.hint });
|
|
7920
|
+
}
|
|
7866
7921
|
if (errCode === "ERR_FORBIDDEN_ENTITY_TYPE" || errCode === "ERR_PLURAL_ENTITY_TYPE") {
|
|
7867
7922
|
const message = error instanceof Error ? error.message : String(error);
|
|
7868
7923
|
logWarn("EntityTypeGuardError:store", req, { code: errCode, message });
|
|
@@ -8083,6 +8138,10 @@ app.post("/sync/webhook", express.raw({ type: "application/json", limit: "2mb" }
|
|
|
8083
8138
|
if (msg === "INVALID_JSON" || msg.startsWith("VALIDATION_ERROR")) {
|
|
8084
8139
|
return sendError(res, 400, "VALIDATION_ERROR", msg);
|
|
8085
8140
|
}
|
|
8141
|
+
if (msg.startsWith("REMOTE_ENTITY_TYPE_REFUSED")) {
|
|
8142
|
+
const { SYNC_ENTITY_TYPE_REFUSED_ERROR_CODE } = await import("./services/sync/remote_snapshot_admission.js");
|
|
8143
|
+
return sendError(res, 403, SYNC_ENTITY_TYPE_REFUSED_ERROR_CODE, msg);
|
|
8144
|
+
}
|
|
8086
8145
|
logError("APIError:sync_webhook", req, err);
|
|
8087
8146
|
return sendError(res, 500, "SYNC_WEBHOOK_FAILED", msg);
|
|
8088
8147
|
}
|
|
@@ -8159,6 +8218,9 @@ app.post("/peers", express.json(), async (req, res) => {
|
|
|
8159
8218
|
return res.status(201).json(out);
|
|
8160
8219
|
}
|
|
8161
8220
|
catch (error) {
|
|
8221
|
+
if (error instanceof AgentCapabilityError) {
|
|
8222
|
+
return handleApiError(req, res, error, "Failed to add peer");
|
|
8223
|
+
}
|
|
8162
8224
|
logError("APIError:peers_add", req, error);
|
|
8163
8225
|
const message = error instanceof Error ? error.message : "Failed to add peer";
|
|
8164
8226
|
if (message.includes("Maximum peers")) {
|
|
@@ -8167,8 +8229,11 @@ app.post("/peers", express.json(), async (req, res) => {
|
|
|
8167
8229
|
return sendError(res, 500, "PEER_ADD_FAILED", message);
|
|
8168
8230
|
}
|
|
8169
8231
|
});
|
|
8170
|
-
// POST /peers/resolve_sync_conflict —
|
|
8171
|
-
//
|
|
8232
|
+
// POST /peers/resolve_sync_conflict — resolve a replication conflict. `prefer_remote`
|
|
8233
|
+
// and `manual` write correction-tier values, so they run the same `correct` capability
|
|
8234
|
+
// gate as `/correct`; `prefer_remote` applies the differing fields as one correction
|
|
8235
|
+
// transaction and reports applied and withheld fields. Registered before
|
|
8236
|
+
// `/peers/:peer_id` so `resolve_sync_conflict` is not captured as a peer_id.
|
|
8172
8237
|
app.post("/peers/resolve_sync_conflict", express.json(), async (req, res) => {
|
|
8173
8238
|
const schema = z.object({
|
|
8174
8239
|
entity_id: z.string().min(1),
|
|
@@ -8199,6 +8264,14 @@ app.post("/peers/resolve_sync_conflict", express.json(), async (req, res) => {
|
|
|
8199
8264
|
return res.json(result);
|
|
8200
8265
|
}
|
|
8201
8266
|
catch (error) {
|
|
8267
|
+
// A capability or protected-type refusal is the caller's authorization,
|
|
8268
|
+
// not a server fault: 403 with the envelope `/correct` returns.
|
|
8269
|
+
if (error instanceof AgentCapabilityError) {
|
|
8270
|
+
logWarn("AgentCapabilityRejection:peers_resolve", req, error.toErrorEnvelope());
|
|
8271
|
+
return res
|
|
8272
|
+
.status(403)
|
|
8273
|
+
.json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelope()));
|
|
8274
|
+
}
|
|
8202
8275
|
logError("APIError:peers_resolve", req, error);
|
|
8203
8276
|
const message = error instanceof Error ? error.message : "resolve failed";
|
|
8204
8277
|
return sendError(res, 500, "PEER_RESOLVE_FAILED", message);
|
|
@@ -8225,11 +8298,16 @@ app.get("/peers/:peer_id", async (req, res) => {
|
|
|
8225
8298
|
app.delete("/peers/:peer_id", async (req, res) => {
|
|
8226
8299
|
try {
|
|
8227
8300
|
const userId = await getAuthenticatedUserId(req);
|
|
8301
|
+
// Agent grants: removing a peer corrects its peer_config.
|
|
8302
|
+
assertCurrentAgentMay("correct", ["peer_config"]);
|
|
8228
8303
|
const { removePeerForUser } = await import("./services/sync/peer_ops.js");
|
|
8229
8304
|
await removePeerForUser({ userId, peer_id: req.params.peer_id });
|
|
8230
8305
|
return res.json({ success: true });
|
|
8231
8306
|
}
|
|
8232
8307
|
catch (error) {
|
|
8308
|
+
if (error instanceof AgentCapabilityError) {
|
|
8309
|
+
return handleApiError(req, res, error, "Failed to remove peer");
|
|
8310
|
+
}
|
|
8233
8311
|
logError("APIError:peers_remove", req, error);
|
|
8234
8312
|
const message = error instanceof Error ? error.message : "Failed to remove peer";
|
|
8235
8313
|
if (message.includes("not found")) {
|
|
@@ -8245,11 +8323,17 @@ app.post("/peers/:peer_id/sync", express.json(), async (req, res) => {
|
|
|
8245
8323
|
const bodySchema = z.object({ limit: z.number().int().positive().max(500).optional() });
|
|
8246
8324
|
const bodyParsed = bodySchema.safeParse(req.body ?? {});
|
|
8247
8325
|
const limit = bodyParsed.success ? bodyParsed.data.limit : undefined;
|
|
8326
|
+
// Agent grants: a sync run sends local data to the peer and advances the
|
|
8327
|
+
// peer_config watermark; pulled rows are still checked per type by store.
|
|
8328
|
+
assertCurrentAgentMay("correct", ["peer_config"]);
|
|
8248
8329
|
const { syncPeerFull } = await import("./services/sync/full_sync.js");
|
|
8249
8330
|
const result = await syncPeerFull({ userId, peer_id: req.params.peer_id, limit });
|
|
8250
8331
|
return res.json(result);
|
|
8251
8332
|
}
|
|
8252
8333
|
catch (error) {
|
|
8334
|
+
if (error instanceof AgentCapabilityError) {
|
|
8335
|
+
return handleApiError(req, res, error, "sync_peer failed");
|
|
8336
|
+
}
|
|
8253
8337
|
logError("APIError:peers_sync", req, error);
|
|
8254
8338
|
const message = error instanceof Error ? error.message : "sync_peer failed";
|
|
8255
8339
|
return sendError(res, 500, "PEER_SYNC_FAILED", message);
|
|
@@ -8350,6 +8434,8 @@ app.post("/entities/merge", async (req, res) => {
|
|
|
8350
8434
|
if (providedUserId && providedUserId !== authenticatedUserId) {
|
|
8351
8435
|
return sendError(res, 403, "FORBIDDEN", "user_id does not match authenticated user.");
|
|
8352
8436
|
}
|
|
8437
|
+
// Agent grants: `merge` on both entities' stored types, before any write.
|
|
8438
|
+
await assertCurrentAgentMayOnEntities("merge", user_id, [from_entity_id, to_entity_id]);
|
|
8353
8439
|
const { mergeEntities } = await import("./services/entity_merge.js");
|
|
8354
8440
|
const result = await mergeEntities({
|
|
8355
8441
|
fromEntityId: from_entity_id,
|
|
@@ -8361,6 +8447,9 @@ app.post("/entities/merge", async (req, res) => {
|
|
|
8361
8447
|
return res.json(result);
|
|
8362
8448
|
}
|
|
8363
8449
|
catch (error) {
|
|
8450
|
+
if (error instanceof AgentCapabilityError) {
|
|
8451
|
+
return handleApiError(req, res, error, "Failed to merge entities");
|
|
8452
|
+
}
|
|
8364
8453
|
const { EntityNotFoundError, EntityAlreadyMergedError } = await import("./services/entity_merge.js");
|
|
8365
8454
|
if (error instanceof EntityNotFoundError) {
|
|
8366
8455
|
return sendError(res, 404, "RESOURCE_NOT_FOUND", error.message);
|
|
@@ -8390,6 +8479,9 @@ app.post("/entities/split", async (req, res) => {
|
|
|
8390
8479
|
return sendError(res, 403, "FORBIDDEN", "user_id does not match authenticated user.");
|
|
8391
8480
|
}
|
|
8392
8481
|
const userId = providedUserId || authenticatedUserId;
|
|
8482
|
+
// Agent grants: `split` on the source's stored type, the new entity's
|
|
8483
|
+
// type and, when the split targets an existing entity, its stored type.
|
|
8484
|
+
await assertCurrentAgentMayOnEntities("split", userId, [parsed.data.source_entity_id, parsed.data.new_entity.target_entity_id ?? ""], [parsed.data.new_entity.entity_type]);
|
|
8393
8485
|
const { splitEntity } = await import("./services/entity_split.js");
|
|
8394
8486
|
const result = await splitEntity({
|
|
8395
8487
|
sourceEntityId: parsed.data.source_entity_id,
|
|
@@ -8407,6 +8499,9 @@ app.post("/entities/split", async (req, res) => {
|
|
|
8407
8499
|
return res.json(result);
|
|
8408
8500
|
}
|
|
8409
8501
|
catch (error) {
|
|
8502
|
+
if (error instanceof AgentCapabilityError) {
|
|
8503
|
+
return handleApiError(req, res, error, "Failed to split entity");
|
|
8504
|
+
}
|
|
8410
8505
|
const { EntityNotFoundError, EntityAlreadyMergedError, SplitPredicateMatchedNothingError, SplitPredicateMatchedAllError, IdempotencyMismatchError, } = await import("./services/entity_split.js");
|
|
8411
8506
|
if (error instanceof EntityNotFoundError) {
|
|
8412
8507
|
return sendError(res, 404, "RESOURCE_NOT_FOUND", error.message);
|
|
@@ -9177,6 +9272,9 @@ app.post("/delete_entity", async (req, res) => {
|
|
|
9177
9272
|
try {
|
|
9178
9273
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9179
9274
|
const { entity_id, entity_type, reason } = parsed.data;
|
|
9275
|
+
// Agent grants: `delete` on the stored type (and the type the request
|
|
9276
|
+
// names), before the deletion observation is written.
|
|
9277
|
+
await assertCurrentAgentMayOnEntities("delete", userId, [entity_id], [entity_type]);
|
|
9180
9278
|
const { softDeleteEntity } = await import("./services/deletion.js");
|
|
9181
9279
|
const result = await softDeleteEntity(entity_id, entity_type, userId, reason);
|
|
9182
9280
|
if (!result.success) {
|
|
@@ -9199,6 +9297,8 @@ app.post("/issues/bulk_close", async (req, res) => {
|
|
|
9199
9297
|
}
|
|
9200
9298
|
try {
|
|
9201
9299
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9300
|
+
// Agent grants: closing corrects the issue (and its GitHub state).
|
|
9301
|
+
await assertCurrentAgentMayOnEntities("correct", userId, parsed.data.entity_ids, ["issue"]);
|
|
9202
9302
|
const { bulkCloseIssues } = await import("./services/issues/inspector_bulk.js");
|
|
9203
9303
|
const { results } = await bulkCloseIssues(userId, parsed.data.entity_ids);
|
|
9204
9304
|
logDebug("Success:issues_bulk_close", req, { count: results.length });
|
|
@@ -9217,6 +9317,8 @@ app.post("/issues/bulk_remove", async (req, res) => {
|
|
|
9217
9317
|
}
|
|
9218
9318
|
try {
|
|
9219
9319
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9320
|
+
// Agent grants: removal closes and soft-deletes the issue.
|
|
9321
|
+
await assertCurrentAgentMayOnEntities("delete", userId, parsed.data.entity_ids, ["issue"]);
|
|
9220
9322
|
const { bulkRemoveIssues } = await import("./services/issues/inspector_bulk.js");
|
|
9221
9323
|
const { results } = await bulkRemoveIssues(userId, parsed.data.entity_ids);
|
|
9222
9324
|
logDebug("Success:issues_bulk_remove", req, { count: results.length });
|
|
@@ -9239,6 +9341,15 @@ const handleIssuesAddMessageHttp = async (req, res) => {
|
|
|
9239
9341
|
const userId = principal.kind === "guest"
|
|
9240
9342
|
? (await resolveGuestScopedEntityAccess(principal, parsed.data.entity_id?.trim() || "", "issue")).userId
|
|
9241
9343
|
: await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9344
|
+
// Agent grants on the owner path: a thread message is an issue-thread
|
|
9345
|
+
// write (and a GitHub comment). The in-process operations layer below does
|
|
9346
|
+
// not carry the caller's grant, so the check runs here, before anything is
|
|
9347
|
+
// written. The guest path stays scoped by its guest access token.
|
|
9348
|
+
if (principal.kind !== "guest") {
|
|
9349
|
+
assertCurrentAgentMay("store", ISSUE_THREAD_ENTITY_TYPES, { guestCapable: true });
|
|
9350
|
+
// Edges from the issue to entities the caller names.
|
|
9351
|
+
await assertCurrentAgentMayLink(userId, "issue", "REFERS_TO", parsed.data.entity_ids_to_link ?? [], { guestCapable: true });
|
|
9352
|
+
}
|
|
9242
9353
|
const { createOperations } = await import("./core/operations.js");
|
|
9243
9354
|
const { NeotomaServer } = await import("./server.js");
|
|
9244
9355
|
const { addIssueMessage, appendGuestIssueMessage } = await import("./services/issues/issue_operations.js");
|
|
@@ -9311,6 +9422,16 @@ const handleIssuesSubmitHttp = async (req, res) => {
|
|
|
9311
9422
|
? ((await resolveGuestUserId(req, principal)) ??
|
|
9312
9423
|
(await getAuthenticatedUserId(req, parsed.data.user_id)))
|
|
9313
9424
|
: await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9425
|
+
// Agent grants on the owner path: it writes an issue thread under the
|
|
9426
|
+
// owner and may open a GitHub issue. The in-process operations layer below
|
|
9427
|
+
// does not carry the caller's grant, so the check runs here, before
|
|
9428
|
+
// anything is written. The guest path stays under access policy, as for
|
|
9429
|
+
// any guest.
|
|
9430
|
+
if (!(principal.kind === "guest" || guestSubmitPayload)) {
|
|
9431
|
+
assertCurrentAgentMay("store", ISSUE_THREAD_ENTITY_TYPES, { guestCapable: true });
|
|
9432
|
+
// Edges from the new issue to entities the caller names.
|
|
9433
|
+
await assertCurrentAgentMayLink(userId, "issue", "REFERS_TO", [...(parsed.data.entity_ids_to_link ?? []), parsed.data.conversation_turn_id ?? ""], { guestCapable: true });
|
|
9434
|
+
}
|
|
9314
9435
|
const { createOperations } = await import("./core/operations.js");
|
|
9315
9436
|
const { NeotomaServer } = await import("./server.js");
|
|
9316
9437
|
const { submitIssue, submitGuestIssue } = await import("./services/issues/issue_operations.js");
|
|
@@ -9434,6 +9555,10 @@ const handleIssuesSyncHttp = async (req, res) => {
|
|
|
9434
9555
|
}
|
|
9435
9556
|
try {
|
|
9436
9557
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9558
|
+
// Agent grants: a sync stores new issue threads, corrects existing ones
|
|
9559
|
+
// and can push local issues to GitHub.
|
|
9560
|
+
assertCurrentAgentMay("store", ISSUE_THREAD_ENTITY_TYPES);
|
|
9561
|
+
assertCurrentAgentMay("correct", ISSUE_THREAD_ENTITY_TYPES);
|
|
9437
9562
|
const { createOperations } = await import("./core/operations.js");
|
|
9438
9563
|
const { NeotomaServer } = await import("./server.js");
|
|
9439
9564
|
const { syncIssuesFromGitHub } = await import("./services/issues/sync_issues_from_github.js");
|
|
@@ -9480,6 +9605,9 @@ app.post("/restore_entity", async (req, res) => {
|
|
|
9480
9605
|
try {
|
|
9481
9606
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9482
9607
|
const { entity_id, entity_type, reason } = parsed.data;
|
|
9608
|
+
// Agent grants: `restore` on the stored type (and the type the request
|
|
9609
|
+
// names), before the restoration observation is written.
|
|
9610
|
+
await assertCurrentAgentMayOnEntities("restore", userId, [entity_id], [entity_type]);
|
|
9483
9611
|
const { restoreEntity } = await import("./services/deletion.js");
|
|
9484
9612
|
const result = await restoreEntity(entity_id, entity_type, userId, reason);
|
|
9485
9613
|
if (!result.success) {
|
|
@@ -9750,6 +9878,8 @@ app.post("/update_schema_incremental", async (req, res) => {
|
|
|
9750
9878
|
// can defer to whichever scope its own read resolved instead of being
|
|
9751
9879
|
// told "global" by a default this route manufactured.
|
|
9752
9880
|
user_specific, activate = true, migrate_existing = false, force = false, } = parsed.data;
|
|
9881
|
+
// Agent grants: `register_schema` on the entity type, before any write.
|
|
9882
|
+
assertCurrentAgentMay("register_schema", [entity_type]);
|
|
9753
9883
|
const { schemaRegistry, loadCodeDefinedSchemaEntry } = await import("./services/schema_registry.js");
|
|
9754
9884
|
// #2454 / #2374 REST parity with the MCP handler: the existence guard
|
|
9755
9885
|
// resolves the SAME row `updateSchemaIncremental` will read —
|
|
@@ -10032,6 +10162,8 @@ app.post("/register_schema", async (req, res) => {
|
|
|
10032
10162
|
try {
|
|
10033
10163
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
10034
10164
|
const { entity_type, schema_definition, reducer_config, schema_version = "1.0", user_specific = false, activate = false, force = false, } = parsed.data;
|
|
10165
|
+
// Agent grants: `register_schema` on the entity type, before any write.
|
|
10166
|
+
assertCurrentAgentMay("register_schema", [entity_type]);
|
|
10035
10167
|
const { schemaRegistry } = await import("./services/schema_registry.js");
|
|
10036
10168
|
// R2 back-compat: existing HTTP/CLI callers may register schemas
|
|
10037
10169
|
// without declaring canonical_name_fields or identity_opt_out (e.g. the
|
|
@@ -10582,6 +10714,8 @@ app.post("/unsubscribe", guestWriteRateLimit, async (req, res) => {
|
|
|
10582
10714
|
return sendError(res, 403, "FORBIDDEN", "Guest access token does not grant access to this subscription");
|
|
10583
10715
|
}
|
|
10584
10716
|
}
|
|
10717
|
+
// Agent grants: unsubscribing corrects the subscription (active=false).
|
|
10718
|
+
assertCurrentAgentMay("correct", ["subscription"], { guestCapable: true });
|
|
10585
10719
|
await unsubscribeUser({ userId, subscription_id: parsed.data.subscription_id });
|
|
10586
10720
|
return res.json({ success: true });
|
|
10587
10721
|
}
|
|
@@ -11279,6 +11413,10 @@ export async function startHTTPServer() {
|
|
|
11279
11413
|
}
|
|
11280
11414
|
throw err;
|
|
11281
11415
|
}
|
|
11416
|
+
// Report the bearer handover slot (NEOTOMA_BEARER_TOKEN_PREVIOUS) at boot,
|
|
11417
|
+
// so a misconfigured slot is loud at startup rather than at the first
|
|
11418
|
+
// consumer's 401. Fail-closed parsing lives in services/bearer_slots.ts.
|
|
11419
|
+
announceBearerHandoverStatus();
|
|
11282
11420
|
// Initialize encryption service
|
|
11283
11421
|
await initServerKeys();
|
|
11284
11422
|
// Seed the built-in schema registry from ENTITY_SCHEMAS (issue #1968).
|