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
@@ -33,12 +33,15 @@ import { prisma } from "../../lib/prisma.js";
33
33
  import type { Id } from "@working-theory/ids";
34
34
 
35
35
  import { defaultTenantPreHandlerPlugin } from "../default-tenant.js";
36
+ import { resolveDefaultTenant } from "../../services/default-tenant.js";
36
37
 
37
38
  // The reserved-range sentinel values from database/tenancy.ts (ADR-0031
38
39
  // rule 7) — re-declared here as literals so the test pins the WIRE values,
39
40
  // not whatever the constants happen to be.
40
41
  const DEFAULT_ORG_ID = "org_00000000-0000-7000-8000-000000000001";
41
42
  const DEFAULT_WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
43
+ const OPERATOR_MEMBERSHIP_ID =
44
+ "member_00000000-0000-7000-8000-000000000001";
42
45
 
43
46
  /** A workspace id belonging to somebody else — never this caller's. */
44
47
  const FOREIGN_WORKSPACE_ID = "wksp_01936b2c-0000-7000-8000-00000000beef";
@@ -103,6 +106,12 @@ function buildApp(
103
106
  app.get("/workspaces/:workspaceId/config", async (req) => ({
104
107
  workspaceId: req.tenant?.workspaceId ?? null,
105
108
  }));
109
+ // The `workspace` module's settings-route GET shape (#5371) — the surface
110
+ // #5515 item 1 asks about. Stubbed rather than imported: what is under test
111
+ // is which tenant the GUARD yields to it, not what the handler then reads.
112
+ app.get("/workspaces/:workspaceId/general", async (req) => ({
113
+ workspaceId: req.tenant?.workspaceId ?? null,
114
+ }));
106
115
  return app;
107
116
  }
108
117
 
@@ -120,6 +129,75 @@ afterAll(() => {
120
129
  process.env = ORIGINAL_ENV;
121
130
  });
122
131
 
132
+ /**
133
+ * #5515 item 1 — `AILK_OPERATOR_EMAIL` is REQUIRED for a workspace-module
134
+ * deployment, and that requirement is what keeps the TOFU operator bind from
135
+ * firing on a settings-route GET.
136
+ *
137
+ * The #5513 review raised this against `apps/api/src/services/tenant-context.ts`
138
+ * before #5677 moved the resolution: registering the `workspace` module's
139
+ * routes under `isModuleEnabled("workspace") || isModuleEnabled("multi-tenant")`
140
+ * put a `tenantPreHandler` route on a build that has no multi-tenant module,
141
+ * so the concern was that the first authenticated account to load the module's
142
+ * Admin pages (`/admin`, renamed from `/workspace` by #5637) would TOFU-bind
143
+ * itself as org owner off a plain settings GET.
144
+ *
145
+ * On a workspace-only build `server.ts`'s `tenantPreHandlerForBuild()` picks
146
+ * THIS guard, which calls `resolveDefaultTenantForOperator` — so the pin is
147
+ * mandatory and unset fails CLOSED. These two cases are the objective record
148
+ * of that, pinned to the settings-route shape rather than the review surface:
149
+ * with the pin unset the bind never runs and the route is 403 to everyone;
150
+ * with it set, only the pinned operator is seated. The documentation half of
151
+ * this item is `.env.example` and `apps/api/CLAUDE.md`. Redesigning #3631's
152
+ * TOFU was explicitly out of scope.
153
+ */
154
+ describe("workspace-module settings route — the operator pin is required (#5515 item 1)", () => {
155
+ const settingsPath = `/workspaces/${DEFAULT_WORKSPACE_ID}/general`;
156
+
157
+ it("DENIES a settings-route GET when AILK_OPERATOR_EMAIL is UNSET — and the TOFU bind never fires", async () => {
158
+ delete process.env.AILK_OPERATOR_EMAIL;
159
+ mockSingleTenantWorkspace();
160
+ mockBoundOperator();
161
+
162
+ const app = buildApp();
163
+ const res = await app.inject({ method: "GET", url: settingsPath });
164
+
165
+ expect(res.statusCode).toBe(403);
166
+ // The load-bearing assertion: no Membership is minted off a settings GET.
167
+ expect(prisma.membership.create).not.toHaveBeenCalled();
168
+ await app.close();
169
+ });
170
+
171
+ it("GRANTS the same settings-route GET once AILK_OPERATOR_EMAIL pins the caller", async () => {
172
+ process.env.AILK_OPERATOR_EMAIL = OPERATOR_EMAIL;
173
+ mockSingleTenantWorkspace();
174
+ mockBoundOperator();
175
+
176
+ const app = buildApp();
177
+ const res = await app.inject({ method: "GET", url: settingsPath });
178
+
179
+ expect(res.statusCode).toBe(200);
180
+ expect(res.json()).toEqual({ workspaceId: DEFAULT_WORKSPACE_ID });
181
+ await app.close();
182
+ });
183
+
184
+ it("DENIES a settings-route GET from a caller who is NOT the pinned operator, minting nothing", async () => {
185
+ process.env.AILK_OPERATOR_EMAIL = OPERATOR_EMAIL;
186
+ mockSingleTenantWorkspace();
187
+ (prisma.user.findUnique as jest.Mock).mockResolvedValue({
188
+ email: "first-visitor@example.com",
189
+ emailVerified: true,
190
+ });
191
+
192
+ const app = buildApp();
193
+ const res = await app.inject({ method: "GET", url: settingsPath });
194
+
195
+ expect(res.statusCode).toBe(403);
196
+ expect(prisma.membership.create).not.toHaveBeenCalled();
197
+ await app.close();
198
+ });
199
+ });
200
+
123
201
  describe("default tenant guard — isolation (#5677)", () => {
124
202
  it("DENIES a foreign workspaceId with 403, before any lookup of the target", async () => {
125
203
  mockSingleTenantWorkspace();
@@ -337,3 +415,107 @@ describe("default tenant guard — isolation (#5677)", () => {
337
415
  await app.close();
338
416
  });
339
417
  });
418
+
419
+ /**
420
+ * The MULTI-TENANT reach into the same bind (#5701 AC-2). `resolveActiveTenant`
421
+ * (`services/tenant-context.ts:89`) calls `resolveDefaultTenant` directly, so
422
+ * `bindOperatorMembership` is reached WITHOUT the verified-email gate that
423
+ * `resolveDefaultTenantForOperator` — the base-build entry the guard above
424
+ * exercises — applies first. These cases drive that entry point directly,
425
+ * which is the only way to observe the bind's own check; through the plugin
426
+ * the wrapper would refuse first and the assertion would prove nothing.
427
+ */
428
+ describe("resolveDefaultTenant — the operator bind's own verified-email check (#5701)", () => {
429
+ it("does NOT create the Membership when the pinned address matches but emailVerified is false", async () => {
430
+ mockSingleTenantWorkspace();
431
+ (prisma.user.findUnique as jest.Mock).mockResolvedValue({
432
+ id: USER_ID,
433
+ email: OPERATOR_EMAIL,
434
+ emailVerified: false,
435
+ });
436
+ (prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
437
+
438
+ const result = await resolveDefaultTenant(USER_ID);
439
+
440
+ expect(result).toBeNull();
441
+ // The bind is the escalation: an unverified address is an unproven claim
442
+ // to the pin, so no `owner` Membership may be minted from it.
443
+ expect(prisma.membership.create).not.toHaveBeenCalled();
444
+ });
445
+
446
+ it("does NOT create the Membership when the pinned caller has no User row", async () => {
447
+ mockSingleTenantWorkspace();
448
+ (prisma.user.findUnique as jest.Mock).mockResolvedValue(null);
449
+ (prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
450
+
451
+ const result = await resolveDefaultTenant(USER_ID);
452
+
453
+ expect(result).toBeNull();
454
+ expect(prisma.membership.create).not.toHaveBeenCalled();
455
+ });
456
+
457
+ it("CREATES the Membership when the pinned address matches and emailVerified is true — unchanged behaviour", async () => {
458
+ mockSingleTenantWorkspace();
459
+ (prisma.user.findUnique as jest.Mock).mockResolvedValue({
460
+ id: USER_ID,
461
+ email: OPERATOR_EMAIL,
462
+ emailVerified: true,
463
+ });
464
+ (prisma.membership.create as jest.Mock).mockResolvedValue({});
465
+ (prisma.membership.findUnique as jest.Mock).mockResolvedValue({
466
+ role: "owner",
467
+ });
468
+
469
+ const result = await resolveDefaultTenant(USER_ID);
470
+
471
+ expect(prisma.membership.create).toHaveBeenCalledTimes(1);
472
+ expect((prisma.membership.create as jest.Mock).mock.calls[0]?.[0]).toEqual({
473
+ data: {
474
+ id: OPERATOR_MEMBERSHIP_ID,
475
+ organizationId: DEFAULT_ORG_ID,
476
+ userId: USER_ID,
477
+ role: "owner",
478
+ },
479
+ });
480
+ expect(result).toEqual({
481
+ userId: USER_ID,
482
+ organizationId: DEFAULT_ORG_ID,
483
+ workspaceId: DEFAULT_WORKSPACE_ID,
484
+ role: "owner",
485
+ });
486
+ });
487
+
488
+ it("leaves the UNPINNED branch untouched — no User read, pure TOFU bind (#5701 D4)", async () => {
489
+ delete process.env.AILK_OPERATOR_EMAIL;
490
+ mockSingleTenantWorkspace();
491
+ (prisma.membership.create as jest.Mock).mockResolvedValue({});
492
+ (prisma.membership.findUnique as jest.Mock).mockResolvedValue({
493
+ role: "owner",
494
+ });
495
+
496
+ const result = await resolveDefaultTenant(USER_ID);
497
+
498
+ // No pin means no email comparison at all — the bind stays first-caller-wins.
499
+ expect(prisma.user.findUnique).not.toHaveBeenCalled();
500
+ expect(prisma.membership.create).toHaveBeenCalledTimes(1);
501
+ expect(result).toEqual({
502
+ userId: USER_ID,
503
+ organizationId: DEFAULT_ORG_ID,
504
+ workspaceId: DEFAULT_WORKSPACE_ID,
505
+ role: "owner",
506
+ });
507
+ });
508
+
509
+ it("still swallows the OPERATOR_MEMBERSHIP_ID unique violation — the TOFU race is unchanged", async () => {
510
+ mockSingleTenantWorkspace();
511
+ (prisma.user.findUnique as jest.Mock).mockResolvedValue({
512
+ id: USER_ID,
513
+ email: OPERATOR_EMAIL,
514
+ emailVerified: true,
515
+ });
516
+ (prisma.membership.create as jest.Mock).mockRejectedValue({ code: "P2002" });
517
+ (prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
518
+
519
+ await expect(resolveDefaultTenant(USER_ID)).resolves.toBeNull();
520
+ });
521
+ });
@@ -485,3 +485,45 @@ describe("cross-isolate limit — does NOT hold by design (#5040, SINGLE-REPLICA
485
485
  }
486
486
  });
487
487
  });
488
+
489
+ // #6019 AC-2 — the `pathOnly` split (#5970 security review, finding 1): a
490
+ // route may carry its credential in the query string
491
+ // (`/v1/requests/:id/confirm?token=…` does), and a 429 is a line an attacker
492
+ // can provoke at will by tripping the limiter — logging the raw target would
493
+ // let them farm credentials out of the operator's own log that way.
494
+ describe("rate-limit warn log — no query-string credential (#5970)", () => {
495
+ it("scrubs the query string from the logged path on a 429", async () => {
496
+ __resetBuckets();
497
+ const logLines: Array<Record<string, unknown>> = [];
498
+ const app = Fastify({
499
+ trustProxy: true,
500
+ logger: {
501
+ level: "warn",
502
+ stream: {
503
+ write(msg: string) {
504
+ logLines.push(JSON.parse(msg) as Record<string, unknown>);
505
+ },
506
+ },
507
+ },
508
+ });
509
+ app.register(rateLimitMiddleware, { perIpPerMinute: 1 });
510
+ app.get("/v1/requests/:id/confirm", async () => ({ ok: true }));
511
+ await app.ready();
512
+
513
+ try {
514
+ const url = "/v1/requests/callbk_1/confirm?token=SUPERSECRETTOKEN";
515
+ const headers = { "x-forwarded-for": "1.2.3.4" };
516
+ await app.inject({ method: "GET", url, headers });
517
+ const limited = await app.inject({ method: "GET", url, headers });
518
+ expect(limited.statusCode).toBe(429);
519
+
520
+ const logged = logLines.find((line) => line["msg"] === "rate_limited");
521
+ expect(logged?.["path"]).toBe("/v1/requests/callbk_1/confirm");
522
+ const text = JSON.stringify(logLines);
523
+ expect(text).not.toContain("SUPERSECRETTOKEN");
524
+ expect(text).not.toContain("token=");
525
+ } finally {
526
+ await app.close();
527
+ }
528
+ });
529
+ });
@@ -187,7 +187,13 @@ export const rateLimitMiddleware: FastifyPluginCallback<RateLimitOptions> = (
187
187
  // Evict stale buckets on every 429 to cap memory under abuse.
188
188
  evictStaleBuckets(now, windowMs);
189
189
 
190
- req.log.warn({ ip, path: req.url, count: bucket.count }, RATE_LIMITED);
190
+ // The path WITHOUT its query string (#5970 security review, finding 1): a
191
+ // route may carry its credential there (`/v1/requests/:id/confirm?token=…`
192
+ // does), and a rate-limit warning is a line an attacker can provoke at
193
+ // will — logging the raw target would let them farm credentials out of
194
+ // the operator's own log by tripping the limiter.
195
+ const pathOnly = req.url.split("?")[0] ?? req.url;
196
+ req.log.warn({ ip, path: pathOnly, count: bucket.count }, RATE_LIMITED);
191
197
 
192
198
  return reply
193
199
  .status(429)