create-ailk 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +12248 -0
  3. package/component-catalog.md +193 -68
  4. package/dist/cli.js +29 -1
  5. package/dist/derive-architecture.js +5 -2
  6. package/dist/lib/api-boot-assertion.d.ts +46 -0
  7. package/dist/lib/api-boot-assertion.js +70 -0
  8. package/dist/lib/extract-module-bundle.d.ts +50 -2
  9. package/dist/lib/extract-module-bundle.js +223 -15
  10. package/dist/lib/fetch-guard.d.ts +28 -0
  11. package/dist/lib/fetch-guard.js +101 -0
  12. package/dist/lib/fetch-module.js +4 -0
  13. package/dist/lib/fetch-template.js +4 -0
  14. package/dist/lib/import-graph.d.ts +29 -0
  15. package/dist/lib/import-graph.js +140 -0
  16. package/dist/module-architecture.d.ts +60 -8
  17. package/dist/module-architecture.js +587 -242
  18. package/dist/parse-args.js +6 -2
  19. package/dist/programmatic.d.ts +10 -0
  20. package/dist/programmatic.js +134 -19
  21. package/dist/site-chrome.d.ts +80 -0
  22. package/dist/site-chrome.js +135 -0
  23. package/dist/surfaces.d.ts +21 -6
  24. package/dist/surfaces.js +209 -36
  25. package/dist/template-manifest.d.ts +88 -3
  26. package/dist/template-manifest.js +281 -29
  27. package/dist/template-names.d.ts +1 -1
  28. package/dist/template-names.js +2 -0
  29. package/package.json +10 -2
  30. package/site-templates/_base/ailk-template.json +6 -0
  31. package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
  32. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +351 -0
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +400 -0
  34. package/site-templates/marketing-site/ailk-template.json +11 -0
  35. package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
  36. package/site-templates/marketing-site/provenance.json +4 -0
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
  38. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +286 -0
  39. package/site-templates/marketing-site-free/ailk-template.json +2 -1
  40. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +279 -0
  41. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +336 -0
  42. package/site-templates/minimalism-site/ailk-template.json +22 -0
  43. package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
  44. package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
  45. package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
  46. package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
  47. package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
  48. package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
  49. package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
  50. package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
  51. package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
  52. package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
  53. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  54. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
  55. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
  56. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
  57. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +244 -0
  58. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
  59. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
  60. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
  61. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
  62. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
  63. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
  64. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
  65. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
  66. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
  67. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
  68. package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
  69. package/site-templates/minimalism-site/provenance.json +4 -0
  70. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
  71. package/site-templates/saas-product/ailk-template.json +16 -0
  72. package/site-templates/saas-product/provenance.json +10 -0
  73. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  74. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  75. package/site-templates/simple-service/ailk-template.json +10 -0
  76. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  77. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  78. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  79. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  80. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  81. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  82. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  83. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  84. package/site-templates/simple-service/provenance.json +4 -0
  85. package/templates/.claude/skills/README.md +16 -25
  86. package/templates/.env.example +102 -12
  87. package/templates/.github/workflows/ci.yml +48 -0
  88. package/templates/README.md +3 -1
  89. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  90. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  91. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  92. package/templates/apps/api/.env.example +13 -0
  93. package/templates/apps/api/CLAUDE.md +29 -2
  94. package/templates/apps/api/eslint.config.mjs +69 -0
  95. package/templates/apps/api/jest.config.cjs +3 -0
  96. package/templates/apps/api/package.json +7 -2
  97. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  98. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +89 -9
  99. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
  100. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  101. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  102. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +10 -1
  103. package/templates/apps/api/src/config/index.ts +62 -0
  104. package/templates/apps/api/src/config/modules.ts +7 -3
  105. package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -2
  106. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  107. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  108. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  109. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  110. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  111. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  112. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  113. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  114. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  115. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  116. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  117. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  118. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  119. package/templates/apps/api/src/lib/site.ts +208 -9
  120. package/templates/apps/api/src/lib/stripe.ts +66 -0
  121. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +521 -0
  122. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +150 -0
  123. package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
  124. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  125. package/templates/apps/api/src/openapi/spec.ts +543 -2
  126. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  127. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  128. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  129. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  130. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  131. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  132. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  133. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  134. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  135. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  136. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  137. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  138. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  139. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  140. package/templates/apps/api/src/routes/content/index.ts +13 -73
  141. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  142. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  143. package/templates/apps/api/src/routes/content/search.ts +129 -0
  144. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  145. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
  146. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  147. package/templates/apps/api/src/routes/leads/list.ts +104 -0
  148. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  149. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  150. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  151. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  152. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  153. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  154. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  155. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  156. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  157. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  158. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  159. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  160. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  161. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  162. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  163. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  164. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  165. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  166. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  167. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  168. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  169. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  170. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  171. package/templates/apps/api/src/server.ts +521 -55
  172. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  173. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  174. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  175. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  176. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  177. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  178. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  179. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
  180. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  181. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
  182. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  183. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  184. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  185. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  186. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  187. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  188. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  189. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  190. package/templates/apps/api/src/services/audit-log.ts +668 -0
  191. package/templates/apps/api/src/services/consent.ts +18 -0
  192. package/templates/apps/api/src/services/default-tenant.ts +300 -0
  193. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  194. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  195. package/templates/apps/api/src/services/entitlements.ts +80 -0
  196. package/templates/apps/api/src/services/lead-promotion.ts +160 -0
  197. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  198. package/templates/apps/api/src/services/leads-inbox.ts +284 -0
  199. package/templates/apps/api/src/services/listing-config.ts +27 -9
  200. package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
  201. package/templates/apps/api/src/services/project-listings.ts +201 -6
  202. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  203. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  204. package/templates/apps/api/src/services/revalidation.ts +173 -0
  205. package/templates/apps/api/src/services/verified-email.ts +71 -0
  206. package/templates/apps/api/src/vercel-handler.ts +75 -35
  207. package/templates/apps/mcp/.env.example +17 -0
  208. package/templates/apps/mcp/CLAUDE.md +58 -25
  209. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  210. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  211. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  212. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  213. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  214. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  215. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  216. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  217. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  218. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  219. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  220. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  221. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  222. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  223. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  224. package/templates/apps/mcp/__tests__/parity.test.ts +12 -9
  225. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  226. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  227. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  228. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  229. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  230. package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
  231. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  232. package/templates/apps/mcp/eslint.config.mjs +69 -0
  233. package/templates/apps/mcp/jest.config.cjs +13 -0
  234. package/templates/apps/mcp/package.json +8 -2
  235. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  236. package/templates/apps/mcp/src/config/modules.ts +10 -5
  237. package/templates/apps/mcp/src/http-transport.ts +102 -29
  238. package/templates/apps/mcp/src/index.ts +73 -11
  239. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  240. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  241. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  242. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  243. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  244. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  245. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  246. package/templates/apps/mcp/src/server.ts +61 -37
  247. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  248. package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
  249. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  250. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  251. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  252. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  253. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  254. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  255. package/templates/apps/mcp/src/tools/index.ts +9 -7
  256. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  257. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  258. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  259. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  260. package/templates/apps/web/.env.example +74 -8
  261. package/templates/apps/web/CLAUDE.md +46 -0
  262. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  263. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  264. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  265. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  266. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  267. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  268. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  269. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  270. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  271. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  272. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  273. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  274. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  275. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  276. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  277. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  278. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  279. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  280. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  281. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +67 -14
  282. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -2
  283. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
  284. package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
  285. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
  286. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
  287. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
  288. package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
  289. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  290. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
  291. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
  292. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
  293. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
  294. package/templates/apps/web/app/[locale]/layout.tsx +308 -65
  295. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  296. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  297. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  298. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  299. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  300. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  301. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  302. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  303. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  304. package/templates/apps/web/auth.ts +407 -8
  305. package/templates/apps/web/components/google-analytics.tsx +18 -1
  306. package/templates/apps/web/eslint.config.mjs +69 -0
  307. package/templates/apps/web/jest.config.cjs +15 -19
  308. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  309. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  310. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  311. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +50 -6
  312. package/templates/apps/web/lib/auth-email.ts +318 -0
  313. package/templates/apps/web/lib/auth-env.ts +85 -0
  314. package/templates/apps/web/lib/auth-routes.ts +55 -0
  315. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  316. package/templates/apps/web/lib/nav-config.ts +27 -7
  317. package/templates/apps/web/lib/server-api.ts +1 -1
  318. package/templates/apps/web/lib/site-chrome.ts +29 -0
  319. package/templates/apps/web/lib/telemetry-scrub.ts +50 -0
  320. package/templates/apps/web/messages/en.json +216 -2
  321. package/templates/apps/web/next.config.mjs +20 -0
  322. package/templates/apps/web/package.json +8 -3
  323. package/templates/apps/web/sentry.client.config.ts +131 -0
  324. package/templates/apps/web/sentry.edge.config.ts +72 -0
  325. package/templates/apps/web/sentry.server.config.ts +72 -0
  326. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  327. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  328. package/templates/apps/web/src/lib/routes.ts +13 -18
  329. package/templates/brand_assets/README.md +24 -0
  330. package/templates/content/en/AboutPage/about.mdx +160 -15
  331. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  332. package/templates/content/en/ContactPage/contact.mdx +90 -13
  333. package/templates/content/en/FAQPage/faq.mdx +62 -22
  334. package/templates/content/en/HomePage/home.mdx +244 -20
  335. package/templates/content/en/LandingPage/blog.mdx +83 -0
  336. package/templates/content/en/LandingPage/features.mdx +103 -0
  337. package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
  338. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  339. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  340. package/templates/database/CHANGELOG.md +149 -0
  341. package/templates/database/README.md +10 -3
  342. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  343. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  344. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  345. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  346. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  347. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  348. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  349. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  350. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  351. package/templates/database/__tests__/seed.test.ts +1 -1
  352. package/templates/database/__tests__/tenancy.test.ts +141 -0
  353. package/templates/database/config/schema.prisma +79 -2
  354. package/templates/database/content/schema.prisma +87 -11
  355. package/templates/database/eslint.config.mjs +69 -0
  356. package/templates/database/inbox/schema.prisma +452 -9
  357. package/templates/database/jest.config.cjs +5 -3
  358. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  359. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  360. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  361. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  362. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  363. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  364. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  365. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  366. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  367. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  368. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  369. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  370. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  371. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  372. package/templates/database/package.json +7 -3
  373. package/templates/database/seed.ts +1 -1
  374. package/templates/database/tenancy.ts +86 -1
  375. package/templates/database/tsconfig.json +1 -0
  376. package/templates/package.json +1 -14
  377. package/versions.json +52 -0
  378. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -103
  379. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  380. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  381. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  382. package/templates/apps/api/src/routes/content/create.ts +0 -115
  383. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  384. package/templates/apps/api/src/routes/content/update.ts +0 -112
  385. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  386. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  387. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  388. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  389. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  390. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  391. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  392. /package/site-templates/{marketing-site-free → _base}/content/en/PrivacyPage/privacy.mdx +0 -0
  393. /package/site-templates/{marketing-site-free → _base}/content/en/TermsPage/terms.mdx +0 -0
  394. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  395. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
  396. /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
@@ -0,0 +1,143 @@
1
+ ---
2
+ type: "LandingPage"
3
+ slug: "how-it-works"
4
+ title: "Get set up in {number} easy steps"
5
+ description: "Summarize the setup process briefly in a way that makes it seem approachable and quick."
6
+ sections:
7
+ [
8
+ {
9
+ "id": "hero",
10
+ "type": "hero",
11
+ "data":
12
+ {
13
+ "eyebrow": "How it Works",
14
+ "headline": "Get set up in {number} easy steps",
15
+ "subheadline": "Summarize the setup process briefly in a way that makes it seem approachable and quick.",
16
+ "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
17
+ "secondaryCta":
18
+ {
19
+ "label": "{Secondary CTA}",
20
+ "href": "/pricing",
21
+ "variant": "outline",
22
+ },
23
+ },
24
+ },
25
+ {
26
+ "id": "steps",
27
+ "type": "feature",
28
+ "data":
29
+ {
30
+ "variant": "alternating",
31
+ "numbered": true,
32
+ "headline": "",
33
+ "features":
34
+ [
35
+ {
36
+ "title": "The first thing they need to do to get the outcome they want",
37
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
38
+ },
39
+ {
40
+ "title": "The second thing they need to do to get the outcome they want",
41
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
42
+ },
43
+ {
44
+ "title": "The third thing they need to do to get the outcome they want",
45
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
46
+ },
47
+ ],
48
+ },
49
+ },
50
+ {
51
+ "id": "logo-cloud",
52
+ "type": "logoCloud",
53
+ "data":
54
+ {
55
+ "headline": "Don’t just take our word for it",
56
+ "logos":
57
+ [
58
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
59
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
60
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
61
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
62
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
63
+ { "name": "Placeholder logo", "src": "/logomark.svg" },
64
+ ],
65
+ "layout": "inline",
66
+ "treatment": "monochrome",
67
+ },
68
+ },
69
+ {
70
+ "id": "testimonial",
71
+ "type": "testimonials",
72
+ "data":
73
+ {
74
+ "variant": "featured",
75
+ "items":
76
+ [
77
+ {
78
+ "quote": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
79
+ "author": "John Smith",
80
+ "role": "CEO, Google",
81
+ },
82
+ ],
83
+ },
84
+ },
85
+ {
86
+ "id": "switch",
87
+ "type": "cta",
88
+ "data":
89
+ {
90
+ "variant": "box",
91
+ "headline": "Switch to {your product} in {time amount} or less",
92
+ "description": "You shouldn't have to {experience a painful problem}. With {your product}, you can finally {achieve a value outcome or get a big improvement}.",
93
+ "chips":
94
+ ["No credit card required", "Cancel any time", "Live chat support"],
95
+ "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
96
+ "secondaryCta":
97
+ {
98
+ "label": "{Secondary CTA}",
99
+ "href": "/pricing",
100
+ "variant": "outline",
101
+ },
102
+ "closingLine": "{Social Proof goes here}",
103
+ },
104
+ },
105
+ {
106
+ "id": "faq",
107
+ "type": "faq",
108
+ "data":
109
+ {
110
+ "headline": "FAQs",
111
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique.",
112
+ "supportLink": { "label": "Contact", "href": "/contact" },
113
+ "presentation": "accordion",
114
+ "initialOpen": "first",
115
+ "items":
116
+ [
117
+ {
118
+ "question": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa?",
119
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
120
+ },
121
+ {
122
+ "question": "Dolorem consectetur quia maxime sunt ut sint. Fuga optio dolorem animi voluptatem laudantium error. Quis accusamus deleniti rer?",
123
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
124
+ },
125
+ {
126
+ "question": "Totam nihil esse. Itaque optio temporibus adipisci. Rerum nisi aperiam aliquid doloremque aliquam?",
127
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
128
+ },
129
+ {
130
+ "question": "Delectus minus officiis et. Perspiciatis illum nisi vitae. Sed consequuntur animi corrupti distinctio ipsam accusamus?",
131
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
132
+ },
133
+ {
134
+ "question": "Aperiam perferendis voluptatem est sit enim sed eaque perspiciatis enim. Est explicabo ea explicabo et et dignissimos aliquid?",
135
+ "answer": "Vel odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa odio odit voluptatum ut veritatis. Ex rerum aspernatur voluptas unde officia. Dolorem officiis laboriosa.",
136
+ },
137
+ ],
138
+ },
139
+ },
140
+ ]
141
+ ---
142
+
143
+ {/* Rendered from frontmatter `sections[]` by the generic `[slug]` route (`app/[locale]/[slug]/page.tsx`). It emits the LandingPage builder's `WebPage` JSON-LD (the scaffold's structured-data gate accepts nothing else for a LandingPage). Placeholder copy, verbatim from the reference — replace before you launch. */}
@@ -0,0 +1,108 @@
1
+ ---
2
+ type: "ReturnsPolicyPage"
3
+ slug: "returns"
4
+ title: "Returns & Refunds"
5
+ description: "How to return an order from this site, how long you have, and how refunds are issued."
6
+ updatedAt: "2026-09-17"
7
+ sections:
8
+ [
9
+ {
10
+ "id": "returns-document",
11
+ "type": "document-chassis",
12
+ "data":
13
+ {
14
+ "headline": "Returns & Refunds",
15
+ "backHref": "/",
16
+ "backLabel": "Back home",
17
+ "navGroupLabel": "Contents",
18
+ "stats":
19
+ [
20
+ { "label": "Last updated", "sublabel": "17 September 2026" },
21
+ { "label": "Applies to", "sublabel": "Orders placed on this site" },
22
+ ],
23
+ "sections":
24
+ [
25
+ {
26
+ "id": "intro",
27
+ "heading": "Returns & Refunds",
28
+ "blocks":
29
+ [
30
+ {
31
+ "kind": "paragraph",
32
+ "text": "This page explains how returns and refunds work on this site. Replace the starter copy below with the policy you actually operate — the headings are the ones a customer and an agent both look for, so keep them and fill in your own terms.",
33
+ },
34
+ ],
35
+ },
36
+ {
37
+ "id": "return-window",
38
+ "heading": "How long you have",
39
+ "blocks":
40
+ [
41
+ {
42
+ "kind": "paragraph",
43
+ "text": "State the return window in days, counted from a date a customer can verify — delivery, not dispatch. If different products carry different windows, say which, and say plainly which items cannot be returned at all.",
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ "id": "condition",
49
+ "heading": "What condition an item must be in",
50
+ "blocks":
51
+ [
52
+ {
53
+ "kind": "paragraph",
54
+ "text": "Describe the condition an item must come back in — unused, original packaging, tags attached, or whatever your standard is. Name anything that voids a return, such as a removed seal on a digital product or a personalised item.",
55
+ },
56
+ ],
57
+ },
58
+ {
59
+ "id": "how-to-return",
60
+ "heading": "How to start a return",
61
+ "blocks":
62
+ [
63
+ {
64
+ "kind": "paragraph",
65
+ "text": "Give the customer one route to start a return and the information they need to hand: an order number, the item, and the reason. If you issue a return label, say who pays for it; if you do not, say that too.",
66
+ },
67
+ ],
68
+ },
69
+ {
70
+ "id": "refunds",
71
+ "heading": "How refunds are issued",
72
+ "blocks":
73
+ [
74
+ {
75
+ "kind": "paragraph",
76
+ "text": "Say what a customer gets back — the item price, the original shipping, or both — and how long the refund takes to reach the payment method it came from. If you offer store credit or an exchange instead, describe when each applies.",
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ "id": "exchanges",
82
+ "heading": "Exchanges and faulty items",
83
+ "blocks":
84
+ [
85
+ {
86
+ "kind": "paragraph",
87
+ "text": "A faulty or incorrectly shipped item is usually not an ordinary return. Set out how those are handled, and say whether the return window above applies to them.",
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ "id": "contact",
93
+ "heading": "Questions about a return",
94
+ "blocks":
95
+ [
96
+ {
97
+ "kind": "paragraph",
98
+ "text": "Point to the one place a customer should write, and say roughly how quickly you answer. A returns policy nobody can ask a question about is a policy that generates disputes.",
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ },
104
+ },
105
+ ]
106
+ ---
107
+
108
+ {/* Rendered from frontmatter `sections[]`; the MDX body is not read by the section-catalog renderer. The JSON-LD for this page — a WebPage wrapping a MerchantReturnPolicy — is emitted by `buildReturnsPolicyPage` (#5947), not hand-rolled here. */}
@@ -0,0 +1,108 @@
1
+ ---
2
+ type: "ShippingPolicyPage"
3
+ slug: "shipping"
4
+ title: "Shipping Policy"
5
+ description: "Where orders from this site ship, how long delivery takes, what it costs, and what happens when a parcel goes missing."
6
+ updatedAt: "2026-09-17"
7
+ sections:
8
+ [
9
+ {
10
+ "id": "shipping-document",
11
+ "type": "document-chassis",
12
+ "data":
13
+ {
14
+ "headline": "Shipping Policy",
15
+ "backHref": "/",
16
+ "backLabel": "Back home",
17
+ "navGroupLabel": "Contents",
18
+ "stats":
19
+ [
20
+ { "label": "Last updated", "sublabel": "17 September 2026" },
21
+ { "label": "Applies to", "sublabel": "Orders shipped from this site" },
22
+ ],
23
+ "sections":
24
+ [
25
+ {
26
+ "id": "intro",
27
+ "heading": "Shipping Policy",
28
+ "blocks":
29
+ [
30
+ {
31
+ "kind": "paragraph",
32
+ "text": "This page explains how orders from this site are shipped. Replace the starter copy below with the policy you actually operate — the headings are the ones a customer and an agent both look for, so keep them and fill in your own terms.",
33
+ },
34
+ ],
35
+ },
36
+ {
37
+ "id": "destinations",
38
+ "heading": "Where we ship",
39
+ "blocks":
40
+ [
41
+ {
42
+ "kind": "paragraph",
43
+ "text": "List the countries or regions you ship to, and say plainly where you do not. If some products are restricted to a subset of those destinations, say which.",
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ "id": "processing-and-transit",
49
+ "heading": "How long it takes",
50
+ "blocks":
51
+ [
52
+ {
53
+ "kind": "paragraph",
54
+ "text": "Give processing time and transit time as two separate figures, in business days, per shipping option. A single blended estimate is the one thing customers most often read as a promise you did not make.",
55
+ },
56
+ ],
57
+ },
58
+ {
59
+ "id": "costs",
60
+ "heading": "What shipping costs",
61
+ "blocks":
62
+ [
63
+ {
64
+ "kind": "paragraph",
65
+ "text": "Set out the cost of each shipping option and any threshold above which it is free. Say whether the figure shown at checkout is final, and whether duties and import taxes are included or collected on delivery.",
66
+ },
67
+ ],
68
+ },
69
+ {
70
+ "id": "tracking",
71
+ "heading": "Tracking your order",
72
+ "blocks":
73
+ [
74
+ {
75
+ "kind": "paragraph",
76
+ "text": "Say when tracking information is sent, where it comes from, and what a customer should do if it has not updated for a while.",
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ "id": "lost-or-damaged",
82
+ "heading": "Lost or damaged parcels",
83
+ "blocks":
84
+ [
85
+ {
86
+ "kind": "paragraph",
87
+ "text": "Describe how long a parcel must be missing before you treat it as lost, what you do then, and what you need from the customer — photographs for damage, a written report for a loss. Returns of damaged goods are the returns policy's business; link to it rather than restating it here.",
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ "id": "contact",
93
+ "heading": "Questions about a delivery",
94
+ "blocks":
95
+ [
96
+ {
97
+ "kind": "paragraph",
98
+ "text": "Point to the one place a customer should write, and say roughly how quickly you answer.",
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ },
104
+ },
105
+ ]
106
+ ---
107
+
108
+ {/* Rendered from frontmatter `sections[]`; the MDX body is not read by the section-catalog renderer. `OfferShippingDetails` is offer-scoped, not page-scoped, so this page emits a plain WebPage (#5947, D3) — real rates belong on an Offer, or reach this page's JSON-LD through the `schema:` escape hatch. */}
@@ -1,5 +1,154 @@
1
1
  # @working-theory/database
2
2
 
3
+ ## 0.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#5813](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/5813) [`518c590`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/518c59023876a0212504ace9a683084df0168be6) Thanks [@claudio-planck](https://github.com/claudio-planck)! - Generalize `Artifact` into the workspace's content store, and add `Asset` ([#5800](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/5800), roadmap D1/D7).
8
+
9
+ `artifacts.session_id` was `NOT NULL` behind an `ON DELETE CASCADE`, so deleting an agent session deleted every artifact it had produced. It is now nullable behind `ON DELETE SET NULL`: the session is provenance, the workspace owns the artifact. Beside it, four columns the content store needs — `content_type` (the discriminator; `text/page` is what the editor writes), `locale` (defaulting to ADR 0019's `product.default_locale`), `slug` (NULL until publish) and `published_version_id`. `(workspace_id, content_type, locale, slug)` is unique and `(workspace_id, content_type, updated_at)` is the list index. `published_version_id` is a plain pointer, never a relation and never `is_current`, which remains the draft tip and is never the publish mechanism.
10
+
11
+ `content_type` is ADDITIVE beside the existing `type` column rather than a rename: `apps/api/src/routes/workspaces/sessions.ts` reads `type` and keeps working unchanged. The migration backfills `content_type` from `type`, so the two agree row-for-row; new code reads `content_type`.
12
+
13
+ `Asset { workspaceId, key, filename, contentType, bytes, size }` follows the inbox namespace's `DeliverableAsset` precedent — bytes in the row, no new infrastructure — with `(workspace_id, key)` unique and a relation-free `workspaceId` (ADR 0009 invariant 1). `@working-theory/ids` registers its `asset_` prefix (ADR 0031 rule 3) and exports `zAssetId`.
14
+
15
+ The migration brackets the FK relaxation with zero-row assertions — no row already NULL and no orphan before the swap; no row lost, no row nulled and no `content_type`/`type` drift after it — so it applies to an empty database and to a populated one, and aborts rather than backfilling past a surprise.
16
+
17
+ - [#6139](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6139) [`9f45297`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/9f4529701aaf2f56acbd2be416e8a8c17358e1aa) Thanks [@samuelhenry](https://github.com/samuelhenry)! - Appointments from Cal.com webhooks ([#6085](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6085), brief H7).
18
+
19
+ Adds `POST /v1/webhooks/calcom` — a signed receiver (HMAC-SHA256 over the raw
20
+ body, `x-cal-signature-256`) that normalizes six `BOOKING_*` trigger events
21
+ into a new `Appointment` row, guarded by three independent per-family
22
+ watermarks (lifecycle / payment / no-show) and a 15-minute replay window —
23
+ and `GET /v1/schedule/appointments`, a tenant-guarded, `owner|admin`-floored
24
+ read of the site's own appointments, scoped strictly to
25
+ `tenant.workspaceId` (never `currentSiteId()`, never a platform-operator
26
+ escape hatch).
27
+
28
+ `@working-theory/database` gains the `Appointment` model
29
+ (`database/inbox/schema.prisma`) and its migration.
30
+ `@working-theory/ids` registers the `appointment` → `appt` prefix (ADR-0031
31
+ rule 3). `@working-theory/validation` gains `AppointmentSchema`,
32
+ `AppointmentListSchema`, `listAppointmentsInputSchema` /
33
+ `listAppointmentsOutputSchema`, and the inbound Cal.com webhook envelope
34
+ schemas. `@working-theory/api-client` gains `schedule.appointments()`.
35
+ `create-ailk` adds the receiver + read + service files (and their tests) to
36
+ the `scheduling` module's `ownedFiles` — a keyless scaffold is unaffected;
37
+ a `--modules scheduling` scaffold now carries the new surface.
38
+
39
+ - [#6118](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6118) [`8b20866`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/8b20866470f5aa3ef9fbbba1d11e4fc3b998da86) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `Asset` gains three columns the lightbox edits in place ([#6092](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6092), S4/D10): `title String?`, `description String?`, `tags String[] @default([])`.
40
+
41
+ `title` and `description` are nullable with no default — `NULL` means the owner has not written one, distinct from an empty string meaning they cleared it, and neither is backfilled from `filename`. `tags` is non-null with an empty-array default: an untagged asset is an empty set, not an unknown one. The column stores tag SLUGS (`ad-creative`, `workspace-memory`, …), never the human labels a surface renders them as, and is deliberately unconstrained — the asset vocabulary and the document vocabulary (a document is a row in this same store) ride the one column, and a fixed set is a UI affordance, not a database constraint.
42
+
43
+ Delivered by one additive migration, `20260918120000_asset_metadata_columns` — no column dropped or renamed, no value backfilled, the `(workspace_id, key)` identity and the `asset_` UUIDv7 CHECK untouched. An `INSERT` naming none of the three columns still succeeds.
44
+
45
+ - [#6202](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6202) [`b85e147`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/b85e147d0d26150eb1a00e1933df138087727628) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `FlowDefinition` and `FlowSession` gain a nullable `workspaceId` column, backfilled 1:1 from `siteId` ([#6155](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6155)).
46
+
47
+ The literal alias `"default"` resolves to the seeded sentinel workspace; every other alias resolves only by exact `workspaces.id` equality. A row whose `siteId` matches no workspace is left `NULL` — unset, never guessed — and stays invisible to every tenant-scoped read rather than visible to the wrong one. `siteId` is unchanged and the public flow engine keeps resolving it; nothing about the request or response shape of `/v1/flows/*` or either MCP flow tool changes.
48
+
49
+ `database/tenancy.ts` also gains `resolveSentinelWorkspaceRow(prisma)` — the sentinel-existence sibling of `resolveDefaultWorkspaceRow`, carrying no `workspace.count()` gate, so a correctly-configured multi-workspace deployment that has not pinned `AILK_SITE_ID` still resolves the `"default"` alias.
50
+
51
+ - [#6218](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6218) [`80f498e`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/80f498e3dcd3d70931045dc376c293c0c9a88c65) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `FlowDefinition` gains four operator-facing columns — `kind`, `title`, `landingPage`, `status` ([#6156](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6156)) — backfilled for every existing row by a structural rule (never from slug), with the visitor-facing `config` left byte-identical.
52
+
53
+ `status` (`draft` | `live`) carries NO column default and is backfilled `'live'` — every row that exists today is already serving, and a silent default would make publication the thing that happens when a future writer says nothing. `title` is likewise `NOT NULL` with no default, backfilled from `slug`. `kind` (`special-report` | `diagnostic` | `listicle` | `waitlist` | `contact` | `newsletter`) keeps its column default (`'contact'`) and is reclassified only where the row's own structure (a projected listing, a recommending-mode config, a `deliverables` block) says otherwise. `landingPage` is nullable with no backfill — a magnet has three public addresses, and NULL means the canonical route serves it.
54
+
55
+ `@working-theory/validation`'s `flow.ts` gains `flowKindSchema` / `flowStatusSchema` / `flowLandingPageSchema` as standalone exports — never members of `flowConfigShapeSchema`, which stays `.strict()` and gains none of the four keys, so every stored `configSnapshot` keeps parsing unchanged.
56
+
57
+ A `status` of `draft` refuses a NEW walk from starting (`resolveSession`'s definition branch) while a walk already in progress finishes on its pinned snapshot — the `sessionId` branch never re-reads the definition. `upsertFlowDefinition` supplies `kind`/`title`/`status` on CREATE only, never on UPDATE, so a re-seed or re-materialize can never overwrite what an operator later sets. The public routes and both MCP flow tools are unchanged.
58
+
59
+ - [#6274](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6274) [`3bd8785`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/3bd87854c6589556426894720c85f54bf2daa801) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `ProjectListing` and `Lead` gain a nullable `workspaceId` column (`workspace_id`), indexed for a per-workspace list and backfilled from `siteId` for any existing row whose alias resolves to a real workspace ([#6203](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6203)). `tenantDb(tenant)` gains `projectListings` and `leads` read accessors, scoped by the injected `workspaceId` and never returning a row from another workspace. `siteId` stays the legacy alias, unchanged, on both models.
60
+
61
+ - [#6280](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6280) [`2e7b0d2`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/2e7b0d2411944002104d5105edb6e8d59c8434c5) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `Lead` gains a nullable triage stamp — `handledAt` (`handled_at`) and `handledBy` (`handled_by`), the actor — plus a `(workspaceId, handledAt)` index for the Needs-you read ([#6204](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6204)). Existing rows are unaffected by the purely-additive migration and read as unhandled. `@working-theory/validation` gains `MESSAGE_LEAD_SOURCES` / `MESSAGE_LEAD_SOURCE_LIST` and `isMessageSource()` — the one closed allowlist declaring which `Lead.source` values are messages (a lead that needs an answer, operator decision Q1(a)), never a denylist and never a pattern test. `tenantDb(tenant).leads` gains `listUnhandledMessages` (bounded, workspace-scoped) and `markHandled` (an idempotent compare-and-swap), and `leads-inbox.ts` exposes the role-floored service pair over them. No route and no MCP tool — nothing new is reachable from this unit.
62
+
63
+ - [#6360](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6360) [`2809d30`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/2809d307d4d130541cb29ea7176b4af17e58679c) Thanks [@claudio-planck](https://github.com/claudio-planck)! - `Lead`, `ProjectListing`, `FlowDefinition`, `FlowSession` gain NOT NULL `workspaceId` plus a `CHECK (workspace_id = site_id)` equality constraint, and `WorkspaceSiteIdentity` gains `@@unique([id, workspaceId])` ([#6254](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6254), the tenancy-fence keystone).
64
+
65
+ The issue's literal ask — a composite foreign key from the four fenced models to `WorkspaceSiteIdentity` — is not buildable against this tree: it crosses the `inbox` → `config` namespace boundary ADR-0009 forbids, `siteId` never holds a `WorkspaceSiteIdentity.id` (it holds the workspace id), and `WorkspaceSiteIdentity` is an optional per-workspace config blob whose required existence would break anonymous lead capture, a never-gate surface. The equality CHECK delivers the same guarantee — Postgres rejects a row whose `workspaceId` disagrees with its site — without a relation and without crossing the boundary; it is strictly tighter than the FK would have been. `docs/database/namespaces.md` documents the pattern for the fan-out ([#6255](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6255)–[#6260](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6260), [#6171](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6171), [#6314](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6314)), which land their own model changes into this same migration, `database/migrations/20260922120000_tenancy_fence`.
66
+
67
+ `DeliverableGrant` (the fifth already-keyed model) is deliberately NOT touched — its `workspaceId` has no source column to derive from and `NULL` is the serve path's fail-closed sentinel; [#6257](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6257) fences it separately, at mint grain.
68
+
69
+ Every write site that creates a `Lead`, `ProjectListing`, `FlowDefinition` or `FlowSession` row now derives `workspaceId` server-side from the resolved site alias (`apps/api/src/lib/site.ts`'s `resolveSiteWorkspaceId()` / `resolveWorkspaceIdForAlias()`), never from a request body. `routeLead`'s file-fallback no longer swallows a constraint violation into a silent success — it re-raises as a `persist_failed` error, while a genuine infrastructure outage still takes the file fallback unchanged.
70
+
71
+ - [#6418](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6418) [`14dcb0f`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/14dcb0fe7864a13f3fb9d573c303ddb8780dbdf5) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `ProjectListing` records the listicle magnet that captured it, so a magnet's Applications list can be scoped to its own submissions ([#6314](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6314)).
72
+
73
+ Nothing constrains a workspace to one listicle-kind `FlowDefinition` — the model carries `@@unique([siteId, slug])` and no `@@unique([siteId, kind])` — so a workspace-only Applications list would silently show two listicle magnets' applications on both of their detail pages, with nothing to notice it. `ProjectListing` carried no reverse link to the magnet that captured it: the two pointers that existed (`FlowDefinition.listingId` and `ProjectListing.experimentKey`) are both written at APPROVAL time, when an approved listing is projected into its own diagnostic sub-flow, and name a different flow entirely.
74
+
75
+ **`@working-theory/database`** — `ProjectListing` gains `capturedByFlowId String? @map("captured_by_flow_id")` and `@@index([workspaceId, capturedByFlowId])`. A plain scalar ref: no `@relation`, no foreign key, no `onDelete`, matching the intra-`inbox` convention every sibling reference on this model already follows (`leadId`, `listingId`, `grantId`, `flowSessionId` carry none, and all 24 foreign keys in the migration history target `auth`). Integrity rests on the server-side resolve and on the read carrying both scope terms; the column's own schema comment states that cost rather than arguing it away. Permanently nullable and **not** a backfill target — no existing row carries evidence naming the magnet that captured it, so every row written before this reads `NULL` for the life of the row: unattributed, absent from every per-magnet list, and unchanged in the workspace Inbox. `NULL` means "not attributed", never "attribution failed" and never "captured directly".
76
+
77
+ **`@working-theory/validation`** — `projectListingStartSchema` gains one optional key, `capturedByFlowSlug: flowSlugSchema.optional()`. Additive on a `.strict()` schema, so every existing caller (the agent channel included) is accepted unchanged. It is a slug rather than an id because no public surface hands a visitor a `flow_<uuidv7>`; the server resolves it against the already-resolved `siteId`/`workspaceId` plus `kind: "listicle"` and stores the id it finds. A slug that resolves to nothing — naming no magnet, another workspace's magnet, or a non-listicle magnet — writes `NULL` and still returns `201`: this is a never-gate anonymous route where a refused body is a lost application, and a distinguishable error would make it a cross-workspace magnet-slug oracle. The operator's signal is a PII-safe server-side log line instead.
78
+
79
+ - [#6383](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6383) [`e71eb5d`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/e71eb5d0d5796732e19dfab7768048a1009ef66d) Thanks [@claudio-planck](https://github.com/claudio-planck)! - **breaking** — the tenancy fence fan-out: thirteen models gain a required `workspace_id`.
80
+
81
+ `Subscriber`, `Callback`, `ConsentGrant`, `Appointment`, `DeliverableAsset`,
82
+ `FlowCheckout`, `WaitlistExperiment`, `WaitlistScore`, `WaitlistFollowUpSend`,
83
+ `PlaybookInstance` and `SiteListingConfig` each gain a NOT NULL
84
+ `workspaceId String @map("workspace_id")`, an `@@index([workspaceId])`, a
85
+ `<table>_workspace_id_format` CHECK and a
86
+ `<table>_workspace_site_agree CHECK (workspace_id = site_id)`. Every
87
+ `create`/`upsert` of these models must now supply `workspaceId`, and it must
88
+ equal the row's `site_id` — a server-resolved workspace id, never a value read
89
+ from a request body.
90
+
91
+ Two models carry no `site_id` and take a different shape:
92
+
93
+ - `ListingCsvSend` gains NOT NULL `workspace_id` plus the format CHECK only —
94
+ no equality CHECK and no foreign key. It is backfilled and stamped from the
95
+ parent `ProjectListing`.
96
+ - `SiteSetting` gains NOT NULL `workspace_id`, and **its unique key moves from
97
+ `key` to `(workspaceId, key)`** — `key` alone is no longer unique, so
98
+ `siteSetting.findUnique`/`upsert` must pass the compound
99
+ `workspaceId_key` selector. Existing rows backfill to the sentinel
100
+ workspace, and every `key` string is left verbatim.
101
+
102
+ `DeliverableGrant.workspaceId` stays **nullable** — NULL remains the serve
103
+ path's fail-closed sentinel for a legacy grant — but gains a NULL-tolerant
104
+ `CHECK (workspace_id IS NULL OR workspace_id = site_id)`, and both mint sites
105
+ now stamp it.
106
+
107
+ Per [#6254](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6254) AC-1 this lands as an amendment to the single M32 migration
108
+ `20260922120000_tenancy_fence` rather than a new migration directory. The
109
+ amendment is a no-op on a database that already applied that migration, which
110
+ is safe here under the operator's 2026-09-20 disposition that no persistent
111
+ database carries state that must survive (spec § 1.5); if that ever stops
112
+ holding, the SQL moves verbatim into a new migration directory and nothing
113
+ else changes.
114
+
115
+ - [#5939](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/5939) [`0d2a2b5`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/0d2a2b54eb622a904d4a8b80c31a495651ef19e7) Thanks [@samuelhenry](https://github.com/samuelhenry)! - Add the shared one-workspace resolution predicate to the `./tenancy` subpath ([#5876](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/5876)).
116
+
117
+ `resolveDefaultWorkspaceRow(prisma)` and its structural `WorkspacePredicateClient` type join the sentinel constants already exported there — the deployment-level predicate `apps/api/src/services/default-tenant.ts` and `apps/mcp`'s new tenant resolution both delegate to, so the one-workspace check exists exactly once in the repository (NFR-7). Additive: no existing export changes shape.
118
+
119
+ ### Patch Changes
120
+
121
+ - [#6172](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6172) [`20bab3b`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/20bab3ba318e4ecb8ae7489e4ad638808a3a165d) Thanks [@samuelhenry](https://github.com/samuelhenry)! - Fix `Appointment.noShow` — restore its `@map("no_show")`, matching every sibling field on the model and the column the `20260918120000_add_appointment` migration actually creates ([#6162](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6162)).
122
+
123
+ Without the mapping, Prisma's generated client targeted the literal column `noShow`, which the live database does not have — every `prisma.appointment.create()` (and any other read/write touching this field) failed at runtime with `The column noShow of relation appointments does not exist in the current database.` No schema shape changed and no migration is added; this restores the field's intended, already-migrated column target. A new `database/__tests__/appointment.db.test.ts` proves the round trip against a live migrated database, and `tools/check-prisma-migration-drift.ts` — a new preflight check — fails when a Prisma schema field/model's implied column/table name disagrees with what the migration history actually creates, catching this exact class before merge.
124
+
125
+ - [#6367](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6367) [`d3f599e`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/d3f599e1f1cadd3d998b92514231b0b4b7b7f77c) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `ProjectListing.consentAt` now declares `@db.Timestamptz(3)`, matching the `TIMESTAMPTZ` column its migration (`20260909180000_listing_draft_and_fields`) actually created ([#6171](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6171)). The Prisma schema previously declared this legal-consent timestamp as a plain, timezone-naive `DateTime`, which Prisma defaults to `TIMESTAMP(3)` on Postgres — a drift between the schema's declared type and the live column's real type. No migration is needed; the column already exists as `TIMESTAMPTZ`.
126
+
127
+ - [#6224](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6224) [`454edc7`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/454edc7bb960666c57b7f2651f644b3788194698) Thanks [@samuelhenry](https://github.com/samuelhenry)! - Add the enabled-meeting-slugs gate ([#6183](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6183) S9b) — the operator's control over which meeting types the site's OWN PAGES publish.
128
+
129
+ A fourth workspace site-config blob, `MeetingsSchema` (`enabledSlugs`, beside `ThemeSchema`/`SiteIdentitySchema`/`SEOSchema`), reads and writes through a new route, `GET|PUT /v1/workspaces/:workspaceId/meetings` (`apps/api/src/routes/workspaces/meetings.ts`) — a new route file and a new handler, but the SAME path shape, tenant guard and `{ok,data}` envelope as its three siblings, registered in its own scope gated on the `scheduling` module alone (its GET needs the scheduling provider seam, which a `workspace`/`multi-tenant`-only build does not carry). The write floor is `owner|admin`, matching every other publication-affecting write in this route family; the GET carries the SAME floor, since it echoes the deployment's provider catalog joined with which rows are enabled — a plain member should not read that. The GET also lists the scheduling provider's own bookable event types (`listEventTypes`, [#6182](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6182)), joined with `enabled: boolean`, so the Admin Meetings page can render a toggle list.
130
+
131
+ A new anonymous route, `GET /v1/schedule/meeting-types`, is what lets the public `/meet` surface — which holds no session — honour the gate at request time for its OWN two pages: `/meet/[slug]` now 404s a disabled slug (gating both the page and `generateMetadata`, so a disabled event's title/description never reach the document head), and `/meet` lists the enabled set rather than a single configured link, titled from one batched `event-meta` read. An unconfigured deployment falls back to the singleton `SCHEDULING_CAL_LINK` already advertises, so no shipped page goes dark on upgrade. Both pages are `force-dynamic`: enabling or disabling a slug takes effect on the next request, no deploy.
132
+
133
+ **This is a publication gate on this site's own pages, not an authorization boundary on the provider.** The three anonymous scheduling routes — `POST /v1/schedule/book`, `GET /v1/schedule/availability`, `GET /v1/schedule/event-meta` — are unchanged and still accept any `calLink` naming ANY event the provider knows about, disabled or not; none of them consults `enabledSlugs`. A disabled event's own page on this site 404s, but the event is still bookable at the provider's own public page and through those three routes directly, and nothing this repo can change alters that. Gating them would change a fenced scheduling-route contract this unit does not touch, and would not close the provider's own page regardless.
134
+
135
+ - [#6407](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/6407) [`87535c1`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/87535c162654d76af314395eb55e87bd0ef96021) Thanks [@samuelhenry](https://github.com/samuelhenry)! - `database/inbox/schema.prisma` drops the 'PLACEMENT IS A HARD REQUIREMENT' comment blocks ([#6261](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/6261)).
136
+
137
+ Comment-only: no model, field, mapping, index or migration changes, so the generated client is byte-identical. The blocks documented a workaround for the tenant-scope drift guard's old model-body collector, which matched `/model\s+(\w+)\s+\{([^}]*)\}/` and stopped at the first closing-curly CHARACTER — including one written inside a doc comment — and so forced `workspaceId` to be declared above any comment that might contain one.
138
+
139
+ The reader behind that guard now lives in `tools/lib/tenant-scope-schema.ts` and parses model bodies by brace DEPTH, outside `//` and `///` comments and outside double-quoted strings. Field placement no longer carries meaning for it, so the per-field 'DO NOT MOVE this field' clauses that referenced the blocks go with them. No field moved.
140
+
141
+ - Updated dependencies [[`8eadc43`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/8eadc437285a0c0121c9f4750ff83dac9591729e), [`2666bf3`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/2666bf3ed24324a37a51cfc5c68019ad5d26403e), [`94ebca8`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/94ebca8f3c1c9f3b744182c720e91417012ecc9b), [`518c590`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/518c59023876a0212504ace9a683084df0168be6), [`8f931f1`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/8f931f1af0d170fb743bf027634154cd53aa1db9), [`ecab283`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/ecab283284d5f6a6ba4a2a08b018b077257de288), [`3bb6a93`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/3bb6a93f41c7dfd8d24ea017f5c403edaae03fcf), [`ab6b792`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/ab6b79258a369ff909fa62d7bffca7fe264fe532), [`9e7df0d`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/9e7df0d93343e126c7b058a66b71ad4dda8ae9e4), [`28f14b5`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/28f14b50dfe29d2d9bf8e75aa368eb1ef3767d47), [`9f45297`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/9f4529701aaf2f56acbd2be416e8a8c17358e1aa), [`80f498e`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/80f498e3dcd3d70931045dc376c293c0c9a88c65), [`454edc7`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/454edc7bb960666c57b7f2651f644b3788194698), [`2e7b0d2`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/2e7b0d2411944002104d5105edb6e8d59c8434c5), [`6d061a0`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/6d061a0cd48baec20899cec2962ff6877addaa85), [`14dcb0f`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/14dcb0fe7864a13f3fb9d573c303ddb8780dbdf5), [`f917f0b`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/f917f0b182ea8daf657b4762551ea5e86fc90616)]:
142
+ - @working-theory/validation@0.25.0
143
+ - @working-theory/ids@0.8.0
144
+
145
+ ## 0.24.0
146
+
147
+ ### Patch Changes
148
+
149
+ - Updated dependencies [[`885adcb`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/885adcbfb23b032424d105942ac0085a03209208), [`c1fb87c`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/c1fb87cbf3ee7ca97951b272cdcdf48c0d1f3a37), [`0b71694`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/0b71694feb662a9686d85cf9478a0b703cc92805), [`6f98cfd`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/6f98cfdef3ebd995dd1c812071cbe62ba4324f64), [`f2a92c5`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/f2a92c585cd5e81fd146c78f65d6fcbca52f7bca), [`ea1e07d`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/ea1e07dde98d451325d9c0e49c1dad3d243c7a68)]:
150
+ - @working-theory/validation@0.24.0
151
+
3
152
  ## 0.23.0
4
153
 
5
154
  ### Patch Changes
@@ -143,11 +143,18 @@ await prisma.userData.upsert({
143
143
  create: { userId, key: "preferences", value: { theme: "dark" } },
144
144
  });
145
145
 
146
- // Store site-wide cache
146
+ // Store a per-workspace setting.
147
+ //
148
+ // #6374 D3 — `SiteSetting` is keyed by (workspaceId, key), NOT by `key`
149
+ // alone: `key` lost its global @unique so two workspaces may each hold a
150
+ // `cached_config` row. `workspaceId` is resolved SERVER-SIDE (see
151
+ // apps/api/src/lib/site.ts's `resolveWorkspaceIdForAlias`) and never read
152
+ // from a request body — `site_settings.workspace_id` is NOT NULL with a
153
+ // format CHECK, so a raw site alias is refused by Postgres.
147
154
  await prisma.siteSetting.upsert({
148
- where: { key: "cached_config" },
155
+ where: { workspaceId_key: { workspaceId, key: "cached_config" } },
149
156
  update: { value: platformConfig },
150
- create: { key: "cached_config", value: platformConfig },
157
+ create: { workspaceId, key: "cached_config", value: platformConfig },
151
158
  });
152
159
  ```
153
160