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,336 +0,0 @@
1
- /**
2
- * @file abandon-email.ts
3
- * @description The abandon-email sweep service (#3962 D4).
4
- *
5
- * `runAbandonEmailSweep` is the one entry point: select partial `FlowSession`
6
- * rows past their flow's configured delay, dispatch per the flow's send
7
- * policy (dark → audit-only stamp; enabled → send + stamp), and record the
8
- * outcome on the session row (D5's idempotency + audit guarantee). Invoked
9
- * by the CLI entry (`src/bin/abandon-sweep.ts`) on an operator-supplied
10
- * cadence — this service itself is stateless and has no timer of its own
11
- * (D1: no queue/cron infrastructure).
12
- *
13
- * Mirrors the lead-routing/flow-engine house shape: type-result returns,
14
- * mocked-dep tests, PII-safe logging via @working-theory/observability
15
- * (siteId/flowSlug/sessionId/mode/outcome only — never email/state values).
16
- */
17
-
18
- import { sendAbandonEmail } from "@working-theory/email";
19
- import { logger } from "@working-theory/observability";
20
- import {
21
- flowConfigSchema,
22
- type AbandonEmailConfig,
23
- } from "@working-theory/validation";
24
-
25
- import { prisma } from "../lib/prisma.js";
26
-
27
- // ─── Types ────────────────────────────────────────────────────────────────────
28
-
29
- export type AbandonEmailSweepSummary = {
30
- scanned: number;
31
- selected: number;
32
- sent: number;
33
- dark: number;
34
- failed: number;
35
- skipped: number;
36
- };
37
-
38
- export type AbandonEmailSweepError = {
39
- kind: "flows_not_configured" | "unexpected_error";
40
- message: string;
41
- };
42
-
43
- export type AbandonEmailSweepResult =
44
- | { ok: true; summary: AbandonEmailSweepSummary }
45
- | { ok: false; error: AbandonEmailSweepError };
46
-
47
- export type RunAbandonEmailSweepOptions = {
48
- siteId?: string;
49
- now?: Date;
50
- };
51
-
52
- type FlowDefinitionRow = {
53
- id: string;
54
- siteId: string;
55
- slug: string;
56
- config: unknown;
57
- };
58
-
59
- type AbandonCandidateRow = {
60
- id: string;
61
- siteId: string;
62
- flowSlug: string;
63
- email: string | null;
64
- locale: string | null;
65
- };
66
-
67
- // ─── DB-required guard (mirrors flow-engine.ts's requireDb) ────────────────────
68
-
69
- const FLOWS_NOT_CONFIGURED_ERROR = {
70
- kind: "flows_not_configured" as const,
71
- message: "Flows are not configured.",
72
- };
73
-
74
- function requireDb(): AbandonEmailSweepError | null {
75
- if (!process.env.DATABASE_URL) return FLOWS_NOT_CONFIGURED_ERROR;
76
- return null;
77
- }
78
-
79
- // ─── PII-safe outcome logging ───────────────────────────────────────────────────
80
-
81
- type AbandonOutcome = "dark" | "skipped" | "sent" | "failed";
82
-
83
- const OUTCOME_MESSAGE: Record<AbandonOutcome, string> = {
84
- dark: "[abandon-email] session selected — dark mode, audit-only",
85
- skipped: "[abandon-email] enabled candidate skipped — no from-address configured",
86
- sent: "[abandon-email] session selected — sent",
87
- failed: "[abandon-email] session selected — send failed",
88
- };
89
-
90
- /**
91
- * The one shape every outcome logs (siteId/flowSlug/sessionId/mode/outcome —
92
- * never email/state values, per this file's PII boundary).
93
- */
94
- function logOutcome(
95
- session: AbandonCandidateRow,
96
- mode: AbandonEmailConfig["mode"],
97
- outcome: AbandonOutcome,
98
- ): void {
99
- logger.info(
100
- {
101
- siteId: session.siteId,
102
- flowSlug: session.flowSlug,
103
- sessionId: session.id,
104
- mode,
105
- outcome,
106
- },
107
- OUTCOME_MESSAGE[outcome],
108
- );
109
- }
110
-
111
- // ─── Copy resolution (D6) ───────────────────────────────────────────────────────
112
-
113
- /**
114
- * `copy[session.locale ?? liveConfig.defaultLocale] ?? copy[liveConfig.defaultLocale]`
115
- * (D6). `defaultLocale` is a required key on every LocalizedString the
116
- * `abandonEmailSchema` superRefine walk enforces, so the final fallback is
117
- * always resolvable for a schema-valid config.
118
- */
119
- function resolveCopy(
120
- copy: Record<string, string>,
121
- sessionLocale: string | null,
122
- defaultLocale: string,
123
- ): string {
124
- const locale = sessionLocale ?? defaultLocale;
125
- return copy[locale] ?? copy[defaultLocale] ?? "";
126
- }
127
-
128
- /** Link = resumeUrl + '?fs=' + sessionId — no other session data (D7, no PII). */
129
- function buildResumeLink(
130
- resumeUrl: string | undefined,
131
- sessionId: string,
132
- ): string | undefined {
133
- if (!resumeUrl) return undefined;
134
- return `${resumeUrl}?fs=${sessionId}`;
135
- }
136
-
137
- // ─── Candidate paging (#9249) ───────────────────────────────────────────────────
138
-
139
- /**
140
- * How many candidates one run reads per flow definition. A flow with more
141
- * past-delay sessions than this processes one page and leaves the rest —
142
- * still `abandonEmailStatus: null` — for the next run. Same page as
143
- * `STANDARD_SEED_PAGE` (lib/tenant-db.ts), this app's other background page.
144
- */
145
- export const ABANDON_CANDIDATE_PAGE_SIZE = 500;
146
-
147
- // ─── The sweep ──────────────────────────────────────────────────────────────────
148
-
149
- /**
150
- * The unguarded sweep body — mutates `summary` in place as it goes, so a
151
- * mid-loop throw still leaves the caller's catch block able to log how far
152
- * it got (code-review finding on #3962's acceptance PR: an unhandled
153
- * rejection mid-sweep should degrade to a structured error with the partial
154
- * progress visible, not vanish).
155
- */
156
- async function sweepAllDefinitions(
157
- opts: RunAbandonEmailSweepOptions,
158
- now: Date,
159
- fromAddress: string | undefined,
160
- summary: AbandonEmailSweepSummary,
161
- ): Promise<void> {
162
- const definitions = (await prisma.flowDefinition.findMany({
163
- ...(opts.siteId !== undefined && { where: { siteId: opts.siteId } }),
164
- select: { id: true, siteId: true, slug: true, config: true },
165
- })) as FlowDefinitionRow[];
166
-
167
- for (const definition of definitions) {
168
- const parsedConfig = flowConfigSchema.safeParse(definition.config);
169
- if (!parsedConfig.success) {
170
- // Surface, do not swallow (code-review finding on #3962's acceptance
171
- // PR) — every sibling path in this feature area (serveFlow /
172
- // upsertFlowDefinition in flow-engine.ts) returns an explicit error on
173
- // a config-parse failure; a stored config that has drifted from the
174
- // schema must not silently stop that flow's abandon-email policy from
175
- // ever running again with no operator-visible signal.
176
- logger.warn(
177
- {
178
- siteId: definition.siteId,
179
- flowSlug: definition.slug,
180
- zodIssue: parsedConfig.error.issues[0]?.message,
181
- },
182
- "[abandon-email] skipping flow — stored config failed schema validation",
183
- );
184
- continue;
185
- }
186
- const config = parsedConfig.data;
187
- const policy: AbandonEmailConfig | undefined = config.abandonEmail;
188
- // Block absent = abandon email OFF for this flow — never selected (D2).
189
- if (!policy) continue;
190
-
191
- const cutoff = new Date(now.getTime() - policy.delayMinutes * 60_000);
192
-
193
- // The #3956 D2 partial-lead query (email != null AND completedAt == null)
194
- // plus the not-yet-processed and delay-window terms (D4/D5).
195
- const candidates = (await prisma.flowSession.findMany({
196
- where: {
197
- flowId: definition.id,
198
- email: { not: null },
199
- completedAt: null,
200
- abandonEmailStatus: null,
201
- updatedAt: { lte: cutoff },
202
- },
203
- select: {
204
- id: true,
205
- siteId: true,
206
- flowSlug: true,
207
- email: true,
208
- locale: true,
209
- },
210
- // #9249 — oldest-updated first, so the page is deterministic (the same
211
- // reason flow-aggregate.ts orders its capped read).
212
- orderBy: { updatedAt: "asc" },
213
- take: ABANDON_CANDIDATE_PAGE_SIZE,
214
- })) as AbandonCandidateRow[];
215
-
216
- summary.scanned += candidates.length;
217
-
218
- const darkIds: string[] = [];
219
-
220
- for (const session of candidates) {
221
- summary.selected += 1;
222
-
223
- if (policy.mode === "dark") {
224
- darkIds.push(session.id);
225
- logOutcome(session, policy.mode, "dark");
226
- continue;
227
- }
228
-
229
- // mode:'enabled' — an env gap must not consume the one send: leave the
230
- // candidate UNSTAMPED so a later configured run still sends it (D4).
231
- if (!fromAddress) {
232
- summary.skipped += 1;
233
- logOutcome(session, policy.mode, "skipped");
234
- continue;
235
- }
236
-
237
- const subject = resolveCopy(
238
- policy.copy.subject,
239
- session.locale,
240
- config.defaultLocale,
241
- );
242
- const body = resolveCopy(
243
- policy.copy.body,
244
- session.locale,
245
- config.defaultLocale,
246
- );
247
- const cta = policy.copy.cta
248
- ? resolveCopy(policy.copy.cta, session.locale, config.defaultLocale)
249
- : undefined;
250
- const resumeLink = buildResumeLink(policy.resumeUrl, session.id);
251
-
252
- const sendResult = await sendAbandonEmail({
253
- // `email: { not: null }` in the WHERE clause above guarantees this.
254
- to: session.email as string,
255
- fromAddress,
256
- subject,
257
- body,
258
- ...(cta !== undefined && { cta }),
259
- ...(resumeLink !== undefined && { resumeLink }),
260
- });
261
-
262
- if (sendResult.ok) {
263
- await prisma.flowSession.update({
264
- where: { id: session.id },
265
- data: { abandonEmailStatus: "sent", abandonEmailAt: now },
266
- });
267
- summary.sent += 1;
268
- logOutcome(session, policy.mode, "sent");
269
- } else {
270
- // Terminal to the sweep — no auto-retry (D5); clearing the stamp is
271
- // the deliberate operator retry.
272
- await prisma.flowSession.update({
273
- where: { id: session.id },
274
- data: { abandonEmailStatus: "failed", abandonEmailAt: now },
275
- });
276
- summary.failed += 1;
277
- logOutcome(session, policy.mode, "failed");
278
- }
279
- }
280
-
281
- // #9249 — one stamp for the whole dark-mode page, not one per row. The
282
- // `abandonEmailStatus: null` guard is the candidate read's own term, so a
283
- // row an overlapping run stamped meanwhile keeps what that run wrote.
284
- if (darkIds.length > 0) {
285
- await prisma.flowSession.updateMany({
286
- where: { id: { in: darkIds }, abandonEmailStatus: null },
287
- data: { abandonEmailStatus: "dark", abandonEmailAt: now },
288
- });
289
- summary.dark += darkIds.length;
290
- }
291
- }
292
- }
293
-
294
- export async function runAbandonEmailSweep(
295
- opts: RunAbandonEmailSweepOptions = {},
296
- ): Promise<AbandonEmailSweepResult> {
297
- const dbError = requireDb();
298
- if (dbError) return { ok: false, error: dbError };
299
-
300
- const now = opts.now ?? new Date();
301
- // #4740: `||`, not `??` — an accidentally-set empty-string override
302
- // (AILK_ABANDON_EMAIL_FROM="") is an env GAP, not a sender, so it falls
303
- // through to the owner-wide default; both empty resolves to `undefined` and
304
- // the `!fromAddress` guard leaves the candidate unstamped. The same shape
305
- // is applied identically in waitlist-followup.ts's sibling sweep.
306
- const fromAddress =
307
- process.env.AILK_ABANDON_EMAIL_FROM ||
308
- process.env.AILK_LEAD_NOTIFICATION_FROM ||
309
- undefined;
310
-
311
- const summary: AbandonEmailSweepSummary = {
312
- scanned: 0,
313
- selected: 0,
314
- sent: 0,
315
- dark: 0,
316
- failed: 0,
317
- skipped: 0,
318
- };
319
-
320
- try {
321
- await sweepAllDefinitions(opts, now, fromAddress, summary);
322
- } catch (err) {
323
- // An unexpected DB/network failure mid-loop must degrade to a
324
- // structured error, not an unhandled rejection (code-review finding on
325
- // #3962's acceptance PR) — the CLI entry's own try/catch expects this
326
- // shape. Progress already stamped on session rows before the throw is
327
- // NOT lost (D5's idempotency means a re-run picks up where this one
328
- // stopped); the partial in-memory summary is logged so it is not
329
- // silently discarded.
330
- const message = err instanceof Error ? err.message : "Unknown error";
331
- logger.error({ partialSummary: summary, err: message }, "[abandon-email] sweep failed mid-run");
332
- return { ok: false, error: { kind: "unexpected_error", message } };
333
- }
334
-
335
- return { ok: true, summary };
336
- }
@@ -1,325 +0,0 @@
1
- /**
2
- * Instant AEO score service (#4666 D3/D4).
3
- *
4
- * Productizes the existing pure `@working-theory/aeo` scorer behind the
5
- * SSRF-guarded external-URL crawler at `@working-theory/aeo/fetch`. The
6
- * rule set, severity table, and `identityMode`/`locale` defaults are
7
- * declared ONCE, here.
8
- */
9
- import {
10
- runAudit,
11
- schemaCoverage,
12
- jsonldValidity,
13
- entityMarkup,
14
- faqHowtoIncentive,
15
- EXAMPLE_SCORE_CRAWL,
16
- EXAMPLE_SCORE_META,
17
- EXAMPLE_SCORE_ANNOTATIONS,
18
- type Rule,
19
- type AuditReport,
20
- type PerRuleResult,
21
- } from "@working-theory/aeo";
22
- import {
23
- assertUrlSafe,
24
- crawlSite,
25
- buildAuditPayload,
26
- type CrawlResult,
27
- type FetchImpl,
28
- type Resolver,
29
- type SkippedEntry,
30
- } from "@working-theory/aeo/fetch";
31
- import type {
32
- AeoDimension,
33
- AeoDimensionId,
34
- AeoScoreResult,
35
- } from "@working-theory/validation";
36
-
37
- /**
38
- * The readiness rules (#5949) are deliberately NOT in this set. They grade
39
- * frontmatter and site-set facts (`AuditInput.readiness`); `buildAuditPayload`
40
- * produces no frontmatter, so on a crawl input all three pass vacuously
41
- * (packages/aeo FR5). Adding them would put three always-100 dimensions into a
42
- * closed four-member public enum. `/v1/aeo/score` has no MCP twin
43
- * (apps/api/CLAUDE.md § API ↔ MCP parity), so this is not a parity divergence.
44
- */
45
- export const AEO_SCORE_EXCLUDED_READINESS_RULES = [
46
- "policies-present",
47
- "offer-on-priced-page",
48
- "tax-code-when-stripe-tax",
49
- ] as const;
50
-
51
- /** The service's fixed rule set — the four content rules only (§ 2.1 FR4). */
52
- export const AEO_SCORE_RULES: Rule[] = [
53
- schemaCoverage,
54
- jsonldValidity,
55
- entityMarkup,
56
- faqHowtoIncentive,
57
- ];
58
-
59
- /** Severity table, verbatim from prospect-audit-site's "Compose the score-led sales report". */
60
- export const AEO_DIMENSION_SEVERITY: Record<
61
- AeoDimensionId,
62
- "high" | "medium" | "low"
63
- > = {
64
- "schema-coverage": "high",
65
- "entity-markup": "high",
66
- "jsonld-validity": "medium",
67
- "faq-howto-incentive": "low",
68
- };
69
-
70
- const AEO_DIMENSION_LABELS: Record<AeoDimensionId, string> = {
71
- "schema-coverage": "Schema Coverage",
72
- "jsonld-validity": "JSON-LD Validity",
73
- "entity-markup": "Entity Markup",
74
- "faq-howto-incentive": "FAQ / HowTo Incentive",
75
- };
76
-
77
- /** D5 — the route's default + the configured-budget cap, both declared once. */
78
- export const AEO_SCORE_DEFAULT_BUDGET_MS = 6000;
79
- export const AEO_SCORE_MAX_BUDGET_MS = 15000;
80
-
81
- /**
82
- * Resolve `AEO_SCORE_BUDGET_MS` for the route. A missing/unparseable/
83
- * non-positive value falls back to the default silently (nothing to warn
84
- * about — the env var is simply unset in dev). A value above the 15 000 ms
85
- * cap is logged and falls back to the default (D5) — raising it beyond the
86
- * crawler's own 15 s per-fetch bound would collide with the handler's 25 s
87
- * request guard once cold start is added. Re-checked on EVERY request, not
88
- * once at process startup — a serverless deployment has no persistent
89
- * startup phase to check this at.
90
- */
91
- export function resolveScoreBudgetMs(
92
- rawEnv: string | undefined,
93
- log?: { warn: (msg: string) => void },
94
- ): number {
95
- if (!rawEnv) return AEO_SCORE_DEFAULT_BUDGET_MS;
96
- const parsed = Number(rawEnv);
97
- if (!Number.isFinite(parsed) || parsed <= 0)
98
- return AEO_SCORE_DEFAULT_BUDGET_MS;
99
- if (parsed > AEO_SCORE_MAX_BUDGET_MS) {
100
- log?.warn(
101
- `AEO_SCORE_BUDGET_MS=${rawEnv} exceeds the ${AEO_SCORE_MAX_BUDGET_MS}ms cap; falling back to the ${AEO_SCORE_DEFAULT_BUDGET_MS}ms default.`,
102
- );
103
- return AEO_SCORE_DEFAULT_BUDGET_MS;
104
- }
105
- return parsed;
106
- }
107
-
108
- const SEVERITY_RANK: Record<"high" | "medium" | "low", number> = {
109
- high: 0,
110
- medium: 1,
111
- low: 2,
112
- };
113
-
114
- /** `lowestTwo` ordering (D4): score asc, then severity high>medium>low, then weight desc. */
115
- function compareDimensions(a: AeoDimension, b: AeoDimension): number {
116
- if (a.score !== b.score) return a.score - b.score;
117
- const severityDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
118
- if (severityDiff !== 0) return severityDiff;
119
- return b.weight - a.weight;
120
- }
121
-
122
- const FAQ_HOWTO_INCENTIVE_ID: AeoDimensionId = "faq-howto-incentive";
123
-
124
- /** The fixed opportunity sentence for the incentive dimension (D4) — its per-page results carry no evidence. */
125
- function faqHowtoOpportunityFinding(slug: string): string {
126
- return `${slug}: no FAQPage or HowTo structured data (a visibility opportunity, not a defect)`;
127
- }
128
-
129
- function buildDimension(
130
- ruleId: AeoDimensionId,
131
- report: AuditReport,
132
- ): AeoDimension {
133
- const perRule = report.perRule.find((r) => r.id === ruleId) as PerRuleResult;
134
- const perPage = perRule.perPage ?? [];
135
- const fraction = perRule.score ?? (perRule.pass ? 1 : 0);
136
- const score = Math.round(fraction * 100);
137
-
138
- let finding: string | null;
139
- let pagesPassing: number;
140
- const pagesTotal = perPage.length > 0 ? perPage.length : 1;
141
-
142
- if (ruleId === FAQ_HOWTO_INCENTIVE_ID) {
143
- pagesPassing = perPage.filter((p) => p.bonus === true).length;
144
- const firstMissing = perPage.find((p) => p.bonus !== true);
145
- finding = firstMissing
146
- ? faqHowtoOpportunityFinding(firstMissing.slug)
147
- : null;
148
- } else {
149
- pagesPassing =
150
- perPage.length > 0
151
- ? perPage.filter((p) => p.pass).length
152
- : perRule.pass
153
- ? 1
154
- : 0;
155
- const firstFailing = perPage.find((p) => !p.pass);
156
- finding = firstFailing
157
- ? (firstFailing.evidence ?? perRule.evidence ?? null)
158
- : null;
159
- }
160
-
161
- return {
162
- id: ruleId,
163
- label: AEO_DIMENSION_LABELS[ruleId],
164
- weight: perRule.weight,
165
- score,
166
- severity: AEO_DIMENSION_SEVERITY[ruleId],
167
- finding,
168
- pagesPassing,
169
- pagesTotal,
170
- };
171
- }
172
-
173
- /**
174
- * `assertUrlSafe`'s per-path rejection text (`packages/aeo/src/fetch/ssrf.ts`)
175
- * can disclose which internal host/IP a crawl target resolved to — an
176
- * anonymous-endpoint recon primitive (AILK #5110, mirroring the #4707 fix
177
- * already applied to `scoreUrl`'s top-level `unavailable`/`ssrf_blocked`
178
- * check below). `crawlSite` re-checks every path individually (DNS-rebinding
179
- * defense) and writes the raw `assertUrlSafe` text straight into
180
- * `SkippedEntry.reason` (`packages/aeo/src/fetch/crawl.ts:125`) — deliberately
181
- * so, per that file's own test
182
- * (`packages/aeo/__tests__/fetch/crawl.test.ts:47-49`), so the redaction
183
- * happens HERE instead, at the boundary where a `ready` result's
184
- * `meta.skipped` is assembled. Every `assertUrlSafe` rejection message has
185
- * one of these six fixed prefixes; `crawlSite`'s other `SkippedEntry.reason`
186
- * producers (`"budget_exceeded"`, a `fetchPage` HTTP/redirect/fetch-failure
187
- * message) never do.
188
- */
189
- const SSRF_GUARD_REASON_PATTERN =
190
- /^(Not a valid URL:|Only http\(s\) URLs allowed,|Blocked host \(internal\/loopback\/metadata\):|DNS resolution failed for|No DNS addresses resolved for|Host .+ resolves to a blocked address:)/;
191
-
192
- /**
193
- * `meta.skipped[]` boundary serializer (#5110). A per-path SSRF-guard
194
- * rejection is logged server-side (the specific host/IP detail, for operator
195
- * diagnosis) and replaced on the wire with the same generic `"ssrf_blocked"`
196
- * sentinel `scoreUrl`'s top-level check already returns (below) — never the
197
- * raw text. Any other skip reason passes through unchanged.
198
- */
199
- function toClientSkippedEntry(entry: SkippedEntry): SkippedEntry {
200
- if (!SSRF_GUARD_REASON_PATTERN.test(entry.reason)) return entry;
201
- console.warn(
202
- "aeo-score: per-path SSRF-guard rejection (redacted on the wire)",
203
- {
204
- path: entry.path,
205
- url: entry.url,
206
- reason: entry.reason,
207
- },
208
- );
209
- return { ...entry, reason: "ssrf_blocked" };
210
- }
211
-
212
- /** Pure: crawl payload → runAudit → dimensions (D3/D4). Never throws on an empty crawl — the caller checks first. */
213
- export function scoreCrawl(
214
- crawl: CrawlResult,
215
- opts?: { now?: () => Date },
216
- ): AeoScoreResult {
217
- if (crawl.pages.length === 0) {
218
- return {
219
- status: "unavailable",
220
- reason: "no_pages_fetched",
221
- message: "No pages were fetched.",
222
- };
223
- }
224
-
225
- const payload = buildAuditPayload(crawl.pages);
226
- const report = runAudit(
227
- { ...payload, locale: "en", identityMode: "organization" },
228
- AEO_SCORE_RULES,
229
- );
230
-
231
- const dimensions = AEO_SCORE_RULES.map((rule) =>
232
- buildDimension(rule.id as AeoDimensionId, report),
233
- );
234
- const lowestTwo = [...dimensions].sort(compareDimensions).slice(0, 2);
235
- const partial = crawl.skipped.some((s) => s.reason === "budget_exceeded");
236
- const now = opts?.now ? opts.now() : new Date();
237
-
238
- return {
239
- status: "ready",
240
- score: Math.round(report.fractionalScore ?? report.score),
241
- dimensions,
242
- lowestTwo,
243
- meta: {
244
- auditedAt: now.toISOString(),
245
- pagesAudited: crawl.pages.length,
246
- partial,
247
- skipped: crawl.skipped.map(toClientSkippedEntry),
248
- schemaVersion: report.meta.schemaVersion,
249
- registryVersion: report.meta.registryVersion,
250
- },
251
- };
252
- }
253
-
254
- export interface ScoreUrlDeps {
255
- fetchImpl?: FetchImpl;
256
- resolver?: Resolver;
257
- budgetMs?: number;
258
- }
259
-
260
- /** `assertUrlSafe` → `crawlSite` → `scoreCrawl` (D3). */
261
- export async function scoreUrl(
262
- url: string,
263
- deps: ScoreUrlDeps = {},
264
- ): Promise<AeoScoreResult> {
265
- const safe = await assertUrlSafe(url, {
266
- ...(deps.resolver !== undefined && { resolver: deps.resolver }),
267
- });
268
- if (!safe.ok) {
269
- return {
270
- status: "unavailable",
271
- reason: "ssrf_blocked",
272
- message: safe.reason,
273
- };
274
- }
275
-
276
- const budgetMs = deps.budgetMs ?? AEO_SCORE_DEFAULT_BUDGET_MS;
277
- try {
278
- const crawl = await crawlSite(url, {
279
- ...(deps.fetchImpl !== undefined && { fetchImpl: deps.fetchImpl }),
280
- ...(deps.resolver !== undefined && { resolver: deps.resolver }),
281
- deadline: Date.now() + budgetMs,
282
- });
283
- if (crawl.pages.length === 0) {
284
- return {
285
- status: "unavailable",
286
- reason: "no_pages_fetched",
287
- message: "No pages were fetched within the crawl budget.",
288
- };
289
- }
290
- return scoreCrawl(crawl);
291
- } catch (e) {
292
- const message = e instanceof Error ? e.message : String(e);
293
- return { status: "unavailable", reason: "crawl_failed", message };
294
- }
295
- }
296
-
297
- /** The bundled pre-launch example — no runtime `fs` read (D6). */
298
- export function loadExampleScore(): AeoScoreResult {
299
- const result = scoreCrawl(EXAMPLE_SCORE_CRAWL);
300
- if (result.status !== "ready") {
301
- // The fixture is committed and known-good; this should never happen.
302
- throw new Error("Example score fixture failed to score.");
303
- }
304
- const annotate = (d: AeoDimension): AeoDimension => ({
305
- ...d,
306
- annotation: EXAMPLE_SCORE_ANNOTATIONS[d.id],
307
- });
308
- return {
309
- ...result,
310
- dimensions: result.dimensions.map(annotate),
311
- lowestTwo: result.lowestTwo.map(annotate),
312
- meta: {
313
- ...result.meta,
314
- example: true,
315
- sourceUrl: EXAMPLE_SCORE_META.sourceUrl,
316
- generatedAt: EXAMPLE_SCORE_META.generatedAt,
317
- // Wire-visible disclosure (acceptance-review finding, #4666): whether
318
- // the crawl data is a genuine fetch of `sourceUrl` or a hand-authored
319
- // representative dataset. Sourced from EXAMPLE_SCORE_META itself, not
320
- // hardcoded here, so regenerating the fixture from a real crawl flips
321
- // this automatically.
322
- illustrative: EXAMPLE_SCORE_META.illustrative,
323
- },
324
- };
325
- }