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
|
@@ -252,6 +252,437 @@ Print:
|
|
|
252
252
|
- Pointer at `scaffold-page` (#362) for wiring pricing UI.
|
|
253
253
|
- Reminder: sandbox-only at v1. Live keys are hook-blocked globally.
|
|
254
254
|
|
|
255
|
+
### Step 10 — Surface-selection branch (`feature:commerce` only, FEAT-023 U1)
|
|
256
|
+
|
|
257
|
+
This step is **additive** — it runs after Step 9's closing summary and
|
|
258
|
+
never changes anything above it. A site without the `commerce`
|
|
259
|
+
entitlement gets exactly today's flow (Steps 1–9) and stops there.
|
|
260
|
+
|
|
261
|
+
**10a — Fail-closed entitlement check.** Determine whether this site is
|
|
262
|
+
entitled to `feature:commerce` (the license-manifest feature added in
|
|
263
|
+
FEAT-023 U1 — `commerce` is included in the `pro` / `pro_plus` /
|
|
264
|
+
`enterprise` tiers, absent from `free`):
|
|
265
|
+
|
|
266
|
+
- If the consumer's project has a resolvable license — a
|
|
267
|
+
`WT_LICENSE_KEY` (or equivalent) env var resolving to a
|
|
268
|
+
`@working-theory/license` `License` — call
|
|
269
|
+
`hasFeature("commerce", license)` (`@working-theory/license-client`,
|
|
270
|
+
static-mode: sync, no network). `true` → entitled; proceed to 10b.
|
|
271
|
+
- **Any other case is NOT entitled — fail closed:** no license
|
|
272
|
+
configured, the license lacks `commerce`, or `hasFeature` throws.
|
|
273
|
+
Do nothing further. The consumer already has the full link path +
|
|
274
|
+
product-creation flow from Steps 1–9; there is no additional
|
|
275
|
+
action or message needed.
|
|
276
|
+
|
|
277
|
+
Do not attempt to determine entitlement by asking the consumer their
|
|
278
|
+
plan conversationally — the check is against the resolved license, not
|
|
279
|
+
a verbal claim.
|
|
280
|
+
|
|
281
|
+
**10b — Offer the surface-selection branch.** For an entitled site,
|
|
282
|
+
ask:
|
|
283
|
+
|
|
284
|
+
> "Want to scaffold a payment surface for these products? Pick one (or
|
|
285
|
+
> 'skip' for now):
|
|
286
|
+
>
|
|
287
|
+
> - **Checkout** — a hosted or embedded checkout flow (one-time or
|
|
288
|
+
> subscription).
|
|
289
|
+
> - **Pay-by-link** — a shareable payment link, no page to build.
|
|
290
|
+
> - **Invoice** — send + track invoices with a status badge.
|
|
291
|
+
> - **Subscription** — recurring billing with a status badge.
|
|
292
|
+
> - **Portal** — a self-serve billing-management page for customers."
|
|
293
|
+
|
|
294
|
+
**10c — Re-run the live-key check.** Before dispatching to any surface
|
|
295
|
+
scaffolder, re-run Step 1's live-key check (env scan + paste
|
|
296
|
+
detection) — belt-and-suspenders, since surface scaffolding may run in
|
|
297
|
+
a session well after the original product-creation pass.
|
|
298
|
+
|
|
299
|
+
**10d — Instantiate the shared config.** Copy
|
|
300
|
+
`templates/stripe-config.template.ts` into the consumer's `apps/api`
|
|
301
|
+
(or the surface's own route directory) as the shared
|
|
302
|
+
`loadStripeSurfaceConfig()` the surface's route handler calls at
|
|
303
|
+
request time. This carries the test-mode-only guard
|
|
304
|
+
(`assertSandboxKey`, `@working-theory/stripe-client`) into the new
|
|
305
|
+
surface entry point — the same belt-and-suspenders guard
|
|
306
|
+
`getStripeClient()` already applies, so a live key is refused here too,
|
|
307
|
+
not only at the shared Stripe client singleton.
|
|
308
|
+
|
|
309
|
+
**10e — Dispatch to the matching surface scaffolder.** **Checkout
|
|
310
|
+
(one-time)** (10e-i, FEAT-023 U2), **Pay by link** (10e-ii, FEAT-023 U3),
|
|
311
|
+
**Invoice** (10g, FEAT-023 U4), **Subscription** (10h, FEAT-023 U5), and
|
|
312
|
+
**Portal** (10i, FEAT-023 U6) are all available inline. If a future choice's
|
|
313
|
+
matching skill is not installed, say so plainly:
|
|
314
|
+
|
|
315
|
+
> "Payment-surface scaffolding for `<choice>` isn't available in this
|
|
316
|
+
> AILK version yet — the foundation (entitlement gate + shared
|
|
317
|
+
> config + UI primitives) is in place; the surface itself ships in a
|
|
318
|
+
> later update."
|
|
319
|
+
|
|
320
|
+
Do not attempt to hand-build the surface yourself when its scaffolder
|
|
321
|
+
is missing — that would bypass the recomposition method (§8: capability
|
|
322
|
+
checklist → `packages/ui` primitives, never a verbatim UI lift) every
|
|
323
|
+
FEAT-023 UI surface must follow.
|
|
324
|
+
|
|
325
|
+
**10e-i — Checkout (one-time), FEAT-023 U2.** When the consumer picks
|
|
326
|
+
**Checkout** and wants a one-time payment (not a subscription — v1 only
|
|
327
|
+
scaffolds one-time; acknowledge and stop if the consumer wants
|
|
328
|
+
recurring billing, that's a future `mode: "subscription"` branch):
|
|
329
|
+
|
|
330
|
+
1. **Pick the product(s).** Offer the `lookup_key`s already created this
|
|
331
|
+
session (Step 3/5) as the checkout's product choices. If none were
|
|
332
|
+
created yet, run Steps 2–5 first (or ask for at least one product).
|
|
333
|
+
2. **Instantiate the route.** Copy
|
|
334
|
+
`templates/checkout-route.template.ts` next to the already-instantiated
|
|
335
|
+
`stripe-config.ts` (Step 10d) in the consumer's `apps/api/src/routes/checkout/`
|
|
336
|
+
(checkout creation is server-side only — `apps/web` may not import
|
|
337
|
+
`@working-theory/stripe-client`, architecture.yaml). Register its exported
|
|
338
|
+
`oneTimeCheckoutHandler` on a `POST` route, e.g.
|
|
339
|
+
`POST /api/checkout/one-time`, SCOPED BEHIND the shared per-IP rate-limit
|
|
340
|
+
middleware (`apps/api/src/middleware/rate-limit.ts`, already present in
|
|
341
|
+
every consumer's `apps/api` — the same one `server.ts` wires around the
|
|
342
|
+
anonymous "leads" routes). This is a public, unauthenticated, single-purpose
|
|
343
|
+
session-creation endpoint by design (see the template's own header comment)
|
|
344
|
+
— the rate limit is its abuse guard against unbounded session-creation /
|
|
345
|
+
card-testing attempts; do not register it unscoped. Ask the consumer to
|
|
346
|
+
confirm the route path if their `apps/api` layout differs from the default.
|
|
347
|
+
|
|
348
|
+
**Two edits are MANDATORY as you instantiate it. The route is fail-closed
|
|
349
|
+
until both are done — every request 404s or 500s. This is deliberate: a
|
|
350
|
+
checkout route that is wrong in either respect is exploitable by an
|
|
351
|
+
anonymous internet caller.**
|
|
352
|
+
|
|
353
|
+
**(a) Pin the purchasable-product allowlist.** Replace the placeholder in
|
|
354
|
+
the route's `ALLOWED_LOOKUP_KEYS` with EXACTLY the `lookup_key`s the
|
|
355
|
+
consumer selected at step 1 — nothing else:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
const ALLOWED_LOOKUP_KEYS: ReadonlySet<string> = new Set<string>([
|
|
359
|
+
"pizza_onetime", // ← the step-1 selection, verbatim
|
|
360
|
+
"combo_onetime",
|
|
361
|
+
]);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Why this is not optional: without it, the route resolves ANY
|
|
365
|
+
caller-supplied `lookupKey` against the merchant's ENTIRE Stripe account.
|
|
366
|
+
A merchant with a public `pizza_onetime` ($15) and a private
|
|
367
|
+
`enterprise_annual_90pct_off` in the same account is one guessed lookup
|
|
368
|
+
key away from selling the discounted price to an anonymous `curl`; any
|
|
369
|
+
`$0` comp/test price becomes a free-fulfillment endpoint. Pin the
|
|
370
|
+
products this surface sells — never the account. Do not offer to
|
|
371
|
+
"allow all keys", and do not read the allowlist from an env var or the
|
|
372
|
+
request: it is a source-pinned constant.
|
|
373
|
+
|
|
374
|
+
**(b) Set `FRONTEND_URL`.** The route locks every `successUrl` /
|
|
375
|
+
`cancelUrl` to the site's own origin, read from `FRONTEND_URL` (the same
|
|
376
|
+
env var the consumer's `apps/api` already uses for CORS —
|
|
377
|
+
`apps/api/src/config/index.ts`). Confirm it is set for each environment
|
|
378
|
+
(dev / preview / prod). If it is unset the route returns
|
|
379
|
+
`500 config-error` and refuses every checkout — fail-closed by design.
|
|
380
|
+
Without the origin lock, an anonymous caller can mint a REAL
|
|
381
|
+
`checkout.stripe.com` session for the real merchant whose success page is
|
|
382
|
+
an attacker's — the victim pays on Stripe's real domain and lands on the
|
|
383
|
+
attacker's "receipt".
|
|
384
|
+
|
|
385
|
+
3. **Compose the checkout page.** In the consumer's `apps/web`, create a
|
|
386
|
+
page that renders `CheckoutForm` (`@working-theory/ui`) with the
|
|
387
|
+
session's products as `CheckoutProduct[]` (`lookupKey` + `name` + price).
|
|
388
|
+
Wire `onCheckout` to POST to the Step-2 route with `{ lookupKey,
|
|
389
|
+
successUrl, cancelUrl }` and, on a `201` response, set
|
|
390
|
+
`window.location.href` to the returned `url`. Pass `testMode` (always
|
|
391
|
+
`true` at v1 — sandbox-only). Ask which page path to use (e.g.
|
|
392
|
+
`app/checkout/page.tsx`), mirroring the link path's "ask which"
|
|
393
|
+
convention. Send the redirects as RELATIVE PATHS (`/checkout/success`,
|
|
394
|
+
`/checkout/cancel`): the route composes them onto the site's own origin
|
|
395
|
+
server-side. A cross-origin absolute URL is refused with
|
|
396
|
+
`400 invalid-redirect-url`.
|
|
397
|
+
4. **Compose the success/cancel pages.** Create two pages (e.g.
|
|
398
|
+
`app/checkout/success/page.tsx`, `app/checkout/cancel/page.tsx`) at the
|
|
399
|
+
`successUrl`/`cancelUrl` the checkout page passed, each rendering
|
|
400
|
+
`PaymentResult` (`@working-theory/ui`) with `outcome="success"` /
|
|
401
|
+
`outcome="cancel"` respectively and the consumer's own copy.
|
|
402
|
+
|
|
403
|
+
**Tell the consumer, in these words: the success page is a DISPLAY-ONLY
|
|
404
|
+
terminal state; it is NEVER proof of payment.** Anyone can navigate
|
|
405
|
+
straight to `/checkout/success` and be shown "Payment successful". So
|
|
406
|
+
never hang fulfillment, an entitlement grant, a license issue, or a file
|
|
407
|
+
download off that page. Real fulfillment must be driven by a verified
|
|
408
|
+
Stripe webhook (signature-checked) or a server-side
|
|
409
|
+
`stripe.checkout.sessions.retrieve(id)` whose `payment_status === "paid"`
|
|
410
|
+
— neither of which this v1 surface scaffolds. If the consumer asks for
|
|
411
|
+
fulfillment, that is the next unit of work, not a tweak to this page.
|
|
412
|
+
|
|
413
|
+
5. **Surface the session id.** After the flow is wired, remind the
|
|
414
|
+
consumer that each created Checkout Session's id is logged (truncated,
|
|
415
|
+
no PII) by the route for verification in
|
|
416
|
+
<https://dashboard.stripe.com/test/payments>.
|
|
417
|
+
6. **Closing note.** Confirm the surface is reachable only because
|
|
418
|
+
`feature:commerce` resolved entitled (10a) — a non-entitled site never
|
|
419
|
+
sees this branch. Note that this entitlement check is SCAFFOLD-TIME only:
|
|
420
|
+
the emitted route reads no license at runtime, so a lapsed
|
|
421
|
+
`feature:commerce` does not switch the endpoint off.
|
|
422
|
+
|
|
423
|
+
Remind the consumer this is test-mode only. Be precise about what the
|
|
424
|
+
live-key guard actually covers: `assertSandboxKey` (10d's
|
|
425
|
+
`loadStripeSurfaceConfig`, and the shared Stripe client singleton) is an
|
|
426
|
+
ALLOW-LIST as of AILK#3734 — it accepts only `sk_test_`, `pk_test_` and
|
|
427
|
+
`rk_test_`, and refuses everything else. A Stripe **restricted** live key
|
|
428
|
+
(`rk_live_…`) — the key type Stripe steers server-side integrations toward
|
|
429
|
+
— IS refused, along with any live prefix Stripe introduces later, because
|
|
430
|
+
it is absent from the allow-list rather than named on a ban-list.
|
|
431
|
+
So: "only recognized test-mode keys are accepted", not "these particular
|
|
432
|
+
live prefixes are blocked". Tell the consumer to use a test-mode key and to
|
|
433
|
+
keep live keys out of the environment entirely anyway — a working guard is
|
|
434
|
+
not a reason to point a live key at a non-production deployment.
|
|
435
|
+
|
|
436
|
+
Finally, confirm all three abuse guards are actually in place: the
|
|
437
|
+
rate-limit middleware is wired (step 2 — an unauthenticated checkout route
|
|
438
|
+
without it is a card-testing vector), `ALLOWED_LOOKUP_KEYS` is pinned to
|
|
439
|
+
the real selection (step 2a), and `FRONTEND_URL` is set for every
|
|
440
|
+
environment (step 2b).
|
|
441
|
+
|
|
442
|
+
**10e-ii — Pay-by-link (payment-link management), FEAT-023 U3.** When
|
|
443
|
+
the consumer picks **Pay by link**, instantiate
|
|
444
|
+
`templates/payment-link-route.template.ts` next to the already-instantiated
|
|
445
|
+
`stripe-config.ts` (Step 10d) in the consumer's
|
|
446
|
+
`apps/api/src/routes/payment-links/` — server-side only, same reason as
|
|
447
|
+
checkout (`apps/web` may not import `@working-theory/stripe-client`,
|
|
448
|
+
architecture.yaml).
|
|
449
|
+
|
|
450
|
+
This is a **MANAGEMENT** surface (list / create / archive payment objects),
|
|
451
|
+
NOT a storefront "buy" action — so its auth posture is the OPPOSITE of the
|
|
452
|
+
checkout route's. Register its three exported handlers INSIDE the
|
|
453
|
+
authenticated scope, so `authPreHandlerPlugin` decorates `request.userId`
|
|
454
|
+
(and applies its same-origin CSRF check) before any handler runs:
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
await fastify.register(async (authed) => {
|
|
458
|
+
authed.register(authPreHandlerPlugin, { frontendOrigin: config.frontendUrl });
|
|
459
|
+
authed.get("/api/payment-links", listPaymentLinksHandler);
|
|
460
|
+
authed.post("/api/payment-links", createPaymentLinkHandler);
|
|
461
|
+
authed.patch("/api/payment-links", updatePaymentLinkHandler);
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
The registration is **load-bearing** — do not copy the checkout route's
|
|
466
|
+
public, rate-limited registration here:
|
|
467
|
+
|
|
468
|
+
1. **Authentication + authorization.** Each handler calls `requireSiteAdmin()`
|
|
469
|
+
FIRST — before the entitlement check and before any Stripe call. It
|
|
470
|
+
re-reads `User.role` from the database and admits only `admin` / `owner`.
|
|
471
|
+
Authentication alone is not the bar: on a site with open signup, "any
|
|
472
|
+
logged-in user" is "the whole internet with an extra step".
|
|
473
|
+
2. **Entitlement is NOT authorization.** The `feature:commerce` check answers
|
|
474
|
+
"does this DEPLOYMENT have commerce?", never "may this CALLER manage
|
|
475
|
+
payment links?" — it reads the site's env license and returns the identical
|
|
476
|
+
answer to an anonymous caller. It is retained, layered UNDER the identity
|
|
477
|
+
gate, never as a substitute for it. (Shipping this surface with ONLY the
|
|
478
|
+
entitlement gate was AILK#3744: a P0 that let an anonymous caller list every
|
|
479
|
+
payment link, archive them all — total denial of revenue — and mint new ones
|
|
480
|
+
against the merchant's Stripe account.)
|
|
481
|
+
3. **CSRF** is inherited from the auth scope's same-origin `Origin` check
|
|
482
|
+
(`apps/api/src/middleware/auth.ts`), which exists ONLY inside that scope.
|
|
483
|
+
4. **Fail-closed by construction.** Mis-register the routes outside the auth
|
|
484
|
+
scope and `request.userId` is `undefined`, so every handler answers 401 — a
|
|
485
|
+
broken feature, never an open door.
|
|
486
|
+
5. **Tenancy.** Deliberately NOT `tenantScope`. This surface manages the SITE
|
|
487
|
+
OWNER's single Stripe catalog (`packages/stripe-client` is single-tenant; a
|
|
488
|
+
Stripe Payment Link carries no org binding). Cross-account access is
|
|
489
|
+
structurally impossible — Stripe object ids are account-scoped, so a foreign
|
|
490
|
+
id returns `resource_missing`, which the route maps to the same 404 as an
|
|
491
|
+
unknown id (no enumeration oracle). The multi-tenant module's org-scoped
|
|
492
|
+
billing is a SEPARATE surface (`apps/api/src/routes/billing/`), which DOES
|
|
493
|
+
derive its org from `request.tenant.organizationId`.
|
|
494
|
+
|
|
495
|
+
Test-mode-only caveats are identical to checkout's (step 10e-i) — state the
|
|
496
|
+
`assertSandboxKey` allow-list (AILK#3734): only recognized test-mode prefixes
|
|
497
|
+
are accepted, so every live key is refused by construction.
|
|
498
|
+
|
|
499
|
+
**10f — Shared UI primitives.** Every surface scaffolder composes from
|
|
500
|
+
`@working-theory/ui`'s `MoneyAmount` (primitive), `PaymentStatusBadge`
|
|
501
|
+
(block), and `StripeActionButton` (block) — built once in FEAT-023 U1
|
|
502
|
+
so no surface hand-rolls its own amount formatting, status chip, or
|
|
503
|
+
checkout/portal button.
|
|
504
|
+
|
|
505
|
+
**10g — Invoice surface, shipped (FEAT-023 U4).** When the consumer picks
|
|
506
|
+
"Invoice" at 10b, this surface IS available (unlike the still-unshipped
|
|
507
|
+
choices in 10e). Invoice management is an ADMIN action, never a customer-
|
|
508
|
+
facing or anonymous one (a security review of the sibling U3 pay-by-link
|
|
509
|
+
template found the entitlement check alone is NOT authentication — see
|
|
510
|
+
`docs/research/commerce-template-audit/invoices.md` finding 7):
|
|
511
|
+
|
|
512
|
+
1. Instantiate `templates/invoice-route.template.ts` into the consumer's
|
|
513
|
+
`apps/api` — mirrors 10d's `stripe-config.template.ts` instantiation. It
|
|
514
|
+
exports a Fastify plugin (`invoiceRoutes`: `GET` list/retrieve, `POST`
|
|
515
|
+
create-draft, `POST .../send` finalize+send) built on
|
|
516
|
+
`@working-theory/stripe-client`'s `createInvoice` / `sendInvoice` /
|
|
517
|
+
`listInvoices` / `retrieveInvoice`.
|
|
518
|
+
2. **Register it inside the TENANT-GUARDED scope `apps/api`'s
|
|
519
|
+
`billingRoutes` use** — the inner `tenantScope` in `server.ts` that
|
|
520
|
+
registers `tenantPreHandlerPlugin` (itself nested inside `authScope`) —
|
|
521
|
+
e.g. `await tenantScope.register(invoiceRoutes);`. **NOT** the outer
|
|
522
|
+
`authScope`. This is REQUIRED, not optional, and it is a SECURITY
|
|
523
|
+
boundary, not a style preference:
|
|
524
|
+
- `authPreHandlerPlugin` (inherited from the enclosing `authScope`) gives
|
|
525
|
+
every handler an authenticated caller plus the cookie path's same-origin
|
|
526
|
+
CSRF check. The template then requires `User.role` in {`admin`,`owner`},
|
|
527
|
+
so anonymous and plain-authenticated callers are refused.
|
|
528
|
+
- `tenantPreHandlerPlugin` (default-deny) populates `request.tenant`, so
|
|
529
|
+
the org is **server-derived** and never client-supplied — the same
|
|
530
|
+
property `routes/billing/read.ts` relies on. Registering in `authScope`
|
|
531
|
+
instead leaves `request.tenant` undefined, and the template then fails
|
|
532
|
+
**closed** (403) rather than serving an unscoped admin surface.
|
|
533
|
+
Tell the consumer explicitly that this route is admin-only, and that a
|
|
534
|
+
Bearer-token caller is denied (a bearer token carries no workspace
|
|
535
|
+
binding) — this is a cookie-session admin-UI surface.
|
|
536
|
+
3. Point the consumer at `@working-theory/ui`'s `InvoiceList` (block),
|
|
537
|
+
`InvoiceDetail` (panel), and `InvoiceForm` (block) to compose the
|
|
538
|
+
admin-facing management page — `InvoiceForm` posts directly to the
|
|
539
|
+
instantiated route; `InvoiceList`/`InvoiceDetail` render already-fetched
|
|
540
|
+
invoice records (reusing `PaymentStatusBadge` for status and
|
|
541
|
+
`MoneyAmount` for amounts, per 10f).
|
|
542
|
+
4. Re-run 10c's live-key check before instantiating (already covered by
|
|
543
|
+
this step's ordering, restated for clarity). The guard has no remaining
|
|
544
|
+
gap: AILK#3734 converted `assertSandboxKey` to an allow-list, so a Stripe
|
|
545
|
+
RESTRICTED key is refused like every other non-test-mode key. Finding 10
|
|
546
|
+
in the audit log records the historical deny-list gap and is marked
|
|
547
|
+
resolved there.
|
|
548
|
+
5. **Multi-tenant module — the surface REFUSES, it does not warn.** v1 has
|
|
549
|
+
ONE deployment-wide `STRIPE_SECRET_KEY` and no per-organization Stripe
|
|
550
|
+
customer namespace, so an invoice carries no org attribution and a
|
|
551
|
+
caller-supplied `invoiceId`/`customerId` cannot be honestly checked
|
|
552
|
+
against the caller's org (findings 7 + 9 in the audit log). The template's
|
|
553
|
+
`requireSingleTenantScope` guard therefore admits **only the deployment's
|
|
554
|
+
own operator org** — the fixed single-tenant sentinel `DEFAULT_ORG_ID`,
|
|
555
|
+
which an organization created through the multi-tenant module never
|
|
556
|
+
carries — and **hard-refuses every other org's admin with a 403
|
|
557
|
+
`tenancy_unsupported`**, before any Stripe call. That is what stops one
|
|
558
|
+
tenant's admin reading another tenant's customer PII (names, emails,
|
|
559
|
+
amounts, line items) out of the shared Stripe account. Tell the consumer
|
|
560
|
+
plainly: **invoice management is an OPERATOR surface at v1, not a
|
|
561
|
+
per-tenant self-service one.** The operator can still see every invoice in
|
|
562
|
+
the deployment-wide Stripe account — they hold the `STRIPE_SECRET_KEY` and
|
|
563
|
+
can read the same data from the Stripe dashboard, so this is not an
|
|
564
|
+
escalation — but per-tenant invoicing needs a per-org Stripe Connect /
|
|
565
|
+
customer-namespace model (tracked follow-up).
|
|
566
|
+
|
|
567
|
+
**`User.role` is DEPLOYMENT-OPERATOR-ONLY, and must not be widened to reach
|
|
568
|
+
this surface.** The `requireSiteAdmin` gate keys on `User.role` — a
|
|
569
|
+
per-deployment String (`"user" | "admin" | "owner"`), DISTINCT from the
|
|
570
|
+
org-membership `Role` enum the multi-tenant module uses. Do not grant
|
|
571
|
+
`User.role = "admin"` to an organization's admins as a way of giving them
|
|
572
|
+
invoice access: that is a site-wide operator grant, not an org-scoped one,
|
|
573
|
+
and until per-org Stripe namespacing exists it would turn the tenancy
|
|
574
|
+
refusal above into a cross-tenant PII leak (names, emails, amounts, line
|
|
575
|
+
items). The correct answer to "our org admins need invoices" at v1 is that
|
|
576
|
+
the surface does not support it yet — not a role change.
|
|
577
|
+
|
|
578
|
+
**10h — Subscription surface, shipped (FEAT-023 U5).** When the consumer picks
|
|
579
|
+
"Subscription" at 10b, this surface IS available. It splits into TWO handlers
|
|
580
|
+
with DELIBERATELY OPPOSITE auth postures — the same split checkout (public)
|
|
581
|
+
vs invoice (admin) already established:
|
|
582
|
+
|
|
583
|
+
1. Instantiate `templates/subscription-route.template.ts` into the consumer's
|
|
584
|
+
`apps/api` — mirrors 10d's `stripe-config.template.ts` instantiation. It
|
|
585
|
+
exports `subscribeHandler` (a standalone Fastify handler) and
|
|
586
|
+
`subscriptionRoutes` (a Fastify plugin), built on
|
|
587
|
+
`@working-theory/stripe-client`'s `createCheckoutSession` (`mode:
|
|
588
|
+
"subscription"`, pinned) and `retrieveSubscriptionStatus`.
|
|
589
|
+
2. **Register `subscribeHandler` PUBLICLY, rate-limited — mirrors 10e-i's
|
|
590
|
+
checkout registration exactly.** It is a storefront "subscribe" action
|
|
591
|
+
(the recurring-price analog of the one-time checkout route): pin
|
|
592
|
+
`ALLOWED_LOOKUP_KEYS` to the RECURRING lookup_key(s) the consumer selected
|
|
593
|
+
(step 1's selection, verbatim — never "allow all"), confirm `FRONTEND_URL`
|
|
594
|
+
is set (the same origin-lock 10e-i requires), and scope it behind the
|
|
595
|
+
shared per-IP rate-limit middleware. Both edits are MANDATORY — the route
|
|
596
|
+
is fail-closed until they're done.
|
|
597
|
+
3. **Register `subscriptionRoutes` inside the SAME tenant-guarded scope
|
|
598
|
+
10g's `invoiceRoutes` uses** — e.g. `await tenantScope.register(subscriptionRoutes);`.
|
|
599
|
+
NOT the outer `authScope`, and NOT the same public scope as
|
|
600
|
+
`subscribeHandler`. This is a READ of a specific subscription's status —
|
|
601
|
+
a MANAGEMENT-shaped action, so it inherits 10g's admin + tenancy guards
|
|
602
|
+
verbatim (`requireSiteAdmin` + `requireSingleTenantScope`, the fixed
|
|
603
|
+
single-tenant sentinel `DEFAULT_ORG_ID`). See the template's own header
|
|
604
|
+
comment for the full registration snippet.
|
|
605
|
+
4. Point the consumer at `@working-theory/ui`'s `PricingTable` (section) and
|
|
606
|
+
`SubscriptionStatus` (block) to compose the subscribe + status pages —
|
|
607
|
+
`PricingTable`'s `onSubscribe` POSTs to step 2's route + redirects to the
|
|
608
|
+
returned Stripe Checkout URL (mirrors `CheckoutForm`'s `onCheckout`
|
|
609
|
+
shape); `SubscriptionStatus` renders an already-fetched status (step 3's
|
|
610
|
+
route) via `PaymentStatusBadge` (reused from U1) plus a portal "Manage"
|
|
611
|
+
action pointing at the U6 billing-portal surface — this surface never
|
|
612
|
+
builds self-service plan-change/cancel itself.
|
|
613
|
+
5. Re-run 10c's live-key check before instantiating. State the CURRENT
|
|
614
|
+
behaviour to the consumer: `assertSandboxKey` is an ALLOW-LIST as of
|
|
615
|
+
AILK#3734 — only `sk_test_`, `pk_test_` and `rk_test_` are accepted, so
|
|
616
|
+
every live key including a restricted one is refused. No surface header
|
|
617
|
+
carries the pre-#3734 deny-list caveat any more (AILK#3765 retired the
|
|
618
|
+
last of them); if you find one, it is stale — fix it rather than repeat it.
|
|
619
|
+
6. **Multi-tenant module — same refusal as invoices, same reason.** v1 has
|
|
620
|
+
ONE deployment-wide `STRIPE_SECRET_KEY` and no per-organization Stripe
|
|
621
|
+
customer namespace, so a subscription carries no org attribution.
|
|
622
|
+
`subscriptionRoutes`'s `requireSingleTenantScope` guard therefore admits
|
|
623
|
+
ONLY the deployment's own operator org and hard-refuses every other org's
|
|
624
|
+
admin with a 403 `tenancy_unsupported`, before any Stripe call. Tell the
|
|
625
|
+
consumer plainly: subscription-status reading is an OPERATOR surface at
|
|
626
|
+
v1, not a per-tenant self-service one — the same disclosed limitation as
|
|
627
|
+
10g's invoice surface (see
|
|
628
|
+
`docs/research/commerce-template-audit/subscriptions.md`).
|
|
629
|
+
7. **No subscription webhook is scaffolded at v1** — the status read is
|
|
630
|
+
retrieve-on-view by the subscription's own Stripe id (mirrors 10g's
|
|
631
|
+
`?invoiceId=` shape with `?subscriptionId=`), not webhook-driven. If a
|
|
632
|
+
future need requires webhook-driven status (e.g. proactive
|
|
633
|
+
past-due notices), that is a new unit of work, not a tweak to this
|
|
634
|
+
surface.
|
|
635
|
+
|
|
636
|
+
**10i — Portal surface, shipped (FEAT-023 U6).** When the consumer picks
|
|
637
|
+
"Portal" at 10b, this surface IS available. It is the smallest FEAT-023
|
|
638
|
+
surface (spec §1) — AILK owns only the entry, Stripe hosts the portal
|
|
639
|
+
itself:
|
|
640
|
+
|
|
641
|
+
1. Instantiate `templates/portal-route.template.ts` into the consumer's
|
|
642
|
+
`apps/api` — mirrors 10d's `stripe-config.template.ts` instantiation. It
|
|
643
|
+
exports `portalRoutes` (a Fastify plugin: a single `POST
|
|
644
|
+
/v1/commerce/portal-session`), built on
|
|
645
|
+
`@working-theory/stripe-client`'s `createBillingPortalSession`.
|
|
646
|
+
2. **Register `portalRoutes` inside the SAME tenant-guarded scope 10g's
|
|
647
|
+
`invoiceRoutes` uses** — e.g. `await tenantScope.register(portalRoutes);`.
|
|
648
|
+
NOT the outer `authScope`. Starting a billing-portal session hands the
|
|
649
|
+
caller a live link into a customer's full billing surface, so — like
|
|
650
|
+
10h's `subscriptionRoutes` status read — this is a MANAGEMENT-shaped
|
|
651
|
+
action, never public/anonymous: it inherits 10g's admin + tenancy guards
|
|
652
|
+
verbatim (`requireSiteAdmin` + `requireSingleTenantScope`, the fixed
|
|
653
|
+
single-tenant sentinel `DEFAULT_ORG_ID`). See the template's own header
|
|
654
|
+
comment for the full registration snippet.
|
|
655
|
+
3. Point the consumer at `@working-theory/ui`'s `PortalEntry` (block) to
|
|
656
|
+
compose a standalone billing-management entry point, AND wire it as the
|
|
657
|
+
target of 10h's `SubscriptionStatus` `manageAction` (the U5↔U6 seam,
|
|
658
|
+
spec § design decision) — both `PortalEntry`'s `onManage` and
|
|
659
|
+
`SubscriptionStatus`'s `manageAction.onManage` POST to step 1's route
|
|
660
|
+
with `{ customerId }` and, on a `200` response, set
|
|
661
|
+
`window.location.href` to the returned `url`. Pass `testMode` (always
|
|
662
|
+
`true` at v1 — sandbox-only).
|
|
663
|
+
4. Re-run 10c's live-key check before instantiating. Note the guard's
|
|
664
|
+
current state (AILK#3734, now an allow-list) — restate the CURRENT state
|
|
665
|
+
to the consumer, same as 10h.
|
|
666
|
+
5. **Multi-tenant module — same refusal as invoices/subscriptions, same
|
|
667
|
+
reason.** v1 has ONE deployment-wide `STRIPE_SECRET_KEY` and no
|
|
668
|
+
per-organization Stripe customer namespace, so a `customerId` carries no
|
|
669
|
+
org attribution. `portalRoutes`'s `requireSingleTenantScope` guard
|
|
670
|
+
therefore admits ONLY the deployment's own operator org and hard-refuses
|
|
671
|
+
every other org's admin with a 403 `tenancy_unsupported`, before any
|
|
672
|
+
Stripe call. Tell the consumer plainly: billing-portal sessions are an
|
|
673
|
+
OPERATOR surface at v1, not a per-tenant self-service one — the same
|
|
674
|
+
disclosed limitation as 10g/10h (see
|
|
675
|
+
`docs/research/commerce-template-audit/portal.md`).
|
|
676
|
+
6. **The return URL is fixed, server-derived, never caller-supplied** — a
|
|
677
|
+
single `PORTAL_RETURN_PATH` composed from `FRONTEND_URL` (the template's
|
|
678
|
+
own `SCAFFOLD-TIME EDIT` if the consumer's account/billing page lives
|
|
679
|
+
elsewhere), mirroring `apps/api/src/routes/billing/portal.ts`'s existing
|
|
680
|
+
convention. There is no caller-chosen destination to validate, unlike
|
|
681
|
+
checkout's `successUrl`/`cancelUrl`.
|
|
682
|
+
7. **No portal-session webhook is scaffolded at v1** — mint-on-demand by
|
|
683
|
+
the caller-supplied `customerId`, not webhook-driven, mirroring 10h's
|
|
684
|
+
escalation-boundary posture.
|
|
685
|
+
|
|
255
686
|
## Output style
|
|
256
687
|
|
|
257
688
|
- **Two questions, two paths.** Don't lead with Stripe assumptions.
|
|
@@ -279,6 +710,15 @@ Print:
|
|
|
279
710
|
recreates.
|
|
280
711
|
- Auto-invoke other skills. Always points at the next skill by name;
|
|
281
712
|
consumer drives.
|
|
713
|
+
- Guess entitlement conversationally. The `feature:commerce`
|
|
714
|
+
surface-selection branch (Step 10) checks the resolved license via
|
|
715
|
+
`hasFeature("commerce", license)` — never a verbal "I'm on Pro"
|
|
716
|
+
claim. No resolvable license → not entitled (fail-closed).
|
|
717
|
+
- Build a payment surface itself for a choice OTHER than checkout
|
|
718
|
+
(U3–U6). This skill's Step 10 checks entitlement, offers the choice,
|
|
719
|
+
and dispatches — the surface scaffolders own the actual page/route.
|
|
720
|
+
Checkout (one-time) is the one exception: Step 10e-i wires it inline
|
|
721
|
+
(FEAT-023 U2).
|
|
282
722
|
|
|
283
723
|
## References
|
|
284
724
|
|
|
@@ -291,6 +731,31 @@ Print:
|
|
|
291
731
|
- `templates/marketplace-cta-route.tsx.tmpl` — emitted route source
|
|
292
732
|
the link path writes. Includes the inline `<MarketplaceCTA>`
|
|
293
733
|
component definition (not a separate package).
|
|
734
|
+
- `templates/stripe-config.template.ts` — shared Stripe surface config
|
|
735
|
+
(env contract + `assertSandboxKey` test-mode guard) every U2–U6
|
|
736
|
+
surface scaffolder instantiates (Step 10d, FEAT-023 U1).
|
|
737
|
+
- `templates/checkout-route.template.ts` — the one-time checkout POST
|
|
738
|
+
route (`mode: "payment"`, `stripe-client/checkout.ts`), instantiated
|
|
739
|
+
by Step 10e-i (FEAT-023 U2).
|
|
740
|
+
- `templates/payment-link-route.template.ts` — the payment-link
|
|
741
|
+
MANAGEMENT route (list / create / archive), instantiated by Step
|
|
742
|
+
10e-ii (FEAT-023 U3). Authenticated + site-admin only, registered
|
|
743
|
+
inside the auth scope — see 10e-ii; its posture is the opposite of
|
|
744
|
+
the public checkout route's.
|
|
745
|
+
- `templates/invoice-route.template.ts` — the Invoice surface's admin-only
|
|
746
|
+
Fastify plugin (create/send/list/retrieve), instantiated at Step 10g
|
|
747
|
+
(FEAT-023 U4, shipped). Registration inside `apps/api`'s tenant-guarded
|
|
748
|
+
scope is REQUIRED — see the template's own header comment.
|
|
749
|
+
- `templates/subscription-route.template.ts` — the Subscription surface's
|
|
750
|
+
`subscribeHandler` (public, rate-limited) + `subscriptionRoutes` (admin +
|
|
751
|
+
tenant-guarded status read), instantiated at Step 10h (FEAT-023 U5,
|
|
752
|
+
shipped). The two handlers have OPPOSITE auth postures — see the
|
|
753
|
+
template's own header comment for both registration points.
|
|
754
|
+
- `templates/portal-route.template.ts` — the Portal surface's admin +
|
|
755
|
+
tenant-guarded `portalRoutes` (mints a billing-portal session for a
|
|
756
|
+
caller-supplied `customerId`), instantiated at Step 10i (FEAT-023 U6,
|
|
757
|
+
shipped). Registration inside `apps/api`'s tenant-guarded scope is
|
|
758
|
+
REQUIRED — see the template's own header comment.
|
|
294
759
|
|
|
295
760
|
## Coordination
|
|
296
761
|
|
|
@@ -307,4 +772,36 @@ Print:
|
|
|
307
772
|
- **`@working-theory/stripe-client`** — wrapper for Stripe SDK. The skill does
|
|
308
773
|
not import this directly (the skill is a prompt/template, not
|
|
309
774
|
runtime code). The consumer's `apps/api/routes/checkout/sessions.ts`
|
|
310
|
-
|
|
775
|
+
(generic primitive) and the Step-10e-i-instantiated
|
|
776
|
+
`checkout-route.template.ts` (one-time surface) use it.
|
|
777
|
+
`assertSandboxKey` is reused by `stripe-config.template.ts`.
|
|
778
|
+
- **`@working-theory/license-client`** — `hasFeature("commerce", license)`
|
|
779
|
+
is the entitlement read Step 10a consults (static-mode, sync, no
|
|
780
|
+
network). The skill does not import this directly either — it
|
|
781
|
+
describes the check a consumer's own license-aware tooling performs.
|
|
782
|
+
- **FEAT-023 U2–U6 (checkout · pay-by-link · invoice · subscription ·
|
|
783
|
+
portal)** — the five payment surfaces Step 10e dispatches to. Checkout
|
|
784
|
+
(one-time) shipped in U2 (10e-i); pay-by-link shipped in U3 (10e-ii);
|
|
785
|
+
invoice shipped in U4 (10g); subscription shipped in U5 (10h); portal
|
|
786
|
+
shipped in U6 (10i).
|
|
787
|
+
- **FEAT-023 U4 (invoice)** — shipped (Step 10g). `@working-theory/stripe-client`'s
|
|
788
|
+
`invoice.ts` (create/send/list/retrieve) + `@working-theory/ui`'s
|
|
789
|
+
`InvoiceList`/`InvoiceDetail`/`InvoiceForm` compose the surface; the
|
|
790
|
+
route template lives at `templates/invoice-route.template.ts`.
|
|
791
|
+
- **FEAT-023 U5 (subscription)** — shipped (Step 10h).
|
|
792
|
+
`@working-theory/stripe-client`'s `checkout.ts` (`createCheckoutSession`
|
|
793
|
+
`mode: "subscription"` + `retrieveSubscriptionStatus`) +
|
|
794
|
+
`@working-theory/ui`'s `PricingTable`/`SubscriptionStatus` compose the
|
|
795
|
+
surface; the route template lives at
|
|
796
|
+
`templates/subscription-route.template.ts`.
|
|
797
|
+
- **FEAT-023 U6 (portal)** — shipped (Step 10i). `@working-theory/stripe-client`'s
|
|
798
|
+
`billing-portal.ts` (`createBillingPortalSession`) + `@working-theory/ui`'s
|
|
799
|
+
`PortalEntry` (reuses U1's `StripeActionButton`) compose the surface — the
|
|
800
|
+
target of U5's `SubscriptionStatus` "Manage" action; the route template
|
|
801
|
+
lives at `templates/portal-route.template.ts`.
|
|
802
|
+
- **`@working-theory/ui`'s `MoneyAmount` / `PaymentStatusBadge` /
|
|
803
|
+
`StripeActionButton`** — the shared presentational components every
|
|
804
|
+
U2–U6 surface composes (Step 10f, FEAT-023 U1).
|
|
805
|
+
- **`@working-theory/ui`'s `CheckoutForm` / `PaymentResult`** — the
|
|
806
|
+
one-time checkout surface's own capability-bearing blocks (FEAT-023
|
|
807
|
+
U2), composed at Step 10e-i steps 3–4.
|