create-ailk 0.23.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 (604) hide show
  1. package/README.md +68 -33
  2. package/component-catalog.json +3 -3
  3. package/component-catalog.md +3 -3
  4. package/dist/add-module.js +10 -3
  5. package/dist/add-route.d.ts +12 -1
  6. package/dist/add-route.js +38 -4
  7. package/dist/cli.js +12 -7
  8. package/dist/derive-architecture.d.ts +3 -4
  9. package/dist/derive-architecture.js +75 -22
  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/lib/scaffold-render-sweep.d.ts +33 -0
  20. package/dist/lib/scaffold-render-sweep.js +45 -0
  21. package/dist/module-architecture.d.ts +20 -3
  22. package/dist/module-architecture.js +122 -538
  23. package/dist/paid-sections.d.ts +25 -1
  24. package/dist/paid-sections.js +88 -3
  25. package/dist/surfaces.js +10 -2
  26. package/dist/sync-routes.d.ts +30 -4
  27. package/dist/sync-routes.js +271 -18
  28. package/dist/template-record.d.ts +14 -4
  29. package/dist/template-record.js +23 -5
  30. package/dist/topology.d.ts +16 -3
  31. package/dist/topology.js +31 -19
  32. package/dist/update.d.ts +10 -1
  33. package/dist/update.js +540 -16
  34. package/package.json +3 -3
  35. package/release-scope.json +1 -1
  36. package/templates/.claude/cloudparity-repo-hooks.json +5 -0
  37. package/templates/.claude/hooks/template-guard.sh +137 -0
  38. package/templates/.claude/rules/extending.md +106 -0
  39. package/templates/.claude/rules/loop-contract.md +2 -2
  40. package/templates/.claude/settings.json +11 -0
  41. package/templates/.env.example +9 -5
  42. package/templates/.github/workflows/ci.yml +7 -4
  43. package/templates/CLAUDE.md +15 -3
  44. package/templates/CONVENTIONS.md +8 -10
  45. package/templates/README.md +14 -13
  46. package/templates/apps/api/.env.example +9 -2
  47. package/templates/apps/api/CLAUDE.md +122 -82
  48. package/templates/apps/api/jest.config.cjs +15 -5
  49. package/templates/apps/api/package.json +16 -20
  50. package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
  51. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
  52. package/templates/apps/api/src/index.ts +1 -1
  53. package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -4
  54. package/templates/apps/api/src/modules/commerce.ts +10 -0
  55. package/templates/apps/api/src/modules/content-write.ts +10 -0
  56. package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
  57. package/templates/apps/api/src/modules/scheduling.ts +10 -0
  58. package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
  59. package/templates/apps/api/src/modules/workspace.ts +10 -0
  60. package/templates/apps/api/src/server.ts +117 -1857
  61. package/templates/apps/api/vercel.json +2 -2
  62. package/templates/apps/mcp/CLAUDE.md +77 -24
  63. package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
  64. package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
  65. package/templates/apps/mcp/jest.config.cjs +6 -1
  66. package/templates/apps/mcp/package.json +5 -12
  67. package/templates/apps/mcp/src/app.ts +8 -7
  68. package/templates/apps/mcp/src/index.ts +6 -34
  69. package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
  70. package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
  71. package/templates/apps/mcp/src/site.ts +12 -0
  72. package/templates/apps/web/.env.example +1 -1
  73. package/templates/apps/web/CLAUDE.md +21 -5
  74. package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
  75. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
  76. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +14 -4
  77. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
  78. package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
  79. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
  80. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +28 -7
  81. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
  82. package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
  83. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
  84. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
  85. package/templates/apps/web/app/api/revalidate/route.ts +3 -3
  86. package/templates/apps/web/auth.ts +2 -2
  87. package/templates/apps/web/jest.config.cjs +1 -0
  88. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +7 -0
  89. package/templates/apps/web/lib/auth-email.ts +1 -1
  90. package/templates/apps/web/lib/magnet-routes.ts +3 -1
  91. package/templates/apps/web/lib/newsletter.actions.ts +9 -6
  92. package/templates/apps/web/lib/relay.ts +2 -2
  93. package/templates/apps/web/lib/release-scope.ts +47 -0
  94. package/templates/apps/web/lib/server-api.ts +1 -1
  95. package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
  96. package/templates/customizations.yaml +37 -0
  97. package/templates/database/prisma.config.ts +10 -14
  98. package/templates/database/seed-test.ts +2 -2
  99. package/templates/database/seed.ts +32 -5
  100. package/templates/package.json +12 -6
  101. package/templates/pnpm-workspace.yaml +0 -1
  102. package/templates/project.yaml +16 -8
  103. package/templates/tools/jest/reporters.cjs +65 -0
  104. package/templates/tools/lint-architecture.ts +757 -7
  105. package/templates/tsconfig.json +1 -2
  106. package/versions.json +48 -35
  107. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
  108. package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
  109. package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
  110. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
  111. package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
  112. package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
  113. package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
  114. package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
  115. package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
  116. package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
  117. package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
  118. package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
  119. package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
  120. package/templates/apps/api/src/bin/spokes.ts +0 -198
  121. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
  122. package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
  123. package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
  124. package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
  125. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
  126. package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
  127. package/templates/apps/api/src/config/index.ts +0 -596
  128. package/templates/apps/api/src/config/modules.ts +0 -83
  129. package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
  130. package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
  131. package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
  132. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
  133. package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
  134. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
  135. package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
  136. package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
  137. package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
  138. package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
  139. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
  140. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
  141. package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
  142. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
  143. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
  144. package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
  145. package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
  146. package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
  147. package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
  148. package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -292
  149. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
  150. package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
  151. package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
  152. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
  153. package/templates/apps/api/src/lib/agent-store.ts +0 -613
  154. package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
  155. package/templates/apps/api/src/lib/consent-store.ts +0 -363
  156. package/templates/apps/api/src/lib/content-adapter.ts +0 -112
  157. package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
  158. package/templates/apps/api/src/lib/error-serializer.ts +0 -164
  159. package/templates/apps/api/src/lib/errors.ts +0 -95
  160. package/templates/apps/api/src/lib/file-fallback.ts +0 -87
  161. package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
  162. package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
  163. package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
  164. package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
  165. package/templates/apps/api/src/lib/license-key-store.ts +0 -45
  166. package/templates/apps/api/src/lib/locales.ts +0 -201
  167. package/templates/apps/api/src/lib/outbound-http.ts +0 -127
  168. package/templates/apps/api/src/lib/prisma.ts +0 -21
  169. package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
  170. package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
  171. package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
  172. package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
  173. package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
  174. package/templates/apps/api/src/lib/site.ts +0 -254
  175. package/templates/apps/api/src/lib/slice-load.ts +0 -90
  176. package/templates/apps/api/src/lib/spoke-store.ts +0 -180
  177. package/templates/apps/api/src/lib/stripe.ts +0 -318
  178. package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
  179. package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
  180. package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
  181. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
  182. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
  183. package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
  184. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
  185. package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
  186. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
  187. package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
  188. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
  189. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
  190. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
  191. package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
  192. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
  193. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
  194. package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
  195. package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
  196. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
  197. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
  198. package/templates/apps/api/src/middleware/auth.ts +0 -864
  199. package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
  200. package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
  201. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -264
  202. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
  203. package/templates/apps/api/src/openapi/index.ts +0 -18
  204. package/templates/apps/api/src/openapi/spec.ts +0 -1815
  205. package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
  206. package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
  207. package/templates/apps/api/src/routes/aeo/index.ts +0 -107
  208. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
  209. package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
  210. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
  211. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
  212. package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
  213. package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
  214. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
  215. package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
  216. package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
  217. package/templates/apps/api/src/routes/auth/user.ts +0 -305
  218. package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -325
  219. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
  220. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1300
  221. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
  222. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
  223. package/templates/apps/api/src/routes/checkout/index.ts +0 -34
  224. package/templates/apps/api/src/routes/checkout/session.ts +0 -171
  225. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
  226. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
  227. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
  228. package/templates/apps/api/src/routes/consent/account.ts +0 -268
  229. package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
  230. package/templates/apps/api/src/routes/consent/index.ts +0 -26
  231. package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
  232. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
  233. package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
  234. package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
  235. package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
  236. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
  237. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
  238. package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
  239. package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
  240. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
  241. package/templates/apps/api/src/routes/content/get-page.ts +0 -268
  242. package/templates/apps/api/src/routes/content/index.ts +0 -49
  243. package/templates/apps/api/src/routes/content/list-pages.ts +0 -95
  244. package/templates/apps/api/src/routes/content/list-types.ts +0 -19
  245. package/templates/apps/api/src/routes/content/offerings.ts +0 -184
  246. package/templates/apps/api/src/routes/content/pricing.ts +0 -176
  247. package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
  248. package/templates/apps/api/src/routes/content/search.ts +0 -129
  249. package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
  250. package/templates/apps/api/src/routes/content/slug.ts +0 -9
  251. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
  252. package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
  253. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
  254. package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
  255. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
  256. package/templates/apps/api/src/routes/leads/index.ts +0 -573
  257. package/templates/apps/api/src/routes/leads/list.ts +0 -104
  258. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
  259. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
  260. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
  261. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -480
  262. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -465
  263. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
  264. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
  265. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
  266. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
  267. package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
  268. package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
  269. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
  270. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
  271. package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
  272. package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
  273. package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
  274. package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
  275. package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
  276. package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
  277. package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
  278. package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
  279. package/templates/apps/api/src/routes/requests/index.ts +0 -54
  280. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
  281. package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
  282. package/templates/apps/api/src/routes/webhooks/README.md +0 -398
  283. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
  284. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
  285. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -448
  286. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
  287. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
  288. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
  289. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
  290. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
  291. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
  292. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
  293. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
  294. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -808
  295. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
  296. package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
  297. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
  298. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2026
  299. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -528
  300. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
  301. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
  302. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
  303. package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
  304. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
  305. package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
  306. package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
  307. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -867
  308. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
  309. package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
  310. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
  311. package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
  312. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
  313. package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
  314. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
  315. package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
  316. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
  317. package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1176
  318. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -444
  319. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -154
  320. package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
  321. package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
  322. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
  323. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
  324. package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
  325. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
  326. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
  327. package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
  328. package/templates/apps/api/src/services/abandon-email.ts +0 -313
  329. package/templates/apps/api/src/services/aeo-score.ts +0 -325
  330. package/templates/apps/api/src/services/answer-distributions.ts +0 -138
  331. package/templates/apps/api/src/services/audit-log.ts +0 -961
  332. package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
  333. package/templates/apps/api/src/services/consent.ts +0 -834
  334. package/templates/apps/api/src/services/default-tenant.ts +0 -416
  335. package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -755
  336. package/templates/apps/api/src/services/dunning-policy.ts +0 -294
  337. package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
  338. package/templates/apps/api/src/services/entitlements.ts +0 -338
  339. package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
  340. package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
  341. package/templates/apps/api/src/services/lead-promotion.ts +0 -160
  342. package/templates/apps/api/src/services/lead-routing.ts +0 -1372
  343. package/templates/apps/api/src/services/leads-inbox.ts +0 -312
  344. package/templates/apps/api/src/services/listing-config.ts +0 -178
  345. package/templates/apps/api/src/services/magnet-completion.ts +0 -242
  346. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -295
  347. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
  348. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -868
  349. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
  350. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -396
  351. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -681
  352. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
  353. package/templates/apps/api/src/services/offerings/catalog.ts +0 -308
  354. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -385
  355. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
  356. package/templates/apps/api/src/services/offerings/connection.ts +0 -1003
  357. package/templates/apps/api/src/services/offerings/index.ts +0 -614
  358. package/templates/apps/api/src/services/offerings/source.ts +0 -140
  359. package/templates/apps/api/src/services/offerings/store.ts +0 -150
  360. package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
  361. package/templates/apps/api/src/services/person.ts +0 -1188
  362. package/templates/apps/api/src/services/plan-offerings.ts +0 -125
  363. package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
  364. package/templates/apps/api/src/services/project-listings.ts +0 -2073
  365. package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
  366. package/templates/apps/api/src/services/request-confirm.ts +0 -698
  367. package/templates/apps/api/src/services/revalidation.ts +0 -235
  368. package/templates/apps/api/src/services/slack-notify.ts +0 -291
  369. package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
  370. package/templates/apps/api/src/services/usage-credits.ts +0 -340
  371. package/templates/apps/api/src/services/verified-email.ts +0 -71
  372. package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
  373. package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
  374. package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
  375. package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
  376. package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
  377. package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
  378. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
  379. package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
  380. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
  381. package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
  382. package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
  383. package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
  384. package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
  385. package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
  386. package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
  387. package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
  388. package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1151
  389. package/templates/apps/mcp/__tests__/index.test.ts +0 -195
  390. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
  391. package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
  392. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -65
  393. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -187
  394. package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
  395. package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
  396. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -165
  397. package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
  398. package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -228
  399. package/templates/apps/mcp/__tests__/locale.test.ts +0 -135
  400. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
  401. package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
  402. package/templates/apps/mcp/__tests__/read_content.test.ts +0 -474
  403. package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -225
  404. package/templates/apps/mcp/__tests__/search_content.test.ts +0 -256
  405. package/templates/apps/mcp/__tests__/server.test.ts +0 -388
  406. package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
  407. package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -307
  408. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -223
  409. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
  410. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
  411. package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
  412. package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
  413. package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
  414. package/templates/apps/mcp/src/abuse/index.ts +0 -342
  415. package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
  416. package/templates/apps/mcp/src/boot.ts +0 -167
  417. package/templates/apps/mcp/src/config/modules.ts +0 -43
  418. package/templates/apps/mcp/src/http-transport.ts +0 -526
  419. package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
  420. package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
  421. package/templates/apps/mcp/src/lib/content-adapter.ts +0 -142
  422. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -139
  423. package/templates/apps/mcp/src/lib/errors.ts +0 -61
  424. package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
  425. package/templates/apps/mcp/src/lib/locales.ts +0 -171
  426. package/templates/apps/mcp/src/lib/prisma.ts +0 -39
  427. package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
  428. package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
  429. package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
  430. package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
  431. package/templates/apps/mcp/src/request-context.ts +0 -52
  432. package/templates/apps/mcp/src/server.ts +0 -343
  433. package/templates/apps/mcp/src/serverless-app.ts +0 -88
  434. package/templates/apps/mcp/src/tools/audit.ts +0 -127
  435. package/templates/apps/mcp/src/tools/capture_lead.ts +0 -64
  436. package/templates/apps/mcp/src/tools/create_project.ts +0 -134
  437. package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
  438. package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
  439. package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
  440. package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
  441. package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
  442. package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
  443. package/templates/apps/mcp/src/tools/index.ts +0 -137
  444. package/templates/apps/mcp/src/tools/list_pages.ts +0 -74
  445. package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
  446. package/templates/apps/mcp/src/tools/proposals.ts +0 -27
  447. package/templates/apps/mcp/src/tools/purchase.ts +0 -205
  448. package/templates/apps/mcp/src/tools/read_content.ts +0 -75
  449. package/templates/apps/mcp/src/tools/request_callback.ts +0 -59
  450. package/templates/apps/mcp/src/tools/search_content.ts +0 -67
  451. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -57
  452. package/templates/apps/mcp/src/workspace-server.ts +0 -224
  453. package/templates/database/CHANGELOG.md +0 -961
  454. package/templates/database/LICENSE +0 -202
  455. package/templates/database/NOTICE +0 -2
  456. package/templates/database/README.md +0 -243
  457. package/templates/database/__tests__/agents.db.test.ts +0 -320
  458. package/templates/database/__tests__/appointment.db.test.ts +0 -106
  459. package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
  460. package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
  461. package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
  462. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
  463. package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
  464. package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
  465. package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
  466. package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
  467. package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
  468. package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
  469. package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
  470. package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
  471. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
  472. package/templates/database/__tests__/flows-presets.test.ts +0 -173
  473. package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
  474. package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
  475. package/templates/database/__tests__/member-area.db.test.ts +0 -257
  476. package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
  477. package/templates/database/__tests__/member-contract.db.test.ts +0 -413
  478. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
  479. package/templates/database/__tests__/offerings-migration.test.ts +0 -197
  480. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
  481. package/templates/database/__tests__/offerings.db.test.ts +0 -303
  482. package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
  483. package/templates/database/__tests__/person-contact.db.test.ts +0 -563
  484. package/templates/database/__tests__/person-notes.db.test.ts +0 -249
  485. package/templates/database/__tests__/person.db.test.ts +0 -586
  486. package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
  487. package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
  488. package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
  489. package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
  490. package/templates/database/__tests__/seed.test.ts +0 -240
  491. package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
  492. package/templates/database/__tests__/tenancy.test.ts +0 -141
  493. package/templates/database/__tests__/user-cascade.test.ts +0 -140
  494. package/templates/database/_gitignore +0 -8
  495. package/templates/database/auth/.gitkeep +0 -0
  496. package/templates/database/auth/schema.prisma +0 -641
  497. package/templates/database/config/.gitkeep +0 -0
  498. package/templates/database/config/schema.prisma +0 -441
  499. package/templates/database/content/.gitkeep +0 -0
  500. package/templates/database/content/schema.prisma +0 -339
  501. package/templates/database/eslint.config.mjs +0 -69
  502. package/templates/database/flows/listing-diagnostic.ts +0 -182
  503. package/templates/database/flows/presets.ts +0 -315
  504. package/templates/database/inbox/.gitkeep +0 -0
  505. package/templates/database/inbox/README.md +0 -3
  506. package/templates/database/inbox/__tests__/schema.test.ts +0 -44
  507. package/templates/database/inbox/schema.prisma +0 -1447
  508. package/templates/database/jest.config.cjs +0 -36
  509. package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
  510. package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
  511. package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
  512. package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
  513. package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
  514. package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
  515. package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
  516. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
  517. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
  518. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
  519. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
  520. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
  521. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
  522. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
  523. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
  524. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
  525. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
  526. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
  527. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
  528. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
  529. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
  530. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
  531. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
  532. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
  533. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
  534. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
  535. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
  536. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
  537. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
  538. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
  539. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
  540. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
  541. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
  542. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
  543. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
  544. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
  545. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
  546. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
  547. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
  548. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
  549. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
  550. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
  551. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
  552. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
  553. package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
  554. package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
  555. package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
  556. package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
  557. package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
  558. package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
  559. package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
  560. package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
  561. package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
  562. package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
  563. package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
  564. package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
  565. package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
  566. package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
  567. package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
  568. package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
  569. package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
  570. package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
  571. package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
  572. package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
  573. package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
  574. package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
  575. package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
  576. package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
  577. package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
  578. package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
  579. package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
  580. package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
  581. package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
  582. package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
  583. package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
  584. package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
  585. package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
  586. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
  587. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
  588. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
  589. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
  590. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
  591. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
  592. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
  593. package/templates/database/migrations/migration_lock.toml +0 -3
  594. package/templates/database/ops/.gitkeep +0 -0
  595. package/templates/database/ops/schema.prisma +0 -43
  596. package/templates/database/package.json +0 -89
  597. package/templates/database/schema.prisma +0 -71
  598. package/templates/database/scripts/db-generate-locked.sh +0 -80
  599. package/templates/database/scripts/person-backfill.sql +0 -132
  600. package/templates/database/tenancy.ts +0 -183
  601. package/templates/database/tsconfig.build.json +0 -16
  602. package/templates/database/tsconfig.flows.json +0 -15
  603. package/templates/database/tsconfig.json +0 -16
  604. package/templates/database/tsconfig.tenancy.json +0 -14
@@ -1,755 +0,0 @@
1
- /**
2
- * @file deliverable-fulfillment.ts
3
- * @description The deliverable-fulfillment service (#3961 D3/D4/D5/D7;
4
- * extended #3998 D1/D3/D5; extended FEAT-074 U4b, #4718 D4).
5
- *
6
- * NOT marked `oss: false` and NOT in signal-collectors's `ownedFiles`
7
- * (module-architecture.ts), despite D-48/D6 placing this file with
8
- * signal-collectors — `services/recommender-capture.ts` imports it directly
9
- * and is itself reached from `server.ts` by a plain, unconditional static
10
- * import today (commerce/#5364 has not yet landed the gate around it).
11
- * Marking or excluding this file broke both the OSS-strip and the create-ailk
12
- * starter snapshot for EVERY module, not just signal-collectors (spec §5.4
13
- * escalation boundary; see module-architecture.ts's comment on this file).
14
- * Revert once #5364 lands.
15
- *
16
- * `fulfillDeliverable` is the one entry point, called from flow-engine's
17
- * completion branch after `routeLead` succeeds and only when the session's
18
- * `configSnapshot` carries a `deliverables` block (D4 — the block is opt-in
19
- * per flow; flow-engine.ts itself gates the call on the block's presence).
20
- * Resolves the request-local segment against the flow's `deliverables`
21
- * asset map (D3), mints a capability-token grant (D5), and sends the
22
- * lead-facing delivery email (D7) — every non-happy path is a log-and-skip,
23
- * never a throw, so a caller that DOES let an exception escape (a DB error)
24
- * is the only case its own wrapping needs to catch (flow-engine.ts's D4
25
- * "the submission still succeeds" posture).
26
- *
27
- * An `entry.assetKey` prefixed `playbook:` (FEAT-074 U4b, #4718 D4) is a
28
- * VIRTUAL asset — a per-lead COMPILED `PlaybookInstance`, never an
29
- * operator-uploaded `DeliverableAsset` row — so it skips the asset lookup
30
- * entirely, requires at least one instance for `(siteId, leadId,
31
- * experimentKey)` (else `playbook_missing`, the `asset_missing` posture's
32
- * virtual-asset twin), and mints the grant with `assetId = entry.assetKey`
33
- * itself (the column is a plain string with no `@relation`, D4 — no schema
34
- * change). Every other `assetKey` follows the unchanged operator-uploaded
35
- * path below.
36
- *
37
- * `resendDeliverableGrant` and `listSendlessGrants` (#3998) are the
38
- * `deliverable:resend` CLI's two modes. Both share the extracted post-mint
39
- * "resolve copy → build link → send → stamp + log" tail with
40
- * `fulfillDeliverable` (#3998 D5) — the ONE place a delivery email is ever
41
- * sent and its outcome stamped. The resend path never mints a grant (#3998
42
- * D3) — it looks the grant up and reuses its id, producing a byte-identical
43
- * download link to the original.
44
- *
45
- * PII boundary: logs carry only `{ siteId, flowSlug, sessionId, outcome }` —
46
- * never email/asset-key/copy values (mirrors flow-engine.ts's + abandon-
47
- * email.ts's boundary).
48
- *
49
- * #6310 D1/D2/D4/D5/D15 — a non-`playbook:` `assetKey` now resolves against
50
- * the workspace-scoped `Asset` store FIRST, via the named boundary
51
- * `lib/deliverable-asset-store.ts` (never a raw `prisma.asset` call here —
52
- * that access is banned by architecture.yaml's tenant-scope
53
- * `forbidden_pattern`), falling back to the LEGACY `DeliverableAsset` store
54
- * on a miss (D3 — existing rows keep delivering, unmigrated). The workspace
55
- * is read from the magnet's own `FlowDefinition.workspaceId`, ONE
56
- * `findUnique` issued only on this branch (never for a `playbook:` key, and
57
- * never before the `no_entry` exit) — a `null`/absent value skips the
58
- * `Asset` lookup entirely and falls straight to the legacy arm, never
59
- * guessing a workspace (D5, mirroring #6155 D3's posture). The grant is
60
- * minted with `workspaceId` ONLY on the `Asset` arm — the same value that
61
- * authorized the resolution — restoring the download route's serve-time
62
- * tenancy re-check for this new grant class (D15).
63
- */
64
-
65
- import { sendDeliverableEmail } from "@working-theory/email";
66
- import { logger } from "@working-theory/observability";
67
- import { flowConfigSchema, type FlowConfig } from "@working-theory/validation";
68
-
69
- import { findDeliverableAssetIdByKey } from "../lib/deliverable-asset-store.js";
70
- import { prisma } from "../lib/prisma.js";
71
- import { resolveWorkspaceIdForAlias } from "../lib/site.js";
72
-
73
- // ─── Types ────────────────────────────────────────────────────────────────────
74
-
75
- export type FulfillDeliverableInput = {
76
- siteId: string;
77
- flowSlug: string;
78
- configSnapshot: FlowConfig;
79
- /** The request-local segment (#3959 D3) — undefined for an N=1 flow. */
80
- segment?: string;
81
- leadId: string;
82
- sessionId: string;
83
- email: string;
84
- locale: string;
85
- };
86
-
87
- type DeliverableAssetRow = {
88
- id: string;
89
- siteId: string;
90
- key: string;
91
- };
92
-
93
- // Derived from the schema type (not hand-rolled) so it never drifts from
94
- // `deliverableAssetMapEntrySchema`'s actual Zod-inferred optional shape.
95
- type DeliverableEntry = NonNullable<
96
- FlowConfig["deliverables"]
97
- >["assets"][number];
98
-
99
- // Same derivation discipline for the copy block (#3998) — never hand-rolled,
100
- // so it can never drift from `deliverableCopySchema`'s actual shape.
101
- type DeliverableCopy = NonNullable<FlowConfig["deliverables"]>["copy"];
102
-
103
- // ─── PII-safe outcome logging (mirrors abandon-email.ts's logOutcome) ──────────
104
-
105
- type FulfillmentOutcome =
106
- | "sent"
107
- | "no_entry"
108
- | "asset_missing"
109
- | "playbook_missing"
110
- | "sender_unconfigured"
111
- | "send_failed";
112
-
113
- // The exact post-mint subset (#3998 D1) — derived from FulfillmentOutcome,
114
- // not a second hand-rolled union, so it can never drift. A grant only ever
115
- // exists for one of these three; `no_entry`/`asset_missing`/
116
- // `playbook_missing` (FEAT-074 U4b, #4718 — the virtual-asset analog of
117
- // `asset_missing`) all occur before a grant is minted and are never
118
- // stamped.
119
- type PostMintOutcome = Extract<
120
- FulfillmentOutcome,
121
- "sent" | "send_failed" | "sender_unconfigured"
122
- >;
123
-
124
- const OUTCOME_MESSAGE: Record<FulfillmentOutcome, string> = {
125
- sent: "[deliverable-fulfillment] delivery email sent",
126
- no_entry: "[deliverable-fulfillment] no matching asset-map entry — skipped",
127
- asset_missing: "[deliverable-fulfillment] asset row not found — skipped",
128
- playbook_missing:
129
- "[deliverable-fulfillment] no PlaybookInstance for this lead/experiment — skipped",
130
- sender_unconfigured:
131
- "[deliverable-fulfillment] no from-address configured — skipped",
132
- send_failed: "[deliverable-fulfillment] email send failed",
133
- };
134
-
135
- function logOutcome(
136
- input: { siteId: string; flowSlug: string; sessionId: string },
137
- outcome: FulfillmentOutcome,
138
- ): void {
139
- logger.info(
140
- {
141
- siteId: input.siteId,
142
- flowSlug: input.flowSlug,
143
- sessionId: input.sessionId,
144
- outcome,
145
- },
146
- OUTCOME_MESSAGE[outcome],
147
- );
148
- }
149
-
150
- // ─── D7 — copy resolution ───────────────────────────────────────────────────────
151
-
152
- /**
153
- * `copy[locale] ?? copy[defaultLocale]` (D7). `defaultLocale` is a required
154
- * key on every `deliverables.copy` LocalizedString the `deliverablesSchema`
155
- * superRefine walk enforces, so the final fallback is always resolvable
156
- * against a schema-valid config.
157
- */
158
- function resolveCopy(
159
- copy: Record<string, string>,
160
- locale: string,
161
- defaultLocale: string,
162
- ): string {
163
- return copy[locale] ?? copy[defaultLocale] ?? "";
164
- }
165
-
166
- // ─── D3 — segment → entry resolution ────────────────────────────────────────────
167
-
168
- /**
169
- * `entry = assets.find(a => a.segment === segment) ?? assets.find(a =>
170
- * a.segment === undefined)` (D3) — the default entry, if declared, is the
171
- * fallback for a derivation miss; a map with segment entries but no default
172
- * relies on the #3959 band map's totality guarantee for the happy path.
173
- */
174
- function resolveEntry(
175
- assets: DeliverableEntry[],
176
- segment: string | undefined,
177
- ): DeliverableEntry | undefined {
178
- return (
179
- assets.find((a) => a.segment === segment) ??
180
- assets.find((a) => a.segment === undefined)
181
- );
182
- }
183
-
184
- // ─── FEAT-074 U4b (#4718) D4 — the `playbook:` virtual asset ────────────────
185
-
186
- const PLAYBOOK_ASSET_PREFIX = "playbook:";
187
-
188
- /**
189
- * `entry.assetKey`'s `experimentKey` suffix when it names the `playbook:`
190
- * virtual asset (spec D4), else `undefined` — the one branch point every
191
- * other `assetKey` (an operator-uploaded `DeliverableAsset` key) never
192
- * takes.
193
- */
194
- function playbookExperimentKey(assetKey: string): string | undefined {
195
- return assetKey.startsWith(PLAYBOOK_ASSET_PREFIX)
196
- ? assetKey.slice(PLAYBOOK_ASSET_PREFIX.length)
197
- : undefined;
198
- }
199
-
200
- // ─── #3998 D1 — send-outcome stamp ───────────────────────────────────────────────
201
-
202
- /**
203
- * Stamps the post-mint outcome on the grant row (#3998 D1). Caught and
204
- * logged, never escalated (#3998 D5) — a stamp write failing must not become
205
- * a new way to fail a submission or a resend; the existing `logOutcome` log
206
- * line is the audit trail of record either way.
207
- */
208
- async function stampOutcome(
209
- grantId: string,
210
- outcome: PostMintOutcome,
211
- now: Date,
212
- ): Promise<void> {
213
- try {
214
- // fence: global-key — `DeliverableGrant.id`, the capability token itself
215
- // (#3961 D5: the grant IS the token). Every caller reaches this line
216
- // inside an already-scoped frame — `fulfillDeliverable` has just MINTED
217
- // this id under the session's workspace, and `resendDeliverableGrant` has
218
- // just read the row back by the same opaque id — so the id is the row's
219
- // own primary key, not a tenant selector, and a `workspaceId` term here
220
- // could only re-state what the mint already fenced. It would also stop
221
- // stamping LEGACY grants, whose `workspaceId` is NULL by the #6254 § 10.1
222
- // Q2 carve-out, turning a post-mint audit stamp into a silent no-op.
223
- await prisma.deliverableGrant.update({
224
- where: { id: grantId },
225
- data: { sendStatus: outcome, sendAt: now },
226
- });
227
- } catch (err) {
228
- logger.warn(
229
- {
230
- grantId,
231
- outcome,
232
- err: err instanceof Error ? err.message : "Unknown error",
233
- },
234
- "[deliverable-fulfillment] failed to stamp send outcome",
235
- );
236
- }
237
- }
238
-
239
- // ─── #7519 D6 — the sender address ──────────────────────────────────────────────
240
-
241
- /**
242
- * The From address every email this service sends carries:
243
- * `AILK_DELIVERABLE_EMAIL_FROM`, else `AILK_LEAD_NOTIFICATION_FROM`,
244
- * `undefined` when neither is set — an empty value counts as unset (#7519 D6). One resolver for the send paths
245
- * (`sendAndStamp`, `sendCompletionEmail`) and the Admin's read
246
- * (`GET /v1/workspaces/:workspaceId/general` `emailFrom`), so the address
247
- * the Admin shows can never differ from the address the engine sends from.
248
- */
249
- export function deliverableSenderAddress(): string | undefined {
250
- // `||`, not `??`: an env var set to "" names no sender, so it falls
251
- // through to the next, and "" never reaches a From header or the Admin.
252
- return (
253
- process.env.AILK_DELIVERABLE_EMAIL_FROM ||
254
- process.env.AILK_LEAD_NOTIFICATION_FROM ||
255
- undefined
256
- );
257
- }
258
-
259
- // ─── #3998 D5 — the shared post-mint tail ────────────────────────────────────────
260
-
261
- type SendAndStampInput = {
262
- siteId: string;
263
- flowSlug: string;
264
- sessionId: string;
265
- grantId: string;
266
- email: string;
267
- locale: string;
268
- defaultLocale: string;
269
- copy: DeliverableCopy;
270
- };
271
-
272
- /**
273
- * The post-mint tail shared by `fulfillDeliverable` and
274
- * `resendDeliverableGrant` (#3998 D5): resolve copy → build link → send →
275
- * stamp + log. The ONE place a delivery email is ever sent and its outcome
276
- * stamped — extracted so the resend CLI never carries a second copy of the
277
- * copy-resolution / link-building logic to drift.
278
- */
279
- async function sendAndStamp(input: SendAndStampInput): Promise<PostMintOutcome> {
280
- const { siteId, flowSlug, sessionId, grantId, email, locale, defaultLocale, copy } =
281
- input;
282
- const logCtx = { siteId, flowSlug, sessionId };
283
-
284
- const fromAddress = deliverableSenderAddress();
285
-
286
- if (!fromAddress) {
287
- await stampOutcome(grantId, "sender_unconfigured", new Date());
288
- logOutcome(logCtx, "sender_unconfigured");
289
- return "sender_unconfigured";
290
- }
291
-
292
- const subject = resolveCopy(copy.subject, locale, defaultLocale);
293
- const body = resolveCopy(copy.body, locale, defaultLocale);
294
- const cta = copy.cta ? resolveCopy(copy.cta, locale, defaultLocale) : undefined;
295
-
296
- // D5 (#3961): no PII in link params — the link carries the opaque grant id
297
- // and nothing else. D3 (#3998): this is the SAME expression on both the
298
- // fulfillment path and the resend path, so a resend's link is
299
- // byte-identical to the original.
300
- const base = process.env.AILK_PUBLIC_API_URL ?? "";
301
- const downloadLink = `${base}/v1/deliverables/${grantId}`;
302
-
303
- const sendResult = await sendDeliverableEmail({
304
- to: email,
305
- fromAddress,
306
- subject,
307
- body,
308
- downloadLink,
309
- ...(cta !== undefined && { cta }),
310
- });
311
-
312
- const outcome: PostMintOutcome = sendResult.ok ? "sent" : "send_failed";
313
- await stampOutcome(grantId, outcome, new Date());
314
- logOutcome(logCtx, outcome);
315
- return outcome;
316
- }
317
-
318
- // ─── #7519 D5 — the completion email (a flow with no deliverable) ──────────────
319
-
320
- type CompletionEmailCopy = NonNullable<FlowConfig["completionEmail"]>;
321
-
322
- export type SendCompletionEmailInput = {
323
- siteId: string;
324
- flowSlug: string;
325
- sessionId: string;
326
- /** The lead's address — `undefined` when the flow asked for none. */
327
- email: string | undefined;
328
- locale: string;
329
- defaultLocale: string;
330
- copy: CompletionEmailCopy;
331
- };
332
-
333
- type CompletionOutcome =
334
- | "sent"
335
- | "send_failed"
336
- | "sender_unconfigured"
337
- | "no_recipient";
338
-
339
- const COMPLETION_OUTCOME_MESSAGE: Record<CompletionOutcome, string> = {
340
- sent: "[deliverable-fulfillment] completion email sent",
341
- send_failed: "[deliverable-fulfillment] completion email send failed",
342
- sender_unconfigured:
343
- "[deliverable-fulfillment] no from-address configured — completion email skipped",
344
- no_recipient:
345
- "[deliverable-fulfillment] no email answer — completion email skipped",
346
- };
347
-
348
- /**
349
- * Sends a flow's `completionEmail` to the lead (#7519 D5) — the email a
350
- * newsletter, contact, waitlist or diagnostic sends on completion, where
351
- * there is no asset to deliver. The same sender, copy resolution and email
352
- * function as the deliverable path, with no download link: it mints no
353
- * grant and stamps nothing. Its outcome is logged with the
354
- * `{ siteId, flowSlug, sessionId }` context the deliverable outcomes use.
355
- * Never throws on its own business-logic misses; the caller's try/catch is
356
- * the residual net for a genuine error.
357
- */
358
- export async function sendCompletionEmail(
359
- input: SendCompletionEmailInput,
360
- ): Promise<CompletionOutcome> {
361
- const { siteId, flowSlug, sessionId, email, locale, defaultLocale, copy } =
362
- input;
363
- const outcome = await sendCompletion();
364
- logger.info(
365
- { siteId, flowSlug, sessionId, outcome },
366
- COMPLETION_OUTCOME_MESSAGE[outcome],
367
- );
368
- return outcome;
369
-
370
- async function sendCompletion(): Promise<CompletionOutcome> {
371
- if (!email) return "no_recipient";
372
- const fromAddress = deliverableSenderAddress();
373
- if (!fromAddress) return "sender_unconfigured";
374
- const cta = copy.cta ? resolveCopy(copy.cta, locale, defaultLocale) : undefined;
375
- const sendResult = await sendDeliverableEmail({
376
- to: email,
377
- fromAddress,
378
- subject: resolveCopy(copy.subject, locale, defaultLocale),
379
- body: resolveCopy(copy.body, locale, defaultLocale),
380
- ...(cta !== undefined && { cta }),
381
- });
382
- return sendResult.ok ? "sent" : "send_failed";
383
- }
384
- }
385
-
386
- // ─── fulfillDeliverable ─────────────────────────────────────────────────────────
387
-
388
- export async function fulfillDeliverable(
389
- input: FulfillDeliverableInput,
390
- ): Promise<{ grantId: string } | undefined> {
391
- const {
392
- siteId,
393
- flowSlug,
394
- configSnapshot,
395
- segment,
396
- leadId,
397
- sessionId,
398
- email,
399
- locale,
400
- } = input;
401
-
402
- // D4: the block is opt-in per flow. Callers (flow-engine.ts) already gate
403
- // the call on this being present; re-checking here is defense in depth,
404
- // never load-bearing for the "no block → no call" AC4 regression floor.
405
- const block = configSnapshot.deliverables;
406
- if (!block) return undefined;
407
-
408
- const logCtx = { siteId, flowSlug, sessionId };
409
-
410
- // D3: no entry (segment derived but unmapped AND no default) — never fail
411
- // the submission, just skip fulfillment.
412
- const entry = resolveEntry(block.assets, segment);
413
- if (!entry) {
414
- logOutcome(logCtx, "no_entry");
415
- return undefined;
416
- }
417
-
418
- // FEAT-074 U4b (#4718) D4 — the `playbook:` virtual asset: a per-lead
419
- // COMPILED PlaybookInstance, never an operator-uploaded DeliverableAsset
420
- // row. Skips the asset lookup entirely; requires at least one instance
421
- // for (siteId, leadId, experimentKey) — the SAME log-and-skip,
422
- // no-grant-minted posture `asset_missing` already has for an
423
- // un-uploaded key (D4).
424
- const playbookKey = playbookExperimentKey(entry.assetKey);
425
-
426
- let assetId: string;
427
- // #6310 D15 — written ONLY on the Asset arm; stays undefined (no column
428
- // written) for a playbook: or legacy dasset_ grant.
429
- let grantWorkspaceId: string | undefined;
430
-
431
- if (playbookKey !== undefined) {
432
- // #6257 — `siteId` is the session's own, resolved by the route that
433
- // opened it (#6254/#6374 AC-7), so `workspaceId` rides beside it: a lead
434
- // id and an experiment key alone would select a row whose tenant the
435
- // query never stated.
436
- const instance = await prisma.playbookInstance.findFirst({
437
- where: { siteId, workspaceId: siteId, leadId, experimentKey: playbookKey },
438
- select: { id: true },
439
- });
440
- if (!instance) {
441
- logOutcome(logCtx, "playbook_missing");
442
- return undefined;
443
- }
444
- assetId = entry.assetKey;
445
- } else {
446
- // #6310 D4 — resolve the workspace from the magnet's own live
447
- // FlowDefinition, ONE query, issued only on this branch. D5 — a null or
448
- // absent workspaceId skips the Asset lookup entirely; it is never
449
- // guessed.
450
- const definition = (await prisma.flowDefinition.findUnique({
451
- where: { siteId_slug: { siteId, slug: flowSlug } },
452
- select: { workspaceId: true },
453
- })) as { workspaceId: string | null } | null;
454
- const workspaceId = definition?.workspaceId ?? null;
455
-
456
- const resolvedAssetId =
457
- workspaceId !== null
458
- ? await findDeliverableAssetIdByKey(workspaceId, entry.assetKey)
459
- : null;
460
-
461
- if (resolvedAssetId !== null && workspaceId !== null) {
462
- // #6310 D2 — Asset resolves first; D15 — the grant carries the SAME
463
- // workspaceId that authorized this resolution.
464
- assetId = resolvedAssetId;
465
- grantWorkspaceId = workspaceId;
466
- } else {
467
- // #8848 D3 (SEC-8848-B) — a `deliverable` flow resolves against the
468
- // Asset store only: it is new, so it has no legacy key to keep, and the
469
- // legacy store also holds the founders' signup CSVs.
470
- if (configSnapshot.deliverable !== undefined) {
471
- logOutcome(logCtx, "asset_missing");
472
- return undefined;
473
- }
474
- // #6310 D2/D3 — legacy fallback. A missing row (config references an
475
- // un-uploaded key) → same skip-and-log posture as before.
476
- // #6257 — the LEGACY store's read, scoped by the same resolved
477
- // workspace id the `Asset` arm above already resolves through
478
- // `lib/deliverable-asset-store.ts`. The compound `[siteId, key]` names
479
- // one row; `workspaceId` is what makes the tenant the QUERY's statement.
480
- const asset = (await prisma.deliverableAsset.findUnique({
481
- where: { siteId_key: { siteId, key: entry.assetKey }, workspaceId: siteId },
482
- })) as DeliverableAssetRow | null;
483
-
484
- if (!asset) {
485
- logOutcome(logCtx, "asset_missing");
486
- return undefined;
487
- }
488
- assetId = asset.id;
489
- }
490
- }
491
-
492
- // D5: the grant IS the token — minted before the send is attempted so a
493
- // later email-config gap or send failure never wastes it (a later
494
- // manual/operator resend can reuse the same grant id).
495
- const grant = (await prisma.deliverableGrant.create({
496
- data: {
497
- siteId,
498
- assetId,
499
- leadId,
500
- sessionId,
501
- // #6374 D4 / AC-8 — the SECOND DeliverableGrant mint site. The column
502
- // stays NULLABLE for LEGACY rows (NULL is the serve path's fail-closed
503
- // sentinel, #6254 § 10.1 Q2) and the migration touches none of them —
504
- // but a grant minted HERE always knows its workspace, so it is never
505
- // left to fail closed.
506
- //
507
- // `grantWorkspaceId` is #6310 D15's value, resolved from the magnet's
508
- // own FlowDefinition on the `asset_` arm; that read is scoped by
509
- // `siteId`, and the keystone's `flow_definitions_workspace_site_agree`
510
- // CHECK makes `workspaceId = siteId` on that row, so the two are the
511
- // SAME value and the `??` is a fallback for the legacy `dasset_` and
512
- // virtual `playbook:` arms rather than a choice between two scopes.
513
- // The new `deliverable_grants_workspace_site_agree` CHECK would reject
514
- // a disagreement loudly either way.
515
- //
516
- // This does NOT change which bytes the download route serves: that
517
- // route branches on the `assetId` PREFIX, and consults
518
- // `grant.workspaceId` only on the `asset_` arm, where this value is
519
- // what it already was.
520
- workspaceId: grantWorkspaceId ?? siteId,
521
- },
522
- select: { id: true },
523
- })) as { id: string };
524
-
525
- // #3998 D5: resolve copy → build link → send → stamp + log, shared with
526
- // the resend path.
527
- await sendAndStamp({
528
- siteId,
529
- flowSlug,
530
- sessionId,
531
- grantId: grant.id,
532
- email,
533
- locale,
534
- defaultLocale: configSnapshot.defaultLocale,
535
- copy: block.copy,
536
- });
537
- return { grantId: grant.id };
538
- }
539
-
540
- // ─── #3998 D3 — resend: reuses the existing grant id, never mints ───────────────
541
-
542
- export type ResendGrantError = {
543
- kind:
544
- | "grant_not_found"
545
- | "lead_not_found"
546
- | "session_not_found"
547
- | "invalid_config_snapshot"
548
- | "no_deliverables_block";
549
- message: string;
550
- };
551
-
552
- export type ResendGrantResult =
553
- | { ok: true; outcome: PostMintOutcome }
554
- | { ok: false; error: ResendGrantError };
555
-
556
- type DeliverableGrantRow = {
557
- id: string;
558
- siteId: string;
559
- leadId: string;
560
- sessionId: string;
561
- };
562
-
563
- type LeadRow = { email: string };
564
-
565
- type FlowSessionConfigRow = {
566
- flowSlug: string;
567
- configSnapshot: unknown;
568
- locale: string | null;
569
- };
570
-
571
- /**
572
- * Re-sends the delivery email for an existing grant (#3998 D3/D4). Reuses
573
- * `grant.id` — this function contains no `deliverableGrant.create` call, so
574
- * a resend can never mint a second capability token for one lead. Resolves
575
- * copy from the session's PINNED `configSnapshot` (never a live
576
- * `FlowDefinition` lookup, #3998 D3) — a mid-flight config edit must never
577
- * change what a resend delivers.
578
- */
579
- export async function resendDeliverableGrant(
580
- grantId: string,
581
- ): Promise<ResendGrantResult> {
582
- // fence: global-key — `DeliverableGrant.id`, the capability token itself
583
- // (#3961 D5). This is the resolver: the operator hands the
584
- // `deliverable:resend` CLI one opaque grant id and nothing else, and the
585
- // WORKSPACE is what this read produces (`grant.siteId`, carried into
586
- // `sendAndStamp` below), so a tenant term here would have to come from the
587
- // answer it is computing. Holding the id is the capability, exactly as it
588
- // is for the download route this resend re-sends a link to.
589
- const grant = (await prisma.deliverableGrant.findUnique({
590
- where: { id: grantId },
591
- select: { id: true, siteId: true, leadId: true, sessionId: true },
592
- })) as DeliverableGrantRow | null;
593
-
594
- if (!grant) {
595
- return {
596
- ok: false,
597
- error: {
598
- kind: "grant_not_found",
599
- message: `No deliverable grant found for id "${grantId}".`,
600
- },
601
- };
602
- }
603
-
604
- const lead = (await prisma.lead.findUnique({
605
- where: { id: grant.leadId },
606
- select: { email: true },
607
- })) as LeadRow | null;
608
-
609
- if (!lead) {
610
- return {
611
- ok: false,
612
- error: {
613
- kind: "lead_not_found",
614
- message: `Grant "${grantId}" references lead "${grant.leadId}", which no longer exists.`,
615
- },
616
- };
617
- }
618
-
619
- const session = (await prisma.flowSession.findUnique({
620
- where: { id: grant.sessionId },
621
- select: { flowSlug: true, configSnapshot: true, locale: true },
622
- })) as FlowSessionConfigRow | null;
623
-
624
- if (!session) {
625
- return {
626
- ok: false,
627
- error: {
628
- kind: "session_not_found",
629
- message: `Grant "${grantId}" references session "${grant.sessionId}", which no longer exists.`,
630
- },
631
- };
632
- }
633
-
634
- // #3998 D3: re-parse the pinned snapshot through flowConfigSchema — a
635
- // snapshot that no longer parses is reported and skipped, never thrown.
636
- const parsed = flowConfigSchema.safeParse(session.configSnapshot);
637
- if (!parsed.success) {
638
- return {
639
- ok: false,
640
- error: {
641
- kind: "invalid_config_snapshot",
642
- message:
643
- parsed.error.issues[0]?.message ??
644
- "The session's configSnapshot failed schema validation.",
645
- },
646
- };
647
- }
648
-
649
- const config = parsed.data;
650
- const block = config.deliverables;
651
- if (!block) {
652
- return {
653
- ok: false,
654
- error: {
655
- kind: "no_deliverables_block",
656
- message: `The session's pinned configSnapshot for grant "${grantId}" carries no deliverables block.`,
657
- },
658
- };
659
- }
660
-
661
- const outcome = await sendAndStamp({
662
- siteId: grant.siteId,
663
- flowSlug: session.flowSlug,
664
- sessionId: grant.sessionId,
665
- grantId: grant.id,
666
- email: lead.email,
667
- locale: session.locale ?? config.defaultLocale,
668
- defaultLocale: config.defaultLocale,
669
- copy: block.copy,
670
- });
671
-
672
- return { ok: true, outcome };
673
- }
674
-
675
- // ─── #3998 D4 — list: sendless grants for one site ──────────────────────────────
676
-
677
- export type SendlessGrantRow = {
678
- grantId: string;
679
- createdAt: Date;
680
- sessionId: string;
681
- };
682
-
683
- export type ListSendlessGrantsOptions = {
684
- siteId: string;
685
- since?: Date;
686
- limit?: number;
687
- };
688
-
689
- type DeliverableGrantListRow = {
690
- id: string;
691
- createdAt: Date;
692
- sessionId: string;
693
- };
694
-
695
- /**
696
- * Lists grants whose post-mint send never completed (`sendStatus IS NULL`,
697
- * #3998 D1's selection term) for one site, oldest first. Read-only — this is
698
- * the `deliverable:resend` CLI's default (no-`--grant`) mode.
699
- */
700
- export async function listSendlessGrants(
701
- opts: ListSendlessGrantsOptions,
702
- ): Promise<SendlessGrantRow[]> {
703
- // #6257 — `opts.siteId` is the one value in this service that arrives as a
704
- // RAW ALIAS: the `deliverable:resend` CLI passes `--site` through verbatim.
705
- // It is resolved here, once, the way `dunning-policy.ts` resolves its own
706
- // parameter — an alias that names no workspace lists nothing rather than
707
- // falling back to the unscoped read it used to make.
708
- const workspaceId = await resolveWorkspaceIdForAlias(opts.siteId);
709
- if (workspaceId === null) {
710
- logger.warn(
711
- { siteId: opts.siteId, outcome: "site_unresolved" },
712
- "[deliverable-fulfillment] --site names no workspace — listing nothing",
713
- );
714
- return [];
715
- }
716
-
717
- // #6257 — LEGACY grants are NOT in this list, and the narrowing is stated
718
- // rather than left for an operator to discover. Two terms exclude them: the
719
- // `workspaceId` filter below (their column is NULL by the #6254 § 10.1 Q2
720
- // carve-out) and the resolved `siteId` (the keystone migration deliberately
721
- // does NOT normalize `deliverable_grants.site_id` — migration.sql:1059 — so
722
- // a legacy row on a `"default"`-aliased deployment still carries the alias).
723
- //
724
- // This is a genuine TIGHTENING beyond the serve path, not parity with it:
725
- // `routes/deliverables/index.ts` consults `grant.workspaceId` only on the
726
- // `asset_` arm, so a legacy `dasset_` or `playbook:` grant still downloads.
727
- // The fence forces it — AC-2 forbids keying this read on the raw alias the
728
- // CLI was given, and a legacy row carries no workspace key to resolve — so
729
- // the list is narrower by construction and the recovery path for such a row
730
- // is `deliverable:resend --grant <id>`, which is unaffected.
731
- logger.info(
732
- { siteId: workspaceId, outcome: "legacy_grants_excluded" },
733
- "[deliverable-fulfillment] grants with no workspace key are NOT listed — resend one by id with --grant",
734
- );
735
-
736
- const rows = (await prisma.deliverableGrant.findMany({
737
- where: {
738
- siteId: workspaceId,
739
- // #6257 — the fence term. See the note above for what it excludes and
740
- // why that is a tightening rather than serve-path parity.
741
- workspaceId,
742
- sendStatus: null,
743
- ...(opts.since !== undefined && { createdAt: { gte: opts.since } }),
744
- },
745
- orderBy: { createdAt: "asc" },
746
- select: { id: true, createdAt: true, sessionId: true },
747
- ...(opts.limit !== undefined && { take: opts.limit }),
748
- })) as DeliverableGrantListRow[];
749
-
750
- return rows.map((row) => ({
751
- grantId: row.id,
752
- createdAt: row.createdAt,
753
- sessionId: row.sessionId,
754
- }));
755
- }