create-ailk 0.1.1 → 0.3.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 (284) hide show
  1. package/component-catalog.md +210 -52
  2. package/dist/cli.js +0 -0
  3. package/dist/lib/apply-module-patches.d.ts +47 -0
  4. package/dist/lib/apply-module-patches.js +370 -0
  5. package/dist/lib/extract-module-bundle.d.ts +12 -3
  6. package/dist/lib/extract-module-bundle.js +236 -24
  7. package/dist/lib/fetch-module.d.ts +3 -38
  8. package/dist/lib/fetch-module.js +26 -8
  9. package/dist/lib/module-license-gate.d.ts +2 -0
  10. package/dist/lib/module-license-gate.js +1 -0
  11. package/dist/lib/paid-paths.d.ts +16 -0
  12. package/dist/lib/paid-paths.js +90 -0
  13. package/dist/module-architecture.d.ts +139 -23
  14. package/dist/module-architecture.js +668 -25
  15. package/dist/parse-args.d.ts +2 -2
  16. package/dist/parse-args.js +3 -1
  17. package/dist/programmatic.d.ts +2 -1
  18. package/dist/programmatic.js +15 -4
  19. package/dist/surfaces.d.ts +12 -36
  20. package/dist/surfaces.js +35 -5
  21. package/dist/sync-routes.js +31 -1
  22. package/package.json +14 -15
  23. package/templates/.claude/agents/web.md +2 -3
  24. package/templates/.claude/rules/architecture.md +5 -5
  25. package/templates/.claude/skills/README.md +2 -1
  26. package/templates/.env.example +21 -9
  27. package/templates/CONVENTIONS.md +6 -7
  28. package/templates/apps/api/.env.example +9 -0
  29. package/templates/apps/api/CLAUDE.md +35 -4
  30. package/templates/apps/api/package.json +0 -1
  31. package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
  32. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
  33. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
  34. package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
  35. package/templates/apps/api/src/config/index.ts +19 -0
  36. package/templates/apps/api/src/config/modules.ts +12 -14
  37. package/templates/apps/api/src/lib/__mocks__/prisma.ts +13 -0
  38. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
  39. package/templates/apps/api/src/lib/slice-load.ts +90 -0
  40. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
  41. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
  42. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
  43. package/templates/apps/api/src/openapi/index.ts +9 -3
  44. package/templates/apps/api/src/openapi/spec.ts +505 -65
  45. package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
  46. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
  47. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
  48. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
  49. package/templates/apps/api/src/routes/content/index.ts +38 -11
  50. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +28 -0
  51. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +144 -0
  52. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
  53. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +478 -0
  54. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
  55. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
  56. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +717 -0
  57. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +35 -0
  58. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +484 -0
  59. package/templates/apps/api/src/routes/project-listings/index.ts +52 -10
  60. package/templates/apps/api/src/routes/project-listings/me.ts +86 -0
  61. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
  62. package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
  63. package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
  64. package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
  65. package/templates/apps/api/src/routes/project-listings/start.ts +38 -0
  66. package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
  67. package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
  68. package/templates/apps/api/src/server.ts +471 -133
  69. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
  70. package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
  71. package/templates/apps/api/src/services/consent.ts +236 -0
  72. package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
  73. package/templates/apps/api/src/services/listing-config.ts +58 -0
  74. package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
  75. package/templates/apps/api/src/services/project-listings.ts +602 -29
  76. package/templates/apps/api/src/vercel-handler.ts +60 -26
  77. package/templates/apps/mcp/.env.example +8 -0
  78. package/templates/apps/mcp/CLAUDE.md +2 -2
  79. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
  80. package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
  81. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
  82. package/templates/apps/mcp/src/config/modules.ts +14 -13
  83. package/templates/apps/mcp/src/server.ts +8 -4
  84. package/templates/apps/mcp/src/tools/index.ts +7 -22
  85. package/templates/apps/web/.env.example +15 -0
  86. package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
  87. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
  88. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
  89. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
  90. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
  91. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
  92. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
  93. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
  94. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
  95. package/templates/apps/web/app/[locale]/layout.tsx +13 -1
  96. package/templates/apps/web/app/llms.txt/route.ts +12 -9
  97. package/templates/apps/web/jest.config.cjs +20 -13
  98. package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
  99. package/templates/apps/web/lib/site-brand.tsx +4 -1
  100. package/templates/apps/web/lib/site-theme.ts +74 -0
  101. package/templates/apps/web/next.config.mjs +1 -3
  102. package/templates/apps/web/package.json +1 -6
  103. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  104. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  105. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  106. package/templates/apps/web/public/favicon-16x16.png +0 -0
  107. package/templates/apps/web/public/favicon-32x32.png +0 -0
  108. package/templates/apps/web/public/favicon.ico +0 -0
  109. package/templates/apps/web/public/favicon.svg +6 -3
  110. package/templates/apps/web/public/lockup-horizontal.svg +4 -0
  111. package/templates/apps/web/public/logomark.svg +4 -0
  112. package/templates/apps/web/public/site.webmanifest +2 -2
  113. package/templates/apps/web/public/wordmark.svg +4 -0
  114. package/templates/content/_site.mdx +12 -0
  115. package/templates/database/CHANGELOG.md +94 -0
  116. package/templates/database/inbox/schema.prisma +165 -0
  117. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
  118. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
  119. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
  120. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
  121. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
  122. package/templates/database/package.json +1 -1
  123. package/templates/database/scripts/db-generate-locked.sh +0 -0
  124. package/templates/package.json +1 -1
  125. package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
  126. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
  127. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
  128. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
  129. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
  130. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
  131. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
  132. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
  133. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
  134. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
  135. package/templates/apps/api/src/__tests__/server.test.ts +0 -253
  136. package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
  137. package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
  138. package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
  139. package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
  140. package/templates/apps/api/src/bin/seed-presets.ts +0 -58
  141. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
  142. package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
  143. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
  144. package/templates/apps/api/src/lib/stripe.ts +0 -52
  145. package/templates/apps/api/src/lib/tenant-db.ts +0 -225
  146. package/templates/apps/api/src/lib/ws-token.ts +0 -91
  147. package/templates/apps/api/src/middleware/tenant.ts +0 -99
  148. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
  149. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
  150. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
  151. package/templates/apps/api/src/routes/billing/index.ts +0 -36
  152. package/templates/apps/api/src/routes/billing/portal.ts +0 -182
  153. package/templates/apps/api/src/routes/billing/read.ts +0 -75
  154. package/templates/apps/api/src/routes/billing/usage.ts +0 -153
  155. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
  156. package/templates/apps/api/src/routes/checkout/index.ts +0 -13
  157. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
  158. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
  159. package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
  160. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
  161. package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
  162. package/templates/apps/api/src/routes/flows/README.md +0 -147
  163. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
  164. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
  165. package/templates/apps/api/src/routes/flows/index.ts +0 -202
  166. package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
  167. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
  168. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
  169. package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
  170. package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
  171. package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
  172. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
  173. package/templates/apps/api/src/routes/schedule/index.ts +0 -249
  174. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
  175. package/templates/apps/api/src/routes/slack/actions.ts +0 -177
  176. package/templates/apps/api/src/routes/slack/index.ts +0 -38
  177. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
  178. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
  179. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
  180. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
  181. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
  182. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
  183. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
  184. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
  185. package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
  186. package/templates/apps/api/src/routes/webhooks/README.md +0 -80
  187. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
  188. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
  189. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
  190. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
  191. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
  192. package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
  193. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
  194. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
  195. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
  196. package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
  197. package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
  198. package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
  199. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
  200. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
  201. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
  202. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
  203. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
  204. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
  205. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
  206. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
  207. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
  208. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
  209. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
  210. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
  211. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
  212. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
  213. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
  214. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
  215. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
  216. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
  217. package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
  218. package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
  219. package/templates/apps/api/src/services/flow-engine.ts +0 -1278
  220. package/templates/apps/api/src/services/lead-promotion.ts +0 -176
  221. package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
  222. package/templates/apps/api/src/services/playbook-compile.ts +0 -398
  223. package/templates/apps/api/src/services/playbook-render.ts +0 -263
  224. package/templates/apps/api/src/services/project-listing-decision.ts +0 -490
  225. package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
  226. package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
  227. package/templates/apps/api/src/services/recommender-capture.ts +0 -835
  228. package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
  229. package/templates/apps/api/src/services/scheduling/index.ts +0 -63
  230. package/templates/apps/api/src/services/scheduling/types.ts +0 -88
  231. package/templates/apps/api/src/services/tenant-context.ts +0 -451
  232. package/templates/apps/api/src/services/usage-metering.ts +0 -699
  233. package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
  234. package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
  235. package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
  236. package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
  237. package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
  238. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
  239. package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
  240. package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
  241. package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
  242. package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
  243. package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
  244. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
  245. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
  246. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
  247. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
  248. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
  249. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
  250. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
  251. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
  252. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
  253. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
  254. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
  255. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
  256. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
  257. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
  258. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
  259. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
  260. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
  261. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
  262. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
  263. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
  264. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
  265. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
  266. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
  267. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
  268. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
  269. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
  270. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
  271. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
  272. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
  273. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
  274. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
  275. package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
  276. package/templates/apps/web/content/en/blog/README.txt +0 -25
  277. package/templates/apps/web/content/en/docs/index.mdx +0 -28
  278. package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
  279. package/templates/apps/web/lib/blog-list-data.ts +0 -95
  280. package/templates/apps/web/lib/blog-post-data.ts +0 -79
  281. package/templates/apps/web/lib/source.ts +0 -34
  282. package/templates/apps/web/public/logomark-dark.svg +0 -4
  283. package/templates/apps/web/public/logomark-light.svg +0 -4
  284. package/templates/apps/web/source.config.ts +0 -78
@@ -1,82 +0,0 @@
1
- /**
2
- * Flow-checkouts respondent route (#4984 — the "my purchases" read behind
3
- * the respondent account surface).
4
- *
5
- * Routes:
6
- * GET /v1/flow-checkouts/me — the caller's OWN flow checkouts, newest first
7
- *
8
- * Registered INSIDE server.ts's `authScope` (auth only — NOT the inner
9
- * `tenantScope`: a respondent has no workspace membership, and the tenant
10
- * guard is default-deny) so `request.userId` is populated by
11
- * `authPreHandlerPlugin` before the handler runs. An unauthenticated caller
12
- * receives the preHandler's existing 401, untouched. The identity is
13
- * `request.userId` ONLY: the route declares no query schema and reads no
14
- * query or body key (an `email` parameter is never parsed, never read),
15
- * never enumerates, never reads across sites — see
16
- * `services/flow-checkouts.ts`, where the only join is the lead-promotion
17
- * link (`Lead.promotedUserId`), never an email match.
18
- *
19
- * Envelope per apps/api/CLAUDE.md: `{ ok: true, data }` /
20
- * `{ ok: false, error: { code, message } }`, real HTTP status codes:
21
- * 401 unauthorized · 403 email_unverified · 404 user_not_found ·
22
- * 500 internal_error (the 200 body failed its own contract schema — a
23
- * drift is never a leak). A verified user with nothing to show is a 200
24
- * with an empty list.
25
- *
26
- * API↔MCP parity: identity-bound (the session IS the identity; the OSS MCP
27
- * surface is identity-blind) — no MCP twin (#3591 exception, the same shape
28
- * as `/v1/billing/*`, `/v1/workspaces/*`, `/v1/waitlist-signups/me`).
29
- */
30
- import { logger } from "@working-theory/observability";
31
- import { flowCheckoutMeResponseSchema } from "@working-theory/validation";
32
- import type { FastifyPluginAsync, FastifyReply } from "fastify";
33
-
34
- import { readMyCheckouts } from "../../services/flow-checkouts.js";
35
-
36
- function unauthorized(reply: FastifyReply) {
37
- return reply.status(401).send({
38
- ok: false,
39
- error: { code: "unauthorized", message: "Authentication required" },
40
- });
41
- }
42
-
43
- const ERROR_STATUS = {
44
- not_found_user: 404,
45
- email_unverified: 403,
46
- } as const;
47
-
48
- const ERROR_CODE = {
49
- not_found_user: "user_not_found",
50
- email_unverified: "email_unverified",
51
- } as const;
52
-
53
- export const flowCheckoutRoutes: FastifyPluginAsync = async (fastify) => {
54
- fastify.get("/v1/flow-checkouts/me", async (request, reply) => {
55
- const userId = request.userId;
56
- if (!userId) return unauthorized(reply);
57
-
58
- const result = await readMyCheckouts(userId);
59
- if (!result.ok) {
60
- return reply.status(ERROR_STATUS[result.error.kind]).send({
61
- ok: false,
62
- error: { code: ERROR_CODE[result.error.kind], message: result.error.message },
63
- });
64
- }
65
-
66
- // D5 — re-validate the body against its own strict contract before it
67
- // leaves the process (the waitlist-signups idiom): a shape drift is an
68
- // internal error, never a leak. Log issues only — never the value.
69
- const parsed = flowCheckoutMeResponseSchema.safeParse(result.value);
70
- if (!parsed.success) {
71
- logger.error(
72
- { route: "flow-checkouts/me", issues: parsed.error.issues },
73
- "[flow-checkouts] response failed its own contract schema",
74
- );
75
- return reply.status(500).send({
76
- ok: false,
77
- error: { code: "internal_error", message: "Response failed contract validation" },
78
- });
79
- }
80
- return reply.status(200).send({ ok: true, data: parsed.data });
81
- });
82
- };
@@ -1,147 +0,0 @@
1
- # Flows — `apps/api/src/routes/flows/`
2
-
3
- ```
4
- GET /v1/flows/:slug — serve a validated flow config
5
- POST /v1/flows/:slug/steps — submit one step's answers; persists
6
- server-side on landing
7
- GET /v1/flows/:slug/aggregate — anonymous per-question response counts
8
- for the results reveal (#4771)
9
- POST /v1/flows/:slug/save — the recommender's "email me this plan"
10
- moment: FlowSession + Lead (+ T+0 grant)
11
- for the permalink's answers (#4841)
12
- POST /v1/flows/:slug/checkout — the purchase moment: the email moment,
13
- the FlowCheckout buyer row at a
14
- SERVER-recomputed price, then a Stripe
15
- TEST-mode Checkout Session (#4841)
16
- ```
17
-
18
- Every route is anonymous — no auth, no `siteId` on the wire (see the
19
- module docstring in `index.ts` for the single-site default). All sit
20
- behind the per-IP rate limiter (`middleware/rate-limit.ts`), registered
21
- once for the whole `flowsScope` in `server.ts`.
22
-
23
- ## The email moment (`recommender.ts`, #4841)
24
-
25
- A recommending flow captures no PII until the visitor chooses save or
26
- checkout (PRD L108). Both routes take the permalink's answer map + an
27
- email; `checkout` additionally takes `offerKey` / `period` (+ `seats`,
28
- `creditStop`) and the two redirect URLs — and **no amount of any name**:
29
- the body schema is `.strict()`, and the server recomputes `price()` from
30
- the register it served (PRD L104). Checkout refuses a register with
31
- `stripeConfigRef: null` or no `rateTable.currency` (409), refuses any key
32
- outside the `sk_test_` allow-list before any side effect (500), and only
33
- opens the Stripe session AFTER the session, lead and buyer row exist — a
34
- Stripe outage returns 502 and the record survives. The Stripe session's
35
- `metadata` carries `flowCheckoutId`, `flowSlug`, `offerKey`, `sessionId`,
36
- `leadId` and the `permalink` (chunked `permalink_2…` past 500 chars) — the
37
- join the webhook (#4838) stamps on.
38
-
39
- ## Checkout redirect origins (#4912)
40
-
41
- `successUrl` and `cancelUrl` must be on **this site's own origin**. The
42
- shared `flowRedirectUrlSchema` pins only "an `http(s)` URL with a host",
43
- so without this gate a caller posting straight at the API — bypassing the
44
- consumer's Server Action, which builds both URLs itself — could mint a
45
- real Checkout Session whose post-payment redirect lands on a host they
46
- control: an open redirect carrying the credibility of a completed
47
- payment, and `cancelUrl` is the same vector without the victim even
48
- paying.
49
-
50
- | Var | Default | Meaning |
51
- | -------------- | ----------------------- | -------------------------------------------------------------------------- |
52
- | `FRONTEND_URL` | `http://localhost:3000` | The site origin checkout redirects are locked to — the allow-list, of one. |
53
-
54
- **The allow-list source is `FRONTEND_URL`**, read per request. It is the
55
- same variable this API already uses as its CORS origin
56
- (`apps/api/src/server.ts`), for the origin check in
57
- `middleware/auth.ts`, and to compose the billing-portal return URL
58
- (`routes/billing/portal.ts`) — reused rather than inventing a second
59
- site-origin contract, exactly as the scaffolded commerce checkout route
60
- does (`packages/create-ailk/templates/.claude/skills/scaffold-commerce/
61
- templates/checkout-route.template.ts`). The default is byte-identical to
62
- the one `apps/api/src/config/index.ts` applies.
63
-
64
- The comparison is **exact origin equality** — scheme, host and port must
65
- all match. A prefix or suffix test would accept
66
- `https://<site-origin>.evil.example`. A relative path cannot arrive: the
67
- shared schema refuses it before this gate runs. A refused redirect is a
68
- `400 invalid_input` raised **before** the register read, so no session,
69
- no lead and no buyer row is written and Stripe is never called; the
70
- offending URL is never echoed back or logged.
71
-
72
- **Operator consequence.** In production, leave `FRONTEND_URL` unset and
73
- your own checkout redirects will 400 — the same fail-closed direction
74
- CORS already takes on this variable. Set it to the origin visitors reach
75
- the site on. A value that is set but not a parseable absolute URL is a
76
- `500 config-error`, never a silently widened allow-list.
77
-
78
- ## Rate limit — env var and default
79
-
80
- | Var | Default | Meaning |
81
- | ----------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
82
- | `AILK_RATE_LIMIT_PER_IP_PER_MINUTE` | `20` | Requests one visitor IP may make against a rate-limited anonymous scope (flows/leads/aeo/schedule) in a 60-second window. |
83
-
84
- **Why 20.** The default is sized to a full flow — one visitor completing
85
- the longest shipped flow in one sitting should never trip the limit. The
86
- longest shipped flow, `ailk-founder-diagnostic`, is **13 steps + 1 GET**
87
- (the initial config fetch) = 14 requests. 20 leaves headroom for an
88
- invisible `aeo_score` step's own submission (fired alongside a visible
89
- step, #4663 D7) and one legitimate resubmit (e.g. a transient failure the
90
- visitor retries).
91
-
92
- If a customer fork ships a longer flow, raise this env var to
93
- `<step count> + 1 (GET) + 1 (aeo_score, if the flow has one) + a small
94
- margin` rather than lowering the floor for every other flow.
95
-
96
- ## Visitor-IP forwarding (#4730)
97
-
98
- A consumer site's own Server Action (`apps/web/app/[locale]/flows/[slug]/
99
- flow.actions.ts`) calls these routes from ITS OWN server process — every
100
- visitor of that site shares one calling process. Without forwarding, the
101
- API would see that process's egress IP for every request, and every
102
- visitor would share ONE rate-limit bucket.
103
-
104
- The fix has two halves:
105
-
106
- 1. `apps/api/src/server.ts` sets `trustProxy: true` on the Fastify
107
- instance, so `req.ip` resolves the LEFTMOST address of a multi-hop
108
- `X-Forwarded-For` chain (Fastify's built-in `@fastify/proxy-addr`
109
- parsing) instead of a socket address that's meaningless once requests
110
- are bridged through `.inject()` (the Vercel serverless handler,
111
- `apps/api/api/index.ts`, never opens a real socket).
112
- 2. The Server Action reads ITS OWN incoming request's `x-forwarded-for`
113
- (falling back to `x-real-ip`) via Next's `headers()` and forwards it
114
- as an explicit `x-forwarded-for` header on the outbound
115
- `flows.submitStep` call. This APPENDS to, rather than replaces,
116
- whatever hop this API's own edge subsequently adds — the leftmost
117
- entry in the resulting chain is still the real visitor.
118
-
119
- **Trade-off, stated plainly.** A caller hitting these routes directly
120
- (bypassing a consumer's Server Action) can set `x-forwarded-for` to
121
- anything — there is no fixed, allowlistable upstream proxy to trust,
122
- because this API's actual "proxies" are arbitrary consumer web
123
- processes. Per-IP rate limiting on these anonymous surfaces is a
124
- best-effort abuse deterrent, not a hard security boundary; this fix
125
- trades a small amount of spoof-resistance for correctness on the
126
- overwhelmingly common path (Server-Action-forwarded traffic), which was
127
- previously broken for every visitor.
128
-
129
- ## GET config fetch is NOT IP-forwarded (yet)
130
-
131
- `apps/web/app/[locale]/flows/[slug]/page.tsx` (a Server Component) calls
132
- `flows.get(slug)` directly — it does not forward a visitor IP header.
133
- That GET therefore still buckets on whatever `req.ip` resolves to for the
134
- calling process (not the visitor), separately from the POST-step bucket
135
- above. This is a known, narrower gap than the one #4730 fixes (a
136
- single-visitor lockout across 13+ POST steps); extending `flows.get` +
137
- `page.tsx` the same way is a natural follow-up if it proves to matter in
138
- practice.
139
-
140
- **What this means concretely, so the gap isn't understated:** a site
141
- receiving more than the default 20 flow-page loads per minute from one
142
- calling process would 429 its own config GET — and that failure would
143
- surface **nowhere in the UI**, because `flows.get` (unlike
144
- `flows.submitStep`) has no 429-to-`rate_limited` mapping at all; the page
145
- would simply fail to render the flow. This is not a regression (the
146
- pre-#4730 shared bucket was smaller, at 10), and no acceptance criterion
147
- covers it — named here so the follow-up starts from the right severity.