create-ailk 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (599) hide show
  1. package/README.md +42 -20
  2. package/component-catalog.json +3 -3
  3. package/component-catalog.md +3 -3
  4. package/dist/add-module.js +4 -2
  5. package/dist/add-route.d.ts +12 -1
  6. package/dist/add-route.js +31 -3
  7. package/dist/cli.js +12 -7
  8. package/dist/derive-architecture.d.ts +3 -4
  9. package/dist/derive-architecture.js +65 -29
  10. package/dist/lib/design-checks.d.ts +9 -4
  11. package/dist/lib/design-checks.js +13 -7
  12. package/dist/lib/extract-module-bundle.js +29 -2
  13. package/dist/lib/fetch-template-snapshot.d.ts +1 -1
  14. package/dist/lib/fetch-template-snapshot.js +7 -3
  15. package/dist/lib/import-graph.d.ts +1 -1
  16. package/dist/lib/import-graph.js +4 -4
  17. package/dist/lib/module-license-gate.d.ts +12 -1
  18. package/dist/lib/module-license-gate.js +34 -12
  19. package/dist/module-architecture.d.ts +20 -3
  20. package/dist/module-architecture.js +117 -550
  21. package/dist/paid-sections.d.ts +25 -1
  22. package/dist/paid-sections.js +88 -3
  23. package/dist/surfaces.js +10 -2
  24. package/dist/template-record.d.ts +14 -4
  25. package/dist/template-record.js +23 -5
  26. package/dist/topology.d.ts +16 -3
  27. package/dist/topology.js +31 -19
  28. package/dist/update.d.ts +10 -1
  29. package/dist/update.js +540 -16
  30. package/package.json +3 -3
  31. package/release-scope.json +1 -1
  32. package/templates/.claude/cloudparity-repo-hooks.json +5 -0
  33. package/templates/.claude/hooks/template-guard.sh +137 -0
  34. package/templates/.claude/rules/extending.md +106 -0
  35. package/templates/.claude/rules/loop-contract.md +2 -2
  36. package/templates/.claude/settings.json +11 -0
  37. package/templates/.env.example +5 -5
  38. package/templates/.github/workflows/ci.yml +7 -4
  39. package/templates/CLAUDE.md +11 -1
  40. package/templates/CONVENTIONS.md +8 -10
  41. package/templates/README.md +14 -13
  42. package/templates/apps/api/.env.example +2 -2
  43. package/templates/apps/api/CLAUDE.md +102 -76
  44. package/templates/apps/api/jest.config.cjs +15 -5
  45. package/templates/apps/api/package.json +16 -20
  46. package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
  47. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
  48. package/templates/apps/api/src/index.ts +1 -1
  49. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
  50. package/templates/apps/api/src/modules/commerce.ts +10 -0
  51. package/templates/apps/api/src/modules/content-write.ts +10 -0
  52. package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
  53. package/templates/apps/api/src/modules/scheduling.ts +10 -0
  54. package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
  55. package/templates/apps/api/src/modules/workspace.ts +10 -0
  56. package/templates/apps/api/src/server.ts +117 -1882
  57. package/templates/apps/api/vercel.json +2 -2
  58. package/templates/apps/mcp/CLAUDE.md +67 -20
  59. package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
  60. package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
  61. package/templates/apps/mcp/jest.config.cjs +6 -1
  62. package/templates/apps/mcp/package.json +5 -13
  63. package/templates/apps/mcp/src/app.ts +8 -7
  64. package/templates/apps/mcp/src/index.ts +6 -34
  65. package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
  66. package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
  67. package/templates/apps/mcp/src/site.ts +12 -0
  68. package/templates/apps/web/.env.example +1 -1
  69. package/templates/apps/web/CLAUDE.md +10 -3
  70. package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
  71. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
  72. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
  73. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
  74. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
  75. package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
  76. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
  77. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
  78. package/templates/apps/web/app/api/revalidate/route.ts +3 -3
  79. package/templates/apps/web/auth.ts +2 -2
  80. package/templates/apps/web/jest.config.cjs +1 -0
  81. package/templates/apps/web/lib/auth-email.ts +1 -1
  82. package/templates/apps/web/lib/relay.ts +2 -2
  83. package/templates/apps/web/lib/release-scope.ts +47 -0
  84. package/templates/apps/web/lib/server-api.ts +1 -1
  85. package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
  86. package/templates/customizations.yaml +37 -0
  87. package/templates/database/prisma.config.ts +10 -14
  88. package/templates/database/seed-test.ts +2 -2
  89. package/templates/database/seed.ts +5 -5
  90. package/templates/package.json +12 -6
  91. package/templates/pnpm-workspace.yaml +0 -1
  92. package/templates/project.yaml +16 -8
  93. package/templates/tools/jest/reporters.cjs +65 -0
  94. package/templates/tools/lint-architecture.ts +553 -7
  95. package/templates/tsconfig.json +1 -2
  96. package/versions.json +48 -35
  97. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
  98. package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
  99. package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
  100. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
  101. package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
  102. package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
  103. package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
  104. package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
  105. package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
  106. package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
  107. package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
  108. package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
  109. package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
  110. package/templates/apps/api/src/bin/spokes.ts +0 -198
  111. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
  112. package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
  113. package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
  114. package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
  115. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
  116. package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
  117. package/templates/apps/api/src/config/index.ts +0 -596
  118. package/templates/apps/api/src/config/modules.ts +0 -83
  119. package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
  120. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
  121. package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
  122. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
  123. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
  124. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
  125. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
  126. package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
  127. package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
  128. package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
  129. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
  130. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
  131. package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
  132. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
  133. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
  134. package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
  135. package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
  136. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
  137. package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
  138. package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
  139. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
  140. package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
  141. package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
  142. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
  143. package/templates/apps/api/src/lib/agent-store.ts +0 -613
  144. package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
  145. package/templates/apps/api/src/lib/consent-store.ts +0 -363
  146. package/templates/apps/api/src/lib/content-adapter.ts +0 -112
  147. package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
  148. package/templates/apps/api/src/lib/error-serializer.ts +0 -164
  149. package/templates/apps/api/src/lib/errors.ts +0 -95
  150. package/templates/apps/api/src/lib/file-fallback.ts +0 -87
  151. package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
  152. package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
  153. package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
  154. package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
  155. package/templates/apps/api/src/lib/license-key-store.ts +0 -45
  156. package/templates/apps/api/src/lib/locales.ts +0 -201
  157. package/templates/apps/api/src/lib/outbound-http.ts +0 -127
  158. package/templates/apps/api/src/lib/prisma.ts +0 -21
  159. package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
  160. package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
  161. package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
  162. package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
  163. package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
  164. package/templates/apps/api/src/lib/site.ts +0 -257
  165. package/templates/apps/api/src/lib/slice-load.ts +0 -90
  166. package/templates/apps/api/src/lib/spoke-store.ts +0 -180
  167. package/templates/apps/api/src/lib/stripe.ts +0 -321
  168. package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
  169. package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
  170. package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
  171. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
  172. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
  173. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
  174. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
  175. package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
  176. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
  177. package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
  178. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
  179. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
  180. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
  181. package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
  182. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
  183. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
  184. package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
  185. package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
  186. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
  187. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
  188. package/templates/apps/api/src/middleware/auth.ts +0 -864
  189. package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
  190. package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
  191. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
  192. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
  193. package/templates/apps/api/src/openapi/index.ts +0 -18
  194. package/templates/apps/api/src/openapi/spec.ts +0 -2148
  195. package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
  196. package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
  197. package/templates/apps/api/src/routes/aeo/index.ts +0 -107
  198. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
  199. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
  200. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
  201. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
  202. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
  203. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
  204. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
  205. package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
  206. package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
  207. package/templates/apps/api/src/routes/auth/user.ts +0 -305
  208. package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
  209. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
  210. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
  211. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
  212. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
  213. package/templates/apps/api/src/routes/checkout/index.ts +0 -34
  214. package/templates/apps/api/src/routes/checkout/session.ts +0 -171
  215. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
  216. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
  217. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
  218. package/templates/apps/api/src/routes/consent/account.ts +0 -268
  219. package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
  220. package/templates/apps/api/src/routes/consent/index.ts +0 -26
  221. package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
  222. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
  223. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
  224. package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
  225. package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
  226. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
  227. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
  228. package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
  229. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
  230. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
  231. package/templates/apps/api/src/routes/content/get-page.ts +0 -183
  232. package/templates/apps/api/src/routes/content/index.ts +0 -49
  233. package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
  234. package/templates/apps/api/src/routes/content/list-types.ts +0 -19
  235. package/templates/apps/api/src/routes/content/offerings.ts +0 -184
  236. package/templates/apps/api/src/routes/content/pricing.ts +0 -176
  237. package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
  238. package/templates/apps/api/src/routes/content/search.ts +0 -100
  239. package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
  240. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
  241. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
  242. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
  243. package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
  244. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
  245. package/templates/apps/api/src/routes/leads/index.ts +0 -639
  246. package/templates/apps/api/src/routes/leads/list.ts +0 -104
  247. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
  248. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
  249. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
  250. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
  251. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
  252. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
  253. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
  254. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
  255. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
  256. package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
  257. package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
  258. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
  259. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
  260. package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
  261. package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
  262. package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
  263. package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
  264. package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
  265. package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
  266. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
  267. package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
  268. package/templates/apps/api/src/routes/requests/index.ts +0 -54
  269. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
  270. package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
  271. package/templates/apps/api/src/routes/webhooks/README.md +0 -398
  272. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
  273. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
  274. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
  275. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
  276. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
  277. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
  278. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
  279. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
  280. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
  281. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
  282. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
  283. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
  284. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
  285. package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
  286. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
  287. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
  288. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
  289. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
  290. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
  291. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
  292. package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
  293. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
  294. package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
  295. package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
  296. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
  297. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
  298. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
  299. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
  300. package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
  301. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
  302. package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
  303. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
  304. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
  305. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
  306. package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
  307. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
  308. package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
  309. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
  310. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
  311. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
  312. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
  313. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
  314. package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
  315. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
  316. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
  317. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
  318. package/templates/apps/api/src/services/abandon-email.ts +0 -336
  319. package/templates/apps/api/src/services/aeo-score.ts +0 -325
  320. package/templates/apps/api/src/services/answer-distributions.ts +0 -138
  321. package/templates/apps/api/src/services/audit-log.ts +0 -961
  322. package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
  323. package/templates/apps/api/src/services/consent.ts +0 -834
  324. package/templates/apps/api/src/services/default-tenant.ts +0 -416
  325. package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
  326. package/templates/apps/api/src/services/dunning-policy.ts +0 -294
  327. package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
  328. package/templates/apps/api/src/services/entitlements.ts +0 -338
  329. package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
  330. package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
  331. package/templates/apps/api/src/services/lead-promotion.ts +0 -160
  332. package/templates/apps/api/src/services/lead-routing.ts +0 -1372
  333. package/templates/apps/api/src/services/leads-inbox.ts +0 -312
  334. package/templates/apps/api/src/services/listing-config.ts +0 -178
  335. package/templates/apps/api/src/services/magnet-completion.ts +0 -242
  336. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
  337. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
  338. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
  339. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
  340. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
  341. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
  342. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
  343. package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
  344. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
  345. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
  346. package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
  347. package/templates/apps/api/src/services/offerings/index.ts +0 -614
  348. package/templates/apps/api/src/services/offerings/source.ts +0 -140
  349. package/templates/apps/api/src/services/offerings/store.ts +0 -150
  350. package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
  351. package/templates/apps/api/src/services/person.ts +0 -1211
  352. package/templates/apps/api/src/services/plan-offerings.ts +0 -125
  353. package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
  354. package/templates/apps/api/src/services/project-listings.ts +0 -2085
  355. package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
  356. package/templates/apps/api/src/services/request-confirm.ts +0 -698
  357. package/templates/apps/api/src/services/revalidation.ts +0 -235
  358. package/templates/apps/api/src/services/slack-notify.ts +0 -291
  359. package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
  360. package/templates/apps/api/src/services/usage-credits.ts +0 -340
  361. package/templates/apps/api/src/services/verified-email.ts +0 -71
  362. package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
  363. package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
  364. package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
  365. package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
  366. package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
  367. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
  368. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
  369. package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
  370. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
  371. package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
  372. package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
  373. package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
  374. package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
  375. package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
  376. package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
  377. package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
  378. package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
  379. package/templates/apps/mcp/__tests__/index.test.ts +0 -195
  380. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
  381. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
  382. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
  383. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
  384. package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
  385. package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
  386. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
  387. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
  388. package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
  389. package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
  390. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
  391. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
  392. package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
  393. package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
  394. package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
  395. package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
  396. package/templates/apps/mcp/__tests__/server.test.ts +0 -388
  397. package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
  398. package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
  399. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
  400. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
  401. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
  402. package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
  403. package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
  404. package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
  405. package/templates/apps/mcp/src/abuse/index.ts +0 -349
  406. package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
  407. package/templates/apps/mcp/src/boot.ts +0 -167
  408. package/templates/apps/mcp/src/config/modules.ts +0 -43
  409. package/templates/apps/mcp/src/http-transport.ts +0 -564
  410. package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
  411. package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
  412. package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
  413. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
  414. package/templates/apps/mcp/src/lib/errors.ts +0 -61
  415. package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
  416. package/templates/apps/mcp/src/lib/locales.ts +0 -171
  417. package/templates/apps/mcp/src/lib/prisma.ts +0 -39
  418. package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
  419. package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
  420. package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
  421. package/templates/apps/mcp/src/lib/site-config.ts +0 -53
  422. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
  423. package/templates/apps/mcp/src/request-context.ts +0 -52
  424. package/templates/apps/mcp/src/server.ts +0 -343
  425. package/templates/apps/mcp/src/serverless-app.ts +0 -88
  426. package/templates/apps/mcp/src/tools/audit.ts +0 -127
  427. package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
  428. package/templates/apps/mcp/src/tools/create_project.ts +0 -134
  429. package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
  430. package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
  431. package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
  432. package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
  433. package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
  434. package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
  435. package/templates/apps/mcp/src/tools/index.ts +0 -138
  436. package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
  437. package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
  438. package/templates/apps/mcp/src/tools/proposals.ts +0 -27
  439. package/templates/apps/mcp/src/tools/purchase.ts +0 -205
  440. package/templates/apps/mcp/src/tools/read_content.ts +0 -81
  441. package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
  442. package/templates/apps/mcp/src/tools/search_content.ts +0 -63
  443. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
  444. package/templates/apps/mcp/src/workspace-server.ts +0 -224
  445. package/templates/database/CHANGELOG.md +0 -981
  446. package/templates/database/LICENSE +0 -202
  447. package/templates/database/NOTICE +0 -2
  448. package/templates/database/README.md +0 -243
  449. package/templates/database/__tests__/agents.db.test.ts +0 -320
  450. package/templates/database/__tests__/appointment.db.test.ts +0 -106
  451. package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
  452. package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
  453. package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
  454. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
  455. package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
  456. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
  457. package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
  458. package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
  459. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
  460. package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
  461. package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
  462. package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
  463. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
  464. package/templates/database/__tests__/flows-presets.test.ts +0 -173
  465. package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
  466. package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
  467. package/templates/database/__tests__/member-area.db.test.ts +0 -257
  468. package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
  469. package/templates/database/__tests__/member-contract.db.test.ts +0 -413
  470. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
  471. package/templates/database/__tests__/offerings-migration.test.ts +0 -197
  472. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
  473. package/templates/database/__tests__/offerings.db.test.ts +0 -303
  474. package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
  475. package/templates/database/__tests__/person-contact.db.test.ts +0 -563
  476. package/templates/database/__tests__/person-notes.db.test.ts +0 -249
  477. package/templates/database/__tests__/person.db.test.ts +0 -586
  478. package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
  479. package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
  480. package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
  481. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
  482. package/templates/database/__tests__/seed.test.ts +0 -272
  483. package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
  484. package/templates/database/__tests__/tenancy.test.ts +0 -141
  485. package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
  486. package/templates/database/__tests__/user-cascade.test.ts +0 -140
  487. package/templates/database/_gitignore +0 -8
  488. package/templates/database/auth/.gitkeep +0 -0
  489. package/templates/database/auth/schema.prisma +0 -641
  490. package/templates/database/config/.gitkeep +0 -0
  491. package/templates/database/config/schema.prisma +0 -441
  492. package/templates/database/content/.gitkeep +0 -0
  493. package/templates/database/content/schema.prisma +0 -339
  494. package/templates/database/eslint.config.mjs +0 -69
  495. package/templates/database/flows/listing-diagnostic.ts +0 -182
  496. package/templates/database/flows/presets.ts +0 -315
  497. package/templates/database/inbox/.gitkeep +0 -0
  498. package/templates/database/inbox/README.md +0 -3
  499. package/templates/database/inbox/__tests__/schema.test.ts +0 -44
  500. package/templates/database/inbox/schema.prisma +0 -1447
  501. package/templates/database/jest.config.cjs +0 -36
  502. package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
  503. package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
  504. package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
  505. package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
  506. package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
  507. package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
  508. package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
  509. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
  510. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
  511. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
  512. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
  513. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
  514. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
  515. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
  516. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
  517. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
  518. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
  519. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
  520. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
  521. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
  522. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
  523. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
  524. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
  525. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
  526. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
  527. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
  528. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
  529. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
  530. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
  531. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
  532. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
  533. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
  534. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
  535. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
  536. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
  537. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
  538. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
  539. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
  540. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
  541. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
  542. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
  543. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
  544. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
  545. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
  546. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
  547. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
  548. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
  549. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
  550. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
  551. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
  552. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
  553. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
  554. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
  555. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
  556. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
  557. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
  558. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
  559. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
  560. package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
  561. package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
  562. package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
  563. package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
  564. package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
  565. package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
  566. package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
  567. package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
  568. package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
  569. package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
  570. package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
  571. package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
  572. package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
  573. package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
  574. package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
  575. package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
  576. package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
  577. package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
  578. package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
  579. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
  580. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
  581. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
  582. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
  583. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
  584. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
  585. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
  586. package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
  587. package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
  588. package/templates/database/migrations/migration_lock.toml +0 -3
  589. package/templates/database/ops/.gitkeep +0 -0
  590. package/templates/database/ops/schema.prisma +0 -43
  591. package/templates/database/package.json +0 -89
  592. package/templates/database/schema.prisma +0 -71
  593. package/templates/database/scripts/db-generate-locked.sh +0 -80
  594. package/templates/database/scripts/person-backfill.sql +0 -132
  595. package/templates/database/tenancy.ts +0 -183
  596. package/templates/database/tsconfig.build.json +0 -16
  597. package/templates/database/tsconfig.flows.json +0 -15
  598. package/templates/database/tsconfig.json +0 -16
  599. package/templates/database/tsconfig.tenancy.json +0 -14
@@ -1,181 +0,0 @@
1
- -- #6155 — FlowDefinition and FlowSession gain a workspace isolation key.
2
- --
3
- -- The ownership brief (docs/internal/decisions/workspace-owns-data-surfaces-render.md)
4
- -- and AD-2 (docs/internal/decisions/flow-engine-admin-surface.md): a workspace
5
- -- owns data, and the flow engine's tenant-scope fence grows to cover it.
6
- -- `siteId` stays a legacy alias for the public path (unchanged); `workspaceId`
7
- -- is the isolation key every authenticated read/write will go through
8
- -- (apps/api/src/lib/tenant-db.ts's new `flows`/`flowSessions` accessors).
9
- --
10
- -- NULLABLE, no default (spec D3) — a row whose `siteId` matches no workspace
11
- -- is left UNSET rather than guessed, and stays invisible to every accessor
12
- -- rather than visible to the wrong tenant (the same "fails empty, never
13
- -- wide" posture apps/api/src/services/leads-inbox.ts already takes).
14
- --
15
- -- RE-RUNNABLE (NFR3): `ADD COLUMN IF NOT EXISTS` / `CREATE INDEX IF NOT
16
- -- EXISTS` cover the column and index. Postgres has no
17
- -- `ADD CONSTRAINT IF NOT EXISTS`, so each format CHECK is wrapped in its own
18
- -- existence-guarded `DO $$` block — the stated exception to NFR3's "in the
19
- -- house style of 20260918120000_asset_metadata_columns" note: that
20
- -- precedent (and 20260713192429_prefixed_uuid_entity_ids's own CHECK
21
- -- additions) rely on `prisma migrate`'s per-migration transaction and
22
- -- ledger, not on idempotent DDL — the guard below is this migration's own
23
- -- addition.
24
- --
25
- -- Rollback: DROP INDEX "flow_definitions_workspace_id_idx",
26
- -- "flow_sessions_workspace_id_idx"; ALTER TABLE "flow_definitions" DROP
27
- -- CONSTRAINT "flow_definitions_workspace_id_format", DROP COLUMN
28
- -- "workspace_id"; ALTER TABLE "flow_sessions" DROP CONSTRAINT
29
- -- "flow_sessions_workspace_id_format", DROP COLUMN "workspace_id". Safe
30
- -- unconditionally at this pin — nothing reads the column until #6156+.
31
-
32
- -- The census the post-assertions compare against. ON COMMIT DROP: `prisma
33
- -- migrate` runs each migration in one transaction, so this disappears with it.
34
- CREATE TEMP TABLE "_6155_flow_census" ON COMMIT DROP AS
35
- SELECT (SELECT count(*) FROM "flow_definitions") AS d,
36
- (SELECT count(*) FROM "flow_sessions") AS s;
37
-
38
- -- AlterTable
39
- ALTER TABLE "flow_definitions" ADD COLUMN IF NOT EXISTS "workspace_id" TEXT;
40
- ALTER TABLE "flow_sessions" ADD COLUMN IF NOT EXISTS "workspace_id" TEXT;
41
-
42
- -- Format CHECKs — NULL-permitting, in the exact shape of the CHECKs
43
- -- 20260713192429_prefixed_uuid_entity_ids/migration.sql:170-197 already
44
- -- applies to every other `workspace_id` column. Existence-guarded because
45
- -- Postgres has no `ADD CONSTRAINT IF NOT EXISTS`.
46
- DO $$ BEGIN
47
- IF NOT EXISTS (
48
- SELECT 1 FROM pg_constraint WHERE conname = 'flow_definitions_workspace_id_format'
49
- ) THEN
50
- ALTER TABLE "flow_definitions" ADD CONSTRAINT "flow_definitions_workspace_id_format"
51
- CHECK ("workspace_id" IS NULL OR "workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
52
- END IF;
53
- END $$;
54
-
55
- DO $$ BEGIN
56
- IF NOT EXISTS (
57
- SELECT 1 FROM pg_constraint WHERE conname = 'flow_sessions_workspace_id_format'
58
- ) THEN
59
- ALTER TABLE "flow_sessions" ADD CONSTRAINT "flow_sessions_workspace_id_format"
60
- CHECK ("workspace_id" IS NULL OR "workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
61
- END IF;
62
- END $$;
63
-
64
- -- CreateIndex
65
- CREATE INDEX IF NOT EXISTS "flow_definitions_workspace_id_idx" ON "flow_definitions"("workspace_id");
66
- CREATE INDEX IF NOT EXISTS "flow_sessions_workspace_id_idx" ON "flow_sessions"("workspace_id");
67
-
68
- -- ── Backfill (spec D2) ──────────────────────────────────────────────────────
69
- -- Both spellings of the alias, one statement per table. The literal alias
70
- -- "default" resolves to the seeded sentinel workspace
71
- -- (database/tenancy.ts DEFAULT_WORKSPACE_ID); every other alias resolves
72
- -- only by exact workspaces.id equality. An inner join to "workspaces" means a
73
- -- row whose alias resolves to nothing is simply not updated and stays NULL
74
- -- — the correct, stated behavior (D3), not an error.
75
- --
76
- -- The sentinel id is written as a literal because SQL cannot import
77
- -- database/tenancy.ts; database/__tests__/tenancy.test.ts pins the same
78
- -- literal against the exported constant so the two cannot drift.
79
- UPDATE "flow_definitions" f
80
- SET "workspace_id" = w."id"
81
- FROM "workspaces" w
82
- WHERE f."workspace_id" IS NULL
83
- AND w."id" = CASE WHEN f."site_id" = 'default'
84
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
85
- ELSE f."site_id" END;
86
-
87
- UPDATE "flow_sessions" f
88
- SET "workspace_id" = w."id"
89
- FROM "workspaces" w
90
- WHERE f."workspace_id" IS NULL
91
- AND w."id" = CASE WHEN f."site_id" = 'default'
92
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
93
- ELSE f."site_id" END;
94
-
95
- -- ── POST-ASSERTIONS ─────────────────────────────────────────────────────────
96
- -- AC-1's own enforcement: nothing lost, nothing guessed, nothing missed that
97
- -- was resolvable. The fourth check is a NOTICE, not an exception — an
98
- -- unpinned/unseeded deployment leaving rows NULL is the expected, documented
99
- -- case (spec § 1.3), never an error this migration should refuse to run over.
100
- DO $$
101
- DECLARE
102
- before_d bigint;
103
- before_s bigint;
104
- after_d bigint;
105
- after_s bigint;
106
- guessed bigint;
107
- missed bigint;
108
- null_d bigint;
109
- null_s bigint;
110
- BEGIN
111
- SELECT d, s INTO before_d, before_s FROM "_6155_flow_census";
112
- SELECT count(*) INTO after_d FROM "flow_definitions";
113
- SELECT count(*) INTO after_s FROM "flow_sessions";
114
-
115
- -- 1. Nothing lost: row counts unchanged on both tables.
116
- IF before_d <> after_d OR before_s <> after_s THEN
117
- RAISE EXCEPTION
118
- '#6155 post-assertion: row count changed across a purely additive migration (flow_definitions % -> %, flow_sessions % -> %)',
119
- before_d, after_d, before_s, after_s;
120
- END IF;
121
-
122
- -- 2. Nothing guessed: no row's workspace_id was set to a value absent
123
- -- from "workspaces" (the inner-join backfill above cannot produce this
124
- -- on its own, but a re-run against a database missing the sentinel row
125
- -- must never leave a dangling reference).
126
- SELECT count(*) INTO guessed FROM "flow_definitions" f
127
- WHERE f."workspace_id" IS NOT NULL
128
- AND NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."id" = f."workspace_id");
129
- IF guessed <> 0 THEN
130
- RAISE EXCEPTION
131
- '#6155 post-assertion: % flow_definitions row(s) carry a workspace_id absent from workspaces',
132
- guessed;
133
- END IF;
134
- SELECT count(*) INTO guessed FROM "flow_sessions" f
135
- WHERE f."workspace_id" IS NOT NULL
136
- AND NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."id" = f."workspace_id");
137
- IF guessed <> 0 THEN
138
- RAISE EXCEPTION
139
- '#6155 post-assertion: % flow_sessions row(s) carry a workspace_id absent from workspaces',
140
- guessed;
141
- END IF;
142
-
143
- -- 3. Nothing missed: no row whose site_id names a workspace that actually
144
- -- exists came out of the backfill with workspace_id still NULL.
145
- SELECT count(*) INTO missed FROM "flow_definitions" f
146
- WHERE f."workspace_id" IS NULL
147
- AND EXISTS (
148
- SELECT 1 FROM "workspaces" w
149
- WHERE w."id" = CASE WHEN f."site_id" = 'default'
150
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
151
- ELSE f."site_id" END
152
- );
153
- IF missed <> 0 THEN
154
- RAISE EXCEPTION
155
- '#6155 post-assertion: % flow_definitions row(s) had a resolvable site_id but came out of the backfill NULL',
156
- missed;
157
- END IF;
158
- SELECT count(*) INTO missed FROM "flow_sessions" f
159
- WHERE f."workspace_id" IS NULL
160
- AND EXISTS (
161
- SELECT 1 FROM "workspaces" w
162
- WHERE w."id" = CASE WHEN f."site_id" = 'default'
163
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
164
- ELSE f."site_id" END
165
- );
166
- IF missed <> 0 THEN
167
- RAISE EXCEPTION
168
- '#6155 post-assertion: % flow_sessions row(s) had a resolvable site_id but came out of the backfill NULL',
169
- missed;
170
- END IF;
171
-
172
- -- 4. A NOTICE, never an exception: an unpinned/unseeded deployment leaving
173
- -- rows NULL is the expected, documented case (spec § 1.3).
174
- SELECT count(*) INTO null_d FROM "flow_definitions" WHERE "workspace_id" IS NULL;
175
- SELECT count(*) INTO null_s FROM "flow_sessions" WHERE "workspace_id" IS NULL;
176
- IF null_d <> 0 OR null_s <> 0 THEN
177
- RAISE NOTICE
178
- '#6155: % flow_definitions row(s) and % flow_sessions row(s) left workspace_id unset — their site_id did not resolve to a workspace this database holds',
179
- null_d, null_s;
180
- END IF;
181
- END $$;
@@ -1,172 +0,0 @@
1
- -- #6156 — S5b: a magnet is a flow row. FlowDefinition gains four
2
- -- operator-facing columns — kind, title, landingPage, status — spec D1.
3
- --
4
- -- STRICTLY ADDITIVE. Nothing dropped, renamed or relaxed; @@unique
5
- -- ([siteId, slug]), the `flow_`+UUIDv7 id default, and `listing_id` are all
6
- -- untouched. The visitor-facing `config` column is byte-identical across
7
- -- this migration (FR4, asserted below) — these four columns are how the
8
- -- operator MANAGES a magnet, never what a visitor's session SEES
9
- -- (AD-3, spec D1/§1.2).
10
- --
11
- -- `status` is backfilled 'live' (every row that exists today is already
12
- -- serving — D1's availability half) and carries NO column default (D1's
13
- -- publication-safety half, SEC-08): a DEFAULT 'live' would make
14
- -- PUBLICATION the thing that happens when a future writer says nothing,
15
- -- which is the exact asymmetry the brief's "a magnet created from the
16
- -- Admin opens as a draft" commitment forbids. So `status` is added
17
- -- nullable, backfilled, then SET NOT NULL with no default — every future
18
- -- writer must name a status or fail to compile.
19
- --
20
- -- `title` is NOT NULL with no column default, by the same three-step, for
21
- -- the positive reason: a nameless magnet must never enter the table
22
- -- silently. The slug backfills every existing row's title — an honest
23
- -- placeholder the operator renames.
24
- --
25
- -- `kind` KEEPS its column default ('contact') — unlike `status`, a
26
- -- mis-classified magnet is a mislabel in a list, not a publication or
27
- -- security control (D1). The backfill below reclassifies the rows D3's
28
- -- structural rule actually matches; every other row stays the default.
29
- --
30
- -- `landing_page` is nullable with NO backfill (D1): a magnet has THREE
31
- -- public addresses (the indexed landing route, the walk route one segment
32
- -- deeper, and the results route), so seeding a derived guess would freeze
33
- -- a route as if it were an operator decision. NULL means "no
34
- -- operator-declared landing page; the canonical route serves it" — true of
35
- -- every existing row.
36
- --
37
- -- `flow_definitions_status_domain` (D2) is a closed two-value CHECK,
38
- -- deliberately narrower than the precedent this namespace otherwise follows
39
- -- (WaitlistExperiment.status, FlowCheckout.status, ProjectListing.status —
40
- -- all plain String with no CHECK): `status` is what makes D10's
41
- -- deny-list gate in `resolveSession` SAFE rather than merely convenient —
42
- -- an unstorable unknown value means "not 'draft'" and "'live'" are the same
43
- -- set at the database level. No CHECK on `kind`: its vocabulary is six
44
- -- values and growing (Epic #6025), so a CHECK there would make every new
45
- -- kind a migration that RELAXES a constraint.
46
- --
47
- -- Statement order (D9) is fixed and load-bearing, not incidental: the
48
- -- `ADD COLUMN` runs FIRST, acquiring ACCESS EXCLUSIVE on `flow_definitions`
49
- -- before the census is taken, so no concurrent committed write (a listing
50
- -- approval via `upsertFlowDefinition`, a waitlist-register materialize) can
51
- -- land between the census and the assertions and falsely trip them. The
52
- -- whole sequence blocks every read AND write to `flow_definitions` for its
53
- -- duration — acceptable only because the table is tiny today (§1.4's
54
- -- census: four seeded presets plus two generated families); a later unit
55
- -- that grows this table materially owes a re-read of this decision rather
56
- -- than an inherited assumption.
57
- --
58
- -- Not re-runnable by `IF NOT EXISTS` guards (unlike #6155's
59
- -- 20260919120000_flow_workspace_key, whose NFR3 needed it for a reason
60
- -- specific to that migration): `prisma migrate`'s per-migration transaction
61
- -- + the `_prisma_migrations` ledger already make re-runnability a
62
- -- non-requirement here.
63
- --
64
- -- Rollback: ALTER TABLE "flow_definitions" DROP COLUMN "kind", DROP COLUMN
65
- -- "title", DROP COLUMN "landing_page", DROP COLUMN "status". Safe
66
- -- unconditionally — dropping "status" drops "flow_definitions_status_domain"
67
- -- with it, the four columns carry no other constraint, no index and no
68
- -- relation, and nothing outside this diff references them.
69
-
70
- -- AlterTable — all four, one statement, taking ACCESS EXCLUSIVE before
71
- -- anything else in this transaction reads the table (D9).
72
- ALTER TABLE "flow_definitions"
73
- ADD COLUMN "kind" TEXT NOT NULL DEFAULT 'contact',
74
- ADD COLUMN "title" TEXT,
75
- ADD COLUMN "landing_page" TEXT,
76
- ADD COLUMN "status" TEXT;
77
-
78
- -- The census the post-assertions compare against — taken SECOND, under the
79
- -- lock the ADD COLUMN above already holds, so it is a stable baseline for
80
- -- every assertion below (D9 / SEC-07). ON COMMIT DROP: `prisma migrate`
81
- -- runs each migration in one transaction, so this disappears with it.
82
- CREATE TEMP TABLE "_6156_flow_def_census" ON COMMIT DROP AS
83
- SELECT count(*) AS n,
84
- md5(string_agg("config"::text, '' ORDER BY "id")) AS config_hash
85
- FROM "flow_definitions";
86
-
87
- -- ── Backfill (D3 / D1) ───────────────────────────────────────────────────
88
- -- 1. `kind` — D3's structural rule, first match wins. Every row not
89
- -- matched by a branch below keeps the column default written above.
90
- UPDATE "flow_definitions"
91
- SET "kind" = CASE
92
- WHEN "listing_id" IS NOT NULL THEN 'diagnostic'
93
- WHEN "config"->>'mode' = 'recommending' THEN 'diagnostic'
94
- WHEN "config" ? 'deliverables' THEN 'special-report'
95
- ELSE 'contact'
96
- END;
97
-
98
- -- 2. `title` = the slug (an honest placeholder); `status` = 'live' (every
99
- -- row that exists today is already serving — D1's availability half).
100
- UPDATE "flow_definitions"
101
- SET "title" = "slug",
102
- "status" = 'live';
103
-
104
- -- SET NOT NULL + the status domain CHECK — AFTER the backfill, so neither
105
- -- constraint is ever evaluated against a NULL this migration itself wrote.
106
- ALTER TABLE "flow_definitions" ALTER COLUMN "title" SET NOT NULL;
107
- ALTER TABLE "flow_definitions" ALTER COLUMN "status" SET NOT NULL;
108
- ALTER TABLE "flow_definitions"
109
- ADD CONSTRAINT "flow_definitions_status_domain"
110
- CHECK ("status" IN ('draft', 'live'));
111
-
112
- -- ── POST-ASSERTIONS (FR2/FR3/FR4 — AC-1) ────────────────────────────────
113
- -- Asserted against the rows the target database actually holds, not a
114
- -- fixture's. A raised exception here rolls the whole migration back
115
- -- (`prisma migrate`'s per-migration transaction).
116
- DO $$
117
- DECLARE
118
- before_n bigint;
119
- before_hash text;
120
- after_n bigint;
121
- after_hash text;
122
- null_cols bigint;
123
- wrong_kind bigint;
124
- BEGIN
125
- SELECT n, config_hash INTO before_n, before_hash FROM "_6156_flow_def_census";
126
- SELECT count(*), md5(string_agg("config"::text, '' ORDER BY "id"))
127
- INTO after_n, after_hash
128
- FROM "flow_definitions";
129
-
130
- -- 1. Nothing lost or gained: row count unchanged across a purely
131
- -- additive-plus-backfill migration.
132
- IF before_n <> after_n THEN
133
- RAISE EXCEPTION
134
- '#6156 post-assertion: flow_definitions row count changed (% -> %)',
135
- before_n, after_n;
136
- END IF;
137
-
138
- -- 2. `config` is byte-identical (FR4) — this migration writes NO column
139
- -- other than kind/title/landing_page/status.
140
- IF before_hash IS DISTINCT FROM after_hash THEN
141
- RAISE EXCEPTION
142
- '#6156 post-assertion: flow_definitions.config changed across a migration that must not touch it';
143
- END IF;
144
-
145
- -- 3. Every pre-existing row leaves with a non-NULL status/title/kind
146
- -- (landing_page is NOT asserted — D1: it is deliberately unbackfilled).
147
- SELECT count(*) INTO null_cols FROM "flow_definitions"
148
- WHERE "status" IS NULL OR "title" IS NULL OR "kind" IS NULL;
149
- IF null_cols <> 0 THEN
150
- RAISE EXCEPTION
151
- '#6156 post-assertion: % flow_definitions row(s) came out of the backfill with a NULL status/title/kind',
152
- null_cols;
153
- END IF;
154
-
155
- -- 4. Backfill rule correctness (D3) — every listing-projected row, and
156
- -- every recommending-mode row, classified 'diagnostic'.
157
- SELECT count(*) INTO wrong_kind FROM "flow_definitions"
158
- WHERE "listing_id" IS NOT NULL AND "kind" <> 'diagnostic';
159
- IF wrong_kind <> 0 THEN
160
- RAISE EXCEPTION
161
- '#6156 post-assertion: % flow_definitions row(s) with listing_id set did not classify as diagnostic',
162
- wrong_kind;
163
- END IF;
164
-
165
- SELECT count(*) INTO wrong_kind FROM "flow_definitions"
166
- WHERE "config"->>'mode' = 'recommending' AND "kind" <> 'diagnostic';
167
- IF wrong_kind <> 0 THEN
168
- RAISE EXCEPTION
169
- '#6156 post-assertion: % flow_definitions row(s) with config.mode = recommending did not classify as diagnostic',
170
- wrong_kind;
171
- END IF;
172
- END $$;
@@ -1,41 +0,0 @@
1
- -- #6183 (S9b) — the WorkspaceMeetings model: the operator's enabled-slug
2
- -- gate for meeting types (spec docs/specs/6183-s9b-enabled-meeting-slugs-live-spec.md
3
- -- D1, D11). A fourth workspace site-config blob, structurally identical to
4
- -- its three siblings (workspace_themes / workspace_site_identities /
5
- -- workspace_seos) — same `@unique(workspace_id)` key, same Json-column
6
- -- convention, validation happens at the API layer.
7
- --
8
- -- One new table, one unique index, in the house style of
9
- -- 20260918120000_add_appointment (CreateTable + CreateIndex immediately
10
- -- followed by the ADR-0031 rule-3 id-format completion, in ONE migration
11
- -- since the table is new and needs no retrofit). No `workspace_id_format`
12
- -- CHECK, matching that same precedent — the baseline migration's 12
13
- -- relation-free scope checks are pinned to it alone
14
- -- (packages/ids/src/__tests__/migration-sync.test.ts), and no per-entity
15
- -- migration since has added one.
16
- --
17
- -- No backfill, no constraint relaxed, no existing table touched.
18
- --
19
- -- Rollback: DROP TABLE "workspace_meetings". Safe unconditionally while
20
- -- nothing but #6183's own code reads it.
21
-
22
- -- CreateTable
23
- CREATE TABLE "workspace_meetings" (
24
- "id" TEXT NOT NULL DEFAULT ('meet_'::text || (uuidv7())::text),
25
- "workspace_id" TEXT NOT NULL,
26
- "data" JSONB NOT NULL,
27
- "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
28
- "updated_at" TIMESTAMPTZ NOT NULL,
29
-
30
- CONSTRAINT "workspace_meetings_pkey" PRIMARY KEY ("id")
31
- );
32
-
33
- -- CreateIndex
34
- CREATE UNIQUE INDEX "workspace_meetings_workspace_id_key" ON "workspace_meetings"("workspace_id");
35
-
36
- -- ADR-0031 rule 3 — workspace_meetings: meet_ (registered in
37
- -- packages/ids/src/registry.ts ENTITY_PREFIX.workspaceMeetings; this
38
- -- migration's path is added to migration-sync.test.ts's PK_CHECK_MIGRATIONS).
39
- ALTER TABLE "workspace_meetings" ALTER COLUMN "id" TYPE text COLLATE "C";
40
- ALTER TABLE "workspace_meetings" ALTER COLUMN "id" SET DEFAULT ('meet_'::text || (uuidv7())::text);
41
- ALTER TABLE "workspace_meetings" ADD CONSTRAINT "workspace_meetings_id_format" CHECK ("id" ~ '^meet_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
@@ -1,185 +0,0 @@
1
- -- #6203 — ProjectListing and Lead gain a workspace isolation key.
2
- --
3
- -- The ownership brief (docs/internal/decisions/workspace-owns-data-surfaces-render.md)
4
- -- and AD-4 (docs/internal/decisions/flow-engine-admin-surface.md): a workspace
5
- -- owns data, and the Inbox's tenant-scope fence grows to cover the two
6
- -- models it reads. `siteId` stays a legacy alias for the anonymous write
7
- -- paths (unchanged); `workspaceId` is the isolation key every Admin read
8
- -- will go through (apps/api/src/lib/tenant-db.ts's new
9
- -- `projectListings`/`leads` accessors). Mirrors
10
- -- database/migrations/20260919120000_flow_workspace_key step for step
11
- -- (#6155's precedent for FlowDefinition/FlowSession).
12
- --
13
- -- NULLABLE, no default (spec D2) — a row whose `siteId` matches no workspace
14
- -- is left UNSET rather than guessed, and stays invisible to every accessor
15
- -- rather than visible to the wrong tenant (the same "fails empty, never
16
- -- wide" posture apps/api/src/services/leads-inbox.ts already takes).
17
- --
18
- -- RE-RUNNABLE (NFR3): `ADD COLUMN IF NOT EXISTS` / `CREATE INDEX IF NOT
19
- -- EXISTS` cover the column and index. Postgres has no
20
- -- `ADD CONSTRAINT IF NOT EXISTS`, so each format CHECK is wrapped in its own
21
- -- existence-guarded `DO $$` block, exactly as the #6155 precedent does.
22
- --
23
- -- Rollback: DROP INDEX "project_listings_workspace_id_idx",
24
- -- "leads_workspace_id_idx"; ALTER TABLE "project_listings" DROP
25
- -- CONSTRAINT "project_listings_workspace_id_format", DROP COLUMN
26
- -- "workspace_id"; ALTER TABLE "leads" DROP CONSTRAINT
27
- -- "leads_workspace_id_format", DROP COLUMN "workspace_id". Safe
28
- -- unconditionally at this pin — nothing reads the column until #6204+.
29
-
30
- -- The census the post-assertions compare against. ON COMMIT DROP: `prisma
31
- -- migrate` runs each migration in one transaction, so this disappears with it.
32
- CREATE TEMP TABLE "_6203_inbox_census" ON COMMIT DROP AS
33
- SELECT (SELECT count(*) FROM "project_listings") AS p,
34
- (SELECT count(*) FROM "leads") AS l;
35
-
36
- -- AlterTable
37
- ALTER TABLE "project_listings" ADD COLUMN IF NOT EXISTS "workspace_id" TEXT;
38
- ALTER TABLE "leads" ADD COLUMN IF NOT EXISTS "workspace_id" TEXT;
39
-
40
- -- Format CHECKs — NULL-permitting, in the exact shape of the CHECKs
41
- -- 20260713192429_prefixed_uuid_entity_ids/migration.sql already applies to
42
- -- every other `workspace_id` column. Existence-guarded because Postgres has
43
- -- no `ADD CONSTRAINT IF NOT EXISTS`. This is what makes the Lead.siteId
44
- -- provenance gap (spec §1.3, §9 SEC-1) non-exploitable as an id-SHAPE
45
- -- attack: a site_id that is not a well-formed wksp_<uuidv7> cannot land in
46
- -- this column at all.
47
- DO $$ BEGIN
48
- IF NOT EXISTS (
49
- SELECT 1 FROM pg_constraint WHERE conname = 'project_listings_workspace_id_format'
50
- ) THEN
51
- ALTER TABLE "project_listings" ADD CONSTRAINT "project_listings_workspace_id_format"
52
- CHECK ("workspace_id" IS NULL OR "workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
53
- END IF;
54
- END $$;
55
-
56
- DO $$ BEGIN
57
- IF NOT EXISTS (
58
- SELECT 1 FROM pg_constraint WHERE conname = 'leads_workspace_id_format'
59
- ) THEN
60
- ALTER TABLE "leads" ADD CONSTRAINT "leads_workspace_id_format"
61
- CHECK ("workspace_id" IS NULL OR "workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
62
- END IF;
63
- END $$;
64
-
65
- -- CreateIndex
66
- CREATE INDEX IF NOT EXISTS "project_listings_workspace_id_idx" ON "project_listings"("workspace_id");
67
- CREATE INDEX IF NOT EXISTS "leads_workspace_id_idx" ON "leads"("workspace_id");
68
-
69
- -- ── Backfill (spec D2, §5.4) ────────────────────────────────────────────────
70
- -- Both spellings of the alias, one statement per table. The literal alias
71
- -- "default" resolves to the seeded sentinel workspace
72
- -- (database/tenancy.ts DEFAULT_WORKSPACE_ID); every other alias resolves
73
- -- only by exact workspaces.id equality. An inner join to "workspaces" means a
74
- -- row whose alias resolves to nothing is simply not updated and stays NULL
75
- -- — the correct, stated behavior (D2), not an error.
76
- --
77
- -- The sentinel id is a literal because SQL cannot import
78
- -- database/tenancy.ts. It is ADR 0031's reserved-range sentinel and changes
79
- -- only with that ADR — no test pins it against SQL (#6155's own migration
80
- -- carries the identical correction: the sibling tenancy.test.ts compares
81
- -- only the imported constant, not this SQL literal).
82
- UPDATE "project_listings" p
83
- SET "workspace_id" = w."id"
84
- FROM "workspaces" w
85
- WHERE p."workspace_id" IS NULL
86
- AND w."id" = CASE WHEN p."site_id" = 'default'
87
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
88
- ELSE p."site_id" END;
89
-
90
- UPDATE "leads" l
91
- SET "workspace_id" = w."id"
92
- FROM "workspaces" w
93
- WHERE l."workspace_id" IS NULL
94
- AND w."id" = CASE WHEN l."site_id" = 'default'
95
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
96
- ELSE l."site_id" END;
97
-
98
- -- ── POST-ASSERTIONS ─────────────────────────────────────────────────────────
99
- -- AC-1's own enforcement: nothing lost, nothing guessed, nothing missed that
100
- -- was resolvable. The fourth check is a NOTICE, not an exception — an
101
- -- unpinned/unseeded deployment leaving rows NULL is the expected, documented
102
- -- case (spec §1.9, NFR4), never an error this migration should refuse to
103
- -- run over.
104
- DO $$
105
- DECLARE
106
- before_p bigint;
107
- before_l bigint;
108
- after_p bigint;
109
- after_l bigint;
110
- guessed bigint;
111
- missed bigint;
112
- null_p bigint;
113
- null_l bigint;
114
- BEGIN
115
- SELECT p, l INTO before_p, before_l FROM "_6203_inbox_census";
116
- SELECT count(*) INTO after_p FROM "project_listings";
117
- SELECT count(*) INTO after_l FROM "leads";
118
-
119
- -- 1. Nothing lost: row counts unchanged on both tables.
120
- IF before_p <> after_p OR before_l <> after_l THEN
121
- RAISE EXCEPTION
122
- '#6203 post-assertion: row count changed across a purely additive migration (project_listings % -> %, leads % -> %)',
123
- before_p, after_p, before_l, after_l;
124
- END IF;
125
-
126
- -- 2. Nothing guessed: no row's workspace_id was set to a value absent
127
- -- from "workspaces" (the inner-join backfill above cannot produce this
128
- -- on its own, but a re-run against a database missing the sentinel row
129
- -- must never leave a dangling reference).
130
- SELECT count(*) INTO guessed FROM "project_listings" p
131
- WHERE p."workspace_id" IS NOT NULL
132
- AND NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."id" = p."workspace_id");
133
- IF guessed <> 0 THEN
134
- RAISE EXCEPTION
135
- '#6203 post-assertion: % project_listings row(s) carry a workspace_id absent from workspaces',
136
- guessed;
137
- END IF;
138
- SELECT count(*) INTO guessed FROM "leads" l
139
- WHERE l."workspace_id" IS NOT NULL
140
- AND NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."id" = l."workspace_id");
141
- IF guessed <> 0 THEN
142
- RAISE EXCEPTION
143
- '#6203 post-assertion: % leads row(s) carry a workspace_id absent from workspaces',
144
- guessed;
145
- END IF;
146
-
147
- -- 3. Nothing missed: no row whose site_id names a workspace that actually
148
- -- exists came out of the backfill with workspace_id still NULL.
149
- SELECT count(*) INTO missed FROM "project_listings" p
150
- WHERE p."workspace_id" IS NULL
151
- AND EXISTS (
152
- SELECT 1 FROM "workspaces" w
153
- WHERE w."id" = CASE WHEN p."site_id" = 'default'
154
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
155
- ELSE p."site_id" END
156
- );
157
- IF missed <> 0 THEN
158
- RAISE EXCEPTION
159
- '#6203 post-assertion: % project_listings row(s) had a resolvable site_id but came out of the backfill NULL',
160
- missed;
161
- END IF;
162
- SELECT count(*) INTO missed FROM "leads" l
163
- WHERE l."workspace_id" IS NULL
164
- AND EXISTS (
165
- SELECT 1 FROM "workspaces" w
166
- WHERE w."id" = CASE WHEN l."site_id" = 'default'
167
- THEN 'wksp_00000000-0000-7000-8000-000000000001'
168
- ELSE l."site_id" END
169
- );
170
- IF missed <> 0 THEN
171
- RAISE EXCEPTION
172
- '#6203 post-assertion: % leads row(s) had a resolvable site_id but came out of the backfill NULL',
173
- missed;
174
- END IF;
175
-
176
- -- 4. A NOTICE, never an exception: an unpinned/unseeded deployment leaving
177
- -- rows NULL is the expected, documented case (spec §1.9).
178
- SELECT count(*) INTO null_p FROM "project_listings" WHERE "workspace_id" IS NULL;
179
- SELECT count(*) INTO null_l FROM "leads" WHERE "workspace_id" IS NULL;
180
- IF null_p <> 0 OR null_l <> 0 THEN
181
- RAISE NOTICE
182
- '#6203: % project_listings row(s) and % leads row(s) left workspace_id unset — their site_id did not resolve to a workspace this database holds',
183
- null_p, null_l;
184
- END IF;
185
- END $$;
@@ -1,70 +0,0 @@
1
- -- #6204 — Lead gains a nullable triage stamp.
2
- --
3
- -- Operator decision Q1(a) (#6026, 2026-09-18): a message is a lead that needs
4
- -- an answer — same capture path, same routing, same tenancy, different
5
- -- follow-up. One nullable column beats a second entity, and
6
- -- docs/internal/decisions/flow-engine-admin-surface.md AD-4 stays true: the
7
- -- Inbox is a READ PROJECTION over tables that already hold decidable state,
8
- -- never a new decision table.
9
- --
10
- -- PURELY ADDITIVE (spec NFR1): two ADD COLUMNs, one CREATE INDEX. No UPDATE, no
11
- -- backfill, no DEFAULT, no NOT NULL — every existing row keeps NULL, which is
12
- -- exactly "unhandled" (spec D5, AC-1). There is no data transformation here, so
13
- -- this migration carries none of the census/post-assertion apparatus
14
- -- 20260919140000_inbox_workspace_key needed to prove ITS backfill correct.
15
- --
16
- -- RE-RUNNABLE (spec NFR2): IF NOT EXISTS on every statement, the house style of
17
- -- 20260919140000_inbox_workspace_key/migration.sql.
18
- --
19
- -- ORDER-SAFE (spec NFR2, security finding S-9): the index references
20
- -- "workspace_id", which 20260919140000_inbox_workspace_key adds — a migration
21
- -- this one sorts strictly after (20260920 > 20260919), so in the normal case
22
- -- the guard below is a formality. It stays a guard rather than an assumption:
23
- -- `IF NOT EXISTS` guards the INDEX's own name, not the column it references,
24
- -- so an unguarded CREATE INDEX would abort inside prisma migrate's
25
- -- per-migration transaction and wedge the ledger for the whole deployment if
26
- -- that migration were ever skipped, renamed, or rolled back on a given
27
- -- database. The two ADD COLUMNs above it are unconditional because they
28
- -- reference nothing another migration owns.
29
- --
30
- -- CREATE INDEX is NOT CONCURRENT (security finding S-11), and cannot be:
31
- -- CONCURRENTLY may not run inside prisma migrate's per-migration transaction.
32
- -- It therefore takes a lock on "leads" for its duration, which blocks the three
33
- -- anonymous capture routes (a write failure there surfaces as 503
34
- -- persist_failed, routes/leads/index.ts), and every subsequent insert
35
- -- pays a fourth index write. Accepted: "leads" carries four indexes already
36
- -- (siteId, createdAt, email, workspaceId) and is small at this pin. A
37
- -- deployment with a large leads table should create this index out-of-band
38
- -- with CONCURRENTLY first — the IF NOT EXISTS guard then makes this
39
- -- statement a no-op.
40
- --
41
- -- Rollback: DROP INDEX "leads_workspace_id_handled_at_idx";
42
- -- ALTER TABLE "leads" DROP COLUMN "handled_at", DROP COLUMN "handled_by".
43
- -- Safe ONLY at this pin, and the qualifier is load-bearing: once the Needs-you
44
- -- section ships, dropping these columns destroys operator triage state. After
45
- -- that point the rollback is a forward migration, not a DROP.
46
-
47
- -- AlterTable
48
- ALTER TABLE "leads" ADD COLUMN IF NOT EXISTS "handled_at" TIMESTAMP(3);
49
- -- The actor (spec D4, security finding S-7) — the sibling of
50
- -- project_listings."decided_by". Plain TEXT, no FK:
51
- -- ADR 0009 D2 forbids a relation crossing from inbox into auth.
52
- ALTER TABLE "leads" ADD COLUMN IF NOT EXISTS "handled_by" TEXT;
53
-
54
- -- CreateIndex — the read is
55
- -- workspace_id = $1 AND handled_at IS NULL AND source = ANY($2)
56
- -- ordered by created_at desc. A two-column (workspace_id, handled_at) index is
57
- -- the one that serves it: it narrows to the tenant and then to the unhandled
58
- -- partition, which is the small side and the side that shrinks as an operator
59
- -- works the queue. `source` is deliberately NOT a third column (spec D10).
60
- DO $$ BEGIN
61
- IF EXISTS (
62
- SELECT 1 FROM information_schema.columns
63
- WHERE table_name = 'leads' AND column_name = 'workspace_id'
64
- ) THEN
65
- CREATE INDEX IF NOT EXISTS "leads_workspace_id_handled_at_idx"
66
- ON "leads"("workspace_id", "handled_at");
67
- ELSE
68
- RAISE NOTICE '#6204: leads.workspace_id absent — index skipped. 20260919140000_inbox_workspace_key has not run against this database; the Needs-you read cannot be served until it does (spec § 10.1).';
69
- END IF;
70
- END $$;