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,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-recipient hourly send budget for the auth transactional-email relay
|
|
3
|
-
* (#5962 § D8c).
|
|
4
|
-
*
|
|
5
|
-
* WHY IT IS PER RECIPIENT AND NOT PER SITE. The first draft of #5962's spec
|
|
6
|
-
* carried a second, site-wide cap. It was removed, and its removal is the
|
|
7
|
-
* point of this module's shape: Better Auth writes the `Verification` row
|
|
8
|
-
* BEFORE it invokes `sendResetPassword`, so every attacker request both feeds
|
|
9
|
-
* a site-wide counter and refreshes its window — roughly one request per 72
|
|
10
|
-
* seconds would hold a site permanently at its cap and silence reset mail for
|
|
11
|
-
* EVERY user, indefinitely. A site-wide cap an attacker can hold open is a
|
|
12
|
-
* denial of the control it implements. `siteVolumeAlert()` below replaces it
|
|
13
|
-
* with an operator SIGNAL that never refuses a send.
|
|
14
|
-
*
|
|
15
|
-
* The per-recipient budget IS a lockout weapon, narrowly: an attacker who
|
|
16
|
-
* requests three resets for one address in an hour denies that person a reset
|
|
17
|
-
* for the rest of it. Accepted for the reason
|
|
18
|
-
* `services/project-listing-resume-email.ts` gives and this module can
|
|
19
|
-
* honestly claim — the window is an hour rather than forever, it clears
|
|
20
|
-
* itself, and it is bounded to ONE victim rather than to the whole site.
|
|
21
|
-
*
|
|
22
|
-
* SINGLE-REPLICA CAVEAT, stated rather than hidden: the store is an in-process
|
|
23
|
-
* Map, so on an N-instance deployment the effective ceiling is N × the cap.
|
|
24
|
-
* Identical to the caveat `middleware/rate-limit.ts` documents for itself. The
|
|
25
|
-
* durable alternative needs a table `database/auth/schema.prisma` does not
|
|
26
|
-
* have, and #5962 adds no migration (Q16).
|
|
27
|
-
*
|
|
28
|
-
* PII: the key is `sha256(lowercased recipient + ":" + kind)`. This module is
|
|
29
|
-
* memory an operator can dump, and a plaintext address in it would be a
|
|
30
|
-
* recipient list.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import { createHash } from "node:crypto";
|
|
34
|
-
|
|
35
|
-
import { getClock } from "@working-theory/common";
|
|
36
|
-
|
|
37
|
-
/** The four fixed message kinds the relay will send. */
|
|
38
|
-
export type AuthEmailKind =
|
|
39
|
-
| "password-reset"
|
|
40
|
-
| "email-verification"
|
|
41
|
-
| "password-changed"
|
|
42
|
-
| "magic-link";
|
|
43
|
-
|
|
44
|
-
/** Sends allowed per (recipient, kind) per hour. */
|
|
45
|
-
export const RECIPIENT_HOURLY_MAX = 3;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Sends per hour, site-wide, past which ONE operator warning is logged. Never
|
|
49
|
-
* a refusal — see the module comment.
|
|
50
|
-
*/
|
|
51
|
-
export const SITE_HOURLY_ALERT_THRESHOLD = 50;
|
|
52
|
-
|
|
53
|
-
const WINDOW_MS = 60 * 60 * 1000;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Cap on distinct entries, independent of the time-based window. The key is
|
|
57
|
-
* derived from a caller-supplied address, so a caller minting a fresh address
|
|
58
|
-
* per request would otherwise grow the map without bound for the lifetime of
|
|
59
|
-
* one isolate. Same reasoning, same shape, as `rate-limit.ts`'s MAX_BUCKETS.
|
|
60
|
-
*/
|
|
61
|
-
export const MAX_BUCKETS = 10_000;
|
|
62
|
-
|
|
63
|
-
type Bucket = { count: number; windowStartMs: number };
|
|
64
|
-
|
|
65
|
-
const buckets = new Map<string, Bucket>();
|
|
66
|
-
|
|
67
|
-
let siteWindowStartMs = 0;
|
|
68
|
-
let siteCount = 0;
|
|
69
|
-
let siteAlertedThisWindow = false;
|
|
70
|
-
|
|
71
|
-
export interface BudgetOptions {
|
|
72
|
-
/** Explicit clock reading. Defaults to `getClock().now()`. Tests inject it. */
|
|
73
|
-
nowMs?: number;
|
|
74
|
-
/** Entry cap. Exposed for tests only — no production caller overrides it. */
|
|
75
|
-
maxBuckets?: number;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function bucketKey(recipient: string, kind: AuthEmailKind): string {
|
|
79
|
-
const normalized = recipient.trim().toLowerCase();
|
|
80
|
-
return createHash("sha256").update(`${normalized}:${kind}`).digest("hex");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Counts one send against `(recipient, kind)` and reports whether it may
|
|
85
|
-
* proceed.
|
|
86
|
-
*
|
|
87
|
-
* `false` means SUPPRESS THE SEND — it never means "tell the caller". The
|
|
88
|
-
* relay answers 202 either way, because an observable suppression is the same
|
|
89
|
-
* account-enumeration oracle the rest of this flow is built to close.
|
|
90
|
-
*/
|
|
91
|
-
export function consumeRecipientBudget(
|
|
92
|
-
recipient: string,
|
|
93
|
-
kind: AuthEmailKind,
|
|
94
|
-
options: BudgetOptions = {},
|
|
95
|
-
): boolean {
|
|
96
|
-
const now = options.nowMs ?? getClock().now();
|
|
97
|
-
const maxBuckets = options.maxBuckets ?? MAX_BUCKETS;
|
|
98
|
-
const key = bucketKey(recipient, kind);
|
|
99
|
-
|
|
100
|
-
let bucket = buckets.get(key);
|
|
101
|
-
|
|
102
|
-
if (!bucket || now - bucket.windowStartMs >= WINDOW_MS) {
|
|
103
|
-
// A genuinely new identity at capacity evicts the single oldest-inserted
|
|
104
|
-
// entry (`Map` iteration order is insertion order) before inserting
|
|
105
|
-
// itself — O(1) amortized, and no behaviour change under the cap.
|
|
106
|
-
if (!buckets.has(key) && buckets.size >= maxBuckets) {
|
|
107
|
-
const oldestKey = buckets.keys().next().value;
|
|
108
|
-
if (oldestKey !== undefined) buckets.delete(oldestKey);
|
|
109
|
-
}
|
|
110
|
-
bucket = { count: 0, windowStartMs: now };
|
|
111
|
-
buckets.set(key, bucket);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (bucket.count >= RECIPIENT_HOURLY_MAX) return false;
|
|
115
|
-
|
|
116
|
-
bucket.count += 1;
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Counts one send site-wide and reports whether THIS call is the one that
|
|
122
|
-
* should emit the hourly operator warning.
|
|
123
|
-
*
|
|
124
|
-
* It is a signal, not a gate: nothing in this module consults its return value
|
|
125
|
-
* to deny a send, and nothing may be added that does.
|
|
126
|
-
*/
|
|
127
|
-
export function siteVolumeAlert(options: BudgetOptions = {}): boolean {
|
|
128
|
-
const now = options.nowMs ?? getClock().now();
|
|
129
|
-
|
|
130
|
-
if (now - siteWindowStartMs >= WINDOW_MS) {
|
|
131
|
-
siteWindowStartMs = now;
|
|
132
|
-
siteCount = 0;
|
|
133
|
-
siteAlertedThisWindow = false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
siteCount += 1;
|
|
137
|
-
|
|
138
|
-
if (siteCount >= SITE_HOURLY_ALERT_THRESHOLD && !siteAlertedThisWindow) {
|
|
139
|
-
siteAlertedThisWindow = true;
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** Test-only reset. Not intended for production use. */
|
|
146
|
-
export function __resetAuthEmailBudget(): void {
|
|
147
|
-
buckets.clear();
|
|
148
|
-
siteWindowStartMs = 0;
|
|
149
|
-
siteCount = 0;
|
|
150
|
-
siteAlertedThisWindow = false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Test-only accessor for the distinct-bucket count. */
|
|
154
|
-
export function __authEmailBucketCount(): number {
|
|
155
|
-
return buckets.size;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** Test-only accessor proving the stored keys carry no plaintext address. */
|
|
159
|
-
export function __authEmailBudgetKeys(): string[] {
|
|
160
|
-
return [...buckets.keys()];
|
|
161
|
-
}
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The consent ledger's read boundary (#8438, spec
|
|
3
|
-
* docs/specs/8438-consent-registry-and-ledger-step-spec.md D4).
|
|
4
|
-
*
|
|
5
|
-
* `consent_grants` is append-only and a Person's current state is the newest
|
|
6
|
-
* row per purpose, so every reader here asks the database for exactly that.
|
|
7
|
-
* Nothing is read through a window: `DISTINCT ON (purpose_id) … ORDER BY
|
|
8
|
-
* purpose_id, created_at DESC, id DESC` is one indexed pass over ALL the
|
|
9
|
-
* matching rows, so no number of other rows can push an answer out
|
|
10
|
-
* (SEC-8438-2).
|
|
11
|
-
*
|
|
12
|
-
* TWO READINGS of a Person's rows (SEC-8438-1). A Person is keyed on an email
|
|
13
|
-
* anyone can type into a form, so a row an anonymous writer attached to it
|
|
14
|
-
* proves nothing about what the person themself chose:
|
|
15
|
-
*
|
|
16
|
-
* - OWN rows: `person_id = P` and a source only a verified caller or a
|
|
17
|
-
* signed link sent to the person can write — `sign_up`,
|
|
18
|
-
* `account_settings`, `email_unsubscribe` (`OWN_CONSENT_SOURCES`). The
|
|
19
|
-
* member's screens, the sign-up prompt, and every future send read only
|
|
20
|
-
* these.
|
|
21
|
-
* - ANY-SOURCE rows: every row with `person_id = P`, plus the cookie rows
|
|
22
|
-
* of every visitor linked to P. The operator's Person page reads these,
|
|
23
|
-
* marked `unverified` when the source proves nothing.
|
|
24
|
-
*
|
|
25
|
-
* Every query takes an explicit `workspaceId`, and a join to a linked
|
|
26
|
-
* visitor's rows matches `workspace_id` on both tables (SEC-8438-11) — a
|
|
27
|
-
* visitor id is client-chosen and not namespaced, so another tenant's rows
|
|
28
|
-
* under the same id must never join.
|
|
29
|
-
*
|
|
30
|
-
* This file is the ONLY non-test reader of `consent_grants` and
|
|
31
|
-
* `consent_visitor_links` (`architecture.yaml` names it as a data-access
|
|
32
|
-
* boundary, the kind `member-store.ts` is). The writes are
|
|
33
|
-
* `services/consent.ts`. Logs carry nothing: no email, visitor id, token or
|
|
34
|
-
* statement.
|
|
35
|
-
*/
|
|
36
|
-
import type { Prisma } from "@working-theory/database";
|
|
37
|
-
import {
|
|
38
|
-
consentPurposeKind,
|
|
39
|
-
type ConsentPurposeKind,
|
|
40
|
-
type ConsentSource,
|
|
41
|
-
type SiteConsentConfig,
|
|
42
|
-
} from "@working-theory/validation";
|
|
43
|
-
|
|
44
|
-
import { prisma } from "./prisma.js";
|
|
45
|
-
|
|
46
|
-
/** What every reader needs of a client: raw reads only. */
|
|
47
|
-
export type ConsentReadClient = Pick<Prisma.TransactionClient, "$queryRaw">;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* The sources only a verified caller or a signed link can write (D4,
|
|
51
|
-
* SEC-8438-1). The SQL below names the same three literals; the store's test
|
|
52
|
-
* asserts the two agree.
|
|
53
|
-
*/
|
|
54
|
-
export const OWN_CONSENT_SOURCES = [
|
|
55
|
-
"sign_up",
|
|
56
|
-
"account_settings",
|
|
57
|
-
"email_unsubscribe",
|
|
58
|
-
] as const satisfies readonly ConsentSource[];
|
|
59
|
-
|
|
60
|
-
/** The sources whose rows prove nothing the client confirmed themself. */
|
|
61
|
-
const UNVERIFIED_SOURCES: ReadonlySet<ConsentSource> = new Set([
|
|
62
|
-
"magnet",
|
|
63
|
-
"api",
|
|
64
|
-
"cookie_banner",
|
|
65
|
-
"legacy",
|
|
66
|
-
// #8765 D13, C10: the payer at a Stripe Checkout is not a signed-in user.
|
|
67
|
-
"checkout",
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
/** Whether a row from `source` is unconfirmed by the client (D4). */
|
|
71
|
-
export function isUnverifiedSource(source: ConsentSource): boolean {
|
|
72
|
-
return UNVERIFIED_SOURCES.has(source);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** A history page's size (D7). */
|
|
76
|
-
export const CONSENT_HISTORY_PAGE_SIZE = 500;
|
|
77
|
-
|
|
78
|
-
/** The most platform rows one user can have in view (D4). */
|
|
79
|
-
const PLATFORM_ROWS_MAX = 100;
|
|
80
|
-
|
|
81
|
-
/** The newest ledger row for one purpose, as the readers serve it. */
|
|
82
|
-
export interface ConsentStateRow {
|
|
83
|
-
id: string;
|
|
84
|
-
purposeId: string;
|
|
85
|
-
granted: boolean;
|
|
86
|
-
at: Date;
|
|
87
|
-
source: ConsentSource;
|
|
88
|
-
sourceRef: string | null;
|
|
89
|
-
policyVersion: number | null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface ConsentHistoryRow extends ConsentStateRow {
|
|
93
|
-
locale: string;
|
|
94
|
-
statement: string;
|
|
95
|
-
subjectType: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** A registry purpose with the newest row read for it and what that means now. */
|
|
99
|
-
export interface PurposeConsentState {
|
|
100
|
-
purposeId: string;
|
|
101
|
-
kind: ConsentPurposeKind;
|
|
102
|
-
row: ConsentStateRow | null;
|
|
103
|
-
/** Granted now: the row's answer, else on for a preference and off otherwise. */
|
|
104
|
-
effective: boolean;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** The effective answer for a purpose given its newest row (D4). */
|
|
108
|
-
export function effectiveConsent(
|
|
109
|
-
kind: ConsentPurposeKind,
|
|
110
|
-
row: { granted: boolean } | null,
|
|
111
|
-
): boolean {
|
|
112
|
-
if (row !== null) return row.granted;
|
|
113
|
-
return kind === "preference";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Fold the newest row per purpose id into the registry's purposes. */
|
|
117
|
-
export function foldRowsIntoPurposes(
|
|
118
|
-
config: SiteConsentConfig,
|
|
119
|
-
rows: readonly ConsentStateRow[],
|
|
120
|
-
): PurposeConsentState[] {
|
|
121
|
-
const byPurpose = new Map(rows.map((row) => [row.purposeId, row]));
|
|
122
|
-
return config.purposes.map((purpose) => {
|
|
123
|
-
const kind = consentPurposeKind(purpose);
|
|
124
|
-
const row = byPurpose.get(purpose.id) ?? null;
|
|
125
|
-
return {
|
|
126
|
-
purposeId: purpose.id,
|
|
127
|
-
kind,
|
|
128
|
-
row,
|
|
129
|
-
effective: effectiveConsent(kind, row),
|
|
130
|
-
};
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* The newest OWN row per purpose id for a Person, unfolded (D4): one indexed
|
|
136
|
-
* `DISTINCT ON` pass over all own rows, never a window.
|
|
137
|
-
*/
|
|
138
|
-
export async function ownConsentRows(
|
|
139
|
-
workspaceId: string,
|
|
140
|
-
personId: string,
|
|
141
|
-
db: ConsentReadClient = prisma,
|
|
142
|
-
): Promise<ConsentStateRow[]> {
|
|
143
|
-
return db.$queryRaw<ConsentStateRow[]>`
|
|
144
|
-
SELECT DISTINCT ON (g."purpose_id")
|
|
145
|
-
g."id", g."purpose_id" AS "purposeId", g."granted", g."created_at" AS "at",
|
|
146
|
-
g."source", g."source_ref" AS "sourceRef", g."policy_version" AS "policyVersion"
|
|
147
|
-
FROM "consent_grants" g
|
|
148
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
149
|
-
AND g."person_id" = ${personId}
|
|
150
|
-
AND g."source" IN ('sign_up', 'account_settings', 'email_unsubscribe')
|
|
151
|
-
ORDER BY g."purpose_id", g."created_at" DESC, g."id" DESC`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* The newest ANY-SOURCE row per purpose id for a Person: `person_id = P` or a
|
|
156
|
-
* cookie row of a visitor linked to P, in this workspace on both tables.
|
|
157
|
-
* Includes purposes the registry no longer declares — the operator still sees
|
|
158
|
-
* what was recorded.
|
|
159
|
-
*/
|
|
160
|
-
export async function anySourceConsentRows(
|
|
161
|
-
workspaceId: string,
|
|
162
|
-
personId: string,
|
|
163
|
-
db: ConsentReadClient = prisma,
|
|
164
|
-
): Promise<ConsentStateRow[]> {
|
|
165
|
-
return db.$queryRaw<ConsentStateRow[]>`
|
|
166
|
-
SELECT DISTINCT ON (g."purpose_id")
|
|
167
|
-
g."id", g."purpose_id" AS "purposeId", g."granted", g."created_at" AS "at",
|
|
168
|
-
g."source", g."source_ref" AS "sourceRef", g."policy_version" AS "policyVersion"
|
|
169
|
-
FROM "consent_grants" g
|
|
170
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
171
|
-
AND (
|
|
172
|
-
g."person_id" = ${personId}
|
|
173
|
-
OR (g."subject_type" = 'visitor' AND g."subject_id" IN (
|
|
174
|
-
SELECT l."visitor_id" FROM "consent_visitor_links" l
|
|
175
|
-
WHERE l."workspace_id" = ${workspaceId} AND l."person_id" = ${personId}
|
|
176
|
-
))
|
|
177
|
-
)
|
|
178
|
-
ORDER BY g."purpose_id", g."created_at" DESC, g."id" DESC`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Whether this Person already holds the `checkout` row of one Checkout
|
|
183
|
-
* Session for one purpose (#8765 D14, NFR7) — the webhook's replay check.
|
|
184
|
-
* Filters on `(workspace_id, person_id, purpose_id)` first, so the existing
|
|
185
|
-
* `(workspace_id, person_id, purpose_id, created_at)` index serves it
|
|
186
|
-
* (SEC-8765-N).
|
|
187
|
-
*/
|
|
188
|
-
export async function personHasCheckoutGrant(
|
|
189
|
-
workspaceId: string,
|
|
190
|
-
personId: string,
|
|
191
|
-
purposeId: string,
|
|
192
|
-
sessionId: string,
|
|
193
|
-
db: ConsentReadClient = prisma,
|
|
194
|
-
): Promise<boolean> {
|
|
195
|
-
const rows = await db.$queryRaw<Array<{ found: number }>>`
|
|
196
|
-
SELECT 1 AS "found"
|
|
197
|
-
FROM "consent_grants" g
|
|
198
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
199
|
-
AND g."person_id" = ${personId}
|
|
200
|
-
AND g."purpose_id" = ${purposeId}
|
|
201
|
-
AND g."source" = 'checkout'
|
|
202
|
-
AND g."source_ref" = ${sessionId}
|
|
203
|
-
LIMIT 1`;
|
|
204
|
-
return rows.length > 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* A Person's OWN reading, per registry purpose (D4). The member's screens and
|
|
209
|
-
* the sign-up prompt read only this.
|
|
210
|
-
*/
|
|
211
|
-
export async function ownConsentState(
|
|
212
|
-
workspaceId: string,
|
|
213
|
-
personId: string,
|
|
214
|
-
config: SiteConsentConfig,
|
|
215
|
-
db: ConsentReadClient = prisma,
|
|
216
|
-
): Promise<PurposeConsentState[]> {
|
|
217
|
-
return foldRowsIntoPurposes(
|
|
218
|
-
config,
|
|
219
|
-
await ownConsentRows(workspaceId, personId, db),
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* A Person's ANY-SOURCE reading, per registry purpose. The operator's Person
|
|
225
|
-
* page and the unsubscribe dedupe read it; a row here is never a member's own
|
|
226
|
-
* answer (D4).
|
|
227
|
-
*/
|
|
228
|
-
export async function anySourceConsentState(
|
|
229
|
-
workspaceId: string,
|
|
230
|
-
personId: string,
|
|
231
|
-
config: SiteConsentConfig,
|
|
232
|
-
db: ConsentReadClient = prisma,
|
|
233
|
-
): Promise<PurposeConsentState[]> {
|
|
234
|
-
return foldRowsIntoPurposes(
|
|
235
|
-
config,
|
|
236
|
-
await anySourceConsentRows(workspaceId, personId, db),
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** One page of a Person's any-source history, newest first. */
|
|
241
|
-
export interface ConsentHistoryPage {
|
|
242
|
-
rows: ConsentHistoryRow[];
|
|
243
|
-
/** The id of the page's last row when another page follows, else `null`. */
|
|
244
|
-
nextCursor: string | null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* A Person's any-source history, 500 rows a page, newest first, keyed by
|
|
249
|
-
* `(created_at, id)`. Every page is complete: nothing is truncated (D7).
|
|
250
|
-
* Returns `"invalid_cursor"` for a cursor that is not one of this Person's
|
|
251
|
-
* rows — checked inside the Person's own scope, so a row id of another
|
|
252
|
-
* Person or workspace reads exactly like an unknown one.
|
|
253
|
-
*/
|
|
254
|
-
export async function personConsentHistory(
|
|
255
|
-
workspaceId: string,
|
|
256
|
-
personId: string,
|
|
257
|
-
cursor: string | undefined,
|
|
258
|
-
db: ConsentReadClient = prisma,
|
|
259
|
-
): Promise<ConsentHistoryPage | "invalid_cursor"> {
|
|
260
|
-
if (cursor !== undefined) {
|
|
261
|
-
const found = await db.$queryRaw<Array<{ id: string }>>`
|
|
262
|
-
SELECT g."id" FROM "consent_grants" g
|
|
263
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
264
|
-
AND g."id" = ${cursor}
|
|
265
|
-
AND (
|
|
266
|
-
g."person_id" = ${personId}
|
|
267
|
-
OR (g."subject_type" = 'visitor' AND g."subject_id" IN (
|
|
268
|
-
SELECT l."visitor_id" FROM "consent_visitor_links" l
|
|
269
|
-
WHERE l."workspace_id" = ${workspaceId} AND l."person_id" = ${personId}
|
|
270
|
-
))
|
|
271
|
-
)
|
|
272
|
-
LIMIT 1`;
|
|
273
|
-
if (found.length === 0) return "invalid_cursor";
|
|
274
|
-
}
|
|
275
|
-
const cursorId = cursor ?? null;
|
|
276
|
-
const rows = await db.$queryRaw<ConsentHistoryRow[]>`
|
|
277
|
-
SELECT g."id", g."purpose_id" AS "purposeId", g."granted", g."created_at" AS "at",
|
|
278
|
-
g."source", g."source_ref" AS "sourceRef", g."policy_version" AS "policyVersion",
|
|
279
|
-
g."locale", g."statement", g."subject_type" AS "subjectType"
|
|
280
|
-
FROM "consent_grants" g
|
|
281
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
282
|
-
AND (
|
|
283
|
-
g."person_id" = ${personId}
|
|
284
|
-
OR (g."subject_type" = 'visitor' AND g."subject_id" IN (
|
|
285
|
-
SELECT l."visitor_id" FROM "consent_visitor_links" l
|
|
286
|
-
WHERE l."workspace_id" = ${workspaceId} AND l."person_id" = ${personId}
|
|
287
|
-
))
|
|
288
|
-
)
|
|
289
|
-
AND (${cursorId}::text IS NULL OR (g."created_at", g."id") < (
|
|
290
|
-
SELECT c."created_at", c."id" FROM "consent_grants" c WHERE c."id" = ${cursorId}::text
|
|
291
|
-
))
|
|
292
|
-
ORDER BY g."created_at" DESC, g."id" DESC
|
|
293
|
-
LIMIT ${CONSENT_HISTORY_PAGE_SIZE + 1}`;
|
|
294
|
-
const page = rows.slice(0, CONSENT_HISTORY_PAGE_SIZE);
|
|
295
|
-
const last = page[page.length - 1];
|
|
296
|
-
return {
|
|
297
|
-
rows: page,
|
|
298
|
-
nextCursor:
|
|
299
|
-
rows.length > CONSENT_HISTORY_PAGE_SIZE && last ? last.id : null,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* The newest row recording a visitor's choice for one purpose — the cookie
|
|
305
|
-
* writer's dedupe: each CHANGE of choice is logged, a replay adds nothing
|
|
306
|
-
* (D3, SEC-8438-2).
|
|
307
|
-
*/
|
|
308
|
-
export async function latestVisitorChoice(
|
|
309
|
-
workspaceId: string,
|
|
310
|
-
visitorId: string,
|
|
311
|
-
purposeId: string,
|
|
312
|
-
db: ConsentReadClient = prisma,
|
|
313
|
-
): Promise<{ granted: boolean; policyVersion: number | null } | null> {
|
|
314
|
-
const rows = await db.$queryRaw<
|
|
315
|
-
Array<{ granted: boolean; policyVersion: number | null }>
|
|
316
|
-
>`
|
|
317
|
-
SELECT g."granted", g."policy_version" AS "policyVersion"
|
|
318
|
-
FROM "consent_grants" g
|
|
319
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
320
|
-
AND g."subject_type" = 'visitor'
|
|
321
|
-
AND g."subject_id" = ${visitorId}
|
|
322
|
-
AND g."purpose_id" = ${purposeId}
|
|
323
|
-
ORDER BY g."created_at" DESC, g."id" DESC
|
|
324
|
-
LIMIT 1`;
|
|
325
|
-
return rows[0] ?? null;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* When the caller's own `User` row was created — the sign-up window's only
|
|
330
|
-
* input (SEC-8438-5: `via: "sign_up"` is honored for a user created within
|
|
331
|
-
* the last 24 hours). `null` when no such user exists.
|
|
332
|
-
*/
|
|
333
|
-
export async function userCreatedAt(
|
|
334
|
-
userId: string,
|
|
335
|
-
db: Pick<Prisma.TransactionClient, "user"> = prisma,
|
|
336
|
-
): Promise<Date | null> {
|
|
337
|
-
const user = await db.user.findUnique({
|
|
338
|
-
where: { id: userId },
|
|
339
|
-
select: { createdAt: true },
|
|
340
|
-
});
|
|
341
|
-
return user?.createdAt ?? null;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* The owner's rows for the Working Theory platform purposes (C8): subject
|
|
346
|
-
* `user` / `userId`, newest per purpose, at most 100.
|
|
347
|
-
*/
|
|
348
|
-
export async function userPlatformRows(
|
|
349
|
-
workspaceId: string,
|
|
350
|
-
userId: string,
|
|
351
|
-
db: ConsentReadClient = prisma,
|
|
352
|
-
): Promise<ConsentStateRow[]> {
|
|
353
|
-
return db.$queryRaw<ConsentStateRow[]>`
|
|
354
|
-
SELECT DISTINCT ON (g."purpose_id")
|
|
355
|
-
g."id", g."purpose_id" AS "purposeId", g."granted", g."created_at" AS "at",
|
|
356
|
-
g."source", g."source_ref" AS "sourceRef", g."policy_version" AS "policyVersion"
|
|
357
|
-
FROM "consent_grants" g
|
|
358
|
-
WHERE g."workspace_id" = ${workspaceId}
|
|
359
|
-
AND g."subject_type" = 'user'
|
|
360
|
-
AND g."subject_id" = ${userId}
|
|
361
|
-
ORDER BY g."purpose_id", g."created_at" DESC, g."id" DESC
|
|
362
|
-
LIMIT ${PLATFORM_ROWS_MAX}`;
|
|
363
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared content adapter singleton for apps/api.
|
|
3
|
-
*
|
|
4
|
-
* Adapter selection is NOT decided here — it is `resolveContentAdapter()`'s job
|
|
5
|
-
* (#5804, roadmap D3: one resolver, one env var). This module supplies the
|
|
6
|
-
* app's own inputs (content root, default locale, manifest path, the projection
|
|
7
|
-
* factory) and memoises the result for the process.
|
|
8
|
-
*
|
|
9
|
-
* `CONTENT_ADAPTER` selects: `mdx` (default) | `projection` | `database`.
|
|
10
|
-
* `CONTENT_BACKEND` is read for one more release by the resolver's shim.
|
|
11
|
-
*
|
|
12
|
-
* Content-root resolution order:
|
|
13
|
-
* 1. `CONTENT_ROOT` env var (explicit override — recommended for production)
|
|
14
|
-
* 2. `<cwd>/content` (works when the server is started from the repo root)
|
|
15
|
-
* 3. `<cwd>/../../content` (works when started from `apps/api/` via pnpm)
|
|
16
|
-
*
|
|
17
|
-
* Manifest-path resolution (projection only):
|
|
18
|
-
* 1. `MANIFEST_PATH` env var (explicit override)
|
|
19
|
-
* 2. `<content-root>/.compiled/manifest.json` (the resolver's default)
|
|
20
|
-
*
|
|
21
|
-
* `workspaceId` is supplied by the CALLER, never read here (#5852). A public
|
|
22
|
-
* read resolves its workspace through `services/public-site-resolver.ts` and
|
|
23
|
-
* passes the result in; a caller that passes none still raises the resolver's
|
|
24
|
-
* "workspaceId required" error under `CONTENT_ADAPTER=database`, rather than
|
|
25
|
-
* silently reading some other workspace's pages. There is no local ADAPTER memo
|
|
26
|
-
* slot: `resolveContentAdapter()` already memoises on the full key
|
|
27
|
-
* (kind + workspaceId + root + locale + manifest), and a second cache keyed on
|
|
28
|
-
* less than that is how one workspace's adapter reaches the next request. This
|
|
29
|
-
* module's own INPUTS — the content root and the default locale, both
|
|
30
|
-
* workspace-independent and both fixed for the process — stay memoised, so a
|
|
31
|
-
* public read costs no more filesystem work than it did before the seam widened.
|
|
32
|
-
*/
|
|
33
|
-
import { existsSync } from "node:fs";
|
|
34
|
-
import { resolve } from "node:path";
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
resetResolvedContentAdapter,
|
|
38
|
-
resolveContentAdapter,
|
|
39
|
-
type ArtifactPrismaClient,
|
|
40
|
-
type ContentAdapter,
|
|
41
|
-
type MdxContentAdapter,
|
|
42
|
-
} from "@working-theory/content-adapters";
|
|
43
|
-
import { createProjectionAdapter } from "@working-theory/content-projection";
|
|
44
|
-
import { loadManifestFromDisk } from "@working-theory/content-projection/node";
|
|
45
|
-
|
|
46
|
-
import { readDefaultLocale } from "./locales.js";
|
|
47
|
-
|
|
48
|
-
/** Process-fixed inputs, resolved once: neither depends on the workspace. */
|
|
49
|
-
let memoisedContentRoot: string | undefined;
|
|
50
|
-
let memoisedDefaultLocale: string | undefined;
|
|
51
|
-
|
|
52
|
-
/** Caller-supplied inputs; everything else is this module's own (#5852 D5). */
|
|
53
|
-
export interface GetContentAdapterOptions {
|
|
54
|
-
/** The resolved tenant. Required by `CONTENT_ADAPTER=database`, ignored otherwise. */
|
|
55
|
-
workspaceId?: string | undefined;
|
|
56
|
-
/** apps/api's client. The database adapter never constructs one. */
|
|
57
|
-
prisma?: ArtifactPrismaClient | undefined;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Resolve the content root. Honours `CONTENT_ROOT` env first, then probes the
|
|
62
|
-
* cwd-relative candidates above.
|
|
63
|
-
*/
|
|
64
|
-
export function getContentRoot(): string {
|
|
65
|
-
if (process.env.CONTENT_ROOT) return process.env.CONTENT_ROOT;
|
|
66
|
-
|
|
67
|
-
const cwd = process.cwd();
|
|
68
|
-
const cwdContent = resolve(cwd, "content");
|
|
69
|
-
const repoContent = resolve(cwd, "../../content");
|
|
70
|
-
if (existsSync(cwdContent)) return cwdContent;
|
|
71
|
-
if (existsSync(repoContent)) return repoContent;
|
|
72
|
-
// Last resort — cwd-relative, even if missing. The adapter will return
|
|
73
|
-
// null/empty for missing pages; routes will surface 404s.
|
|
74
|
-
return cwdContent;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Returns the content adapter for the given tenant, memoised by the resolver.
|
|
79
|
-
* Safe to call repeatedly. Adapter type is determined by `CONTENT_ADAPTER` env,
|
|
80
|
-
* read by the resolver.
|
|
81
|
-
*/
|
|
82
|
-
export function getContentAdapter(
|
|
83
|
-
options: GetContentAdapterOptions = {},
|
|
84
|
-
): ContentAdapter {
|
|
85
|
-
memoisedContentRoot ??= getContentRoot();
|
|
86
|
-
memoisedDefaultLocale ??= readDefaultLocale();
|
|
87
|
-
|
|
88
|
-
return resolveContentAdapter({
|
|
89
|
-
workspaceId: options.workspaceId,
|
|
90
|
-
prisma: options.prisma,
|
|
91
|
-
contentRoot: memoisedContentRoot,
|
|
92
|
-
defaultLocale: memoisedDefaultLocale,
|
|
93
|
-
manifestPath: process.env.MANIFEST_PATH,
|
|
94
|
-
// The projection adapter lives in a package that depends on
|
|
95
|
-
// content-adapters, so the resolver cannot import it without closing a
|
|
96
|
-
// cycle. apps/api depends on both, and injects it.
|
|
97
|
-
createProjectionAdapter: (manifestPath) =>
|
|
98
|
-
createProjectionAdapter({ manifest: loadManifestFromDisk(manifestPath) }),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Test/utility helper — drop the memoised adapter so the next call rebuilds
|
|
104
|
-
* with current env. Not used in production.
|
|
105
|
-
*/
|
|
106
|
-
export function resetContentAdapter(): void {
|
|
107
|
-
memoisedContentRoot = undefined;
|
|
108
|
-
memoisedDefaultLocale = undefined;
|
|
109
|
-
resetResolvedContentAdapter();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type { MdxContentAdapter };
|