create-ailk 0.5.0 → 0.6.1

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 (329) 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/cli.js +54 -5
  5. package/dist/copy.d.ts +14 -0
  6. package/dist/copy.js +38 -1
  7. package/dist/lib/api-boot-assertion.d.ts +46 -0
  8. package/dist/lib/api-boot-assertion.js +70 -0
  9. package/dist/lib/extract-module-bundle.d.ts +20 -1
  10. package/dist/lib/extract-module-bundle.js +64 -5
  11. package/dist/lib/import-graph.d.ts +29 -0
  12. package/dist/lib/import-graph.js +140 -0
  13. package/dist/lib/module-license-gate.d.ts +6 -1
  14. package/dist/lib/module-license-gate.js +6 -1
  15. package/dist/lib/pro-callout.js +1 -1
  16. package/dist/module-architecture.d.ts +1 -0
  17. package/dist/module-architecture.js +253 -127
  18. package/dist/modules/commerce/0.6.0.tar.gz +0 -0
  19. package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
  20. package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
  21. package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
  22. package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
  23. package/dist/modules/workspace/0.6.0.tar.gz +0 -0
  24. package/dist/programmatic.js +14 -10
  25. package/dist/surfaces.js +105 -7
  26. package/dist/template-manifest.d.ts +33 -0
  27. package/dist/template-manifest.js +99 -16
  28. package/dist/template-names.d.ts +1 -1
  29. package/dist/template-names.js +1 -0
  30. package/package.json +3 -2
  31. package/site-templates/marketing-site/DESIGN.md +148 -0
  32. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
  34. package/site-templates/marketing-site/ailk-template.json +1 -1
  35. package/site-templates/marketing-site-free/DESIGN.md +148 -0
  36. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
  38. package/site-templates/minimalism-site/DESIGN.md +148 -0
  39. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
  40. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
  41. package/site-templates/minimalism-site/ailk-template.json +2 -1
  42. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  43. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
  44. package/site-templates/saas-product/DESIGN.md +148 -0
  45. package/site-templates/simple-service/DESIGN.md +148 -0
  46. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  47. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  48. package/site-templates/simple-service/ailk-template.json +10 -0
  49. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  50. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  51. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  52. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  53. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  54. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  55. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  56. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  57. package/site-templates/simple-service/provenance.json +4 -0
  58. package/templates/.env.example +72 -7
  59. package/templates/CLAUDE.md +2 -3
  60. package/templates/DESIGN.md +155 -0
  61. package/templates/README.md +42 -8
  62. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  63. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  64. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  65. package/templates/apps/api/.env.example +13 -0
  66. package/templates/apps/api/CLAUDE.md +26 -2
  67. package/templates/apps/api/jest.config.cjs +3 -0
  68. package/templates/apps/api/package.json +1 -0
  69. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  70. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
  71. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  72. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  73. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
  74. package/templates/apps/api/src/config/index.ts +62 -0
  75. package/templates/apps/api/src/config/modules.ts +7 -3
  76. package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
  77. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  78. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  79. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  80. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  81. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  82. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  83. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  84. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  85. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  86. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  87. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  88. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  89. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  90. package/templates/apps/api/src/lib/site.ts +208 -9
  91. package/templates/apps/api/src/lib/stripe.ts +66 -0
  92. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
  93. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
  94. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  95. package/templates/apps/api/src/openapi/spec.ts +543 -2
  96. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  97. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  98. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  99. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  100. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  101. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  102. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  103. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  104. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  105. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  106. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  107. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  108. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  109. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  110. package/templates/apps/api/src/routes/content/index.ts +13 -73
  111. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  112. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  113. package/templates/apps/api/src/routes/content/search.ts +129 -0
  114. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  115. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  116. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  118. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  119. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  120. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  121. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  122. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  123. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  124. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  125. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  126. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  127. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  128. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  129. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  130. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  131. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  132. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  133. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  134. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  135. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  136. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  137. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  138. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  139. package/templates/apps/api/src/server.ts +439 -49
  140. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  141. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  142. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  143. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  144. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  145. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  146. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  147. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  148. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  149. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  150. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  151. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  152. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  153. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  154. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  155. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  156. package/templates/apps/api/src/services/audit-log.ts +668 -0
  157. package/templates/apps/api/src/services/consent.ts +18 -0
  158. package/templates/apps/api/src/services/default-tenant.ts +70 -31
  159. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  160. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  161. package/templates/apps/api/src/services/entitlements.ts +80 -0
  162. package/templates/apps/api/src/services/lead-promotion.ts +13 -14
  163. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  164. package/templates/apps/api/src/services/leads-inbox.ts +23 -2
  165. package/templates/apps/api/src/services/listing-config.ts +27 -9
  166. package/templates/apps/api/src/services/project-listings.ts +201 -6
  167. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  168. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  169. package/templates/apps/api/src/services/revalidation.ts +173 -0
  170. package/templates/apps/api/src/services/verified-email.ts +71 -0
  171. package/templates/apps/api/src/vercel-handler.ts +75 -35
  172. package/templates/apps/mcp/.env.example +17 -0
  173. package/templates/apps/mcp/CLAUDE.md +57 -24
  174. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  175. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  176. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  177. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  178. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  179. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  180. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  181. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  182. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  183. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  184. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  185. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  186. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  187. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  188. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  189. package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
  190. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  191. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  192. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  193. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  194. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  195. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  196. package/templates/apps/mcp/jest.config.cjs +13 -0
  197. package/templates/apps/mcp/package.json +2 -0
  198. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  199. package/templates/apps/mcp/src/config/modules.ts +10 -5
  200. package/templates/apps/mcp/src/http-transport.ts +102 -29
  201. package/templates/apps/mcp/src/index.ts +73 -11
  202. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  203. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  204. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  205. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  206. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  207. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  208. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  209. package/templates/apps/mcp/src/server.ts +59 -35
  210. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  211. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  212. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  213. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  214. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  215. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  216. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  217. package/templates/apps/mcp/src/tools/index.ts +6 -4
  218. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  219. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  220. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  221. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  222. package/templates/apps/web/.env.example +71 -5
  223. package/templates/apps/web/CLAUDE.md +44 -0
  224. package/templates/apps/web/PRODUCT.md +86 -0
  225. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  226. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  227. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  228. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  229. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  230. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  231. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  232. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  233. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  234. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  235. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  236. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  237. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  238. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  239. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  240. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  241. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  242. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  243. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  244. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
  245. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
  246. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  247. package/templates/apps/web/app/[locale]/layout.tsx +133 -87
  248. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  249. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  250. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  251. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  252. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  253. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  254. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  255. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  256. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  257. package/templates/apps/web/auth.ts +407 -8
  258. package/templates/apps/web/jest.config.cjs +8 -4
  259. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  260. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  261. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  262. package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
  263. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
  264. package/templates/apps/web/lib/auth-email.ts +318 -0
  265. package/templates/apps/web/lib/auth-env.ts +85 -0
  266. package/templates/apps/web/lib/auth-routes.ts +55 -0
  267. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  268. package/templates/apps/web/lib/site-brand.tsx +4 -8
  269. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  270. package/templates/apps/web/messages/en.json +187 -0
  271. package/templates/apps/web/middleware.ts +1 -1
  272. package/templates/apps/web/package.json +2 -1
  273. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  274. package/templates/apps/web/src/lib/commerce-sections.ts +93 -0
  275. package/templates/content/_site.mdx +18 -3
  276. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  277. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  278. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  279. package/templates/database/CHANGELOG.md +149 -0
  280. package/templates/database/README.md +10 -3
  281. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  282. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  283. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  284. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  285. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  286. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  287. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  288. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  289. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  290. package/templates/database/__tests__/tenancy.test.ts +141 -0
  291. package/templates/database/config/schema.prisma +79 -2
  292. package/templates/database/content/schema.prisma +87 -11
  293. package/templates/database/inbox/schema.prisma +452 -9
  294. package/templates/database/jest.config.cjs +5 -3
  295. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  296. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  297. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  298. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  299. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  300. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  301. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  302. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  303. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  304. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  305. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  306. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  307. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  308. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  309. package/templates/database/package.json +3 -2
  310. package/templates/database/tenancy.ts +86 -1
  311. package/templates/database/tsconfig.json +1 -0
  312. package/versions.json +34 -34
  313. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  314. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  315. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  316. package/templates/apps/api/src/routes/content/create.ts +0 -115
  317. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  318. package/templates/apps/api/src/routes/content/update.ts +0 -112
  319. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  320. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  321. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  322. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  323. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  324. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  325. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  326. package/templates/apps/web/public/logo-dark.svg +0 -21
  327. package/templates/apps/web/public/logo-light.svg +0 -21
  328. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  329. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Per-recipient hourly send budget for the auth transactional-email relay
3
+ * (#5962 § D8c).
4
+ *
5
+ * WHY IT IS PER RECIPIENT AND NOT PER SITE. The first draft of #5962's spec
6
+ * carried a second, site-wide cap. It was removed, and its removal is the
7
+ * point of this module's shape: Better Auth writes the `Verification` row
8
+ * BEFORE it invokes `sendResetPassword`, so every attacker request both feeds
9
+ * a site-wide counter and refreshes its window — roughly one request per 72
10
+ * seconds would hold a site permanently at its cap and silence reset mail for
11
+ * EVERY user, indefinitely. A site-wide cap an attacker can hold open is a
12
+ * denial of the control it implements. `siteVolumeAlert()` below replaces it
13
+ * with an operator SIGNAL that never refuses a send.
14
+ *
15
+ * The per-recipient budget IS a lockout weapon, narrowly: an attacker who
16
+ * requests three resets for one address in an hour denies that person a reset
17
+ * for the rest of it. Accepted for the reason
18
+ * `services/project-listing-resume-email.ts` gives and this module can
19
+ * honestly claim — the window is an hour rather than forever, it clears
20
+ * itself, and it is bounded to ONE victim rather than to the whole site.
21
+ *
22
+ * SINGLE-REPLICA CAVEAT, stated rather than hidden: the store is an in-process
23
+ * Map, so on an N-instance deployment the effective ceiling is N × the cap.
24
+ * Identical to the caveat `middleware/rate-limit.ts` documents for itself. The
25
+ * durable alternative needs a table `database/auth/schema.prisma` does not
26
+ * have, and #5962 adds no migration (Q16).
27
+ *
28
+ * PII: the key is `sha256(lowercased recipient + ":" + kind)`. This module is
29
+ * memory an operator can dump, and a plaintext address in it would be a
30
+ * recipient list.
31
+ */
32
+
33
+ import { createHash } from "node:crypto";
34
+
35
+ import { getClock } from "@working-theory/common";
36
+
37
+ /** The three fixed message kinds the relay will send. */
38
+ export type AuthEmailKind =
39
+ | "password-reset"
40
+ | "email-verification"
41
+ | "password-changed";
42
+
43
+ /** Sends allowed per (recipient, kind) per hour. */
44
+ export const RECIPIENT_HOURLY_MAX = 3;
45
+
46
+ /**
47
+ * Sends per hour, site-wide, past which ONE operator warning is logged. Never
48
+ * a refusal — see the module comment.
49
+ */
50
+ export const SITE_HOURLY_ALERT_THRESHOLD = 50;
51
+
52
+ const WINDOW_MS = 60 * 60 * 1000;
53
+
54
+ /**
55
+ * Cap on distinct entries, independent of the time-based window. The key is
56
+ * derived from a caller-supplied address, so a caller minting a fresh address
57
+ * per request would otherwise grow the map without bound for the lifetime of
58
+ * one isolate. Same reasoning, same shape, as `rate-limit.ts`'s MAX_BUCKETS.
59
+ */
60
+ export const MAX_BUCKETS = 10_000;
61
+
62
+ type Bucket = { count: number; windowStartMs: number };
63
+
64
+ const buckets = new Map<string, Bucket>();
65
+
66
+ let siteWindowStartMs = 0;
67
+ let siteCount = 0;
68
+ let siteAlertedThisWindow = false;
69
+
70
+ export interface BudgetOptions {
71
+ /** Explicit clock reading. Defaults to `getClock().now()`. Tests inject it. */
72
+ nowMs?: number;
73
+ /** Entry cap. Exposed for tests only — no production caller overrides it. */
74
+ maxBuckets?: number;
75
+ }
76
+
77
+ function bucketKey(recipient: string, kind: AuthEmailKind): string {
78
+ const normalized = recipient.trim().toLowerCase();
79
+ return createHash("sha256").update(`${normalized}:${kind}`).digest("hex");
80
+ }
81
+
82
+ /**
83
+ * Counts one send against `(recipient, kind)` and reports whether it may
84
+ * proceed.
85
+ *
86
+ * `false` means SUPPRESS THE SEND — it never means "tell the caller". The
87
+ * relay answers 202 either way, because an observable suppression is the same
88
+ * account-enumeration oracle the rest of this flow is built to close.
89
+ */
90
+ export function consumeRecipientBudget(
91
+ recipient: string,
92
+ kind: AuthEmailKind,
93
+ options: BudgetOptions = {},
94
+ ): boolean {
95
+ const now = options.nowMs ?? getClock().now();
96
+ const maxBuckets = options.maxBuckets ?? MAX_BUCKETS;
97
+ const key = bucketKey(recipient, kind);
98
+
99
+ let bucket = buckets.get(key);
100
+
101
+ if (!bucket || now - bucket.windowStartMs >= WINDOW_MS) {
102
+ // A genuinely new identity at capacity evicts the single oldest-inserted
103
+ // entry (`Map` iteration order is insertion order) before inserting
104
+ // itself — O(1) amortized, and no behaviour change under the cap.
105
+ if (!buckets.has(key) && buckets.size >= maxBuckets) {
106
+ const oldestKey = buckets.keys().next().value;
107
+ if (oldestKey !== undefined) buckets.delete(oldestKey);
108
+ }
109
+ bucket = { count: 0, windowStartMs: now };
110
+ buckets.set(key, bucket);
111
+ }
112
+
113
+ if (bucket.count >= RECIPIENT_HOURLY_MAX) return false;
114
+
115
+ bucket.count += 1;
116
+ return true;
117
+ }
118
+
119
+ /**
120
+ * Counts one send site-wide and reports whether THIS call is the one that
121
+ * should emit the hourly operator warning.
122
+ *
123
+ * It is a signal, not a gate: nothing in this module consults its return value
124
+ * to deny a send, and nothing may be added that does.
125
+ */
126
+ export function siteVolumeAlert(options: BudgetOptions = {}): boolean {
127
+ const now = options.nowMs ?? getClock().now();
128
+
129
+ if (now - siteWindowStartMs >= WINDOW_MS) {
130
+ siteWindowStartMs = now;
131
+ siteCount = 0;
132
+ siteAlertedThisWindow = false;
133
+ }
134
+
135
+ siteCount += 1;
136
+
137
+ if (siteCount >= SITE_HOURLY_ALERT_THRESHOLD && !siteAlertedThisWindow) {
138
+ siteAlertedThisWindow = true;
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+
144
+ /** Test-only reset. Not intended for production use. */
145
+ export function __resetAuthEmailBudget(): void {
146
+ buckets.clear();
147
+ siteWindowStartMs = 0;
148
+ siteCount = 0;
149
+ siteAlertedThisWindow = false;
150
+ }
151
+
152
+ /** Test-only accessor for the distinct-bucket count. */
153
+ export function __authEmailBucketCount(): number {
154
+ return buckets.size;
155
+ }
156
+
157
+ /** Test-only accessor proving the stored keys carry no plaintext address. */
158
+ export function __authEmailBudgetKeys(): string[] {
159
+ return [...buckets.keys()];
160
+ }
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * Shared content adapter singleton for apps/api.
3
3
  *
4
- * The MDX adapter is the v1 default. Set `CONTENT_ADAPTER=projection` to opt
5
- * into the build-time projection adapter backed by a compiled manifest.
4
+ * Adapter selection is NOT decided here — it is `resolveContentAdapter()`'s job
5
+ * (#5804, roadmap D3: one resolver, one env var). This module supplies the
6
+ * app's own inputs (content root, default locale, manifest path, the projection
7
+ * factory) and memoises the result for the process.
6
8
  *
7
- * Adapter selection:
8
- * - (unset or any value other than "projection") → MDX adapter
9
- * - "projection" → projection adapter (requires compiled manifest)
9
+ * `CONTENT_ADAPTER` selects: `mdx` (default) | `projection` | `database`.
10
+ * `CONTENT_BACKEND` is read for one more release by the resolver's shim.
10
11
  *
11
12
  * Content-root resolution order:
12
13
  * 1. `CONTENT_ROOT` env var (explicit override — recommended for production)
@@ -15,13 +16,27 @@
15
16
  *
16
17
  * Manifest-path resolution (projection only):
17
18
  * 1. `MANIFEST_PATH` env var (explicit override)
18
- * 2. `<content-root>/.compiled/manifest.json` (default)
19
+ * 2. `<content-root>/.compiled/manifest.json` (the resolver's default)
20
+ *
21
+ * `workspaceId` is supplied by the CALLER, never read here (#5852). A public
22
+ * read resolves its workspace through `services/public-site-resolver.ts` and
23
+ * passes the result in; a caller that passes none still raises the resolver's
24
+ * "workspaceId required" error under `CONTENT_ADAPTER=database`, rather than
25
+ * silently reading some other workspace's pages. There is no local ADAPTER memo
26
+ * slot: `resolveContentAdapter()` already memoises on the full key
27
+ * (kind + workspaceId + root + locale + manifest), and a second cache keyed on
28
+ * less than that is how one workspace's adapter reaches the next request. This
29
+ * module's own INPUTS — the content root and the default locale, both
30
+ * workspace-independent and both fixed for the process — stay memoised, so a
31
+ * public read costs no more filesystem work than it did before the seam widened.
19
32
  */
20
33
  import { existsSync } from "node:fs";
21
34
  import { resolve } from "node:path";
22
35
 
23
36
  import {
24
- createMdxAdapter,
37
+ resetResolvedContentAdapter,
38
+ resolveContentAdapter,
39
+ type ArtifactPrismaClient,
25
40
  type ContentAdapter,
26
41
  type MdxContentAdapter,
27
42
  } from "@working-theory/content-adapters";
@@ -30,7 +45,17 @@ import { loadManifestFromDisk } from "@working-theory/content-projection/node";
30
45
 
31
46
  import { readDefaultLocale } from "./locales.js";
32
47
 
33
- let cachedAdapter: ContentAdapter | undefined;
48
+ /** Process-fixed inputs, resolved once: neither depends on the workspace. */
49
+ let memoisedContentRoot: string | undefined;
50
+ let memoisedDefaultLocale: string | undefined;
51
+
52
+ /** Caller-supplied inputs; everything else is this module's own (#5852 D5). */
53
+ export interface GetContentAdapterOptions {
54
+ /** The resolved tenant. Required by `CONTENT_ADAPTER=database`, ignored otherwise. */
55
+ workspaceId?: string | undefined;
56
+ /** apps/api's client. The database adapter never constructs one. */
57
+ prisma?: ArtifactPrismaClient | undefined;
58
+ }
34
59
 
35
60
  /**
36
61
  * Resolve the content root. Honours `CONTENT_ROOT` env first, then probes the
@@ -50,37 +75,38 @@ export function getContentRoot(): string {
50
75
  }
51
76
 
52
77
  /**
53
- * Returns the process-wide content adapter, lazily constructed on first use.
54
- * Safe to call repeatedly. Adapter type is determined by `CONTENT_ADAPTER` env.
78
+ * Returns the content adapter for the given tenant, memoised by the resolver.
79
+ * Safe to call repeatedly. Adapter type is determined by `CONTENT_ADAPTER` env,
80
+ * read by the resolver.
55
81
  */
56
- export function getContentAdapter(): ContentAdapter {
57
- if (!cachedAdapter) {
58
- cachedAdapter = buildAdapter();
59
- }
60
- return cachedAdapter;
61
- }
62
-
63
- function buildAdapter(): ContentAdapter {
64
- const contentRoot = getContentRoot();
65
-
66
- if (process.env.CONTENT_ADAPTER === "projection") {
67
- const manifestPath =
68
- process.env.MANIFEST_PATH ??
69
- resolve(contentRoot, ".compiled", "manifest.json");
70
-
71
- const manifest = loadManifestFromDisk(manifestPath);
72
- return createProjectionAdapter({ manifest });
73
- }
82
+ export function getContentAdapter(
83
+ options: GetContentAdapterOptions = {},
84
+ ): ContentAdapter {
85
+ memoisedContentRoot ??= getContentRoot();
86
+ memoisedDefaultLocale ??= readDefaultLocale();
74
87
 
75
- return createMdxAdapter({ contentRoot, defaultLocale: readDefaultLocale() });
88
+ return resolveContentAdapter({
89
+ workspaceId: options.workspaceId,
90
+ prisma: options.prisma,
91
+ contentRoot: memoisedContentRoot,
92
+ defaultLocale: memoisedDefaultLocale,
93
+ manifestPath: process.env.MANIFEST_PATH,
94
+ // The projection adapter lives in a package that depends on
95
+ // content-adapters, so the resolver cannot import it without closing a
96
+ // cycle. apps/api depends on both, and injects it.
97
+ createProjectionAdapter: (manifestPath) =>
98
+ createProjectionAdapter({ manifest: loadManifestFromDisk(manifestPath) }),
99
+ });
76
100
  }
77
101
 
78
102
  /**
79
- * Test/utility helper — drop the cached adapter so the next call rebuilds
103
+ * Test/utility helper — drop the memoised adapter so the next call rebuilds
80
104
  * with current env. Not used in production.
81
105
  */
82
106
  export function resetContentAdapter(): void {
83
- cachedAdapter = undefined;
107
+ memoisedContentRoot = undefined;
108
+ memoisedDefaultLocale = undefined;
109
+ resetResolvedContentAdapter();
84
110
  }
85
111
 
86
112
  export type { MdxContentAdapter };
@@ -0,0 +1,76 @@
1
+ // oss: false
2
+ /**
3
+ * The magnet-deliverable Asset store's data-access boundary (#6310 D16).
4
+ *
5
+ * `deliverable-fulfillment.ts` resolves `deliverables.assets[].assetKey`
6
+ * against the workspace-scoped `Asset` model before falling back to the
7
+ * legacy `DeliverableAsset` store (D1/D2), and the download route
8
+ * (`routes/deliverables/index.ts`) serves an `asset_`-class grant's bytes
9
+ * from the same model (D6). Neither caller may run a raw `prisma.asset`
10
+ * query directly — that access is banned by the tenant-scope
11
+ * `forbidden_pattern` in architecture.yaml — and neither is `lib/asset-store.ts`
12
+ * a fit: it is `workspace`/`multi-tenant`-owned (`MODULE_SLICES`), so a
13
+ * `signal-collectors` file (the route) cannot import it, and
14
+ * `deliverable-fulfillment.ts` ships in every scaffold regardless of module
15
+ * selection. This file is its own named boundary instead — modelled
16
+ * line-for-line on `lib/asset-store.ts` — exporting exactly two scoped
17
+ * reads and nothing else:
18
+ *
19
+ * - `findDeliverableAssetIdByKey(workspaceId, key)` — the mint-time
20
+ * resolution (D2): does an `Asset` exist at this workspace's `key`?
21
+ * - `readDeliverableAsset(assetId, workspaceId)` — the serve-time read
22
+ * (D6): the bytes, scoped by the `workspaceId` the grant carries
23
+ * (D15), so a rewritten or mis-set grant can never serve another
24
+ * workspace's bytes.
25
+ *
26
+ * No write, no `findMany`, no list, no unscoped read — both functions take
27
+ * a `workspaceId` the caller resolved server-side, never a client-supplied
28
+ * value, so neither can enumerate or cross a tenant boundary (NFR3).
29
+ */
30
+ import { prisma } from "./prisma.js";
31
+
32
+ /** What the mint-time resolution needs, and nothing more. */
33
+ export async function findDeliverableAssetIdByKey(
34
+ workspaceId: string,
35
+ key: string,
36
+ ): Promise<string | null> {
37
+ const row = await prisma.asset.findFirst({
38
+ where: { workspaceId, key },
39
+ select: { id: true },
40
+ });
41
+ return row?.id ?? null;
42
+ }
43
+
44
+ /** What the serve path needs, and deliberately nothing more (no key, no workspace). */
45
+ export type DeliverableAssetBytes = {
46
+ filename: string;
47
+ contentType: string;
48
+ bytes: Buffer;
49
+ };
50
+
51
+ /**
52
+ * Resolve one asset's bytes by id, scoped by `workspaceId` (D15). A foreign-
53
+ * workspace id — or any other miss — returns `null`; the caller turns that
54
+ * into the same uniform 404 an unknown grant gets (D6/FR5).
55
+ */
56
+ export async function readDeliverableAsset(
57
+ assetId: string,
58
+ workspaceId: string | null,
59
+ ): Promise<DeliverableAssetBytes | null> {
60
+ if (workspaceId === null) return null;
61
+
62
+ const row = await prisma.asset.findFirst({
63
+ where: { id: assetId, workspaceId },
64
+ select: { filename: true, contentType: true, bytes: true },
65
+ });
66
+ if (!row) return null;
67
+
68
+ return {
69
+ ...row,
70
+ // Prisma maps a `Bytes` column to `Uint8Array`, and Fastify treats only
71
+ // a `Buffer` as a raw payload — this wraps rather than copies
72
+ // (`Buffer.from(buffer, byteOffset, length)` is a view), mirroring
73
+ // `asset-store.ts`'s own identical wrap.
74
+ bytes: Buffer.from(row.bytes.buffer, row.bytes.byteOffset, row.bytes.byteLength),
75
+ };
76
+ }
@@ -20,6 +20,26 @@ import type { FastifyError, FastifyInstance } from "fastify";
20
20
 
21
21
  const GENERIC_INTERNAL_BODY = { error: "internal_error" } as const;
22
22
 
23
+ /**
24
+ * The request target with its QUERY STRING removed (#5970 security review,
25
+ * finding 1).
26
+ *
27
+ * A route may carry its credential in the query — `GET
28
+ * /v1/requests/:id/confirm?token=…` does, because a link in an email has
29
+ * nowhere else to put one — and this handler logs `path` on EVERY error on
30
+ * EVERY route. Logging the raw target would write that credential into the
31
+ * API's own log the moment anything went wrong, which is precisely when logs
32
+ * get read, copied into tickets, and shipped to third parties.
33
+ *
34
+ * The path itself is kept: it is what makes the line useful, and a route
35
+ * parameter like `:id` is an identifier rather than a secret. The matching
36
+ * strip lives on the Fastify logger's own `req` serializer in `server.ts`.
37
+ */
38
+ function pathWithoutQuery(url: string): string {
39
+ const queryAt = url.indexOf("?");
40
+ return queryAt === -1 ? url : url.slice(0, queryAt);
41
+ }
42
+
23
43
  export function registerErrorSerializer(server: FastifyInstance): void {
24
44
  server.setErrorHandler(async (err: FastifyError, request, reply) => {
25
45
  // Validation errors (Fastify's schema validator) — keep structured detail.
@@ -29,7 +49,7 @@ export function registerErrorSerializer(server: FastifyInstance): void {
29
49
  request.log.warn(
30
50
  {
31
51
  method: request.method,
32
- path: request.url,
52
+ path: pathWithoutQuery(request.url),
33
53
  validation: err.validation,
34
54
  },
35
55
  "request validation failed",
@@ -48,7 +68,7 @@ export function registerErrorSerializer(server: FastifyInstance): void {
48
68
  request.log.info(
49
69
  {
50
70
  method: request.method,
51
- path: request.url,
71
+ path: pathWithoutQuery(request.url),
52
72
  statusCode: status,
53
73
  errName: err.name,
54
74
  },
@@ -63,7 +83,7 @@ export function registerErrorSerializer(server: FastifyInstance): void {
63
83
  request.log.error(
64
84
  {
65
85
  method: request.method,
66
- path: request.url,
86
+ path: pathWithoutQuery(request.url),
67
87
  errName: err.name,
68
88
  errMessage: err.message,
69
89
  errStack: err.stack,
@@ -20,7 +20,11 @@
20
20
  * finding waiting to happen.
21
21
  *
22
22
  * Test-mode escape hatch — `allowPrivate: true` suppresses the SSRF gate.
23
- * Use only for explicit, internal-by-design traffic (typically tests).
23
+ * Use only for explicit, internal-by-design traffic (typically tests). One
24
+ * production case is sanctioned (#5851): a destination read from an
25
+ * operator-configured deployment environment variable that is never
26
+ * user-controlled, such as `CONTENT_REVALIDATE_URL`, whose legitimate value is
27
+ * loopback in local development and single-host deployments.
24
28
  */
25
29
 
26
30
  const DEFAULT_TIMEOUT_MS = 10_000;
@@ -29,7 +33,8 @@ export type OutboundFetchOptions = RequestInit & {
29
33
  /** Override the default 10s timeout. */
30
34
  timeoutMs?: number;
31
35
  /**
32
- * Suppress the SSRF allow-list — test-mode escape hatch only.
36
+ * Suppress the SSRF allow-list — tests, plus the sanctioned production case
37
+ * in the module doc (an operator-configured deployment-env destination).
33
38
  * NEVER pass true for user-controlled URLs.
34
39
  */
35
40
  allowPrivate?: boolean;