create-ailk 0.1.0 → 0.2.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 +124 -3
- package/component-catalog.md +456 -0
- package/dist/cli.js +92 -16
- package/dist/component-catalog.d.ts +11 -0
- package/dist/component-catalog.js +39 -0
- package/dist/copy.d.ts +7 -0
- package/dist/copy.js +24 -0
- package/dist/derive-architecture.d.ts +13 -0
- package/dist/derive-architecture.js +238 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +19 -2
- package/dist/lib/extract-module-bundle.d.ts +16 -0
- package/dist/lib/extract-module-bundle.js +184 -0
- package/dist/lib/fetch-module.d.ts +55 -0
- package/dist/lib/fetch-module.js +125 -0
- package/dist/lib/module-license-gate.d.ts +48 -0
- package/dist/lib/module-license-gate.js +67 -0
- package/dist/lib/module-tier.d.ts +35 -0
- package/dist/lib/module-tier.js +37 -0
- package/dist/module-architecture.d.ts +175 -0
- package/dist/module-architecture.js +663 -0
- package/dist/parse-args.d.ts +47 -1
- package/dist/parse-args.js +143 -2
- package/dist/programmatic.d.ts +160 -3
- package/dist/programmatic.js +196 -7
- package/dist/surfaces.d.ts +211 -0
- package/dist/surfaces.js +532 -0
- package/dist/sync-routes.d.ts +92 -0
- package/dist/sync-routes.js +350 -0
- package/package.json +12 -5
- package/templates/.claude/rules/architecture.md +6 -2
- package/templates/.claude/skills/README.md +9 -9
- package/templates/.claude/skills/add-schema/SKILL.md +14 -7
- package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
- package/templates/.claude/skills/provision-config/SKILL.md +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
- package/templates/.env.example +126 -2
- package/templates/CLAUDE.md +11 -5
- package/templates/README.md +1 -1
- package/templates/apps/api/.env.example +38 -0
- package/templates/apps/api/CLAUDE.md +78 -1
- package/templates/apps/api/LICENSE +201 -0
- package/templates/apps/api/api/index.ts +6 -141
- package/templates/apps/api/jest.config.cjs +16 -0
- package/templates/apps/api/package.json +22 -6
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
- package/templates/apps/api/src/__tests__/server.test.ts +46 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
- package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
- package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
- package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
- package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
- package/templates/apps/api/src/bin/seed-presets.ts +58 -0
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
- package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
- package/templates/apps/api/src/cli.ts +5 -11
- package/templates/apps/api/src/config/index.ts +35 -0
- package/templates/apps/api/src/config/modules.ts +43 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
- package/templates/apps/api/src/lib/site.ts +16 -0
- package/templates/apps/api/src/lib/stripe.ts +22 -0
- package/templates/apps/api/src/lib/tenant-db.ts +225 -0
- package/templates/apps/api/src/lib/ws-token.ts +91 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
- package/templates/apps/api/src/middleware/auth.ts +43 -8
- package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
- package/templates/apps/api/src/middleware/tenant.ts +99 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
- package/templates/apps/api/src/openapi/spec.ts +312 -3
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
- package/templates/apps/api/src/routes/aeo/index.ts +107 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
- package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
- package/templates/apps/api/src/routes/billing/index.ts +36 -0
- package/templates/apps/api/src/routes/billing/portal.ts +182 -0
- package/templates/apps/api/src/routes/billing/read.ts +75 -0
- package/templates/apps/api/src/routes/billing/usage.ts +153 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
- package/templates/apps/api/src/routes/content/create.ts +1 -0
- package/templates/apps/api/src/routes/content/delete.ts +1 -0
- package/templates/apps/api/src/routes/content/index.ts +42 -6
- package/templates/apps/api/src/routes/content/update.ts +1 -0
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
- package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
- package/templates/apps/api/src/routes/flows/README.md +147 -0
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
- package/templates/apps/api/src/routes/flows/index.ts +202 -0
- package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
- package/templates/apps/api/src/routes/leads/index.ts +43 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +558 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +869 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
- package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
- package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
- package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
- package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
- package/templates/apps/api/src/routes/schedule/index.ts +249 -0
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
- package/templates/apps/api/src/routes/slack/actions.ts +177 -0
- package/templates/apps/api/src/routes/slack/index.ts +38 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
- package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
- package/templates/apps/api/src/routes/webhooks/README.md +35 -12
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
- package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
- package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
- package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
- package/templates/apps/api/src/server.ts +307 -7
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
- package/templates/apps/api/src/services/abandon-email.ts +307 -0
- package/templates/apps/api/src/services/aeo-score.ts +288 -0
- package/templates/apps/api/src/services/answer-distributions.ts +138 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
- package/templates/apps/api/src/services/entitlements.ts +102 -0
- package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
- package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
- package/templates/apps/api/src/services/flow-engine.ts +1296 -0
- package/templates/apps/api/src/services/lead-promotion.ts +176 -0
- package/templates/apps/api/src/services/lead-routing.ts +3 -3
- package/templates/apps/api/src/services/listing-config.ts +102 -0
- package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
- package/templates/apps/api/src/services/listing-promotion.ts +342 -0
- package/templates/apps/api/src/services/playbook-compile.ts +398 -0
- package/templates/apps/api/src/services/playbook-render.ts +263 -0
- package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
- package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
- package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
- package/templates/apps/api/src/services/project-listings.ts +1472 -0
- package/templates/apps/api/src/services/recommender-capture.ts +835 -0
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
- package/templates/apps/api/src/services/scheduling/index.ts +63 -0
- package/templates/apps/api/src/services/scheduling/types.ts +88 -0
- package/templates/apps/api/src/services/slack-notify.ts +240 -0
- package/templates/apps/api/src/services/tenant-context.ts +451 -0
- package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
- package/templates/apps/api/src/services/usage-credits.ts +340 -0
- package/templates/apps/api/src/services/usage-metering.ts +699 -0
- package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
- package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
- package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
- package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
- package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
- package/templates/apps/api/src/vercel-handler.ts +165 -0
- package/templates/apps/api/tsconfig.json +0 -12
- package/templates/apps/mcp/CLAUDE.md +19 -9
- package/templates/apps/mcp/LICENSE +201 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
- package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
- package/templates/apps/mcp/package.json +6 -4
- package/templates/apps/mcp/src/config/modules.ts +37 -0
- package/templates/apps/mcp/src/server.ts +109 -18
- package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
- package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
- package/templates/apps/mcp/src/tools/create_page.ts +1 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
- package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
- package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
- package/templates/apps/mcp/src/tools/index.ts +25 -0
- package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
- package/templates/apps/mcp/src/tools/purchase.ts +3 -0
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
- package/templates/apps/mcp/src/tools/update_page.ts +1 -0
- package/templates/apps/mcp/tsconfig.json +1 -9
- package/templates/apps/web/.env.example +44 -0
- package/templates/apps/web/CLAUDE.md +28 -0
- package/templates/apps/web/LICENSE +201 -0
- package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
- package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
- package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
- package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
- package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
- package/templates/apps/web/__tests__/setup.ts +22 -32
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/layout.tsx +127 -16
- package/templates/apps/web/app/[locale]/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
- package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
- package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
- package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
- package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/globals.css +35 -0
- package/templates/apps/web/app/llms.txt/route.ts +2 -1
- package/templates/apps/web/app/og/route.tsx +10 -2
- package/templates/apps/web/auth.ts +8 -0
- package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
- package/templates/apps/web/content/en/blog/README.txt +25 -0
- package/templates/apps/web/content/en/docs/index.mdx +28 -0
- package/templates/apps/web/jest.config.cjs +90 -27
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
- package/templates/apps/web/lib/account-config.ts +13 -0
- package/templates/apps/web/lib/server-api.ts +85 -0
- package/templates/apps/web/lib/site-brand.tsx +121 -0
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/messages/en.json +275 -7
- package/templates/apps/web/middleware.ts +11 -3
- package/templates/apps/web/next.config.mjs +8 -2
- package/templates/apps/web/package.json +17 -7
- package/templates/apps/web/postcss.config.mjs +23 -0
- package/templates/apps/web/public/.well-known/security.txt +3 -3
- package/templates/apps/web/src/lib/routes.ts +15 -2
- package/templates/apps/web/tsconfig.json +1 -16
- package/templates/content/_site.mdx +36 -25
- package/templates/content/en/AboutPage/about.mdx +24 -14
- package/templates/content/en/ContactPage/contact.mdx +23 -14
- package/templates/content/en/FAQPage/faq.mdx +10 -392
- package/templates/content/en/HomePage/home.mdx +14 -285
- package/templates/database/CHANGELOG.md +443 -0
- package/templates/database/LICENSE +201 -0
- package/templates/database/README.md +3 -1
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
- package/templates/database/__tests__/flows-presets.test.ts +173 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
- package/templates/database/__tests__/seed.test.ts +88 -0
- package/templates/database/__tests__/user-cascade.test.ts +19 -4
- package/templates/database/auth/schema.prisma +521 -0
- package/templates/database/config/schema.prisma +234 -0
- package/templates/database/content/schema.prisma +263 -0
- package/templates/database/flows/listing-diagnostic.ts +182 -0
- package/templates/database/flows/presets.ts +315 -0
- package/templates/database/inbox/__tests__/schema.test.ts +4 -0
- package/templates/database/inbox/schema.prisma +574 -3
- package/templates/database/jest.config.cjs +8 -0
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/ops/schema.prisma +27 -0
- package/templates/database/package.json +42 -4
- package/templates/database/prisma.config.ts +1 -1
- package/templates/database/schema.prisma +19 -435
- package/templates/database/seed-test.ts +14 -4
- package/templates/database/seed.ts +148 -0
- package/templates/database/tenancy.ts +45 -0
- package/templates/database/tsconfig.flows.json +15 -0
- package/templates/database/tsconfig.json +7 -1
- package/templates/database/tsconfig.tenancy.json +14 -0
- package/templates/package.json +30 -55
- package/templates/pnpm-workspace.yaml +0 -3
- package/templates/project.yaml +1 -1
- package/templates/tsconfig.json +1 -37
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
- package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
- package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
- package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
- package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
- package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
- package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
- package/templates/apps/web/__tests__/page.test.tsx +0 -230
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
- package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
- package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
- package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
- package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
- package/templates/apps/web/content/en/blog/index.mdx +0 -15
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
- package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +0 -85
- package/templates/content/ar/HomePage/home.mdx +0 -123
- package/templates/content/ar/_site.mdx +0 -35
- package/templates/content/de/_site.mdx +0 -29
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.schema.json +0 -82
- package/templates/content/en/BookCall/book-call.mdx +0 -19
- package/templates/content/en/BookCall/book-call.schema.json +0 -121
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
- package/templates/content/en/CareersList/careers.mdx +0 -24
- package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
- package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
- package/templates/content/en/ClientsList/clients.mdx +0 -38
- package/templates/content/en/ContactPage/contact.schema.json +0 -100
- package/templates/content/en/ContactPoint/primary.mdx +0 -14
- package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
- package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
- package/templates/content/en/CoursesList/courses.mdx +0 -22
- package/templates/content/en/DownloadsList/downloads.mdx +0 -22
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
- package/templates/content/en/EventsList/events.mdx +0 -26
- package/templates/content/en/FAQPage/faq.schema.json +0 -231
- package/templates/content/en/GlossaryList/glossary.mdx +0 -19
- package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
- package/templates/content/en/HomePage/home.schema.json +0 -88
- package/templates/content/en/HomePage/launch-service.mdx +0 -475
- package/templates/content/en/HomePage/launch-service.schema.json +0 -138
- package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
- package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
- package/templates/content/en/Integration/github-actions.mdx +0 -31
- package/templates/content/en/Integration/openai-api.mdx +0 -26
- package/templates/content/en/Integration/vercel.mdx +0 -32
- package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
- package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
- package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
- package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
- package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
- package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
- package/templates/content/en/LandingPage/developers.mdx +0 -19
- package/templates/content/en/LandingPage/developers.schema.json +0 -109
- package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
- package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
- package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
- package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
- package/templates/content/en/LandingPage/platform.mdx +0 -369
- package/templates/content/en/LandingPage/platform.schema.json +0 -166
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
- package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
- package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
- package/templates/content/en/Organization/site-owner.mdx +0 -16
- package/templates/content/en/Partner/prisma.mdx +0 -31
- package/templates/content/en/Partner/supabase.mdx +0 -30
- package/templates/content/en/Partner/vercel.mdx +0 -28
- package/templates/content/en/PartnersList/partners.mdx +0 -22
- package/templates/content/en/Person/founder.mdx +0 -18
- package/templates/content/en/Pricing/pricing.mdx +0 -538
- package/templates/content/en/Pricing/pricing.schema.json +0 -209
- package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
- package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
- package/templates/content/en/Product/ailk.mdx +0 -19
- package/templates/content/en/Product/ailk.schema.json +0 -94
- package/templates/content/en/Product/api-launch-kit.mdx +0 -26
- package/templates/content/en/Product/launch-kit.mdx +0 -28
- package/templates/content/en/ProductsList/products.mdx +0 -26
- package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
- package/templates/content/en/ProjectsList/projects.mdx +0 -25
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
- package/templates/content/en/ResourcesHub/resources.mdx +0 -22
- package/templates/content/en/Service/consulting.mdx +0 -22
- package/templates/content/en/Service/implementation.mdx +0 -30
- package/templates/content/en/Service/launch-service.mdx +0 -232
- package/templates/content/en/Service/launch.mdx +0 -228
- package/templates/content/en/ServicesList/services.mdx +0 -84
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
- package/templates/content/en/TeamList/team.mdx +0 -63
- package/templates/content/en/TeamMember/founder.mdx +0 -72
- package/templates/content/en/TermsPage/terms.mdx +0 -19
- package/templates/content/en/TermsPage/terms.schema.json +0 -99
- package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
- package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
- package/templates/content/en-XA/AboutPage/about.mdx +0 -8
- package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
- package/templates/content/en-XA/HomePage/home.mdx +0 -8
- package/templates/content/es/_site.mdx +0 -30
- package/templates/content/fr/_site.mdx +0 -30
- package/templates/content/ja/_site.mdx +0 -30
- package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
- package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
- package/templates/content/pt-BR/HomePage/home.mdx +0 -23
|
@@ -5,9 +5,50 @@
|
|
|
5
5
|
* Covers all models accessed in route tests:
|
|
6
6
|
* auth models (user, apiToken, userData) — used by auth route tests
|
|
7
7
|
* inbox models (lead, subscriber, callback) — used by leads route tests (#507)
|
|
8
|
+
* inbox models (flowDefinition, flowSession) — used by flow-engine service
|
|
9
|
+
* and flows route tests (#3956)
|
|
10
|
+
* inbox models (deliverableAsset, deliverableGrant) — used by the
|
|
11
|
+
* deliverable-fulfillment service + deliverables route tests (#3961)
|
|
12
|
+
* inbox model (waitlistExperiment) — used by the waitlist-experiments
|
|
13
|
+
* service tests (#4661 D5)
|
|
14
|
+
* inbox model waitlistExperiment (its `findUnique` reader) + the new
|
|
15
|
+
* inbox model waitlistScore (its writer) — used by the A2 waitlist-scoring
|
|
16
|
+
* service tests (#4662 D6)
|
|
17
|
+
* inbox model playbookInstance (its `create` writer, append-only, plus
|
|
18
|
+
* `findFirst` for its two U4b readers) — used by the U4a playbook-compile
|
|
19
|
+
* service tests (FEAT-074, #4715) and the U4b deliverable-fulfillment +
|
|
20
|
+
* deliverables-route tests (#4718)
|
|
21
|
+
* inbox model flowCheckout (create / update / updateMany / findUnique /
|
|
22
|
+
* findMany) — the recommender buyer record written by the
|
|
23
|
+
* recommender-capture service (#4837) and stamped by the webhook (#4838)
|
|
24
|
+
* inbox model consentGrant (its `createMany` append-only writer + `findMany`
|
|
25
|
+
* reader) — used by the #5275 consent service + project-listings draft tests
|
|
26
|
+
* inbox model waitlistFollowUpSend (its `create` stamp writer + `findMany`
|
|
27
|
+
* idempotency reader) + callback.findMany (booked-call suppression) +
|
|
28
|
+
* waitlistScore.findMany (tier lookup) — used by the #4732
|
|
29
|
+
* waitlist-followup sweep service tests
|
|
30
|
+
* tenancy models (workspace, membership) — used by tenant-context service tests (#3627)
|
|
31
|
+
* membership.findMany + lead.findMany/updateMany — used by lead-promotion
|
|
32
|
+
* service tests (#3586); lead-promotion.ts never queries membership/workspace
|
|
33
|
+
* directly (it resolves workspaces via tenant-context.ts's listTenancy), so
|
|
34
|
+
* membership.findMany's mocked return shape carries the nested
|
|
35
|
+
* organization.workspaces payload listTenancy expects.
|
|
8
36
|
*
|
|
9
37
|
* Per-test return values: import `prisma` from this module and call
|
|
10
38
|
* (prisma.lead.create as jest.Mock).mockResolvedValue({ id: "..." })
|
|
39
|
+
*
|
|
40
|
+
* usageEvent/usagePeriod (content) + llmModel/llmModelPricing/aiMarkupConfig
|
|
41
|
+
* (config) — used by usage-metering service tests (#3713, FEAT-047 P2.S1).
|
|
42
|
+
* These FEAT-047 P1 models (#3709/#3710) are not yet in the generated Prisma
|
|
43
|
+
* client `usage-metering.ts` compiles against; it casts `prisma` to a local
|
|
44
|
+
* structural delegate type, and this mock provides the matching runtime
|
|
45
|
+
* shape so the mocked-prisma tests can drive it end to end.
|
|
46
|
+
*
|
|
47
|
+
* usageEvent.aggregate + usageBillingReport (content) — used by
|
|
48
|
+
* usage-aggregation service tests (#3714, FEAT-047 P2.S2; `aggregate` since
|
|
49
|
+
* #3739 M2 — was `findMany` + a JS accumulation loop). Same not-yet-
|
|
50
|
+
* generated-client situation; `usage-aggregation.ts` casts `prisma` to its
|
|
51
|
+
* own structural delegate type, and this mock's runtime shape matches it.
|
|
11
52
|
*/
|
|
12
53
|
|
|
13
54
|
export const prisma = {
|
|
@@ -28,18 +69,218 @@ export const prisma = {
|
|
|
28
69
|
upsert: jest.fn(),
|
|
29
70
|
deleteMany: jest.fn(),
|
|
30
71
|
},
|
|
72
|
+
// ── Tenancy namespace (Organization, Workspace, Membership) ──────────────
|
|
73
|
+
organization: {
|
|
74
|
+
findUnique: jest.fn(),
|
|
75
|
+
findFirst: jest.fn(),
|
|
76
|
+
update: jest.fn(),
|
|
77
|
+
// `updateMany` is the credit waterfall's compare-and-swap write (#3730 H1):
|
|
78
|
+
// usage-credits.ts guards its `where` on every column value the read
|
|
79
|
+
// observed, so a concurrent writer makes the predicate miss and the update
|
|
80
|
+
// matches 0 rows. `update` stays mocked beside it — usage-credits.test.ts
|
|
81
|
+
// models it as an unconditional auto-commit so the PRE-fix read-then-write
|
|
82
|
+
// path can still run and double-spend; that asymmetry is what makes the
|
|
83
|
+
// atomicity test load-bearing rather than an API-shape check.
|
|
84
|
+
updateMany: jest.fn(),
|
|
85
|
+
},
|
|
86
|
+
workspace: {
|
|
87
|
+
findUnique: jest.fn(),
|
|
88
|
+
count: jest.fn(),
|
|
89
|
+
},
|
|
90
|
+
membership: {
|
|
91
|
+
findUnique: jest.fn(),
|
|
92
|
+
findMany: jest.fn(),
|
|
93
|
+
create: jest.fn(),
|
|
94
|
+
},
|
|
95
|
+
// ── Config namespace (per-workspace site config — #3641/#3634) ───────────
|
|
96
|
+
workspaceTheme: {
|
|
97
|
+
findUnique: jest.fn(),
|
|
98
|
+
upsert: jest.fn(),
|
|
99
|
+
deleteMany: jest.fn(),
|
|
100
|
+
},
|
|
101
|
+
workspaceSiteIdentity: {
|
|
102
|
+
findUnique: jest.fn(),
|
|
103
|
+
upsert: jest.fn(),
|
|
104
|
+
deleteMany: jest.fn(),
|
|
105
|
+
},
|
|
106
|
+
workspaceSEO: {
|
|
107
|
+
findUnique: jest.fn(),
|
|
108
|
+
upsert: jest.fn(),
|
|
109
|
+
deleteMany: jest.fn(),
|
|
110
|
+
},
|
|
31
111
|
// ── Inbox namespace (Lead, Subscriber, Callback) ─────────────────────────
|
|
32
112
|
lead: {
|
|
33
113
|
create: jest.fn(),
|
|
114
|
+
// `findUnique` — used by the deliverable-resend service (#3998) to
|
|
115
|
+
// resolve a grant's stored lead email.
|
|
116
|
+
findUnique: jest.fn(),
|
|
117
|
+
findMany: jest.fn(),
|
|
118
|
+
updateMany: jest.fn(),
|
|
34
119
|
},
|
|
35
120
|
subscriber: {
|
|
36
121
|
create: jest.fn(),
|
|
37
122
|
findUnique: jest.fn(),
|
|
38
123
|
},
|
|
124
|
+
// `findMany` — the #4732 follow-up sweep's booked-call suppression check
|
|
125
|
+
// (D2: a site's Callback rows fetched once per sweep run, filtered in
|
|
126
|
+
// application code for `payload.email` matches).
|
|
39
127
|
callback: {
|
|
40
128
|
create: jest.fn(),
|
|
129
|
+
findMany: jest.fn(),
|
|
130
|
+
},
|
|
131
|
+
// ── Inbox namespace (FlowDefinition, FlowSession — #3956) ────────────────
|
|
132
|
+
// findMany on both models — used by the abandon-email sweep (#3962 D4),
|
|
133
|
+
// which enumerates every FlowDefinition (to read its live abandonEmail
|
|
134
|
+
// policy) and every candidate FlowSession per definition.
|
|
135
|
+
flowDefinition: {
|
|
136
|
+
findUnique: jest.fn(),
|
|
137
|
+
findMany: jest.fn(),
|
|
138
|
+
upsert: jest.fn(),
|
|
139
|
+
},
|
|
140
|
+
// `deleteMany` — used by the FlowSession retention/cleanup sweep
|
|
141
|
+
// (flow-engine.ts's `cleanupAbandonedSessions`, #3974 AC3).
|
|
142
|
+
flowSession: {
|
|
143
|
+
create: jest.fn(),
|
|
144
|
+
findUnique: jest.fn(),
|
|
145
|
+
findMany: jest.fn(),
|
|
146
|
+
update: jest.fn(),
|
|
147
|
+
deleteMany: jest.fn(),
|
|
148
|
+
},
|
|
149
|
+
// ── Inbox namespace (DeliverableAsset, DeliverableGrant — #3961) ─────────
|
|
150
|
+
deliverableAsset: {
|
|
151
|
+
findUnique: jest.fn(),
|
|
152
|
+
upsert: jest.fn(),
|
|
153
|
+
},
|
|
154
|
+
// `update` — the send-outcome stamp (#3998 D1). `findMany` — the
|
|
155
|
+
// deliverable:resend CLI's list mode (#3998 D4), selecting on
|
|
156
|
+
// `sendStatus: null`.
|
|
157
|
+
deliverableGrant: {
|
|
158
|
+
create: jest.fn(),
|
|
159
|
+
findUnique: jest.fn(),
|
|
160
|
+
update: jest.fn(),
|
|
161
|
+
findMany: jest.fn(),
|
|
162
|
+
},
|
|
163
|
+
// ── Inbox namespace (FlowCheckout — #4837 D4) ─────────────────────────────
|
|
164
|
+
// `create` — recordCheckout's one write; `update` — attachStripeSession;
|
|
165
|
+
// `updateMany` — the webhook's guarded status stamp (#4838); `findUnique` /
|
|
166
|
+
// `findMany` — the webhook's amount read + the dashboard's checkedOut read
|
|
167
|
+
// (#4839).
|
|
168
|
+
flowCheckout: {
|
|
169
|
+
create: jest.fn(),
|
|
170
|
+
update: jest.fn(),
|
|
171
|
+
updateMany: jest.fn(),
|
|
172
|
+
findUnique: jest.fn(),
|
|
173
|
+
findMany: jest.fn(),
|
|
174
|
+
},
|
|
175
|
+
// ── Config namespace (SiteListingConfig — #5033) ─────────────────────────
|
|
176
|
+
// `findUnique` — the ONLY read path (`listingConfigFor`), keyed on siteId.
|
|
177
|
+
// Read server-side by the anonymous create handler and the public read; no
|
|
178
|
+
// route writes it, so no write delegate is mocked.
|
|
179
|
+
siteListingConfig: {
|
|
180
|
+
findUnique: jest.fn(),
|
|
181
|
+
},
|
|
182
|
+
// ── Inbox namespace (ProjectListing — #4998, reshaped #5033) ─────────────
|
|
183
|
+
// `create` — the POST route's row write (after routeLead's Lead write).
|
|
184
|
+
// `findFirst` — the tenant-scoped get/patch reads (scoped by siteId + id, a
|
|
185
|
+
// foreign id is not_found) and `decideListing`'s pre-write windowDays read.
|
|
186
|
+
// `findMany` — the tenant list, the anonymous public read, and
|
|
187
|
+
// `firstFreeSlug`'s site-scoped collision read. `updateMany` — the
|
|
188
|
+
// `decideListing` guarded compare-and-swap write (`where` includes
|
|
189
|
+
// `status: "requested"`, so a concurrent double-decide matches 0 rows on
|
|
190
|
+
// the loser) and `setListingFeatured`'s scoped flag write.
|
|
191
|
+
projectListing: {
|
|
192
|
+
create: jest.fn(),
|
|
193
|
+
findFirst: jest.fn(),
|
|
194
|
+
findMany: jest.fn(),
|
|
195
|
+
update: jest.fn(),
|
|
196
|
+
updateMany: jest.fn(),
|
|
197
|
+
},
|
|
198
|
+
// ── Inbox namespace (WaitlistExperiment — #4661 D5) ──────────────────────
|
|
199
|
+
// `upsert` — the ONLY write path into waitlist_experiments
|
|
200
|
+
// (upsertWaitlistExperiment). `findUnique` — read by the A2 scoring glue
|
|
201
|
+
// (scoreCompletedSignup, #4662 D6) to load the owning experiment's config.
|
|
202
|
+
// `findMany` — the #4771 flow-aggregate service's slug -> register
|
|
203
|
+
// resolver (a scoped-by-siteId read, matched in-memory against every
|
|
204
|
+
// waitlist's `flowSlug`).
|
|
205
|
+
waitlistExperiment: {
|
|
206
|
+
upsert: jest.fn(),
|
|
207
|
+
findUnique: jest.fn(),
|
|
208
|
+
findFirst: jest.fn(),
|
|
209
|
+
findMany: jest.fn(),
|
|
210
|
+
},
|
|
211
|
+
// ── Inbox namespace (WaitlistScore — #4662 D6) ───────────────────────────
|
|
212
|
+
// `upsert` — the ONLY write path into waitlist_scores (scoreCompletedSignup).
|
|
213
|
+
// `findMany` — the #4732 follow-up sweep's tier lookup, batched by leadId.
|
|
214
|
+
// `count` — the #4998 project-listings dashboard's waitlistCount join.
|
|
215
|
+
waitlistScore: {
|
|
216
|
+
upsert: jest.fn(),
|
|
217
|
+
count: jest.fn(),
|
|
218
|
+
findMany: jest.fn(),
|
|
219
|
+
},
|
|
220
|
+
// ── Inbox namespace (PlaybookInstance — FEAT-074 U4a #4715, extended U4b #4718) ──
|
|
221
|
+
// `create` — the ONLY write path into playbook_instances
|
|
222
|
+
// (compileAndStorePlaybook); append-only, never upsert/update. `findFirst`
|
|
223
|
+
// — the `playbook:` virtual-asset existence check
|
|
224
|
+
// (deliverable-fulfillment.ts, U4b D4) + the download route's
|
|
225
|
+
// newest-instance read (playbook-render route, U4b D4).
|
|
226
|
+
playbookInstance: {
|
|
227
|
+
create: jest.fn(),
|
|
228
|
+
findFirst: jest.fn(),
|
|
229
|
+
},
|
|
230
|
+
// ── Inbox namespace (WaitlistFollowUpSend — #4732) ───────────────────────
|
|
231
|
+
// `findMany` — the idempotency reader (already-sent (leadId, followUpKey)
|
|
232
|
+
// pairs). `create` — the send-attempt stamp ('sent' | 'failed'), written
|
|
233
|
+
// for every attempted send, never for a suppressed/skipped candidate.
|
|
234
|
+
waitlistFollowUpSend: {
|
|
235
|
+
findMany: jest.fn(),
|
|
236
|
+
create: jest.fn(),
|
|
237
|
+
},
|
|
238
|
+
// ── Inbox namespace (ListingCsvSend — #5001) ─────────────────────────────
|
|
239
|
+
// `findFirst` — the per-listing watermark read (newest `sentAt`). `create`
|
|
240
|
+
// — the post-send stamp, written only after a successful send so a failed
|
|
241
|
+
// send leaves those rows for next week's CSV.
|
|
242
|
+
listingCsvSend: {
|
|
243
|
+
findFirst: jest.fn(),
|
|
244
|
+
create: jest.fn(),
|
|
245
|
+
},
|
|
246
|
+
// ── Inbox namespace (ConsentGrant — #5275) ───────────────────────────────
|
|
247
|
+
// `createMany` — the ONLY write path into consent_grants (recordConsent /
|
|
248
|
+
// recordLegacyConsent); append-only, never update/upsert. `findMany` — the
|
|
249
|
+
// "what did this subject agree to" read.
|
|
250
|
+
consentGrant: {
|
|
251
|
+
createMany: jest.fn(),
|
|
252
|
+
findMany: jest.fn(),
|
|
253
|
+
},
|
|
254
|
+
// ── Content namespace (UsageEvent, UsagePeriod — FEAT-047 P1.S1/#3709) ──
|
|
255
|
+
usageEvent: {
|
|
256
|
+
create: jest.fn(),
|
|
257
|
+
aggregate: jest.fn(),
|
|
258
|
+
},
|
|
259
|
+
usagePeriod: {
|
|
260
|
+
upsert: jest.fn(),
|
|
261
|
+
},
|
|
262
|
+
// ── Content namespace (UsageBillingReport — FEAT-047 P1.S1/#3709) ────────
|
|
263
|
+
usageBillingReport: {
|
|
264
|
+
upsert: jest.fn(),
|
|
265
|
+
},
|
|
266
|
+
// ── Config namespace (LlmModel, LlmModelPricing, AiMarkupConfig — FEAT-047 P1.S2/#3710) ──
|
|
267
|
+
llmModel: {
|
|
268
|
+
findFirst: jest.fn(),
|
|
269
|
+
},
|
|
270
|
+
llmModelPricing: {
|
|
271
|
+
findFirst: jest.fn(),
|
|
272
|
+
},
|
|
273
|
+
aiMarkupConfig: {
|
|
274
|
+
findFirst: jest.fn(),
|
|
41
275
|
},
|
|
42
276
|
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
277
|
+
// `$transaction` (interactive form) — usage-metering.ts writes the UsageEvent
|
|
278
|
+
// and the UsagePeriod aggregate atomically (#3727 H3: a non-atomic pair lets
|
|
279
|
+
// a failed upsert strand a persisted event that a retry then double-bills).
|
|
280
|
+
// Tests supply the per-case implementation; usage-metering.test.ts's
|
|
281
|
+
// `mockTransaction` helper models commit/rollback so atomicity is asserted,
|
|
282
|
+
// not assumed.
|
|
283
|
+
$transaction: jest.fn(),
|
|
43
284
|
$connect: jest.fn(),
|
|
44
285
|
$disconnect: jest.fn(),
|
|
45
286
|
};
|
|
@@ -34,7 +34,7 @@ import type { LeadCaptureRecord } from "../lead-capture-store.js";
|
|
|
34
34
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
35
35
|
|
|
36
36
|
const record: LeadCaptureRecord = {
|
|
37
|
-
id: "
|
|
37
|
+
id: "fallback-1700000000000-test1",
|
|
38
38
|
createdAt: "2024-01-01T00:00:00.000Z",
|
|
39
39
|
source: "contact",
|
|
40
40
|
data: { email: "test@example.com", message: "hello" },
|
|
@@ -160,7 +160,7 @@ describe("appendLeadRecord", () => {
|
|
|
160
160
|
// Real O_APPEND atomicity is kernel-guaranteed for local filesystems.
|
|
161
161
|
it("50 concurrent writes all resolve ok:true with flag:a", async () => {
|
|
162
162
|
const promises = Array.from({ length: 50 }, (_, i) =>
|
|
163
|
-
appendLeadRecord({ ...record, id: `
|
|
163
|
+
appendLeadRecord({ ...record, id: `fallback-1700000000000-${i}` }),
|
|
164
164
|
);
|
|
165
165
|
const results = await Promise.all(promises);
|
|
166
166
|
expect(results).toHaveLength(50);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file site.ts
|
|
3
|
+
* @description The current-site resolution helper (#3958 §3.2).
|
|
4
|
+
*
|
|
5
|
+
* Extracted verbatim from `routes/flows/index.ts`'s private
|
|
6
|
+
* `currentSiteId()` so the flow-preset seed (`services/flow-engine.ts` →
|
|
7
|
+
* `bin/seed-presets.ts`) resolves the SAME site id the `/v1/flows/*` routes
|
|
8
|
+
* resolve — a seed hard-coding `"default"` would write presets the API
|
|
9
|
+
* cannot find whenever `AILK_SITE_ID` is set (a silent, environment-dependent
|
|
10
|
+
* 404). Behavior is byte-identical to the pre-extraction helper: same env
|
|
11
|
+
* var, same fallback, same call sites.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export function currentSiteId(): string {
|
|
15
|
+
return process.env.AILK_SITE_ID ?? "default";
|
|
16
|
+
}
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
* '@working-theory/stripe-client' — single chokepoint for AILK-internal ergonomics
|
|
9
9
|
* (logger context, request-id propagation) without touching every consumer.
|
|
10
10
|
*/
|
|
11
|
+
import { getStripeClient } from "@working-theory/stripe-client";
|
|
12
|
+
|
|
11
13
|
export {
|
|
12
14
|
assertSandboxKey,
|
|
15
|
+
createBillingPortalSession,
|
|
13
16
|
createCheckoutSession,
|
|
17
|
+
createQuotedCheckoutSession,
|
|
14
18
|
getStripeClient,
|
|
15
19
|
STRIPE_API_VERSION,
|
|
16
20
|
StripeConfigError,
|
|
@@ -21,10 +25,28 @@ export {
|
|
|
21
25
|
} from "@working-theory/stripe-client";
|
|
22
26
|
|
|
23
27
|
export type {
|
|
28
|
+
CreateBillingPortalSessionInput,
|
|
29
|
+
CreateBillingPortalSessionOutput,
|
|
24
30
|
CreateCheckoutSessionInput,
|
|
25
31
|
CreateCheckoutSessionOutput,
|
|
26
32
|
CreateCheckoutSessionParams,
|
|
33
|
+
CreateQuotedCheckoutSessionInput,
|
|
34
|
+
CreateQuotedCheckoutSessionOutput,
|
|
27
35
|
Stripe,
|
|
28
36
|
StripeCheckoutSession,
|
|
29
37
|
StripeWebhookEvent,
|
|
30
38
|
} from "@working-theory/stripe-client";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Expire a Checkout Session (#4912 FR7/D7).
|
|
42
|
+
*
|
|
43
|
+
* The compensation the recommender's attach-miss branch owes: the Stripe
|
|
44
|
+
* session already exists, its id never reached the `FlowCheckout` row, and
|
|
45
|
+
* nothing will ever join the two — so the session is closed rather than left
|
|
46
|
+
* live and payable (#4863). Built on the already-re-exported
|
|
47
|
+
* `getStripeClient`, whose construction asserts the `sk_test_` allow-list, so
|
|
48
|
+
* this call inherits the test-mode gate like every other Stripe call here.
|
|
49
|
+
*/
|
|
50
|
+
export async function expireCheckoutSession(stripeSessionId: string): Promise<void> {
|
|
51
|
+
await getStripeClient().checkout.sessions.expire(stripeSessionId);
|
|
52
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// oss: false
|
|
2
|
+
/**
|
|
3
|
+
* Tenant-scoped data-access boundary (the multi-tenant module's scope layer —
|
|
4
|
+
* FEAT-042, design decision (a)).
|
|
5
|
+
*
|
|
6
|
+
* Every domain-content query (agent_sessions, conversation_events,
|
|
7
|
+
* session_read_receipts, artifacts, artifact_versions) goes through
|
|
8
|
+
* `tenantDb(tenant)`, which injects the resolved `TenantContext.workspaceId`
|
|
9
|
+
* into EVERY where/data clause. There is deliberately no method that accepts
|
|
10
|
+
* a caller-supplied workspaceId, and lookups by id are `findFirst({ id,
|
|
11
|
+
* workspaceId })` — never `findUnique({ id })` — so a foreign-tenant row is
|
|
12
|
+
* indistinguishable from a missing one.
|
|
13
|
+
*
|
|
14
|
+
* Raw `prisma.<domainModel>` access outside this file and
|
|
15
|
+
* `../services/tenant-context.ts` is forbidden by the tenant-scope
|
|
16
|
+
* `forbidden_pattern` in architecture.yaml (CI: `pnpm architecture`).
|
|
17
|
+
*/
|
|
18
|
+
import type {
|
|
19
|
+
AgentSession,
|
|
20
|
+
Artifact,
|
|
21
|
+
ConversationEvent,
|
|
22
|
+
Prisma,
|
|
23
|
+
SessionReadReceipt,
|
|
24
|
+
WorkspaceSEO,
|
|
25
|
+
WorkspaceSiteIdentity,
|
|
26
|
+
WorkspaceTheme,
|
|
27
|
+
} from "@working-theory/database";
|
|
28
|
+
import type { Id } from "@working-theory/ids";
|
|
29
|
+
|
|
30
|
+
import type { TenantContext } from "../services/tenant-context.js";
|
|
31
|
+
|
|
32
|
+
import { prisma } from "./prisma.js";
|
|
33
|
+
|
|
34
|
+
/** Explicit surface (declaration-emit portability + a reviewable contract). */
|
|
35
|
+
export interface TenantDb {
|
|
36
|
+
agentSession: {
|
|
37
|
+
list(limit: number): Promise<AgentSession[]>;
|
|
38
|
+
byId(id: Id<"agentSession">): Promise<AgentSession | null>;
|
|
39
|
+
create(data: { title?: string | null }): Promise<AgentSession>;
|
|
40
|
+
updateDraft(
|
|
41
|
+
id: Id<"agentSession">,
|
|
42
|
+
draftMessage: string | null,
|
|
43
|
+
): Promise<{ count: number }>;
|
|
44
|
+
};
|
|
45
|
+
conversationEvent: {
|
|
46
|
+
listSince(
|
|
47
|
+
sessionId: Id<"agentSession">,
|
|
48
|
+
sinceSeq: number,
|
|
49
|
+
limit: number,
|
|
50
|
+
): Promise<ConversationEvent[]>;
|
|
51
|
+
};
|
|
52
|
+
readReceipt: {
|
|
53
|
+
listForUser(): Promise<Array<Pick<SessionReadReceipt, "sessionId" | "lastReadSeq">>>;
|
|
54
|
+
upsert(
|
|
55
|
+
sessionId: Id<"agentSession">,
|
|
56
|
+
lastReadSeq: number,
|
|
57
|
+
): Promise<SessionReadReceipt>;
|
|
58
|
+
};
|
|
59
|
+
artifact: {
|
|
60
|
+
listBySession(sessionId: Id<"agentSession">): Promise<Artifact[]>;
|
|
61
|
+
byId(id: Id<"artifact">): Promise<Artifact | null>;
|
|
62
|
+
};
|
|
63
|
+
artifactVersion: {
|
|
64
|
+
listByArtifact(
|
|
65
|
+
artifactId: Id<"artifact">,
|
|
66
|
+
): Promise<
|
|
67
|
+
Array<{ id: Id<"artifactVersion">; savedAt: Date; isCurrent: boolean }>
|
|
68
|
+
>;
|
|
69
|
+
};
|
|
70
|
+
// Per-workspace site config (#3634). Each model is `workspaceId @unique`, so
|
|
71
|
+
// the unique key IS the tenant scope — `findUnique`/`upsert` on
|
|
72
|
+
// `{ workspaceId }` never accepts a caller-supplied scope.
|
|
73
|
+
theme: {
|
|
74
|
+
get(): Promise<WorkspaceTheme | null>;
|
|
75
|
+
upsert(data: unknown): Promise<WorkspaceTheme>;
|
|
76
|
+
};
|
|
77
|
+
siteIdentity: {
|
|
78
|
+
get(): Promise<WorkspaceSiteIdentity | null>;
|
|
79
|
+
upsert(data: unknown): Promise<WorkspaceSiteIdentity>;
|
|
80
|
+
};
|
|
81
|
+
seo: {
|
|
82
|
+
get(): Promise<WorkspaceSEO | null>;
|
|
83
|
+
upsert(data: unknown): Promise<WorkspaceSEO>;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function tenantDb(tenant: TenantContext): TenantDb {
|
|
88
|
+
const { workspaceId, userId } = tenant;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
agentSession: {
|
|
92
|
+
list(limit: number) {
|
|
93
|
+
return prisma.agentSession.findMany({
|
|
94
|
+
where: { workspaceId },
|
|
95
|
+
orderBy: [
|
|
96
|
+
{ lastMessageAt: { sort: "desc", nulls: "last" } },
|
|
97
|
+
{ createdAt: "desc" },
|
|
98
|
+
],
|
|
99
|
+
take: limit,
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
byId(id: string) {
|
|
103
|
+
return prisma.agentSession.findFirst({
|
|
104
|
+
where: { id, workspaceId },
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
create(data: { title?: string | null; agentId?: string }) {
|
|
108
|
+
return prisma.agentSession.create({
|
|
109
|
+
data: { title: data.title ?? null, workspaceId },
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
updateDraft(id: string, draftMessage: string | null) {
|
|
113
|
+
return prisma.agentSession.updateMany({
|
|
114
|
+
where: { id, workspaceId },
|
|
115
|
+
data: { draftMessage },
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
conversationEvent: {
|
|
121
|
+
listSince(sessionId: string, sinceSeq: number, limit: number) {
|
|
122
|
+
return prisma.conversationEvent.findMany({
|
|
123
|
+
where: { sessionId, workspaceId, seq: { gt: sinceSeq } },
|
|
124
|
+
orderBy: { seq: "asc" },
|
|
125
|
+
take: limit,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
readReceipt: {
|
|
131
|
+
/** Read positions for the CALLING user across this workspace's sessions. */
|
|
132
|
+
listForUser() {
|
|
133
|
+
return prisma.sessionReadReceipt.findMany({
|
|
134
|
+
where: { workspaceId, userId },
|
|
135
|
+
select: { sessionId: true, lastReadSeq: true },
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
upsert(sessionId: string, lastReadSeq: number) {
|
|
139
|
+
return prisma.sessionReadReceipt.upsert({
|
|
140
|
+
where: { sessionId_userId: { sessionId, userId } },
|
|
141
|
+
create: { sessionId, workspaceId, userId, lastReadSeq },
|
|
142
|
+
update: { lastReadSeq },
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
artifact: {
|
|
148
|
+
listBySession(sessionId: string) {
|
|
149
|
+
return prisma.artifact.findMany({
|
|
150
|
+
where: { sessionId, workspaceId },
|
|
151
|
+
orderBy: { createdAt: "asc" },
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
byId(id: string) {
|
|
155
|
+
return prisma.artifact.findFirst({ where: { id, workspaceId } });
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
artifactVersion: {
|
|
160
|
+
listByArtifact(artifactId: string) {
|
|
161
|
+
// Brand assertion at the Prisma read edge (the DB CHECK constraints
|
|
162
|
+
// guarantee the id shape).
|
|
163
|
+
return prisma.artifactVersion.findMany({
|
|
164
|
+
where: { artifactId, workspaceId },
|
|
165
|
+
orderBy: { savedAt: "desc" },
|
|
166
|
+
select: {
|
|
167
|
+
id: true,
|
|
168
|
+
savedAt: true,
|
|
169
|
+
isCurrent: true,
|
|
170
|
+
},
|
|
171
|
+
}) as Promise<
|
|
172
|
+
Array<{ id: Id<"artifactVersion">; savedAt: Date; isCurrent: boolean }>
|
|
173
|
+
>;
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// Site config — scoped by the `@unique` workspaceId (the unique key IS the
|
|
178
|
+
// scope). The validated blob is stored in the `data` Json column; validation
|
|
179
|
+
// happens at the route boundary against the shared @working-theory/site-config
|
|
180
|
+
// schema, so the DB layer stores an already-validated object.
|
|
181
|
+
theme: {
|
|
182
|
+
get() {
|
|
183
|
+
return prisma.workspaceTheme.findUnique({ where: { workspaceId } });
|
|
184
|
+
},
|
|
185
|
+
upsert(data: unknown) {
|
|
186
|
+
const json = data as Prisma.InputJsonValue;
|
|
187
|
+
return prisma.workspaceTheme.upsert({
|
|
188
|
+
where: { workspaceId },
|
|
189
|
+
create: { workspaceId, data: json },
|
|
190
|
+
update: { data: json },
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
siteIdentity: {
|
|
196
|
+
get() {
|
|
197
|
+
return prisma.workspaceSiteIdentity.findUnique({
|
|
198
|
+
where: { workspaceId },
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
upsert(data: unknown) {
|
|
202
|
+
const json = data as Prisma.InputJsonValue;
|
|
203
|
+
return prisma.workspaceSiteIdentity.upsert({
|
|
204
|
+
where: { workspaceId },
|
|
205
|
+
create: { workspaceId, data: json },
|
|
206
|
+
update: { data: json },
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
seo: {
|
|
212
|
+
get() {
|
|
213
|
+
return prisma.workspaceSEO.findUnique({ where: { workspaceId } });
|
|
214
|
+
},
|
|
215
|
+
upsert(data: unknown) {
|
|
216
|
+
const json = data as Prisma.InputJsonValue;
|
|
217
|
+
return prisma.workspaceSEO.upsert({
|
|
218
|
+
where: { workspaceId },
|
|
219
|
+
create: { workspaceId, data: json },
|
|
220
|
+
update: { data: json },
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// oss: false
|
|
2
|
+
/**
|
|
3
|
+
* Short-lived stream tokens (BACKEND-REQUIREMENTS R-T3): the push channel is
|
|
4
|
+
* authenticated by a per-session token minted over REST, not the ambient
|
|
5
|
+
* cookie. Compact HMAC-SHA256 JWT via node:crypto — no new dependency; the
|
|
6
|
+
* Phase-6 realtime server verifies with `verifyWsToken`.
|
|
7
|
+
*
|
|
8
|
+
* Secret resolution mirrors apps/web's Better Auth pattern: AILK_WS_TOKEN_SECRET
|
|
9
|
+
* wins, BETTER_AUTH_SECRET is the shared fallback, and a dev-only default
|
|
10
|
+
* keeps the OSS zero-config boot working. Production deployments MUST set one
|
|
11
|
+
* of the two env vars.
|
|
12
|
+
*/
|
|
13
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
import { getClock } from "@working-theory/common";
|
|
16
|
+
import type { Id } from "@working-theory/ids";
|
|
17
|
+
|
|
18
|
+
export const WS_TOKEN_TTL_SECONDS = 60;
|
|
19
|
+
|
|
20
|
+
export interface WsTokenClaims {
|
|
21
|
+
/** Caller user id. */
|
|
22
|
+
sub: Id<"user">;
|
|
23
|
+
/** Agent-session id the stream is scoped to. */
|
|
24
|
+
sid: Id<"agentSession">;
|
|
25
|
+
/** Workspace id the session belongs to (tenant scope). */
|
|
26
|
+
wid: Id<"workspace">;
|
|
27
|
+
iat: number;
|
|
28
|
+
exp: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveSecret(): string {
|
|
32
|
+
const secret =
|
|
33
|
+
process.env.AILK_WS_TOKEN_SECRET ?? process.env.BETTER_AUTH_SECRET;
|
|
34
|
+
if (secret) return secret;
|
|
35
|
+
// Fail CLOSED in production (security review M2): a token-minting path
|
|
36
|
+
// must never run on a guessable default secret.
|
|
37
|
+
if (process.env.NODE_ENV === "production") {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"ws-token: set AILK_WS_TOKEN_SECRET or BETTER_AUTH_SECRET in production",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return "ailk-dev-secret-do-not-use-in-production";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function b64url(input: Buffer | string): string {
|
|
46
|
+
return Buffer.from(input).toString("base64url");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sign(payload: string): string {
|
|
50
|
+
return createHmac("sha256", resolveSecret()).update(payload).digest("base64url");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Mint a short-lived stream token for (user, session, workspace). */
|
|
54
|
+
export function mintWsToken(params: {
|
|
55
|
+
userId: Id<"user">;
|
|
56
|
+
sessionId: Id<"agentSession">;
|
|
57
|
+
workspaceId: Id<"workspace">;
|
|
58
|
+
}): { token: string; expiresAt: string } {
|
|
59
|
+
const nowSec = Math.floor(getClock().now() / 1000);
|
|
60
|
+
const claims: WsTokenClaims = {
|
|
61
|
+
sub: params.userId,
|
|
62
|
+
sid: params.sessionId,
|
|
63
|
+
wid: params.workspaceId,
|
|
64
|
+
iat: nowSec,
|
|
65
|
+
exp: nowSec + WS_TOKEN_TTL_SECONDS,
|
|
66
|
+
};
|
|
67
|
+
const head = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
68
|
+
const body = b64url(JSON.stringify(claims));
|
|
69
|
+
const token = `${head}.${body}.${sign(`${head}.${body}`)}`;
|
|
70
|
+
return { token, expiresAt: new Date(claims.exp * 1000).toISOString() };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Verify a stream token; returns claims or null (invalid / expired). */
|
|
74
|
+
export function verifyWsToken(token: string): WsTokenClaims | null {
|
|
75
|
+
const [head, body, sig] = token.split(".");
|
|
76
|
+
if (!head || !body || !sig) return null;
|
|
77
|
+
const expected = sign(`${head}.${body}`);
|
|
78
|
+
const a = Buffer.from(sig);
|
|
79
|
+
const b = Buffer.from(expected);
|
|
80
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
|
|
81
|
+
try {
|
|
82
|
+
const claims = JSON.parse(
|
|
83
|
+
Buffer.from(body, "base64url").toString("utf-8"),
|
|
84
|
+
) as WsTokenClaims;
|
|
85
|
+
if (typeof claims.exp !== "number") return null;
|
|
86
|
+
if (claims.exp * 1000 <= getClock().now()) return null;
|
|
87
|
+
return claims;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -72,7 +72,13 @@ describe("S13 adversarial — session fixation", () => {
|
|
|
72
72
|
expect(res.statusCode).toBe(401);
|
|
73
73
|
expect(prisma.session.findUnique).toHaveBeenCalledWith({
|
|
74
74
|
where: { token: "pre_rotation_token" },
|
|
75
|
-
select: {
|
|
75
|
+
select: {
|
|
76
|
+
id: true,
|
|
77
|
+
userId: true,
|
|
78
|
+
expiresAt: true,
|
|
79
|
+
activeOrganizationId: true,
|
|
80
|
+
activeWorkspaceId: true,
|
|
81
|
+
},
|
|
76
82
|
});
|
|
77
83
|
});
|
|
78
84
|
|