@agent-native/creative-context 0.0.0-beta-20260820202238

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 (695) hide show
  1. package/README.md +154 -0
  2. package/agent-native.eject.json +41 -0
  3. package/agent-native.package.json +119 -0
  4. package/dist/actions/confirm-canonical-logo.d.ts +6 -0
  5. package/dist/actions/confirm-canonical-logo.d.ts.map +1 -0
  6. package/dist/actions/confirm-canonical-logo.js +19 -0
  7. package/dist/actions/confirm-canonical-logo.js.map +1 -0
  8. package/dist/actions/continue-context-import.d.ts +22 -0
  9. package/dist/actions/continue-context-import.d.ts.map +1 -0
  10. package/dist/actions/continue-context-import.js +13 -0
  11. package/dist/actions/continue-context-import.js.map +1 -0
  12. package/dist/actions/creative-context-a2a.d.ts +10 -0
  13. package/dist/actions/creative-context-a2a.d.ts.map +1 -0
  14. package/dist/actions/creative-context-a2a.js +66 -0
  15. package/dist/actions/creative-context-a2a.js.map +1 -0
  16. package/dist/actions/get-brand-profile.d.ts +33 -0
  17. package/dist/actions/get-brand-profile.d.ts.map +1 -0
  18. package/dist/actions/get-brand-profile.js +13 -0
  19. package/dist/actions/get-brand-profile.js.map +1 -0
  20. package/dist/actions/get-context-import-status.d.ts +22 -0
  21. package/dist/actions/get-context-import-status.d.ts.map +1 -0
  22. package/dist/actions/get-context-import-status.js +12 -0
  23. package/dist/actions/get-context-import-status.js.map +1 -0
  24. package/dist/actions/get-context-item.d.ts +2 -0
  25. package/dist/actions/get-context-item.d.ts.map +1 -0
  26. package/dist/actions/get-context-item.js +2 -0
  27. package/dist/actions/get-context-item.js.map +1 -0
  28. package/dist/actions/get-context-pack.d.ts +7 -0
  29. package/dist/actions/get-context-pack.d.ts.map +1 -0
  30. package/dist/actions/get-context-pack.js +12 -0
  31. package/dist/actions/get-context-pack.js.map +1 -0
  32. package/dist/actions/get-creative-context.d.ts +157 -0
  33. package/dist/actions/get-creative-context.d.ts.map +1 -0
  34. package/dist/actions/get-creative-context.js +148 -0
  35. package/dist/actions/get-creative-context.js.map +1 -0
  36. package/dist/actions/get-google-picker-session.d.ts +10 -0
  37. package/dist/actions/get-google-picker-session.d.ts.map +1 -0
  38. package/dist/actions/get-google-picker-session.js +38 -0
  39. package/dist/actions/get-google-picker-session.js.map +1 -0
  40. package/dist/actions/index.d.ts +3 -0
  41. package/dist/actions/index.d.ts.map +1 -0
  42. package/dist/actions/index.js +73 -0
  43. package/dist/actions/index.js.map +1 -0
  44. package/dist/actions/infer-brand-dna.d.ts +52 -0
  45. package/dist/actions/infer-brand-dna.d.ts.map +1 -0
  46. package/dist/actions/infer-brand-dna.js +13 -0
  47. package/dist/actions/infer-brand-dna.js.map +1 -0
  48. package/dist/actions/list-canonical-logo-candidates.d.ts +9 -0
  49. package/dist/actions/list-canonical-logo-candidates.d.ts.map +1 -0
  50. package/dist/actions/list-canonical-logo-candidates.js +82 -0
  51. package/dist/actions/list-canonical-logo-candidates.js.map +1 -0
  52. package/dist/actions/list-context-connections.d.ts +22 -0
  53. package/dist/actions/list-context-connections.d.ts.map +1 -0
  54. package/dist/actions/list-context-connections.js +43 -0
  55. package/dist/actions/list-context-connections.js.map +1 -0
  56. package/dist/actions/list-context-memberships.d.ts +11 -0
  57. package/dist/actions/list-context-memberships.d.ts.map +1 -0
  58. package/dist/actions/list-context-memberships.js +47 -0
  59. package/dist/actions/list-context-memberships.js.map +1 -0
  60. package/dist/actions/list-context-packs.d.ts +10 -0
  61. package/dist/actions/list-context-packs.d.ts.map +1 -0
  62. package/dist/actions/list-context-packs.js +16 -0
  63. package/dist/actions/list-context-packs.js.map +1 -0
  64. package/dist/actions/list-context-sources.d.ts +12 -0
  65. package/dist/actions/list-context-sources.d.ts.map +1 -0
  66. package/dist/actions/list-context-sources.js +20 -0
  67. package/dist/actions/list-context-sources.js.map +1 -0
  68. package/dist/actions/list-context-suggestions.d.ts +13 -0
  69. package/dist/actions/list-context-suggestions.d.ts.map +1 -0
  70. package/dist/actions/list-context-suggestions.js +28 -0
  71. package/dist/actions/list-context-suggestions.js.map +1 -0
  72. package/dist/actions/list-creative-contexts.d.ts +12 -0
  73. package/dist/actions/list-creative-contexts.d.ts.map +1 -0
  74. package/dist/actions/list-creative-contexts.js +28 -0
  75. package/dist/actions/list-creative-contexts.js.map +1 -0
  76. package/dist/actions/manage-brand-profile.d.ts +28 -0
  77. package/dist/actions/manage-brand-profile.d.ts.map +1 -0
  78. package/dist/actions/manage-brand-profile.js +47 -0
  79. package/dist/actions/manage-brand-profile.js.map +1 -0
  80. package/dist/actions/manage-context-membership.d.ts +64 -0
  81. package/dist/actions/manage-context-membership.d.ts.map +1 -0
  82. package/dist/actions/manage-context-membership.js +70 -0
  83. package/dist/actions/manage-context-membership.js.map +1 -0
  84. package/dist/actions/manage-context-pack.d.ts +36 -0
  85. package/dist/actions/manage-context-pack.d.ts.map +1 -0
  86. package/dist/actions/manage-context-pack.js +71 -0
  87. package/dist/actions/manage-context-pack.js.map +1 -0
  88. package/dist/actions/manage-context-source.d.ts +51 -0
  89. package/dist/actions/manage-context-source.d.ts.map +1 -0
  90. package/dist/actions/manage-context-source.js +130 -0
  91. package/dist/actions/manage-context-source.js.map +1 -0
  92. package/dist/actions/manage-creative-context.d.ts +28 -0
  93. package/dist/actions/manage-creative-context.d.ts.map +1 -0
  94. package/dist/actions/manage-creative-context.js +65 -0
  95. package/dist/actions/manage-creative-context.js.map +1 -0
  96. package/dist/actions/manage-layout-template.d.ts +13 -0
  97. package/dist/actions/manage-layout-template.d.ts.map +1 -0
  98. package/dist/actions/manage-layout-template.js +43 -0
  99. package/dist/actions/manage-layout-template.js.map +1 -0
  100. package/dist/actions/preview-context-import.d.ts +23 -0
  101. package/dist/actions/preview-context-import.d.ts.map +1 -0
  102. package/dist/actions/preview-context-import.js +57 -0
  103. package/dist/actions/preview-context-import.js.map +1 -0
  104. package/dist/actions/process-context-purge.d.ts +37 -0
  105. package/dist/actions/process-context-purge.d.ts.map +1 -0
  106. package/dist/actions/process-context-purge.js +20 -0
  107. package/dist/actions/process-context-purge.js.map +1 -0
  108. package/dist/actions/propose-brand-dna.d.ts +15 -0
  109. package/dist/actions/propose-brand-dna.d.ts.map +1 -0
  110. package/dist/actions/propose-brand-dna.js +19 -0
  111. package/dist/actions/propose-brand-dna.js.map +1 -0
  112. package/dist/actions/propose-canonical-logo.d.ts +9 -0
  113. package/dist/actions/propose-canonical-logo.d.ts.map +1 -0
  114. package/dist/actions/propose-canonical-logo.js +16 -0
  115. package/dist/actions/propose-canonical-logo.js.map +1 -0
  116. package/dist/actions/publish-brand-dna.d.ts +13 -0
  117. package/dist/actions/publish-brand-dna.d.ts.map +1 -0
  118. package/dist/actions/publish-brand-dna.js +23 -0
  119. package/dist/actions/publish-brand-dna.js.map +1 -0
  120. package/dist/actions/recommend-context-roots.d.ts +14 -0
  121. package/dist/actions/recommend-context-roots.d.ts.map +1 -0
  122. package/dist/actions/recommend-context-roots.js +22 -0
  123. package/dist/actions/recommend-context-roots.js.map +1 -0
  124. package/dist/actions/record-context-feedback.d.ts +10 -0
  125. package/dist/actions/record-context-feedback.d.ts.map +1 -0
  126. package/dist/actions/record-context-feedback.js +14 -0
  127. package/dist/actions/record-context-feedback.js.map +1 -0
  128. package/dist/actions/review-context-items.d.ts +35 -0
  129. package/dist/actions/review-context-items.d.ts.map +1 -0
  130. package/dist/actions/review-context-items.js +65 -0
  131. package/dist/actions/review-context-items.js.map +1 -0
  132. package/dist/actions/run-embedding-bakeoff.d.ts +15 -0
  133. package/dist/actions/run-embedding-bakeoff.d.ts.map +1 -0
  134. package/dist/actions/run-embedding-bakeoff.js +72 -0
  135. package/dist/actions/run-embedding-bakeoff.js.map +1 -0
  136. package/dist/actions/search-creative-context.d.ts +80 -0
  137. package/dist/actions/search-creative-context.d.ts.map +1 -0
  138. package/dist/actions/search-creative-context.js +42 -0
  139. package/dist/actions/search-creative-context.js.map +1 -0
  140. package/dist/actions/set-brand-dna.d.ts +15 -0
  141. package/dist/actions/set-brand-dna.d.ts.map +1 -0
  142. package/dist/actions/set-brand-dna.js +19 -0
  143. package/dist/actions/set-brand-dna.js.map +1 -0
  144. package/dist/actions/start-context-enrichment.d.ts +25 -0
  145. package/dist/actions/start-context-enrichment.d.ts.map +1 -0
  146. package/dist/actions/start-context-enrichment.js +48 -0
  147. package/dist/actions/start-context-enrichment.js.map +1 -0
  148. package/dist/actions/start-context-import.d.ts +24 -0
  149. package/dist/actions/start-context-import.d.ts.map +1 -0
  150. package/dist/actions/start-context-import.js +37 -0
  151. package/dist/actions/start-context-import.js.map +1 -0
  152. package/dist/client/CreativeContextChip.d.ts +18 -0
  153. package/dist/client/CreativeContextChip.d.ts.map +1 -0
  154. package/dist/client/CreativeContextChip.js +80 -0
  155. package/dist/client/CreativeContextChip.js.map +1 -0
  156. package/dist/client/CreativeContextPanel.d.ts +73 -0
  157. package/dist/client/CreativeContextPanel.d.ts.map +1 -0
  158. package/dist/client/CreativeContextPanel.js +1171 -0
  159. package/dist/client/CreativeContextPanel.js.map +1 -0
  160. package/dist/client/CreativeContextSettingsLink.d.ts +4 -0
  161. package/dist/client/CreativeContextSettingsLink.d.ts.map +1 -0
  162. package/dist/client/CreativeContextSettingsLink.js +8 -0
  163. package/dist/client/CreativeContextSettingsLink.js.map +1 -0
  164. package/dist/client/CreativeContextShareTab.d.ts +56 -0
  165. package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
  166. package/dist/client/CreativeContextShareTab.js +213 -0
  167. package/dist/client/CreativeContextShareTab.js.map +1 -0
  168. package/dist/client/actions.d.ts +474 -0
  169. package/dist/client/actions.d.ts.map +1 -0
  170. package/dist/client/actions.js +411 -0
  171. package/dist/client/actions.js.map +1 -0
  172. package/dist/client/agent-tab.d.ts +10 -0
  173. package/dist/client/agent-tab.d.ts.map +1 -0
  174. package/dist/client/agent-tab.js +32 -0
  175. package/dist/client/agent-tab.js.map +1 -0
  176. package/dist/client/application-state.d.ts +24 -0
  177. package/dist/client/application-state.d.ts.map +1 -0
  178. package/dist/client/application-state.js +111 -0
  179. package/dist/client/application-state.js.map +1 -0
  180. package/dist/client/google-slides-picker.d.ts +19 -0
  181. package/dist/client/google-slides-picker.d.ts.map +1 -0
  182. package/dist/client/google-slides-picker.js +106 -0
  183. package/dist/client/google-slides-picker.js.map +1 -0
  184. package/dist/client/index.d.ts +10 -0
  185. package/dist/client/index.d.ts.map +1 -0
  186. package/dist/client/index.js +10 -0
  187. package/dist/client/index.js.map +1 -0
  188. package/dist/client/messages.d.ts +5 -0
  189. package/dist/client/messages.d.ts.map +1 -0
  190. package/dist/client/messages.js +1158 -0
  191. package/dist/client/messages.js.map +1 -0
  192. package/dist/connectors/context.d.ts +9 -0
  193. package/dist/connectors/context.d.ts.map +1 -0
  194. package/dist/connectors/context.js +59 -0
  195. package/dist/connectors/context.js.map +1 -0
  196. package/dist/connectors/document-parser.d.ts +2 -0
  197. package/dist/connectors/document-parser.d.ts.map +1 -0
  198. package/dist/connectors/document-parser.js +2 -0
  199. package/dist/connectors/document-parser.js.map +1 -0
  200. package/dist/connectors/figma-native.d.ts +18 -0
  201. package/dist/connectors/figma-native.d.ts.map +1 -0
  202. package/dist/connectors/figma-native.js +883 -0
  203. package/dist/connectors/figma-native.js.map +1 -0
  204. package/dist/connectors/figma.d.ts +10 -0
  205. package/dist/connectors/figma.d.ts.map +1 -0
  206. package/dist/connectors/figma.js +166 -0
  207. package/dist/connectors/figma.js.map +1 -0
  208. package/dist/connectors/google-slides-native.d.ts +58 -0
  209. package/dist/connectors/google-slides-native.d.ts.map +1 -0
  210. package/dist/connectors/google-slides-native.js +1118 -0
  211. package/dist/connectors/google-slides-native.js.map +1 -0
  212. package/dist/connectors/google-slides.d.ts +15 -0
  213. package/dist/connectors/google-slides.d.ts.map +1 -0
  214. package/dist/connectors/google-slides.js +895 -0
  215. package/dist/connectors/google-slides.js.map +1 -0
  216. package/dist/connectors/index.d.ts +21 -0
  217. package/dist/connectors/index.d.ts.map +1 -0
  218. package/dist/connectors/index.js +45 -0
  219. package/dist/connectors/index.js.map +1 -0
  220. package/dist/connectors/manual.d.ts +9 -0
  221. package/dist/connectors/manual.d.ts.map +1 -0
  222. package/dist/connectors/manual.js +91 -0
  223. package/dist/connectors/manual.js.map +1 -0
  224. package/dist/connectors/normalize.d.ts +42 -0
  225. package/dist/connectors/normalize.d.ts.map +1 -0
  226. package/dist/connectors/normalize.js +335 -0
  227. package/dist/connectors/normalize.js.map +1 -0
  228. package/dist/connectors/notion.d.ts +10 -0
  229. package/dist/connectors/notion.d.ts.map +1 -0
  230. package/dist/connectors/notion.js +393 -0
  231. package/dist/connectors/notion.js.map +1 -0
  232. package/dist/connectors/private-artifacts.d.ts +39 -0
  233. package/dist/connectors/private-artifacts.d.ts.map +1 -0
  234. package/dist/connectors/private-artifacts.js +175 -0
  235. package/dist/connectors/private-artifacts.js.map +1 -0
  236. package/dist/connectors/provider-response.d.ts +20 -0
  237. package/dist/connectors/provider-response.d.ts.map +1 -0
  238. package/dist/connectors/provider-response.js +98 -0
  239. package/dist/connectors/provider-response.js.map +1 -0
  240. package/dist/connectors/recommendations.d.ts +25 -0
  241. package/dist/connectors/recommendations.d.ts.map +1 -0
  242. package/dist/connectors/recommendations.js +172 -0
  243. package/dist/connectors/recommendations.js.map +1 -0
  244. package/dist/connectors/registry.d.ts +10 -0
  245. package/dist/connectors/registry.d.ts.map +1 -0
  246. package/dist/connectors/registry.js +34 -0
  247. package/dist/connectors/registry.js.map +1 -0
  248. package/dist/connectors/rendered-design.d.ts +68 -0
  249. package/dist/connectors/rendered-design.d.ts.map +1 -0
  250. package/dist/connectors/rendered-design.js +362 -0
  251. package/dist/connectors/rendered-design.js.map +1 -0
  252. package/dist/connectors/rendered-page.d.ts +115 -0
  253. package/dist/connectors/rendered-page.d.ts.map +1 -0
  254. package/dist/connectors/rendered-page.js +1036 -0
  255. package/dist/connectors/rendered-page.js.map +1 -0
  256. package/dist/connectors/smart-defaults.d.ts +9 -0
  257. package/dist/connectors/smart-defaults.d.ts.map +1 -0
  258. package/dist/connectors/smart-defaults.js +72 -0
  259. package/dist/connectors/smart-defaults.js.map +1 -0
  260. package/dist/connectors/types.d.ts +79 -0
  261. package/dist/connectors/types.d.ts.map +1 -0
  262. package/dist/connectors/types.js +2 -0
  263. package/dist/connectors/types.js.map +1 -0
  264. package/dist/connectors/upload.d.ts +9 -0
  265. package/dist/connectors/upload.d.ts.map +1 -0
  266. package/dist/connectors/upload.js +440 -0
  267. package/dist/connectors/upload.js.map +1 -0
  268. package/dist/connectors/website.d.ts +20 -0
  269. package/dist/connectors/website.d.ts.map +1 -0
  270. package/dist/connectors/website.js +616 -0
  271. package/dist/connectors/website.js.map +1 -0
  272. package/dist/embeddings/index.d.ts +3 -0
  273. package/dist/embeddings/index.d.ts.map +1 -0
  274. package/dist/embeddings/index.js +3 -0
  275. package/dist/embeddings/index.js.map +1 -0
  276. package/dist/embeddings/providers.d.ts +2 -0
  277. package/dist/embeddings/providers.d.ts.map +1 -0
  278. package/dist/embeddings/providers.js +2 -0
  279. package/dist/embeddings/providers.js.map +1 -0
  280. package/dist/embeddings/types.d.ts +2 -0
  281. package/dist/embeddings/types.d.ts.map +1 -0
  282. package/dist/embeddings/types.js +2 -0
  283. package/dist/embeddings/types.js.map +1 -0
  284. package/dist/eval/acceptance-fixtures.d.ts +3 -0
  285. package/dist/eval/acceptance-fixtures.d.ts.map +1 -0
  286. package/dist/eval/acceptance-fixtures.js +84 -0
  287. package/dist/eval/acceptance-fixtures.js.map +1 -0
  288. package/dist/eval/acceptance.d.ts +114 -0
  289. package/dist/eval/acceptance.d.ts.map +1 -0
  290. package/dist/eval/acceptance.js +260 -0
  291. package/dist/eval/acceptance.js.map +1 -0
  292. package/dist/eval/fixtures.d.ts +16 -0
  293. package/dist/eval/fixtures.d.ts.map +1 -0
  294. package/dist/eval/fixtures.js +119 -0
  295. package/dist/eval/fixtures.js.map +1 -0
  296. package/dist/eval/index.d.ts +63 -0
  297. package/dist/eval/index.d.ts.map +1 -0
  298. package/dist/eval/index.js +174 -0
  299. package/dist/eval/index.js.map +1 -0
  300. package/dist/index.d.ts +5 -0
  301. package/dist/index.d.ts.map +1 -0
  302. package/dist/index.js +5 -0
  303. package/dist/index.js.map +1 -0
  304. package/dist/jobs/background-worker.d.ts +31 -0
  305. package/dist/jobs/background-worker.d.ts.map +1 -0
  306. package/dist/jobs/background-worker.js +315 -0
  307. package/dist/jobs/background-worker.js.map +1 -0
  308. package/dist/jobs/import-runner.d.ts +3 -0
  309. package/dist/jobs/import-runner.d.ts.map +1 -0
  310. package/dist/jobs/import-runner.js +613 -0
  311. package/dist/jobs/import-runner.js.map +1 -0
  312. package/dist/jobs/index.d.ts +9 -0
  313. package/dist/jobs/index.d.ts.map +1 -0
  314. package/dist/jobs/index.js +8 -0
  315. package/dist/jobs/index.js.map +1 -0
  316. package/dist/jobs/progress.d.ts +3 -0
  317. package/dist/jobs/progress.d.ts.map +1 -0
  318. package/dist/jobs/progress.js +30 -0
  319. package/dist/jobs/progress.js.map +1 -0
  320. package/dist/jobs/rebuild.d.ts +13 -0
  321. package/dist/jobs/rebuild.d.ts.map +1 -0
  322. package/dist/jobs/rebuild.js +178 -0
  323. package/dist/jobs/rebuild.js.map +1 -0
  324. package/dist/jobs/server-worker.d.ts +16 -0
  325. package/dist/jobs/server-worker.d.ts.map +1 -0
  326. package/dist/jobs/server-worker.js +262 -0
  327. package/dist/jobs/server-worker.js.map +1 -0
  328. package/dist/jobs/store-adapter.d.ts +3 -0
  329. package/dist/jobs/store-adapter.d.ts.map +1 -0
  330. package/dist/jobs/store-adapter.js +37 -0
  331. package/dist/jobs/store-adapter.js.map +1 -0
  332. package/dist/jobs/types.d.ts +138 -0
  333. package/dist/jobs/types.d.ts.map +1 -0
  334. package/dist/jobs/types.js +2 -0
  335. package/dist/jobs/types.js.map +1 -0
  336. package/dist/jobs/worker.d.ts +27 -0
  337. package/dist/jobs/worker.d.ts.map +1 -0
  338. package/dist/jobs/worker.js +90 -0
  339. package/dist/jobs/worker.js.map +1 -0
  340. package/dist/media-url.d.ts +7 -0
  341. package/dist/media-url.d.ts.map +1 -0
  342. package/dist/media-url.js +15 -0
  343. package/dist/media-url.js.map +1 -0
  344. package/dist/native-artifact-reassembly.d.ts +25 -0
  345. package/dist/native-artifact-reassembly.d.ts.map +1 -0
  346. package/dist/native-artifact-reassembly.js +262 -0
  347. package/dist/native-artifact-reassembly.js.map +1 -0
  348. package/dist/native-artifact.d.ts +88 -0
  349. package/dist/native-artifact.d.ts.map +1 -0
  350. package/dist/native-artifact.js +86 -0
  351. package/dist/native-artifact.js.map +1 -0
  352. package/dist/schema/index.d.ts +7111 -0
  353. package/dist/schema/index.d.ts.map +1 -0
  354. package/dist/schema/index.js +469 -0
  355. package/dist/schema/index.js.map +1 -0
  356. package/dist/schema/migrations.d.ts +10 -0
  357. package/dist/schema/migrations.d.ts.map +1 -0
  358. package/dist/schema/migrations.js +333 -0
  359. package/dist/schema/migrations.js.map +1 -0
  360. package/dist/search/fusion.d.ts +2 -0
  361. package/dist/search/fusion.d.ts.map +1 -0
  362. package/dist/search/fusion.js +2 -0
  363. package/dist/search/fusion.js.map +1 -0
  364. package/dist/search/mode.d.ts +4 -0
  365. package/dist/search/mode.d.ts.map +1 -0
  366. package/dist/search/mode.js +13 -0
  367. package/dist/search/mode.js.map +1 -0
  368. package/dist/search/postgres-fts.d.ts +2 -0
  369. package/dist/search/postgres-fts.d.ts.map +1 -0
  370. package/dist/search/postgres-fts.js +2 -0
  371. package/dist/search/postgres-fts.js.map +1 -0
  372. package/dist/server/brand-context.d.ts +15 -0
  373. package/dist/server/brand-context.d.ts.map +1 -0
  374. package/dist/server/brand-context.js +149 -0
  375. package/dist/server/brand-context.js.map +1 -0
  376. package/dist/server/context.d.ts +77 -0
  377. package/dist/server/context.d.ts.map +1 -0
  378. package/dist/server/context.js +63 -0
  379. package/dist/server/context.js.map +1 -0
  380. package/dist/server/enrichment.d.ts +24 -0
  381. package/dist/server/enrichment.d.ts.map +1 -0
  382. package/dist/server/enrichment.js +138 -0
  383. package/dist/server/enrichment.js.map +1 -0
  384. package/dist/server/generation-artifact-access.d.ts +21 -0
  385. package/dist/server/generation-artifact-access.d.ts.map +1 -0
  386. package/dist/server/generation-artifact-access.js +113 -0
  387. package/dist/server/generation-artifact-access.js.map +1 -0
  388. package/dist/server/generation-context.d.ts +220 -0
  389. package/dist/server/generation-context.d.ts.map +1 -0
  390. package/dist/server/generation-context.js +432 -0
  391. package/dist/server/generation-context.js.map +1 -0
  392. package/dist/server/index.d.ts +27 -0
  393. package/dist/server/index.d.ts.map +1 -0
  394. package/dist/server/index.js +115 -0
  395. package/dist/server/index.js.map +1 -0
  396. package/dist/server/isolated-a2a.d.ts +175 -0
  397. package/dist/server/isolated-a2a.d.ts.map +1 -0
  398. package/dist/server/isolated-a2a.js +312 -0
  399. package/dist/server/isolated-a2a.js.map +1 -0
  400. package/dist/server/media.d.ts +15 -0
  401. package/dist/server/media.d.ts.map +1 -0
  402. package/dist/server/media.js +108 -0
  403. package/dist/server/media.js.map +1 -0
  404. package/dist/server/native-resource-capture.d.ts +78 -0
  405. package/dist/server/native-resource-capture.d.ts.map +1 -0
  406. package/dist/server/native-resource-capture.js +94 -0
  407. package/dist/server/native-resource-capture.js.map +1 -0
  408. package/dist/server/prompt-provider.d.ts +5 -0
  409. package/dist/server/prompt-provider.d.ts.map +1 -0
  410. package/dist/server/prompt-provider.js +59 -0
  411. package/dist/server/prompt-provider.js.map +1 -0
  412. package/dist/server/public-serialization.d.ts +160 -0
  413. package/dist/server/public-serialization.d.ts.map +1 -0
  414. package/dist/server/public-serialization.js +108 -0
  415. package/dist/server/public-serialization.js.map +1 -0
  416. package/dist/server/resource-paths.d.ts +3 -0
  417. package/dist/server/resource-paths.d.ts.map +1 -0
  418. package/dist/server/resource-paths.js +5 -0
  419. package/dist/server/resource-paths.js.map +1 -0
  420. package/dist/server/retrieval.d.ts +83 -0
  421. package/dist/server/retrieval.d.ts.map +1 -0
  422. package/dist/server/retrieval.js +445 -0
  423. package/dist/server/retrieval.js.map +1 -0
  424. package/dist/server/safe-native-preview.d.ts +15 -0
  425. package/dist/server/safe-native-preview.d.ts.map +1 -0
  426. package/dist/server/safe-native-preview.js +148 -0
  427. package/dist/server/safe-native-preview.js.map +1 -0
  428. package/dist/store/brand.d.ts +47 -0
  429. package/dist/store/brand.d.ts.map +1 -0
  430. package/dist/store/brand.js +346 -0
  431. package/dist/store/brand.js.map +1 -0
  432. package/dist/store/content.d.ts +134 -0
  433. package/dist/store/content.d.ts.map +1 -0
  434. package/dist/store/content.js +1677 -0
  435. package/dist/store/content.js.map +1 -0
  436. package/dist/store/contexts.d.ts +216 -0
  437. package/dist/store/contexts.d.ts.map +1 -0
  438. package/dist/store/contexts.js +1346 -0
  439. package/dist/store/contexts.js.map +1 -0
  440. package/dist/store/generation.d.ts +28 -0
  441. package/dist/store/generation.d.ts.map +1 -0
  442. package/dist/store/generation.js +140 -0
  443. package/dist/store/generation.js.map +1 -0
  444. package/dist/store/helpers.d.ts +12 -0
  445. package/dist/store/helpers.d.ts.map +1 -0
  446. package/dist/store/helpers.js +41 -0
  447. package/dist/store/helpers.js.map +1 -0
  448. package/dist/store/index.d.ts +11 -0
  449. package/dist/store/index.d.ts.map +1 -0
  450. package/dist/store/index.js +11 -0
  451. package/dist/store/index.js.map +1 -0
  452. package/dist/store/inference.d.ts +57 -0
  453. package/dist/store/inference.d.ts.map +1 -0
  454. package/dist/store/inference.js +279 -0
  455. package/dist/store/inference.js.map +1 -0
  456. package/dist/store/jobs.d.ts +80 -0
  457. package/dist/store/jobs.d.ts.map +1 -0
  458. package/dist/store/jobs.js +428 -0
  459. package/dist/store/jobs.js.map +1 -0
  460. package/dist/store/native-artifact-summary.d.ts +7 -0
  461. package/dist/store/native-artifact-summary.d.ts.map +1 -0
  462. package/dist/store/native-artifact-summary.js +10 -0
  463. package/dist/store/native-artifact-summary.js.map +1 -0
  464. package/dist/store/packs.d.ts +36 -0
  465. package/dist/store/packs.d.ts.map +1 -0
  466. package/dist/store/packs.js +438 -0
  467. package/dist/store/packs.js.map +1 -0
  468. package/dist/store/purge.d.ts +16 -0
  469. package/dist/store/purge.d.ts.map +1 -0
  470. package/dist/store/purge.js +354 -0
  471. package/dist/store/purge.js.map +1 -0
  472. package/dist/store/sources.d.ts +76 -0
  473. package/dist/store/sources.d.ts.map +1 -0
  474. package/dist/store/sources.js +525 -0
  475. package/dist/store/sources.js.map +1 -0
  476. package/dist/store/suggestions.d.ts +29 -0
  477. package/dist/store/suggestions.d.ts.map +1 -0
  478. package/dist/store/suggestions.js +270 -0
  479. package/dist/store/suggestions.js.map +1 -0
  480. package/dist/types.d.ts +578 -0
  481. package/dist/types.d.ts.map +1 -0
  482. package/dist/types.js +10 -0
  483. package/dist/types.js.map +1 -0
  484. package/dist/untrusted-reference.d.ts +8 -0
  485. package/dist/untrusted-reference.d.ts.map +1 -0
  486. package/dist/untrusted-reference.js +19 -0
  487. package/dist/untrusted-reference.js.map +1 -0
  488. package/dist/vector/index.d.ts +2 -0
  489. package/dist/vector/index.d.ts.map +1 -0
  490. package/dist/vector/index.js +2 -0
  491. package/dist/vector/index.js.map +1 -0
  492. package/dist/vector/pgvector.d.ts +2 -0
  493. package/dist/vector/pgvector.d.ts.map +1 -0
  494. package/dist/vector/pgvector.js +2 -0
  495. package/dist/vector/pgvector.js.map +1 -0
  496. package/docs/evaluation.md +92 -0
  497. package/docs/llms-full.txt +168 -0
  498. package/docs/llms.txt +76 -0
  499. package/docs/skills/creative-context/SKILL.md +90 -0
  500. package/package.json +100 -0
  501. package/src/actions/agent-input-schemas.spec.ts +46 -0
  502. package/src/actions/confirm-canonical-logo.ts +20 -0
  503. package/src/actions/continue-context-import.ts +15 -0
  504. package/src/actions/creative-context-a2a.spec.ts +162 -0
  505. package/src/actions/creative-context-a2a.ts +88 -0
  506. package/src/actions/get-brand-profile.ts +16 -0
  507. package/src/actions/get-context-import-status.ts +13 -0
  508. package/src/actions/get-context-item.ts +1 -0
  509. package/src/actions/get-context-pack.ts +14 -0
  510. package/src/actions/get-creative-context.spec.ts +435 -0
  511. package/src/actions/get-creative-context.ts +197 -0
  512. package/src/actions/get-google-picker-session.spec.ts +62 -0
  513. package/src/actions/get-google-picker-session.ts +45 -0
  514. package/src/actions/index.ts +75 -0
  515. package/src/actions/infer-brand-dna.ts +15 -0
  516. package/src/actions/job-public-serialization.spec.ts +143 -0
  517. package/src/actions/list-canonical-logo-candidates.ts +121 -0
  518. package/src/actions/list-context-connections.spec.ts +23 -0
  519. package/src/actions/list-context-connections.ts +51 -0
  520. package/src/actions/list-context-memberships.ts +56 -0
  521. package/src/actions/list-context-packs.ts +17 -0
  522. package/src/actions/list-context-sources.ts +21 -0
  523. package/src/actions/list-context-suggestions.ts +30 -0
  524. package/src/actions/list-creative-contexts.spec.ts +38 -0
  525. package/src/actions/list-creative-contexts.ts +32 -0
  526. package/src/actions/manage-brand-profile.ts +58 -0
  527. package/src/actions/manage-context-membership.ts +80 -0
  528. package/src/actions/manage-context-pack.ts +81 -0
  529. package/src/actions/manage-context-source.ts +151 -0
  530. package/src/actions/manage-creative-context.ts +73 -0
  531. package/src/actions/manage-layout-template.ts +51 -0
  532. package/src/actions/preview-context-import.spec.ts +64 -0
  533. package/src/actions/preview-context-import.ts +61 -0
  534. package/src/actions/process-context-purge.ts +28 -0
  535. package/src/actions/propose-brand-dna.ts +22 -0
  536. package/src/actions/propose-canonical-logo.ts +19 -0
  537. package/src/actions/publish-brand-dna.ts +25 -0
  538. package/src/actions/recommend-context-roots.ts +24 -0
  539. package/src/actions/record-context-feedback.ts +16 -0
  540. package/src/actions/review-context-items.ts +69 -0
  541. package/src/actions/run-embedding-bakeoff.ts +90 -0
  542. package/src/actions/search-creative-context.ts +48 -0
  543. package/src/actions/set-brand-dna.ts +22 -0
  544. package/src/actions/start-context-enrichment.ts +52 -0
  545. package/src/actions/start-context-import.ts +38 -0
  546. package/src/client/CreativeContextChip.spec.ts +59 -0
  547. package/src/client/CreativeContextChip.tsx +243 -0
  548. package/src/client/CreativeContextPanel.tsx +3600 -0
  549. package/src/client/CreativeContextSettingsLink.tsx +30 -0
  550. package/src/client/CreativeContextShareTab.spec.ts +70 -0
  551. package/src/client/CreativeContextShareTab.tsx +666 -0
  552. package/src/client/actions.spec.ts +177 -0
  553. package/src/client/actions.ts +1024 -0
  554. package/src/client/agent-tab.tsx +56 -0
  555. package/src/client/application-state.spec.ts +46 -0
  556. package/src/client/application-state.ts +158 -0
  557. package/src/client/google-slides-picker.spec.ts +36 -0
  558. package/src/client/google-slides-picker.ts +146 -0
  559. package/src/client/index.ts +102 -0
  560. package/src/client/messages.ts +1370 -0
  561. package/src/client/source-config.spec.ts +166 -0
  562. package/src/connectors/connectors.spec.ts +778 -0
  563. package/src/connectors/context.ts +83 -0
  564. package/src/connectors/document-parser.ts +5 -0
  565. package/src/connectors/figma-native.ts +1192 -0
  566. package/src/connectors/figma-native.visual.spec.ts +221 -0
  567. package/src/connectors/figma.spec.ts +812 -0
  568. package/src/connectors/figma.ts +257 -0
  569. package/src/connectors/fixtures/google-slides-native.json +353 -0
  570. package/src/connectors/google-slides-native.spec.ts +804 -0
  571. package/src/connectors/google-slides-native.ts +1656 -0
  572. package/src/connectors/google-slides-native.visual.spec.ts +119 -0
  573. package/src/connectors/google-slides.ts +1171 -0
  574. package/src/connectors/index.ts +112 -0
  575. package/src/connectors/manual.ts +132 -0
  576. package/src/connectors/normalize.spec.ts +144 -0
  577. package/src/connectors/normalize.ts +475 -0
  578. package/src/connectors/notion.ts +529 -0
  579. package/src/connectors/private-artifacts.spec.ts +95 -0
  580. package/src/connectors/private-artifacts.ts +248 -0
  581. package/src/connectors/provider-response.ts +144 -0
  582. package/src/connectors/recommendations.ts +237 -0
  583. package/src/connectors/registry.ts +48 -0
  584. package/src/connectors/rendered-design.spec.ts +171 -0
  585. package/src/connectors/rendered-design.ts +495 -0
  586. package/src/connectors/rendered-page.spec.ts +388 -0
  587. package/src/connectors/rendered-page.ts +1387 -0
  588. package/src/connectors/smart-defaults.spec.ts +135 -0
  589. package/src/connectors/smart-defaults.ts +87 -0
  590. package/src/connectors/types.ts +113 -0
  591. package/src/connectors/upload.spec.ts +175 -0
  592. package/src/connectors/upload.ts +548 -0
  593. package/src/connectors/website.spec.ts +465 -0
  594. package/src/connectors/website.ts +777 -0
  595. package/src/embeddings/index.ts +2 -0
  596. package/src/embeddings/providers.spec.ts +73 -0
  597. package/src/embeddings/providers.ts +1 -0
  598. package/src/embeddings/types.ts +6 -0
  599. package/src/eval/acceptance-fixtures.ts +97 -0
  600. package/src/eval/acceptance.spec.ts +92 -0
  601. package/src/eval/acceptance.ts +371 -0
  602. package/src/eval/connectors-golden.spec.ts +421 -0
  603. package/src/eval/eval.spec.ts +166 -0
  604. package/src/eval/fixtures/README.md +17 -0
  605. package/src/eval/fixtures/figma-brand-system-v1.json +140 -0
  606. package/src/eval/fixtures/figma-brand-system-v2.json +127 -0
  607. package/src/eval/fixtures/launch-hero.png +0 -0
  608. package/src/eval/fixtures/launch-system-v1.pptx +0 -0
  609. package/src/eval/fixtures/launch-system-v2.pptx +0 -0
  610. package/src/eval/fixtures/notion-brand-root.json +218 -0
  611. package/src/eval/fixtures/pricing-hero.png +0 -0
  612. package/src/eval/fixtures/rendered-brand-page.html +76 -0
  613. package/src/eval/fixtures.spec.ts +43 -0
  614. package/src/eval/fixtures.ts +136 -0
  615. package/src/eval/index.ts +291 -0
  616. package/src/eval/library-flow.acceptance.spec.tsx +145 -0
  617. package/src/index.ts +4 -0
  618. package/src/jobs/background-worker.ts +423 -0
  619. package/src/jobs/import-runner.spec.ts +582 -0
  620. package/src/jobs/import-runner.ts +791 -0
  621. package/src/jobs/index.ts +40 -0
  622. package/src/jobs/progress.ts +37 -0
  623. package/src/jobs/rebuild.ts +245 -0
  624. package/src/jobs/server-worker.spec.ts +181 -0
  625. package/src/jobs/server-worker.ts +354 -0
  626. package/src/jobs/store-adapter.ts +52 -0
  627. package/src/jobs/types.ts +147 -0
  628. package/src/jobs/worker.ts +152 -0
  629. package/src/manifest.spec.ts +26 -0
  630. package/src/media-url.ts +17 -0
  631. package/src/native-artifact-reassembly.spec.ts +386 -0
  632. package/src/native-artifact-reassembly.ts +406 -0
  633. package/src/native-artifact.ts +102 -0
  634. package/src/schema/index.ts +527 -0
  635. package/src/schema/migrations.spec.ts +41 -0
  636. package/src/schema/migrations.ts +338 -0
  637. package/src/search/fusion.spec.ts +21 -0
  638. package/src/search/fusion.ts +1 -0
  639. package/src/search/mode.spec.ts +28 -0
  640. package/src/search/mode.ts +21 -0
  641. package/src/search/postgres-fts.spec.ts +63 -0
  642. package/src/search/postgres-fts.ts +1 -0
  643. package/src/server/brand-context.spec.ts +43 -0
  644. package/src/server/brand-context.ts +160 -0
  645. package/src/server/context.ts +162 -0
  646. package/src/server/enrichment.spec.ts +94 -0
  647. package/src/server/enrichment.ts +166 -0
  648. package/src/server/generation-artifact-access.spec.ts +104 -0
  649. package/src/server/generation-artifact-access.ts +217 -0
  650. package/src/server/generation-context-a2a.spec.ts +282 -0
  651. package/src/server/generation-context-off.spec.ts +213 -0
  652. package/src/server/generation-context.spec.ts +124 -0
  653. package/src/server/generation-context.ts +639 -0
  654. package/src/server/index.spec.ts +30 -0
  655. package/src/server/index.ts +163 -0
  656. package/src/server/isolated-a2a.spec.ts +184 -0
  657. package/src/server/isolated-a2a.ts +416 -0
  658. package/src/server/media.spec.ts +120 -0
  659. package/src/server/media.ts +136 -0
  660. package/src/server/native-resource-capture.spec.ts +121 -0
  661. package/src/server/native-resource-capture.ts +198 -0
  662. package/src/server/prompt-provider.spec.ts +78 -0
  663. package/src/server/prompt-provider.ts +73 -0
  664. package/src/server/public-serialization.spec.ts +150 -0
  665. package/src/server/public-serialization.ts +130 -0
  666. package/src/server/resource-paths.spec.ts +9 -0
  667. package/src/server/resource-paths.ts +5 -0
  668. package/src/server/retrieval-vector.spec.ts +180 -0
  669. package/src/server/retrieval.ts +590 -0
  670. package/src/server/safe-native-preview.spec.ts +29 -0
  671. package/src/server/safe-native-preview.ts +199 -0
  672. package/src/store/access.integration.spec.ts +3523 -0
  673. package/src/store/brand.ts +458 -0
  674. package/src/store/content.ts +2317 -0
  675. package/src/store/contexts.ts +1840 -0
  676. package/src/store/generation.ts +226 -0
  677. package/src/store/helpers.ts +53 -0
  678. package/src/store/index.ts +10 -0
  679. package/src/store/inference.spec.ts +84 -0
  680. package/src/store/inference.ts +339 -0
  681. package/src/store/invariants.spec.ts +119 -0
  682. package/src/store/jobs.ts +674 -0
  683. package/src/store/native-artifact-summary.ts +13 -0
  684. package/src/store/packs.ts +721 -0
  685. package/src/store/purge.ts +463 -0
  686. package/src/store/sources.spec.ts +102 -0
  687. package/src/store/sources.ts +692 -0
  688. package/src/store/suggestions.ts +361 -0
  689. package/src/types.ts +671 -0
  690. package/src/untrusted-reference.spec.ts +22 -0
  691. package/src/untrusted-reference.ts +24 -0
  692. package/src/vector/index.ts +1 -0
  693. package/src/vector/pgvector.spec.ts +115 -0
  694. package/src/vector/pgvector.ts +1 -0
  695. package/tsconfig.json +31 -0
@@ -0,0 +1,3523 @@
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("rejects an oversized compiled layout before creating its projection", async () => {
270
+ const { exec, runWithRequestContext, store } = await setup();
271
+ const server = await import("../server/index.js");
272
+ const promote = vi.fn(async () => {});
273
+ server.configureCreativeContext({
274
+ projections: {
275
+ layoutTemplate: { promote, demote: vi.fn(async () => {}) },
276
+ },
277
+ });
278
+ const html = `<div class="fmd-slide google-slides-native" data-slide-id="slide-1"><p>${"界".repeat(44_000)}</p></div>`;
279
+ await exec.execute({
280
+ sql: `UPDATE creative_context_items
281
+ SET mime_type = ?, provenance = ? WHERE id = ?`,
282
+ args: [
283
+ "text/html",
284
+ JSON.stringify({
285
+ compiler: "@agent-native/creative-context:google-slides-native",
286
+ }),
287
+ "allowed",
288
+ ],
289
+ });
290
+ await exec.execute({
291
+ sql: `UPDATE creative_context_item_versions
292
+ SET content = ?, mime_type = ?, metadata = ? WHERE id = ?`,
293
+ args: [
294
+ html,
295
+ "text/html",
296
+ JSON.stringify({
297
+ nativeArtifact: {
298
+ schemaVersion: 1,
299
+ app: "slides",
300
+ format: "slides-html",
301
+ rootExternalId: "presentation-1:slide-1",
302
+ fidelityReport: {
303
+ exact: { count: 1 },
304
+ approximated: { count: 0, reasons: [] },
305
+ imageFallback: { count: 0, reasons: [] },
306
+ },
307
+ },
308
+ }),
309
+ "allowed-v2",
310
+ ],
311
+ });
312
+ const suggestion = await runWithRequestContext(
313
+ { userEmail: "alice@example.test", orgId: "org-1" },
314
+ () =>
315
+ store.proposeCreativeContextSuggestion({
316
+ kind: "layout-template",
317
+ itemId: "allowed",
318
+ itemVersionId: "allowed-v2",
319
+ }),
320
+ );
321
+
322
+ await expect(
323
+ runWithRequestContext(
324
+ { userEmail: "alice@example.test", orgId: "org-1" },
325
+ () =>
326
+ store.applyLayoutTemplateSuggestion({
327
+ suggestionId: suggestion.id,
328
+ operation: "promote",
329
+ }),
330
+ ),
331
+ ).rejects.toThrow(/native content.*exceeds.*split the artifact/i);
332
+ expect(promote).not.toHaveBeenCalled();
333
+ const projectionCount = await exec.execute({
334
+ sql: "SELECT COUNT(*) AS count FROM creative_context_items WHERE kind = 'layout_template'",
335
+ args: [],
336
+ });
337
+ expect(Number(projectionCount.rows[0]?.count)).toBe(0);
338
+ });
339
+
340
+ it("never trusts a caller-supplied layout projection item id", async () => {
341
+ const { exec, runWithRequestContext, store } = await setup();
342
+ const server = await import("../server/index.js");
343
+ const demote = vi.fn(async () => {});
344
+ server.configureCreativeContext({
345
+ projections: {
346
+ layoutTemplate: { promote: vi.fn(async () => {}), demote },
347
+ },
348
+ });
349
+ const proposed = await runWithRequestContext(
350
+ { userEmail: "alice@example.test", orgId: "org-1" },
351
+ () =>
352
+ store.proposeCreativeContextSuggestion({
353
+ kind: "layout-template",
354
+ itemId: "allowed",
355
+ itemVersionId: "allowed-v2",
356
+ payload: { projectionItemId: "guessed-item", note: "keep" },
357
+ }),
358
+ );
359
+ expect(proposed.payload).toEqual({ note: "keep" });
360
+
361
+ await exec.execute({
362
+ sql: `INSERT INTO creative_context_sources
363
+ (id, name, kind, config, upstream_access, status, health_status,
364
+ item_count, restricted_item_count, owner_email, org_id, visibility,
365
+ created_at, updated_at)
366
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
367
+ args: [
368
+ "outside-source",
369
+ "Outside tenant",
370
+ "manual",
371
+ "{}",
372
+ "available",
373
+ "active",
374
+ "healthy",
375
+ 1,
376
+ 0,
377
+ "mallory@example.test",
378
+ "org-2",
379
+ "private",
380
+ "2026-07-16T00:00:00.000Z",
381
+ "2026-07-16T00:00:00.000Z",
382
+ ],
383
+ });
384
+ await exec.execute({
385
+ sql: `INSERT INTO creative_context_items
386
+ (id, source_id, external_id, kind, title, current_version_id,
387
+ current_content_hash, status, upstream_access, curation_status,
388
+ curation_rank, inventory_state, index_state, tags, colors, provenance,
389
+ metadata, created_at, updated_at, owner_email, org_id)
390
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
391
+ args: [
392
+ "outside-projection",
393
+ "outside-source",
394
+ "outside-projection",
395
+ "layout_template",
396
+ "Outside projection",
397
+ "outside-version",
398
+ "outside-hash",
399
+ "active",
400
+ "available",
401
+ "included",
402
+ "canonical",
403
+ "available",
404
+ "indexed",
405
+ "[]",
406
+ "[]",
407
+ JSON.stringify({
408
+ promotedFromSuggestionId: "malicious-layout-suggestion",
409
+ }),
410
+ "{}",
411
+ "2026-07-16T00:00:00.000Z",
412
+ "2026-07-16T00:00:00.000Z",
413
+ "mallory@example.test",
414
+ "org-2",
415
+ ],
416
+ });
417
+ await exec.execute({
418
+ sql: `INSERT INTO creative_context_suggestions
419
+ (id, kind, status, item_id, item_version_id, payload, created_at,
420
+ updated_at, owner_email, org_id)
421
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
422
+ args: [
423
+ "malicious-layout-suggestion",
424
+ "layout-template",
425
+ "promoted",
426
+ "allowed",
427
+ "allowed-v2",
428
+ JSON.stringify({ projectionItemId: "outside-projection" }),
429
+ "2026-07-16T00:00:00.000Z",
430
+ "2026-07-16T00:00:00.000Z",
431
+ "alice@example.test",
432
+ "org-1",
433
+ ],
434
+ });
435
+
436
+ await runWithRequestContext(
437
+ { userEmail: "alice@example.test", orgId: "org-1" },
438
+ () =>
439
+ store.applyLayoutTemplateSuggestion({
440
+ suggestionId: "malicious-layout-suggestion",
441
+ operation: "demote",
442
+ }),
443
+ );
444
+
445
+ const outside = await exec.execute(
446
+ "SELECT status FROM creative_context_items WHERE id = 'outside-projection'",
447
+ );
448
+ expect(outside.rows[0]?.status).toBe("active");
449
+ expect(demote).toHaveBeenCalledWith({
450
+ suggestionId: "malicious-layout-suggestion",
451
+ projectionItemId: null,
452
+ });
453
+ });
454
+
455
+ it("requires a host access proof before sharing generation history", async () => {
456
+ const { exec, runWithRequestContext, store } = await setup();
457
+ const { assertGenerationArtifactAccess } =
458
+ await import("../server/generation-artifact-access.js");
459
+ const asBob = <T>(fn: () => Promise<T>) =>
460
+ runWithRequestContext(
461
+ { userEmail: "bob@example.test", orgId: "org-1" },
462
+ fn,
463
+ );
464
+ const artifact = {
465
+ appId: "slides",
466
+ artifactType: "deck",
467
+ artifactId: "shared-deck",
468
+ };
469
+ const aliceProof = await runWithRequestContext(
470
+ { userEmail: "alice@example.test", orgId: "org-1" },
471
+ () =>
472
+ assertGenerationArtifactAccess(
473
+ artifact,
474
+ {
475
+ resourceType: "creative-context-source",
476
+ resourceId: "source-1",
477
+ },
478
+ "editor",
479
+ ),
480
+ );
481
+ await runWithRequestContext(
482
+ { userEmail: "alice@example.test", orgId: "org-1" },
483
+ () =>
484
+ store.recordGenerationCreativeContext(
485
+ {
486
+ ...artifact,
487
+ contextMode: "auto",
488
+ contextPackId: null,
489
+ reuseLabels: [],
490
+ elementProvenance: [
491
+ { elementId: "slide-1", influence: "generated" },
492
+ ],
493
+ },
494
+ { artifactAccess: aliceProof },
495
+ ),
496
+ );
497
+
498
+ await expect(
499
+ asBob(() => store.getGenerationCreativeContext(artifact)),
500
+ ).resolves.toBeNull();
501
+ await expect(
502
+ asBob(() =>
503
+ assertGenerationArtifactAccess(
504
+ artifact,
505
+ {
506
+ resourceType: "creative-context-source",
507
+ resourceId: "source-1",
508
+ },
509
+ "viewer",
510
+ ),
511
+ ),
512
+ ).rejects.toThrow(/no access/i);
513
+ await expect(
514
+ asBob(() =>
515
+ store.getGenerationCreativeContext(artifact, {
516
+ artifactAccess: {
517
+ identityKey: JSON.stringify([
518
+ artifact.appId,
519
+ artifact.artifactType,
520
+ artifact.artifactId,
521
+ ]),
522
+ minRole: "viewer",
523
+ } as never,
524
+ }),
525
+ ),
526
+ ).rejects.toThrow(/verified by the host application/i);
527
+
528
+ await asBob(() =>
529
+ store.recordGenerationCreativeContext({
530
+ ...artifact,
531
+ contextMode: "auto",
532
+ contextPackId: null,
533
+ reuseLabels: [],
534
+ elementProvenance: [{ elementId: "poison", influence: "generated" }],
535
+ }),
536
+ );
537
+ const poison = await exec.execute(
538
+ "SELECT org_id FROM creative_context_generation_records WHERE owner_email = 'bob@example.test'",
539
+ );
540
+ expect(poison.rows[0]?.org_id).toBeNull();
541
+ await expect(
542
+ runWithRequestContext(
543
+ { userEmail: "alice@example.test", orgId: "org-1" },
544
+ () =>
545
+ store.getGenerationCreativeContext(artifact, {
546
+ artifactAccess: aliceProof,
547
+ }),
548
+ ),
549
+ ).resolves.toMatchObject({
550
+ artifactId: "shared-deck",
551
+ elementProvenance: [{ elementId: "slide-1" }],
552
+ });
553
+
554
+ await exec.execute({
555
+ sql: `INSERT INTO creative_context_source_shares
556
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
557
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
558
+ args: [
559
+ "source-share-bob",
560
+ "source-1",
561
+ "user",
562
+ "bob@example.test",
563
+ "viewer",
564
+ "alice@example.test",
565
+ "2026-07-16T00:00:00.000Z",
566
+ ],
567
+ });
568
+ const bobProof = await asBob(() =>
569
+ assertGenerationArtifactAccess(
570
+ artifact,
571
+ {
572
+ resourceType: "creative-context-source",
573
+ resourceId: "source-1",
574
+ },
575
+ "viewer",
576
+ ),
577
+ );
578
+ await expect(
579
+ asBob(() =>
580
+ store.getGenerationCreativeContext(artifact, {
581
+ artifactAccess: bobProof,
582
+ }),
583
+ ),
584
+ ).resolves.toMatchObject({
585
+ artifactId: "shared-deck",
586
+ elementProvenance: [{ elementId: "slide-1" }],
587
+ });
588
+ await expect(
589
+ asBob(() =>
590
+ store.getGenerationCreativeContext(
591
+ { ...artifact, artifactId: "arbitrary-unasserted-id" },
592
+ { artifactAccess: bobProof },
593
+ ),
594
+ ),
595
+ ).rejects.toThrow(/verified by the host application/i);
596
+ });
597
+
598
+ it("appends media enrichment while preserving version-pinned pack evidence", async () => {
599
+ const { exec, runWithRequestContext, store } = await setup();
600
+ await exec.execute({
601
+ sql: `INSERT INTO creative_context_media
602
+ (id, item_id, item_version_id, kind, mime_type, access_mode,
603
+ storage_key, caption_status, palette, metadata, created_at,
604
+ owner_email, org_id)
605
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
606
+ args: [
607
+ "media-before-enrichment",
608
+ "allowed",
609
+ "allowed-v2",
610
+ "image",
611
+ "image/png",
612
+ "private",
613
+ "private-blob:fixture",
614
+ "pending",
615
+ "[]",
616
+ "{}",
617
+ "2026-07-16T00:00:00.000Z",
618
+ "alice@example.test",
619
+ "org-1",
620
+ ],
621
+ });
622
+ const asAlice = <T>(fn: () => Promise<T>) =>
623
+ runWithRequestContext(
624
+ { userEmail: "alice@example.test", orgId: "org-1" },
625
+ fn,
626
+ );
627
+ const pack = await asAlice(() =>
628
+ store.createContextPack({
629
+ name: "Pinned before enrichment",
630
+ members: [
631
+ {
632
+ itemId: "allowed",
633
+ itemVersionId: "allowed-v2",
634
+ reason: "Exact pre-enrichment evidence",
635
+ },
636
+ ],
637
+ }),
638
+ );
639
+
640
+ const enriched = await asAlice(() =>
641
+ store.appendMediaEnrichmentVersion({
642
+ mediaId: "media-before-enrichment",
643
+ palette: ["#663399", "#ffffff"],
644
+ contentHash: "enriched-image-hash",
645
+ caption: "Builder dashboard on a purple background",
646
+ captionStatus: "complete",
647
+ ocrText: "Ship faster",
648
+ }),
649
+ );
650
+
651
+ expect(enriched.appended).toBe(true);
652
+ expect(enriched.itemVersionId).not.toBe("allowed-v2");
653
+ expect(enriched.mediaId).not.toBe("media-before-enrichment");
654
+ const pinned = await asAlice(() =>
655
+ store.getCreativeContextItem("allowed", "allowed-v2"),
656
+ );
657
+ expect(pinned?.media).toEqual([
658
+ expect.objectContaining({
659
+ id: "media-before-enrichment",
660
+ caption: null,
661
+ captionStatus: "pending",
662
+ palette: [],
663
+ }),
664
+ ]);
665
+ const current = await asAlice(() =>
666
+ store.getCreativeContextItem("allowed"),
667
+ );
668
+ expect(current?.version.id).toBe(enriched.itemVersionId);
669
+ expect(current?.media).toEqual([
670
+ expect.objectContaining({
671
+ id: enriched.mediaId,
672
+ caption: "Builder dashboard on a purple background",
673
+ captionStatus: "complete",
674
+ ocrText: "Ship faster",
675
+ palette: ["#663399", "#ffffff"],
676
+ }),
677
+ ]);
678
+ const pinnedPack = await asAlice(() => store.getContextPack(pack.id));
679
+ expect(pinnedPack?.members).toEqual([
680
+ expect.objectContaining({
681
+ itemId: "allowed",
682
+ itemVersionId: "allowed-v2",
683
+ }),
684
+ ]);
685
+
686
+ const resync = await asAlice(() =>
687
+ store.ingestItems({
688
+ sourceId: "source-1",
689
+ items: [
690
+ {
691
+ externalId: "allowed",
692
+ kind: "slide",
693
+ title: "allowed title",
694
+ content: "allowed canonical version 2",
695
+ contentHash: "allowed-hash-v2",
696
+ upstreamAccess: "available",
697
+ },
698
+ ],
699
+ }),
700
+ );
701
+ expect(resync.unchanged).toBe(1);
702
+ const afterResync = await asAlice(() =>
703
+ store.getCreativeContextItem("allowed"),
704
+ );
705
+ expect(afterResync?.version.id).toBe(enriched.itemVersionId);
706
+ expect(afterResync?.media[0]?.captionStatus).toBe("complete");
707
+
708
+ const retried = await asAlice(() =>
709
+ store.appendMediaEnrichmentVersion({
710
+ mediaId: "media-before-enrichment",
711
+ palette: ["#663399", "#ffffff"],
712
+ contentHash: "enriched-image-hash",
713
+ caption: "Builder dashboard on a purple background",
714
+ captionStatus: "complete",
715
+ ocrText: "Ship faster",
716
+ }),
717
+ );
718
+ expect(retried).toEqual({
719
+ itemId: "allowed",
720
+ itemVersionId: enriched.itemVersionId,
721
+ mediaId: enriched.mediaId,
722
+ appended: false,
723
+ });
724
+ });
725
+
726
+ it("reloads a concurrently inserted item instead of failing its idempotent ingest", async () => {
727
+ const { exec, runWithRequestContext, store } = await setup();
728
+ const ingest = () =>
729
+ runWithRequestContext(
730
+ { userEmail: "alice@example.test", orgId: "org-1" },
731
+ () =>
732
+ store.ingestItems({
733
+ sourceId: "source-1",
734
+ items: [
735
+ {
736
+ externalId: "concurrent-item",
737
+ kind: "slide",
738
+ title: "Concurrent item",
739
+ content: "Same immutable content",
740
+ contentHash: "concurrent-hash",
741
+ provenance: { fixture: true },
742
+ },
743
+ ],
744
+ }),
745
+ );
746
+
747
+ const results = await Promise.all([ingest(), ingest()]);
748
+ expect(results.reduce((total, result) => total + result.created, 0)).toBe(
749
+ 1,
750
+ );
751
+ expect(results.reduce((total, result) => total + result.unchanged, 0)).toBe(
752
+ 1,
753
+ );
754
+ const items = await exec.execute({
755
+ sql: "SELECT id FROM creative_context_items WHERE source_id = ? AND external_id = ?",
756
+ args: ["source-1", "concurrent-item"],
757
+ });
758
+ expect(items.rows).toHaveLength(1);
759
+ const versions = await exec.execute({
760
+ sql: "SELECT id FROM creative_context_item_versions WHERE item_id = ?",
761
+ args: [String(items.rows[0]!.id)],
762
+ });
763
+ expect(versions.rows).toHaveLength(1);
764
+ });
765
+
766
+ it("rejects direct ingest that bypasses normalized SQL text limits", async () => {
767
+ const { exec, runWithRequestContext, store } = await setup();
768
+ const asAlice = <T>(fn: () => Promise<T>) =>
769
+ runWithRequestContext(
770
+ { userEmail: "alice@example.test", orgId: "org-1" },
771
+ fn,
772
+ );
773
+ const ingest = (
774
+ item: Parameters<typeof store.ingestItems>[0]["items"][0],
775
+ ) =>
776
+ asAlice(() => store.ingestItems({ sourceId: "source-1", items: [item] }));
777
+ const base = {
778
+ kind: "document",
779
+ title: "Oversized direct ingest",
780
+ contentHash: "oversized-direct-hash",
781
+ };
782
+
783
+ await expect(
784
+ ingest({
785
+ ...base,
786
+ externalId: "oversized-searchable",
787
+ content: "界".repeat(22_000),
788
+ }),
789
+ ).rejects.toThrow(/searchable content.*exceeds.*normalize or chunk/i);
790
+ await expect(
791
+ ingest({
792
+ ...base,
793
+ externalId: "oversized-summary",
794
+ content: "bounded",
795
+ summary: "🙂".repeat(2_049),
796
+ }),
797
+ ).rejects.toThrow(/summary.*exceeds.*normalize the summary/i);
798
+ await expect(
799
+ ingest({
800
+ ...base,
801
+ externalId: "oversized-chunks",
802
+ content: "bounded",
803
+ chunks: [
804
+ { ordinal: 0, text: "a".repeat(40_000) },
805
+ { ordinal: 1, text: "b".repeat(30_000) },
806
+ ],
807
+ }),
808
+ ).rejects.toThrow(/chunks exceed.*normalize or split/i);
809
+ await expect(
810
+ ingest({
811
+ ...base,
812
+ externalId: "oversized-native",
813
+ kind: "google-slides-slide",
814
+ mimeType: "text/html",
815
+ content: `<div>${"🙂".repeat(32_768)}</div>`,
816
+ metadata: {
817
+ nativeArtifact: { app: "slides", format: "slides-html" },
818
+ },
819
+ }),
820
+ ).rejects.toThrow(/native content.*exceeds.*private blob storage/i);
821
+
822
+ const rows = await exec.execute({
823
+ sql: "SELECT id FROM creative_context_items WHERE external_id LIKE 'oversized-%'",
824
+ args: [],
825
+ });
826
+ expect(rows.rows).toHaveLength(0);
827
+ });
828
+
829
+ it("keeps prior native code immutable across resync and resolves hierarchical children by source version", async () => {
830
+ const { runWithRequestContext, store } = await setup();
831
+ const asAlice = <T>(fn: () => Promise<T>) =>
832
+ runWithRequestContext(
833
+ { userEmail: "alice@example.test", orgId: "org-1" },
834
+ fn,
835
+ );
836
+ const firstHtml =
837
+ "<!doctype html><html><head></head><body><div>Version one</div></body></html>";
838
+ const secondHtml =
839
+ "<!doctype html><html><head></head><body><div>Version two</div></body></html>";
840
+ const first = await asAlice(() =>
841
+ store.ingestItems({
842
+ sourceId: "source-1",
843
+ items: [
844
+ {
845
+ externalId: "figma-file:frame-1",
846
+ kind: "figma-frame",
847
+ title: "Native frame",
848
+ mimeType: "text/html",
849
+ content: firstHtml,
850
+ contentHash: "native-code-v1",
851
+ sourceVersion: "figma-v1",
852
+ provenance: {
853
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
854
+ },
855
+ },
856
+ ],
857
+ }),
858
+ );
859
+ const itemId = first.itemIds[0]!;
860
+ const firstDetail = await asAlice(() =>
861
+ store.getCreativeContextItem(itemId),
862
+ );
863
+ const firstVersionId = firstDetail!.version.id;
864
+
865
+ const second = await asAlice(() =>
866
+ store.ingestItems({
867
+ sourceId: "source-1",
868
+ items: [
869
+ {
870
+ externalId: "figma-file:frame-1",
871
+ kind: "figma-frame",
872
+ title: "Native frame",
873
+ mimeType: "text/html",
874
+ content: secondHtml,
875
+ contentHash: "native-code-v2",
876
+ sourceVersion: "figma-v2",
877
+ provenance: {
878
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
879
+ },
880
+ },
881
+ ],
882
+ }),
883
+ );
884
+ expect(second.versioned).toBe(1);
885
+ await expect(
886
+ asAlice(() => store.getCreativeContextItem(itemId, firstVersionId)),
887
+ ).resolves.toMatchObject({ version: { content: firstHtml } });
888
+ await expect(
889
+ asAlice(() => store.getCreativeContextItem(itemId)),
890
+ ).resolves.toMatchObject({ version: { content: secondHtml } });
891
+ await expect(
892
+ asAlice(() =>
893
+ store.getCreativeContextItemByExternalId({
894
+ sourceId: "source-1",
895
+ externalId: "figma-file:frame-1",
896
+ sourceVersion: "figma-v1",
897
+ }),
898
+ ),
899
+ ).resolves.toMatchObject({
900
+ version: { id: firstVersionId, content: firstHtml },
901
+ });
902
+ });
903
+
904
+ it("versions notes-only changes while packs retain the prior immutable evidence", async () => {
905
+ const { exec, runWithRequestContext, store } = await setup();
906
+ const asAlice = <T>(fn: () => Promise<T>) =>
907
+ runWithRequestContext(
908
+ { userEmail: "alice@example.test", orgId: "org-1" },
909
+ fn,
910
+ );
911
+ const slide = (notes: string) =>
912
+ normalizeContextItem({
913
+ externalId: "notes-deck:slide-1",
914
+ kind: "google-slides-slide",
915
+ title: "Launch plan",
916
+ mimeType: "text/html",
917
+ content: '<div class="fmd-slide">Unchanged slide</div>',
918
+ summary: `Speaker notes: ${notes}`,
919
+ sourceVersion: "same-provider-revision",
920
+ metadata: {
921
+ speakerNotes: notes,
922
+ nativeArtifact: {
923
+ schemaVersion: 1,
924
+ app: "slides",
925
+ format: "slides-html",
926
+ },
927
+ },
928
+ chunks: [
929
+ {
930
+ ordinal: 0,
931
+ kind: "slides-native-lexical",
932
+ text: `Unchanged slide Speaker notes ${notes}`,
933
+ },
934
+ ],
935
+ });
936
+
937
+ const first = await asAlice(() =>
938
+ store.ingestItems({ sourceId: "source-1", items: [slide("Draft")] }),
939
+ );
940
+ const itemId = first.itemIds[0]!;
941
+ const firstDetail = await asAlice(() =>
942
+ store.getCreativeContextItem(itemId),
943
+ );
944
+ const firstVersionId = firstDetail!.version.id;
945
+ const pack = await asAlice(() =>
946
+ store.createContextPack({
947
+ name: "Notes evidence",
948
+ members: [{ itemId }],
949
+ }),
950
+ );
951
+
952
+ const second = await asAlice(() =>
953
+ store.ingestItems({ sourceId: "source-1", items: [slide("Approved")] }),
954
+ );
955
+ expect(second).toMatchObject({ versioned: 1, unchanged: 0 });
956
+ await expect(
957
+ asAlice(() => store.getCreativeContextItem(itemId, firstVersionId)),
958
+ ).resolves.toMatchObject({
959
+ version: { versionNumber: 1, summary: "Speaker notes: Draft" },
960
+ chunks: [
961
+ expect.objectContaining({ text: expect.stringContaining("Draft") }),
962
+ ],
963
+ });
964
+ await expect(
965
+ asAlice(() => store.getCreativeContextItem(itemId)),
966
+ ).resolves.toMatchObject({
967
+ version: { versionNumber: 2, summary: "Speaker notes: Approved" },
968
+ chunks: [
969
+ expect.objectContaining({ text: expect.stringContaining("Approved") }),
970
+ ],
971
+ });
972
+ await expect(
973
+ asAlice(() => store.getContextPack(pack.id)),
974
+ ).resolves.toMatchObject({
975
+ members: [expect.objectContaining({ itemVersionId: firstVersionId })],
976
+ });
977
+ const versions = await exec.execute({
978
+ sql: "SELECT version_number FROM creative_context_item_versions WHERE item_id = ? ORDER BY version_number",
979
+ args: [itemId],
980
+ });
981
+ expect(versions.rows).toEqual([
982
+ expect.objectContaining({ version_number: 1 }),
983
+ expect.objectContaining({ version_number: 2 }),
984
+ ]);
985
+ });
986
+
987
+ it("dedupes reordered evidence but versions metadata and media-only changes", async () => {
988
+ const { exec, runWithRequestContext, store } = await setup();
989
+ const asAlice = <T>(fn: () => Promise<T>) =>
990
+ runWithRequestContext(
991
+ { userEmail: "alice@example.test", orgId: "org-1" },
992
+ fn,
993
+ );
994
+ const item = (input: { reversed: boolean; assetRole: string }) => {
995
+ const chunks = [
996
+ { ordinal: 0, kind: "text", text: "Stable body" },
997
+ { ordinal: 1, kind: "code", text: "font-family: Inter" },
998
+ ];
999
+ const media = [
1000
+ {
1001
+ kind: "image" as const,
1002
+ accessMode: "public" as const,
1003
+ url: "https://cdn.example.test/a.png",
1004
+ contentHash: "asset-a",
1005
+ metadata: { role: input.assetRole },
1006
+ },
1007
+ {
1008
+ kind: "image" as const,
1009
+ accessMode: "public" as const,
1010
+ url: "https://cdn.example.test/b.png",
1011
+ contentHash: "asset-b",
1012
+ metadata: { role: "supporting" },
1013
+ },
1014
+ ];
1015
+ const edges = [
1016
+ { relation: "uses-asset", toExternalId: "asset-a" },
1017
+ { relation: "uses-asset", toExternalId: "asset-b" },
1018
+ ];
1019
+ return normalizeContextItem({
1020
+ externalId: "media-evidence",
1021
+ kind: "design-artifact",
1022
+ title: "Media evidence",
1023
+ content: "Stable body",
1024
+ sourceVersion: "stable-source-version",
1025
+ metadata: { fidelity: { exact: 2 }, provider: "fixture" },
1026
+ chunks: input.reversed ? [...chunks].reverse() : chunks,
1027
+ media: input.reversed ? [...media].reverse() : media,
1028
+ edges: input.reversed ? [...edges].reverse() : edges,
1029
+ });
1030
+ };
1031
+
1032
+ const original = item({ reversed: false, assetRole: "hero" });
1033
+ const reordered = item({ reversed: true, assetRole: "hero" });
1034
+ expect(reordered.contentHash).toBe(original.contentHash);
1035
+ const first = await asAlice(() =>
1036
+ store.ingestItems({ sourceId: "source-1", items: [original] }),
1037
+ );
1038
+ const itemId = first.itemIds[0]!;
1039
+ const firstVersionId = (await asAlice(() =>
1040
+ store.getCreativeContextItem(itemId),
1041
+ ))!.version.id;
1042
+ const pack = await asAlice(() =>
1043
+ store.createContextPack({
1044
+ name: "Media evidence",
1045
+ members: [{ itemId }],
1046
+ }),
1047
+ );
1048
+ const unchanged = await asAlice(() =>
1049
+ store.ingestItems({ sourceId: "source-1", items: [reordered] }),
1050
+ );
1051
+ expect(unchanged).toMatchObject({ versioned: 0, unchanged: 1 });
1052
+
1053
+ const changed = await asAlice(() =>
1054
+ store.ingestItems({
1055
+ sourceId: "source-1",
1056
+ items: [item({ reversed: true, assetRole: "canonical-logo" })],
1057
+ }),
1058
+ );
1059
+ expect(changed).toMatchObject({ versioned: 1, unchanged: 0 });
1060
+ await expect(
1061
+ asAlice(() => store.getCreativeContextItem(itemId, firstVersionId)),
1062
+ ).resolves.toMatchObject({
1063
+ version: { versionNumber: 1 },
1064
+ media: [
1065
+ expect.objectContaining({ metadata: { role: "hero" } }),
1066
+ expect.objectContaining({ metadata: { role: "supporting" } }),
1067
+ ],
1068
+ });
1069
+ await expect(
1070
+ asAlice(() => store.getCreativeContextItem(itemId)),
1071
+ ).resolves.toMatchObject({
1072
+ version: { versionNumber: 2 },
1073
+ media: expect.arrayContaining([
1074
+ expect.objectContaining({ metadata: { role: "canonical-logo" } }),
1075
+ ]),
1076
+ });
1077
+ await expect(
1078
+ asAlice(() => store.getContextPack(pack.id)),
1079
+ ).resolves.toMatchObject({
1080
+ members: [expect.objectContaining({ itemVersionId: firstVersionId })],
1081
+ });
1082
+ const versions = await exec.execute({
1083
+ sql: "SELECT version_number FROM creative_context_item_versions WHERE item_id = ? ORDER BY version_number",
1084
+ args: [itemId],
1085
+ });
1086
+ expect(versions.rows).toHaveLength(2);
1087
+ });
1088
+
1089
+ it("pins hierarchical parent versions to exact child versions across later resyncs and access loss", async () => {
1090
+ const { exec, runWithRequestContext, store } = await setup();
1091
+ const { reassembleNativeCreativeArtifact } =
1092
+ await import("../native-artifact-reassembly.js");
1093
+ const asAlice = <T>(fn: () => Promise<T>) =>
1094
+ runWithRequestContext(
1095
+ { userEmail: "alice@example.test", orgId: "org-1" },
1096
+ fn,
1097
+ );
1098
+ const fidelityReport = {
1099
+ exact: { count: 1 },
1100
+ approximated: { count: 0, reasons: [] },
1101
+ imageFallback: { count: 0, reasons: [] },
1102
+ };
1103
+ const parentExternalId = "figma-file:parent";
1104
+ const childExternalId = "figma-file:child";
1105
+ const parentHtml = (label: string) =>
1106
+ `<!doctype html><html><head></head><body><div data-label="${label}"><div data-creative-context-child="${childExternalId}"></div></div></body></html>`;
1107
+ const childHtml = (label: string) =>
1108
+ `<!doctype html><html><head></head><body><div>${label}</div></body></html>`;
1109
+ const nativeMetadata = (externalId: string, child = false) => ({
1110
+ nativeArtifact: {
1111
+ schemaVersion: 1,
1112
+ app: "design",
1113
+ format: "design-html",
1114
+ rootExternalId: externalId,
1115
+ ...(child
1116
+ ? {}
1117
+ : {
1118
+ childExternalIds: [childExternalId],
1119
+ manifest: {
1120
+ kind: "hierarchical-artboard",
1121
+ children: [
1122
+ {
1123
+ externalId: childExternalId,
1124
+ sourceNodeId: "2:1",
1125
+ bounds: { x: 10, y: 20, width: 200, height: 100 },
1126
+ zOrder: 0,
1127
+ },
1128
+ ],
1129
+ },
1130
+ }),
1131
+ fidelityReport,
1132
+ },
1133
+ });
1134
+ const provenance = {
1135
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
1136
+ };
1137
+ const parentEdges = [
1138
+ {
1139
+ relation: "contains-native-child",
1140
+ toExternalId: childExternalId,
1141
+ },
1142
+ ];
1143
+
1144
+ const first = await asAlice(() =>
1145
+ store.ingestItems({
1146
+ sourceId: "source-1",
1147
+ items: [
1148
+ {
1149
+ externalId: parentExternalId,
1150
+ kind: "figma-artboard-manifest",
1151
+ title: "Parent",
1152
+ mimeType: "text/html",
1153
+ content: parentHtml("parent-v1"),
1154
+ contentHash: "parent-v1",
1155
+ sourceVersion: "figma-v1",
1156
+ provenance,
1157
+ metadata: nativeMetadata(parentExternalId),
1158
+ edges: parentEdges,
1159
+ },
1160
+ {
1161
+ externalId: childExternalId,
1162
+ kind: "figma-frame",
1163
+ title: "Child",
1164
+ mimeType: "text/html",
1165
+ content: childHtml("Child v1"),
1166
+ contentHash: "child-v1",
1167
+ sourceVersion: "figma-v1",
1168
+ provenance,
1169
+ metadata: nativeMetadata(childExternalId, true),
1170
+ },
1171
+ ],
1172
+ }),
1173
+ );
1174
+ const parentId = first.itemIds[0]!;
1175
+ const childId = first.itemIds[1]!;
1176
+ const childV1 = await asAlice(() => store.getCreativeContextItem(childId));
1177
+
1178
+ await asAlice(() =>
1179
+ store.ingestItems({
1180
+ sourceId: "source-1",
1181
+ items: [
1182
+ {
1183
+ externalId: parentExternalId,
1184
+ kind: "figma-artboard-manifest",
1185
+ title: "Parent",
1186
+ mimeType: "text/html",
1187
+ content: parentHtml("parent-v2"),
1188
+ contentHash: "parent-v2",
1189
+ sourceVersion: "figma-v2",
1190
+ provenance,
1191
+ metadata: nativeMetadata(parentExternalId),
1192
+ edges: parentEdges,
1193
+ },
1194
+ {
1195
+ externalId: childExternalId,
1196
+ kind: "figma-frame",
1197
+ title: "Child",
1198
+ mimeType: "text/html",
1199
+ content: childHtml("Child v1"),
1200
+ contentHash: "child-v1",
1201
+ sourceVersion: "figma-v2",
1202
+ provenance,
1203
+ metadata: nativeMetadata(childExternalId, true),
1204
+ },
1205
+ ],
1206
+ }),
1207
+ );
1208
+ const parentV2 = await asAlice(() =>
1209
+ store.getCreativeContextItem(parentId),
1210
+ );
1211
+ expect(parentV2?.edges).toEqual(
1212
+ expect.arrayContaining([
1213
+ expect.objectContaining({
1214
+ relation: "contains-native-child",
1215
+ toItemId: childId,
1216
+ toItemVersionId: childV1!.version.id,
1217
+ }),
1218
+ ]),
1219
+ );
1220
+
1221
+ await asAlice(() =>
1222
+ store.ingestItems({
1223
+ sourceId: "source-1",
1224
+ items: [
1225
+ {
1226
+ externalId: childExternalId,
1227
+ kind: "figma-frame",
1228
+ title: "Child",
1229
+ mimeType: "text/html",
1230
+ content: childHtml("Child v2"),
1231
+ contentHash: "child-v2",
1232
+ sourceVersion: "figma-v3",
1233
+ provenance,
1234
+ metadata: nativeMetadata(childExternalId, true),
1235
+ },
1236
+ ],
1237
+ }),
1238
+ );
1239
+ const pinnedParent = await asAlice(() =>
1240
+ store.getCreativeContextItem(parentId, parentV2!.version.id),
1241
+ );
1242
+ const reassembled = await asAlice(() =>
1243
+ reassembleNativeCreativeArtifact({
1244
+ root: pinnedParent!,
1245
+ app: "design",
1246
+ format: "design-html",
1247
+ resolveChild: store.getCreativeContextItemByExternalId,
1248
+ }),
1249
+ );
1250
+ expect(reassembled.html).toContain("Child v1");
1251
+ expect(reassembled.html).not.toContain("Child v2");
1252
+
1253
+ await exec.execute({
1254
+ sql: "UPDATE creative_context_items SET status = 'unavailable' WHERE id = ?",
1255
+ args: [childId],
1256
+ });
1257
+ await expect(
1258
+ asAlice(() =>
1259
+ reassembleNativeCreativeArtifact({
1260
+ root: pinnedParent!,
1261
+ app: "design",
1262
+ format: "design-html",
1263
+ resolveChild: store.getCreativeContextItemByExternalId,
1264
+ }),
1265
+ ),
1266
+ ).rejects.toThrow("unavailable at the pinned source version");
1267
+ });
1268
+
1269
+ it("publishes trusted Slides and Figma hierarchies without losing compiler metadata or child pins", async () => {
1270
+ const { runWithRequestContext, store } = await setup();
1271
+ const { reassembleNativeCreativeArtifact } =
1272
+ await import("../native-artifact-reassembly.js");
1273
+ const asAlice = <T>(fn: () => Promise<T>) =>
1274
+ runWithRequestContext(
1275
+ { userEmail: "alice@example.test", orgId: "org-1" },
1276
+ fn,
1277
+ );
1278
+ const asBob = <T>(fn: () => Promise<T>) =>
1279
+ runWithRequestContext(
1280
+ { userEmail: "bob@example.test", orgId: "org-1" },
1281
+ fn,
1282
+ );
1283
+ const fidelityReport = {
1284
+ exact: { count: 1 },
1285
+ approximated: { count: 0, reasons: [] },
1286
+ imageFallback: { count: 0, reasons: [] },
1287
+ };
1288
+ for (const fixture of [
1289
+ {
1290
+ app: "slides" as const,
1291
+ format: "slides-html" as const,
1292
+ compiler: "@agent-native/creative-context:google-slides-native",
1293
+ },
1294
+ {
1295
+ app: "design" as const,
1296
+ format: "design-html" as const,
1297
+ compiler: "@agent-native/core/ingestion:figma-node-to-html",
1298
+ },
1299
+ ]) {
1300
+ const rootExternalId = `${fixture.app}:published-root`;
1301
+ const childExternalId = `${fixture.app}:published-child`;
1302
+ const rootHtml = `<!doctype html><html><head></head><body><div data-creative-context-child="${childExternalId}"></div></body></html>`;
1303
+ const childHtml = `<!doctype html><html><head></head><body><div>${fixture.app} child</div></body></html>`;
1304
+ const ingested = await asAlice(() =>
1305
+ store.ingestItems({
1306
+ sourceId: "source-1",
1307
+ items: [
1308
+ {
1309
+ externalId: rootExternalId,
1310
+ kind: "native-manifest",
1311
+ title: `${fixture.app} root`,
1312
+ mimeType: "text/html",
1313
+ content: rootHtml,
1314
+ contentHash: `${fixture.app}-root-v1`,
1315
+ provenance: { compiler: fixture.compiler },
1316
+ metadata: {
1317
+ nativeArtifact: {
1318
+ schemaVersion: 1,
1319
+ app: fixture.app,
1320
+ format: fixture.format,
1321
+ rootExternalId,
1322
+ childExternalIds: [childExternalId],
1323
+ manifest: {
1324
+ kind: "hierarchical-artboard",
1325
+ children: [
1326
+ {
1327
+ externalId: childExternalId,
1328
+ sourceNodeId: "child-1",
1329
+ bounds: { x: 0, y: 0, width: 10, height: 10 },
1330
+ zOrder: 0,
1331
+ },
1332
+ ],
1333
+ },
1334
+ fidelityReport,
1335
+ },
1336
+ },
1337
+ edges: [
1338
+ {
1339
+ relation: "contains-native-child",
1340
+ toExternalId: childExternalId,
1341
+ },
1342
+ ],
1343
+ },
1344
+ {
1345
+ externalId: childExternalId,
1346
+ kind: "native-child",
1347
+ title: `${fixture.app} child`,
1348
+ mimeType: "text/html",
1349
+ content: childHtml,
1350
+ contentHash: `${fixture.app}-child-v1`,
1351
+ provenance: { compiler: fixture.compiler },
1352
+ metadata: {
1353
+ nativeArtifact: {
1354
+ schemaVersion: 1,
1355
+ app: fixture.app,
1356
+ format: fixture.format,
1357
+ rootExternalId: childExternalId,
1358
+ fidelityReport,
1359
+ },
1360
+ },
1361
+ },
1362
+ ],
1363
+ }),
1364
+ );
1365
+ const context = await asAlice(() =>
1366
+ store.createCreativeContext({
1367
+ name: `${fixture.app} fidelity`,
1368
+ kind: "specialty",
1369
+ }),
1370
+ );
1371
+ await asAlice(() =>
1372
+ store.manageContextMembership({
1373
+ operation: "submit",
1374
+ contextId: context!.id,
1375
+ itemId: ingested.itemIds[0],
1376
+ confirmBroaderPublication: true,
1377
+ }),
1378
+ );
1379
+ const membership = (
1380
+ await asAlice(() =>
1381
+ store.listContextMemberships({ contextId: context!.id, limit: 20 }),
1382
+ )
1383
+ ).memberships.find(
1384
+ (entry) => entry.artifactKey === `source-1:${rootExternalId}`,
1385
+ )!;
1386
+ const published = await asBob(() =>
1387
+ store.getCreativeContextItem(
1388
+ membership.publishedItemId!,
1389
+ membership.publishedItemVersionId!,
1390
+ ),
1391
+ );
1392
+ expect(published?.item.provenance.compiler).toBe(fixture.compiler);
1393
+ expect(published?.version.metadata).toMatchObject({
1394
+ nativeArtifact: { app: fixture.app, format: fixture.format },
1395
+ });
1396
+ const reassembled = await asBob(() =>
1397
+ reassembleNativeCreativeArtifact({
1398
+ root: published!,
1399
+ app: fixture.app,
1400
+ format: fixture.format,
1401
+ resolveChild: store.getCreativeContextItemByExternalId,
1402
+ }),
1403
+ );
1404
+ expect(reassembled.html).toContain(`${fixture.app} child`);
1405
+ expect(reassembled.evidence).toHaveLength(2);
1406
+
1407
+ const childEdge = published!.edges.find(
1408
+ (edge) => edge.relation === "contains-native-child",
1409
+ );
1410
+ expect(childEdge).toMatchObject({
1411
+ toItemId: expect.any(String),
1412
+ toItemVersionId: expect.any(String),
1413
+ });
1414
+ const pack = await asAlice(() =>
1415
+ store.createContextPack({
1416
+ name: `${fixture.app} hierarchical replay`,
1417
+ baseContextId: context!.id,
1418
+ members: [
1419
+ {
1420
+ itemId: published!.item.id,
1421
+ itemVersionId: published!.version.id,
1422
+ reason: "Explicit root",
1423
+ score: 0.91,
1424
+ scoreMetadata: { source: "test" },
1425
+ },
1426
+ ],
1427
+ }),
1428
+ );
1429
+ expect(pack.members).toEqual([
1430
+ expect.objectContaining({
1431
+ itemId: published!.item.id,
1432
+ itemVersionId: published!.version.id,
1433
+ ordinal: 0,
1434
+ reason: "Explicit root",
1435
+ score: 0.91,
1436
+ scoreMetadata: { source: "test" },
1437
+ }),
1438
+ expect.objectContaining({
1439
+ itemId: childEdge!.toItemId,
1440
+ itemVersionId: childEdge!.toItemVersionId,
1441
+ ordinal: 1,
1442
+ reason: "Native artifact dependency",
1443
+ score: null,
1444
+ }),
1445
+ ]);
1446
+ await asAlice(() =>
1447
+ store.manageContextMembership({
1448
+ operation: "remove",
1449
+ contextId: context!.id,
1450
+ membershipId: membership.id,
1451
+ }),
1452
+ );
1453
+ const removedMemberships = await asAlice(() =>
1454
+ store.listContextMemberships({ contextId: context!.id, limit: 20 }),
1455
+ );
1456
+ expect(
1457
+ removedMemberships.memberships.filter(
1458
+ (entry) =>
1459
+ entry.publishedItemId === published!.item.id ||
1460
+ entry.publishedItemId === childEdge!.toItemId,
1461
+ ),
1462
+ ).toEqual(
1463
+ expect.arrayContaining([
1464
+ expect.objectContaining({
1465
+ publishedItemId: published!.item.id,
1466
+ status: "removed",
1467
+ }),
1468
+ expect.objectContaining({
1469
+ publishedItemId: childEdge!.toItemId,
1470
+ status: "removed",
1471
+ }),
1472
+ ]),
1473
+ );
1474
+ const replayPack = await asBob(() => store.getContextPack(pack.id));
1475
+ expect(replayPack?.members).toHaveLength(2);
1476
+ const replayRoot = await asBob(() =>
1477
+ store.getCreativeContextItem(published!.item.id, published!.version.id),
1478
+ );
1479
+ const replayChild = await asBob(() =>
1480
+ store.getCreativeContextItem(
1481
+ childEdge!.toItemId!,
1482
+ childEdge!.toItemVersionId!,
1483
+ ),
1484
+ );
1485
+ expect(replayRoot?.version.id).toBe(published!.version.id);
1486
+ expect(replayChild?.version.id).toBe(childEdge!.toItemVersionId);
1487
+ const replayed = await asBob(() =>
1488
+ reassembleNativeCreativeArtifact({
1489
+ root: replayRoot!,
1490
+ app: fixture.app,
1491
+ format: fixture.format,
1492
+ resolveChild: store.getCreativeContextItemByExternalId,
1493
+ }),
1494
+ );
1495
+ expect(replayed.html).toContain(`${fixture.app} child`);
1496
+ expect(replayed.evidence).toEqual(
1497
+ expect.arrayContaining([
1498
+ {
1499
+ itemId: published!.item.id,
1500
+ itemVersionId: published!.version.id,
1501
+ },
1502
+ {
1503
+ itemId: childEdge!.toItemId,
1504
+ itemVersionId: childEdge!.toItemVersionId,
1505
+ },
1506
+ ]),
1507
+ );
1508
+ }
1509
+ });
1510
+
1511
+ it("does not turn a forged compiler id into a trusted published artifact", async () => {
1512
+ const { runWithRequestContext, store } = await setup();
1513
+ const { reassembleNativeCreativeArtifact } =
1514
+ await import("../native-artifact-reassembly.js");
1515
+ const asAlice = <T>(fn: () => Promise<T>) =>
1516
+ runWithRequestContext(
1517
+ { userEmail: "alice@example.test", orgId: "org-1" },
1518
+ fn,
1519
+ );
1520
+ const ingested = await asAlice(() =>
1521
+ store.ingestItems({
1522
+ sourceId: "source-1",
1523
+ items: [
1524
+ {
1525
+ externalId: "forged-native",
1526
+ kind: "native",
1527
+ title: "Forged",
1528
+ mimeType: "text/html",
1529
+ content:
1530
+ "<!doctype html><html><head></head><body><div>forged</div></body></html>",
1531
+ contentHash: "forged-v1",
1532
+ provenance: { compiler: "attacker/compiler" },
1533
+ metadata: {
1534
+ nativeArtifact: {
1535
+ schemaVersion: 1,
1536
+ app: "design",
1537
+ format: "design-html",
1538
+ rootExternalId: "forged-native",
1539
+ fidelityReport: {
1540
+ exact: { count: 1 },
1541
+ approximated: { count: 0, reasons: [] },
1542
+ imageFallback: { count: 0, reasons: [] },
1543
+ },
1544
+ },
1545
+ },
1546
+ },
1547
+ ],
1548
+ }),
1549
+ );
1550
+ const context = await asAlice(() =>
1551
+ store.createCreativeContext({ name: "forged", kind: "specialty" }),
1552
+ );
1553
+ await asAlice(() =>
1554
+ store.manageContextMembership({
1555
+ operation: "submit",
1556
+ contextId: context!.id,
1557
+ itemId: ingested.itemIds[0],
1558
+ confirmBroaderPublication: true,
1559
+ }),
1560
+ );
1561
+ const membership = (
1562
+ await asAlice(() =>
1563
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1564
+ )
1565
+ ).memberships[0]!;
1566
+ const published = await asAlice(() =>
1567
+ store.getCreativeContextItem(
1568
+ membership.publishedItemId!,
1569
+ membership.publishedItemVersionId!,
1570
+ ),
1571
+ );
1572
+ await expect(
1573
+ asAlice(() =>
1574
+ reassembleNativeCreativeArtifact({
1575
+ root: published!,
1576
+ app: "design",
1577
+ format: "design-html",
1578
+ resolveChild: store.getCreativeContextItemByExternalId,
1579
+ }),
1580
+ ),
1581
+ ).rejects.toThrow(/trusted native artifact compiler/i);
1582
+ });
1583
+
1584
+ it("keeps pending submissions private to their submitter and context reviewers", async () => {
1585
+ const { exec, runWithRequestContext, store } = await setup();
1586
+ const asUser = <T>(userEmail: string, fn: () => Promise<T>) =>
1587
+ runWithRequestContext({ userEmail, orgId: "org-1" }, fn);
1588
+ const asAlice = <T>(fn: () => Promise<T>) =>
1589
+ asUser("alice@example.test", fn);
1590
+ const asBob = <T>(fn: () => Promise<T>) => asUser("bob@example.test", fn);
1591
+ const asCarol = <T>(fn: () => Promise<T>) =>
1592
+ asUser("carol@example.test", fn);
1593
+ const asDrew = <T>(fn: () => Promise<T>) => asUser("drew@example.test", fn);
1594
+ const context = await asAlice(() =>
1595
+ store.createCreativeContext({
1596
+ name: "Review-only governance",
1597
+ kind: "specialty",
1598
+ approvalPolicy: "review",
1599
+ }),
1600
+ );
1601
+ const timestamp = "2026-07-17T00:00:00.000Z";
1602
+ await exec.execute({
1603
+ sql: `INSERT INTO creative_context_source_shares
1604
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
1605
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1606
+ args: [
1607
+ "source-share-bob",
1608
+ "source-1",
1609
+ "user",
1610
+ "bob@example.test",
1611
+ "admin",
1612
+ "alice@example.test",
1613
+ timestamp,
1614
+ ],
1615
+ });
1616
+ for (const [id, email] of [
1617
+ ["context-share-bob", "bob@example.test"],
1618
+ ["context-share-drew", "drew@example.test"],
1619
+ ]) {
1620
+ await exec.execute({
1621
+ sql: `INSERT INTO creative_context_shares
1622
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
1623
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
1624
+ args: [
1625
+ id,
1626
+ context!.id,
1627
+ "user",
1628
+ email,
1629
+ "editor",
1630
+ "alice@example.test",
1631
+ timestamp,
1632
+ ],
1633
+ });
1634
+ }
1635
+ await exec.execute({
1636
+ sql: "UPDATE creative_context_items SET thumbnail_blob_ref = ? WHERE id = ?",
1637
+ args: ["creative-context-blob:v1:pending-preview-opaque", "allowed"],
1638
+ });
1639
+
1640
+ await expect(
1641
+ asCarol(() =>
1642
+ store.manageContextMembership({
1643
+ operation: "submit",
1644
+ contextId: context!.id,
1645
+ itemId: "allowed",
1646
+ }),
1647
+ ),
1648
+ ).rejects.toThrow();
1649
+ expect(
1650
+ (await asBob(() => store.getCreativeContextById(context!.id)))?.access,
1651
+ ).toMatchObject({ canSubmit: true, canReview: false });
1652
+
1653
+ const submitted = await asBob(() =>
1654
+ store.manageContextMembership({
1655
+ operation: "submit",
1656
+ contextId: context!.id,
1657
+ itemId: "allowed",
1658
+ confirmBroaderPublication: true,
1659
+ }),
1660
+ );
1661
+ const membershipId = submitted.membershipId;
1662
+ await expect(
1663
+ asBob(() =>
1664
+ store.manageContextMembership({
1665
+ operation: "approve",
1666
+ contextId: context!.id,
1667
+ membershipId,
1668
+ }),
1669
+ ),
1670
+ ).rejects.toThrow();
1671
+ await expect(
1672
+ asDrew(() =>
1673
+ store.manageContextMembership({
1674
+ operation: "remove",
1675
+ contextId: context!.id,
1676
+ membershipId,
1677
+ }),
1678
+ ),
1679
+ ).rejects.toThrow();
1680
+ await expect(
1681
+ asBob(() =>
1682
+ store.updateCreativeContext(context!.id, { name: "Unapproved edit" }),
1683
+ ),
1684
+ ).rejects.toThrow();
1685
+
1686
+ const ownPending = await asBob(() =>
1687
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1688
+ );
1689
+ expect(ownPending.memberships).toEqual([
1690
+ expect.objectContaining({
1691
+ id: membershipId,
1692
+ artifactKey: "source-1:allowed",
1693
+ pendingSubmission: expect.objectContaining({
1694
+ submittedBy: "bob@example.test",
1695
+ proposedItem: expect.objectContaining({
1696
+ title: "allowed title",
1697
+ itemVersionId: expect.any(String),
1698
+ }),
1699
+ }),
1700
+ }),
1701
+ ]);
1702
+ const proposedItem =
1703
+ ownPending.memberships[0]?.pendingSubmission?.proposedItem;
1704
+ expect(proposedItem).toMatchObject({
1705
+ id: expect.any(String),
1706
+ itemVersionId: expect.any(String),
1707
+ });
1708
+ expect(JSON.stringify(ownPending)).not.toContain("pending-preview-opaque");
1709
+ await expect(
1710
+ asBob(() =>
1711
+ store.readPendingCreativeContextMedia({
1712
+ itemId: proposedItem!.id,
1713
+ itemVersionId: proposedItem!.itemVersionId,
1714
+ }),
1715
+ ),
1716
+ ).resolves.toMatchObject({
1717
+ storageKey: "creative-context-blob:v1:pending-preview-opaque",
1718
+ });
1719
+ await expect(
1720
+ asAlice(() =>
1721
+ store.readPendingCreativeContextMedia({
1722
+ itemId: proposedItem!.id,
1723
+ itemVersionId: proposedItem!.itemVersionId,
1724
+ }),
1725
+ ),
1726
+ ).resolves.toMatchObject({
1727
+ storageKey: "creative-context-blob:v1:pending-preview-opaque",
1728
+ });
1729
+ await expect(
1730
+ asDrew(() =>
1731
+ store.readPendingCreativeContextMedia({
1732
+ itemId: proposedItem!.id,
1733
+ itemVersionId: proposedItem!.itemVersionId,
1734
+ }),
1735
+ ),
1736
+ ).resolves.toBeNull();
1737
+ await expect(
1738
+ asCarol(() =>
1739
+ store.readPendingCreativeContextMedia({
1740
+ itemId: proposedItem!.id,
1741
+ itemVersionId: proposedItem!.itemVersionId,
1742
+ }),
1743
+ ),
1744
+ ).resolves.toBeNull();
1745
+ await expect(
1746
+ asDrew(() =>
1747
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1748
+ ),
1749
+ ).resolves.toMatchObject({ memberships: [] });
1750
+ await expect(
1751
+ asCarol(() =>
1752
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1753
+ ),
1754
+ ).resolves.toMatchObject({ memberships: [] });
1755
+
1756
+ const reviewerView = await asAlice(() =>
1757
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1758
+ );
1759
+ expect(reviewerView.memberships[0]).toMatchObject({
1760
+ id: membershipId,
1761
+ pendingSubmission: expect.objectContaining({
1762
+ submittedBy: "bob@example.test",
1763
+ proposedItem: expect.objectContaining({
1764
+ title: "allowed title",
1765
+ }),
1766
+ }),
1767
+ });
1768
+ expect(
1769
+ (await asAlice(() => store.getCreativeContextById(context!.id)))?.access,
1770
+ ).toMatchObject({ canReview: true, canAdmin: true });
1771
+
1772
+ await asAlice(() =>
1773
+ store.manageContextMembership({
1774
+ operation: "approve",
1775
+ contextId: context!.id,
1776
+ membershipId,
1777
+ }),
1778
+ );
1779
+ const approvedForViewer = await asCarol(() =>
1780
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1781
+ );
1782
+ expect(approvedForViewer.memberships).toEqual([
1783
+ expect.objectContaining({
1784
+ id: membershipId,
1785
+ artifactKey: "source-1:allowed",
1786
+ pendingSubmissionId: null,
1787
+ pendingSubmission: null,
1788
+ publishedItem: expect.objectContaining({ id: expect.any(String) }),
1789
+ }),
1790
+ ]);
1791
+
1792
+ const approvedVersionId =
1793
+ approvedForViewer.memberships[0]!.publishedItemVersionId;
1794
+ await asAlice(() =>
1795
+ store.ingestItems({
1796
+ sourceId: "source-1",
1797
+ items: [
1798
+ {
1799
+ externalId: "allowed",
1800
+ kind: "slide",
1801
+ title: "allowed title update",
1802
+ content: "new source version pending review",
1803
+ contentHash: "allowed-hash-v3",
1804
+ upstreamAccess: "available",
1805
+ },
1806
+ ],
1807
+ }),
1808
+ );
1809
+ await asBob(() =>
1810
+ store.manageContextMembership({
1811
+ operation: "submit",
1812
+ contextId: context!.id,
1813
+ itemId: "allowed",
1814
+ confirmBroaderPublication: true,
1815
+ }),
1816
+ );
1817
+ const viewerDuringUpdate = await asCarol(() =>
1818
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1819
+ );
1820
+ expect(viewerDuringUpdate.memberships[0]).toMatchObject({
1821
+ publishedItemVersionId: approvedVersionId,
1822
+ pendingSubmissionId: null,
1823
+ pendingSubmission: null,
1824
+ });
1825
+ const reviewerDuringUpdate = await asAlice(() =>
1826
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1827
+ );
1828
+ expect(reviewerDuringUpdate.memberships[0]).toMatchObject({
1829
+ publishedItemVersionId: approvedVersionId,
1830
+ pendingSubmission: expect.objectContaining({ status: "pending" }),
1831
+ });
1832
+ });
1833
+
1834
+ it("applies open and admins-only publication policies to editor submissions", async () => {
1835
+ const { exec, runWithRequestContext, store } = await setup();
1836
+ const asUser = <T>(userEmail: string, fn: () => Promise<T>) =>
1837
+ runWithRequestContext({ userEmail, orgId: "org-1" }, fn);
1838
+ const asAlice = <T>(fn: () => Promise<T>) =>
1839
+ asUser("alice@example.test", fn);
1840
+ const asBob = <T>(fn: () => Promise<T>) => asUser("bob@example.test", fn);
1841
+ await exec.execute({
1842
+ sql: "UPDATE creative_context_sources SET visibility = 'org' WHERE id = 'source-1'",
1843
+ args: [],
1844
+ });
1845
+ const [open, adminsOnly] = await Promise.all([
1846
+ asAlice(() =>
1847
+ store.createCreativeContext({
1848
+ name: "Open",
1849
+ kind: "specialty",
1850
+ approvalPolicy: "open",
1851
+ }),
1852
+ ),
1853
+ asAlice(() =>
1854
+ store.createCreativeContext({
1855
+ name: "Admin publishing",
1856
+ kind: "specialty",
1857
+ approvalPolicy: "admins-only",
1858
+ }),
1859
+ ),
1860
+ ]);
1861
+ for (const [id, contextId] of [
1862
+ ["open-editor", open!.id],
1863
+ ["admins-editor", adminsOnly!.id],
1864
+ ]) {
1865
+ await exec.execute({
1866
+ sql: `INSERT INTO creative_context_shares
1867
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
1868
+ VALUES (?, ?, 'user', 'bob@example.test', 'editor', 'alice@example.test', ?)`,
1869
+ args: [id, contextId, "2026-07-17T00:00:00.000Z"],
1870
+ });
1871
+ }
1872
+ const openSubmission = await asBob(() =>
1873
+ store.manageContextMembership({
1874
+ operation: "submit",
1875
+ contextId: open!.id,
1876
+ itemId: "allowed",
1877
+ }),
1878
+ );
1879
+ expect(openSubmission.submission).toMatchObject({ status: "approved" });
1880
+
1881
+ const governedSubmission = await asBob(() =>
1882
+ store.manageContextMembership({
1883
+ operation: "submit",
1884
+ contextId: adminsOnly!.id,
1885
+ itemId: "allowed",
1886
+ }),
1887
+ );
1888
+ expect(governedSubmission.submission).toMatchObject({ status: "pending" });
1889
+ await expect(
1890
+ asBob(() =>
1891
+ store.manageContextMembership({
1892
+ operation: "approve",
1893
+ contextId: adminsOnly!.id,
1894
+ membershipId: governedSubmission.membershipId,
1895
+ }),
1896
+ ),
1897
+ ).rejects.toThrow();
1898
+ await expect(
1899
+ asAlice(() =>
1900
+ store.manageContextMembership({
1901
+ operation: "approve",
1902
+ contextId: adminsOnly!.id,
1903
+ membershipId: governedSubmission.membershipId,
1904
+ }),
1905
+ ),
1906
+ ).resolves.toEqual({ approved: true });
1907
+ });
1908
+
1909
+ it("serializes concurrent Default creation to one scope-owned context", async () => {
1910
+ const { exec, runWithRequestContext, store } = await setup();
1911
+ const createDefault = () =>
1912
+ runWithRequestContext(
1913
+ { userEmail: "alice@example.test", orgId: "org-1" },
1914
+ () => store.createCreativeContext({ name: "Default", kind: "default" }),
1915
+ );
1916
+ const [first, second] = await Promise.all([
1917
+ createDefault(),
1918
+ createDefault(),
1919
+ ]);
1920
+ expect(first?.id).toBe(second?.id);
1921
+ const rows = await exec.execute({
1922
+ sql: `SELECT id FROM creative_contexts
1923
+ WHERE default_scope_key = ?`,
1924
+ args: ["org:org-1"],
1925
+ });
1926
+ expect(rows.rows).toHaveLength(1);
1927
+ });
1928
+
1929
+ it("keeps Default non-archivable and does not broaden private corpus during org backfill", async () => {
1930
+ const { runWithRequestContext, store } = await setup();
1931
+ const asAlice = <T>(fn: () => Promise<T>) =>
1932
+ runWithRequestContext(
1933
+ { userEmail: "alice@example.test", orgId: "org-1" },
1934
+ fn,
1935
+ );
1936
+ const context = await asAlice(() => store.ensureDefaultCreativeContext());
1937
+ expect(context).toMatchObject({ kind: "default", visibility: "org" });
1938
+ await expect(
1939
+ asAlice(() => store.archiveCreativeContext(context!.id)),
1940
+ ).rejects.toThrow(/Default Creative Context cannot be archived/);
1941
+ const memberships = await asAlice(() =>
1942
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
1943
+ );
1944
+ expect(memberships.memberships).toEqual([]);
1945
+ });
1946
+
1947
+ it("applies each Creative Context's rank to the same item at retrieval time", async () => {
1948
+ const { exec, runWithRequestContext, store } = await setup();
1949
+ const asAlice = <T>(fn: () => Promise<T>) =>
1950
+ runWithRequestContext(
1951
+ { userEmail: "alice@example.test", orgId: "org-1" },
1952
+ fn,
1953
+ );
1954
+ const [canonical, normal] = await Promise.all([
1955
+ asAlice(() =>
1956
+ store.createCreativeContext({
1957
+ name: "Canonical lane",
1958
+ kind: "specialty",
1959
+ }),
1960
+ ),
1961
+ asAlice(() =>
1962
+ store.createCreativeContext({ name: "Normal lane", kind: "specialty" }),
1963
+ ),
1964
+ ]);
1965
+ for (const [id, contextId, rank] of [
1966
+ ["canonical-membership", canonical!.id, "canonical"],
1967
+ ["normal-membership", normal!.id, "normal"],
1968
+ ]) {
1969
+ await exec.execute({
1970
+ sql: `INSERT INTO creative_context_memberships
1971
+ (id, context_id, artifact_key, published_item_id, published_item_version_id,
1972
+ rank, status, created_at, updated_at, owner_email, org_id)
1973
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1974
+ args: [
1975
+ id,
1976
+ contextId,
1977
+ "source-1:allowed",
1978
+ "allowed",
1979
+ "allowed-v2",
1980
+ rank,
1981
+ "active",
1982
+ "2026-07-17T00:00:00.000Z",
1983
+ "2026-07-17T00:00:00.000Z",
1984
+ "alice@example.test",
1985
+ "org-1",
1986
+ ],
1987
+ });
1988
+ }
1989
+ const [canonicalResults, normalResults, canonicalDocuments] =
1990
+ await Promise.all([
1991
+ asAlice(() =>
1992
+ store.listAccessibleLexicalCandidates({
1993
+ query: "canonical version 2",
1994
+ contextId: canonical!.id,
1995
+ limit: 10,
1996
+ }),
1997
+ ),
1998
+ asAlice(() =>
1999
+ store.listAccessibleLexicalCandidates({
2000
+ query: "canonical version 2",
2001
+ contextId: normal!.id,
2002
+ limit: 10,
2003
+ }),
2004
+ ),
2005
+ asAlice(() =>
2006
+ store.listAccessibleSearchDocuments({
2007
+ contextId: canonical!.id,
2008
+ limit: 10,
2009
+ }),
2010
+ ),
2011
+ ]);
2012
+ expect(canonicalResults.results[0]).toMatchObject({
2013
+ itemId: "allowed",
2014
+ itemVersionId: "allowed-v2",
2015
+ });
2016
+ expect(canonicalResults.results[0]!.score).toBeGreaterThan(
2017
+ normalResults.results[0]!.score,
2018
+ );
2019
+ expect(canonicalDocuments[0]).toMatchObject({
2020
+ itemId: "allowed",
2021
+ curationRank: "canonical",
2022
+ });
2023
+ });
2024
+
2025
+ it("finds native artifacts through bounded code-token chunks", async () => {
2026
+ const { runWithRequestContext, store } = await setup();
2027
+ const { performCreativeContextSearch } =
2028
+ await import("../server/retrieval.js");
2029
+ const asAlice = <T>(fn: () => Promise<T>) =>
2030
+ runWithRequestContext(
2031
+ { userEmail: "alice@example.test", orgId: "org-1" },
2032
+ fn,
2033
+ );
2034
+ const ingested = await asAlice(() =>
2035
+ store.ingestItems({
2036
+ sourceId: "source-1",
2037
+ items: [
2038
+ {
2039
+ externalId: "figma-file:metric-card",
2040
+ kind: "figma-frame",
2041
+ title: "Metric card",
2042
+ mimeType: "text/html",
2043
+ content:
2044
+ '<!doctype html><html><head></head><body><div class="metric-card"></div></body></html>',
2045
+ contentHash: "metric-card-native-v1",
2046
+ chunks: [
2047
+ {
2048
+ ordinal: 0,
2049
+ kind: "code",
2050
+ text: "metric-card display-grid align-items-center",
2051
+ metadata: {
2052
+ role: "code-tokens",
2053
+ format: "design-html",
2054
+ },
2055
+ },
2056
+ ],
2057
+ },
2058
+ ],
2059
+ }),
2060
+ );
2061
+
2062
+ const result = await asAlice(() =>
2063
+ performCreativeContextSearch({
2064
+ query: "metric-card align-items-center",
2065
+ limit: 5,
2066
+ matchMode: "allTerms",
2067
+ snapshot: false,
2068
+ }),
2069
+ );
2070
+ expect(result.results).toEqual(
2071
+ expect.arrayContaining([
2072
+ expect.objectContaining({
2073
+ itemId: ingested.itemIds[0],
2074
+ kind: "figma-frame",
2075
+ }),
2076
+ ]),
2077
+ );
2078
+ });
2079
+
2080
+ it("reloads a concurrently discovered inventory item instead of duplicating it", async () => {
2081
+ const { exec, runWithRequestContext, store } = await setup();
2082
+ const upsert = () =>
2083
+ runWithRequestContext(
2084
+ { userEmail: "alice@example.test", orgId: "org-1" },
2085
+ () =>
2086
+ store.upsertSourceInventory({
2087
+ sourceId: "source-1",
2088
+ items: [
2089
+ {
2090
+ externalId: "concurrent-inventory",
2091
+ kind: "document",
2092
+ title: "Concurrent inventory",
2093
+ },
2094
+ ],
2095
+ }),
2096
+ );
2097
+
2098
+ const results = await Promise.all([upsert(), upsert()]);
2099
+ expect(results.reduce((total, result) => total + result.created, 0)).toBe(
2100
+ 1,
2101
+ );
2102
+ expect(results.reduce((total, result) => total + result.updated, 0)).toBe(
2103
+ 1,
2104
+ );
2105
+ const items = await exec.execute({
2106
+ sql: "SELECT id FROM creative_context_items WHERE source_id = ? AND external_id = ?",
2107
+ args: ["source-1", "concurrent-inventory"],
2108
+ });
2109
+ expect(items.rows).toHaveLength(1);
2110
+ });
2111
+
2112
+ it("reconciles removed and restored inventory without weakening source access", async () => {
2113
+ const { exec, runWithRequestContext, store } = await setup();
2114
+ const asAlice = <T>(fn: () => Promise<T>) =>
2115
+ runWithRequestContext(
2116
+ { userEmail: "alice@example.test", orgId: "org-1" },
2117
+ fn,
2118
+ );
2119
+ const asBob = <T>(fn: () => Promise<T>) =>
2120
+ runWithRequestContext(
2121
+ { userEmail: "bob@example.test", orgId: "org-1" },
2122
+ fn,
2123
+ );
2124
+
2125
+ await expect(
2126
+ asBob(() =>
2127
+ store.reconcileSourceInventory({
2128
+ sourceId: "source-1",
2129
+ presentExternalIds: ["allowed"],
2130
+ }),
2131
+ ),
2132
+ ).rejects.toThrow();
2133
+
2134
+ await expect(
2135
+ asAlice(() =>
2136
+ store.reconcileSourceInventory({
2137
+ sourceId: "source-1",
2138
+ presentExternalIds: ["allowed"],
2139
+ completedAt: "2026-07-16T01:00:00.000Z",
2140
+ }),
2141
+ ),
2142
+ ).resolves.toEqual({ removed: 1, restored: 0 });
2143
+ let rows = await exec.execute(
2144
+ "SELECT id, status, inventory_state FROM creative_context_items ORDER BY id",
2145
+ );
2146
+ expect(rows.rows).toEqual([
2147
+ expect.objectContaining({
2148
+ id: "allowed",
2149
+ status: "active",
2150
+ inventory_state: "available",
2151
+ }),
2152
+ expect.objectContaining({
2153
+ id: "restricted",
2154
+ status: "unavailable",
2155
+ inventory_state: "removed",
2156
+ }),
2157
+ ]);
2158
+ let source = await exec.execute(
2159
+ "SELECT item_count, restricted_item_count FROM creative_context_sources WHERE id = 'source-1'",
2160
+ );
2161
+ expect(source.rows[0]).toMatchObject({
2162
+ item_count: 1,
2163
+ restricted_item_count: 0,
2164
+ });
2165
+
2166
+ await expect(
2167
+ asAlice(() =>
2168
+ store.reconcileSourceInventory({
2169
+ sourceId: "source-1",
2170
+ presentExternalIds: ["allowed", "restricted"],
2171
+ completedAt: "2026-07-16T02:00:00.000Z",
2172
+ }),
2173
+ ),
2174
+ ).resolves.toEqual({ removed: 0, restored: 1 });
2175
+ rows = await exec.execute(
2176
+ "SELECT status, inventory_state, upstream_access, curation_status FROM creative_context_items WHERE id = 'restricted'",
2177
+ );
2178
+ expect(rows.rows[0]).toMatchObject({
2179
+ status: "active",
2180
+ inventory_state: "available",
2181
+ upstream_access: "restricted",
2182
+ curation_status: "review",
2183
+ });
2184
+ source = await exec.execute(
2185
+ "SELECT item_count, restricted_item_count FROM creative_context_sources WHERE id = 'source-1'",
2186
+ );
2187
+ expect(source.rows[0]).toMatchObject({
2188
+ item_count: 2,
2189
+ restricted_item_count: 1,
2190
+ });
2191
+ });
2192
+
2193
+ it("creates one daily maintenance job across concurrent app workers", async () => {
2194
+ const { exec, runWithRequestContext, store } = await setup();
2195
+ const create = () =>
2196
+ runWithRequestContext(
2197
+ { userEmail: "alice@example.test", orgId: "org-1" },
2198
+ () =>
2199
+ store.createDailyMaintenanceJob({
2200
+ sourceId: "source-1",
2201
+ scheduledAt: "2026-07-16T17:00:00.000Z",
2202
+ }),
2203
+ );
2204
+ const results = await Promise.all([create(), create(), create(), create()]);
2205
+ expect(results.filter((result) => result.created)).toHaveLength(1);
2206
+ expect(new Set(results.map((result) => result.job.id))).toHaveLength(1);
2207
+ const rows = await exec.execute({
2208
+ sql: "SELECT id FROM creative_context_jobs WHERE scoped_dedupe_key = ?",
2209
+ args: ["daily-maintenance:source-1:2026-07-16"],
2210
+ });
2211
+ expect(rows.rows).toHaveLength(1);
2212
+ });
2213
+
2214
+ it("deduplicates jobs inside one tenant without colliding across scopes", async () => {
2215
+ const { exec, runWithRequestContext, store } = await setup();
2216
+ const create = (userEmail: string, orgId: string | undefined) =>
2217
+ runWithRequestContext({ userEmail, orgId }, () =>
2218
+ store.createJob({
2219
+ kind: "metadata-refresh",
2220
+ dedupeKey: "same-logical-operation",
2221
+ }),
2222
+ );
2223
+
2224
+ const aliceOrgOne = await create("alice@example.test", "org-1");
2225
+ const aliceOrgOneAgain = await create("alice@example.test", "org-1");
2226
+ const bobOrgOne = await create("bob@example.test", "org-1");
2227
+ const aliceOrgTwo = await create("alice@example.test", "org-2");
2228
+ const alicePersonal = await create("alice@example.test", undefined);
2229
+
2230
+ expect(aliceOrgOneAgain.id).toBe(aliceOrgOne.id);
2231
+ expect(
2232
+ new Set([aliceOrgOne.id, bobOrgOne.id, aliceOrgTwo.id, alicePersonal.id]),
2233
+ ).toHaveLength(4);
2234
+ const rows = await exec.execute(
2235
+ `SELECT dedupe_key, dedupe_scope, scoped_dedupe_key
2236
+ FROM creative_context_jobs
2237
+ WHERE scoped_dedupe_key = 'same-logical-operation'`,
2238
+ );
2239
+ expect(rows.rows).toHaveLength(4);
2240
+ expect(new Set(rows.rows.map((row) => row.dedupe_scope))).toHaveLength(4);
2241
+ expect(rows.rows.every((row) => row.dedupe_key === null)).toBe(true);
2242
+ });
2243
+
2244
+ it("immediately tombstones sources when workspace connection access is removed", async () => {
2245
+ const { exec, runWithRequestContext, store } = await setup();
2246
+ await exec.execute(
2247
+ "UPDATE creative_context_sources SET connection_id = 'connection-1' WHERE id = 'source-1'",
2248
+ );
2249
+ const result = await runWithRequestContext(
2250
+ { userEmail: "alice@example.test", orgId: "org-1" },
2251
+ () =>
2252
+ store.handleWorkspaceConnectionLifecycle({
2253
+ type: "grant-revoked",
2254
+ connectionId: "connection-1",
2255
+ appId: "slides",
2256
+ ownerEmail: "alice@example.test",
2257
+ orgId: "org-1",
2258
+ }),
2259
+ );
2260
+ expect(result).toEqual({ sources: 1, jobs: 1 });
2261
+ const source = await exec.execute(
2262
+ "SELECT status, health_status FROM creative_context_sources WHERE id = 'source-1'",
2263
+ );
2264
+ expect(source.rows[0]).toMatchObject({
2265
+ status: "paused",
2266
+ health_status: "needs_setup",
2267
+ });
2268
+ const items = await exec.execute(
2269
+ "SELECT DISTINCT status, inventory_state FROM creative_context_items WHERE source_id = 'source-1'",
2270
+ );
2271
+ expect(items.rows).toEqual([
2272
+ expect.objectContaining({
2273
+ status: "unavailable",
2274
+ inventory_state: "removed",
2275
+ }),
2276
+ ]);
2277
+ const jobs = await exec.execute(
2278
+ "SELECT kind, source_id FROM creative_context_jobs",
2279
+ );
2280
+ expect(jobs.rows).toEqual([
2281
+ expect.objectContaining({ kind: "purge", source_id: "source-1" }),
2282
+ ]);
2283
+ });
2284
+
2285
+ it("searches an immutable pack at its pinned historical item version", async () => {
2286
+ const { exec, runWithRequestContext, store } = await setup();
2287
+ await exec.execute({
2288
+ sql: `INSERT INTO creative_context_packs
2289
+ (id, name, context_mode, request, created_at, owner_email, org_id, visibility)
2290
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
2291
+ args: [
2292
+ "pack-v1",
2293
+ "Historical launch pack",
2294
+ "manual",
2295
+ "{}",
2296
+ "2026-07-16T00:00:00.000Z",
2297
+ "alice@example.test",
2298
+ "org-1",
2299
+ "private",
2300
+ ],
2301
+ });
2302
+ await exec.execute({
2303
+ sql: `INSERT INTO creative_context_pack_members
2304
+ (id, pack_id, item_id, item_version_id, ordinal, score_metadata,
2305
+ created_at, owner_email, org_id)
2306
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2307
+ args: [
2308
+ "member-v1",
2309
+ "pack-v1",
2310
+ "allowed",
2311
+ "allowed-v1",
2312
+ 0,
2313
+ "{}",
2314
+ "2026-07-16T00:00:00.000Z",
2315
+ "alice@example.test",
2316
+ "org-1",
2317
+ ],
2318
+ });
2319
+ const result = await runWithRequestContext(
2320
+ { userEmail: "alice@example.test", orgId: "org-1" },
2321
+ () =>
2322
+ store.listAccessibleLexicalCandidates({
2323
+ query: "version 1",
2324
+ packId: "pack-v1",
2325
+ limit: 10,
2326
+ }),
2327
+ );
2328
+ expect(result.results).toEqual([
2329
+ expect.objectContaining({
2330
+ itemId: "allowed",
2331
+ itemVersionId: "allowed-v1",
2332
+ chunkId: "allowed-chunk-v1",
2333
+ }),
2334
+ ]);
2335
+ });
2336
+
2337
+ it("limits pack replay bypasses to context-owned published snapshots", async () => {
2338
+ const { exec, runWithRequestContext, store } = await setup();
2339
+ const asAlice = <T>(fn: () => Promise<T>) =>
2340
+ runWithRequestContext(
2341
+ { userEmail: "alice@example.test", orgId: "org-1" },
2342
+ fn,
2343
+ );
2344
+ const asBob = <T>(fn: () => Promise<T>) =>
2345
+ runWithRequestContext(
2346
+ { userEmail: "bob@example.test", orgId: "org-1" },
2347
+ fn,
2348
+ );
2349
+
2350
+ await exec.execute({
2351
+ sql: `INSERT INTO creative_context_packs
2352
+ (id, name, context_mode, request, created_at, owner_email, org_id, visibility)
2353
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
2354
+ args: [
2355
+ "unsafe-pack",
2356
+ "Unsafe library replay",
2357
+ "manual",
2358
+ "{}",
2359
+ "2026-07-16T00:00:00.000Z",
2360
+ "alice@example.test",
2361
+ "org-1",
2362
+ "private",
2363
+ ],
2364
+ });
2365
+ await exec.execute({
2366
+ sql: `INSERT INTO creative_context_pack_members
2367
+ (id, pack_id, item_id, item_version_id, ordinal, score_metadata,
2368
+ created_at, owner_email, org_id)
2369
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2370
+ args: [
2371
+ "unsafe-member",
2372
+ "unsafe-pack",
2373
+ "allowed",
2374
+ "allowed-v1",
2375
+ 0,
2376
+ "{}",
2377
+ "2026-07-16T00:00:00.000Z",
2378
+ "alice@example.test",
2379
+ "org-1",
2380
+ ],
2381
+ });
2382
+ await exec.execute({
2383
+ sql: `INSERT INTO creative_context_pack_shares
2384
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
2385
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
2386
+ args: [
2387
+ "unsafe-pack-share",
2388
+ "unsafe-pack",
2389
+ "user",
2390
+ "bob@example.test",
2391
+ "viewer",
2392
+ "alice@example.test",
2393
+ "2026-07-16T00:00:00.000Z",
2394
+ ],
2395
+ });
2396
+ await exec.execute(
2397
+ "UPDATE creative_context_sources SET upstream_access = 'restricted' WHERE id = 'source-1'",
2398
+ );
2399
+
2400
+ await expect(
2401
+ asBob(() => store.getCreativeContextItem("allowed", "allowed-v1")),
2402
+ ).resolves.toBeNull();
2403
+ await expect(
2404
+ asBob(() =>
2405
+ store.listAccessibleLexicalCandidates({
2406
+ query: "version 1",
2407
+ packId: "unsafe-pack",
2408
+ limit: 10,
2409
+ }),
2410
+ ),
2411
+ ).resolves.toEqual({ results: [], nextCursor: undefined });
2412
+
2413
+ for (const source of [
2414
+ ["context-staging-source", "Context staging"],
2415
+ ["context-published-source", "Context published"],
2416
+ ]) {
2417
+ await exec.execute({
2418
+ sql: `INSERT INTO creative_context_sources
2419
+ (id, name, kind, config, upstream_access, status, health_status,
2420
+ item_count, restricted_item_count, owner_email, org_id, visibility,
2421
+ created_at, updated_at)
2422
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2423
+ args: [
2424
+ source[0],
2425
+ source[1],
2426
+ "native-app",
2427
+ "{}",
2428
+ "available",
2429
+ "active",
2430
+ "healthy",
2431
+ 1,
2432
+ 0,
2433
+ "alice@example.test",
2434
+ "org-1",
2435
+ "private",
2436
+ "2026-07-16T00:00:00.000Z",
2437
+ "2026-07-16T00:00:00.000Z",
2438
+ ],
2439
+ });
2440
+ }
2441
+ await exec.execute({
2442
+ sql: `INSERT INTO creative_contexts
2443
+ (id, name, kind, staging_source_id, published_source_id, approval_policy,
2444
+ created_at, updated_at, owner_email, org_id, visibility)
2445
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2446
+ args: [
2447
+ "context-owned",
2448
+ "Governed context",
2449
+ "specialty",
2450
+ "context-staging-source",
2451
+ "context-published-source",
2452
+ "review",
2453
+ "2026-07-16T00:00:00.000Z",
2454
+ "2026-07-16T00:00:00.000Z",
2455
+ "alice@example.test",
2456
+ "org-1",
2457
+ "private",
2458
+ ],
2459
+ });
2460
+ await exec.execute({
2461
+ sql: `INSERT INTO creative_context_items
2462
+ (id, source_id, external_id, kind, title, current_version_id,
2463
+ current_content_hash, status, upstream_access, curation_status,
2464
+ curation_rank, inventory_state, index_state, tags, colors, provenance,
2465
+ metadata, created_at, updated_at, owner_email, org_id)
2466
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2467
+ args: [
2468
+ "published-item",
2469
+ "context-published-source",
2470
+ "published-artifact",
2471
+ "slide",
2472
+ "Published evidence",
2473
+ "published-version",
2474
+ "published-hash",
2475
+ "active",
2476
+ "available",
2477
+ "included",
2478
+ "normal",
2479
+ "available",
2480
+ "pending",
2481
+ "[]",
2482
+ "[]",
2483
+ "{}",
2484
+ "{}",
2485
+ "2026-07-16T00:00:00.000Z",
2486
+ "2026-07-16T00:00:00.000Z",
2487
+ "alice@example.test",
2488
+ "org-1",
2489
+ ],
2490
+ });
2491
+ await exec.execute({
2492
+ sql: `INSERT INTO creative_context_item_versions
2493
+ (id, item_id, version_number, content_hash, title, content, parse_status,
2494
+ metadata, created_at, owner_email, org_id)
2495
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2496
+ args: [
2497
+ "published-version",
2498
+ "published-item",
2499
+ 1,
2500
+ "published-hash",
2501
+ "Published evidence",
2502
+ "historical governed context evidence",
2503
+ "parsed",
2504
+ "{}",
2505
+ "2026-07-16T00:00:00.000Z",
2506
+ "alice@example.test",
2507
+ "org-1",
2508
+ ],
2509
+ });
2510
+ await exec.execute({
2511
+ sql: `INSERT INTO creative_context_chunks
2512
+ (id, item_id, item_version_id, ordinal, kind, text, metadata,
2513
+ created_at, owner_email, org_id)
2514
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2515
+ args: [
2516
+ "published-chunk",
2517
+ "published-item",
2518
+ "published-version",
2519
+ 0,
2520
+ "text",
2521
+ "historical governed context evidence",
2522
+ "{}",
2523
+ "2026-07-16T00:00:00.000Z",
2524
+ "alice@example.test",
2525
+ "org-1",
2526
+ ],
2527
+ });
2528
+ await exec.execute({
2529
+ sql: `INSERT INTO creative_context_memberships
2530
+ (id, context_id, artifact_key, published_item_id, published_item_version_id,
2531
+ rank, status, created_at, updated_at, owner_email, org_id)
2532
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2533
+ args: [
2534
+ "removed-membership",
2535
+ "context-owned",
2536
+ "published-artifact",
2537
+ "published-item",
2538
+ "published-version",
2539
+ "normal",
2540
+ "removed",
2541
+ "2026-07-16T00:00:00.000Z",
2542
+ "2026-07-16T00:00:00.000Z",
2543
+ "alice@example.test",
2544
+ "org-1",
2545
+ ],
2546
+ });
2547
+ await exec.execute({
2548
+ sql: `INSERT INTO creative_context_published_snapshots
2549
+ (id, context_id, source_id, membership_id, item_id, item_version_id,
2550
+ submission_id, created_at, owner_email, org_id)
2551
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2552
+ args: [
2553
+ "published-snapshot",
2554
+ "context-owned",
2555
+ "context-published-source",
2556
+ "removed-membership",
2557
+ "published-item",
2558
+ "published-version",
2559
+ "approved-submission",
2560
+ "2026-07-16T00:00:00.000Z",
2561
+ "alice@example.test",
2562
+ "org-1",
2563
+ ],
2564
+ });
2565
+ const historicalPack = await asAlice(() =>
2566
+ store.createContextPack({
2567
+ name: "Historical governed replay",
2568
+ baseContextId: "context-owned",
2569
+ members: [
2570
+ { itemId: "published-item", itemVersionId: "published-version" },
2571
+ ],
2572
+ }),
2573
+ );
2574
+ await exec.execute({
2575
+ sql: `INSERT INTO creative_context_pack_shares
2576
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
2577
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
2578
+ args: [
2579
+ "historical-pack-share",
2580
+ historicalPack.id,
2581
+ "user",
2582
+ "bob@example.test",
2583
+ "viewer",
2584
+ "alice@example.test",
2585
+ "2026-07-16T00:00:00.000Z",
2586
+ ],
2587
+ });
2588
+
2589
+ await expect(
2590
+ asBob(() => store.getContextPack(historicalPack.id)),
2591
+ ).resolves.toMatchObject({
2592
+ members: [
2593
+ expect.objectContaining({
2594
+ itemId: "published-item",
2595
+ itemVersionId: "published-version",
2596
+ }),
2597
+ ],
2598
+ });
2599
+ await expect(
2600
+ asBob(() =>
2601
+ store.getCreativeContextItem("published-item", "published-version"),
2602
+ ),
2603
+ ).resolves.toMatchObject({
2604
+ version: { id: "published-version" },
2605
+ });
2606
+ await expect(
2607
+ asBob(() =>
2608
+ store.listAccessibleLexicalCandidates({
2609
+ query: "historical governed",
2610
+ packId: historicalPack.id,
2611
+ limit: 10,
2612
+ }),
2613
+ ),
2614
+ ).resolves.toMatchObject({
2615
+ results: [
2616
+ expect.objectContaining({ itemVersionId: "published-version" }),
2617
+ ],
2618
+ });
2619
+ });
2620
+
2621
+ it("legal purge follows provenance into staged and context-owned copies", async () => {
2622
+ const { exec, runWithRequestContext, store } = await setup();
2623
+ const asAlice = <T>(fn: () => Promise<T>) =>
2624
+ runWithRequestContext(
2625
+ { userEmail: "alice@example.test", orgId: "org-1" },
2626
+ fn,
2627
+ );
2628
+ const context = await asAlice(() =>
2629
+ store.createCreativeContext({
2630
+ name: "Purge provenance",
2631
+ kind: "specialty",
2632
+ approvalPolicy: "open",
2633
+ }),
2634
+ );
2635
+ const submitted = await asAlice(() =>
2636
+ store.manageContextMembership({
2637
+ operation: "submit",
2638
+ contextId: context!.id,
2639
+ itemId: "allowed",
2640
+ confirmBroaderPublication: true,
2641
+ }),
2642
+ );
2643
+ const membership = (
2644
+ await asAlice(() =>
2645
+ store.listContextMemberships({ contextId: context!.id, limit: 10 }),
2646
+ )
2647
+ ).memberships[0]!;
2648
+ await asAlice(() =>
2649
+ store.createContextPack({
2650
+ name: "Published snapshot receipt",
2651
+ members: [
2652
+ {
2653
+ itemId: membership.publishedItemId!,
2654
+ itemVersionId: membership.publishedItemVersionId!,
2655
+ },
2656
+ ],
2657
+ }),
2658
+ );
2659
+
2660
+ await expect(
2661
+ asAlice(() => store.purgeContextSourceArtifacts("source-1")),
2662
+ ).resolves.toMatchObject({ sourceId: "source-1" });
2663
+
2664
+ const membershipRows = await exec.execute({
2665
+ sql: `SELECT status, published_item_id, published_item_version_id,
2666
+ pending_submission_id FROM creative_context_memberships WHERE id = ?`,
2667
+ args: [submitted.membershipId],
2668
+ });
2669
+ expect(membershipRows.rows[0]).toMatchObject({
2670
+ status: "removed",
2671
+ published_item_id: null,
2672
+ published_item_version_id: null,
2673
+ pending_submission_id: null,
2674
+ });
2675
+ for (const table of [
2676
+ "creative_context_submissions",
2677
+ "creative_context_published_snapshots",
2678
+ ]) {
2679
+ const rows = await exec.execute({
2680
+ sql: `SELECT id FROM ${table} WHERE context_id = ?`,
2681
+ args: [context!.id],
2682
+ });
2683
+ expect(rows.rows).toEqual([]);
2684
+ }
2685
+ const copiedItems = await exec.execute({
2686
+ sql: `SELECT id FROM creative_context_items
2687
+ WHERE id = ? OR source_id IN (
2688
+ SELECT staging_source_id FROM creative_contexts WHERE id = ?
2689
+ UNION
2690
+ SELECT published_source_id FROM creative_contexts WHERE id = ?
2691
+ )`,
2692
+ args: [membership.publishedItemId, context!.id, context!.id],
2693
+ });
2694
+ expect(copiedItems.rows).toEqual([]);
2695
+ });
2696
+
2697
+ it("allows explicit deprecated search without making removed items retrievable", async () => {
2698
+ const { exec, runWithRequestContext, store } = await setup();
2699
+ await exec.execute(
2700
+ "UPDATE creative_context_items SET status = 'deprecated' WHERE id = 'allowed'",
2701
+ );
2702
+ const search = (statuses: any[]) =>
2703
+ runWithRequestContext(
2704
+ { userEmail: "alice@example.test", orgId: "org-1" },
2705
+ () =>
2706
+ store.listAccessibleLexicalCandidates({
2707
+ query: "canonical version 2",
2708
+ statuses,
2709
+ limit: 10,
2710
+ }),
2711
+ );
2712
+ await expect(search(["deprecated"])).resolves.toMatchObject({
2713
+ results: [expect.objectContaining({ itemId: "allowed" })],
2714
+ });
2715
+ await expect(search(["deleted"])).rejects.toThrow(
2716
+ "cannot include removed items",
2717
+ );
2718
+ });
2719
+
2720
+ it("stops injecting published DNA immediately when its source is purged", async () => {
2721
+ const { exec, runWithRequestContext, store } = await setup();
2722
+ await exec.execute({
2723
+ sql: `INSERT INTO creative_context_brand_profiles
2724
+ (id, name, current_dna_version_id, created_at, updated_at,
2725
+ owner_email, org_id, visibility)
2726
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
2727
+ args: [
2728
+ "profile-1",
2729
+ "Brand",
2730
+ "dna-1",
2731
+ "2026-07-16T00:00:00.000Z",
2732
+ "2026-07-16T00:00:00.000Z",
2733
+ "alice@example.test",
2734
+ "org-1",
2735
+ "private",
2736
+ ],
2737
+ });
2738
+ await exec.execute({
2739
+ sql: `INSERT INTO creative_context_brand_dna_versions
2740
+ (id, profile_id, version_number, payload, content_hash, status,
2741
+ created_at, owner_email, org_id)
2742
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2743
+ args: [
2744
+ "dna-1",
2745
+ "profile-1",
2746
+ 1,
2747
+ '{"summary":"Imported brand","colors":["#112233"]}',
2748
+ "dna-hash",
2749
+ "published",
2750
+ "2026-07-16T00:00:00.000Z",
2751
+ "alice@example.test",
2752
+ "org-1",
2753
+ ],
2754
+ });
2755
+ await exec.execute({
2756
+ sql: `INSERT INTO creative_context_brand_dna_evidence
2757
+ (id, dna_version_id, item_id, item_version_id, created_at,
2758
+ owner_email, org_id)
2759
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
2760
+ args: [
2761
+ "evidence-1",
2762
+ "dna-1",
2763
+ "allowed",
2764
+ "allowed-v1",
2765
+ "2026-07-16T00:00:00.000Z",
2766
+ "alice@example.test",
2767
+ "org-1",
2768
+ ],
2769
+ });
2770
+
2771
+ const result = await runWithRequestContext(
2772
+ { userEmail: "alice@example.test", orgId: "org-1" },
2773
+ () => store.purgeContextSourceArtifacts("source-1"),
2774
+ );
2775
+
2776
+ expect(result).toMatchObject({
2777
+ invalidatedBrandProfiles: 1,
2778
+ dnaRecomputeJobs: 0,
2779
+ });
2780
+ const profile = await exec.execute(
2781
+ "SELECT current_dna_version_id FROM creative_context_brand_profiles WHERE id = 'profile-1'",
2782
+ );
2783
+ expect(profile.rows[0]?.current_dna_version_id).toBeNull();
2784
+ const jobs = await exec.execute(
2785
+ "SELECT id FROM creative_context_jobs WHERE kind = 'brand-dna'",
2786
+ );
2787
+ expect(jobs.rows).toEqual([]);
2788
+ });
2789
+
2790
+ it("publishes only the exact human-reviewed DNA proposal", async () => {
2791
+ const { runWithRequestContext, store } = await setup();
2792
+ const asAlice = <T>(fn: () => Promise<T>) =>
2793
+ runWithRequestContext(
2794
+ { userEmail: "alice@example.test", orgId: "org-1" },
2795
+ fn,
2796
+ );
2797
+ const proposed = await asAlice(() =>
2798
+ store.saveBrandDnaCandidate({
2799
+ name: "Reviewed brand",
2800
+ status: "proposed",
2801
+ dna: { summary: "Warm and direct" },
2802
+ evidenceItemIds: ["allowed"],
2803
+ }),
2804
+ );
2805
+ await expect(
2806
+ asAlice(() =>
2807
+ store.publishBrandDna({
2808
+ profileId: proposed.profile.id,
2809
+ proposalVersionId: proposed.dna.id,
2810
+ confirmation: {
2811
+ proposalVersionId: proposed.dna.id,
2812
+ contentHash: "0".repeat(64),
2813
+ },
2814
+ }),
2815
+ ),
2816
+ ).rejects.toThrow("changed after review");
2817
+ await expect(
2818
+ asAlice(() =>
2819
+ store.publishBrandDna({
2820
+ profileId: proposed.profile.id,
2821
+ proposalVersionId: proposed.dna.id,
2822
+ confirmation: {
2823
+ proposalVersionId: proposed.dna.id,
2824
+ contentHash: proposed.dna.contentHash,
2825
+ },
2826
+ }),
2827
+ ),
2828
+ ).resolves.toMatchObject({
2829
+ profile: { currentDnaVersionId: proposed.dna.id },
2830
+ dna: { id: proposed.dna.id, status: "published" },
2831
+ });
2832
+ });
2833
+
2834
+ it("executes every advertised enrichment worker idempotently", async () => {
2835
+ const { exec, runWithRequestContext, store } = await setup();
2836
+ const jobs = await import("../jobs/index.js");
2837
+ const dispatch = vi.fn(async () => {});
2838
+ const unregister =
2839
+ jobs.registerCreativeContextImportContinuationDispatcher(dispatch);
2840
+ await exec.execute(
2841
+ "UPDATE creative_context_items SET title = 'Primary brand logo', curation_rank = 'canonical' WHERE id = 'allowed'",
2842
+ );
2843
+ const asAlice = <T>(fn: () => Promise<T>) =>
2844
+ runWithRequestContext(
2845
+ { userEmail: "alice@example.test", orgId: "org-1" },
2846
+ fn,
2847
+ );
2848
+ const run = async (kind: any) => {
2849
+ const job = await asAlice(() =>
2850
+ store.createJob({
2851
+ sourceId: "source-1",
2852
+ kind,
2853
+ request: {},
2854
+ }),
2855
+ );
2856
+ return jobs.processCreativeContextBackgroundJob({
2857
+ jobId: job.id,
2858
+ ownerEmail: "alice@example.test",
2859
+ orgId: "org-1",
2860
+ appId: "slides",
2861
+ workerId: `worker:${kind}`,
2862
+ });
2863
+ };
2864
+
2865
+ await run("canonical-logo");
2866
+ await run("canonical-logo");
2867
+ await run("layout-suggestion");
2868
+ const refresh = await run("metadata-refresh");
2869
+
2870
+ const suggestions = await exec.execute(
2871
+ "SELECT kind, item_id, item_version_id FROM creative_context_suggestions ORDER BY kind",
2872
+ );
2873
+ expect(suggestions.rows).toEqual([
2874
+ expect.objectContaining({
2875
+ kind: "canonical-logo",
2876
+ item_id: "allowed",
2877
+ item_version_id: "allowed-v2",
2878
+ }),
2879
+ expect.objectContaining({
2880
+ kind: "layout-template",
2881
+ item_id: "allowed",
2882
+ item_version_id: "allowed-v2",
2883
+ }),
2884
+ ]);
2885
+ expect(refresh).toMatchObject({
2886
+ status: "completed",
2887
+ result: { importJobId: expect.any(String) },
2888
+ });
2889
+ expect(dispatch).toHaveBeenCalledTimes(1);
2890
+ const importJobs = await exec.execute(
2891
+ "SELECT id FROM creative_context_jobs WHERE kind = 'import' AND scoped_dedupe_key IS NOT NULL",
2892
+ );
2893
+ expect(importJobs.rows).toHaveLength(1);
2894
+ unregister();
2895
+ });
2896
+
2897
+ it("enforces review, source access, revocation, exact current versions, and org vectors", async () => {
2898
+ const { exec, runWithRequestContext, store } = await setup();
2899
+ const asBob = <T>(fn: () => Promise<T>) =>
2900
+ runWithRequestContext(
2901
+ { userEmail: "bob@example.test", orgId: "org-1" },
2902
+ fn,
2903
+ );
2904
+ const asAlice = <T>(fn: () => Promise<T>) =>
2905
+ runWithRequestContext(
2906
+ { userEmail: "alice@example.test", orgId: "org-1" },
2907
+ fn,
2908
+ );
2909
+
2910
+ await expect(
2911
+ asBob(() => store.listAccessibleSearchDocuments({ limit: 20 })),
2912
+ ).resolves.toEqual([]);
2913
+ await exec.execute({
2914
+ sql: `INSERT INTO creative_context_source_shares
2915
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
2916
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
2917
+ args: [
2918
+ "share-1",
2919
+ "source-1",
2920
+ "user",
2921
+ "bob@example.test",
2922
+ "viewer",
2923
+ "alice@example.test",
2924
+ "2026-07-16T00:00:00.000Z",
2925
+ ],
2926
+ });
2927
+ const initial = await asBob(() =>
2928
+ store.listAccessibleSearchDocuments({ limit: 20 }),
2929
+ );
2930
+ expect(initial.map((document) => document.itemId)).toEqual(["allowed"]);
2931
+ expect(initial[0]?.itemVersionId).toBe("allowed-v2");
2932
+ expect(initial[0]?.body).toContain("version 2");
2933
+
2934
+ const privatePack = await asAlice(() =>
2935
+ store.createContextPack({
2936
+ name: "Private generation intent",
2937
+ members: [{ itemId: "allowed", itemVersionId: "allowed-v2" }],
2938
+ }),
2939
+ );
2940
+ await expect(
2941
+ asBob(() => store.getContextPack(privatePack.id)),
2942
+ ).resolves.toBeNull();
2943
+ await exec.execute({
2944
+ sql: `INSERT INTO creative_context_pack_shares
2945
+ (id, resource_id, principal_type, principal_id, role, created_by, created_at)
2946
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
2947
+ args: [
2948
+ "pack-share-1",
2949
+ privatePack.id,
2950
+ "user",
2951
+ "bob@example.test",
2952
+ "viewer",
2953
+ "alice@example.test",
2954
+ "2026-07-16T00:00:00.000Z",
2955
+ ],
2956
+ });
2957
+ await expect(
2958
+ asBob(() => store.getContextPack(privatePack.id)),
2959
+ ).resolves.toMatchObject({
2960
+ id: privatePack.id,
2961
+ members: [
2962
+ expect.objectContaining({
2963
+ itemId: "allowed",
2964
+ itemVersionId: "allowed-v2",
2965
+ }),
2966
+ ],
2967
+ });
2968
+
2969
+ await expect(
2970
+ asAlice(() =>
2971
+ store.createContextPack({
2972
+ name: "Bypass attempt",
2973
+ members: [{ itemId: "restricted" }],
2974
+ }),
2975
+ ),
2976
+ ).rejects.toThrow(/must be accessible/);
2977
+
2978
+ await exec.execute(
2979
+ "UPDATE creative_context_items SET curation_status = 'included' WHERE id = 'restricted'",
2980
+ );
2981
+ const approved = await asBob(() =>
2982
+ store.listAccessibleSearchDocuments({ limit: 20 }),
2983
+ );
2984
+ expect(new Set(approved.map((document) => document.itemId))).toEqual(
2985
+ new Set(["allowed", "restricted"]),
2986
+ );
2987
+
2988
+ await exec.execute(
2989
+ "UPDATE creative_context_sources SET upstream_access = 'restricted' WHERE id = 'source-1'",
2990
+ );
2991
+ await expect(
2992
+ asBob(() => store.listAccessibleSearchDocuments({ limit: 20 })),
2993
+ ).resolves.toEqual([]);
2994
+ await expect(
2995
+ asBob(() => store.getCreativeContextItem("allowed")),
2996
+ ).resolves.toBeNull();
2997
+
2998
+ await exec.execute(
2999
+ "UPDATE creative_context_sources SET upstream_access = 'available' WHERE id = 'source-1'",
3000
+ );
3001
+ await exec.execute("DELETE FROM creative_context_source_shares");
3002
+ await expect(
3003
+ asBob(() => store.listAccessibleSearchDocuments({ limit: 20 })),
3004
+ ).resolves.toEqual([]);
3005
+
3006
+ await exec.execute(
3007
+ "UPDATE creative_context_sources SET visibility = 'org' WHERE id = 'source-1'",
3008
+ );
3009
+ const set = await asAlice(() =>
3010
+ store.createEmbeddingSet({
3011
+ name: "Approved winner",
3012
+ provider: "test",
3013
+ family: "test:family",
3014
+ model: "test-model",
3015
+ version: "1",
3016
+ dimensions: 3,
3017
+ }),
3018
+ );
3019
+ await asAlice(() =>
3020
+ store.recordEmbeddingMetadata({
3021
+ embeddingSetId: set.id,
3022
+ itemId: "allowed",
3023
+ itemVersionId: "allowed-v2",
3024
+ chunkId: "allowed-chunk-v2",
3025
+ targetType: "chunk",
3026
+ targetId: "allowed-chunk-v2",
3027
+ vectorKey: "vector-allowed",
3028
+ dimensions: 3,
3029
+ }),
3030
+ );
3031
+ await expect(
3032
+ asBob(() => store.getActiveEmbeddingSet()),
3033
+ ).resolves.toMatchObject({ id: set.id });
3034
+ await expect(
3035
+ asBob(() =>
3036
+ store.listEmbeddingMetadata({
3037
+ embeddingSetId: set.id,
3038
+ itemVersionIds: ["allowed-v2"],
3039
+ }),
3040
+ ),
3041
+ ).resolves.toMatchObject([{ vectorKey: "vector-allowed" }]);
3042
+ await expect(
3043
+ asBob(() =>
3044
+ store.createEmbeddingSet({
3045
+ name: "Unauthorized rotation",
3046
+ provider: "test",
3047
+ family: "other",
3048
+ model: "other",
3049
+ version: "1",
3050
+ dimensions: 2,
3051
+ }),
3052
+ ),
3053
+ ).rejects.toThrow(/owners or admins/);
3054
+ await exec.execute(
3055
+ "UPDATE creative_context_sources SET upstream_access = 'restricted' WHERE id = 'source-1'",
3056
+ );
3057
+ await expect(
3058
+ asBob(() =>
3059
+ store.listEmbeddingMetadata({
3060
+ embeddingSetId: set.id,
3061
+ itemVersionIds: ["allowed-v2"],
3062
+ }),
3063
+ ),
3064
+ ).resolves.toEqual([]);
3065
+ });
3066
+
3067
+ it("revokes newly restricted unchanged items but preserves explicit approvals across resync", async () => {
3068
+ const { exec, runWithRequestContext, store } = await setup();
3069
+ const asAlice = <T>(fn: () => Promise<T>) =>
3070
+ runWithRequestContext(
3071
+ { userEmail: "alice@example.test", orgId: "org-1" },
3072
+ fn,
3073
+ );
3074
+ await exec.execute(
3075
+ "UPDATE creative_context_items SET curation_status = 'included' WHERE id = 'restricted'",
3076
+ );
3077
+ const base = {
3078
+ externalId: "restricted",
3079
+ kind: "slide",
3080
+ title: "restricted title",
3081
+ content: "restricted canonical version 2",
3082
+ contentHash: "restricted-hash-v2",
3083
+ upstreamAccess: "restricted" as const,
3084
+ curationStatus: "review" as const,
3085
+ };
3086
+ await asAlice(() =>
3087
+ store.ingestItems({ sourceId: "source-1", items: [base] }),
3088
+ );
3089
+ let row = await exec.execute(
3090
+ "SELECT curation_status FROM creative_context_items WHERE id = 'restricted'",
3091
+ );
3092
+ expect(row.rows[0]?.curation_status).toBe("included");
3093
+
3094
+ await asAlice(() =>
3095
+ store.ingestItems({
3096
+ sourceId: "source-1",
3097
+ items: [
3098
+ {
3099
+ ...base,
3100
+ content: "restricted canonical version 3",
3101
+ contentHash: "restricted-hash-v3",
3102
+ },
3103
+ ],
3104
+ }),
3105
+ );
3106
+ row = await exec.execute(
3107
+ "SELECT curation_status FROM creative_context_items WHERE id = 'restricted'",
3108
+ );
3109
+ expect(row.rows[0]?.curation_status).toBe("included");
3110
+
3111
+ await exec.execute(
3112
+ "UPDATE creative_context_items SET upstream_access = 'available', curation_status = 'included' WHERE id = 'allowed'",
3113
+ );
3114
+ await asAlice(() =>
3115
+ store.ingestItems({
3116
+ sourceId: "source-1",
3117
+ items: [
3118
+ {
3119
+ externalId: "allowed",
3120
+ kind: "slide",
3121
+ title: "allowed title",
3122
+ content: "allowed canonical version 2",
3123
+ contentHash: "allowed-hash-v2",
3124
+ upstreamAccess: "restricted",
3125
+ curationStatus: "review",
3126
+ },
3127
+ ],
3128
+ }),
3129
+ );
3130
+ row = await exec.execute(
3131
+ "SELECT upstream_access, curation_status FROM creative_context_items WHERE id = 'allowed'",
3132
+ );
3133
+ expect(row.rows[0]).toMatchObject({
3134
+ upstream_access: "restricted",
3135
+ curation_status: "review",
3136
+ });
3137
+ });
3138
+
3139
+ it("purges dependent references and invalidates affected published DNA", async () => {
3140
+ const { exec, runWithRequestContext, store } = await setup();
3141
+ const statements: Array<{ sql: string; args: unknown[] }> = [
3142
+ {
3143
+ sql: `INSERT INTO creative_context_packs
3144
+ (id, name, context_mode, request, created_at, owner_email, org_id, visibility)
3145
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3146
+ args: [
3147
+ "pack-purge",
3148
+ "Purge pack",
3149
+ "manual",
3150
+ "{}",
3151
+ "2026-07-16T00:00:00.000Z",
3152
+ "alice@example.test",
3153
+ "org-1",
3154
+ "private",
3155
+ ],
3156
+ },
3157
+ {
3158
+ sql: `INSERT INTO creative_context_pack_members
3159
+ (id, pack_id, item_id, item_version_id, ordinal, score_metadata,
3160
+ created_at, owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3161
+ args: [
3162
+ "member-purge",
3163
+ "pack-purge",
3164
+ "allowed",
3165
+ "allowed-v2",
3166
+ 0,
3167
+ "{}",
3168
+ "2026-07-16T00:00:00.000Z",
3169
+ "alice@example.test",
3170
+ "org-1",
3171
+ ],
3172
+ },
3173
+ {
3174
+ sql: `INSERT INTO creative_context_brand_profiles
3175
+ (id, name, current_dna_version_id, created_at, updated_at,
3176
+ owner_email, org_id, visibility) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
3177
+ args: [
3178
+ "profile-purge",
3179
+ "Purge profile",
3180
+ "dna-purge",
3181
+ "2026-07-16T00:00:00.000Z",
3182
+ "2026-07-16T00:00:00.000Z",
3183
+ "alice@example.test",
3184
+ "org-1",
3185
+ "private",
3186
+ ],
3187
+ },
3188
+ {
3189
+ sql: `INSERT INTO creative_context_brand_dna_versions
3190
+ (id, profile_id, version_number, payload, content_hash, status,
3191
+ created_at, owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3192
+ args: [
3193
+ "dna-purge",
3194
+ "profile-purge",
3195
+ 1,
3196
+ "{}",
3197
+ "dna-hash",
3198
+ "published",
3199
+ "2026-07-16T00:00:00.000Z",
3200
+ "alice@example.test",
3201
+ "org-1",
3202
+ ],
3203
+ },
3204
+ {
3205
+ sql: `INSERT INTO creative_context_brand_dna_evidence
3206
+ (id, dna_version_id, item_id, item_version_id, created_at,
3207
+ owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?)`,
3208
+ args: [
3209
+ "evidence-purge",
3210
+ "dna-purge",
3211
+ "allowed",
3212
+ "allowed-v2",
3213
+ "2026-07-16T00:00:00.000Z",
3214
+ "alice@example.test",
3215
+ "org-1",
3216
+ ],
3217
+ },
3218
+ {
3219
+ sql: `INSERT INTO creative_context_edges
3220
+ (id, from_item_id, from_item_version_id, to_item_id,
3221
+ to_item_version_id, relation, metadata, created_at, owner_email, org_id)
3222
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3223
+ args: [
3224
+ "edge-to-purge",
3225
+ "outside-item",
3226
+ "outside-version",
3227
+ "allowed",
3228
+ "allowed-v2",
3229
+ "derived-from",
3230
+ "{}",
3231
+ "2026-07-16T00:00:00.000Z",
3232
+ "alice@example.test",
3233
+ "org-1",
3234
+ ],
3235
+ },
3236
+ {
3237
+ sql: `INSERT INTO creative_context_suggestions
3238
+ (id, kind, status, item_id, item_version_id, payload, created_at,
3239
+ updated_at, owner_email, org_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3240
+ args: [
3241
+ "suggestion-purge",
3242
+ "layout-template",
3243
+ "promoted",
3244
+ "allowed",
3245
+ "allowed-v2",
3246
+ '{"projectionItemId":"outside-item"}',
3247
+ "2026-07-16T00:00:00.000Z",
3248
+ "2026-07-16T00:00:00.000Z",
3249
+ "alice@example.test",
3250
+ "org-1",
3251
+ ],
3252
+ },
3253
+ {
3254
+ sql: `INSERT INTO creative_context_generation_records
3255
+ (id, app_id, artifact_type, artifact_id, context_mode,
3256
+ context_pack_id, element_provenance, created_at, owner_email, org_id)
3257
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3258
+ args: [
3259
+ "generation-purge",
3260
+ "slides",
3261
+ "deck",
3262
+ "deck-1",
3263
+ "pinned",
3264
+ "pack-purge",
3265
+ '[{"elementId":"title","itemId":"allowed","itemVersionId":"allowed-v2","influence":"adapted","label":"Brand title"}]',
3266
+ "2026-07-16T00:00:00.000Z",
3267
+ "alice@example.test",
3268
+ "org-1",
3269
+ ],
3270
+ },
3271
+ ];
3272
+ for (const statement of statements) await exec.execute(statement);
3273
+
3274
+ const result = await runWithRequestContext(
3275
+ { userEmail: "alice@example.test", orgId: "org-1" },
3276
+ () => store.purgeContextSourceArtifacts("source-1"),
3277
+ );
3278
+ expect(result).toMatchObject({
3279
+ purgedItems: 2,
3280
+ demotedLayouts: 1,
3281
+ invalidatedBrandProfiles: 1,
3282
+ dnaRecomputeJobs: 0,
3283
+ });
3284
+ for (const table of [
3285
+ "creative_context_pack_members",
3286
+ "creative_context_brand_dna_evidence",
3287
+ "creative_context_edges",
3288
+ "creative_context_suggestions",
3289
+ "creative_context_items",
3290
+ ]) {
3291
+ const rows = await exec.execute(`SELECT id FROM ${table}`);
3292
+ expect(rows.rows, table).toHaveLength(0);
3293
+ }
3294
+ const generation = await exec.execute(
3295
+ "SELECT element_provenance FROM creative_context_generation_records WHERE id = 'generation-purge'",
3296
+ );
3297
+ expect(JSON.parse(String(generation.rows[0]?.element_provenance))).toEqual([
3298
+ expect.objectContaining({
3299
+ elementId: "title",
3300
+ referenceUnavailable: true,
3301
+ }),
3302
+ ]);
3303
+ expect(String(generation.rows[0]?.element_provenance)).not.toContain(
3304
+ "allowed-v2",
3305
+ );
3306
+ const pack = await exec.execute(
3307
+ "SELECT archived_at FROM creative_context_packs WHERE id = 'pack-purge'",
3308
+ );
3309
+ expect(pack.rows[0]?.archived_at).toEqual(expect.any(String));
3310
+ const jobs = await exec.execute(
3311
+ "SELECT kind FROM creative_context_jobs WHERE kind = 'brand-dna'",
3312
+ );
3313
+ expect(jobs.rows).toEqual([]);
3314
+ const profile = await exec.execute(
3315
+ "SELECT current_dna_version_id FROM creative_context_brand_profiles WHERE id = 'profile-purge'",
3316
+ );
3317
+ expect(profile.rows[0]?.current_dna_version_id).toBeNull();
3318
+ });
3319
+
3320
+ it("scans beyond early rows and preserves exact lexical mode semantics", async () => {
3321
+ const { exec, runWithRequestContext, store } = await setup();
3322
+ await exec.execute({
3323
+ sql: `INSERT INTO creative_context_items
3324
+ (id, source_id, external_id, kind, title, current_version_id,
3325
+ current_content_hash, status, upstream_access, curation_status,
3326
+ curation_rank, inventory_state, index_state, tags, colors, provenance,
3327
+ metadata, created_at, updated_at, owner_email, org_id)
3328
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3329
+ args: [
3330
+ "tagged-late",
3331
+ "source-1",
3332
+ "tagged-late",
3333
+ "slide",
3334
+ "Tagged late",
3335
+ "tagged-late-v1",
3336
+ "tagged-late-hash",
3337
+ "active",
3338
+ "available",
3339
+ "included",
3340
+ "normal",
3341
+ "available",
3342
+ "indexed",
3343
+ '["late-tag"]',
3344
+ '["#123456"]',
3345
+ "{}",
3346
+ "{}",
3347
+ "2026-07-16T00:00:00.000Z",
3348
+ "2026-07-16T00:00:00.000Z",
3349
+ "alice@example.test",
3350
+ "org-1",
3351
+ ],
3352
+ });
3353
+ await exec.execute({
3354
+ sql: `INSERT INTO creative_context_item_versions
3355
+ (id, item_id, version_number, content_hash, title, content,
3356
+ parse_status, metadata, created_at, owner_email, org_id)
3357
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3358
+ args: [
3359
+ "tagged-late-v1",
3360
+ "tagged-late",
3361
+ 1,
3362
+ "tagged-late-hash",
3363
+ "Tagged late",
3364
+ "late filtered content",
3365
+ "parsed",
3366
+ "{}",
3367
+ "2026-07-16T00:00:00.000Z",
3368
+ "alice@example.test",
3369
+ "org-1",
3370
+ ],
3371
+ });
3372
+ const values: string[] = [];
3373
+ const args: unknown[] = [];
3374
+ const addChunk = (id: string, ordinal: number, text: string) => {
3375
+ values.push("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
3376
+ args.push(
3377
+ id,
3378
+ "allowed",
3379
+ "allowed-v2",
3380
+ ordinal,
3381
+ "text",
3382
+ text,
3383
+ "{}",
3384
+ "2026-07-16T00:00:00.000Z",
3385
+ "alice@example.test",
3386
+ "org-1",
3387
+ );
3388
+ };
3389
+ for (let index = 1; index <= 1_005; index += 1) {
3390
+ addChunk(
3391
+ `a-irrelevant-${String(index).padStart(4, "0")}`,
3392
+ index,
3393
+ "irrelevant corpus row",
3394
+ );
3395
+ }
3396
+ addChunk("x-separated", 1_006, "needle middle alpha");
3397
+ addChunk("y-decoy", 1_007, "alpha only");
3398
+ addChunk("z-later-match", 1_008, "needle alpha literal 100%_match");
3399
+ values.push("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
3400
+ args.push(
3401
+ "zz-tagged-late",
3402
+ "tagged-late",
3403
+ "tagged-late-v1",
3404
+ 0,
3405
+ "text",
3406
+ "late filtered content",
3407
+ "{}",
3408
+ "2026-07-16T00:00:00.000Z",
3409
+ "alice@example.test",
3410
+ "org-1",
3411
+ );
3412
+ await exec.execute({
3413
+ sql: `INSERT INTO creative_context_chunks
3414
+ (id, item_id, item_version_id, ordinal, kind, text, metadata,
3415
+ created_at, owner_email, org_id) VALUES ${values.join(",")}`,
3416
+ args,
3417
+ });
3418
+ const search = (query: string, matchMode: any) =>
3419
+ runWithRequestContext(
3420
+ { userEmail: "alice@example.test", orgId: "org-1" },
3421
+ () =>
3422
+ store.listAccessibleLexicalCandidates({
3423
+ query,
3424
+ matchMode,
3425
+ limit: 20,
3426
+ }),
3427
+ );
3428
+ const allTerms = await search("needle alpha", "allTerms");
3429
+ expect(allTerms.results.map((result) => result.chunkId)).toEqual(
3430
+ expect.arrayContaining(["x-separated", "z-later-match"]),
3431
+ );
3432
+ expect(allTerms.results.map((result) => result.chunkId)).not.toContain(
3433
+ "y-decoy",
3434
+ );
3435
+ const anyTerm = await search("needle alpha", "anyTerm");
3436
+ expect(anyTerm.results.map((result) => result.chunkId)).toContain(
3437
+ "y-decoy",
3438
+ );
3439
+ const phrase = await search("needle alpha", "phrase");
3440
+ expect(phrase.results.map((result) => result.chunkId)).toContain(
3441
+ "z-later-match",
3442
+ );
3443
+ expect(phrase.results.map((result) => result.chunkId)).not.toContain(
3444
+ "x-separated",
3445
+ );
3446
+ const regex = await search("needle\\s+alpha", "regex");
3447
+ expect(regex.results.map((result) => result.chunkId)).toContain(
3448
+ "z-later-match",
3449
+ );
3450
+ const escaped = await search("100%_match", "phrase");
3451
+ expect(escaped.results.map((result) => result.chunkId)).toEqual([
3452
+ "z-later-match",
3453
+ ]);
3454
+ const filtered = await runWithRequestContext(
3455
+ { userEmail: "alice@example.test", orgId: "org-1" },
3456
+ () =>
3457
+ store.listAccessibleSearchDocuments({
3458
+ tags: ["late-tag"],
3459
+ colors: ["#123456"],
3460
+ limit: 1,
3461
+ }),
3462
+ );
3463
+ expect(filtered.map((document) => document.itemId)).toEqual([
3464
+ "tagged-late",
3465
+ ]);
3466
+
3467
+ await exec.execute({
3468
+ sql: `INSERT INTO creative_context_embedding_sets
3469
+ (id, name, provider, family, model, version, dimensions, metric,
3470
+ status, metadata, created_at, owner_email, org_id)
3471
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3472
+ args: [
3473
+ "set-late",
3474
+ "Late vector set",
3475
+ "test",
3476
+ "test-family",
3477
+ "test-model",
3478
+ "v1",
3479
+ 2,
3480
+ "cosine",
3481
+ "active",
3482
+ "{}",
3483
+ "2026-07-16T00:00:00.000Z",
3484
+ "alice@example.test",
3485
+ "org-1",
3486
+ ],
3487
+ });
3488
+ await exec.execute({
3489
+ sql: `INSERT INTO creative_context_embeddings
3490
+ (id, embedding_set_id, family, model, version, item_id,
3491
+ item_version_id, target_type, target_id, vector_key, dimensions,
3492
+ created_at, owner_email, org_id)
3493
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3494
+ args: [
3495
+ "embedding-late",
3496
+ "set-late",
3497
+ "test-family",
3498
+ "test-model",
3499
+ "v1",
3500
+ "tagged-late",
3501
+ "tagged-late-v1",
3502
+ "item",
3503
+ "tagged-late-v1",
3504
+ "vector-late",
3505
+ 2,
3506
+ "2026-07-16T00:00:00.000Z",
3507
+ "alice@example.test",
3508
+ "org-1",
3509
+ ],
3510
+ });
3511
+ const vectorCandidates = await runWithRequestContext(
3512
+ { userEmail: "alice@example.test", orgId: "org-1" },
3513
+ () =>
3514
+ store.listEmbeddingMetadata({
3515
+ embeddingSetId: "set-late",
3516
+ tags: ["late-tag"],
3517
+ }),
3518
+ );
3519
+ expect(vectorCandidates.map((candidate) => candidate.vectorKey)).toEqual([
3520
+ "vector-late",
3521
+ ]);
3522
+ });
3523
+ });