create-ailk 0.23.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -33
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +10 -3
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +38 -4
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +75 -22
- 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 +1 -1
- package/dist/lib/fetch-template-snapshot.js +7 -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 +34 -12
- package/dist/lib/scaffold-render-sweep.d.ts +33 -0
- package/dist/lib/scaffold-render-sweep.js +45 -0
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +122 -538
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/surfaces.js +10 -2
- package/dist/sync-routes.d.ts +30 -4
- package/dist/sync-routes.js +271 -18
- package/dist/template-record.d.ts +14 -4
- package/dist/template-record.js +23 -5
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +31 -19
- package/dist/update.d.ts +10 -1
- package/dist/update.js +540 -16
- package/package.json +3 -3
- package/release-scope.json +1 -1
- 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 +9 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +15 -3
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +9 -2
- package/templates/apps/api/CLAUDE.md +122 -82
- 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.ts +7 -4
- 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 -1857
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +77 -24
- 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 -12
- 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 +21 -5
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +14 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/intake-script.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 +28 -7
- 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 +2 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +7 -0
- package/templates/apps/web/lib/auth-email.ts +1 -1
- package/templates/apps/web/lib/magnet-routes.ts +3 -1
- package/templates/apps/web/lib/newsletter.actions.ts +9 -6
- 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/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 +32 -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 +757 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +48 -35
- 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 -292
- 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 -254
- 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 -318
- 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 -264
- 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 -1815
- 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 -325
- 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 -1300
- 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 -268
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -95
- 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 -129
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- 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 -573
- 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 -480
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -465
- 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 -448
- 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 -808
- 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 -2026
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -528
- 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 -867
- 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 -1176
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -444
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -154
- 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 -313
- 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 -755
- 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 -295
- 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 -868
- 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 -396
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -681
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -308
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -385
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1003
- 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 -1188
- 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 -2073
- 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 -1151
- 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 -65
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -187
- 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 -165
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -228
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -135
- 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__/read_content.test.ts +0 -474
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -225
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -256
- 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 -307
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -223
- 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 -342
- 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 -526
- 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 -142
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -139
- 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/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 -64
- 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 -137
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -74
- 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 -75
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -59
- package/templates/apps/mcp/src/tools/search_content.ts +0 -67
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -57
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -961
- 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 -240
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- 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/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,398 +0,0 @@
|
|
|
1
|
-
# Webhooks — `apps/api/src/routes/webhooks/`
|
|
2
|
-
|
|
3
|
-
Stripe webhook receiver at `POST /v1/webhooks/stripe`. Verifies
|
|
4
|
-
signatures, dispatches events via a `switch`, and fires server-side
|
|
5
|
-
telemetry. Handles `customer.created`, `checkout.session.completed`, and
|
|
6
|
-
`checkout.session.expired` (the last two also stamp the recommender's
|
|
7
|
-
buyer row — below).
|
|
8
|
-
|
|
9
|
-
## Commerce claim — signature first, claim second (D-64, #5796)
|
|
10
|
-
|
|
11
|
-
The receiver is a base-tree **primitive**: it registers on every build, with
|
|
12
|
-
no `isModuleEnabled("commerce")` gate, and decides whether to act on the JWT
|
|
13
|
-
`features[]` claim `commerce` (`lib/require-commerce-claim.ts`, read at call
|
|
14
|
-
time through `checkLicense()`). The order inside `stripe.ts` is fixed and
|
|
15
|
-
load-bearing: **the Stripe signature is verified BEFORE the claim is
|
|
16
|
-
consulted.** An unsigned body therefore never reaches the claim check on its
|
|
17
|
-
own merits, and the two unclaimed outcomes are:
|
|
18
|
-
|
|
19
|
-
- **Signed, verifiable event, licence read and positively unclaimed** →
|
|
20
|
-
**200 `{received: true}`**, the event dropped with a structured `warn` log
|
|
21
|
-
line (`outcome: "dropped_no_commerce_claim"`). A 4xx here would have Stripe
|
|
22
|
-
retry the event for days against a site that turned commerce off. It is
|
|
23
|
-
logged at `warn`, not `info`: a discarded Stripe event is never routine.
|
|
24
|
-
- **Missing secret / non-Buffer body / missing or invalid signature, licence
|
|
25
|
-
read and unclaimed** → the **module-disabled 404**, byte-identical to a
|
|
26
|
-
build that never carried the commerce module. A site without the claim is
|
|
27
|
-
indistinguishable from a site without the module, which is the point.
|
|
28
|
-
|
|
29
|
-
With the claim, every mapping in the § above is exactly what it was before
|
|
30
|
-
the gate existed.
|
|
31
|
-
|
|
32
|
-
### The claim is a TRI-state, and `undetermined` is never a drop
|
|
33
|
-
|
|
34
|
-
Security review (2026-09-16, High) against the first cut of this gate: a
|
|
35
|
-
boolean `hasCommerceClaim()` returned one `false` both for "the licence says
|
|
36
|
-
no" and for "the licence could not be read". On the second, a **live** commerce
|
|
37
|
-
site whose read merely failed would ack a signed `checkout.session.completed`
|
|
38
|
-
200 and lose it — Stripe records success, never redelivers, and the charge is
|
|
39
|
-
never mapped to a customer. The pre-#5796 module flag was build-time and its
|
|
40
|
-
off state 404'd, which Stripe retried; the runtime gate introduced the loss.
|
|
41
|
-
|
|
42
|
-
So `resolveCommerceClaim()` answers `claimed | unclaimed | undetermined`, and
|
|
43
|
-
**`undetermined` → 503 with `Retry-After: 300`**, which makes Stripe queue the
|
|
44
|
-
event and redeliver it once the read succeeds — well inside Stripe's three-day
|
|
45
|
-
redelivery window. The drop and the hold are both logged at `warn`
|
|
46
|
-
(`outcome: "dropped_no_commerce_claim"` and `outcome: "license_undetermined"`):
|
|
47
|
-
neither a discarded nor a held Stripe event is routine.
|
|
48
|
-
|
|
49
|
-
`undetermined` covers every read that established no "no" (operator-disposed,
|
|
50
|
-
round 2):
|
|
51
|
-
|
|
52
|
-
- **`checkLicense()` threw.**
|
|
53
|
-
- **A revoked licence** (`revoked === true`). It keeps verifying locally until
|
|
54
|
-
JWT `exp` (ADR 0014), so this is a paying commerce site mid-window whose
|
|
55
|
-
events are still real. Closing the gate is right; dropping the events is the
|
|
56
|
-
review's High under another label.
|
|
57
|
-
- **A licence is configured yet resolves free** — the expired case, and any
|
|
58
|
-
other JWT that no longer verifies. `checkLicense()` returns the _same_
|
|
59
|
-
`FREE_RESULT` for expired, invalid and keyless alike, so the only signal that
|
|
60
|
-
separates them is whether a licence is CONFIGURED at all. The gate tests that
|
|
61
|
-
the same way `check-license.ts` resolves a token — `AILK_LICENSE_KEY`, else
|
|
62
|
-
the refresh storage file — for presence only: it parses no JWT and
|
|
63
|
-
re-implements no verification. Reading the storage file matters, because a
|
|
64
|
-
deployment can be fully licensed with no env key at all; an env-only test
|
|
65
|
-
read such a site as keyless, so its EXPIRED licence dropped already-charged
|
|
66
|
-
events.
|
|
67
|
-
- **The licence storage file is UNREADABLE** (#5820) — EACCES, EIO, EMFILE, a
|
|
68
|
-
directory at its path, a filesystem that went away. See below.
|
|
69
|
-
|
|
70
|
-
`unclaimed` is therefore reserved for a positively-established "no": a licence
|
|
71
|
-
that reads successfully and simply lacks the claim, or a keyless deployment
|
|
72
|
-
that configures no licence at all — where "no licence at all" now means the
|
|
73
|
-
storage file was positively read as ABSENT, not merely that reading it failed.
|
|
74
|
-
|
|
75
|
-
### `unreadable` is not `absent` (#5820)
|
|
76
|
-
|
|
77
|
-
The gate reads the storage file through `readStoredJwtState()`
|
|
78
|
-
(`packages/license/src/storage.ts`), which answers **`absent` · `unreadable` ·
|
|
79
|
-
`present`**:
|
|
80
|
-
|
|
81
|
-
- **`absent`** — ENOENT, or a file holding nothing but whitespace. The only
|
|
82
|
-
state that is evidence of an unlicensed site, and therefore the only one on
|
|
83
|
-
this path that may drop a signed event.
|
|
84
|
-
- **`unreadable`** — every other read failure. Evidence of nothing, so it
|
|
85
|
-
resolves `undetermined` → **503 + `Retry-After: 300`**, logged with the
|
|
86
|
-
errno `code` (`outcome: "license_storage_unreadable"`).
|
|
87
|
-
- **`present`** — the token; resolution continues exactly as for an env key.
|
|
88
|
-
|
|
89
|
-
This closes the last gap in the tri-state. `readStoredJwt()` catches every read
|
|
90
|
-
error and returns the same `null` it returns for "no file", so before this a
|
|
91
|
-
transient EACCES on a storage-licensed site read as keyless: a signed
|
|
92
|
-
`checkout.session.completed` in that window was acked 200, Stripe never
|
|
93
|
-
redelivered it, and the charge was never mapped. `readStoredJwt()`'s own
|
|
94
|
-
contract is unchanged — `checkLicense()` and every other caller still see one
|
|
95
|
-
`null` for both states — so only the gate's resolution moved.
|
|
96
|
-
|
|
97
|
-
**The checkout launcher does not hold.** It answers the module-disabled 404 on
|
|
98
|
-
both `unclaimed` and `undetermined`: a checkout that cannot be started loses
|
|
99
|
-
nothing, so there is no call to leak a "configured but unreadable" shape on it.
|
|
100
|
-
The webhook holds because by the time an event arrives the buyer's money has
|
|
101
|
-
already moved.
|
|
102
|
-
|
|
103
|
-
## How to add a new event handler
|
|
104
|
-
|
|
105
|
-
Add a `case` arm to the `switch` in `stripe.ts`:
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
case "invoice.payment_succeeded": {
|
|
109
|
-
const invoice = event.data.object as { ... };
|
|
110
|
-
await fireTelemetryWithTimeout(request, "invoice-paid", { ... });
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Customer forks edit `stripe.ts` directly. v1 ships no pluggable handler
|
|
116
|
-
registry — if multiple forks request one, file an issue.
|
|
117
|
-
|
|
118
|
-
## Recommender purchase stamp (#4838)
|
|
119
|
-
|
|
120
|
-
A Checkout Session the recommender opened (`POST /v1/flows/:slug/checkout`,
|
|
121
|
-
#4841) carries `metadata.flowCheckoutId` — the `FlowCheckout` buyer row's
|
|
122
|
-
id. On `checkout.session.completed` the handler stamps that row
|
|
123
|
-
`status: "completed"` (+ `completedAt`, `stripeSessionId`,
|
|
124
|
-
`stripeCustomerId`); on `checkout.session.expired` it stamps
|
|
125
|
-
`status: "expired"`. Both are ONE guarded update —
|
|
126
|
-
`updateMany({ where: { id, status: "created" } })` — so the stamp is
|
|
127
|
-
at-most-once regardless of the in-memory store below: a replay, a restart
|
|
128
|
-
between deliveries, or a late `expired` after a `completed` matches zero
|
|
129
|
-
rows and logs `outcome: "stale_or_unknown"`. A charged `amount_total` that
|
|
130
|
-
differs from the quoted `amountMinor` is logged (`expected` / `received`)
|
|
131
|
-
and the row is still stamped — the quote is never overwritten. The stamp
|
|
132
|
-
runs beside the telemetry + billing tasks, never instead of them, and an
|
|
133
|
-
event without the key never touches `flowCheckout`. Always 200.
|
|
134
|
-
|
|
135
|
-
## Site filter (#7868)
|
|
136
|
-
|
|
137
|
-
The hub's Stripe account is shared with its spoke sites, so this endpoint
|
|
138
|
-
receives every site's events. A Checkout session says which site opened it
|
|
139
|
-
through `metadata.site`.
|
|
140
|
-
|
|
141
|
-
- **The stamp.** Every session the hub creates — `POST /v1/checkout/sessions`
|
|
142
|
-
and the recommender's quoted checkout (`POST /v1/flows/:slug/checkout`) —
|
|
143
|
-
carries `metadata.site` = `currentSiteId()`: `AILK_SITE_ID`, else
|
|
144
|
-
`"default"`. The key, the stamp and the foreign test are defined once in
|
|
145
|
-
`lib/site.ts` (`SITE_STAMP_KEY`, `siteStamp()`, `isForeignSiteStamp()`).
|
|
146
|
-
No request field can set or override it.
|
|
147
|
-
- **The filter.** A `checkout.session.completed` or `checkout.session.expired`
|
|
148
|
-
whose `metadata.site` is a non-empty string other than `currentSiteId()` is
|
|
149
|
-
another site's. The handler takes no action — no telemetry, billing bind,
|
|
150
|
-
recommender stamp or request-paid stamp — logs `outcome: "foreign_site"`
|
|
151
|
-
with the foreign `stamp` (first 64 characters) and the hub's `ownSite`, and
|
|
152
|
-
answers 200. The comparison is exact and case-sensitive. The event is still
|
|
153
|
-
marked seen, so a redelivery is the usual duplicate short-circuit.
|
|
154
|
-
- **Unstamped is own.** A session with no stamp, or an empty one, is the hub's
|
|
155
|
-
and is handled as before: hub Payment Links, hosted invoices, portal events
|
|
156
|
-
and sessions opened before the stamp shipped carry none, so strict equality
|
|
157
|
-
would stop hub checkout (roadmap D4).
|
|
158
|
-
- **One alias per deployment.** Two deployments on one Stripe account that
|
|
159
|
-
both leave `AILK_SITE_ID` unset both stamp `"default"` and treat each
|
|
160
|
-
other's sessions as their own — give each a distinct `AILK_SITE_ID`. The
|
|
161
|
-
alias is a deployment-wide constant: every instance and environment on the
|
|
162
|
-
account must agree on it, and change it only while no Checkout session is
|
|
163
|
-
open (a session lives up to 24 hours). A session whose stamp no longer
|
|
164
|
-
matches is acked as foreign and never retried, so its hub side effects are
|
|
165
|
-
lost; the `stamp` / `ownSite` pair in the log line is how to spot that drift.
|
|
166
|
-
A subscription keeps its stamp for its whole life and each finalized
|
|
167
|
-
invoice keeps an immutable copy of it, so a slug collision or a mis-stamp
|
|
168
|
-
on those lasts as long as the subscription — not the 24 hours a session
|
|
169
|
-
lives.
|
|
170
|
-
- **Billing events (#8024).** One gate before the event switch applies the
|
|
171
|
-
same test to every `customer.*`, `customer.subscription.*` and `invoice.*`
|
|
172
|
-
event, handled type or not. It reads the stamp from the event object's own
|
|
173
|
-
metadata — the Subscription's (where spoke-checkout writes `site` through
|
|
174
|
-
`subscription_data.metadata`) or the Customer's — and, for an invoice, from
|
|
175
|
-
its own metadata, then the subscription snapshot under
|
|
176
|
-
`parent.subscription_details.metadata` (the pinned API version), then the
|
|
177
|
-
top-level `subscription_details.metadata` (an endpoint on a pre-`basil`
|
|
178
|
-
version). Any foreign source makes the event foreign; the first is logged.
|
|
179
|
-
A source is read only when `data.object.object` is the family's own type
|
|
180
|
-
(`subscription`, `customer`, `invoice`), so a `customer.source.*` card or a
|
|
181
|
-
`customer.tax_id.*` object is never read. A foreign event answers 200,
|
|
182
|
-
logs `outcome: "foreign_site"` (the same five fields), writes nothing — no
|
|
183
|
-
billing bind, request-paid stamp or dunning record — and is marked seen.
|
|
184
|
-
The decision uses the payload alone: no Stripe call.
|
|
185
|
-
- **What stays unstamped.** The filter cannot see a stamp nobody wrote, so
|
|
186
|
-
these are still handled as the hub's own, exactly as before: spoke
|
|
187
|
-
customers (Checkout cannot put metadata on a customer it creates); quoted
|
|
188
|
-
spoke subscriptions and their invoices (`createQuotedCheckoutSession`
|
|
189
|
-
sends no `subscription_data` yet); a fixed spoke subscription whose `site`
|
|
190
|
-
was later removed; spoke subscriptions created before the stamp shipped,
|
|
191
|
-
or through a Payment Link or the Dashboard; and the billing app's sessions
|
|
192
|
-
and subscriptions on the shared account. Where such a subscription sits on
|
|
193
|
-
a hub User's own Stripe customer (a known buyer reused by a spoke), its
|
|
194
|
-
events keep reaching that User's dunning record.
|
|
195
|
-
- **A label, not a boundary.** The stamp is written by whoever holds the
|
|
196
|
-
account's secret key. It separates cooperating sites on one account, not
|
|
197
|
-
tenants. Whoever holds a write key on the account can also make the hub
|
|
198
|
-
skip one of its own objects' billing events by stamping it with another
|
|
199
|
-
site, which silences the hub's dunning record for that subscription — the
|
|
200
|
-
same key can already cancel or re-price it outright.
|
|
201
|
-
|
|
202
|
-
## Per-Organization endpoint (#8932)
|
|
203
|
-
|
|
204
|
-
`POST /v1/webhooks/stripe/:connectionToken` (`stripe-offerings.ts`) is the
|
|
205
|
-
receiver for one Organization's own Stripe connection — the Offerings money
|
|
206
|
-
path (spec `docs/specs/8932-the-per-organization-stripe-webhook-spec.md`,
|
|
207
|
-
contract `docs/specs/8879-the-offerings-security-contract-key-spec.md` W1,
|
|
208
|
-
W3–W11, G5, T8). The 43-character token selects one `StripeConnection` row
|
|
209
|
-
(`findStripeConnectionByWebhookToken`, #8907); that row's decrypted secret
|
|
210
|
-
verifies the signature through `verifyWebhookSignatureKeyless` (no env key),
|
|
211
|
-
and its `organizationId` is the only Organization the event can act for.
|
|
212
|
-
Registered beside the legacy route on every build, with its own 100/min
|
|
213
|
-
per-IP bucket; the commerce claim is read at call time.
|
|
214
|
-
|
|
215
|
-
**The ladder** — the first step that answers ends the request; every `200`
|
|
216
|
-
body is `{ received: true }` and the outcome is the log line's only:
|
|
217
|
-
|
|
218
|
-
| # | Step | Answer | Seen |
|
|
219
|
-
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------- |
|
|
220
|
-
| 1 | the token resolves to no row (or is malformed) | `404 not-found`, logs nothing | no |
|
|
221
|
-
| 1a | the token lookup rejects | `503 webhook-unavailable` | no |
|
|
222
|
-
| 2 | the row has no usable secret | `503 webhook-not-configured` | no |
|
|
223
|
-
| 3–6 | signature settled, then the claim (as the legacy route): `undetermined` → `503` + `Retry-After: 300`; `unclaimed` → signed `200 dropped_no_commerce_claim`, unsigned the module-disabled `404`; then the held `400` | as stated | no |
|
|
224
|
-
| 7 | `event.livemode` is not the row's mode | `200 livemode_mismatch` | no |
|
|
225
|
-
| 8 | not `checkout.session.completed` / `.async_payment_succeeded` on a session object | `200 ignored_type` | no |
|
|
226
|
-
| 9–11 | the hub's session record (#8930): rejects / unreadable / none | `503 record-unavailable` / `500 record-unreadable` / `200 unrecorded` | no |
|
|
227
|
-
| 12 | another Organization's record, or `metadata.site` is not the record's workspace | `200 foreign_organization` | no |
|
|
228
|
-
| 13 | the record's mode is not the row's | `200 livemode_mismatch` | no |
|
|
229
|
-
| 14 | already seen for this Organization | `200 duplicate` | — |
|
|
230
|
-
| 15 | a `completed` session neither `paid` nor `no_payment_required` | `200 unpaid` | yes |
|
|
231
|
-
| 16 | the connection's client or its `listLineItems` call fails | `503 stripe-unavailable` (never the env key) | no |
|
|
232
|
-
| 17 | not exactly one line item whose Price id is the record's | `200 offering_mismatch` | yes |
|
|
233
|
-
| 18 | `routeLead` (below) | `200 fulfilled` / `already_fulfilled` / `unfulfillable`, else `503 fulfilment-unavailable` | yes / no |
|
|
234
|
-
|
|
235
|
-
**The lead.** One per session: `routeLead` with the idempotency key
|
|
236
|
-
`"stripe-offerings " + sessionId` — a space, which no `Idempotency-Key`
|
|
237
|
-
header can carry, so no anonymous lead can take the key first — and
|
|
238
|
-
`offeringsCheckout: true`. Its offering is the record's FULL lookup key,
|
|
239
|
-
never `metadata.ailk_offering`. A live session's lead is marked
|
|
240
|
-
`spokeCheckout`, the hold #8793's `releaseHeldCheckoutPlan` releases at the
|
|
241
|
-
buyer's verified first link; no path here writes `Person.planOffering`. A
|
|
242
|
-
test-mode session's lead is written unmarked: no hold, purchase message,
|
|
243
|
-
email or consent grant. A replay or a conflict on that key is
|
|
244
|
-
`already_fulfilled`; a lead the database did not take is a `503`, so Stripe
|
|
245
|
-
retries.
|
|
246
|
-
|
|
247
|
-
**The legacy endpoint steps aside (W8).** `POST /v1/webhooks/stripe` acks
|
|
248
|
-
any event carrying an `ailk_offering` key — in a session's metadata or any
|
|
249
|
-
site-stamp source above — with `200 { received: true }` and
|
|
250
|
-
`outcome: "offerings_path"`, **before** `markSeen`, and writes nothing.
|
|
251
|
-
Every event without the key takes the legacy path unchanged.
|
|
252
|
-
|
|
253
|
-
**Two dedup stores.** The legacy route keeps `hasSeen` / `markSeen`; the
|
|
254
|
-
per-Organization route uses its own `hasSeenForOrganization` /
|
|
255
|
-
`markSeenForOrganization`, keyed `<organizationId>:<eventId>`. Neither
|
|
256
|
-
endpoint's store can swallow the other's event. Both are in-memory per
|
|
257
|
-
instance; the per-Organization route's correctness rests on the lead's
|
|
258
|
-
database key.
|
|
259
|
-
|
|
260
|
-
**Logs.** Each line carries `organizationId`, `eventType`, an 8-character
|
|
261
|
-
`eventIdPrefix` and `outcome` (fewer before an event is verified) — never
|
|
262
|
-
the token, the secret, a full Stripe id, an email, a lookup key, a metadata
|
|
263
|
-
value or Stripe product text. Request and error lines log the path with
|
|
264
|
-
the token replaced whole by `:connectionToken` (`redactLogPath`).
|
|
265
|
-
|
|
266
|
-
**W11.** No billing identity is bound from a connection's event.
|
|
267
|
-
|
|
268
|
-
## Dunning (#6034)
|
|
269
|
-
|
|
270
|
-
Two arms record a per-subscriber lifecycle state so a failed renewal is no
|
|
271
|
-
longer silent:
|
|
272
|
-
|
|
273
|
-
- **`invoice.payment_failed`** — arms dunning for the `User` matched by the
|
|
274
|
-
invoice's Stripe customer id: the recorded state becomes `past_due`,
|
|
275
|
-
`dunningArmedAt` is stamped once (never moved while the subscription stays
|
|
276
|
-
failing), `accessState` stays `granted`. **This arm never revokes.**
|
|
277
|
-
- **`customer.subscription.updated`** — maps the narrowed Stripe status
|
|
278
|
-
(`services/dunning-policy.ts`'s reuse of
|
|
279
|
-
`@working-theory/stripe-client`'s `toSubscriptionLifecycleStatus`) onto the
|
|
280
|
-
same record. `active`/`trialing` return the entry to a clean state
|
|
281
|
-
(`dunningArmedAt: null`, `accessRevokesAt: null`) so a recovered
|
|
282
|
-
subscriber's read shows nothing. `past_due`/`unpaid` arm exactly as
|
|
283
|
-
`invoice.payment_failed` does. `canceled` is the **only revoking
|
|
284
|
-
transition** — access is revoked once a per-tenant grace period, computed
|
|
285
|
-
from the FIRST failure, has already passed; if it hasn't, access is
|
|
286
|
-
retained and a revocation is scheduled.
|
|
287
|
-
|
|
288
|
-
### The record
|
|
289
|
-
|
|
290
|
-
One `UserData` row per subscriber (`userId_key` unique), reserved key
|
|
291
|
-
`commerce.subscription_dunning`, holding a MAP keyed by Stripe subscription
|
|
292
|
-
id — one customer can hold more than one subscription, and a flat blob would
|
|
293
|
-
let a second subscription's `active` clobber the first's `past_due`:
|
|
294
|
-
|
|
295
|
-
```jsonc
|
|
296
|
-
{
|
|
297
|
-
"v": 1,
|
|
298
|
-
"subscriptions": {
|
|
299
|
-
"sub_1AbC…": {
|
|
300
|
-
"status": "past_due", // "past_due" | "active" | "canceled"
|
|
301
|
-
"accessState": "granted", // "granted" | "revoked"
|
|
302
|
-
"dunningArmedAt": "2026-09-18T04:12:00.000Z", // ISO-8601, or null
|
|
303
|
-
"accessRevokesAt": null, // ISO-8601, or null — set only on `canceled`
|
|
304
|
-
"lastEventAt": 1789000000, // Stripe event.created, seconds
|
|
305
|
-
"lastEventId": "evt_1XyZ…",
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
}
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Capped at 50 subscriptions per customer, evicting the oldest SETTLED entries
|
|
312
|
-
(`status: "active"`, `accessState: "granted"`) first — an entry still being
|
|
313
|
-
dunned is never evicted mid-decision.
|
|
314
|
-
|
|
315
|
-
**No scheduler flips this.** `accessRevokesAt` is a lazily-evaluated
|
|
316
|
-
deadline; nothing wakes up to act on it. Two exported read-side predicates
|
|
317
|
-
(`services/dunning-policy.ts`) are the reader's contract instead:
|
|
318
|
-
|
|
319
|
-
- `isSubscriberRevoked(entry, now?)` — access is gone: either already stamped
|
|
320
|
-
`revoked`, or `now` has passed the scheduled `accessRevokesAt`.
|
|
321
|
-
- `isSubscriberActionRequired(entry, now?)` — the buyer must act (#6038's
|
|
322
|
-
banner predicate). Deliberately WIDER than `status === "past_due"`: a
|
|
323
|
-
subscription cancelled with its grace window still open
|
|
324
|
-
(`status: "canceled"`, `accessState: "granted"`, `accessRevokesAt`
|
|
325
|
-
non-null) is a buyer about to lose access who hasn't been told.
|
|
326
|
-
|
|
327
|
-
**Ordering, ties, and durable replay.** Every write is a compare-and-swap
|
|
328
|
-
whose `where` restates BOTH the observed `updatedAt` AND the observed
|
|
329
|
-
`value` — a concurrent writer makes the predicate miss rather than silently
|
|
330
|
-
overwrite. The SAME `event.id` replayed (even after a process restart, when
|
|
331
|
-
the in-memory `hasSeen` map has forgotten it) is a no-op; a strictly OLDER
|
|
332
|
-
event is stale; an EQUAL-timestamp write that would move the entry toward
|
|
333
|
-
LESS restriction (setting `status` to `active`, clearing `accessRevokesAt`,
|
|
334
|
-
or moving `accessState` from `revoked` to `granted`) is rejected as a stale
|
|
335
|
-
tie — this is what stops a reordered delivery from permanently un-revoking a
|
|
336
|
-
cancelled subscriber. A genuinely newer event is always admitted, including
|
|
337
|
-
one that restores a terminal entry to `active`/`granted` (a real
|
|
338
|
-
reactivation).
|
|
339
|
-
|
|
340
|
-
### The grace policy
|
|
341
|
-
|
|
342
|
-
`dunningPolicyFor(siteId)` reads a `SiteSetting` row keyed
|
|
343
|
-
`commerce.dunning_policy:<siteId>` (`SiteSetting.key` has no `siteId`
|
|
344
|
-
column of its own, so per-tenant scoping lives in the key string — never an
|
|
345
|
-
unsuffixed fallback). Absent, malformed, non-numeric, negative, non-finite,
|
|
346
|
-
or out-of-range (`> 90`) all resolve to a documented default (14 days) or a
|
|
347
|
-
clamp, logged, never thrown. On a **multi-tenant** deployment this key is
|
|
348
|
-
shared by every seller on the deployment (one process-wide `siteId`) — a
|
|
349
|
-
one-time `warn` log names that on the first read.
|
|
350
|
-
|
|
351
|
-
### The reserved key namespace
|
|
352
|
-
|
|
353
|
-
The record above would be worthless if its own subject could rewrite it:
|
|
354
|
-
`GET`/`PUT`/`DELETE /v1/user/data/:key` (`routes/auth/user.ts`) refuse a
|
|
355
|
-
`commerce.`- or `system.`-prefixed key (case-insensitive) with `403
|
|
356
|
-
{error: "reserved-key"}` before any Prisma call. `PUT` additionally bounds
|
|
357
|
-
the body at 64 KiB. Every unreserved key's behavior is unchanged.
|
|
358
|
-
|
|
359
|
-
## Idempotency caveat (load-bearing)
|
|
360
|
-
|
|
361
|
-
Stripe uses **at-least-once delivery semantics** and occasionally
|
|
362
|
-
double-delivers events. The in-memory store (`lib/webhook-idempotency.ts`)
|
|
363
|
-
short-circuits a replayed `event.id` within one process; it is not durable
|
|
364
|
-
across a restart or a second API instance. Telemetry-only side effects make
|
|
365
|
-
that an acceptable counting drift.
|
|
366
|
-
|
|
367
|
-
**A side effect with write semantics carries its own durable layer.** The
|
|
368
|
-
recommender stamp above uses a guarded conditional update (the row's
|
|
369
|
-
`status: "created"` is the key). A future write that cannot be expressed as
|
|
370
|
-
a guarded update (confirmation email, refund processing) needs a
|
|
371
|
-
`webhook_events` table keyed on `event.id` with an early-return for
|
|
372
|
-
already-seen IDs — add it first.
|
|
373
|
-
|
|
374
|
-
## Env contract
|
|
375
|
-
|
|
376
|
-
Both vars are **server-only** — never use a `NEXT_PUBLIC_` prefix.
|
|
377
|
-
|
|
378
|
-
| Var | Required | Notes |
|
|
379
|
-
| ----------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
380
|
-
| `STRIPE_WEBHOOK_SECRET` | For the route to do anything | `whsec_*` from Stripe Dashboard |
|
|
381
|
-
| `STRIPE_SECRET_KEY` | For checkout + sandbox check | Must match an allow-listed test-mode prefix (`sk_test_*` / `pk_test_*` / `rk_test_*`); anything else — including live and restricted-live keys — is runtime-blocked by `assertSandboxKey`. The commit-time hook is a partial backstop only: it greps for `sk_live_` / `pk_live_` and does **not** catch restricted-live (`rk_live_`) keys, so the runtime allow-list is the load-bearing guard |
|
|
382
|
-
|
|
383
|
-
Without `STRIPE_WEBHOOK_SECRET`, the route returns 503 on every request.
|
|
384
|
-
|
|
385
|
-
## Deployment note
|
|
386
|
-
|
|
387
|
-
Customer forks must:
|
|
388
|
-
|
|
389
|
-
1. Configure a Stripe Dashboard webhook endpoint pointing at
|
|
390
|
-
`https://<your-host>/v1/webhooks/stripe`.
|
|
391
|
-
2. Copy the endpoint's **Signing secret** (`whsec_*`) into
|
|
392
|
-
`STRIPE_WEBHOOK_SECRET` in their environment.
|
|
393
|
-
|
|
394
|
-
Without both, telemetry will not fire and 503s will be logged.
|
|
395
|
-
|
|
396
|
-
## Brief
|
|
397
|
-
|
|
398
|
-
`docs/internal/decisions/ailk-stripe-webhook-receiver.md`
|