create-ailk 0.4.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 (396) hide show
  1. package/README.md +55 -8
  2. package/component-catalog.json +12248 -0
  3. package/component-catalog.md +193 -68
  4. package/dist/cli.js +29 -1
  5. package/dist/derive-architecture.js +5 -2
  6. package/dist/lib/api-boot-assertion.d.ts +46 -0
  7. package/dist/lib/api-boot-assertion.js +70 -0
  8. package/dist/lib/extract-module-bundle.d.ts +50 -2
  9. package/dist/lib/extract-module-bundle.js +223 -15
  10. package/dist/lib/fetch-guard.d.ts +28 -0
  11. package/dist/lib/fetch-guard.js +101 -0
  12. package/dist/lib/fetch-module.js +4 -0
  13. package/dist/lib/fetch-template.js +4 -0
  14. package/dist/lib/import-graph.d.ts +29 -0
  15. package/dist/lib/import-graph.js +140 -0
  16. package/dist/module-architecture.d.ts +60 -8
  17. package/dist/module-architecture.js +587 -242
  18. package/dist/parse-args.js +6 -2
  19. package/dist/programmatic.d.ts +10 -0
  20. package/dist/programmatic.js +134 -19
  21. package/dist/site-chrome.d.ts +80 -0
  22. package/dist/site-chrome.js +135 -0
  23. package/dist/surfaces.d.ts +21 -6
  24. package/dist/surfaces.js +209 -36
  25. package/dist/template-manifest.d.ts +88 -3
  26. package/dist/template-manifest.js +281 -29
  27. package/dist/template-names.d.ts +1 -1
  28. package/dist/template-names.js +2 -0
  29. package/package.json +10 -2
  30. package/site-templates/_base/ailk-template.json +6 -0
  31. package/site-templates/_base/content/en/Pricing/pricing.mdx +169 -0
  32. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +351 -0
  33. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +400 -0
  34. package/site-templates/marketing-site/ailk-template.json +11 -0
  35. package/site-templates/marketing-site/content/en/blog/05-this-vc-morning-routine-will-make-you-super-annoying.mdx +36 -0
  36. package/site-templates/marketing-site/provenance.json +4 -0
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +288 -0
  38. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +286 -0
  39. package/site-templates/marketing-site-free/ailk-template.json +2 -1
  40. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +279 -0
  41. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +336 -0
  42. package/site-templates/minimalism-site/ailk-template.json +22 -0
  43. package/site-templates/minimalism-site/assets/avatar-placeholder-1.svg +7 -0
  44. package/site-templates/minimalism-site/assets/avatar-placeholder-2.svg +7 -0
  45. package/site-templates/minimalism-site/assets/avatar-placeholder-3.svg +7 -0
  46. package/site-templates/minimalism-site/assets/avatar-placeholder-4.svg +7 -0
  47. package/site-templates/minimalism-site/assets/avatar-placeholder-5.svg +7 -0
  48. package/site-templates/minimalism-site/assets/avatar-placeholder-6.svg +7 -0
  49. package/site-templates/minimalism-site/assets/hero-backdrop.svg +17 -0
  50. package/site-templates/minimalism-site/assets/logo-placeholder-1.svg +6 -0
  51. package/site-templates/minimalism-site/assets/logo-placeholder-2.svg +6 -0
  52. package/site-templates/minimalism-site/assets/logo-placeholder-3.svg +6 -0
  53. package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
  54. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +110 -0
  55. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +68 -0
  56. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +57 -0
  57. package/site-templates/minimalism-site/content/en/HomePage/home.mdx +244 -0
  58. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +77 -0
  59. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +73 -0
  60. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +52 -0
  61. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +52 -0
  62. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +52 -0
  63. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +52 -0
  64. package/site-templates/minimalism-site/content/en/Service/first-engagement.mdx +38 -0
  65. package/site-templates/minimalism-site/content/en/Service/second-engagement.mdx +38 -0
  66. package/site-templates/minimalism-site/content/en/Service/third-engagement.mdx +38 -0
  67. package/site-templates/minimalism-site/content/en/ServicesList/services.mdx +73 -0
  68. package/site-templates/minimalism-site/flows/referral.flow.json +71 -0
  69. package/site-templates/minimalism-site/provenance.json +4 -0
  70. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +61 -0
  71. package/site-templates/saas-product/ailk-template.json +16 -0
  72. package/site-templates/saas-product/provenance.json +10 -0
  73. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
  74. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
  75. package/site-templates/simple-service/ailk-template.json +10 -0
  76. package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
  77. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
  78. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
  79. package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
  80. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
  81. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
  82. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
  83. package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
  84. package/site-templates/simple-service/provenance.json +4 -0
  85. package/templates/.claude/skills/README.md +16 -25
  86. package/templates/.env.example +102 -12
  87. package/templates/.github/workflows/ci.yml +48 -0
  88. package/templates/README.md +3 -1
  89. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
  90. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
  91. package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
  92. package/templates/apps/api/.env.example +13 -0
  93. package/templates/apps/api/CLAUDE.md +29 -2
  94. package/templates/apps/api/eslint.config.mjs +69 -0
  95. package/templates/apps/api/jest.config.cjs +3 -0
  96. package/templates/apps/api/package.json +7 -2
  97. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
  98. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +89 -9
  99. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +167 -0
  100. package/templates/apps/api/src/__tests__/server.test.ts +471 -0
  101. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
  102. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +10 -1
  103. package/templates/apps/api/src/config/index.ts +62 -0
  104. package/templates/apps/api/src/config/modules.ts +7 -3
  105. package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -2
  106. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
  107. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
  108. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
  109. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
  110. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
  111. package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
  112. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
  113. package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
  114. package/templates/apps/api/src/lib/content-adapter.ts +57 -31
  115. package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
  116. package/templates/apps/api/src/lib/error-serializer.ts +23 -3
  117. package/templates/apps/api/src/lib/outbound-http.ts +7 -2
  118. package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
  119. package/templates/apps/api/src/lib/site.ts +208 -9
  120. package/templates/apps/api/src/lib/stripe.ts +66 -0
  121. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +521 -0
  122. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +150 -0
  123. package/templates/apps/api/src/middleware/default-tenant.ts +136 -0
  124. package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
  125. package/templates/apps/api/src/openapi/spec.ts +543 -2
  126. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
  127. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
  128. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
  129. package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
  130. package/templates/apps/api/src/routes/auth/user.ts +61 -0
  131. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
  132. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
  133. package/templates/apps/api/src/routes/checkout/index.ts +34 -0
  134. package/templates/apps/api/src/routes/checkout/session.ts +139 -0
  135. package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
  136. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
  137. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
  138. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
  139. package/templates/apps/api/src/routes/content/get-page.ts +94 -12
  140. package/templates/apps/api/src/routes/content/index.ts +13 -73
  141. package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
  142. package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
  143. package/templates/apps/api/src/routes/content/search.ts +129 -0
  144. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
  145. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +171 -0
  146. package/templates/apps/api/src/routes/leads/index.ts +100 -13
  147. package/templates/apps/api/src/routes/leads/list.ts +104 -0
  148. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
  149. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
  150. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
  151. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
  152. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
  153. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
  154. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
  155. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
  156. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
  157. package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
  158. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
  159. package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
  160. package/templates/apps/api/src/routes/requests/index.ts +54 -0
  161. package/templates/apps/api/src/routes/webhooks/README.md +265 -0
  162. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
  163. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
  164. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
  165. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
  166. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
  167. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
  168. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
  169. package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
  170. package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
  171. package/templates/apps/api/src/server.ts +521 -55
  172. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
  173. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
  174. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
  175. package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
  176. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
  177. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
  178. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
  179. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +408 -0
  180. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
  181. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +329 -0
  182. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
  183. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
  184. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
  185. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
  186. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
  187. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
  188. package/templates/apps/api/src/services/abandon-email.ts +8 -2
  189. package/templates/apps/api/src/services/aeo-score.ts +49 -12
  190. package/templates/apps/api/src/services/audit-log.ts +668 -0
  191. package/templates/apps/api/src/services/consent.ts +18 -0
  192. package/templates/apps/api/src/services/default-tenant.ts +300 -0
  193. package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
  194. package/templates/apps/api/src/services/dunning-policy.ts +294 -0
  195. package/templates/apps/api/src/services/entitlements.ts +80 -0
  196. package/templates/apps/api/src/services/lead-promotion.ts +160 -0
  197. package/templates/apps/api/src/services/lead-routing.ts +161 -2
  198. package/templates/apps/api/src/services/leads-inbox.ts +284 -0
  199. package/templates/apps/api/src/services/listing-config.ts +27 -9
  200. package/templates/apps/api/src/services/owner-workspaces.ts +132 -0
  201. package/templates/apps/api/src/services/project-listings.ts +201 -6
  202. package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
  203. package/templates/apps/api/src/services/request-confirm.ts +646 -0
  204. package/templates/apps/api/src/services/revalidation.ts +173 -0
  205. package/templates/apps/api/src/services/verified-email.ts +71 -0
  206. package/templates/apps/api/src/vercel-handler.ts +75 -35
  207. package/templates/apps/mcp/.env.example +17 -0
  208. package/templates/apps/mcp/CLAUDE.md +58 -25
  209. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
  210. package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
  211. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
  212. package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
  213. package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
  214. package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
  215. package/templates/apps/mcp/__tests__/index.test.ts +39 -0
  216. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
  217. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
  218. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
  219. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
  220. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
  221. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
  222. package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
  223. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
  224. package/templates/apps/mcp/__tests__/parity.test.ts +12 -9
  225. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
  226. package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
  227. package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
  228. package/templates/apps/mcp/__tests__/server.test.ts +67 -14
  229. package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
  230. package/templates/apps/mcp/__tests__/tools/{scaffold.test.ts → create_project.test.ts} +23 -23
  231. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
  232. package/templates/apps/mcp/eslint.config.mjs +69 -0
  233. package/templates/apps/mcp/jest.config.cjs +13 -0
  234. package/templates/apps/mcp/package.json +8 -2
  235. package/templates/apps/mcp/src/abuse/index.ts +159 -24
  236. package/templates/apps/mcp/src/config/modules.ts +10 -5
  237. package/templates/apps/mcp/src/http-transport.ts +102 -29
  238. package/templates/apps/mcp/src/index.ts +73 -11
  239. package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
  240. package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
  241. package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
  242. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
  243. package/templates/apps/mcp/src/lib/prisma.ts +39 -0
  244. package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
  245. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
  246. package/templates/apps/mcp/src/server.ts +61 -37
  247. package/templates/apps/mcp/src/tools/audit.ts +26 -2
  248. package/templates/apps/mcp/src/tools/{scaffold.ts → create_project.ts} +3 -3
  249. package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
  250. package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
  251. package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
  252. package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
  253. package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
  254. package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
  255. package/templates/apps/mcp/src/tools/index.ts +9 -7
  256. package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
  257. package/templates/apps/mcp/src/tools/purchase.ts +128 -45
  258. package/templates/apps/mcp/src/tools/read_content.ts +20 -6
  259. package/templates/apps/mcp/src/tools/search_content.ts +11 -4
  260. package/templates/apps/web/.env.example +74 -8
  261. package/templates/apps/web/CLAUDE.md +46 -0
  262. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
  263. package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
  264. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
  265. package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
  266. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
  267. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
  268. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
  269. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
  270. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
  271. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
  272. package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
  273. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
  274. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
  275. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
  276. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
  277. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
  278. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
  279. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
  280. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
  281. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +67 -14
  282. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -2
  283. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +181 -0
  284. package/templates/apps/web/app/[locale]/(authed)/leads/components/LeadsTable.tsx +137 -0
  285. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +111 -0
  286. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +1 -1
  287. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +111 -0
  288. package/templates/apps/web/app/[locale]/[slug]/page.tsx +65 -0
  289. package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
  290. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +1 -1
  291. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +1 -1
  292. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +6 -5
  293. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +1 -1
  294. package/templates/apps/web/app/[locale]/layout.tsx +308 -65
  295. package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
  296. package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
  297. package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
  298. package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
  299. package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
  300. package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
  301. package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
  302. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
  303. package/templates/apps/web/app/api/revalidate/route.ts +194 -0
  304. package/templates/apps/web/auth.ts +407 -8
  305. package/templates/apps/web/components/google-analytics.tsx +18 -1
  306. package/templates/apps/web/eslint.config.mjs +69 -0
  307. package/templates/apps/web/jest.config.cjs +15 -19
  308. package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
  309. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
  310. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
  311. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +50 -6
  312. package/templates/apps/web/lib/auth-email.ts +318 -0
  313. package/templates/apps/web/lib/auth-env.ts +85 -0
  314. package/templates/apps/web/lib/auth-routes.ts +55 -0
  315. package/templates/apps/web/lib/meeting-slugs.ts +53 -0
  316. package/templates/apps/web/lib/nav-config.ts +27 -7
  317. package/templates/apps/web/lib/server-api.ts +1 -1
  318. package/templates/apps/web/lib/site-chrome.ts +29 -0
  319. package/templates/apps/web/lib/telemetry-scrub.ts +50 -0
  320. package/templates/apps/web/messages/en.json +216 -2
  321. package/templates/apps/web/next.config.mjs +20 -0
  322. package/templates/apps/web/package.json +8 -3
  323. package/templates/apps/web/sentry.client.config.ts +131 -0
  324. package/templates/apps/web/sentry.edge.config.ts +72 -0
  325. package/templates/apps/web/sentry.server.config.ts +72 -0
  326. package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
  327. package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
  328. package/templates/apps/web/src/lib/routes.ts +13 -18
  329. package/templates/brand_assets/README.md +24 -0
  330. package/templates/content/en/AboutPage/about.mdx +160 -15
  331. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
  332. package/templates/content/en/ContactPage/contact.mdx +90 -13
  333. package/templates/content/en/FAQPage/faq.mdx +62 -22
  334. package/templates/content/en/HomePage/home.mdx +244 -20
  335. package/templates/content/en/LandingPage/blog.mdx +83 -0
  336. package/templates/content/en/LandingPage/features.mdx +103 -0
  337. package/templates/content/en/LandingPage/how-it-works.mdx +143 -0
  338. package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
  339. package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
  340. package/templates/database/CHANGELOG.md +149 -0
  341. package/templates/database/README.md +10 -3
  342. package/templates/database/__tests__/appointment.db.test.ts +106 -0
  343. package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
  344. package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
  345. package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
  346. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
  347. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
  348. package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
  349. package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
  350. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
  351. package/templates/database/__tests__/seed.test.ts +1 -1
  352. package/templates/database/__tests__/tenancy.test.ts +141 -0
  353. package/templates/database/config/schema.prisma +79 -2
  354. package/templates/database/content/schema.prisma +87 -11
  355. package/templates/database/eslint.config.mjs +69 -0
  356. package/templates/database/inbox/schema.prisma +452 -9
  357. package/templates/database/jest.config.cjs +5 -3
  358. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
  359. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
  360. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
  361. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
  362. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
  363. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
  364. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
  365. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
  366. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
  367. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
  368. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
  369. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
  370. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
  371. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
  372. package/templates/database/package.json +7 -3
  373. package/templates/database/seed.ts +1 -1
  374. package/templates/database/tenancy.ts +86 -1
  375. package/templates/database/tsconfig.json +1 -0
  376. package/templates/package.json +1 -14
  377. package/versions.json +52 -0
  378. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -103
  379. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
  380. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
  381. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
  382. package/templates/apps/api/src/routes/content/create.ts +0 -115
  383. package/templates/apps/api/src/routes/content/delete.ts +0 -82
  384. package/templates/apps/api/src/routes/content/update.ts +0 -112
  385. package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
  386. package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
  387. package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
  388. package/templates/apps/mcp/src/tools/create_page.ts +0 -96
  389. package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
  390. package/templates/apps/mcp/src/tools/update_page.ts +0 -92
  391. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
  392. /package/site-templates/{marketing-site-free → _base}/content/en/PrivacyPage/privacy.mdx +0 -0
  393. /package/site-templates/{marketing-site-free → _base}/content/en/TermsPage/terms.mdx +0 -0
  394. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
  395. /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
  396. /package/templates/apps/web/{src/components → components}/SiteNav.tsx +0 -0
@@ -0,0 +1,210 @@
1
+ -- #6314 — ProjectListing gains `captured_by_flow_id`: the listicle-kind
2
+ -- FlowDefinition that CAPTURED the application, so a magnet's own
3
+ -- Applications list can be scoped to its own submissions rather than to the
4
+ -- whole workspace. Nothing constrains a workspace to one listicle magnet
5
+ -- ("flow_definitions" carries a (site_id, slug) unique and no
6
+ -- (site_id, kind) one), so workspace-only scoping would silently conflate
7
+ -- two listicle magnets' applications.
8
+ --
9
+ -- A NEW MIGRATION DIRECTORY, not an amendment of the already-merged
10
+ -- 20260922120000_tenancy_fence (spec D8). #6254 AC-1's one-migration-per-
11
+ -- milestone rule names #6314 by number in that migration's own header, so
12
+ -- this is a DEPARTURE taken deliberately and with the operator's
13
+ -- confirmation (2026-09-20), not a gap that rule failed to cover. Two
14
+ -- reasons, in order of weight:
15
+ -- 1. Prisma records a per-migration CHECKSUM. `prisma migrate deploy`
16
+ -- refuses a migration whose file no longer matches `_prisma_migrations`,
17
+ -- so a database that already applied the fence directory would never
18
+ -- receive this column or its index while the new code assumes both.
19
+ -- "Editing it in place is a silent no-op" is true of the SQL; it is not
20
+ -- true of the checksum.
21
+ -- 2. apps/api/CLAUDE.md: "Never edit a shipped migration — add a new one."
22
+ -- Where a spec's convenience and an in-repo rule disagree, the rule wins.
23
+ --
24
+ -- PURELY ADDITIVE: one nullable column, one index. NO UPDATE, NO backfill,
25
+ -- and NO foreign key.
26
+ --
27
+ -- NO BACKFILL, deliberately (spec § 3.5 / AC-3, operator ruling 2026-09-19).
28
+ -- No existing row carries evidence naming the magnet that captured it — no
29
+ -- referrer, no session link, no slug. A backfill would have to guess "the
30
+ -- workspace's only listicle magnet", which is exactly the conflation this
31
+ -- column exists to remove, made permanent and unauditable. Every row written
32
+ -- before this migration therefore reads NULL for the life of the row:
33
+ -- unattributed, excluded from every per-magnet list (NULL never equals a
34
+ -- magnet id, so the exclusion is a property of the query, not a filter that
35
+ -- can be forgotten), and UNCHANGED in the workspace Inbox — no operator loses
36
+ -- access to any application. This is the same discipline the founder-name
37
+ -- parts (#5407), the address parts (#5279) and `owner_user_id` (#5295) each
38
+ -- follow: a value that can only be guessed must not be asserted.
39
+ --
40
+ -- NO foreign key, deliberately (spec § 3.2, operator decision 2026-09-20,
41
+ -- which withdrew the 2026-09-19 ruling's "on-delete set-null" clause). Both
42
+ -- models live in the `inbox` namespace, so ADR 0009 invariant #1 (which bans
43
+ -- CROSS-namespace relations) does not reach this column — permission was
44
+ -- never the question. The convention is: of the 24 foreign-key constraints
45
+ -- under database/migrations/**, every one targets the `auth` namespace
46
+ -- ("users"); the intra-inbox columns listing_id, lead_id, grant_id and
47
+ -- flow_session_id carry none. The composite (workspace_id, …) shape was
48
+ -- itself offered and declined on "listing_csv_sends" the day before this
49
+ -- spec's pin. Integrity rests on the server-side resolve in
50
+ -- `startProjectListingDraft` (site_id + workspace_id + kind = 'listicle')
51
+ -- and on the read carrying the injected workspace_id alongside the magnet id
52
+ -- — so a dangling or foreign value yields no rows, never another workspace's.
53
+ -- Nothing deletes a flow_definitions row either (routes/magnets D5: "No
54
+ -- DELETE"), so ON DELETE SET NULL would have described no live code path.
55
+ --
56
+ -- RE-RUNNABLE: IF NOT EXISTS on both DDL statements, the census temp table is
57
+ -- dropped-if-exists before it is built, and post-assertion 2 is skipped on a
58
+ -- re-apply (it proves this file grew no backfill, which only the first
59
+ -- application can show — see its own comment). So re-applying this directory
60
+ -- against a database that already has the column is inert rather than an abort
61
+ -- that would wedge the ledger.
62
+ --
63
+ -- ORDER-SAFE: the index references "workspace_id", added by
64
+ -- 20260919140000_inbox_workspace_key and made NOT NULL by
65
+ -- 20260922120000_tenancy_fence — both of which this migration sorts strictly
66
+ -- after. The guard below is therefore a formality in the normal case, kept
67
+ -- for the reason 20260920130000_lead_workspace_source_index keeps one: IF NOT
68
+ -- EXISTS guards the INDEX's own name, not the column it references, so an
69
+ -- unguarded CREATE INDEX would abort inside prisma migrate's per-migration
70
+ -- transaction and wedge the ledger for the whole deployment if that earlier
71
+ -- migration were ever skipped, renamed, or rolled back on a given database.
72
+ --
73
+ -- CREATE INDEX is NOT CONCURRENT, and cannot be: CONCURRENTLY may not run
74
+ -- inside prisma migrate's per-migration transaction. It therefore takes a
75
+ -- lock on "project_listings" for its duration — the same accepted risk
76
+ -- 20260920130000_lead_workspace_source_index names. A deployment with a large
77
+ -- project_listings table should create this index out-of-band with
78
+ -- CONCURRENTLY first; the IF NOT EXISTS guard then makes the statement a
79
+ -- no-op.
80
+ --
81
+ -- Rollback:
82
+ -- DROP INDEX "project_listings_workspace_id_captured_by_flow_id_idx";
83
+ -- ALTER TABLE "project_listings" DROP COLUMN "captured_by_flow_id";
84
+ -- Safe at any point: the column is nullable and unconstrained, and no row's
85
+ -- correctness depends on it beyond per-magnet attribution.
86
+
87
+ -- A census, so the post-assertions below can prove nothing moved.
88
+ --
89
+ -- `column_preexisted` is captured HERE, before the ALTER, and it is what keeps
90
+ -- assertion 2 honest on a RE-APPLY. That assertion proves a property of THIS
91
+ -- FILE (it performs no backfill), which is only observable on the application
92
+ -- that first adds the column: once the application has written even one real
93
+ -- attribution, "no row is attributed" is false for reasons this migration had
94
+ -- nothing to do with. Asserting it unconditionally would abort inside prisma
95
+ -- migrate's per-migration transaction and wedge the ledger for every later
96
+ -- migration on that database — on a restored snapshot, a `migrate resolve
97
+ -- --rolled-back` redeploy, or a branch of production.
98
+ -- `pg_temp.`-qualified: DROP TABLE does not distinguish temporary from
99
+ -- permanent relations, and at this point the session holds no temp table, so
100
+ -- an unqualified name would resolve through search_path to `public` and drop
101
+ -- a same-named PERMANENT table — inside the transaction prisma commits.
102
+ DROP TABLE IF EXISTS pg_temp."_6314_capture_census";
103
+ CREATE TEMP TABLE "_6314_capture_census" AS
104
+ SELECT
105
+ count(*) AS p,
106
+ EXISTS (
107
+ SELECT 1 FROM information_schema.columns
108
+ -- `table_schema = current_schema()` is load-bearing, not tidiness:
109
+ -- `information_schema.columns` spans EVERY schema in the database, so
110
+ -- without it the probe answers "does any table anywhere named
111
+ -- project_listings carry this column" rather than "does the table this
112
+ -- migration is about to alter carry it". A pre-migration snapshot in a
113
+ -- side schema (`CREATE TABLE backup.project_listings AS SELECT ...`, a
114
+ -- `pg_dump -n` restore) would then make a genuine FIRST apply take the
115
+ -- skip branch below and wave a real backfill through unchecked — the
116
+ -- exact AC-8 property assertion 2 exists to prove.
117
+ WHERE table_schema = current_schema()
118
+ AND table_name = 'project_listings'
119
+ AND column_name = 'captured_by_flow_id'
120
+ ) AS column_preexisted
121
+ FROM "project_listings";
122
+
123
+ -- AlterTable — the capturing magnet's id. Nullable and unconstrained, so a
124
+ -- row predating this migration reads NULL and nothing here can reject it.
125
+ ALTER TABLE "project_listings" ADD COLUMN IF NOT EXISTS "captured_by_flow_id" TEXT;
126
+
127
+ -- CreateIndex — the read is workspace_id = $1 AND captured_by_flow_id = $2,
128
+ -- ordered by created_at desc. A two-column (workspace_id, captured_by_flow_id)
129
+ -- index serves it directly. With no foreign key there is no cascade, so no
130
+ -- referencing-side index is owed beyond this one.
131
+ DO $$ BEGIN
132
+ IF EXISTS (
133
+ SELECT 1 FROM information_schema.columns
134
+ -- `table_schema = current_schema()` for the same reason the census probe
135
+ -- above carries it, and the failure direction here is WORSE. A decoy
136
+ -- `project_listings` in another schema makes this EXISTS spuriously true;
137
+ -- the CREATE INDEX below then runs against the table search_path actually
138
+ -- resolves, hits `column "workspace_id" does not exist`, and aborts the
139
+ -- whole migration transaction — a wedged ledger, where the census probe's
140
+ -- equivalent merely skips an assertion. The guard must ask about the table
141
+ -- the statement it protects will really touch.
142
+ WHERE table_schema = current_schema()
143
+ AND table_name = 'project_listings'
144
+ AND column_name = 'workspace_id'
145
+ ) THEN
146
+ CREATE INDEX IF NOT EXISTS "project_listings_workspace_id_captured_by_flow_id_idx"
147
+ ON "project_listings"("workspace_id", "captured_by_flow_id");
148
+ ELSE
149
+ RAISE NOTICE '#6314: project_listings.workspace_id absent — index skipped. 20260919140000_inbox_workspace_key has not run against this database; the per-magnet Applications read cannot be served efficiently until it does.';
150
+ END IF;
151
+ END $$;
152
+
153
+ -- Post-assertions, in 20260922120000_tenancy_fence's own idiom.
154
+ DO $$
155
+ DECLARE
156
+ before_p bigint;
157
+ after_p bigint;
158
+ attributed bigint;
159
+ preexisted boolean;
160
+ BEGIN
161
+ SELECT p, column_preexisted INTO before_p, preexisted FROM "_6314_capture_census";
162
+ SELECT count(*) INTO after_p FROM "project_listings";
163
+
164
+ -- 1. Nothing LOST. Deliberately `<`, not `<>`.
165
+ --
166
+ -- An equality test here has no true-positive mode and one guaranteed
167
+ -- false-positive mode, which is the worst shape a guard can have. No
168
+ -- statement in this file modifies a row of "project_listings" (the whole
169
+ -- file is five statements; read them), so the two counts can only diverge
170
+ -- through a write committed by ANOTHER session between them. The census
171
+ -- takes ACCESS SHARE; the ALTER takes ACCESS EXCLUSIVE, and until it does,
172
+ -- the table stays open to writers. "project_listings" is written by an
173
+ -- ANONYMOUS, unauthenticated route, so on a live deploy one application
174
+ -- started mid-migration aborts the migration — and the retry meets the
175
+ -- same race. That is a wedged ledger caused by the system working
176
+ -- normally.
177
+ --
178
+ -- `<` keeps the property the assertion is actually for — this migration
179
+ -- must never destroy a row — while letting a concurrent INSERT through.
180
+ -- A DELETE or TRUNCATE creeping into this file still trips it.
181
+ IF after_p < before_p THEN
182
+ RAISE EXCEPTION
183
+ '#6314 post-assertion: project_listings LOST row(s) across a purely additive migration (% -> %)',
184
+ before_p, after_p;
185
+ END IF;
186
+
187
+ -- 2. Nothing guessed (AC-8): every PRE-EXISTING row reads NULL. This
188
+ -- migration performs no UPDATE, so a non-NULL row on the application that
189
+ -- first adds the column would mean the file grew a backfill the ruling
190
+ -- refused.
191
+ --
192
+ -- Skipped when the column already existed: on a re-apply, attributed rows
193
+ -- are the APPLICATION's own writes, not a backfill, and failing on them
194
+ -- would wedge the migration ledger (see the census above). The assertion
195
+ -- still fires where it can mean anything — the first application, which is
196
+ -- the only run that can introduce a backfill.
197
+ IF preexisted THEN
198
+ RAISE NOTICE '#6314 post-assertion 2 skipped: captured_by_flow_id already existed, so this is a re-apply and any attributed row is an application write rather than a backfill.';
199
+ ELSE
200
+ SELECT count(*) INTO attributed
201
+ FROM "project_listings" WHERE "captured_by_flow_id" IS NOT NULL;
202
+ IF attributed <> 0 THEN
203
+ RAISE EXCEPTION
204
+ '#6314 post-assertion: % project_listings row(s) carry a captured_by_flow_id after a migration that performs no UPDATE — existing rows must stay unattributed (no backfill by guess)',
205
+ attributed;
206
+ END IF;
207
+ END IF;
208
+ END $$;
209
+
210
+ DROP TABLE pg_temp."_6314_capture_census";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@working-theory/database",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -50,7 +50,7 @@
50
50
  }
51
51
  },
52
52
  "scripts": {
53
- "lint": "tsx ../scripts/lint-package.ts",
53
+ "lint": "eslint .",
54
54
  "build": "tsc -b tsconfig.build.json && tsc -b tsconfig.tenancy.json && tsc -b tsconfig.flows.json",
55
55
  "db:generate": "prisma generate && pnpm run build",
56
56
  "db:migrate": "prisma migrate dev",
@@ -78,6 +78,10 @@
78
78
  "prisma": "^7.8.0",
79
79
  "ts-jest": "^29.4.11",
80
80
  "tsx": "^4.22.4",
81
- "typescript": "^6.0.3"
81
+ "typescript": "^6.0.3",
82
+ "eslint": "^10.6.0",
83
+ "@eslint/js": "^10.0.1",
84
+ "globals": "^17.7.0",
85
+ "typescript-eslint": "^8.62.1"
82
86
  }
83
87
  }
@@ -104,7 +104,7 @@ async function main(): Promise<void> {
104
104
  // Dynamic imports: the generated client + adapter are constructed only when
105
105
  // this module actually runs as the seed entrypoint — never at import time
106
106
  // (spec §5.2 Quality Constraints), matching the DI note on seed-test.ts's
107
- // sibling and the lazy-require pattern in apps-paid/workspace/auth.ts.
107
+ // sibling and the lazy-require pattern in apps-paid/admin/auth.ts.
108
108
  const [{ PrismaClient: RealPrismaClient }, { PrismaPg }] = await Promise.all([
109
109
  import("./generated/client/client.js"),
110
110
  import("@prisma/adapter-pg"),
@@ -22,7 +22,7 @@
22
22
  // that predicate, never a hardcoded literal. They carry the branded `Id<E>`
23
23
  // types so consumers get compile-time entity safety (ADR-0031 rule 5).
24
24
 
25
- import type { Id } from "@working-theory/ids";
25
+ import { isSentinel, type Id } from "@working-theory/ids";
26
26
 
27
27
  /** Well-known default Organization identity — a fixed reserved-range sentinel. */
28
28
  export const DEFAULT_ORG_ID =
@@ -43,3 +43,88 @@ export const DEFAULT_WORKSPACE_SLUG = "default";
43
43
  */
44
44
  export const OPERATOR_MEMBERSHIP_ID =
45
45
  "member_00000000-0000-7000-8000-000000000001" as Id<"membership">;
46
+
47
+ /**
48
+ * The minimal structural client the one-workspace predicate needs — no
49
+ * `@prisma/client` import, so this file stays a leaf `tsconfig.tenancy.json`
50
+ * can build without the generated client (#5876 spec D2). `apps/api` and
51
+ * `apps/mcp` are both `database`-importers (`architecture.yaml`
52
+ * `may_import_from`), and `database/**` is already wholesale-excluded from
53
+ * the tenant-scope `forbidden_pattern` — the only file this predicate could
54
+ * live in without widening that security allowlist.
55
+ */
56
+ export interface WorkspacePredicateClient {
57
+ workspace: {
58
+ count(): Promise<number>;
59
+ findUnique(args: {
60
+ where: { id: string };
61
+ }): Promise<{ organizationId: string } | null>;
62
+ };
63
+ }
64
+
65
+ /**
66
+ * The one-workspace deployment predicate (#5876 spec D2), shared by every
67
+ * caller across the package boundary: `workspace.count() !== 1 ⇒ null`, then
68
+ * the sole workspace must be the well-known default AND hang off a sentinel
69
+ * Organization, or the deployment is not unambiguously single-tenant.
70
+ *
71
+ * Returns the **row** (`{ id, organizationId }`), not a projection — a
72
+ * caller that needs `organizationId` (as `resolveDefaultTenant` in
73
+ * `apps/api/src/services/default-tenant.ts` does) would otherwise have to
74
+ * run a second `findUnique`, which is the second-predicate drift NFR-7
75
+ * forbids. A caller that only needs the id projects `.id` itself.
76
+ *
77
+ * Carries no authentication — it answers "is this deployment one-workspace,
78
+ * and which workspace is it," nothing about the caller.
79
+ */
80
+ export async function resolveDefaultWorkspaceRow(
81
+ prisma: WorkspacePredicateClient,
82
+ ): Promise<{ id: Id<"workspace">; organizationId: string } | null> {
83
+ const workspaceCount = await prisma.workspace.count();
84
+ if (workspaceCount !== 1) {
85
+ return null;
86
+ }
87
+ const workspace = await prisma.workspace.findUnique({
88
+ where: { id: DEFAULT_WORKSPACE_ID },
89
+ });
90
+ // The default workspace must hang off the sentinel default Organization
91
+ // (ADR-0031 rule 7): keyed on the reserved-range predicate, not a literal.
92
+ if (!workspace || !isSentinel(workspace.organizationId)) {
93
+ return null;
94
+ }
95
+ return { id: DEFAULT_WORKSPACE_ID, organizationId: workspace.organizationId };
96
+ }
97
+
98
+ /**
99
+ * The sentinel-EXISTENCE predicate (#6155 spec D2 rule 2), a SIBLING of
100
+ * {@link resolveDefaultWorkspaceRow}, not a replacement for it: it answers
101
+ * "does the well-known default workspace exist", never "is this deployment
102
+ * unambiguously single-tenant" — so it carries NO `workspace.count()` gate.
103
+ *
104
+ * The two questions coincide on a one-workspace deployment and diverge on a
105
+ * correctly-configured MULTI-workspace one: there,
106
+ * `resolveDefaultWorkspaceRow`'s count gate returns `null` even though the
107
+ * seeded sentinel workspace is present and resolvable. The site-alias
108
+ * resolver (`apps/api/src/lib/site.ts`) needs the sentinel-existence answer
109
+ * — the literal alias `"default"` names the seeded sentinel workspace on
110
+ * EVERY database, single- or multi-tenant (`database/seed.ts` upserts it
111
+ * unconditionally) — so it calls this function, not
112
+ * `resolveDefaultWorkspaceRow`.
113
+ *
114
+ * `resolveDefaultWorkspaceRow` is deliberately NOT modified or loosened:
115
+ * `resolveDefaultTenant` (`apps/api/src/services/default-tenant.ts`) depends
116
+ * on its single-tenancy meaning, so widening it would silently widen
117
+ * single-tenant detection for callers that are correct today. One function,
118
+ * one question.
119
+ */
120
+ export async function resolveSentinelWorkspaceRow(
121
+ prisma: WorkspacePredicateClient,
122
+ ): Promise<{ id: Id<"workspace">; organizationId: string } | null> {
123
+ const workspace = await prisma.workspace.findUnique({
124
+ where: { id: DEFAULT_WORKSPACE_ID },
125
+ });
126
+ if (!workspace || !isSentinel(workspace.organizationId)) {
127
+ return null;
128
+ }
129
+ return { id: DEFAULT_WORKSPACE_ID, organizationId: workspace.organizationId };
130
+ }
@@ -10,6 +10,7 @@
10
10
  "seed.ts",
11
11
  "seed-test.ts",
12
12
  "tenancy.ts",
13
+ "__tests__/fixtures/appointment-create-probe.ts",
13
14
  "flows/**/*.ts"
14
15
  ]
15
16
  }
@@ -45,10 +45,8 @@
45
45
  "globals": "^17.7.0",
46
46
  "gray-matter": "^4.0.3",
47
47
  "http-server": "^14.1.1",
48
- "husky": "^9.1.7",
49
48
  "jest": "^30.4.2",
50
49
  "jiti": "^2.4.2",
51
- "lint-staged": "^15.3.0",
52
50
  "prettier": "^3.9.4",
53
51
  "ts-declaration-location": "^1.0.7",
54
52
  "ts-jest": "^29.4.11",
@@ -60,18 +58,7 @@
60
58
  "yaml": "^2.9.0",
61
59
  "zod": "^4.2.1",
62
60
  "zod-to-ts": "^2.1.0",
63
- "@working-theory/cli": "^0.23.0"
64
- },
65
- "lint-staged": {
66
- "*.{ts,tsx}": [
67
- "eslint --fix --cache"
68
- ],
69
- "*.{js,jsx,json,md,mdx,yaml,yml,css,scss,html}": [
70
- "prettier --write"
71
- ],
72
- "{apps,packages,database}/**": [
73
- "bash -c 'pnpm architecture'"
74
- ]
61
+ "@working-theory/cli": "workspace:*"
75
62
  },
76
63
  "packageManager": "pnpm@10.34.5+sha512.a4ee05f2f73658255bd6a89859c065a45c28a57daefae2c893a168ee2b73168c37b91e83e57ea67654ad03f03031746430e8bce38e362e042605fb8abc80192e",
77
64
  "engines": {
package/versions.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "@working-theory/admin-ui": "0.25.0",
3
+ "@working-theory/aeo": "0.25.0",
4
+ "@working-theory/agent-context": "0.25.0",
5
+ "@working-theory/agent-contract": "0.1.0",
6
+ "@working-theory/agent-memory": "0.0.0",
7
+ "@working-theory/ai": "0.1.0",
8
+ "@working-theory/analytics": "0.25.0",
9
+ "@working-theory/api-client": "0.25.0",
10
+ "@working-theory/cda-filesystem": "0.0.0",
11
+ "@working-theory/cli": "0.25.0",
12
+ "@working-theory/common": "0.25.0",
13
+ "@working-theory/consent": "0.1.0",
14
+ "@working-theory/content-adapters": "0.25.0",
15
+ "@working-theory/content-projection": "0.25.0",
16
+ "@working-theory/email": "0.25.0",
17
+ "@working-theory/env": "0.0.0",
18
+ "@working-theory/eslint-config": "0.25.0",
19
+ "@working-theory/fumadocs-schema": "0.25.0",
20
+ "@working-theory/ids": "0.8.0",
21
+ "@working-theory/jsonld": "0.25.0",
22
+ "@working-theory/license": "0.25.0",
23
+ "@working-theory/metadata": "0.25.0",
24
+ "@working-theory/observability": "0.25.0",
25
+ "@working-theory/page-renderer": "0.25.0",
26
+ "@working-theory/schema": "0.25.0",
27
+ "@working-theory/schema-pack-registry": "0.25.0",
28
+ "@working-theory/schema-validator": "0.25.0",
29
+ "@working-theory/sections-registry": "0.25.0",
30
+ "@working-theory/site-config": "0.25.0",
31
+ "@working-theory/stripe-client": "0.25.0",
32
+ "@working-theory/templates": "0.25.0",
33
+ "@working-theory/theme": "0.25.0",
34
+ "@working-theory/ui": "0.25.0",
35
+ "@working-theory/validation": "0.25.0",
36
+ "@working-theory/web-i18n": "0.25.0",
37
+ "@working-theory/billing-client": "0.0.0",
38
+ "@working-theory/branding": "0.0.0",
39
+ "@working-theory/license-client": "0.0.0",
40
+ "@working-theory/license-manifest": "0.0.0",
41
+ "@working-theory/marketing-api-client": "0.0.0",
42
+ "@working-theory/partner-session": "0.0.0",
43
+ "@working-theory/preview-protocol": "0.0.0",
44
+ "@working-theory/ui-commerce": "0.23.2",
45
+ "@working-theory/ui-docs-blog": "0.23.2",
46
+ "@working-theory/ui-scheduling": "0.23.2",
47
+ "@working-theory/ui-signal-collectors": "0.24.0",
48
+ "@working-theory/web": "0.0.0",
49
+ "@working-theory/api": "1.0.0",
50
+ "@working-theory/mcp": "0.0.0",
51
+ "@working-theory/database": "0.25.0"
52
+ }
@@ -1,103 +0,0 @@
1
- ---
2
- type: "Pricing"
3
- slug: "pricing"
4
- title: "Pricing"
5
- description: "Replace this with one sentence naming how Your Company charges — the unit, the range, and what moves the number. It is the description answer engines quote."
6
- sections:
7
- [
8
- {
9
- "id": "hero",
10
- "type": "hero",
11
- "data":
12
- {
13
- "headline": "Pricing",
14
- "subheadline": "Replace this with the one thing a buyer most needs to know about what they will pay.",
15
- },
16
- },
17
- {
18
- "id": "plans",
19
- "type": "pricing",
20
- "data":
21
- {
22
- "headline": "Replace these plans with your own",
23
- "description": "Three placeholder plans. Rename them, set real prices, and list what each one actually includes. Delete any plan you do not offer — an empty tier is worse than one fewer.",
24
- "plans":
25
- [
26
- {
27
- "name": "Plan one",
28
- "description": "Replace this with who this plan is for.",
29
- "price": { "custom": "Your price" },
30
- "features":
31
- [
32
- "Replace this line with something the plan includes.",
33
- "One line per included thing.",
34
- "Name the limit, not just the feature.",
35
- ],
36
- "cta": { "label": "Get in touch", "href": "/contact" },
37
- },
38
- {
39
- "name": "Plan two",
40
- "description": "Replace this with who this plan is for.",
41
- "price": { "custom": "Your price" },
42
- "features":
43
- [
44
- "Everything in plan one.",
45
- "Replace this line with what the step up buys.",
46
- "Say the number when there is one.",
47
- ],
48
- "cta": { "label": "Get in touch", "href": "/contact" },
49
- "highlighted": true,
50
- },
51
- {
52
- "name": "Plan three",
53
- "description": "Replace this with who this plan is for.",
54
- "price": { "custom": "Let's talk" },
55
- "features":
56
- [
57
- "Everything in plan two.",
58
- "Replace this line with the work you quote per engagement.",
59
- "Scope, not adjectives.",
60
- ],
61
- "cta":
62
- {
63
- "label": "Book a call",
64
- "href": "/contact",
65
- "variant": "outline",
66
- },
67
- },
68
- ],
69
- },
70
- },
71
- {
72
- "id": "pricing-faq",
73
- "type": "faq",
74
- "data":
75
- {
76
- "headline": "Pricing questions",
77
- "items":
78
- [
79
- {
80
- "question": "Replace this with the pricing question you are actually asked.",
81
- "answer": "Answer it in the first sentence, with the number in it. A pricing answer that opens with context is the one buyers stop reading.",
82
- },
83
- {
84
- "question": "What happens when the scope changes?",
85
- "answer": "Replace this with how you handle a change in scope — who decides, how it is priced, and when the new price starts. Say it plainly here and you will not have to say it awkwardly later.",
86
- },
87
- ],
88
- },
89
- },
90
- {
91
- "id": "cta",
92
- "type": "cta",
93
- "data":
94
- {
95
- "headline": "Not sure which plan fits?",
96
- "description": "Replace this with an invitation to the conversation you actually want to have.",
97
- "primaryCta": { "label": "Get in touch", "href": "/contact" },
98
- },
99
- },
100
- ]
101
- ---
102
-
103
- {/* Rendered from frontmatter `sections[]`. Seed content — replace the plan names, prices and features above with your own before you launch. */}