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.
Files changed (340) hide show
  1. package/README.md +5 -5
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +256 -38
  4. package/dist/actions.js.map +1 -1
  5. package/dist/cli/harness_configure.d.ts +2 -1
  6. package/dist/cli/harness_configure.d.ts.map +1 -1
  7. package/dist/cli/harness_configure.js.map +1 -1
  8. package/dist/cli/index.d.ts +14 -0
  9. package/dist/cli/index.d.ts.map +1 -1
  10. package/dist/cli/index.js +53 -8
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/mcp_config_scan.d.ts +29 -0
  13. package/dist/cli/mcp_config_scan.d.ts.map +1 -1
  14. package/dist/cli/mcp_config_scan.js +35 -6
  15. package/dist/cli/mcp_config_scan.js.map +1 -1
  16. package/dist/cli/setup.d.ts +24 -3
  17. package/dist/cli/setup.d.ts.map +1 -1
  18. package/dist/cli/setup.js +65 -3
  19. package/dist/cli/setup.js.map +1 -1
  20. package/dist/docs/architecture/architecture.md +1 -1
  21. package/dist/docs/conventions/documentation_standards.md +1 -1
  22. package/dist/docs/developer/atomic_corrections.md +2 -2
  23. package/dist/docs/developer/cli_agent_instructions.md +14 -3
  24. package/dist/docs/developer/cli_reference.md +13 -2
  25. package/dist/docs/developer/mcp/instructions.md +15 -4
  26. package/dist/docs/developer/mcp_codex_remote_setup.md +61 -0
  27. package/dist/docs/developer/mcp_continue_setup.md +9 -17
  28. package/dist/docs/developer/mcp_letta_setup.md +5 -2
  29. package/dist/docs/developer/mcp_openclaw_setup.md +5 -3
  30. package/dist/docs/developer/mcp_overview.md +39 -19
  31. package/dist/docs/developer/mcp_vscode_setup.md +10 -23
  32. package/dist/docs/developer/mcp_windsurf_setup.md +9 -25
  33. package/dist/docs/developer/seo_analytics_verification.md +12 -10
  34. package/dist/docs/developer/site_launch_readiness.md +56 -0
  35. package/dist/docs/developer/tunnels.md +1 -1
  36. package/dist/docs/infrastructure/deployment.md +1 -1
  37. package/dist/docs/integrations/es/matrix.md +38 -0
  38. package/dist/docs/integrations/matrix.md +26 -71
  39. package/dist/docs/legal/site_privacy_notice.md +30 -8
  40. package/dist/docs/security/advisories/2026-11-04-peer-sync-and-agent-grant-scope.md +41 -0
  41. package/dist/docs/security/advisories/README.md +1 -0
  42. package/dist/docs/site/site_doc_manifest.yaml +7 -4
  43. package/dist/docs/specs/MCP_SPEC.md +7 -2
  44. package/dist/docs/subsystems/agent_capabilities.md +105 -4
  45. package/dist/docs/subsystems/auth.md +45 -0
  46. package/dist/docs/subsystems/conflict_resolution.md +18 -4
  47. package/dist/docs/subsystems/errors.md +1 -0
  48. package/dist/docs/subsystems/observation_architecture.md +2 -1
  49. package/dist/docs/subsystems/peer_sync.md +146 -24
  50. package/dist/docs/subsystems/reducer.md +130 -10
  51. package/dist/docs/testing/automated_test_catalog.md +97 -24
  52. package/dist/inspector/assets/{access_policies-CJOlV60d.js → access_policies-CecrDHE5.js} +1 -1
  53. package/dist/inspector/assets/{agent_badge-BnBqn8kt.js → agent_badge-bFKcTLc3.js} +1 -1
  54. package/dist/inspector/assets/{agent_detail-wP0ccjhX.js → agent_detail-ByxqvVXp.js} +1 -1
  55. package/dist/inspector/assets/{agent_filter-Bp2xA131.js → agent_filter-CIlnJqXp.js} +1 -1
  56. package/dist/inspector/assets/{agent_grant_detail-HIGr5dq_.js → agent_grant_detail-BS9vi68s.js} +1 -1
  57. package/dist/inspector/assets/agent_grant_form-CUNvJrPS.js +1 -0
  58. package/dist/inspector/assets/{agent_grants-CTKkadE5.js → agent_grants-DKd7ld_4.js} +1 -1
  59. package/dist/inspector/assets/{agents-CPwJLNJN.js → agents-CE6YYOPn.js} +1 -1
  60. package/dist/inspector/assets/{api_not_configured_state-ivqIcs2c.js → api_not_configured_state-BdXbCQAz.js} +1 -1
  61. package/dist/inspector/assets/{attribution_card-5ZAqRz6K.js → attribution_card-Df6GMziR.js} +1 -1
  62. package/dist/inspector/assets/{attribution_summary-D_MHFv7e.js → attribution_summary-B2T-E3h9.js} +1 -1
  63. package/dist/inspector/assets/{bundles-QfMciBj9.js → bundles-DPrW5kwK.js} +1 -1
  64. package/dist/inspector/assets/{card-1rkzdi9W.js → card-xB0wTMgy.js} +1 -1
  65. package/dist/inspector/assets/{checkbox-CO5dXSZf.js → checkbox-C6aOTIf3.js} +1 -1
  66. package/dist/inspector/assets/{compliance-OTGEtY7Y.js → compliance-CKOQJGVG.js} +1 -1
  67. package/dist/inspector/assets/{confirm-dialog-D0cZ5vck.js → confirm-dialog-CrSwx0Db.js} +1 -1
  68. package/dist/inspector/assets/{conversation_common-cSILaHXq.js → conversation_common-C5W4gEXW.js} +1 -1
  69. package/dist/inspector/assets/{conversation_detail-DT-lGapN.js → conversation_detail-Uz4wDgPf.js} +1 -1
  70. package/dist/inspector/assets/{copy_id_button-Dz0JDn4l.js → copy_id_button-CDMxVOzU.js} +1 -1
  71. package/dist/inspector/assets/{copyable_code_block-B2f99g8Q.js → copyable_code_block-CPl0aWFe.js} +1 -1
  72. package/dist/inspector/assets/{corrections-KvxGvvLV.js → corrections-Dwj1Fugu.js} +1 -1
  73. package/dist/inspector/assets/{dashboard-BudvW21X.js → dashboard-BBCSRZNU.js} +1 -1
  74. package/dist/inspector/assets/{data-table-d9EMxowf.js → data-table-aUtH1nlD.js} +1 -1
  75. package/dist/inspector/assets/{design-DyiQbEeP.js → design-D0gJ9iB8.js} +1 -1
  76. package/dist/inspector/assets/{dialog-BmW1Iw8o.js → dialog-CMWtV0zN.js} +1 -1
  77. package/dist/inspector/assets/{docs-CT3IYX0E.js → docs-Byar2vOe.js} +1 -1
  78. package/dist/inspector/assets/{docs_page_layout-CosNjUZa.js → docs_page_layout-CP64UQbJ.js} +1 -1
  79. package/dist/inspector/assets/{embed_graph-Dk1-zVnz.js → embed_graph-CnaGuKds.js} +1 -1
  80. package/dist/inspector/assets/{empty_state-B8Ot2oGB.js → empty_state-BzT7NRtZ.js} +1 -1
  81. package/dist/inspector/assets/{entities-Dt5Ts5UN.js → entities-fkG18fss.js} +1 -1
  82. package/dist/inspector/assets/{entity_correct-D2PHEpGY.js → entity_correct-mnPBHieg.js} +1 -1
  83. package/dist/inspector/assets/{entity_history-DHgNythA.js → entity_history-Th7OnpfA.js} +1 -1
  84. package/dist/inspector/assets/{entity_history_sections-Cnxblw-Z.js → entity_history_sections-DwEU4Y5F.js} +1 -1
  85. package/dist/inspector/assets/{entity_link-DWf1VD1n.js → entity_link-U1JSr9H2.js} +1 -1
  86. package/dist/inspector/assets/{entity_relationships_by_type-B0-TLC5o.js → entity_relationships_by_type-B6ZTLPGk.js} +1 -1
  87. package/dist/inspector/assets/{entity_segment_page-CzZ0VhKm.js → entity_segment_page-N7j8YJxA.js} +1 -1
  88. package/dist/inspector/assets/{entity_table_column_toggle-C73aErGE.js → entity_table_column_toggle-DfKBCgU2.js} +1 -1
  89. package/dist/inspector/assets/{entity_timeline-SAq5qvB5.js → entity_timeline-B7sMnfd3.js} +1 -1
  90. package/dist/inspector/assets/{entity_types-DEcDvyO0.js → entity_types-D5la65Sn.js} +1 -1
  91. package/dist/inspector/assets/{faq-DazWIFdv.js → faq-C-BJiIWQ.js} +1 -1
  92. package/dist/inspector/assets/{graph_auto_fit-CSzD7yNh.js → graph_auto_fit-Cl1sM7g6.js} +1 -1
  93. package/dist/inspector/assets/{graph_explorer-BEXXiMQ1.js → graph_explorer-BF8NyJLR.js} +1 -1
  94. package/dist/inspector/assets/home-Czrx0JG4.js +1 -0
  95. package/dist/inspector/assets/{index-fc4sMprn.js → index-BcN-bmX4.js} +2 -2
  96. package/dist/inspector/assets/{index-Dt_q_A1j.js → index-Bf45K-hH.js} +1 -1
  97. package/dist/inspector/assets/{index-Cii0RALL.js → index-DnnkhCRn.js} +1 -1
  98. package/dist/inspector/assets/{interpretations-DtzoiGuK.js → interpretations-BON8Hv6B.js} +1 -1
  99. package/dist/inspector/assets/interpretations-D1AiR24H.js +1 -0
  100. package/dist/inspector/assets/{issue_author_attribution-DtmFaluD.js → issue_author_attribution-BiIemiwE.js} +1 -1
  101. package/dist/inspector/assets/{issue_detail-ChHiz9I_.js → issue_detail-DKwqgprb.js} +1 -1
  102. package/dist/inspector/assets/{issues-BoxR2nB2.js → issues-DN0qXAD9.js} +1 -1
  103. package/dist/inspector/assets/{json_viewer-KAFp_j-8.js → json_viewer-o1fpEDum.js} +1 -1
  104. package/dist/inspector/assets/{label-Dn0ARjYl.js → label-6dQuJkiv.js} +1 -1
  105. package/dist/inspector/assets/{live_relative_time-gOIFOOLR.js → live_relative_time-DRapJJqL.js} +1 -1
  106. package/dist/inspector/assets/{mobile_filter_popover-2ezU-mlZ.js → mobile_filter_popover-CG_CrM1J.js} +1 -1
  107. package/dist/inspector/assets/{not_found-DwX7ikQC.js → not_found-ZFJp_piF.js} +1 -1
  108. package/dist/inspector/assets/{oauth_callback-BDGLOyTN.js → oauth_callback-0LtMaQug.js} +1 -1
  109. package/dist/inspector/assets/{oauth_signin-CGcCtq-V.js → oauth_signin-Dojz-oXP.js} +1 -1
  110. package/dist/inspector/assets/{observations-DvcUGpHm.js → observations-L18GAXwN.js} +1 -1
  111. package/dist/inspector/assets/{page_shell-DiwchdUC.js → page_shell-DGmu9DSu.js} +1 -1
  112. package/dist/inspector/assets/{pagination-BGIUHQkm.js → pagination-D06zx3An.js} +1 -1
  113. package/dist/inspector/assets/{peer_detail-DeD1sTeJ.js → peer_detail-ChA0ildZ.js} +1 -1
  114. package/dist/inspector/assets/{peers-CXe47m55.js → peers-1DpY9nuL.js} +1 -1
  115. package/dist/inspector/assets/{pin_primitive_button-C37Ha0Bt.js → pin_primitive_button-C69_BXo8.js} +1 -1
  116. package/dist/inspector/assets/{recent_activity-CzQjt4ym.js → recent_activity-_tzwJHSQ.js} +1 -1
  117. package/dist/inspector/assets/{recent_conversations-BnUl4h4t.js → recent_conversations-DIuO8UEC.js} +1 -1
  118. package/dist/inspector/assets/{recent_conversations-BFYZTxUJ.js → recent_conversations-DyySBTfu.js} +1 -1
  119. package/dist/inspector/assets/{recent_records_feed-y1n0RuMa.js → recent_records_feed-C3kqxaSx.js} +1 -1
  120. package/dist/inspector/assets/record_activity-D6T1ehSh.js +1 -0
  121. package/dist/inspector/assets/{relationship_detail-ic83ZCwG.js → relationship_detail-DB9gZgOc.js} +1 -1
  122. package/dist/inspector/assets/{relationship_panel_groups-CI1fZIU_.js → relationship_panel_groups-DDcasdh0.js} +1 -1
  123. package/dist/inspector/assets/{relationships-C7iSdgXG.js → relationships-DE8zWSeh.js} +1 -1
  124. package/dist/inspector/assets/{relationships-C1JrEl85.js → relationships-r726Am6k.js} +1 -1
  125. package/dist/inspector/assets/{sandbox-Dst-WJhB.js → sandbox-CK8Xh0zh.js} +1 -1
  126. package/dist/inspector/assets/{schema_detail-C7_JSWAw.js → schema_detail-tV9qZlDI.js} +1 -1
  127. package/dist/inspector/assets/{schemas-BgrIpRVJ.js → schemas-r5vde1ev.js} +1 -1
  128. package/dist/inspector/assets/{search-BUmT32dy.js → search-CYsHQGOl.js} +1 -1
  129. package/dist/inspector/assets/{segmented_control-t1pBImFE.js → segmented_control-Dh7hbz4k.js} +1 -1
  130. package/dist/inspector/assets/{select-DU7Jwzuy.js → select-BKEI7cq9.js} +1 -1
  131. package/dist/inspector/assets/{settings-BAPHY6RY.js → settings-CGFwUgc3.js} +1 -1
  132. package/dist/inspector/assets/{source_content-CIhKrGsl.js → source_content-BVWgibsY.js} +1 -1
  133. package/dist/inspector/assets/{source_content_open_button-Erw2ErgG.js → source_content_open_button-CILzjRlO.js} +1 -1
  134. package/dist/inspector/assets/{source_detail-C0XOo0Uu.js → source_detail-C1w4In3r.js} +1 -1
  135. package/dist/inspector/assets/{source_link-CkcRMEO3.js → source_link-DlxqKShY.js} +1 -1
  136. package/dist/inspector/assets/{sources-C0JBEINb.js → sources-C3c88YnL.js} +1 -1
  137. package/dist/inspector/assets/{subscriptions-DxU4kHGt.js → subscriptions-BJI8nfBi.js} +1 -1
  138. package/dist/inspector/assets/{switch-BhKadp29.js → switch-Dx7yDS8J.js} +1 -1
  139. package/dist/inspector/assets/{tabs-d81XObYb.js → tabs-C7CNmtlx.js} +1 -1
  140. package/dist/inspector/assets/{textarea-DTiKLH24.js → textarea-CnKEdeS7.js} +1 -1
  141. package/dist/inspector/assets/{timeline-BEyKQpER.js → timeline-DFvizOXt.js} +1 -1
  142. package/dist/inspector/assets/{timeline-98QXcGln.js → timeline-TBibq40-.js} +1 -1
  143. package/dist/inspector/assets/{timeline_event_detail-DzGzqtrx.js → timeline_event_detail-DXpLKXwn.js} +1 -1
  144. package/dist/inspector/assets/{turn_detail-CDP_FEPT.js → turn_detail-DxoBBnAV.js} +1 -1
  145. package/dist/inspector/assets/{turns-DLmr1Iue.js → turns-DYRndDTk.js} +1 -1
  146. package/dist/inspector/assets/{use_agents-D3rvlr5v.js → use_agents-EhoEA4-8.js} +1 -1
  147. package/dist/inspector/assets/{use_entities-CVikeUOv.js → use_entities-DzF_NpWe.js} +1 -1
  148. package/dist/inspector/assets/use_interpretations-CmKAxtHd.js +1 -0
  149. package/dist/inspector/assets/{use_mutations-BhwSLXcJ.js → use_mutations-Da9jgbQb.js} +1 -1
  150. package/dist/inspector/assets/{use_peers-QTm1Z_BV.js → use_peers-C1-jKVUX.js} +1 -1
  151. package/dist/inspector/assets/{use_relationships-DtymxAI9.js → use_relationships-ClEy1YjY.js} +1 -1
  152. package/dist/inspector/assets/{use_stats-BW1Z-dI2.js → use_stats-jYkK0Ydj.js} +1 -1
  153. package/dist/inspector/assets/{use_timeline-DND87Sof.js → use_timeline-CNpY1sVJ.js} +1 -1
  154. package/dist/inspector/assets/{use_turns-Dfb6Q8Ru.js → use_turns-aYgpXeJo.js} +1 -1
  155. package/dist/inspector/assets/{world_time_event_timeline-DxzejNrJ.js → world_time_event_timeline-4k2EdpBk.js} +1 -1
  156. package/dist/inspector/index.html +1 -1
  157. package/dist/reducers/observation_reducer.d.ts +7 -0
  158. package/dist/reducers/observation_reducer.d.ts.map +1 -1
  159. package/dist/reducers/observation_reducer.js +60 -7
  160. package/dist/reducers/observation_reducer.js.map +1 -1
  161. package/dist/repositories/sqlite/sqlite_client.d.ts +13 -1
  162. package/dist/repositories/sqlite/sqlite_client.d.ts.map +1 -1
  163. package/dist/repositories/sqlite/sqlite_client.js +56 -0
  164. package/dist/repositories/sqlite/sqlite_client.js.map +1 -1
  165. package/dist/repositories/worker/worker_file_database.d.ts +16 -1
  166. package/dist/repositories/worker/worker_file_database.d.ts.map +1 -1
  167. package/dist/repositories/worker/worker_file_database.js +280 -29
  168. package/dist/repositories/worker/worker_file_database.js.map +1 -1
  169. package/dist/server.d.ts +10 -0
  170. package/dist/server.d.ts.map +1 -1
  171. package/dist/server.js +185 -25
  172. package/dist/server.js.map +1 -1
  173. package/dist/services/agent_capabilities.d.ts +25 -1
  174. package/dist/services/agent_capabilities.d.ts.map +1 -1
  175. package/dist/services/agent_capabilities.js +5 -8
  176. package/dist/services/agent_capabilities.js.map +1 -1
  177. package/dist/services/agent_grants.d.ts.map +1 -1
  178. package/dist/services/agent_grants.js +7 -0
  179. package/dist/services/agent_grants.js.map +1 -1
  180. package/dist/services/batch_correction.d.ts +2 -1
  181. package/dist/services/batch_correction.d.ts.map +1 -1
  182. package/dist/services/batch_correction.js +29 -1
  183. package/dist/services/batch_correction.js.map +1 -1
  184. package/dist/services/bearer_slots.d.ts +109 -0
  185. package/dist/services/bearer_slots.d.ts.map +1 -0
  186. package/dist/services/bearer_slots.js +275 -0
  187. package/dist/services/bearer_slots.js.map +1 -0
  188. package/dist/services/bounded_identity_read.d.ts +26 -0
  189. package/dist/services/bounded_identity_read.d.ts.map +1 -0
  190. package/dist/services/bounded_identity_read.js +154 -0
  191. package/dist/services/bounded_identity_read.js.map +1 -0
  192. package/dist/services/bundles/activation.d.ts +4 -2
  193. package/dist/services/bundles/activation.d.ts.map +1 -1
  194. package/dist/services/bundles/activation.js +13 -9
  195. package/dist/services/bundles/activation.js.map +1 -1
  196. package/dist/services/correction.d.ts.map +1 -1
  197. package/dist/services/correction.js +14 -2
  198. package/dist/services/correction.js.map +1 -1
  199. package/dist/services/correction_identity.d.ts +63 -0
  200. package/dist/services/correction_identity.d.ts.map +1 -0
  201. package/dist/services/correction_identity.js +57 -0
  202. package/dist/services/correction_identity.js.map +1 -0
  203. package/dist/services/correction_transaction.d.ts.map +1 -1
  204. package/dist/services/correction_transaction.js +9 -3
  205. package/dist/services/correction_transaction.js.map +1 -1
  206. package/dist/services/current_state_reads.d.ts +56 -0
  207. package/dist/services/current_state_reads.d.ts.map +1 -0
  208. package/dist/services/current_state_reads.js +337 -0
  209. package/dist/services/current_state_reads.js.map +1 -0
  210. package/dist/services/entity_lifecycle_storage.d.ts.map +1 -1
  211. package/dist/services/entity_lifecycle_storage.js +17 -4
  212. package/dist/services/entity_lifecycle_storage.js.map +1 -1
  213. package/dist/services/entity_population_certificate.d.ts +2 -0
  214. package/dist/services/entity_population_certificate.d.ts.map +1 -0
  215. package/dist/services/entity_population_certificate.js +74 -0
  216. package/dist/services/entity_population_certificate.js.map +1 -0
  217. package/dist/services/entity_queries.d.ts +0 -5
  218. package/dist/services/entity_queries.d.ts.map +1 -1
  219. package/dist/services/entity_queries.js +296 -275
  220. package/dist/services/entity_queries.js.map +1 -1
  221. package/dist/services/interpretation.d.ts +12 -0
  222. package/dist/services/interpretation.d.ts.map +1 -1
  223. package/dist/services/interpretation.js +88 -29
  224. package/dist/services/interpretation.js.map +1 -1
  225. package/dist/services/mcp_lookup_timing.d.ts +25 -0
  226. package/dist/services/mcp_lookup_timing.d.ts.map +1 -0
  227. package/dist/services/mcp_lookup_timing.js +156 -0
  228. package/dist/services/mcp_lookup_timing.js.map +1 -0
  229. package/dist/services/mutation_capability.d.ts +105 -0
  230. package/dist/services/mutation_capability.d.ts.map +1 -0
  231. package/dist/services/mutation_capability.js +262 -0
  232. package/dist/services/mutation_capability.js.map +1 -0
  233. package/dist/services/net/private_host_guard.d.ts +6 -2
  234. package/dist/services/net/private_host_guard.d.ts.map +1 -1
  235. package/dist/services/net/private_host_guard.js +14 -3
  236. package/dist/services/net/private_host_guard.js.map +1 -1
  237. package/dist/services/oauth_key_gate.d.ts.map +1 -1
  238. package/dist/services/oauth_key_gate.js +5 -0
  239. package/dist/services/oauth_key_gate.js.map +1 -1
  240. package/dist/services/observation_storage.d.ts.map +1 -1
  241. package/dist/services/observation_storage.js +4 -0
  242. package/dist/services/observation_storage.js.map +1 -1
  243. package/dist/services/rendered_page/copy_controls.d.ts +8 -0
  244. package/dist/services/rendered_page/copy_controls.d.ts.map +1 -0
  245. package/dist/services/rendered_page/copy_controls.js +122 -0
  246. package/dist/services/rendered_page/copy_controls.js.map +1 -0
  247. package/dist/services/rendered_page/html_template.d.ts.map +1 -1
  248. package/dist/services/rendered_page/html_template.js +9 -0
  249. package/dist/services/rendered_page/html_template.js.map +1 -1
  250. package/dist/services/request_context.d.ts +3 -0
  251. package/dist/services/request_context.d.ts.map +1 -1
  252. package/dist/services/request_context.js.map +1 -1
  253. package/dist/services/root_landing/harness_snippets.d.ts.map +1 -1
  254. package/dist/services/root_landing/harness_snippets.js +23 -49
  255. package/dist/services/root_landing/harness_snippets.js.map +1 -1
  256. package/dist/services/root_landing/index.d.ts +1 -6
  257. package/dist/services/root_landing/index.d.ts.map +1 -1
  258. package/dist/services/root_landing/index.js +2 -16
  259. package/dist/services/root_landing/index.js.map +1 -1
  260. package/dist/services/root_landing/site_nav.js +1 -1
  261. package/dist/services/root_landing/site_nav.js.map +1 -1
  262. package/dist/services/schema_registry.d.ts +29 -11
  263. package/dist/services/schema_registry.d.ts.map +1 -1
  264. package/dist/services/schema_registry.js +193 -58
  265. package/dist/services/schema_registry.js.map +1 -1
  266. package/dist/services/store_admission.d.ts +26 -0
  267. package/dist/services/store_admission.d.ts.map +1 -1
  268. package/dist/services/store_admission.js +74 -6
  269. package/dist/services/store_admission.js.map +1 -1
  270. package/dist/services/sync/conflict_resolver.d.ts +39 -5
  271. package/dist/services/sync/conflict_resolver.d.ts.map +1 -1
  272. package/dist/services/sync/conflict_resolver.js +250 -18
  273. package/dist/services/sync/conflict_resolver.js.map +1 -1
  274. package/dist/services/sync/full_sync.d.ts +12 -0
  275. package/dist/services/sync/full_sync.d.ts.map +1 -1
  276. package/dist/services/sync/full_sync.js +78 -9
  277. package/dist/services/sync/full_sync.js.map +1 -1
  278. package/dist/services/sync/peer_ops.d.ts +8 -0
  279. package/dist/services/sync/peer_ops.d.ts.map +1 -1
  280. package/dist/services/sync/peer_ops.js +26 -0
  281. package/dist/services/sync/peer_ops.js.map +1 -1
  282. package/dist/services/sync/remote_snapshot_admission.d.ts +144 -0
  283. package/dist/services/sync/remote_snapshot_admission.d.ts.map +1 -0
  284. package/dist/services/sync/remote_snapshot_admission.js +248 -0
  285. package/dist/services/sync/remote_snapshot_admission.js.map +1 -0
  286. package/dist/services/sync/seed_peer_schema.d.ts.map +1 -1
  287. package/dist/services/sync/seed_peer_schema.js +8 -0
  288. package/dist/services/sync/seed_peer_schema.js.map +1 -1
  289. package/dist/services/sync/sync_webhook_inbound.d.ts.map +1 -1
  290. package/dist/services/sync/sync_webhook_inbound.js +37 -9
  291. package/dist/services/sync/sync_webhook_inbound.js.map +1 -1
  292. package/dist/services/sync/sync_webhook_outbound.d.ts +8 -0
  293. package/dist/services/sync/sync_webhook_outbound.d.ts.map +1 -1
  294. package/dist/services/sync/sync_webhook_outbound.js +74 -0
  295. package/dist/services/sync/sync_webhook_outbound.js.map +1 -1
  296. package/dist/shared/action_handlers/entity_handlers.d.ts.map +1 -1
  297. package/dist/shared/action_handlers/entity_handlers.js +3 -20
  298. package/dist/shared/action_handlers/entity_handlers.js.map +1 -1
  299. package/dist/shared/action_handlers/exact_identity_read.d.ts +8 -0
  300. package/dist/shared/action_handlers/exact_identity_read.d.ts.map +1 -0
  301. package/dist/shared/action_handlers/exact_identity_read.js +67 -0
  302. package/dist/shared/action_handlers/exact_identity_read.js.map +1 -0
  303. package/dist/shared/action_schemas.d.ts +31 -12
  304. package/dist/shared/action_schemas.d.ts.map +1 -1
  305. package/dist/shared/action_schemas.js +22 -5
  306. package/dist/shared/action_schemas.js.map +1 -1
  307. package/dist/shared/contract_mappings.d.ts.map +1 -1
  308. package/dist/shared/contract_mappings.js +1 -0
  309. package/dist/shared/contract_mappings.js.map +1 -1
  310. package/dist/shared/harness_connections.d.ts +37 -0
  311. package/dist/shared/harness_connections.d.ts.map +1 -0
  312. package/dist/shared/harness_connections.js +339 -0
  313. package/dist/shared/harness_connections.js.map +1 -0
  314. package/dist/shared/http_content_negotiation.d.ts +7 -0
  315. package/dist/shared/http_content_negotiation.d.ts.map +1 -0
  316. package/dist/shared/http_content_negotiation.js +17 -0
  317. package/dist/shared/http_content_negotiation.js.map +1 -0
  318. package/dist/shared/openapi_types.d.ts +433 -12
  319. package/dist/shared/openapi_types.d.ts.map +1 -1
  320. package/dist/shared/schema_planning.d.ts +24 -0
  321. package/dist/shared/schema_planning.d.ts.map +1 -0
  322. package/dist/shared/schema_planning.js +22 -0
  323. package/dist/shared/schema_planning.js.map +1 -0
  324. package/dist/tool_definitions.d.ts +6 -0
  325. package/dist/tool_definitions.d.ts.map +1 -1
  326. package/dist/tool_definitions.js +27 -34
  327. package/dist/tool_definitions.js.map +1 -1
  328. package/docs/README.md +1 -1
  329. package/docs/developer/mcp/instructions.md +15 -4
  330. package/openapi.yaml +412 -11
  331. package/package.json +7 -7
  332. package/skills/continue-session/SKILL.md +547 -0
  333. package/skills/end/SKILL.md +3 -1
  334. package/skills/status/SKILL.md +302 -80
  335. package/skills/where/SKILL.md +36 -30
  336. package/dist/inspector/assets/agent_grant_form-BoNBvzEf.js +0 -1
  337. package/dist/inspector/assets/home-3AZtmjKp.js +0 -1
  338. package/dist/inspector/assets/interpretations-CjR2csNL.js +0 -1
  339. package/dist/inspector/assets/record_activity-kXm-jtQ6.js +0 -1
  340. 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
- if (safeCompareTokens(token, process.env.NEOTOMA_BEARER_TOKEN)) {
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: redactSensitiveFields(req.query),
2213
+ query: redactEntityReadInput(req.query),
2181
2214
  headers: redactHeaders(req.headers),
2182
- body: redactSensitiveFields(req.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: redactSensitiveFields(req.query),
2225
+ query: redactEntityReadInput(req.query),
2193
2226
  headers: redactHeaders(req.headers),
2194
- body: redactSensitiveFields(req.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: redactSensitiveFields(req.query),
2255
+ query: redactEntityReadInput(req.query),
2223
2256
  headers: redactHeaders(req.headers),
2224
- body: redactSensitiveFields(req.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
- if (safeCompareTokens(token, process.env.NEOTOMA_BEARER_TOKEN)) {
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=bearer_env user_id=${devUser.id}`);
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
- // SECURITY (Phase 2, stored XSS in publish_rendered_page): html_body is
4598
- // author-supplied and served verbatim, and the GLOBAL CSP allows
4599
- // 'unsafe-inline' scripts — so an injected <script> in a rendered page would
4600
- // execute in this origin (a page minted with a guest access_token is viewable
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 allSchemas = await schemaRegistry.listEntityTypes(keyword, userId);
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 — stub (use `correct` today). Registered
8171
- // before `/peers/:peer_id` so `resolve_sync_conflict` is not captured as a peer_id.
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 { isEntityDeleted } = await import("./services/deletion.js");
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 isEntityDeleted(entity_id, userId)))
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).