create-ailk 0.21.0 → 0.23.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 (342) hide show
  1. package/component-catalog.json +186 -8
  2. package/component-catalog.md +9 -7
  3. package/dist/add-module.js +20 -14
  4. package/dist/add-route.d.ts +55 -1
  5. package/dist/add-route.js +294 -12
  6. package/dist/cli.js +27 -10
  7. package/dist/derive-architecture.d.ts +12 -3
  8. package/dist/derive-architecture.js +20 -8
  9. package/dist/design-package.js +7 -2
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +5 -5
  12. package/dist/lib/npm-auth.d.ts +9 -0
  13. package/dist/lib/npm-auth.js +48 -0
  14. package/dist/lib/scaffold-render-sweep.d.ts +3 -2
  15. package/dist/lib/scaffold-render-sweep.js +5 -8
  16. package/dist/module-architecture.js +99 -9
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/programmatic.js +19 -5
  20. package/dist/seed-flows.d.ts +4 -1
  21. package/dist/seed-flows.js +50 -4
  22. package/dist/site-chrome.d.ts +8 -0
  23. package/dist/site-chrome.js +17 -1
  24. package/dist/spoke-checkout.d.ts +65 -41
  25. package/dist/spoke-checkout.js +208 -430
  26. package/dist/starter-grid.d.ts +0 -3
  27. package/dist/starter-grid.js +24 -14
  28. package/dist/surfaces.js +52 -26
  29. package/dist/sync-routes.d.ts +38 -1
  30. package/dist/sync-routes.js +187 -13
  31. package/dist/template-record.d.ts +4 -3
  32. package/dist/template-record.js +8 -4
  33. package/dist/topology.js +25 -1
  34. package/dist/update.js +15 -0
  35. package/package.json +3 -3
  36. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  37. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  38. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  39. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  40. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  41. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  42. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  43. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  44. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  45. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  46. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  47. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  48. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  49. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  50. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  51. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  52. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  53. package/templates/.env.example +9 -7
  54. package/templates/.github/workflows/ci.yml +13 -0
  55. package/templates/README.md +1 -0
  56. package/templates/_gitignore +4 -0
  57. package/templates/apps/api/.env.example +27 -0
  58. package/templates/apps/api/CLAUDE.md +87 -17
  59. package/templates/apps/api/package.json +1 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  61. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  62. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  63. package/templates/apps/api/src/config/index.ts +109 -0
  64. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  65. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  66. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  67. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  68. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  69. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  70. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  71. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  72. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  73. package/templates/apps/api/src/lib/stripe.ts +74 -4
  74. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  75. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  77. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  78. package/templates/apps/api/src/middleware/auth.ts +13 -6
  79. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  80. package/templates/apps/api/src/openapi/spec.ts +33 -3
  81. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  82. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  83. package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
  84. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  85. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
  86. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  87. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  88. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  89. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  90. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  91. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  92. package/templates/apps/api/src/routes/consent/account.ts +268 -0
  93. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  94. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  95. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  96. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  97. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  98. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  99. package/templates/apps/api/src/routes/content/index.ts +16 -2
  100. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  101. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  102. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  103. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  104. package/templates/apps/api/src/routes/leads/index.ts +164 -7
  105. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  106. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  111. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  112. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  113. package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
  114. package/templates/apps/api/src/server.ts +163 -46
  115. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  116. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
  117. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  118. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
  119. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  120. package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
  121. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
  122. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  123. package/templates/apps/api/src/services/consent.ts +95 -0
  124. package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
  125. package/templates/apps/api/src/services/entitlements.ts +157 -1
  126. package/templates/apps/api/src/services/lead-routing.ts +431 -47
  127. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  128. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
  129. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  130. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
  131. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  132. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
  133. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
  134. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  135. package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
  136. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
  137. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  138. package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
  139. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  140. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  141. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  142. package/templates/apps/api/src/services/person.ts +164 -17
  143. package/templates/apps/api/src/services/plan-offerings.ts +125 -0
  144. package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
  145. package/templates/apps/mcp/CLAUDE.md +22 -14
  146. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  147. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  148. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  149. package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
  150. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  151. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  152. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  153. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  154. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  155. package/templates/apps/mcp/src/server.ts +5 -0
  156. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  157. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  158. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  159. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  160. package/templates/apps/web/.env.example +24 -31
  161. package/templates/apps/web/CLAUDE.md +393 -9
  162. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  163. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  164. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  165. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  166. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  167. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  168. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  169. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  170. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  171. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  172. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  173. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  174. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  175. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  176. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  177. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  178. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  179. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  183. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  184. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  185. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  186. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  187. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  188. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  189. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  190. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  191. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  192. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  193. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  194. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  195. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  196. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
  197. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
  198. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
  199. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
  200. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  201. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  202. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
  203. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
  204. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
  205. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  206. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  207. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  208. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  209. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  210. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  211. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  212. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
  213. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  214. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
  215. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
  216. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
  217. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
  218. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  219. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
  220. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  221. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  222. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
  223. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  224. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  225. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  226. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  227. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  228. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  229. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  230. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  231. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
  232. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  233. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  234. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  235. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  236. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  237. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  238. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  239. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  240. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  241. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  242. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  243. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  244. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  245. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  246. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  247. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  248. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  249. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  250. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  251. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  252. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  253. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  254. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  255. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  256. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  257. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  258. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  259. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  260. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  261. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  262. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  263. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  264. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  265. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  266. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  267. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  268. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  269. package/templates/apps/web/auth.ts +28 -2
  270. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  271. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  272. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  273. package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
  274. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  275. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
  276. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  277. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  278. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
  279. package/templates/apps/web/lib/account-invite.ts +37 -0
  280. package/templates/apps/web/lib/auth-routes.ts +36 -2
  281. package/templates/apps/web/lib/checkout-config.ts +16 -0
  282. package/templates/apps/web/lib/consent-policies.ts +22 -0
  283. package/templates/apps/web/lib/deck-page.ts +7 -0
  284. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  285. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  286. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  287. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  288. package/templates/apps/web/lib/magnet-routes.ts +42 -0
  289. package/templates/apps/web/lib/modules.generated.ts +9 -0
  290. package/templates/apps/web/lib/newsletter.actions.ts +202 -0
  291. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  292. package/templates/apps/web/lib/posting-title.ts +17 -0
  293. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  294. package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
  295. package/templates/apps/web/lib/site-chrome.ts +2 -0
  296. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  297. package/templates/apps/web/messages/en.json +71 -20
  298. package/templates/apps/web/middleware.ts +12 -2
  299. package/templates/apps/web/package.json +1 -0
  300. package/templates/apps/web/security-headers.config.ts +17 -0
  301. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  302. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  303. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  304. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  305. package/templates/content/en/BlogList/blog.mdx +4 -71
  306. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  307. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  308. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  309. package/templates/content/en/BookCall/start-project.mdx +1 -17
  310. package/templates/content/en/ContactPage/contact.mdx +6 -63
  311. package/templates/content/en/HomePage/home.mdx +3 -21
  312. package/templates/database/CHANGELOG.md +69 -0
  313. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  314. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  315. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  316. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  317. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  318. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  319. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  320. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  321. package/templates/database/__tests__/seed.test.ts +64 -1
  322. package/templates/database/auth/schema.prisma +4 -0
  323. package/templates/database/config/schema.prisma +91 -0
  324. package/templates/database/inbox/schema.prisma +17 -2
  325. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  326. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  327. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  328. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  329. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  330. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  331. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  332. package/templates/database/package.json +1 -1
  333. package/templates/database/seed.ts +62 -3
  334. package/versions.json +35 -35
  335. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  336. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
  337. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  338. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  339. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  340. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  341. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  342. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The workspace's offerings as plan keys (#8514 D9) — moved here unchanged
3
+ * from `routes/workspaces/plan.ts` by #8757 D4, so the magnet PATCH's
4
+ * save-time offering check and the lead route's submit-time re-check
5
+ * (`services/magnet-completion.ts`) read the same list `GET …/plan-offerings`
6
+ * serves.
7
+ *
8
+ * Two sources (#8934, spec
9
+ * docs/specs/8934-re-source-the-plan-offerings-spec.md D1–D4):
10
+ *
11
+ * - A connected workspace with the `commerce` claim lists its Offerings
12
+ * shown set (`getWorkspaceShownSet`, read from the database and never from
13
+ * Stripe — contract T7): one item per sellable Price, in the reader's
14
+ * order, `key` the Price's full lookup key (G5), `name` the product name
15
+ * and its billing word, `type` the product's group. The page list is not
16
+ * consulted.
17
+ * - An unclaimed workspace, or one the reader answers `null` for (no
18
+ * workspace, no connection, no selection), lists its OWN Service then
19
+ * Product pages: `key` is the page slug's last segment when it is an
20
+ * offering key (else the page is skipped), the first page per key wins, at
21
+ * most 200; `name` is the page title. Off the database content adapter only
22
+ * the default workspace has content (`workspaceContentAdapter`), so every
23
+ * other workspace reads `[]`.
24
+ *
25
+ * Never throws: a failed read answers `{ failed }` with a fixed word or the
26
+ * error's class name only, so a caller can tell "none listed" from "could not
27
+ * tell" — a failure is never a pass. `LicenseUndetermined` (the licence could
28
+ * not be read, so the Offerings services may not be called — 8876 D10),
29
+ * `OfferingsSnapshotExpired` and `OfferingsSnapshotEmpty` (the rows say what
30
+ * is shown, but no current snapshot says which keys it sells).
31
+ *
32
+ * Base tree (SEC-8757-D): no `oss: false` marker and no `MODULE_SLICES`
33
+ * entry, because the never-gate lead route reaches it.
34
+ */
35
+ import {
36
+ PLAN_OFFERINGS_MAX,
37
+ planOfferingSchema,
38
+ type PlanOfferingItem,
39
+ } from "@working-theory/validation";
40
+
41
+ import { resolveCommerceClaim } from "../lib/require-commerce-claim.js";
42
+
43
+ import { getWorkspaceShownSet } from "./offerings/index.js";
44
+ import { workspaceContentAdapter } from "./public-site-resolver.js";
45
+
46
+ /** `planOfferingItemSchema.name`'s bound, in UTF-16 units. */
47
+ const NAME_MAX = 200;
48
+
49
+ /** The workspace's offerings as plan keys (D3). */
50
+ export async function listPlanOfferings(
51
+ workspaceId: string,
52
+ ): Promise<PlanOfferingItem[] | { failed: string }> {
53
+ try {
54
+ const claim = await resolveCommerceClaim();
55
+ if (claim === "undetermined") return { failed: "LicenseUndetermined" };
56
+ if (claim === "unclaimed") return await pageOfferings(workspaceId);
57
+ const shown = await getWorkspaceShownSet(workspaceId);
58
+ if (shown === null) return await pageOfferings(workspaceId);
59
+ if (shown.state === "expired") return { failed: "OfferingsSnapshotExpired" };
60
+ if (shown.state === "empty") return { failed: "OfferingsSnapshotEmpty" };
61
+ return collect(
62
+ shown.offerings.flatMap((offering) =>
63
+ offering.prices.map((price) => ({
64
+ key: price.lookupKey,
65
+ name: planOfferingName(offering.name, price.interval),
66
+ type: offering.ailkGroup,
67
+ })),
68
+ ),
69
+ );
70
+ } catch (error) {
71
+ // The error's class name only — never its message, which may name a slug.
72
+ return { failed: error instanceof Error ? error.name : typeof error };
73
+ }
74
+ }
75
+
76
+ /** The workspace's Service then Product pages as plan keys (D4, #8514 D9). */
77
+ async function pageOfferings(workspaceId: string): Promise<PlanOfferingItem[]> {
78
+ const adapter = await workspaceContentAdapter(workspaceId);
79
+ if (!adapter) return [];
80
+ const [services, products] = await Promise.all([
81
+ adapter.listPages("Service"),
82
+ adapter.listPages("Product"),
83
+ ]);
84
+ const typed = [
85
+ ...services.map((page) => ({ page, type: "service" as const })),
86
+ ...products.map((page) => ({ page, type: "product" as const })),
87
+ ];
88
+ return collect(
89
+ typed.map(({ page, type }) => ({
90
+ key: page.slug.split("/").filter(Boolean).pop() ?? "",
91
+ name: page.title.trim().slice(0, NAME_MAX),
92
+ type,
93
+ })),
94
+ );
95
+ }
96
+
97
+ /** The guards both sources share: a valid key, a name, each key once, at most 200. */
98
+ function collect(candidates: PlanOfferingItem[]): PlanOfferingItem[] {
99
+ const offerings: PlanOfferingItem[] = [];
100
+ const seen = new Set<string>();
101
+ for (const { key, name, type } of candidates) {
102
+ if (offerings.length >= PLAN_OFFERINGS_MAX) break;
103
+ if (!planOfferingSchema.safeParse(key).success || name === "" || seen.has(key)) continue;
104
+ seen.add(key);
105
+ offerings.push({ key, name, type });
106
+ }
107
+ return offerings;
108
+ }
109
+
110
+ /**
111
+ * `<product name> (<word>)`, the word from the Price's interval (D2). Whole
112
+ * code points are dropped from the end of the product name until the name
113
+ * fits `NAME_MAX` units, so a surrogate pair is never split; the word is
114
+ * always kept.
115
+ */
116
+ function planOfferingName(productName: string, interval: "month" | "year" | null): string {
117
+ const word = interval === "month" ? "Monthly" : interval === "year" ? "Annual" : "One-time";
118
+ const suffix = ` (${word})`;
119
+ let name = "";
120
+ for (const point of productName) {
121
+ if (name.length + point.length + suffix.length > NAME_MAX) break;
122
+ name += point;
123
+ }
124
+ return name + suffix;
125
+ }
@@ -62,13 +62,16 @@
62
62
  * prefix — and never reaches the wire.
63
63
  *
64
64
  * THE FLOWS ROUTES' WORKSPACE (#7844 D1). `resolveCallerWorkspace` below is
65
- * the one function the five `/v1/flows` routes resolve their workspace with:
66
- * a spoke resolves to its own workspace (`viaSpoke: true`), a keyless call to
67
- * the `AILK_SITE_ID` alias exactly as before, and anything else to
68
- * `unresolved` with one of two reasons, `spoke-refused` or `alias-unresolved`.
69
- * The reasons are for `request.log` only; the routes answer both the same
70
- * way. It lives here rather than under `routes/flows/` because the two flows
71
- * route files sit in different module slices, and this file is base tree.
65
+ * the one function the five `/v1/flows` routes (and the open deliverable)
66
+ * resolve their workspace with: a spoke resolves to its own workspace
67
+ * (`viaSpoke: true`), a keyless call through branch 2's one-workspace
68
+ * predicate (#9145 — never the `AILK_SITE_ID` alias, so every anonymous
69
+ * route answers the same workspace for the same deployment), and anything
70
+ * else to `unresolved` with one of two reasons, `spoke-refused` or
71
+ * `ambiguous`. The reasons are for `request.log` only; the routes answer both
72
+ * the same way. It lives here rather than under `routes/flows/` because the
73
+ * two flows route files sit in different module slices, and this file is
74
+ * base tree.
72
75
  */
73
76
  import {
74
77
  resolvedAdapterKind,
@@ -246,14 +249,22 @@ export async function resolveCallerSite(
246
249
  * `request.log` only; the routes answer both reasons the same way.
247
250
  */
248
251
  export type CallerWorkspace =
249
- | { kind: "resolved"; workspaceId: string; viaSpoke: boolean }
250
- | { kind: "unresolved"; reason: "spoke-refused" | "alias-unresolved" };
252
+ | {
253
+ kind: "resolved";
254
+ workspaceId: string;
255
+ viaSpoke: boolean;
256
+ /** #8771 D3 — set exactly when `viaSpoke` is true: the registered spoke's id. */
257
+ spokeId?: string;
258
+ }
259
+ | { kind: "unresolved"; reason: "spoke-refused" | "ambiguous" };
251
260
 
252
261
  /**
253
262
  * Resolve the workspace a `/v1/flows` request serves. A spoke resolves to its
254
- * own workspace; a keyless call to the `AILK_SITE_ID` alias, exactly as the
255
- * flows routes did before #7844; a refused caller to nothing. There is no
256
- * fallback: a spoke never reaches the alias workspace (D5).
263
+ * own workspace; a keyless call through `resolveDefaultWorkspaceId`, the
264
+ * predicate `resolvePublicWorkspace` uses (#9145) — on any content adapter,
265
+ * since a flow is a database row whatever serves the pages; a refused caller
266
+ * to nothing. There is no fallback: a spoke never reaches the default
267
+ * workspace (D5).
257
268
  *
258
269
  * Calls `resolveCallerSite` with no options, so a failed hub-status read on a
259
270
  * keyless call rejects (#7844 C1).
@@ -270,13 +281,14 @@ export async function resolveCallerWorkspace(
270
281
  kind: "resolved",
271
282
  workspaceId: caller.workspaceId,
272
283
  viaSpoke: true,
284
+ spokeId: caller.spokeId,
273
285
  };
274
286
  }
275
287
  // Deferred, like every database import in this file.
276
- const { resolveSiteWorkspaceId } = await import("../lib/site.js");
277
- const workspaceId = await resolveSiteWorkspaceId();
288
+ const { resolveDefaultWorkspaceId } = await import("./default-tenant.js");
289
+ const workspaceId = await resolveDefaultWorkspaceId();
278
290
  return workspaceId === null
279
- ? { kind: "unresolved", reason: "alias-unresolved" }
291
+ ? { kind: "unresolved", reason: "ambiguous" }
280
292
  : { kind: "resolved", workspaceId, viaSpoke: false };
281
293
  }
282
294
 
@@ -77,32 +77,32 @@ Every tool maps 1:1 to an `apps/api` endpoint. When you change a tool, change th
77
77
  **Conversion-write (3):** `capture_lead`, `subscribe_newsletter`, `request_callback`
78
78
  **Maintenance (2):** `audit`, `create_project`
79
79
  **Flow (2):** `get_flow`, `submit_flow_step` — module-gated (`module: "signal-collectors"`, D-48, #5366) and licence-feature-tagged (`feature: "flows-deliverables"`, #7136): identity-blind like every tool in this list, registered only when the signal-collectors module is present, and gated on the deployment's `flows-deliverables` licence feature under `MCP_FEATURE_GATE_MODE`, the same shape Booking below has. `submit_flow_step` takes the HTTP write branch (its own daily cap, `MCP_ABUSE_SUBMIT_FLOW_STEP_DAILY_CAP`, default 50 — #8071)
80
- **Booking (3):** `list_availability`, `get_event_meta`, `create_booking` — module-gated (`module: "scheduling"`) and licence-feature-tagged (`feature: "scheduling"`, #7136), gated on the deployment's `scheduling` licence feature under `MCP_FEATURE_GATE_MODE`. `create_booking` takes the HTTP write branch (its own daily cap, `MCP_ABUSE_CREATE_BOOKING_DAILY_CAP`, default 20, and an `attendee.email` format check — #8071)
80
+ **Booking (4):** `list_availability`, `get_event_meta`, `list_meetings`, `create_booking` — module-gated (`module: "scheduling"`) and licence-feature-tagged (`feature: "scheduling"`, #7136), gated on the deployment's `scheduling` licence feature under `MCP_FEATURE_GATE_MODE`. `create_booking` takes the HTTP write branch (its own daily cap, `MCP_ABUSE_CREATE_BOOKING_DAILY_CAP`, default 20, and an `attendee.email` format check — #8071). `list_meetings` (#8843 D5) twins `GET /v1/schedule/meetings`: it takes no argument and returns the site's Live meetings — slug, visibility, position and whether each can be booked, plus a bookable one's calLink, title, length, description and price; `private` means unlisted, not secret
81
81
 
82
- ### Why the parity test counts 20 and a keyless server registers 19
82
+ ### Why the parity test counts 21 and a keyless server registers 20
83
83
 
84
- - **20 in the `TOOLS` array / parity test**: the `TOOLS` array in
85
- `packages/metadata/src/mcp-tool-catalog.ts` defines 20 anonymous tools; the
86
- parity test counts that full array. It also holds the fifteen signed-in
84
+ - **21 in the `TOOLS` array / parity test**: the `TOOLS` array in
85
+ `packages/metadata/src/mcp-tool-catalog.ts` defines 21 anonymous tools; the
86
+ parity test counts that full array. It also holds the eighteen signed-in
87
87
  workspace tools (#3591), which no anonymous view returns — see § The
88
88
  signed-in workspace surface below.
89
- - **19 registered on a KEYLESS deployment, 20 on a commerce-claimed one**
89
+ - **20 registered on a KEYLESS deployment, 21 on a commerce-claimed one**
90
90
  (#5971): `createMcpServer` (`apps/mcp/src/server.ts`) registers
91
91
  `getMcpToolRegistrations()` minus `OSS_DISABLED_TOOLS` (now empty), minus
92
92
  module-gated tools whose module is off, minus any `addon`-tagged tool whose
93
93
  claim this deployment does not hold. `start_purchase` carries
94
94
  `addon: "commerce"`, so it is the one entry whose presence depends on the
95
- licence — 19 without the claim, 20 with it.
96
- - **19 free anonymous tools**: since #5853 retired the three content-write
95
+ licence — 20 without the claim, 21 with it.
96
+ - **20 free anonymous tools**: since #5853 retired the three content-write
97
97
  tools, every registered tool remains anonymous — call-time gating checks a
98
- licence claim, never an identity. The five carrying `tier: "pro"`
98
+ licence claim, never an identity. The six carrying `tier: "pro"`
99
99
  (`get_flow`, `submit_flow_step`, `list_availability`, `get_event_meta`,
100
- `create_booking`) are gated at REGISTRATION **and at call time** (#7315):
100
+ `list_meetings`, `create_booking`) are gated at REGISTRATION **and at call time** (#7315):
101
101
  each needs its module, and each catalog row names the licence `feature:`
102
102
  that entitles it. `start_purchase` is gated at call time by both this
103
103
  server-wide gate and its own Gate 2 in `tools/purchase.ts`.
104
104
  `MCP_FEATURE_GATE_MODE` (`packages/metadata`) decides what an unheld
105
- feature does. In `"warn"` mode (this release) the five register on module
105
+ feature does. In `"warn"` mode (this release) the six register on module
106
106
  presence alone, and the boot (`bootMcp()`) logs one warning (stderr) per tool
107
107
  served without its feature. In `"refuse"` mode (the next release) they
108
108
  also need the deployment's licence to hold the feature, and
@@ -126,10 +126,14 @@ here. Spec: `docs/specs/3591-tenant-scoped-agent-auth-secure-spec.md` D13–D16.
126
126
  - **apps/mcp checks only the key's shape.** The key is attached as the SDK's `req.auth` for that
127
127
  request alone and reaches a tool as `extra.authInfo.token`. apps/api is the one place it is
128
128
  checked, and its tenant guard and the caller's clamped role decide every answer.
129
- - **Fifteen tools, each a `twin`.** The catalog rows carry `surface: "workspace"`, `tier: "pro"`
129
+ - **Eighteen tools, each a `twin`.** The catalog rows carry `surface: "workspace"`, `tier: "pro"`
130
130
  and `modulesAnyOf` (the modules any one of which registers the tool — its twin route's gate):
131
131
  `get_agent_context`, the four message tools, the three submission tools, the three
132
- application tools and (#8514) the two People notes tools, `get_person_notes` and
132
+ listing application tools (#9137: `list_listing_applications`, `get_listing_application`,
133
+ `decide_listing_application` — a `ProjectListing` row, never a job application, which is a Job
134
+ application magnet's submission; the pre-rename `list_inbox_applications`,
135
+ `get_inbox_application` and `decide_inbox_application` stay registered for one release as
136
+ deprecated aliases of the same handlers, each description opening `Deprecated: use …`) and (#8514) the two People notes tools, `get_person_notes` and
133
137
  `set_person_notes` — the team's private note on one client, which an agent reads and writes
134
138
  only with the `member-pieces-manage` grant; `set_person_notes` requires the `ifUpdatedAt` it
135
139
  read and is refused `notes_changed` when someone saved since — and (#8516) the two proposals
@@ -219,7 +223,11 @@ Example (lead routing):
219
223
  - Don't add a tool without the matching `apps/api` endpoint.
220
224
  - Don't put business logic in tool handlers — they're adapters.
221
225
 
222
- Last updated: 2026-10-14 — #8516: the signed-in workspace surface is fifteen tools — `list_proposals` and `create_proposal` (`tools/proposals.ts`) twin `GET`/`POST /v1/workspaces/:workspaceId/people/:personId/proposals` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
226
+ Last updated: 2026-10-18 — #9137: the three listing application tools are `list_listing_applications`, `get_listing_application` and `decide_listing_application`, and their pre-rename names stay one release as deprecated aliases — the signed-in workspace surface is eighteen tools — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
227
+
228
+ Prior: 2026-10-18 — #8843 PR A: `list_meetings` twins `GET /v1/schedule/meetings` — Booking is four tools, the counts are 21 in TOOLS, 20 registered keyless and 21 commerce-claimed, and six tools carry `tier: "pro"` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/server.ts` and `tests/parity/api-parity/kinds/site.ts`.
229
+
230
+ Prior: 2026-10-14 — #8516: the signed-in workspace surface is fifteen tools — `list_proposals` and `create_proposal` (`tools/proposals.ts`) twin `GET`/`POST /v1/workspaces/:workspaceId/people/:personId/proposals` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
223
231
 
224
232
  Prior: 2026-10-13 — #8514: the signed-in workspace surface is thirteen tools — `get_person_notes` and `set_person_notes` (`tools/person_notes.ts`) twin `GET`/`PUT /v1/workspaces/:workspaceId/people/:personId/notes` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
225
233
 
@@ -105,9 +105,14 @@ type GateMode = NonNullable<McpCatalogOptions["featureGate"]>;
105
105
 
106
106
  type McpFeature = NonNullable<McpToolCatalogEntry["feature"]>;
107
107
 
108
- /** The five licence-feature tools, by the feature that entitles them. */
108
+ /** The six licence-feature tools, by the feature that entitles them. */
109
109
  const FEATURE_TOOLS: Record<McpFeature, readonly string[]> = {
110
- scheduling: ["list_availability", "get_event_meta", "create_booking"],
110
+ scheduling: [
111
+ "list_availability",
112
+ "get_event_meta",
113
+ "list_meetings",
114
+ "create_booking",
115
+ ],
111
116
  "flows-deliverables": ["get_flow", "submit_flow_step"],
112
117
  };
113
118
 
@@ -318,14 +323,14 @@ describe.each([
318
323
  const bootNames = Object.keys(
319
324
  registeredToolsOf(createMcpServer()),
320
325
  ).sort();
321
- // Both counts below are of the served catalog — the full catalog (20 at
326
+ // Both counts below are of the served catalog — the full catalog (21 at
322
327
  // boot, 14 after the re-read) minus this seam's UNSERVED_MCP_TOOLS (#8665).
323
328
  expect(bootNames).toEqual(
324
329
  servedCatalog(bootOptions)
325
330
  .map((t) => t.name)
326
331
  .sort(),
327
332
  );
328
- expect(bootNames).toHaveLength(20 - UNSERVED_MCP_TOOLS.length);
333
+ expect(bootNames).toHaveLength(21 - UNSERVED_MCP_TOOLS.length);
329
334
 
330
335
  startMcpClaimRefresh({ checkLicense: resolving({ features: [] }) });
331
336
  await jest.advanceTimersByTimeAsync(MCP_CLAIM_REFRESH_MS);