create-ailk 0.1.0 → 0.2.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 (620) hide show
  1. package/README.md +124 -3
  2. package/component-catalog.md +456 -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 +12 -5
  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 +78 -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 +241 -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 +558 -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 +869 -0
  115. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
  116. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
  118. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
  119. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
  120. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
  121. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
  122. package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
  123. package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
  124. package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
  125. package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
  126. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
  127. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
  128. package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
  129. package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
  130. package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
  131. package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
  132. package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
  133. package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
  134. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
  135. package/templates/apps/api/src/routes/schedule/index.ts +249 -0
  136. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
  137. package/templates/apps/api/src/routes/slack/actions.ts +177 -0
  138. package/templates/apps/api/src/routes/slack/index.ts +38 -0
  139. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
  140. package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
  141. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
  142. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
  143. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
  144. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
  145. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
  146. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
  147. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
  148. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
  149. package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
  150. package/templates/apps/api/src/routes/webhooks/README.md +35 -12
  151. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
  152. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
  153. package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
  154. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
  155. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
  156. package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
  157. package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
  158. package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
  159. package/templates/apps/api/src/server.ts +307 -7
  160. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
  161. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
  162. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
  163. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
  164. package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
  165. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
  166. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
  167. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
  168. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
  169. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
  170. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
  171. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
  172. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
  173. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
  174. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
  175. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
  176. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
  177. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
  178. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
  179. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
  180. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
  181. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
  182. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
  183. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
  184. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
  185. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
  186. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
  187. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
  188. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
  189. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
  190. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
  191. package/templates/apps/api/src/services/abandon-email.ts +307 -0
  192. package/templates/apps/api/src/services/aeo-score.ts +288 -0
  193. package/templates/apps/api/src/services/answer-distributions.ts +138 -0
  194. package/templates/apps/api/src/services/consent.ts +236 -0
  195. package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
  196. package/templates/apps/api/src/services/entitlements.ts +102 -0
  197. package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
  198. package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
  199. package/templates/apps/api/src/services/flow-engine.ts +1296 -0
  200. package/templates/apps/api/src/services/lead-promotion.ts +176 -0
  201. package/templates/apps/api/src/services/lead-routing.ts +3 -3
  202. package/templates/apps/api/src/services/listing-config.ts +102 -0
  203. package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
  204. package/templates/apps/api/src/services/listing-promotion.ts +342 -0
  205. package/templates/apps/api/src/services/playbook-compile.ts +398 -0
  206. package/templates/apps/api/src/services/playbook-render.ts +263 -0
  207. package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
  208. package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
  209. package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
  210. package/templates/apps/api/src/services/project-listings.ts +1472 -0
  211. package/templates/apps/api/src/services/recommender-capture.ts +835 -0
  212. package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
  213. package/templates/apps/api/src/services/scheduling/index.ts +63 -0
  214. package/templates/apps/api/src/services/scheduling/types.ts +88 -0
  215. package/templates/apps/api/src/services/slack-notify.ts +240 -0
  216. package/templates/apps/api/src/services/tenant-context.ts +451 -0
  217. package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
  218. package/templates/apps/api/src/services/usage-credits.ts +340 -0
  219. package/templates/apps/api/src/services/usage-metering.ts +699 -0
  220. package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
  221. package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
  222. package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
  223. package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
  224. package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
  225. package/templates/apps/api/src/vercel-handler.ts +165 -0
  226. package/templates/apps/api/tsconfig.json +0 -12
  227. package/templates/apps/mcp/CLAUDE.md +19 -9
  228. package/templates/apps/mcp/LICENSE +201 -0
  229. package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
  230. package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
  231. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
  232. package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
  233. package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
  234. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
  235. package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
  236. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
  237. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
  238. package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
  239. package/templates/apps/mcp/package.json +6 -4
  240. package/templates/apps/mcp/src/config/modules.ts +37 -0
  241. package/templates/apps/mcp/src/server.ts +109 -18
  242. package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
  243. package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
  244. package/templates/apps/mcp/src/tools/create_page.ts +1 -0
  245. package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
  246. package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
  247. package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
  248. package/templates/apps/mcp/src/tools/index.ts +25 -0
  249. package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
  250. package/templates/apps/mcp/src/tools/purchase.ts +3 -0
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
  252. package/templates/apps/mcp/src/tools/update_page.ts +1 -0
  253. package/templates/apps/mcp/tsconfig.json +1 -9
  254. package/templates/apps/web/.env.example +44 -0
  255. package/templates/apps/web/CLAUDE.md +28 -0
  256. package/templates/apps/web/LICENSE +201 -0
  257. package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
  258. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
  259. package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
  260. package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
  261. package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
  262. package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
  263. package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
  264. package/templates/apps/web/__tests__/setup.ts +22 -32
  265. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
  266. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
  267. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
  268. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
  269. package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
  270. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
  271. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
  272. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
  273. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
  274. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
  275. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
  276. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
  277. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
  278. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
  279. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
  280. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
  281. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
  282. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
  283. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
  284. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
  285. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
  286. package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
  287. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
  288. package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
  289. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
  290. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
  291. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  292. package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
  293. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
  294. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  295. package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
  296. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
  297. package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
  298. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
  299. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
  300. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
  301. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
  302. package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
  303. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
  304. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
  305. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
  306. package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
  307. package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
  308. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
  309. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  310. package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
  311. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
  312. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
  313. package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
  314. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
  315. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
  316. package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
  317. package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
  318. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
  319. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
  320. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
  321. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
  322. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
  323. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
  324. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
  325. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
  326. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
  327. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
  328. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
  329. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
  330. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
  331. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
  332. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
  333. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
  334. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  335. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
  336. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  337. package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
  338. package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
  339. package/templates/apps/web/app/[locale]/layout.tsx +127 -16
  340. package/templates/apps/web/app/[locale]/page.tsx +10 -1
  341. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
  342. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  343. package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
  344. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
  345. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  346. package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
  347. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
  348. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
  349. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
  350. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
  351. package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
  352. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
  353. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
  354. package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
  355. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
  356. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
  357. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
  358. package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
  359. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
  360. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  361. package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
  362. package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
  363. package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
  364. package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
  365. package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
  366. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
  367. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
  368. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  369. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
  370. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  371. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
  372. package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
  373. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
  374. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
  375. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
  376. package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
  377. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
  378. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  379. package/templates/apps/web/app/globals.css +35 -0
  380. package/templates/apps/web/app/llms.txt/route.ts +2 -1
  381. package/templates/apps/web/app/og/route.tsx +10 -2
  382. package/templates/apps/web/auth.ts +8 -0
  383. package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
  384. package/templates/apps/web/content/en/blog/README.txt +25 -0
  385. package/templates/apps/web/content/en/docs/index.mdx +28 -0
  386. package/templates/apps/web/jest.config.cjs +90 -27
  387. package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
  388. package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
  389. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
  390. package/templates/apps/web/lib/account-config.ts +13 -0
  391. package/templates/apps/web/lib/server-api.ts +85 -0
  392. package/templates/apps/web/lib/site-brand.tsx +121 -0
  393. package/templates/apps/web/lib/site-theme.ts +74 -0
  394. package/templates/apps/web/messages/en.json +275 -7
  395. package/templates/apps/web/middleware.ts +11 -3
  396. package/templates/apps/web/next.config.mjs +8 -2
  397. package/templates/apps/web/package.json +17 -7
  398. package/templates/apps/web/postcss.config.mjs +23 -0
  399. package/templates/apps/web/public/.well-known/security.txt +3 -3
  400. package/templates/apps/web/src/lib/routes.ts +15 -2
  401. package/templates/apps/web/tsconfig.json +1 -16
  402. package/templates/content/_site.mdx +36 -25
  403. package/templates/content/en/AboutPage/about.mdx +24 -14
  404. package/templates/content/en/ContactPage/contact.mdx +23 -14
  405. package/templates/content/en/FAQPage/faq.mdx +10 -392
  406. package/templates/content/en/HomePage/home.mdx +14 -285
  407. package/templates/database/CHANGELOG.md +443 -0
  408. package/templates/database/LICENSE +201 -0
  409. package/templates/database/README.md +3 -1
  410. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
  411. package/templates/database/__tests__/flows-presets.test.ts +173 -0
  412. package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
  413. package/templates/database/__tests__/seed.test.ts +88 -0
  414. package/templates/database/__tests__/user-cascade.test.ts +19 -4
  415. package/templates/database/auth/schema.prisma +521 -0
  416. package/templates/database/config/schema.prisma +234 -0
  417. package/templates/database/content/schema.prisma +263 -0
  418. package/templates/database/flows/listing-diagnostic.ts +182 -0
  419. package/templates/database/flows/presets.ts +315 -0
  420. package/templates/database/inbox/__tests__/schema.test.ts +4 -0
  421. package/templates/database/inbox/schema.prisma +574 -3
  422. package/templates/database/jest.config.cjs +8 -0
  423. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
  424. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
  425. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
  426. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
  427. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
  428. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
  429. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
  430. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
  431. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
  432. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
  433. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
  434. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
  435. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
  436. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
  437. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
  438. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
  439. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
  440. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
  441. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
  442. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
  443. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
  444. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
  445. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
  446. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
  447. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
  448. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
  449. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
  450. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
  451. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
  452. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
  453. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
  454. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
  455. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
  456. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
  457. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
  458. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
  459. package/templates/database/ops/schema.prisma +27 -0
  460. package/templates/database/package.json +42 -4
  461. package/templates/database/prisma.config.ts +1 -1
  462. package/templates/database/schema.prisma +19 -435
  463. package/templates/database/seed-test.ts +14 -4
  464. package/templates/database/seed.ts +148 -0
  465. package/templates/database/tenancy.ts +45 -0
  466. package/templates/database/tsconfig.flows.json +15 -0
  467. package/templates/database/tsconfig.json +7 -1
  468. package/templates/database/tsconfig.tenancy.json +14 -0
  469. package/templates/package.json +30 -55
  470. package/templates/pnpm-workspace.yaml +0 -3
  471. package/templates/project.yaml +1 -1
  472. package/templates/tsconfig.json +1 -37
  473. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
  474. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
  475. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
  476. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
  477. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
  478. package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
  479. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
  480. package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
  481. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
  482. package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
  483. package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
  484. package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
  485. package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
  486. package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
  487. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
  488. package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
  489. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
  490. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
  491. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
  492. package/templates/apps/web/__tests__/page.test.tsx +0 -230
  493. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
  494. package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
  495. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
  496. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
  497. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
  498. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
  499. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
  500. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
  501. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
  502. package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
  503. package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
  504. package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
  505. package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
  506. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
  507. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
  508. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
  509. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
  510. package/templates/apps/web/content/en/blog/index.mdx +0 -15
  511. package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
  512. package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
  513. package/templates/content/.gitkeep +0 -0
  514. package/templates/content/CLAUDE.md +0 -85
  515. package/templates/content/ar/HomePage/home.mdx +0 -123
  516. package/templates/content/ar/_site.mdx +0 -35
  517. package/templates/content/de/_site.mdx +0 -29
  518. package/templates/content/en/.gitkeep +0 -0
  519. package/templates/content/en/AboutPage/about.schema.json +0 -82
  520. package/templates/content/en/BookCall/book-call.mdx +0 -19
  521. package/templates/content/en/BookCall/book-call.schema.json +0 -121
  522. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
  523. package/templates/content/en/CareersList/careers.mdx +0 -24
  524. package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
  525. package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
  526. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
  527. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
  528. package/templates/content/en/ClientsList/clients.mdx +0 -38
  529. package/templates/content/en/ContactPage/contact.schema.json +0 -100
  530. package/templates/content/en/ContactPoint/primary.mdx +0 -14
  531. package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
  532. package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
  533. package/templates/content/en/CoursesList/courses.mdx +0 -22
  534. package/templates/content/en/DownloadsList/downloads.mdx +0 -22
  535. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
  536. package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
  537. package/templates/content/en/EventsList/events.mdx +0 -26
  538. package/templates/content/en/FAQPage/faq.schema.json +0 -231
  539. package/templates/content/en/GlossaryList/glossary.mdx +0 -19
  540. package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
  541. package/templates/content/en/HomePage/home.schema.json +0 -88
  542. package/templates/content/en/HomePage/launch-service.mdx +0 -475
  543. package/templates/content/en/HomePage/launch-service.schema.json +0 -138
  544. package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
  545. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
  546. package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
  547. package/templates/content/en/Integration/github-actions.mdx +0 -31
  548. package/templates/content/en/Integration/openai-api.mdx +0 -26
  549. package/templates/content/en/Integration/vercel.mdx +0 -32
  550. package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
  551. package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
  552. package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
  553. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
  554. package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
  555. package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
  556. package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
  557. package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
  558. package/templates/content/en/LandingPage/developers.mdx +0 -19
  559. package/templates/content/en/LandingPage/developers.schema.json +0 -109
  560. package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
  561. package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
  562. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
  563. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
  564. package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
  565. package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
  566. package/templates/content/en/LandingPage/platform.mdx +0 -369
  567. package/templates/content/en/LandingPage/platform.schema.json +0 -166
  568. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
  569. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
  570. package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
  571. package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
  572. package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
  573. package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
  574. package/templates/content/en/Organization/site-owner.mdx +0 -16
  575. package/templates/content/en/Partner/prisma.mdx +0 -31
  576. package/templates/content/en/Partner/supabase.mdx +0 -30
  577. package/templates/content/en/Partner/vercel.mdx +0 -28
  578. package/templates/content/en/PartnersList/partners.mdx +0 -22
  579. package/templates/content/en/Person/founder.mdx +0 -18
  580. package/templates/content/en/Pricing/pricing.mdx +0 -538
  581. package/templates/content/en/Pricing/pricing.schema.json +0 -209
  582. package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
  583. package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
  584. package/templates/content/en/Product/ailk.mdx +0 -19
  585. package/templates/content/en/Product/ailk.schema.json +0 -94
  586. package/templates/content/en/Product/api-launch-kit.mdx +0 -26
  587. package/templates/content/en/Product/launch-kit.mdx +0 -28
  588. package/templates/content/en/ProductsList/products.mdx +0 -26
  589. package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
  590. package/templates/content/en/ProjectsList/projects.mdx +0 -25
  591. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
  592. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
  593. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
  594. package/templates/content/en/ResourcesHub/resources.mdx +0 -22
  595. package/templates/content/en/Service/consulting.mdx +0 -22
  596. package/templates/content/en/Service/implementation.mdx +0 -30
  597. package/templates/content/en/Service/launch-service.mdx +0 -232
  598. package/templates/content/en/Service/launch.mdx +0 -228
  599. package/templates/content/en/ServicesList/services.mdx +0 -84
  600. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
  601. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
  602. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
  603. package/templates/content/en/TeamList/team.mdx +0 -63
  604. package/templates/content/en/TeamMember/founder.mdx +0 -72
  605. package/templates/content/en/TermsPage/terms.mdx +0 -19
  606. package/templates/content/en/TermsPage/terms.schema.json +0 -99
  607. package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
  608. package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
  609. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
  610. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
  611. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
  612. package/templates/content/en-XA/AboutPage/about.mdx +0 -8
  613. package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
  614. package/templates/content/en-XA/HomePage/home.mdx +0 -8
  615. package/templates/content/es/_site.mdx +0 -30
  616. package/templates/content/fr/_site.mdx +0 -30
  617. package/templates/content/ja/_site.mdx +0 -30
  618. package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
  619. package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
  620. package/templates/content/pt-BR/HomePage/home.mdx +0 -23
@@ -5,9 +5,50 @@
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 consentGrant (its `createMany` append-only writer + `findMany`
25
+ * reader) — used by the #5275 consent service + project-listings draft tests
26
+ * inbox model waitlistFollowUpSend (its `create` stamp writer + `findMany`
27
+ * idempotency reader) + callback.findMany (booked-call suppression) +
28
+ * waitlistScore.findMany (tier lookup) — used by the #4732
29
+ * waitlist-followup sweep service tests
30
+ * tenancy models (workspace, membership) — used by tenant-context service tests (#3627)
31
+ * membership.findMany + lead.findMany/updateMany — used by lead-promotion
32
+ * service tests (#3586); lead-promotion.ts never queries membership/workspace
33
+ * directly (it resolves workspaces via tenant-context.ts's listTenancy), so
34
+ * membership.findMany's mocked return shape carries the nested
35
+ * organization.workspaces payload listTenancy expects.
8
36
  *
9
37
  * Per-test return values: import `prisma` from this module and call
10
38
  * (prisma.lead.create as jest.Mock).mockResolvedValue({ id: "..." })
39
+ *
40
+ * usageEvent/usagePeriod (content) + llmModel/llmModelPricing/aiMarkupConfig
41
+ * (config) — used by usage-metering service tests (#3713, FEAT-047 P2.S1).
42
+ * These FEAT-047 P1 models (#3709/#3710) are not yet in the generated Prisma
43
+ * client `usage-metering.ts` compiles against; it casts `prisma` to a local
44
+ * structural delegate type, and this mock provides the matching runtime
45
+ * shape so the mocked-prisma tests can drive it end to end.
46
+ *
47
+ * usageEvent.aggregate + usageBillingReport (content) — used by
48
+ * usage-aggregation service tests (#3714, FEAT-047 P2.S2; `aggregate` since
49
+ * #3739 M2 — was `findMany` + a JS accumulation loop). Same not-yet-
50
+ * generated-client situation; `usage-aggregation.ts` casts `prisma` to its
51
+ * own structural delegate type, and this mock's runtime shape matches it.
11
52
  */
12
53
 
13
54
  export const prisma = {
@@ -28,18 +69,218 @@ export const prisma = {
28
69
  upsert: jest.fn(),
29
70
  deleteMany: jest.fn(),
30
71
  },
72
+ // ── Tenancy namespace (Organization, Workspace, Membership) ──────────────
73
+ organization: {
74
+ findUnique: jest.fn(),
75
+ findFirst: jest.fn(),
76
+ update: jest.fn(),
77
+ // `updateMany` is the credit waterfall's compare-and-swap write (#3730 H1):
78
+ // usage-credits.ts guards its `where` on every column value the read
79
+ // observed, so a concurrent writer makes the predicate miss and the update
80
+ // matches 0 rows. `update` stays mocked beside it — usage-credits.test.ts
81
+ // models it as an unconditional auto-commit so the PRE-fix read-then-write
82
+ // path can still run and double-spend; that asymmetry is what makes the
83
+ // atomicity test load-bearing rather than an API-shape check.
84
+ updateMany: jest.fn(),
85
+ },
86
+ workspace: {
87
+ findUnique: jest.fn(),
88
+ count: jest.fn(),
89
+ },
90
+ membership: {
91
+ findUnique: jest.fn(),
92
+ findMany: jest.fn(),
93
+ create: jest.fn(),
94
+ },
95
+ // ── Config namespace (per-workspace site config — #3641/#3634) ───────────
96
+ workspaceTheme: {
97
+ findUnique: jest.fn(),
98
+ upsert: jest.fn(),
99
+ deleteMany: jest.fn(),
100
+ },
101
+ workspaceSiteIdentity: {
102
+ findUnique: jest.fn(),
103
+ upsert: jest.fn(),
104
+ deleteMany: jest.fn(),
105
+ },
106
+ workspaceSEO: {
107
+ findUnique: jest.fn(),
108
+ upsert: jest.fn(),
109
+ deleteMany: jest.fn(),
110
+ },
31
111
  // ── Inbox namespace (Lead, Subscriber, Callback) ─────────────────────────
32
112
  lead: {
33
113
  create: jest.fn(),
114
+ // `findUnique` — used by the deliverable-resend service (#3998) to
115
+ // resolve a grant's stored lead email.
116
+ findUnique: jest.fn(),
117
+ findMany: jest.fn(),
118
+ updateMany: jest.fn(),
34
119
  },
35
120
  subscriber: {
36
121
  create: jest.fn(),
37
122
  findUnique: jest.fn(),
38
123
  },
124
+ // `findMany` — the #4732 follow-up sweep's booked-call suppression check
125
+ // (D2: a site's Callback rows fetched once per sweep run, filtered in
126
+ // application code for `payload.email` matches).
39
127
  callback: {
40
128
  create: jest.fn(),
129
+ findMany: jest.fn(),
130
+ },
131
+ // ── Inbox namespace (FlowDefinition, FlowSession — #3956) ────────────────
132
+ // findMany on both models — used by the abandon-email sweep (#3962 D4),
133
+ // which enumerates every FlowDefinition (to read its live abandonEmail
134
+ // policy) and every candidate FlowSession per definition.
135
+ flowDefinition: {
136
+ findUnique: jest.fn(),
137
+ findMany: jest.fn(),
138
+ upsert: jest.fn(),
139
+ },
140
+ // `deleteMany` — used by the FlowSession retention/cleanup sweep
141
+ // (flow-engine.ts's `cleanupAbandonedSessions`, #3974 AC3).
142
+ flowSession: {
143
+ create: jest.fn(),
144
+ findUnique: jest.fn(),
145
+ findMany: jest.fn(),
146
+ update: jest.fn(),
147
+ deleteMany: jest.fn(),
148
+ },
149
+ // ── Inbox namespace (DeliverableAsset, DeliverableGrant — #3961) ─────────
150
+ deliverableAsset: {
151
+ findUnique: jest.fn(),
152
+ upsert: jest.fn(),
153
+ },
154
+ // `update` — the send-outcome stamp (#3998 D1). `findMany` — the
155
+ // deliverable:resend CLI's list mode (#3998 D4), selecting on
156
+ // `sendStatus: null`.
157
+ deliverableGrant: {
158
+ create: jest.fn(),
159
+ findUnique: jest.fn(),
160
+ update: jest.fn(),
161
+ findMany: jest.fn(),
162
+ },
163
+ // ── Inbox namespace (FlowCheckout — #4837 D4) ─────────────────────────────
164
+ // `create` — recordCheckout's one write; `update` — attachStripeSession;
165
+ // `updateMany` — the webhook's guarded status stamp (#4838); `findUnique` /
166
+ // `findMany` — the webhook's amount read + the dashboard's checkedOut read
167
+ // (#4839).
168
+ flowCheckout: {
169
+ create: jest.fn(),
170
+ update: jest.fn(),
171
+ updateMany: jest.fn(),
172
+ findUnique: jest.fn(),
173
+ findMany: jest.fn(),
174
+ },
175
+ // ── Config namespace (SiteListingConfig — #5033) ─────────────────────────
176
+ // `findUnique` — the ONLY read path (`listingConfigFor`), keyed on siteId.
177
+ // Read server-side by the anonymous create handler and the public read; no
178
+ // route writes it, so no write delegate is mocked.
179
+ siteListingConfig: {
180
+ findUnique: jest.fn(),
181
+ },
182
+ // ── Inbox namespace (ProjectListing — #4998, reshaped #5033) ─────────────
183
+ // `create` — the POST route's row write (after routeLead's Lead write).
184
+ // `findFirst` — the tenant-scoped get/patch reads (scoped by siteId + id, a
185
+ // foreign id is not_found) and `decideListing`'s pre-write windowDays read.
186
+ // `findMany` — the tenant list, the anonymous public read, and
187
+ // `firstFreeSlug`'s site-scoped collision read. `updateMany` — the
188
+ // `decideListing` guarded compare-and-swap write (`where` includes
189
+ // `status: "requested"`, so a concurrent double-decide matches 0 rows on
190
+ // the loser) and `setListingFeatured`'s scoped flag write.
191
+ projectListing: {
192
+ create: jest.fn(),
193
+ findFirst: jest.fn(),
194
+ findMany: jest.fn(),
195
+ update: jest.fn(),
196
+ updateMany: jest.fn(),
197
+ },
198
+ // ── Inbox namespace (WaitlistExperiment — #4661 D5) ──────────────────────
199
+ // `upsert` — the ONLY write path into waitlist_experiments
200
+ // (upsertWaitlistExperiment). `findUnique` — read by the A2 scoring glue
201
+ // (scoreCompletedSignup, #4662 D6) to load the owning experiment's config.
202
+ // `findMany` — the #4771 flow-aggregate service's slug -> register
203
+ // resolver (a scoped-by-siteId read, matched in-memory against every
204
+ // waitlist's `flowSlug`).
205
+ waitlistExperiment: {
206
+ upsert: jest.fn(),
207
+ findUnique: jest.fn(),
208
+ findFirst: jest.fn(),
209
+ findMany: jest.fn(),
210
+ },
211
+ // ── Inbox namespace (WaitlistScore — #4662 D6) ───────────────────────────
212
+ // `upsert` — the ONLY write path into waitlist_scores (scoreCompletedSignup).
213
+ // `findMany` — the #4732 follow-up sweep's tier lookup, batched by leadId.
214
+ // `count` — the #4998 project-listings dashboard's waitlistCount join.
215
+ waitlistScore: {
216
+ upsert: jest.fn(),
217
+ count: jest.fn(),
218
+ findMany: jest.fn(),
219
+ },
220
+ // ── Inbox namespace (PlaybookInstance — FEAT-074 U4a #4715, extended U4b #4718) ──
221
+ // `create` — the ONLY write path into playbook_instances
222
+ // (compileAndStorePlaybook); append-only, never upsert/update. `findFirst`
223
+ // — the `playbook:` virtual-asset existence check
224
+ // (deliverable-fulfillment.ts, U4b D4) + the download route's
225
+ // newest-instance read (playbook-render route, U4b D4).
226
+ playbookInstance: {
227
+ create: jest.fn(),
228
+ findFirst: jest.fn(),
229
+ },
230
+ // ── Inbox namespace (WaitlistFollowUpSend — #4732) ───────────────────────
231
+ // `findMany` — the idempotency reader (already-sent (leadId, followUpKey)
232
+ // pairs). `create` — the send-attempt stamp ('sent' | 'failed'), written
233
+ // for every attempted send, never for a suppressed/skipped candidate.
234
+ waitlistFollowUpSend: {
235
+ findMany: jest.fn(),
236
+ create: jest.fn(),
237
+ },
238
+ // ── Inbox namespace (ListingCsvSend — #5001) ─────────────────────────────
239
+ // `findFirst` — the per-listing watermark read (newest `sentAt`). `create`
240
+ // — the post-send stamp, written only after a successful send so a failed
241
+ // send leaves those rows for next week's CSV.
242
+ listingCsvSend: {
243
+ findFirst: jest.fn(),
244
+ create: jest.fn(),
245
+ },
246
+ // ── Inbox namespace (ConsentGrant — #5275) ───────────────────────────────
247
+ // `createMany` — the ONLY write path into consent_grants (recordConsent /
248
+ // recordLegacyConsent); append-only, never update/upsert. `findMany` — the
249
+ // "what did this subject agree to" read.
250
+ consentGrant: {
251
+ createMany: jest.fn(),
252
+ findMany: jest.fn(),
253
+ },
254
+ // ── Content namespace (UsageEvent, UsagePeriod — FEAT-047 P1.S1/#3709) ──
255
+ usageEvent: {
256
+ create: jest.fn(),
257
+ aggregate: jest.fn(),
258
+ },
259
+ usagePeriod: {
260
+ upsert: jest.fn(),
261
+ },
262
+ // ── Content namespace (UsageBillingReport — FEAT-047 P1.S1/#3709) ────────
263
+ usageBillingReport: {
264
+ upsert: jest.fn(),
265
+ },
266
+ // ── Config namespace (LlmModel, LlmModelPricing, AiMarkupConfig — FEAT-047 P1.S2/#3710) ──
267
+ llmModel: {
268
+ findFirst: jest.fn(),
269
+ },
270
+ llmModelPricing: {
271
+ findFirst: jest.fn(),
272
+ },
273
+ aiMarkupConfig: {
274
+ findFirst: jest.fn(),
41
275
  },
42
276
  // ── Lifecycle ────────────────────────────────────────────────────────────
277
+ // `$transaction` (interactive form) — usage-metering.ts writes the UsageEvent
278
+ // and the UsagePeriod aggregate atomically (#3727 H3: a non-atomic pair lets
279
+ // a failed upsert strand a persisted event that a retry then double-bills).
280
+ // Tests supply the per-case implementation; usage-metering.test.ts's
281
+ // `mockTransaction` helper models commit/rollback so atomicity is asserted,
282
+ // not assumed.
283
+ $transaction: jest.fn(),
43
284
  $connect: jest.fn(),
44
285
  $disconnect: jest.fn(),
45
286
  };
@@ -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