create-ailk 0.5.0 → 0.6.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 +55 -8
- package/component-catalog.json +336 -27
- package/component-catalog.md +25 -12
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/module-architecture.d.ts +1 -0
- package/dist/module-architecture.js +253 -127
- package/dist/surfaces.js +105 -7
- package/dist/template-manifest.d.ts +33 -0
- package/dist/template-manifest.js +99 -16
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +1 -0
- package/package.json +3 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
- package/site-templates/marketing-site/ailk-template.json +1 -1
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
- package/site-templates/minimalism-site/ailk-template.json +2 -1
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.env.example +72 -7
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +26 -2
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +439 -49
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +70 -31
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +13 -14
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +23 -2
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +57 -24
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +2 -0
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +59 -35
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +6 -4
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +71 -5
- package/templates/apps/web/CLAUDE.md +44 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/layout.tsx +133 -87
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/jest.config.cjs +8 -4
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +187 -0
- package/templates/apps/web/package.json +2 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +61 -0
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +142 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +1 -1
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/versions.json +34 -34
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
|
@@ -1,92 +1,175 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AGENT TOOL — purchase (`start_purchase`)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Starts a purchase for a named offering and returns a **Stripe-hosted
|
|
5
|
+
* Checkout URL**. Nothing is charged here: the tool builds the payload the
|
|
6
|
+
* HTTP twin `POST /v1/checkout/sessions` validates, and the buyer completes
|
|
7
|
+
* payment on Stripe's own page. That hosted handoff is the money boundary
|
|
8
|
+
* `.claude/rules/vocabulary.md` § Commerce requires — no card data reaches
|
|
9
|
+
* us, and we are not a Stripe Connect platform.
|
|
5
10
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
+
* VISIBILITY — the commerce add-on claim (#5971, superseding S15a/#610).
|
|
12
|
+
* This tool carries `addon: "commerce"` in the catalog, and since #5971 that
|
|
13
|
+
* marker is its visibility gate: it reaches neither the discovery manifest
|
|
14
|
+
* nor the running server's tool list unless the deployment positively holds
|
|
15
|
+
* the `commerce` licence claim. Two gates enforce that, deliberately:
|
|
11
16
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
17
|
+
* 1. REGISTRATION — `shouldRegister()` in `../server.js` drops the entry.
|
|
18
|
+
* 2. CALL TIME — the first statement below re-reads the claim.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
20
|
+
* Gate 2 is not redundant. The HTTP transport's stateless mode builds a fresh
|
|
21
|
+
* `McpServer` per request, so the gate must be re-read rather than captured;
|
|
22
|
+
* `ailkPurchase` is an exported function a scaffold or a test can call
|
|
23
|
+
* directly, bypassing registration entirely; and it gives "calling it by name
|
|
24
|
+
* on a keyless site" a deterministic, asserted body rather than whatever the
|
|
25
|
+
* SDK emits for an unregistered name. That body is `notFoundError("Not
|
|
26
|
+
* found")` — the MCP twin of the checkout route's own
|
|
27
|
+
* `commerceDisabledResponse()`, same code, same message, no hint that a
|
|
28
|
+
* licence exists.
|
|
21
29
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
30
|
+
* REDIRECTS — never caller-supplied on this surface (#6001). An anonymous
|
|
31
|
+
* caller has no way to prove it controls a redirect target, so
|
|
32
|
+
* `successUrl` / `cancelUrl` are ALWAYS derived from `AILK_BASE_URL` below,
|
|
33
|
+
* never accepted from the input, and `allowPromotionCodes` is never accepted
|
|
34
|
+
* either. This function's own gate is `purchaseMcpInputSchema` — a
|
|
35
|
+
* `.strict()` `.pick()` of the shared `purchaseInputSchema` exposing exactly
|
|
36
|
+
* `productCode` + `quantity` — so a DIRECT call to `ailkPurchase` (the
|
|
37
|
+
* surface this file's own tests exercise, per the note above) carrying any
|
|
38
|
+
* of the three fails validation outright. A call through the registered SDK
|
|
39
|
+
* transport has them stripped one layer earlier, by the SDK's own arg parse
|
|
40
|
+
* against the registered shape, before this function ever runs — they are
|
|
41
|
+
* simply not part of that shape. Either path is safe: the payload built
|
|
42
|
+
* below can never carry a caller-supplied redirect. The shared
|
|
43
|
+
* `purchaseInputSchema` (and the Server Action / API route that use it) is
|
|
44
|
+
* unaffected: those are not anonymous surfaces and keep accepting a
|
|
45
|
+
* caller-supplied redirect.
|
|
27
46
|
*
|
|
28
|
-
*
|
|
47
|
+
* The file stays in the BASE tree and is never added to
|
|
48
|
+
* `MODULE_SLICES.commerce.ownedFiles`: `../server.js` statically imports
|
|
49
|
+
* `ailkPurchase`, so a slice that deleted it from a keyless scaffold would
|
|
50
|
+
* produce `ERR_MODULE_NOT_FOUND` inside the server bootstrap. A keyless
|
|
51
|
+
* scaffold carries the file and registers nothing — exactly how it already
|
|
52
|
+
* carries `apps/api/src/routes/checkout/sessions.ts`.
|
|
29
53
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
54
|
+
* Schemas live in `@working-theory/validation` per ADR 0013. Network calls go
|
|
55
|
+
* through `@working-theory/api-client` per the architecture invariants (apps
|
|
56
|
+
* may not import each other; the shared HTTP surface lives in packages/).
|
|
32
57
|
*
|
|
33
|
-
*
|
|
34
|
-
* Network calls go through `@working-theory/api-client` per architecture invariants
|
|
35
|
-
* (apps may not import from each other; shared HTTP surface lives in packages/).
|
|
58
|
+
* Spec: `docs/specs/5971-start-purchase-hosted-checkout-spec.md`.
|
|
36
59
|
*/
|
|
37
60
|
|
|
38
61
|
import { checkout } from "@working-theory/api-client";
|
|
39
62
|
import {
|
|
40
|
-
|
|
41
|
-
type
|
|
63
|
+
purchaseMcpInputSchema,
|
|
64
|
+
type PurchaseMcpInput,
|
|
42
65
|
type PurchaseOutput,
|
|
43
66
|
} from "@working-theory/validation";
|
|
44
67
|
|
|
68
|
+
import { mcpCommerceClaim } from "../lib/commerce-claim.js";
|
|
69
|
+
import {
|
|
70
|
+
notFoundError,
|
|
71
|
+
validationError,
|
|
72
|
+
type ErrorBody,
|
|
73
|
+
} from "../lib/errors.js";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The module-disabled body. Identical to what a build without commerce
|
|
77
|
+
* produces and to the checkout route's own `commerceDisabledResponse()` —
|
|
78
|
+
* it must not leak that a licence gate is what answered.
|
|
79
|
+
*/
|
|
80
|
+
function commerceDisabledBody(): ErrorBody {
|
|
81
|
+
return notFoundError("Not found");
|
|
82
|
+
}
|
|
83
|
+
|
|
45
84
|
/**
|
|
46
85
|
* Export name `ailkPurchase` satisfies the parity-test naming convention
|
|
47
86
|
* (ailk<PascalVerb>) so the harness can discover it.
|
|
48
87
|
*
|
|
49
|
-
* Paired with `purchaseAction` Server Action in
|
|
88
|
+
* Paired with `purchaseAction` Server Action in
|
|
89
|
+
* apps/web/app/[locale]/dev/purchase/.
|
|
90
|
+
*
|
|
91
|
+
* Returns a typed error BODY rather than throwing on every failure an agent
|
|
92
|
+
* can act on (FR-2) — MCP tools surface errors as their result body, the
|
|
93
|
+
* pattern `lib/require-paid-tier.ts` already establishes.
|
|
50
94
|
*/
|
|
51
95
|
export async function ailkPurchase(
|
|
52
|
-
input:
|
|
53
|
-
): Promise<PurchaseOutput> {
|
|
54
|
-
//
|
|
55
|
-
|
|
96
|
+
input: PurchaseMcpInput,
|
|
97
|
+
): Promise<PurchaseOutput | ErrorBody> {
|
|
98
|
+
// Gate 2 — call time. Before validation, before any side effect.
|
|
99
|
+
if (mcpCommerceClaim() !== "claimed") {
|
|
100
|
+
return commerceDisabledBody();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Validate input before any side effect (security requirement). Strict
|
|
104
|
+
// (#6001): a DIRECT call carrying successUrl, cancelUrl or
|
|
105
|
+
// allowPromotionCodes fails here — this function never accepts a
|
|
106
|
+
// caller-supplied redirect. (A call through the registered SDK transport
|
|
107
|
+
// never reaches this line with those fields present at all — see the
|
|
108
|
+
// REDIRECTS note above.) Every field this schema DOES accept mirrors the
|
|
109
|
+
// route's own validation (spec D5), so an input this accepts is an input
|
|
110
|
+
// the route accepts.
|
|
111
|
+
const parsed = purchaseMcpInputSchema.safeParse(input);
|
|
112
|
+
if (!parsed.success) {
|
|
113
|
+
return validationError("Invalid start_purchase input", {
|
|
114
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
115
|
+
path: issue.path as (string | number)[],
|
|
116
|
+
message: issue.message,
|
|
117
|
+
})),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
56
120
|
|
|
57
121
|
const baseUrl =
|
|
58
122
|
process.env.AILK_BASE_URL ||
|
|
59
123
|
process.env.NEXT_PUBLIC_BASE_URL ||
|
|
60
124
|
"http://localhost:3000";
|
|
61
125
|
|
|
62
|
-
const
|
|
63
|
-
input.successUrl ?? `${baseUrl}/purchase/success`;
|
|
64
|
-
const cancelUrl =
|
|
65
|
-
input.cancelUrl ?? `${baseUrl}/purchase/cancel`;
|
|
126
|
+
const { productCode, quantity } = parsed.data;
|
|
66
127
|
|
|
128
|
+
// successUrl/cancelUrl are ALWAYS derived from baseUrl (#6001) — never
|
|
129
|
+
// accepted from the caller. allowPromotionCodes is never forwarded: it is
|
|
130
|
+
// not part of purchaseMcpInputSchema, so it can never be present here.
|
|
131
|
+
//
|
|
132
|
+
// #6084 D1 — `successUrl` carries Stripe's own `{CHECKOUT_SESSION_ID}`
|
|
133
|
+
// template token, substituted into the redirect at completion time. This
|
|
134
|
+
// is what lets `/purchase/success` read the session back (D1). `cancelUrl`
|
|
135
|
+
// deliberately carries NO template: that page reads nothing (D4), and
|
|
136
|
+
// Stripe's substitution contract is documented for `success_url` only.
|
|
67
137
|
const result = await checkout.createSession({
|
|
68
|
-
productCode
|
|
69
|
-
...(
|
|
70
|
-
successUrl
|
|
71
|
-
cancelUrl
|
|
72
|
-
...(input.allowPromotionCodes !== undefined && {
|
|
73
|
-
allowPromotionCodes: input.allowPromotionCodes,
|
|
74
|
-
}),
|
|
138
|
+
productCode,
|
|
139
|
+
...(quantity !== undefined && { quantity }),
|
|
140
|
+
successUrl: `${baseUrl}/purchase/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
141
|
+
cancelUrl: `${baseUrl}/purchase/cancel`,
|
|
75
142
|
});
|
|
76
143
|
|
|
77
144
|
if (!result.ok) {
|
|
78
|
-
|
|
79
|
-
|
|
145
|
+
// An unknown offering is the route's 404 `unknown-product-code`, and
|
|
146
|
+
// `packages/stripe-client` throws it BEFORE `stripe.checkout.sessions
|
|
147
|
+
// .create` — so no Checkout Session exists (FR-2).
|
|
148
|
+
if (result.status === 404) {
|
|
149
|
+
// Deliberately does NOT echo the caller's productCode. The HTTP twin
|
|
150
|
+
// states and tests that invariant ("Error responses never echo user
|
|
151
|
+
// input", apps/api/src/routes/checkout/sessions.ts), and parity means
|
|
152
|
+
// the pair answers alike.
|
|
153
|
+
return notFoundError("No offering matches that product code.");
|
|
154
|
+
}
|
|
155
|
+
return validationError(
|
|
156
|
+
`checkout-unavailable: POST /v1/checkout/sessions returned ${result.status}`,
|
|
157
|
+
{ status: result.status },
|
|
80
158
|
);
|
|
81
159
|
}
|
|
82
160
|
|
|
161
|
+
// Only `url` is surfaced. The route's `sessionId` is deliberately withheld:
|
|
162
|
+
// an anonymous agent has no use for it and it is the one field that
|
|
163
|
+
// identifies a specific buyer's session (spec D5).
|
|
83
164
|
return { url: result.body.url };
|
|
84
165
|
}
|
|
85
166
|
|
|
86
167
|
// Re-export schemas/types from @working-theory/validation for barrel consumers.
|
|
87
168
|
export {
|
|
88
169
|
purchaseInputSchema,
|
|
170
|
+
purchaseMcpInputSchema,
|
|
89
171
|
purchaseOutputSchema,
|
|
90
172
|
type PurchaseInput,
|
|
173
|
+
type PurchaseMcpInput,
|
|
91
174
|
type PurchaseOutput,
|
|
92
175
|
} from "@working-theory/validation";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* read_content — returns a single page's frontmatter + raw MDX body.
|
|
3
3
|
*
|
|
4
|
-
* Pure read:
|
|
4
|
+
* Pure read: resolves its adapter through `getMcpMdxContentAdapter()` — the
|
|
5
|
+
* MDX-narrowed twin of the one resolver seam (#5805, ADR 0011; needed here
|
|
6
|
+
* for `getPageSource()`'s raw body, which the base `ContentAdapter` contract
|
|
7
|
+
* doesn't carry), MDX by default.
|
|
5
8
|
* Composes getPageBySlug (frontmatter) + getPageSource (body) into a single
|
|
6
9
|
* output shape. Returns null when the slug is not found — never throws for
|
|
7
10
|
* a missing page.
|
|
@@ -10,14 +13,15 @@
|
|
|
10
13
|
* allowlist; invalid or unknown values silently fall back to the project's
|
|
11
14
|
* default locale (AC-i18n-07). The adapter applies its own fallback chain.
|
|
12
15
|
*
|
|
13
|
-
* Input: { type: SchemaOrgType, slug: string, locale?: string }
|
|
16
|
+
* Input: { type: SchemaOrgType, slug: string, locale?: string } — `type`
|
|
17
|
+
* admits the full SchemaOrgType set (#6030 D1), mirrored in
|
|
18
|
+
* @working-theory/validation's MCP_SCHEMA_ORG_TYPES.
|
|
14
19
|
* Output: { slug, type, frontmatter, body } | null
|
|
15
20
|
* Session/scope: none — read-only public content.
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
23
|
import { join } from "node:path";
|
|
19
24
|
|
|
20
|
-
import { createMdxAdapter } from "@working-theory/content-adapters";
|
|
21
25
|
import {
|
|
22
26
|
readContentInputSchema,
|
|
23
27
|
readContentOutputSchema,
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
type ReadContentOutput,
|
|
26
30
|
} from "@working-theory/validation";
|
|
27
31
|
|
|
32
|
+
import { getMcpMdxContentAdapter } from "../lib/content-adapter.js";
|
|
28
33
|
import { resolveLocale } from "../lib/resolve-locale.js";
|
|
29
34
|
|
|
30
35
|
export { readContentInputSchema, readContentOutputSchema };
|
|
@@ -47,14 +52,23 @@ export async function ailkReadContent(
|
|
|
47
52
|
const root = contentRoot ?? join(process.cwd(), "content");
|
|
48
53
|
const locale = resolveLocale(input.locale);
|
|
49
54
|
|
|
50
|
-
const adapter =
|
|
55
|
+
const adapter = getMcpMdxContentAdapter({ contentRoot: root, locale });
|
|
51
56
|
|
|
52
57
|
const source = await adapter.getPageSource(input.type, input.slug, locale);
|
|
53
58
|
if (source === null) return null;
|
|
54
59
|
|
|
55
60
|
return readContentOutputSchema.parse({
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
// Prefer the stored frontmatter's slug/type; fall back to the
|
|
62
|
+
// request-validated input (#5876 spec D9). On the MDX path the
|
|
63
|
+
// frontmatter always carries both — required, non-optional fields of
|
|
64
|
+
// FrontmatterSchema — so this is inert there and the mdx payload stays
|
|
65
|
+
// byte-identical. The fallback matters for cross-adapter identity: the
|
|
66
|
+
// Epic #5324 AC-WCE-04 comparison needs `read_content`'s payload to be
|
|
67
|
+
// identical across adapters, which a request-validated value guarantees
|
|
68
|
+
// and a stored-bookkeeping value only guarantees if the write path
|
|
69
|
+
// agrees.
|
|
70
|
+
slug: source.frontmatter.slug ?? input.slug,
|
|
71
|
+
type: source.frontmatter.type ?? input.type,
|
|
58
72
|
frontmatter: source.frontmatter as Record<string, unknown>,
|
|
59
73
|
body: source.body,
|
|
60
74
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* search_content — full-text search across published content pages.
|
|
3
3
|
*
|
|
4
|
-
* Pure read:
|
|
4
|
+
* Pure read: resolves its adapter through `getMcpContentAdapter()` — the one
|
|
5
|
+
* resolver seam (#5805, ADR 0011), MDX by default.
|
|
5
6
|
* Delegates to adapter.searchPages which scores by title/description/body match.
|
|
6
7
|
* Limit defaults to 10; max 50. Query capped at 200 chars (enforced by Zod).
|
|
7
8
|
*
|
|
@@ -9,14 +10,18 @@
|
|
|
9
10
|
* requested locale's content directory is searched. Invalid or unknown values
|
|
10
11
|
* silently fall back to the project's default locale (AC-i18n-07).
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
+
* #5831: optional `category` parameter — a leaf slug from the site-category
|
|
14
|
+
* registry. Unlike `locale`, an unrecognised value is a validation error, not
|
|
15
|
+
* a silent fallback: the registry is closed, so a bad slug is a caller mistake.
|
|
16
|
+
*
|
|
17
|
+
* Input: { query: string, type?: SchemaOrgType, category?: CategorySlug,
|
|
18
|
+
* limit?: number, locale?: string }
|
|
13
19
|
* Output: { results: Array<{ slug, type, title, snippet, score }> }
|
|
14
20
|
* Session/scope: none — read-only public content.
|
|
15
21
|
*/
|
|
16
22
|
|
|
17
23
|
import { join } from "node:path";
|
|
18
24
|
|
|
19
|
-
import { createMdxAdapter } from "@working-theory/content-adapters";
|
|
20
25
|
import {
|
|
21
26
|
searchContentInputSchema,
|
|
22
27
|
searchContentOutputSchema,
|
|
@@ -24,6 +29,7 @@ import {
|
|
|
24
29
|
type SearchContentOutput,
|
|
25
30
|
} from "@working-theory/validation";
|
|
26
31
|
|
|
32
|
+
import { getMcpContentAdapter } from "../lib/content-adapter.js";
|
|
27
33
|
import { resolveLocale } from "../lib/resolve-locale.js";
|
|
28
34
|
|
|
29
35
|
export { searchContentInputSchema, searchContentOutputSchema };
|
|
@@ -45,11 +51,12 @@ export async function ailkSearchContent(
|
|
|
45
51
|
|
|
46
52
|
const root = contentRoot ?? join(process.cwd(), "content");
|
|
47
53
|
const locale = resolveLocale(input.locale);
|
|
48
|
-
const adapter =
|
|
54
|
+
const adapter = getMcpContentAdapter({ contentRoot: root, locale });
|
|
49
55
|
|
|
50
56
|
const options = {
|
|
51
57
|
limit: input.limit ?? DEFAULT_LIMIT,
|
|
52
58
|
...(input.type !== undefined && { type: input.type }),
|
|
59
|
+
...(input.category !== undefined && { category: input.category }),
|
|
53
60
|
locale,
|
|
54
61
|
};
|
|
55
62
|
|
|
@@ -5,16 +5,25 @@
|
|
|
5
5
|
# overrides apps/web reads at runtime.
|
|
6
6
|
|
|
7
7
|
# =============================================================================
|
|
8
|
-
# Content
|
|
8
|
+
# Content adapter
|
|
9
9
|
# =============================================================================
|
|
10
10
|
# Which content adapter apps/web uses.
|
|
11
11
|
# mdx — reads /content/<Type>/<slug>.mdx (default)
|
|
12
|
-
#
|
|
13
|
-
|
|
12
|
+
# projection — compiled build-time manifest
|
|
13
|
+
# database — the Artifact store (needs a workspaceId)
|
|
14
|
+
CONTENT_ADAPTER=mdx
|
|
15
|
+
# CONTENT_BACKEND is deprecated — read for one release, then removed.
|
|
14
16
|
|
|
15
17
|
# Override the on-disk content root. Defaults to <repo-root>/content.
|
|
16
18
|
# AILK_CONTENT_ROOT=
|
|
17
19
|
|
|
20
|
+
# The shared secret /api/revalidate verifies apps/api's publish webhook
|
|
21
|
+
# against — the SAME value as the API's CONTENT_REVALIDATE_SECRET (#5877).
|
|
22
|
+
# Needed when CONTENT_ADAPTER=database. Unset means the route returns 503 and
|
|
23
|
+
# the site is not push-revalidated: a publish changes nothing user-visible
|
|
24
|
+
# until a rebuild.
|
|
25
|
+
# CONTENT_REVALIDATE_SECRET=
|
|
26
|
+
|
|
18
27
|
# =============================================================================
|
|
19
28
|
# apps/api base URL (browser-side lead-capture forms) — #3965
|
|
20
29
|
# =============================================================================
|
|
@@ -46,9 +55,22 @@ CONTENT_BACKEND=mdx
|
|
|
46
55
|
# =============================================================================
|
|
47
56
|
# Better Auth (apps/web)
|
|
48
57
|
# =============================================================================
|
|
49
|
-
# Base URL Better Auth uses for OAuth callback construction
|
|
58
|
+
# Base URL Better Auth uses for OAuth callback construction — and, since #5962,
|
|
59
|
+
# the ORIGIN the password-reset link is built on and the sole member of the
|
|
60
|
+
# trustedOrigins allow-list.
|
|
61
|
+
#
|
|
62
|
+
# REQUIRED for password reset, and not as a convenience: with this unset Better
|
|
63
|
+
# Auth recomputes both the base URL and the allow-list PER REQUEST from the
|
|
64
|
+
# request itself, so `Host: evil.tld` plus `redirectTo: https://evil.tld/x`
|
|
65
|
+
# passes its own origin check and the emailed reset link is built on the
|
|
66
|
+
# attacker's host. It is a conjunct of resetSendConfigured(), so a deployment
|
|
67
|
+
# that has not pinned its origin does not offer reset at all.
|
|
50
68
|
# BETTER_AUTH_URL=http://localhost:3000
|
|
51
|
-
#
|
|
69
|
+
#
|
|
70
|
+
# Secret used to sign session cookies AND the email-verification tokens.
|
|
71
|
+
# REQUIRED in production: apps/web/auth.ts now THROWS at boot without it rather
|
|
72
|
+
# than falling back to a development string that is published in this
|
|
73
|
+
# repository. Also a conjunct of resetSendConfigured().
|
|
52
74
|
# BETTER_AUTH_SECRET=
|
|
53
75
|
#
|
|
54
76
|
# OAuth provider env vars — set both id + secret to activate a provider.
|
|
@@ -57,6 +79,50 @@ CONTENT_BACKEND=mdx
|
|
|
57
79
|
# GOOGLE_CLIENT_SECRET=
|
|
58
80
|
# GITHUB_CLIENT_ID=
|
|
59
81
|
# GITHUB_CLIENT_SECRET=
|
|
82
|
+
#
|
|
83
|
+
# Auth-surface flags (#5963) — what the (auth) pages OFFER, read through
|
|
84
|
+
# apps/web/lib/auth-env.ts. Both default OFF, which is what a scaffold is:
|
|
85
|
+
# auth.ts does not enable emailAndPassword, so there is no public sign-up and
|
|
86
|
+
# no password-reset flow to advertise. Set to "1" or "true" once the
|
|
87
|
+
# server-side flow is wired.
|
|
88
|
+
# Open public sign-up: /sign-up renders the form instead of the invitation copy.
|
|
89
|
+
# AILK_AUTH_SIGNUP_OPEN=
|
|
90
|
+
# Password reset available: /forgot-password and /reset-password render their
|
|
91
|
+
# forms instead of the not-available state.
|
|
92
|
+
# AILK_AUTH_PASSWORD_RESET=
|
|
93
|
+
#
|
|
94
|
+
# -----------------------------------------------------------------------------
|
|
95
|
+
# Password reset — the server-side flow (#5962)
|
|
96
|
+
# -----------------------------------------------------------------------------
|
|
97
|
+
# Shared secret for the signed hop to apps/api's /v1/auth/transactional-email,
|
|
98
|
+
# which is what actually sends auth mail: architecture.yaml forbids apps/web
|
|
99
|
+
# from importing packages/email, so apps/web signs a body and POSTs it. Must
|
|
100
|
+
# match AUTH_EMAIL_WEBHOOK_SECRET on the apps/api deployment. Deployment-only —
|
|
101
|
+
# never NEXT_PUBLIC_, and never committed.
|
|
102
|
+
#
|
|
103
|
+
# Reset is offered only when ALL FIVE of these hold — AILK_AUTH_PASSWORD_RESET,
|
|
104
|
+
# the password method (AUTH_EMAIL_SERVER + AUTH_EMAIL_FROM), this secret plus
|
|
105
|
+
# AUTH_EMAIL_FROM, BETTER_AUTH_URL, and BETTER_AUTH_SECRET. Fail closed: a site
|
|
106
|
+
# must never tell a visitor "check your email" for mail nothing can send.
|
|
107
|
+
# AUTH_EMAIL_WEBHOOK_SECRET=
|
|
108
|
+
#
|
|
109
|
+
# Comma-separated CIDRs of YOUR edge/proxy, for Better Auth's rate-limit IP key.
|
|
110
|
+
#
|
|
111
|
+
# Unset is the honest default — this repo cannot know your deployment's edge
|
|
112
|
+
# topology — but read both failure modes before leaving it that way:
|
|
113
|
+
#
|
|
114
|
+
# * Multi-hop X-Forwarded-For with this UNSET: Better Auth resolves the
|
|
115
|
+
# client IP to null and every caller collapses into ONE SHARED BUCKET for
|
|
116
|
+
# the whole deployment (keyed on the constant "no-trusted-ip"). Because a
|
|
117
|
+
# shared bucket turns a long window into a site-wide lockout anyone can
|
|
118
|
+
# hold open, apps/web does NOT apply its widened reset windows in this
|
|
119
|
+
# state — it falls through to Better Auth's own defaults for those paths
|
|
120
|
+
# (60 s / 3) so an outage recovers by itself. Set this variable to get the
|
|
121
|
+
# stricter per-caller limits (5 per 900 s on the reset request).
|
|
122
|
+
# * Single-hop X-Forwarded-For with this UNSET: the client's own header is
|
|
123
|
+
# trusted, so an attacker mints a fresh rate-limit bucket per request.
|
|
124
|
+
#
|
|
125
|
+
# AILK_AUTH_TRUSTED_PROXIES=
|
|
60
126
|
|
|
61
127
|
# =============================================================================
|
|
62
128
|
# Observability
|
|
@@ -66,6 +66,50 @@ Don't ship a page after one render. The expected per-page loop:
|
|
|
66
66
|
- Fix mismatches. Be specific in self-review: "heading is 32px, reference shows 24px", not "looks off."
|
|
67
67
|
- Repeat until no visible differences and no responsive issues at any of the four breakpoints.
|
|
68
68
|
|
|
69
|
+
## The `(auth)` route group — which predicate an affordance keys on
|
|
70
|
+
|
|
71
|
+
`lib/auth-env.ts` exports two reset predicates, and they are not
|
|
72
|
+
interchangeable. Picking the wrong one is how this surface has broken twice
|
|
73
|
+
(#6018, then #6061 — the follow-up #6068 documents the rule here so there is
|
|
74
|
+
no third time).
|
|
75
|
+
|
|
76
|
+
| Predicate | Question it answers | Keys |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| `isResetAvailable()` | *Does the operator INTEND to offer reset?* — the `AILK_AUTH_PASSWORD_RESET` flag, alone | nothing user-visible |
|
|
79
|
+
| `resetSendConfigured()` | *Can this deployment actually COMPLETE a reset?* — that flag AND the password method AND a send seam AND `BETTER_AUTH_URL` AND `BETTER_AUTH_SECRET` | every user-visible reset affordance |
|
|
80
|
+
|
|
81
|
+
**The rule: every user-visible reset affordance keys on
|
|
82
|
+
`resetSendConfigured()`.** That is the `/forgot-password` and `/reset-password`
|
|
83
|
+
page branches, the "Forgot password?" link on `/sign-in`, and anything added
|
|
84
|
+
later that offers a visitor a reset step. The flag alone is a real deployment
|
|
85
|
+
state — intent set, transport missing — and a flag-keyed affordance in that
|
|
86
|
+
state is a link to a page that renders its own not-available card, or a form
|
|
87
|
+
whose only possible outcome is the error state. Whether an affordance is a link
|
|
88
|
+
or a whole page makes no difference to the rule.
|
|
89
|
+
|
|
90
|
+
**Server callbacks are not affordances, and one is deliberately outside the
|
|
91
|
+
predicate.** `sendResetPassword` IS gated — a defined callback under a page
|
|
92
|
+
that denies the flow exists would be a live anonymous mail-sending surface.
|
|
93
|
+
`auth.ts`'s `onPasswordReset` is NOT, and must stay that way: Better Auth
|
|
94
|
+
registers the `/reset-password` POST on `emailAndPassword.enabled`, so a token
|
|
95
|
+
minted before an operator removed a conjunct stays consumable for its whole
|
|
96
|
+
hour, and gating the callback would make exactly those resets the ones that
|
|
97
|
+
skip sibling-token invalidation and the password-changed notice. Offering a
|
|
98
|
+
step is predicated; cleaning up after one that already happened is not.
|
|
99
|
+
|
|
100
|
+
**Anything `onPasswordReset` calls must not be able to reject it.** It runs
|
|
101
|
+
AFTER the password has changed and the presented token has been consumed, so a
|
|
102
|
+
rejection is not a failed reset — it is a 500 shown to someone whose password
|
|
103
|
+
DID change, over a token they cannot retry with. Both halves of that callback
|
|
104
|
+
are guarded at the call site, and `lib/auth-email.ts` guards its own signing
|
|
105
|
+
and transport steps (#6068).
|
|
106
|
+
|
|
107
|
+
**Mocking `lib/auth-env` in a test?** The suites under `(auth)` use PARTIAL
|
|
108
|
+
module mocks, so a member the factory omits is `undefined` rather than the real
|
|
109
|
+
function — the page under test throws instead of taking a branch. Declare
|
|
110
|
+
every predicate the pages call, binding the two reset ones to the same
|
|
111
|
+
`jest.fn()` when the case does not need them to disagree.
|
|
112
|
+
|
|
69
113
|
## Conversational intake (contact page)
|
|
70
114
|
|
|
71
115
|
The contact page (`apps/web/app/[locale]/contact/`) consumes
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
* default) instead of a hardcoded vendor-brand literal.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
getMcpToolCatalog,
|
|
21
|
+
type McpToolCatalogEntry,
|
|
22
|
+
} from "@working-theory/metadata";
|
|
20
23
|
|
|
21
24
|
import { SITE_NAME } from "../../../lib/site-brand";
|
|
22
25
|
|
|
@@ -74,6 +77,17 @@ export interface BuildMcpManifestOptions {
|
|
|
74
77
|
* Typically sourced from the MCP_PUBLIC_HTTP_URL env var in the web app.
|
|
75
78
|
*/
|
|
76
79
|
mcpHttpUrl?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Whether this deployment holds the `commerce` add-on licence claim
|
|
82
|
+
* (#5971). Add-on-gated tools — today only `start_purchase` — are advertised
|
|
83
|
+
* only when it does, so the discovery manifest and the running MCP server
|
|
84
|
+
* agree about what this site offers.
|
|
85
|
+
*
|
|
86
|
+
* Injected rather than resolved here: this builder is PURE (same input →
|
|
87
|
+
* same output), a property its own suite asserts. `route.ts` resolves the
|
|
88
|
+
* claim and passes it in. Omitted ⇒ false, the fail-closed default.
|
|
89
|
+
*/
|
|
90
|
+
commerceClaimed?: boolean;
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
/** Path the MCP server is mounted at, relative to baseUrl. */
|
|
@@ -105,7 +119,9 @@ export function buildMcpManifest(opts: BuildMcpManifestOptions): McpManifest {
|
|
|
105
119
|
description:
|
|
106
120
|
"AX-first OSS template — ship software with API + MCP as canonical agent surfaces.",
|
|
107
121
|
mcpEndpoint: `${opts.baseUrl}${MCP_ENDPOINT_PATH}`,
|
|
108
|
-
tools: getMcpToolCatalog(
|
|
122
|
+
tools: getMcpToolCatalog({
|
|
123
|
+
addons: { commerce: opts.commerceClaimed === true },
|
|
124
|
+
}),
|
|
109
125
|
transport: {
|
|
110
126
|
type: "http",
|
|
111
127
|
version: MCP_HTTP_VERSION,
|
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
* URL, and discover the MCP endpoint + tool catalog without out-of-band
|
|
6
6
|
* coordination. Issue #209 (initial), #300 (moved to correct URL + CORS).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* @working-theory/metadata; the AILK version is read from this app's
|
|
8
|
+
* Assembly — no fetch(). The catalog is read in-process from
|
|
9
|
+
* @working-theory/metadata; the AILK version is read from this app's
|
|
10
|
+
* package.json; the `commerce` add-on claim is read locally through
|
|
11
|
+
* `checkLicense()` (#5971) so an add-on-gated tool is advertised on exactly
|
|
12
|
+
* the deployments whose MCP server registers it. Without that, a KEYED site
|
|
13
|
+
* would register `start_purchase` while hiding it from its own manifest —
|
|
14
|
+
* the surface disagreement `apps/mcp/__tests__/catalog-drift.test.ts` exists
|
|
15
|
+
* to make impossible.
|
|
10
16
|
*
|
|
11
17
|
* Mirrors /.well-known/ai-plugin.json conventions: Response.json with a
|
|
12
18
|
* conservative cache-control header.
|
|
@@ -17,6 +23,7 @@
|
|
|
17
23
|
*/
|
|
18
24
|
|
|
19
25
|
import packageJson from "../../../package.json";
|
|
26
|
+
import { resolveCommerceClaim } from "../../../src/lib/commerce-claim";
|
|
20
27
|
import { resolveBaseUrl } from "../../../src/lib/routes";
|
|
21
28
|
|
|
22
29
|
import { buildMcpManifest } from "./build-manifest";
|
|
@@ -34,7 +41,7 @@ export function OPTIONS(): Response {
|
|
|
34
41
|
});
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
export function GET(): Response {
|
|
44
|
+
export async function GET(): Promise<Response> {
|
|
38
45
|
// MCP_PUBLIC_HTTP_URL advertises the standalone HTTP transport endpoint
|
|
39
46
|
// (apps/mcp running in HTTP mode). When set, the manifest includes it
|
|
40
47
|
// as an additional transport descriptor for discovering agents.
|
|
@@ -43,6 +50,7 @@ export function GET(): Response {
|
|
|
43
50
|
const manifest = buildMcpManifest({
|
|
44
51
|
baseUrl: resolveBaseUrl(),
|
|
45
52
|
ailkVersion: packageJson.version,
|
|
53
|
+
commerceClaimed: await resolveCommerceClaim(),
|
|
46
54
|
// Spread conditional to satisfy exactOptionalPropertyTypes: omit the key
|
|
47
55
|
// entirely when the env var is not set rather than passing undefined.
|
|
48
56
|
...(mcpHttpUrlEnv ? { mcpHttpUrl: mcpHttpUrlEnv } : {}),
|