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,721 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file default-tenant.test.ts
|
|
3
|
-
*
|
|
4
|
-
* Tenant-isolation coverage for the BASE-build tenant guard (#5677) —
|
|
5
|
-
* `middleware/default-tenant.ts`, the guard a build WITHOUT the multi-tenant
|
|
6
|
-
* module runs so the already-built review dashboard is reachable at Pro.
|
|
7
|
-
*
|
|
8
|
-
* SECURITY-SENSITIVE (tenant isolation). Two load-bearing groups here. The
|
|
9
|
-
* OPERATOR-PIN cases are the #5693 security-review remediation: on this build
|
|
10
|
-
* the review surface and `isPlatformOperator`'s unfiltered `{kind: "all"}`
|
|
11
|
-
* scope became reachable together, so the guard seats only the caller whose
|
|
12
|
-
* email matches `AILK_OPERATOR_EMAIL` and seats nobody when it is unset —
|
|
13
|
-
* including a caller who already holds the operator Membership. The FOREIGN
|
|
14
|
-
* WORKSPACE case is the other: a single-tenant caller naming another workspace's id in the
|
|
15
|
-
* URL is refused with the same 403 the paid slice returns, before any lookup
|
|
16
|
-
* of the target. This proves the base path did not relax the default-deny
|
|
17
|
-
* posture — it reproduces it. The multi-tenant slice's own isolation tests
|
|
18
|
-
* (`services/__tests__/tenant-context.test.ts`, #3631) are untouched and cover
|
|
19
|
-
* the module path.
|
|
20
|
-
*
|
|
21
|
-
* Mocks the module-level `prisma` singleton via the repo's manual-mock
|
|
22
|
-
* convention — `resolveDefaultTenant` reads the singleton directly and has no
|
|
23
|
-
* DI seam, the same shape `tenant-context.test.ts` uses.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import Fastify from "fastify";
|
|
27
|
-
import type { FastifyInstance } from "fastify";
|
|
28
|
-
|
|
29
|
-
// eslint-disable-next-line no-restricted-syntax -- lib/prisma.ts wraps the external DB boundary (PrismaClient requires a live connection); __mocks__/prisma.ts is the repo-standard test double for this boundary.
|
|
30
|
-
jest.mock("../../lib/prisma.js");
|
|
31
|
-
|
|
32
|
-
import { prisma } from "../../lib/prisma.js";
|
|
33
|
-
import type { Id } from "@working-theory/ids";
|
|
34
|
-
|
|
35
|
-
import { defaultTenantPreHandlerPlugin } from "../default-tenant.js";
|
|
36
|
-
import { resolveDefaultTenant } from "../../services/default-tenant.js";
|
|
37
|
-
|
|
38
|
-
// The reserved-range sentinel values from database/tenancy.ts (ADR-0031
|
|
39
|
-
// rule 7) — re-declared here as literals so the test pins the WIRE values,
|
|
40
|
-
// not whatever the constants happen to be.
|
|
41
|
-
const DEFAULT_ORG_ID = "org_00000000-0000-7000-8000-000000000001";
|
|
42
|
-
const DEFAULT_WORKSPACE_ID = "wksp_00000000-0000-7000-8000-000000000001";
|
|
43
|
-
const OPERATOR_MEMBERSHIP_ID =
|
|
44
|
-
"member_00000000-0000-7000-8000-000000000001";
|
|
45
|
-
|
|
46
|
-
/** A workspace id belonging to somebody else — never this caller's. */
|
|
47
|
-
const FOREIGN_WORKSPACE_ID = "wksp_01936b2c-0000-7000-8000-00000000beef";
|
|
48
|
-
|
|
49
|
-
const USER_ID = "solo-operator" as Id<"user">;
|
|
50
|
-
const OPERATOR_EMAIL = "operator@example.com";
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Stubs the single-tenant prerequisites `resolveDefaultTenant` checks:
|
|
54
|
-
* `workspace.count() === 1` and the sentinel default Workspace present on the
|
|
55
|
-
* sentinel default Organization.
|
|
56
|
-
*/
|
|
57
|
-
function mockSingleTenantWorkspace(): void {
|
|
58
|
-
(prisma.workspace.count as jest.Mock).mockResolvedValue(1);
|
|
59
|
-
(prisma.workspace.findUnique as jest.Mock).mockResolvedValue({
|
|
60
|
-
id: DEFAULT_WORKSPACE_ID,
|
|
61
|
-
organizationId: DEFAULT_ORG_ID,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The caller holds the operator Membership on the default Organization AND is
|
|
67
|
-
* the pinned operator — the only combination this guard grants on.
|
|
68
|
-
*/
|
|
69
|
-
function mockBoundOperator(): void {
|
|
70
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
71
|
-
email: OPERATOR_EMAIL,
|
|
72
|
-
emailVerified: true,
|
|
73
|
-
});
|
|
74
|
-
(prisma.membership.create as jest.Mock).mockResolvedValue({});
|
|
75
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue({
|
|
76
|
-
role: "owner",
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Builds an app whose routes sit behind the base guard. `authenticated`
|
|
82
|
-
* mirrors what the dual-mode auth preHandler decorates upstream: a cookie
|
|
83
|
-
* session sets both `userId` and `sessionTenant`; a bearer request sets only
|
|
84
|
-
* `userId`.
|
|
85
|
-
*/
|
|
86
|
-
function buildApp(
|
|
87
|
-
authenticated: "cookie" | "bearer" | "none" = "cookie",
|
|
88
|
-
): FastifyInstance {
|
|
89
|
-
const app = Fastify();
|
|
90
|
-
app.addHook("preHandler", async (req) => {
|
|
91
|
-
if (authenticated === "none") return;
|
|
92
|
-
req.userId = USER_ID;
|
|
93
|
-
if (authenticated === "cookie") {
|
|
94
|
-
req.sessionTenant = {
|
|
95
|
-
sessionRowId: "session-1" as Id<"session">,
|
|
96
|
-
activeOrganizationId: null,
|
|
97
|
-
activeWorkspaceId: null,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
app.register(defaultTenantPreHandlerPlugin);
|
|
102
|
-
app.get("/scoped", async (req) => ({
|
|
103
|
-
workspaceId: req.tenant?.workspaceId ?? null,
|
|
104
|
-
role: req.tenant?.role ?? null,
|
|
105
|
-
}));
|
|
106
|
-
app.get("/workspaces/:workspaceId/config", async (req) => ({
|
|
107
|
-
workspaceId: req.tenant?.workspaceId ?? null,
|
|
108
|
-
}));
|
|
109
|
-
// The `workspace` module's settings-route GET shape (#5371) — the surface
|
|
110
|
-
// #5515 item 1 asks about. Stubbed rather than imported: what is under test
|
|
111
|
-
// is which tenant the GUARD yields to it, not what the handler then reads.
|
|
112
|
-
app.get("/workspaces/:workspaceId/general", async (req) => ({
|
|
113
|
-
workspaceId: req.tenant?.workspaceId ?? null,
|
|
114
|
-
}));
|
|
115
|
-
return app;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const ORIGINAL_ENV = process.env;
|
|
119
|
-
|
|
120
|
-
beforeEach(() => {
|
|
121
|
-
jest.clearAllMocks();
|
|
122
|
-
process.env = { ...ORIGINAL_ENV };
|
|
123
|
-
// Pinned by default: this guard grants on no other configuration, so the
|
|
124
|
-
// cases below would all be trivially 403 without it.
|
|
125
|
-
process.env.AILK_OPERATOR_EMAIL = OPERATOR_EMAIL;
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
afterAll(() => {
|
|
129
|
-
process.env = ORIGINAL_ENV;
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* #5515 item 1 — `AILK_OPERATOR_EMAIL` is REQUIRED for a workspace-module
|
|
134
|
-
* deployment, and that requirement is what keeps the TOFU operator bind from
|
|
135
|
-
* firing on a settings-route GET.
|
|
136
|
-
*
|
|
137
|
-
* The #5513 review raised this against `apps/api/src/services/tenant-context.ts`
|
|
138
|
-
* before #5677 moved the resolution: registering the `workspace` module's
|
|
139
|
-
* routes under `isModuleEnabled("workspace") || isModuleEnabled("multi-tenant")`
|
|
140
|
-
* put a `tenantPreHandler` route on a build that has no multi-tenant module,
|
|
141
|
-
* so the concern was that the first authenticated account to load the module's
|
|
142
|
-
* Admin pages (`/admin`, renamed from `/workspace` by #5637) would TOFU-bind
|
|
143
|
-
* itself as org owner off a plain settings GET.
|
|
144
|
-
*
|
|
145
|
-
* On a workspace-only build `server.ts`'s `tenantPreHandlerForBuild()` picks
|
|
146
|
-
* THIS guard, which calls `resolveDefaultTenantForOperator` — so the pin is
|
|
147
|
-
* mandatory and unset fails CLOSED. These two cases are the objective record
|
|
148
|
-
* of that, pinned to the settings-route shape rather than the review surface:
|
|
149
|
-
* with the pin unset the bind never runs and the route is 403 to everyone;
|
|
150
|
-
* with it set, only the pinned operator is seated. The documentation half of
|
|
151
|
-
* this item is `.env.example` and `apps/api/CLAUDE.md`. Redesigning #3631's
|
|
152
|
-
* TOFU was explicitly out of scope.
|
|
153
|
-
*/
|
|
154
|
-
describe("workspace-module settings route — the operator pin is required (#5515 item 1)", () => {
|
|
155
|
-
const settingsPath = `/workspaces/${DEFAULT_WORKSPACE_ID}/general`;
|
|
156
|
-
|
|
157
|
-
it("DENIES a settings-route GET when AILK_OPERATOR_EMAIL is UNSET — and the TOFU bind never fires", async () => {
|
|
158
|
-
delete process.env.AILK_OPERATOR_EMAIL;
|
|
159
|
-
mockSingleTenantWorkspace();
|
|
160
|
-
mockBoundOperator();
|
|
161
|
-
|
|
162
|
-
const app = buildApp();
|
|
163
|
-
const res = await app.inject({ method: "GET", url: settingsPath });
|
|
164
|
-
|
|
165
|
-
expect(res.statusCode).toBe(403);
|
|
166
|
-
// The load-bearing assertion: no Membership is minted off a settings GET.
|
|
167
|
-
expect(prisma.membership.create).not.toHaveBeenCalled();
|
|
168
|
-
await app.close();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("GRANTS the same settings-route GET once AILK_OPERATOR_EMAIL pins the caller", async () => {
|
|
172
|
-
process.env.AILK_OPERATOR_EMAIL = OPERATOR_EMAIL;
|
|
173
|
-
mockSingleTenantWorkspace();
|
|
174
|
-
mockBoundOperator();
|
|
175
|
-
|
|
176
|
-
const app = buildApp();
|
|
177
|
-
const res = await app.inject({ method: "GET", url: settingsPath });
|
|
178
|
-
|
|
179
|
-
expect(res.statusCode).toBe(200);
|
|
180
|
-
expect(res.json()).toEqual({ workspaceId: DEFAULT_WORKSPACE_ID });
|
|
181
|
-
await app.close();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("DENIES a settings-route GET from a caller who is NOT the pinned operator, minting nothing", async () => {
|
|
185
|
-
process.env.AILK_OPERATOR_EMAIL = OPERATOR_EMAIL;
|
|
186
|
-
mockSingleTenantWorkspace();
|
|
187
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
188
|
-
email: "first-visitor@example.com",
|
|
189
|
-
emailVerified: true,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const app = buildApp();
|
|
193
|
-
const res = await app.inject({ method: "GET", url: settingsPath });
|
|
194
|
-
|
|
195
|
-
expect(res.statusCode).toBe(403);
|
|
196
|
-
expect(prisma.membership.create).not.toHaveBeenCalled();
|
|
197
|
-
await app.close();
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
describe("default tenant guard — isolation (#5677)", () => {
|
|
202
|
-
it("DENIES a foreign workspaceId with 403, before any lookup of the target", async () => {
|
|
203
|
-
mockSingleTenantWorkspace();
|
|
204
|
-
mockBoundOperator();
|
|
205
|
-
const app = buildApp();
|
|
206
|
-
|
|
207
|
-
const res = await app.inject({
|
|
208
|
-
method: "GET",
|
|
209
|
-
url: `/workspaces/${FOREIGN_WORKSPACE_ID}/config`,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
expect(res.statusCode).toBe(403);
|
|
213
|
-
expect(res.json()).toEqual({
|
|
214
|
-
ok: false,
|
|
215
|
-
error: { code: "forbidden", message: "Forbidden" },
|
|
216
|
-
});
|
|
217
|
-
// No enumeration: the foreign id is never handed to a lookup. The only
|
|
218
|
-
// workspace read is `resolveDefaultTenant`'s own by-sentinel-id read.
|
|
219
|
-
const workspaceReads = (prisma.workspace.findUnique as jest.Mock).mock.calls;
|
|
220
|
-
expect(workspaceReads).toHaveLength(1);
|
|
221
|
-
expect(workspaceReads[0]?.[0]?.where?.id).toBe(DEFAULT_WORKSPACE_ID);
|
|
222
|
-
await app.close();
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it("GRANTS the caller's OWN workspaceId — the same URL shape, its own id", async () => {
|
|
226
|
-
mockSingleTenantWorkspace();
|
|
227
|
-
mockBoundOperator();
|
|
228
|
-
const app = buildApp();
|
|
229
|
-
|
|
230
|
-
const res = await app.inject({
|
|
231
|
-
method: "GET",
|
|
232
|
-
url: `/workspaces/${DEFAULT_WORKSPACE_ID}/config`,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
expect(res.statusCode).toBe(200);
|
|
236
|
-
expect(res.json()).toEqual({ workspaceId: DEFAULT_WORKSPACE_ID });
|
|
237
|
-
await app.close();
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it("resolves the SEEDED default workspace for an authenticated site owner — no tenant is synthesized", async () => {
|
|
241
|
-
mockSingleTenantWorkspace();
|
|
242
|
-
mockBoundOperator();
|
|
243
|
-
const app = buildApp();
|
|
244
|
-
|
|
245
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
246
|
-
|
|
247
|
-
expect(res.statusCode).toBe(200);
|
|
248
|
-
expect(res.json()).toEqual({
|
|
249
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
250
|
-
role: "owner",
|
|
251
|
-
});
|
|
252
|
-
await app.close();
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("DENIES a caller holding no Membership on the default Organization (a second OAuth sign-in after TOFU)", async () => {
|
|
256
|
-
mockSingleTenantWorkspace();
|
|
257
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
258
|
-
email: OPERATOR_EMAIL,
|
|
259
|
-
emailVerified: true,
|
|
260
|
-
});
|
|
261
|
-
(prisma.membership.create as jest.Mock).mockRejectedValue({ code: "P2002" });
|
|
262
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
|
|
263
|
-
const app = buildApp();
|
|
264
|
-
|
|
265
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
266
|
-
|
|
267
|
-
expect(res.statusCode).toBe(403);
|
|
268
|
-
await app.close();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("DENIES once a SECOND workspace exists — the guard never grants outside an unambiguously single-tenant deployment", async () => {
|
|
272
|
-
(prisma.workspace.count as jest.Mock).mockResolvedValue(2);
|
|
273
|
-
mockBoundOperator();
|
|
274
|
-
const app = buildApp();
|
|
275
|
-
|
|
276
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
277
|
-
|
|
278
|
-
expect(res.statusCode).toBe(403);
|
|
279
|
-
expect(prisma.workspace.findUnique).not.toHaveBeenCalled();
|
|
280
|
-
await app.close();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("DENIES when the sole workspace is NOT the well-known default", async () => {
|
|
284
|
-
(prisma.workspace.count as jest.Mock).mockResolvedValue(1);
|
|
285
|
-
(prisma.workspace.findUnique as jest.Mock).mockResolvedValue(null);
|
|
286
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
287
|
-
email: OPERATOR_EMAIL,
|
|
288
|
-
emailVerified: true,
|
|
289
|
-
});
|
|
290
|
-
const app = buildApp();
|
|
291
|
-
|
|
292
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
293
|
-
|
|
294
|
-
expect(res.statusCode).toBe(403);
|
|
295
|
-
await app.close();
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it("DENIES when the sole default workspace hangs off a NON-sentinel organization", async () => {
|
|
299
|
-
(prisma.workspace.count as jest.Mock).mockResolvedValue(1);
|
|
300
|
-
(prisma.workspace.findUnique as jest.Mock).mockResolvedValue({
|
|
301
|
-
id: DEFAULT_WORKSPACE_ID,
|
|
302
|
-
organizationId: "org_01936b2c-0000-7000-8000-00000000beef",
|
|
303
|
-
});
|
|
304
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
305
|
-
email: OPERATOR_EMAIL,
|
|
306
|
-
emailVerified: true,
|
|
307
|
-
});
|
|
308
|
-
const app = buildApp();
|
|
309
|
-
|
|
310
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
311
|
-
|
|
312
|
-
expect(res.statusCode).toBe(403);
|
|
313
|
-
expect(prisma.membership.create).not.toHaveBeenCalled();
|
|
314
|
-
await app.close();
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it("DENIES a bearer-authenticated caller with 403 — no workspace binding on an agent token (#3591)", async () => {
|
|
318
|
-
mockSingleTenantWorkspace();
|
|
319
|
-
mockBoundOperator();
|
|
320
|
-
const app = buildApp("bearer");
|
|
321
|
-
|
|
322
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
323
|
-
|
|
324
|
-
expect(res.statusCode).toBe(403);
|
|
325
|
-
expect(prisma.workspace.count).not.toHaveBeenCalled();
|
|
326
|
-
await app.close();
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it("DENIES when AILK_OPERATOR_EMAIL is UNSET, even for a caller holding the owner Membership — an unpinned deployment seats nobody (#5693 security review)", async () => {
|
|
330
|
-
delete process.env.AILK_OPERATOR_EMAIL;
|
|
331
|
-
mockSingleTenantWorkspace();
|
|
332
|
-
mockBoundOperator();
|
|
333
|
-
const app = buildApp();
|
|
334
|
-
|
|
335
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
336
|
-
|
|
337
|
-
expect(res.statusCode).toBe(403);
|
|
338
|
-
// Fails closed BEFORE the TOFU bind: an unpinned deployment must not seat
|
|
339
|
-
// whoever authenticates first, because `isPlatformOperator` would later
|
|
340
|
-
// promote exactly that identity to an unfiltered read.
|
|
341
|
-
expect(prisma.membership.create).not.toHaveBeenCalled();
|
|
342
|
-
expect(prisma.workspace.count).not.toHaveBeenCalled();
|
|
343
|
-
await app.close();
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("DENIES a caller who ALREADY HOLDS the owner Membership but is not the pinned operator — a squatter bound before the pin existed stays denied (#5693 security review)", async () => {
|
|
347
|
-
mockSingleTenantWorkspace();
|
|
348
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
349
|
-
email: "squatter@evil.example",
|
|
350
|
-
emailVerified: true,
|
|
351
|
-
});
|
|
352
|
-
(prisma.membership.create as jest.Mock).mockResolvedValue({});
|
|
353
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue({
|
|
354
|
-
role: "owner",
|
|
355
|
-
});
|
|
356
|
-
const app = buildApp();
|
|
357
|
-
|
|
358
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
359
|
-
|
|
360
|
-
expect(res.statusCode).toBe(403);
|
|
361
|
-
// The Membership row is not enough — the email is re-checked per request,
|
|
362
|
-
// so a pre-existing bind cannot be inherited by adding the pin later.
|
|
363
|
-
expect(prisma.workspace.count).not.toHaveBeenCalled();
|
|
364
|
-
await app.close();
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it("GRANTS the pinned operator case-insensitively, with surrounding whitespace trimmed", async () => {
|
|
368
|
-
process.env.AILK_OPERATOR_EMAIL = " OPERATOR@Example.COM ";
|
|
369
|
-
mockSingleTenantWorkspace();
|
|
370
|
-
mockBoundOperator();
|
|
371
|
-
const app = buildApp();
|
|
372
|
-
|
|
373
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
374
|
-
|
|
375
|
-
expect(res.statusCode).toBe(200);
|
|
376
|
-
expect(res.json()).toEqual({
|
|
377
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
378
|
-
role: "owner",
|
|
379
|
-
});
|
|
380
|
-
await app.close();
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("DENIES the pinned operator whose email is UNVERIFIED — an unverified address is an unproven claim to the pin", async () => {
|
|
384
|
-
mockSingleTenantWorkspace();
|
|
385
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
386
|
-
email: OPERATOR_EMAIL,
|
|
387
|
-
emailVerified: false,
|
|
388
|
-
});
|
|
389
|
-
const app = buildApp();
|
|
390
|
-
|
|
391
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
392
|
-
|
|
393
|
-
expect(res.statusCode).toBe(403);
|
|
394
|
-
expect(prisma.workspace.count).not.toHaveBeenCalled();
|
|
395
|
-
await app.close();
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it("DENIES when the pinned caller has no User row", async () => {
|
|
399
|
-
mockSingleTenantWorkspace();
|
|
400
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue(null);
|
|
401
|
-
const app = buildApp();
|
|
402
|
-
|
|
403
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
404
|
-
|
|
405
|
-
expect(res.statusCode).toBe(403);
|
|
406
|
-
await app.close();
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it("returns 401 for an unauthenticated caller", async () => {
|
|
410
|
-
const app = buildApp("none");
|
|
411
|
-
|
|
412
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
413
|
-
|
|
414
|
-
expect(res.statusCode).toBe(401);
|
|
415
|
-
await app.close();
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* The MULTI-TENANT reach into the same bind (#5701 AC-2). `resolveActiveTenant`
|
|
421
|
-
* (`services/tenant-context.ts:89`) calls `resolveDefaultTenant` directly, so
|
|
422
|
-
* `bindOperatorMembership` is reached WITHOUT the verified-email gate that
|
|
423
|
-
* `resolveDefaultTenantForOperator` — the base-build entry the guard above
|
|
424
|
-
* exercises — applies first. These cases drive that entry point directly,
|
|
425
|
-
* which is the only way to observe the bind's own check; through the plugin
|
|
426
|
-
* the wrapper would refuse first and the assertion would prove nothing.
|
|
427
|
-
*/
|
|
428
|
-
describe("resolveDefaultTenant — the operator bind's own verified-email check (#5701)", () => {
|
|
429
|
-
it("does NOT create the Membership when the pinned address matches but emailVerified is false", async () => {
|
|
430
|
-
mockSingleTenantWorkspace();
|
|
431
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
432
|
-
id: USER_ID,
|
|
433
|
-
email: OPERATOR_EMAIL,
|
|
434
|
-
emailVerified: false,
|
|
435
|
-
});
|
|
436
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
|
|
437
|
-
|
|
438
|
-
const result = await resolveDefaultTenant(USER_ID);
|
|
439
|
-
|
|
440
|
-
expect(result).toBeNull();
|
|
441
|
-
// The bind is the escalation: an unverified address is an unproven claim
|
|
442
|
-
// to the pin, so no `owner` Membership may be minted from it.
|
|
443
|
-
expect(prisma.membership.create).not.toHaveBeenCalled();
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it("does NOT create the Membership when the pinned caller has no User row", async () => {
|
|
447
|
-
mockSingleTenantWorkspace();
|
|
448
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue(null);
|
|
449
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
|
|
450
|
-
|
|
451
|
-
const result = await resolveDefaultTenant(USER_ID);
|
|
452
|
-
|
|
453
|
-
expect(result).toBeNull();
|
|
454
|
-
expect(prisma.membership.create).not.toHaveBeenCalled();
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it("CREATES the Membership when the pinned address matches and emailVerified is true — unchanged behaviour", async () => {
|
|
458
|
-
mockSingleTenantWorkspace();
|
|
459
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
460
|
-
id: USER_ID,
|
|
461
|
-
email: OPERATOR_EMAIL,
|
|
462
|
-
emailVerified: true,
|
|
463
|
-
});
|
|
464
|
-
(prisma.membership.create as jest.Mock).mockResolvedValue({});
|
|
465
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue({
|
|
466
|
-
role: "owner",
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
const result = await resolveDefaultTenant(USER_ID);
|
|
470
|
-
|
|
471
|
-
expect(prisma.membership.create).toHaveBeenCalledTimes(1);
|
|
472
|
-
expect((prisma.membership.create as jest.Mock).mock.calls[0]?.[0]).toEqual({
|
|
473
|
-
data: {
|
|
474
|
-
id: OPERATOR_MEMBERSHIP_ID,
|
|
475
|
-
organizationId: DEFAULT_ORG_ID,
|
|
476
|
-
userId: USER_ID,
|
|
477
|
-
role: "owner",
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
expect(result).toEqual({
|
|
481
|
-
userId: USER_ID,
|
|
482
|
-
organizationId: DEFAULT_ORG_ID,
|
|
483
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
484
|
-
role: "owner",
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
it("leaves the UNPINNED branch untouched — no User read, pure TOFU bind (#5701 D4)", async () => {
|
|
489
|
-
delete process.env.AILK_OPERATOR_EMAIL;
|
|
490
|
-
mockSingleTenantWorkspace();
|
|
491
|
-
(prisma.membership.create as jest.Mock).mockResolvedValue({});
|
|
492
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue({
|
|
493
|
-
role: "owner",
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
const result = await resolveDefaultTenant(USER_ID);
|
|
497
|
-
|
|
498
|
-
// No pin means no email comparison at all — the bind stays first-caller-wins.
|
|
499
|
-
expect(prisma.user.findUnique).not.toHaveBeenCalled();
|
|
500
|
-
expect(prisma.membership.create).toHaveBeenCalledTimes(1);
|
|
501
|
-
expect(result).toEqual({
|
|
502
|
-
userId: USER_ID,
|
|
503
|
-
organizationId: DEFAULT_ORG_ID,
|
|
504
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
505
|
-
role: "owner",
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
it("still swallows the OPERATOR_MEMBERSHIP_ID unique violation — the TOFU race is unchanged", async () => {
|
|
510
|
-
mockSingleTenantWorkspace();
|
|
511
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
512
|
-
id: USER_ID,
|
|
513
|
-
email: OPERATOR_EMAIL,
|
|
514
|
-
emailVerified: true,
|
|
515
|
-
});
|
|
516
|
-
(prisma.membership.create as jest.Mock).mockRejectedValue({ code: "P2002" });
|
|
517
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue(null);
|
|
518
|
-
|
|
519
|
-
await expect(resolveDefaultTenant(USER_ID)).resolves.toBeNull();
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// ── #3591 — the base-build agent branch (spec D5, D6, D7) ───────────────────
|
|
524
|
-
|
|
525
|
-
describe("defaultTenantPreHandler — agents (#3591 D5, D7)", () => {
|
|
526
|
-
const AGENT_ID = "agent_00000000-0000-7000-8000-0000000000c1" as Id<"agent">;
|
|
527
|
-
|
|
528
|
-
function agentBinding(
|
|
529
|
-
overrides: Partial<{
|
|
530
|
-
workspaceId: string;
|
|
531
|
-
organizationId: string;
|
|
532
|
-
responsibleMembershipId: string;
|
|
533
|
-
grants: string[];
|
|
534
|
-
}> = {},
|
|
535
|
-
) {
|
|
536
|
-
return {
|
|
537
|
-
agentId: AGENT_ID,
|
|
538
|
-
workspaceId: (overrides.workspaceId ?? DEFAULT_WORKSPACE_ID) as Id<"workspace">,
|
|
539
|
-
organizationId: (overrides.organizationId ?? DEFAULT_ORG_ID) as Id<"organization">,
|
|
540
|
-
responsibleMembershipId: (overrides.responsibleMembershipId ??
|
|
541
|
-
OPERATOR_MEMBERSHIP_ID) as Id<"membership">,
|
|
542
|
-
permissionLevel: "member" as const,
|
|
543
|
-
grants: overrides.grants ?? [],
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/** An agent caller: `request.agent` set, `request.sessionTenant` never. */
|
|
548
|
-
function buildAgentApp(agent: ReturnType<typeof agentBinding>): FastifyInstance {
|
|
549
|
-
const app = Fastify();
|
|
550
|
-
app.addHook("preHandler", async (req) => {
|
|
551
|
-
req.userId = USER_ID;
|
|
552
|
-
req.agent = agent;
|
|
553
|
-
});
|
|
554
|
-
app.register(defaultTenantPreHandlerPlugin);
|
|
555
|
-
app.get("/scoped", async (req) => ({ tenant: req.tenant ?? null }));
|
|
556
|
-
app.get("/workspaces/:workspaceId/general", async (req) => ({
|
|
557
|
-
workspaceId: req.tenant?.workspaceId ?? null,
|
|
558
|
-
}));
|
|
559
|
-
return app;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function mockOperatorMembership(): void {
|
|
563
|
-
mockBoundOperator();
|
|
564
|
-
(prisma.membership.findUnique as jest.Mock).mockResolvedValue({
|
|
565
|
-
id: OPERATOR_MEMBERSHIP_ID,
|
|
566
|
-
role: "owner",
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
it("the pinned operator's agent, bound to the default workspace, is granted the operator's tenant with role member", async () => {
|
|
571
|
-
mockSingleTenantWorkspace();
|
|
572
|
-
mockOperatorMembership();
|
|
573
|
-
const app = buildAgentApp(agentBinding());
|
|
574
|
-
|
|
575
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
576
|
-
|
|
577
|
-
expect(res.statusCode).toBe(200);
|
|
578
|
-
expect(res.json().tenant).toEqual({
|
|
579
|
-
userId: USER_ID,
|
|
580
|
-
organizationId: DEFAULT_ORG_ID,
|
|
581
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
582
|
-
role: "member",
|
|
583
|
-
agent: true,
|
|
584
|
-
});
|
|
585
|
-
await app.close();
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it("a granted agent of the owner-operator carries its grants on the tenant; a forged one is dropped (#7864 D3)", async () => {
|
|
589
|
-
mockSingleTenantWorkspace();
|
|
590
|
-
mockOperatorMembership();
|
|
591
|
-
const app = buildAgentApp(
|
|
592
|
-
agentBinding({ grants: ["magnets-manage", "agents-manage", "content-edit"] }),
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
596
|
-
|
|
597
|
-
expect(res.statusCode).toBe(200);
|
|
598
|
-
expect(res.json().tenant).toEqual({
|
|
599
|
-
userId: USER_ID,
|
|
600
|
-
organizationId: DEFAULT_ORG_ID,
|
|
601
|
-
workspaceId: DEFAULT_WORKSPACE_ID,
|
|
602
|
-
role: "member",
|
|
603
|
-
agent: true,
|
|
604
|
-
grants: ["content-edit", "magnets-manage"],
|
|
605
|
-
});
|
|
606
|
-
await app.close();
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
it("reaches the grant through the operator's own reads: the same membership compound key", async () => {
|
|
610
|
-
mockSingleTenantWorkspace();
|
|
611
|
-
mockOperatorMembership();
|
|
612
|
-
const app = buildAgentApp(agentBinding());
|
|
613
|
-
|
|
614
|
-
await app.inject({ method: "GET", url: "/scoped" });
|
|
615
|
-
|
|
616
|
-
expect(prisma.membership.findUnique).toHaveBeenCalledWith({
|
|
617
|
-
where: {
|
|
618
|
-
organizationId_userId: { organizationId: DEFAULT_ORG_ID, userId: USER_ID },
|
|
619
|
-
},
|
|
620
|
-
select: { id: true, role: true },
|
|
621
|
-
});
|
|
622
|
-
await app.close();
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
it.each([
|
|
626
|
-
["bound to another workspace", { workspaceId: FOREIGN_WORKSPACE_ID }],
|
|
627
|
-
["created under another membership", { responsibleMembershipId: "member_00000000-0000-7000-8000-0000000000dd" }],
|
|
628
|
-
["of another organization", { organizationId: "org_00000000-0000-7000-8000-0000000000dd" }],
|
|
629
|
-
])("refuses (403) an agent %s", async (_label, overrides) => {
|
|
630
|
-
mockSingleTenantWorkspace();
|
|
631
|
-
mockOperatorMembership();
|
|
632
|
-
const app = buildAgentApp(agentBinding(overrides));
|
|
633
|
-
|
|
634
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
635
|
-
|
|
636
|
-
expect(res.statusCode).toBe(403);
|
|
637
|
-
await app.close();
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
it("refuses (403) the agent of a person who is not the pinned operator, before any membership read", async () => {
|
|
641
|
-
mockSingleTenantWorkspace();
|
|
642
|
-
mockOperatorMembership();
|
|
643
|
-
(prisma.user.findUnique as jest.Mock).mockResolvedValue({
|
|
644
|
-
email: "someone-else@example.com",
|
|
645
|
-
emailVerified: true,
|
|
646
|
-
});
|
|
647
|
-
const app = buildAgentApp(agentBinding());
|
|
648
|
-
|
|
649
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
650
|
-
|
|
651
|
-
expect(res.statusCode).toBe(403);
|
|
652
|
-
expect(prisma.membership.findUnique).not.toHaveBeenCalled();
|
|
653
|
-
await app.close();
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
it("refuses (403) every agent when the operator pin is unset", async () => {
|
|
657
|
-
delete process.env.AILK_OPERATOR_EMAIL;
|
|
658
|
-
mockSingleTenantWorkspace();
|
|
659
|
-
mockOperatorMembership();
|
|
660
|
-
const app = buildAgentApp(agentBinding());
|
|
661
|
-
|
|
662
|
-
const res = await app.inject({ method: "GET", url: "/scoped" });
|
|
663
|
-
|
|
664
|
-
expect(res.statusCode).toBe(403);
|
|
665
|
-
await app.close();
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
it("refuses (403) an agent naming a foreign workspace in the URL, before the handler runs", async () => {
|
|
669
|
-
mockSingleTenantWorkspace();
|
|
670
|
-
mockOperatorMembership();
|
|
671
|
-
const app = buildAgentApp(agentBinding());
|
|
672
|
-
|
|
673
|
-
const res = await app.inject({
|
|
674
|
-
method: "GET",
|
|
675
|
-
url: `/workspaces/${FOREIGN_WORKSPACE_ID}/general`,
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
expect(res.statusCode).toBe(403);
|
|
679
|
-
await app.close();
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
describe("defaultTenantPreHandlerPlugin — the tenantGuarded tag (#3591 D6)", () => {
|
|
684
|
-
it("tags every route registered after it in its scope, and no route before it or outside it", async () => {
|
|
685
|
-
// A root onRoute hook runs first and keeps each route's options object;
|
|
686
|
-
// the guard's own hook then tags that same object.
|
|
687
|
-
const routes: Record<string, { config?: { tenantGuarded?: boolean } }> = {};
|
|
688
|
-
const app = Fastify();
|
|
689
|
-
app.addHook("onRoute", (opts) => {
|
|
690
|
-
if (opts.method === "GET") routes[opts.url] = opts;
|
|
691
|
-
});
|
|
692
|
-
app.get("/before", async () => ({}));
|
|
693
|
-
app.register(async (scope) => {
|
|
694
|
-
scope.get("/scope-before", async () => ({}));
|
|
695
|
-
await scope.register(defaultTenantPreHandlerPlugin);
|
|
696
|
-
scope.get("/after", async () => ({}));
|
|
697
|
-
await scope.register(async (child) => {
|
|
698
|
-
child.get("/child", async () => ({}));
|
|
699
|
-
});
|
|
700
|
-
});
|
|
701
|
-
app.register(async (sibling) => {
|
|
702
|
-
sibling.get("/sibling", async () => ({}));
|
|
703
|
-
});
|
|
704
|
-
await app.ready();
|
|
705
|
-
const tagged = Object.fromEntries(
|
|
706
|
-
["/before", "/scope-before", "/after", "/child", "/sibling"].map((url) => [
|
|
707
|
-
url,
|
|
708
|
-
routes[url]?.config?.tenantGuarded,
|
|
709
|
-
]),
|
|
710
|
-
);
|
|
711
|
-
|
|
712
|
-
expect(tagged).toEqual({
|
|
713
|
-
"/before": undefined,
|
|
714
|
-
"/scope-before": undefined,
|
|
715
|
-
"/after": true,
|
|
716
|
-
"/child": true,
|
|
717
|
-
"/sibling": undefined,
|
|
718
|
-
});
|
|
719
|
-
await app.close();
|
|
720
|
-
});
|
|
721
|
-
});
|