neotoma 0.26.0 → 0.28.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 +5 -5
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +256 -38
- package/dist/actions.js.map +1 -1
- package/dist/cli/harness_configure.d.ts +2 -1
- package/dist/cli/harness_configure.d.ts.map +1 -1
- package/dist/cli/harness_configure.js.map +1 -1
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +53 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp_config_scan.d.ts +29 -0
- package/dist/cli/mcp_config_scan.d.ts.map +1 -1
- package/dist/cli/mcp_config_scan.js +35 -6
- package/dist/cli/mcp_config_scan.js.map +1 -1
- package/dist/cli/setup.d.ts +24 -3
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +65 -3
- package/dist/cli/setup.js.map +1 -1
- package/dist/docs/architecture/architecture.md +1 -1
- package/dist/docs/conventions/documentation_standards.md +1 -1
- package/dist/docs/developer/atomic_corrections.md +2 -2
- package/dist/docs/developer/cli_agent_instructions.md +14 -3
- package/dist/docs/developer/cli_reference.md +13 -2
- package/dist/docs/developer/mcp/instructions.md +15 -4
- 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/security/advisories/2026-11-04-peer-sync-and-agent-grant-scope.md +41 -0
- package/dist/docs/security/advisories/README.md +1 -0
- 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 +97 -24
- package/dist/inspector/assets/{access_policies-CJOlV60d.js → access_policies-CecrDHE5.js} +1 -1
- package/dist/inspector/assets/{agent_badge-BnBqn8kt.js → agent_badge-bFKcTLc3.js} +1 -1
- package/dist/inspector/assets/{agent_detail-wP0ccjhX.js → agent_detail-ByxqvVXp.js} +1 -1
- package/dist/inspector/assets/{agent_filter-Bp2xA131.js → agent_filter-CIlnJqXp.js} +1 -1
- package/dist/inspector/assets/{agent_grant_detail-HIGr5dq_.js → agent_grant_detail-BS9vi68s.js} +1 -1
- package/dist/inspector/assets/agent_grant_form-CUNvJrPS.js +1 -0
- package/dist/inspector/assets/{agent_grants-CTKkadE5.js → agent_grants-DKd7ld_4.js} +1 -1
- package/dist/inspector/assets/{agents-CPwJLNJN.js → agents-CE6YYOPn.js} +1 -1
- package/dist/inspector/assets/{api_not_configured_state-ivqIcs2c.js → api_not_configured_state-BdXbCQAz.js} +1 -1
- package/dist/inspector/assets/{attribution_card-5ZAqRz6K.js → attribution_card-Df6GMziR.js} +1 -1
- package/dist/inspector/assets/{attribution_summary-D_MHFv7e.js → attribution_summary-B2T-E3h9.js} +1 -1
- package/dist/inspector/assets/{bundles-QfMciBj9.js → bundles-DPrW5kwK.js} +1 -1
- package/dist/inspector/assets/{card-1rkzdi9W.js → card-xB0wTMgy.js} +1 -1
- package/dist/inspector/assets/{checkbox-CO5dXSZf.js → checkbox-C6aOTIf3.js} +1 -1
- package/dist/inspector/assets/{compliance-OTGEtY7Y.js → compliance-CKOQJGVG.js} +1 -1
- package/dist/inspector/assets/{confirm-dialog-D0cZ5vck.js → confirm-dialog-CrSwx0Db.js} +1 -1
- package/dist/inspector/assets/{conversation_common-cSILaHXq.js → conversation_common-C5W4gEXW.js} +1 -1
- package/dist/inspector/assets/{conversation_detail-DT-lGapN.js → conversation_detail-Uz4wDgPf.js} +1 -1
- package/dist/inspector/assets/{copy_id_button-Dz0JDn4l.js → copy_id_button-CDMxVOzU.js} +1 -1
- package/dist/inspector/assets/{copyable_code_block-B2f99g8Q.js → copyable_code_block-CPl0aWFe.js} +1 -1
- package/dist/inspector/assets/{corrections-KvxGvvLV.js → corrections-Dwj1Fugu.js} +1 -1
- package/dist/inspector/assets/{dashboard-BudvW21X.js → dashboard-BBCSRZNU.js} +1 -1
- package/dist/inspector/assets/{data-table-d9EMxowf.js → data-table-aUtH1nlD.js} +1 -1
- package/dist/inspector/assets/{design-DyiQbEeP.js → design-D0gJ9iB8.js} +1 -1
- package/dist/inspector/assets/{dialog-BmW1Iw8o.js → dialog-CMWtV0zN.js} +1 -1
- package/dist/inspector/assets/{docs-CT3IYX0E.js → docs-Byar2vOe.js} +1 -1
- package/dist/inspector/assets/{docs_page_layout-CosNjUZa.js → docs_page_layout-CP64UQbJ.js} +1 -1
- package/dist/inspector/assets/{embed_graph-Dk1-zVnz.js → embed_graph-CnaGuKds.js} +1 -1
- package/dist/inspector/assets/{empty_state-B8Ot2oGB.js → empty_state-BzT7NRtZ.js} +1 -1
- package/dist/inspector/assets/{entities-Dt5Ts5UN.js → entities-fkG18fss.js} +1 -1
- package/dist/inspector/assets/{entity_correct-D2PHEpGY.js → entity_correct-mnPBHieg.js} +1 -1
- package/dist/inspector/assets/{entity_history-DHgNythA.js → entity_history-Th7OnpfA.js} +1 -1
- package/dist/inspector/assets/{entity_history_sections-Cnxblw-Z.js → entity_history_sections-DwEU4Y5F.js} +1 -1
- package/dist/inspector/assets/{entity_link-DWf1VD1n.js → entity_link-U1JSr9H2.js} +1 -1
- package/dist/inspector/assets/{entity_relationships_by_type-B0-TLC5o.js → entity_relationships_by_type-B6ZTLPGk.js} +1 -1
- package/dist/inspector/assets/{entity_segment_page-CzZ0VhKm.js → entity_segment_page-N7j8YJxA.js} +1 -1
- package/dist/inspector/assets/{entity_table_column_toggle-C73aErGE.js → entity_table_column_toggle-DfKBCgU2.js} +1 -1
- package/dist/inspector/assets/{entity_timeline-SAq5qvB5.js → entity_timeline-B7sMnfd3.js} +1 -1
- package/dist/inspector/assets/{entity_types-DEcDvyO0.js → entity_types-D5la65Sn.js} +1 -1
- package/dist/inspector/assets/{faq-DazWIFdv.js → faq-C-BJiIWQ.js} +1 -1
- package/dist/inspector/assets/{graph_auto_fit-CSzD7yNh.js → graph_auto_fit-Cl1sM7g6.js} +1 -1
- package/dist/inspector/assets/{graph_explorer-BEXXiMQ1.js → graph_explorer-BF8NyJLR.js} +1 -1
- package/dist/inspector/assets/home-Czrx0JG4.js +1 -0
- package/dist/inspector/assets/{index-fc4sMprn.js → index-BcN-bmX4.js} +2 -2
- package/dist/inspector/assets/{index-Dt_q_A1j.js → index-Bf45K-hH.js} +1 -1
- package/dist/inspector/assets/{index-Cii0RALL.js → index-DnnkhCRn.js} +1 -1
- package/dist/inspector/assets/{interpretations-DtzoiGuK.js → interpretations-BON8Hv6B.js} +1 -1
- package/dist/inspector/assets/interpretations-D1AiR24H.js +1 -0
- package/dist/inspector/assets/{issue_author_attribution-DtmFaluD.js → issue_author_attribution-BiIemiwE.js} +1 -1
- package/dist/inspector/assets/{issue_detail-ChHiz9I_.js → issue_detail-DKwqgprb.js} +1 -1
- package/dist/inspector/assets/{issues-BoxR2nB2.js → issues-DN0qXAD9.js} +1 -1
- package/dist/inspector/assets/{json_viewer-KAFp_j-8.js → json_viewer-o1fpEDum.js} +1 -1
- package/dist/inspector/assets/{label-Dn0ARjYl.js → label-6dQuJkiv.js} +1 -1
- package/dist/inspector/assets/{live_relative_time-gOIFOOLR.js → live_relative_time-DRapJJqL.js} +1 -1
- package/dist/inspector/assets/{mobile_filter_popover-2ezU-mlZ.js → mobile_filter_popover-CG_CrM1J.js} +1 -1
- package/dist/inspector/assets/{not_found-DwX7ikQC.js → not_found-ZFJp_piF.js} +1 -1
- package/dist/inspector/assets/{oauth_callback-BDGLOyTN.js → oauth_callback-0LtMaQug.js} +1 -1
- package/dist/inspector/assets/{oauth_signin-CGcCtq-V.js → oauth_signin-Dojz-oXP.js} +1 -1
- package/dist/inspector/assets/{observations-DvcUGpHm.js → observations-L18GAXwN.js} +1 -1
- package/dist/inspector/assets/{page_shell-DiwchdUC.js → page_shell-DGmu9DSu.js} +1 -1
- package/dist/inspector/assets/{pagination-BGIUHQkm.js → pagination-D06zx3An.js} +1 -1
- package/dist/inspector/assets/{peer_detail-DeD1sTeJ.js → peer_detail-ChA0ildZ.js} +1 -1
- package/dist/inspector/assets/{peers-CXe47m55.js → peers-1DpY9nuL.js} +1 -1
- package/dist/inspector/assets/{pin_primitive_button-C37Ha0Bt.js → pin_primitive_button-C69_BXo8.js} +1 -1
- package/dist/inspector/assets/{recent_activity-CzQjt4ym.js → recent_activity-_tzwJHSQ.js} +1 -1
- package/dist/inspector/assets/{recent_conversations-BnUl4h4t.js → recent_conversations-DIuO8UEC.js} +1 -1
- package/dist/inspector/assets/{recent_conversations-BFYZTxUJ.js → recent_conversations-DyySBTfu.js} +1 -1
- package/dist/inspector/assets/{recent_records_feed-y1n0RuMa.js → recent_records_feed-C3kqxaSx.js} +1 -1
- package/dist/inspector/assets/record_activity-D6T1ehSh.js +1 -0
- package/dist/inspector/assets/{relationship_detail-ic83ZCwG.js → relationship_detail-DB9gZgOc.js} +1 -1
- package/dist/inspector/assets/{relationship_panel_groups-CI1fZIU_.js → relationship_panel_groups-DDcasdh0.js} +1 -1
- package/dist/inspector/assets/{relationships-C7iSdgXG.js → relationships-DE8zWSeh.js} +1 -1
- package/dist/inspector/assets/{relationships-C1JrEl85.js → relationships-r726Am6k.js} +1 -1
- package/dist/inspector/assets/{sandbox-Dst-WJhB.js → sandbox-CK8Xh0zh.js} +1 -1
- package/dist/inspector/assets/{schema_detail-C7_JSWAw.js → schema_detail-tV9qZlDI.js} +1 -1
- package/dist/inspector/assets/{schemas-BgrIpRVJ.js → schemas-r5vde1ev.js} +1 -1
- package/dist/inspector/assets/{search-BUmT32dy.js → search-CYsHQGOl.js} +1 -1
- package/dist/inspector/assets/{segmented_control-t1pBImFE.js → segmented_control-Dh7hbz4k.js} +1 -1
- package/dist/inspector/assets/{select-DU7Jwzuy.js → select-BKEI7cq9.js} +1 -1
- package/dist/inspector/assets/{settings-BAPHY6RY.js → settings-CGFwUgc3.js} +1 -1
- package/dist/inspector/assets/{source_content-CIhKrGsl.js → source_content-BVWgibsY.js} +1 -1
- package/dist/inspector/assets/{source_content_open_button-Erw2ErgG.js → source_content_open_button-CILzjRlO.js} +1 -1
- package/dist/inspector/assets/{source_detail-C0XOo0Uu.js → source_detail-C1w4In3r.js} +1 -1
- package/dist/inspector/assets/{source_link-CkcRMEO3.js → source_link-DlxqKShY.js} +1 -1
- package/dist/inspector/assets/{sources-C0JBEINb.js → sources-C3c88YnL.js} +1 -1
- package/dist/inspector/assets/{subscriptions-DxU4kHGt.js → subscriptions-BJI8nfBi.js} +1 -1
- package/dist/inspector/assets/{switch-BhKadp29.js → switch-Dx7yDS8J.js} +1 -1
- package/dist/inspector/assets/{tabs-d81XObYb.js → tabs-C7CNmtlx.js} +1 -1
- package/dist/inspector/assets/{textarea-DTiKLH24.js → textarea-CnKEdeS7.js} +1 -1
- package/dist/inspector/assets/{timeline-BEyKQpER.js → timeline-DFvizOXt.js} +1 -1
- package/dist/inspector/assets/{timeline-98QXcGln.js → timeline-TBibq40-.js} +1 -1
- package/dist/inspector/assets/{timeline_event_detail-DzGzqtrx.js → timeline_event_detail-DXpLKXwn.js} +1 -1
- package/dist/inspector/assets/{turn_detail-CDP_FEPT.js → turn_detail-DxoBBnAV.js} +1 -1
- package/dist/inspector/assets/{turns-DLmr1Iue.js → turns-DYRndDTk.js} +1 -1
- package/dist/inspector/assets/{use_agents-D3rvlr5v.js → use_agents-EhoEA4-8.js} +1 -1
- package/dist/inspector/assets/{use_entities-CVikeUOv.js → use_entities-DzF_NpWe.js} +1 -1
- package/dist/inspector/assets/use_interpretations-CmKAxtHd.js +1 -0
- package/dist/inspector/assets/{use_mutations-BhwSLXcJ.js → use_mutations-Da9jgbQb.js} +1 -1
- package/dist/inspector/assets/{use_peers-QTm1Z_BV.js → use_peers-C1-jKVUX.js} +1 -1
- package/dist/inspector/assets/{use_relationships-DtymxAI9.js → use_relationships-ClEy1YjY.js} +1 -1
- package/dist/inspector/assets/{use_stats-BW1Z-dI2.js → use_stats-jYkK0Ydj.js} +1 -1
- package/dist/inspector/assets/{use_timeline-DND87Sof.js → use_timeline-CNpY1sVJ.js} +1 -1
- package/dist/inspector/assets/{use_turns-Dfb6Q8Ru.js → use_turns-aYgpXeJo.js} +1 -1
- package/dist/inspector/assets/{world_time_event_timeline-DxzejNrJ.js → world_time_event_timeline-4k2EdpBk.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 +13 -1
- package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
- package/dist/repositories/sqlite/sqlite_client.js +56 -0
- package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
- package/dist/repositories/worker/worker_file_database.d.ts +16 -1
- package/dist/repositories/worker/worker_file_database.d.ts.map +1 -1
- package/dist/repositories/worker/worker_file_database.js +280 -29
- package/dist/repositories/worker/worker_file_database.js.map +1 -1
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +185 -25
- 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/bounded_identity_read.d.ts +26 -0
- package/dist/services/bounded_identity_read.d.ts.map +1 -0
- package/dist/services/bounded_identity_read.js +154 -0
- package/dist/services/bounded_identity_read.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/correction_transaction.d.ts.map +1 -1
- package/dist/services/correction_transaction.js +9 -3
- package/dist/services/correction_transaction.js.map +1 -1
- package/dist/services/current_state_reads.d.ts +56 -0
- package/dist/services/current_state_reads.d.ts.map +1 -0
- package/dist/services/current_state_reads.js +337 -0
- package/dist/services/current_state_reads.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 +74 -0
- package/dist/services/entity_population_certificate.js.map +1 -0
- package/dist/services/entity_queries.d.ts +0 -5
- package/dist/services/entity_queries.d.ts.map +1 -1
- package/dist/services/entity_queries.js +296 -275
- 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/mcp_lookup_timing.d.ts +25 -0
- package/dist/services/mcp_lookup_timing.d.ts.map +1 -0
- package/dist/services/mcp_lookup_timing.js +156 -0
- package/dist/services/mcp_lookup_timing.js.map +1 -0
- 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/request_context.d.ts +3 -0
- package/dist/services/request_context.d.ts.map +1 -1
- package/dist/services/request_context.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/schema_registry.d.ts +29 -11
- package/dist/services/schema_registry.d.ts.map +1 -1
- package/dist/services/schema_registry.js +193 -58
- package/dist/services/schema_registry.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_handlers/exact_identity_read.d.ts +8 -0
- package/dist/shared/action_handlers/exact_identity_read.d.ts.map +1 -0
- package/dist/shared/action_handlers/exact_identity_read.js +67 -0
- package/dist/shared/action_handlers/exact_identity_read.js.map +1 -0
- package/dist/shared/action_schemas.d.ts +31 -12
- package/dist/shared/action_schemas.d.ts.map +1 -1
- package/dist/shared/action_schemas.js +22 -5
- package/dist/shared/action_schemas.js.map +1 -1
- package/dist/shared/contract_mappings.d.ts.map +1 -1
- package/dist/shared/contract_mappings.js +1 -0
- package/dist/shared/contract_mappings.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 +433 -12
- package/dist/shared/openapi_types.d.ts.map +1 -1
- package/dist/shared/schema_planning.d.ts +24 -0
- package/dist/shared/schema_planning.d.ts.map +1 -0
- package/dist/shared/schema_planning.js +22 -0
- package/dist/shared/schema_planning.js.map +1 -0
- package/dist/tool_definitions.d.ts +6 -0
- package/dist/tool_definitions.d.ts.map +1 -1
- package/dist/tool_definitions.js +27 -34
- package/dist/tool_definitions.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/developer/mcp/instructions.md +15 -4
- package/openapi.yaml +412 -11
- package/package.json +7 -7
- package/skills/continue-session/SKILL.md +547 -0
- package/skills/end/SKILL.md +3 -1
- package/skills/status/SKILL.md +302 -80
- package/skills/where/SKILL.md +36 -30
- package/dist/inspector/assets/agent_grant_form-BoNBvzEf.js +0 -1
- package/dist/inspector/assets/home-3AZtmjKp.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/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";
|
|
@@ -45,6 +46,7 @@ import { IssueTransportError, IssueValidationError } from "./services/issues/err
|
|
|
45
46
|
import { externalActorFromCallerInput } from "./services/issues/external_actor_builder.js";
|
|
46
47
|
import { getCurrentAAuthAdmission, getCurrentAgentIdentity, getCurrentAttribution, getCurrentExternalActor, runWithAuthenticatedPrincipal, runWithExternalActor, runWithRequestContext, } from "./services/request_context.js";
|
|
47
48
|
import { assertCanWriteProtectedBatch } from "./services/protected_entity_types.js";
|
|
49
|
+
import { createMcpLookupReceiptObserver, observeMcpLookupResponse, } from "./services/mcp_lookup_timing.js";
|
|
48
50
|
import { redactMemberAttribution } from "./services/attribution_redaction.js";
|
|
49
51
|
import { createAgentIdentity as buildAgentIdentity, getAgentIdentityFromRequest, normaliseClientName, } from "./crypto/agent_identity.js";
|
|
50
52
|
import { initServerKeys } from "./services/encryption_service.js";
|
|
@@ -73,12 +75,14 @@ import { getSandboxTermsResponse } from "./services/sandbox/terms.js";
|
|
|
73
75
|
import { resolveSandboxReportTransport } from "./services/sandbox/transport.js";
|
|
74
76
|
import { getDb } from "./repositories/db/connection.js";
|
|
75
77
|
import { getMcpAuthToken } from "./crypto/mcp_auth_token.js";
|
|
78
|
+
import { announceBearerHandoverStatus, matchConfiguredBearer, recordPreviousBearerUse, } from "./services/bearer_slots.js";
|
|
76
79
|
import { isOauthKeyCredentialValid, normalizeOauthNextPath, OAuthKeySessionStore, } from "./services/oauth_key_gate.js";
|
|
77
80
|
import { buildGoogleAuthorizeUrl, createGoogleSigninNonce, consumeGoogleSigninNonce, exchangeGoogleAuthorizationCode, getSharedGraphUserId, GoogleIdTokenError, isGoogleSigninEnabled, verifyGoogleIdToken, } from "./services/google_oidc.js";
|
|
78
81
|
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";
|
|
79
82
|
import { DEFAULT_OBSERVATION_SOURCE } from "./services/observation_insert.js";
|
|
80
83
|
import { getMimeTypeFromExtension } from "./services/file_text_extraction.js";
|
|
81
84
|
import { queryEntitiesWithCount } from "./shared/action_handlers/entity_handlers.js";
|
|
85
|
+
import { EXACT_IDENTITY_REQUEST_MESSAGE, exactIdentityRequested, readExactIdentity, } from "./shared/action_handlers/exact_identity_read.js";
|
|
82
86
|
import { retrieveEntityByIdentifierWithFallback } from "./shared/action_handlers/entity_identifier_handler.js";
|
|
83
87
|
import { prepareEntitySnapshotWithEmbedding, upsertEntitySnapshotWithEmbedding, } from "./services/entity_snapshot_embedding.js";
|
|
84
88
|
import { isNeotomaEntityId } from "./shared/neotoma_entity_id.js";
|
|
@@ -108,6 +112,9 @@ export function getResolvedServerMode() {
|
|
|
108
112
|
return _resolvedServerMode;
|
|
109
113
|
}
|
|
110
114
|
export const app = express();
|
|
115
|
+
const mcpLookupReceiptObserver = createMcpLookupReceiptObserver();
|
|
116
|
+
if (mcpLookupReceiptObserver)
|
|
117
|
+
app.use(mcpLookupReceiptObserver);
|
|
111
118
|
// Trust proxy headers (required for express-rate-limit when X-Forwarded-For is present)
|
|
112
119
|
app.set("trust proxy", true);
|
|
113
120
|
// Configure CSP to allow CDN scripts for the uploader and API connects
|
|
@@ -230,7 +237,10 @@ app.use(express.json({
|
|
|
230
237
|
req.rawBody = Buffer.from(buf);
|
|
231
238
|
},
|
|
232
239
|
}));
|
|
233
|
-
app.use(morgan("dev"
|
|
240
|
+
app.use(morgan("dev", {
|
|
241
|
+
// Suppress identity-bearing URLs independently of route spelling or method.
|
|
242
|
+
skip: (req) => exactIdentityRequested(req.query),
|
|
243
|
+
}));
|
|
234
244
|
app.use(cookieParser());
|
|
235
245
|
app.use(unknownFieldsGuard);
|
|
236
246
|
// Sandbox-mode response header. Stamped on every response so clients can
|
|
@@ -1762,6 +1772,7 @@ app.all("/mcp", async (req, res) => {
|
|
|
1762
1772
|
const authHeader = (req.headers["authorization"] || req.headers["Authorization"]);
|
|
1763
1773
|
let connectionIdHeader = (req.headers["x-connection-id"] || req.headers["X-Connection-Id"]);
|
|
1764
1774
|
let bearerValidated = false;
|
|
1775
|
+
let primaryBearerValidated = false;
|
|
1765
1776
|
// Development connection identities are assigned by this gate, not by the
|
|
1766
1777
|
// client. A client-sent value is honoured only where the gate would assign
|
|
1767
1778
|
// the same identity itself: a local caller in development mode. Anywhere
|
|
@@ -1805,7 +1816,21 @@ app.all("/mcp", async (req, res) => {
|
|
|
1805
1816
|
}
|
|
1806
1817
|
if (authHeader?.startsWith("Bearer ") && process.env.NEOTOMA_BEARER_TOKEN) {
|
|
1807
1818
|
const token = authHeader.slice(7).trim();
|
|
1808
|
-
|
|
1819
|
+
// Either configured slot (primary, or the retiring bearer while its
|
|
1820
|
+
// handover window is open) takes the SAME branch: same principal,
|
|
1821
|
+
// same grants. See src/services/bearer_slots.ts.
|
|
1822
|
+
const slot = matchConfiguredBearer(token);
|
|
1823
|
+
if (slot) {
|
|
1824
|
+
primaryBearerValidated = slot === "primary";
|
|
1825
|
+
if (slot === "previous") {
|
|
1826
|
+
recordPreviousBearerUse({
|
|
1827
|
+
surface: "mcp",
|
|
1828
|
+
method: req.method,
|
|
1829
|
+
path: req.path,
|
|
1830
|
+
userAgent: req.headers["user-agent"],
|
|
1831
|
+
clientName: req.headers["x-client-name"],
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1809
1834
|
setGateConnectionIdHeader(req, "dev-local");
|
|
1810
1835
|
connectionIdHeader = "dev-local";
|
|
1811
1836
|
bearerValidated = true;
|
|
@@ -1966,6 +1991,8 @@ app.all("/mcp", async (req, res) => {
|
|
|
1966
1991
|
return res.status(shaped.status).json(shaped.body);
|
|
1967
1992
|
}
|
|
1968
1993
|
// Legacy session path (2025-11-25 and earlier). Unchanged by #2070.
|
|
1994
|
+
// A qualified lookup can disconnect while stale-session recovery is still pending.
|
|
1995
|
+
const mcpLookupTiming = primaryBearerValidated ? observeMcpLookupResponse(req, res) : undefined;
|
|
1969
1996
|
// Get or create transport
|
|
1970
1997
|
let transport = sessionId ? mcpTransports.get(sessionId) : undefined;
|
|
1971
1998
|
let serverInstance = sessionId
|
|
@@ -2103,6 +2130,7 @@ app.all("/mcp", async (req, res) => {
|
|
|
2103
2130
|
// The gate's resolved connection id: the MCP server reads this, never
|
|
2104
2131
|
// the request's X-Connection-Id header.
|
|
2105
2132
|
mcpConnectionId: connectionIdHeader ?? null,
|
|
2133
|
+
mcpLookupTiming,
|
|
2106
2134
|
}, () => transport.handleRequest(req, res, req.body));
|
|
2107
2135
|
}
|
|
2108
2136
|
catch (error) {
|
|
@@ -2173,13 +2201,18 @@ function redactSensitiveFields(obj) {
|
|
|
2173
2201
|
}
|
|
2174
2202
|
return redacted;
|
|
2175
2203
|
}
|
|
2204
|
+
function redactEntityReadInput(value) {
|
|
2205
|
+
return exactIdentityRequested(value)
|
|
2206
|
+
? { read_mode: "exact_identity" }
|
|
2207
|
+
: redactSensitiveFields(value);
|
|
2208
|
+
}
|
|
2176
2209
|
function logDebug(event, req, extra) {
|
|
2177
2210
|
const safe = {
|
|
2178
2211
|
method: req.method,
|
|
2179
2212
|
path: req.path,
|
|
2180
|
-
query:
|
|
2213
|
+
query: redactEntityReadInput(req.query),
|
|
2181
2214
|
headers: redactHeaders(req.headers),
|
|
2182
|
-
body:
|
|
2215
|
+
body: redactEntityReadInput(req.body),
|
|
2183
2216
|
...(extra ? redactSensitiveFields(extra) : {}),
|
|
2184
2217
|
};
|
|
2185
2218
|
// eslint-disable-next-line no-console
|
|
@@ -2189,9 +2222,9 @@ function logWarn(event, req, extra) {
|
|
|
2189
2222
|
const safe = {
|
|
2190
2223
|
method: req.method,
|
|
2191
2224
|
path: req.path,
|
|
2192
|
-
query:
|
|
2225
|
+
query: redactEntityReadInput(req.query),
|
|
2193
2226
|
headers: redactHeaders(req.headers),
|
|
2194
|
-
body:
|
|
2227
|
+
body: redactEntityReadInput(req.body),
|
|
2195
2228
|
...(extra ? redactSensitiveFields(extra) : {}),
|
|
2196
2229
|
};
|
|
2197
2230
|
// eslint-disable-next-line no-console
|
|
@@ -2219,9 +2252,9 @@ function logError(event, req, error, extra) {
|
|
|
2219
2252
|
const payload = {
|
|
2220
2253
|
method: req.method,
|
|
2221
2254
|
path: req.path,
|
|
2222
|
-
query:
|
|
2255
|
+
query: redactEntityReadInput(req.query),
|
|
2223
2256
|
headers: redactHeaders(req.headers),
|
|
2224
|
-
body:
|
|
2257
|
+
body: redactEntityReadInput(req.body),
|
|
2225
2258
|
error: error instanceof Error
|
|
2226
2259
|
? {
|
|
2227
2260
|
name: error.name,
|
|
@@ -3825,10 +3858,22 @@ app.use(async (req, res, next) => {
|
|
|
3825
3858
|
}
|
|
3826
3859
|
if (headerAuth.startsWith("Bearer ") && process.env.NEOTOMA_BEARER_TOKEN) {
|
|
3827
3860
|
const token = headerAuth.slice(7).trim();
|
|
3828
|
-
|
|
3861
|
+
// Either configured slot takes the SAME branch: same principal, same
|
|
3862
|
+
// grants. See src/services/bearer_slots.ts.
|
|
3863
|
+
const slot = matchConfiguredBearer(token);
|
|
3864
|
+
if (slot) {
|
|
3865
|
+
if (slot === "previous") {
|
|
3866
|
+
recordPreviousBearerUse({
|
|
3867
|
+
surface: "rest",
|
|
3868
|
+
method: req.method,
|
|
3869
|
+
path: req.path,
|
|
3870
|
+
userAgent: req.headers["user-agent"],
|
|
3871
|
+
clientName: req.headers["x-client-name"],
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3829
3874
|
const devUser = await ensureLocalDevUser();
|
|
3830
3875
|
stampUserPrincipal(req, devUser.id);
|
|
3831
|
-
logger.info(`[Auth] ${req.method} ${req.path} auth_method
|
|
3876
|
+
logger.info(`[Auth] ${req.method} ${req.path} auth_method=${slot === "previous" ? "bearer_env_previous" : "bearer_env"} user_id=${devUser.id}`);
|
|
3832
3877
|
return next();
|
|
3833
3878
|
}
|
|
3834
3879
|
}
|
|
@@ -4267,6 +4312,18 @@ function handleApiError(req, res, error, defaultMessage, errorCode = "DB_QUERY_F
|
|
|
4267
4312
|
// (POST body, or query-string params coerced by coerceEntitiesQueryParams).
|
|
4268
4313
|
// REQUIRES AUTHENTICATION - all queries filtered by authenticated user_id.
|
|
4269
4314
|
async function runEntitiesQuery(req, res, rawInput) {
|
|
4315
|
+
if (exactIdentityRequested(rawInput)) {
|
|
4316
|
+
try {
|
|
4317
|
+
const userId = await getAuthenticatedUserId(req);
|
|
4318
|
+
const exact_identity = await readExactIdentity(rawInput, userId);
|
|
4319
|
+
if (!exact_identity)
|
|
4320
|
+
return sendError(res, 400, "VALIDATION_ERROR", EXACT_IDENTITY_REQUEST_MESSAGE);
|
|
4321
|
+
return res.set("Cache-Control", "no-store").json({ exact_identity });
|
|
4322
|
+
}
|
|
4323
|
+
catch (error) {
|
|
4324
|
+
return handleApiError(req, res, error, "Could not verify exact identity");
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4270
4327
|
const parsed = EntitiesQueryRequestSchema.safeParse(rawInput);
|
|
4271
4328
|
if (!parsed.success) {
|
|
4272
4329
|
logWarn("ValidationError:entities_query", req, {
|
|
@@ -4389,6 +4446,8 @@ function coerceEntitiesQueryParams(query) {
|
|
|
4389
4446
|
// `GET /entities?entity_type=...&search=...` no longer hit a 404 and silently
|
|
4390
4447
|
// degrade. REQUIRES AUTHENTICATION - identical user scoping to the POST path.
|
|
4391
4448
|
app.get("/entities", async (req, res) => {
|
|
4449
|
+
if (exactIdentityRequested(req.query))
|
|
4450
|
+
return sendError(res, 400, "VALIDATION_ERROR", "Exact identity requires POST /entities/query.");
|
|
4392
4451
|
return runEntitiesQuery(req, res, coerceEntitiesQueryParams(req.query));
|
|
4393
4452
|
});
|
|
4394
4453
|
// POST /api/entities/query - Query entities with filters
|
|
@@ -4594,26 +4653,10 @@ app.get("/entities/:id/html", async (req, res) => {
|
|
|
4594
4653
|
if (etag)
|
|
4595
4654
|
res.setHeader("ETag", etag);
|
|
4596
4655
|
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("; "));
|
|
4656
|
+
// Author scripts stay blocked. Opted-in copy blocks permit only the fixed
|
|
4657
|
+
// renderer-owned helper, confined to an opaque-origin sandbox.
|
|
4658
|
+
const { renderedPageContentSecurityPolicy } = await import("./services/rendered_page/copy_controls.js");
|
|
4659
|
+
res.setHeader("Content-Security-Policy", renderedPageContentSecurityPolicy(htmlBody));
|
|
4617
4660
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
4618
4661
|
return res.send(html);
|
|
4619
4662
|
}
|
|
@@ -4671,6 +4714,10 @@ app.post("/entities/:id/batch_correct", async (req, res) => {
|
|
|
4671
4714
|
if (error instanceof Error && error.message.includes("Not authenticated")) {
|
|
4672
4715
|
return sendError(res, 401, "AUTH_REQUIRED", error.message);
|
|
4673
4716
|
}
|
|
4717
|
+
// Capability denial: same 403 `capability_denied` envelope as /correct.
|
|
4718
|
+
if (error instanceof AgentCapabilityError) {
|
|
4719
|
+
return handleApiError(req, res, error, "Failed to apply batch correction", "DB_QUERY_FAILED", "AgentCapabilityRejection:entity_batch_correct");
|
|
4720
|
+
}
|
|
4674
4721
|
logError("APIError:entity_batch_correct", req, error);
|
|
4675
4722
|
const message = error instanceof Error ? error.message : "Failed to apply batch correction";
|
|
4676
4723
|
return sendError(res, 500, "DB_QUERY_FAILED", message);
|
|
@@ -4911,7 +4958,42 @@ app.get("/schemas", async (req, res) => {
|
|
|
4911
4958
|
// restricted WHICH entity types appear; it never corrected the version, so
|
|
4912
4959
|
// it could not compensate. `userId` is already required above, so scoping
|
|
4913
4960
|
// here changes no caller's authorization, only the accuracy of the version.
|
|
4914
|
-
const
|
|
4961
|
+
const catalog = await schemaRegistry.listEntityTypesResult(keyword, userId);
|
|
4962
|
+
const allSchemas = catalog.entity_types;
|
|
4963
|
+
const summary = req.query.summary === "true";
|
|
4964
|
+
if (summary) {
|
|
4965
|
+
const selected = filterEntityType
|
|
4966
|
+
? allSchemas.filter((schema) => schema.entity_type === filterEntityType)
|
|
4967
|
+
: allSchemas;
|
|
4968
|
+
const sorted = [...selected].sort((a, b) => a.entity_type.localeCompare(b.entity_type));
|
|
4969
|
+
const page = sorted.slice(offset, offset + limit);
|
|
4970
|
+
const reasons = [...catalog.registry_coverage.reasons];
|
|
4971
|
+
if (filterEntityType)
|
|
4972
|
+
reasons.push("filtered");
|
|
4973
|
+
if (offset !== 0 || page.length !== sorted.length)
|
|
4974
|
+
reasons.push("paginated");
|
|
4975
|
+
const complete = catalog.registry_coverage.state === "complete" && reasons.length === 0;
|
|
4976
|
+
return res.json({
|
|
4977
|
+
schemas: page.map(({ entity_type, schema_version, field_names, planning, planning_error }) => ({
|
|
4978
|
+
entity_type,
|
|
4979
|
+
schema_version,
|
|
4980
|
+
field_count: field_names.length,
|
|
4981
|
+
...(planning ? { planning } : {}),
|
|
4982
|
+
...(planning_error ? { planning_error } : {}),
|
|
4983
|
+
})),
|
|
4984
|
+
total: sorted.length,
|
|
4985
|
+
limit,
|
|
4986
|
+
offset,
|
|
4987
|
+
registry_coverage: {
|
|
4988
|
+
...catalog.registry_coverage,
|
|
4989
|
+
state: complete ? "complete" : "unknown",
|
|
4990
|
+
reasons: [...new Set(reasons)].sort(),
|
|
4991
|
+
returned_count: page.length,
|
|
4992
|
+
exhausted: complete,
|
|
4993
|
+
scope: { ...catalog.registry_coverage.scope, entity_type: filterEntityType || null },
|
|
4994
|
+
},
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4915
4997
|
// Filter to only show global schemas (user_id is null) or user-specific schemas for this user
|
|
4916
4998
|
// Also fetch metadata (including icons) for each schema
|
|
4917
4999
|
const { data: dbSchemas, error: dbError } = await db
|
|
@@ -4942,6 +5024,16 @@ app.get("/schemas", async (req, res) => {
|
|
|
4942
5024
|
const paginatedSchemas = sortedSchemas.slice(offset, offset + limit);
|
|
4943
5025
|
return res.json({
|
|
4944
5026
|
schemas: paginatedSchemas,
|
|
5027
|
+
registry_coverage: {
|
|
5028
|
+
...catalog.registry_coverage,
|
|
5029
|
+
state: "unknown",
|
|
5030
|
+
exhausted: false,
|
|
5031
|
+
returned_count: paginatedSchemas.length,
|
|
5032
|
+
reasons: [
|
|
5033
|
+
...new Set([...catalog.registry_coverage.reasons, "legacy_filtered_view"]),
|
|
5034
|
+
].sort(),
|
|
5035
|
+
scope: { ...catalog.registry_coverage.scope, entity_type: filterEntityType || null },
|
|
5036
|
+
},
|
|
4945
5037
|
total: productionSchemas.length,
|
|
4946
5038
|
limit,
|
|
4947
5039
|
offset,
|
|
@@ -6097,6 +6189,10 @@ app.post("/interpretations/create", async (req, res) => {
|
|
|
6097
6189
|
}
|
|
6098
6190
|
try {
|
|
6099
6191
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
6192
|
+
// Same per-type `store` capability scope `/store` applies, before any
|
|
6193
|
+
// source lookup or write. No-op without an agent identity.
|
|
6194
|
+
const { assertInterpretationStoreCapability, runInterpretation } = await import("./services/interpretation.js");
|
|
6195
|
+
await assertInterpretationStoreCapability(userId, parsed.data.entities);
|
|
6100
6196
|
const { data: source, error: sourceError } = await db
|
|
6101
6197
|
.from("sources")
|
|
6102
6198
|
.select("id")
|
|
@@ -6108,7 +6204,6 @@ app.post("/interpretations/create", async (req, res) => {
|
|
|
6108
6204
|
if (!source) {
|
|
6109
6205
|
return sendError(res, 404, "SOURCE_NOT_FOUND", "Source not found for authenticated user");
|
|
6110
6206
|
}
|
|
6111
|
-
const { runInterpretation } = await import("./services/interpretation.js");
|
|
6112
6207
|
const interpretationResult = await runInterpretation({
|
|
6113
6208
|
userId,
|
|
6114
6209
|
sourceId: parsed.data.source_id,
|
|
@@ -6190,6 +6285,14 @@ app.post("/interpretations/create", async (req, res) => {
|
|
|
6190
6285
|
});
|
|
6191
6286
|
}
|
|
6192
6287
|
catch (error) {
|
|
6288
|
+
// Capability denial: same 403 `capability_denied` envelope as `/store`.
|
|
6289
|
+
if (error instanceof AgentCapabilityError) {
|
|
6290
|
+
return handleApiError(req, res, error, "Failed to create interpretation", "INTERPRETATION_CREATE_FAILED", "AgentCapabilityRejection:interpretations_create");
|
|
6291
|
+
}
|
|
6292
|
+
if (error instanceof InvalidEntityTypeError) {
|
|
6293
|
+
logWarn("InvalidEntityTypeError:interpretations_create", req, { code: error.code });
|
|
6294
|
+
return sendError(res, 400, error.code, error.message, { hint: error.hint });
|
|
6295
|
+
}
|
|
6193
6296
|
logError("APIError:interpretations_create", req, error);
|
|
6194
6297
|
const message = error instanceof Error ? error.message : "Failed to create interpretation";
|
|
6195
6298
|
return sendError(res, 500, "INTERPRETATION_CREATE_FAILED", message);
|
|
@@ -6302,6 +6405,9 @@ app.post("/observations/create", async (req, res) => {
|
|
|
6302
6405
|
.status(403)
|
|
6303
6406
|
.json(buildErrorEnvelope("FORBIDDEN", "user_id does not match authenticated user."));
|
|
6304
6407
|
}
|
|
6408
|
+
// Same per-type `store` capability scope `/store` applies, before the
|
|
6409
|
+
// entity is resolved or created. No-op without an agent identity.
|
|
6410
|
+
assertCurrentAgentMayStoreEntityTypes([entity_type]);
|
|
6305
6411
|
const { resolveEntity } = await import("./services/entity_resolution.js");
|
|
6306
6412
|
const { createObservation } = await import("./services/observation_storage.js");
|
|
6307
6413
|
const { getSnapshot, recomputeSnapshot } = await import("./services/snapshot_computation.js");
|
|
@@ -6333,6 +6439,10 @@ app.post("/observations/create", async (req, res) => {
|
|
|
6333
6439
|
});
|
|
6334
6440
|
}
|
|
6335
6441
|
catch (error) {
|
|
6442
|
+
// Capability denial: same 403 `capability_denied` envelope as `/store`.
|
|
6443
|
+
if (error instanceof AgentCapabilityError) {
|
|
6444
|
+
return handleApiError(req, res, error, "Failed to create observation", "DB_QUERY_FAILED", "AgentCapabilityRejection:observations_create");
|
|
6445
|
+
}
|
|
6336
6446
|
logError("APIError:observations_create", req, error);
|
|
6337
6447
|
const message = error instanceof Error ? error.message : "Failed to create observation";
|
|
6338
6448
|
return res.status(500).json(buildErrorEnvelope("DB_QUERY_FAILED", message));
|
|
@@ -7619,6 +7729,9 @@ export async function storeUnstructuredForApi(params) {
|
|
|
7619
7729
|
observations_created: 0,
|
|
7620
7730
|
};
|
|
7621
7731
|
}
|
|
7732
|
+
// Agent grants: an upload is recorded under its asset type (the type the
|
|
7733
|
+
// MCP leg writes for the same bytes). Checked before the source is stored.
|
|
7734
|
+
assertCurrentAgentMay("store", [assetEntityTypeForMimeType(mimeType)], { guestCapable: true });
|
|
7622
7735
|
if (storageMode === "reference") {
|
|
7623
7736
|
if (!filePath) {
|
|
7624
7737
|
throw new Error("filePath is required for reference storage mode");
|
|
@@ -7693,9 +7806,24 @@ async function handleStorePost(req, res) {
|
|
|
7693
7806
|
const { assertConditionalStoreRequest } = await import("./services/store_condition_request.js");
|
|
7694
7807
|
assertConditionalStoreRequest(parsed.data);
|
|
7695
7808
|
const hasEntities = Boolean(parsed.data.entities?.length);
|
|
7809
|
+
// Every entity must name its type as a non-empty string, for every caller
|
|
7810
|
+
// kind, before either leg of the request writes anything.
|
|
7811
|
+
if (hasEntities)
|
|
7812
|
+
assertStoreEntityTypesNamed(parsed.data.entities);
|
|
7696
7813
|
const hasFileContent = Boolean(parsed.data.file_content && parsed.data.mime_type);
|
|
7697
7814
|
const hasFilePath = Boolean(parsed.data.file_path);
|
|
7698
7815
|
const hasUnstructured = hasFileContent || hasFilePath;
|
|
7816
|
+
// Agent grants on the file leg, before either leg writes, so a combined
|
|
7817
|
+
// entities-and-file call refused on the file writes no entities either.
|
|
7818
|
+
if (hasUnstructured && parsed.data.commit !== false) {
|
|
7819
|
+
const uploadMime = parsed.data.mime_type ||
|
|
7820
|
+
(parsed.data.file_path
|
|
7821
|
+
? getMimeTypeFromExtension(path.extname(parsed.data.file_path).toLowerCase())
|
|
7822
|
+
: undefined);
|
|
7823
|
+
assertCurrentAgentMay("store", [assetEntityTypeForMimeType(uploadMime)], {
|
|
7824
|
+
guestCapable: true,
|
|
7825
|
+
});
|
|
7826
|
+
}
|
|
7699
7827
|
if (hasEntities && hasUnstructured && parsed.data.commit !== false) {
|
|
7700
7828
|
const { claimCombinedStoreKeys } = await import("./services/store_combined_admission.js");
|
|
7701
7829
|
await claimCombinedStoreKeys({
|
|
@@ -7863,6 +7991,10 @@ async function handleStorePost(req, res) {
|
|
|
7863
7991
|
logWarn("AgentGrantPinConflict:store", req, { code: error.code });
|
|
7864
7992
|
return sendError(res, 409, error.code, error.message, { field: error.field });
|
|
7865
7993
|
}
|
|
7994
|
+
if (error instanceof InvalidEntityTypeError) {
|
|
7995
|
+
logWarn("InvalidEntityTypeError:store", req, { code: error.code });
|
|
7996
|
+
return sendError(res, 400, error.code, error.message, { hint: error.hint });
|
|
7997
|
+
}
|
|
7866
7998
|
if (errCode === "ERR_FORBIDDEN_ENTITY_TYPE" || errCode === "ERR_PLURAL_ENTITY_TYPE") {
|
|
7867
7999
|
const message = error instanceof Error ? error.message : String(error);
|
|
7868
8000
|
logWarn("EntityTypeGuardError:store", req, { code: errCode, message });
|
|
@@ -8083,6 +8215,10 @@ app.post("/sync/webhook", express.raw({ type: "application/json", limit: "2mb" }
|
|
|
8083
8215
|
if (msg === "INVALID_JSON" || msg.startsWith("VALIDATION_ERROR")) {
|
|
8084
8216
|
return sendError(res, 400, "VALIDATION_ERROR", msg);
|
|
8085
8217
|
}
|
|
8218
|
+
if (msg.startsWith("REMOTE_ENTITY_TYPE_REFUSED")) {
|
|
8219
|
+
const { SYNC_ENTITY_TYPE_REFUSED_ERROR_CODE } = await import("./services/sync/remote_snapshot_admission.js");
|
|
8220
|
+
return sendError(res, 403, SYNC_ENTITY_TYPE_REFUSED_ERROR_CODE, msg);
|
|
8221
|
+
}
|
|
8086
8222
|
logError("APIError:sync_webhook", req, err);
|
|
8087
8223
|
return sendError(res, 500, "SYNC_WEBHOOK_FAILED", msg);
|
|
8088
8224
|
}
|
|
@@ -8159,6 +8295,9 @@ app.post("/peers", express.json(), async (req, res) => {
|
|
|
8159
8295
|
return res.status(201).json(out);
|
|
8160
8296
|
}
|
|
8161
8297
|
catch (error) {
|
|
8298
|
+
if (error instanceof AgentCapabilityError) {
|
|
8299
|
+
return handleApiError(req, res, error, "Failed to add peer");
|
|
8300
|
+
}
|
|
8162
8301
|
logError("APIError:peers_add", req, error);
|
|
8163
8302
|
const message = error instanceof Error ? error.message : "Failed to add peer";
|
|
8164
8303
|
if (message.includes("Maximum peers")) {
|
|
@@ -8167,8 +8306,11 @@ app.post("/peers", express.json(), async (req, res) => {
|
|
|
8167
8306
|
return sendError(res, 500, "PEER_ADD_FAILED", message);
|
|
8168
8307
|
}
|
|
8169
8308
|
});
|
|
8170
|
-
// POST /peers/resolve_sync_conflict —
|
|
8171
|
-
//
|
|
8309
|
+
// POST /peers/resolve_sync_conflict — resolve a replication conflict. `prefer_remote`
|
|
8310
|
+
// and `manual` write correction-tier values, so they run the same `correct` capability
|
|
8311
|
+
// gate as `/correct`; `prefer_remote` applies the differing fields as one correction
|
|
8312
|
+
// transaction and reports applied and withheld fields. Registered before
|
|
8313
|
+
// `/peers/:peer_id` so `resolve_sync_conflict` is not captured as a peer_id.
|
|
8172
8314
|
app.post("/peers/resolve_sync_conflict", express.json(), async (req, res) => {
|
|
8173
8315
|
const schema = z.object({
|
|
8174
8316
|
entity_id: z.string().min(1),
|
|
@@ -8199,6 +8341,14 @@ app.post("/peers/resolve_sync_conflict", express.json(), async (req, res) => {
|
|
|
8199
8341
|
return res.json(result);
|
|
8200
8342
|
}
|
|
8201
8343
|
catch (error) {
|
|
8344
|
+
// A capability or protected-type refusal is the caller's authorization,
|
|
8345
|
+
// not a server fault: 403 with the envelope `/correct` returns.
|
|
8346
|
+
if (error instanceof AgentCapabilityError) {
|
|
8347
|
+
logWarn("AgentCapabilityRejection:peers_resolve", req, error.toErrorEnvelope());
|
|
8348
|
+
return res
|
|
8349
|
+
.status(403)
|
|
8350
|
+
.json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelope()));
|
|
8351
|
+
}
|
|
8202
8352
|
logError("APIError:peers_resolve", req, error);
|
|
8203
8353
|
const message = error instanceof Error ? error.message : "resolve failed";
|
|
8204
8354
|
return sendError(res, 500, "PEER_RESOLVE_FAILED", message);
|
|
@@ -8225,11 +8375,16 @@ app.get("/peers/:peer_id", async (req, res) => {
|
|
|
8225
8375
|
app.delete("/peers/:peer_id", async (req, res) => {
|
|
8226
8376
|
try {
|
|
8227
8377
|
const userId = await getAuthenticatedUserId(req);
|
|
8378
|
+
// Agent grants: removing a peer corrects its peer_config.
|
|
8379
|
+
assertCurrentAgentMay("correct", ["peer_config"]);
|
|
8228
8380
|
const { removePeerForUser } = await import("./services/sync/peer_ops.js");
|
|
8229
8381
|
await removePeerForUser({ userId, peer_id: req.params.peer_id });
|
|
8230
8382
|
return res.json({ success: true });
|
|
8231
8383
|
}
|
|
8232
8384
|
catch (error) {
|
|
8385
|
+
if (error instanceof AgentCapabilityError) {
|
|
8386
|
+
return handleApiError(req, res, error, "Failed to remove peer");
|
|
8387
|
+
}
|
|
8233
8388
|
logError("APIError:peers_remove", req, error);
|
|
8234
8389
|
const message = error instanceof Error ? error.message : "Failed to remove peer";
|
|
8235
8390
|
if (message.includes("not found")) {
|
|
@@ -8245,11 +8400,17 @@ app.post("/peers/:peer_id/sync", express.json(), async (req, res) => {
|
|
|
8245
8400
|
const bodySchema = z.object({ limit: z.number().int().positive().max(500).optional() });
|
|
8246
8401
|
const bodyParsed = bodySchema.safeParse(req.body ?? {});
|
|
8247
8402
|
const limit = bodyParsed.success ? bodyParsed.data.limit : undefined;
|
|
8403
|
+
// Agent grants: a sync run sends local data to the peer and advances the
|
|
8404
|
+
// peer_config watermark; pulled rows are still checked per type by store.
|
|
8405
|
+
assertCurrentAgentMay("correct", ["peer_config"]);
|
|
8248
8406
|
const { syncPeerFull } = await import("./services/sync/full_sync.js");
|
|
8249
8407
|
const result = await syncPeerFull({ userId, peer_id: req.params.peer_id, limit });
|
|
8250
8408
|
return res.json(result);
|
|
8251
8409
|
}
|
|
8252
8410
|
catch (error) {
|
|
8411
|
+
if (error instanceof AgentCapabilityError) {
|
|
8412
|
+
return handleApiError(req, res, error, "sync_peer failed");
|
|
8413
|
+
}
|
|
8253
8414
|
logError("APIError:peers_sync", req, error);
|
|
8254
8415
|
const message = error instanceof Error ? error.message : "sync_peer failed";
|
|
8255
8416
|
return sendError(res, 500, "PEER_SYNC_FAILED", message);
|
|
@@ -8350,6 +8511,8 @@ app.post("/entities/merge", async (req, res) => {
|
|
|
8350
8511
|
if (providedUserId && providedUserId !== authenticatedUserId) {
|
|
8351
8512
|
return sendError(res, 403, "FORBIDDEN", "user_id does not match authenticated user.");
|
|
8352
8513
|
}
|
|
8514
|
+
// Agent grants: `merge` on both entities' stored types, before any write.
|
|
8515
|
+
await assertCurrentAgentMayOnEntities("merge", user_id, [from_entity_id, to_entity_id]);
|
|
8353
8516
|
const { mergeEntities } = await import("./services/entity_merge.js");
|
|
8354
8517
|
const result = await mergeEntities({
|
|
8355
8518
|
fromEntityId: from_entity_id,
|
|
@@ -8361,6 +8524,9 @@ app.post("/entities/merge", async (req, res) => {
|
|
|
8361
8524
|
return res.json(result);
|
|
8362
8525
|
}
|
|
8363
8526
|
catch (error) {
|
|
8527
|
+
if (error instanceof AgentCapabilityError) {
|
|
8528
|
+
return handleApiError(req, res, error, "Failed to merge entities");
|
|
8529
|
+
}
|
|
8364
8530
|
const { EntityNotFoundError, EntityAlreadyMergedError } = await import("./services/entity_merge.js");
|
|
8365
8531
|
if (error instanceof EntityNotFoundError) {
|
|
8366
8532
|
return sendError(res, 404, "RESOURCE_NOT_FOUND", error.message);
|
|
@@ -8390,6 +8556,9 @@ app.post("/entities/split", async (req, res) => {
|
|
|
8390
8556
|
return sendError(res, 403, "FORBIDDEN", "user_id does not match authenticated user.");
|
|
8391
8557
|
}
|
|
8392
8558
|
const userId = providedUserId || authenticatedUserId;
|
|
8559
|
+
// Agent grants: `split` on the source's stored type, the new entity's
|
|
8560
|
+
// type and, when the split targets an existing entity, its stored type.
|
|
8561
|
+
await assertCurrentAgentMayOnEntities("split", userId, [parsed.data.source_entity_id, parsed.data.new_entity.target_entity_id ?? ""], [parsed.data.new_entity.entity_type]);
|
|
8393
8562
|
const { splitEntity } = await import("./services/entity_split.js");
|
|
8394
8563
|
const result = await splitEntity({
|
|
8395
8564
|
sourceEntityId: parsed.data.source_entity_id,
|
|
@@ -8407,6 +8576,9 @@ app.post("/entities/split", async (req, res) => {
|
|
|
8407
8576
|
return res.json(result);
|
|
8408
8577
|
}
|
|
8409
8578
|
catch (error) {
|
|
8579
|
+
if (error instanceof AgentCapabilityError) {
|
|
8580
|
+
return handleApiError(req, res, error, "Failed to split entity");
|
|
8581
|
+
}
|
|
8410
8582
|
const { EntityNotFoundError, EntityAlreadyMergedError, SplitPredicateMatchedNothingError, SplitPredicateMatchedAllError, IdempotencyMismatchError, } = await import("./services/entity_split.js");
|
|
8411
8583
|
if (error instanceof EntityNotFoundError) {
|
|
8412
8584
|
return sendError(res, 404, "RESOURCE_NOT_FOUND", error.message);
|
|
@@ -8479,12 +8651,14 @@ app.post("/get_entity_snapshot", async (req, res) => {
|
|
|
8479
8651
|
}
|
|
8480
8652
|
// Materialization is derived state. Validate owned current authority before
|
|
8481
8653
|
// serving its row, including restrictive corruption acquisition failures.
|
|
8654
|
+
// The check reads the entity's lifecycle authority rows, not its whole
|
|
8655
|
+
// observation history (#2792).
|
|
8482
8656
|
try {
|
|
8483
|
-
const {
|
|
8657
|
+
const { isOwnedEntityHiddenAfterCutover } = await import("./services/current_state_reads.js");
|
|
8484
8658
|
const { hasRecordedEntityLifecycleCutover } = await import("./services/entity_lifecycle_storage.js");
|
|
8485
8659
|
const { getDb } = await import("./repositories/db/connection.js");
|
|
8486
8660
|
if ((await hasRecordedEntityLifecycleCutover(await getDb())) &&
|
|
8487
|
-
(await
|
|
8661
|
+
(await isOwnedEntityHiddenAfterCutover(entity_id, userId)))
|
|
8488
8662
|
return sendError(res, 404, "RESOURCE_NOT_FOUND", "Entity not found");
|
|
8489
8663
|
}
|
|
8490
8664
|
catch (err) {
|
|
@@ -9177,6 +9351,9 @@ app.post("/delete_entity", async (req, res) => {
|
|
|
9177
9351
|
try {
|
|
9178
9352
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9179
9353
|
const { entity_id, entity_type, reason } = parsed.data;
|
|
9354
|
+
// Agent grants: `delete` on the stored type (and the type the request
|
|
9355
|
+
// names), before the deletion observation is written.
|
|
9356
|
+
await assertCurrentAgentMayOnEntities("delete", userId, [entity_id], [entity_type]);
|
|
9180
9357
|
const { softDeleteEntity } = await import("./services/deletion.js");
|
|
9181
9358
|
const result = await softDeleteEntity(entity_id, entity_type, userId, reason);
|
|
9182
9359
|
if (!result.success) {
|
|
@@ -9199,6 +9376,8 @@ app.post("/issues/bulk_close", async (req, res) => {
|
|
|
9199
9376
|
}
|
|
9200
9377
|
try {
|
|
9201
9378
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9379
|
+
// Agent grants: closing corrects the issue (and its GitHub state).
|
|
9380
|
+
await assertCurrentAgentMayOnEntities("correct", userId, parsed.data.entity_ids, ["issue"]);
|
|
9202
9381
|
const { bulkCloseIssues } = await import("./services/issues/inspector_bulk.js");
|
|
9203
9382
|
const { results } = await bulkCloseIssues(userId, parsed.data.entity_ids);
|
|
9204
9383
|
logDebug("Success:issues_bulk_close", req, { count: results.length });
|
|
@@ -9217,6 +9396,8 @@ app.post("/issues/bulk_remove", async (req, res) => {
|
|
|
9217
9396
|
}
|
|
9218
9397
|
try {
|
|
9219
9398
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9399
|
+
// Agent grants: removal closes and soft-deletes the issue.
|
|
9400
|
+
await assertCurrentAgentMayOnEntities("delete", userId, parsed.data.entity_ids, ["issue"]);
|
|
9220
9401
|
const { bulkRemoveIssues } = await import("./services/issues/inspector_bulk.js");
|
|
9221
9402
|
const { results } = await bulkRemoveIssues(userId, parsed.data.entity_ids);
|
|
9222
9403
|
logDebug("Success:issues_bulk_remove", req, { count: results.length });
|
|
@@ -9239,6 +9420,15 @@ const handleIssuesAddMessageHttp = async (req, res) => {
|
|
|
9239
9420
|
const userId = principal.kind === "guest"
|
|
9240
9421
|
? (await resolveGuestScopedEntityAccess(principal, parsed.data.entity_id?.trim() || "", "issue")).userId
|
|
9241
9422
|
: await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9423
|
+
// Agent grants on the owner path: a thread message is an issue-thread
|
|
9424
|
+
// write (and a GitHub comment). The in-process operations layer below does
|
|
9425
|
+
// not carry the caller's grant, so the check runs here, before anything is
|
|
9426
|
+
// written. The guest path stays scoped by its guest access token.
|
|
9427
|
+
if (principal.kind !== "guest") {
|
|
9428
|
+
assertCurrentAgentMay("store", ISSUE_THREAD_ENTITY_TYPES, { guestCapable: true });
|
|
9429
|
+
// Edges from the issue to entities the caller names.
|
|
9430
|
+
await assertCurrentAgentMayLink(userId, "issue", "REFERS_TO", parsed.data.entity_ids_to_link ?? [], { guestCapable: true });
|
|
9431
|
+
}
|
|
9242
9432
|
const { createOperations } = await import("./core/operations.js");
|
|
9243
9433
|
const { NeotomaServer } = await import("./server.js");
|
|
9244
9434
|
const { addIssueMessage, appendGuestIssueMessage } = await import("./services/issues/issue_operations.js");
|
|
@@ -9311,6 +9501,16 @@ const handleIssuesSubmitHttp = async (req, res) => {
|
|
|
9311
9501
|
? ((await resolveGuestUserId(req, principal)) ??
|
|
9312
9502
|
(await getAuthenticatedUserId(req, parsed.data.user_id)))
|
|
9313
9503
|
: await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9504
|
+
// Agent grants on the owner path: it writes an issue thread under the
|
|
9505
|
+
// owner and may open a GitHub issue. The in-process operations layer below
|
|
9506
|
+
// does not carry the caller's grant, so the check runs here, before
|
|
9507
|
+
// anything is written. The guest path stays under access policy, as for
|
|
9508
|
+
// any guest.
|
|
9509
|
+
if (!(principal.kind === "guest" || guestSubmitPayload)) {
|
|
9510
|
+
assertCurrentAgentMay("store", ISSUE_THREAD_ENTITY_TYPES, { guestCapable: true });
|
|
9511
|
+
// Edges from the new issue to entities the caller names.
|
|
9512
|
+
await assertCurrentAgentMayLink(userId, "issue", "REFERS_TO", [...(parsed.data.entity_ids_to_link ?? []), parsed.data.conversation_turn_id ?? ""], { guestCapable: true });
|
|
9513
|
+
}
|
|
9314
9514
|
const { createOperations } = await import("./core/operations.js");
|
|
9315
9515
|
const { NeotomaServer } = await import("./server.js");
|
|
9316
9516
|
const { submitIssue, submitGuestIssue } = await import("./services/issues/issue_operations.js");
|
|
@@ -9434,6 +9634,10 @@ const handleIssuesSyncHttp = async (req, res) => {
|
|
|
9434
9634
|
}
|
|
9435
9635
|
try {
|
|
9436
9636
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9637
|
+
// Agent grants: a sync stores new issue threads, corrects existing ones
|
|
9638
|
+
// and can push local issues to GitHub.
|
|
9639
|
+
assertCurrentAgentMay("store", ISSUE_THREAD_ENTITY_TYPES);
|
|
9640
|
+
assertCurrentAgentMay("correct", ISSUE_THREAD_ENTITY_TYPES);
|
|
9437
9641
|
const { createOperations } = await import("./core/operations.js");
|
|
9438
9642
|
const { NeotomaServer } = await import("./server.js");
|
|
9439
9643
|
const { syncIssuesFromGitHub } = await import("./services/issues/sync_issues_from_github.js");
|
|
@@ -9480,6 +9684,9 @@ app.post("/restore_entity", async (req, res) => {
|
|
|
9480
9684
|
try {
|
|
9481
9685
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9482
9686
|
const { entity_id, entity_type, reason } = parsed.data;
|
|
9687
|
+
// Agent grants: `restore` on the stored type (and the type the request
|
|
9688
|
+
// names), before the restoration observation is written.
|
|
9689
|
+
await assertCurrentAgentMayOnEntities("restore", userId, [entity_id], [entity_type]);
|
|
9483
9690
|
const { restoreEntity } = await import("./services/deletion.js");
|
|
9484
9691
|
const result = await restoreEntity(entity_id, entity_type, userId, reason);
|
|
9485
9692
|
if (!result.success) {
|
|
@@ -9744,12 +9951,14 @@ app.post("/update_schema_incremental", async (req, res) => {
|
|
|
9744
9951
|
}
|
|
9745
9952
|
try {
|
|
9746
9953
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
9747
|
-
const { entity_type, fields_to_add, fields_to_remove, canonical_name_fields, schema_version,
|
|
9954
|
+
const { entity_type, fields_to_add, fields_to_remove, canonical_name_fields, planning, schema_version,
|
|
9748
9955
|
// #2374: no local `= false` default. `user_specific` stays
|
|
9749
9956
|
// `undefined` when the caller omitted it, so `updateSchemaIncremental`
|
|
9750
9957
|
// can defer to whichever scope its own read resolved instead of being
|
|
9751
9958
|
// told "global" by a default this route manufactured.
|
|
9752
9959
|
user_specific, activate = true, migrate_existing = false, force = false, } = parsed.data;
|
|
9960
|
+
// Agent grants: `register_schema` on the entity type, before any write.
|
|
9961
|
+
assertCurrentAgentMay("register_schema", [entity_type]);
|
|
9753
9962
|
const { schemaRegistry, loadCodeDefinedSchemaEntry } = await import("./services/schema_registry.js");
|
|
9754
9963
|
// #2454 / #2374 REST parity with the MCP handler: the existence guard
|
|
9755
9964
|
// resolves the SAME row `updateSchemaIncremental` will read —
|
|
@@ -9818,6 +10027,7 @@ app.post("/update_schema_incremental", async (req, res) => {
|
|
|
9818
10027
|
// re-key (the MCP server handler had it, but this Express route — which
|
|
9819
10028
|
// the CLI offline transport uses — did not).
|
|
9820
10029
|
canonical_name_fields,
|
|
10030
|
+
planning,
|
|
9821
10031
|
schema_version,
|
|
9822
10032
|
user_specific,
|
|
9823
10033
|
// #2374: pass userId unconditionally — same reasoning as the MCP
|
|
@@ -10032,6 +10242,8 @@ app.post("/register_schema", async (req, res) => {
|
|
|
10032
10242
|
try {
|
|
10033
10243
|
const userId = await getAuthenticatedUserId(req, parsed.data.user_id);
|
|
10034
10244
|
const { entity_type, schema_definition, reducer_config, schema_version = "1.0", user_specific = false, activate = false, force = false, } = parsed.data;
|
|
10245
|
+
// Agent grants: `register_schema` on the entity type, before any write.
|
|
10246
|
+
assertCurrentAgentMay("register_schema", [entity_type]);
|
|
10035
10247
|
const { schemaRegistry } = await import("./services/schema_registry.js");
|
|
10036
10248
|
// R2 back-compat: existing HTTP/CLI callers may register schemas
|
|
10037
10249
|
// without declaring canonical_name_fields or identity_opt_out (e.g. the
|
|
@@ -10582,6 +10794,8 @@ app.post("/unsubscribe", guestWriteRateLimit, async (req, res) => {
|
|
|
10582
10794
|
return sendError(res, 403, "FORBIDDEN", "Guest access token does not grant access to this subscription");
|
|
10583
10795
|
}
|
|
10584
10796
|
}
|
|
10797
|
+
// Agent grants: unsubscribing corrects the subscription (active=false).
|
|
10798
|
+
assertCurrentAgentMay("correct", ["subscription"], { guestCapable: true });
|
|
10585
10799
|
await unsubscribeUser({ userId, subscription_id: parsed.data.subscription_id });
|
|
10586
10800
|
return res.json({ success: true });
|
|
10587
10801
|
}
|
|
@@ -11279,6 +11493,10 @@ export async function startHTTPServer() {
|
|
|
11279
11493
|
}
|
|
11280
11494
|
throw err;
|
|
11281
11495
|
}
|
|
11496
|
+
// Report the bearer handover slot (NEOTOMA_BEARER_TOKEN_PREVIOUS) at boot,
|
|
11497
|
+
// so a misconfigured slot is loud at startup rather than at the first
|
|
11498
|
+
// consumer's 401. Fail-closed parsing lives in services/bearer_slots.ts.
|
|
11499
|
+
announceBearerHandoverStatus();
|
|
11282
11500
|
// Initialize encryption service
|
|
11283
11501
|
await initServerKeys();
|
|
11284
11502
|
// Seed the built-in schema registry from ENTITY_SCHEMAS (issue #1968).
|