@agent-native/creative-context 0.1.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 (618) hide show
  1. package/README.md +108 -0
  2. package/agent-native.package.json +104 -0
  3. package/dist/actions/confirm-canonical-logo.d.ts +6 -0
  4. package/dist/actions/confirm-canonical-logo.d.ts.map +1 -0
  5. package/dist/actions/confirm-canonical-logo.js +19 -0
  6. package/dist/actions/confirm-canonical-logo.js.map +1 -0
  7. package/dist/actions/continue-context-import.d.ts +7 -0
  8. package/dist/actions/continue-context-import.d.ts.map +1 -0
  9. package/dist/actions/continue-context-import.js +10 -0
  10. package/dist/actions/continue-context-import.js.map +1 -0
  11. package/dist/actions/creative-context-a2a.d.ts +10 -0
  12. package/dist/actions/creative-context-a2a.d.ts.map +1 -0
  13. package/dist/actions/creative-context-a2a.js +66 -0
  14. package/dist/actions/creative-context-a2a.js.map +1 -0
  15. package/dist/actions/get-brand-profile.d.ts +33 -0
  16. package/dist/actions/get-brand-profile.d.ts.map +1 -0
  17. package/dist/actions/get-brand-profile.js +13 -0
  18. package/dist/actions/get-brand-profile.js.map +1 -0
  19. package/dist/actions/get-context-import-status.d.ts +22 -0
  20. package/dist/actions/get-context-import-status.d.ts.map +1 -0
  21. package/dist/actions/get-context-import-status.js +12 -0
  22. package/dist/actions/get-context-import-status.js.map +1 -0
  23. package/dist/actions/get-context-item.d.ts +2 -0
  24. package/dist/actions/get-context-item.d.ts.map +1 -0
  25. package/dist/actions/get-context-item.js +2 -0
  26. package/dist/actions/get-context-item.js.map +1 -0
  27. package/dist/actions/get-context-pack.d.ts +7 -0
  28. package/dist/actions/get-context-pack.d.ts.map +1 -0
  29. package/dist/actions/get-context-pack.js +12 -0
  30. package/dist/actions/get-context-pack.js.map +1 -0
  31. package/dist/actions/get-creative-context.d.ts +119 -0
  32. package/dist/actions/get-creative-context.d.ts.map +1 -0
  33. package/dist/actions/get-creative-context.js +112 -0
  34. package/dist/actions/get-creative-context.js.map +1 -0
  35. package/dist/actions/index.d.ts +3 -0
  36. package/dist/actions/index.d.ts.map +1 -0
  37. package/dist/actions/index.js +63 -0
  38. package/dist/actions/index.js.map +1 -0
  39. package/dist/actions/infer-brand-dna.d.ts +52 -0
  40. package/dist/actions/infer-brand-dna.d.ts.map +1 -0
  41. package/dist/actions/infer-brand-dna.js +13 -0
  42. package/dist/actions/infer-brand-dna.js.map +1 -0
  43. package/dist/actions/list-canonical-logo-candidates.d.ts +9 -0
  44. package/dist/actions/list-canonical-logo-candidates.d.ts.map +1 -0
  45. package/dist/actions/list-canonical-logo-candidates.js +82 -0
  46. package/dist/actions/list-canonical-logo-candidates.js.map +1 -0
  47. package/dist/actions/list-context-connections.d.ts +22 -0
  48. package/dist/actions/list-context-connections.d.ts.map +1 -0
  49. package/dist/actions/list-context-connections.js +43 -0
  50. package/dist/actions/list-context-connections.js.map +1 -0
  51. package/dist/actions/list-context-packs.d.ts +10 -0
  52. package/dist/actions/list-context-packs.d.ts.map +1 -0
  53. package/dist/actions/list-context-packs.js +16 -0
  54. package/dist/actions/list-context-packs.js.map +1 -0
  55. package/dist/actions/list-context-sources.d.ts +12 -0
  56. package/dist/actions/list-context-sources.d.ts.map +1 -0
  57. package/dist/actions/list-context-sources.js +20 -0
  58. package/dist/actions/list-context-sources.js.map +1 -0
  59. package/dist/actions/list-context-suggestions.d.ts +13 -0
  60. package/dist/actions/list-context-suggestions.d.ts.map +1 -0
  61. package/dist/actions/list-context-suggestions.js +28 -0
  62. package/dist/actions/list-context-suggestions.js.map +1 -0
  63. package/dist/actions/manage-brand-profile.d.ts +28 -0
  64. package/dist/actions/manage-brand-profile.d.ts.map +1 -0
  65. package/dist/actions/manage-brand-profile.js +47 -0
  66. package/dist/actions/manage-brand-profile.js.map +1 -0
  67. package/dist/actions/manage-context-pack.d.ts +33 -0
  68. package/dist/actions/manage-context-pack.d.ts.map +1 -0
  69. package/dist/actions/manage-context-pack.js +65 -0
  70. package/dist/actions/manage-context-pack.js.map +1 -0
  71. package/dist/actions/manage-context-source.d.ts +51 -0
  72. package/dist/actions/manage-context-source.d.ts.map +1 -0
  73. package/dist/actions/manage-context-source.js +137 -0
  74. package/dist/actions/manage-context-source.js.map +1 -0
  75. package/dist/actions/manage-layout-template.d.ts +13 -0
  76. package/dist/actions/manage-layout-template.d.ts.map +1 -0
  77. package/dist/actions/manage-layout-template.js +43 -0
  78. package/dist/actions/manage-layout-template.js.map +1 -0
  79. package/dist/actions/preview-context-import.d.ts +23 -0
  80. package/dist/actions/preview-context-import.d.ts.map +1 -0
  81. package/dist/actions/preview-context-import.js +57 -0
  82. package/dist/actions/preview-context-import.js.map +1 -0
  83. package/dist/actions/process-context-purge.d.ts +22 -0
  84. package/dist/actions/process-context-purge.d.ts.map +1 -0
  85. package/dist/actions/process-context-purge.js +19 -0
  86. package/dist/actions/process-context-purge.js.map +1 -0
  87. package/dist/actions/propose-brand-dna.d.ts +15 -0
  88. package/dist/actions/propose-brand-dna.d.ts.map +1 -0
  89. package/dist/actions/propose-brand-dna.js +19 -0
  90. package/dist/actions/propose-brand-dna.js.map +1 -0
  91. package/dist/actions/propose-canonical-logo.d.ts +9 -0
  92. package/dist/actions/propose-canonical-logo.d.ts.map +1 -0
  93. package/dist/actions/propose-canonical-logo.js +16 -0
  94. package/dist/actions/propose-canonical-logo.js.map +1 -0
  95. package/dist/actions/publish-brand-dna.d.ts +13 -0
  96. package/dist/actions/publish-brand-dna.d.ts.map +1 -0
  97. package/dist/actions/publish-brand-dna.js +23 -0
  98. package/dist/actions/publish-brand-dna.js.map +1 -0
  99. package/dist/actions/recommend-context-roots.d.ts +14 -0
  100. package/dist/actions/recommend-context-roots.d.ts.map +1 -0
  101. package/dist/actions/recommend-context-roots.js +22 -0
  102. package/dist/actions/recommend-context-roots.js.map +1 -0
  103. package/dist/actions/record-context-feedback.d.ts +10 -0
  104. package/dist/actions/record-context-feedback.d.ts.map +1 -0
  105. package/dist/actions/record-context-feedback.js +14 -0
  106. package/dist/actions/record-context-feedback.js.map +1 -0
  107. package/dist/actions/review-context-items.d.ts +35 -0
  108. package/dist/actions/review-context-items.d.ts.map +1 -0
  109. package/dist/actions/review-context-items.js +65 -0
  110. package/dist/actions/review-context-items.js.map +1 -0
  111. package/dist/actions/run-embedding-bakeoff.d.ts +15 -0
  112. package/dist/actions/run-embedding-bakeoff.d.ts.map +1 -0
  113. package/dist/actions/run-embedding-bakeoff.js +72 -0
  114. package/dist/actions/run-embedding-bakeoff.js.map +1 -0
  115. package/dist/actions/search-creative-context.d.ts +75 -0
  116. package/dist/actions/search-creative-context.d.ts.map +1 -0
  117. package/dist/actions/search-creative-context.js +41 -0
  118. package/dist/actions/search-creative-context.js.map +1 -0
  119. package/dist/actions/set-brand-dna.d.ts +15 -0
  120. package/dist/actions/set-brand-dna.d.ts.map +1 -0
  121. package/dist/actions/set-brand-dna.js +19 -0
  122. package/dist/actions/set-brand-dna.js.map +1 -0
  123. package/dist/actions/start-context-enrichment.d.ts +10 -0
  124. package/dist/actions/start-context-enrichment.d.ts.map +1 -0
  125. package/dist/actions/start-context-enrichment.js +46 -0
  126. package/dist/actions/start-context-enrichment.js.map +1 -0
  127. package/dist/actions/start-context-import.d.ts +9 -0
  128. package/dist/actions/start-context-import.d.ts.map +1 -0
  129. package/dist/actions/start-context-import.js +36 -0
  130. package/dist/actions/start-context-import.js.map +1 -0
  131. package/dist/client/CreativeContextChip.d.ts +13 -0
  132. package/dist/client/CreativeContextChip.d.ts.map +1 -0
  133. package/dist/client/CreativeContextChip.js +45 -0
  134. package/dist/client/CreativeContextChip.js.map +1 -0
  135. package/dist/client/CreativeContextPanel.d.ts +131 -0
  136. package/dist/client/CreativeContextPanel.d.ts.map +1 -0
  137. package/dist/client/CreativeContextPanel.js +778 -0
  138. package/dist/client/CreativeContextPanel.js.map +1 -0
  139. package/dist/client/CreativeContextSettingsLink.d.ts +4 -0
  140. package/dist/client/CreativeContextSettingsLink.d.ts.map +1 -0
  141. package/dist/client/CreativeContextSettingsLink.js +8 -0
  142. package/dist/client/CreativeContextSettingsLink.js.map +1 -0
  143. package/dist/client/actions.d.ts +256 -0
  144. package/dist/client/actions.d.ts.map +1 -0
  145. package/dist/client/actions.js +89 -0
  146. package/dist/client/actions.js.map +1 -0
  147. package/dist/client/agent-tab.d.ts +9 -0
  148. package/dist/client/agent-tab.d.ts.map +1 -0
  149. package/dist/client/agent-tab.js +32 -0
  150. package/dist/client/agent-tab.js.map +1 -0
  151. package/dist/client/application-state.d.ts +22 -0
  152. package/dist/client/application-state.d.ts.map +1 -0
  153. package/dist/client/application-state.js +88 -0
  154. package/dist/client/application-state.js.map +1 -0
  155. package/dist/client/index.d.ts +8 -0
  156. package/dist/client/index.d.ts.map +1 -0
  157. package/dist/client/index.js +8 -0
  158. package/dist/client/index.js.map +1 -0
  159. package/dist/client/messages.d.ts +5 -0
  160. package/dist/client/messages.d.ts.map +1 -0
  161. package/dist/client/messages.js +1059 -0
  162. package/dist/client/messages.js.map +1 -0
  163. package/dist/connectors/context.d.ts +9 -0
  164. package/dist/connectors/context.d.ts.map +1 -0
  165. package/dist/connectors/context.js +59 -0
  166. package/dist/connectors/context.js.map +1 -0
  167. package/dist/connectors/document-parser.d.ts +2 -0
  168. package/dist/connectors/document-parser.d.ts.map +1 -0
  169. package/dist/connectors/document-parser.js +2 -0
  170. package/dist/connectors/document-parser.js.map +1 -0
  171. package/dist/connectors/figma-native.d.ts +18 -0
  172. package/dist/connectors/figma-native.d.ts.map +1 -0
  173. package/dist/connectors/figma-native.js +883 -0
  174. package/dist/connectors/figma-native.js.map +1 -0
  175. package/dist/connectors/figma.d.ts +10 -0
  176. package/dist/connectors/figma.d.ts.map +1 -0
  177. package/dist/connectors/figma.js +166 -0
  178. package/dist/connectors/figma.js.map +1 -0
  179. package/dist/connectors/google-slides-native.d.ts +58 -0
  180. package/dist/connectors/google-slides-native.d.ts.map +1 -0
  181. package/dist/connectors/google-slides-native.js +1118 -0
  182. package/dist/connectors/google-slides-native.js.map +1 -0
  183. package/dist/connectors/google-slides.d.ts +15 -0
  184. package/dist/connectors/google-slides.d.ts.map +1 -0
  185. package/dist/connectors/google-slides.js +896 -0
  186. package/dist/connectors/google-slides.js.map +1 -0
  187. package/dist/connectors/index.d.ts +20 -0
  188. package/dist/connectors/index.d.ts.map +1 -0
  189. package/dist/connectors/index.js +44 -0
  190. package/dist/connectors/index.js.map +1 -0
  191. package/dist/connectors/manual.d.ts +9 -0
  192. package/dist/connectors/manual.d.ts.map +1 -0
  193. package/dist/connectors/manual.js +91 -0
  194. package/dist/connectors/manual.js.map +1 -0
  195. package/dist/connectors/normalize.d.ts +35 -0
  196. package/dist/connectors/normalize.d.ts.map +1 -0
  197. package/dist/connectors/normalize.js +216 -0
  198. package/dist/connectors/normalize.js.map +1 -0
  199. package/dist/connectors/notion.d.ts +10 -0
  200. package/dist/connectors/notion.d.ts.map +1 -0
  201. package/dist/connectors/notion.js +393 -0
  202. package/dist/connectors/notion.js.map +1 -0
  203. package/dist/connectors/private-artifacts.d.ts +39 -0
  204. package/dist/connectors/private-artifacts.d.ts.map +1 -0
  205. package/dist/connectors/private-artifacts.js +175 -0
  206. package/dist/connectors/private-artifacts.js.map +1 -0
  207. package/dist/connectors/provider-response.d.ts +20 -0
  208. package/dist/connectors/provider-response.d.ts.map +1 -0
  209. package/dist/connectors/provider-response.js +98 -0
  210. package/dist/connectors/provider-response.js.map +1 -0
  211. package/dist/connectors/recommendations.d.ts +25 -0
  212. package/dist/connectors/recommendations.d.ts.map +1 -0
  213. package/dist/connectors/recommendations.js +172 -0
  214. package/dist/connectors/recommendations.js.map +1 -0
  215. package/dist/connectors/registry.d.ts +10 -0
  216. package/dist/connectors/registry.d.ts.map +1 -0
  217. package/dist/connectors/registry.js +34 -0
  218. package/dist/connectors/registry.js.map +1 -0
  219. package/dist/connectors/rendered-page.d.ts +101 -0
  220. package/dist/connectors/rendered-page.d.ts.map +1 -0
  221. package/dist/connectors/rendered-page.js +504 -0
  222. package/dist/connectors/rendered-page.js.map +1 -0
  223. package/dist/connectors/smart-defaults.d.ts +9 -0
  224. package/dist/connectors/smart-defaults.d.ts.map +1 -0
  225. package/dist/connectors/smart-defaults.js +72 -0
  226. package/dist/connectors/smart-defaults.js.map +1 -0
  227. package/dist/connectors/types.d.ts +79 -0
  228. package/dist/connectors/types.d.ts.map +1 -0
  229. package/dist/connectors/types.js +2 -0
  230. package/dist/connectors/types.js.map +1 -0
  231. package/dist/connectors/upload.d.ts +9 -0
  232. package/dist/connectors/upload.d.ts.map +1 -0
  233. package/dist/connectors/upload.js +440 -0
  234. package/dist/connectors/upload.js.map +1 -0
  235. package/dist/connectors/website.d.ts +20 -0
  236. package/dist/connectors/website.d.ts.map +1 -0
  237. package/dist/connectors/website.js +616 -0
  238. package/dist/connectors/website.js.map +1 -0
  239. package/dist/embeddings/index.d.ts +3 -0
  240. package/dist/embeddings/index.d.ts.map +1 -0
  241. package/dist/embeddings/index.js +3 -0
  242. package/dist/embeddings/index.js.map +1 -0
  243. package/dist/embeddings/providers.d.ts +7 -0
  244. package/dist/embeddings/providers.d.ts.map +1 -0
  245. package/dist/embeddings/providers.js +176 -0
  246. package/dist/embeddings/providers.js.map +1 -0
  247. package/dist/embeddings/types.d.ts +19 -0
  248. package/dist/embeddings/types.d.ts.map +1 -0
  249. package/dist/embeddings/types.js +2 -0
  250. package/dist/embeddings/types.js.map +1 -0
  251. package/dist/eval/acceptance-fixtures.d.ts +3 -0
  252. package/dist/eval/acceptance-fixtures.d.ts.map +1 -0
  253. package/dist/eval/acceptance-fixtures.js +84 -0
  254. package/dist/eval/acceptance-fixtures.js.map +1 -0
  255. package/dist/eval/acceptance.d.ts +114 -0
  256. package/dist/eval/acceptance.d.ts.map +1 -0
  257. package/dist/eval/acceptance.js +260 -0
  258. package/dist/eval/acceptance.js.map +1 -0
  259. package/dist/eval/fixtures.d.ts +16 -0
  260. package/dist/eval/fixtures.d.ts.map +1 -0
  261. package/dist/eval/fixtures.js +119 -0
  262. package/dist/eval/fixtures.js.map +1 -0
  263. package/dist/eval/index.d.ts +63 -0
  264. package/dist/eval/index.d.ts.map +1 -0
  265. package/dist/eval/index.js +174 -0
  266. package/dist/eval/index.js.map +1 -0
  267. package/dist/index.d.ts +5 -0
  268. package/dist/index.d.ts.map +1 -0
  269. package/dist/index.js +5 -0
  270. package/dist/index.js.map +1 -0
  271. package/dist/jobs/background-worker.d.ts +31 -0
  272. package/dist/jobs/background-worker.d.ts.map +1 -0
  273. package/dist/jobs/background-worker.js +315 -0
  274. package/dist/jobs/background-worker.js.map +1 -0
  275. package/dist/jobs/import-runner.d.ts +3 -0
  276. package/dist/jobs/import-runner.d.ts.map +1 -0
  277. package/dist/jobs/import-runner.js +613 -0
  278. package/dist/jobs/import-runner.js.map +1 -0
  279. package/dist/jobs/index.d.ts +9 -0
  280. package/dist/jobs/index.d.ts.map +1 -0
  281. package/dist/jobs/index.js +8 -0
  282. package/dist/jobs/index.js.map +1 -0
  283. package/dist/jobs/progress.d.ts +3 -0
  284. package/dist/jobs/progress.d.ts.map +1 -0
  285. package/dist/jobs/progress.js +30 -0
  286. package/dist/jobs/progress.js.map +1 -0
  287. package/dist/jobs/rebuild.d.ts +13 -0
  288. package/dist/jobs/rebuild.d.ts.map +1 -0
  289. package/dist/jobs/rebuild.js +178 -0
  290. package/dist/jobs/rebuild.js.map +1 -0
  291. package/dist/jobs/server-worker.d.ts +16 -0
  292. package/dist/jobs/server-worker.d.ts.map +1 -0
  293. package/dist/jobs/server-worker.js +252 -0
  294. package/dist/jobs/server-worker.js.map +1 -0
  295. package/dist/jobs/store-adapter.d.ts +3 -0
  296. package/dist/jobs/store-adapter.d.ts.map +1 -0
  297. package/dist/jobs/store-adapter.js +37 -0
  298. package/dist/jobs/store-adapter.js.map +1 -0
  299. package/dist/jobs/types.d.ts +138 -0
  300. package/dist/jobs/types.d.ts.map +1 -0
  301. package/dist/jobs/types.js +2 -0
  302. package/dist/jobs/types.js.map +1 -0
  303. package/dist/jobs/worker.d.ts +27 -0
  304. package/dist/jobs/worker.d.ts.map +1 -0
  305. package/dist/jobs/worker.js +90 -0
  306. package/dist/jobs/worker.js.map +1 -0
  307. package/dist/media-url.d.ts +7 -0
  308. package/dist/media-url.d.ts.map +1 -0
  309. package/dist/media-url.js +15 -0
  310. package/dist/media-url.js.map +1 -0
  311. package/dist/native-artifact-reassembly.d.ts +25 -0
  312. package/dist/native-artifact-reassembly.d.ts.map +1 -0
  313. package/dist/native-artifact-reassembly.js +262 -0
  314. package/dist/native-artifact-reassembly.js.map +1 -0
  315. package/dist/native-artifact.d.ts +88 -0
  316. package/dist/native-artifact.d.ts.map +1 -0
  317. package/dist/native-artifact.js +86 -0
  318. package/dist/native-artifact.js.map +1 -0
  319. package/dist/schema/index.d.ts +5519 -0
  320. package/dist/schema/index.d.ts.map +1 -0
  321. package/dist/schema/index.js +374 -0
  322. package/dist/schema/index.js.map +1 -0
  323. package/dist/schema/migrations.d.ts +10 -0
  324. package/dist/schema/migrations.d.ts.map +1 -0
  325. package/dist/schema/migrations.js +253 -0
  326. package/dist/schema/migrations.js.map +1 -0
  327. package/dist/search/fusion.d.ts +15 -0
  328. package/dist/search/fusion.d.ts.map +1 -0
  329. package/dist/search/fusion.js +25 -0
  330. package/dist/search/fusion.js.map +1 -0
  331. package/dist/search/mode.d.ts +4 -0
  332. package/dist/search/mode.d.ts.map +1 -0
  333. package/dist/search/mode.js +13 -0
  334. package/dist/search/mode.js.map +1 -0
  335. package/dist/search/postgres-fts.d.ts +22 -0
  336. package/dist/search/postgres-fts.d.ts.map +1 -0
  337. package/dist/search/postgres-fts.js +83 -0
  338. package/dist/search/postgres-fts.js.map +1 -0
  339. package/dist/server/brand-context.d.ts +15 -0
  340. package/dist/server/brand-context.d.ts.map +1 -0
  341. package/dist/server/brand-context.js +149 -0
  342. package/dist/server/brand-context.js.map +1 -0
  343. package/dist/server/context.d.ts +77 -0
  344. package/dist/server/context.d.ts.map +1 -0
  345. package/dist/server/context.js +63 -0
  346. package/dist/server/context.js.map +1 -0
  347. package/dist/server/enrichment.d.ts +23 -0
  348. package/dist/server/enrichment.d.ts.map +1 -0
  349. package/dist/server/enrichment.js +136 -0
  350. package/dist/server/enrichment.js.map +1 -0
  351. package/dist/server/generation-artifact-access.d.ts +21 -0
  352. package/dist/server/generation-artifact-access.d.ts.map +1 -0
  353. package/dist/server/generation-artifact-access.js +111 -0
  354. package/dist/server/generation-artifact-access.js.map +1 -0
  355. package/dist/server/generation-context.d.ts +204 -0
  356. package/dist/server/generation-context.d.ts.map +1 -0
  357. package/dist/server/generation-context.js +310 -0
  358. package/dist/server/generation-context.js.map +1 -0
  359. package/dist/server/index.d.ts +22 -0
  360. package/dist/server/index.d.ts.map +1 -0
  361. package/dist/server/index.js +96 -0
  362. package/dist/server/index.js.map +1 -0
  363. package/dist/server/isolated-a2a.d.ts +172 -0
  364. package/dist/server/isolated-a2a.d.ts.map +1 -0
  365. package/dist/server/isolated-a2a.js +310 -0
  366. package/dist/server/isolated-a2a.js.map +1 -0
  367. package/dist/server/media.d.ts +15 -0
  368. package/dist/server/media.d.ts.map +1 -0
  369. package/dist/server/media.js +93 -0
  370. package/dist/server/media.js.map +1 -0
  371. package/dist/server/prompt-provider.d.ts +5 -0
  372. package/dist/server/prompt-provider.d.ts.map +1 -0
  373. package/dist/server/prompt-provider.js +59 -0
  374. package/dist/server/prompt-provider.js.map +1 -0
  375. package/dist/server/public-serialization.d.ts +160 -0
  376. package/dist/server/public-serialization.d.ts.map +1 -0
  377. package/dist/server/public-serialization.js +108 -0
  378. package/dist/server/public-serialization.js.map +1 -0
  379. package/dist/server/resource-paths.d.ts +3 -0
  380. package/dist/server/resource-paths.d.ts.map +1 -0
  381. package/dist/server/resource-paths.js +5 -0
  382. package/dist/server/resource-paths.js.map +1 -0
  383. package/dist/server/retrieval.d.ts +78 -0
  384. package/dist/server/retrieval.d.ts.map +1 -0
  385. package/dist/server/retrieval.js +443 -0
  386. package/dist/server/retrieval.js.map +1 -0
  387. package/dist/server/untrusted-reference.d.ts +8 -0
  388. package/dist/server/untrusted-reference.d.ts.map +1 -0
  389. package/dist/server/untrusted-reference.js +19 -0
  390. package/dist/server/untrusted-reference.js.map +1 -0
  391. package/dist/store/brand.d.ts +47 -0
  392. package/dist/store/brand.d.ts.map +1 -0
  393. package/dist/store/brand.js +360 -0
  394. package/dist/store/brand.js.map +1 -0
  395. package/dist/store/content.d.ts +120 -0
  396. package/dist/store/content.d.ts.map +1 -0
  397. package/dist/store/content.js +1437 -0
  398. package/dist/store/content.js.map +1 -0
  399. package/dist/store/generation.d.ts +28 -0
  400. package/dist/store/generation.d.ts.map +1 -0
  401. package/dist/store/generation.js +140 -0
  402. package/dist/store/generation.js.map +1 -0
  403. package/dist/store/helpers.d.ts +12 -0
  404. package/dist/store/helpers.d.ts.map +1 -0
  405. package/dist/store/helpers.js +41 -0
  406. package/dist/store/helpers.js.map +1 -0
  407. package/dist/store/index.d.ts +10 -0
  408. package/dist/store/index.d.ts.map +1 -0
  409. package/dist/store/index.js +10 -0
  410. package/dist/store/index.js.map +1 -0
  411. package/dist/store/inference.d.ts +57 -0
  412. package/dist/store/inference.d.ts.map +1 -0
  413. package/dist/store/inference.js +279 -0
  414. package/dist/store/inference.js.map +1 -0
  415. package/dist/store/jobs.d.ts +80 -0
  416. package/dist/store/jobs.d.ts.map +1 -0
  417. package/dist/store/jobs.js +428 -0
  418. package/dist/store/jobs.js.map +1 -0
  419. package/dist/store/packs.d.ts +33 -0
  420. package/dist/store/packs.d.ts.map +1 -0
  421. package/dist/store/packs.js +321 -0
  422. package/dist/store/packs.js.map +1 -0
  423. package/dist/store/purge.d.ts +16 -0
  424. package/dist/store/purge.d.ts.map +1 -0
  425. package/dist/store/purge.js +256 -0
  426. package/dist/store/purge.js.map +1 -0
  427. package/dist/store/sources.d.ts +76 -0
  428. package/dist/store/sources.d.ts.map +1 -0
  429. package/dist/store/sources.js +538 -0
  430. package/dist/store/sources.js.map +1 -0
  431. package/dist/store/suggestions.d.ts +28 -0
  432. package/dist/store/suggestions.d.ts.map +1 -0
  433. package/dist/store/suggestions.js +250 -0
  434. package/dist/store/suggestions.js.map +1 -0
  435. package/dist/types.d.ts +493 -0
  436. package/dist/types.d.ts.map +1 -0
  437. package/dist/types.js +2 -0
  438. package/dist/types.js.map +1 -0
  439. package/dist/vector/index.d.ts +2 -0
  440. package/dist/vector/index.d.ts.map +1 -0
  441. package/dist/vector/index.js +2 -0
  442. package/dist/vector/index.js.map +1 -0
  443. package/dist/vector/pgvector.d.ts +28 -0
  444. package/dist/vector/pgvector.d.ts.map +1 -0
  445. package/dist/vector/pgvector.js +117 -0
  446. package/dist/vector/pgvector.js.map +1 -0
  447. package/docs/evaluation.md +92 -0
  448. package/docs/llms-full.txt +168 -0
  449. package/docs/llms.txt +76 -0
  450. package/docs/skills/creative-context/SKILL.md +74 -0
  451. package/package.json +91 -0
  452. package/src/actions/agent-input-schemas.spec.ts +46 -0
  453. package/src/actions/confirm-canonical-logo.ts +20 -0
  454. package/src/actions/continue-context-import.ts +12 -0
  455. package/src/actions/creative-context-a2a.spec.ts +64 -0
  456. package/src/actions/creative-context-a2a.ts +88 -0
  457. package/src/actions/get-brand-profile.ts +16 -0
  458. package/src/actions/get-context-import-status.ts +13 -0
  459. package/src/actions/get-context-item.ts +1 -0
  460. package/src/actions/get-context-pack.ts +14 -0
  461. package/src/actions/get-creative-context.spec.ts +315 -0
  462. package/src/actions/get-creative-context.ts +148 -0
  463. package/src/actions/index.ts +65 -0
  464. package/src/actions/infer-brand-dna.ts +15 -0
  465. package/src/actions/list-canonical-logo-candidates.ts +121 -0
  466. package/src/actions/list-context-connections.spec.ts +23 -0
  467. package/src/actions/list-context-connections.ts +51 -0
  468. package/src/actions/list-context-packs.ts +17 -0
  469. package/src/actions/list-context-sources.ts +21 -0
  470. package/src/actions/list-context-suggestions.ts +30 -0
  471. package/src/actions/manage-brand-profile.ts +58 -0
  472. package/src/actions/manage-context-pack.ts +75 -0
  473. package/src/actions/manage-context-source.ts +154 -0
  474. package/src/actions/manage-layout-template.ts +51 -0
  475. package/src/actions/preview-context-import.spec.ts +64 -0
  476. package/src/actions/preview-context-import.ts +61 -0
  477. package/src/actions/process-context-purge.ts +25 -0
  478. package/src/actions/propose-brand-dna.ts +22 -0
  479. package/src/actions/propose-canonical-logo.ts +19 -0
  480. package/src/actions/publish-brand-dna.ts +25 -0
  481. package/src/actions/recommend-context-roots.ts +24 -0
  482. package/src/actions/record-context-feedback.ts +16 -0
  483. package/src/actions/review-context-items.ts +69 -0
  484. package/src/actions/run-embedding-bakeoff.ts +90 -0
  485. package/src/actions/search-creative-context.ts +47 -0
  486. package/src/actions/set-brand-dna.ts +22 -0
  487. package/src/actions/start-context-enrichment.ts +50 -0
  488. package/src/actions/start-context-import.ts +37 -0
  489. package/src/client/CreativeContextChip.tsx +162 -0
  490. package/src/client/CreativeContextPanel.tsx +2442 -0
  491. package/src/client/CreativeContextSettingsLink.tsx +30 -0
  492. package/src/client/actions.ts +438 -0
  493. package/src/client/agent-tab.tsx +58 -0
  494. package/src/client/application-state.spec.ts +41 -0
  495. package/src/client/application-state.ts +130 -0
  496. package/src/client/index.ts +62 -0
  497. package/src/client/messages.ts +1240 -0
  498. package/src/client/source-config.spec.ts +152 -0
  499. package/src/connectors/connectors.spec.ts +778 -0
  500. package/src/connectors/context.ts +83 -0
  501. package/src/connectors/document-parser.ts +5 -0
  502. package/src/connectors/figma-native.ts +1192 -0
  503. package/src/connectors/figma-native.visual.spec.ts +221 -0
  504. package/src/connectors/figma.spec.ts +812 -0
  505. package/src/connectors/figma.ts +257 -0
  506. package/src/connectors/fixtures/google-slides-native.json +353 -0
  507. package/src/connectors/google-slides-native.spec.ts +804 -0
  508. package/src/connectors/google-slides-native.ts +1656 -0
  509. package/src/connectors/google-slides-native.visual.spec.ts +119 -0
  510. package/src/connectors/google-slides.ts +1172 -0
  511. package/src/connectors/index.ts +103 -0
  512. package/src/connectors/manual.ts +132 -0
  513. package/src/connectors/normalize.ts +277 -0
  514. package/src/connectors/notion.ts +529 -0
  515. package/src/connectors/private-artifacts.spec.ts +95 -0
  516. package/src/connectors/private-artifacts.ts +248 -0
  517. package/src/connectors/provider-response.ts +144 -0
  518. package/src/connectors/recommendations.ts +237 -0
  519. package/src/connectors/registry.ts +48 -0
  520. package/src/connectors/rendered-page.ts +705 -0
  521. package/src/connectors/smart-defaults.spec.ts +135 -0
  522. package/src/connectors/smart-defaults.ts +87 -0
  523. package/src/connectors/types.ts +115 -0
  524. package/src/connectors/upload.spec.ts +175 -0
  525. package/src/connectors/upload.ts +548 -0
  526. package/src/connectors/website.spec.ts +509 -0
  527. package/src/connectors/website.ts +777 -0
  528. package/src/embeddings/index.ts +2 -0
  529. package/src/embeddings/providers.spec.ts +73 -0
  530. package/src/embeddings/providers.ts +228 -0
  531. package/src/embeddings/types.ts +24 -0
  532. package/src/eval/acceptance-fixtures.ts +97 -0
  533. package/src/eval/acceptance.spec.ts +92 -0
  534. package/src/eval/acceptance.ts +371 -0
  535. package/src/eval/connectors-golden.spec.ts +421 -0
  536. package/src/eval/eval.spec.ts +166 -0
  537. package/src/eval/fixtures/README.md +17 -0
  538. package/src/eval/fixtures/figma-brand-system-v1.json +140 -0
  539. package/src/eval/fixtures/figma-brand-system-v2.json +127 -0
  540. package/src/eval/fixtures/launch-hero.png +0 -0
  541. package/src/eval/fixtures/launch-system-v1.pptx +0 -0
  542. package/src/eval/fixtures/launch-system-v2.pptx +0 -0
  543. package/src/eval/fixtures/notion-brand-root.json +218 -0
  544. package/src/eval/fixtures/pricing-hero.png +0 -0
  545. package/src/eval/fixtures/rendered-brand-page.html +76 -0
  546. package/src/eval/fixtures.spec.ts +43 -0
  547. package/src/eval/fixtures.ts +136 -0
  548. package/src/eval/index.ts +291 -0
  549. package/src/eval/library-flow.acceptance.spec.tsx +141 -0
  550. package/src/index.ts +4 -0
  551. package/src/jobs/background-worker.ts +423 -0
  552. package/src/jobs/import-runner.spec.ts +582 -0
  553. package/src/jobs/import-runner.ts +792 -0
  554. package/src/jobs/index.ts +40 -0
  555. package/src/jobs/progress.ts +37 -0
  556. package/src/jobs/rebuild.ts +245 -0
  557. package/src/jobs/server-worker.spec.ts +159 -0
  558. package/src/jobs/server-worker.ts +344 -0
  559. package/src/jobs/store-adapter.ts +52 -0
  560. package/src/jobs/types.ts +147 -0
  561. package/src/jobs/worker.ts +152 -0
  562. package/src/manifest.spec.ts +26 -0
  563. package/src/media-url.ts +17 -0
  564. package/src/native-artifact-reassembly.spec.ts +386 -0
  565. package/src/native-artifact-reassembly.ts +406 -0
  566. package/src/native-artifact.ts +102 -0
  567. package/src/schema/index.ts +411 -0
  568. package/src/schema/migrations.spec.ts +41 -0
  569. package/src/schema/migrations.ts +258 -0
  570. package/src/search/fusion.spec.ts +21 -0
  571. package/src/search/fusion.ts +39 -0
  572. package/src/search/mode.spec.ts +28 -0
  573. package/src/search/mode.ts +21 -0
  574. package/src/search/postgres-fts.spec.ts +71 -0
  575. package/src/search/postgres-fts.ts +112 -0
  576. package/src/server/brand-context.spec.ts +43 -0
  577. package/src/server/brand-context.ts +160 -0
  578. package/src/server/context.ts +162 -0
  579. package/src/server/enrichment.ts +163 -0
  580. package/src/server/generation-artifact-access.ts +216 -0
  581. package/src/server/generation-context-a2a.spec.ts +131 -0
  582. package/src/server/generation-context-off.spec.ts +213 -0
  583. package/src/server/generation-context.spec.ts +124 -0
  584. package/src/server/generation-context.ts +493 -0
  585. package/src/server/index.ts +135 -0
  586. package/src/server/isolated-a2a.spec.ts +164 -0
  587. package/src/server/isolated-a2a.ts +413 -0
  588. package/src/server/media.spec.ts +92 -0
  589. package/src/server/media.ts +117 -0
  590. package/src/server/prompt-provider.spec.ts +78 -0
  591. package/src/server/prompt-provider.ts +73 -0
  592. package/src/server/public-serialization.spec.ts +149 -0
  593. package/src/server/public-serialization.ts +130 -0
  594. package/src/server/resource-paths.spec.ts +9 -0
  595. package/src/server/resource-paths.ts +5 -0
  596. package/src/server/retrieval-vector.spec.ts +180 -0
  597. package/src/server/retrieval.ts +587 -0
  598. package/src/server/untrusted-reference.spec.ts +22 -0
  599. package/src/server/untrusted-reference.ts +24 -0
  600. package/src/store/access.integration.spec.ts +1922 -0
  601. package/src/store/brand.ts +477 -0
  602. package/src/store/content.ts +1900 -0
  603. package/src/store/generation.ts +226 -0
  604. package/src/store/helpers.ts +53 -0
  605. package/src/store/index.ts +9 -0
  606. package/src/store/inference.spec.ts +84 -0
  607. package/src/store/inference.ts +339 -0
  608. package/src/store/invariants.spec.ts +119 -0
  609. package/src/store/jobs.ts +672 -0
  610. package/src/store/packs.ts +466 -0
  611. package/src/store/purge.ts +321 -0
  612. package/src/store/sources.spec.ts +102 -0
  613. package/src/store/sources.ts +708 -0
  614. package/src/store/suggestions.ts +333 -0
  615. package/src/types.ts +571 -0
  616. package/src/vector/index.ts +1 -0
  617. package/src/vector/pgvector.spec.ts +98 -0
  618. package/src/vector/pgvector.ts +164 -0
@@ -0,0 +1,1922 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+
7
+ import { normalizeContextItem } from "../connectors/normalize.js";
8
+
9
+ vi.setConfig({ hookTimeout: 30_000, testTimeout: 30_000 });
10
+
11
+ const originalDatabaseUrl = process.env.DATABASE_URL;
12
+ let tempDir: string;
13
+
14
+ beforeEach(() => {
15
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "creative-context-access-"));
16
+ process.env.DATABASE_URL = `file:${path.join(tempDir, "app.db")}`;
17
+ delete process.env.DATABASE_AUTH_TOKEN;
18
+ vi.resetModules();
19
+ });
20
+
21
+ afterEach(async () => {
22
+ const { closeDbExec } = await import("@agent-native/core/db");
23
+ await closeDbExec();
24
+ if (originalDatabaseUrl === undefined) delete process.env.DATABASE_URL;
25
+ else process.env.DATABASE_URL = originalDatabaseUrl;
26
+ fs.rmSync(tempDir, { recursive: true, force: true });
27
+ });
28
+
29
+ async function setup() {
30
+ const [{ getDbExec, runMigrations }, { runWithRequestContext }] =
31
+ await Promise.all([
32
+ import("@agent-native/core/db"),
33
+ import("@agent-native/core/server"),
34
+ ]);
35
+ const [{ creativeContextMigrations }, server, store] = await Promise.all([
36
+ import("../schema/migrations.js"),
37
+ import("../server/index.js"),
38
+ import("./index.js"),
39
+ ]);
40
+ server.configureCreativeContext();
41
+ await runMigrations(creativeContextMigrations, {
42
+ table: "creative_context_test_migrations",
43
+ })({});
44
+ const exec = getDbExec();
45
+ await exec.execute(`
46
+ CREATE TABLE IF NOT EXISTS org_members (
47
+ id TEXT PRIMARY KEY, org_id TEXT NOT NULL, email TEXT NOT NULL,
48
+ role TEXT NOT NULL, joined_at INTEGER NOT NULL
49
+ )
50
+ `);
51
+ await exec.execute({
52
+ sql: "INSERT INTO org_members (id, org_id, email, role, joined_at) VALUES (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)",
53
+ args: [
54
+ "member-alice",
55
+ "org-1",
56
+ "alice@example.test",
57
+ "owner",
58
+ 0,
59
+ "member-bob",
60
+ "org-1",
61
+ "bob@example.test",
62
+ "member",
63
+ 0,
64
+ ],
65
+ });
66
+ await exec.execute({
67
+ sql: `INSERT INTO creative_context_sources
68
+ (id, name, kind, config, upstream_access, status, health_status,
69
+ item_count, restricted_item_count, owner_email, org_id, visibility,
70
+ created_at, updated_at)
71
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
72
+ args: [
73
+ "source-1",
74
+ "Brand corpus",
75
+ "website",
76
+ "{}",
77
+ "available",
78
+ "active",
79
+ "healthy",
80
+ 2,
81
+ 1,
82
+ "alice@example.test",
83
+ "org-1",
84
+ "private",
85
+ "2026-07-16T00:00:00.000Z",
86
+ "2026-07-16T00:00:00.000Z",
87
+ ],
88
+ });
89
+ for (const item of [
90
+ { id: "allowed", access: "available", curation: "included" },
91
+ { id: "restricted", access: "restricted", curation: "review" },
92
+ ]) {
93
+ await exec.execute({
94
+ sql: `INSERT INTO creative_context_items
95
+ (id, source_id, external_id, kind, title, current_version_id,
96
+ current_content_hash, status, upstream_access, curation_status,
97
+ curation_rank, inventory_state, index_state, tags, colors, provenance,
98
+ metadata, created_at, updated_at, owner_email, org_id)
99
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
100
+ args: [
101
+ item.id,
102
+ "source-1",
103
+ item.id,
104
+ "slide",
105
+ `${item.id} title`,
106
+ `${item.id}-v2`,
107
+ `${item.id}-hash-v2`,
108
+ "active",
109
+ item.access,
110
+ item.curation,
111
+ "normal",
112
+ "available",
113
+ "pending",
114
+ "[]",
115
+ "[]",
116
+ "{}",
117
+ "{}",
118
+ "2026-07-16T00:00:00.000Z",
119
+ "2026-07-16T00:00:00.000Z",
120
+ "alice@example.test",
121
+ "org-1",
122
+ ],
123
+ });
124
+ for (const version of [1, 2]) {
125
+ await exec.execute({
126
+ sql: `INSERT INTO creative_context_item_versions
127
+ (id, item_id, version_number, content_hash, title, content,
128
+ parse_status, metadata, created_at, owner_email, org_id)
129
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
130
+ args: [
131
+ `${item.id}-v${version}`,
132
+ item.id,
133
+ version,
134
+ `${item.id}-hash-v${version}`,
135
+ `${item.id} title`,
136
+ `${item.id} canonical version ${version}`,
137
+ "parsed",
138
+ "{}",
139
+ `2026-07-1${version}T00:00:00.000Z`,
140
+ "alice@example.test",
141
+ "org-1",
142
+ ],
143
+ });
144
+ await exec.execute({
145
+ sql: `INSERT INTO creative_context_chunks
146
+ (id, item_id, item_version_id, ordinal, kind, text, metadata,
147
+ created_at, owner_email, org_id)
148
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
149
+ args: [
150
+ `${item.id}-chunk-v${version}`,
151
+ item.id,
152
+ `${item.id}-v${version}`,
153
+ 0,
154
+ "text",
155
+ `${item.id} canonical version ${version}`,
156
+ "{}",
157
+ `2026-07-1${version}T00:00:00.000Z`,
158
+ "alice@example.test",
159
+ "org-1",
160
+ ],
161
+ });
162
+ }
163
+ }
164
+ return { exec, runWithRequestContext, store };
165
+ }
166
+
167
+ describe("creative context access and revocation", () => {
168
+ it("promotes the exact compiled Slides layout instead of a supplied snapshot", async () => {
169
+ const { exec, runWithRequestContext, store } = await setup();
170
+ const server = await import("../server/index.js");
171
+ const promote = vi.fn(async () => {});
172
+ server.configureCreativeContext({
173
+ projections: {
174
+ layoutTemplate: {
175
+ promote,
176
+ demote: vi.fn(async () => {}),
177
+ },
178
+ },
179
+ });
180
+ const html =
181
+ '<div class="fmd-slide google-slides-native" data-slide-id="slide-1"><p>Exact layout</p></div>';
182
+ const nativeArtifact = {
183
+ schemaVersion: 1,
184
+ app: "slides",
185
+ format: "slides-html",
186
+ rootExternalId: "presentation-1:slide-1",
187
+ fidelityReport: {
188
+ exact: { count: 1 },
189
+ approximated: { count: 0, reasons: [] },
190
+ imageFallback: { count: 0, reasons: [] },
191
+ },
192
+ };
193
+ await exec.execute({
194
+ sql: `UPDATE creative_context_items
195
+ SET mime_type = ?, provenance = ? WHERE id = ?`,
196
+ args: [
197
+ "text/html",
198
+ JSON.stringify({
199
+ compiler: "@agent-native/creative-context:google-slides-native",
200
+ }),
201
+ "allowed",
202
+ ],
203
+ });
204
+ await exec.execute({
205
+ sql: `UPDATE creative_context_item_versions
206
+ SET content = ?, mime_type = ?, metadata = ? WHERE id = ?`,
207
+ args: [
208
+ html,
209
+ "text/html",
210
+ JSON.stringify({ nativeArtifact }),
211
+ "allowed-v2",
212
+ ],
213
+ });
214
+
215
+ const suggestion = await runWithRequestContext(
216
+ { userEmail: "alice@example.test", orgId: "org-1" },
217
+ () =>
218
+ store.proposeCreativeContextSuggestion({
219
+ kind: "layout-template",
220
+ itemId: "allowed",
221
+ itemVersionId: "allowed-v2",
222
+ payload: { htmlSnapshot: "<p>Untrusted supplied snapshot</p>" },
223
+ }),
224
+ );
225
+ await runWithRequestContext(
226
+ { userEmail: "alice@example.test", orgId: "org-1" },
227
+ () =>
228
+ store.applyLayoutTemplateSuggestion({
229
+ suggestionId: suggestion.id,
230
+ operation: "promote",
231
+ }),
232
+ );
233
+
234
+ expect(promote).toHaveBeenCalledWith(
235
+ expect.objectContaining({
236
+ itemId: "allowed",
237
+ itemVersionId: "allowed-v2",
238
+ htmlSnapshot: html,
239
+ }),
240
+ );
241
+ const projection = await exec.execute({
242
+ sql: `SELECT i.mime_type, i.provenance, v.content, v.metadata
243
+ FROM creative_context_items i
244
+ JOIN creative_context_item_versions v ON v.id = i.current_version_id
245
+ WHERE i.kind = 'layout_template'`,
246
+ args: [],
247
+ });
248
+ expect(projection.rows).toHaveLength(1);
249
+ expect(projection.rows[0]).toMatchObject({
250
+ mime_type: "text/html",
251
+ content: html,
252
+ });
253
+ expect(String(projection.rows[0]?.content)).not.toContain(
254
+ "Untrusted supplied snapshot",
255
+ );
256
+ expect(JSON.parse(String(projection.rows[0]?.provenance))).toMatchObject({
257
+ compiler: "@agent-native/creative-context:google-slides-native",
258
+ promotedFromItemId: "allowed",
259
+ promotedFromItemVersionId: "allowed-v2",
260
+ });
261
+ expect(JSON.parse(String(projection.rows[0]?.metadata))).toMatchObject({
262
+ nativeArtifact: {
263
+ app: "slides",
264
+ format: "slides-html",
265
+ },
266
+ });
267
+ });
268
+
269
+ it("shares generation history only after an app asserts same-org artifact access", async () => {
270
+ const { exec, runWithRequestContext, store } = await setup();
271
+ await exec.execute({
272
+ sql: `INSERT INTO creative_context_generation_records
273
+ (id, app_id, artifact_type, artifact_id, context_mode,
274
+ context_pack_id, element_provenance, created_at, owner_email, org_id)
275
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
276
+ args: [
277
+ "record-org-1",
278
+ "slides",
279
+ "deck",
280
+ "shared-deck",
281
+ "auto",
282
+ null,
283
+ '[{"elementId":"slide-1","influence":"generated"}]',
284
+ "2026-07-16T00:00:00.000Z",
285
+ "alice@example.test",
286
+ "org-1",
287
+ "record-other-org",
288
+ "slides",
289
+ "deck",
290
+ "shared-deck",
291
+ "auto",
292
+ null,
293
+ '[{"elementId":"secret","influence":"generated"}]',
294
+ "2026-07-16T01:00:00.000Z",
295
+ "mallory@example.test",
296
+ "org-2",
297
+ ],
298
+ });
299
+ const asBob = <T>(fn: () => Promise<T>) =>
300
+ runWithRequestContext(
301
+ { userEmail: "bob@example.test", orgId: "org-1" },
302
+ fn,
303
+ );
304
+ const artifact = {
305
+ appId: "slides",
306
+ artifactType: "deck",
307
+ artifactId: "shared-deck",
308
+ };
309
+
310
+ await expect(
311
+ asBob(() => store.getGenerationCreativeContext(artifact)),
312
+ ).resolves.toBeNull();
313
+ await expect(
314
+ asBob(() =>
315
+ store.getGenerationCreativeContext(artifact, {
316
+ accessScope: "artifact-access-asserted",
317
+ }),
318
+ ),
319
+ ).resolves.toMatchObject({
320
+ id: "record-org-1",
321
+ artifactId: "shared-deck",
322
+ elementProvenance: [{ elementId: "slide-1" }],
323
+ });
324
+ await expect(
325
+ asBob(() =>
326
+ store.getGenerationCreativeContext(
327
+ { ...artifact, artifactId: "arbitrary-unasserted-id" },
328
+ { accessScope: "owner" },
329
+ ),
330
+ ),
331
+ ).resolves.toBeNull();
332
+ });
333
+
334
+ it("reloads a concurrently inserted item instead of failing its idempotent ingest", async () => {
335
+ const { exec, runWithRequestContext, store } = await setup();
336
+ const ingest = () =>
337
+ runWithRequestContext(
338
+ { userEmail: "alice@example.test", orgId: "org-1" },
339
+ () =>
340
+ store.ingestItems({
341
+ sourceId: "source-1",
342
+ items: [
343
+ {
344
+ externalId: "concurrent-item",
345
+ kind: "slide",
346
+ title: "Concurrent item",
347
+ content: "Same immutable content",
348
+ contentHash: "concurrent-hash",
349
+ provenance: { fixture: true },
350
+ },
351
+ ],
352
+ }),
353
+ );
354
+
355
+ const results = await Promise.all([ingest(), ingest()]);
356
+ expect(results.reduce((total, result) => total + result.created, 0)).toBe(
357
+ 1,
358
+ );
359
+ expect(results.reduce((total, result) => total + result.unchanged, 0)).toBe(
360
+ 1,
361
+ );
362
+ const items = await exec.execute({
363
+ sql: "SELECT id FROM creative_context_items WHERE source_id = ? AND external_id = ?",
364
+ args: ["source-1", "concurrent-item"],
365
+ });
366
+ expect(items.rows).toHaveLength(1);
367
+ const versions = await exec.execute({
368
+ sql: "SELECT id FROM creative_context_item_versions WHERE item_id = ?",
369
+ args: [String(items.rows[0]!.id)],
370
+ });
371
+ expect(versions.rows).toHaveLength(1);
372
+ });
373
+
374
+ it("keeps prior native code immutable across resync and resolves hierarchical children by source version", async () => {
375
+ const { runWithRequestContext, store } = await setup();
376
+ const asAlice = <T>(fn: () => Promise<T>) =>
377
+ runWithRequestContext(
378
+ { userEmail: "alice@example.test", orgId: "org-1" },
379
+ fn,
380
+ );
381
+ const firstHtml =
382
+ "<!doctype html><html><head></head><body><div>Version one</div></body></html>";
383
+ const secondHtml =
384
+ "<!doctype html><html><head></head><body><div>Version two</div></body></html>";
385
+ const first = await asAlice(() =>
386
+ store.ingestItems({
387
+ sourceId: "source-1",
388
+ items: [
389
+ {
390
+ externalId: "figma-file:frame-1",
391
+ kind: "figma-frame",
392
+ title: "Native frame",
393
+ mimeType: "text/html",
394
+ content: firstHtml,
395
+ contentHash: "native-code-v1",
396
+ sourceVersion: "figma-v1",
397
+ provenance: {
398
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
399
+ },
400
+ },
401
+ ],
402
+ }),
403
+ );
404
+ const itemId = first.itemIds[0]!;
405
+ const firstDetail = await asAlice(() =>
406
+ store.getCreativeContextItem(itemId),
407
+ );
408
+ const firstVersionId = firstDetail!.version.id;
409
+
410
+ const second = await asAlice(() =>
411
+ store.ingestItems({
412
+ sourceId: "source-1",
413
+ items: [
414
+ {
415
+ externalId: "figma-file:frame-1",
416
+ kind: "figma-frame",
417
+ title: "Native frame",
418
+ mimeType: "text/html",
419
+ content: secondHtml,
420
+ contentHash: "native-code-v2",
421
+ sourceVersion: "figma-v2",
422
+ provenance: {
423
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
424
+ },
425
+ },
426
+ ],
427
+ }),
428
+ );
429
+ expect(second.versioned).toBe(1);
430
+ await expect(
431
+ asAlice(() => store.getCreativeContextItem(itemId, firstVersionId)),
432
+ ).resolves.toMatchObject({ version: { content: firstHtml } });
433
+ await expect(
434
+ asAlice(() => store.getCreativeContextItem(itemId)),
435
+ ).resolves.toMatchObject({ version: { content: secondHtml } });
436
+ await expect(
437
+ asAlice(() =>
438
+ store.getCreativeContextItemByExternalId({
439
+ sourceId: "source-1",
440
+ externalId: "figma-file:frame-1",
441
+ sourceVersion: "figma-v1",
442
+ }),
443
+ ),
444
+ ).resolves.toMatchObject({
445
+ version: { id: firstVersionId, content: firstHtml },
446
+ });
447
+ });
448
+
449
+ it("versions notes-only changes while packs retain the prior immutable evidence", async () => {
450
+ const { exec, runWithRequestContext, store } = await setup();
451
+ const asAlice = <T>(fn: () => Promise<T>) =>
452
+ runWithRequestContext(
453
+ { userEmail: "alice@example.test", orgId: "org-1" },
454
+ fn,
455
+ );
456
+ const slide = (notes: string) =>
457
+ normalizeContextItem({
458
+ externalId: "notes-deck:slide-1",
459
+ kind: "google-slides-slide",
460
+ title: "Launch plan",
461
+ mimeType: "text/html",
462
+ content: '<div class="fmd-slide">Unchanged slide</div>',
463
+ summary: `Speaker notes: ${notes}`,
464
+ sourceVersion: "same-provider-revision",
465
+ metadata: {
466
+ speakerNotes: notes,
467
+ nativeArtifact: {
468
+ schemaVersion: 1,
469
+ app: "slides",
470
+ format: "slides-html",
471
+ },
472
+ },
473
+ chunks: [
474
+ {
475
+ ordinal: 0,
476
+ kind: "slides-native-lexical",
477
+ text: `Unchanged slide Speaker notes ${notes}`,
478
+ },
479
+ ],
480
+ });
481
+
482
+ const first = await asAlice(() =>
483
+ store.ingestItems({ sourceId: "source-1", items: [slide("Draft")] }),
484
+ );
485
+ const itemId = first.itemIds[0]!;
486
+ const firstDetail = await asAlice(() =>
487
+ store.getCreativeContextItem(itemId),
488
+ );
489
+ const firstVersionId = firstDetail!.version.id;
490
+ const pack = await asAlice(() =>
491
+ store.createContextPack({
492
+ name: "Notes evidence",
493
+ members: [{ itemId }],
494
+ }),
495
+ );
496
+
497
+ const second = await asAlice(() =>
498
+ store.ingestItems({ sourceId: "source-1", items: [slide("Approved")] }),
499
+ );
500
+ expect(second).toMatchObject({ versioned: 1, unchanged: 0 });
501
+ await expect(
502
+ asAlice(() => store.getCreativeContextItem(itemId, firstVersionId)),
503
+ ).resolves.toMatchObject({
504
+ version: { versionNumber: 1, summary: "Speaker notes: Draft" },
505
+ chunks: [
506
+ expect.objectContaining({ text: expect.stringContaining("Draft") }),
507
+ ],
508
+ });
509
+ await expect(
510
+ asAlice(() => store.getCreativeContextItem(itemId)),
511
+ ).resolves.toMatchObject({
512
+ version: { versionNumber: 2, summary: "Speaker notes: Approved" },
513
+ chunks: [
514
+ expect.objectContaining({ text: expect.stringContaining("Approved") }),
515
+ ],
516
+ });
517
+ await expect(
518
+ asAlice(() => store.getContextPack(pack.id)),
519
+ ).resolves.toMatchObject({
520
+ members: [expect.objectContaining({ itemVersionId: firstVersionId })],
521
+ });
522
+ const versions = await exec.execute({
523
+ sql: "SELECT version_number FROM creative_context_item_versions WHERE item_id = ? ORDER BY version_number",
524
+ args: [itemId],
525
+ });
526
+ expect(versions.rows).toEqual([
527
+ expect.objectContaining({ version_number: 1 }),
528
+ expect.objectContaining({ version_number: 2 }),
529
+ ]);
530
+ });
531
+
532
+ it("dedupes reordered evidence but versions metadata and media-only changes", async () => {
533
+ const { exec, runWithRequestContext, store } = await setup();
534
+ const asAlice = <T>(fn: () => Promise<T>) =>
535
+ runWithRequestContext(
536
+ { userEmail: "alice@example.test", orgId: "org-1" },
537
+ fn,
538
+ );
539
+ const item = (input: { reversed: boolean; assetRole: string }) => {
540
+ const chunks = [
541
+ { ordinal: 0, kind: "text", text: "Stable body" },
542
+ { ordinal: 1, kind: "code", text: "font-family: Inter" },
543
+ ];
544
+ const media = [
545
+ {
546
+ kind: "image" as const,
547
+ accessMode: "public" as const,
548
+ url: "https://cdn.example.test/a.png",
549
+ contentHash: "asset-a",
550
+ metadata: { role: input.assetRole },
551
+ },
552
+ {
553
+ kind: "image" as const,
554
+ accessMode: "public" as const,
555
+ url: "https://cdn.example.test/b.png",
556
+ contentHash: "asset-b",
557
+ metadata: { role: "supporting" },
558
+ },
559
+ ];
560
+ const edges = [
561
+ { relation: "uses-asset", toExternalId: "asset-a" },
562
+ { relation: "uses-asset", toExternalId: "asset-b" },
563
+ ];
564
+ return normalizeContextItem({
565
+ externalId: "media-evidence",
566
+ kind: "design-artifact",
567
+ title: "Media evidence",
568
+ content: "Stable body",
569
+ sourceVersion: "stable-source-version",
570
+ metadata: { fidelity: { exact: 2 }, provider: "fixture" },
571
+ chunks: input.reversed ? [...chunks].reverse() : chunks,
572
+ media: input.reversed ? [...media].reverse() : media,
573
+ edges: input.reversed ? [...edges].reverse() : edges,
574
+ });
575
+ };
576
+
577
+ const original = item({ reversed: false, assetRole: "hero" });
578
+ const reordered = item({ reversed: true, assetRole: "hero" });
579
+ expect(reordered.contentHash).toBe(original.contentHash);
580
+ const first = await asAlice(() =>
581
+ store.ingestItems({ sourceId: "source-1", items: [original] }),
582
+ );
583
+ const itemId = first.itemIds[0]!;
584
+ const firstVersionId = (await asAlice(() =>
585
+ store.getCreativeContextItem(itemId),
586
+ ))!.version.id;
587
+ const pack = await asAlice(() =>
588
+ store.createContextPack({
589
+ name: "Media evidence",
590
+ members: [{ itemId }],
591
+ }),
592
+ );
593
+ const unchanged = await asAlice(() =>
594
+ store.ingestItems({ sourceId: "source-1", items: [reordered] }),
595
+ );
596
+ expect(unchanged).toMatchObject({ versioned: 0, unchanged: 1 });
597
+
598
+ const changed = await asAlice(() =>
599
+ store.ingestItems({
600
+ sourceId: "source-1",
601
+ items: [item({ reversed: true, assetRole: "canonical-logo" })],
602
+ }),
603
+ );
604
+ expect(changed).toMatchObject({ versioned: 1, unchanged: 0 });
605
+ await expect(
606
+ asAlice(() => store.getCreativeContextItem(itemId, firstVersionId)),
607
+ ).resolves.toMatchObject({
608
+ version: { versionNumber: 1 },
609
+ media: [
610
+ expect.objectContaining({ metadata: { role: "hero" } }),
611
+ expect.objectContaining({ metadata: { role: "supporting" } }),
612
+ ],
613
+ });
614
+ await expect(
615
+ asAlice(() => store.getCreativeContextItem(itemId)),
616
+ ).resolves.toMatchObject({
617
+ version: { versionNumber: 2 },
618
+ media: expect.arrayContaining([
619
+ expect.objectContaining({ metadata: { role: "canonical-logo" } }),
620
+ ]),
621
+ });
622
+ await expect(
623
+ asAlice(() => store.getContextPack(pack.id)),
624
+ ).resolves.toMatchObject({
625
+ members: [expect.objectContaining({ itemVersionId: firstVersionId })],
626
+ });
627
+ const versions = await exec.execute({
628
+ sql: "SELECT version_number FROM creative_context_item_versions WHERE item_id = ? ORDER BY version_number",
629
+ args: [itemId],
630
+ });
631
+ expect(versions.rows).toHaveLength(2);
632
+ });
633
+
634
+ it("pins hierarchical parent versions to exact child versions across later resyncs and access loss", async () => {
635
+ const { exec, runWithRequestContext, store } = await setup();
636
+ const { reassembleNativeCreativeArtifact } =
637
+ await import("../native-artifact-reassembly.js");
638
+ const asAlice = <T>(fn: () => Promise<T>) =>
639
+ runWithRequestContext(
640
+ { userEmail: "alice@example.test", orgId: "org-1" },
641
+ fn,
642
+ );
643
+ const fidelityReport = {
644
+ exact: { count: 1 },
645
+ approximated: { count: 0, reasons: [] },
646
+ imageFallback: { count: 0, reasons: [] },
647
+ };
648
+ const parentExternalId = "figma-file:parent";
649
+ const childExternalId = "figma-file:child";
650
+ const parentHtml = (label: string) =>
651
+ `<!doctype html><html><head></head><body><div data-label="${label}"><div data-creative-context-child="${childExternalId}"></div></div></body></html>`;
652
+ const childHtml = (label: string) =>
653
+ `<!doctype html><html><head></head><body><div>${label}</div></body></html>`;
654
+ const nativeMetadata = (externalId: string, child = false) => ({
655
+ nativeArtifact: {
656
+ schemaVersion: 1,
657
+ app: "design",
658
+ format: "design-html",
659
+ rootExternalId: externalId,
660
+ ...(child
661
+ ? {}
662
+ : {
663
+ childExternalIds: [childExternalId],
664
+ manifest: {
665
+ kind: "hierarchical-artboard",
666
+ children: [
667
+ {
668
+ externalId: childExternalId,
669
+ sourceNodeId: "2:1",
670
+ bounds: { x: 10, y: 20, width: 200, height: 100 },
671
+ zOrder: 0,
672
+ },
673
+ ],
674
+ },
675
+ }),
676
+ fidelityReport,
677
+ },
678
+ });
679
+ const provenance = {
680
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
681
+ };
682
+ const parentEdges = [
683
+ {
684
+ relation: "contains-native-child",
685
+ toExternalId: childExternalId,
686
+ },
687
+ ];
688
+
689
+ const first = await asAlice(() =>
690
+ store.ingestItems({
691
+ sourceId: "source-1",
692
+ items: [
693
+ {
694
+ externalId: parentExternalId,
695
+ kind: "figma-artboard-manifest",
696
+ title: "Parent",
697
+ mimeType: "text/html",
698
+ content: parentHtml("parent-v1"),
699
+ contentHash: "parent-v1",
700
+ sourceVersion: "figma-v1",
701
+ provenance,
702
+ metadata: nativeMetadata(parentExternalId),
703
+ edges: parentEdges,
704
+ },
705
+ {
706
+ externalId: childExternalId,
707
+ kind: "figma-frame",
708
+ title: "Child",
709
+ mimeType: "text/html",
710
+ content: childHtml("Child v1"),
711
+ contentHash: "child-v1",
712
+ sourceVersion: "figma-v1",
713
+ provenance,
714
+ metadata: nativeMetadata(childExternalId, true),
715
+ },
716
+ ],
717
+ }),
718
+ );
719
+ const parentId = first.itemIds[0]!;
720
+ const childId = first.itemIds[1]!;
721
+ const childV1 = await asAlice(() => store.getCreativeContextItem(childId));
722
+
723
+ await asAlice(() =>
724
+ store.ingestItems({
725
+ sourceId: "source-1",
726
+ items: [
727
+ {
728
+ externalId: parentExternalId,
729
+ kind: "figma-artboard-manifest",
730
+ title: "Parent",
731
+ mimeType: "text/html",
732
+ content: parentHtml("parent-v2"),
733
+ contentHash: "parent-v2",
734
+ sourceVersion: "figma-v2",
735
+ provenance,
736
+ metadata: nativeMetadata(parentExternalId),
737
+ edges: parentEdges,
738
+ },
739
+ {
740
+ externalId: childExternalId,
741
+ kind: "figma-frame",
742
+ title: "Child",
743
+ mimeType: "text/html",
744
+ content: childHtml("Child v1"),
745
+ contentHash: "child-v1",
746
+ sourceVersion: "figma-v2",
747
+ provenance,
748
+ metadata: nativeMetadata(childExternalId, true),
749
+ },
750
+ ],
751
+ }),
752
+ );
753
+ const parentV2 = await asAlice(() =>
754
+ store.getCreativeContextItem(parentId),
755
+ );
756
+ expect(parentV2?.edges).toEqual(
757
+ expect.arrayContaining([
758
+ expect.objectContaining({
759
+ relation: "contains-native-child",
760
+ toItemId: childId,
761
+ toItemVersionId: childV1!.version.id,
762
+ }),
763
+ ]),
764
+ );
765
+
766
+ await asAlice(() =>
767
+ store.ingestItems({
768
+ sourceId: "source-1",
769
+ items: [
770
+ {
771
+ externalId: childExternalId,
772
+ kind: "figma-frame",
773
+ title: "Child",
774
+ mimeType: "text/html",
775
+ content: childHtml("Child v2"),
776
+ contentHash: "child-v2",
777
+ sourceVersion: "figma-v3",
778
+ provenance,
779
+ metadata: nativeMetadata(childExternalId, true),
780
+ },
781
+ ],
782
+ }),
783
+ );
784
+ const pinnedParent = await asAlice(() =>
785
+ store.getCreativeContextItem(parentId, parentV2!.version.id),
786
+ );
787
+ const reassembled = await asAlice(() =>
788
+ reassembleNativeCreativeArtifact({
789
+ root: pinnedParent!,
790
+ app: "design",
791
+ format: "design-html",
792
+ resolveChild: store.getCreativeContextItemByExternalId,
793
+ }),
794
+ );
795
+ expect(reassembled.html).toContain("Child v1");
796
+ expect(reassembled.html).not.toContain("Child v2");
797
+
798
+ await exec.execute({
799
+ sql: "UPDATE creative_context_items SET status = 'unavailable' WHERE id = ?",
800
+ args: [childId],
801
+ });
802
+ await expect(
803
+ asAlice(() =>
804
+ reassembleNativeCreativeArtifact({
805
+ root: pinnedParent!,
806
+ app: "design",
807
+ format: "design-html",
808
+ resolveChild: store.getCreativeContextItemByExternalId,
809
+ }),
810
+ ),
811
+ ).rejects.toThrow("unavailable at the pinned source version");
812
+ });
813
+
814
+ it("finds native artifacts through bounded code-token chunks", async () => {
815
+ const { runWithRequestContext, store } = await setup();
816
+ const { performCreativeContextSearch } =
817
+ await import("../server/retrieval.js");
818
+ const asAlice = <T>(fn: () => Promise<T>) =>
819
+ runWithRequestContext(
820
+ { userEmail: "alice@example.test", orgId: "org-1" },
821
+ fn,
822
+ );
823
+ const ingested = await asAlice(() =>
824
+ store.ingestItems({
825
+ sourceId: "source-1",
826
+ items: [
827
+ {
828
+ externalId: "figma-file:metric-card",
829
+ kind: "figma-frame",
830
+ title: "Metric card",
831
+ mimeType: "text/html",
832
+ content:
833
+ '<!doctype html><html><head></head><body><div class="metric-card"></div></body></html>',
834
+ contentHash: "metric-card-native-v1",
835
+ chunks: [
836
+ {
837
+ ordinal: 0,
838
+ kind: "code",
839
+ text: "metric-card display-grid align-items-center",
840
+ metadata: {
841
+ role: "code-tokens",
842
+ format: "design-html",
843
+ },
844
+ },
845
+ ],
846
+ },
847
+ ],
848
+ }),
849
+ );
850
+
851
+ const result = await asAlice(() =>
852
+ performCreativeContextSearch({
853
+ query: "metric-card align-items-center",
854
+ limit: 5,
855
+ matchMode: "allTerms",
856
+ snapshot: false,
857
+ }),
858
+ );
859
+ expect(result.results).toEqual(
860
+ expect.arrayContaining([
861
+ expect.objectContaining({
862
+ itemId: ingested.itemIds[0],
863
+ kind: "figma-frame",
864
+ }),
865
+ ]),
866
+ );
867
+ });
868
+
869
+ it("reloads a concurrently discovered inventory item instead of duplicating it", async () => {
870
+ const { exec, runWithRequestContext, store } = await setup();
871
+ const upsert = () =>
872
+ runWithRequestContext(
873
+ { userEmail: "alice@example.test", orgId: "org-1" },
874
+ () =>
875
+ store.upsertSourceInventory({
876
+ sourceId: "source-1",
877
+ items: [
878
+ {
879
+ externalId: "concurrent-inventory",
880
+ kind: "document",
881
+ title: "Concurrent inventory",
882
+ },
883
+ ],
884
+ }),
885
+ );
886
+
887
+ const results = await Promise.all([upsert(), upsert()]);
888
+ expect(results.reduce((total, result) => total + result.created, 0)).toBe(
889
+ 1,
890
+ );
891
+ expect(results.reduce((total, result) => total + result.updated, 0)).toBe(
892
+ 1,
893
+ );
894
+ const items = await exec.execute({
895
+ sql: "SELECT id FROM creative_context_items WHERE source_id = ? AND external_id = ?",
896
+ args: ["source-1", "concurrent-inventory"],
897
+ });
898
+ expect(items.rows).toHaveLength(1);
899
+ });
900
+
901
+ it("reconciles removed and restored inventory without weakening source access", async () => {
902
+ const { exec, runWithRequestContext, store } = await setup();
903
+ const asAlice = <T>(fn: () => Promise<T>) =>
904
+ runWithRequestContext(
905
+ { userEmail: "alice@example.test", orgId: "org-1" },
906
+ fn,
907
+ );
908
+ const asBob = <T>(fn: () => Promise<T>) =>
909
+ runWithRequestContext(
910
+ { userEmail: "bob@example.test", orgId: "org-1" },
911
+ fn,
912
+ );
913
+
914
+ await expect(
915
+ asBob(() =>
916
+ store.reconcileSourceInventory({
917
+ sourceId: "source-1",
918
+ presentExternalIds: ["allowed"],
919
+ }),
920
+ ),
921
+ ).rejects.toThrow();
922
+
923
+ await expect(
924
+ asAlice(() =>
925
+ store.reconcileSourceInventory({
926
+ sourceId: "source-1",
927
+ presentExternalIds: ["allowed"],
928
+ completedAt: "2026-07-16T01:00:00.000Z",
929
+ }),
930
+ ),
931
+ ).resolves.toEqual({ removed: 1, restored: 0 });
932
+ let rows = await exec.execute(
933
+ "SELECT id, status, inventory_state FROM creative_context_items ORDER BY id",
934
+ );
935
+ expect(rows.rows).toEqual([
936
+ expect.objectContaining({
937
+ id: "allowed",
938
+ status: "active",
939
+ inventory_state: "available",
940
+ }),
941
+ expect.objectContaining({
942
+ id: "restricted",
943
+ status: "unavailable",
944
+ inventory_state: "removed",
945
+ }),
946
+ ]);
947
+ let source = await exec.execute(
948
+ "SELECT item_count, restricted_item_count FROM creative_context_sources WHERE id = 'source-1'",
949
+ );
950
+ expect(source.rows[0]).toMatchObject({
951
+ item_count: 1,
952
+ restricted_item_count: 0,
953
+ });
954
+
955
+ await expect(
956
+ asAlice(() =>
957
+ store.reconcileSourceInventory({
958
+ sourceId: "source-1",
959
+ presentExternalIds: ["allowed", "restricted"],
960
+ completedAt: "2026-07-16T02:00:00.000Z",
961
+ }),
962
+ ),
963
+ ).resolves.toEqual({ removed: 0, restored: 1 });
964
+ rows = await exec.execute(
965
+ "SELECT status, inventory_state, upstream_access, curation_status FROM creative_context_items WHERE id = 'restricted'",
966
+ );
967
+ expect(rows.rows[0]).toMatchObject({
968
+ status: "active",
969
+ inventory_state: "available",
970
+ upstream_access: "restricted",
971
+ curation_status: "review",
972
+ });
973
+ source = await exec.execute(
974
+ "SELECT item_count, restricted_item_count FROM creative_context_sources WHERE id = 'source-1'",
975
+ );
976
+ expect(source.rows[0]).toMatchObject({
977
+ item_count: 2,
978
+ restricted_item_count: 1,
979
+ });
980
+ });
981
+
982
+ it("creates one daily maintenance job across concurrent app workers", async () => {
983
+ const { exec, runWithRequestContext, store } = await setup();
984
+ const create = () =>
985
+ runWithRequestContext(
986
+ { userEmail: "alice@example.test", orgId: "org-1" },
987
+ () =>
988
+ store.createDailyMaintenanceJob({
989
+ sourceId: "source-1",
990
+ scheduledAt: "2026-07-16T17:00:00.000Z",
991
+ }),
992
+ );
993
+ const results = await Promise.all([create(), create(), create(), create()]);
994
+ expect(results.filter((result) => result.created)).toHaveLength(1);
995
+ expect(new Set(results.map((result) => result.job.id))).toHaveLength(1);
996
+ const rows = await exec.execute({
997
+ sql: "SELECT id FROM creative_context_jobs WHERE scoped_dedupe_key = ?",
998
+ args: ["daily-maintenance:source-1:2026-07-16"],
999
+ });
1000
+ expect(rows.rows).toHaveLength(1);
1001
+ });
1002
+
1003
+ it("immediately tombstones sources when workspace connection access is removed", async () => {
1004
+ const { exec, runWithRequestContext, store } = await setup();
1005
+ await exec.execute(
1006
+ "UPDATE creative_context_sources SET connection_id = 'connection-1' WHERE id = 'source-1'",
1007
+ );
1008
+ const result = await runWithRequestContext(
1009
+ { userEmail: "alice@example.test", orgId: "org-1" },
1010
+ () =>
1011
+ store.handleWorkspaceConnectionLifecycle({
1012
+ type: "grant-revoked",
1013
+ connectionId: "connection-1",
1014
+ appId: "slides",
1015
+ ownerEmail: "alice@example.test",
1016
+ orgId: "org-1",
1017
+ }),
1018
+ );
1019
+ expect(result).toEqual({ sources: 1, jobs: 1 });
1020
+ const source = await exec.execute(
1021
+ "SELECT status, health_status FROM creative_context_sources WHERE id = 'source-1'",
1022
+ );
1023
+ expect(source.rows[0]).toMatchObject({
1024
+ status: "paused",
1025
+ health_status: "needs_setup",
1026
+ });
1027
+ const items = await exec.execute(
1028
+ "SELECT DISTINCT status, inventory_state FROM creative_context_items WHERE source_id = 'source-1'",
1029
+ );
1030
+ expect(items.rows).toEqual([
1031
+ expect.objectContaining({
1032
+ status: "unavailable",
1033
+ inventory_state: "removed",
1034
+ }),
1035
+ ]);
1036
+ const jobs = await exec.execute(
1037
+ "SELECT kind, source_id FROM creative_context_jobs",
1038
+ );
1039
+ expect(jobs.rows).toEqual([
1040
+ expect.objectContaining({ kind: "purge", source_id: "source-1" }),
1041
+ ]);
1042
+ });
1043
+
1044
+ it("searches an immutable pack at its pinned historical item version", async () => {
1045
+ const { exec, runWithRequestContext, store } = await setup();
1046
+ await exec.execute({
1047
+ sql: `INSERT INTO creative_context_packs
1048
+ (id, name, context_mode, request, created_at, owner_email, org_id, visibility)
1049
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1050
+ args: [
1051
+ "pack-v1",
1052
+ "Historical launch pack",
1053
+ "manual",
1054
+ "{}",
1055
+ "2026-07-16T00:00:00.000Z",
1056
+ "alice@example.test",
1057
+ "org-1",
1058
+ "private",
1059
+ ],
1060
+ });
1061
+ await exec.execute({
1062
+ sql: `INSERT INTO creative_context_pack_members
1063
+ (id, pack_id, item_id, item_version_id, ordinal, score_metadata,
1064
+ created_at, owner_email, org_id)
1065
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1066
+ args: [
1067
+ "member-v1",
1068
+ "pack-v1",
1069
+ "allowed",
1070
+ "allowed-v1",
1071
+ 0,
1072
+ "{}",
1073
+ "2026-07-16T00:00:00.000Z",
1074
+ "alice@example.test",
1075
+ "org-1",
1076
+ ],
1077
+ });
1078
+ const result = await runWithRequestContext(
1079
+ { userEmail: "alice@example.test", orgId: "org-1" },
1080
+ () =>
1081
+ store.listAccessibleLexicalCandidates({
1082
+ query: "version 1",
1083
+ packId: "pack-v1",
1084
+ limit: 10,
1085
+ }),
1086
+ );
1087
+ expect(result.results).toEqual([
1088
+ expect.objectContaining({
1089
+ itemId: "allowed",
1090
+ itemVersionId: "allowed-v1",
1091
+ chunkId: "allowed-chunk-v1",
1092
+ }),
1093
+ ]);
1094
+ });
1095
+
1096
+ it("allows explicit deprecated search without making removed items retrievable", async () => {
1097
+ const { exec, runWithRequestContext, store } = await setup();
1098
+ await exec.execute(
1099
+ "UPDATE creative_context_items SET status = 'deprecated' WHERE id = 'allowed'",
1100
+ );
1101
+ const search = (statuses: any[]) =>
1102
+ runWithRequestContext(
1103
+ { userEmail: "alice@example.test", orgId: "org-1" },
1104
+ () =>
1105
+ store.listAccessibleLexicalCandidates({
1106
+ query: "canonical version 2",
1107
+ statuses,
1108
+ limit: 10,
1109
+ }),
1110
+ );
1111
+ await expect(search(["deprecated"])).resolves.toMatchObject({
1112
+ results: [expect.objectContaining({ itemId: "allowed" })],
1113
+ });
1114
+ await expect(search(["deleted"])).rejects.toThrow(
1115
+ "cannot include removed items",
1116
+ );
1117
+ });
1118
+
1119
+ it("stops injecting published DNA immediately when its source is purged", async () => {
1120
+ const { exec, runWithRequestContext, store } = await setup();
1121
+ await exec.execute({
1122
+ sql: `INSERT INTO creative_context_brand_profiles
1123
+ (id, name, current_dna_version_id, created_at, updated_at,
1124
+ owner_email, org_id, visibility)
1125
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1126
+ args: [
1127
+ "profile-1",
1128
+ "Brand",
1129
+ "dna-1",
1130
+ "2026-07-16T00:00:00.000Z",
1131
+ "2026-07-16T00:00:00.000Z",
1132
+ "alice@example.test",
1133
+ "org-1",
1134
+ "private",
1135
+ ],
1136
+ });
1137
+ await exec.execute({
1138
+ sql: `INSERT INTO creative_context_brand_dna_versions
1139
+ (id, profile_id, version_number, payload, content_hash, status,
1140
+ created_at, owner_email, org_id)
1141
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1142
+ args: [
1143
+ "dna-1",
1144
+ "profile-1",
1145
+ 1,
1146
+ '{"summary":"Imported brand","colors":["#112233"]}',
1147
+ "dna-hash",
1148
+ "published",
1149
+ "2026-07-16T00:00:00.000Z",
1150
+ "alice@example.test",
1151
+ "org-1",
1152
+ ],
1153
+ });
1154
+ await exec.execute({
1155
+ sql: `INSERT INTO creative_context_brand_dna_evidence
1156
+ (id, dna_version_id, item_id, item_version_id, created_at,
1157
+ owner_email, org_id)
1158
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1159
+ args: [
1160
+ "evidence-1",
1161
+ "dna-1",
1162
+ "allowed",
1163
+ "allowed-v1",
1164
+ "2026-07-16T00:00:00.000Z",
1165
+ "alice@example.test",
1166
+ "org-1",
1167
+ ],
1168
+ });
1169
+
1170
+ const result = await runWithRequestContext(
1171
+ { userEmail: "alice@example.test", orgId: "org-1" },
1172
+ () => store.purgeContextSourceArtifacts("source-1"),
1173
+ );
1174
+
1175
+ expect(result).toMatchObject({
1176
+ invalidatedBrandProfiles: 1,
1177
+ dnaRecomputeJobs: 0,
1178
+ });
1179
+ const profile = await exec.execute(
1180
+ "SELECT current_dna_version_id FROM creative_context_brand_profiles WHERE id = 'profile-1'",
1181
+ );
1182
+ expect(profile.rows[0]?.current_dna_version_id).toBeNull();
1183
+ const jobs = await exec.execute(
1184
+ "SELECT id FROM creative_context_jobs WHERE kind = 'brand-dna'",
1185
+ );
1186
+ expect(jobs.rows).toEqual([]);
1187
+ });
1188
+
1189
+ it("publishes only the exact human-reviewed DNA proposal", async () => {
1190
+ const { runWithRequestContext, store } = await setup();
1191
+ const asAlice = <T>(fn: () => Promise<T>) =>
1192
+ runWithRequestContext(
1193
+ { userEmail: "alice@example.test", orgId: "org-1" },
1194
+ fn,
1195
+ );
1196
+ const proposed = await asAlice(() =>
1197
+ store.saveBrandDnaCandidate({
1198
+ name: "Reviewed brand",
1199
+ status: "proposed",
1200
+ dna: { summary: "Warm and direct" },
1201
+ evidenceItemIds: ["allowed"],
1202
+ }),
1203
+ );
1204
+ await expect(
1205
+ asAlice(() =>
1206
+ store.publishBrandDna({
1207
+ profileId: proposed.profile.id,
1208
+ proposalVersionId: proposed.dna.id,
1209
+ confirmation: {
1210
+ proposalVersionId: proposed.dna.id,
1211
+ contentHash: "0".repeat(64),
1212
+ },
1213
+ }),
1214
+ ),
1215
+ ).rejects.toThrow("changed after review");
1216
+ await expect(
1217
+ asAlice(() =>
1218
+ store.publishBrandDna({
1219
+ profileId: proposed.profile.id,
1220
+ proposalVersionId: proposed.dna.id,
1221
+ confirmation: {
1222
+ proposalVersionId: proposed.dna.id,
1223
+ contentHash: proposed.dna.contentHash,
1224
+ },
1225
+ }),
1226
+ ),
1227
+ ).resolves.toMatchObject({
1228
+ profile: { currentDnaVersionId: proposed.dna.id },
1229
+ dna: { id: proposed.dna.id, status: "published" },
1230
+ });
1231
+ });
1232
+
1233
+ it("executes every advertised enrichment worker idempotently", async () => {
1234
+ const { exec, runWithRequestContext, store } = await setup();
1235
+ const jobs = await import("../jobs/index.js");
1236
+ const dispatch = vi.fn(async () => {});
1237
+ const unregister =
1238
+ jobs.registerCreativeContextImportContinuationDispatcher(dispatch);
1239
+ await exec.execute(
1240
+ "UPDATE creative_context_items SET title = 'Primary brand logo', curation_rank = 'canonical' WHERE id = 'allowed'",
1241
+ );
1242
+ const asAlice = <T>(fn: () => Promise<T>) =>
1243
+ runWithRequestContext(
1244
+ { userEmail: "alice@example.test", orgId: "org-1" },
1245
+ fn,
1246
+ );
1247
+ const run = async (kind: any) => {
1248
+ const job = await asAlice(() =>
1249
+ store.createJob({
1250
+ sourceId: "source-1",
1251
+ kind,
1252
+ request: {},
1253
+ }),
1254
+ );
1255
+ return jobs.processCreativeContextBackgroundJob({
1256
+ jobId: job.id,
1257
+ ownerEmail: "alice@example.test",
1258
+ orgId: "org-1",
1259
+ appId: "slides",
1260
+ workerId: `worker:${kind}`,
1261
+ });
1262
+ };
1263
+
1264
+ await run("canonical-logo");
1265
+ await run("canonical-logo");
1266
+ await run("layout-suggestion");
1267
+ const refresh = await run("metadata-refresh");
1268
+
1269
+ const suggestions = await exec.execute(
1270
+ "SELECT kind, item_id, item_version_id FROM creative_context_suggestions ORDER BY kind",
1271
+ );
1272
+ expect(suggestions.rows).toEqual([
1273
+ expect.objectContaining({
1274
+ kind: "canonical-logo",
1275
+ item_id: "allowed",
1276
+ item_version_id: "allowed-v2",
1277
+ }),
1278
+ expect.objectContaining({
1279
+ kind: "layout-template",
1280
+ item_id: "allowed",
1281
+ item_version_id: "allowed-v2",
1282
+ }),
1283
+ ]);
1284
+ expect(refresh).toMatchObject({
1285
+ status: "completed",
1286
+ result: { importJobId: expect.any(String) },
1287
+ });
1288
+ expect(dispatch).toHaveBeenCalledTimes(1);
1289
+ const importJobs = await exec.execute(
1290
+ "SELECT id FROM creative_context_jobs WHERE kind = 'import' AND scoped_dedupe_key IS NOT NULL",
1291
+ );
1292
+ expect(importJobs.rows).toHaveLength(1);
1293
+ unregister();
1294
+ });
1295
+
1296
+ it("enforces review, source access, revocation, exact current versions, and org vectors", async () => {
1297
+ const { exec, runWithRequestContext, store } = await setup();
1298
+ const asBob = <T>(fn: () => Promise<T>) =>
1299
+ runWithRequestContext(
1300
+ { userEmail: "bob@example.test", orgId: "org-1" },
1301
+ fn,
1302
+ );
1303
+ const asAlice = <T>(fn: () => Promise<T>) =>
1304
+ runWithRequestContext(
1305
+ { userEmail: "alice@example.test", orgId: "org-1" },
1306
+ fn,
1307
+ );
1308
+
1309
+ await expect(
1310
+ asBob(() => store.listAccessibleSearchDocuments({ limit: 20 })),
1311
+ ).resolves.toEqual([]);
1312
+ await exec.execute({
1313
+ sql: `INSERT INTO creative_context_source_shares
1314
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
1315
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1316
+ args: [
1317
+ "share-1",
1318
+ "source-1",
1319
+ "user",
1320
+ "bob@example.test",
1321
+ "viewer",
1322
+ "alice@example.test",
1323
+ "2026-07-16T00:00:00.000Z",
1324
+ ],
1325
+ });
1326
+ const initial = await asBob(() =>
1327
+ store.listAccessibleSearchDocuments({ limit: 20 }),
1328
+ );
1329
+ expect(initial.map((document) => document.itemId)).toEqual(["allowed"]);
1330
+ expect(initial[0]?.itemVersionId).toBe("allowed-v2");
1331
+ expect(initial[0]?.body).toContain("version 2");
1332
+
1333
+ const privatePack = await asAlice(() =>
1334
+ store.createContextPack({
1335
+ name: "Private generation intent",
1336
+ members: [{ itemId: "allowed", itemVersionId: "allowed-v2" }],
1337
+ }),
1338
+ );
1339
+ await expect(
1340
+ asBob(() => store.getContextPack(privatePack.id)),
1341
+ ).resolves.toBeNull();
1342
+ await exec.execute({
1343
+ sql: `INSERT INTO creative_context_pack_shares
1344
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
1345
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1346
+ args: [
1347
+ "pack-share-1",
1348
+ privatePack.id,
1349
+ "user",
1350
+ "bob@example.test",
1351
+ "viewer",
1352
+ "alice@example.test",
1353
+ "2026-07-16T00:00:00.000Z",
1354
+ ],
1355
+ });
1356
+ await expect(
1357
+ asBob(() => store.getContextPack(privatePack.id)),
1358
+ ).resolves.toMatchObject({
1359
+ id: privatePack.id,
1360
+ members: [
1361
+ expect.objectContaining({
1362
+ itemId: "allowed",
1363
+ itemVersionId: "allowed-v2",
1364
+ }),
1365
+ ],
1366
+ });
1367
+
1368
+ await expect(
1369
+ asAlice(() =>
1370
+ store.createContextPack({
1371
+ name: "Bypass attempt",
1372
+ members: [{ itemId: "restricted" }],
1373
+ }),
1374
+ ),
1375
+ ).rejects.toThrow(/must be accessible/);
1376
+
1377
+ await exec.execute(
1378
+ "UPDATE creative_context_items SET curation_status = 'included' WHERE id = 'restricted'",
1379
+ );
1380
+ const approved = await asBob(() =>
1381
+ store.listAccessibleSearchDocuments({ limit: 20 }),
1382
+ );
1383
+ expect(new Set(approved.map((document) => document.itemId))).toEqual(
1384
+ new Set(["allowed", "restricted"]),
1385
+ );
1386
+
1387
+ await exec.execute(
1388
+ "UPDATE creative_context_sources SET upstream_access = 'restricted' WHERE id = 'source-1'",
1389
+ );
1390
+ await expect(
1391
+ asBob(() => store.listAccessibleSearchDocuments({ limit: 20 })),
1392
+ ).resolves.toEqual([]);
1393
+ await expect(
1394
+ asBob(() => store.getCreativeContextItem("allowed")),
1395
+ ).resolves.toBeNull();
1396
+
1397
+ await exec.execute(
1398
+ "UPDATE creative_context_sources SET upstream_access = 'available' WHERE id = 'source-1'",
1399
+ );
1400
+ await exec.execute("DELETE FROM creative_context_source_shares");
1401
+ await expect(
1402
+ asBob(() => store.listAccessibleSearchDocuments({ limit: 20 })),
1403
+ ).resolves.toEqual([]);
1404
+
1405
+ await exec.execute(
1406
+ "UPDATE creative_context_sources SET visibility = 'org' WHERE id = 'source-1'",
1407
+ );
1408
+ const set = await asAlice(() =>
1409
+ store.createEmbeddingSet({
1410
+ name: "Approved winner",
1411
+ provider: "test",
1412
+ family: "test:family",
1413
+ model: "test-model",
1414
+ version: "1",
1415
+ dimensions: 3,
1416
+ }),
1417
+ );
1418
+ await asAlice(() =>
1419
+ store.recordEmbeddingMetadata({
1420
+ embeddingSetId: set.id,
1421
+ itemId: "allowed",
1422
+ itemVersionId: "allowed-v2",
1423
+ chunkId: "allowed-chunk-v2",
1424
+ targetType: "chunk",
1425
+ targetId: "allowed-chunk-v2",
1426
+ vectorKey: "vector-allowed",
1427
+ dimensions: 3,
1428
+ }),
1429
+ );
1430
+ await expect(
1431
+ asBob(() => store.getActiveEmbeddingSet()),
1432
+ ).resolves.toMatchObject({ id: set.id });
1433
+ await expect(
1434
+ asBob(() =>
1435
+ store.listEmbeddingMetadata({
1436
+ embeddingSetId: set.id,
1437
+ itemVersionIds: ["allowed-v2"],
1438
+ }),
1439
+ ),
1440
+ ).resolves.toMatchObject([{ vectorKey: "vector-allowed" }]);
1441
+ await expect(
1442
+ asBob(() =>
1443
+ store.createEmbeddingSet({
1444
+ name: "Unauthorized rotation",
1445
+ provider: "test",
1446
+ family: "other",
1447
+ model: "other",
1448
+ version: "1",
1449
+ dimensions: 2,
1450
+ }),
1451
+ ),
1452
+ ).rejects.toThrow(/owners or admins/);
1453
+ await exec.execute(
1454
+ "UPDATE creative_context_sources SET upstream_access = 'restricted' WHERE id = 'source-1'",
1455
+ );
1456
+ await expect(
1457
+ asBob(() =>
1458
+ store.listEmbeddingMetadata({
1459
+ embeddingSetId: set.id,
1460
+ itemVersionIds: ["allowed-v2"],
1461
+ }),
1462
+ ),
1463
+ ).resolves.toEqual([]);
1464
+ });
1465
+
1466
+ it("revokes newly restricted unchanged items but preserves explicit approvals across resync", async () => {
1467
+ const { exec, runWithRequestContext, store } = await setup();
1468
+ const asAlice = <T>(fn: () => Promise<T>) =>
1469
+ runWithRequestContext(
1470
+ { userEmail: "alice@example.test", orgId: "org-1" },
1471
+ fn,
1472
+ );
1473
+ await exec.execute(
1474
+ "UPDATE creative_context_items SET curation_status = 'included' WHERE id = 'restricted'",
1475
+ );
1476
+ const base = {
1477
+ externalId: "restricted",
1478
+ kind: "slide",
1479
+ title: "restricted title",
1480
+ content: "restricted canonical version 2",
1481
+ contentHash: "restricted-hash-v2",
1482
+ upstreamAccess: "restricted" as const,
1483
+ curationStatus: "review" as const,
1484
+ };
1485
+ await asAlice(() =>
1486
+ store.ingestItems({ sourceId: "source-1", items: [base] }),
1487
+ );
1488
+ let row = await exec.execute(
1489
+ "SELECT curation_status FROM creative_context_items WHERE id = 'restricted'",
1490
+ );
1491
+ expect(row.rows[0]?.curation_status).toBe("included");
1492
+
1493
+ await asAlice(() =>
1494
+ store.ingestItems({
1495
+ sourceId: "source-1",
1496
+ items: [
1497
+ {
1498
+ ...base,
1499
+ content: "restricted canonical version 3",
1500
+ contentHash: "restricted-hash-v3",
1501
+ },
1502
+ ],
1503
+ }),
1504
+ );
1505
+ row = await exec.execute(
1506
+ "SELECT curation_status FROM creative_context_items WHERE id = 'restricted'",
1507
+ );
1508
+ expect(row.rows[0]?.curation_status).toBe("included");
1509
+
1510
+ await exec.execute(
1511
+ "UPDATE creative_context_items SET upstream_access = 'available', curation_status = 'included' WHERE id = 'allowed'",
1512
+ );
1513
+ await asAlice(() =>
1514
+ store.ingestItems({
1515
+ sourceId: "source-1",
1516
+ items: [
1517
+ {
1518
+ externalId: "allowed",
1519
+ kind: "slide",
1520
+ title: "allowed title",
1521
+ content: "allowed canonical version 2",
1522
+ contentHash: "allowed-hash-v2",
1523
+ upstreamAccess: "restricted",
1524
+ curationStatus: "review",
1525
+ },
1526
+ ],
1527
+ }),
1528
+ );
1529
+ row = await exec.execute(
1530
+ "SELECT upstream_access, curation_status FROM creative_context_items WHERE id = 'allowed'",
1531
+ );
1532
+ expect(row.rows[0]).toMatchObject({
1533
+ upstream_access: "restricted",
1534
+ curation_status: "review",
1535
+ });
1536
+ });
1537
+
1538
+ it("purges dependent references and invalidates affected published DNA", async () => {
1539
+ const { exec, runWithRequestContext, store } = await setup();
1540
+ const statements: Array<{ sql: string; args: unknown[] }> = [
1541
+ {
1542
+ sql: `INSERT INTO creative_context_packs
1543
+ (id, name, context_mode, request, created_at, owner_email, org_id, visibility)
1544
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1545
+ args: [
1546
+ "pack-purge",
1547
+ "Purge pack",
1548
+ "manual",
1549
+ "{}",
1550
+ "2026-07-16T00:00:00.000Z",
1551
+ "alice@example.test",
1552
+ "org-1",
1553
+ "private",
1554
+ ],
1555
+ },
1556
+ {
1557
+ sql: `INSERT INTO creative_context_pack_members
1558
+ (id, pack_id, item_id, item_version_id, ordinal, score_metadata,
1559
+ created_at, owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1560
+ args: [
1561
+ "member-purge",
1562
+ "pack-purge",
1563
+ "allowed",
1564
+ "allowed-v2",
1565
+ 0,
1566
+ "{}",
1567
+ "2026-07-16T00:00:00.000Z",
1568
+ "alice@example.test",
1569
+ "org-1",
1570
+ ],
1571
+ },
1572
+ {
1573
+ sql: `INSERT INTO creative_context_brand_profiles
1574
+ (id, name, current_dna_version_id, created_at, updated_at,
1575
+ owner_email, org_id, visibility) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
1576
+ args: [
1577
+ "profile-purge",
1578
+ "Purge profile",
1579
+ "dna-purge",
1580
+ "2026-07-16T00:00:00.000Z",
1581
+ "2026-07-16T00:00:00.000Z",
1582
+ "alice@example.test",
1583
+ "org-1",
1584
+ "private",
1585
+ ],
1586
+ },
1587
+ {
1588
+ sql: `INSERT INTO creative_context_brand_dna_versions
1589
+ (id, profile_id, version_number, payload, content_hash, status,
1590
+ created_at, owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1591
+ args: [
1592
+ "dna-purge",
1593
+ "profile-purge",
1594
+ 1,
1595
+ "{}",
1596
+ "dna-hash",
1597
+ "published",
1598
+ "2026-07-16T00:00:00.000Z",
1599
+ "alice@example.test",
1600
+ "org-1",
1601
+ ],
1602
+ },
1603
+ {
1604
+ sql: `INSERT INTO creative_context_brand_dna_evidence
1605
+ (id, dna_version_id, item_id, item_version_id, created_at,
1606
+ owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?)`,
1607
+ args: [
1608
+ "evidence-purge",
1609
+ "dna-purge",
1610
+ "allowed",
1611
+ "allowed-v2",
1612
+ "2026-07-16T00:00:00.000Z",
1613
+ "alice@example.test",
1614
+ "org-1",
1615
+ ],
1616
+ },
1617
+ {
1618
+ sql: `INSERT INTO creative_context_edges
1619
+ (id, from_item_id, from_item_version_id, to_item_id,
1620
+ to_item_version_id, relation, metadata, created_at, owner_email, org_id)
1621
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1622
+ args: [
1623
+ "edge-to-purge",
1624
+ "outside-item",
1625
+ "outside-version",
1626
+ "allowed",
1627
+ "allowed-v2",
1628
+ "derived-from",
1629
+ "{}",
1630
+ "2026-07-16T00:00:00.000Z",
1631
+ "alice@example.test",
1632
+ "org-1",
1633
+ ],
1634
+ },
1635
+ {
1636
+ sql: `INSERT INTO creative_context_suggestions
1637
+ (id, kind, status, item_id, item_version_id, payload, created_at,
1638
+ updated_at, owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1639
+ args: [
1640
+ "suggestion-purge",
1641
+ "layout-template",
1642
+ "promoted",
1643
+ "allowed",
1644
+ "allowed-v2",
1645
+ '{"projectionItemId":"outside-item"}',
1646
+ "2026-07-16T00:00:00.000Z",
1647
+ "2026-07-16T00:00:00.000Z",
1648
+ "alice@example.test",
1649
+ "org-1",
1650
+ ],
1651
+ },
1652
+ {
1653
+ sql: `INSERT INTO creative_context_generation_records
1654
+ (id, app_id, artifact_type, artifact_id, context_mode,
1655
+ context_pack_id, element_provenance, created_at, owner_email, org_id)
1656
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1657
+ args: [
1658
+ "generation-purge",
1659
+ "slides",
1660
+ "deck",
1661
+ "deck-1",
1662
+ "pinned",
1663
+ "pack-purge",
1664
+ '[{"elementId":"title","itemId":"allowed","itemVersionId":"allowed-v2","influence":"adapted","label":"Brand title"}]',
1665
+ "2026-07-16T00:00:00.000Z",
1666
+ "alice@example.test",
1667
+ "org-1",
1668
+ ],
1669
+ },
1670
+ ];
1671
+ for (const statement of statements) await exec.execute(statement);
1672
+
1673
+ const result = await runWithRequestContext(
1674
+ { userEmail: "alice@example.test", orgId: "org-1" },
1675
+ () => store.purgeContextSourceArtifacts("source-1"),
1676
+ );
1677
+ expect(result).toMatchObject({
1678
+ purgedItems: 2,
1679
+ demotedLayouts: 1,
1680
+ invalidatedBrandProfiles: 1,
1681
+ dnaRecomputeJobs: 0,
1682
+ });
1683
+ for (const table of [
1684
+ "creative_context_pack_members",
1685
+ "creative_context_brand_dna_evidence",
1686
+ "creative_context_edges",
1687
+ "creative_context_suggestions",
1688
+ "creative_context_items",
1689
+ ]) {
1690
+ const rows = await exec.execute(`SELECT id FROM ${table}`);
1691
+ expect(rows.rows, table).toHaveLength(0);
1692
+ }
1693
+ const generation = await exec.execute(
1694
+ "SELECT element_provenance FROM creative_context_generation_records WHERE id = 'generation-purge'",
1695
+ );
1696
+ expect(JSON.parse(String(generation.rows[0]?.element_provenance))).toEqual([
1697
+ expect.objectContaining({
1698
+ elementId: "title",
1699
+ referenceUnavailable: true,
1700
+ }),
1701
+ ]);
1702
+ expect(String(generation.rows[0]?.element_provenance)).not.toContain(
1703
+ "allowed-v2",
1704
+ );
1705
+ const pack = await exec.execute(
1706
+ "SELECT archived_at FROM creative_context_packs WHERE id = 'pack-purge'",
1707
+ );
1708
+ expect(pack.rows[0]?.archived_at).toEqual(expect.any(String));
1709
+ const jobs = await exec.execute(
1710
+ "SELECT kind FROM creative_context_jobs WHERE kind = 'brand-dna'",
1711
+ );
1712
+ expect(jobs.rows).toEqual([]);
1713
+ const profile = await exec.execute(
1714
+ "SELECT current_dna_version_id FROM creative_context_brand_profiles WHERE id = 'profile-purge'",
1715
+ );
1716
+ expect(profile.rows[0]?.current_dna_version_id).toBeNull();
1717
+ });
1718
+
1719
+ it("scans beyond early rows and preserves exact lexical mode semantics", async () => {
1720
+ const { exec, runWithRequestContext, store } = await setup();
1721
+ await exec.execute({
1722
+ sql: `INSERT INTO creative_context_items
1723
+ (id, source_id, external_id, kind, title, current_version_id,
1724
+ current_content_hash, status, upstream_access, curation_status,
1725
+ curation_rank, inventory_state, index_state, tags, colors, provenance,
1726
+ metadata, created_at, updated_at, owner_email, org_id)
1727
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1728
+ args: [
1729
+ "tagged-late",
1730
+ "source-1",
1731
+ "tagged-late",
1732
+ "slide",
1733
+ "Tagged late",
1734
+ "tagged-late-v1",
1735
+ "tagged-late-hash",
1736
+ "active",
1737
+ "available",
1738
+ "included",
1739
+ "normal",
1740
+ "available",
1741
+ "indexed",
1742
+ '["late-tag"]',
1743
+ '["#123456"]',
1744
+ "{}",
1745
+ "{}",
1746
+ "2026-07-16T00:00:00.000Z",
1747
+ "2026-07-16T00:00:00.000Z",
1748
+ "alice@example.test",
1749
+ "org-1",
1750
+ ],
1751
+ });
1752
+ await exec.execute({
1753
+ sql: `INSERT INTO creative_context_item_versions
1754
+ (id, item_id, version_number, content_hash, title, content,
1755
+ parse_status, metadata, created_at, owner_email, org_id)
1756
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1757
+ args: [
1758
+ "tagged-late-v1",
1759
+ "tagged-late",
1760
+ 1,
1761
+ "tagged-late-hash",
1762
+ "Tagged late",
1763
+ "late filtered content",
1764
+ "parsed",
1765
+ "{}",
1766
+ "2026-07-16T00:00:00.000Z",
1767
+ "alice@example.test",
1768
+ "org-1",
1769
+ ],
1770
+ });
1771
+ const values: string[] = [];
1772
+ const args: unknown[] = [];
1773
+ const addChunk = (id: string, ordinal: number, text: string) => {
1774
+ values.push("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1775
+ args.push(
1776
+ id,
1777
+ "allowed",
1778
+ "allowed-v2",
1779
+ ordinal,
1780
+ "text",
1781
+ text,
1782
+ "{}",
1783
+ "2026-07-16T00:00:00.000Z",
1784
+ "alice@example.test",
1785
+ "org-1",
1786
+ );
1787
+ };
1788
+ for (let index = 1; index <= 1_005; index += 1) {
1789
+ addChunk(
1790
+ `a-irrelevant-${String(index).padStart(4, "0")}`,
1791
+ index,
1792
+ "irrelevant corpus row",
1793
+ );
1794
+ }
1795
+ addChunk("x-separated", 1_006, "needle middle alpha");
1796
+ addChunk("y-decoy", 1_007, "alpha only");
1797
+ addChunk("z-later-match", 1_008, "needle alpha literal 100%_match");
1798
+ values.push("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
1799
+ args.push(
1800
+ "zz-tagged-late",
1801
+ "tagged-late",
1802
+ "tagged-late-v1",
1803
+ 0,
1804
+ "text",
1805
+ "late filtered content",
1806
+ "{}",
1807
+ "2026-07-16T00:00:00.000Z",
1808
+ "alice@example.test",
1809
+ "org-1",
1810
+ );
1811
+ await exec.execute({
1812
+ sql: `INSERT INTO creative_context_chunks
1813
+ (id, item_id, item_version_id, ordinal, kind, text, metadata,
1814
+ created_at, owner_email, org_id) VALUES ${values.join(",")}`,
1815
+ args,
1816
+ });
1817
+ const search = (query: string, matchMode: any) =>
1818
+ runWithRequestContext(
1819
+ { userEmail: "alice@example.test", orgId: "org-1" },
1820
+ () =>
1821
+ store.listAccessibleLexicalCandidates({
1822
+ query,
1823
+ matchMode,
1824
+ limit: 20,
1825
+ }),
1826
+ );
1827
+ const allTerms = await search("needle alpha", "allTerms");
1828
+ expect(allTerms.results.map((result) => result.chunkId)).toEqual(
1829
+ expect.arrayContaining(["x-separated", "z-later-match"]),
1830
+ );
1831
+ expect(allTerms.results.map((result) => result.chunkId)).not.toContain(
1832
+ "y-decoy",
1833
+ );
1834
+ const anyTerm = await search("needle alpha", "anyTerm");
1835
+ expect(anyTerm.results.map((result) => result.chunkId)).toContain(
1836
+ "y-decoy",
1837
+ );
1838
+ const phrase = await search("needle alpha", "phrase");
1839
+ expect(phrase.results.map((result) => result.chunkId)).toContain(
1840
+ "z-later-match",
1841
+ );
1842
+ expect(phrase.results.map((result) => result.chunkId)).not.toContain(
1843
+ "x-separated",
1844
+ );
1845
+ const regex = await search("needle\\s+alpha", "regex");
1846
+ expect(regex.results.map((result) => result.chunkId)).toContain(
1847
+ "z-later-match",
1848
+ );
1849
+ const escaped = await search("100%_match", "phrase");
1850
+ expect(escaped.results.map((result) => result.chunkId)).toEqual([
1851
+ "z-later-match",
1852
+ ]);
1853
+ const filtered = await runWithRequestContext(
1854
+ { userEmail: "alice@example.test", orgId: "org-1" },
1855
+ () =>
1856
+ store.listAccessibleSearchDocuments({
1857
+ tags: ["late-tag"],
1858
+ colors: ["#123456"],
1859
+ limit: 1,
1860
+ }),
1861
+ );
1862
+ expect(filtered.map((document) => document.itemId)).toEqual([
1863
+ "tagged-late",
1864
+ ]);
1865
+
1866
+ await exec.execute({
1867
+ sql: `INSERT INTO creative_context_embedding_sets
1868
+ (id, name, provider, family, model, version, dimensions, metric,
1869
+ status, metadata, created_at, owner_email, org_id)
1870
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1871
+ args: [
1872
+ "set-late",
1873
+ "Late vector set",
1874
+ "test",
1875
+ "test-family",
1876
+ "test-model",
1877
+ "v1",
1878
+ 2,
1879
+ "cosine",
1880
+ "active",
1881
+ "{}",
1882
+ "2026-07-16T00:00:00.000Z",
1883
+ "alice@example.test",
1884
+ "org-1",
1885
+ ],
1886
+ });
1887
+ await exec.execute({
1888
+ sql: `INSERT INTO creative_context_embeddings
1889
+ (id, embedding_set_id, family, model, version, item_id,
1890
+ item_version_id, target_type, target_id, vector_key, dimensions,
1891
+ created_at, owner_email, org_id)
1892
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1893
+ args: [
1894
+ "embedding-late",
1895
+ "set-late",
1896
+ "test-family",
1897
+ "test-model",
1898
+ "v1",
1899
+ "tagged-late",
1900
+ "tagged-late-v1",
1901
+ "item",
1902
+ "tagged-late-v1",
1903
+ "vector-late",
1904
+ 2,
1905
+ "2026-07-16T00:00:00.000Z",
1906
+ "alice@example.test",
1907
+ "org-1",
1908
+ ],
1909
+ });
1910
+ const vectorCandidates = await runWithRequestContext(
1911
+ { userEmail: "alice@example.test", orgId: "org-1" },
1912
+ () =>
1913
+ store.listEmbeddingMetadata({
1914
+ embeddingSetId: "set-late",
1915
+ tags: ["late-tag"],
1916
+ }),
1917
+ );
1918
+ expect(vectorCandidates.map((candidate) => candidate.vectorKey)).toEqual([
1919
+ "vector-late",
1920
+ ]);
1921
+ });
1922
+ });