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
@@ -33,6 +33,7 @@ jest.mock("@working-theory/observability", () => ({
33
33
  import { prisma } from "../../lib/prisma.js";
34
34
  import {
35
35
  fulfillDeliverable,
36
+ listSendlessGrants,
36
37
  type FulfillDeliverableInput,
37
38
  } from "../deliverable-fulfillment";
38
39
 
@@ -45,6 +46,17 @@ const SESSION_ID = "flsess_00000000-0000-7000-8000-0000000000c1";
45
46
  const LEAD_ID = "lead_00000000-0000-7000-8000-0000000000e1";
46
47
  const GRANT_ID = "dgrant_00000000-0000-7000-8000-0000000000g1";
47
48
 
49
+ // #6257 — the seeded sentinel workspace the `"default"` alias resolves to
50
+ // (`@working-theory/database/tenancy`'s DEFAULT_WORKSPACE_ID), with a
51
+ // sentinel-shaped organizationId so `resolveSentinelWorkspaceRow`'s own
52
+ // `isSentinel` check passes. Used by ONE test — the `listSendlessGrants`
53
+ // resolution test, the only test in this file that exercises an alias at
54
+ // all — to prove that read scopes by the id it RESOLVED rather than by the
55
+ // alias string the CLI was given. The other fence tests below run no
56
+ // resolution: `fulfillDeliverable` is handed an already-resolved id.
57
+ const SENTINEL_WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
58
+ const SENTINEL_ORGANIZATION_ID = "org_00000000-0000-7000-8000-000000000001";
59
+
48
60
  function deliverablesBlock() {
49
61
  return {
50
62
  assets: [
@@ -122,7 +134,12 @@ describe("fulfillDeliverable", () => {
122
134
 
123
135
  expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
124
136
  expect.objectContaining({
125
- where: { siteId_key: { siteId: SITE_ID, key: "guide-small" } },
137
+ where: {
138
+ siteId_key: { siteId: SITE_ID, key: "guide-small" },
139
+ // #6257 — the legacy store's read is fenced by the same resolved
140
+ // workspace id the grant is minted under.
141
+ workspaceId: SITE_ID,
142
+ },
126
143
  }),
127
144
  );
128
145
  });
@@ -138,7 +155,10 @@ describe("fulfillDeliverable", () => {
138
155
 
139
156
  expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
140
157
  expect.objectContaining({
141
- where: { siteId_key: { siteId: SITE_ID, key: "guide-large" } },
158
+ where: {
159
+ siteId_key: { siteId: SITE_ID, key: "guide-large" },
160
+ workspaceId: SITE_ID,
161
+ },
142
162
  }),
143
163
  );
144
164
  });
@@ -148,7 +168,10 @@ describe("fulfillDeliverable", () => {
148
168
 
149
169
  expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
150
170
  expect.objectContaining({
151
- where: { siteId_key: { siteId: SITE_ID, key: "guide-default" } },
171
+ where: {
172
+ siteId_key: { siteId: SITE_ID, key: "guide-default" },
173
+ workspaceId: SITE_ID,
174
+ },
152
175
  }),
153
176
  );
154
177
  });
@@ -158,7 +181,10 @@ describe("fulfillDeliverable", () => {
158
181
 
159
182
  expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
160
183
  expect.objectContaining({
161
- where: { siteId_key: { siteId: SITE_ID, key: "guide-default" } },
184
+ where: {
185
+ siteId_key: { siteId: SITE_ID, key: "guide-default" },
186
+ workspaceId: SITE_ID,
187
+ },
162
188
  }),
163
189
  );
164
190
  });
@@ -178,6 +204,9 @@ describe("fulfillDeliverable", () => {
178
204
  );
179
205
 
180
206
  expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
207
+ // #6310 FR8: a no_entry resolution exits before EITHER store is read.
208
+ expect(prisma.flowDefinition.findUnique).not.toHaveBeenCalled();
209
+ expect(prisma.asset.findFirst).not.toHaveBeenCalled();
181
210
  expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
182
211
  expect(mockSendDeliverableEmail).not.toHaveBeenCalled();
183
212
  // AC4: no_entry is a pre-mint outcome — no grant exists to stamp.
@@ -234,7 +263,13 @@ describe("fulfillDeliverable", () => {
234
263
 
235
264
  expect(prisma.playbookInstance.findFirst).toHaveBeenCalledWith(
236
265
  expect.objectContaining({
237
- where: { siteId: SITE_ID, leadId: LEAD_ID, experimentKey: "targeting-maturity" },
266
+ where: {
267
+ siteId: SITE_ID,
268
+ // #6257 — the playbook existence check is fenced too.
269
+ workspaceId: SITE_ID,
270
+ leadId: LEAD_ID,
271
+ experimentKey: "targeting-maturity",
272
+ },
238
273
  }),
239
274
  );
240
275
  expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
@@ -296,6 +331,166 @@ describe("fulfillDeliverable", () => {
296
331
  expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
297
332
  });
298
333
 
334
+ it("a playbook: deliverable issues zero Asset or FlowDefinition reads (#6310 FR8)", async () => {
335
+ (prisma.playbookInstance.findFirst as jest.Mock).mockResolvedValue({
336
+ id: "pbk_00000000-0000-7000-8000-0000000000p1",
337
+ });
338
+
339
+ await fulfillDeliverable(
340
+ baseInput({ segment: undefined, configSnapshot: playbookConfigSnapshot() }),
341
+ );
342
+
343
+ expect(prisma.flowDefinition.findUnique).not.toHaveBeenCalled();
344
+ expect(prisma.asset.findFirst).not.toHaveBeenCalled();
345
+ });
346
+
347
+ // ── #6310 D1/D2/D4/D5/D15 — Asset-first resolution ───────────────────────
348
+
349
+ const WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
350
+ const OTHER_WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000002";
351
+ const NEW_ASSET_ID = "asset_00000000-0000-7000-8000-0000000000b1";
352
+
353
+ it("resolves against the workspace Asset store first, and mints the grant with the asset_ id AND workspaceId (D1/D2/D15)", async () => {
354
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
355
+ workspaceId: WORKSPACE_ID,
356
+ });
357
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID });
358
+
359
+ await fulfillDeliverable(baseInput());
360
+
361
+ expect(prisma.flowDefinition.findUnique).toHaveBeenCalledWith(
362
+ expect.objectContaining({
363
+ where: { siteId_slug: { siteId: SITE_ID, slug: "get-a-quote" } },
364
+ select: { workspaceId: true },
365
+ }),
366
+ );
367
+ expect(prisma.asset.findFirst).toHaveBeenCalledWith({
368
+ where: { workspaceId: WORKSPACE_ID, key: "guide-small" },
369
+ select: { id: true },
370
+ });
371
+ expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
372
+ expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
373
+ expect.objectContaining({
374
+ data: expect.objectContaining({
375
+ siteId: SITE_ID,
376
+ assetId: NEW_ASSET_ID,
377
+ workspaceId: WORKSPACE_ID,
378
+ leadId: LEAD_ID,
379
+ sessionId: SESSION_ID,
380
+ }),
381
+ }),
382
+ );
383
+ });
384
+
385
+ it("falls back to the legacy DeliverableAsset store on an Asset miss, and mints a grant with NO workspaceId (D2/D3)", async () => {
386
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
387
+ workspaceId: WORKSPACE_ID,
388
+ });
389
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
390
+
391
+ await fulfillDeliverable(baseInput());
392
+
393
+ expect(prisma.deliverableAsset.findUnique).toHaveBeenCalledWith(
394
+ expect.objectContaining({
395
+ where: {
396
+ siteId_key: { siteId: SITE_ID, key: "guide-small" },
397
+ // #6257 — the legacy store's read is fenced by the same resolved
398
+ // workspace id the grant is minted under.
399
+ workspaceId: SITE_ID,
400
+ },
401
+ }),
402
+ );
403
+ const [[createArgs]] = (prisma.deliverableGrant.create as jest.Mock).mock
404
+ .calls as [[{ data: Record<string, unknown> }]];
405
+ expect(createArgs.data["assetId"]).toBe(
406
+ "dasset_00000000-0000-7000-8000-0000000000a1",
407
+ );
408
+ // #6374 D4 / AC-8 — this mint site now ALWAYS stamps `workspaceId`,
409
+ // including on the legacy `dasset_` arm this test exercises, and the
410
+ // value is the grant's own `siteId`. NULL remains the sentinel for the
411
+ // rows that already EXIST (the migration touches none of them); it is no
412
+ // longer the sentinel for a row this code mints, because a mint always
413
+ // knows its workspace. The download route is unaffected: it branches on
414
+ // the `assetId` PREFIX and consults `workspaceId` only on the `asset_`
415
+ // arm.
416
+ expect(createArgs.data["workspaceId"]).toBe(createArgs.data["siteId"]);
417
+ expect(createArgs.data["workspaceId"]).toBe(SITE_ID);
418
+ });
419
+
420
+ it("prefers the Asset match over an existing legacy DeliverableAsset row at the SAME key (D2 order)", async () => {
421
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
422
+ workspaceId: WORKSPACE_ID,
423
+ });
424
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID });
425
+ // beforeEach's default deliverableAsset.findUnique mock already resolves
426
+ // a row at this key — it must never even be consulted.
427
+
428
+ await fulfillDeliverable(baseInput());
429
+
430
+ expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
431
+ expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
432
+ expect.objectContaining({
433
+ data: expect.objectContaining({ assetId: NEW_ASSET_ID }),
434
+ }),
435
+ );
436
+ });
437
+
438
+ it("skips the Asset lookup entirely when the definition's workspaceId is null — never guesses one (D5)", async () => {
439
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
440
+ workspaceId: null,
441
+ });
442
+
443
+ await fulfillDeliverable(baseInput());
444
+
445
+ expect(prisma.asset.findFirst).not.toHaveBeenCalled();
446
+ expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
447
+ });
448
+
449
+ it("skips the Asset lookup entirely when no FlowDefinition row resolves (D5)", async () => {
450
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue(null);
451
+
452
+ await fulfillDeliverable(baseInput());
453
+
454
+ expect(prisma.asset.findFirst).not.toHaveBeenCalled();
455
+ expect(prisma.deliverableAsset.findUnique).toHaveBeenCalled();
456
+ });
457
+
458
+ it("mints no grant and logs asset_missing when NEITHER store resolves the key", async () => {
459
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
460
+ workspaceId: WORKSPACE_ID,
461
+ });
462
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
463
+ (prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue(null);
464
+
465
+ await fulfillDeliverable(baseInput());
466
+
467
+ expect(prisma.deliverableGrant.create).not.toHaveBeenCalled();
468
+ expect(mockLoggerInfo).toHaveBeenCalledWith(
469
+ expect.objectContaining({ outcome: "asset_missing" }),
470
+ expect.any(String),
471
+ );
472
+ });
473
+
474
+ it("never reaches a key that exists only in ANOTHER workspace (NFR3) — the lookup is scoped to this flow's own workspaceId alone", async () => {
475
+ (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
476
+ workspaceId: WORKSPACE_ID,
477
+ });
478
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue(null);
479
+ (prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue(null);
480
+
481
+ await fulfillDeliverable(baseInput());
482
+
483
+ expect(prisma.asset.findFirst).toHaveBeenCalledWith({
484
+ where: { workspaceId: WORKSPACE_ID, key: "guide-small" },
485
+ select: { id: true },
486
+ });
487
+ expect(prisma.asset.findFirst).not.toHaveBeenCalledWith(
488
+ expect.objectContaining({
489
+ where: expect.objectContaining({ workspaceId: OTHER_WORKSPACE_ID }),
490
+ }),
491
+ );
492
+ });
493
+
299
494
  // ── D5 — grant mint ───────────────────────────────────────────────────────
300
495
 
301
496
  it("mints the grant with assetId/leadId/sessionId", async () => {
@@ -521,3 +716,103 @@ describe("fulfillDeliverable", () => {
521
716
  );
522
717
  });
523
718
  });
719
+
720
+ // ─── #6257 — the fence: every fenced read carries workspaceId ───────────────
721
+
722
+ /**
723
+ * Unit C2's contract for this service (#6257 AC-1/AC-2). Three of its reads
724
+ * are workspace-scopable and are scoped here; the rest are keyed on the grant
725
+ * id itself — the capability token — and carry a global-key marker comment
726
+ * at the call site instead.
727
+ *
728
+ * `fulfillDeliverable`'s `siteId` is the session's own, resolved before it
729
+ * reaches this service (#6254/#6374 AC-7); `listSendlessGrants` takes an
730
+ * OPERATOR-SUPPLIED alias off the `deliverable:resend` CLI, so it resolves
731
+ * that alias itself and fails closed when it names no workspace.
732
+ */
733
+ describe("#6257 — every fenced read carries workspaceId", () => {
734
+ const originalEnv = process.env;
735
+
736
+ beforeEach(() => {
737
+ jest.clearAllMocks();
738
+ process.env = {
739
+ ...originalEnv,
740
+ AILK_PUBLIC_API_URL: "https://api.example.com",
741
+ AILK_DELIVERABLE_EMAIL_FROM: "deliverable-default@example.com",
742
+ };
743
+ mockSendDeliverableEmail.mockResolvedValue({ ok: true, messageId: "msg-test" });
744
+ (prisma.deliverableAsset.findUnique as jest.Mock).mockResolvedValue({
745
+ id: "dasset_00000000-0000-7000-8000-0000000000a1",
746
+ siteId: SITE_ID,
747
+ key: "guide-small",
748
+ });
749
+ (prisma.deliverableGrant.create as jest.Mock).mockResolvedValue({ id: GRANT_ID });
750
+ (prisma.deliverableGrant.update as jest.Mock).mockResolvedValue({ id: GRANT_ID });
751
+ (prisma.deliverableGrant.findMany as jest.Mock).mockResolvedValue([]);
752
+ });
753
+
754
+ afterEach(() => {
755
+ process.env = originalEnv;
756
+ });
757
+
758
+ it("scopes the LEGACY DeliverableAsset lookup by the session's workspace id", async () => {
759
+ await fulfillDeliverable(baseInput());
760
+
761
+ const { where } = (prisma.deliverableAsset.findUnique as jest.Mock).mock.calls[0][0];
762
+ expect(where.workspaceId).toBe(SITE_ID);
763
+ expect(where.siteId_key).toEqual({ siteId: SITE_ID, key: "guide-small" });
764
+ });
765
+
766
+ it("scopes the PlaybookInstance existence check by the session's workspace id", async () => {
767
+ (prisma.playbookInstance.findFirst as jest.Mock).mockResolvedValue({ id: "pbk_1" });
768
+
769
+ await fulfillDeliverable(
770
+ baseInput({
771
+ segment: undefined,
772
+ configSnapshot: {
773
+ slug: "get-a-quote",
774
+ defaultLocale: "en",
775
+ steps: [],
776
+ deliverables: {
777
+ assets: [{ assetKey: "playbook:targeting-maturity" }],
778
+ copy: { subject: en("x"), body: en("y") },
779
+ },
780
+ },
781
+ }),
782
+ );
783
+
784
+ expect(
785
+ (prisma.playbookInstance.findFirst as jest.Mock).mock.calls[0][0].where.workspaceId,
786
+ ).toBe(SITE_ID);
787
+ });
788
+
789
+ it("listSendlessGrants resolves the CLI alias and scopes the list by the RESOLVED workspace id", async () => {
790
+ // The `"default"` alias resolves to the seeded sentinel workspace — the
791
+ // list must be scoped by THAT id, never by the alias string the operator
792
+ // typed on the command line. Passing the alias and asserting the SENTINEL
793
+ // id back is what discriminates a resolution from a passthrough: a test
794
+ // that passed the workspace id as the alias would assert the same string
795
+ // either way and could not fail (security review, round 1).
796
+ (prisma.workspace.findUnique as jest.Mock).mockImplementation(
797
+ async ({ where }: { where: { id: string } }) =>
798
+ where.id === SENTINEL_WORKSPACE_ID
799
+ ? { id: SENTINEL_WORKSPACE_ID, organizationId: SENTINEL_ORGANIZATION_ID }
800
+ : null,
801
+ );
802
+
803
+ await listSendlessGrants({ siteId: "default" });
804
+
805
+ const { where } = (prisma.deliverableGrant.findMany as jest.Mock).mock.calls[0][0];
806
+ expect(where.workspaceId).toBe(SENTINEL_WORKSPACE_ID);
807
+ expect(where.siteId).toBe(SENTINEL_WORKSPACE_ID);
808
+ expect(where.workspaceId).not.toBe("default");
809
+ expect(where.sendStatus).toBeNull();
810
+ });
811
+
812
+ it("listSendlessGrants fails closed on an alias that resolves to no workspace", async () => {
813
+ (prisma.workspace.findUnique as jest.Mock).mockResolvedValue(null);
814
+
815
+ await expect(listSendlessGrants({ siteId: "no-such-site" })).resolves.toEqual([]);
816
+ expect(prisma.deliverableGrant.findMany).not.toHaveBeenCalled();
817
+ });
818
+ });
@@ -34,6 +34,10 @@ jest.mock("@working-theory/observability", () => ({
34
34
  },
35
35
  }));
36
36
 
37
+ import {
38
+ mockNoWorkspace,
39
+ mockWorkspaceExists,
40
+ } from "../../lib/__mocks__/prisma.js";
37
41
  import { prisma } from "../../lib/prisma.js";
38
42
  import {
39
43
  listSendlessGrants,
@@ -294,9 +298,25 @@ describe("resendDeliverableGrant", () => {
294
298
  describe("listSendlessGrants", () => {
295
299
  beforeEach(() => {
296
300
  jest.clearAllMocks();
301
+ // #6335 — the shared prisma mock no longer resolves a workspace for any
302
+ // id asked of it, so this describe states the one it expects.
303
+ // `listSendlessGrants` resolves its `--site` alias before it reads
304
+ // (#6257); without this the resolution returns null, the function
305
+ // returns [] early, and every assertion below would be vacuous.
306
+ mockWorkspaceExists(prisma, SITE_ID);
297
307
  (prisma.deliverableGrant.findMany as jest.Mock).mockResolvedValue([]);
298
308
  });
299
309
 
310
+ it("returns nothing and reads nothing when the site alias resolves to no workspace", async () => {
311
+ // The rejection path, so the explicit setup above is proven load-bearing
312
+ // rather than decorative (#6335 AC-6).
313
+ mockNoWorkspace(prisma);
314
+
315
+ await expect(listSendlessGrants({ siteId: SITE_ID })).resolves.toEqual([]);
316
+
317
+ expect(prisma.deliverableGrant.findMany).not.toHaveBeenCalled();
318
+ });
319
+
300
320
  // ── D4 — list mode selects on sendStatus: null + siteId, no send ────────
301
321
 
302
322
  it("selects on siteId + sendStatus: null and performs no send", async () => {