create-ailk 0.22.0 → 0.24.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 +27 -14
- package/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +17 -10
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +301 -13
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +26 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +36 -2
- package/dist/lib/scaffold-render-sweep.js +50 -8
- package/dist/module-architecture.js +115 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +68 -5
- package/dist/sync-routes.js +455 -29
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +13 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/CLAUDE.md +4 -2
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +34 -0
- package/templates/apps/api/CLAUDE.md +94 -19
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/site.ts +4 -1
- package/templates/apps/api/src/lib/stripe.ts +77 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
- package/templates/apps/api/src/openapi/spec.ts +343 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +34 -119
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/search.ts +16 -45
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +377 -308
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
- package/templates/apps/api/src/server.ts +165 -51
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
- package/templates/apps/api/src/services/abandon-email.ts +28 -5
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +147 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/project-listings.ts +43 -31
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +32 -18
- package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
- package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
- package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
- package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
- package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/package.json +1 -0
- package/templates/apps/mcp/src/abuse/index.ts +18 -11
- package/templates/apps/mcp/src/http-transport.ts +47 -9
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/lib/site-config.ts +53 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/index.ts +22 -21
- package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/tools/read_content.ts +46 -40
- package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
- package/templates/apps/mcp/src/tools/search_content.ts +20 -24
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +324 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +44 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +205 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +51 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +63 -1
- package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +57 -2
- package/templates/tools/lint-architecture.ts +205 -1
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -176,6 +176,14 @@ export function deriveListingSlug(name: string): string {
|
|
|
176
176
|
*/
|
|
177
177
|
const SLUG_COLLISION_SCAN_LIMIT = 200;
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* The most active rows the anonymous page-render read returns (#9243). No
|
|
181
|
+
* consumer in this repo names a shelf size, so the bound is the one the other
|
|
182
|
+
* anonymous listing read already carries — the directory's
|
|
183
|
+
* `LISTING_DIRECTORY_MAX_ENTRIES` — rather than a second number to keep in step.
|
|
184
|
+
*/
|
|
185
|
+
const PUBLIC_LISTINGS_MAX_ROWS = LISTING_DIRECTORY_MAX_ENTRIES;
|
|
186
|
+
|
|
179
187
|
/**
|
|
180
188
|
* The first free slug for `base` on this site. Uniqueness is already scoped
|
|
181
189
|
* `@@unique([siteId, slug])`, so the suffix is too: a second "Atlas" on this
|
|
@@ -412,10 +420,10 @@ function toResponseRow(
|
|
|
412
420
|
* Since #5036 D6d the listing stores the experiment's KEY rather than its id,
|
|
413
421
|
* and that key is the same string `WaitlistScore.experimentKey` holds — so the
|
|
414
422
|
* `WaitlistExperiment` read that existed purely to translate one into the
|
|
415
|
-
* other is gone.
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
423
|
+
* other is gone. ONE `WaitlistScore.groupBy` over every DISTINCT key remains
|
|
424
|
+
* (#9243): one read across every returned row (`waitlist-dashboard.ts`'s
|
|
425
|
+
* discipline), however many experiments the page carries — the per-key
|
|
426
|
+
* `count()` fan-out this replaced ran on the anonymous public read.
|
|
419
427
|
*/
|
|
420
428
|
async function attachWaitlistCounts(
|
|
421
429
|
siteId: string,
|
|
@@ -426,29 +434,28 @@ async function attachWaitlistCounts(
|
|
|
426
434
|
];
|
|
427
435
|
if (experimentKeys.length === 0) return new Map();
|
|
428
436
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
);
|
|
451
|
-
return counts;
|
|
437
|
+
// fence: workspaceId === the scope id this query already filters siteId by
|
|
438
|
+
// (workspace_site_agree CHECK, 20260922120000_tenancy_fence). Filtering the
|
|
439
|
+
// CONSTRAINED column makes the scope correct by construction; the format
|
|
440
|
+
// CHECK makes a raw alias match nothing rather than by accident.
|
|
441
|
+
//
|
|
442
|
+
// What that id IS depends on the caller, and only two of the six are
|
|
443
|
+
// route-resolved: `listProjectListings` and `getProjectListing` receive
|
|
444
|
+
// `tenant.workspaceId`, so for them this is an isolation control.
|
|
445
|
+
// `listMyProjectListings` and the two `publicProjectListings` call sites
|
|
446
|
+
// pass a ProjectListing row's OWN `siteId`, so for them it is a
|
|
447
|
+
// CONSISTENCY control — the count agrees with the row it is reported
|
|
448
|
+
// against — exactly as `project-listing-decision.ts#waitlistCountFor`
|
|
449
|
+
// spells out (#6258 § 1.6 / § 5 D8, security review L-2).
|
|
450
|
+
//
|
|
451
|
+
// A key with no score rows is absent from a GROUP BY result; `waitlistCountFor`
|
|
452
|
+
// reads it as 0, the same value the per-key `count()` produced.
|
|
453
|
+
const grouped = await prisma.waitlistScore.groupBy({
|
|
454
|
+
by: ["experimentKey"],
|
|
455
|
+
where: { siteId, workspaceId: siteId, experimentKey: { in: experimentKeys } },
|
|
456
|
+
_count: { _all: true },
|
|
457
|
+
});
|
|
458
|
+
return new Map(grouped.map((g) => [g.experimentKey, g._count._all]));
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
function waitlistCountFor(
|
|
@@ -1902,10 +1909,10 @@ function orderPublicListings<T extends { row: { id: string; decidedAt: Date | nu
|
|
|
1902
1909
|
}
|
|
1903
1910
|
|
|
1904
1911
|
/**
|
|
1905
|
-
* The page-render read (#4998 AC4, reshaped by #5033
|
|
1906
|
-
*
|
|
1907
|
-
* `
|
|
1908
|
-
* this function.
|
|
1912
|
+
* The page-render read (#4998 AC4, reshaped by #5033, bounded by #9243): the
|
|
1913
|
+
* newest-decided `PUBLIC_LISTINGS_MAX_ROWS` `active` rows for the site, the
|
|
1914
|
+
* THREE newest by `decidedAt` first, then the rest by `waitlistCount` desc,
|
|
1915
|
+
* then `decidedAt` desc. `founderEmail` never leaves this function.
|
|
1909
1916
|
*
|
|
1910
1917
|
* Rows whose window has elapsed are returned and read `closed` — computed,
|
|
1911
1918
|
* per row, against one instant, with no job having run. They are not filtered
|
|
@@ -1917,6 +1924,11 @@ export async function publicProjectListings(siteId: string): Promise<ProjectList
|
|
|
1917
1924
|
const listingConfig = await listingConfigFor(siteId);
|
|
1918
1925
|
const rows = await prisma.projectListing.findMany({
|
|
1919
1926
|
where: { siteId, status: "active" },
|
|
1927
|
+
// Same window as the directory read: with a `take`, the rows kept must be
|
|
1928
|
+
// chosen, and "newest decided first, NULLS LAST" is the order the public
|
|
1929
|
+
// shelf leads with anyway.
|
|
1930
|
+
orderBy: [{ decidedAt: { sort: "desc", nulls: "last" } }, { id: "asc" }],
|
|
1931
|
+
take: PUBLIC_LISTINGS_MAX_ROWS,
|
|
1920
1932
|
select: PROJECT_LISTING_SELECT,
|
|
1921
1933
|
});
|
|
1922
1934
|
const counts = await attachWaitlistCounts(siteId, rows);
|
|
@@ -62,13 +62,16 @@
|
|
|
62
62
|
* prefix — and never reaches the wire.
|
|
63
63
|
*
|
|
64
64
|
* THE FLOWS ROUTES' WORKSPACE (#7844 D1). `resolveCallerWorkspace` below is
|
|
65
|
-
* the one function the five `/v1/flows` routes
|
|
66
|
-
* a spoke resolves to its own workspace
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
65
|
+
* the one function the five `/v1/flows` routes (and the open deliverable)
|
|
66
|
+
* resolve their workspace with: a spoke resolves to its own workspace
|
|
67
|
+
* (`viaSpoke: true`), a keyless call through branch 2's one-workspace
|
|
68
|
+
* predicate (#9145 — never the `AILK_SITE_ID` alias, so every anonymous
|
|
69
|
+
* route answers the same workspace for the same deployment), and anything
|
|
70
|
+
* else to `unresolved` with one of two reasons, `spoke-refused` or
|
|
71
|
+
* `ambiguous`. The reasons are for `request.log` only; the routes answer both
|
|
72
|
+
* the same way. It lives here rather than under `routes/flows/` because the
|
|
73
|
+
* two flows route files sit in different module slices, and this file is
|
|
74
|
+
* base tree.
|
|
72
75
|
*/
|
|
73
76
|
import {
|
|
74
77
|
resolvedAdapterKind,
|
|
@@ -253,13 +256,15 @@ export type CallerWorkspace =
|
|
|
253
256
|
/** #8771 D3 — set exactly when `viaSpoke` is true: the registered spoke's id. */
|
|
254
257
|
spokeId?: string;
|
|
255
258
|
}
|
|
256
|
-
| { kind: "unresolved"; reason: "spoke-refused" | "
|
|
259
|
+
| { kind: "unresolved"; reason: "spoke-refused" | "ambiguous" };
|
|
257
260
|
|
|
258
261
|
/**
|
|
259
262
|
* Resolve the workspace a `/v1/flows` request serves. A spoke resolves to its
|
|
260
|
-
* own workspace; a keyless call
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
+
* own workspace; a keyless call through `resolveDefaultWorkspaceId`, the
|
|
264
|
+
* predicate `resolvePublicWorkspace` uses (#9145) — on any content adapter,
|
|
265
|
+
* since a flow is a database row whatever serves the pages; a refused caller
|
|
266
|
+
* to nothing. There is no fallback: a spoke never reaches the default
|
|
267
|
+
* workspace (D5).
|
|
263
268
|
*
|
|
264
269
|
* Calls `resolveCallerSite` with no options, so a failed hub-status read on a
|
|
265
270
|
* keyless call rejects (#7844 C1).
|
|
@@ -280,10 +285,10 @@ export async function resolveCallerWorkspace(
|
|
|
280
285
|
};
|
|
281
286
|
}
|
|
282
287
|
// Deferred, like every database import in this file.
|
|
283
|
-
const {
|
|
284
|
-
const workspaceId = await
|
|
288
|
+
const { resolveDefaultWorkspaceId } = await import("./default-tenant.js");
|
|
289
|
+
const workspaceId = await resolveDefaultWorkspaceId();
|
|
285
290
|
return workspaceId === null
|
|
286
|
-
? { kind: "unresolved", reason: "
|
|
291
|
+
? { kind: "unresolved", reason: "ambiguous" }
|
|
287
292
|
: { kind: "resolved", workspaceId, viaSpoke: false };
|
|
288
293
|
}
|
|
289
294
|
|
|
@@ -43,8 +43,10 @@ Inherits root `CLAUDE.md`. This file adds MCP-specific rules.
|
|
|
43
43
|
name points straight at the Vercel project; a second proxy in front keys every caller on
|
|
44
44
|
that proxy. It is a function, not `trustProxy: 1`, because Fastify 5.12 treats a numeric
|
|
45
45
|
hop count as trusting nothing. The long-running entry trusts no proxy.
|
|
46
|
-
- **Per address, per instance.** 60 requests a minute across
|
|
47
|
-
|
|
46
|
+
- **Per address, per instance.** 60 requests a minute across every request
|
|
47
|
+
(`MCP_RATE_LIMIT_READ_PER_MIN`), plus a tighter 10 a minute on the six anonymous write tools
|
|
48
|
+
(`MCP_RATE_LIMIT_WRITE_PER_MIN`, #9251), refused with the same 429 shape; `capture_lead` 50 a
|
|
49
|
+
day, `start_purchase` 20 a day,
|
|
48
50
|
`create_booking` 20 a day and `submit_flow_step` 50 a day, each on its own budget.
|
|
49
51
|
`subscribe_newsletter` and `request_callback` have no daily cap. Each warm instance counts
|
|
50
52
|
on its own and a cold start resets the counts, so a caller reaching N instances gets up to
|
|
@@ -73,36 +75,36 @@ Every tool maps 1:1 to an `apps/api` endpoint. When you change a tool, change th
|
|
|
73
75
|
|
|
74
76
|
**The HTTP write branch (6).** `WRITE_TOOLS` in `src/http-transport.ts` — `capture_lead`, `subscribe_newsletter`, `request_callback`, `start_purchase`, `create_booking`, `submit_flow_step` — takes the write-origin allow-list and the AbuseGuard (UA, honeypot, email format, per-IP daily cap); every other tool takes the read branch (UA only). A tool that sends caller-chosen data to the hub belongs in it: `__tests__/write-tool-guard.test.ts` fails when a catalog tool that sends a non-GET request through `@working-theory/api-client` is missing, and makes every new catalog tool pick a branch (#8071).
|
|
75
77
|
|
|
76
|
-
**Read (9):** `list_pages`, `read_content`, `search_content`, `get_site_info`, `get_business_hours`, `get_locations`, `get_offerings`, `get_pricing`, `get_faq`
|
|
77
|
-
**Conversion-write (3):** `capture_lead`, `subscribe_newsletter`, `request_callback`
|
|
78
|
+
**Read (9):** `list_pages`, `read_content`, `search_content`, `get_site_info`, `get_business_hours`, `get_locations`, `get_offerings`, `get_pricing`, `get_faq`. The first three adopted their `apps/api` routes' arguments and results (#7167, #7168, #7169 — the operator's decision of 2026-10-19): `search_content` takes `q` and answers the route's `ItemList` (`query`, `items`, `totalItems`); `list_pages` requires `type`, takes `page`/`pageSize` and answers the paginated `ItemList` (`items`, `pagination`); `read_content` takes `include` and answers the page's JSON-LD at the root with `page`, plus `body` only with `include: "body"` — a miss is the not-found body. Each parses the same `@working-theory/validation` export its route parses with and answers through the same `@working-theory/content-adapters` builder, so the parity guard holds them as `twin`s
|
|
79
|
+
**Conversion-write (3):** `capture_lead`, `subscribe_newsletter`, `request_callback` — each takes the body its `POST /v1/leads/{submit,subscribe,callback}` route parses (`leadSubmitBodySchema`: `source`, `email`, `consent`, `payload`; `leadSubscribeBodySchema`: `email`, `source`; `leadCallbackBodySchema`: `name`, `phone`, `preferredTime`, `payload`) and returns the route's 201 body `{ id, persisted, email }` unchanged (#7171, #7172, #7173 — the operator's decision of 2026-10-19), so the parity guard holds the three as `twin`s. `source` is the agent's to choose — `mcp` unless the operator says otherwise — and a consent-bearing source needs `consent: true`, exactly as a form does; `capture_lead` still spreads the spoke marker `payload.site` from `AILK_LEAD_SITE` last. The AbuseGuard reads the first two's `email` at the top level, else inside `payload` (the route lifts it from there), so a malformed address is refused wherever it sits
|
|
78
80
|
**Maintenance (2):** `audit`, `create_project`
|
|
79
81
|
**Flow (2):** `get_flow`, `submit_flow_step` — module-gated (`module: "signal-collectors"`, D-48, #5366) and licence-feature-tagged (`feature: "flows-deliverables"`, #7136): identity-blind like every tool in this list, registered only when the signal-collectors module is present, and gated on the deployment's `flows-deliverables` licence feature under `MCP_FEATURE_GATE_MODE`, the same shape Booking below has. `submit_flow_step` takes the HTTP write branch (its own daily cap, `MCP_ABUSE_SUBMIT_FLOW_STEP_DAILY_CAP`, default 50 — #8071)
|
|
80
|
-
**Booking (
|
|
82
|
+
**Booking (4):** `list_availability`, `get_event_meta`, `list_meetings`, `create_booking` — module-gated (`module: "scheduling"`) and licence-feature-tagged (`feature: "scheduling"`, #7136), gated on the deployment's `scheduling` licence feature under `MCP_FEATURE_GATE_MODE`. `create_booking` takes the HTTP write branch (its own daily cap, `MCP_ABUSE_CREATE_BOOKING_DAILY_CAP`, default 20, and an `attendee.email` format check — #8071). `list_meetings` (#8843 D5) twins `GET /v1/schedule/meetings`: it takes no argument and returns the site's Live meetings — slug, visibility, position and whether each can be booked, plus a bookable one's calLink, title, length, description and price; `private` means unlisted, not secret
|
|
81
83
|
|
|
82
|
-
### Why the parity test counts
|
|
84
|
+
### Why the parity test counts 21 and a keyless server registers 20
|
|
83
85
|
|
|
84
|
-
- **
|
|
85
|
-
`packages/metadata/src/mcp-tool-catalog.ts` defines
|
|
86
|
-
parity test counts that full array. It also holds the
|
|
86
|
+
- **21 in the `TOOLS` array / parity test**: the `TOOLS` array in
|
|
87
|
+
`packages/metadata/src/mcp-tool-catalog.ts` defines 21 anonymous tools; the
|
|
88
|
+
parity test counts that full array. It also holds the eighteen signed-in
|
|
87
89
|
workspace tools (#3591), which no anonymous view returns — see § The
|
|
88
90
|
signed-in workspace surface below.
|
|
89
|
-
- **
|
|
91
|
+
- **20 registered on a KEYLESS deployment, 21 on a commerce-claimed one**
|
|
90
92
|
(#5971): `createMcpServer` (`apps/mcp/src/server.ts`) registers
|
|
91
93
|
`getMcpToolRegistrations()` minus `OSS_DISABLED_TOOLS` (now empty), minus
|
|
92
94
|
module-gated tools whose module is off, minus any `addon`-tagged tool whose
|
|
93
95
|
claim this deployment does not hold. `start_purchase` carries
|
|
94
96
|
`addon: "commerce"`, so it is the one entry whose presence depends on the
|
|
95
|
-
licence —
|
|
96
|
-
- **
|
|
97
|
+
licence — 20 without the claim, 21 with it.
|
|
98
|
+
- **20 free anonymous tools**: since #5853 retired the three content-write
|
|
97
99
|
tools, every registered tool remains anonymous — call-time gating checks a
|
|
98
|
-
licence claim, never an identity. The
|
|
100
|
+
licence claim, never an identity. The six carrying `tier: "pro"`
|
|
99
101
|
(`get_flow`, `submit_flow_step`, `list_availability`, `get_event_meta`,
|
|
100
|
-
`create_booking`) are gated at REGISTRATION **and at call time** (#7315):
|
|
102
|
+
`list_meetings`, `create_booking`) are gated at REGISTRATION **and at call time** (#7315):
|
|
101
103
|
each needs its module, and each catalog row names the licence `feature:`
|
|
102
104
|
that entitles it. `start_purchase` is gated at call time by both this
|
|
103
105
|
server-wide gate and its own Gate 2 in `tools/purchase.ts`.
|
|
104
106
|
`MCP_FEATURE_GATE_MODE` (`packages/metadata`) decides what an unheld
|
|
105
|
-
feature does. In `"warn"` mode (this release) the
|
|
107
|
+
feature does. In `"warn"` mode (this release) the six register on module
|
|
106
108
|
presence alone, and the boot (`bootMcp()`) logs one warning (stderr) per tool
|
|
107
109
|
served without its feature. In `"refuse"` mode (the next release) they
|
|
108
110
|
also need the deployment's licence to hold the feature, and
|
|
@@ -126,10 +128,14 @@ here. Spec: `docs/specs/3591-tenant-scoped-agent-auth-secure-spec.md` D13–D16.
|
|
|
126
128
|
- **apps/mcp checks only the key's shape.** The key is attached as the SDK's `req.auth` for that
|
|
127
129
|
request alone and reaches a tool as `extra.authInfo.token`. apps/api is the one place it is
|
|
128
130
|
checked, and its tenant guard and the caller's clamped role decide every answer.
|
|
129
|
-
- **
|
|
131
|
+
- **Eighteen tools, each a `twin`.** The catalog rows carry `surface: "workspace"`, `tier: "pro"`
|
|
130
132
|
and `modulesAnyOf` (the modules any one of which registers the tool — its twin route's gate):
|
|
131
133
|
`get_agent_context`, the four message tools, the three submission tools, the three
|
|
132
|
-
application tools
|
|
134
|
+
listing application tools (#9137: `list_listing_applications`, `get_listing_application`,
|
|
135
|
+
`decide_listing_application` — a `ProjectListing` row, never a job application, which is a Job
|
|
136
|
+
application magnet's submission; the pre-rename `list_inbox_applications`,
|
|
137
|
+
`get_inbox_application` and `decide_inbox_application` stay registered for one release as
|
|
138
|
+
deprecated aliases of the same handlers, each description opening `Deprecated: use …`) and (#8514) the two People notes tools, `get_person_notes` and
|
|
133
139
|
`set_person_notes` — the team's private note on one client, which an agent reads and writes
|
|
134
140
|
only with the `member-pieces-manage` grant; `set_person_notes` requires the `ifUpdatedAt` it
|
|
135
141
|
read and is refused `notes_changed` when someone saved since — and (#8516) the two proposals
|
|
@@ -219,7 +225,15 @@ Example (lead routing):
|
|
|
219
225
|
- Don't add a tool without the matching `apps/api` endpoint.
|
|
220
226
|
- Don't put business logic in tool handlers — they're adapters.
|
|
221
227
|
|
|
222
|
-
Last updated: 2026-10-
|
|
228
|
+
Last updated: 2026-10-20 — #7171, #7172, #7173: the three lead writes adopted their routes' bodies and 201 results and are parity `twin`s — the Conversion-write line records the adopted shapes — verified against `apps/mcp/src/tools/{capture_lead,subscribe_newsletter,request_callback}.ts`, `packages/validation/src/schemas/leads.ts` and `tests/parity/api-parity/kinds/site.ts`.
|
|
229
|
+
|
|
230
|
+
Prior: 2026-10-20 — #7167, #7168, #7169: the three content reads adopted their routes' arguments and results and are parity `twin`s — the Read line records the adopted shapes — verified against `apps/mcp/src/tools/{search_content,list_pages,read_content}.ts`, `packages/validation/src/schemas/content-reads.ts`, `packages/content-adapters/src/content-reads.ts` and `tests/parity/api-parity/kinds/site.ts`.
|
|
231
|
+
|
|
232
|
+
Prior: 2026-10-18 — #9137: the three listing application tools are `list_listing_applications`, `get_listing_application` and `decide_listing_application`, and their pre-rename names stay one release as deprecated aliases — the signed-in workspace surface is eighteen tools — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
|
|
233
|
+
|
|
234
|
+
Prior: 2026-10-18 — #8843 PR A: `list_meetings` twins `GET /v1/schedule/meetings` — Booking is four tools, the counts are 21 in TOOLS, 20 registered keyless and 21 commerce-claimed, and six tools carry `tier: "pro"` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/server.ts` and `tests/parity/api-parity/kinds/site.ts`.
|
|
235
|
+
|
|
236
|
+
Prior: 2026-10-14 — #8516: the signed-in workspace surface is fifteen tools — `list_proposals` and `create_proposal` (`tools/proposals.ts`) twin `GET`/`POST /v1/workspaces/:workspaceId/people/:personId/proposals` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
|
|
223
237
|
|
|
224
238
|
Prior: 2026-10-13 — #8514: the signed-in workspace surface is thirteen tools — `get_person_notes` and `set_person_notes` (`tools/person_notes.ts`) twin `GET`/`PUT /v1/workspaces/:workspaceId/people/:personId/notes` — verified against `packages/metadata/src/mcp-tool-catalog.ts`, `apps/mcp/src/workspace-server.ts` and `tests/parity/api-parity/kinds/site.ts`.
|
|
225
239
|
|
|
@@ -134,9 +134,9 @@ function captureLead(id: number, name: string): object {
|
|
|
134
134
|
params: {
|
|
135
135
|
name: "capture_lead",
|
|
136
136
|
arguments: {
|
|
137
|
-
|
|
137
|
+
source: "mcp",
|
|
138
138
|
email: `${name.toLowerCase()}@example.com`,
|
|
139
|
-
message: "Interested in your services.",
|
|
139
|
+
payload: { name, message: "Interested in your services." },
|
|
140
140
|
},
|
|
141
141
|
},
|
|
142
142
|
};
|
|
@@ -105,9 +105,14 @@ type GateMode = NonNullable<McpCatalogOptions["featureGate"]>;
|
|
|
105
105
|
|
|
106
106
|
type McpFeature = NonNullable<McpToolCatalogEntry["feature"]>;
|
|
107
107
|
|
|
108
|
-
/** The
|
|
108
|
+
/** The six licence-feature tools, by the feature that entitles them. */
|
|
109
109
|
const FEATURE_TOOLS: Record<McpFeature, readonly string[]> = {
|
|
110
|
-
scheduling: [
|
|
110
|
+
scheduling: [
|
|
111
|
+
"list_availability",
|
|
112
|
+
"get_event_meta",
|
|
113
|
+
"list_meetings",
|
|
114
|
+
"create_booking",
|
|
115
|
+
],
|
|
111
116
|
"flows-deliverables": ["get_flow", "submit_flow_step"],
|
|
112
117
|
};
|
|
113
118
|
|
|
@@ -318,14 +323,14 @@ describe.each([
|
|
|
318
323
|
const bootNames = Object.keys(
|
|
319
324
|
registeredToolsOf(createMcpServer()),
|
|
320
325
|
).sort();
|
|
321
|
-
// Both counts below are of the served catalog — the full catalog (
|
|
326
|
+
// Both counts below are of the served catalog — the full catalog (21 at
|
|
322
327
|
// boot, 14 after the re-read) minus this seam's UNSERVED_MCP_TOOLS (#8665).
|
|
323
328
|
expect(bootNames).toEqual(
|
|
324
329
|
servedCatalog(bootOptions)
|
|
325
330
|
.map((t) => t.name)
|
|
326
331
|
.sort(),
|
|
327
332
|
);
|
|
328
|
-
expect(bootNames).toHaveLength(
|
|
333
|
+
expect(bootNames).toHaveLength(21 - UNSERVED_MCP_TOOLS.length);
|
|
329
334
|
|
|
330
335
|
startMcpClaimRefresh({ checkLicense: resolving({ features: [] }) });
|
|
331
336
|
await jest.advanceTimersByTimeAsync(MCP_CLAIM_REFRESH_MS);
|