create-ailk 0.23.0 → 0.25.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/README.md +68 -33
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +10 -3
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +38 -4
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +75 -22
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +1 -1
- package/dist/lib/fetch-template-snapshot.js +7 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +34 -12
- package/dist/lib/scaffold-render-sweep.d.ts +33 -0
- package/dist/lib/scaffold-render-sweep.js +45 -0
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +122 -538
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/surfaces.js +10 -2
- package/dist/sync-routes.d.ts +30 -4
- package/dist/sync-routes.js +271 -18
- package/dist/template-record.d.ts +14 -4
- package/dist/template-record.js +23 -5
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +31 -19
- package/dist/update.d.ts +10 -1
- package/dist/update.js +540 -16
- package/package.json +3 -3
- package/release-scope.json +1 -1
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +9 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +15 -3
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +9 -2
- package/templates/apps/api/CLAUDE.md +122 -82
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -4
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1857
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +77 -24
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -12
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +21 -5
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +14 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +28 -7
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +2 -2
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +7 -0
- package/templates/apps/web/lib/auth-email.ts +1 -1
- package/templates/apps/web/lib/magnet-routes.ts +3 -1
- package/templates/apps/web/lib/newsletter.actions.ts +9 -6
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +32 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +757 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +48 -35
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -292
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -254
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -318
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -264
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -1815
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -325
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1300
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -268
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -95
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -129
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -573
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -480
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -465
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -448
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -808
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2026
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -528
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -867
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1176
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -444
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -154
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -313
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -755
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -295
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -868
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -396
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -681
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -308
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -385
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1003
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1188
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2073
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1151
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -65
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -187
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -165
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -228
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -135
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -474
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -225
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -256
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -307
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -223
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -342
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -526
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -142
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -139
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -64
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -137
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -74
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -75
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -59
- package/templates/apps/mcp/src/tools/search_content.ts +0 -67
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -57
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -961
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -240
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file revalidation.ts
|
|
3
|
-
* @description The signed webhook `apps/api` fires at the site's
|
|
4
|
-
* `/api/revalidate` on publish and unpublish (#5851, roadmap D6). One POST of
|
|
5
|
-
* `{ workspaceId, type, slug, locale }` to `CONTENT_REVALIDATE_URL`, signed
|
|
6
|
-
* HMAC-SHA256 with `CONTENT_REVALIDATE_SECRET` over the base string
|
|
7
|
-
* `v0:{timestamp}:{event}:{body}` (#5902) — the Slack shape
|
|
8
|
-
* (`routes/slack/actions.ts:56`) with the event folded in, so neither a replay
|
|
9
|
-
* outside the receiver's 5-minute window nor an in-flight event flip verifies.
|
|
10
|
-
*
|
|
11
|
-
* `type`, `slug` and `locale` are validated against bounded Zod shapes FIRST,
|
|
12
|
-
* before the config check and before any outbound call; an invalid value
|
|
13
|
-
* throws. That is the one way this function rejects, and it is a programmer
|
|
14
|
-
* error rather than a delivery failure: the publish routes already hold these
|
|
15
|
-
* values to the same shapes.
|
|
16
|
-
*
|
|
17
|
-
* Either var absent means "revalidation off": one `warn` and return. Both set
|
|
18
|
-
* means three bounded attempts (500 ms then 2000 ms backoff, 10 s per-attempt
|
|
19
|
-
* timeout — 32.5 s worst case), every attempt logged, and a final failure
|
|
20
|
-
* LOGGED rather than thrown. Delivery never rejects, which is what lets the
|
|
21
|
-
* publish path `await` it and still always complete.
|
|
22
|
-
*
|
|
23
|
-
* PII boundary: log lines carry only
|
|
24
|
-
* `{ event, workspaceId, type, slug, locale, attempt/attempts, status/lastStatus, errName }`.
|
|
25
|
-
* Never the secret, never the signature, never a response body, never page
|
|
26
|
-
* content.
|
|
27
|
-
*
|
|
28
|
-
* The publish and unpublish handlers in `routes/workspaces/content.ts` call
|
|
29
|
-
* this; the receiver is `apps/web/app/api/revalidate/route.ts`. This module
|
|
30
|
-
* fixes the wire contract that receiver implements: method POST; the four-key
|
|
31
|
-
* JSON body; headers `content-type: application/json`,
|
|
32
|
-
* `x-ailk-revalidate-timestamp: <unix seconds>`,
|
|
33
|
-
* `x-ailk-revalidate-event: publish | unpublish` and
|
|
34
|
-
* `x-ailk-revalidate-signature: sha256=<lowercase hex>`. A receiver rejects a
|
|
35
|
-
* timestamp more than 5 minutes from its own clock, then recomputes the HMAC
|
|
36
|
-
* over `v0:{timestamp}:{event}:` + the RAW request body and compares in
|
|
37
|
-
* constant time.
|
|
38
|
-
*/
|
|
39
|
-
import { createHmac } from "node:crypto";
|
|
40
|
-
|
|
41
|
-
import {
|
|
42
|
-
SCHEMA_ORG_TYPES,
|
|
43
|
-
SLUG_PATTERN,
|
|
44
|
-
} from "@working-theory/content-adapters";
|
|
45
|
-
import { createLogger } from "@working-theory/observability";
|
|
46
|
-
import { localeSchema } from "@working-theory/validation";
|
|
47
|
-
import { z } from "zod";
|
|
48
|
-
|
|
49
|
-
import { config } from "../config/index.js";
|
|
50
|
-
import { outboundFetch } from "../lib/outbound-http.js";
|
|
51
|
-
|
|
52
|
-
// The environment is the api's own NODE_ENV, as the observability `logger`
|
|
53
|
-
// singleton reads it. Omitted, createLogger assumes development and asks for a
|
|
54
|
-
// pino-pretty transport a production install does not carry — a throw at load
|
|
55
|
-
// that took the whole api down once content-write was enabled (#7502).
|
|
56
|
-
const log = createLogger({
|
|
57
|
-
service: "revalidation",
|
|
58
|
-
environment: config.NODE_ENV,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const SIGNATURE_HEADER = "x-ailk-revalidate-signature";
|
|
62
|
-
const EVENT_HEADER = "x-ailk-revalidate-event";
|
|
63
|
-
const TIMESTAMP_HEADER = "x-ailk-revalidate-timestamp";
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The bounds on the three fields that become the site's cache tags
|
|
67
|
-
* (`content:{workspace}:{type}:{slug}:{locale}`). `type` and `slug` are exactly
|
|
68
|
-
* the content routes' `addressParamsSchema` shapes and `locale` is the BCP-47
|
|
69
|
-
* subset `resolveLocale` produces, so the publish path can never trip this
|
|
70
|
-
* after its write has committed. None admits a `:`, which is what stops a
|
|
71
|
-
* crafted `type` colliding with another page's tag (#6005 M3); the receiver
|
|
72
|
-
* applies the same shapes.
|
|
73
|
-
*/
|
|
74
|
-
const targetFieldsSchema = z.object({
|
|
75
|
-
type: z.enum(SCHEMA_ORG_TYPES),
|
|
76
|
-
slug: z.string().regex(SLUG_PATTERN),
|
|
77
|
-
locale: localeSchema,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Backoff before attempt 2 and attempt 3. Three attempts total, no fourth:
|
|
82
|
-
* 3 × the wrapper's 10 s timeout plus these two sleeps is 32.5 s worst case,
|
|
83
|
-
* which fits inside the PRD's 60 s publish-to-live budget.
|
|
84
|
-
*/
|
|
85
|
-
const RETRY_BACKOFF_MS = [500, 2000];
|
|
86
|
-
const MAX_ATTEMPTS = RETRY_BACKOFF_MS.length + 1;
|
|
87
|
-
|
|
88
|
-
export interface RevalidateTarget {
|
|
89
|
-
workspaceId: string;
|
|
90
|
-
type: string;
|
|
91
|
-
slug: string;
|
|
92
|
-
locale: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface RevalidateOptions {
|
|
96
|
-
event: "publish" | "unpublish";
|
|
97
|
-
/**
|
|
98
|
-
* Waits out one backoff. A test seam (#8418): the publish routes never pass
|
|
99
|
-
* it, so production always waits on the real timer. A test passes one that
|
|
100
|
-
* records the delay and resolves at once, so the retry schedule is asserted
|
|
101
|
-
* as scheduled rather than measured against a loaded runner's clock.
|
|
102
|
-
*
|
|
103
|
-
* @internal
|
|
104
|
-
*/
|
|
105
|
-
sleep?: (ms: number) => Promise<void>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function realSleep(ms: number): Promise<void> {
|
|
109
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 429 and 5xx are worth another attempt. Every other non-2xx is terminal — a
|
|
114
|
-
* 401 from a wrong secret or a 404 from a wrong path will not succeed on a
|
|
115
|
-
* second try, and retrying it triples the log noise for a misconfiguration.
|
|
116
|
-
*/
|
|
117
|
-
function isRetryableStatus(status: number): boolean {
|
|
118
|
-
return status === 429 || status >= 500;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Tells the site to revalidate one piece of content. Resolves on success,
|
|
123
|
-
* exhausted retries, or unconfigured, so an `await` in the publish path cannot
|
|
124
|
-
* fail the publish it follows. Rejects only when `type`, `slug` or `locale` is
|
|
125
|
-
* out of bounds, before anything is signed or sent.
|
|
126
|
-
*/
|
|
127
|
-
export async function notifyRevalidate(
|
|
128
|
-
target: RevalidateTarget,
|
|
129
|
-
options: RevalidateOptions,
|
|
130
|
-
): Promise<void> {
|
|
131
|
-
targetFieldsSchema.parse({
|
|
132
|
-
type: target.type,
|
|
133
|
-
slug: target.slug,
|
|
134
|
-
locale: target.locale,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const { sleep = realSleep } = options;
|
|
138
|
-
const url = config.CONTENT_REVALIDATE_URL;
|
|
139
|
-
const secret = config.CONTENT_REVALIDATE_SECRET;
|
|
140
|
-
|
|
141
|
-
// The PII-safe key set every line in this module carries.
|
|
142
|
-
const base = { event: options.event, ...target };
|
|
143
|
-
|
|
144
|
-
if (!url || !secret) {
|
|
145
|
-
// Signing with an empty key would be worse than skipping: every delivery
|
|
146
|
-
// would be rejected by the receiver with no local signal.
|
|
147
|
-
const reason = !url ? "url_unset" : "secret_unset";
|
|
148
|
-
log.warn({ ...base, reason }, "revalidate.skipped");
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ONE serialization: this exact string is both signed and sent, so a
|
|
153
|
-
// key-order or whitespace drift between signer and sender is impossible by
|
|
154
|
-
// construction.
|
|
155
|
-
const body = JSON.stringify({
|
|
156
|
-
workspaceId: target.workspaceId,
|
|
157
|
-
type: target.type,
|
|
158
|
-
slug: target.slug,
|
|
159
|
-
locale: target.locale,
|
|
160
|
-
});
|
|
161
|
-
// Taken once per call and shared by every attempt: the retry schedule's
|
|
162
|
-
// 32.5 s worst case sits well inside the receiver's 5-minute tolerance.
|
|
163
|
-
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
164
|
-
const signature = createHmac("sha256", secret)
|
|
165
|
-
.update(`v0:${timestamp}:${options.event}:${body}`, "utf8")
|
|
166
|
-
.digest("hex");
|
|
167
|
-
|
|
168
|
-
let attempt = 0;
|
|
169
|
-
let lastStatus: number | undefined;
|
|
170
|
-
let errName: string | undefined;
|
|
171
|
-
|
|
172
|
-
while (attempt < MAX_ATTEMPTS) {
|
|
173
|
-
attempt++;
|
|
174
|
-
let retryable: boolean;
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const res = await outboundFetch(url, {
|
|
178
|
-
method: "POST",
|
|
179
|
-
headers: {
|
|
180
|
-
"content-type": "application/json",
|
|
181
|
-
[TIMESTAMP_HEADER]: timestamp,
|
|
182
|
-
[EVENT_HEADER]: options.event,
|
|
183
|
-
[SIGNATURE_HEADER]: `sha256=${signature}`,
|
|
184
|
-
},
|
|
185
|
-
body,
|
|
186
|
-
// The SSRF gate is waived deliberately and narrowly: this URL comes
|
|
187
|
-
// from deployment environment, never from a request body or a
|
|
188
|
-
// database row. Leaving it on would break local development (the site
|
|
189
|
-
// is http://localhost:3000) and any single-host deployment.
|
|
190
|
-
allowPrivate: true,
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
if (res.ok) {
|
|
194
|
-
log.info({ ...base, attempt, status: res.status }, "revalidate.sent");
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
lastStatus = res.status;
|
|
199
|
-
errName = undefined;
|
|
200
|
-
retryable = isRetryableStatus(res.status);
|
|
201
|
-
} catch (err) {
|
|
202
|
-
lastStatus = undefined;
|
|
203
|
-
errName = err instanceof Error ? err.name : "UnknownError";
|
|
204
|
-
retryable = true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
log.warn(
|
|
208
|
-
{
|
|
209
|
-
...base,
|
|
210
|
-
attempt,
|
|
211
|
-
...(lastStatus !== undefined && { status: lastStatus }),
|
|
212
|
-
...(errName !== undefined && { errName }),
|
|
213
|
-
},
|
|
214
|
-
"revalidate.failed",
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
if (!retryable) break;
|
|
218
|
-
|
|
219
|
-
// Undefined on the last attempt — the schedule has one entry fewer than
|
|
220
|
-
// MAX_ATTEMPTS, so running out of backoffs IS the "no fourth attempt" bound.
|
|
221
|
-
const backoffMs = RETRY_BACKOFF_MS[attempt - 1];
|
|
222
|
-
if (backoffMs === undefined) break;
|
|
223
|
-
await sleep(backoffMs);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
log.error(
|
|
227
|
-
{
|
|
228
|
-
...base,
|
|
229
|
-
attempts: attempt,
|
|
230
|
-
...(lastStatus !== undefined && { lastStatus }),
|
|
231
|
-
...(errName !== undefined && { errName }),
|
|
232
|
-
},
|
|
233
|
-
"revalidate.failed",
|
|
234
|
-
);
|
|
235
|
-
}
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file slack-notify.ts
|
|
3
|
-
* @description The operator's one free action per application (#4999): post
|
|
4
|
-
* a new `ProjectListing` to the site's Slack channel with Approve/Reject
|
|
5
|
-
* buttons, then update that SAME message in place once a decision lands.
|
|
6
|
-
*
|
|
7
|
-
* A thin `fetch` client over the Slack Web API — no Slack SDK (this
|
|
8
|
-
* environment's contract). `AILK_SLACK_BOT_TOKEN`/`AILK_SLACK_CHANNEL_ID`
|
|
9
|
-
* absent means Slack is off: both exports become no-ops that log
|
|
10
|
-
* `slack-skipped` (id only) and never throw — a create must never fail
|
|
11
|
-
* because Slack is unconfigured (or down), and a decision must always be
|
|
12
|
-
* appliable even if the outcome can't be posted back.
|
|
13
|
-
*
|
|
14
|
-
* PII boundary: only `listingId` (plus a Slack API `error` code, never a
|
|
15
|
-
* response body) ever reaches a log line here — never `founderEmail`,
|
|
16
|
-
* `founderName`, `oneLiner`, `problem`, or any other applicant-authored text.
|
|
17
|
-
* The Block Kit body sent TO Slack necessarily carries that text (that's the
|
|
18
|
-
* point of the notification) — Slack, not this process's own logs, is the
|
|
19
|
-
* destination for it.
|
|
20
|
-
*
|
|
21
|
-
* Security note (PR #4999 review): every applicant-authored string
|
|
22
|
-
* (`oneLiner`/`problem`/`founderName`/`founderCompany`/`url`) is
|
|
23
|
-
* submitted by an ANONYMOUS, unauthenticated caller (`POST
|
|
24
|
-
* /v1/project-listings`) and lands in `mrkdwn` fields, which Slack parses for
|
|
25
|
-
* `<!channel>`/`<!here>`/`<@U…>` mentions and `<url|label>` masked links — an
|
|
26
|
-
* applicant could otherwise mass-ping the channel or mask a phishing link
|
|
27
|
-
* behind innocuous-looking text. `escapeMrkdwn` (Slack's own documented
|
|
28
|
-
* escaping: `&` -> `&`, `<` -> `<`, `>` -> `>`, in that order) runs
|
|
29
|
-
* on every such string, and on the Slack-supplied `decidedByName` too (lower
|
|
30
|
-
* risk — only a member of the notified channel controls it — but the same
|
|
31
|
-
* fix applies for defense in depth).
|
|
32
|
-
*
|
|
33
|
-
* Tenant binding (#7522): the posted actions block's `block_id` names the
|
|
34
|
-
* listing's own workspace (`listing_decision:<workspaceId>`), and
|
|
35
|
-
* `POST /v1/slack/actions` reads it back through `parseListingDecisionBlockId`
|
|
36
|
-
* — the producer and the parser live side by side here so the format cannot
|
|
37
|
-
* drift. Button `value`s stay the bare listing id.
|
|
38
|
-
*/
|
|
39
|
-
import { zWorkspaceId } from "@working-theory/ids";
|
|
40
|
-
import { logger } from "@working-theory/observability";
|
|
41
|
-
|
|
42
|
-
import { config } from "../config/index.js";
|
|
43
|
-
import { prisma } from "../lib/prisma.js";
|
|
44
|
-
|
|
45
|
-
const SLACK_API_BASE = "https://slack.com/api";
|
|
46
|
-
|
|
47
|
-
/** The subset of a ProjectListing this module ever renders into a Slack message. */
|
|
48
|
-
export type ProjectListingSlackInfo = {
|
|
49
|
-
id: string;
|
|
50
|
-
/**
|
|
51
|
-
* Nullable since #5036 D1: every application field is nullable at rest so a
|
|
52
|
-
* half-filled draft is storable. A listing only reaches Slack once it has
|
|
53
|
-
* been SUBMITTED, so in practice these are set — the nulls are rendered as
|
|
54
|
-
* an em dash rather than asserted away, because a `!` here would turn a data
|
|
55
|
-
* surprise into a crash inside a notifier that is not allowed to throw.
|
|
56
|
-
*/
|
|
57
|
-
name: string | null;
|
|
58
|
-
oneLiner: string | null;
|
|
59
|
-
problem: string | null;
|
|
60
|
-
url: string | null;
|
|
61
|
-
/** The six-rung product ladder that replaced `stage` (#5036 D2). */
|
|
62
|
-
product: string | null;
|
|
63
|
-
founderName: string;
|
|
64
|
-
founderCompany: string | null;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/** What an absent field renders as in a Slack field. */
|
|
68
|
-
const ABSENT = "—";
|
|
69
|
-
|
|
70
|
-
/** The Approve/Reject actions block's `block_id` stem — declared once (#7522 D1). */
|
|
71
|
-
const LISTING_DECISION_BLOCK = "listing_decision";
|
|
72
|
-
const LISTING_DECISION_BLOCK_PREFIX = `${LISTING_DECISION_BLOCK}:`;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* The instant (Unix seconds, a whole UTC hour) from which every posted
|
|
76
|
-
* Approve/Reject message names its workspace in `block_id` (#7522 D4). A
|
|
77
|
-
* click that names no workspace is honoured only for a listing whose message
|
|
78
|
-
* was posted before it (`slackTs` below it) or that has no message at all —
|
|
79
|
-
* a legacy-shaped click on a newer message cannot be a real click.
|
|
80
|
-
*/
|
|
81
|
-
export const LISTING_DECISION_BLOCK_NAMED_SINCE_SECONDS = 1791000000; // 2026-10-03T04:00:00Z
|
|
82
|
-
|
|
83
|
-
/** The actions block's `block_id` for a listing in `workspaceId` (#7522 D1). */
|
|
84
|
-
export function listingDecisionBlockId(workspaceId: string): string {
|
|
85
|
-
return `${LISTING_DECISION_BLOCK_PREFIX}${workspaceId}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type ListingDecisionBlock =
|
|
89
|
-
| { kind: "legacy" }
|
|
90
|
-
| { kind: "named"; workspaceId: string }
|
|
91
|
-
| { kind: "invalid" };
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Reads a clicked action's `block_id` back (#7522 D2). Absent or the bare
|
|
95
|
-
* stem is the pre-#7522 message shape (`legacy`); the stem plus a valid
|
|
96
|
-
* workspace id is `named`; anything else — a malformed suffix included — is
|
|
97
|
-
* `invalid`, never `legacy`, so a bad workspace name cannot widen the scope.
|
|
98
|
-
*/
|
|
99
|
-
export function parseListingDecisionBlockId(blockId: string | undefined): ListingDecisionBlock {
|
|
100
|
-
if (blockId === undefined || blockId === LISTING_DECISION_BLOCK) return { kind: "legacy" };
|
|
101
|
-
// The payload is JSON — a non-string `block_id` is possible on the wire.
|
|
102
|
-
if (typeof blockId !== "string" || !blockId.startsWith(LISTING_DECISION_BLOCK_PREFIX)) {
|
|
103
|
-
return { kind: "invalid" };
|
|
104
|
-
}
|
|
105
|
-
const workspaceId = blockId.slice(LISTING_DECISION_BLOCK_PREFIX.length);
|
|
106
|
-
return zWorkspaceId.safeParse(workspaceId).success
|
|
107
|
-
? { kind: "named", workspaceId }
|
|
108
|
-
: { kind: "invalid" };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
type SlackApiResult = {
|
|
112
|
-
ok: boolean;
|
|
113
|
-
error?: string;
|
|
114
|
-
ts?: string;
|
|
115
|
-
channel?: string;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Slack's own documented `mrkdwn` escaping — `&` first, then `<`/`>` — so an
|
|
120
|
-
* applicant-authored string can never open a `<!channel>`/`<@U…>` mention or
|
|
121
|
-
* a `<url|label>` masked link inside a `mrkdwn` field.
|
|
122
|
-
*/
|
|
123
|
-
function escapeMrkdwn(value: string): string {
|
|
124
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function callSlackApi(
|
|
128
|
-
token: string,
|
|
129
|
-
method: "chat.postMessage" | "chat.update",
|
|
130
|
-
body: Record<string, unknown>,
|
|
131
|
-
): Promise<SlackApiResult> {
|
|
132
|
-
const res = await fetch(`${SLACK_API_BASE}/${method}`, {
|
|
133
|
-
method: "POST",
|
|
134
|
-
headers: {
|
|
135
|
-
"content-type": "application/json; charset=utf-8",
|
|
136
|
-
authorization: `Bearer ${token}`,
|
|
137
|
-
},
|
|
138
|
-
body: JSON.stringify(body),
|
|
139
|
-
});
|
|
140
|
-
return (await res.json()) as SlackApiResult;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* The header + info-fields blocks shared by the initial post and the decided
|
|
145
|
-
* update. Every applicant-authored string is `escapeMrkdwn`-ed before it
|
|
146
|
-
* enters a `mrkdwn` field (see the module doc's security note) — an
|
|
147
|
-
* anonymous applicant's `oneLiner`/`problem`/`founderName`/
|
|
148
|
-
* `founderCompany`/`url` can otherwise inject a Slack mention or a masked
|
|
149
|
-
* phishing link. `listing.name` is NOT escaped here — it renders in the
|
|
150
|
-
* `header` block as `plain_text`, which Slack never interprets as markup.
|
|
151
|
-
*/
|
|
152
|
-
function infoBlocks(listing: ProjectListingSlackInfo): unknown[] {
|
|
153
|
-
const fields = [
|
|
154
|
-
{ type: "mrkdwn", text: `*One-liner:*\n${escapeMrkdwn(listing.oneLiner ?? ABSENT)}` },
|
|
155
|
-
{ type: "mrkdwn", text: `*Problem:*\n${escapeMrkdwn(listing.problem ?? ABSENT)}` },
|
|
156
|
-
{ type: "mrkdwn", text: `*Product:*\n${escapeMrkdwn(listing.product ?? ABSENT)}` },
|
|
157
|
-
...(listing.url ? [{ type: "mrkdwn", text: `*URL:*\n${escapeMrkdwn(listing.url)}` }] : []),
|
|
158
|
-
{
|
|
159
|
-
type: "mrkdwn",
|
|
160
|
-
text: `*Founder:*\n${escapeMrkdwn(listing.founderName)}${
|
|
161
|
-
listing.founderCompany ? ` (${escapeMrkdwn(listing.founderCompany)})` : ""
|
|
162
|
-
}`,
|
|
163
|
-
},
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
return [
|
|
167
|
-
{ type: "header", text: { type: "plain_text", text: listing.name ?? ABSENT, emoji: true } },
|
|
168
|
-
{ type: "section", fields },
|
|
169
|
-
];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Posts the new application to Slack (`chat.postMessage`) with Approve/Reject
|
|
174
|
-
* buttons carrying the listing id as `value`, in an actions block whose
|
|
175
|
-
* `block_id` names the listing's workspace (#7522 D5). On success, stamps the
|
|
176
|
-
* returned `channel`/`ts` onto the row so `updateListingMessage` can later
|
|
177
|
-
* target the same message. No-op (logs `slack-skipped`) when Slack env is
|
|
178
|
-
* absent; never throws — a Slack failure (missing config, API error, network
|
|
179
|
-
* error) never fails the create that already succeeded.
|
|
180
|
-
*/
|
|
181
|
-
export async function postListingApplication(
|
|
182
|
-
listing: ProjectListingSlackInfo & { workspaceId: string },
|
|
183
|
-
): Promise<void> {
|
|
184
|
-
const token = config.AILK_SLACK_BOT_TOKEN;
|
|
185
|
-
const channel = config.AILK_SLACK_CHANNEL_ID;
|
|
186
|
-
if (!token || !channel) {
|
|
187
|
-
logger.info({ listingId: listing.id }, "slack-skipped");
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const result = await callSlackApi(token, "chat.postMessage", {
|
|
193
|
-
channel,
|
|
194
|
-
// `text:` is mrkdwn-parsed and is what Slack shows in the notification
|
|
195
|
-
// fallback, so the applicant-authored name is escaped here even though
|
|
196
|
-
// the same value is safe unescaped in the `plain_text` header block
|
|
197
|
-
// (PR #5042 security review, finding 5).
|
|
198
|
-
text: `New project application: ${escapeMrkdwn(listing.name ?? ABSENT)}`,
|
|
199
|
-
blocks: [
|
|
200
|
-
...infoBlocks(listing),
|
|
201
|
-
{
|
|
202
|
-
type: "actions",
|
|
203
|
-
block_id: listingDecisionBlockId(listing.workspaceId),
|
|
204
|
-
elements: [
|
|
205
|
-
{
|
|
206
|
-
type: "button",
|
|
207
|
-
text: { type: "plain_text", text: "Approve", emoji: true },
|
|
208
|
-
style: "primary",
|
|
209
|
-
action_id: "listing_approve",
|
|
210
|
-
value: listing.id,
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
type: "button",
|
|
214
|
-
text: { type: "plain_text", text: "Reject", emoji: true },
|
|
215
|
-
style: "danger",
|
|
216
|
-
action_id: "listing_reject",
|
|
217
|
-
value: listing.id,
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
if (!result.ok) {
|
|
225
|
-
logger.warn({ listingId: listing.id, slackError: result.error }, "slack post failed");
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
await prisma.projectListing.update({
|
|
230
|
-
where: { id: listing.id },
|
|
231
|
-
data: { slackChannel: result.channel ?? channel, slackTs: result.ts ?? null },
|
|
232
|
-
});
|
|
233
|
-
} catch (err) {
|
|
234
|
-
logger.warn(
|
|
235
|
-
{ listingId: listing.id, errName: err instanceof Error ? err.name : "UnknownError" },
|
|
236
|
-
"slack post threw",
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Replaces the posted message's actions block with an outcome line
|
|
243
|
-
* (`chat.update`) — same header + info fields, Approve/Reject buttons gone.
|
|
244
|
-
* No-op (logs `slack-skipped`) when Slack env is absent OR the row never
|
|
245
|
-
* carries `slackChannel`/`slackTs` (the post was skipped or failed); never
|
|
246
|
-
* throws — the decision has already been applied by the time this runs.
|
|
247
|
-
*/
|
|
248
|
-
export async function updateListingMessage(
|
|
249
|
-
listing: ProjectListingSlackInfo & { slackChannel: string | null; slackTs: string | null },
|
|
250
|
-
/** The stored decision status (#5033 — `active` is the approved state). */
|
|
251
|
-
outcome: "active" | "rejected",
|
|
252
|
-
decidedByName: string,
|
|
253
|
-
): Promise<void> {
|
|
254
|
-
const token = config.AILK_SLACK_BOT_TOKEN;
|
|
255
|
-
if (!token || !listing.slackChannel || !listing.slackTs) {
|
|
256
|
-
logger.info({ listingId: listing.id }, "slack-skipped");
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// decidedByName is Slack-supplied (payload.user.username/name) — escaped
|
|
261
|
-
// for the same reason applicant text is (see the module doc's security
|
|
262
|
-
// note), even though only a member of the notified channel controls it.
|
|
263
|
-
const safeDecidedByName = escapeMrkdwn(decidedByName);
|
|
264
|
-
// The operator-facing copy stays "Approved" — the vocabulary change is in
|
|
265
|
-
// the stored status, not in what a human reads in the channel.
|
|
266
|
-
const outcomeLine =
|
|
267
|
-
outcome === "active"
|
|
268
|
-
? `:white_check_mark: Approved by ${safeDecidedByName}`
|
|
269
|
-
: `:x: Rejected by ${safeDecidedByName}`;
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
const result = await callSlackApi(token, "chat.update", {
|
|
273
|
-
channel: listing.slackChannel,
|
|
274
|
-
ts: listing.slackTs,
|
|
275
|
-
text: `${escapeMrkdwn(listing.name ?? ABSENT)} — ${outcomeLine}`,
|
|
276
|
-
blocks: [
|
|
277
|
-
...infoBlocks(listing),
|
|
278
|
-
{ type: "section", text: { type: "mrkdwn", text: outcomeLine } },
|
|
279
|
-
],
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
if (!result.ok) {
|
|
283
|
-
logger.warn({ listingId: listing.id, slackError: result.error }, "slack update failed");
|
|
284
|
-
}
|
|
285
|
-
} catch (err) {
|
|
286
|
-
logger.warn(
|
|
287
|
-
{ listingId: listing.id, errName: err instanceof Error ? err.name : "UnknownError" },
|
|
288
|
-
"slack update threw",
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
}
|