create-ailk 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -22
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +2 -1
- package/dist/lib/fetch-template-snapshot.js +8 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +36 -15
- package/dist/lib/module-tier.d.ts +2 -8
- package/dist/lib/module-tier.js +13 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +129 -552
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/starter-grid.d.ts +6 -6
- package/dist/starter-grid.js +12 -5
- package/dist/surfaces.js +39 -2
- package/dist/template-record.d.ts +21 -3
- package/dist/template-record.js +38 -7
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +54 -23
- package/dist/update.d.ts +10 -1
- package/dist/update.js +715 -21
- package/package.json +3 -3
- package/release-scope.json +2 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
- package/site-templates/marketing-site/ailk-template.json +7 -1
- package/site-templates/marketing-site/provenance.json +7 -1
- package/site-templates/saas-product/ailk-template.json +2 -1
- package/site-templates/saas-product/provenance.json +1 -0
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +20 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +23 -2
- package/templates/apps/api/CLAUDE.md +119 -83
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +16 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +90 -26
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +31 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
- package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/layout.tsx +33 -13
- package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
- package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +10 -6
- package/templates/apps/web/i18n/request.ts +49 -1
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
- package/templates/apps/web/lib/auth-email.ts +103 -21
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
- package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/sign-up-agreements.ts +291 -28
- package/templates/apps/web/lib/site-contributions.ts +18 -0
- package/templates/apps/web/messages/en.json +186 -20
- package/templates/apps/web/messages/site/en.json +1 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +49 -36
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Offerings checkout (#8931, spec
|
|
3
|
-
* docs/specs/8931-offerings-checkout-authorization-server-side-spec.md D1,
|
|
4
|
-
* D2, D5, D7, D9, D11; contract
|
|
5
|
-
* docs/specs/8879-the-offerings-security-contract-key-spec.md A1–A9, A11,
|
|
6
|
-
* A13, G1, G3, G6, K6).
|
|
7
|
-
*
|
|
8
|
-
* THE PATH. `resolveOfferingsCheckoutPath` decides, from the server-resolved
|
|
9
|
-
* workspace and its Organization's connection alone, whether a checkout
|
|
10
|
-
* request takes the Offerings path, the deployment's env-key path, or
|
|
11
|
-
* neither. No request body field names a workspace or an Organization (A1).
|
|
12
|
-
* A spoke's workspace with no connection is `unconnected`, never the env
|
|
13
|
-
* key: that key sells the deployment's own products, not a spoke's (#9126).
|
|
14
|
-
*
|
|
15
|
-
* THE SALE. `startOfferingsCheckout` parses the lookup key by the one grammar
|
|
16
|
-
* before any read (A3), refuses the hub's own plan keys at any period
|
|
17
|
-
* (#9338 DD5), authorizes it at click time against org-enabled ∩
|
|
18
|
-
* workspace-shown from the rows — never the snapshot (A4), and for a
|
|
19
|
-
* workspace saved per Price only a shown Price (#9070 D5) — resolves it live
|
|
20
|
-
* with the connection's own client, sells the authorized Price id in an
|
|
21
|
-
* anonymous hosted session stamped `{ site, ailk_offering }` by the server
|
|
22
|
-
* (A6, A7), and records the session before it answers; a failed record
|
|
23
|
-
* expires the session (A11). Every miss is one `unknown`; every failure one
|
|
24
|
-
* `unavailable`; there is never a snapshot fallback (A5), and this path never
|
|
25
|
-
* sells on the environment key: an Organization that owns it checks out on the
|
|
26
|
-
* env path until it has a stored connection (#9296, #8936).
|
|
27
|
-
*
|
|
28
|
-
* THE READ. `readOfferingsCheckoutSession` reads a recorded session with its
|
|
29
|
-
* record's own connection, never a caller value (A13). Only a stored connection
|
|
30
|
-
* records a session (#9296), so this read never runs on the environment key.
|
|
31
|
-
*
|
|
32
|
-
* LOGGING (D11, T6). Only `workspaceId` or `organizationId`, an 8-character
|
|
33
|
-
* session-id prefix, a fixed `reason`, and an error's class name. Never a
|
|
34
|
-
* lookup key, a Price or Product id, Stripe text, a URL, a full session id
|
|
35
|
-
* or an error message.
|
|
36
|
-
*/
|
|
37
|
-
import { logger } from "@working-theory/observability";
|
|
38
|
-
import {
|
|
39
|
-
OFFERINGS_NAME_MAX,
|
|
40
|
-
isPlanPurchaseKey,
|
|
41
|
-
lookupKeyPeriodSchema,
|
|
42
|
-
parseLookupKey,
|
|
43
|
-
} from "@working-theory/validation";
|
|
44
|
-
import type { FastifyRequest } from "fastify";
|
|
45
|
-
|
|
46
|
-
import {
|
|
47
|
-
createCheckoutSessionForPrice,
|
|
48
|
-
expireCheckoutSessionWith,
|
|
49
|
-
findActivePriceByLookupKey,
|
|
50
|
-
retrieveCheckoutSession,
|
|
51
|
-
type RetrieveCheckoutSessionOutput,
|
|
52
|
-
UnknownSessionError,
|
|
53
|
-
} from "../../lib/stripe.js";
|
|
54
|
-
import { resolvePublicWorkspace } from "../public-site-resolver.js";
|
|
55
|
-
|
|
56
|
-
import { normalizeCatalog, normalizeText } from "./catalog.js";
|
|
57
|
-
import {
|
|
58
|
-
CheckoutSessionRecordError,
|
|
59
|
-
readCheckoutSessionRecord,
|
|
60
|
-
recordCheckoutSession,
|
|
61
|
-
} from "./checkout-session-record.js";
|
|
62
|
-
import {
|
|
63
|
-
getOfferingsConnectionState,
|
|
64
|
-
stripeClientForOrganization,
|
|
65
|
-
} from "./connection.js";
|
|
66
|
-
import { organizationIdOfWorkspace } from "./store.js";
|
|
67
|
-
|
|
68
|
-
import { getSellableProductIds } from "./index.js";
|
|
69
|
-
|
|
70
|
-
/** One seller's Offerings checkouts a minute, across every caller (D4). */
|
|
71
|
-
export const OFFERINGS_CHECKOUT_PER_WORKSPACE_PER_MINUTE = 60;
|
|
72
|
-
|
|
73
|
-
/** The resolver's own header; its presence is what makes a refusal a refused key (D2). */
|
|
74
|
-
const SPOKE_KEY_HEADER = "x-ailk-spoke-key";
|
|
75
|
-
|
|
76
|
-
export type OfferingsCheckoutPath =
|
|
77
|
-
| { kind: "env" }
|
|
78
|
-
| { kind: "refused" }
|
|
79
|
-
| { kind: "unavailable" }
|
|
80
|
-
| { kind: "unconnected"; workspaceId: string }
|
|
81
|
-
| { kind: "offerings"; workspaceId: string; organizationId: string };
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Which path a checkout request takes (D2). Rejects when a read rejects —
|
|
85
|
-
* the resolver's, the Organization's or the connection summary's — so a
|
|
86
|
-
* workspace it could not read never falls through to the env key.
|
|
87
|
-
*/
|
|
88
|
-
export async function resolveOfferingsCheckoutPath(
|
|
89
|
-
request: FastifyRequest,
|
|
90
|
-
): Promise<OfferingsCheckoutPath> {
|
|
91
|
-
const site = await resolvePublicWorkspace(request);
|
|
92
|
-
if (site.kind === "not-required") return { kind: "env" };
|
|
93
|
-
if (site.kind === "unresolved") {
|
|
94
|
-
// A keyless call on a hub that requires a key is also `spoke-refused`;
|
|
95
|
-
// only a presented key that was refused is `refused` (A2, C6).
|
|
96
|
-
return site.reason === "spoke-refused" &&
|
|
97
|
-
request.headers[SPOKE_KEY_HEADER] !== undefined
|
|
98
|
-
? { kind: "refused" }
|
|
99
|
-
: { kind: "env" };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const { workspaceId } = site;
|
|
103
|
-
// A keyless call resolves only where no spoke key is required (a deployment
|
|
104
|
-
// that is not a hub, or a hub's AILK_HUB_REQUIRE_SPOKE_KEY=off opt-out),
|
|
105
|
-
// whose env key sells its own products; a presented key resolved only to
|
|
106
|
-
// its spoke's workspace, which sells on its own connection or not at all
|
|
107
|
-
// (#9126).
|
|
108
|
-
const noConnection: OfferingsCheckoutPath =
|
|
109
|
-
request.headers[SPOKE_KEY_HEADER] !== undefined
|
|
110
|
-
? { kind: "unconnected", workspaceId }
|
|
111
|
-
: { kind: "env" };
|
|
112
|
-
const organizationId = await organizationIdOfWorkspace(workspaceId);
|
|
113
|
-
if (organizationId === null) return noConnection;
|
|
114
|
-
const summary = await getOfferingsConnectionState(organizationId);
|
|
115
|
-
if (summary === null) return noConnection;
|
|
116
|
-
// Never the rows, which read a corrupt connection as an empty set (404).
|
|
117
|
-
if (summary === "unreadable") return { kind: "unavailable" };
|
|
118
|
-
// An Organization on the environment key never takes the Offerings path
|
|
119
|
-
// (#9296): that path stamps `ailk_offering`, which the legacy webhook acks
|
|
120
|
-
// `offerings_path` without recording and the per-Organization receiver takes
|
|
121
|
-
// only for a stored connection's token, so a sale on the environment key
|
|
122
|
-
// would be charged and never fulfilled. Its checkout stays on the env path
|
|
123
|
-
// until environment-key fulfilment exists (#8936 stores the connection).
|
|
124
|
-
if (summary === "environment") return noConnection;
|
|
125
|
-
return { kind: "offerings", workspaceId, organizationId };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface OfferingsCheckoutInput {
|
|
129
|
-
workspaceId: string;
|
|
130
|
-
organizationId: string;
|
|
131
|
-
productCode: string;
|
|
132
|
-
successUrl: string;
|
|
133
|
-
cancelUrl: string;
|
|
134
|
-
quantity: number;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export type OfferingsCheckoutOutcome =
|
|
138
|
-
| { kind: "created"; sessionId: string; url: string }
|
|
139
|
-
| { kind: "unknown" }
|
|
140
|
-
| { kind: "unavailable" };
|
|
141
|
-
|
|
142
|
-
const UNKNOWN = { kind: "unknown" } as const;
|
|
143
|
-
const UNAVAILABLE = { kind: "unavailable" } as const;
|
|
144
|
-
|
|
145
|
-
function errName(err: unknown): string {
|
|
146
|
-
return err instanceof Error ? err.name : "unknown";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Authorize the key, sell its Price and record the session (D5–D7). Stops at
|
|
151
|
-
* the first step that fails; never rejects.
|
|
152
|
-
*/
|
|
153
|
-
export async function startOfferingsCheckout(
|
|
154
|
-
input: OfferingsCheckoutInput,
|
|
155
|
-
): Promise<OfferingsCheckoutOutcome> {
|
|
156
|
-
const { workspaceId, organizationId, productCode } = input;
|
|
157
|
-
const unknown = (): OfferingsCheckoutOutcome => {
|
|
158
|
-
logger.info(
|
|
159
|
-
{ workspaceId, reason: "offerings_unknown_product" },
|
|
160
|
-
"[offerings] checkout key not sellable",
|
|
161
|
-
);
|
|
162
|
-
return UNKNOWN;
|
|
163
|
-
};
|
|
164
|
-
const unavailable = (
|
|
165
|
-
reason: string,
|
|
166
|
-
err?: unknown,
|
|
167
|
-
): OfferingsCheckoutOutcome => {
|
|
168
|
-
logger.error(
|
|
169
|
-
{
|
|
170
|
-
workspaceId,
|
|
171
|
-
reason,
|
|
172
|
-
...(err !== undefined && { errName: errName(err) }),
|
|
173
|
-
},
|
|
174
|
-
"[offerings] checkout unavailable",
|
|
175
|
-
);
|
|
176
|
-
return UNAVAILABLE;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// 1. Grammar first (A3): no read and no Stripe call for a key G1 refuses.
|
|
180
|
-
const parsed = parseLookupKey(productCode);
|
|
181
|
-
if (parsed === null) return unknown();
|
|
182
|
-
|
|
183
|
-
// 1b. Never a hub plan (#9338 DD5): AILK's own Pro / Pro Plus, at any
|
|
184
|
-
// period, for every Organization. A sale here would stamp `ailk_offering`,
|
|
185
|
-
// which no billing path mints a licence for — the buyer would be charged
|
|
186
|
-
// with no key. Same 404, same log reason, before any read.
|
|
187
|
-
if (isPlanPurchaseKey(productCode)) return unknown();
|
|
188
|
-
|
|
189
|
-
// 2. The connection's own client (K6) — before the rows, so a key that
|
|
190
|
-
// cannot be used answers 502 whatever the rows hold.
|
|
191
|
-
let connection: Awaited<ReturnType<typeof stripeClientForOrganization>>;
|
|
192
|
-
try {
|
|
193
|
-
connection = await stripeClientForOrganization(organizationId);
|
|
194
|
-
} catch (err) {
|
|
195
|
-
return unavailable("offerings_connection_unusable", err);
|
|
196
|
-
}
|
|
197
|
-
if (connection.kind !== "ok")
|
|
198
|
-
return unavailable("offerings_connection_unusable");
|
|
199
|
-
const { client, mode } = connection;
|
|
200
|
-
|
|
201
|
-
// 3. The rows (A4): org-enabled ∩ workspace-shown, never the snapshot.
|
|
202
|
-
let sellable: Awaited<ReturnType<typeof getSellableProductIds>>;
|
|
203
|
-
try {
|
|
204
|
-
sellable = await getSellableProductIds(workspaceId);
|
|
205
|
-
} catch (err) {
|
|
206
|
-
return unavailable("offerings_rows_failed", err);
|
|
207
|
-
}
|
|
208
|
-
if (sellable === null || sellable.productIds.length === 0) return unknown();
|
|
209
|
-
if (sellable.organizationId !== organizationId) {
|
|
210
|
-
return unavailable("offerings_rows_failed");
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 4. The live lookup (A4), on the connection's client.
|
|
214
|
-
let price: Awaited<ReturnType<typeof findActivePriceByLookupKey>>;
|
|
215
|
-
try {
|
|
216
|
-
price = await findActivePriceByLookupKey(client, productCode);
|
|
217
|
-
} catch (err) {
|
|
218
|
-
return unavailable("offerings_price_lookup_failed", err);
|
|
219
|
-
}
|
|
220
|
-
if (price === null) return unknown();
|
|
221
|
-
|
|
222
|
-
// 5. The intersection and the period (A4, G3): the Price's own Product
|
|
223
|
-
// must be sellable here — and, for a workspace saved per Price, the Price
|
|
224
|
-
// itself shown (#9070 D5) — and the one normalizer must keep this exact
|
|
225
|
-
// Price under this key and period.
|
|
226
|
-
const product: unknown = price.product;
|
|
227
|
-
if (typeof product !== "object" || product === null) return unknown();
|
|
228
|
-
const productId = (product as { id?: unknown }).id;
|
|
229
|
-
if (
|
|
230
|
-
typeof productId !== "string" ||
|
|
231
|
-
!sellable.productIds.includes(productId) ||
|
|
232
|
-
(sellable.priceIds !== null && !sellable.priceIds.includes(price.id))
|
|
233
|
-
) {
|
|
234
|
-
return unknown();
|
|
235
|
-
}
|
|
236
|
-
const offering = normalizeCatalog({
|
|
237
|
-
products: [product],
|
|
238
|
-
prices: [price],
|
|
239
|
-
}).offerings.find((o) => o.productId === productId);
|
|
240
|
-
const authorized = offering?.prices.some(
|
|
241
|
-
(p) =>
|
|
242
|
-
p.priceId === price.id &&
|
|
243
|
-
p.lookupKey === productCode &&
|
|
244
|
-
p.period === parsed.period,
|
|
245
|
-
);
|
|
246
|
-
if (!authorized) return unknown();
|
|
247
|
-
|
|
248
|
-
// 6. The sale (A6, A7): the authorized Price id, anonymous, stamped by the
|
|
249
|
-
// server, its mode from the period step 5 checked against the Price.
|
|
250
|
-
const metadata = { site: workspaceId, ailk_offering: productCode };
|
|
251
|
-
let created: { sessionId: string; url: string };
|
|
252
|
-
try {
|
|
253
|
-
created = await createCheckoutSessionForPrice(client, {
|
|
254
|
-
priceId: price.id,
|
|
255
|
-
mode:
|
|
256
|
-
parsed.period === lookupKeyPeriodSchema.enum.once
|
|
257
|
-
? "payment"
|
|
258
|
-
: "subscription",
|
|
259
|
-
successUrl: input.successUrl,
|
|
260
|
-
cancelUrl: input.cancelUrl,
|
|
261
|
-
quantity: input.quantity,
|
|
262
|
-
allowPromotionCodes: true,
|
|
263
|
-
customerCreation: "always",
|
|
264
|
-
metadata,
|
|
265
|
-
});
|
|
266
|
-
} catch (err) {
|
|
267
|
-
return unavailable("offerings_session_create_failed", err);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// 7. The record (A11), before the URL leaves the server. A failed record
|
|
271
|
-
// expires the session on the same client: an unrecorded session is one the
|
|
272
|
-
// webhook never fulfils, so no buyer may reach it.
|
|
273
|
-
const sessionIdPrefix = created.sessionId.substring(0, 8);
|
|
274
|
-
try {
|
|
275
|
-
await recordCheckoutSession({
|
|
276
|
-
sessionId: created.sessionId,
|
|
277
|
-
workspaceId,
|
|
278
|
-
organizationId,
|
|
279
|
-
priceId: price.id,
|
|
280
|
-
lookupKey: productCode,
|
|
281
|
-
mode,
|
|
282
|
-
});
|
|
283
|
-
} catch (err) {
|
|
284
|
-
logger.error(
|
|
285
|
-
{
|
|
286
|
-
workspaceId,
|
|
287
|
-
sessionIdPrefix,
|
|
288
|
-
reason: "offerings_record_failed",
|
|
289
|
-
errName: errName(err),
|
|
290
|
-
...(err instanceof CheckoutSessionRecordError && {
|
|
291
|
-
recordReason: err.reason,
|
|
292
|
-
}),
|
|
293
|
-
},
|
|
294
|
-
"[offerings] checkout session record failed; expiring the session",
|
|
295
|
-
);
|
|
296
|
-
try {
|
|
297
|
-
await expireCheckoutSessionWith(client, created.sessionId);
|
|
298
|
-
} catch (expireErr) {
|
|
299
|
-
logger.error(
|
|
300
|
-
{
|
|
301
|
-
workspaceId,
|
|
302
|
-
sessionIdPrefix,
|
|
303
|
-
reason: "offerings_expire_failed",
|
|
304
|
-
errName: errName(expireErr),
|
|
305
|
-
},
|
|
306
|
-
"[offerings] unrecorded checkout session could not be expired",
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
return UNAVAILABLE;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return { kind: "created", sessionId: created.sessionId, url: created.url };
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export type OfferingsSessionRead =
|
|
316
|
-
| { kind: "env" }
|
|
317
|
-
| { kind: "read"; session: RetrieveCheckoutSessionOutput }
|
|
318
|
-
| { kind: "unknown" }
|
|
319
|
-
| { kind: "unavailable" };
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Read a session the Offerings checkout recorded, with its record's own
|
|
323
|
-
* connection (D9, A13). No record is `env`; a record that cannot be read, or
|
|
324
|
-
* a connection that is gone, unusable or in another mode, is `unavailable` —
|
|
325
|
-
* never the env path. Never rejects.
|
|
326
|
-
*/
|
|
327
|
-
export async function readOfferingsCheckoutSession(
|
|
328
|
-
sessionId: string,
|
|
329
|
-
): Promise<OfferingsSessionRead> {
|
|
330
|
-
const sessionIdPrefix = sessionId.substring(0, 8);
|
|
331
|
-
|
|
332
|
-
let record: Awaited<ReturnType<typeof readCheckoutSessionRecord>>;
|
|
333
|
-
try {
|
|
334
|
-
record = await readCheckoutSessionRecord(sessionId);
|
|
335
|
-
} catch (err) {
|
|
336
|
-
logger.error(
|
|
337
|
-
{
|
|
338
|
-
sessionIdPrefix,
|
|
339
|
-
reason: "offerings_record_failed",
|
|
340
|
-
errName: errName(err),
|
|
341
|
-
},
|
|
342
|
-
"[offerings] checkout session record read failed",
|
|
343
|
-
);
|
|
344
|
-
return UNAVAILABLE;
|
|
345
|
-
}
|
|
346
|
-
if (record === null) return { kind: "env" };
|
|
347
|
-
if (record === "unreadable") return UNAVAILABLE;
|
|
348
|
-
|
|
349
|
-
const { organizationId } = record;
|
|
350
|
-
let connection: Awaited<ReturnType<typeof stripeClientForOrganization>>;
|
|
351
|
-
try {
|
|
352
|
-
connection = await stripeClientForOrganization(organizationId);
|
|
353
|
-
} catch (err) {
|
|
354
|
-
logger.error(
|
|
355
|
-
{
|
|
356
|
-
organizationId,
|
|
357
|
-
reason: "offerings_connection_unusable",
|
|
358
|
-
errName: errName(err),
|
|
359
|
-
},
|
|
360
|
-
"[offerings] checkout session read unavailable",
|
|
361
|
-
);
|
|
362
|
-
return UNAVAILABLE;
|
|
363
|
-
}
|
|
364
|
-
// C7: only the connection the session was made on, in that mode.
|
|
365
|
-
if (connection.kind !== "ok" || connection.mode !== record.mode) {
|
|
366
|
-
logger.error(
|
|
367
|
-
{ organizationId, reason: "offerings_connection_unusable" },
|
|
368
|
-
"[offerings] checkout session read unavailable",
|
|
369
|
-
);
|
|
370
|
-
return UNAVAILABLE;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
let session: RetrieveCheckoutSessionOutput;
|
|
374
|
-
try {
|
|
375
|
-
session = await retrieveCheckoutSession(
|
|
376
|
-
{ sessionId },
|
|
377
|
-
{ client: connection.client },
|
|
378
|
-
);
|
|
379
|
-
} catch (err) {
|
|
380
|
-
if (err instanceof UnknownSessionError) return UNKNOWN;
|
|
381
|
-
logger.error(
|
|
382
|
-
{
|
|
383
|
-
organizationId,
|
|
384
|
-
reason: "offerings_session_read_failed",
|
|
385
|
-
errName: errName(err),
|
|
386
|
-
},
|
|
387
|
-
"[offerings] checkout session read unavailable",
|
|
388
|
-
);
|
|
389
|
-
return UNAVAILABLE;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// The seller's Product names are Stripe text: served only as T1 bounds them.
|
|
393
|
-
return {
|
|
394
|
-
kind: "read",
|
|
395
|
-
session: {
|
|
396
|
-
...session,
|
|
397
|
-
lineItems: session.lineItems.map((line) => ({
|
|
398
|
-
...line,
|
|
399
|
-
description: normalizeText(line.description, OFFERINGS_NAME_MAX) ?? "",
|
|
400
|
-
})),
|
|
401
|
-
},
|
|
402
|
-
};
|
|
403
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Offerings Checkout session record (#8930, spec
|
|
3
|
-
* docs/specs/8930-the-offerings-checkout-session-record-spec.md D4, D5, D7;
|
|
4
|
-
* contract docs/specs/8879-the-offerings-security-contract-key-spec.md A11,
|
|
5
|
-
* W5, A13) — the ONE file naming the OfferingsCheckoutSession delegate.
|
|
6
|
-
*
|
|
7
|
-
* WRITE ONCE. The checkout route records `{ sessionId, workspaceId,
|
|
8
|
-
* organizationId, priceId, lookupKey, mode }` before it answers, and the
|
|
9
|
-
* per-Organization webhook and the session read act only on a session
|
|
10
|
-
* recorded here — never on metadata alone. One `create` and one `findUnique`
|
|
11
|
-
* by Checkout Session id; nothing here updates, upserts, deletes or lists, so
|
|
12
|
-
* no record is rebound to another workspace and none is enumerated.
|
|
13
|
-
*
|
|
14
|
-
* `null` means one thing: the database holds no row for that id (or the id
|
|
15
|
-
* has a shape no row can hold). A database error rejects `read_failed`, and a
|
|
16
|
-
* row that fails the parse answers `"unreadable"` — neither is ever `null`,
|
|
17
|
-
* because a caller that read `null` would treat a paid session as foreign.
|
|
18
|
-
*
|
|
19
|
-
* ERRORS AND LOGGING (D7, contract T6). A `CheckoutSessionRecordError` carries
|
|
20
|
-
* a fixed message per reason and no `cause`, so no Prisma or Postgres text — a
|
|
21
|
-
* CHECK violation prints the failing row — reaches a log line or an HTTP
|
|
22
|
-
* error. Logs carry only a fixed `reason` (and the workspace on a failed
|
|
23
|
-
* write); never a session id, Price id, lookup key or mode.
|
|
24
|
-
*/
|
|
25
|
-
import { idRegex } from "@working-theory/ids";
|
|
26
|
-
import { logger } from "@working-theory/observability";
|
|
27
|
-
import {
|
|
28
|
-
offeringsStripeIdSchema,
|
|
29
|
-
parseLookupKey,
|
|
30
|
-
} from "@working-theory/validation";
|
|
31
|
-
import { z } from "zod";
|
|
32
|
-
|
|
33
|
-
import { prisma } from "../../lib/prisma.js";
|
|
34
|
-
import { isUniqueViolation } from "../default-tenant.js";
|
|
35
|
-
|
|
36
|
-
import { organizationIdOfWorkspace } from "./store.js";
|
|
37
|
-
|
|
38
|
-
export type CheckoutSessionMode = "test" | "live";
|
|
39
|
-
|
|
40
|
-
export interface CheckoutSessionRecordInput {
|
|
41
|
-
sessionId: string;
|
|
42
|
-
workspaceId: string;
|
|
43
|
-
organizationId: string;
|
|
44
|
-
priceId: string;
|
|
45
|
-
lookupKey: string;
|
|
46
|
-
mode: CheckoutSessionMode;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface CheckoutSessionRecord extends CheckoutSessionRecordInput {
|
|
50
|
-
createdAt: Date;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type CheckoutSessionRecordFailure =
|
|
54
|
-
| "invalid"
|
|
55
|
-
| "workspace_mismatch"
|
|
56
|
-
| "conflict"
|
|
57
|
-
| "write_failed"
|
|
58
|
-
| "read_failed";
|
|
59
|
-
|
|
60
|
-
const FAILURE_MESSAGES: Record<CheckoutSessionRecordFailure, string> = {
|
|
61
|
-
invalid: "The Checkout session record is malformed",
|
|
62
|
-
workspace_mismatch:
|
|
63
|
-
"The Checkout session record names a workspace outside its Organization",
|
|
64
|
-
conflict: "The Checkout session is already recorded",
|
|
65
|
-
write_failed: "The Checkout session record could not be written",
|
|
66
|
-
read_failed: "The Checkout session record could not be read",
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export class CheckoutSessionRecordError extends Error {
|
|
70
|
-
readonly reason: CheckoutSessionRecordFailure;
|
|
71
|
-
|
|
72
|
-
constructor(reason: CheckoutSessionRecordFailure) {
|
|
73
|
-
super(FAILURE_MESSAGES[reason]);
|
|
74
|
-
this.name = "CheckoutSessionRecordError";
|
|
75
|
-
this.reason = reason;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** A Checkout Session id: the migration's `_session_id_format` CHECK body. */
|
|
80
|
-
const SESSION_ID_PATTERN = /^cs_(test|live)_[A-Za-z0-9]{1,250}$/;
|
|
81
|
-
|
|
82
|
-
/** Every column the migration CHECKs, mirrored so a refused write never reaches Postgres. */
|
|
83
|
-
const recordShape = {
|
|
84
|
-
sessionId: z.string().regex(SESSION_ID_PATTERN),
|
|
85
|
-
workspaceId: z.string().regex(idRegex("workspace")),
|
|
86
|
-
organizationId: z.string().regex(idRegex("organization")),
|
|
87
|
-
priceId: offeringsStripeIdSchema,
|
|
88
|
-
lookupKey: z.string().refine((key) => parseLookupKey(key) !== null),
|
|
89
|
-
mode: z.enum(["test", "live"]),
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/** The `_session_mode_agree` CHECK: a live key makes a `cs_live_` session. */
|
|
93
|
-
const modeAgrees = (record: {
|
|
94
|
-
sessionId: string;
|
|
95
|
-
mode: CheckoutSessionMode;
|
|
96
|
-
}): boolean =>
|
|
97
|
-
(record.mode === "live") === record.sessionId.startsWith("cs_live_");
|
|
98
|
-
|
|
99
|
-
const recordInputSchema = z.object(recordShape).strict().refine(modeAgrees);
|
|
100
|
-
const recordRowSchema = z
|
|
101
|
-
.object({ ...recordShape, createdAt: z.date() })
|
|
102
|
-
.strict()
|
|
103
|
-
.refine(modeAgrees);
|
|
104
|
-
|
|
105
|
-
/** The record's columns, never its `id`. */
|
|
106
|
-
const RECORD_SELECT = {
|
|
107
|
-
sessionId: true,
|
|
108
|
-
workspaceId: true,
|
|
109
|
-
organizationId: true,
|
|
110
|
-
priceId: true,
|
|
111
|
-
lookupKey: true,
|
|
112
|
-
mode: true,
|
|
113
|
-
createdAt: true,
|
|
114
|
-
} as const;
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Record one Checkout Session, once, for a workspace of the named
|
|
118
|
-
* Organization. Rejects `invalid` (a malformed input), `workspace_mismatch`
|
|
119
|
-
* (no such workspace, or another Organization's), `conflict` (the session is
|
|
120
|
-
* already recorded; the first record stands) or `write_failed`.
|
|
121
|
-
*/
|
|
122
|
-
export async function recordCheckoutSession(
|
|
123
|
-
input: CheckoutSessionRecordInput,
|
|
124
|
-
): Promise<void> {
|
|
125
|
-
const parsed = recordInputSchema.safeParse(input);
|
|
126
|
-
if (!parsed.success) throw new CheckoutSessionRecordError("invalid");
|
|
127
|
-
const data = parsed.data;
|
|
128
|
-
|
|
129
|
-
let organizationId: string | null;
|
|
130
|
-
try {
|
|
131
|
-
organizationId = await organizationIdOfWorkspace(data.workspaceId);
|
|
132
|
-
} catch {
|
|
133
|
-
throw writeFailed(data.workspaceId);
|
|
134
|
-
}
|
|
135
|
-
if (organizationId !== data.organizationId) {
|
|
136
|
-
throw new CheckoutSessionRecordError("workspace_mismatch");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
await prisma.offeringsCheckoutSession.create({
|
|
141
|
-
data,
|
|
142
|
-
select: { sessionId: true },
|
|
143
|
-
});
|
|
144
|
-
} catch (err) {
|
|
145
|
-
if (isUniqueViolation(err))
|
|
146
|
-
throw new CheckoutSessionRecordError("conflict");
|
|
147
|
-
throw writeFailed(data.workspaceId);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* The record of one Checkout Session: the record, `"unreadable"` for a row
|
|
153
|
-
* that fails the parse, or `null` when no row holds that id. Rejects
|
|
154
|
-
* `read_failed` when the database read fails.
|
|
155
|
-
*/
|
|
156
|
-
export async function readCheckoutSessionRecord(
|
|
157
|
-
sessionId: unknown,
|
|
158
|
-
): Promise<CheckoutSessionRecord | "unreadable" | null> {
|
|
159
|
-
if (typeof sessionId !== "string" || !SESSION_ID_PATTERN.test(sessionId))
|
|
160
|
-
return null;
|
|
161
|
-
|
|
162
|
-
let row: unknown;
|
|
163
|
-
try {
|
|
164
|
-
row = await prisma.offeringsCheckoutSession.findUnique({
|
|
165
|
-
where: { sessionId },
|
|
166
|
-
select: RECORD_SELECT,
|
|
167
|
-
});
|
|
168
|
-
} catch {
|
|
169
|
-
logger.error(
|
|
170
|
-
{ reason: "checkout_record_read_failed" },
|
|
171
|
-
"[offerings] checkout session record read failed",
|
|
172
|
-
);
|
|
173
|
-
throw new CheckoutSessionRecordError("read_failed");
|
|
174
|
-
}
|
|
175
|
-
if (row === null) return null;
|
|
176
|
-
|
|
177
|
-
const record = recordRowSchema.safeParse(row);
|
|
178
|
-
if (record.success) return record.data;
|
|
179
|
-
logger.error(
|
|
180
|
-
{ reason: "checkout_record_unreadable" },
|
|
181
|
-
"[offerings] checkout session record is unreadable",
|
|
182
|
-
);
|
|
183
|
-
return "unreadable";
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function writeFailed(workspaceId: string): CheckoutSessionRecordError {
|
|
187
|
-
logger.error(
|
|
188
|
-
{ workspaceId, reason: "checkout_record_write_failed" },
|
|
189
|
-
"[offerings] checkout session record write failed",
|
|
190
|
-
);
|
|
191
|
-
return new CheckoutSessionRecordError("write_failed");
|
|
192
|
-
}
|