@atomicmemory/core 1.0.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 (589) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +201 -0
  3. package/README.md +314 -0
  4. package/dist/app/bind-ephemeral.d.ts +18 -0
  5. package/dist/app/bind-ephemeral.js +22 -0
  6. package/dist/app/cors-headers.d.ts +12 -0
  7. package/dist/app/cors-headers.js +18 -0
  8. package/dist/app/create-app.d.ts +25 -0
  9. package/dist/app/create-app.js +156 -0
  10. package/dist/app/runtime-config-route-snapshot.d.ts +27 -0
  11. package/dist/app/runtime-config-route-snapshot.js +27 -0
  12. package/dist/app/runtime-container.d.ts +281 -0
  13. package/dist/app/runtime-container.js +297 -0
  14. package/dist/app/startup-checks.d.ts +28 -0
  15. package/dist/app/startup-checks.js +45 -0
  16. package/dist/bin.d.ts +17 -0
  17. package/dist/bin.js +128 -0
  18. package/dist/config.d.ts +680 -0
  19. package/dist/config.js +808 -0
  20. package/dist/db/agent-trust-repository.d.ts +49 -0
  21. package/dist/db/agent-trust-repository.js +66 -0
  22. package/dist/db/belief-edges-repository.d.ts +68 -0
  23. package/dist/db/belief-edges-repository.js +124 -0
  24. package/dist/db/claim-repository.d.ts +6 -0
  25. package/dist/db/claim-repository.js +4 -0
  26. package/dist/db/contradictions-repository.d.ts +56 -0
  27. package/dist/db/contradictions-repository.js +88 -0
  28. package/dist/db/document-chunk-repository.d.ts +48 -0
  29. package/dist/db/document-chunk-repository.js +145 -0
  30. package/dist/db/document-chunk-types.d.ts +35 -0
  31. package/dist/db/document-chunk-types.js +9 -0
  32. package/dist/db/document-list-cursor.d.ts +45 -0
  33. package/dist/db/document-list-cursor.js +111 -0
  34. package/dist/db/document-list-repository.d.ts +103 -0
  35. package/dist/db/document-list-repository.js +204 -0
  36. package/dist/db/entity-cards-repository.d.ts +37 -0
  37. package/dist/db/entity-cards-repository.js +46 -0
  38. package/dist/db/entity-values-repository.d.ts +26 -0
  39. package/dist/db/entity-values-repository.js +57 -0
  40. package/dist/db/link-repository.d.ts +30 -0
  41. package/dist/db/link-repository.js +54 -0
  42. package/dist/db/memory-repository.d.ts +163 -0
  43. package/dist/db/memory-repository.js +232 -0
  44. package/dist/db/migrate.d.ts +6 -0
  45. package/dist/db/migrate.js +36 -0
  46. package/dist/db/mmr.d.ts +14 -0
  47. package/dist/db/mmr.js +57 -0
  48. package/dist/db/passport-feed-repository.d.ts +91 -0
  49. package/dist/db/passport-feed-repository.js +198 -0
  50. package/dist/db/pg-episode-store.d.ts +19 -0
  51. package/dist/db/pg-episode-store.js +17 -0
  52. package/dist/db/pg-link-store.d.ts +17 -0
  53. package/dist/db/pg-link-store.js +14 -0
  54. package/dist/db/pg-memory-store.d.ts +68 -0
  55. package/dist/db/pg-memory-store.js +53 -0
  56. package/dist/db/pg-recap-store.d.ts +13 -0
  57. package/dist/db/pg-recap-store.js +19 -0
  58. package/dist/db/pg-representation-store.d.ts +17 -0
  59. package/dist/db/pg-representation-store.js +17 -0
  60. package/dist/db/pg-search-store.d.ts +29 -0
  61. package/dist/db/pg-search-store.js +47 -0
  62. package/dist/db/pool.d.ts +5 -0
  63. package/dist/db/pool.js +21 -0
  64. package/dist/db/ppr.d.ts +56 -0
  65. package/dist/db/ppr.js +178 -0
  66. package/dist/db/query-helpers.d.ts +44 -0
  67. package/dist/db/query-helpers.js +60 -0
  68. package/dist/db/raw-doc-artifact-sync.d.ts +128 -0
  69. package/dist/db/raw-doc-artifact-sync.js +259 -0
  70. package/dist/db/raw-document-blob-repository.d.ts +148 -0
  71. package/dist/db/raw-document-blob-repository.js +300 -0
  72. package/dist/db/raw-document-repository.d.ts +104 -0
  73. package/dist/db/raw-document-repository.js +410 -0
  74. package/dist/db/raw-document-status-repository.d.ts +122 -0
  75. package/dist/db/raw-document-status-repository.js +183 -0
  76. package/dist/db/raw-document-types.d.ts +236 -0
  77. package/dist/db/raw-document-types.js +10 -0
  78. package/dist/db/raw-storage-reconciliation-repository.d.ts +110 -0
  79. package/dist/db/raw-storage-reconciliation-repository.js +200 -0
  80. package/dist/db/reflection-jobs-repository.d.ts +33 -0
  81. package/dist/db/reflection-jobs-repository.js +48 -0
  82. package/dist/db/reflections-repository.d.ts +41 -0
  83. package/dist/db/reflections-repository.js +83 -0
  84. package/dist/db/repository-claims.d.ts +141 -0
  85. package/dist/db/repository-claims.js +376 -0
  86. package/dist/db/repository-deferred-audn.d.ts +33 -0
  87. package/dist/db/repository-deferred-audn.js +69 -0
  88. package/dist/db/repository-document-delete.d.ts +53 -0
  89. package/dist/db/repository-document-delete.js +156 -0
  90. package/dist/db/repository-entities.d.ts +114 -0
  91. package/dist/db/repository-entities.js +317 -0
  92. package/dist/db/repository-entity-attributes.d.ts +41 -0
  93. package/dist/db/repository-entity-attributes.js +65 -0
  94. package/dist/db/repository-entity-graph.d.ts +32 -0
  95. package/dist/db/repository-entity-graph.js +87 -0
  96. package/dist/db/repository-first-mentions.d.ts +41 -0
  97. package/dist/db/repository-first-mentions.js +79 -0
  98. package/dist/db/repository-lessons.d.ts +51 -0
  99. package/dist/db/repository-lessons.js +90 -0
  100. package/dist/db/repository-links.d.ts +26 -0
  101. package/dist/db/repository-links.js +105 -0
  102. package/dist/db/repository-observation.d.ts +26 -0
  103. package/dist/db/repository-observation.js +51 -0
  104. package/dist/db/repository-read.d.ts +56 -0
  105. package/dist/db/repository-read.js +271 -0
  106. package/dist/db/repository-recaps.d.ts +59 -0
  107. package/dist/db/repository-recaps.js +158 -0
  108. package/dist/db/repository-representations.d.ts +48 -0
  109. package/dist/db/repository-representations.js +162 -0
  110. package/dist/db/repository-temporal-state.d.ts +35 -0
  111. package/dist/db/repository-temporal-state.js +46 -0
  112. package/dist/db/repository-tll.d.ts +88 -0
  113. package/dist/db/repository-tll.js +179 -0
  114. package/dist/db/repository-types.d.ts +313 -0
  115. package/dist/db/repository-types.js +142 -0
  116. package/dist/db/repository-user-profiles.d.ts +17 -0
  117. package/dist/db/repository-user-profiles.js +28 -0
  118. package/dist/db/repository-vector-search.d.ts +33 -0
  119. package/dist/db/repository-vector-search.js +373 -0
  120. package/dist/db/repository-wipe.d.ts +34 -0
  121. package/dist/db/repository-wipe.js +94 -0
  122. package/dist/db/repository-write.d.ts +61 -0
  123. package/dist/db/repository-write.js +279 -0
  124. package/dist/db/schema.sql +1355 -0
  125. package/dist/db/storage-artifact-delete-tx.d.ts +56 -0
  126. package/dist/db/storage-artifact-delete-tx.js +123 -0
  127. package/dist/db/storage-artifact-providers.d.ts +21 -0
  128. package/dist/db/storage-artifact-providers.js +21 -0
  129. package/dist/db/storage-artifact-recovery-repository.d.ts +66 -0
  130. package/dist/db/storage-artifact-recovery-repository.js +58 -0
  131. package/dist/db/storage-artifact-repository.d.ts +329 -0
  132. package/dist/db/storage-artifact-repository.js +497 -0
  133. package/dist/db/stores.d.ts +220 -0
  134. package/dist/db/stores.js +12 -0
  135. package/dist/db/summaries-repository.d.ts +74 -0
  136. package/dist/db/summaries-repository.js +125 -0
  137. package/dist/eval/beam-10m-loader.d.ts +98 -0
  138. package/dist/eval/beam-10m-loader.js +128 -0
  139. package/dist/index.d.ts +18 -0
  140. package/dist/index.js +17 -0
  141. package/dist/middleware/require-bearer.d.ts +27 -0
  142. package/dist/middleware/require-bearer.js +60 -0
  143. package/dist/middleware/validate-response.d.ts +33 -0
  144. package/dist/middleware/validate-response.js +55 -0
  145. package/dist/middleware/validate.d.ts +43 -0
  146. package/dist/middleware/validate.js +85 -0
  147. package/dist/routes/agents.d.ts +13 -0
  148. package/dist/routes/agents.js +89 -0
  149. package/dist/routes/document-response-formatters.d.ts +98 -0
  150. package/dist/routes/document-response-formatters.js +243 -0
  151. package/dist/routes/documents.d.ts +74 -0
  152. package/dist/routes/documents.js +425 -0
  153. package/dist/routes/memories.d.ts +29 -0
  154. package/dist/routes/memories.js +725 -0
  155. package/dist/routes/memory-response-formatters.d.ts +179 -0
  156. package/dist/routes/memory-response-formatters.js +210 -0
  157. package/dist/routes/public-raw-storage-metadata.d.ts +54 -0
  158. package/dist/routes/public-raw-storage-metadata.js +56 -0
  159. package/dist/routes/reflect.d.ts +14 -0
  160. package/dist/routes/reflect.js +19 -0
  161. package/dist/routes/response-schema-map.d.ts +14 -0
  162. package/dist/routes/response-schema-map.js +69 -0
  163. package/dist/routes/route-errors.d.ts +12 -0
  164. package/dist/routes/route-errors.js +30 -0
  165. package/dist/routes/storage-error-handlers.d.ts +34 -0
  166. package/dist/routes/storage-error-handlers.js +185 -0
  167. package/dist/routes/storage-response-formatters.d.ts +44 -0
  168. package/dist/routes/storage-response-formatters.js +155 -0
  169. package/dist/routes/storage.d.ts +38 -0
  170. package/dist/routes/storage.js +369 -0
  171. package/dist/routes/upstream-provider-errors.d.ts +19 -0
  172. package/dist/routes/upstream-provider-errors.js +95 -0
  173. package/dist/schemas/agents.d.ts +79 -0
  174. package/dist/schemas/agents.js +126 -0
  175. package/dist/schemas/common.d.ts +110 -0
  176. package/dist/schemas/common.js +190 -0
  177. package/dist/schemas/document-list-responses.d.ts +102 -0
  178. package/dist/schemas/document-list-responses.js +87 -0
  179. package/dist/schemas/document-list-schemas.d.ts +123 -0
  180. package/dist/schemas/document-list-schemas.js +174 -0
  181. package/dist/schemas/document-response-schemas.d.ts +610 -0
  182. package/dist/schemas/document-response-schemas.js +264 -0
  183. package/dist/schemas/document-status-envelope.d.ts +48 -0
  184. package/dist/schemas/document-status-envelope.js +54 -0
  185. package/dist/schemas/documents.d.ts +292 -0
  186. package/dist/schemas/documents.js +449 -0
  187. package/dist/schemas/errors.d.ts +75 -0
  188. package/dist/schemas/errors.js +105 -0
  189. package/dist/schemas/memories.d.ts +378 -0
  190. package/dist/schemas/memories.js +542 -0
  191. package/dist/schemas/openapi.d.ts +24 -0
  192. package/dist/schemas/openapi.js +1038 -0
  193. package/dist/schemas/response-scalars.d.ts +10 -0
  194. package/dist/schemas/response-scalars.js +10 -0
  195. package/dist/schemas/responses.d.ts +536 -0
  196. package/dist/schemas/responses.js +350 -0
  197. package/dist/schemas/search-response-parts.d.ts +97 -0
  198. package/dist/schemas/search-response-parts.js +103 -0
  199. package/dist/schemas/storage-schemas.d.ts +175 -0
  200. package/dist/schemas/storage-schemas.js +277 -0
  201. package/dist/schemas/zod-setup.d.ts +15 -0
  202. package/dist/schemas/zod-setup.js +17 -0
  203. package/dist/server.d.ts +13 -0
  204. package/dist/server.js +57 -0
  205. package/dist/services/abstract-query-policy.d.ts +13 -0
  206. package/dist/services/abstract-query-policy.js +50 -0
  207. package/dist/services/affinity-clustering.d.ts +66 -0
  208. package/dist/services/affinity-clustering.js +125 -0
  209. package/dist/services/agentic-retrieval.d.ts +38 -0
  210. package/dist/services/agentic-retrieval.js +126 -0
  211. package/dist/services/answer-format.d.ts +56 -0
  212. package/dist/services/answer-format.js +118 -0
  213. package/dist/services/answer-rescue.d.ts +72 -0
  214. package/dist/services/answer-rescue.js +177 -0
  215. package/dist/services/answer-verifier.d.ts +24 -0
  216. package/dist/services/answer-verifier.js +73 -0
  217. package/dist/services/api-retry.d.ts +6 -0
  218. package/dist/services/api-retry.js +41 -0
  219. package/dist/services/assistant-turn-filter.d.ts +20 -0
  220. package/dist/services/assistant-turn-filter.js +69 -0
  221. package/dist/services/atomicmem-uri.d.ts +33 -0
  222. package/dist/services/atomicmem-uri.js +86 -0
  223. package/dist/services/audit-events.d.ts +54 -0
  224. package/dist/services/audit-events.js +56 -0
  225. package/dist/services/chunked-extraction.d.ts +21 -0
  226. package/dist/services/chunked-extraction.js +108 -0
  227. package/dist/services/claim-slotting.d.ts +27 -0
  228. package/dist/services/claim-slotting.js +38 -0
  229. package/dist/services/claude-code-llm.d.ts +19 -0
  230. package/dist/services/claude-code-llm.js +96 -0
  231. package/dist/services/composite-dedup.d.ts +50 -0
  232. package/dist/services/composite-dedup.js +153 -0
  233. package/dist/services/composite-grouping.d.ts +41 -0
  234. package/dist/services/composite-grouping.js +111 -0
  235. package/dist/services/composite-staleness.d.ts +20 -0
  236. package/dist/services/composite-staleness.js +50 -0
  237. package/dist/services/conciseness-preference.d.ts +14 -0
  238. package/dist/services/conciseness-preference.js +42 -0
  239. package/dist/services/conflict-policy.d.ts +20 -0
  240. package/dist/services/conflict-policy.js +335 -0
  241. package/dist/services/consensus-extraction.d.ts +39 -0
  242. package/dist/services/consensus-extraction.js +147 -0
  243. package/dist/services/consensus-validation.d.ts +52 -0
  244. package/dist/services/consensus-validation.js +206 -0
  245. package/dist/services/consolidation-service.d.ts +60 -0
  246. package/dist/services/consolidation-service.js +171 -0
  247. package/dist/services/content-detection.d.ts +18 -0
  248. package/dist/services/content-detection.js +25 -0
  249. package/dist/services/contradiction-surfacing.d.ts +62 -0
  250. package/dist/services/contradiction-surfacing.js +111 -0
  251. package/dist/services/cost-telemetry.d.ts +39 -0
  252. package/dist/services/cost-telemetry.js +58 -0
  253. package/dist/services/counter-evidence.d.ts +34 -0
  254. package/dist/services/counter-evidence.js +92 -0
  255. package/dist/services/current-state-ranking.d.ts +21 -0
  256. package/dist/services/current-state-ranking.js +152 -0
  257. package/dist/services/deferred-audn.d.ts +47 -0
  258. package/dist/services/deferred-audn.js +162 -0
  259. package/dist/services/document-chunker.d.ts +50 -0
  260. package/dist/services/document-chunker.js +153 -0
  261. package/dist/services/document-failure-markers.d.ts +91 -0
  262. package/dist/services/document-failure-markers.js +305 -0
  263. package/dist/services/document-indexer.d.ts +122 -0
  264. package/dist/services/document-indexer.js +405 -0
  265. package/dist/services/document-service.d.ts +245 -0
  266. package/dist/services/document-service.js +325 -0
  267. package/dist/services/document-upload-artifact-sync.d.ts +80 -0
  268. package/dist/services/document-upload-artifact-sync.js +162 -0
  269. package/dist/services/document-upload-beta2-recovery.d.ts +72 -0
  270. package/dist/services/document-upload-beta2-recovery.js +94 -0
  271. package/dist/services/document-upload.d.ts +44 -0
  272. package/dist/services/document-upload.js +353 -0
  273. package/dist/services/embedding.d.ts +57 -0
  274. package/dist/services/embedding.js +416 -0
  275. package/dist/services/entity-attribute-extractor.d.ts +34 -0
  276. package/dist/services/entity-attribute-extractor.js +117 -0
  277. package/dist/services/entity-card-synthesis.d.ts +54 -0
  278. package/dist/services/entity-card-synthesis.js +92 -0
  279. package/dist/services/entity-dedup.d.ts +9 -0
  280. package/dist/services/entity-dedup.js +14 -0
  281. package/dist/services/entity-graph.d.ts +17 -0
  282. package/dist/services/entity-graph.js +135 -0
  283. package/dist/services/entropy-gate.d.ts +52 -0
  284. package/dist/services/entropy-gate.js +56 -0
  285. package/dist/services/episode-fetcher.d.ts +47 -0
  286. package/dist/services/episode-fetcher.js +128 -0
  287. package/dist/services/event-anchor-facts.d.ts +8 -0
  288. package/dist/services/event-anchor-facts.js +205 -0
  289. package/dist/services/event-chain-detector.d.ts +52 -0
  290. package/dist/services/event-chain-detector.js +83 -0
  291. package/dist/services/extraction-cache.d.ts +9 -0
  292. package/dist/services/extraction-cache.js +54 -0
  293. package/dist/services/extraction-enrichment.d.ts +9 -0
  294. package/dist/services/extraction-enrichment.js +223 -0
  295. package/dist/services/extraction.d.ts +69 -0
  296. package/dist/services/extraction.js +596 -0
  297. package/dist/services/fact-normalization.d.ts +12 -0
  298. package/dist/services/fact-normalization.js +248 -0
  299. package/dist/services/filecoin-observability.d.ts +127 -0
  300. package/dist/services/filecoin-observability.js +200 -0
  301. package/dist/services/first-mention-service.d.ts +76 -0
  302. package/dist/services/first-mention-service.js +186 -0
  303. package/dist/services/hierarchical-retrieval.d.ts +49 -0
  304. package/dist/services/hierarchical-retrieval.js +50 -0
  305. package/dist/services/ingest-fact-pipeline.d.ts +32 -0
  306. package/dist/services/ingest-fact-pipeline.js +212 -0
  307. package/dist/services/ingest-post-write.d.ts +50 -0
  308. package/dist/services/ingest-post-write.js +117 -0
  309. package/dist/services/ingest-trace.d.ts +32 -0
  310. package/dist/services/ingest-trace.js +60 -0
  311. package/dist/services/input-sanitizer.d.ts +41 -0
  312. package/dist/services/input-sanitizer.js +135 -0
  313. package/dist/services/iterative-retrieval.d.ts +26 -0
  314. package/dist/services/iterative-retrieval.js +139 -0
  315. package/dist/services/keyword-expansion.d.ts +10 -0
  316. package/dist/services/keyword-expansion.js +26 -0
  317. package/dist/services/lesson-service.d.ts +68 -0
  318. package/dist/services/lesson-service.js +178 -0
  319. package/dist/services/literal-extractor.d.ts +16 -0
  320. package/dist/services/literal-extractor.js +74 -0
  321. package/dist/services/literal-list-protection.d.ts +17 -0
  322. package/dist/services/literal-list-protection.js +134 -0
  323. package/dist/services/literal-query-expansion.d.ts +20 -0
  324. package/dist/services/literal-query-expansion.js +181 -0
  325. package/dist/services/llm.d.ts +61 -0
  326. package/dist/services/llm.js +265 -0
  327. package/dist/services/memcell-projection.d.ts +17 -0
  328. package/dist/services/memcell-projection.js +41 -0
  329. package/dist/services/memory-audn.d.ts +43 -0
  330. package/dist/services/memory-audn.js +419 -0
  331. package/dist/services/memory-crud.d.ts +93 -0
  332. package/dist/services/memory-crud.js +255 -0
  333. package/dist/services/memory-ingest.d.ts +21 -0
  334. package/dist/services/memory-ingest.js +249 -0
  335. package/dist/services/memory-lifecycle.d.ts +75 -0
  336. package/dist/services/memory-lifecycle.js +108 -0
  337. package/dist/services/memory-lineage.d.ts +181 -0
  338. package/dist/services/memory-lineage.js +232 -0
  339. package/dist/services/memory-network.d.ts +40 -0
  340. package/dist/services/memory-network.js +75 -0
  341. package/dist/services/memory-search-types.d.ts +25 -0
  342. package/dist/services/memory-search-types.js +10 -0
  343. package/dist/services/memory-search.d.ts +48 -0
  344. package/dist/services/memory-search.js +505 -0
  345. package/dist/services/memory-service-types.d.ts +371 -0
  346. package/dist/services/memory-service-types.js +8 -0
  347. package/dist/services/memory-service.d.ts +152 -0
  348. package/dist/services/memory-service.js +225 -0
  349. package/dist/services/memory-storage.d.ts +33 -0
  350. package/dist/services/memory-storage.js +328 -0
  351. package/dist/services/msr-aggregator.d.ts +38 -0
  352. package/dist/services/msr-aggregator.js +97 -0
  353. package/dist/services/msr-detector.d.ts +35 -0
  354. package/dist/services/msr-detector.js +65 -0
  355. package/dist/services/namespace-retrieval.d.ts +60 -0
  356. package/dist/services/namespace-retrieval.js +180 -0
  357. package/dist/services/observation-date-extraction.d.ts +12 -0
  358. package/dist/services/observation-date-extraction.js +50 -0
  359. package/dist/services/observation-service.d.ts +27 -0
  360. package/dist/services/observation-service.js +84 -0
  361. package/dist/services/packaging-observability.d.ts +29 -0
  362. package/dist/services/packaging-observability.js +146 -0
  363. package/dist/services/query-expansion.d.ts +83 -0
  364. package/dist/services/query-expansion.js +242 -0
  365. package/dist/services/query-keyword-matches.d.ts +6 -0
  366. package/dist/services/query-keyword-matches.js +56 -0
  367. package/dist/services/query-term-visibility.d.ts +28 -0
  368. package/dist/services/query-term-visibility.js +100 -0
  369. package/dist/services/quick-extraction.d.ts +25 -0
  370. package/dist/services/quick-extraction.js +431 -0
  371. package/dist/services/quoted-entity-extraction.d.ts +10 -0
  372. package/dist/services/quoted-entity-extraction.js +161 -0
  373. package/dist/services/raw-storage-reconciler-backoff.d.ts +8 -0
  374. package/dist/services/raw-storage-reconciler-backoff.js +14 -0
  375. package/dist/services/raw-storage-reconciler-scheduler.d.ts +29 -0
  376. package/dist/services/raw-storage-reconciler-scheduler.js +43 -0
  377. package/dist/services/raw-storage-reconciler.d.ts +71 -0
  378. package/dist/services/raw-storage-reconciler.js +278 -0
  379. package/dist/services/recap-builder.d.ts +49 -0
  380. package/dist/services/recap-builder.js +157 -0
  381. package/dist/services/reflect-jobs.d.ts +23 -0
  382. package/dist/services/reflect-jobs.js +36 -0
  383. package/dist/services/reflect-prompts.d.ts +71 -0
  384. package/dist/services/reflect-prompts.js +99 -0
  385. package/dist/services/reflect-retrieval.d.ts +33 -0
  386. package/dist/services/reflect-retrieval.js +30 -0
  387. package/dist/services/reflect.d.ts +49 -0
  388. package/dist/services/reflect.js +84 -0
  389. package/dist/services/relative-temporal.d.ts +14 -0
  390. package/dist/services/relative-temporal.js +163 -0
  391. package/dist/services/relevance-policy.d.ts +37 -0
  392. package/dist/services/relevance-policy.js +109 -0
  393. package/dist/services/rerank.d.ts +32 -0
  394. package/dist/services/rerank.js +118 -0
  395. package/dist/services/reranker.d.ts +20 -0
  396. package/dist/services/reranker.js +99 -0
  397. package/dist/services/retrieval-channel-rules.d.ts +34 -0
  398. package/dist/services/retrieval-channel-rules.js +41 -0
  399. package/dist/services/retrieval-config-overlay.d.ts +36 -0
  400. package/dist/services/retrieval-config-overlay.js +44 -0
  401. package/dist/services/retrieval-format.d.ts +119 -0
  402. package/dist/services/retrieval-format.js +559 -0
  403. package/dist/services/retrieval-policy.d.ts +69 -0
  404. package/dist/services/retrieval-policy.js +275 -0
  405. package/dist/services/retrieval-profiles.d.ts +37 -0
  406. package/dist/services/retrieval-profiles.js +90 -0
  407. package/dist/services/retrieval-side-effects.d.ts +14 -0
  408. package/dist/services/retrieval-side-effects.js +26 -0
  409. package/dist/services/retrieval-trace.d.ts +108 -0
  410. package/dist/services/retrieval-trace.js +147 -0
  411. package/dist/services/rrf-fusion.d.ts +18 -0
  412. package/dist/services/rrf-fusion.js +34 -0
  413. package/dist/services/search-pipeline.d.ts +71 -0
  414. package/dist/services/search-pipeline.js +788 -0
  415. package/dist/services/session-date.d.ts +20 -0
  416. package/dist/services/session-date.js +61 -0
  417. package/dist/services/session-packaging.d.ts +53 -0
  418. package/dist/services/session-packaging.js +182 -0
  419. package/dist/services/session-summary-generator.d.ts +53 -0
  420. package/dist/services/session-summary-generator.js +134 -0
  421. package/dist/services/specialists/cr-specialist.d.ts +52 -0
  422. package/dist/services/specialists/cr-specialist.js +121 -0
  423. package/dist/services/specialists/dispatch.d.ts +53 -0
  424. package/dist/services/specialists/dispatch.js +102 -0
  425. package/dist/services/specialists/ie-ku-specialist.d.ts +37 -0
  426. package/dist/services/specialists/ie-ku-specialist.js +63 -0
  427. package/dist/services/specialists/msr-specialist.d.ts +61 -0
  428. package/dist/services/specialists/msr-specialist.js +162 -0
  429. package/dist/services/specialists/tr-specialist.d.ts +37 -0
  430. package/dist/services/specialists/tr-specialist.js +146 -0
  431. package/dist/services/storage-key-prefix.d.ts +42 -0
  432. package/dist/services/storage-key-prefix.js +45 -0
  433. package/dist/services/storage-put-recovery.d.ts +71 -0
  434. package/dist/services/storage-put-recovery.js +269 -0
  435. package/dist/services/storage-service-errors.d.ts +124 -0
  436. package/dist/services/storage-service-errors.js +189 -0
  437. package/dist/services/storage-service.d.ts +176 -0
  438. package/dist/services/storage-service.js +423 -0
  439. package/dist/services/subject-aware-ranking.d.ts +19 -0
  440. package/dist/services/subject-aware-ranking.js +161 -0
  441. package/dist/services/supplemental-extraction.d.ts +7 -0
  442. package/dist/services/supplemental-extraction.js +116 -0
  443. package/dist/services/tbc-execution.d.ts +49 -0
  444. package/dist/services/tbc-execution.js +284 -0
  445. package/dist/services/temporal-classifier.d.ts +56 -0
  446. package/dist/services/temporal-classifier.js +94 -0
  447. package/dist/services/temporal-endpoint-evidence.d.ts +12 -0
  448. package/dist/services/temporal-endpoint-evidence.js +313 -0
  449. package/dist/services/temporal-fingerprint.d.ts +6 -0
  450. package/dist/services/temporal-fingerprint.js +12 -0
  451. package/dist/services/temporal-format.d.ts +9 -0
  452. package/dist/services/temporal-format.js +21 -0
  453. package/dist/services/temporal-intent.d.ts +39 -0
  454. package/dist/services/temporal-intent.js +78 -0
  455. package/dist/services/temporal-query-constraints.d.ts +16 -0
  456. package/dist/services/temporal-query-constraints.js +107 -0
  457. package/dist/services/temporal-query-expansion.d.ts +14 -0
  458. package/dist/services/temporal-query-expansion.js +131 -0
  459. package/dist/services/temporal-rerank.d.ts +22 -0
  460. package/dist/services/temporal-rerank.js +47 -0
  461. package/dist/services/temporal-result-protection.d.ts +7 -0
  462. package/dist/services/temporal-result-protection.js +60 -0
  463. package/dist/services/temporal-state-write.d.ts +57 -0
  464. package/dist/services/temporal-state-write.js +45 -0
  465. package/dist/services/tiered-context.d.ts +87 -0
  466. package/dist/services/tiered-context.js +214 -0
  467. package/dist/services/tiered-loading.d.ts +88 -0
  468. package/dist/services/tiered-loading.js +263 -0
  469. package/dist/services/timeline-pack.d.ts +36 -0
  470. package/dist/services/timeline-pack.js +50 -0
  471. package/dist/services/timing.d.ts +13 -0
  472. package/dist/services/timing.js +72 -0
  473. package/dist/services/tll-augmentation.d.ts +20 -0
  474. package/dist/services/tll-augmentation.js +125 -0
  475. package/dist/services/tll-retrieval.d.ts +55 -0
  476. package/dist/services/tll-retrieval.js +101 -0
  477. package/dist/services/topic-abstraction.d.ts +36 -0
  478. package/dist/services/topic-abstraction.js +105 -0
  479. package/dist/services/trust-scoring.d.ts +43 -0
  480. package/dist/services/trust-scoring.js +89 -0
  481. package/dist/services/typed-belief-calculus.d.ts +126 -0
  482. package/dist/services/typed-belief-calculus.js +204 -0
  483. package/dist/services/upload-config.d.ts +34 -0
  484. package/dist/services/upload-config.js +23 -0
  485. package/dist/services/upload-decision.d.ts +65 -0
  486. package/dist/services/upload-decision.js +98 -0
  487. package/dist/services/upload-helpers.d.ts +107 -0
  488. package/dist/services/upload-helpers.js +148 -0
  489. package/dist/services/user-profile-builder.d.ts +22 -0
  490. package/dist/services/user-profile-builder.js +109 -0
  491. package/dist/services/voyage-embedding.d.ts +22 -0
  492. package/dist/services/voyage-embedding.js +77 -0
  493. package/dist/services/write-security.d.ts +31 -0
  494. package/dist/services/write-security.js +64 -0
  495. package/dist/storage/artifact-public-redaction.d.ts +34 -0
  496. package/dist/storage/artifact-public-redaction.js +83 -0
  497. package/dist/storage/cleanup.d.ts +103 -0
  498. package/dist/storage/cleanup.js +138 -0
  499. package/dist/storage/codec-factory.d.ts +17 -0
  500. package/dist/storage/codec-factory.js +33 -0
  501. package/dist/storage/codecs/aes-gcm-codec.d.ts +44 -0
  502. package/dist/storage/codecs/aes-gcm-codec.js +108 -0
  503. package/dist/storage/codecs/noop-codec.d.ts +16 -0
  504. package/dist/storage/codecs/noop-codec.js +23 -0
  505. package/dist/storage/factory.d.ts +44 -0
  506. package/dist/storage/factory.js +99 -0
  507. package/dist/storage/filecoin-cid-validation.d.ts +82 -0
  508. package/dist/storage/filecoin-cid-validation.js +122 -0
  509. package/dist/storage/filecoin-public-metadata.d.ts +73 -0
  510. package/dist/storage/filecoin-public-metadata.js +110 -0
  511. package/dist/storage/local-fs-store.d.ts +39 -0
  512. package/dist/storage/local-fs-store.js +145 -0
  513. package/dist/storage/pointer-uri-allowlist.d.ts +38 -0
  514. package/dist/storage/pointer-uri-allowlist.js +70 -0
  515. package/dist/storage/provider-metadata-projection.d.ts +27 -0
  516. package/dist/storage/provider-metadata-projection.js +68 -0
  517. package/dist/storage/providers/filecoin/backend.d.ts +42 -0
  518. package/dist/storage/providers/filecoin/backend.js +250 -0
  519. package/dist/storage/providers/filecoin/config.d.ts +70 -0
  520. package/dist/storage/providers/filecoin/config.js +275 -0
  521. package/dist/storage/providers/filecoin/errors.d.ts +45 -0
  522. package/dist/storage/providers/filecoin/errors.js +56 -0
  523. package/dist/storage/providers/filecoin/filecoin-pin-car.d.ts +78 -0
  524. package/dist/storage/providers/filecoin/filecoin-pin-car.js +155 -0
  525. package/dist/storage/providers/filecoin/filecoin-pin-client.d.ts +92 -0
  526. package/dist/storage/providers/filecoin/filecoin-pin-client.js +199 -0
  527. package/dist/storage/providers/filecoin/filecoin-pin-mapping.d.ts +58 -0
  528. package/dist/storage/providers/filecoin/filecoin-pin-mapping.js +103 -0
  529. package/dist/storage/providers/filecoin/filecoin-pin-timeout.d.ts +30 -0
  530. package/dist/storage/providers/filecoin/filecoin-pin-timeout.js +53 -0
  531. package/dist/storage/providers/filecoin/filecoin-pin-vendor.d.ts +111 -0
  532. package/dist/storage/providers/filecoin/filecoin-pin-vendor.js +87 -0
  533. package/dist/storage/providers/filecoin/hints.d.ts +71 -0
  534. package/dist/storage/providers/filecoin/hints.js +123 -0
  535. package/dist/storage/providers/filecoin/index.d.ts +51 -0
  536. package/dist/storage/providers/filecoin/index.js +103 -0
  537. package/dist/storage/providers/filecoin/ipfs-cid.d.ts +50 -0
  538. package/dist/storage/providers/filecoin/ipfs-cid.js +64 -0
  539. package/dist/storage/providers/filecoin/metadata.d.ts +72 -0
  540. package/dist/storage/providers/filecoin/metadata.js +137 -0
  541. package/dist/storage/providers/filecoin/piece-cid.d.ts +48 -0
  542. package/dist/storage/providers/filecoin/piece-cid.js +57 -0
  543. package/dist/storage/providers/filecoin/provider-client.d.ts +234 -0
  544. package/dist/storage/providers/filecoin/provider-client.js +27 -0
  545. package/dist/storage/providers/filecoin/readiness.d.ts +62 -0
  546. package/dist/storage/providers/filecoin/readiness.js +85 -0
  547. package/dist/storage/providers/filecoin/retriever.d.ts +82 -0
  548. package/dist/storage/providers/filecoin/retriever.js +63 -0
  549. package/dist/storage/providers/filecoin/skeleton-client.d.ts +36 -0
  550. package/dist/storage/providers/filecoin/skeleton-client.js +55 -0
  551. package/dist/storage/providers/filecoin/synapse-client.d.ts +169 -0
  552. package/dist/storage/providers/filecoin/synapse-client.js +343 -0
  553. package/dist/storage/providers/filecoin/synapse-construction.d.ts +26 -0
  554. package/dist/storage/providers/filecoin/synapse-construction.js +47 -0
  555. package/dist/storage/providers/filecoin/synapse-error-mapping.d.ts +23 -0
  556. package/dist/storage/providers/filecoin/synapse-error-mapping.js +49 -0
  557. package/dist/storage/providers/filecoin/synapse-readiness.d.ts +37 -0
  558. package/dist/storage/providers/filecoin/synapse-readiness.js +231 -0
  559. package/dist/storage/providers/filecoin/uri.d.ts +49 -0
  560. package/dist/storage/providers/filecoin/uri.js +84 -0
  561. package/dist/storage/providers/filecoin/verified-fetch-lifecycle.d.ts +77 -0
  562. package/dist/storage/providers/filecoin/verified-fetch-lifecycle.js +196 -0
  563. package/dist/storage/providers/filecoin/verified-fetch-retriever.d.ts +54 -0
  564. package/dist/storage/providers/filecoin/verified-fetch-retriever.js +81 -0
  565. package/dist/storage/providers/filecoin/verified-fetch-vendor.d.ts +71 -0
  566. package/dist/storage/providers/filecoin/verified-fetch-vendor.js +94 -0
  567. package/dist/storage/raw-content-codec.d.ts +89 -0
  568. package/dist/storage/raw-content-codec.js +47 -0
  569. package/dist/storage/raw-content-store-backend-adapter.d.ts +28 -0
  570. package/dist/storage/raw-content-store-backend-adapter.js +67 -0
  571. package/dist/storage/raw-content-store.d.ts +228 -0
  572. package/dist/storage/raw-content-store.js +27 -0
  573. package/dist/storage/s3-store.d.ts +42 -0
  574. package/dist/storage/s3-store.js +181 -0
  575. package/dist/storage/storage-backend-registry.d.ts +58 -0
  576. package/dist/storage/storage-backend-registry.js +56 -0
  577. package/dist/storage/storage-backend.d.ts +82 -0
  578. package/dist/storage/storage-backend.js +14 -0
  579. package/dist/storage/storage-capabilities.d.ts +56 -0
  580. package/dist/storage/storage-capabilities.js +170 -0
  581. package/dist/storage/store-registry.d.ts +67 -0
  582. package/dist/storage/store-registry.js +77 -0
  583. package/dist/vector-math.d.ts +15 -0
  584. package/dist/vector-math.js +31 -0
  585. package/dist/xml-escape.d.ts +5 -0
  586. package/dist/xml-escape.js +7 -0
  587. package/openapi.json +15395 -0
  588. package/openapi.yaml +10794 -0
  589. package/package.json +119 -0
@@ -0,0 +1,1355 @@
1
+ /**
2
+ * atomicmemory-core Schema — active memory projection plus contradiction-safe
3
+ * claim/version history. Idempotent: safe to re-run on every startup.
4
+ *
5
+ * IMPORTANT: This schema uses CREATE TABLE/INDEX IF NOT EXISTS so it can run
6
+ * on every app startup without data loss. Adding new columns to existing tables
7
+ * requires explicit ALTER TABLE ... ADD COLUMN IF NOT EXISTS statements — a
8
+ * plain column definition inside CREATE TABLE IF NOT EXISTS will be silently
9
+ * ignored if the table already exists.
10
+ */
11
+
12
+ CREATE EXTENSION IF NOT EXISTS vector;
13
+ CREATE EXTENSION IF NOT EXISTS pgcrypto;
14
+
15
+ CREATE TABLE IF NOT EXISTS episodes (
16
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
17
+ user_id TEXT NOT NULL,
18
+ content TEXT NOT NULL,
19
+ source_site TEXT NOT NULL,
20
+ source_url TEXT NOT NULL DEFAULT '',
21
+ session_id TEXT,
22
+ workspace_id UUID DEFAULT NULL,
23
+ agent_id UUID DEFAULT NULL,
24
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_episodes_user_site ON episodes (user_id, source_site);
28
+
29
+ CREATE TABLE IF NOT EXISTS canonical_memory_objects (
30
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
31
+ user_id TEXT NOT NULL,
32
+ object_family TEXT NOT NULL
33
+ CHECK (object_family IN ('ingested_fact')),
34
+ payload_format TEXT NOT NULL DEFAULT 'json',
35
+ canonical_payload JSONB NOT NULL,
36
+ provenance JSONB NOT NULL DEFAULT '{}',
37
+ observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
38
+ lineage JSONB NOT NULL DEFAULT '{}',
39
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
40
+ );
41
+
42
+ CREATE INDEX IF NOT EXISTS idx_canonical_memory_objects_user_created
43
+ ON canonical_memory_objects (user_id, created_at DESC);
44
+
45
+ CREATE TABLE IF NOT EXISTS memories (
46
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
47
+ user_id TEXT NOT NULL,
48
+ content TEXT NOT NULL,
49
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
50
+ memory_type TEXT NOT NULL DEFAULT 'semantic'
51
+ CHECK (memory_type IN ('episodic', 'semantic', 'procedural', 'composite')),
52
+ importance REAL NOT NULL DEFAULT 0.5
53
+ CHECK (importance >= 0.0 AND importance <= 1.0),
54
+ source_site TEXT NOT NULL,
55
+ source_url TEXT NOT NULL DEFAULT '',
56
+ episode_id UUID, -- FK to episodes removed: non-transactional writes with pgvector can't guarantee ordering
57
+ status TEXT NOT NULL DEFAULT 'active'
58
+ CHECK (status IN ('active', 'needs_clarification')),
59
+ metadata JSONB DEFAULT '{}',
60
+ keywords TEXT NOT NULL DEFAULT '',
61
+ namespace TEXT DEFAULT NULL,
62
+ summary TEXT NOT NULL DEFAULT '', -- L0: abstract/headline (~100 tokens)
63
+ overview TEXT NOT NULL DEFAULT '', -- L1: condensed overview (~1000 tokens)
64
+ trust_score REAL NOT NULL DEFAULT 1.0 -- Phase 3: source + content trust (0.0–1.0)
65
+ CHECK (trust_score >= 0.0 AND trust_score <= 1.0),
66
+ observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- when the conversation actually happened (vs created_at = DB insertion time)
67
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
68
+ last_accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
69
+ access_count INTEGER NOT NULL DEFAULT 0,
70
+ expired_at TIMESTAMPTZ DEFAULT NULL, -- Phase 4: set when superseded (temporal invalidation)
71
+ deleted_at TIMESTAMPTZ DEFAULT NULL,
72
+ -- Phase 7: 4-network memory separation (Hindsight-inspired)
73
+ network TEXT NOT NULL DEFAULT 'experience'
74
+ CHECK (network IN ('world', 'experience', 'opinion', 'observation')),
75
+ opinion_confidence REAL DEFAULT NULL
76
+ CHECK (opinion_confidence IS NULL OR (opinion_confidence >= 0.0 AND opinion_confidence <= 1.0)),
77
+ observation_subject TEXT DEFAULT NULL,
78
+ -- Phase 8: deferred AUDN reconciliation
79
+ deferred_audn BOOLEAN NOT NULL DEFAULT false,
80
+ audn_candidates JSONB DEFAULT NULL, -- serialized candidates for background reconciliation
81
+ -- Phase 9: workspace / multi-agent scoping
82
+ workspace_id UUID DEFAULT NULL,
83
+ agent_id UUID DEFAULT NULL,
84
+ visibility TEXT DEFAULT NULL
85
+ CHECK (visibility IS NULL OR visibility IN ('agent_only', 'restricted', 'workspace'))
86
+ );
87
+
88
+ CREATE INDEX IF NOT EXISTS idx_memories_deferred_audn ON memories (user_id)
89
+ WHERE deferred_audn = true AND deleted_at IS NULL;
90
+
91
+ CREATE INDEX IF NOT EXISTS idx_memories_user_site ON memories (user_id, source_site)
92
+ WHERE deleted_at IS NULL AND expired_at IS NULL;
93
+ CREATE INDEX IF NOT EXISTS idx_memories_user_created ON memories (user_id, created_at)
94
+ WHERE deleted_at IS NULL AND expired_at IS NULL;
95
+
96
+ CREATE INDEX IF NOT EXISTS idx_memories_embedding ON memories
97
+ USING hnsw (embedding vector_cosine_ops)
98
+ WITH (m = 16, ef_construction = 200);
99
+
100
+ -- Full-text search: indexes both paraphrased content AND extracted keywords.
101
+ -- Keywords preserve proper nouns, dates, and project names that paraphrasing loses.
102
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS search_vector tsvector
103
+ GENERATED ALWAYS AS (
104
+ to_tsvector('english', content) || to_tsvector('simple', keywords)
105
+ ) STORED;
106
+
107
+ CREATE INDEX IF NOT EXISTS idx_memories_fts ON memories USING gin (search_vector)
108
+ WHERE deleted_at IS NULL AND expired_at IS NULL;
109
+
110
+ CREATE INDEX IF NOT EXISTS idx_memories_namespace ON memories (namespace)
111
+ WHERE deleted_at IS NULL AND expired_at IS NULL AND namespace IS NOT NULL;
112
+
113
+ CREATE INDEX IF NOT EXISTS idx_memories_network ON memories (user_id, network)
114
+ WHERE deleted_at IS NULL AND expired_at IS NULL;
115
+
116
+ CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories (workspace_id, agent_id)
117
+ WHERE deleted_at IS NULL AND expired_at IS NULL AND workspace_id IS NOT NULL;
118
+
119
+ -- Visibility grants for restricted memories (workspace scoping)
120
+ CREATE TABLE IF NOT EXISTS memory_visibility_grants (
121
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
122
+ memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
123
+ grantee_agent_id UUID NOT NULL,
124
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
125
+ UNIQUE (memory_id, grantee_agent_id)
126
+ );
127
+
128
+ CREATE INDEX IF NOT EXISTS idx_visibility_grants_memory ON memory_visibility_grants (memory_id);
129
+ CREATE INDEX IF NOT EXISTS idx_visibility_grants_agent ON memory_visibility_grants (grantee_agent_id);
130
+
131
+ CREATE INDEX IF NOT EXISTS idx_memories_observation_subject ON memories (user_id, observation_subject)
132
+ WHERE network = 'observation' AND deleted_at IS NULL AND expired_at IS NULL;
133
+
134
+ -- Workspace columns added via ALTER TABLE at the bottom of this file (Phase 5 Step 9).
135
+ CREATE TABLE IF NOT EXISTS memory_atomic_facts (
136
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
137
+ user_id TEXT NOT NULL,
138
+ parent_memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
139
+ fact_text TEXT NOT NULL,
140
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
141
+ fact_type TEXT NOT NULL DEFAULT 'knowledge'
142
+ CHECK (fact_type IN ('preference', 'project', 'knowledge', 'person', 'plan')),
143
+ importance REAL NOT NULL DEFAULT 0.5
144
+ CHECK (importance >= 0.0 AND importance <= 1.0),
145
+ source_site TEXT NOT NULL,
146
+ source_url TEXT NOT NULL DEFAULT '',
147
+ episode_id UUID,
148
+ keywords TEXT NOT NULL DEFAULT '',
149
+ metadata JSONB NOT NULL DEFAULT '{}',
150
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
151
+ );
152
+
153
+ CREATE INDEX IF NOT EXISTS idx_memory_atomic_facts_parent ON memory_atomic_facts (parent_memory_id);
154
+ CREATE INDEX IF NOT EXISTS idx_memory_atomic_facts_user ON memory_atomic_facts (user_id, created_at DESC);
155
+ CREATE INDEX IF NOT EXISTS idx_memory_atomic_facts_embedding ON memory_atomic_facts
156
+ USING hnsw (embedding vector_cosine_ops)
157
+ WITH (m = 16, ef_construction = 200);
158
+
159
+ ALTER TABLE memory_atomic_facts ADD COLUMN IF NOT EXISTS search_vector tsvector
160
+ GENERATED ALWAYS AS (
161
+ to_tsvector('english', fact_text) || to_tsvector('simple', keywords)
162
+ ) STORED;
163
+
164
+ CREATE INDEX IF NOT EXISTS idx_memory_atomic_facts_fts ON memory_atomic_facts USING gin (search_vector);
165
+
166
+ -- Workspace columns added via ALTER TABLE at the bottom of this file (Phase 5 Step 9).
167
+ CREATE TABLE IF NOT EXISTS memory_foresight (
168
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
169
+ user_id TEXT NOT NULL,
170
+ parent_memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
171
+ content TEXT NOT NULL,
172
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
173
+ foresight_type TEXT NOT NULL DEFAULT 'plan'
174
+ CHECK (foresight_type IN ('plan', 'goal', 'scheduled', 'expected_state')),
175
+ source_site TEXT NOT NULL,
176
+ source_url TEXT NOT NULL DEFAULT '',
177
+ episode_id UUID,
178
+ metadata JSONB NOT NULL DEFAULT '{}',
179
+ valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
180
+ valid_to TIMESTAMPTZ DEFAULT NULL,
181
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
182
+ );
183
+
184
+ CREATE INDEX IF NOT EXISTS idx_memory_foresight_parent ON memory_foresight (parent_memory_id);
185
+ CREATE INDEX IF NOT EXISTS idx_memory_foresight_user_valid ON memory_foresight (user_id, valid_from, valid_to);
186
+ CREATE INDEX IF NOT EXISTS idx_memory_foresight_embedding ON memory_foresight
187
+ USING hnsw (embedding vector_cosine_ops)
188
+ WITH (m = 16, ef_construction = 200);
189
+
190
+ -- Observation regeneration trigger (async, decoupled from ingest)
191
+ CREATE TABLE IF NOT EXISTS observation_dirty (
192
+ user_id TEXT NOT NULL,
193
+ subject TEXT NOT NULL,
194
+ marked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
195
+ PRIMARY KEY (user_id, subject)
196
+ );
197
+
198
+ -- SCOPE_TODO: Claims are intentionally user-scoped — AUDN contradiction resolution
199
+ -- is cross-workspace. Workspace-scoped claims are a Phase 8+ concern.
200
+ CREATE TABLE IF NOT EXISTS memory_claims (
201
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
202
+ user_id TEXT NOT NULL,
203
+ claim_type TEXT NOT NULL DEFAULT 'fact',
204
+ status TEXT NOT NULL DEFAULT 'active'
205
+ CHECK (status IN ('active', 'deleted')),
206
+ current_version_id UUID DEFAULT NULL,
207
+ slot_key TEXT DEFAULT NULL,
208
+ subject_entity_id UUID DEFAULT NULL,
209
+ relation_type TEXT DEFAULT NULL
210
+ CHECK (relation_type IS NULL OR relation_type IN (
211
+ 'uses', 'works_on', 'works_at', 'located_in', 'knows',
212
+ 'prefers', 'created', 'belongs_to', 'studies', 'manages'
213
+ )),
214
+ object_entity_id UUID DEFAULT NULL,
215
+ valid_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
216
+ invalid_at TIMESTAMPTZ DEFAULT NULL,
217
+ invalidated_at TIMESTAMPTZ DEFAULT NULL,
218
+ invalidated_by_version_id UUID DEFAULT NULL,
219
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
220
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
221
+ CHECK (invalid_at IS NULL OR invalid_at >= valid_at)
222
+ );
223
+
224
+ CREATE INDEX IF NOT EXISTS idx_memory_claims_user ON memory_claims (user_id);
225
+ CREATE INDEX IF NOT EXISTS idx_memory_claims_user_valid
226
+ ON memory_claims (user_id, valid_at, invalid_at);
227
+ CREATE INDEX IF NOT EXISTS idx_memory_claims_user_slot
228
+ ON memory_claims (user_id, slot_key)
229
+ WHERE slot_key IS NOT NULL;
230
+
231
+ -- SCOPE_TODO: Claim versions inherit user-scoped claim ownership — same rationale as memory_claims.
232
+ CREATE TABLE IF NOT EXISTS memory_claim_versions (
233
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
234
+ claim_id UUID NOT NULL REFERENCES memory_claims(id) ON DELETE CASCADE,
235
+ user_id TEXT NOT NULL,
236
+ memory_id UUID UNIQUE REFERENCES memories(id) ON DELETE SET NULL,
237
+ content TEXT NOT NULL,
238
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
239
+ importance REAL NOT NULL DEFAULT 0.5
240
+ CHECK (importance >= 0.0 AND importance <= 1.0),
241
+ source_site TEXT NOT NULL,
242
+ source_url TEXT NOT NULL DEFAULT '',
243
+ episode_id UUID /* REFERENCES episodes(id) ON DELETE SET NULL -- removed for non-transactional pgvector compat */,
244
+ valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
245
+ valid_to TIMESTAMPTZ DEFAULT NULL,
246
+ superseded_by_version_id UUID DEFAULT NULL,
247
+ mutation_type TEXT DEFAULT NULL
248
+ CHECK (mutation_type IS NULL OR mutation_type IN ('add', 'update', 'supersede', 'delete', 'clarify')),
249
+ mutation_reason TEXT DEFAULT NULL,
250
+ previous_version_id UUID DEFAULT NULL,
251
+ actor_model TEXT DEFAULT NULL,
252
+ contradiction_confidence REAL DEFAULT NULL,
253
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
254
+ );
255
+
256
+ CREATE INDEX IF NOT EXISTS idx_memory_claim_versions_claim ON memory_claim_versions (claim_id);
257
+ CREATE INDEX IF NOT EXISTS idx_memory_claim_versions_user_valid
258
+ ON memory_claim_versions (user_id, valid_from, valid_to);
259
+ CREATE INDEX IF NOT EXISTS idx_memory_claim_versions_claim_valid
260
+ ON memory_claim_versions (claim_id, valid_from, valid_to);
261
+
262
+ CREATE INDEX IF NOT EXISTS idx_memory_claim_versions_embedding ON memory_claim_versions
263
+ USING hnsw (embedding vector_cosine_ops)
264
+ WITH (m = 16, ef_construction = 200);
265
+
266
+ CREATE TABLE IF NOT EXISTS memory_evidence (
267
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
268
+ claim_version_id UUID NOT NULL
269
+ REFERENCES memory_claim_versions(id) ON DELETE CASCADE,
270
+ episode_id UUID /* REFERENCES episodes(id) ON DELETE SET NULL -- removed for non-transactional pgvector compat */,
271
+ memory_id UUID REFERENCES memories(id) ON DELETE SET NULL,
272
+ quote_text TEXT NOT NULL DEFAULT '',
273
+ speaker TEXT DEFAULT NULL,
274
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
275
+ );
276
+
277
+ CREATE INDEX IF NOT EXISTS idx_memory_evidence_version ON memory_evidence (claim_version_id);
278
+
279
+ -- Memory links for 1-hop link expansion (Phase 2, A-MEM style)
280
+ -- Bidirectional: stored as (source_id, target_id) where source_id < target_id
281
+ -- to avoid duplicate pairs. Query both directions at read time.
282
+ CREATE TABLE IF NOT EXISTS memory_links (
283
+ source_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
284
+ target_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
285
+ similarity REAL NOT NULL CHECK (similarity >= 0.0 AND similarity <= 1.0),
286
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
287
+ PRIMARY KEY (source_id, target_id)
288
+ );
289
+
290
+ CREATE INDEX IF NOT EXISTS idx_memory_links_target ON memory_links (target_id);
291
+
292
+ -- Phase 5: Entity graph — structured entities extracted from memories
293
+ -- SCOPE_TODO: Entities are user-global (entity dedup crosses workspace boundaries).
294
+ CREATE TABLE IF NOT EXISTS entities (
295
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
296
+ user_id TEXT NOT NULL,
297
+ name TEXT NOT NULL,
298
+ normalized_name TEXT NOT NULL,
299
+ entity_type TEXT NOT NULL
300
+ CHECK (entity_type IN ('person', 'tool', 'project', 'organization', 'place', 'concept')),
301
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
302
+ alias_names TEXT[] NOT NULL DEFAULT '{}',
303
+ normalized_alias_names TEXT[] NOT NULL DEFAULT '{}',
304
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
305
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
306
+ );
307
+
308
+ CREATE INDEX IF NOT EXISTS idx_entities_user ON entities (user_id);
309
+ CREATE INDEX IF NOT EXISTS idx_entities_user_type ON entities (user_id, entity_type);
310
+ CREATE INDEX IF NOT EXISTS idx_entities_user_normalized
311
+ ON entities (user_id, entity_type, normalized_name);
312
+ CREATE INDEX IF NOT EXISTS idx_entities_embedding ON entities
313
+ USING hnsw (embedding vector_cosine_ops)
314
+ WITH (m = 16, ef_construction = 200);
315
+
316
+ -- Junction table: many memories ↔ many entities
317
+ CREATE TABLE IF NOT EXISTS memory_entities (
318
+ memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
319
+ entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
320
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
321
+ PRIMARY KEY (memory_id, entity_id)
322
+ );
323
+
324
+ CREATE INDEX IF NOT EXISTS idx_memory_entities_entity ON memory_entities (entity_id);
325
+
326
+ -- Phase 4: Temporal Linkage List (TLL).
327
+ -- Per-entity sparse graph of event nodes with predecessor/successor edges.
328
+ -- Karpathy-minimal: append on ingest, traverse on EO/MSR/TR queries.
329
+ -- Targets the abilities Mem0 explicitly admits their architecture doesn't
330
+ -- crack at 10M (temporal reasoning, event ordering, multi-session reasoning).
331
+ -- The unique architectural primitive nobody has shipped publicly.
332
+ CREATE TABLE IF NOT EXISTS temporal_linkage_list (
333
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
334
+ user_id TEXT NOT NULL,
335
+ entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
336
+ memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
337
+ predecessor_memory_id UUID DEFAULT NULL REFERENCES memories(id) ON DELETE CASCADE,
338
+ observation_date TIMESTAMPTZ NOT NULL,
339
+ position_in_chain INTEGER NOT NULL,
340
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
341
+ UNIQUE (user_id, entity_id, memory_id)
342
+ );
343
+
344
+ CREATE INDEX IF NOT EXISTS idx_tll_entity_chain
345
+ ON temporal_linkage_list (user_id, entity_id, position_in_chain);
346
+ CREATE INDEX IF NOT EXISTS idx_tll_memory
347
+ ON temporal_linkage_list (memory_id);
348
+
349
+ -- Defense-in-depth: unique (chain, position) so any future code path that
350
+ -- bypasses the advisory-lock append fails at the DB layer instead of
351
+ -- silently producing duplicate positions. Idempotent for fresh and
352
+ -- existing schemas.
353
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_tll_chain_position_unique
354
+ ON temporal_linkage_list (user_id, entity_id, position_in_chain);
355
+
356
+ -- Align predecessor FK with memory FK (CASCADE) so a hard-deleted memory
357
+ -- removes the dependent chain node instead of leaving a half-broken
358
+ -- predecessor pointer that breaks backward chain traversal. Idempotent:
359
+ -- re-applying the constraint overwrites any prior ON DELETE SET NULL
360
+ -- definition. Required for existing databases since the table-level
361
+ -- CREATE TABLE IF NOT EXISTS above does not update column constraints.
362
+ DO $$
363
+ BEGIN
364
+ IF EXISTS (
365
+ SELECT 1 FROM information_schema.table_constraints
366
+ WHERE table_name = 'temporal_linkage_list'
367
+ AND constraint_name = 'temporal_linkage_list_predecessor_memory_id_fkey'
368
+ ) THEN
369
+ ALTER TABLE temporal_linkage_list
370
+ DROP CONSTRAINT temporal_linkage_list_predecessor_memory_id_fkey;
371
+ END IF;
372
+ ALTER TABLE temporal_linkage_list
373
+ ADD CONSTRAINT temporal_linkage_list_predecessor_memory_id_fkey
374
+ FOREIGN KEY (predecessor_memory_id) REFERENCES memories(id)
375
+ ON DELETE CASCADE;
376
+ END$$;
377
+
378
+ -- =====================================================================
379
+ -- First-mention events (chronological topic-introduction list)
380
+ -- =====================================================================
381
+ -- Per-user list of "the first time topic X was introduced in conversation."
382
+ -- Distinct from facts (which are atomic claims) and memories (which are
383
+ -- ingested chunks). The grain matches event-ordering rubrics:
384
+ -- "in what order did the user bring up these aspects."
385
+ --
386
+ -- Generated post-ingest by FirstMentionService via a single LLM call that
387
+ -- scans the full conversation and outputs a JSON array of first-mention
388
+ -- events. Idempotent on (user_id, memory_id) so re-running doesn't duplicate.
389
+ CREATE TABLE IF NOT EXISTS first_mention_events (
390
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
391
+ user_id TEXT NOT NULL,
392
+ topic TEXT NOT NULL,
393
+ turn_id INTEGER NOT NULL,
394
+ memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
395
+ anchor_date TIMESTAMPTZ DEFAULT NULL,
396
+ position_in_conversation INTEGER NOT NULL,
397
+ source_site TEXT,
398
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
399
+ UNIQUE (user_id, memory_id)
400
+ );
401
+
402
+ CREATE INDEX IF NOT EXISTS idx_first_mention_user_position
403
+ ON first_mention_events (user_id, position_in_conversation);
404
+
405
+ CREATE INDEX IF NOT EXISTS idx_first_mention_user_topic
406
+ ON first_mention_events USING GIN (to_tsvector('english', topic));
407
+
408
+ -- Entity relations: typed, directed edges between entities with temporal validity
409
+ CREATE TABLE IF NOT EXISTS entity_relations (
410
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
411
+ user_id TEXT NOT NULL,
412
+ source_entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
413
+ target_entity_id UUID NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
414
+ relation_type TEXT NOT NULL
415
+ CHECK (relation_type IN (
416
+ 'uses', 'works_on', 'works_at', 'located_in', 'knows',
417
+ 'prefers', 'created', 'belongs_to', 'studies', 'manages'
418
+ )),
419
+ source_memory_id UUID REFERENCES memories(id) ON DELETE SET NULL,
420
+ confidence REAL NOT NULL DEFAULT 1.0
421
+ CHECK (confidence >= 0.0 AND confidence <= 1.0),
422
+ valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
423
+ valid_to TIMESTAMPTZ DEFAULT NULL,
424
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
425
+ UNIQUE (source_entity_id, target_entity_id, relation_type)
426
+ );
427
+
428
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_source ON entity_relations (source_entity_id);
429
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_target ON entity_relations (target_entity_id);
430
+ CREATE INDEX IF NOT EXISTS idx_entity_relations_user ON entity_relations (user_id);
431
+
432
+ -- Phase 6: Lessons store — detected failure patterns for pre-action defense (A-MemGuard)
433
+ -- SCOPE_TODO: Lessons are user-global (failure patterns are personal, not per-workspace).
434
+ CREATE TABLE IF NOT EXISTS lessons (
435
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
436
+ user_id TEXT NOT NULL,
437
+ lesson_type TEXT NOT NULL
438
+ CHECK (lesson_type IN (
439
+ 'injection_blocked', 'false_memory', 'contradiction_pattern',
440
+ 'user_reported', 'consensus_violation', 'trust_violation'
441
+ )),
442
+ pattern TEXT NOT NULL,
443
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
444
+ source_memory_ids UUID[] NOT NULL DEFAULT '{}',
445
+ source_query TEXT DEFAULT NULL,
446
+ severity TEXT NOT NULL DEFAULT 'medium'
447
+ CHECK (severity IN ('low', 'medium', 'high', 'critical')),
448
+ active BOOLEAN NOT NULL DEFAULT true,
449
+ metadata JSONB NOT NULL DEFAULT '{}',
450
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
451
+ );
452
+
453
+ CREATE INDEX IF NOT EXISTS idx_lessons_user_active ON lessons (user_id) WHERE active = true;
454
+ CREATE INDEX IF NOT EXISTS idx_lessons_type ON lessons (user_id, lesson_type);
455
+ CREATE INDEX IF NOT EXISTS idx_lessons_embedding ON lessons
456
+ USING hnsw (embedding vector_cosine_ops)
457
+ WITH (m = 16, ef_construction = 200);
458
+
459
+ -- Temporal metadata index (observed_at separates conversation time from DB insertion time)
460
+ CREATE INDEX IF NOT EXISTS idx_memories_user_observed ON memories (user_id, observed_at)
461
+ WHERE deleted_at IS NULL AND expired_at IS NULL;
462
+
463
+ -- Agent trust levels for multi-agent conflict resolution (from hive-mind Phase 4)
464
+ CREATE TABLE IF NOT EXISTS agent_trust (
465
+ agent_id TEXT PRIMARY KEY,
466
+ user_id TEXT NOT NULL,
467
+ trust_level REAL NOT NULL DEFAULT 0.5
468
+ CHECK (trust_level >= 0.0 AND trust_level <= 1.0),
469
+ display_name TEXT DEFAULT NULL,
470
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
471
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
472
+ );
473
+
474
+ CREATE INDEX IF NOT EXISTS idx_agent_trust_user ON agent_trust (user_id);
475
+
476
+ -- Conflict tracking for CLARIFY escalation and auto-resolution
477
+ CREATE TABLE IF NOT EXISTS memory_conflicts (
478
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
479
+ user_id TEXT NOT NULL,
480
+ new_memory_id UUID REFERENCES memories(id) ON DELETE SET NULL,
481
+ existing_memory_id UUID REFERENCES memories(id) ON DELETE SET NULL,
482
+ new_agent_id TEXT DEFAULT NULL,
483
+ existing_agent_id TEXT DEFAULT NULL,
484
+ new_trust_level REAL DEFAULT NULL,
485
+ existing_trust_level REAL DEFAULT NULL,
486
+ contradiction_confidence REAL NOT NULL DEFAULT 0.5,
487
+ clarification_note TEXT DEFAULT NULL,
488
+ status TEXT NOT NULL DEFAULT 'open'
489
+ CHECK (status IN ('open', 'auto_resolved', 'resolved_new', 'resolved_existing', 'resolved_both')),
490
+ resolution_policy TEXT DEFAULT NULL,
491
+ resolved_at TIMESTAMPTZ DEFAULT NULL,
492
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
493
+ auto_resolve_after TIMESTAMPTZ DEFAULT NULL
494
+ );
495
+
496
+ CREATE INDEX IF NOT EXISTS idx_conflicts_user_status ON memory_conflicts (user_id, status)
497
+ WHERE status = 'open';
498
+ CREATE INDEX IF NOT EXISTS idx_conflicts_auto_resolve ON memory_conflicts (auto_resolve_after)
499
+ WHERE status = 'open' AND auto_resolve_after IS NOT NULL;
500
+
501
+ -- ---------------------------------------------------------------------------
502
+ -- Phase 5 Step 9: Add workspace scope columns to representation tables.
503
+ -- These are idempotent ALTER TABLE statements that run safely on every startup.
504
+ -- NULL means the row was created by user-scoped ingest (pre-Phase 5).
505
+ -- ---------------------------------------------------------------------------
506
+
507
+ ALTER TABLE memory_atomic_facts ADD COLUMN IF NOT EXISTS workspace_id UUID DEFAULT NULL;
508
+ ALTER TABLE memory_atomic_facts ADD COLUMN IF NOT EXISTS agent_id UUID DEFAULT NULL;
509
+
510
+ ALTER TABLE memory_foresight ADD COLUMN IF NOT EXISTS workspace_id UUID DEFAULT NULL;
511
+ ALTER TABLE memory_foresight ADD COLUMN IF NOT EXISTS agent_id UUID DEFAULT NULL;
512
+
513
+ CREATE INDEX IF NOT EXISTS idx_memory_atomic_facts_workspace
514
+ ON memory_atomic_facts (workspace_id) WHERE workspace_id IS NOT NULL;
515
+ CREATE INDEX IF NOT EXISTS idx_memory_foresight_workspace
516
+ ON memory_foresight (workspace_id) WHERE workspace_id IS NOT NULL;
517
+
518
+ -- ---------------------------------------------------------------------------
519
+ -- TBC Phase 3 (2026-05-06): Typed Belief Calculus first-class storage.
520
+ -- Promotes belief state from `memories.metadata` JSONB into typed columns +
521
+ -- a new `belief_edges` table. All additions are idempotent (IF NOT EXISTS).
522
+ -- Pre-migration databases stay queryable; tbc-execution.ts dual-writes
523
+ -- during the migration window. Design doc: docs/tbc-phase-3-schema.md.
524
+ -- Activated only when TBC_ENABLED=true; defaults preserve existing behavior.
525
+ -- ---------------------------------------------------------------------------
526
+
527
+ -- Belief confidence in [0,1]; default 1.0 = "fully believed" (matches AUDN's
528
+ -- no-confidence-tracking baseline).
529
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS confidence REAL DEFAULT 1.0
530
+ CHECK (confidence >= 0.0 AND confidence <= 1.0);
531
+
532
+ -- Belief tier — controls how the claim influences answer generation.
533
+ -- standard: default tier; normal weight in retrieval
534
+ -- directive: promoted; injected as a "must follow" rule in answer prompt
535
+ -- demoted: challenged; lower weight + flagged for re-evaluation
536
+ -- retracted: believed false; excluded from default retrieval
537
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS belief_tier TEXT DEFAULT 'standard'
538
+ CHECK (belief_tier IN ('standard', 'directive', 'demoted', 'retracted'));
539
+
540
+ -- The TBC operator that most recently mutated this memory.
541
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS mutation_type TEXT DEFAULT NULL
542
+ CHECK (mutation_type IS NULL OR mutation_type IN (
543
+ 'AFFIRM', 'UPDATE', 'RETRACT', 'SUPERSEDE',
544
+ 'PROMOTE', 'DEMOTE', 'EVIDENCE_FOR', 'COUNTER'
545
+ ));
546
+
547
+ -- Tier-aware retrieval index (directives surface fast, retracted excluded).
548
+ CREATE INDEX IF NOT EXISTS idx_memories_belief_tier
549
+ ON memories (user_id, belief_tier)
550
+ WHERE deleted_at IS NULL AND expired_at IS NULL AND belief_tier != 'standard';
551
+
552
+ -- Confidence-weighted retrieval index (low-confidence demotion).
553
+ CREATE INDEX IF NOT EXISTS idx_memories_confidence
554
+ ON memories (user_id, confidence DESC)
555
+ WHERE deleted_at IS NULL AND expired_at IS NULL;
556
+
557
+ -- belief_edges: typed belief graph between claims.
558
+ -- evidence_for: source supports target's confidence (positive weight)
559
+ -- counter: source contradicts target's confidence (negative weight)
560
+ -- supersedes: source replaces target (more specific or general)
561
+ -- promotes: source promoted target to directive tier
562
+ -- demotes: source challenged target without retracting
563
+ CREATE TABLE IF NOT EXISTS belief_edges (
564
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
565
+ user_id TEXT NOT NULL,
566
+ source_id UUID NOT NULL,
567
+ target_id UUID NOT NULL,
568
+ edge_type TEXT NOT NULL CHECK (edge_type IN (
569
+ 'evidence_for', 'counter', 'supersedes', 'promotes', 'demotes'
570
+ )),
571
+ weight REAL NOT NULL DEFAULT 0.0
572
+ CHECK (weight >= -1.0 AND weight <= 1.0),
573
+ rationale TEXT NOT NULL DEFAULT '',
574
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
575
+ workspace_id UUID DEFAULT NULL,
576
+ agent_id UUID DEFAULT NULL
577
+ );
578
+
579
+ -- For "all evidence pointing at this claim" queries (queryable belief state).
580
+ CREATE INDEX IF NOT EXISTS idx_belief_edges_target
581
+ ON belief_edges (target_id, edge_type, created_at DESC);
582
+
583
+ -- For "all claims this evidence supports/counters" queries.
584
+ CREATE INDEX IF NOT EXISTS idx_belief_edges_source
585
+ ON belief_edges (source_id, edge_type);
586
+
587
+ -- User-scoped traversal (multi-tenant safety).
588
+ CREATE INDEX IF NOT EXISTS idx_belief_edges_user_target
589
+ ON belief_edges (user_id, target_id);
590
+
591
+ -- ---------------------------------------------------------------------------
592
+ -- Hierarchical Retrieval (2026-05-07): three-level memory hierarchy for
593
+ -- BEAM-10M scale (10 conversations × ~1.4M tokens each = ~14M total context).
594
+ -- session_summaries + conv_summaries indexed via HNSW on summary_embedding.
595
+ -- Activated only when HIERARCHICAL_RETRIEVAL_ENABLED=true; defaults preserve
596
+ -- existing flat-retrieval behavior. Design doc: docs/hierarchical-retrieval.md.
597
+ -- ---------------------------------------------------------------------------
598
+
599
+ CREATE TABLE IF NOT EXISTS session_summaries (
600
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
601
+ user_id TEXT NOT NULL,
602
+ session_id TEXT NOT NULL,
603
+ conversation_id TEXT NOT NULL,
604
+ session_index INTEGER NOT NULL,
605
+ summary_text TEXT NOT NULL,
606
+ summary_embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
607
+ topics TEXT[] NOT NULL DEFAULT '{}',
608
+ fact_count INTEGER NOT NULL DEFAULT 0,
609
+ occurred_start TIMESTAMPTZ DEFAULT NULL,
610
+ occurred_end TIMESTAMPTZ DEFAULT NULL,
611
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
612
+ workspace_id UUID DEFAULT NULL,
613
+ agent_id UUID DEFAULT NULL
614
+ );
615
+
616
+ CREATE TABLE IF NOT EXISTS conv_summaries (
617
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
618
+ user_id TEXT NOT NULL,
619
+ conversation_id TEXT NOT NULL,
620
+ summary_text TEXT NOT NULL,
621
+ summary_embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
622
+ session_count INTEGER NOT NULL DEFAULT 0,
623
+ fact_count INTEGER NOT NULL DEFAULT 0,
624
+ occurred_start TIMESTAMPTZ DEFAULT NULL,
625
+ occurred_end TIMESTAMPTZ DEFAULT NULL,
626
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
627
+ workspace_id UUID DEFAULT NULL,
628
+ agent_id UUID DEFAULT NULL
629
+ );
630
+
631
+ -- Stage-1 retrieval: top-K conversations by summary similarity.
632
+ CREATE INDEX IF NOT EXISTS idx_conv_summaries_embedding
633
+ ON conv_summaries USING hnsw (summary_embedding vector_cosine_ops)
634
+ WITH (m = 16, ef_construction = 200);
635
+
636
+ -- Stage-2 retrieval: top-K sessions within selected conversations.
637
+ CREATE INDEX IF NOT EXISTS idx_session_summaries_embedding
638
+ ON session_summaries USING hnsw (summary_embedding vector_cosine_ops)
639
+ WITH (m = 16, ef_construction = 200);
640
+
641
+ -- User-scoped lookups for both summary tables.
642
+ CREATE INDEX IF NOT EXISTS idx_session_summaries_user_conv
643
+ ON session_summaries (user_id, conversation_id, session_index);
644
+
645
+ CREATE INDEX IF NOT EXISTS idx_conv_summaries_user
646
+ ON conv_summaries (user_id, conversation_id);
647
+
648
+ -- ---------------------------------------------------------------------------
649
+ -- Sprint 3 (2026-05-10): Topic-abstraction layer for the EO experiment.
650
+ -- Per-memory conceptual topic (3-7 word LLM summary at higher abstraction
651
+ -- than raw facts) + its embedding. Surfaced via a dedicated RRF channel at
652
+ -- retrieval. Activated only when TOPIC_ABSTRACTION_ENABLED=true; defaults
653
+ -- preserve existing behavior on un-migrated rows. Design doc:
654
+ -- benchmarks-sprint3/2026-05-10-am-baseline-and-rerank-design.md.
655
+ -- ---------------------------------------------------------------------------
656
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS topic_abstraction TEXT NOT NULL DEFAULT '';
657
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS topic_embedding vector({{EMBEDDING_DIMENSIONS}}) DEFAULT NULL;
658
+ -- Pointer to the recap this memory has been consolidated into (NULL until
659
+ -- the Recap-layer builder runs). Used to filter out already-consolidated
660
+ -- memories from future recap-cluster candidates.
661
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS recap_id UUID DEFAULT NULL;
662
+
663
+ CREATE INDEX IF NOT EXISTS idx_memories_topic_embedding
664
+ ON memories USING hnsw (topic_embedding vector_cosine_ops)
665
+ WITH (m = 16, ef_construction = 200)
666
+ WHERE topic_embedding IS NOT NULL AND deleted_at IS NULL AND expired_at IS NULL;
667
+
668
+ -- ---------------------------------------------------------------------------
669
+ -- Sprint 3 (2026-05-10): Recap layer for cross-session synthesis.
670
+ -- A Recap is an LLM-synthesized narrative aggregating N memories that share
671
+ -- a conceptual topic. Surfaced via its own RRF channel at retrieval. Cog-sci
672
+ -- analogue: hippocampal consolidation. Three of the four next-gen memory
673
+ -- systems converge on this primitive (Hindsight observations, Honcho
674
+ -- dreaming, X-Mem Episodes, EverMemOS multi-pass restructuring). Activated
675
+ -- only when RECAP_LAYER_ENABLED=true.
676
+ -- ---------------------------------------------------------------------------
677
+ CREATE TABLE IF NOT EXISTS recaps (
678
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
679
+ user_id TEXT NOT NULL,
680
+ recap_text TEXT NOT NULL,
681
+ recap_embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
682
+ topic TEXT NOT NULL DEFAULT '',
683
+ member_memory_ids UUID[] NOT NULL DEFAULT '{}',
684
+ member_count INTEGER NOT NULL DEFAULT 0,
685
+ time_range_start TIMESTAMPTZ DEFAULT NULL,
686
+ time_range_end TIMESTAMPTZ DEFAULT NULL,
687
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
688
+ workspace_id UUID DEFAULT NULL
689
+ );
690
+
691
+ CREATE INDEX IF NOT EXISTS idx_recaps_user_topic
692
+ ON recaps (user_id, topic) WHERE workspace_id IS NULL;
693
+
694
+ CREATE INDEX IF NOT EXISTS idx_recaps_embedding
695
+ ON recaps USING hnsw (recap_embedding vector_cosine_ops)
696
+ WITH (m = 16, ef_construction = 200);
697
+
698
+ -- ---------------------------------------------------------------------------
699
+ -- Sprint 3 v1.5 (2026-05-11): user-profile channel (H2 from haiku-080).
700
+ -- One row per user holds the synthesized profile that is pinned to every
701
+ -- answer prompt. Updated by user-profile-builder.ts after each ingest
702
+ -- that stores >= 3 new facts. See also:
703
+ -- src/db/migrations/20260511_user_profiles.sql (migration provenance copy)
704
+ -- ---------------------------------------------------------------------------
705
+ CREATE TABLE IF NOT EXISTS user_profiles (
706
+ user_id TEXT PRIMARY KEY,
707
+ profile_text TEXT NOT NULL,
708
+ source_memory_ids TEXT[] NOT NULL,
709
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
710
+ );
711
+
712
+ CREATE INDEX IF NOT EXISTS user_profiles_updated_at_idx
713
+ ON user_profiles (updated_at DESC);
714
+
715
+ -- ---------------------------------------------------------------------------
716
+ -- Sprint 4 (2026-05-11): Entity-Attribute Index (EAI).
717
+ -- Stores (entity, attribute, value) triples extracted at ingest time, indexed
718
+ -- for fast lookup by entity name and/or attribute key on queries like
719
+ -- "how many X did I do?" or "what is my Y?". Distinct from the entity graph
720
+ -- (entities, entity_relations) which captures structural relations.
721
+ -- See also: src/db/migrations/20260511_entity_attributes.sql.
722
+ -- Activated only when ENTITY_ATTRIBUTES_ENABLED=true.
723
+ -- ---------------------------------------------------------------------------
724
+ CREATE TABLE IF NOT EXISTS entity_attributes (
725
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
726
+ user_id TEXT NOT NULL,
727
+ entity_name TEXT NOT NULL,
728
+ attribute_key TEXT NOT NULL,
729
+ attribute_value TEXT NOT NULL,
730
+ value_type TEXT NOT NULL CHECK (value_type IN ('number','string','list','boolean','date')),
731
+ source_memory_id UUID,
732
+ observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
733
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
734
+ );
735
+
736
+ CREATE INDEX IF NOT EXISTS idx_entity_attributes_user_entity
737
+ ON entity_attributes (user_id, lower(entity_name));
738
+ CREATE INDEX IF NOT EXISTS idx_entity_attributes_user_attribute
739
+ ON entity_attributes (user_id, lower(attribute_key));
740
+ CREATE INDEX IF NOT EXISTS idx_entity_attributes_observed
741
+ ON entity_attributes (user_id, observed_at DESC);
742
+
743
+ -- ---------------------------------------------------------------------------
744
+ -- BEAM-0.85 Phase 2 (2026-05-12): Literal value extraction for IE/KU.
745
+ -- Captures exact (entity, attribute, value) triples from ingested facts so
746
+ -- specialist lookup can answer literal factual questions via SQL.
747
+ -- See also: src/db/migrations/20260512_entity_values.sql.
748
+ -- ---------------------------------------------------------------------------
749
+ CREATE TABLE IF NOT EXISTS entity_values (
750
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
751
+ user_id TEXT NOT NULL,
752
+ entity TEXT NOT NULL,
753
+ attribute TEXT NOT NULL,
754
+ value TEXT NOT NULL,
755
+ value_type TEXT NOT NULL CHECK (value_type IN ('date', 'number', 'string', 'duration', 'list')),
756
+ observed_at TIMESTAMPTZ NOT NULL,
757
+ fact_id UUID NOT NULL,
758
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
759
+ );
760
+
761
+ CREATE INDEX IF NOT EXISTS ix_entity_values_lookup
762
+ ON entity_values (user_id, lower(entity), lower(attribute), observed_at DESC);
763
+
764
+ CREATE INDEX IF NOT EXISTS ix_entity_values_fact
765
+ ON entity_values (fact_id);
766
+
767
+ -- ---------------------------------------------------------------------------
768
+ -- BEAM-0.85 (2026-05-12): Async Reflect step storage.
769
+ -- Stores synthesized observations per (user_id, conversation_id), plus the
770
+ -- Postgres-backed queue used by the reflect worker.
771
+ -- See also: src/db/migrations/20260512_session_reflections.sql.
772
+ -- ---------------------------------------------------------------------------
773
+ CREATE TABLE IF NOT EXISTS session_reflections (
774
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
775
+ user_id TEXT NOT NULL,
776
+ conversation_id TEXT NOT NULL,
777
+ observation TEXT NOT NULL,
778
+ observation_type TEXT NOT NULL CHECK (observation_type IN (
779
+ 'entity_state', 'event_summary', 'preference',
780
+ 'contradiction', 'decision', 'numeric_value'
781
+ )),
782
+ evidence_memory_ids TEXT[] NOT NULL,
783
+ embedding vector({{EMBEDDING_DIMENSIONS}}),
784
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
785
+ );
786
+
787
+ CREATE INDEX IF NOT EXISTS ix_session_reflections_user_conv
788
+ ON session_reflections (user_id, conversation_id);
789
+
790
+ CREATE INDEX IF NOT EXISTS ix_session_reflections_embedding
791
+ ON session_reflections USING hnsw (embedding vector_cosine_ops);
792
+
793
+ CREATE TABLE IF NOT EXISTS reflection_jobs (
794
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
795
+ user_id TEXT NOT NULL,
796
+ conversation_id TEXT NOT NULL,
797
+ status TEXT NOT NULL DEFAULT 'pending'
798
+ CHECK (status IN ('pending', 'in_progress', 'completed', 'failed')),
799
+ attempts INTEGER NOT NULL DEFAULT 0,
800
+ last_error TEXT,
801
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
802
+ last_tried_at TIMESTAMPTZ
803
+ );
804
+
805
+ CREATE UNIQUE INDEX IF NOT EXISTS ix_reflection_jobs_pending_unique
806
+ ON reflection_jobs (user_id, conversation_id)
807
+ WHERE status IN ('pending', 'in_progress');
808
+
809
+ CREATE INDEX IF NOT EXISTS ix_reflection_jobs_status_created
810
+ ON reflection_jobs (status, created_at);
811
+
812
+ -- ---------------------------------------------------------------------------
813
+ -- BEAM-0.85 (2026-05-12): Always-on per-entity ENTITY_CARD channel.
814
+ -- Mirrors Honcho's "peer card" pattern. The Reflect worker (Sonnet 4.5)
815
+ -- maintains one card per (user_id, conversation_id, entity_name); the
816
+ -- search pipeline injects all cards for the active conversation at the top
817
+ -- of every answer-LLM prompt under the `## ENTITY_STATE` heading.
818
+ -- See also: src/db/migrations/20260512_entity_cards.sql.
819
+ -- Activated only when ENTITY_CARD_ENABLED=true.
820
+ -- ---------------------------------------------------------------------------
821
+ CREATE TABLE IF NOT EXISTS entity_cards (
822
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
823
+ user_id TEXT NOT NULL,
824
+ conversation_id TEXT NOT NULL,
825
+ entity_name TEXT NOT NULL,
826
+ card_text TEXT NOT NULL,
827
+ source_observation_ids TEXT[] NOT NULL DEFAULT '{}',
828
+ version INTEGER NOT NULL DEFAULT 1,
829
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
830
+ );
831
+ CREATE UNIQUE INDEX IF NOT EXISTS ix_entity_cards_unique
832
+ ON entity_cards (user_id, conversation_id, entity_name);
833
+ CREATE INDEX IF NOT EXISTS ix_entity_cards_user_conv
834
+ ON entity_cards (user_id, conversation_id);
835
+
836
+ -- ---------------------------------------------------------------------------
837
+ -- BEAM CR fix (2026-05-12): AUDN bilateral preservation for contradictions.
838
+ -- When the flag-gated bilateral path fires instead of DELETE/SUPERSEDE,
839
+ -- both prior + new memory remain in `memories` with `contradiction_active=true`
840
+ -- and `contradicts_memory_id` pointing at the counterpart. A row in
841
+ -- `memory_contradictions` records the conflict with both summaries verbatim
842
+ -- so retrieval can quote BOTH sides for CR-style questions.
843
+ -- See also: src/db/migrations/20260512_audn_bilateral.sql.
844
+ -- Activated only when CONTRADICTION_PRESERVATION_ENABLED=true.
845
+ -- ---------------------------------------------------------------------------
846
+ ALTER TABLE memories
847
+ ADD COLUMN IF NOT EXISTS contradicts_memory_id UUID REFERENCES memories(id) ON DELETE SET NULL,
848
+ ADD COLUMN IF NOT EXISTS contradiction_active BOOLEAN NOT NULL DEFAULT false;
849
+
850
+ CREATE INDEX IF NOT EXISTS ix_memories_contradiction_active
851
+ ON memories (user_id, contradiction_active) WHERE contradiction_active = true;
852
+
853
+ CREATE TABLE IF NOT EXISTS memory_contradictions (
854
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
855
+ user_id TEXT NOT NULL,
856
+ conversation_id TEXT,
857
+ left_memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
858
+ right_memory_id UUID NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
859
+ left_summary TEXT NOT NULL,
860
+ right_summary TEXT NOT NULL,
861
+ resolved BOOLEAN NOT NULL DEFAULT false,
862
+ resolution_note TEXT,
863
+ detected_at TIMESTAMPTZ NOT NULL DEFAULT now()
864
+ );
865
+
866
+ CREATE INDEX IF NOT EXISTS ix_memory_contradictions_user
867
+ ON memory_contradictions (user_id, conversation_id);
868
+ CREATE INDEX IF NOT EXISTS ix_memory_contradictions_left
869
+ ON memory_contradictions (left_memory_id);
870
+ CREATE INDEX IF NOT EXISTS ix_memory_contradictions_right
871
+ ON memory_contradictions (right_memory_id);
872
+
873
+ -- ---------------------------------------------------------------------------
874
+ -- BEAM v38 (2026-05-12): Temporal state layer — focused Mem0
875
+ -- temporal-reasoning subset for KU lift.
876
+ --
877
+ -- Adds three columns on `memories` describing an evolving fact:
878
+ -- state_key stable identifier for an evolving fact ("user:1:location")
879
+ -- event_start when the fact became true
880
+ -- event_end when the fact stopped being true (NULL = still active)
881
+ --
882
+ -- See also: src/db/migrations/20260512_temporal_state.sql.
883
+ -- Activated only when TEMPORAL_STATE_ENABLED=true.
884
+ -- ---------------------------------------------------------------------------
885
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS state_key TEXT DEFAULT NULL;
886
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS event_start TIMESTAMPTZ DEFAULT NULL;
887
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS event_end TIMESTAMPTZ DEFAULT NULL;
888
+
889
+ CREATE INDEX IF NOT EXISTS idx_memories_state_key_active
890
+ ON memories (user_id, state_key)
891
+ WHERE event_end IS NULL
892
+ AND state_key IS NOT NULL
893
+ AND deleted_at IS NULL;
894
+
895
+ CREATE INDEX IF NOT EXISTS idx_memories_state_key_all
896
+ ON memories (user_id, state_key)
897
+ WHERE state_key IS NOT NULL
898
+ AND deleted_at IS NULL;
899
+ -- Document pipeline (Phase 1 of the large-file ingestion plan).
900
+ --
901
+ -- See `Atomicmemory-research/docs/core-repo/design/large-file-ingestion-and-raw-storage-plan-2026-05-08.md`.
902
+ --
903
+ -- Phase 1 ships the pointer-only document registry: `raw_sources` is a
904
+ -- per-(user, source_site, provider, account) namespace; `raw_documents`
905
+ -- represents one registered upstream object. The CHECK constraints on
906
+ -- `storage_mode` / `registration_status` / `raw_storage_status` accept the
907
+ -- full enum that later phases will populate (managed_blob, inline_text_stored,
908
+ -- blob_stored) so Phase 3 doesn't need a CHECK migration. The service layer
909
+ -- enforces `storage_mode = 'pointer_only'` until Phase 3 lands.
910
+ -- ---------------------------------------------------------------------------
911
+
912
+ CREATE TABLE IF NOT EXISTS raw_sources (
913
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
914
+ user_id TEXT NOT NULL,
915
+ source_site TEXT NOT NULL,
916
+ provider TEXT NOT NULL,
917
+ account_id TEXT,
918
+ storage_mode TEXT NOT NULL DEFAULT 'pointer_only'
919
+ CHECK (storage_mode IN ('pointer_only', 'managed_blob', 'inline_small_text')),
920
+ retention_policy JSONB NOT NULL DEFAULT '{}'::jsonb,
921
+ consent_policy JSONB NOT NULL DEFAULT '{}'::jsonb,
922
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
923
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
924
+ );
925
+
926
+ -- COALESCE(account_id, '') keeps NULL account_ids in a single namespace slot
927
+ -- (Postgres treats NULLs as distinct in plain unique indexes, which would let
928
+ -- two rows with the same user/source/provider but different consent contexts
929
+ -- collide).
930
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_raw_sources_namespace
931
+ ON raw_sources (user_id, source_site, provider, COALESCE(account_id, ''));
932
+ CREATE INDEX IF NOT EXISTS idx_raw_sources_user
933
+ ON raw_sources (user_id);
934
+
935
+ CREATE TABLE IF NOT EXISTS raw_documents (
936
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
937
+ user_id TEXT NOT NULL,
938
+ raw_source_id UUID NOT NULL REFERENCES raw_sources(id) ON DELETE CASCADE,
939
+ external_id TEXT NOT NULL,
940
+ external_uri TEXT,
941
+ display_name TEXT,
942
+ mime_type TEXT,
943
+ size_bytes BIGINT,
944
+ content_hash TEXT,
945
+ provider_version TEXT,
946
+ source_modified_at TIMESTAMPTZ,
947
+ storage_mode TEXT NOT NULL DEFAULT 'pointer_only'
948
+ CHECK (storage_mode IN ('pointer_only', 'managed_blob', 'inline_small_text')),
949
+ -- storage_uri / storage_provider stay NULL in Phase 1 (no managed blob).
950
+ storage_uri TEXT,
951
+ storage_provider TEXT,
952
+ registration_status TEXT NOT NULL DEFAULT 'registered'
953
+ CHECK (registration_status IN ('registered', 'registration_failed')),
954
+ raw_storage_status TEXT NOT NULL DEFAULT 'pointer_recorded'
955
+ CHECK (raw_storage_status IN
956
+ ('pointer_recorded', 'blob_stored', 'inline_text_stored', 'raw_storage_failed', 'blob_deleted')),
957
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
958
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
959
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
960
+ deleted_at TIMESTAMPTZ
961
+ );
962
+
963
+ -- Phase 3 broadened the `raw_storage_status` CHECK to include
964
+ -- `blob_deleted` (terminal post-cleanup state for tombstoned managed-blob
965
+ -- rows). The Filecoin raw-content-store lifecycle refactor (Slice 2)
966
+ -- further broadens it to include the eventual-provider states:
967
+ -- * `blob_pending` — provider accepted the upload but storage /
968
+ -- retrievability is not yet confirmed (e.g. a Filecoin onramp
969
+ -- that returns before the deal is sealed). Slice 3 writes this
970
+ -- when `put()` returns `status: 'pending'`.
971
+ -- * `blob_available` — schema-reserved for the Phase 3 reconciliation
972
+ -- worker that promotes `blob_pending` rows once `head()` confirms
973
+ -- retrievability. No Phase-1 writer.
974
+ -- * `blob_archival_failed` — schema-reserved for the Phase 3
975
+ -- reconciler's permanent-failure path. No Phase-1 writer.
976
+ -- * `blob_tombstoned` — schema-reserved for Phase 2 Filecoin
977
+ -- deletes when the provider supports unpin-only semantics. No
978
+ -- Phase-1 writer.
979
+ -- * `blob_uploading` (Phase 5) — transient state during the upload
980
+ -- pipeline's α/β/β2/γ split. Phase α writes this with a claim_id
981
+ -- after seizing the slot; Phase γ flips it to the final terminal
982
+ -- state after the adapter returns. A row that stays in
983
+ -- `blob_uploading` past a process restart is recoverable via
984
+ -- same-bytes idempotent retry of `uploadRaw` — the reconciler
985
+ -- does NOT process `blob_uploading` rows.
986
+ -- Idempotent ALTER so the new values are accepted on existing test
987
+ -- DBs whose CREATE TABLE IF NOT EXISTS already locked in the prior CHECK.
988
+ ALTER TABLE raw_documents
989
+ DROP CONSTRAINT IF EXISTS raw_documents_raw_storage_status_check;
990
+ ALTER TABLE raw_documents
991
+ ADD CONSTRAINT raw_documents_raw_storage_status_check
992
+ CHECK (raw_storage_status IN
993
+ ('pointer_recorded', 'blob_stored', 'inline_text_stored', 'raw_storage_failed', 'blob_deleted',
994
+ 'blob_pending', 'blob_available', 'blob_archival_failed', 'blob_tombstoned',
995
+ 'blob_uploading'));
996
+
997
+ -- Filecoin lifecycle refactor (Phase 5) — typed claim + scheduling
998
+ -- columns moved out of JSONB. The upload pipeline's α/β/β2/γ split
999
+ -- writes a per-row claim_id when seizing the slot; the Phase 6
1000
+ -- reconciler claims `blob_pending` rows on the same columns and
1001
+ -- advances `next_check_at` via exponential backoff. `pending_since`
1002
+ -- is the durable "row entered blob_pending at" timestamp the
1003
+ -- observability layer reads for the `pending_age_seconds` metric.
1004
+ ALTER TABLE raw_documents
1005
+ ADD COLUMN IF NOT EXISTS raw_storage_claim_id TEXT,
1006
+ ADD COLUMN IF NOT EXISTS raw_storage_claimed_at TIMESTAMPTZ,
1007
+ ADD COLUMN IF NOT EXISTS raw_storage_last_checked_at TIMESTAMPTZ,
1008
+ ADD COLUMN IF NOT EXISTS raw_storage_next_check_at TIMESTAMPTZ,
1009
+ ADD COLUMN IF NOT EXISTS raw_storage_reconcile_attempts INTEGER NOT NULL DEFAULT 0,
1010
+ ADD COLUMN IF NOT EXISTS raw_storage_pending_since TIMESTAMPTZ;
1011
+
1012
+ -- Provider-side metadata for the managed blob (CID, piece CID, deal
1013
+ -- id, onramp request id, gateway URL, etc.). Opaque to the upload
1014
+ -- pipeline; the adapter's `put()` returns the shape and the row
1015
+ -- formatter forwards it verbatim. Default `'{}'` so existing rows
1016
+ -- and the pointer-only path stay schema-clean.
1017
+ ALTER TABLE raw_documents
1018
+ ADD COLUMN IF NOT EXISTS raw_storage_metadata JSONB NOT NULL DEFAULT '{}'::jsonb;
1019
+
1020
+ -- Phase 2 indexing fingerprint. Distinct from `content_hash` (which is
1021
+ -- the upstream/provider-supplied raw-content hash) so that indexing
1022
+ -- never overwrites caller-provided metadata. NULL means "not yet
1023
+ -- indexed under the current chunker_version"; the indexer's idempotency
1024
+ -- check compares the input text's hash against this column.
1025
+ ALTER TABLE raw_documents ADD COLUMN IF NOT EXISTS indexed_content_hash TEXT;
1026
+ ALTER TABLE raw_documents ADD COLUMN IF NOT EXISTS indexed_at TIMESTAMPTZ;
1027
+
1028
+ -- Phase B (document-ingest hardening) — per-layer status + last_error.
1029
+ --
1030
+ -- The audit at `Atomicmemory-research/docs/core-repo/design/document-ingest-audit.md`
1031
+ -- and the rev-18 hardening plan call for durable, observable
1032
+ -- per-layer status so the UI/API stops pretending indexing is
1033
+ -- instant and partial failures stop creating silent orphans.
1034
+ --
1035
+ -- * extraction_status — text-extraction layer: 'not_required'
1036
+ -- (e.g. URL pointer with no body), 'pending' (registered, awaiting
1037
+ -- extraction), 'running' (in-flight), 'complete', 'unsupported'
1038
+ -- (`.parquet`, etc.), 'failed'.
1039
+ -- * semantic_index_status — chunk + embed + index pipeline:
1040
+ -- 'not_required', 'pending', 'running', 'complete', 'failed',
1041
+ -- 'stale' (re-index needed; reserved).
1042
+ -- * last_error — JSONB envelope `{ layer, code, message, occurred_at }`
1043
+ -- scoped to the most-recent failure for any layer; cleared on the
1044
+ -- next successful transition for that layer.
1045
+ --
1046
+ -- Cross-walk to spec naming (`Atomicmemory-research/docs/core-repo/design/ingestion-variations-supported-2026-05-09.md`):
1047
+ -- `raw_storage_status` retains its prior values
1048
+ -- (`pointer_recorded` / `blob_stored` / `inline_text_stored` /
1049
+ -- `raw_storage_failed` / `blob_deleted`); rename can land later if
1050
+ -- the migration is worth the churn.
1051
+ ALTER TABLE raw_documents
1052
+ ADD COLUMN IF NOT EXISTS extraction_status TEXT NOT NULL DEFAULT 'not_required';
1053
+ ALTER TABLE raw_documents
1054
+ ADD COLUMN IF NOT EXISTS semantic_index_status TEXT NOT NULL DEFAULT 'not_required';
1055
+ ALTER TABLE raw_documents ADD COLUMN IF NOT EXISTS last_error JSONB;
1056
+
1057
+ ALTER TABLE raw_documents
1058
+ DROP CONSTRAINT IF EXISTS raw_documents_extraction_status_check;
1059
+ ALTER TABLE raw_documents
1060
+ ADD CONSTRAINT raw_documents_extraction_status_check
1061
+ CHECK (extraction_status IN
1062
+ ('not_required', 'pending', 'running', 'complete', 'unsupported', 'failed'));
1063
+
1064
+ ALTER TABLE raw_documents
1065
+ DROP CONSTRAINT IF EXISTS raw_documents_semantic_index_status_check;
1066
+ ALTER TABLE raw_documents
1067
+ ADD CONSTRAINT raw_documents_semantic_index_status_check
1068
+ CHECK (semantic_index_status IN
1069
+ ('not_required', 'pending', 'running', 'complete', 'failed', 'stale'));
1070
+
1071
+ -- Recovery-relevant rows: documents with at least one layer in a
1072
+ -- failure state. Partial index keeps it small on healthy deployments.
1073
+ CREATE INDEX IF NOT EXISTS idx_raw_documents_status_failed
1074
+ ON raw_documents (user_id)
1075
+ WHERE deleted_at IS NULL
1076
+ AND (
1077
+ extraction_status = 'failed'
1078
+ OR semantic_index_status = 'failed'
1079
+ OR raw_storage_status = 'raw_storage_failed'
1080
+ );
1081
+
1082
+ -- Active-row idempotency: at most one live registration per
1083
+ -- (user, source, external_id, version). Soft-deleted rows are excluded so
1084
+ -- a re-registration after deletion creates a fresh row instead of colliding.
1085
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_raw_documents_active_unique
1086
+ ON raw_documents (user_id, raw_source_id, external_id, COALESCE(provider_version, ''))
1087
+ WHERE deleted_at IS NULL;
1088
+ CREATE INDEX IF NOT EXISTS idx_raw_documents_user
1089
+ ON raw_documents (user_id) WHERE deleted_at IS NULL;
1090
+ CREATE INDEX IF NOT EXISTS idx_raw_documents_source
1091
+ ON raw_documents (raw_source_id) WHERE deleted_at IS NULL;
1092
+
1093
+ -- Memory provenance to documents/chunks. document_chunk_id is unused in
1094
+ -- Phase 1 (chunks ship in Phase 2) but the column lands now so memories
1095
+ -- created during the Phase 1 → 2 transition don't need a backfill migration.
1096
+ -- No FK constraint yet — added once raw_documents/document_chunks deletion
1097
+ -- semantics are reviewed alongside the chunk table in Phase 2.
1098
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS raw_document_id UUID DEFAULT NULL;
1099
+ ALTER TABLE memories ADD COLUMN IF NOT EXISTS document_chunk_id UUID DEFAULT NULL;
1100
+
1101
+ CREATE INDEX IF NOT EXISTS idx_memories_raw_document
1102
+ ON memories (user_id, raw_document_id)
1103
+ WHERE raw_document_id IS NOT NULL AND deleted_at IS NULL;
1104
+ CREATE INDEX IF NOT EXISTS idx_memories_document_chunk
1105
+ ON memories (user_id, document_chunk_id)
1106
+ WHERE document_chunk_id IS NOT NULL AND deleted_at IS NULL;
1107
+
1108
+ -- Phase D — passport-feed grouped query support (rev 18).
1109
+ -- The `GROUP BY raw_document_id` + `ARRAY_AGG(... ORDER BY created_at
1110
+ -- DESC, id DESC)[1]` pattern in `passport-feed-repository.ts` lifts
1111
+ -- (created_at DESC, id DESC) inside each (user_id, raw_document_id)
1112
+ -- partition. A composite partial index on those four columns lets
1113
+ -- Postgres skip the secondary sort entirely on the grouped branch
1114
+ -- under real volume; the partial WHERE clause keeps the index lean
1115
+ -- (memory rows that aren't document-backed or are tombstoned never
1116
+ -- enter the hot path).
1117
+ CREATE INDEX IF NOT EXISTS idx_memories_passport_grouped
1118
+ ON memories (user_id, raw_document_id, created_at DESC, id DESC)
1119
+ WHERE raw_document_id IS NOT NULL AND deleted_at IS NULL;
1120
+
1121
+ -- Phase D — passport-feed standalone branch support (rev 18).
1122
+ -- The standalone-memory branch of the UNION ALL pages by
1123
+ -- `(created_at DESC, id DESC)` filtered to memories with
1124
+ -- `raw_document_id IS NULL`. The pre-existing
1125
+ -- `idx_memories_user_created` is the wrong shape (no IS NULL
1126
+ -- partial, no `id` tie-breaker). This partial index matches the
1127
+ -- exact predicate the cursor walks.
1128
+ CREATE INDEX IF NOT EXISTS idx_memories_passport_standalone
1129
+ ON memories (user_id, created_at DESC, id DESC)
1130
+ WHERE raw_document_id IS NULL AND deleted_at IS NULL;
1131
+
1132
+ -- ---------------------------------------------------------------------------
1133
+ -- Document chunks (Phase 2 of the large-file ingestion plan).
1134
+ --
1135
+ -- One row per deterministic chunk derived from a registered document.
1136
+ -- Chunks store their own embedding (so the chunk-level vector store
1137
+ -- supports raw chunk lookup / debug / re-index without touching memories)
1138
+ -- AND each chunk creates a sibling row in `memories` with
1139
+ -- raw_document_id + document_chunk_id provenance — that's the surface
1140
+ -- the existing /v1/memories/search retrieval pipeline finds.
1141
+ --
1142
+ -- (parser_version, chunker_version) pair lets a future code change
1143
+ -- re-chunk a document without colliding with the prior generation —
1144
+ -- the partial unique index keys on chunker_version, so a bumped
1145
+ -- chunker_version causes fresh inserts to coexist with the old soft-
1146
+ -- deleted rows. Phase 2 only ships chunker_version='phase2-fixed-v1'
1147
+ -- and parser_version='phase2-text-v1'; future phases bump.
1148
+ -- ---------------------------------------------------------------------------
1149
+
1150
+ CREATE TABLE IF NOT EXISTS document_chunks (
1151
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1152
+ user_id TEXT NOT NULL,
1153
+ raw_document_id UUID NOT NULL REFERENCES raw_documents(id) ON DELETE CASCADE,
1154
+ chunk_index INTEGER NOT NULL CHECK (chunk_index >= 0),
1155
+ content TEXT NOT NULL,
1156
+ content_hash TEXT NOT NULL,
1157
+ char_start INTEGER NOT NULL CHECK (char_start >= 0),
1158
+ char_end INTEGER NOT NULL CHECK (char_end >= char_start),
1159
+ token_count INTEGER NOT NULL CHECK (token_count >= 0),
1160
+ embedding vector({{EMBEDDING_DIMENSIONS}}) NOT NULL,
1161
+ parser_version TEXT NOT NULL,
1162
+ chunker_version TEXT NOT NULL,
1163
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
1164
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1165
+ deleted_at TIMESTAMPTZ
1166
+ );
1167
+
1168
+ -- Active-row uniqueness on (raw_document_id, chunk_index, chunker_version).
1169
+ -- Soft-deleted rows are excluded so a re-index after a previous chunker
1170
+ -- run leaves audit history intact while letting the new run succeed.
1171
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_document_chunks_active_unique
1172
+ ON document_chunks (raw_document_id, chunk_index, chunker_version)
1173
+ WHERE deleted_at IS NULL;
1174
+
1175
+ CREATE INDEX IF NOT EXISTS idx_document_chunks_document
1176
+ ON document_chunks (raw_document_id)
1177
+ WHERE deleted_at IS NULL;
1178
+ CREATE INDEX IF NOT EXISTS idx_document_chunks_user
1179
+ ON document_chunks (user_id)
1180
+ WHERE deleted_at IS NULL;
1181
+ CREATE INDEX IF NOT EXISTS idx_document_chunks_embedding
1182
+ ON document_chunks USING hnsw (embedding vector_cosine_ops)
1183
+ WITH (m = 16, ef_construction = 200);
1184
+
1185
+ -- ---------------------------------------------------------------------------
1186
+ -- Storage artifacts (Step 4 of the storage-sibling plan).
1187
+ --
1188
+ -- One row per artifact tracked by the direct storage API, independent
1189
+ -- of `raw_documents`. Pointer-mode rows carry a registered URI and
1190
+ -- never persist bytes (the server NEVER fetches the URI itself);
1191
+ -- managed-mode rows carry the adapter-returned URI plus the usual
1192
+ -- pending → available / deleting → deleted/delete_failed lifecycle.
1193
+ --
1194
+ -- Owner scoping lives on `user_id`. `org_id` / `project_id` are
1195
+ -- reserved for future multi-tenancy and stay NULL in v1.
1196
+ --
1197
+ -- Internal-only columns:
1198
+ -- * `plaintext_hash` — SHA-256 of caller bytes; never on the wire
1199
+ -- by default. The Step-5 response formatter exposes it only when
1200
+ -- the row's `disclose_content_hash = true` AND the caller opted in
1201
+ -- at put time.
1202
+ -- * `stored_hash` — SHA-256 of the bytes the adapter actually wrote;
1203
+ -- never on the wire under any condition.
1204
+ -- * `last_error` — internal failure envelope for delete retries.
1205
+ -- Step 5 is responsible for projecting the wire shape; this PR is
1206
+ -- DB-only and is allowed to keep the internal columns visible in the
1207
+ -- repository's row type.
1208
+ --
1209
+ -- FK direction: `raw_documents.storage_artifact_id REFERENCES
1210
+ -- storage_artifacts(id)`. Step 7 wires the document-ingestion paths
1211
+ -- to populate this column; Step 4 just defines the persistence
1212
+ -- surface and the reverse-lookup index.
1213
+ -- ---------------------------------------------------------------------------
1214
+
1215
+ CREATE TABLE IF NOT EXISTS storage_artifacts (
1216
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1217
+ user_id TEXT NOT NULL,
1218
+ org_id TEXT,
1219
+ project_id TEXT,
1220
+ provider TEXT NOT NULL,
1221
+ mode TEXT NOT NULL CHECK (mode IN ('pointer', 'managed')),
1222
+ -- Nullable while the row is in `pending` (managed put before
1223
+ -- backend.put has returned a URI); set to the adapter URI on
1224
+ -- success, stays NULL on `failed`. `pointer` rows always supply
1225
+ -- the URI at insert time. The partial unique index below covers
1226
+ -- the post-set side of the contract.
1227
+ uri TEXT,
1228
+ status TEXT NOT NULL
1229
+ CHECK (status IN
1230
+ ('stored', 'pending', 'available', 'unavailable',
1231
+ 'deleting', 'deleted', 'delete_failed', 'failed')),
1232
+ size_bytes BIGINT,
1233
+ content_type TEXT,
1234
+ -- Internal; never on the wire by default.
1235
+ plaintext_hash TEXT,
1236
+ -- Internal; never on the wire ever.
1237
+ stored_hash TEXT,
1238
+ content_encoding TEXT NOT NULL DEFAULT 'identity'
1239
+ CHECK (content_encoding IN ('identity', 'aes_gcm')),
1240
+ disclose_content_hash BOOLEAN NOT NULL DEFAULT FALSE,
1241
+ identifiers JSONB NOT NULL DEFAULT '{}'::jsonb,
1242
+ lifecycle JSONB NOT NULL DEFAULT '{}'::jsonb,
1243
+ replication JSONB,
1244
+ verification JSONB,
1245
+ retrieval JSONB,
1246
+ provider_details JSONB,
1247
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
1248
+ last_error JSONB,
1249
+ -- CAS token for the upload pipeline (pending-row-first put). Held
1250
+ -- by `claimPendingArtifact`, cleared by `recordUploadedArtifact` /
1251
+ -- `markPutFailed`. Distinct from `delete_attempt_id` so the two
1252
+ -- lifecycle phases never collide.
1253
+ put_attempt_id UUID,
1254
+ delete_attempt_id UUID,
1255
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1256
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1257
+ deleted_at TIMESTAMPTZ
1258
+ );
1259
+
1260
+ -- Forward-compat migration for deployments that already created
1261
+ -- `storage_artifacts` with `uri NOT NULL`. Idempotent: only fires
1262
+ -- the ALTER when information_schema reports the column as still
1263
+ -- NOT NULL, so a re-run against a freshly upgraded DB is a no-op
1264
+ -- without relying on `EXCEPTION WHEN OTHERS` (workspace rule
1265
+ -- forbids silent error swallowing).
1266
+ DO $$
1267
+ BEGIN
1268
+ IF EXISTS (
1269
+ SELECT 1 FROM information_schema.columns
1270
+ WHERE table_name = 'storage_artifacts'
1271
+ AND column_name = 'uri'
1272
+ AND is_nullable = 'NO'
1273
+ ) THEN
1274
+ ALTER TABLE storage_artifacts ALTER COLUMN uri DROP NOT NULL;
1275
+ END IF;
1276
+ END $$;
1277
+ ALTER TABLE storage_artifacts
1278
+ ADD COLUMN IF NOT EXISTS put_attempt_id UUID;
1279
+
1280
+ -- Concurrent `putManaged` callers must not produce two rows whose
1281
+ -- adapter URI collides. The unique index is partial: only managed
1282
+ -- rows with a URI set and not soft-deleted participate. Pointer
1283
+ -- rows are exempt — a single user legitimately has multiple
1284
+ -- pointer rows for the same caller-supplied URI (e.g. one created
1285
+ -- by `putPointer` against the active backend, another auto-paired
1286
+ -- to a document registration via `EXTERNAL_POINTER_PROVIDER`).
1287
+ -- `pending` / `failed` rows carry `uri IS NULL` and also fall out
1288
+ -- of the constraint naturally.
1289
+ CREATE UNIQUE INDEX IF NOT EXISTS uniq_storage_artifacts_user_managed_uri
1290
+ ON storage_artifacts (user_id, uri)
1291
+ WHERE uri IS NOT NULL AND deleted_at IS NULL AND mode = 'managed';
1292
+
1293
+ -- One-time cleanup: drop the over-broad index from an earlier rev
1294
+ -- of this migration. Idempotent.
1295
+ DROP INDEX IF EXISTS uniq_storage_artifacts_user_uri;
1296
+
1297
+ CREATE INDEX IF NOT EXISTS idx_storage_artifacts_user_status
1298
+ ON storage_artifacts (user_id, status) WHERE deleted_at IS NULL;
1299
+ CREATE INDEX IF NOT EXISTS idx_storage_artifacts_user_provider
1300
+ ON storage_artifacts (user_id, provider) WHERE deleted_at IS NULL;
1301
+ -- Cursor pagination keyed on (created_at DESC, id DESC) within a
1302
+ -- user. Partial index keeps it lean for healthy deployments.
1303
+ CREATE INDEX IF NOT EXISTS idx_storage_artifacts_user_created
1304
+ ON storage_artifacts (user_id, created_at DESC, id DESC)
1305
+ WHERE deleted_at IS NULL;
1306
+
1307
+ -- Non-partial unique index on (id, user_id). `id` alone is already the
1308
+ -- primary key; the composite uniqueness exists so the owner-scoped
1309
+ -- composite foreign key on raw_documents below has a valid target.
1310
+ -- Postgres accepts a non-partial unique index as an FK target, and
1311
+ -- this is the only place we need the (id, user_id) pair indexed.
1312
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_storage_artifacts_id_user
1313
+ ON storage_artifacts (id, user_id);
1314
+
1315
+ -- Reverse pointer from documents to their backing artifact. The FK is
1316
+ -- COMPOSITE on (storage_artifact_id, user_id) so the schema itself
1317
+ -- makes a USER_B raw_document pointing at USER_A's artifact
1318
+ -- impossible — the row would have to match BOTH columns of the
1319
+ -- referenced storage_artifacts row, and the artifact's `user_id`
1320
+ -- column carries the canonical owner. Populated in Step 7.
1321
+ --
1322
+ -- NULL `storage_artifact_id` is legitimate for rows registered
1323
+ -- without an `external_uri` (pointer-only registration stub) or rows
1324
+ -- that pre-date Step 7.
1325
+ ALTER TABLE raw_documents
1326
+ ADD COLUMN IF NOT EXISTS storage_artifact_id UUID NULL;
1327
+ -- This file is reapplied on every startup (`migrate.ts` runs schema.sql
1328
+ -- against the configured DB). The composite FK is therefore added
1329
+ -- only when it doesn't already exist, so repeated boots don't take
1330
+ -- ACCESS EXCLUSIVE on raw_documents to revalidate the same constraint.
1331
+ -- The legacy single-column FK is dropped the same way for one-time
1332
+ -- cleanup against any dev/test DB that applied the earlier shape.
1333
+ DO $$
1334
+ BEGIN
1335
+ IF EXISTS (
1336
+ SELECT 1 FROM pg_constraint
1337
+ WHERE conname = 'raw_documents_storage_artifact_id_fkey'
1338
+ AND conrelid = 'raw_documents'::regclass
1339
+ ) THEN
1340
+ ALTER TABLE raw_documents
1341
+ DROP CONSTRAINT raw_documents_storage_artifact_id_fkey;
1342
+ END IF;
1343
+ IF NOT EXISTS (
1344
+ SELECT 1 FROM pg_constraint
1345
+ WHERE conname = 'raw_documents_storage_artifact_owner_fkey'
1346
+ AND conrelid = 'raw_documents'::regclass
1347
+ ) THEN
1348
+ ALTER TABLE raw_documents
1349
+ ADD CONSTRAINT raw_documents_storage_artifact_owner_fkey
1350
+ FOREIGN KEY (storage_artifact_id, user_id)
1351
+ REFERENCES storage_artifacts (id, user_id);
1352
+ END IF;
1353
+ END $$;
1354
+ CREATE INDEX IF NOT EXISTS idx_raw_documents_storage_artifact
1355
+ ON raw_documents (storage_artifact_id) WHERE storage_artifact_id IS NOT NULL;