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
@@ -11,21 +11,30 @@
11
11
 
12
12
  /// Lead — anonymous lead submission (Phase B submit_lead tool / B6 route)
13
13
  model Lead {
14
- id String @id @default(cuid())
14
+ id String @id @default(dbgenerated("('lead_'::text || (uuidv7())::text)")) @db.Text
15
15
  siteId String @map("site_id")
16
16
  source String
17
17
  email String
18
18
  payload Json
19
19
  createdAt DateTime @default(now()) @map("created_at")
20
20
 
21
+ // Lead → user promotion (#3586). Plain scalars — no cross-namespace
22
+ // @relation into auth.User (ADR 0009 invariant #1). Set by
23
+ // apps/api/src/services/lead-promotion.ts once the reconciling user's
24
+ // email is verified and the lead's siteId resolves to one of that user's
25
+ // own workspaces (tenant-safety, spec D3).
26
+ promotedUserId String? @map("promoted_user_id")
27
+ promotedAt DateTime? @map("promoted_at")
28
+
21
29
  @@index([siteId])
22
30
  @@index([createdAt])
31
+ @@index([email])
23
32
  @@map("leads")
24
33
  }
25
34
 
26
35
  /// Subscriber — newsletter subscription (Phase B subscribe_newsletter tool / B6 route)
27
36
  model Subscriber {
28
- id String @id @default(cuid())
37
+ id String @id @default(dbgenerated("('subscr_'::text || (uuidv7())::text)")) @db.Text
29
38
  siteId String @map("site_id")
30
39
  email String
31
40
  source String
@@ -38,7 +47,7 @@ model Subscriber {
38
47
 
39
48
  /// Callback — callback request (Phase B request_callback tool / B6 route)
40
49
  model Callback {
41
- id String @id @default(cuid())
50
+ id String @id @default(dbgenerated("('callbk_'::text || (uuidv7())::text)")) @db.Text
42
51
  siteId String @map("site_id")
43
52
  name String
44
53
  phone String
@@ -50,3 +59,565 @@ model Callback {
50
59
  @@index([createdAt])
51
60
  @@map("callbacks")
52
61
  }
62
+
63
+ /// FlowDefinition — server-owned flow config, one live definition per
64
+ /// (siteId, slug) (#3956 D2). No versioning at this layer — a session
65
+ /// snapshots the config at start (see FlowSession.configSnapshot), so a
66
+ /// mid-flight definition edit never corrupts a live session.
67
+ model FlowDefinition {
68
+ id String @id @default(dbgenerated("('flow_'::text || (uuidv7())::text)")) @db.Text
69
+ siteId String @map("site_id")
70
+ slug String
71
+ config Json
72
+ // #5036 D6d — the listing this definition was projected from, when it was.
73
+ // Null for every flow that is not a listing diagnostic. Before this column
74
+ // the only link was that the generated flow's slug HAPPENED to equal the
75
+ // listing's slug, which nothing enforced; a slug collision would have
76
+ // attributed a submission to the wrong founder. ADR 0009 forbids a relation
77
+ // across the namespace boundary, so this is a plain string — but a stated
78
+ // one rather than a coincidence.
79
+ listingId String? @map("listing_id")
80
+ createdAt DateTime @default(now()) @map("created_at")
81
+ updatedAt DateTime @updatedAt @map("updated_at")
82
+
83
+ @@unique([siteId, slug])
84
+ @@map("flow_definitions")
85
+ }
86
+
87
+ /// FlowSession — a flow walk in progress or completed; the server-side
88
+ /// partial-lead record (#3956 D2/D9). `configSnapshot` pins the definition at
89
+ /// session start; `state` accumulates each landed step's values immediately
90
+ /// on submission (the per-step persistence this feature exists for).
91
+ /// `email` is denormalized from the landed answers the moment a
92
+ /// `mapsTo:'email'` field lands, so the #3962 abandon-email query
93
+ /// (`email != null AND completedAt == null`) is one indexed read.
94
+ model FlowSession {
95
+ id String @id @default(dbgenerated("('flsess_'::text || (uuidv7())::text)")) @db.Text
96
+ siteId String @map("site_id")
97
+ // Informational only — never joined on (ADR 0009 D1: logical, not FK,
98
+ // isolation). Session resumption + completion key on flowSlug, not this
99
+ // column; deliberate, per code-review finding on #3956's acceptance PR.
100
+ flowId String @map("flow_id")
101
+ flowSlug String @map("flow_slug")
102
+ configSnapshot Json @map("config_snapshot")
103
+ state Json @default("{}")
104
+ email String?
105
+ // The session's locale (#3962 D3) — set once at create from the first
106
+ // landed step's `input.locale ?? configSnapshot.defaultLocale`; not
107
+ // updated on later steps. Null on pre-migration rows (no backfill; the
108
+ // abandon-email sweep falls back to the live config's defaultLocale).
109
+ locale String?
110
+ // Server-derived segment (#3959 D3) — set from the segmentation step's
111
+ // band map at landing time, persisted in the same write as `state`. No
112
+ // index: #3960/#3961/#3962 read it off rows they already fetch by
113
+ // existing indexes (subtraction-first, spec Clarifications).
114
+ segment String?
115
+ // D2 (#4663) — every derived flag, re-derived and persisted at each
116
+ // landing (`{ [flagName]: true }`). `{}` on a snapshot declaring no
117
+ // `flags` (the regression floor — byte-identical to a pre-#4663 row).
118
+ flags Json @default("{}")
119
+ // D6 (#4663) — the mid-flow aeo_score step's persisted result (`ready` /
120
+ // `unavailable`). Null until the step lands, or after a URL change resets
121
+ // it (D3). Never `computing` — that status never reaches the wire from
122
+ // this repo (packages/validation/src/schemas/aeo-score.ts).
123
+ aeoScore Json? @map("aeo_score")
124
+ leadId String? @map("lead_id")
125
+ // #5036 D6d — copied from the FlowDefinition at session create, so a
126
+ // submission is attributable to ONE listing without going through the slug.
127
+ // Null for every session on a flow that is not a listing diagnostic.
128
+ listingId String? @map("listing_id")
129
+ completedAt DateTime? @map("completed_at")
130
+ // Abandon-email audit stamp (#3962 D5) — set at most once by the sweep,
131
+ // per outcome ('sent' | 'dark' | 'failed'). The `abandonEmailStatus ==
132
+ // null` selection term is what makes the sweep idempotent across any
133
+ // number of overlapping runs; 'failed' is terminal (no auto-retry).
134
+ abandonEmailStatus String? @map("abandon_email_status")
135
+ abandonEmailAt DateTime? @map("abandon_email_at")
136
+ createdAt DateTime @default(now()) @map("created_at")
137
+ updatedAt DateTime @updatedAt @map("updated_at")
138
+
139
+ @@index([siteId])
140
+ @@index([siteId, completedAt])
141
+ @@index([email])
142
+ // Backs the abandon-email sweep's per-definition selection query
143
+ // (apps/api/src/services/abandon-email.ts — flowId equality + completedAt
144
+ // null + abandonEmailStatus null + updatedAt window), run once per
145
+ // FlowDefinition on every sweep — code-review finding on #3962's
146
+ // acceptance PR (without this, that filter set is a full table scan).
147
+ @@index([flowId, completedAt, abandonEmailStatus])
148
+ // #5036 D6d — backs the listing-to-submissions read the explicit reference
149
+ // exists to make possible.
150
+ @@index([listingId])
151
+ @@map("flow_sessions")
152
+ }
153
+
154
+ /// DeliverableAsset — an operator-uploaded asset the funnel's `deliverables`
155
+ /// config block references by `key` (#3961 D1). Bytes live IN THE DATABASE
156
+ /// (Postgres bytea) behind one internal resolver — zero new infrastructure;
157
+ /// reversibility is structural (a later blob-storage swap changes only the
158
+ /// resolver + the upload CLI, never the config contract, grant model, or
159
+ /// email link shape). Uploaded via the `deliverable:upload` CLI
160
+ /// (apps/api/src/bin/deliverable-upload.ts) only — no HTTP upload route.
161
+ model DeliverableAsset {
162
+ id String @id @default(dbgenerated("('dasset_'::text || (uuidv7())::text)")) @db.Text
163
+ siteId String @map("site_id")
164
+ // The assetKey a flow config's deliverables.assets entry references.
165
+ key String
166
+ // Download filename (Content-Disposition). Stored RAW at upload time
167
+ // (the `deliverable:upload` CLI writes the operator's `--filename` or the
168
+ // uploaded file's own basename verbatim) — sanitization to a safe charset
169
+ // happens at SERVE time only, in the download route's `sanitizeFilename`
170
+ // (apps/api/src/routes/deliverables/index.ts, #4002). Never trust this
171
+ // column as pre-sanitized; the serve-time guard is load-bearing, not
172
+ // redundant.
173
+ filename String
174
+ contentType String @map("content_type")
175
+ bytes Bytes
176
+ size Int
177
+ createdAt DateTime @default(now()) @map("created_at")
178
+ updatedAt DateTime @updatedAt @map("updated_at")
179
+
180
+ @@unique([siteId, key])
181
+ @@map("deliverable_assets")
182
+ }
183
+
184
+ /// DeliverableGrant — the capability token minted per fulfilled lead (#3961
185
+ /// D5). The grant's id IS the token: the download route
186
+ /// (GET /v1/deliverables/:grantId) looks up bytes by grant id alone, so
187
+ /// nothing is publicly enumerable by key, filename, or any other guessable
188
+ /// value — only the uuidv7-random grant id, delivered once via the lead's
189
+ /// own email. `assetId`/`leadId` are stable-id refs (ADR 0009 invariant 1
190
+ /// posture) — no cross-model `@relation`.
191
+ model DeliverableGrant {
192
+ id String @id @default(dbgenerated("('dgrant_'::text || (uuidv7())::text)")) @db.Text
193
+ siteId String @map("site_id")
194
+ assetId String @map("asset_id")
195
+ leadId String @map("lead_id")
196
+ sessionId String @map("session_id")
197
+ createdAt DateTime @default(now()) @map("created_at")
198
+ // Post-mint send-outcome stamp (#3998 D1) — set at most once, by the
199
+ // send-and-stamp tail shared between fulfillDeliverable and the
200
+ // deliverable:resend CLI. 'sent' | 'send_failed' | 'sender_unconfigured' —
201
+ // the exact post-mint subset of deliverable-fulfillment.ts's
202
+ // FulfillmentOutcome union; carried here (not a Prisma enum — this repo
203
+ // uses none), mirroring FlowSession.abandonEmailStatus/abandonEmailAt.
204
+ // NULL means the process never reached a post-mint outcome — the crash
205
+ // window between mint and send (#3961 D5's unhandled consequence) that
206
+ // this column makes queryable. This is the SELECTION term for the resend
207
+ // CLI's list mode only; a resend itself is an explicit operator act on one
208
+ // named grant and is NOT guarded by this column (#3998 D3).
209
+ sendStatus String? @map("send_status")
210
+ sendAt DateTime? @map("send_at")
211
+
212
+ // Backs the deliverable:resend CLI's list-mode query (siteId + sendStatus
213
+ // IS NULL) — without this, that filter set is a full table scan (the same
214
+ // lesson #3962's code-review finding established for FlowSession).
215
+ @@index([siteId, sendStatus])
216
+ @@map("deliverable_grants")
217
+ }
218
+
219
+ /// WaitlistExperiment — one ratified experiment config per (siteId, key), the
220
+ /// runtime twin of a site-plan waitlistExperiments[] entry (#4661 D3/D7).
221
+ /// `config` is the validated experimentConfig (D1) with the three registers
222
+ /// EMBEDDED. Written only via upsertWaitlistExperiment (D5).
223
+ model WaitlistExperiment {
224
+ id String @id @default(dbgenerated("('wlexp_'::text || (uuidv7())::text)")) @db.Text
225
+ siteId String @map("site_id")
226
+ key String
227
+ mode String // lead-capture | qualifying | paid
228
+ status String // draft | live | closed
229
+ config Json
230
+ createdAt DateTime @default(now()) @map("created_at")
231
+ updatedAt DateTime @updatedAt @map("updated_at")
232
+
233
+ @@unique([siteId, key])
234
+ @@map("waitlist_experiments")
235
+ }
236
+
237
+ /// WaitlistScore — the rules layer's output for one signup (#4661 D3; written
238
+ /// by A2 #4662). leadId is a plain scalar into inbox.Lead (ADR 0009). One row
239
+ /// per lead — re-scoring overwrites, never appends.
240
+ model WaitlistScore {
241
+ id String @id @default(dbgenerated("('wlscore_'::text || (uuidv7())::text)")) @db.Text
242
+ siteId String @map("site_id")
243
+ leadId String @map("lead_id")
244
+ experimentKey String @map("experiment_key")
245
+ waitlistKey String @map("waitlist_key")
246
+ tier String
247
+ score Int
248
+ categoryScores Json @map("category_scores")
249
+ ruleTrace Json @map("rule_trace")
250
+ createdAt DateTime @default(now()) @map("created_at")
251
+ updatedAt DateTime @updatedAt @map("updated_at")
252
+
253
+ // #5036 D6d — one score row per (lead, EXPERIMENT), not per lead.
254
+ //
255
+ // `@@unique([leadId])` was right for a single instrument and wrong for a
256
+ // marketplace of them: a visitor who joins two founders' waitlists is one
257
+ // lead with two scores, and the narrower constraint permitted one. The
258
+ // second write silently overwrote the first, so one founder's evidence
259
+ // disappeared and their weekly CSV under-reported with no error raised.
260
+ // Scoping the key to the experiment keeps the overwrite-on-retake behaviour
261
+ // that `scoreCompletedSignup` relies on, per project rather than globally.
262
+ @@unique([leadId, experimentKey])
263
+ @@index([siteId, experimentKey, waitlistKey])
264
+ @@map("waitlist_scores")
265
+ }
266
+
267
+ /// PlaybookInstance — a compiled, person-scoped Playbook (FEAT-074 U4a,
268
+ /// #4715). Append-only: `scoreCompletedSignup` (A2) fires the compile on
269
+ /// every `targeting-maturity` completion, so a retake writes a NEW row
270
+ /// rather than overwriting — unlike WaitlistScore, which is
271
+ /// @@unique([leadId, experimentKey]) and overwritten on retake of the SAME
272
+ /// experiment (D2; scoped per experiment by #5036 D6d). `tier`/`ruleTrace` are SNAPSHOTS of the
273
+ /// WaitlistScore at compile time; `scoreId` is kept only as the provenance
274
+ /// pointer to the (possibly since-overwritten) live row. `source` is the
275
+ /// U1 `playbook-source` machine-block object; `markdown` is the rendered
276
+ /// `PLAYBOOK-<slug>.md` body U4b (#4718) reads to render + deliver.
277
+ model PlaybookInstance {
278
+ id String @id @default(dbgenerated("('pbk_'::text || (uuidv7())::text)")) @db.Text
279
+ siteId String @map("site_id")
280
+ leadId String @map("lead_id")
281
+ experimentKey String @map("experiment_key")
282
+ scoreId String @map("score_id")
283
+ tier String
284
+ level Int
285
+ ruleTrace Json @map("rule_trace")
286
+ source Json
287
+ markdown String @db.Text
288
+ createdAt DateTime @default(now()) @map("created_at")
289
+
290
+ @@index([siteId, leadId, createdAt])
291
+ @@map("playbook_instances")
292
+ }
293
+
294
+ /// WaitlistFollowUpSend — the audit stamp for one attempted follow-up email
295
+ /// (#4732 D1/D5). `@@unique([leadId, followUpKey])` IS the sweep's
296
+ /// idempotency guarantee (mirrors WaitlistScore's own `@@unique([leadId])`
297
+ /// shape) — the stateless `runWaitlistFollowUpSweep` (#4732)
298
+ /// selects candidates by the ABSENCE of a matching row, so a re-run never
299
+ /// double-sends. `status` is 'sent' | 'failed' (no Prisma enum — this repo
300
+ /// uses none, mirroring DeliverableGrant.sendStatus's free-string
301
+ /// convention); 'failed' is terminal, no auto-retry (the abandon-email
302
+ /// sweep's own posture) — a suppressed/skipped candidate (wrong tier, not
303
+ /// yet due, a booked call, or unsubscribed) is NEVER stamped here at all, so
304
+ /// it is re-evaluated on every later sweep run until it either sends or its
305
+ /// completedAt/offsetDays window is permanently past.
306
+ model WaitlistFollowUpSend {
307
+ id String @id @default(dbgenerated("('wlfu_'::text || (uuidv7())::text)")) @db.Text
308
+ siteId String @map("site_id")
309
+ leadId String @map("lead_id")
310
+ followUpKey String @map("follow_up_key")
311
+ sentAt DateTime @map("sent_at")
312
+ status String
313
+
314
+ @@unique([leadId, followUpKey])
315
+ @@index([siteId])
316
+ @@map("waitlist_followup_sends")
317
+ }
318
+
319
+ /// ListingCsvSend — the weekly founder-CSV send stamp (#5001). One row per
320
+ /// DELIVERED CSV for one ProjectListing: `sentAt` is the watermark the next
321
+ /// sweep selects after (signups whose WaitlistScore landed at or before it are
322
+ /// already in a delivered CSV), `rowCount` is how many rows that CSV carried,
323
+ /// and `grantId` is the DeliverableGrant whose id is the capability token in
324
+ /// the founder's download link. Plain-scalar refs to ProjectListing /
325
+ /// DeliverableGrant (ADR 0009 invariant 1) — no cross-model @relation. A
326
+ /// listing with no new signups gets no row, so "no new rows ⇒ no send" is
327
+ /// visible in the data, not only in the sweep's control flow.
328
+ model ListingCsvSend {
329
+ id String @id @default(dbgenerated("('lcsv_'::text || (uuidv7())::text)")) @db.Text
330
+ listingId String @map("listing_id")
331
+ sentAt DateTime @map("sent_at")
332
+ rowCount Int @map("row_count")
333
+ grantId String @map("grant_id")
334
+
335
+ @@index([listingId, sentAt])
336
+ @@map("listing_csv_sends")
337
+ }
338
+
339
+ /// FlowCheckout — the recommender buyer record (#4837 D2/D4): one row per
340
+ /// checkout ATTEMPT. `amountMinor` is the server's own `price().total` × 100
341
+ /// (D6); `quote` is the full WaitlistPriceQuote beside it, so an onboarding
342
+ /// reader sees the seat line and credit line, not only a total. `status` is
343
+ /// 'created' | 'completed' | 'expired' (no Prisma enum — this repo uses none,
344
+ /// mirroring DeliverableGrant.sendStatus's free-string convention); the
345
+ /// webhook (#4838) advances it with a guarded update on 'created'. Plain-scalar
346
+ /// refs to Lead / FlowSession (ADR 0009 invariant 1) — no cross-model
347
+ /// @relation. `permalink` is the answers-only payload string the visitor's
348
+ /// recommendation was rendered from (#4770). Written only by
349
+ /// apps/api/src/services/recommender-capture.ts.
350
+ model FlowCheckout {
351
+ id String @id @default(dbgenerated("('flchk_'::text || (uuidv7())::text)")) @db.Text
352
+ siteId String @map("site_id")
353
+ flowSlug String @map("flow_slug")
354
+ experimentKey String @map("experiment_key")
355
+ sessionId String @map("session_id")
356
+ leadId String @map("lead_id")
357
+ offerKey String @map("offer_key")
358
+ seats Int
359
+ period String
360
+ creditStop Json @map("credit_stop")
361
+ amountMinor Int @map("amount_minor")
362
+ currency String
363
+ quote Json
364
+ permalink String @db.Text
365
+ stripeSessionId String? @map("stripe_session_id")
366
+ // The hosted Checkout page URL an identical resubmission is replayed with
367
+ // (#4912 D6) — stamped in the same write as `stripeSessionId`, because a
368
+ // Checkout Session URL cannot be reconstructed from its id.
369
+ checkoutUrl String? @db.Text @map("checkout_url")
370
+ // The server-derived fingerprint of the checkout request (#4912 D5): a
371
+ // SHA-256 hex digest, never the request fields themselves. Null on
372
+ // pre-#4912 rows and on a row whose key a reconcile released.
373
+ idempotencyKey String? @map("idempotency_key")
374
+ stripeCustomerId String? @map("stripe_customer_id")
375
+ status String
376
+ completedAt DateTime? @map("completed_at")
377
+ createdAt DateTime @default(now()) @map("created_at")
378
+ updatedAt DateTime @updatedAt @map("updated_at")
379
+
380
+ // The webhook's join (#4838) — nullable, so `created` rows with no session
381
+ // id yet never collide.
382
+ @@unique([stripeSessionId])
383
+ // The /checkout idempotency guarantee (#4912 D5), tenant-scoped so a
384
+ // fingerprint can never reach another site's row. Nullable, so pre-#4912
385
+ // rows and reconciled rows never collide.
386
+ @@unique([siteId, idempotencyKey])
387
+ // The dashboard's checkedOut read (#4839 D3): siteId + experimentKey + status.
388
+ @@index([siteId, experimentKey, status])
389
+ // Onboarding's read: everything a lead bought.
390
+ @@index([leadId])
391
+ @@map("flow_checkouts")
392
+ }
393
+
394
+ /// ProjectListing — the founder's application row for the project-listings
395
+ /// workflow (#4998): apply → Slack approve/reject → build issue → data-driven
396
+ /// page + diagnostic → weekly CSV. `leadId` is a plain scalar ref into this
397
+ /// namespace's own `Lead` (ADR 0009 invariant 1) — the POST route writes both
398
+ /// rows via `routeLead` (kind `submit`, source `projects-apply`) then this
399
+ /// row, never the reverse. `questions` is the diagnostic template instance
400
+ /// (Q2–Q7 + up to 3 extras, bounded by `projectListingQuestionsSchema` in
401
+ /// `@working-theory/validation`) per
402
+ /// docs/decisions/2026-09-07-project-listings-workflow.md (consumer repo,
403
+ /// salessmyth/samhenry — this row and its routes vendor down, operator
404
+ /// 2026-09-07 option 2). `status` is a free string (`applied` | `approved` |
405
+ /// `rejected` | `active`, no Prisma enum — this repo uses none, mirroring
406
+ /// `FlowCheckout.status`'s convention). Those are the four STORED values
407
+ /// (#5033): the fifth lifecycle state, `closed`, is COMPUTED in the read
408
+ /// from `windowEndsAt` and never written, so an expired listing cannot stay
409
+ /// publicly open because a scheduled job failed. `draft` is defined so the
410
+ /// vocabulary never migrates again and is reachable through no route today.
411
+ /// `decidedAt`/`decidedBy` stamp the approve/reject moment, and an approve
412
+ /// also stamps `windowEndsAt = decidedAt + windowDays`. `windowDays` is the
413
+ /// applicant's requested window ALREADY CLAMPED at create against the site's
414
+ /// `SiteListingConfig` (database/config/schema.prisma) — the config itself
415
+ /// is never accepted from a request body. `issueUrl` (the build issue,
416
+ /// #5000) and `experimentId` (the generated flow's waitlist experiment,
417
+ /// #5001) are null until their respective downstream unit lands.
418
+ /// `featured` is the operator-set shelf flag, surfaced on the public read
419
+ /// only where the site's config enables it. `headline`, `logoMarkUrl` and
420
+ /// `logoWordmarkUrl` are the listing card's presentation fields; the two
421
+ /// logo columns ship nullable with NO upload path (#5033 D5 — no storage
422
+ /// dependency exists, and accepting arbitrary files from anonymous
423
+ /// strangers deserves its own review), and a card with both absent renders
424
+ /// a designed name-only treatment. `slackChannel`/`slackTs`
425
+ /// (#4999) are the posted application message's coordinates, stamped by
426
+ /// `postListingApplication` right after `chat.postMessage` succeeds, so the
427
+ /// decision handler can `chat.update` the same message in place; null when
428
+ /// Slack is unconfigured or the post failed. Still out of scope here: the
429
+ /// GitHub issue (#5000), the diagnostic flow + weekly CSV (#5001).
430
+ model ProjectListing {
431
+ id String @id @default(dbgenerated("('prjl_'::text || (uuidv7())::text)")) @db.Text
432
+ siteId String @map("site_id")
433
+ // #5036 — written at SUBMIT, not at start. The founder's contact details are
434
+ // already captured on THIS row at step one, so a `Lead` at step one adds no
435
+ // capture; what it does add is an operator notification email per abandoned
436
+ // first step, on an anonymous route (PR #5042 security review, finding 4).
437
+ // `routeLead` remains the one Lead write path; it just runs later.
438
+ leadId String? @map("lead_id")
439
+ // #5036 D1 — every field the SUBMIT payload requires is nullable at rest.
440
+ // Validation moved from write-time to submit-time when the application
441
+ // became a ten-step draft that saves as it goes; a half-filled draft is a
442
+ // legitimate row, and a NOT NULL here would make one unstorable. Only
443
+ // `submit` asserts completeness.
444
+ slug String?
445
+ name String?
446
+ oneLiner String? @map("one_liner")
447
+ problem String? @db.Text
448
+ url String?
449
+ // #5036 D2 — the six-rung product ladder that replaced the four-value
450
+ // `stage`. The migration remaps every old value by an explicit map; there
451
+ // is no shim, because the two ladders do not align rung-for-rung.
452
+ product String?
453
+ // #5036 D3/D5 — the two segmentation axes shared with the diagnostic.
454
+ commitment String?
455
+ teamComposition String? @map("team_composition")
456
+ // #5036 D5 — exactly three {question, answer} entries, the detail page's
457
+ // fixed FAQ slot.
458
+ faqs Json?
459
+ anythingElse String? @db.Text @map("anything_else")
460
+ // #5036 D6 — a legal control, not a contact detail: visitors answering this
461
+ // founder's diagnostic are owed the identity and address of the party
462
+ // collecting their data. Carried on the PUBLIC read for that reason.
463
+ //
464
+ // #5279 — still the single-line DISPLAY string. Where the six part columns
465
+ // below are written, this is COMPOSED from them server-side; where they are
466
+ // not, it is the line the founder typed, verbatim.
467
+ address String?
468
+ // #5279 — the structured parts of the address above, beside it rather than
469
+ // instead of it. Nullable for the same reason as the #5036 D1 block above: a
470
+ // row written before #5279 has no parts, and none may ever be GUESSED from
471
+ // its `address` string. Parts compose into a line deterministically; a line
472
+ // does not decompose into parts, and a wrong split silently corrupts the
473
+ // party identification the listing's privacy notice points at — so the
474
+ // migration deliberately backfills nothing and a null part stays null for
475
+ // the life of the row. Composition runs one way only, in `draftUpdateData`
476
+ // (apps/api/src/services/project-listings.ts).
477
+ addressLine1 String? @map("address_line1")
478
+ addressLine2 String? @map("address_line2")
479
+ addressCity String? @map("address_city")
480
+ addressRegion String? @map("address_region")
481
+ addressPostalCode String? @map("address_postal_code")
482
+ addressCountry String? @map("address_country")
483
+ questions Json?
484
+ // #5285 — the answers to the site's OWN questions: one flat JSON object of
485
+ // `{ questionId: answer }` for everything a site asks that the PLATFORM does
486
+ // not read. The field contract (packages/validation/.../listing-
487
+ // application-contract.ts) names what the platform reads and therefore what
488
+ // earns a column; everything else was previously unaskable without a
489
+ // migration and a release, which is what this column removes.
490
+ //
491
+ // BOUNDED, not free-form, and that is the point: the write path is
492
+ // anonymous (a resume token and nothing else), so an unbounded JSON bag on
493
+ // it is a place for a stranger to park arbitrary text in the table. The
494
+ // server accepts only ids the SITE declared in its own `SiteListingConfig`,
495
+ // at that site's own count and length caps, and refuses the whole request
496
+ // otherwise — so what lands here is never larger than the questions the
497
+ // operator chose to ask. Null on every row written before #5285 and on
498
+ // every site that asks nothing of its own.
499
+ //
500
+ // Only the `public`-visibility subset rides the public read; an
501
+ // `operator-only` answer reaches the dashboard and the review payload and
502
+ // stops there.
503
+ siteAnswers Json? @map("site_answers")
504
+ founderName String @map("founder_name")
505
+ founderEmail String @map("founder_email")
506
+ founderCompany String? @map("founder_company")
507
+ status String
508
+ // #5036 D1/D6 — when the founder ticked step one's consent box. Never null:
509
+ // a row cannot exist without it, because `start` requires it.
510
+ consentAt DateTime @default(now()) @map("consent_at")
511
+ // #5036 D1 — sha-256 of the opaque resume token, never the token itself.
512
+ // The token is the ONLY credential on an anonymous surface, so the column
513
+ // holds a digest an attacker who reads the table still cannot present.
514
+ // Cleared at submit, which makes a leaked token inert the moment the draft
515
+ // stops being a draft.
516
+ resumeTokenHash String? @unique @map("resume_token_hash")
517
+ headline String?
518
+ logoMarkUrl String? @map("logo_mark_url")
519
+ logoWordmarkUrl String? @map("logo_wordmark_url")
520
+ featured Boolean @default(false)
521
+ decidedAt DateTime? @map("decided_at")
522
+ decidedBy String? @map("decided_by")
523
+ // The operator's copy-edit audit trio. A listing that is `requested` or
524
+ // `active` can have its PUBLISHED COPY trimmed by the operator (headline,
525
+ // oneLiner, problem, faqs, questions) — the third review outcome beside
526
+ // approve and reject. `copyEditedAt`/`copyEditedBy` mirror the decision pair
527
+ // directly above and are re-stamped on every edit.
528
+ //
529
+ // `submittedCopy` is WRITE-ONCE, on the FIRST edit only: it holds those five
530
+ // fields exactly as the applicant submitted them. Null for the untouched
531
+ // majority, which is why "what did the applicant actually send" reads as the
532
+ // snapshot if present and the row itself otherwise. Never cleared, so the
533
+ // submitted copy stays recoverable for the life of the row.
534
+ submittedCopy Json? @map("submitted_copy")
535
+ copyEditedAt DateTime? @map("copy_edited_at")
536
+ copyEditedBy String? @map("copy_edited_by")
537
+ issueUrl String? @map("issue_url")
538
+ // #5036 D6d — the experiment's KEY, not its id. `WaitlistScore` holds
539
+ // `experimentKey`, so holding the id here meant every listing-to-submissions
540
+ // read fetched the experiment purely to learn its key. One hop, the same
541
+ // string on both sides. ADR 0009 forbids a relation across the namespace
542
+ // boundary, so this is a plain string matched by convention — which is
543
+ // exactly why it has to be the string the other side actually stores.
544
+ experimentKey String? @map("experiment_key")
545
+ windowDays Int? @map("window_days")
546
+ windowEndsAt DateTime? @map("window_ends_at")
547
+ // The posted application message's coordinates (#4999) — set by
548
+ // `postListingApplication` right after `chat.postMessage` succeeds, so the
549
+ // Approve/Reject decision handler can `chat.update` the SAME message in
550
+ // place. Null when Slack is unconfigured or the post failed (create never
551
+ // rolls back on a Slack failure).
552
+ slackChannel String? @map("slack_channel")
553
+ slackTs String? @map("slack_ts")
554
+ // Listing -> owner-user link (#5295). Plain scalars — no cross-namespace
555
+ // @relation into auth.User (ADR 0009 invariant #1), exactly as
556
+ // `Lead.promotedUserId` / `promotedAt` above. Set by
557
+ // apps/api/src/services/listing-promotion.ts once the reconciling user's own
558
+ // email is VERIFIED, that verified address matches `founderEmail`, and this
559
+ // row's `siteId` resolves to one of that user's own workspaces.
560
+ //
561
+ // `founderEmail` is an UNVERIFIED string typed into an anonymous form, so it
562
+ // can never by itself say who owns the row: two people who type the same
563
+ // address are two people. This column is the only ownership claim any read
564
+ // may join on, and nothing derives it from `founderEmail` at read time.
565
+ //
566
+ // Nullable PERMANENTLY, not pending a backfill. A listing submitted by
567
+ // someone who never signs in has no owner for the life of the row, and that
568
+ // is a legitimate resting state: a backfill from `founderEmail` would assert
569
+ // a link nobody proved, which is the bug this column exists to remove.
570
+ ownerUserId String? @map("owner_user_id")
571
+ ownerLinkedAt DateTime? @map("owner_linked_at")
572
+ createdAt DateTime @default(now()) @map("created_at")
573
+ updatedAt DateTime @updatedAt @map("updated_at")
574
+
575
+ @@unique([siteId, slug])
576
+ @@index([siteId, status])
577
+ // #5036 D7 — the concurrent-listing limit counts a founder's non-terminal
578
+ // rows on every submit, from an anonymous route. Indexed so a stranger
579
+ // cannot make that count a table scan.
580
+ @@index([siteId, founderEmail])
581
+ // The self-scoped read's whole WHERE clause (#5295) is `{ ownerUserId }` —
582
+ // the owner link IS the scope, so no site term joins it. Single-column by
583
+ // design: the promotion match (`founderEmail` + `siteId` + null owner) is
584
+ // already served by the (site_id, founder_email) index above.
585
+ @@index([ownerUserId])
586
+ @@map("project_listings")
587
+ }
588
+
589
+ /// ConsentGrant — one person's answer to one consent purpose, with the exact
590
+ /// wording they read (#5275). APPEND-ONLY: current state is the newest row
591
+ /// per (subject, purpose), so a later withdrawal never destroys the evidence
592
+ /// of the earlier grant (D9) — no `updatedAt`, and deliberately NO unique
593
+ /// constraint on subject + purpose. DECLINES ARE STORED as `granted: false`
594
+ /// (D8): an absent row cannot distinguish "they said no" from "we never
595
+ /// asked", and on an optional purpose that distinction is the only thing the
596
+ /// record is for. The subject is a type + id pair of plain scalars
597
+ /// (`project_listing` / `subscriber` / …) with NO cross-namespace @relation
598
+ /// (D10, ADR 0009 invariant 1) — which is what lets a new surface adopt
599
+ /// consent with a service call and no migration. `statement` is the resolved
600
+ /// text DENORMALIZED at write time (D7): a pointer into the site's config row
601
+ /// would prove nothing once that row is edited, and `statementHash` groups
602
+ /// identical wordings. `locale` is the locale the statement was actually
603
+ /// served in, not the one requested. `purposeId` is the site-config registry
604
+ /// id — the join key across surfaces. Written only by
605
+ /// apps/api/src/services/consent.ts.
606
+ model ConsentGrant {
607
+ id String @id @default(dbgenerated("('cnsnt_'::text || (uuidv7())::text)")) @db.Text
608
+ siteId String @map("site_id")
609
+ subjectType String @map("subject_type")
610
+ subjectId String @map("subject_id")
611
+ purposeId String @map("purpose_id")
612
+ granted Boolean
613
+ locale String
614
+ statement String @db.Text
615
+ statementHash String @map("statement_hash")
616
+ createdAt DateTime @default(now()) @map("created_at")
617
+
618
+ // "What did this subject agree to" — every row for one subject, newest last.
619
+ @@index([siteId, subjectType, subjectId])
620
+ // "Who agreed to this purpose" — the cross-surface count the id exists for (D1/D2).
621
+ @@index([siteId, purposeId])
622
+ @@map("consent_grants")
623
+ }
@@ -6,6 +6,14 @@ module.exports = {
6
6
  testPathIgnorePatterns: ["/node_modules/", "/generated/"],
7
7
  moduleFileExtensions: ["ts", "js", "json"],
8
8
  moduleNameMapper: {
9
+ // Map the validation workspace package to its TypeScript source so
10
+ // ts-jest compiles it directly (#3958) — mirrors apps/api/jest.config.cjs's
11
+ // identical mapping. The published dist is ESM-only and this package's
12
+ // CJS ts-jest transform can't load it; database/flows/presets.ts's test
13
+ // needs the real flowConfigSchema value at runtime (a value import, not a
14
+ // type-only one — unlike ../tenancy.ts's `import type { Id }` from
15
+ // @working-theory/ids, which erases to nothing and needs no mapping).
16
+ "^@working-theory/validation$": "<rootDir>/../packages/validation/src/index.ts",
9
17
  "^(\\.{1,2}/.*)\\.js$": "$1",
10
18
  },
11
19
  transform: {