create-ailk 0.18.0 → 0.19.1
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/LICENSE +6 -5
- package/NOTICE +2 -0
- package/dist/add-module.js +11 -3
- package/dist/category-profile.d.ts +10 -0
- package/dist/category-profile.js +21 -0
- package/dist/derive-architecture.d.ts +5 -1
- package/dist/derive-architecture.js +8 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/license-key-files.d.ts +6 -3
- package/dist/lib/license-key-files.js +8 -2
- package/dist/lib/scaffold-render-sweep.d.ts +32 -0
- package/dist/lib/scaffold-render-sweep.js +76 -3
- package/dist/license-set.d.ts +1 -1
- package/dist/license-set.js +13 -8
- package/dist/module-architecture.js +128 -3
- package/dist/programmatic.js +11 -1
- package/dist/site-roster.js +21 -4
- package/dist/spoke-checkout.d.ts +11 -3
- package/dist/spoke-checkout.js +62 -12
- package/dist/starter-grid.d.ts +1 -1
- package/dist/surfaces.d.ts +14 -0
- package/dist/surfaces.js +170 -1
- package/dist/template-manifest.d.ts +4 -3
- package/dist/topology.d.ts +7 -2
- package/dist/topology.js +88 -3
- package/package.json +5 -4
- package/templates/.env.example +3 -3
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/apps/api/.env.example +17 -1
- package/templates/apps/api/CLAUDE.md +31 -7
- package/templates/apps/api/LICENSE +6 -5
- package/templates/apps/api/NOTICE +2 -0
- package/templates/apps/api/jest.config.cjs +4 -29
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +19 -0
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +132 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +17 -0
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +237 -0
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +334 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +38 -0
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +217 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +7 -0
- package/templates/apps/api/src/lib/consent-store.ts +335 -0
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +5 -4
- package/templates/apps/api/src/lib/stripe.ts +176 -3
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +272 -39
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +51 -0
- package/templates/apps/api/src/middleware/auth.ts +214 -46
- package/templates/apps/api/src/middleware/rate-limit.ts +5 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +167 -0
- package/templates/apps/api/src/routes/checkout/session.ts +3 -3
- package/templates/apps/api/src/routes/checkout/sessions.ts +6 -4
- package/templates/apps/api/src/routes/consent/cookie.ts +90 -0
- package/templates/apps/api/src/routes/consent/index.ts +18 -0
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +127 -0
- package/templates/apps/api/src/routes/leads/index.ts +19 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +5 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +17 -4
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +2 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +159 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +158 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +2 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +17 -0
- package/templates/apps/api/src/server.ts +218 -16
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +503 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +18 -3
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +173 -4
- package/templates/apps/api/src/services/__tests__/person.test.ts +392 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +87 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +57 -17
- package/templates/apps/api/src/services/consent.ts +488 -3
- package/templates/apps/api/src/services/lead-routing.ts +135 -14
- package/templates/apps/api/src/services/person.ts +501 -1
- package/templates/apps/api/src/services/project-listings.ts +7 -2
- package/templates/apps/api/src/services/request-confirm.ts +52 -0
- package/templates/apps/api/src/services/revalidation.ts +11 -1
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/.env.example +10 -0
- package/templates/apps/mcp/CLAUDE.md +9 -4
- package/templates/apps/mcp/LICENSE +6 -5
- package/templates/apps/mcp/NOTICE +2 -0
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +198 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +15 -3
- package/templates/apps/mcp/__tests__/boot.test.ts +78 -1
- package/templates/apps/mcp/__tests__/http-transport.test.ts +291 -2
- package/templates/apps/mcp/__tests__/request_callback.test.ts +69 -7
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +69 -8
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +69 -7
- package/templates/apps/mcp/jest.config.cjs +3 -44
- package/templates/apps/mcp/src/abuse/index.ts +117 -21
- package/templates/apps/mcp/src/boot.ts +5 -23
- package/templates/apps/mcp/src/http-transport.ts +93 -73
- package/templates/apps/mcp/src/index.ts +2 -0
- package/templates/apps/mcp/src/request-context.ts +52 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +9 -1
- package/templates/apps/mcp/src/tools/request_callback.ts +9 -1
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +9 -1
- package/templates/apps/web/LICENSE +6 -5
- package/templates/apps/web/NOTICE +2 -0
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/account-frame-sign-out.test.tsx +175 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +302 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +724 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +346 -41
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +11 -13
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +146 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +232 -29
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +305 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-popups.test.tsx +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +140 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +163 -72
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +451 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/security.test.tsx +409 -0
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +65 -380
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/support.test.tsx +16 -11
- package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +1 -1
- package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +188 -54
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +24 -45
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +253 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +145 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +100 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +179 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +514 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +288 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +123 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +82 -0
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +174 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing-source.ts +147 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/request-quote.tsx +229 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/schedule-service.tsx +276 -0
- package/templates/apps/web/app/[locale]/(authed)/account/components/send-message.tsx +128 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/billing-anchor.tsx +27 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/contact-actions.ts +81 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/deletion-request.tsx +132 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +193 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/profile-form.tsx +104 -0
- package/templates/apps/web/app/[locale]/(authed)/account/general/profile-source.ts +73 -0
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +18 -8
- package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +4 -6
- package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +30 -4
- package/templates/apps/web/app/[locale]/(authed)/account/new-actions.ts +219 -0
- package/templates/apps/web/app/[locale]/(authed)/account/new-menu.tsx +340 -0
- package/templates/apps/web/app/[locale]/(authed)/account/new-popups.tsx +107 -0
- package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +57 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +85 -26
- package/templates/apps/web/app/[locale]/(authed)/account/profile-actions.ts +116 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/page.tsx +132 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/security-panel.tsx +102 -0
- package/templates/apps/web/app/[locale]/(authed)/account/security/security-source.ts +62 -0
- package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +13 -354
- package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +116 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-in-method.ts +37 -0
- package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +9 -10
- package/templates/apps/web/app/[locale]/layout.tsx +23 -4
- package/templates/apps/web/app/api/auth/hub/__tests__/continuation.test.ts +143 -0
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +62 -10
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +22 -7
- package/templates/apps/web/app/api/auth/hub/continuation.ts +73 -0
- package/templates/apps/web/app/api/auth/hub/continue/[ref]/__tests__/route.test.ts +188 -0
- package/templates/apps/web/app/api/auth/hub/continue/[ref]/route.ts +53 -0
- package/templates/apps/web/app/robots.txt/route.ts +10 -4
- package/templates/apps/web/auth.ts +25 -2
- package/templates/apps/web/lib/__tests__/account-config.test.ts +258 -46
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +84 -0
- package/templates/apps/web/lib/account-config.ts +188 -52
- package/templates/apps/web/lib/auth-email.ts +45 -10
- package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +143 -0
- package/templates/apps/web/lib/hub-session/index.ts +1 -0
- package/templates/apps/web/lib/hub-session/reseal.ts +90 -0
- package/templates/apps/web/lib/hub-session/seal.ts +13 -9
- package/templates/apps/web/lib/topology.generated.ts +33 -0
- package/templates/apps/web/messages/en.json +194 -33
- package/templates/database/CHANGELOG.md +37 -0
- package/templates/database/LICENSE +6 -5
- package/templates/database/NOTICE +2 -0
- package/templates/database/README.md +1 -1
- package/templates/database/__tests__/consent-ledger.db.test.ts +345 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +6 -2
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +182 -0
- package/templates/database/__tests__/member-area.db.test.ts +15 -3
- package/templates/database/__tests__/member-contract-migration.test.ts +85 -0
- package/templates/database/__tests__/member-contract.db.test.ts +413 -0
- package/templates/database/__tests__/person-contact.db.test.ts +563 -0
- package/templates/database/__tests__/person.db.test.ts +3 -1
- package/templates/database/__tests__/seed.test.ts +54 -2
- package/templates/database/inbox/schema.prisma +166 -25
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +192 -0
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +20 -0
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +122 -0
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +231 -0
- package/templates/database/package.json +3 -2
- package/templates/database/seed.ts +34 -2
- package/versions.json +36 -36
- package/templates/apps/api/src/__tests__/content-write-production-boot.test.ts +0 -104
- package/templates/apps/api/src/__tests__/cors.test.ts +0 -276
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +0 -279
- package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +0 -122
- package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +0 -167
- package/templates/apps/api/src/__tests__/root-modules.test.ts +0 -175
- package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +0 -117
- package/templates/apps/api/src/__tests__/server.test.ts +0 -591
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +0 -175
- package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +0 -293
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +0 -1620
- package/templates/apps/api/src/routes/project-listings/__tests__/directory.test.ts +0 -528
- package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +0 -198
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +0 -652
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -408
- package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +0 -476
- package/templates/apps/mcp/__tests__/audit.test.ts +0 -346
- package/templates/apps/mcp/__tests__/parity.test.ts +0 -325
- package/templates/apps/mcp/__tests__/workspace-surface.test.ts +0 -364
- package/templates/apps/web/app/[locale]/(authed)/account/settings/color-mode.tsx +0 -68
- package/templates/apps/web/app/[locale]/(authed)/account/settings/deletion-request.tsx +0 -55
- package/templates/apps/web/app/[locale]/(authed)/account/settings/name-form.tsx +0 -118
- package/templates/database/__tests__/build.test.ts +0 -131
|
@@ -55,6 +55,10 @@ import {
|
|
|
55
55
|
import { tokenRoutes } from "./routes/auth/tokens.js";
|
|
56
56
|
import { transactionalEmailRoute } from "./routes/auth/transactional-email.js";
|
|
57
57
|
import { userRoutes } from "./routes/auth/user.js";
|
|
58
|
+
import {
|
|
59
|
+
consentCookieRoute,
|
|
60
|
+
consentUnsubscribeRoute,
|
|
61
|
+
} from "./routes/consent/index.js";
|
|
58
62
|
import { getPageRoute } from "./routes/content/get-page.js";
|
|
59
63
|
import { listPagesRoute } from "./routes/content/list-pages.js";
|
|
60
64
|
import { listTypesRoute } from "./routes/content/list-types.js";
|
|
@@ -76,6 +80,7 @@ import {
|
|
|
76
80
|
registerTestAuthRoute,
|
|
77
81
|
shouldMountTestAuth,
|
|
78
82
|
} from "./routes/test/auth/session.js";
|
|
83
|
+
import { consentLinkSecretConfigured } from "./services/consent.js";
|
|
79
84
|
|
|
80
85
|
/**
|
|
81
86
|
* What a request looks like in a log line — Fastify's own default shape, minus
|
|
@@ -111,12 +116,18 @@ import {
|
|
|
111
116
|
*/
|
|
112
117
|
const SESSION_ID_LOG_SEGMENT = /^cs_[A-Za-z0-9_]+$/;
|
|
113
118
|
const LEAD_ID_LOG_SEGMENT = idRegex("lead");
|
|
119
|
+
// #8438 SEC-8438-10 — a third arm for `person_…` segments. The consents route
|
|
120
|
+
// (and every other `…/people/:personId…` route) would otherwise write a full
|
|
121
|
+
// Person id into the request log line, beside `remoteAddress`.
|
|
122
|
+
const PERSON_ID_LOG_SEGMENT = idRegex("person");
|
|
114
123
|
|
|
115
124
|
function redactSensitiveIdSegments(url: string): string {
|
|
116
125
|
return url
|
|
117
126
|
.split("/")
|
|
118
127
|
.map((segment) =>
|
|
119
|
-
SESSION_ID_LOG_SEGMENT.test(segment) ||
|
|
128
|
+
SESSION_ID_LOG_SEGMENT.test(segment) ||
|
|
129
|
+
LEAD_ID_LOG_SEGMENT.test(segment) ||
|
|
130
|
+
PERSON_ID_LOG_SEGMENT.test(segment)
|
|
120
131
|
? `${segment.slice(0, 8)}…`
|
|
121
132
|
: segment,
|
|
122
133
|
)
|
|
@@ -228,9 +239,35 @@ const WORKSPACES_PEOPLE = "./routes/workspaces/people.js";
|
|
|
228
239
|
const WORKSPACES_PIECES = "./routes/workspaces/pieces.js";
|
|
229
240
|
const WORKSPACES_PLAN = "./routes/workspaces/plan.js";
|
|
230
241
|
const WORKSPACES_MESSAGES_OUT = "./routes/workspaces/messages-out.js";
|
|
242
|
+
// #8171 D8 — the workspace's Requests read (the inbound member rows), the
|
|
243
|
+
// SAME scope.
|
|
244
|
+
const WORKSPACES_REQUESTS = "./routes/workspaces/requests.js";
|
|
231
245
|
// #7877 D11 — the member reads: auth only, under the SAME module predicate.
|
|
232
246
|
const MEMBER_PIECES = "./routes/member/pieces.js";
|
|
233
247
|
const MEMBER_MESSAGES = "./routes/member/messages.js";
|
|
248
|
+
// #8169 D9 — the Phase 1 member reads. Five under the SAME predicate; the two
|
|
249
|
+
// that reach services/scheduling/ under scheduling && (workspace ||
|
|
250
|
+
// multi-tenant), because only the scheduling module delivers that directory.
|
|
251
|
+
const MEMBER_PURCHASES = "./routes/member/purchases.js";
|
|
252
|
+
const MEMBER_APPOINTMENTS = "./routes/member/appointments.js";
|
|
253
|
+
const MEMBER_ARTIFACTS = "./routes/member/artifacts.js";
|
|
254
|
+
const MEMBER_REQUEST_FORM = "./routes/member/request-form.js";
|
|
255
|
+
const MEMBER_BILLING = "./routes/member/billing.js";
|
|
256
|
+
const MEMBER_MEETING_TYPES = "./routes/member/meeting-types.js";
|
|
257
|
+
const MEMBER_AVAILABILITY = "./routes/member/availability.js";
|
|
258
|
+
// #8170 D11 — the member writes. Four under the tenancy predicate (the
|
|
259
|
+
// message POST rides MEMBER_MESSAGES); the booking, which reaches
|
|
260
|
+
// services/scheduling/, under scheduling && (workspace || multi-tenant).
|
|
261
|
+
const MEMBER_REQUESTS = "./routes/member/requests.js";
|
|
262
|
+
const MEMBER_ARTIFACT_REVIEW = "./routes/member/artifact-review.js";
|
|
263
|
+
const MEMBER_PROFILE = "./routes/member/profile.js";
|
|
264
|
+
const MEMBER_DELETION_REQUEST = "./routes/member/deletion-request.js";
|
|
265
|
+
const MEMBER_BOOKINGS = "./routes/member/bookings.js";
|
|
266
|
+
// #8438 D6 — the member's own consents, under the tenancy predicate beside the
|
|
267
|
+
// other member routes. D8 — the Admin owner's consents to Working Theory,
|
|
268
|
+
// under multi-tenant alone (the module that contributes the Admin app).
|
|
269
|
+
const MEMBER_CONSENTS = "./routes/member/consents.js";
|
|
270
|
+
const CONSENT_PLATFORM = "./routes/consent/platform.js";
|
|
234
271
|
// #3591 PR-B — the agent seat's management routes and an agent's own
|
|
235
272
|
// context, siblings of WORKSPACES_MEMBERS in the same workspace/multi-tenant
|
|
236
273
|
// scope.
|
|
@@ -316,6 +353,11 @@ const SLICE_PLUGINS = {
|
|
|
316
353
|
specifier: WORKSPACES_PIECES,
|
|
317
354
|
load: () => import(WORKSPACES_PIECES),
|
|
318
355
|
},
|
|
356
|
+
// #8171 D8 — the Requests read, the SAME scope.
|
|
357
|
+
workspaceRequestRoutes: {
|
|
358
|
+
specifier: WORKSPACES_REQUESTS,
|
|
359
|
+
load: () => import(WORKSPACES_REQUESTS),
|
|
360
|
+
},
|
|
319
361
|
workspacePlanRoutes: {
|
|
320
362
|
specifier: WORKSPACES_PLAN,
|
|
321
363
|
load: () => import(WORKSPACES_PLAN),
|
|
@@ -333,6 +375,68 @@ const SLICE_PLUGINS = {
|
|
|
333
375
|
specifier: MEMBER_MESSAGES,
|
|
334
376
|
load: () => import(MEMBER_MESSAGES),
|
|
335
377
|
},
|
|
378
|
+
// #8169 D9 — the Phase 1 member reads, in authScope beside the two above.
|
|
379
|
+
memberPurchaseRoutes: {
|
|
380
|
+
specifier: MEMBER_PURCHASES,
|
|
381
|
+
load: () => import(MEMBER_PURCHASES),
|
|
382
|
+
},
|
|
383
|
+
memberAppointmentRoutes: {
|
|
384
|
+
specifier: MEMBER_APPOINTMENTS,
|
|
385
|
+
load: () => import(MEMBER_APPOINTMENTS),
|
|
386
|
+
},
|
|
387
|
+
memberArtifactRoutes: {
|
|
388
|
+
specifier: MEMBER_ARTIFACTS,
|
|
389
|
+
load: () => import(MEMBER_ARTIFACTS),
|
|
390
|
+
},
|
|
391
|
+
memberRequestFormRoutes: {
|
|
392
|
+
specifier: MEMBER_REQUEST_FORM,
|
|
393
|
+
load: () => import(MEMBER_REQUEST_FORM),
|
|
394
|
+
},
|
|
395
|
+
memberBillingRoutes: {
|
|
396
|
+
specifier: MEMBER_BILLING,
|
|
397
|
+
load: () => import(MEMBER_BILLING),
|
|
398
|
+
},
|
|
399
|
+
// The two that reach services/scheduling/ (scheduling-owned, D9).
|
|
400
|
+
memberMeetingTypeRoutes: {
|
|
401
|
+
specifier: MEMBER_MEETING_TYPES,
|
|
402
|
+
load: () => import(MEMBER_MEETING_TYPES),
|
|
403
|
+
},
|
|
404
|
+
memberAvailabilityRoutes: {
|
|
405
|
+
specifier: MEMBER_AVAILABILITY,
|
|
406
|
+
load: () => import(MEMBER_AVAILABILITY),
|
|
407
|
+
},
|
|
408
|
+
// #8170 D11 — the member writes, in authScope beside the reads.
|
|
409
|
+
memberRequestRoutes: {
|
|
410
|
+
specifier: MEMBER_REQUESTS,
|
|
411
|
+
load: () => import(MEMBER_REQUESTS),
|
|
412
|
+
},
|
|
413
|
+
memberArtifactReviewRoutes: {
|
|
414
|
+
specifier: MEMBER_ARTIFACT_REVIEW,
|
|
415
|
+
load: () => import(MEMBER_ARTIFACT_REVIEW),
|
|
416
|
+
},
|
|
417
|
+
memberProfileRoutes: {
|
|
418
|
+
specifier: MEMBER_PROFILE,
|
|
419
|
+
load: () => import(MEMBER_PROFILE),
|
|
420
|
+
},
|
|
421
|
+
memberDeletionRequestRoutes: {
|
|
422
|
+
specifier: MEMBER_DELETION_REQUEST,
|
|
423
|
+
load: () => import(MEMBER_DELETION_REQUEST),
|
|
424
|
+
},
|
|
425
|
+
// #8438 D6 — the member's own consents, the SAME scope as the writes above.
|
|
426
|
+
memberConsentRoutes: {
|
|
427
|
+
specifier: MEMBER_CONSENTS,
|
|
428
|
+
load: () => import(MEMBER_CONSENTS),
|
|
429
|
+
},
|
|
430
|
+
// #8438 D8 — the owner's consents to Working Theory (multi-tenant only).
|
|
431
|
+
consentPlatformRoutes: {
|
|
432
|
+
specifier: CONSENT_PLATFORM,
|
|
433
|
+
load: () => import(CONSENT_PLATFORM),
|
|
434
|
+
},
|
|
435
|
+
// The one that reaches services/scheduling/ (scheduling-owned, D11).
|
|
436
|
+
memberBookingRoutes: {
|
|
437
|
+
specifier: MEMBER_BOOKINGS,
|
|
438
|
+
load: () => import(MEMBER_BOOKINGS),
|
|
439
|
+
},
|
|
336
440
|
// #3591 PR-B — the agent seat, the SAME scope.
|
|
337
441
|
workspaceAgentRoutes: {
|
|
338
442
|
specifier: WORKSPACES_AGENTS,
|
|
@@ -565,24 +669,29 @@ async function tenantPreHandlerForBuild(): Promise<FastifyPluginCallback> {
|
|
|
565
669
|
}
|
|
566
670
|
|
|
567
671
|
/**
|
|
568
|
-
* The commerce boot-time
|
|
672
|
+
* The commerce boot-time key admission — the one other slice entry that is
|
|
569
673
|
* not a route plugin, mirroring `tenantPreHandlerSlicePlugin()` above (D3).
|
|
570
|
-
* `lib/stripe.ts` is commerce-owned (D5) and its `
|
|
674
|
+
* `lib/stripe.ts` is commerce-owned (D5) and its `admitApiStripeKey` is the
|
|
571
675
|
* sole `apps/api` reach into the Stripe SDK wrapper package, so it can no
|
|
572
676
|
* longer be a static import (spec D1) once the module is stripped. The
|
|
573
677
|
* widened `const specifier: string` annotation is what defeats `tsc`
|
|
574
678
|
* resolution, exactly as it does above.
|
|
679
|
+
*
|
|
680
|
+
* #8450 D4: called with NO argument, so the admission covers the same
|
|
681
|
+
* `process.env` key `getStripeClient()` later reads. `STRIPE_MODE` unset is
|
|
682
|
+
* today's sandbox-only check; only a restricted live key beside
|
|
683
|
+
* `STRIPE_MODE=live` on a production build comes back `"live"`.
|
|
575
684
|
*/
|
|
576
|
-
async function
|
|
685
|
+
async function admitCommerceStripeKey(): Promise<"test" | "live"> {
|
|
577
686
|
const specifier: string = "./lib/stripe.js";
|
|
578
687
|
const mod = (await loadSlice(specifier, () => import(specifier))) as {
|
|
579
|
-
|
|
688
|
+
admitApiStripeKey?: () => "test" | "live";
|
|
580
689
|
};
|
|
581
|
-
const
|
|
582
|
-
if (!
|
|
583
|
-
throw new Error("slice export
|
|
690
|
+
const admitApiStripeKeyFn = mod.admitApiStripeKey;
|
|
691
|
+
if (!admitApiStripeKeyFn) {
|
|
692
|
+
throw new Error("slice export admitApiStripeKey missing from " + specifier);
|
|
584
693
|
}
|
|
585
|
-
|
|
694
|
+
return admitApiStripeKeyFn();
|
|
586
695
|
}
|
|
587
696
|
|
|
588
697
|
/**
|
|
@@ -598,6 +707,18 @@ async function assertCommerceSandboxKey(key: string): Promise<void> {
|
|
|
598
707
|
* carries a localhost default.
|
|
599
708
|
*/
|
|
600
709
|
function warnOnUnsetProductionProtections(): void {
|
|
710
|
+
// #8438 D9 — AILK_CONSENT_LINK_SECRET is optional, but a value shorter than
|
|
711
|
+
// 32 bytes is a misconfiguration worth an error at boot: it is treated as
|
|
712
|
+
// unset, so no unsubscribe link can be minted or honored. Never the value.
|
|
713
|
+
if (
|
|
714
|
+
process.env["AILK_CONSENT_LINK_SECRET"] !== undefined &&
|
|
715
|
+
process.env["AILK_CONSENT_LINK_SECRET"] !== "" &&
|
|
716
|
+
!consentLinkSecretConfigured()
|
|
717
|
+
) {
|
|
718
|
+
logger.error(
|
|
719
|
+
"[startup] AILK_CONSENT_LINK_SECRET is set but shorter than 32 bytes — treated as unset; no unsubscribe link can be minted or honored",
|
|
720
|
+
);
|
|
721
|
+
}
|
|
601
722
|
if (config.NODE_ENV !== "production") return;
|
|
602
723
|
if (!process.env["FRONTEND_URL"]) {
|
|
603
724
|
logger.warn(
|
|
@@ -635,14 +756,17 @@ export async function build() {
|
|
|
635
756
|
// site renewed. Never throws; without DATABASE_URL the file store stays.
|
|
636
757
|
await registerLicenseKeyStore();
|
|
637
758
|
|
|
638
|
-
// Runtime
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
// since D-64 (#5796): the
|
|
642
|
-
// tree now, so the key's
|
|
643
|
-
// question. A build with no
|
|
759
|
+
// Runtime key admission (#8450 D4): refuse, before any webhook or checkout
|
|
760
|
+
// call can exercise it, every live Stripe key this process cannot vouch
|
|
761
|
+
// for — all of them unless STRIPE_MODE=live sits beside a restricted live
|
|
762
|
+
// key on a production build. UNCONDITIONAL since D-64 (#5796): the
|
|
763
|
+
// commerce primitives and lib/stripe.ts are base tree now, so the key's
|
|
764
|
+
// presence — not a module flag — is the whole question. A build with no
|
|
765
|
+
// STRIPE_SECRET_KEY has nothing to admit.
|
|
766
|
+
let stripeMode: "test" | "live" = "test";
|
|
644
767
|
if (config.STRIPE_SECRET_KEY) {
|
|
645
|
-
await
|
|
768
|
+
stripeMode = await admitCommerceStripeKey();
|
|
769
|
+
logger.info({ stripeMode }, "[startup] stripe key admitted");
|
|
646
770
|
}
|
|
647
771
|
|
|
648
772
|
// trustProxy: true (#4730) — the Vercel serverless handler
|
|
@@ -855,6 +979,10 @@ export async function build() {
|
|
|
855
979
|
await authScope.register(
|
|
856
980
|
await sliceRoutePlugin("workspaceContextRoutes"),
|
|
857
981
|
);
|
|
982
|
+
// #8438 D8 — the Admin owner's consents to Working Theory. Auth only,
|
|
983
|
+
// NOT tenant-guarded (the owner role is checked inside the route), and
|
|
984
|
+
// deliberately NOT in `HUB_SESSION_ROUTES`: a cookie session only.
|
|
985
|
+
await authScope.register(await sliceRoutePlugin("consentPlatformRoutes"));
|
|
858
986
|
}
|
|
859
987
|
|
|
860
988
|
// The leads INBOX read (#5678) — auth only, NOT tenant-guarded, for the
|
|
@@ -927,6 +1055,32 @@ export async function build() {
|
|
|
927
1055
|
if (isModuleEnabled("workspace") || isModuleEnabled("multi-tenant")) {
|
|
928
1056
|
await authScope.register(await sliceRoutePlugin("memberPieceRoutes"));
|
|
929
1057
|
await authScope.register(await sliceRoutePlugin("memberMessageRoutes"));
|
|
1058
|
+
// #8169 D9 — the Phase 1 member reads, under the same predicate.
|
|
1059
|
+
await authScope.register(await sliceRoutePlugin("memberPurchaseRoutes"));
|
|
1060
|
+
await authScope.register(await sliceRoutePlugin("memberAppointmentRoutes"));
|
|
1061
|
+
await authScope.register(await sliceRoutePlugin("memberArtifactRoutes"));
|
|
1062
|
+
await authScope.register(await sliceRoutePlugin("memberRequestFormRoutes"));
|
|
1063
|
+
await authScope.register(await sliceRoutePlugin("memberBillingRoutes"));
|
|
1064
|
+
// #8170 D11 — the member writes, under the same predicate.
|
|
1065
|
+
await authScope.register(await sliceRoutePlugin("memberRequestRoutes"));
|
|
1066
|
+
await authScope.register(await sliceRoutePlugin("memberArtifactReviewRoutes"));
|
|
1067
|
+
await authScope.register(await sliceRoutePlugin("memberProfileRoutes"));
|
|
1068
|
+
await authScope.register(await sliceRoutePlugin("memberDeletionRequestRoutes"));
|
|
1069
|
+
// #8438 D6 — the member's own consents, under the same predicate.
|
|
1070
|
+
await authScope.register(await sliceRoutePlugin("memberConsentRoutes"));
|
|
1071
|
+
}
|
|
1072
|
+
// #8169 D9 — the two member reads that reach services/scheduling/, which
|
|
1073
|
+
// only the scheduling module delivers: a scaffold with a tenancy module
|
|
1074
|
+
// and no scheduling carries neither file, and one with scheduling and no
|
|
1075
|
+
// tenancy module registers neither.
|
|
1076
|
+
if (
|
|
1077
|
+
isModuleEnabled("scheduling") &&
|
|
1078
|
+
(isModuleEnabled("workspace") || isModuleEnabled("multi-tenant"))
|
|
1079
|
+
) {
|
|
1080
|
+
await authScope.register(await sliceRoutePlugin("memberMeetingTypeRoutes"));
|
|
1081
|
+
await authScope.register(await sliceRoutePlugin("memberAvailabilityRoutes"));
|
|
1082
|
+
// #8170 D11 — the member booking, under the same predicate.
|
|
1083
|
+
await authScope.register(await sliceRoutePlugin("memberBookingRoutes"));
|
|
930
1084
|
}
|
|
931
1085
|
|
|
932
1086
|
// Tenant-guarded domain routes (multi-tenant module) — the tenant
|
|
@@ -1104,6 +1258,11 @@ export async function build() {
|
|
|
1104
1258
|
await tenantScope.register(
|
|
1105
1259
|
await sliceRoutePlugin("workspacePieceRoutes"),
|
|
1106
1260
|
);
|
|
1261
|
+
// #8171 D8 — the Requests read: the inbound member rows, behind
|
|
1262
|
+
// `inbox-read`; the content only to a role holding `people-read`.
|
|
1263
|
+
await tenantScope.register(
|
|
1264
|
+
await sliceRoutePlugin("workspaceRequestRoutes"),
|
|
1265
|
+
);
|
|
1107
1266
|
await tenantScope.register(
|
|
1108
1267
|
await sliceRoutePlugin("workspacePlanRoutes"),
|
|
1109
1268
|
);
|
|
@@ -1218,7 +1377,25 @@ export async function build() {
|
|
|
1218
1377
|
// `GET /v1/checkout/sessions/:id` — the GET in its own nested,
|
|
1219
1378
|
// rate-limited sub-scope (D16), so this preHandler applies to both while
|
|
1220
1379
|
// the GET's own limiter applies to it alone.
|
|
1380
|
+
//
|
|
1381
|
+
// #8450 § 9 SEC-14 — a LIVE process gives both routes their own per-IP
|
|
1382
|
+
// `checkout` bucket on top of the global limiter, because live, the public
|
|
1383
|
+
// GET spends one read of the shared live Stripe account per request. It
|
|
1384
|
+
// lowers the per-IP ceiling per instance (the POST's from the global 20 to
|
|
1385
|
+
// 10; the POST and GET now also share one count); like every limiter here
|
|
1386
|
+
// it is in-memory, so it does not bound the total across serverless
|
|
1387
|
+
// instances. `onRequest`, so it counts before the claim gate. Accepted
|
|
1388
|
+
// residual: a server-side caller — apps/web's server actions, an MCP host's
|
|
1389
|
+
// `purchase` tool — puts all its buyers on its own egress IP, so they share
|
|
1390
|
+
// one bucket; `AILK_RATE_LIMIT_CHECKOUT_PER_MINUTE` raises the ceiling. A
|
|
1391
|
+
// test-mode process registers nothing new.
|
|
1221
1392
|
await server.register(async (checkoutScope) => {
|
|
1393
|
+
if (stripeMode === "live") {
|
|
1394
|
+
await checkoutScope.register(rateLimitMiddleware, {
|
|
1395
|
+
scope: "checkout",
|
|
1396
|
+
perIpPerMinute: DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1222
1399
|
checkoutScope.addHook("preHandler", requireCommerceClaim);
|
|
1223
1400
|
await checkoutScope.register(await sliceRoutePlugin("checkoutRoutes"));
|
|
1224
1401
|
});
|
|
@@ -1357,6 +1534,31 @@ export async function build() {
|
|
|
1357
1534
|
// 10/min on form submissions. The global unauth ceiling above applies.
|
|
1358
1535
|
await server.register(leadCaptureStatusRoute);
|
|
1359
1536
|
|
|
1537
|
+
// The consent floor (#8438 D5, D9) — the cookie banner's log and the
|
|
1538
|
+
// unsubscribe landing. Top level, outside every module gate: consent
|
|
1539
|
+
// logging is part of the never-gated privacy floor. Each is in its OWN
|
|
1540
|
+
// rate-limited scope (`consent`, `unsubscribe`) so filling one never blocks
|
|
1541
|
+
// the other; with a forward key set, a spoke's visitors are counted one by
|
|
1542
|
+
// one (both routes are on FORWARDED_VISITOR_ROUTES). Without one, the
|
|
1543
|
+
// server's own IP is the bucket — the posture of the lead routes. Neither
|
|
1544
|
+
// is behind `authScope`: the caller is a visitor, or a person in their mail.
|
|
1545
|
+
await server.register(async (consentScope) => {
|
|
1546
|
+
consentScope.register(rateLimitMiddleware, {
|
|
1547
|
+
perIpPerMinute: DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
1548
|
+
scope: "consent",
|
|
1549
|
+
trustedKeyDigests: visitorForwardKeyDigests,
|
|
1550
|
+
});
|
|
1551
|
+
await consentScope.register(consentCookieRoute);
|
|
1552
|
+
});
|
|
1553
|
+
await server.register(async (unsubscribeScope) => {
|
|
1554
|
+
unsubscribeScope.register(rateLimitMiddleware, {
|
|
1555
|
+
perIpPerMinute: DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
1556
|
+
scope: "unsubscribe",
|
|
1557
|
+
trustedKeyDigests: visitorForwardKeyDigests,
|
|
1558
|
+
});
|
|
1559
|
+
await unsubscribeScope.register(consentUnsubscribeRoute);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1360
1562
|
// Leads routes — rate-limit middleware scoped to this sub-router. Own
|
|
1361
1563
|
// default (10/min, pre-#4730) rather than inheriting the wider
|
|
1362
1564
|
// flows-sized default (#5111, DEF-004 — #4730's shared-default bump was
|