create-ailk 0.1.1 → 0.3.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/component-catalog.md +210 -52
- package/dist/cli.js +0 -0
- package/dist/lib/apply-module-patches.d.ts +47 -0
- package/dist/lib/apply-module-patches.js +370 -0
- package/dist/lib/extract-module-bundle.d.ts +12 -3
- package/dist/lib/extract-module-bundle.js +236 -24
- package/dist/lib/fetch-module.d.ts +3 -38
- package/dist/lib/fetch-module.js +26 -8
- package/dist/lib/module-license-gate.d.ts +2 -0
- package/dist/lib/module-license-gate.js +1 -0
- package/dist/lib/paid-paths.d.ts +16 -0
- package/dist/lib/paid-paths.js +90 -0
- package/dist/module-architecture.d.ts +139 -23
- package/dist/module-architecture.js +668 -25
- package/dist/parse-args.d.ts +2 -2
- package/dist/parse-args.js +3 -1
- package/dist/programmatic.d.ts +2 -1
- package/dist/programmatic.js +15 -4
- package/dist/surfaces.d.ts +12 -36
- package/dist/surfaces.js +35 -5
- package/dist/sync-routes.js +31 -1
- package/package.json +14 -15
- package/templates/.claude/agents/web.md +2 -3
- package/templates/.claude/rules/architecture.md +5 -5
- package/templates/.claude/skills/README.md +2 -1
- package/templates/.env.example +21 -9
- package/templates/CONVENTIONS.md +6 -7
- package/templates/apps/api/.env.example +9 -0
- package/templates/apps/api/CLAUDE.md +35 -4
- package/templates/apps/api/package.json +0 -1
- package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
- package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
- package/templates/apps/api/src/config/index.ts +19 -0
- package/templates/apps/api/src/config/modules.ts +12 -14
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +13 -0
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
- package/templates/apps/api/src/lib/slice-load.ts +90 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
- package/templates/apps/api/src/openapi/index.ts +9 -3
- package/templates/apps/api/src/openapi/spec.ts +505 -65
- package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
- package/templates/apps/api/src/routes/content/index.ts +38 -11
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +28 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +144 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +478 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +717 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +35 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +484 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +52 -10
- package/templates/apps/api/src/routes/project-listings/me.ts +86 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +38 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
- package/templates/apps/api/src/server.ts +471 -133
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
- package/templates/apps/api/src/services/listing-config.ts +58 -0
- package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
- package/templates/apps/api/src/services/project-listings.ts +602 -29
- package/templates/apps/api/src/vercel-handler.ts +60 -26
- package/templates/apps/mcp/.env.example +8 -0
- package/templates/apps/mcp/CLAUDE.md +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
- package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
- package/templates/apps/mcp/src/config/modules.ts +14 -13
- package/templates/apps/mcp/src/server.ts +8 -4
- package/templates/apps/mcp/src/tools/index.ts +7 -22
- package/templates/apps/web/.env.example +15 -0
- package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/layout.tsx +13 -1
- package/templates/apps/web/app/llms.txt/route.ts +12 -9
- package/templates/apps/web/jest.config.cjs +20 -13
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -1
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/next.config.mjs +1 -3
- package/templates/apps/web/package.json +1 -6
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +6 -3
- package/templates/apps/web/public/lockup-horizontal.svg +4 -0
- package/templates/apps/web/public/logomark.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +2 -2
- package/templates/apps/web/public/wordmark.svg +4 -0
- package/templates/content/_site.mdx +12 -0
- package/templates/database/CHANGELOG.md +94 -0
- package/templates/database/inbox/schema.prisma +165 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
- package/templates/database/package.json +1 -1
- package/templates/database/scripts/db-generate-locked.sh +0 -0
- package/templates/package.json +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
- package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
- package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
- package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
- package/templates/apps/api/src/__tests__/server.test.ts +0 -253
- package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
- package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
- package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
- package/templates/apps/api/src/bin/seed-presets.ts +0 -58
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
- package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
- package/templates/apps/api/src/lib/stripe.ts +0 -52
- package/templates/apps/api/src/lib/tenant-db.ts +0 -225
- package/templates/apps/api/src/lib/ws-token.ts +0 -91
- package/templates/apps/api/src/middleware/tenant.ts +0 -99
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
- package/templates/apps/api/src/routes/billing/index.ts +0 -36
- package/templates/apps/api/src/routes/billing/portal.ts +0 -182
- package/templates/apps/api/src/routes/billing/read.ts +0 -75
- package/templates/apps/api/src/routes/billing/usage.ts +0 -153
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
- package/templates/apps/api/src/routes/checkout/index.ts +0 -13
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
- package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
- package/templates/apps/api/src/routes/flows/README.md +0 -147
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
- package/templates/apps/api/src/routes/flows/index.ts +0 -202
- package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
- package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
- package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
- package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
- package/templates/apps/api/src/routes/schedule/index.ts +0 -249
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
- package/templates/apps/api/src/routes/slack/actions.ts +0 -177
- package/templates/apps/api/src/routes/slack/index.ts +0 -38
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
- package/templates/apps/api/src/routes/webhooks/README.md +0 -80
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
- package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
- package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
- package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
- package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
- package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
- package/templates/apps/api/src/services/flow-engine.ts +0 -1278
- package/templates/apps/api/src/services/lead-promotion.ts +0 -176
- package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
- package/templates/apps/api/src/services/playbook-compile.ts +0 -398
- package/templates/apps/api/src/services/playbook-render.ts +0 -263
- package/templates/apps/api/src/services/project-listing-decision.ts +0 -490
- package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
- package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
- package/templates/apps/api/src/services/recommender-capture.ts +0 -835
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
- package/templates/apps/api/src/services/scheduling/index.ts +0 -63
- package/templates/apps/api/src/services/scheduling/types.ts +0 -88
- package/templates/apps/api/src/services/tenant-context.ts +0 -451
- package/templates/apps/api/src/services/usage-metering.ts +0 -699
- package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
- package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
- package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
- package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
- package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
- package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
- package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
- package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
- package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
- package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
- package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
- package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
- package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
- package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
- package/templates/apps/web/content/en/blog/README.txt +0 -25
- package/templates/apps/web/content/en/docs/index.mdx +0 -28
- package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
- package/templates/apps/web/lib/blog-list-data.ts +0 -95
- package/templates/apps/web/lib/blog-post-data.ts +0 -79
- package/templates/apps/web/lib/source.ts +0 -34
- package/templates/apps/web/public/logomark-dark.svg +0 -4
- package/templates/apps/web/public/logomark-light.svg +0 -4
- package/templates/apps/web/source.config.ts +0 -78
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Tenant-scoped data-access boundary (the multi-tenant module's scope layer —
|
|
4
|
-
* FEAT-042, design decision (a)).
|
|
5
|
-
*
|
|
6
|
-
* Every domain-content query (agent_sessions, conversation_events,
|
|
7
|
-
* session_read_receipts, artifacts, artifact_versions) goes through
|
|
8
|
-
* `tenantDb(tenant)`, which injects the resolved `TenantContext.workspaceId`
|
|
9
|
-
* into EVERY where/data clause. There is deliberately no method that accepts
|
|
10
|
-
* a caller-supplied workspaceId, and lookups by id are `findFirst({ id,
|
|
11
|
-
* workspaceId })` — never `findUnique({ id })` — so a foreign-tenant row is
|
|
12
|
-
* indistinguishable from a missing one.
|
|
13
|
-
*
|
|
14
|
-
* Raw `prisma.<domainModel>` access outside this file and
|
|
15
|
-
* `../services/tenant-context.ts` is forbidden by the tenant-scope
|
|
16
|
-
* `forbidden_pattern` in architecture.yaml (CI: `pnpm architecture`).
|
|
17
|
-
*/
|
|
18
|
-
import type {
|
|
19
|
-
AgentSession,
|
|
20
|
-
Artifact,
|
|
21
|
-
ConversationEvent,
|
|
22
|
-
Prisma,
|
|
23
|
-
SessionReadReceipt,
|
|
24
|
-
WorkspaceSEO,
|
|
25
|
-
WorkspaceSiteIdentity,
|
|
26
|
-
WorkspaceTheme,
|
|
27
|
-
} from "@working-theory/database";
|
|
28
|
-
import type { Id } from "@working-theory/ids";
|
|
29
|
-
|
|
30
|
-
import type { TenantContext } from "../services/tenant-context.js";
|
|
31
|
-
|
|
32
|
-
import { prisma } from "./prisma.js";
|
|
33
|
-
|
|
34
|
-
/** Explicit surface (declaration-emit portability + a reviewable contract). */
|
|
35
|
-
export interface TenantDb {
|
|
36
|
-
agentSession: {
|
|
37
|
-
list(limit: number): Promise<AgentSession[]>;
|
|
38
|
-
byId(id: Id<"agentSession">): Promise<AgentSession | null>;
|
|
39
|
-
create(data: { title?: string | null }): Promise<AgentSession>;
|
|
40
|
-
updateDraft(
|
|
41
|
-
id: Id<"agentSession">,
|
|
42
|
-
draftMessage: string | null,
|
|
43
|
-
): Promise<{ count: number }>;
|
|
44
|
-
};
|
|
45
|
-
conversationEvent: {
|
|
46
|
-
listSince(
|
|
47
|
-
sessionId: Id<"agentSession">,
|
|
48
|
-
sinceSeq: number,
|
|
49
|
-
limit: number,
|
|
50
|
-
): Promise<ConversationEvent[]>;
|
|
51
|
-
};
|
|
52
|
-
readReceipt: {
|
|
53
|
-
listForUser(): Promise<Array<Pick<SessionReadReceipt, "sessionId" | "lastReadSeq">>>;
|
|
54
|
-
upsert(
|
|
55
|
-
sessionId: Id<"agentSession">,
|
|
56
|
-
lastReadSeq: number,
|
|
57
|
-
): Promise<SessionReadReceipt>;
|
|
58
|
-
};
|
|
59
|
-
artifact: {
|
|
60
|
-
listBySession(sessionId: Id<"agentSession">): Promise<Artifact[]>;
|
|
61
|
-
byId(id: Id<"artifact">): Promise<Artifact | null>;
|
|
62
|
-
};
|
|
63
|
-
artifactVersion: {
|
|
64
|
-
listByArtifact(
|
|
65
|
-
artifactId: Id<"artifact">,
|
|
66
|
-
): Promise<
|
|
67
|
-
Array<{ id: Id<"artifactVersion">; savedAt: Date; isCurrent: boolean }>
|
|
68
|
-
>;
|
|
69
|
-
};
|
|
70
|
-
// Per-workspace site config (#3634). Each model is `workspaceId @unique`, so
|
|
71
|
-
// the unique key IS the tenant scope — `findUnique`/`upsert` on
|
|
72
|
-
// `{ workspaceId }` never accepts a caller-supplied scope.
|
|
73
|
-
theme: {
|
|
74
|
-
get(): Promise<WorkspaceTheme | null>;
|
|
75
|
-
upsert(data: unknown): Promise<WorkspaceTheme>;
|
|
76
|
-
};
|
|
77
|
-
siteIdentity: {
|
|
78
|
-
get(): Promise<WorkspaceSiteIdentity | null>;
|
|
79
|
-
upsert(data: unknown): Promise<WorkspaceSiteIdentity>;
|
|
80
|
-
};
|
|
81
|
-
seo: {
|
|
82
|
-
get(): Promise<WorkspaceSEO | null>;
|
|
83
|
-
upsert(data: unknown): Promise<WorkspaceSEO>;
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function tenantDb(tenant: TenantContext): TenantDb {
|
|
88
|
-
const { workspaceId, userId } = tenant;
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
agentSession: {
|
|
92
|
-
list(limit: number) {
|
|
93
|
-
return prisma.agentSession.findMany({
|
|
94
|
-
where: { workspaceId },
|
|
95
|
-
orderBy: [
|
|
96
|
-
{ lastMessageAt: { sort: "desc", nulls: "last" } },
|
|
97
|
-
{ createdAt: "desc" },
|
|
98
|
-
],
|
|
99
|
-
take: limit,
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
byId(id: string) {
|
|
103
|
-
return prisma.agentSession.findFirst({
|
|
104
|
-
where: { id, workspaceId },
|
|
105
|
-
});
|
|
106
|
-
},
|
|
107
|
-
create(data: { title?: string | null; agentId?: string }) {
|
|
108
|
-
return prisma.agentSession.create({
|
|
109
|
-
data: { title: data.title ?? null, workspaceId },
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
updateDraft(id: string, draftMessage: string | null) {
|
|
113
|
-
return prisma.agentSession.updateMany({
|
|
114
|
-
where: { id, workspaceId },
|
|
115
|
-
data: { draftMessage },
|
|
116
|
-
});
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
conversationEvent: {
|
|
121
|
-
listSince(sessionId: string, sinceSeq: number, limit: number) {
|
|
122
|
-
return prisma.conversationEvent.findMany({
|
|
123
|
-
where: { sessionId, workspaceId, seq: { gt: sinceSeq } },
|
|
124
|
-
orderBy: { seq: "asc" },
|
|
125
|
-
take: limit,
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
readReceipt: {
|
|
131
|
-
/** Read positions for the CALLING user across this workspace's sessions. */
|
|
132
|
-
listForUser() {
|
|
133
|
-
return prisma.sessionReadReceipt.findMany({
|
|
134
|
-
where: { workspaceId, userId },
|
|
135
|
-
select: { sessionId: true, lastReadSeq: true },
|
|
136
|
-
});
|
|
137
|
-
},
|
|
138
|
-
upsert(sessionId: string, lastReadSeq: number) {
|
|
139
|
-
return prisma.sessionReadReceipt.upsert({
|
|
140
|
-
where: { sessionId_userId: { sessionId, userId } },
|
|
141
|
-
create: { sessionId, workspaceId, userId, lastReadSeq },
|
|
142
|
-
update: { lastReadSeq },
|
|
143
|
-
});
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
artifact: {
|
|
148
|
-
listBySession(sessionId: string) {
|
|
149
|
-
return prisma.artifact.findMany({
|
|
150
|
-
where: { sessionId, workspaceId },
|
|
151
|
-
orderBy: { createdAt: "asc" },
|
|
152
|
-
});
|
|
153
|
-
},
|
|
154
|
-
byId(id: string) {
|
|
155
|
-
return prisma.artifact.findFirst({ where: { id, workspaceId } });
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
artifactVersion: {
|
|
160
|
-
listByArtifact(artifactId: string) {
|
|
161
|
-
// Brand assertion at the Prisma read edge (the DB CHECK constraints
|
|
162
|
-
// guarantee the id shape).
|
|
163
|
-
return prisma.artifactVersion.findMany({
|
|
164
|
-
where: { artifactId, workspaceId },
|
|
165
|
-
orderBy: { savedAt: "desc" },
|
|
166
|
-
select: {
|
|
167
|
-
id: true,
|
|
168
|
-
savedAt: true,
|
|
169
|
-
isCurrent: true,
|
|
170
|
-
},
|
|
171
|
-
}) as Promise<
|
|
172
|
-
Array<{ id: Id<"artifactVersion">; savedAt: Date; isCurrent: boolean }>
|
|
173
|
-
>;
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
// Site config — scoped by the `@unique` workspaceId (the unique key IS the
|
|
178
|
-
// scope). The validated blob is stored in the `data` Json column; validation
|
|
179
|
-
// happens at the route boundary against the shared @working-theory/site-config
|
|
180
|
-
// schema, so the DB layer stores an already-validated object.
|
|
181
|
-
theme: {
|
|
182
|
-
get() {
|
|
183
|
-
return prisma.workspaceTheme.findUnique({ where: { workspaceId } });
|
|
184
|
-
},
|
|
185
|
-
upsert(data: unknown) {
|
|
186
|
-
const json = data as Prisma.InputJsonValue;
|
|
187
|
-
return prisma.workspaceTheme.upsert({
|
|
188
|
-
where: { workspaceId },
|
|
189
|
-
create: { workspaceId, data: json },
|
|
190
|
-
update: { data: json },
|
|
191
|
-
});
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
siteIdentity: {
|
|
196
|
-
get() {
|
|
197
|
-
return prisma.workspaceSiteIdentity.findUnique({
|
|
198
|
-
where: { workspaceId },
|
|
199
|
-
});
|
|
200
|
-
},
|
|
201
|
-
upsert(data: unknown) {
|
|
202
|
-
const json = data as Prisma.InputJsonValue;
|
|
203
|
-
return prisma.workspaceSiteIdentity.upsert({
|
|
204
|
-
where: { workspaceId },
|
|
205
|
-
create: { workspaceId, data: json },
|
|
206
|
-
update: { data: json },
|
|
207
|
-
});
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
seo: {
|
|
212
|
-
get() {
|
|
213
|
-
return prisma.workspaceSEO.findUnique({ where: { workspaceId } });
|
|
214
|
-
},
|
|
215
|
-
upsert(data: unknown) {
|
|
216
|
-
const json = data as Prisma.InputJsonValue;
|
|
217
|
-
return prisma.workspaceSEO.upsert({
|
|
218
|
-
where: { workspaceId },
|
|
219
|
-
create: { workspaceId, data: json },
|
|
220
|
-
update: { data: json },
|
|
221
|
-
});
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Short-lived stream tokens (BACKEND-REQUIREMENTS R-T3): the push channel is
|
|
4
|
-
* authenticated by a per-session token minted over REST, not the ambient
|
|
5
|
-
* cookie. Compact HMAC-SHA256 JWT via node:crypto — no new dependency; the
|
|
6
|
-
* Phase-6 realtime server verifies with `verifyWsToken`.
|
|
7
|
-
*
|
|
8
|
-
* Secret resolution mirrors apps/web's Better Auth pattern: AILK_WS_TOKEN_SECRET
|
|
9
|
-
* wins, BETTER_AUTH_SECRET is the shared fallback, and a dev-only default
|
|
10
|
-
* keeps the OSS zero-config boot working. Production deployments MUST set one
|
|
11
|
-
* of the two env vars.
|
|
12
|
-
*/
|
|
13
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
14
|
-
|
|
15
|
-
import { getClock } from "@working-theory/common";
|
|
16
|
-
import type { Id } from "@working-theory/ids";
|
|
17
|
-
|
|
18
|
-
export const WS_TOKEN_TTL_SECONDS = 60;
|
|
19
|
-
|
|
20
|
-
export interface WsTokenClaims {
|
|
21
|
-
/** Caller user id. */
|
|
22
|
-
sub: Id<"user">;
|
|
23
|
-
/** Agent-session id the stream is scoped to. */
|
|
24
|
-
sid: Id<"agentSession">;
|
|
25
|
-
/** Workspace id the session belongs to (tenant scope). */
|
|
26
|
-
wid: Id<"workspace">;
|
|
27
|
-
iat: number;
|
|
28
|
-
exp: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function resolveSecret(): string {
|
|
32
|
-
const secret =
|
|
33
|
-
process.env.AILK_WS_TOKEN_SECRET ?? process.env.BETTER_AUTH_SECRET;
|
|
34
|
-
if (secret) return secret;
|
|
35
|
-
// Fail CLOSED in production (security review M2): a token-minting path
|
|
36
|
-
// must never run on a guessable default secret.
|
|
37
|
-
if (process.env.NODE_ENV === "production") {
|
|
38
|
-
throw new Error(
|
|
39
|
-
"ws-token: set AILK_WS_TOKEN_SECRET or BETTER_AUTH_SECRET in production",
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
return "ailk-dev-secret-do-not-use-in-production";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function b64url(input: Buffer | string): string {
|
|
46
|
-
return Buffer.from(input).toString("base64url");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function sign(payload: string): string {
|
|
50
|
-
return createHmac("sha256", resolveSecret()).update(payload).digest("base64url");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Mint a short-lived stream token for (user, session, workspace). */
|
|
54
|
-
export function mintWsToken(params: {
|
|
55
|
-
userId: Id<"user">;
|
|
56
|
-
sessionId: Id<"agentSession">;
|
|
57
|
-
workspaceId: Id<"workspace">;
|
|
58
|
-
}): { token: string; expiresAt: string } {
|
|
59
|
-
const nowSec = Math.floor(getClock().now() / 1000);
|
|
60
|
-
const claims: WsTokenClaims = {
|
|
61
|
-
sub: params.userId,
|
|
62
|
-
sid: params.sessionId,
|
|
63
|
-
wid: params.workspaceId,
|
|
64
|
-
iat: nowSec,
|
|
65
|
-
exp: nowSec + WS_TOKEN_TTL_SECONDS,
|
|
66
|
-
};
|
|
67
|
-
const head = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
68
|
-
const body = b64url(JSON.stringify(claims));
|
|
69
|
-
const token = `${head}.${body}.${sign(`${head}.${body}`)}`;
|
|
70
|
-
return { token, expiresAt: new Date(claims.exp * 1000).toISOString() };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Verify a stream token; returns claims or null (invalid / expired). */
|
|
74
|
-
export function verifyWsToken(token: string): WsTokenClaims | null {
|
|
75
|
-
const [head, body, sig] = token.split(".");
|
|
76
|
-
if (!head || !body || !sig) return null;
|
|
77
|
-
const expected = sign(`${head}.${body}`);
|
|
78
|
-
const a = Buffer.from(sig);
|
|
79
|
-
const b = Buffer.from(expected);
|
|
80
|
-
if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
|
|
81
|
-
try {
|
|
82
|
-
const claims = JSON.parse(
|
|
83
|
-
Buffer.from(body, "base64url").toString("utf-8"),
|
|
84
|
-
) as WsTokenClaims;
|
|
85
|
-
if (typeof claims.exp !== "number") return null;
|
|
86
|
-
if (claims.exp * 1000 <= getClock().now()) return null;
|
|
87
|
-
return claims;
|
|
88
|
-
} catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// oss: false
|
|
2
|
-
/**
|
|
3
|
-
* Tenant guard preHandler (multi-tenant module — FEAT-042).
|
|
4
|
-
*
|
|
5
|
-
* Runs AFTER the dual-mode auth preHandler inside the workspace route scope.
|
|
6
|
-
* Default-deny: resolves the caller's active tenant via
|
|
7
|
-
* `services/tenant-context.ts#resolveActiveTenant` and rejects everything
|
|
8
|
-
* that is not an explicit grant:
|
|
9
|
-
*
|
|
10
|
-
* - bearer-authenticated requests (no workspace binding yet — #3591) → 403
|
|
11
|
-
* - cookie sessions with no / stale active workspace → 403
|
|
12
|
-
* - revoked membership → 403
|
|
13
|
-
* - URL workspaceId ≠ the session's active workspace → 403
|
|
14
|
-
* (compared BEFORE any lookup of the target — no tenant enumeration)
|
|
15
|
-
*
|
|
16
|
-
* On grant it decorates `request.tenant` (a `TenantContext`); route handlers
|
|
17
|
-
* pass that to `tenantDb(tenant)` so every domain query is workspace-scoped.
|
|
18
|
-
* Routes whose target is a session/artifact id (no workspaceId param) rely on
|
|
19
|
-
* the scope layer's `findFirst({ id, workspaceId })` — a foreign target is a
|
|
20
|
-
* 404, indistinguishable from a missing one.
|
|
21
|
-
*/
|
|
22
|
-
import type {
|
|
23
|
-
FastifyPluginCallback,
|
|
24
|
-
FastifyReply,
|
|
25
|
-
FastifyRequest,
|
|
26
|
-
} from "fastify";
|
|
27
|
-
|
|
28
|
-
import {
|
|
29
|
-
resolveActiveTenant,
|
|
30
|
-
type TenantContext,
|
|
31
|
-
} from "../services/tenant-context.js";
|
|
32
|
-
|
|
33
|
-
declare module "fastify" {
|
|
34
|
-
interface FastifyRequest {
|
|
35
|
-
tenant?: TenantContext;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const FORBIDDEN = {
|
|
40
|
-
ok: false,
|
|
41
|
-
error: { code: "forbidden", message: "Forbidden" },
|
|
42
|
-
} as const;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Resolve + attach the active tenant, or reply 403. Registered as a hook in
|
|
46
|
-
* the workspace route scope (after auth), so no tenant-scoped handler runs
|
|
47
|
-
* without a granted TenantContext.
|
|
48
|
-
*/
|
|
49
|
-
export async function tenantPreHandler(
|
|
50
|
-
request: FastifyRequest,
|
|
51
|
-
reply: FastifyReply,
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
const userId = request.userId;
|
|
54
|
-
if (!userId) {
|
|
55
|
-
reply.code(401).send({
|
|
56
|
-
ok: false,
|
|
57
|
-
error: { code: "unauthorized", message: "Authentication required" },
|
|
58
|
-
});
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const resolved = await resolveActiveTenant({
|
|
63
|
-
userId,
|
|
64
|
-
sessionTenant: request.sessionTenant,
|
|
65
|
-
});
|
|
66
|
-
if (!resolved.ok) {
|
|
67
|
-
reply.code(403).send(FORBIDDEN);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// URL-scoped routes: the target workspace MUST be the session's active
|
|
72
|
-
// workspace. Compared before any target lookup — a foreign (or nonexistent)
|
|
73
|
-
// workspaceId gets the same 403.
|
|
74
|
-
const params = request.params as { workspaceId?: string } | undefined;
|
|
75
|
-
if (params?.workspaceId && params.workspaceId !== resolved.value.workspaceId) {
|
|
76
|
-
reply.code(403).send(FORBIDDEN);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
request.tenant = resolved.value;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Plugin form, mirroring `authPreHandlerPlugin`: skip-override so the hook
|
|
85
|
-
* propagates to sibling route plugins registered in the same scope.
|
|
86
|
-
*/
|
|
87
|
-
export const tenantPreHandlerPlugin: FastifyPluginCallback = (
|
|
88
|
-
fastify,
|
|
89
|
-
_opts,
|
|
90
|
-
done,
|
|
91
|
-
) => {
|
|
92
|
-
fastify.decorateRequest("tenant", undefined);
|
|
93
|
-
fastify.addHook("preHandler", tenantPreHandler);
|
|
94
|
-
done();
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
(tenantPreHandlerPlugin as FastifyPluginCallback & { [key: symbol]: boolean })[
|
|
98
|
-
Symbol.for("skip-override")
|
|
99
|
-
] = true;
|