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,471 @@
1
+ /**
2
+ * @file server.test.ts
3
+ * @description Unit tests for server.ts - Server setup
4
+ *
5
+ * Uses the real build() function with real route plugins. All internal modules
6
+ * run real; external boundaries (CORS, rate-limit) are mocked at the plugin
7
+ * boundary since they would require live network/Redis in production.
8
+ */
9
+
10
+ import { build, requestLogSerializer } from "../server";
11
+
12
+ // External service boundaries — mocked because they require live infrastructure
13
+ // or have ESM-only transitive deps that Jest's CJS preset cannot resolve:
14
+ // @fastify/cors, @fastify/rate-limit — Fastify plugins that would require
15
+ // live network infrastructure (CORS policy sync, Redis store) in unit tests.
16
+ // @working-theory/email — transitive dep on @react-email/components which is ESM-only
17
+ // and cannot be resolved by jest-resolve in CommonJS mode.
18
+ jest.mock("@fastify/cors", () => {
19
+ return jest.fn().mockResolvedValue(undefined);
20
+ });
21
+
22
+ jest.mock("@fastify/rate-limit", () => {
23
+ return jest.fn().mockResolvedValue(undefined);
24
+ });
25
+
26
+ jest.mock("@working-theory/email", () => ({
27
+ sendLeadNotification: jest.fn().mockResolvedValue({ ok: true }),
28
+ }));
29
+
30
+ describe("Server", () => {
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- server is typed via Fastify generics; explicit any avoids pulling in the full FastifyInstance type here
32
+ let app: any;
33
+
34
+ beforeEach(async () => {
35
+ jest.clearAllMocks();
36
+ });
37
+
38
+ afterEach(async () => {
39
+ if (app) {
40
+ await app.close();
41
+ }
42
+ });
43
+
44
+ describe("build function", () => {
45
+ it("should build Fastify server instance", async () => {
46
+ // ACT
47
+ app = await build();
48
+
49
+ // ASSERT
50
+ expect(app).toBeDefined();
51
+ expect(typeof app.inject).toBe("function");
52
+ });
53
+
54
+ it("should register health check route returning static OK without external calls", async () => {
55
+ // ARRANGE
56
+ app = await build();
57
+
58
+ // ACT
59
+ const response = await app.inject({
60
+ method: "GET",
61
+ url: "/health",
62
+ });
63
+
64
+ // ASSERT
65
+ expect(response.statusCode).toBe(200);
66
+ const body = JSON.parse(response.body);
67
+ expect(body.status).toBe("healthy");
68
+ expect(body.timestamp).toBeDefined();
69
+ // No external dependencies in the health check anymore
70
+ expect(body.platform).toBeUndefined();
71
+ });
72
+
73
+ it("should register root endpoint", async () => {
74
+ // ARRANGE
75
+ app = await build();
76
+
77
+ // ACT
78
+ const response = await app.inject({
79
+ method: "GET",
80
+ url: "/",
81
+ });
82
+
83
+ // ASSERT
84
+ expect(response.statusCode).toBe(200);
85
+ const body = JSON.parse(response.body);
86
+ expect(body.name).toBe("AI Launch Kit API");
87
+ expect(body.endpoints).toBeDefined();
88
+ });
89
+
90
+ it("should register CORS", async () => {
91
+ // ACT
92
+ app = await build();
93
+
94
+ // ASSERT - CORS should be registered (no error thrown)
95
+ expect(app).toBeDefined();
96
+ });
97
+
98
+ it("should register all route plugins", async () => {
99
+ // ARRANGE
100
+ app = await build();
101
+
102
+ // ACT & ASSERT - Test that routes are registered by checking they don't 404
103
+ const healthResponse = await app.inject({
104
+ method: "GET",
105
+ url: "/health",
106
+ });
107
+ expect(healthResponse.statusCode).toBe(200);
108
+
109
+ const rootResponse = await app.inject({
110
+ method: "GET",
111
+ url: "/",
112
+ });
113
+ expect(rootResponse.statusCode).toBe(200);
114
+ });
115
+
116
+ it("should include all endpoints in root response", async () => {
117
+ // ARRANGE
118
+ app = await build();
119
+
120
+ // ACT
121
+ const response = await app.inject({
122
+ method: "GET",
123
+ url: "/",
124
+ });
125
+
126
+ // ASSERT
127
+ const body = JSON.parse(response.body);
128
+ expect(body.endpoints).toHaveProperty("health");
129
+ expect(body.endpoints).toHaveProperty("tokens");
130
+ expect(body.endpoints).toHaveProperty("profile");
131
+ });
132
+
133
+ it("startup throws when LEAD_CAPTURE_FILE points at a missing directory", async () => {
134
+ // ARRANGE — set LEAD_CAPTURE_FILE to a path whose parent dir doesn't exist
135
+ // so the real validateLeadCaptureDir() throws (AC8). Restore after test.
136
+ const prior = process.env.LEAD_CAPTURE_FILE;
137
+ process.env.LEAD_CAPTURE_FILE = "/nonexistent-ailk-test-dir/leads.jsonl";
138
+
139
+ try {
140
+ // ACT & ASSERT
141
+ await expect(build()).rejects.toThrow(/LEAD_CAPTURE_FILE/);
142
+ } finally {
143
+ if (prior === undefined) {
144
+ delete process.env.LEAD_CAPTURE_FILE;
145
+ } else {
146
+ process.env.LEAD_CAPTURE_FILE = prior;
147
+ }
148
+ }
149
+ });
150
+ });
151
+ });
152
+
153
+ // ── Bootstrap test: raw-body parser scope encapsulation (AC 13) ──────────────
154
+ //
155
+ // Proves that registering the webhooks plugin (with its raw-body parser scoped
156
+ // to application/json) does NOT leak that parser to other routes. The global
157
+ // Fastify JSON parser must remain active for all non-webhook routes.
158
+
159
+ import Fastify from "fastify";
160
+ import { webhooksRoutes } from "../routes/webhooks/index.js";
161
+
162
+ describe("Parser scope — webhooks raw-body parser does not leak", () => {
163
+ it("other routes still receive parsed JSON objects after webhooks plugin registers", async () => {
164
+ // Build a minimal server that mirrors the encapsulation pattern in server.ts:
165
+ // 1. Register a JSON route that echoes the parsed body.
166
+ // 2. Register the webhooks plugin inside an encapsulated scope with a
167
+ // raw-body (buffer) parser.
168
+ // If the raw-body parser leaks out of the webhooks scope, the echo route
169
+ // would receive a Buffer instead of a parsed object.
170
+
171
+ const server = Fastify({ logger: false });
172
+
173
+ // A simple JSON echo route — simulates any existing JSON-parsed route
174
+ server.post("/api/echo", async (req) => ({ received: req.body }));
175
+
176
+ // Mirror of server.ts registration pattern
177
+ await server.register(async (webhooksScope) => {
178
+ webhooksScope.addContentTypeParser(
179
+ "application/json",
180
+ { parseAs: "buffer" },
181
+ (_req, body, done) => {
182
+ done(null, body);
183
+ },
184
+ );
185
+ await webhooksScope.register(webhooksRoutes);
186
+ });
187
+
188
+ await server.ready();
189
+
190
+ try {
191
+ const response = await server.inject({
192
+ method: "POST",
193
+ url: "/api/echo",
194
+ payload: { hello: "world" },
195
+ headers: { "content-type": "application/json" },
196
+ });
197
+
198
+ expect(response.statusCode).toBe(200);
199
+ const body = JSON.parse(response.body);
200
+ // Must be a plain object, not a stringified Buffer
201
+ expect(body.received).toEqual({ hello: "world" });
202
+ expect(Buffer.isBuffer(body.received)).toBe(false);
203
+ } finally {
204
+ await server.close();
205
+ }
206
+ });
207
+
208
+ describe("per-scope rate-limit defaults (#5111, DEF-004)", () => {
209
+ // Real end-to-end behavior against the real in-house rate-limit
210
+ // middleware (NOT mocked — this repo's convention is to mock only real
211
+ // external boundaries, never an internal module, see the file header).
212
+ // aeoScope's `/v1/aeo/score/example` needs no DB/network (a bundled
213
+ // example, `loadExampleScore()`); flowsScope's `/v1/flows/:slug` with a
214
+ // schema-invalid slug returns 400 before any DB access — both let the
215
+ // onRequest rate-limit hook be the only thing under test, regardless of
216
+ // the eventual handler status.
217
+ it("caps aeoScope at the strict pre-#4730 default (10/min) — the 11th request 429s", async () => {
218
+ app = await build();
219
+ const ip = "198.51.100.11";
220
+
221
+ for (let i = 0; i < 10; i++) {
222
+ const res = await app.inject({
223
+ method: "GET",
224
+ url: "/v1/aeo/score/example",
225
+ headers: { "x-forwarded-for": ip },
226
+ });
227
+ expect(res.statusCode).not.toBe(429);
228
+ }
229
+
230
+ const eleventh = await app.inject({
231
+ method: "GET",
232
+ url: "/v1/aeo/score/example",
233
+ headers: { "x-forwarded-for": ip },
234
+ });
235
+ expect(eleventh.statusCode).toBe(429);
236
+ });
237
+
238
+ it("leaves flowsScope above the strict default — 11 requests never 429", async () => {
239
+ // #5366 (D-48): /v1/flows/:slug only registers when signal-collectors
240
+ // is enabled.
241
+ const prior = process.env.AILK_ENABLED_MODULES;
242
+ process.env.AILK_ENABLED_MODULES = "signal-collectors";
243
+ try {
244
+ app = await build();
245
+ const ip = "198.51.100.12";
246
+
247
+ for (let i = 0; i < 11; i++) {
248
+ const res = await app.inject({
249
+ method: "GET",
250
+ url: "/v1/flows/not-a-real-slug!!!",
251
+ headers: { "x-forwarded-for": ip },
252
+ });
253
+ // Schema-invalid slug → 400, well before any rate limit or DB call.
254
+ expect(res.statusCode).toBe(400);
255
+ }
256
+ } finally {
257
+ if (prior === undefined) {
258
+ delete process.env.AILK_ENABLED_MODULES;
259
+ } else {
260
+ process.env.AILK_ENABLED_MODULES = prior;
261
+ }
262
+ }
263
+ });
264
+ });
265
+ });
266
+
267
+ // ── #5970 — the request log must never carry a query string ────────────────
268
+ //
269
+ // The security review's High finding: a bare `logger: true` gives pino's stock
270
+ // `req` serializer, which logs `req.raw.url` VERBATIM — so every open of
271
+ // `/v1/requests/:id/confirm?token=…` wrote a live seven-day credential into
272
+ // the log drain, from a hook that fires before any route-level code can
273
+ // intervene. The four fixes for that finding shipped with no assertion between
274
+ // them, which is precisely how this class of bug comes back: a future refactor
275
+ // restores `request.url` and nothing turns red.
276
+ //
277
+ // Asserted both ways — the serializer's own output, and a real captured line,
278
+ // so neither the function nor its WIRING can regress unnoticed.
279
+ describe("#5970 — the request log carries no query string", () => {
280
+ const asRequest = (url: string) =>
281
+ ({
282
+ url,
283
+ method: "GET",
284
+ hostname: "api.example.com",
285
+ ip: "203.0.113.1",
286
+ }) as never;
287
+
288
+ it("drops the query string but keeps the path and its route parameters", () => {
289
+ expect(
290
+ requestLogSerializer(
291
+ asRequest("/v1/requests/callbk_1/confirm?token=SUPERSECRETTOKEN"),
292
+ ),
293
+ ).toEqual({
294
+ method: "GET",
295
+ url: "/v1/requests/callbk_1/confirm",
296
+ host: "api.example.com",
297
+ remoteAddress: "203.0.113.1",
298
+ });
299
+ });
300
+
301
+ it("leaves a URL with no query exactly as it is", () => {
302
+ expect(requestLogSerializer(asRequest("/health")).url).toBe("/health");
303
+ });
304
+
305
+ it("is actually wired into the instance — a captured line carries no token", async () => {
306
+ const lines: string[] = [];
307
+ const fastify = require("fastify")({
308
+ logger: {
309
+ level: "info",
310
+ serializers: { req: requestLogSerializer },
311
+ stream: { write: (msg: string) => lines.push(msg) },
312
+ },
313
+ });
314
+ fastify.get("/v1/requests/:id/confirm", async () => ({ ok: true }));
315
+ await fastify.ready();
316
+ try {
317
+ await fastify.inject({
318
+ method: "GET",
319
+ url: "/v1/requests/callbk_1/confirm?token=SUPERSECRETTOKEN",
320
+ });
321
+ const captured = lines.join("\n");
322
+ expect(captured).toContain("/v1/requests/callbk_1/confirm");
323
+ expect(captured).not.toContain("SUPERSECRETTOKEN");
324
+ expect(captured).not.toContain("token=");
325
+ } finally {
326
+ await fastify.close();
327
+ }
328
+ });
329
+ });
330
+
331
+ // #6084 D14/AC-9 — `GET /v1/checkout/sessions/:id` puts its credential in
332
+ // the PATH, which the #5970 query-string strip above does not cover. A
333
+ // `cs_…`-shaped PATH SEGMENT is truncated to its first 8 characters plus
334
+ // `…`; the rule is segment-scoped so it cannot corrupt an unrelated URL.
335
+ describe("#6084 D14 — a cs_… path segment is truncated to 8 characters", () => {
336
+ const asRequest = (url: string) =>
337
+ ({
338
+ url,
339
+ method: "GET",
340
+ hostname: "api.example.com",
341
+ ip: "203.0.113.1",
342
+ }) as never;
343
+
344
+ it("truncates a cs_… session id segment to its first 8 characters plus an ellipsis", () => {
345
+ expect(
346
+ requestLogSerializer(
347
+ asRequest(
348
+ "/v1/checkout/sessions/cs_test_a1B2c3D4e5F6g7H8i9J0",
349
+ ),
350
+ ),
351
+ ).toEqual({
352
+ method: "GET",
353
+ url: "/v1/checkout/sessions/cs_test_…",
354
+ host: "api.example.com",
355
+ remoteAddress: "203.0.113.1",
356
+ });
357
+ });
358
+
359
+ it("leaves an unrelated path byte-identical to today (no cs_ segment present)", () => {
360
+ expect(requestLogSerializer(asRequest("/v1/checkout/sessions")).url).toBe(
361
+ "/v1/checkout/sessions",
362
+ );
363
+ });
364
+
365
+ it("does not truncate a segment that merely CONTAINS cs_ mid-string, only one that IS cs_…-shaped", () => {
366
+ expect(
367
+ requestLogSerializer(asRequest("/v1/content/discs_catalog")).url,
368
+ ).toBe("/v1/content/discs_catalog");
369
+ });
370
+
371
+ it("is actually wired into the instance — a captured line carries no full session id", async () => {
372
+ const lines: string[] = [];
373
+ const fastify = require("fastify")({
374
+ logger: {
375
+ level: "info",
376
+ serializers: { req: requestLogSerializer },
377
+ stream: { write: (msg: string) => lines.push(msg) },
378
+ },
379
+ });
380
+ fastify.get("/v1/checkout/sessions/:id", async () => ({ ok: true }));
381
+ await fastify.ready();
382
+ try {
383
+ await fastify.inject({
384
+ method: "GET",
385
+ url: "/v1/checkout/sessions/cs_test_a1B2c3D4e5F6g7H8i9J0",
386
+ });
387
+ const captured = lines.join("\n");
388
+ expect(captured).toContain("/v1/checkout/sessions/cs_test_…");
389
+ expect(captured).not.toContain(
390
+ "cs_test_a1B2c3D4e5F6g7H8i9J0",
391
+ );
392
+ } finally {
393
+ await fastify.close();
394
+ }
395
+ });
396
+ });
397
+
398
+ // #6216 D11/§ 9 A-3 — the SAME rule's second arm: a `lead_…`-shaped PATH
399
+ // SEGMENT (every item read / PATCH under `/v1/workspaces/:id/messages/:leadId`)
400
+ // is truncated to 8 characters plus `…`, exactly like the `cs_` case above.
401
+ // The `cs_…` behaviour must stay byte-identical (asserted in the SAME test).
402
+ describe("#6216 D11 — a lead_… path segment is truncated to 8 characters, cs_ unchanged", () => {
403
+ const LEAD_ID = "lead_01928374-65ba-7cde-89ab-0123456789ab";
404
+
405
+ const asRequest = (url: string) =>
406
+ ({
407
+ url,
408
+ method: "GET",
409
+ hostname: "api.example.com",
410
+ ip: "203.0.113.1",
411
+ }) as never;
412
+
413
+ it("truncates a lead_… id segment to its first 8 characters plus an ellipsis", () => {
414
+ expect(
415
+ requestLogSerializer(
416
+ asRequest(`/v1/workspaces/wksp_00000000-0000-7000-8000-000000000001/messages/${LEAD_ID}`),
417
+ ),
418
+ ).toEqual({
419
+ method: "GET",
420
+ url: "/v1/workspaces/wksp_00000000-0000-7000-8000-000000000001/messages/lead_019…",
421
+ host: "api.example.com",
422
+ remoteAddress: "203.0.113.1",
423
+ });
424
+ });
425
+
426
+ it("leaves the list path (no :leadId segment) byte-identical to today", () => {
427
+ expect(
428
+ requestLogSerializer(
429
+ asRequest(
430
+ "/v1/workspaces/wksp_00000000-0000-7000-8000-000000000001/messages",
431
+ ),
432
+ ).url,
433
+ ).toBe(
434
+ "/v1/workspaces/wksp_00000000-0000-7000-8000-000000000001/messages",
435
+ );
436
+ });
437
+
438
+ it("the cs_… behaviour is byte-identical after adding the lead_ arm", () => {
439
+ expect(
440
+ requestLogSerializer(
441
+ asRequest("/v1/checkout/sessions/cs_test_a1B2c3D4e5F6g7H8i9J0"),
442
+ ).url,
443
+ ).toBe("/v1/checkout/sessions/cs_test_…");
444
+ });
445
+
446
+ it("is actually wired into the instance — a captured line carries no full lead id", async () => {
447
+ const lines: string[] = [];
448
+ const fastify = require("fastify")({
449
+ logger: {
450
+ level: "info",
451
+ serializers: { req: requestLogSerializer },
452
+ stream: { write: (msg: string) => lines.push(msg) },
453
+ },
454
+ });
455
+ fastify.get("/v1/workspaces/:workspaceId/messages/:leadId", async () => ({
456
+ ok: true,
457
+ }));
458
+ await fastify.ready();
459
+ try {
460
+ await fastify.inject({
461
+ method: "GET",
462
+ url: `/v1/workspaces/wksp_00000000-0000-7000-8000-000000000001/messages/${LEAD_ID}`,
463
+ });
464
+ const captured = lines.join("\n");
465
+ expect(captured).toContain("/messages/lead_019…");
466
+ expect(captured).not.toContain(LEAD_ID);
467
+ } finally {
468
+ await fastify.close();
469
+ }
470
+ });
471
+ });
@@ -368,3 +368,48 @@ describe('vercel-handler — one build per process (#5332)', () => {
368
368
  expect(sentMessage(res)).toBe('Server build failed: Server initialization timeout after 10s');
369
369
  });
370
370
  });
371
+
372
+ // #6019 AC-2 — the PATHNAME, never the raw target (#5970 security review
373
+ // round 2, H1): this is the first log line the production entry point emits,
374
+ // ahead of Fastify's own query-stripped one, so a route parameter's
375
+ // credential in the query string (`/v1/requests/:id/confirm?token=…`) must
376
+ // never reach it.
377
+ describe('vercel-handler — the request log carries no query string (#5970)', () => {
378
+ beforeEach(() => {
379
+ jest.resetModules();
380
+ });
381
+
382
+ it('logs the pathname, not the raw target, on request start', async () => {
383
+ const inject = jest.fn().mockResolvedValue({
384
+ statusCode: 200,
385
+ headers: {},
386
+ body: 'ok',
387
+ rawPayload: Buffer.from('ok'),
388
+ });
389
+ jest.doMock('../server', () => ({ build: jest.fn().mockResolvedValue({ inject }) }));
390
+ const handler = loadHandler();
391
+
392
+ const req: MockVercelRequest = {
393
+ method: 'GET',
394
+ url: '/v1/requests/callbk_1/confirm?token=SUPERSECRETTOKEN',
395
+ headers: {},
396
+ body: undefined,
397
+ query: { token: 'SUPERSECRETTOKEN' },
398
+ };
399
+ const res = makeRes();
400
+
401
+ await handler(asRequest(req), asResponse(res));
402
+
403
+ const { logger } = require('@working-theory/observability') as {
404
+ logger: { info: jest.Mock };
405
+ };
406
+ const startCall = logger.info.mock.calls.find(
407
+ ([, msg]) => msg === '[Vercel Handler] Starting request',
408
+ );
409
+ expect(startCall?.[0]).toEqual({
410
+ method: 'GET',
411
+ pathname: '/v1/requests/callbk_1/confirm',
412
+ });
413
+ expect(JSON.stringify(logger.info.mock.calls)).not.toContain('SUPERSECRETTOKEN');
414
+ });
415
+ });
@@ -70,6 +70,8 @@ const MULTI_TENANT_ONLY_ROUTES = [
70
70
  { method: 'GET' as const, url: '/v1/workspaces' },
71
71
  { method: 'GET' as const, url: '/v1/billing' },
72
72
  { method: 'GET' as const, url: '/v1/waitlist-experiments' },
73
+ { method: 'POST' as const, url: `/v1/workspaces/${WS}/activate` },
74
+ { method: 'GET' as const, url: `/v1/workspaces/${WS}/sessions` },
73
75
  ];
74
76
 
75
77
  async function withEnabledModules<T>(
@@ -54,6 +54,41 @@ export interface Config {
54
54
  // origin, which stays correct for any agent that fetched the spec from this
55
55
  // API. See resolveServers() in src/openapi/spec.ts.
56
56
  AILK_PUBLIC_API_URL?: string;
57
+
58
+ // Request-and-confirm (#5970) — the callback → confirm → Payment Link path.
59
+ // All five optional and all read through services/request-confirm.ts, never
60
+ // from here directly; they are declared so the env contract is complete and
61
+ // one place lists them.
62
+ //
63
+ // AILK_REQUEST_CONFIRM_ENABLED is the switch: "true" turns the path on,
64
+ // anything else (including absent, the default) leaves the callback route
65
+ // behaving byte-for-byte as it did before the feature existed. TO and FROM
66
+ // fall back to the AILK_LEAD_NOTIFICATION_* pair; the link's base is the
67
+ // existing AILK_PUBLIC_API_URL above, since the confirm route is on this
68
+ // API. None of the five is a secret, so project.yaml's required_secrets
69
+ // gains nothing.
70
+ AILK_REQUEST_CONFIRM_ENABLED?: string;
71
+ AILK_REQUEST_CONFIRM_TO?: string;
72
+ AILK_REQUEST_CONFIRM_FROM?: string;
73
+ AILK_REQUEST_CONFIRM_CURRENCY?: string;
74
+ AILK_REQUEST_CONFIRM_MAX_MINOR?: string;
75
+
76
+ // Publish revalidation (#5851) — the signed webhook services/revalidation.ts
77
+ // POSTs at the site's /api/revalidate on publish/unpublish.
78
+ // CONTENT_REVALIDATE_SECRET is the HMAC-SHA256 signing key. Both optional;
79
+ // either absent means "revalidation off" — the service is a logged no-op and
80
+ // the publish that called it still completes.
81
+ CONTENT_REVALIDATE_URL?: string;
82
+ CONTENT_REVALIDATE_SECRET?: string;
83
+
84
+ // Scheduling webhook receiver (#6085, brief H7) — the HMAC-SHA256 key
85
+ // routes/webhooks/calcom.ts verifies POST /v1/webhooks/calcom's
86
+ // x-cal-signature-256 header against (§ D2, § D14). Read the same way
87
+ // STRIPE_WEBHOOK_SECRET is above — through `config`, never `process.env`
88
+ // at the call site. Unset makes the receiver answer 503 and accept
89
+ // nothing (§ D2 row 1) — the same fail-closed posture STRIPE_WEBHOOK_SECRET
90
+ // unset already has.
91
+ CAL_WEBHOOK_SECRET?: string;
57
92
  }
58
93
 
59
94
  function validateConfig(): Config {
@@ -104,6 +139,33 @@ function validateConfig(): Config {
104
139
  ...(process.env.AILK_PUBLIC_API_URL !== undefined && {
105
140
  AILK_PUBLIC_API_URL: process.env.AILK_PUBLIC_API_URL,
106
141
  }),
142
+
143
+ ...(process.env.AILK_REQUEST_CONFIRM_ENABLED !== undefined && {
144
+ AILK_REQUEST_CONFIRM_ENABLED: process.env.AILK_REQUEST_CONFIRM_ENABLED,
145
+ }),
146
+ ...(process.env.AILK_REQUEST_CONFIRM_TO !== undefined && {
147
+ AILK_REQUEST_CONFIRM_TO: process.env.AILK_REQUEST_CONFIRM_TO,
148
+ }),
149
+ ...(process.env.AILK_REQUEST_CONFIRM_FROM !== undefined && {
150
+ AILK_REQUEST_CONFIRM_FROM: process.env.AILK_REQUEST_CONFIRM_FROM,
151
+ }),
152
+ ...(process.env.AILK_REQUEST_CONFIRM_CURRENCY !== undefined && {
153
+ AILK_REQUEST_CONFIRM_CURRENCY: process.env.AILK_REQUEST_CONFIRM_CURRENCY,
154
+ }),
155
+ ...(process.env.AILK_REQUEST_CONFIRM_MAX_MINOR !== undefined && {
156
+ AILK_REQUEST_CONFIRM_MAX_MINOR: process.env.AILK_REQUEST_CONFIRM_MAX_MINOR,
157
+ }),
158
+
159
+ ...(process.env.CONTENT_REVALIDATE_URL !== undefined && {
160
+ CONTENT_REVALIDATE_URL: process.env.CONTENT_REVALIDATE_URL,
161
+ }),
162
+ ...(process.env.CONTENT_REVALIDATE_SECRET !== undefined && {
163
+ CONTENT_REVALIDATE_SECRET: process.env.CONTENT_REVALIDATE_SECRET,
164
+ }),
165
+
166
+ ...(process.env.CAL_WEBHOOK_SECRET !== undefined && {
167
+ CAL_WEBHOOK_SECRET: process.env.CAL_WEBHOOK_SECRET,
168
+ }),
107
169
  };
108
170
  }
109
171
 
@@ -12,9 +12,13 @@
12
12
  * Recognized module keys: `docs`, `multi-tenant`, `workspace`, `scheduling`,
13
13
  * `signal-collectors`, `commerce` (the `ModuleName` vocabulary
14
14
  * `packages/create-ailk/src/module-architecture.ts` already models via
15
- * `MODULE_SLICES`) plus `content-write` — the Pro
16
- * content-write surface (`create`/`update`/`delete` under
17
- * `apps/api/src/routes/content/`). `content-write` is intentionally NOT a
15
+ * `MODULE_SLICES`) plus `content-write` — the Pro content-write surface,
16
+ * which since #5853 means the tenant-guarded editor routes in
17
+ * `apps/api/src/routes/workspaces/content.ts` (the untenanted
18
+ * `create`/`update`/`delete` under `apps/api/src/routes/content/` that it used
19
+ * to name were retired by deletion). The flag's NAME, registration grain and
20
+ * default-off semantics are unchanged (D8) — only its documented meaning
21
+ * widened. `content-write` is intentionally NOT a
18
22
  * `MODULE_SLICES` entry: it is not (yet) a `create-ailk --modules`
19
23
  * selector — a customer never opts into it at scaffold time, it is a
20
24
  * license-tier line drawn inside the single `apps/api` app — so it stays a