create-ailk 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +336 -27
  3. package/component-catalog.md +25 -12
  4. package/dist/lib/api-boot-assertion.d.ts +46 -0
  5. package/dist/lib/api-boot-assertion.js +70 -0
  6. package/dist/lib/import-graph.d.ts +29 -0
  7. package/dist/lib/import-graph.js +140 -0
  8. package/dist/module-architecture.d.ts +1 -0
  9. package/dist/module-architecture.js +253 -127
  10. package/dist/surfaces.js +105 -7
  11. package/dist/template-manifest.d.ts +33 -0
  12. package/dist/template-manifest.js +99 -16
  13. package/dist/template-names.d.ts +1 -1
  14. package/dist/template-names.js +1 -0
  15. package/package.json +3 -2
  16. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
  17. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
  18. package/site-templates/marketing-site/ailk-template.json +1 -1
  19. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
  20. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
  21. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
  22. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
  23. package/site-templates/minimalism-site/ailk-template.json +2 -1
  24. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  25. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
  26. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  27. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  28. package/site-templates/simple-service/ailk-template.json +10 -0
  29. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  30. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  31. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  32. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  33. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  34. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  35. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  36. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  37. package/site-templates/simple-service/provenance.json +4 -0
  38. package/templates/.env.example +72 -7
  39. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  40. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  41. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  42. package/templates/apps/api/.env.example +13 -0
  43. package/templates/apps/api/CLAUDE.md +26 -2
  44. package/templates/apps/api/jest.config.cjs +3 -0
  45. package/templates/apps/api/package.json +1 -0
  46. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  47. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
  48. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  49. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  50. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
  51. package/templates/apps/api/src/config/index.ts +62 -0
  52. package/templates/apps/api/src/config/modules.ts +7 -3
  53. package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
  54. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  55. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  56. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  57. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  58. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  59. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  60. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  61. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  62. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  63. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  64. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  65. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  66. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  67. package/templates/apps/api/src/lib/site.ts +208 -9
  68. package/templates/apps/api/src/lib/stripe.ts +66 -0
  69. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
  70. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
  71. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  72. package/templates/apps/api/src/openapi/spec.ts +543 -2
  73. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  74. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  75. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  76. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  77. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  78. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  79. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  80. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  81. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  82. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  83. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  84. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  85. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  86. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  87. package/templates/apps/api/src/routes/content/index.ts +13 -73
  88. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  89. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  90. package/templates/apps/api/src/routes/content/search.ts +129 -0
  91. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  92. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  93. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  94. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  95. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  96. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  97. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  98. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  99. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  100. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  101. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  102. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  103. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  104. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  105. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  106. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  111. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  112. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  113. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  114. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  115. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  116. package/templates/apps/api/src/server.ts +439 -49
  117. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  118. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  119. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  120. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  121. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  122. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  123. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  124. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  125. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  126. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  127. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  128. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  129. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  130. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  131. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  132. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  133. package/templates/apps/api/src/services/audit-log.ts +668 -0
  134. package/templates/apps/api/src/services/consent.ts +18 -0
  135. package/templates/apps/api/src/services/default-tenant.ts +70 -31
  136. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  137. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  138. package/templates/apps/api/src/services/entitlements.ts +80 -0
  139. package/templates/apps/api/src/services/lead-promotion.ts +13 -14
  140. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  141. package/templates/apps/api/src/services/leads-inbox.ts +23 -2
  142. package/templates/apps/api/src/services/listing-config.ts +27 -9
  143. package/templates/apps/api/src/services/project-listings.ts +201 -6
  144. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  145. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  146. package/templates/apps/api/src/services/revalidation.ts +173 -0
  147. package/templates/apps/api/src/services/verified-email.ts +71 -0
  148. package/templates/apps/api/src/vercel-handler.ts +75 -35
  149. package/templates/apps/mcp/.env.example +17 -0
  150. package/templates/apps/mcp/CLAUDE.md +57 -24
  151. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  152. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  153. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  154. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  155. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  156. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  157. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  158. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  159. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  160. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  161. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  162. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  163. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  164. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  165. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  166. package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
  167. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  168. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  169. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  170. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  171. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  172. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  173. package/templates/apps/mcp/jest.config.cjs +13 -0
  174. package/templates/apps/mcp/package.json +2 -0
  175. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  176. package/templates/apps/mcp/src/config/modules.ts +10 -5
  177. package/templates/apps/mcp/src/http-transport.ts +102 -29
  178. package/templates/apps/mcp/src/index.ts +73 -11
  179. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  180. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  181. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  182. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  183. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  184. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  185. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  186. package/templates/apps/mcp/src/server.ts +59 -35
  187. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  188. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  189. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  190. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  191. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  192. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  193. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  194. package/templates/apps/mcp/src/tools/index.ts +6 -4
  195. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  196. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  197. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  198. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  199. package/templates/apps/web/.env.example +71 -5
  200. package/templates/apps/web/CLAUDE.md +44 -0
  201. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  202. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  203. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  204. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  205. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  206. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  207. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  208. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  209. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  210. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  211. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  212. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  213. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  214. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  215. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  216. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  217. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  218. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  219. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  220. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
  221. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
  222. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  223. package/templates/apps/web/app/[locale]/layout.tsx +133 -87
  224. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  225. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  226. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  227. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  228. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  229. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  230. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  231. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  232. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  233. package/templates/apps/web/auth.ts +407 -8
  234. package/templates/apps/web/jest.config.cjs +8 -4
  235. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  236. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  237. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  238. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
  239. package/templates/apps/web/lib/auth-email.ts +318 -0
  240. package/templates/apps/web/lib/auth-env.ts +85 -0
  241. package/templates/apps/web/lib/auth-routes.ts +55 -0
  242. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  243. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  244. package/templates/apps/web/messages/en.json +187 -0
  245. package/templates/apps/web/package.json +2 -1
  246. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  247. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  248. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  249. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  250. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  251. package/templates/database/CHANGELOG.md +142 -0
  252. package/templates/database/README.md +10 -3
  253. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  254. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  255. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  256. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  257. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  258. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  259. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  260. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  261. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  262. package/templates/database/__tests__/tenancy.test.ts +141 -0
  263. package/templates/database/config/schema.prisma +79 -2
  264. package/templates/database/content/schema.prisma +87 -11
  265. package/templates/database/inbox/schema.prisma +452 -9
  266. package/templates/database/jest.config.cjs +5 -3
  267. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  268. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  269. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  270. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  271. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  272. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  273. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  274. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  275. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  276. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  277. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  278. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  279. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  280. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  281. package/templates/database/package.json +1 -1
  282. package/templates/database/tenancy.ts +86 -1
  283. package/templates/database/tsconfig.json +1 -0
  284. package/versions.json +34 -34
  285. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  286. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  287. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  288. package/templates/apps/api/src/routes/content/create.ts +0 -115
  289. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  290. package/templates/apps/api/src/routes/content/update.ts +0 -112
  291. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  292. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  293. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  294. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  295. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  296. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  297. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  298. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  299. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
@@ -0,0 +1,265 @@
1
+ # Webhooks — `apps/api/src/routes/webhooks/`
2
+
3
+ Stripe webhook receiver at `POST /v1/webhooks/stripe`. Verifies
4
+ signatures, dispatches events via a `switch`, and fires server-side
5
+ telemetry. Handles `customer.created`, `checkout.session.completed`, and
6
+ `checkout.session.expired` (the last two also stamp the recommender's
7
+ buyer row — below).
8
+
9
+ ## Commerce claim — signature first, claim second (D-64, #5796)
10
+
11
+ The receiver is a base-tree **primitive**: it registers on every build, with
12
+ no `isModuleEnabled("commerce")` gate, and decides whether to act on the JWT
13
+ `features[]` claim `commerce` (`lib/require-commerce-claim.ts`, read at call
14
+ time through `checkLicense()`). The order inside `stripe.ts` is fixed and
15
+ load-bearing: **the Stripe signature is verified BEFORE the claim is
16
+ consulted.** An unsigned body therefore never reaches the claim check on its
17
+ own merits, and the two unclaimed outcomes are:
18
+
19
+ - **Signed, verifiable event, licence read and positively unclaimed** →
20
+ **200 `{received: true}`**, the event dropped with a structured `warn` log
21
+ line (`outcome: "dropped_no_commerce_claim"`). A 4xx here would have Stripe
22
+ retry the event for days against a site that turned commerce off. It is
23
+ logged at `warn`, not `info`: a discarded Stripe event is never routine.
24
+ - **Missing secret / non-Buffer body / missing or invalid signature, licence
25
+ read and unclaimed** → the **module-disabled 404**, byte-identical to a
26
+ build that never carried the commerce module. A site without the claim is
27
+ indistinguishable from a site without the module, which is the point.
28
+
29
+ With the claim, every mapping in the § above is exactly what it was before
30
+ the gate existed.
31
+
32
+ ### The claim is a TRI-state, and `undetermined` is never a drop
33
+
34
+ Security review (2026-09-16, High) against the first cut of this gate: a
35
+ boolean `hasCommerceClaim()` returned one `false` both for "the licence says
36
+ no" and for "the licence could not be read". On the second, a **live** commerce
37
+ site whose read merely failed would ack a signed `checkout.session.completed`
38
+ 200 and lose it — Stripe records success, never redelivers, and the charge is
39
+ never mapped to a customer. The pre-#5796 module flag was build-time and its
40
+ off state 404'd, which Stripe retried; the runtime gate introduced the loss.
41
+
42
+ So `resolveCommerceClaim()` answers `claimed | unclaimed | undetermined`, and
43
+ **`undetermined` → 503 with `Retry-After: 300`**, which makes Stripe queue the
44
+ event and redeliver it once the read succeeds — well inside Stripe's three-day
45
+ redelivery window. The drop and the hold are both logged at `warn`
46
+ (`outcome: "dropped_no_commerce_claim"` and `outcome: "license_undetermined"`):
47
+ neither a discarded nor a held Stripe event is routine.
48
+
49
+ `undetermined` covers every read that established no "no" (operator-disposed,
50
+ round 2):
51
+
52
+ - **`checkLicense()` threw.**
53
+ - **A revoked licence** (`revoked === true`). It keeps verifying locally until
54
+ JWT `exp` (ADR 0014), so this is a paying commerce site mid-window whose
55
+ events are still real. Closing the gate is right; dropping the events is the
56
+ review's High under another label.
57
+ - **A licence is configured yet resolves free** — the expired case, and any
58
+ other JWT that no longer verifies. `checkLicense()` returns the _same_
59
+ `FREE_RESULT` for expired, invalid and keyless alike, so the only signal that
60
+ separates them is whether a licence is CONFIGURED at all. The gate tests that
61
+ the same way `check-license.ts` resolves a token — `AILK_LICENSE_KEY`, else
62
+ the refresh storage file — for presence only: it parses no JWT and
63
+ re-implements no verification. Reading the storage file matters, because a
64
+ deployment can be fully licensed with no env key at all; an env-only test
65
+ read such a site as keyless, so its EXPIRED licence dropped already-charged
66
+ events.
67
+ - **The licence storage file is UNREADABLE** (#5820) — EACCES, EIO, EMFILE, a
68
+ directory at its path, a filesystem that went away. See below.
69
+
70
+ `unclaimed` is therefore reserved for a positively-established "no": a licence
71
+ that reads successfully and simply lacks the claim, or a keyless deployment
72
+ that configures no licence at all — where "no licence at all" now means the
73
+ storage file was positively read as ABSENT, not merely that reading it failed.
74
+
75
+ ### `unreadable` is not `absent` (#5820)
76
+
77
+ The gate reads the storage file through `readStoredJwtState()`
78
+ (`packages/license/src/storage.ts`), which answers **`absent` · `unreadable` ·
79
+ `present`**:
80
+
81
+ - **`absent`** — ENOENT, or a file holding nothing but whitespace. The only
82
+ state that is evidence of an unlicensed site, and therefore the only one on
83
+ this path that may drop a signed event.
84
+ - **`unreadable`** — every other read failure. Evidence of nothing, so it
85
+ resolves `undetermined` → **503 + `Retry-After: 300`**, logged with the
86
+ errno `code` (`outcome: "license_storage_unreadable"`).
87
+ - **`present`** — the token; resolution continues exactly as for an env key.
88
+
89
+ This closes the last gap in the tri-state. `readStoredJwt()` catches every read
90
+ error and returns the same `null` it returns for "no file", so before this a
91
+ transient EACCES on a storage-licensed site read as keyless: a signed
92
+ `checkout.session.completed` in that window was acked 200, Stripe never
93
+ redelivered it, and the charge was never mapped. `readStoredJwt()`'s own
94
+ contract is unchanged — `checkLicense()` and every other caller still see one
95
+ `null` for both states — so only the gate's resolution moved.
96
+
97
+ **The checkout launcher does not hold.** It answers the module-disabled 404 on
98
+ both `unclaimed` and `undetermined`: a checkout that cannot be started loses
99
+ nothing, so there is no call to leak a "configured but unreadable" shape on it.
100
+ The webhook holds because by the time an event arrives the buyer's money has
101
+ already moved.
102
+
103
+ ## How to add a new event handler
104
+
105
+ Add a `case` arm to the `switch` in `stripe.ts`:
106
+
107
+ ```ts
108
+ case "invoice.payment_succeeded": {
109
+ const invoice = event.data.object as { ... };
110
+ await fireTelemetryWithTimeout(request, "invoice-paid", { ... });
111
+ break;
112
+ }
113
+ ```
114
+
115
+ Customer forks edit `stripe.ts` directly. v1 ships no pluggable handler
116
+ registry — if multiple forks request one, file an issue.
117
+
118
+ ## Recommender purchase stamp (#4838)
119
+
120
+ A Checkout Session the recommender opened (`POST /v1/flows/:slug/checkout`,
121
+ #4841) carries `metadata.flowCheckoutId` — the `FlowCheckout` buyer row's
122
+ id. On `checkout.session.completed` the handler stamps that row
123
+ `status: "completed"` (+ `completedAt`, `stripeSessionId`,
124
+ `stripeCustomerId`); on `checkout.session.expired` it stamps
125
+ `status: "expired"`. Both are ONE guarded update —
126
+ `updateMany({ where: { id, status: "created" } })` — so the stamp is
127
+ at-most-once regardless of the in-memory store below: a replay, a restart
128
+ between deliveries, or a late `expired` after a `completed` matches zero
129
+ rows and logs `outcome: "stale_or_unknown"`. A charged `amount_total` that
130
+ differs from the quoted `amountMinor` is logged (`expected` / `received`)
131
+ and the row is still stamped — the quote is never overwritten. The stamp
132
+ runs beside the telemetry + billing tasks, never instead of them, and an
133
+ event without the key never touches `flowCheckout`. Always 200.
134
+
135
+ ## Dunning (#6034)
136
+
137
+ Two arms record a per-subscriber lifecycle state so a failed renewal is no
138
+ longer silent:
139
+
140
+ - **`invoice.payment_failed`** — arms dunning for the `User` matched by the
141
+ invoice's Stripe customer id: the recorded state becomes `past_due`,
142
+ `dunningArmedAt` is stamped once (never moved while the subscription stays
143
+ failing), `accessState` stays `granted`. **This arm never revokes.**
144
+ - **`customer.subscription.updated`** — maps the narrowed Stripe status
145
+ (`services/dunning-policy.ts`'s reuse of
146
+ `@working-theory/stripe-client`'s `toSubscriptionLifecycleStatus`) onto the
147
+ same record. `active`/`trialing` return the entry to a clean state
148
+ (`dunningArmedAt: null`, `accessRevokesAt: null`) so a recovered
149
+ subscriber's read shows nothing. `past_due`/`unpaid` arm exactly as
150
+ `invoice.payment_failed` does. `canceled` is the **only revoking
151
+ transition** — access is revoked once a per-tenant grace period, computed
152
+ from the FIRST failure, has already passed; if it hasn't, access is
153
+ retained and a revocation is scheduled.
154
+
155
+ ### The record
156
+
157
+ One `UserData` row per subscriber (`userId_key` unique), reserved key
158
+ `commerce.subscription_dunning`, holding a MAP keyed by Stripe subscription
159
+ id — one customer can hold more than one subscription, and a flat blob would
160
+ let a second subscription's `active` clobber the first's `past_due`:
161
+
162
+ ```jsonc
163
+ {
164
+ "v": 1,
165
+ "subscriptions": {
166
+ "sub_1AbC…": {
167
+ "status": "past_due", // "past_due" | "active" | "canceled"
168
+ "accessState": "granted", // "granted" | "revoked"
169
+ "dunningArmedAt": "2026-09-18T04:12:00.000Z", // ISO-8601, or null
170
+ "accessRevokesAt": null, // ISO-8601, or null — set only on `canceled`
171
+ "lastEventAt": 1789000000, // Stripe event.created, seconds
172
+ "lastEventId": "evt_1XyZ…",
173
+ },
174
+ },
175
+ }
176
+ ```
177
+
178
+ Capped at 50 subscriptions per customer, evicting the oldest SETTLED entries
179
+ (`status: "active"`, `accessState: "granted"`) first — an entry still being
180
+ dunned is never evicted mid-decision.
181
+
182
+ **No scheduler flips this.** `accessRevokesAt` is a lazily-evaluated
183
+ deadline; nothing wakes up to act on it. Two exported read-side predicates
184
+ (`services/dunning-policy.ts`) are the reader's contract instead:
185
+
186
+ - `isSubscriberRevoked(entry, now?)` — access is gone: either already stamped
187
+ `revoked`, or `now` has passed the scheduled `accessRevokesAt`.
188
+ - `isSubscriberActionRequired(entry, now?)` — the buyer must act (#6038's
189
+ banner predicate). Deliberately WIDER than `status === "past_due"`: a
190
+ subscription cancelled with its grace window still open
191
+ (`status: "canceled"`, `accessState: "granted"`, `accessRevokesAt`
192
+ non-null) is a buyer about to lose access who hasn't been told.
193
+
194
+ **Ordering, ties, and durable replay.** Every write is a compare-and-swap
195
+ whose `where` restates BOTH the observed `updatedAt` AND the observed
196
+ `value` — a concurrent writer makes the predicate miss rather than silently
197
+ overwrite. The SAME `event.id` replayed (even after a process restart, when
198
+ the in-memory `hasSeen` map has forgotten it) is a no-op; a strictly OLDER
199
+ event is stale; an EQUAL-timestamp write that would move the entry toward
200
+ LESS restriction (setting `status` to `active`, clearing `accessRevokesAt`,
201
+ or moving `accessState` from `revoked` to `granted`) is rejected as a stale
202
+ tie — this is what stops a reordered delivery from permanently un-revoking a
203
+ cancelled subscriber. A genuinely newer event is always admitted, including
204
+ one that restores a terminal entry to `active`/`granted` (a real
205
+ reactivation).
206
+
207
+ ### The grace policy
208
+
209
+ `dunningPolicyFor(siteId)` reads a `SiteSetting` row keyed
210
+ `commerce.dunning_policy:<siteId>` (`SiteSetting.key` has no `siteId`
211
+ column of its own, so per-tenant scoping lives in the key string — never an
212
+ unsuffixed fallback). Absent, malformed, non-numeric, negative, non-finite,
213
+ or out-of-range (`> 90`) all resolve to a documented default (14 days) or a
214
+ clamp, logged, never thrown. On a **multi-tenant** deployment this key is
215
+ shared by every seller on the deployment (one process-wide `siteId`) — a
216
+ one-time `warn` log names that on the first read.
217
+
218
+ ### The reserved key namespace
219
+
220
+ The record above would be worthless if its own subject could rewrite it:
221
+ `GET`/`PUT`/`DELETE /v1/user/data/:key` (`routes/auth/user.ts`) refuse a
222
+ `commerce.`- or `system.`-prefixed key (case-insensitive) with `403
223
+ {error: "reserved-key"}` before any Prisma call. `PUT` additionally bounds
224
+ the body at 64 KiB. Every unreserved key's behavior is unchanged.
225
+
226
+ ## Idempotency caveat (load-bearing)
227
+
228
+ Stripe uses **at-least-once delivery semantics** and occasionally
229
+ double-delivers events. The in-memory store (`lib/webhook-idempotency.ts`)
230
+ short-circuits a replayed `event.id` within one process; it is not durable
231
+ across a restart or a second API instance. Telemetry-only side effects make
232
+ that an acceptable counting drift.
233
+
234
+ **A side effect with write semantics carries its own durable layer.** The
235
+ recommender stamp above uses a guarded conditional update (the row's
236
+ `status: "created"` is the key). A future write that cannot be expressed as
237
+ a guarded update (confirmation email, refund processing) needs a
238
+ `webhook_events` table keyed on `event.id` with an early-return for
239
+ already-seen IDs — add it first.
240
+
241
+ ## Env contract
242
+
243
+ Both vars are **server-only** — never use a `NEXT_PUBLIC_` prefix.
244
+
245
+ | Var | Required | Notes |
246
+ | ----------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
247
+ | `STRIPE_WEBHOOK_SECRET` | For the route to do anything | `whsec_*` from Stripe Dashboard |
248
+ | `STRIPE_SECRET_KEY` | For checkout + sandbox check | Must match an allow-listed test-mode prefix (`sk_test_*` / `pk_test_*` / `rk_test_*`); anything else — including live and restricted-live keys — is runtime-blocked by `assertSandboxKey`. The commit-time hook is a partial backstop only: it greps for `sk_live_` / `pk_live_` and does **not** catch restricted-live (`rk_live_`) keys, so the runtime allow-list is the load-bearing guard |
249
+
250
+ Without `STRIPE_WEBHOOK_SECRET`, the route returns 503 on every request.
251
+
252
+ ## Deployment note
253
+
254
+ Customer forks must:
255
+
256
+ 1. Configure a Stripe Dashboard webhook endpoint pointing at
257
+ `https://<your-host>/v1/webhooks/stripe`.
258
+ 2. Copy the endpoint's **Signing secret** (`whsec_*`) into
259
+ `STRIPE_WEBHOOK_SECRET` in their environment.
260
+
261
+ Without both, telemetry will not fire and 503s will be logged.
262
+
263
+ ## Brief
264
+
265
+ `docs/internal/decisions/ailk-stripe-webhook-receiver.md`