create-ailk 0.1.0 → 0.1.1

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 (605) hide show
  1. package/README.md +124 -3
  2. package/component-catalog.md +295 -0
  3. package/dist/cli.js +92 -16
  4. package/dist/component-catalog.d.ts +11 -0
  5. package/dist/component-catalog.js +39 -0
  6. package/dist/copy.d.ts +7 -0
  7. package/dist/copy.js +24 -0
  8. package/dist/derive-architecture.d.ts +13 -0
  9. package/dist/derive-architecture.js +238 -0
  10. package/dist/index.d.ts +7 -2
  11. package/dist/index.js +19 -2
  12. package/dist/lib/extract-module-bundle.d.ts +16 -0
  13. package/dist/lib/extract-module-bundle.js +184 -0
  14. package/dist/lib/fetch-module.d.ts +55 -0
  15. package/dist/lib/fetch-module.js +125 -0
  16. package/dist/lib/module-license-gate.d.ts +48 -0
  17. package/dist/lib/module-license-gate.js +67 -0
  18. package/dist/lib/module-tier.d.ts +35 -0
  19. package/dist/lib/module-tier.js +37 -0
  20. package/dist/module-architecture.d.ts +175 -0
  21. package/dist/module-architecture.js +663 -0
  22. package/dist/parse-args.d.ts +47 -1
  23. package/dist/parse-args.js +143 -2
  24. package/dist/programmatic.d.ts +160 -3
  25. package/dist/programmatic.js +196 -7
  26. package/dist/surfaces.d.ts +211 -0
  27. package/dist/surfaces.js +532 -0
  28. package/dist/sync-routes.d.ts +92 -0
  29. package/dist/sync-routes.js +350 -0
  30. package/package.json +22 -14
  31. package/templates/.claude/rules/architecture.md +6 -2
  32. package/templates/.claude/skills/README.md +9 -9
  33. package/templates/.claude/skills/add-schema/SKILL.md +14 -7
  34. package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
  35. package/templates/.claude/skills/provision-config/SKILL.md +1 -1
  36. package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
  37. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
  38. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
  39. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
  40. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
  41. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
  42. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
  43. package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
  44. package/templates/.env.example +126 -2
  45. package/templates/CLAUDE.md +11 -5
  46. package/templates/README.md +1 -1
  47. package/templates/apps/api/.env.example +38 -0
  48. package/templates/apps/api/CLAUDE.md +75 -1
  49. package/templates/apps/api/LICENSE +201 -0
  50. package/templates/apps/api/api/index.ts +6 -141
  51. package/templates/apps/api/jest.config.cjs +16 -0
  52. package/templates/apps/api/package.json +22 -6
  53. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
  54. package/templates/apps/api/src/__tests__/server.test.ts +46 -0
  55. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
  56. package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
  57. package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
  58. package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
  59. package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
  60. package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
  61. package/templates/apps/api/src/bin/seed-presets.ts +58 -0
  62. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
  63. package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
  64. package/templates/apps/api/src/cli.ts +5 -11
  65. package/templates/apps/api/src/config/index.ts +35 -0
  66. package/templates/apps/api/src/config/modules.ts +43 -0
  67. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
  68. package/templates/apps/api/src/lib/__mocks__/prisma.ts +231 -0
  69. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
  70. package/templates/apps/api/src/lib/site.ts +16 -0
  71. package/templates/apps/api/src/lib/stripe.ts +22 -0
  72. package/templates/apps/api/src/lib/tenant-db.ts +225 -0
  73. package/templates/apps/api/src/lib/ws-token.ts +91 -0
  74. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
  75. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
  77. package/templates/apps/api/src/middleware/auth.ts +43 -8
  78. package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
  79. package/templates/apps/api/src/middleware/tenant.ts +99 -0
  80. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
  81. package/templates/apps/api/src/openapi/spec.ts +312 -3
  82. package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
  83. package/templates/apps/api/src/routes/aeo/index.ts +107 -0
  84. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
  85. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
  86. package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
  87. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
  88. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
  89. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
  90. package/templates/apps/api/src/routes/billing/index.ts +36 -0
  91. package/templates/apps/api/src/routes/billing/portal.ts +182 -0
  92. package/templates/apps/api/src/routes/billing/read.ts +75 -0
  93. package/templates/apps/api/src/routes/billing/usage.ts +153 -0
  94. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
  95. package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
  96. package/templates/apps/api/src/routes/content/create.ts +1 -0
  97. package/templates/apps/api/src/routes/content/delete.ts +1 -0
  98. package/templates/apps/api/src/routes/content/index.ts +42 -6
  99. package/templates/apps/api/src/routes/content/update.ts +1 -0
  100. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
  101. package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
  102. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
  103. package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
  104. package/templates/apps/api/src/routes/flows/README.md +147 -0
  105. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
  106. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
  107. package/templates/apps/api/src/routes/flows/index.ts +202 -0
  108. package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
  109. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
  110. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
  111. package/templates/apps/api/src/routes/leads/index.ts +43 -9
  112. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +547 -0
  113. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
  114. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +742 -0
  115. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
  116. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +364 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
  118. package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
  119. package/templates/apps/api/src/routes/project-listings/index.ts +105 -0
  120. package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
  121. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
  122. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
  123. package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
  124. package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
  125. package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
  126. package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
  127. package/templates/apps/api/src/routes/project-listings/start.ts +98 -0
  128. package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
  129. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
  130. package/templates/apps/api/src/routes/schedule/index.ts +249 -0
  131. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
  132. package/templates/apps/api/src/routes/slack/actions.ts +177 -0
  133. package/templates/apps/api/src/routes/slack/index.ts +38 -0
  134. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
  135. package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
  136. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
  137. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
  138. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
  139. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
  140. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
  141. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
  142. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
  143. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
  144. package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
  145. package/templates/apps/api/src/routes/webhooks/README.md +35 -12
  146. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
  147. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
  148. package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
  149. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
  150. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
  151. package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
  152. package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
  153. package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
  154. package/templates/apps/api/src/server.ts +293 -7
  155. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
  156. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
  157. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
  158. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
  159. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
  160. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
  161. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
  162. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
  163. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
  164. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
  165. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
  166. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
  167. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
  168. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
  169. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
  170. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
  171. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
  172. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
  173. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
  174. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
  175. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
  176. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
  177. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
  178. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
  179. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
  180. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
  181. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
  182. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
  183. package/templates/apps/api/src/services/abandon-email.ts +307 -0
  184. package/templates/apps/api/src/services/aeo-score.ts +288 -0
  185. package/templates/apps/api/src/services/answer-distributions.ts +138 -0
  186. package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
  187. package/templates/apps/api/src/services/entitlements.ts +102 -0
  188. package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
  189. package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
  190. package/templates/apps/api/src/services/flow-engine.ts +1278 -0
  191. package/templates/apps/api/src/services/lead-promotion.ts +176 -0
  192. package/templates/apps/api/src/services/lead-routing.ts +3 -3
  193. package/templates/apps/api/src/services/listing-config.ts +102 -0
  194. package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
  195. package/templates/apps/api/src/services/playbook-compile.ts +398 -0
  196. package/templates/apps/api/src/services/playbook-render.ts +263 -0
  197. package/templates/apps/api/src/services/project-listing-decision.ts +490 -0
  198. package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
  199. package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
  200. package/templates/apps/api/src/services/project-listings.ts +1124 -0
  201. package/templates/apps/api/src/services/recommender-capture.ts +835 -0
  202. package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
  203. package/templates/apps/api/src/services/scheduling/index.ts +63 -0
  204. package/templates/apps/api/src/services/scheduling/types.ts +88 -0
  205. package/templates/apps/api/src/services/slack-notify.ts +240 -0
  206. package/templates/apps/api/src/services/tenant-context.ts +451 -0
  207. package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
  208. package/templates/apps/api/src/services/usage-credits.ts +340 -0
  209. package/templates/apps/api/src/services/usage-metering.ts +699 -0
  210. package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
  211. package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
  212. package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
  213. package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
  214. package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
  215. package/templates/apps/api/src/vercel-handler.ts +165 -0
  216. package/templates/apps/api/tsconfig.json +0 -12
  217. package/templates/apps/mcp/CLAUDE.md +19 -9
  218. package/templates/apps/mcp/LICENSE +201 -0
  219. package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
  220. package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
  221. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
  222. package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
  223. package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
  224. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
  225. package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
  226. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
  227. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
  228. package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
  229. package/templates/apps/mcp/package.json +6 -4
  230. package/templates/apps/mcp/src/config/modules.ts +37 -0
  231. package/templates/apps/mcp/src/server.ts +109 -18
  232. package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
  233. package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
  234. package/templates/apps/mcp/src/tools/create_page.ts +1 -0
  235. package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
  236. package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
  237. package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
  238. package/templates/apps/mcp/src/tools/index.ts +25 -0
  239. package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
  240. package/templates/apps/mcp/src/tools/purchase.ts +3 -0
  241. package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
  242. package/templates/apps/mcp/src/tools/update_page.ts +1 -0
  243. package/templates/apps/mcp/tsconfig.json +1 -9
  244. package/templates/apps/web/.env.example +44 -0
  245. package/templates/apps/web/CLAUDE.md +28 -0
  246. package/templates/apps/web/LICENSE +201 -0
  247. package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
  248. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
  249. package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
  250. package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
  251. package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
  252. package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
  253. package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
  254. package/templates/apps/web/__tests__/setup.ts +22 -32
  255. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
  256. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
  257. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
  258. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
  259. package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
  260. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
  261. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
  262. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
  263. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
  264. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
  265. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
  266. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
  267. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
  268. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
  269. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
  270. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
  271. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
  272. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
  273. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
  274. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
  275. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
  276. package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
  277. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
  278. package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
  279. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
  280. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
  281. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  282. package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
  283. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
  284. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  285. package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
  286. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
  287. package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
  288. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
  289. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
  290. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
  291. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
  292. package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
  293. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
  294. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
  295. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
  296. package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
  297. package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
  298. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
  299. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  300. package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
  301. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
  302. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
  303. package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
  304. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
  305. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
  306. package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
  307. package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
  308. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
  309. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
  310. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
  311. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
  312. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
  313. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
  314. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
  315. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
  316. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
  317. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
  318. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
  319. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
  320. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
  321. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
  322. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
  323. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
  324. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  325. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
  326. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  327. package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
  328. package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
  329. package/templates/apps/web/app/[locale]/layout.tsx +115 -16
  330. package/templates/apps/web/app/[locale]/page.tsx +10 -1
  331. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
  332. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  333. package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
  334. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
  335. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  336. package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
  337. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
  338. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
  339. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
  340. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
  341. package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
  342. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
  343. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
  344. package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
  345. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
  346. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
  347. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
  348. package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
  349. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
  350. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  351. package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
  352. package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
  353. package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
  354. package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
  355. package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
  356. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
  357. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
  358. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  359. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
  360. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  361. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
  362. package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
  363. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
  364. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
  365. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
  366. package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
  367. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
  368. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  369. package/templates/apps/web/app/globals.css +35 -0
  370. package/templates/apps/web/app/llms.txt/route.ts +2 -1
  371. package/templates/apps/web/app/og/route.tsx +10 -2
  372. package/templates/apps/web/auth.ts +8 -0
  373. package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
  374. package/templates/apps/web/content/en/blog/README.txt +25 -0
  375. package/templates/apps/web/content/en/docs/index.mdx +28 -0
  376. package/templates/apps/web/jest.config.cjs +84 -27
  377. package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
  378. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
  379. package/templates/apps/web/lib/account-config.ts +13 -0
  380. package/templates/apps/web/lib/server-api.ts +85 -0
  381. package/templates/apps/web/lib/site-brand.tsx +118 -0
  382. package/templates/apps/web/messages/en.json +275 -7
  383. package/templates/apps/web/middleware.ts +11 -3
  384. package/templates/apps/web/next.config.mjs +8 -2
  385. package/templates/apps/web/package.json +16 -7
  386. package/templates/apps/web/postcss.config.mjs +23 -0
  387. package/templates/apps/web/public/.well-known/security.txt +3 -3
  388. package/templates/apps/web/src/lib/routes.ts +15 -2
  389. package/templates/apps/web/tsconfig.json +1 -16
  390. package/templates/content/_site.mdx +24 -25
  391. package/templates/content/en/AboutPage/about.mdx +24 -14
  392. package/templates/content/en/ContactPage/contact.mdx +23 -14
  393. package/templates/content/en/FAQPage/faq.mdx +10 -392
  394. package/templates/content/en/HomePage/home.mdx +14 -285
  395. package/templates/database/CHANGELOG.md +382 -0
  396. package/templates/database/LICENSE +201 -0
  397. package/templates/database/README.md +3 -1
  398. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
  399. package/templates/database/__tests__/flows-presets.test.ts +173 -0
  400. package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
  401. package/templates/database/__tests__/seed.test.ts +88 -0
  402. package/templates/database/__tests__/user-cascade.test.ts +19 -4
  403. package/templates/database/auth/schema.prisma +521 -0
  404. package/templates/database/config/schema.prisma +234 -0
  405. package/templates/database/content/schema.prisma +263 -0
  406. package/templates/database/flows/listing-diagnostic.ts +182 -0
  407. package/templates/database/flows/presets.ts +315 -0
  408. package/templates/database/inbox/__tests__/schema.test.ts +4 -0
  409. package/templates/database/inbox/schema.prisma +476 -3
  410. package/templates/database/jest.config.cjs +8 -0
  411. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
  412. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
  413. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
  414. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
  415. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
  416. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
  417. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
  418. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
  419. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
  420. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
  421. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
  422. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
  423. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
  424. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
  425. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
  426. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
  427. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
  428. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
  429. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
  430. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
  431. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
  432. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
  433. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
  434. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
  435. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
  436. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
  437. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
  438. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
  439. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
  440. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
  441. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
  442. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
  443. package/templates/database/ops/schema.prisma +27 -0
  444. package/templates/database/package.json +42 -4
  445. package/templates/database/prisma.config.ts +1 -1
  446. package/templates/database/schema.prisma +19 -435
  447. package/templates/database/scripts/db-generate-locked.sh +0 -0
  448. package/templates/database/seed-test.ts +14 -4
  449. package/templates/database/seed.ts +148 -0
  450. package/templates/database/tenancy.ts +45 -0
  451. package/templates/database/tsconfig.flows.json +15 -0
  452. package/templates/database/tsconfig.json +7 -1
  453. package/templates/database/tsconfig.tenancy.json +14 -0
  454. package/templates/package.json +30 -55
  455. package/templates/pnpm-workspace.yaml +0 -3
  456. package/templates/project.yaml +1 -1
  457. package/templates/tsconfig.json +1 -37
  458. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
  459. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
  460. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
  461. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
  462. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
  463. package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
  464. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
  465. package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
  466. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
  467. package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
  468. package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
  469. package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
  470. package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
  471. package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
  472. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
  473. package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
  474. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
  475. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
  476. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
  477. package/templates/apps/web/__tests__/page.test.tsx +0 -230
  478. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
  479. package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
  480. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
  481. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
  482. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
  483. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
  484. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
  485. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
  486. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
  487. package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
  488. package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
  489. package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
  490. package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
  491. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
  492. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
  493. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
  494. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
  495. package/templates/apps/web/content/en/blog/index.mdx +0 -15
  496. package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
  497. package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
  498. package/templates/content/.gitkeep +0 -0
  499. package/templates/content/CLAUDE.md +0 -85
  500. package/templates/content/ar/HomePage/home.mdx +0 -123
  501. package/templates/content/ar/_site.mdx +0 -35
  502. package/templates/content/de/_site.mdx +0 -29
  503. package/templates/content/en/.gitkeep +0 -0
  504. package/templates/content/en/AboutPage/about.schema.json +0 -82
  505. package/templates/content/en/BookCall/book-call.mdx +0 -19
  506. package/templates/content/en/BookCall/book-call.schema.json +0 -121
  507. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
  508. package/templates/content/en/CareersList/careers.mdx +0 -24
  509. package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
  510. package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
  511. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
  512. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
  513. package/templates/content/en/ClientsList/clients.mdx +0 -38
  514. package/templates/content/en/ContactPage/contact.schema.json +0 -100
  515. package/templates/content/en/ContactPoint/primary.mdx +0 -14
  516. package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
  517. package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
  518. package/templates/content/en/CoursesList/courses.mdx +0 -22
  519. package/templates/content/en/DownloadsList/downloads.mdx +0 -22
  520. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
  521. package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
  522. package/templates/content/en/EventsList/events.mdx +0 -26
  523. package/templates/content/en/FAQPage/faq.schema.json +0 -231
  524. package/templates/content/en/GlossaryList/glossary.mdx +0 -19
  525. package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
  526. package/templates/content/en/HomePage/home.schema.json +0 -88
  527. package/templates/content/en/HomePage/launch-service.mdx +0 -475
  528. package/templates/content/en/HomePage/launch-service.schema.json +0 -138
  529. package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
  530. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
  531. package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
  532. package/templates/content/en/Integration/github-actions.mdx +0 -31
  533. package/templates/content/en/Integration/openai-api.mdx +0 -26
  534. package/templates/content/en/Integration/vercel.mdx +0 -32
  535. package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
  536. package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
  537. package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
  538. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
  539. package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
  540. package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
  541. package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
  542. package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
  543. package/templates/content/en/LandingPage/developers.mdx +0 -19
  544. package/templates/content/en/LandingPage/developers.schema.json +0 -109
  545. package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
  546. package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
  547. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
  548. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
  549. package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
  550. package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
  551. package/templates/content/en/LandingPage/platform.mdx +0 -369
  552. package/templates/content/en/LandingPage/platform.schema.json +0 -166
  553. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
  554. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
  555. package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
  556. package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
  557. package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
  558. package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
  559. package/templates/content/en/Organization/site-owner.mdx +0 -16
  560. package/templates/content/en/Partner/prisma.mdx +0 -31
  561. package/templates/content/en/Partner/supabase.mdx +0 -30
  562. package/templates/content/en/Partner/vercel.mdx +0 -28
  563. package/templates/content/en/PartnersList/partners.mdx +0 -22
  564. package/templates/content/en/Person/founder.mdx +0 -18
  565. package/templates/content/en/Pricing/pricing.mdx +0 -538
  566. package/templates/content/en/Pricing/pricing.schema.json +0 -209
  567. package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
  568. package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
  569. package/templates/content/en/Product/ailk.mdx +0 -19
  570. package/templates/content/en/Product/ailk.schema.json +0 -94
  571. package/templates/content/en/Product/api-launch-kit.mdx +0 -26
  572. package/templates/content/en/Product/launch-kit.mdx +0 -28
  573. package/templates/content/en/ProductsList/products.mdx +0 -26
  574. package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
  575. package/templates/content/en/ProjectsList/projects.mdx +0 -25
  576. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
  577. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
  578. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
  579. package/templates/content/en/ResourcesHub/resources.mdx +0 -22
  580. package/templates/content/en/Service/consulting.mdx +0 -22
  581. package/templates/content/en/Service/implementation.mdx +0 -30
  582. package/templates/content/en/Service/launch-service.mdx +0 -232
  583. package/templates/content/en/Service/launch.mdx +0 -228
  584. package/templates/content/en/ServicesList/services.mdx +0 -84
  585. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
  586. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
  587. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
  588. package/templates/content/en/TeamList/team.mdx +0 -63
  589. package/templates/content/en/TeamMember/founder.mdx +0 -72
  590. package/templates/content/en/TermsPage/terms.mdx +0 -19
  591. package/templates/content/en/TermsPage/terms.schema.json +0 -99
  592. package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
  593. package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
  594. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
  595. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
  596. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
  597. package/templates/content/en-XA/AboutPage/about.mdx +0 -8
  598. package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
  599. package/templates/content/en-XA/HomePage/home.mdx +0 -8
  600. package/templates/content/es/_site.mdx +0 -30
  601. package/templates/content/fr/_site.mdx +0 -30
  602. package/templates/content/ja/_site.mdx +0 -30
  603. package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
  604. package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
  605. package/templates/content/pt-BR/HomePage/home.mdx +0 -23
@@ -5,9 +5,48 @@
5
5
  * Covers all models accessed in route tests:
6
6
  * auth models (user, apiToken, userData) — used by auth route tests
7
7
  * inbox models (lead, subscriber, callback) — used by leads route tests (#507)
8
+ * inbox models (flowDefinition, flowSession) — used by flow-engine service
9
+ * and flows route tests (#3956)
10
+ * inbox models (deliverableAsset, deliverableGrant) — used by the
11
+ * deliverable-fulfillment service + deliverables route tests (#3961)
12
+ * inbox model (waitlistExperiment) — used by the waitlist-experiments
13
+ * service tests (#4661 D5)
14
+ * inbox model waitlistExperiment (its `findUnique` reader) + the new
15
+ * inbox model waitlistScore (its writer) — used by the A2 waitlist-scoring
16
+ * service tests (#4662 D6)
17
+ * inbox model playbookInstance (its `create` writer, append-only, plus
18
+ * `findFirst` for its two U4b readers) — used by the U4a playbook-compile
19
+ * service tests (FEAT-074, #4715) and the U4b deliverable-fulfillment +
20
+ * deliverables-route tests (#4718)
21
+ * inbox model flowCheckout (create / update / updateMany / findUnique /
22
+ * findMany) — the recommender buyer record written by the
23
+ * recommender-capture service (#4837) and stamped by the webhook (#4838)
24
+ * inbox model waitlistFollowUpSend (its `create` stamp writer + `findMany`
25
+ * idempotency reader) + callback.findMany (booked-call suppression) +
26
+ * waitlistScore.findMany (tier lookup) — used by the #4732
27
+ * waitlist-followup sweep service tests
28
+ * tenancy models (workspace, membership) — used by tenant-context service tests (#3627)
29
+ * membership.findMany + lead.findMany/updateMany — used by lead-promotion
30
+ * service tests (#3586); lead-promotion.ts never queries membership/workspace
31
+ * directly (it resolves workspaces via tenant-context.ts's listTenancy), so
32
+ * membership.findMany's mocked return shape carries the nested
33
+ * organization.workspaces payload listTenancy expects.
8
34
  *
9
35
  * Per-test return values: import `prisma` from this module and call
10
36
  * (prisma.lead.create as jest.Mock).mockResolvedValue({ id: "..." })
37
+ *
38
+ * usageEvent/usagePeriod (content) + llmModel/llmModelPricing/aiMarkupConfig
39
+ * (config) — used by usage-metering service tests (#3713, FEAT-047 P2.S1).
40
+ * These FEAT-047 P1 models (#3709/#3710) are not yet in the generated Prisma
41
+ * client `usage-metering.ts` compiles against; it casts `prisma` to a local
42
+ * structural delegate type, and this mock provides the matching runtime
43
+ * shape so the mocked-prisma tests can drive it end to end.
44
+ *
45
+ * usageEvent.aggregate + usageBillingReport (content) — used by
46
+ * usage-aggregation service tests (#3714, FEAT-047 P2.S2; `aggregate` since
47
+ * #3739 M2 — was `findMany` + a JS accumulation loop). Same not-yet-
48
+ * generated-client situation; `usage-aggregation.ts` casts `prisma` to its
49
+ * own structural delegate type, and this mock's runtime shape matches it.
11
50
  */
12
51
 
13
52
  export const prisma = {
@@ -28,18 +67,210 @@ export const prisma = {
28
67
  upsert: jest.fn(),
29
68
  deleteMany: jest.fn(),
30
69
  },
70
+ // ── Tenancy namespace (Organization, Workspace, Membership) ──────────────
71
+ organization: {
72
+ findUnique: jest.fn(),
73
+ findFirst: jest.fn(),
74
+ update: jest.fn(),
75
+ // `updateMany` is the credit waterfall's compare-and-swap write (#3730 H1):
76
+ // usage-credits.ts guards its `where` on every column value the read
77
+ // observed, so a concurrent writer makes the predicate miss and the update
78
+ // matches 0 rows. `update` stays mocked beside it — usage-credits.test.ts
79
+ // models it as an unconditional auto-commit so the PRE-fix read-then-write
80
+ // path can still run and double-spend; that asymmetry is what makes the
81
+ // atomicity test load-bearing rather than an API-shape check.
82
+ updateMany: jest.fn(),
83
+ },
84
+ workspace: {
85
+ findUnique: jest.fn(),
86
+ count: jest.fn(),
87
+ },
88
+ membership: {
89
+ findUnique: jest.fn(),
90
+ findMany: jest.fn(),
91
+ create: jest.fn(),
92
+ },
93
+ // ── Config namespace (per-workspace site config — #3641/#3634) ───────────
94
+ workspaceTheme: {
95
+ findUnique: jest.fn(),
96
+ upsert: jest.fn(),
97
+ deleteMany: jest.fn(),
98
+ },
99
+ workspaceSiteIdentity: {
100
+ findUnique: jest.fn(),
101
+ upsert: jest.fn(),
102
+ deleteMany: jest.fn(),
103
+ },
104
+ workspaceSEO: {
105
+ findUnique: jest.fn(),
106
+ upsert: jest.fn(),
107
+ deleteMany: jest.fn(),
108
+ },
31
109
  // ── Inbox namespace (Lead, Subscriber, Callback) ─────────────────────────
32
110
  lead: {
33
111
  create: jest.fn(),
112
+ // `findUnique` — used by the deliverable-resend service (#3998) to
113
+ // resolve a grant's stored lead email.
114
+ findUnique: jest.fn(),
115
+ findMany: jest.fn(),
116
+ updateMany: jest.fn(),
34
117
  },
35
118
  subscriber: {
36
119
  create: jest.fn(),
37
120
  findUnique: jest.fn(),
38
121
  },
122
+ // `findMany` — the #4732 follow-up sweep's booked-call suppression check
123
+ // (D2: a site's Callback rows fetched once per sweep run, filtered in
124
+ // application code for `payload.email` matches).
39
125
  callback: {
40
126
  create: jest.fn(),
127
+ findMany: jest.fn(),
128
+ },
129
+ // ── Inbox namespace (FlowDefinition, FlowSession — #3956) ────────────────
130
+ // findMany on both models — used by the abandon-email sweep (#3962 D4),
131
+ // which enumerates every FlowDefinition (to read its live abandonEmail
132
+ // policy) and every candidate FlowSession per definition.
133
+ flowDefinition: {
134
+ findUnique: jest.fn(),
135
+ findMany: jest.fn(),
136
+ upsert: jest.fn(),
137
+ },
138
+ // `deleteMany` — used by the FlowSession retention/cleanup sweep
139
+ // (flow-engine.ts's `cleanupAbandonedSessions`, #3974 AC3).
140
+ flowSession: {
141
+ create: jest.fn(),
142
+ findUnique: jest.fn(),
143
+ findMany: jest.fn(),
144
+ update: jest.fn(),
145
+ deleteMany: jest.fn(),
146
+ },
147
+ // ── Inbox namespace (DeliverableAsset, DeliverableGrant — #3961) ─────────
148
+ deliverableAsset: {
149
+ findUnique: jest.fn(),
150
+ upsert: jest.fn(),
151
+ },
152
+ // `update` — the send-outcome stamp (#3998 D1). `findMany` — the
153
+ // deliverable:resend CLI's list mode (#3998 D4), selecting on
154
+ // `sendStatus: null`.
155
+ deliverableGrant: {
156
+ create: jest.fn(),
157
+ findUnique: jest.fn(),
158
+ update: jest.fn(),
159
+ findMany: jest.fn(),
160
+ },
161
+ // ── Inbox namespace (FlowCheckout — #4837 D4) ─────────────────────────────
162
+ // `create` — recordCheckout's one write; `update` — attachStripeSession;
163
+ // `updateMany` — the webhook's guarded status stamp (#4838); `findUnique` /
164
+ // `findMany` — the webhook's amount read + the dashboard's checkedOut read
165
+ // (#4839).
166
+ flowCheckout: {
167
+ create: jest.fn(),
168
+ update: jest.fn(),
169
+ updateMany: jest.fn(),
170
+ findUnique: jest.fn(),
171
+ findMany: jest.fn(),
172
+ },
173
+ // ── Config namespace (SiteListingConfig — #5033) ─────────────────────────
174
+ // `findUnique` — the ONLY read path (`listingConfigFor`), keyed on siteId.
175
+ // Read server-side by the anonymous create handler and the public read; no
176
+ // route writes it, so no write delegate is mocked.
177
+ siteListingConfig: {
178
+ findUnique: jest.fn(),
179
+ },
180
+ // ── Inbox namespace (ProjectListing — #4998, reshaped #5033) ─────────────
181
+ // `create` — the POST route's row write (after routeLead's Lead write).
182
+ // `findFirst` — the tenant-scoped get/patch reads (scoped by siteId + id, a
183
+ // foreign id is not_found) and `decideListing`'s pre-write windowDays read.
184
+ // `findMany` — the tenant list, the anonymous public read, and
185
+ // `firstFreeSlug`'s site-scoped collision read. `updateMany` — the
186
+ // `decideListing` guarded compare-and-swap write (`where` includes
187
+ // `status: "requested"`, so a concurrent double-decide matches 0 rows on
188
+ // the loser) and `setListingFeatured`'s scoped flag write.
189
+ projectListing: {
190
+ create: jest.fn(),
191
+ findFirst: jest.fn(),
192
+ findMany: jest.fn(),
193
+ update: jest.fn(),
194
+ updateMany: jest.fn(),
195
+ },
196
+ // ── Inbox namespace (WaitlistExperiment — #4661 D5) ──────────────────────
197
+ // `upsert` — the ONLY write path into waitlist_experiments
198
+ // (upsertWaitlistExperiment). `findUnique` — read by the A2 scoring glue
199
+ // (scoreCompletedSignup, #4662 D6) to load the owning experiment's config.
200
+ // `findMany` — the #4771 flow-aggregate service's slug -> register
201
+ // resolver (a scoped-by-siteId read, matched in-memory against every
202
+ // waitlist's `flowSlug`).
203
+ waitlistExperiment: {
204
+ upsert: jest.fn(),
205
+ findUnique: jest.fn(),
206
+ findFirst: jest.fn(),
207
+ findMany: jest.fn(),
208
+ },
209
+ // ── Inbox namespace (WaitlistScore — #4662 D6) ───────────────────────────
210
+ // `upsert` — the ONLY write path into waitlist_scores (scoreCompletedSignup).
211
+ // `findMany` — the #4732 follow-up sweep's tier lookup, batched by leadId.
212
+ // `count` — the #4998 project-listings dashboard's waitlistCount join.
213
+ waitlistScore: {
214
+ upsert: jest.fn(),
215
+ count: jest.fn(),
216
+ findMany: jest.fn(),
217
+ },
218
+ // ── Inbox namespace (PlaybookInstance — FEAT-074 U4a #4715, extended U4b #4718) ──
219
+ // `create` — the ONLY write path into playbook_instances
220
+ // (compileAndStorePlaybook); append-only, never upsert/update. `findFirst`
221
+ // — the `playbook:` virtual-asset existence check
222
+ // (deliverable-fulfillment.ts, U4b D4) + the download route's
223
+ // newest-instance read (playbook-render route, U4b D4).
224
+ playbookInstance: {
225
+ create: jest.fn(),
226
+ findFirst: jest.fn(),
227
+ },
228
+ // ── Inbox namespace (WaitlistFollowUpSend — #4732) ───────────────────────
229
+ // `findMany` — the idempotency reader (already-sent (leadId, followUpKey)
230
+ // pairs). `create` — the send-attempt stamp ('sent' | 'failed'), written
231
+ // for every attempted send, never for a suppressed/skipped candidate.
232
+ waitlistFollowUpSend: {
233
+ findMany: jest.fn(),
234
+ create: jest.fn(),
235
+ },
236
+ // ── Inbox namespace (ListingCsvSend — #5001) ─────────────────────────────
237
+ // `findFirst` — the per-listing watermark read (newest `sentAt`). `create`
238
+ // — the post-send stamp, written only after a successful send so a failed
239
+ // send leaves those rows for next week's CSV.
240
+ listingCsvSend: {
241
+ findFirst: jest.fn(),
242
+ create: jest.fn(),
243
+ },
244
+ // ── Content namespace (UsageEvent, UsagePeriod — FEAT-047 P1.S1/#3709) ──
245
+ usageEvent: {
246
+ create: jest.fn(),
247
+ aggregate: jest.fn(),
248
+ },
249
+ usagePeriod: {
250
+ upsert: jest.fn(),
251
+ },
252
+ // ── Content namespace (UsageBillingReport — FEAT-047 P1.S1/#3709) ────────
253
+ usageBillingReport: {
254
+ upsert: jest.fn(),
255
+ },
256
+ // ── Config namespace (LlmModel, LlmModelPricing, AiMarkupConfig — FEAT-047 P1.S2/#3710) ──
257
+ llmModel: {
258
+ findFirst: jest.fn(),
259
+ },
260
+ llmModelPricing: {
261
+ findFirst: jest.fn(),
262
+ },
263
+ aiMarkupConfig: {
264
+ findFirst: jest.fn(),
41
265
  },
42
266
  // ── Lifecycle ────────────────────────────────────────────────────────────
267
+ // `$transaction` (interactive form) — usage-metering.ts writes the UsageEvent
268
+ // and the UsagePeriod aggregate atomically (#3727 H3: a non-atomic pair lets
269
+ // a failed upsert strand a persisted event that a retry then double-bills).
270
+ // Tests supply the per-case implementation; usage-metering.test.ts's
271
+ // `mockTransaction` helper models commit/rollback so atomicity is asserted,
272
+ // not assumed.
273
+ $transaction: jest.fn(),
43
274
  $connect: jest.fn(),
44
275
  $disconnect: jest.fn(),
45
276
  };
@@ -34,7 +34,7 @@ import type { LeadCaptureRecord } from "../lead-capture-store.js";
34
34
  // ─── Helpers ─────────────────────────────────────────────────────────────────
35
35
 
36
36
  const record: LeadCaptureRecord = {
37
- id: "cuid-test-1",
37
+ id: "fallback-1700000000000-test1",
38
38
  createdAt: "2024-01-01T00:00:00.000Z",
39
39
  source: "contact",
40
40
  data: { email: "test@example.com", message: "hello" },
@@ -160,7 +160,7 @@ describe("appendLeadRecord", () => {
160
160
  // Real O_APPEND atomicity is kernel-guaranteed for local filesystems.
161
161
  it("50 concurrent writes all resolve ok:true with flag:a", async () => {
162
162
  const promises = Array.from({ length: 50 }, (_, i) =>
163
- appendLeadRecord({ ...record, id: `cuid-${i}` }),
163
+ appendLeadRecord({ ...record, id: `fallback-1700000000000-${i}` }),
164
164
  );
165
165
  const results = await Promise.all(promises);
166
166
  expect(results).toHaveLength(50);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @file site.ts
3
+ * @description The current-site resolution helper (#3958 §3.2).
4
+ *
5
+ * Extracted verbatim from `routes/flows/index.ts`'s private
6
+ * `currentSiteId()` so the flow-preset seed (`services/flow-engine.ts` →
7
+ * `bin/seed-presets.ts`) resolves the SAME site id the `/v1/flows/*` routes
8
+ * resolve — a seed hard-coding `"default"` would write presets the API
9
+ * cannot find whenever `AILK_SITE_ID` is set (a silent, environment-dependent
10
+ * 404). Behavior is byte-identical to the pre-extraction helper: same env
11
+ * var, same fallback, same call sites.
12
+ */
13
+
14
+ export function currentSiteId(): string {
15
+ return process.env.AILK_SITE_ID ?? "default";
16
+ }
@@ -8,9 +8,13 @@
8
8
  * '@working-theory/stripe-client' — single chokepoint for AILK-internal ergonomics
9
9
  * (logger context, request-id propagation) without touching every consumer.
10
10
  */
11
+ import { getStripeClient } from "@working-theory/stripe-client";
12
+
11
13
  export {
12
14
  assertSandboxKey,
15
+ createBillingPortalSession,
13
16
  createCheckoutSession,
17
+ createQuotedCheckoutSession,
14
18
  getStripeClient,
15
19
  STRIPE_API_VERSION,
16
20
  StripeConfigError,
@@ -21,10 +25,28 @@ export {
21
25
  } from "@working-theory/stripe-client";
22
26
 
23
27
  export type {
28
+ CreateBillingPortalSessionInput,
29
+ CreateBillingPortalSessionOutput,
24
30
  CreateCheckoutSessionInput,
25
31
  CreateCheckoutSessionOutput,
26
32
  CreateCheckoutSessionParams,
33
+ CreateQuotedCheckoutSessionInput,
34
+ CreateQuotedCheckoutSessionOutput,
27
35
  Stripe,
28
36
  StripeCheckoutSession,
29
37
  StripeWebhookEvent,
30
38
  } from "@working-theory/stripe-client";
39
+
40
+ /**
41
+ * Expire a Checkout Session (#4912 FR7/D7).
42
+ *
43
+ * The compensation the recommender's attach-miss branch owes: the Stripe
44
+ * session already exists, its id never reached the `FlowCheckout` row, and
45
+ * nothing will ever join the two — so the session is closed rather than left
46
+ * live and payable (#4863). Built on the already-re-exported
47
+ * `getStripeClient`, whose construction asserts the `sk_test_` allow-list, so
48
+ * this call inherits the test-mode gate like every other Stripe call here.
49
+ */
50
+ export async function expireCheckoutSession(stripeSessionId: string): Promise<void> {
51
+ await getStripeClient().checkout.sessions.expire(stripeSessionId);
52
+ }
@@ -0,0 +1,225 @@
1
+ // oss: false
2
+ /**
3
+ * Tenant-scoped data-access boundary (the multi-tenant module's scope layer —
4
+ * FEAT-042, design decision (a)).
5
+ *
6
+ * Every domain-content query (agent_sessions, conversation_events,
7
+ * session_read_receipts, artifacts, artifact_versions) goes through
8
+ * `tenantDb(tenant)`, which injects the resolved `TenantContext.workspaceId`
9
+ * into EVERY where/data clause. There is deliberately no method that accepts
10
+ * a caller-supplied workspaceId, and lookups by id are `findFirst({ id,
11
+ * workspaceId })` — never `findUnique({ id })` — so a foreign-tenant row is
12
+ * indistinguishable from a missing one.
13
+ *
14
+ * Raw `prisma.<domainModel>` access outside this file and
15
+ * `../services/tenant-context.ts` is forbidden by the tenant-scope
16
+ * `forbidden_pattern` in architecture.yaml (CI: `pnpm architecture`).
17
+ */
18
+ import type {
19
+ AgentSession,
20
+ Artifact,
21
+ ConversationEvent,
22
+ Prisma,
23
+ SessionReadReceipt,
24
+ WorkspaceSEO,
25
+ WorkspaceSiteIdentity,
26
+ WorkspaceTheme,
27
+ } from "@working-theory/database";
28
+ import type { Id } from "@working-theory/ids";
29
+
30
+ import type { TenantContext } from "../services/tenant-context.js";
31
+
32
+ import { prisma } from "./prisma.js";
33
+
34
+ /** Explicit surface (declaration-emit portability + a reviewable contract). */
35
+ export interface TenantDb {
36
+ agentSession: {
37
+ list(limit: number): Promise<AgentSession[]>;
38
+ byId(id: Id<"agentSession">): Promise<AgentSession | null>;
39
+ create(data: { title?: string | null }): Promise<AgentSession>;
40
+ updateDraft(
41
+ id: Id<"agentSession">,
42
+ draftMessage: string | null,
43
+ ): Promise<{ count: number }>;
44
+ };
45
+ conversationEvent: {
46
+ listSince(
47
+ sessionId: Id<"agentSession">,
48
+ sinceSeq: number,
49
+ limit: number,
50
+ ): Promise<ConversationEvent[]>;
51
+ };
52
+ readReceipt: {
53
+ listForUser(): Promise<Array<Pick<SessionReadReceipt, "sessionId" | "lastReadSeq">>>;
54
+ upsert(
55
+ sessionId: Id<"agentSession">,
56
+ lastReadSeq: number,
57
+ ): Promise<SessionReadReceipt>;
58
+ };
59
+ artifact: {
60
+ listBySession(sessionId: Id<"agentSession">): Promise<Artifact[]>;
61
+ byId(id: Id<"artifact">): Promise<Artifact | null>;
62
+ };
63
+ artifactVersion: {
64
+ listByArtifact(
65
+ artifactId: Id<"artifact">,
66
+ ): Promise<
67
+ Array<{ id: Id<"artifactVersion">; savedAt: Date; isCurrent: boolean }>
68
+ >;
69
+ };
70
+ // Per-workspace site config (#3634). Each model is `workspaceId @unique`, so
71
+ // the unique key IS the tenant scope — `findUnique`/`upsert` on
72
+ // `{ workspaceId }` never accepts a caller-supplied scope.
73
+ theme: {
74
+ get(): Promise<WorkspaceTheme | null>;
75
+ upsert(data: unknown): Promise<WorkspaceTheme>;
76
+ };
77
+ siteIdentity: {
78
+ get(): Promise<WorkspaceSiteIdentity | null>;
79
+ upsert(data: unknown): Promise<WorkspaceSiteIdentity>;
80
+ };
81
+ seo: {
82
+ get(): Promise<WorkspaceSEO | null>;
83
+ upsert(data: unknown): Promise<WorkspaceSEO>;
84
+ };
85
+ }
86
+
87
+ export function tenantDb(tenant: TenantContext): TenantDb {
88
+ const { workspaceId, userId } = tenant;
89
+
90
+ return {
91
+ agentSession: {
92
+ list(limit: number) {
93
+ return prisma.agentSession.findMany({
94
+ where: { workspaceId },
95
+ orderBy: [
96
+ { lastMessageAt: { sort: "desc", nulls: "last" } },
97
+ { createdAt: "desc" },
98
+ ],
99
+ take: limit,
100
+ });
101
+ },
102
+ byId(id: string) {
103
+ return prisma.agentSession.findFirst({
104
+ where: { id, workspaceId },
105
+ });
106
+ },
107
+ create(data: { title?: string | null; agentId?: string }) {
108
+ return prisma.agentSession.create({
109
+ data: { title: data.title ?? null, workspaceId },
110
+ });
111
+ },
112
+ updateDraft(id: string, draftMessage: string | null) {
113
+ return prisma.agentSession.updateMany({
114
+ where: { id, workspaceId },
115
+ data: { draftMessage },
116
+ });
117
+ },
118
+ },
119
+
120
+ conversationEvent: {
121
+ listSince(sessionId: string, sinceSeq: number, limit: number) {
122
+ return prisma.conversationEvent.findMany({
123
+ where: { sessionId, workspaceId, seq: { gt: sinceSeq } },
124
+ orderBy: { seq: "asc" },
125
+ take: limit,
126
+ });
127
+ },
128
+ },
129
+
130
+ readReceipt: {
131
+ /** Read positions for the CALLING user across this workspace's sessions. */
132
+ listForUser() {
133
+ return prisma.sessionReadReceipt.findMany({
134
+ where: { workspaceId, userId },
135
+ select: { sessionId: true, lastReadSeq: true },
136
+ });
137
+ },
138
+ upsert(sessionId: string, lastReadSeq: number) {
139
+ return prisma.sessionReadReceipt.upsert({
140
+ where: { sessionId_userId: { sessionId, userId } },
141
+ create: { sessionId, workspaceId, userId, lastReadSeq },
142
+ update: { lastReadSeq },
143
+ });
144
+ },
145
+ },
146
+
147
+ artifact: {
148
+ listBySession(sessionId: string) {
149
+ return prisma.artifact.findMany({
150
+ where: { sessionId, workspaceId },
151
+ orderBy: { createdAt: "asc" },
152
+ });
153
+ },
154
+ byId(id: string) {
155
+ return prisma.artifact.findFirst({ where: { id, workspaceId } });
156
+ },
157
+ },
158
+
159
+ artifactVersion: {
160
+ listByArtifact(artifactId: string) {
161
+ // Brand assertion at the Prisma read edge (the DB CHECK constraints
162
+ // guarantee the id shape).
163
+ return prisma.artifactVersion.findMany({
164
+ where: { artifactId, workspaceId },
165
+ orderBy: { savedAt: "desc" },
166
+ select: {
167
+ id: true,
168
+ savedAt: true,
169
+ isCurrent: true,
170
+ },
171
+ }) as Promise<
172
+ Array<{ id: Id<"artifactVersion">; savedAt: Date; isCurrent: boolean }>
173
+ >;
174
+ },
175
+ },
176
+
177
+ // Site config — scoped by the `@unique` workspaceId (the unique key IS the
178
+ // scope). The validated blob is stored in the `data` Json column; validation
179
+ // happens at the route boundary against the shared @working-theory/site-config
180
+ // schema, so the DB layer stores an already-validated object.
181
+ theme: {
182
+ get() {
183
+ return prisma.workspaceTheme.findUnique({ where: { workspaceId } });
184
+ },
185
+ upsert(data: unknown) {
186
+ const json = data as Prisma.InputJsonValue;
187
+ return prisma.workspaceTheme.upsert({
188
+ where: { workspaceId },
189
+ create: { workspaceId, data: json },
190
+ update: { data: json },
191
+ });
192
+ },
193
+ },
194
+
195
+ siteIdentity: {
196
+ get() {
197
+ return prisma.workspaceSiteIdentity.findUnique({
198
+ where: { workspaceId },
199
+ });
200
+ },
201
+ upsert(data: unknown) {
202
+ const json = data as Prisma.InputJsonValue;
203
+ return prisma.workspaceSiteIdentity.upsert({
204
+ where: { workspaceId },
205
+ create: { workspaceId, data: json },
206
+ update: { data: json },
207
+ });
208
+ },
209
+ },
210
+
211
+ seo: {
212
+ get() {
213
+ return prisma.workspaceSEO.findUnique({ where: { workspaceId } });
214
+ },
215
+ upsert(data: unknown) {
216
+ const json = data as Prisma.InputJsonValue;
217
+ return prisma.workspaceSEO.upsert({
218
+ where: { workspaceId },
219
+ create: { workspaceId, data: json },
220
+ update: { data: json },
221
+ });
222
+ },
223
+ },
224
+ };
225
+ }
@@ -0,0 +1,91 @@
1
+ // oss: false
2
+ /**
3
+ * Short-lived stream tokens (BACKEND-REQUIREMENTS R-T3): the push channel is
4
+ * authenticated by a per-session token minted over REST, not the ambient
5
+ * cookie. Compact HMAC-SHA256 JWT via node:crypto — no new dependency; the
6
+ * Phase-6 realtime server verifies with `verifyWsToken`.
7
+ *
8
+ * Secret resolution mirrors apps/web's Better Auth pattern: AILK_WS_TOKEN_SECRET
9
+ * wins, BETTER_AUTH_SECRET is the shared fallback, and a dev-only default
10
+ * keeps the OSS zero-config boot working. Production deployments MUST set one
11
+ * of the two env vars.
12
+ */
13
+ import { createHmac, timingSafeEqual } from "node:crypto";
14
+
15
+ import { getClock } from "@working-theory/common";
16
+ import type { Id } from "@working-theory/ids";
17
+
18
+ export const WS_TOKEN_TTL_SECONDS = 60;
19
+
20
+ export interface WsTokenClaims {
21
+ /** Caller user id. */
22
+ sub: Id<"user">;
23
+ /** Agent-session id the stream is scoped to. */
24
+ sid: Id<"agentSession">;
25
+ /** Workspace id the session belongs to (tenant scope). */
26
+ wid: Id<"workspace">;
27
+ iat: number;
28
+ exp: number;
29
+ }
30
+
31
+ function resolveSecret(): string {
32
+ const secret =
33
+ process.env.AILK_WS_TOKEN_SECRET ?? process.env.BETTER_AUTH_SECRET;
34
+ if (secret) return secret;
35
+ // Fail CLOSED in production (security review M2): a token-minting path
36
+ // must never run on a guessable default secret.
37
+ if (process.env.NODE_ENV === "production") {
38
+ throw new Error(
39
+ "ws-token: set AILK_WS_TOKEN_SECRET or BETTER_AUTH_SECRET in production",
40
+ );
41
+ }
42
+ return "ailk-dev-secret-do-not-use-in-production";
43
+ }
44
+
45
+ function b64url(input: Buffer | string): string {
46
+ return Buffer.from(input).toString("base64url");
47
+ }
48
+
49
+ function sign(payload: string): string {
50
+ return createHmac("sha256", resolveSecret()).update(payload).digest("base64url");
51
+ }
52
+
53
+ /** Mint a short-lived stream token for (user, session, workspace). */
54
+ export function mintWsToken(params: {
55
+ userId: Id<"user">;
56
+ sessionId: Id<"agentSession">;
57
+ workspaceId: Id<"workspace">;
58
+ }): { token: string; expiresAt: string } {
59
+ const nowSec = Math.floor(getClock().now() / 1000);
60
+ const claims: WsTokenClaims = {
61
+ sub: params.userId,
62
+ sid: params.sessionId,
63
+ wid: params.workspaceId,
64
+ iat: nowSec,
65
+ exp: nowSec + WS_TOKEN_TTL_SECONDS,
66
+ };
67
+ const head = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
68
+ const body = b64url(JSON.stringify(claims));
69
+ const token = `${head}.${body}.${sign(`${head}.${body}`)}`;
70
+ return { token, expiresAt: new Date(claims.exp * 1000).toISOString() };
71
+ }
72
+
73
+ /** Verify a stream token; returns claims or null (invalid / expired). */
74
+ export function verifyWsToken(token: string): WsTokenClaims | null {
75
+ const [head, body, sig] = token.split(".");
76
+ if (!head || !body || !sig) return null;
77
+ const expected = sign(`${head}.${body}`);
78
+ const a = Buffer.from(sig);
79
+ const b = Buffer.from(expected);
80
+ if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
81
+ try {
82
+ const claims = JSON.parse(
83
+ Buffer.from(body, "base64url").toString("utf-8"),
84
+ ) as WsTokenClaims;
85
+ if (typeof claims.exp !== "number") return null;
86
+ if (claims.exp * 1000 <= getClock().now()) return null;
87
+ return claims;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
@@ -72,7 +72,13 @@ describe("S13 adversarial — session fixation", () => {
72
72
  expect(res.statusCode).toBe(401);
73
73
  expect(prisma.session.findUnique).toHaveBeenCalledWith({
74
74
  where: { token: "pre_rotation_token" },
75
- select: { userId: true, expiresAt: true },
75
+ select: {
76
+ id: true,
77
+ userId: true,
78
+ expiresAt: true,
79
+ activeOrganizationId: true,
80
+ activeWorkspaceId: true,
81
+ },
76
82
  });
77
83
  });
78
84
 
@@ -72,7 +72,13 @@ describe("auth preHandler — cookie path", () => {
72
72
  // The signature suffix must be stripped before the Session lookup.
73
73
  expect(prisma.session.findUnique).toHaveBeenCalledWith({
74
74
  where: { token: "sess_tok_abc" },
75
- select: { userId: true, expiresAt: true },
75
+ select: {
76
+ id: true,
77
+ userId: true,
78
+ expiresAt: true,
79
+ activeOrganizationId: true,
80
+ activeWorkspaceId: true,
81
+ },
76
82
  });
77
83
  });
78
84