@anchrd/intel 0.59.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 (364) hide show
  1. package/README.md +496 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +416 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +128 -0
  11. package/dist/adapters/content/content.types.d.ts +38 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-audit.d.ts +62 -0
  14. package/dist/adapters/db/db-audit.js +150 -0
  15. package/dist/adapters/db/db-boards.d.ts +6 -0
  16. package/dist/adapters/db/db-boards.js +324 -0
  17. package/dist/adapters/db/db-feed.d.ts +30 -0
  18. package/dist/adapters/db/db-feed.js +276 -0
  19. package/dist/adapters/db/db-flows.d.ts +42 -0
  20. package/dist/adapters/db/db-flows.js +1311 -0
  21. package/dist/adapters/db/db-grants.d.ts +133 -0
  22. package/dist/adapters/db/db-grants.js +269 -0
  23. package/dist/adapters/db/db-indexing.d.ts +3 -0
  24. package/dist/adapters/db/db-indexing.js +200 -0
  25. package/dist/adapters/db/db-oauth.d.ts +3 -0
  26. package/dist/adapters/db/db-oauth.js +26 -0
  27. package/dist/adapters/db/db-prompts.d.ts +46 -0
  28. package/dist/adapters/db/db-prompts.js +144 -0
  29. package/dist/adapters/db/db.d.ts +6 -0
  30. package/dist/adapters/db/db.js +1687 -0
  31. package/dist/adapters/db/db.types.d.ts +16 -0
  32. package/dist/adapters/db/db.types.js +1 -0
  33. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  34. package/dist/adapters/document-converter/document-converter.js +26 -0
  35. package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
  36. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  37. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  38. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  39. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  40. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  41. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  42. package/dist/adapters/index-queue/index-queue.js +12 -0
  43. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  44. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  45. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  46. package/dist/adapters/json-schema/json-schema.js +77 -0
  47. package/dist/adapters/openid/openid.d.ts +5 -0
  48. package/dist/adapters/openid/openid.js +301 -0
  49. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  50. package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
  51. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  52. package/dist/adapters/remote-tools/remote-tools.js +129 -0
  53. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  54. package/dist/adapters/semantic-index/semantic-index.js +139 -0
  55. package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
  56. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  57. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  58. package/dist/adapters/session-cookie/session-cookie.js +87 -0
  59. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  60. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  61. package/dist/audit/audit.d.ts +2 -0
  62. package/dist/audit/audit.js +87 -0
  63. package/dist/audit/audit.types.d.ts +23 -0
  64. package/dist/audit/audit.types.js +1 -0
  65. package/dist/auth/auth.d.ts +2 -0
  66. package/dist/auth/auth.js +284 -0
  67. package/dist/auth/auth.types.d.ts +108 -0
  68. package/dist/auth/auth.types.js +1 -0
  69. package/dist/boards/boards.d.ts +44 -0
  70. package/dist/boards/boards.js +447 -0
  71. package/dist/boards/boards.types.d.ts +117 -0
  72. package/dist/boards/boards.types.js +1 -0
  73. package/dist/build/build.d.ts +2 -0
  74. package/dist/build/build.js +192 -0
  75. package/dist/build/build.types.d.ts +17 -0
  76. package/dist/build/build.types.js +1 -0
  77. package/dist/bundle/bundle.d.ts +4 -0
  78. package/dist/bundle/bundle.js +1194 -0
  79. package/dist/bundle/bundle.types.d.ts +33 -0
  80. package/dist/bundle/bundle.types.js +1 -0
  81. package/dist/cli/cli.d.ts +4 -0
  82. package/dist/cli/cli.js +262 -0
  83. package/dist/cli/cli.types.d.ts +14 -0
  84. package/dist/cli/cli.types.js +1 -0
  85. package/dist/contract/audit.d.ts +50 -0
  86. package/dist/contract/audit.js +72 -0
  87. package/dist/contract/board.d.ts +150 -0
  88. package/dist/contract/board.js +262 -0
  89. package/dist/contract/bundle.d.ts +147 -0
  90. package/dist/contract/bundle.js +139 -0
  91. package/dist/contract/contract.d.ts +112 -0
  92. package/dist/contract/contract.js +143 -0
  93. package/dist/contract/feed.d.ts +126 -0
  94. package/dist/contract/feed.js +149 -0
  95. package/dist/contract/flow-run.d.ts +346 -0
  96. package/dist/contract/flow-run.js +194 -0
  97. package/dist/contract/flow.d.ts +1057 -0
  98. package/dist/contract/flow.js +552 -0
  99. package/dist/contract/node.d.ts +551 -0
  100. package/dist/contract/node.js +456 -0
  101. package/dist/contract/share.d.ts +216 -0
  102. package/dist/contract/share.js +134 -0
  103. package/dist/contract/table.d.ts +171 -0
  104. package/dist/contract/table.js +117 -0
  105. package/dist/contract/tool.d.ts +122 -0
  106. package/dist/contract/tool.js +172 -0
  107. package/dist/feed/feed.d.ts +2 -0
  108. package/dist/feed/feed.js +77 -0
  109. package/dist/feed/feed.types.d.ts +23 -0
  110. package/dist/feed/feed.types.js +1 -0
  111. package/dist/flows/flows.d.ts +41 -0
  112. package/dist/flows/flows.js +2216 -0
  113. package/dist/flows/flows.types.d.ts +339 -0
  114. package/dist/flows/flows.types.js +1 -0
  115. package/dist/http/http.d.ts +9 -0
  116. package/dist/http/http.js +874 -0
  117. package/dist/http/http.types.d.ts +22 -0
  118. package/dist/http/http.types.js +1 -0
  119. package/dist/indexing/indexing.d.ts +6 -0
  120. package/dist/indexing/indexing.js +239 -0
  121. package/dist/indexing/indexing.types.d.ts +11 -0
  122. package/dist/indexing/indexing.types.js +1 -0
  123. package/dist/intel/intel.d.ts +3 -0
  124. package/dist/intel/intel.js +194 -0
  125. package/dist/intel/intel.types.d.ts +24 -0
  126. package/dist/intel/intel.types.js +1 -0
  127. package/dist/mcp/mcp.d.ts +2 -0
  128. package/dist/mcp/mcp.js +1292 -0
  129. package/dist/mcp/mcp.types.d.ts +25 -0
  130. package/dist/mcp/mcp.types.js +1 -0
  131. package/dist/nodes/document-links/document-links.d.ts +20 -0
  132. package/dist/nodes/document-links/document-links.js +57 -0
  133. package/dist/nodes/nodes.d.ts +2 -0
  134. package/dist/nodes/nodes.js +1667 -0
  135. package/dist/nodes/nodes.types.d.ts +511 -0
  136. package/dist/nodes/nodes.types.js +1 -0
  137. package/dist/prepare/prepare.d.ts +4 -0
  138. package/dist/prepare/prepare.js +16 -0
  139. package/dist/prepare/prepare.types.d.ts +9 -0
  140. package/dist/prepare/prepare.types.js +1 -0
  141. package/dist/prompts/prompts.d.ts +2 -0
  142. package/dist/prompts/prompts.js +65 -0
  143. package/dist/prompts/prompts.types.d.ts +71 -0
  144. package/dist/prompts/prompts.types.js +1 -0
  145. package/dist/shared/callers/callers.d.ts +28 -0
  146. package/dist/shared/callers/callers.js +30 -0
  147. package/dist/shared/csv/csv.d.ts +13 -0
  148. package/dist/shared/csv/csv.js +85 -0
  149. package/dist/shared/document-text/document-text.d.ts +21 -0
  150. package/dist/shared/document-text/document-text.js +31 -0
  151. package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
  152. package/dist/shared/gate-authorization/gate-authorization.js +26 -0
  153. package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
  154. package/dist/shared/grant-expiry/grant-expiry.js +26 -0
  155. package/dist/shared/intel-error/intel-error.d.ts +22 -0
  156. package/dist/shared/intel-error/intel-error.js +27 -0
  157. package/dist/shared/plain-title/plain-title.d.ts +16 -0
  158. package/dist/shared/plain-title/plain-title.js +45 -0
  159. package/dist/shared/problem-details/problem-details.d.ts +30 -0
  160. package/dist/shared/problem-details/problem-details.js +18 -0
  161. package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
  162. package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
  163. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  164. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  165. package/dist/shared/sha256/sha256.d.ts +2 -0
  166. package/dist/shared/sha256/sha256.js +14 -0
  167. package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
  168. package/dist/tools/tool-servers/tool-servers.js +177 -0
  169. package/dist/tools/tools.d.ts +2 -0
  170. package/dist/tools/tools.js +207 -0
  171. package/dist/tools/tools.types.d.ts +56 -0
  172. package/dist/tools/tools.types.js +1 -0
  173. package/examples/branding/favicon.svg +5 -0
  174. package/examples/branding/logo.svg +5 -0
  175. package/examples/branding/theme.css +16 -0
  176. package/examples/dev.vars.example +7 -0
  177. package/examples/intel.json +9 -0
  178. package/migrations/0000_intel_foundation.sql +203 -0
  179. package/migrations/0001_portal_native_tools.sql +21 -0
  180. package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
  181. package/migrations/0003_folder_permissions.sql +211 -0
  182. package/migrations/0004_subflow_runs.sql +14 -0
  183. package/migrations/0005_flow_node_cleanup.sql +28 -0
  184. package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
  185. package/migrations/0006_links_are_written_in_the_text.sql +20 -0
  186. package/migrations/0007_no_node_waits.sql +69 -0
  187. package/migrations/0008_three_layers.sql +130 -0
  188. package/migrations/0009_no_context_policy.sql +48 -0
  189. package/migrations/0010_no_run_waits_a_year.sql +27 -0
  190. package/migrations/0011_one_name_for_the_tree.sql +53 -0
  191. package/migrations/0012_table_snapshots.sql +29 -0
  192. package/migrations/0017_a_vector_per_card.sql +38 -0
  193. package/migrations/0018_no_context_policy_at_last.sql +97 -0
  194. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  195. package/migrations/0020_cascade_purge_replay.sql +11 -0
  196. package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
  197. package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
  198. package/migrations/0023_a_board_and_its_tasks.sql +116 -0
  199. package/migrations/0024_the_archive_is_a_column.sql +33 -0
  200. package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
  201. package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
  202. package/migrations/0027_the_runs_of_every_flow.sql +27 -0
  203. package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
  204. package/package.json +118 -0
  205. package/src/contract/CLAUDE.md +152 -0
  206. package/src/contract/README.md +102 -0
  207. package/src/contract/audit.ts +85 -0
  208. package/src/contract/board.ts +306 -0
  209. package/src/contract/bundle.ts +157 -0
  210. package/src/contract/contract.ts +161 -0
  211. package/src/contract/feed.ts +165 -0
  212. package/src/contract/flow-run.ts +241 -0
  213. package/src/contract/flow.ts +675 -0
  214. package/src/contract/node.ts +583 -0
  215. package/src/contract/share.ts +196 -0
  216. package/src/contract/table.ts +178 -0
  217. package/src/contract/tool.ts +202 -0
  218. package/ui/README.md +110 -0
  219. package/ui/components.json +21 -0
  220. package/ui/index.html +13 -0
  221. package/ui/package.json +11 -0
  222. package/ui/scripts/lint-tokens.mjs +187 -0
  223. package/ui/src/access-summary/access-summary.tsx +385 -0
  224. package/ui/src/app/action-slot/action-slot.tsx +27 -0
  225. package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
  226. package/ui/src/app/app-tree/app-tree.tsx +1175 -0
  227. package/ui/src/app/app.tsx +87 -0
  228. package/ui/src/app/header-search/header-search.tsx +294 -0
  229. package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
  230. package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
  231. package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
  232. package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
  233. package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
  234. package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
  235. package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
  236. package/ui/src/app/tree-move/tree-move.tsx +427 -0
  237. package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
  238. package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
  239. package/ui/src/app/user-footer/user-footer.tsx +188 -0
  240. package/ui/src/app/view-menu/view-menu.tsx +115 -0
  241. package/ui/src/app-root/app-root.tsx +109 -0
  242. package/ui/src/app-root/app-root.types.ts +27 -0
  243. package/ui/src/archive/archive.tsx +417 -0
  244. package/ui/src/attachment-detail/attachment-detail.ts +52 -0
  245. package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
  246. package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
  247. package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
  248. package/ui/src/board/board-assignee/board-assignee.ts +48 -0
  249. package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
  250. package/ui/src/board/board-chip/board-chip.tsx +46 -0
  251. package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
  252. package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
  253. package/ui/src/board/board-data/board-data.ts +195 -0
  254. package/ui/src/board/board-data/board-data.types.ts +38 -0
  255. package/ui/src/board/board-dates/board-dates.ts +40 -0
  256. package/ui/src/board/board-dates/board-dates.tsx +105 -0
  257. package/ui/src/board/board-kanban/board-kanban.ts +162 -0
  258. package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
  259. package/ui/src/board/board-open-task/board-open-task.ts +32 -0
  260. package/ui/src/board/board-panel/board-panel.tsx +259 -0
  261. package/ui/src/board/board-settings/board-settings.tsx +226 -0
  262. package/ui/src/board/board-status/board-status.tsx +60 -0
  263. package/ui/src/board/board-stripes/board-stripes.ts +128 -0
  264. package/ui/src/board/board-table/board-table.ts +53 -0
  265. package/ui/src/board/board-table/board-table.tsx +610 -0
  266. package/ui/src/board/board-task/board-task.ts +108 -0
  267. package/ui/src/board/board-task/board-task.tsx +335 -0
  268. package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
  269. package/ui/src/branding/branding.tsx +17 -0
  270. package/ui/src/branding/custom-logo.ts +1 -0
  271. package/ui/src/branding/favicon.default.svg +5 -0
  272. package/ui/src/branding/favicon.svg +5 -0
  273. package/ui/src/components/ui/avatar.tsx +39 -0
  274. package/ui/src/components/ui/button.tsx +64 -0
  275. package/ui/src/components/ui/collapsible.tsx +20 -0
  276. package/ui/src/components/ui/command.tsx +160 -0
  277. package/ui/src/components/ui/dialog.tsx +143 -0
  278. package/ui/src/components/ui/dropdown-menu.tsx +187 -0
  279. package/ui/src/components/ui/input.tsx +21 -0
  280. package/ui/src/components/ui/popover.tsx +41 -0
  281. package/ui/src/components/ui/select.tsx +163 -0
  282. package/ui/src/components/ui/separator.tsx +26 -0
  283. package/ui/src/components/ui/sheet.tsx +136 -0
  284. package/ui/src/components/ui/sidebar.tsx +693 -0
  285. package/ui/src/components/ui/skeleton.tsx +13 -0
  286. package/ui/src/components/ui/switch.tsx +25 -0
  287. package/ui/src/components/ui/table.tsx +82 -0
  288. package/ui/src/components/ui/tabs.tsx +52 -0
  289. package/ui/src/components/ui/tooltip.tsx +51 -0
  290. package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
  291. package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
  292. package/ui/src/data/query-client.ts +18 -0
  293. package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
  294. package/ui/src/data/request-refusal/request-refusal.ts +51 -0
  295. package/ui/src/data/sign-in/sign-in.ts +97 -0
  296. package/ui/src/data/sign-in/sign-in.types.ts +15 -0
  297. package/ui/src/document-link/document-link.tsx +114 -0
  298. package/ui/src/document-markdown/document-markdown.ts +195 -0
  299. package/ui/src/editor-schema/editor-schema.ts +23 -0
  300. package/ui/src/entry-picker/entry-picker.tsx +238 -0
  301. package/ui/src/feed/feed.tsx +344 -0
  302. package/ui/src/file-preview/file-preview-view.tsx +496 -0
  303. package/ui/src/file-preview/file-preview.tsx +28 -0
  304. package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
  305. package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
  306. package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
  307. package/ui/src/file-preview/word-file-preview.tsx +5 -0
  308. package/ui/src/flow-runs/flow-runs.tsx +238 -0
  309. package/ui/src/flows/flows.tsx +1646 -0
  310. package/ui/src/flows/node-icon/node-icon.ts +34 -0
  311. package/ui/src/flows/node-palette/node-palette.tsx +214 -0
  312. package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
  313. package/ui/src/folder-contents/folder-contents.tsx +321 -0
  314. package/ui/src/folder-index/folder-index.ts +55 -0
  315. package/ui/src/frontmatter/frontmatter.tsx +193 -0
  316. package/ui/src/graph-pane/graph-pane.tsx +53 -0
  317. package/ui/src/hooks/use-mobile.ts +19 -0
  318. package/ui/src/i18n/custom.json +4 -0
  319. package/ui/src/i18n/de.json +609 -0
  320. package/ui/src/i18n/en.json +609 -0
  321. package/ui/src/i18n/es.json +609 -0
  322. package/ui/src/i18n/i18n-context.tsx +65 -0
  323. package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
  324. package/ui/src/i18n/i18n.ts +32 -0
  325. package/ui/src/i18n/i18n.types.ts +49 -0
  326. package/ui/src/kind-icon.ts +97 -0
  327. package/ui/src/lib/utils.ts +6 -0
  328. package/ui/src/main.tsx +68 -0
  329. package/ui/src/modal/modal.tsx +53 -0
  330. package/ui/src/node-details/node-details.tsx +545 -0
  331. package/ui/src/node-editor/node-editor.tsx +304 -0
  332. package/ui/src/node-graph/graph-notice.tsx +21 -0
  333. package/ui/src/node-graph/node-graph.ts +214 -0
  334. package/ui/src/node-graph/node-graph.tsx +295 -0
  335. package/ui/src/node-import/node-import.tsx +418 -0
  336. package/ui/src/node-save-error.ts +46 -0
  337. package/ui/src/node-source/node-source.tsx +128 -0
  338. package/ui/src/node-table/node-table.tsx +107 -0
  339. package/ui/src/nodes/nodes.tsx +240 -0
  340. package/ui/src/rename-resource/rename-resource.ts +80 -0
  341. package/ui/src/resolved-names/resolved-names.tsx +67 -0
  342. package/ui/src/resource-error.ts +33 -0
  343. package/ui/src/resource-menu/resource-menu.tsx +1156 -0
  344. package/ui/src/router/router-context.ts +6 -0
  345. package/ui/src/router/router.tsx +67 -0
  346. package/ui/src/router/router.types.ts +8 -0
  347. package/ui/src/router/selection-search.ts +88 -0
  348. package/ui/src/save-button/save-button.tsx +94 -0
  349. package/ui/src/section-hint/section-hint.tsx +40 -0
  350. package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
  351. package/ui/src/styles.css +189 -0
  352. package/ui/src/table-columns/table-columns.tsx +379 -0
  353. package/ui/src/theme/custom.css +1 -0
  354. package/ui/src/theme/theme-context.tsx +89 -0
  355. package/ui/src/theme/theme.ts +70 -0
  356. package/ui/src/time/relative-time.tsx +41 -0
  357. package/ui/src/time/time-context.tsx +78 -0
  358. package/ui/src/time/time.ts +165 -0
  359. package/ui/src/title-row/title-row.tsx +185 -0
  360. package/ui/src/tools/tools.tsx +546 -0
  361. package/ui/src/user-name/user-name.ts +61 -0
  362. package/ui/src/verbatim/verbatim.tsx +68 -0
  363. package/ui/tsconfig.json +17 -0
  364. package/ui/vite.config.ts +129 -0
@@ -0,0 +1,133 @@
1
+ import type { ResourceGrant, ResourceVerb } from "../../contract/share.js";
2
+ export interface GrantActor {
3
+ id: string;
4
+ email: string;
5
+ isAdmin?: boolean;
6
+ }
7
+ /**
8
+ * A grant row as every reader of one wants it: the subject column already aliased, so the same row
9
+ * shape comes back from `node_grants` and from `flow_grants`.
10
+ *
11
+ * ⚠️ The alias is what lets `mapGrant` stay one function. Two mappers would each carry the same
12
+ * `principal_type` switch, and the day a fourth principal shape arrives one of them gets it.
13
+ */
14
+ export interface GrantRow {
15
+ id: string;
16
+ resource_id: string;
17
+ principal_type: "user" | "email" | "organization";
18
+ principal_id: string;
19
+ verb: ResourceVerb;
20
+ expires_at: string | null;
21
+ created_by: string;
22
+ created_at: string;
23
+ }
24
+ export declare const grantColumns: (subjectColumn: string) => string;
25
+ export declare function mapGrant(row: GrantRow): ResourceGrant;
26
+ export declare function principalColumns(principal: ResourceGrant["principal"]): {
27
+ type: ResourceGrant["principal"]["type"];
28
+ id: string;
29
+ };
30
+ /**
31
+ * Whether a grant row is in force at the moment bound after it. Takes one binding: that moment.
32
+ *
33
+ * ⚠️ The ONE place this repository decides what an expiry means, for both grant tables and for
34
+ * every statement that reads either — the point check, the subtree walk, and the two effective
35
+ * views alike. It is a fragment rather than part of `grantExists` because half the readers do not
36
+ * ask about a principal at all: `listEffectiveAccess` lists every grant along a path, and
37
+ * `callReach` asks only whether an organization-wide `execute` stands. Those had written the
38
+ * condition out by hand, which is exactly the second set of doors the header above warns about —
39
+ * a grace period, a different comparison, and the two spellings drift apart in silence.
40
+ *
41
+ * `git grep "expires_at IS NULL" -- packages/intel/src` must find this line and nothing else.
42
+ */
43
+ export declare const grantInForce = "(grant_row.expires_at IS NULL OR grant_row.expires_at > ?)";
44
+ export declare const subtreeCte: string;
45
+ export declare function subtreeBindings(actor: GrantActor, verb: ResourceVerb, now: string): unknown[];
46
+ /**
47
+ * One named folder and everything filed beneath it, appended to a statement that already carries
48
+ * `subtreeCte`. It answers "where", never "whether": a statement joins `descendants` *in addition
49
+ * to* `allowed`, so the scope can only take rows away from an answer the actor was already entitled
50
+ * to. Joining it instead of `allowed` would turn a search argument into a permission, which is
51
+ * exactly what ADR-0004 forbids — the caller supplies the id, and a caller is not a grant.
52
+ *
53
+ * ⚠️ `UNION`, never `UNION ALL`, for the reason `nodeVerbQuery` gives: a cycle in `parent_id` can
54
+ * reach the table and this walks downwards into it.
55
+ */
56
+ export declare const descendantsCte = "descendants(id) AS (\n SELECT scope.id FROM nodes scope WHERE scope.id = ?\n UNION\n SELECT child.id\n FROM nodes child\n JOIN descendants parent ON child.parent_id = parent.id\n )";
57
+ /**
58
+ * Both walks in one statement: what the actor may open, and what they may run. Its bindings are
59
+ * `subtreeBindings(actor, "read", now)` followed by `subtreeBindings(actor, "execute", now)`, in
60
+ * that order.
61
+ */
62
+ export declare const readableOrRunnableCte: string;
63
+ /**
64
+ * A flow the actor may open *or* may run, for a statement carrying `readableOrRunnableCte`. It is
65
+ * the callable rule of ADR-0004 §2/§3 — `execute` without `read` is the library, a building block
66
+ * anyone may run and few may open — asked of a whole list of flows at once instead of one flow at a
67
+ * time. Its bindings follow the two CTEs' and are `flowCallableBindings(actor, now)`.
68
+ *
69
+ * ⚠️ Both direct grants, and both are needed. A flow handed out on its own for `execute` alone is
70
+ * the library case shrunk to one flow — runnable by people who may not open it — and leaving `read`
71
+ * out here would hide a flow somebody was explicitly given to look at.
72
+ */
73
+ export declare const flowCallable: string;
74
+ export declare function flowCallableBindings(actor: GrantActor, now: string): unknown[];
75
+ /**
76
+ * The point check for one node: the node itself and every ancestor above it. Cheaper than
77
+ * the subtree walk and the same answer, because a grant reaches down and never sideways.
78
+ *
79
+ * ⚠️ `UNION`, never `UNION ALL`. The service refuses to move a node into its own descendant, but a
80
+ * cycle in `parent_id` can still reach the table — a restore, a repair, a future writer. `UNION ALL`
81
+ * would walk such a ring without end, and this query runs on the authorization path of every
82
+ * request, so the damage lands in the database rather than in one tab. `UNION` folds the repeated
83
+ * row away and the recursion stops on its own.
84
+ */
85
+ export declare const nodeVerbQuery: string;
86
+ export declare function nodeVerbBindings(nodeId: string, actor: GrantActor, verb: ResourceVerb, now: string): unknown[];
87
+ /**
88
+ * The same question for a flow. Three things reach it, and they are OR-ed rather than ranked: its
89
+ * owner, a grant on the folder it is filed in or any folder above that, and — since #530 — a grant
90
+ * that sits on the flow itself. Its owner keeps it the way a node's owner keeps theirs, otherwise a
91
+ * flow at the root of the tree would be unreachable by the person who created it. `UNION` for the
92
+ * same reason as above.
93
+ *
94
+ * ⚠️ The direct grant is asked BEFORE the ancestor walk, and only because it is cheaper: a point
95
+ * lookup on an indexed column against a recursive walk of the tree. It decides nothing the walk
96
+ * would have decided differently — both are the same OR.
97
+ */
98
+ export declare const flowVerbQuery: string;
99
+ export declare function flowVerbBindings(flowId: string, actor: GrantActor, verb: ResourceVerb, now: string): unknown[];
100
+ /**
101
+ * The predicate a query over `flows` uses when it already carries `subtreeCte` for the same verb.
102
+ * Its bindings follow the CTE's.
103
+ *
104
+ * ⚠️ The verb is now a binding of its own, and it has to be the SAME verb the CTE was seeded with.
105
+ * The walk answers "which folders", the direct grant answers "this flow" — asking them for two
106
+ * different verbs would produce a predicate that is neither, and it is the kind of mismatch nothing
107
+ * fails on: the query still runs and quietly hands out the wrong list.
108
+ */
109
+ /**
110
+ * ⚠️ The parent is a PARAMETER for exactly one caller, and giving it one was cheaper than the
111
+ * alternative. The feed reads events about flows that no longer exist: after `flows.purge` the row
112
+ * is gone, so `flow.parent_id`, `flow.owner_id` and every `flow_grants` row are gone with it, and
113
+ * the only anchor left is the `parentId` the purge wrote into its own metadata (#763, D73). A
114
+ * second, similar-looking predicate beside this one is the drift `destructive.md` warns about: two
115
+ * expressions with similar names are two different questions, and the one that goes stale is the
116
+ * copy.
117
+ */
118
+ /**
119
+ * ⚠️ **The ALIAS is a parameter too, since #767, and leaving it hard-wired was a real fault rather
120
+ * than an inelegance.** The feed's third branch asks about a flow reached through its RUN, so the
121
+ * flow sits under a second join (`run_flow`) while `flow` is NULL on that row. Against the
122
+ * hard-wired name the owner branch and the direct-grant branch both compared NULL — so a flow
123
+ * somebody OWNS but that hangs at the top level, with no folder above it to carry a grant, answered
124
+ * "not visible" to its own owner.
125
+ *
126
+ * ⚠️ It fails in the safe direction, which is exactly what made it worth a test rather than a
127
+ * comment: nothing leaks, a row is only ever missing. A flow filed in a readable FOLDER answers
128
+ * correctly through the third branch, so every test with a folder is green either way — and the two
129
+ * tests that caught it are the ones that seed a flow at the root.
130
+ */
131
+ export declare function flowInSubtreeOver(parentExpression: string, flowAlias?: string): string;
132
+ export declare const flowInSubtree: string;
133
+ export declare function flowInSubtreeBindings(actor: GrantActor, verb: ResourceVerb, now: string): unknown[];
@@ -0,0 +1,269 @@
1
+ export const grantColumns = (subjectColumn) => `id, ${subjectColumn} AS resource_id, principal_type, principal_id, verb, expires_at,
2
+ created_by, created_at`;
3
+ export function mapGrant(row) {
4
+ let principal;
5
+ switch (row.principal_type) {
6
+ case "user":
7
+ principal = { type: "user", id: row.principal_id };
8
+ break;
9
+ case "email":
10
+ principal = { type: "email", email: row.principal_id };
11
+ break;
12
+ case "organization":
13
+ principal = { type: "organization" };
14
+ break;
15
+ default:
16
+ throw new Error("Unsupported grant principal type");
17
+ }
18
+ return {
19
+ id: row.id,
20
+ resourceId: row.resource_id,
21
+ principal,
22
+ verb: row.verb,
23
+ expiresAt: row.expires_at,
24
+ createdBy: row.created_by,
25
+ createdAt: row.created_at,
26
+ };
27
+ }
28
+ // How a principal is written into the subject columns of both grant tables. An organization is the
29
+ // literal `*` every reader above matches on; an email is folded to lower case on the way in so the
30
+ // comparison there has one shape to worry about.
31
+ export function principalColumns(principal) {
32
+ if (principal.type === "user")
33
+ return { type: "user", id: principal.id };
34
+ if (principal.type === "email")
35
+ return { type: "email", id: principal.email.toLowerCase() };
36
+ return { type: "organization", id: "*" };
37
+ }
38
+ const principalMatch = `(
39
+ (grant_row.principal_type = 'user' AND grant_row.principal_id = ?)
40
+ OR (grant_row.principal_type = 'email' AND lower(grant_row.principal_id) = lower(?))
41
+ OR (grant_row.principal_type = 'organization' AND grant_row.principal_id = '*')
42
+ )`;
43
+ /**
44
+ * Whether a grant row is in force at the moment bound after it. Takes one binding: that moment.
45
+ *
46
+ * ⚠️ The ONE place this repository decides what an expiry means, for both grant tables and for
47
+ * every statement that reads either — the point check, the subtree walk, and the two effective
48
+ * views alike. It is a fragment rather than part of `grantExists` because half the readers do not
49
+ * ask about a principal at all: `listEffectiveAccess` lists every grant along a path, and
50
+ * `callReach` asks only whether an organization-wide `execute` stands. Those had written the
51
+ * condition out by hand, which is exactly the second set of doors the header above warns about —
52
+ * a grace period, a different comparison, and the two spellings drift apart in silence.
53
+ *
54
+ * `git grep "expires_at IS NULL" -- packages/intel/src` must find this line and nothing else.
55
+ */
56
+ export const grantInForce = "(grant_row.expires_at IS NULL OR grant_row.expires_at > ?)";
57
+ /**
58
+ * One grant row that is in force, asked of one table.
59
+ *
60
+ * ⚠️ The table is a parameter and the rest is not, which is the whole reason #530 could give a flow
61
+ * its own grants without a second set of doors. `flow_grants` and `node_grants` differ in their
62
+ * subject column and in nothing else — same principal shapes, same verbs, same expiry — so a
63
+ * question asked of one is asked of the other by the same string. A hand-written copy for flows is
64
+ * how one of the two would eventually stop honouring `expires_at`.
65
+ */
66
+ function grantExists(table, subjectColumn, targetColumn) {
67
+ return `EXISTS (
68
+ SELECT 1 FROM ${table} grant_row
69
+ WHERE grant_row.${subjectColumn} = ${targetColumn}
70
+ AND ${principalMatch}
71
+ AND grant_row.verb = ?
72
+ AND ${grantInForce}
73
+ )`;
74
+ }
75
+ const nodeGrantExists = (nodeColumn) => grantExists("node_grants", "node_id", nodeColumn);
76
+ // A grant that sits on the flow itself (#530), beside whatever its folder already passes down. It
77
+ // adds to the folder's reach and never replaces it, exactly as a grant on a document adds to its
78
+ // folder's — the two are OR-ed everywhere this appears.
79
+ const flowGrantExists = (flowColumn) => grantExists("flow_grants", "flow_id", flowColumn);
80
+ // Bindings for one `grantExists`: principal (twice), verb, and the moment expiry is measured
81
+ // against. Callers concatenate these in SQL order; the helpers below say which order that is.
82
+ function grantBindings(actor, verb, now) {
83
+ return [actor.id, actor.email, verb, now];
84
+ }
85
+ /**
86
+ * Seeds every node the actor reaches for one verb and walks downwards, which is what "a grant
87
+ * applies to everything beneath it" means in SQL. The name is a parameter because one statement can
88
+ * need two of these — asking `read` and `execute` at once is what turns "one query per callee" into
89
+ * one query (#30) — and a second walk written out by hand would be a second copy of the rule.
90
+ */
91
+ function subtreeWalk(name) {
92
+ return `${name}(id, parent_id) AS (
93
+ SELECT seed.id, seed.parent_id
94
+ FROM nodes seed
95
+ WHERE ? = 1
96
+ OR seed.owner_id = ?
97
+ OR ${nodeGrantExists("seed.id")}
98
+ UNION
99
+ SELECT child.id, child.parent_id
100
+ FROM nodes child
101
+ JOIN ${name} parent ON child.parent_id = parent.id
102
+ )`;
103
+ }
104
+ // The walk under the name every single-verb statement already uses. Callers append their own SELECT
105
+ // and may append further CTEs with a comma.
106
+ export const subtreeCte = `WITH RECURSIVE ${subtreeWalk("allowed")}`;
107
+ export function subtreeBindings(actor, verb, now) {
108
+ return [actor.isAdmin ? 1 : 0, actor.id, ...grantBindings(actor, verb, now)];
109
+ }
110
+ /**
111
+ * One named folder and everything filed beneath it, appended to a statement that already carries
112
+ * `subtreeCte`. It answers "where", never "whether": a statement joins `descendants` *in addition
113
+ * to* `allowed`, so the scope can only take rows away from an answer the actor was already entitled
114
+ * to. Joining it instead of `allowed` would turn a search argument into a permission, which is
115
+ * exactly what ADR-0004 forbids — the caller supplies the id, and a caller is not a grant.
116
+ *
117
+ * ⚠️ `UNION`, never `UNION ALL`, for the reason `nodeVerbQuery` gives: a cycle in `parent_id` can
118
+ * reach the table and this walks downwards into it.
119
+ */
120
+ export const descendantsCte = `descendants(id) AS (
121
+ SELECT scope.id FROM nodes scope WHERE scope.id = ?
122
+ UNION
123
+ SELECT child.id
124
+ FROM nodes child
125
+ JOIN descendants parent ON child.parent_id = parent.id
126
+ )`;
127
+ /**
128
+ * Both walks in one statement: what the actor may open, and what they may run. Its bindings are
129
+ * `subtreeBindings(actor, "read", now)` followed by `subtreeBindings(actor, "execute", now)`, in
130
+ * that order.
131
+ */
132
+ export const readableOrRunnableCte = `WITH RECURSIVE ${subtreeWalk("readable")},
133
+ ${subtreeWalk("runnable")}`;
134
+ /**
135
+ * A flow the actor may open *or* may run, for a statement carrying `readableOrRunnableCte`. It is
136
+ * the callable rule of ADR-0004 §2/§3 — `execute` without `read` is the library, a building block
137
+ * anyone may run and few may open — asked of a whole list of flows at once instead of one flow at a
138
+ * time. Its bindings follow the two CTEs' and are `flowCallableBindings(actor, now)`.
139
+ *
140
+ * ⚠️ Both direct grants, and both are needed. A flow handed out on its own for `execute` alone is
141
+ * the library case shrunk to one flow — runnable by people who may not open it — and leaving `read`
142
+ * out here would hide a flow somebody was explicitly given to look at.
143
+ */
144
+ export const flowCallable = `(
145
+ ? = 1
146
+ OR flow.owner_id = ?
147
+ OR flow.parent_id IN (SELECT id FROM readable)
148
+ OR flow.parent_id IN (SELECT id FROM runnable)
149
+ OR ${flowGrantExists("flow.id")}
150
+ OR ${flowGrantExists("flow.id")}
151
+ )`;
152
+ export function flowCallableBindings(actor, now) {
153
+ return [
154
+ actor.isAdmin ? 1 : 0,
155
+ actor.id,
156
+ ...grantBindings(actor, "read", now),
157
+ ...grantBindings(actor, "execute", now),
158
+ ];
159
+ }
160
+ /**
161
+ * The point check for one node: the node itself and every ancestor above it. Cheaper than
162
+ * the subtree walk and the same answer, because a grant reaches down and never sideways.
163
+ *
164
+ * ⚠️ `UNION`, never `UNION ALL`. The service refuses to move a node into its own descendant, but a
165
+ * cycle in `parent_id` can still reach the table — a restore, a repair, a future writer. `UNION ALL`
166
+ * would walk such a ring without end, and this query runs on the authorization path of every
167
+ * request, so the damage lands in the database rather than in one tab. `UNION` folds the repeated
168
+ * row away and the recursion stops on its own.
169
+ */
170
+ export const nodeVerbQuery = `WITH RECURSIVE ancestors(id, parent_id, owner_id) AS (
171
+ SELECT id, parent_id, owner_id FROM nodes WHERE id = ?
172
+ UNION
173
+ SELECT parent.id, parent.parent_id, parent.owner_id
174
+ FROM nodes parent
175
+ JOIN ancestors child ON child.parent_id = parent.id
176
+ )
177
+ SELECT 1 AS allowed
178
+ FROM ancestors
179
+ WHERE ? = 1
180
+ OR owner_id = ?
181
+ OR ${nodeGrantExists("ancestors.id")}
182
+ LIMIT 1`;
183
+ export function nodeVerbBindings(nodeId, actor, verb, now) {
184
+ return [nodeId, actor.isAdmin ? 1 : 0, actor.id, ...grantBindings(actor, verb, now)];
185
+ }
186
+ /**
187
+ * The same question for a flow. Three things reach it, and they are OR-ed rather than ranked: its
188
+ * owner, a grant on the folder it is filed in or any folder above that, and — since #530 — a grant
189
+ * that sits on the flow itself. Its owner keeps it the way a node's owner keeps theirs, otherwise a
190
+ * flow at the root of the tree would be unreachable by the person who created it. `UNION` for the
191
+ * same reason as above.
192
+ *
193
+ * ⚠️ The direct grant is asked BEFORE the ancestor walk, and only because it is cheaper: a point
194
+ * lookup on an indexed column against a recursive walk of the tree. It decides nothing the walk
195
+ * would have decided differently — both are the same OR.
196
+ */
197
+ export const flowVerbQuery = `WITH RECURSIVE ancestors(id, parent_id) AS (
198
+ SELECT folder.id, folder.parent_id
199
+ FROM nodes folder
200
+ JOIN flows flow ON flow.parent_id = folder.id
201
+ WHERE flow.id = ?
202
+ UNION
203
+ SELECT parent.id, parent.parent_id
204
+ FROM nodes parent
205
+ JOIN ancestors child ON child.parent_id = parent.id
206
+ )
207
+ SELECT 1 AS allowed
208
+ FROM flows flow
209
+ WHERE flow.id = ?
210
+ AND (
211
+ ? = 1
212
+ OR flow.owner_id = ?
213
+ OR ${flowGrantExists("flow.id")}
214
+ OR EXISTS (SELECT 1 FROM ancestors WHERE ${nodeGrantExists("ancestors.id")})
215
+ )
216
+ LIMIT 1`;
217
+ export function flowVerbBindings(flowId, actor, verb, now) {
218
+ return [
219
+ flowId,
220
+ flowId,
221
+ actor.isAdmin ? 1 : 0,
222
+ actor.id,
223
+ ...grantBindings(actor, verb, now),
224
+ ...grantBindings(actor, verb, now),
225
+ ];
226
+ }
227
+ /**
228
+ * The predicate a query over `flows` uses when it already carries `subtreeCte` for the same verb.
229
+ * Its bindings follow the CTE's.
230
+ *
231
+ * ⚠️ The verb is now a binding of its own, and it has to be the SAME verb the CTE was seeded with.
232
+ * The walk answers "which folders", the direct grant answers "this flow" — asking them for two
233
+ * different verbs would produce a predicate that is neither, and it is the kind of mismatch nothing
234
+ * fails on: the query still runs and quietly hands out the wrong list.
235
+ */
236
+ /**
237
+ * ⚠️ The parent is a PARAMETER for exactly one caller, and giving it one was cheaper than the
238
+ * alternative. The feed reads events about flows that no longer exist: after `flows.purge` the row
239
+ * is gone, so `flow.parent_id`, `flow.owner_id` and every `flow_grants` row are gone with it, and
240
+ * the only anchor left is the `parentId` the purge wrote into its own metadata (#763, D73). A
241
+ * second, similar-looking predicate beside this one is the drift `destructive.md` warns about: two
242
+ * expressions with similar names are two different questions, and the one that goes stale is the
243
+ * copy.
244
+ */
245
+ /**
246
+ * ⚠️ **The ALIAS is a parameter too, since #767, and leaving it hard-wired was a real fault rather
247
+ * than an inelegance.** The feed's third branch asks about a flow reached through its RUN, so the
248
+ * flow sits under a second join (`run_flow`) while `flow` is NULL on that row. Against the
249
+ * hard-wired name the owner branch and the direct-grant branch both compared NULL — so a flow
250
+ * somebody OWNS but that hangs at the top level, with no folder above it to carry a grant, answered
251
+ * "not visible" to its own owner.
252
+ *
253
+ * ⚠️ It fails in the safe direction, which is exactly what made it worth a test rather than a
254
+ * comment: nothing leaks, a row is only ever missing. A flow filed in a readable FOLDER answers
255
+ * correctly through the third branch, so every test with a folder is green either way — and the two
256
+ * tests that caught it are the ones that seed a flow at the root.
257
+ */
258
+ export function flowInSubtreeOver(parentExpression, flowAlias = "flow") {
259
+ return `(
260
+ ? = 1
261
+ OR ${flowAlias}.owner_id = ?
262
+ OR ${parentExpression} IN (SELECT id FROM allowed)
263
+ OR ${flowGrantExists(`${flowAlias}.id`)}
264
+ )`;
265
+ }
266
+ export const flowInSubtree = flowInSubtreeOver("flow.parent_id");
267
+ export function flowInSubtreeBindings(actor, verb, now) {
268
+ return [actor.isAdmin ? 1 : 0, actor.id, ...grantBindings(actor, verb, now)];
269
+ }
@@ -0,0 +1,3 @@
1
+ import type { NodeIndexRepository } from "../../nodes/nodes.types.js";
2
+ import type { D1Database } from "./db.types.js";
3
+ export declare function createNodeIndexRepository(db: D1Database): NodeIndexRepository;
@@ -0,0 +1,200 @@
1
+ // How much of a chunk is kept as the passage a searcher is shown. The same 480 characters
2
+ // `hydrateVisibleCitations` cuts out of the full-text row, so a citation reads the same length
3
+ // whether it came from a card's own vector or from the fallback beside it.
4
+ const maxPassageCharacters = 480;
5
+ /**
6
+ * What goes into the `content` column beside the title (#816).
7
+ *
8
+ * The description is a sentence ABOUT the node, not a name, so it belongs on the side the query
9
+ * weights at 1.0 and draws its snippet from — `bm25(node_fts, 0.0, 0.0, 4.0, 1.0)` and
10
+ * `snippet(node_fts, 3, ...)` in `searchVisible`. In `title` it would let a sentence outrank a
11
+ * matching name at four times the weight, and a description-only match would then produce a snippet
12
+ * cut from a body the searcher's word never appears in.
13
+ *
14
+ * ⚠️ Applied to every chunk, and that is deliberately NOT the rule the title follows on the vector
15
+ * side. There a prefix lands in the fingerprint, so putting it on every chunk would cost one
16
+ * embedding per chunk each time the node is renamed (anchrd/intel#301); a full-text row has neither
17
+ * a fingerprint nor an embedding behind it, so the reason for the restriction does not exist here.
18
+ * Since #390 every kind brings exactly one chunk anyway.
19
+ */
20
+ function indexedContent(description, text) {
21
+ const sentence = description?.trim() ?? "";
22
+ if (sentence === "")
23
+ return text;
24
+ return text === "" ? sentence : `${sentence}\n\n${text}`;
25
+ }
26
+ function mapTarget(row, contentKeys) {
27
+ return {
28
+ nodeId: row.node_id,
29
+ versionId: row.version_id,
30
+ title: row.title,
31
+ description: row.description,
32
+ contentKeys,
33
+ mediaType: row.media_type,
34
+ size: row.size,
35
+ kind: row.kind,
36
+ updatedAt: row.updated_at,
37
+ };
38
+ }
39
+ export function createNodeIndexRepository(db) {
40
+ return {
41
+ async getTarget(versionId) {
42
+ const row = await db
43
+ .prepare(`SELECT n.id AS node_id, v.id AS version_id, n.title, n.description, v.content_key,
44
+ v.media_type, v.size, n.kind, n.updated_at
45
+ FROM node_versions v
46
+ JOIN nodes n ON n.id = v.node_id
47
+ WHERE v.id = ? AND n.current_version_id = v.id AND n.archived_at IS NULL`)
48
+ .bind(versionId)
49
+ .first();
50
+ if (!row)
51
+ return null;
52
+ if (row.kind !== "table")
53
+ return mapTarget(row, [row.content_key]);
54
+ // A table's rows live in every segment since the newest snapshot, so indexing the newest one
55
+ // alone would make a search find only what the last append added (#40) — and indexing the
56
+ // segments before a snapshot would make it find rows a mutation removed (#135). The same
57
+ // reading rule as `listVersionContentKeys`, because the index must describe what a read sees.
58
+ const segments = await db
59
+ .prepare(`SELECT content_key FROM node_versions
60
+ WHERE node_id = ? AND sequence >= COALESCE((
61
+ SELECT MAX(sequence) FROM node_versions
62
+ WHERE node_id = ? AND segment = 'snapshot'
63
+ ), 0)
64
+ ORDER BY sequence`)
65
+ .bind(row.node_id, row.node_id)
66
+ .all();
67
+ return mapTarget(row, (segments.results ?? []).map((segment) => segment.content_key));
68
+ },
69
+ /**
70
+ * Every passage of this node, replacing every passage it had.
71
+ *
72
+ * ⚠️ One row per chunk, in ONE batch with the delete in front of it. Most kinds bring exactly
73
+ * one chunk and this is the statement pair it always was; a board brings one per task (#285),
74
+ * and a search can then answer with the card that matched rather than with the whole board.
75
+ *
76
+ * ⚠️ A board with no tasks writes no row at all, and that is right: there is nothing to find in
77
+ * it. What must not happen is the delete landing without the inserts, which is why they share a
78
+ * batch — D1 runs it as one transaction.
79
+ */
80
+ async replace(target, chunks) {
81
+ await db.batch([
82
+ db
83
+ .prepare(`DELETE FROM node_fts WHERE node_id = ? AND EXISTS (
84
+ SELECT 1 FROM nodes
85
+ WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
86
+ )`)
87
+ .bind(target.nodeId, target.nodeId, target.versionId),
88
+ ...chunks.map((chunk) => db
89
+ .prepare(`INSERT INTO node_fts (version_id, node_id, title, content)
90
+ SELECT ?, ?, ?, ? WHERE EXISTS (
91
+ SELECT 1 FROM nodes
92
+ WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
93
+ )`)
94
+ .bind(target.versionId, target.nodeId, chunk.title, indexedContent(target.description, chunk.text), target.nodeId, target.versionId)),
95
+ ]);
96
+ },
97
+ /**
98
+ * ⚠️ `archived_at IS NOT NULL` carries this on its own, and it is the whole guard
99
+ * (anchrd/intel#348). `getTarget` above answers null for two reasons and only one of them means
100
+ * "purge": the other is a queue message for a version a later save superseded, and Cloudflare
101
+ * Queues deliver at least once, so those arrive as a matter of course. Acting on one would empty
102
+ * the vector index of a live board behind the back of the save that had just filled it — and
103
+ * nothing would report it, because a search that finds less is not an error.
104
+ *
105
+ * ⚠️ Deliberately NOT `current_version_id = v.id` as well, although that is what would make this
106
+ * the mirror image of `getTarget`. Symmetry is not a reason to write a condition: once the node
107
+ * IS archived, every version of it says the same thing about that node's vectors, so the extra
108
+ * clause could only ever refuse a purge that was right. A condition no test can turn red is a
109
+ * condition the next reader has to guess the purpose of.
110
+ */
111
+ async archivedNodeId(versionId) {
112
+ const row = await db
113
+ .prepare(`SELECT n.id AS node_id
114
+ FROM node_versions v
115
+ JOIN nodes n ON n.id = v.node_id
116
+ WHERE v.id = ? AND n.archived_at IS NOT NULL`)
117
+ .bind(versionId)
118
+ .first();
119
+ return row?.node_id ?? null;
120
+ },
121
+ async listVectors(nodeId) {
122
+ const result = await db
123
+ .prepare(`SELECT chunk_key, fingerprint, passage FROM node_vectors WHERE node_id = ?`)
124
+ .bind(nodeId)
125
+ .all();
126
+ return (result.results ?? []).map((row) => ({
127
+ chunkKey: row.chunk_key,
128
+ fingerprint: row.fingerprint,
129
+ passage: row.passage,
130
+ }));
131
+ },
132
+ /**
133
+ * The record of every vector this node has, replacing the record it had.
134
+ *
135
+ * ⚠️ The same delete-then-insert in ONE batch as `replace` above, and the same guard in front of
136
+ * every statement: a pass for a version that is no longer current, or for a node that has been
137
+ * archived meanwhile, writes nothing at all rather than half a record. What must not happen is
138
+ * the delete landing without the inserts — the next pass would then re-embed the whole board,
139
+ * which is exactly the cost anchrd/intel#301 exists to avoid.
140
+ */
141
+ async replaceVectors(target, records) {
142
+ await db.batch([
143
+ db
144
+ .prepare(`DELETE FROM node_vectors WHERE node_id = ? AND EXISTS (
145
+ SELECT 1 FROM nodes
146
+ WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
147
+ )`)
148
+ .bind(target.nodeId, target.nodeId, target.versionId),
149
+ ...records.map((record) => db
150
+ .prepare(`INSERT INTO node_vectors (node_id, chunk_key, version_id, fingerprint, passage)
151
+ SELECT ?, ?, ?, ?, ? WHERE EXISTS (
152
+ SELECT 1 FROM nodes
153
+ WHERE id = ? AND current_version_id = ? AND archived_at IS NULL
154
+ )`)
155
+ .bind(target.nodeId, record.chunkKey, target.versionId, record.fingerprint, record.passage.slice(0, maxPassageCharacters), target.nodeId, target.versionId)),
156
+ ]);
157
+ },
158
+ /**
159
+ * The record of one node, gone — because its vectors are (anchrd/intel#348).
160
+ *
161
+ * ⚠️ No guard at all, and the absence is deliberate in both directions. There is no
162
+ * `archived_at IS NOT NULL`: the state this write exists to prevent is a row that outlives the
163
+ * vector it names, and a node restored between the deletion and this statement would keep
164
+ * exactly that — a record claiming vectors that are gone, which the next pass reads as "already
165
+ * embedded" and skips, leaving those cards silently unfindable. And there is no version
166
+ * condition: `archivedNodeId` has already established which node this is, from that same
167
+ * version, and a second reading of a moving row is a second answer rather than a safer one.
168
+ */
169
+ async deleteVectors(nodeId) {
170
+ await db.prepare("DELETE FROM node_vectors WHERE node_id = ?").bind(nodeId).run();
171
+ },
172
+ async markIndexed(versionId, occurredAt) {
173
+ await db
174
+ .prepare(`UPDATE node_index_state
175
+ SET status = 'indexed', attempt_count = attempt_count + 1,
176
+ last_error = NULL, updated_at = ?
177
+ WHERE version_id = ? AND EXISTS (
178
+ SELECT 1 FROM node_versions version
179
+ JOIN nodes node ON node.id = version.node_id
180
+ WHERE version.id = ? AND node.current_version_id = version.id
181
+ AND node.archived_at IS NULL
182
+ )`)
183
+ .bind(occurredAt, versionId, versionId)
184
+ .run();
185
+ },
186
+ async markError(versionId, message, occurredAt) {
187
+ await db
188
+ .prepare(`UPDATE node_index_state
189
+ SET status = 'error', attempt_count = attempt_count + 1,
190
+ last_error = ?, updated_at = ? WHERE version_id = ? AND EXISTS (
191
+ SELECT 1 FROM node_versions version
192
+ JOIN nodes node ON node.id = version.node_id
193
+ WHERE version.id = ? AND node.current_version_id = version.id
194
+ AND node.archived_at IS NULL
195
+ )`)
196
+ .bind(message.slice(0, 500), occurredAt, versionId, versionId)
197
+ .run();
198
+ },
199
+ };
200
+ }
@@ -0,0 +1,3 @@
1
+ import type { OAuthClientStore } from "../../auth/auth.types.js";
2
+ import type { D1Database } from "./db.types.js";
3
+ export declare function createOAuthClientStore(db: D1Database): OAuthClientStore;
@@ -0,0 +1,26 @@
1
+ export function createOAuthClientStore(db) {
2
+ return {
3
+ async get(issuer, redirectUri) {
4
+ const row = await db
5
+ .prepare("SELECT client_id FROM oauth_clients WHERE issuer = ? AND redirect_uri = ?")
6
+ .bind(issuer, redirectUri)
7
+ .first();
8
+ return row?.client_id ?? null;
9
+ },
10
+ async put(input) {
11
+ await db
12
+ .prepare(`INSERT OR IGNORE INTO oauth_clients (
13
+ issuer, redirect_uri, client_id, created_at
14
+ ) VALUES (?, ?, ?, ?)`)
15
+ .bind(input.issuer, input.redirectUri, input.clientId, input.createdAt)
16
+ .run();
17
+ const stored = await db
18
+ .prepare("SELECT client_id FROM oauth_clients WHERE issuer = ? AND redirect_uri = ?")
19
+ .bind(input.issuer, input.redirectUri)
20
+ .first();
21
+ if (!stored)
22
+ throw new Error("OAuth client registration was not stored");
23
+ return stored.client_id;
24
+ },
25
+ };
26
+ }