create-ailk 0.5.0 → 0.6.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 +55 -8
- package/component-catalog.json +336 -27
- package/component-catalog.md +25 -12
- package/dist/cli.js +54 -5
- package/dist/copy.d.ts +14 -0
- package/dist/copy.js +38 -1
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/extract-module-bundle.d.ts +20 -1
- package/dist/lib/extract-module-bundle.js +64 -5
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/lib/module-license-gate.d.ts +6 -1
- package/dist/lib/module-license-gate.js +6 -1
- package/dist/lib/pro-callout.js +1 -1
- package/dist/module-architecture.d.ts +1 -0
- package/dist/module-architecture.js +253 -127
- package/dist/modules/commerce/0.6.0.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.0.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.0.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.0.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.0.tar.gz +0 -0
- package/dist/modules/workspace/0.6.0.tar.gz +0 -0
- package/dist/programmatic.js +14 -10
- package/dist/surfaces.js +105 -7
- package/dist/template-manifest.d.ts +33 -0
- package/dist/template-manifest.js +99 -16
- package/dist/template-names.d.ts +1 -1
- package/dist/template-names.js +1 -0
- package/package.json +3 -2
- package/site-templates/marketing-site/DESIGN.md +148 -0
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +22 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +9 -2
- package/site-templates/marketing-site/ailk-template.json +1 -1
- package/site-templates/marketing-site-free/DESIGN.md +148 -0
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +2 -2
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -0
- package/site-templates/minimalism-site/DESIGN.md +148 -0
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +10 -2
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +47 -35
- package/site-templates/minimalism-site/ailk-template.json +2 -1
- package/site-templates/minimalism-site/assets/wordmark-placeholder.svg +28 -0
- package/site-templates/minimalism-site/content/en/HomePage/home.mdx +49 -19
- package/site-templates/saas-product/DESIGN.md +148 -0
- package/site-templates/simple-service/DESIGN.md +148 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +240 -0
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +278 -0
- package/site-templates/simple-service/ailk-template.json +10 -0
- package/site-templates/simple-service/content/en/AboutPage/about.mdx +38 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +67 -0
- package/site-templates/simple-service/content/en/FAQPage/faq.mdx +56 -0
- package/site-templates/simple-service/content/en/HomePage/home.mdx +85 -0
- package/site-templates/simple-service/content/en/Service/landscape-design.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +40 -0
- package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +40 -0
- package/site-templates/simple-service/content/en/ServicesList/services.mdx +72 -0
- package/site-templates/simple-service/provenance.json +4 -0
- package/templates/.env.example +72 -7
- package/templates/CLAUDE.md +2 -3
- package/templates/DESIGN.md +155 -0
- package/templates/README.md +42 -8
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-31.552Z-fallback-1789916251590-037e9005.json +8 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.840Z-fallback-1789916253842-b52659e9.json +12 -0
- package/templates/apps/api/.ailk/inbox/2026-09-20T14-57-33.844Z-fallback-1789916253845-6b266f8c.json +12 -0
- package/templates/apps/api/.env.example +13 -0
- package/templates/apps/api/CLAUDE.md +26 -2
- package/templates/apps/api/jest.config.cjs +3 -0
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +407 -0
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +83 -7
- package/templates/apps/api/src/__tests__/server.test.ts +471 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +45 -0
- package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +2 -0
- package/templates/apps/api/src/config/index.ts +62 -0
- package/templates/apps/api/src/config/modules.ts +7 -3
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +238 -2
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +143 -0
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +23 -0
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +125 -0
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +20 -0
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +317 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +238 -0
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +75 -0
- package/templates/apps/api/src/lib/auth-email-budget.ts +160 -0
- package/templates/apps/api/src/lib/content-adapter.ts +57 -31
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +76 -0
- package/templates/apps/api/src/lib/error-serializer.ts +23 -3
- package/templates/apps/api/src/lib/outbound-http.ts +7 -2
- package/templates/apps/api/src/lib/require-commerce-claim.ts +250 -0
- package/templates/apps/api/src/lib/site.ts +208 -9
- package/templates/apps/api/src/lib/stripe.ts +66 -0
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +182 -0
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +42 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +7 -1
- package/templates/apps/api/src/openapi/spec.ts +543 -2
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +48 -0
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +309 -0
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +123 -0
- package/templates/apps/api/src/routes/auth/transactional-email.ts +273 -0
- package/templates/apps/api/src/routes/auth/user.ts +61 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +434 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +888 -0
- package/templates/apps/api/src/routes/checkout/index.ts +34 -0
- package/templates/apps/api/src/routes/checkout/session.ts +139 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +197 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +554 -0
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +108 -0
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +450 -0
- package/templates/apps/api/src/routes/content/get-page.ts +94 -12
- package/templates/apps/api/src/routes/content/index.ts +13 -73
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -3
- package/templates/apps/api/src/routes/content/rate-limit.ts +15 -6
- package/templates/apps/api/src/routes/content/search.ts +129 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +447 -31
- package/templates/apps/api/src/routes/leads/index.ts +100 -13
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +351 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +4 -1
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +58 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +107 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +87 -2
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +51 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +5 -1
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +686 -0
- package/templates/apps/api/src/routes/requests/confirm.ts +601 -0
- package/templates/apps/api/src/routes/requests/index.ts +54 -0
- package/templates/apps/api/src/routes/webhooks/README.md +265 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +749 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +330 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +262 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +285 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +213 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +301 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +648 -0
- package/templates/apps/api/src/routes/webhooks/index.ts +89 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +1399 -0
- package/templates/apps/api/src/server.ts +439 -49
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +39 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +217 -40
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +620 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +2 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +300 -5
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +20 -0
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +368 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +160 -0
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +166 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +154 -0
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +137 -0
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +444 -0
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +238 -0
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +78 -0
- package/templates/apps/api/src/services/abandon-email.ts +8 -2
- package/templates/apps/api/src/services/aeo-score.ts +49 -12
- package/templates/apps/api/src/services/audit-log.ts +668 -0
- package/templates/apps/api/src/services/consent.ts +18 -0
- package/templates/apps/api/src/services/default-tenant.ts +70 -31
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +141 -13
- package/templates/apps/api/src/services/dunning-policy.ts +294 -0
- package/templates/apps/api/src/services/entitlements.ts +80 -0
- package/templates/apps/api/src/services/lead-promotion.ts +13 -14
- package/templates/apps/api/src/services/lead-routing.ts +161 -2
- package/templates/apps/api/src/services/leads-inbox.ts +23 -2
- package/templates/apps/api/src/services/listing-config.ts +27 -9
- package/templates/apps/api/src/services/project-listings.ts +201 -6
- package/templates/apps/api/src/services/public-site-resolver.ts +106 -0
- package/templates/apps/api/src/services/request-confirm.ts +646 -0
- package/templates/apps/api/src/services/revalidation.ts +173 -0
- package/templates/apps/api/src/services/verified-email.ts +71 -0
- package/templates/apps/api/src/vercel-handler.ts +75 -35
- package/templates/apps/mcp/.env.example +17 -0
- package/templates/apps/mcp/CLAUDE.md +57 -24
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +6 -0
- package/templates/apps/mcp/__tests__/audit.test.ts +137 -8
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +79 -36
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +111 -2
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +82 -2
- package/templates/apps/mcp/__tests__/http-transport.test.ts +541 -2
- package/templates/apps/mcp/__tests__/index.test.ts +39 -0
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +143 -0
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +111 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +65 -0
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +188 -0
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +165 -0
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +124 -0
- package/templates/apps/mcp/__tests__/list_pages.test.ts +116 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +7 -1
- package/templates/apps/mcp/__tests__/parity.test.ts +10 -7
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +89 -22
- package/templates/apps/mcp/__tests__/read_content.test.ts +322 -1
- package/templates/apps/mcp/__tests__/search_content.test.ts +105 -2
- package/templates/apps/mcp/__tests__/server.test.ts +67 -14
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +252 -0
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +300 -123
- package/templates/apps/mcp/jest.config.cjs +13 -0
- package/templates/apps/mcp/package.json +2 -0
- package/templates/apps/mcp/src/abuse/index.ts +159 -24
- package/templates/apps/mcp/src/config/modules.ts +10 -5
- package/templates/apps/mcp/src/http-transport.ts +102 -29
- package/templates/apps/mcp/src/index.ts +73 -11
- package/templates/apps/mcp/src/lib/audit-single-page.ts +90 -3
- package/templates/apps/mcp/src/lib/commerce-claim.ts +94 -0
- package/templates/apps/mcp/src/lib/content-adapter.ts +113 -0
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +139 -0
- package/templates/apps/mcp/src/lib/prisma.ts +39 -0
- package/templates/apps/mcp/src/lib/public-workspace.ts +133 -0
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +59 -0
- package/templates/apps/mcp/src/server.ts +59 -35
- package/templates/apps/mcp/src/tools/audit.ts +26 -2
- package/templates/apps/mcp/src/tools/get_business_hours.ts +4 -3
- package/templates/apps/mcp/src/tools/get_faq.ts +4 -3
- package/templates/apps/mcp/src/tools/get_locations.ts +5 -3
- package/templates/apps/mcp/src/tools/get_offerings.ts +16 -4
- package/templates/apps/mcp/src/tools/get_pricing.ts +25 -6
- package/templates/apps/mcp/src/tools/get_site_info.ts +4 -3
- package/templates/apps/mcp/src/tools/index.ts +6 -4
- package/templates/apps/mcp/src/tools/list_pages.ts +4 -3
- package/templates/apps/mcp/src/tools/purchase.ts +128 -45
- package/templates/apps/mcp/src/tools/read_content.ts +20 -6
- package/templates/apps/mcp/src/tools/search_content.ts +11 -4
- package/templates/apps/web/.env.example +71 -5
- package/templates/apps/web/CLAUDE.md +44 -0
- package/templates/apps/web/PRODUCT.md +86 -0
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +18 -2
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +11 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +282 -0
- package/templates/apps/web/app/[locale]/(auth)/__tests__/password-reset.test.tsx +515 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +89 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +109 -0
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +92 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +135 -0
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +109 -0
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/page.test.tsx +48 -2
- package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/page.tsx +20 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +125 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +96 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +101 -0
- package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +102 -0
- package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +20 -10
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +84 -0
- package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +58 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +63 -8
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +18 -1
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/layout.tsx +133 -87
- package/templates/apps/web/app/[locale]/purchase/__tests__/cancel.test.tsx +162 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/module-scope.test.ts +68 -0
- package/templates/apps/web/app/[locale]/purchase/__tests__/success.test.tsx +361 -0
- package/templates/apps/web/app/[locale]/purchase/cancel/page.tsx +115 -0
- package/templates/apps/web/app/[locale]/purchase/success/page.tsx +307 -0
- package/templates/apps/web/app/[locale]/returns/page.tsx +44 -0
- package/templates/apps/web/app/[locale]/shipping/page.tsx +44 -0
- package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +670 -0
- package/templates/apps/web/app/api/revalidate/route.ts +194 -0
- package/templates/apps/web/auth.ts +407 -8
- package/templates/apps/web/jest.config.cjs +8 -4
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +214 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +74 -0
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +72 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +135 -20
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +18 -1
- package/templates/apps/web/lib/auth-email.ts +318 -0
- package/templates/apps/web/lib/auth-env.ts +85 -0
- package/templates/apps/web/lib/auth-routes.ts +55 -0
- package/templates/apps/web/lib/meeting-slugs.ts +53 -0
- package/templates/apps/web/lib/site-brand.tsx +4 -8
- package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +187 -0
- package/templates/apps/web/middleware.ts +1 -1
- package/templates/apps/web/package.json +2 -1
- package/templates/apps/web/src/lib/commerce-claim.ts +33 -0
- package/templates/apps/web/src/lib/commerce-sections.ts +93 -0
- package/templates/content/_site.mdx +18 -3
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +108 -0
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +108 -0
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +108 -0
- package/templates/database/CHANGELOG.md +149 -0
- package/templates/database/README.md +10 -3
- package/templates/database/__tests__/appointment.db.test.ts +106 -0
- package/templates/database/__tests__/artifact-content-store.db.test.ts +316 -0
- package/templates/database/__tests__/composite-fence.db.test.ts +206 -0
- package/templates/database/__tests__/fence-fan-out.db.test.ts +711 -0
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +79 -0
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +100 -0
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +263 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +85 -9
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +111 -0
- package/templates/database/__tests__/tenancy.test.ts +141 -0
- package/templates/database/config/schema.prisma +79 -2
- package/templates/database/content/schema.prisma +87 -11
- package/templates/database/inbox/schema.prisma +452 -9
- package/templates/database/jest.config.cjs +5 -3
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +15 -0
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +158 -0
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +40 -0
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +67 -0
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +70 -0
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +181 -0
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +172 -0
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +41 -0
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +185 -0
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +70 -0
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +57 -0
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +68 -0
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +1067 -0
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +210 -0
- package/templates/database/package.json +3 -2
- package/templates/database/tenancy.ts +86 -1
- package/templates/database/tsconfig.json +1 -0
- package/versions.json +34 -34
- package/templates/apps/api/src/routes/content/__tests__/create.test.ts +0 -253
- package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +0 -215
- package/templates/apps/api/src/routes/content/__tests__/update.test.ts +0 -266
- package/templates/apps/api/src/routes/content/create.ts +0 -115
- package/templates/apps/api/src/routes/content/delete.ts +0 -82
- package/templates/apps/api/src/routes/content/update.ts +0 -112
- package/templates/apps/mcp/__tests__/create_page.test.ts +0 -202
- package/templates/apps/mcp/__tests__/delete_page.test.ts +0 -177
- package/templates/apps/mcp/__tests__/update_page.test.ts +0 -196
- package/templates/apps/mcp/src/tools/create_page.ts +0 -96
- package/templates/apps/mcp/src/tools/delete_page.ts +0 -74
- package/templates/apps/mcp/src/tools/update_page.ts +0 -92
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +0 -123
- package/templates/apps/web/public/logo-dark.svg +0 -21
- package/templates/apps/web/public/logo-light.svg +0 -21
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/__tests__/next-path.test.ts +0 -0
- /package/templates/apps/web/app/[locale]/{sign-in → (auth)/sign-in}/next-path.ts +0 -0
|
@@ -56,6 +56,15 @@ export interface ConsentWriteClient {
|
|
|
56
56
|
|
|
57
57
|
interface ConsentGrantRow {
|
|
58
58
|
siteId: string;
|
|
59
|
+
/**
|
|
60
|
+
* #6374 — the tenancy fence's isolation key, equal to `siteId` by
|
|
61
|
+
* construction. Non-optional on purpose: `consent_grants.workspace_id` is
|
|
62
|
+
* NOT NULL as of
|
|
63
|
+
* database/migrations/20260922120000_tenancy_fence, so a row builder that
|
|
64
|
+
* forgot it would fail at runtime; declaring it required here makes the
|
|
65
|
+
* type checker say so instead.
|
|
66
|
+
*/
|
|
67
|
+
workspaceId: string;
|
|
59
68
|
subjectType: string;
|
|
60
69
|
subjectId: string;
|
|
61
70
|
purposeId: string;
|
|
@@ -169,6 +178,12 @@ export async function recordConsent(
|
|
|
169
178
|
const { statement, locale } = resolveConsentStatement(purpose, input.locale);
|
|
170
179
|
rows.push({
|
|
171
180
|
siteId: input.siteId,
|
|
181
|
+
// #6374 — the isolation key, equal to `siteId` by construction.
|
|
182
|
+
// `input.siteId` is the caller's already-resolved site id; the
|
|
183
|
+
// `consent_grants_workspace_site_agree` CHECK makes a disagreement
|
|
184
|
+
// unstorable, and `consent_grants_workspace_id_format` rejects a raw
|
|
185
|
+
// alias loudly. Never read from a request body (AC-6).
|
|
186
|
+
workspaceId: input.siteId,
|
|
172
187
|
subjectType: input.subjectType,
|
|
173
188
|
subjectId: input.subjectId,
|
|
174
189
|
purposeId,
|
|
@@ -222,6 +237,9 @@ export async function recordLegacyConsent(
|
|
|
222
237
|
data: [
|
|
223
238
|
{
|
|
224
239
|
siteId: input.siteId,
|
|
240
|
+
// #6374 — the isolation key, equal to `siteId` by construction; the
|
|
241
|
+
// same stamp the D10 path's row builder above applies.
|
|
242
|
+
workspaceId: input.siteId,
|
|
225
243
|
subjectType: input.subjectType,
|
|
226
244
|
subjectId: input.subjectId,
|
|
227
245
|
purposeId: purpose.id,
|
|
@@ -38,13 +38,15 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import {
|
|
40
40
|
DEFAULT_ORG_ID,
|
|
41
|
-
DEFAULT_WORKSPACE_ID,
|
|
42
41
|
OPERATOR_MEMBERSHIP_ID,
|
|
42
|
+
resolveDefaultWorkspaceRow as resolveDefaultWorkspaceRowShared,
|
|
43
43
|
} from "@working-theory/database/tenancy";
|
|
44
|
-
import {
|
|
44
|
+
import type { Id } from "@working-theory/ids";
|
|
45
45
|
|
|
46
46
|
import { prisma } from "../lib/prisma.js";
|
|
47
47
|
|
|
48
|
+
import { findVerifiedEmail } from "./verified-email.js";
|
|
49
|
+
|
|
48
50
|
export type MembershipRole = "owner" | "admin" | "member";
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -93,9 +95,11 @@ export function isUniqueViolation(error: unknown): boolean {
|
|
|
93
95
|
* multi-tenant state (`count > 1`).
|
|
94
96
|
*
|
|
95
97
|
* When `AILK_OPERATOR_EMAIL` is set (spec D4 — trimmed + lowercased before
|
|
96
|
-
* comparison), the bind is attempted ONLY for the caller whose
|
|
97
|
-
* matches it
|
|
98
|
-
*
|
|
98
|
+
* comparison), the bind is attempted ONLY for the caller whose VERIFIED
|
|
99
|
+
* `User.email` matches it (#5701 AC-2 — the read goes through
|
|
100
|
+
* `findVerifiedEmail`, so an unverified address is refused here and not only
|
|
101
|
+
* at the base-build entry above); every other caller returns without writing
|
|
102
|
+
* and is denied downstream. An empty/whitespace-only `AILK_OPERATOR_EMAIL` normalizes to
|
|
99
103
|
* `""`, which is falsy — treated as unset (pure TOFU), so a blank pin can
|
|
100
104
|
* never "match" a blank `User.email`. When unset, the bind is pure TOFU —
|
|
101
105
|
* first caller wins.
|
|
@@ -103,11 +107,18 @@ export function isUniqueViolation(error: unknown): boolean {
|
|
|
103
107
|
async function bindOperatorMembership(userId: Id<"user">): Promise<void> {
|
|
104
108
|
const pinnedEmail = process.env.AILK_OPERATOR_EMAIL?.trim().toLowerCase();
|
|
105
109
|
if (pinnedEmail) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
// The verified-email read is `services/verified-email.ts` (#5701 D4) — the
|
|
111
|
+
// same one `resolveDefaultTenantForOperator` uses. It returns the address
|
|
112
|
+
// only when `emailVerified === true`, so an address that merely CLAIMS to
|
|
113
|
+
// be the pin can no longer mint `OPERATOR_MEMBERSHIP_ID`. Both refusals
|
|
114
|
+
// (`user_not_found`, `email_unverified`) take the branch's existing early
|
|
115
|
+
// return — the caller is then unbound and denied by the
|
|
116
|
+
// `membership.findUnique` that follows.
|
|
117
|
+
const verified = await findVerifiedEmail(userId);
|
|
118
|
+
if (!verified.ok) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (verified.value.email.trim().toLowerCase() !== pinnedEmail) {
|
|
111
122
|
return;
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -185,23 +196,60 @@ export async function resolveDefaultTenantForOperator(
|
|
|
185
196
|
if (!pinnedEmail) {
|
|
186
197
|
return null;
|
|
187
198
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// `services/flow-checkouts.ts`) refuse before any Lead read for the same
|
|
195
|
-
// reason. An unverified address is an unproven claim to the pinned one.
|
|
196
|
-
if (!user || !user.emailVerified) {
|
|
199
|
+
// The verified-email read is `services/verified-email.ts` (#5701 D4) — the
|
|
200
|
+
// one this and the four other email-keyed paths share. It returns the
|
|
201
|
+
// address only when `emailVerified === true`, which is what makes a match
|
|
202
|
+
// against the pin a proven claim rather than an asserted one.
|
|
203
|
+
const verified = await findVerifiedEmail(userId);
|
|
204
|
+
if (!verified.ok) {
|
|
197
205
|
return null;
|
|
198
206
|
}
|
|
199
|
-
if (
|
|
207
|
+
if (verified.value.email.trim().toLowerCase() !== pinnedEmail) {
|
|
200
208
|
return null;
|
|
201
209
|
}
|
|
202
210
|
return resolveDefaultTenant(userId);
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
/**
|
|
214
|
+
* The DEPLOYMENT half of the default resolution, extracted so it exists once
|
|
215
|
+
* (#5852 D2). Returns the sole, well-known default Workspace row, or `null`
|
|
216
|
+
* the instant the deployment is not unambiguously one-workspace — a second
|
|
217
|
+
* Workspace exists, none does, or the sole one is not the seeded default.
|
|
218
|
+
*
|
|
219
|
+
* Carries no authentication: it answers "is this deployment one-workspace, and
|
|
220
|
+
* which workspace is it", nothing about the caller. `resolveDefaultTenant`
|
|
221
|
+
* adds the authentication half on top; `services/public-site-resolver.ts`
|
|
222
|
+
* (unauthenticated public reads) uses the deployment half alone. One
|
|
223
|
+
* predicate, reached two ways — never two predicates.
|
|
224
|
+
*
|
|
225
|
+
* The predicate itself is `@working-theory/database/tenancy`'s
|
|
226
|
+
* `resolveDefaultWorkspaceRow` (#5876 spec D2/D3) — this is now a thin
|
|
227
|
+
* pass-through of `apps/api`'s own `prisma` singleton, kept as a named local
|
|
228
|
+
* function (rather than re-exporting the shared one directly) so every
|
|
229
|
+
* existing caller — this file's own `resolveDefaultWorkspaceId` and
|
|
230
|
+
* `resolveDefaultTenant`, and `services/public-site-resolver.ts`'s dynamic
|
|
231
|
+
* import of the wrapper — is untouched. There is exactly one implementation
|
|
232
|
+
* of the one-workspace predicate in the repository (NFR-7); apps/mcp's own
|
|
233
|
+
* resolution (`apps/mcp/src/lib/public-workspace.ts`) delegates to the same
|
|
234
|
+
* shared export.
|
|
235
|
+
*/
|
|
236
|
+
async function resolveDefaultWorkspaceRow(): Promise<{
|
|
237
|
+
id: string;
|
|
238
|
+
organizationId: string;
|
|
239
|
+
} | null> {
|
|
240
|
+
return resolveDefaultWorkspaceRowShared(prisma);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The one-workspace deployment predicate, as a workspace id (#5852 D2).
|
|
245
|
+
* `null` means the deployment is not unambiguously one-workspace, and every
|
|
246
|
+
* caller fails closed on it.
|
|
247
|
+
*/
|
|
248
|
+
export async function resolveDefaultWorkspaceId(): Promise<Id<"workspace"> | null> {
|
|
249
|
+
const workspace = await resolveDefaultWorkspaceRow();
|
|
250
|
+
return workspace ? (workspace.id as Id<"workspace">) : null;
|
|
251
|
+
}
|
|
252
|
+
|
|
205
253
|
/**
|
|
206
254
|
* Resolves the default single-tenant scope when the deployment is
|
|
207
255
|
* unambiguously single-tenant — the default Workspace (`DEFAULT_WORKSPACE_ID`)
|
|
@@ -226,17 +274,8 @@ export async function resolveDefaultTenantForOperator(
|
|
|
226
274
|
export async function resolveDefaultTenant(
|
|
227
275
|
userId: Id<"user">,
|
|
228
276
|
): Promise<TenantContext | null> {
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
const workspace = await prisma.workspace.findUnique({
|
|
234
|
-
where: { id: DEFAULT_WORKSPACE_ID },
|
|
235
|
-
select: { id: true, organizationId: true },
|
|
236
|
-
});
|
|
237
|
-
// The default workspace must hang off the sentinel default Organization
|
|
238
|
-
// (ADR-0031 rule 7): keyed on the reserved-range predicate, not a literal.
|
|
239
|
-
if (!workspace || !isSentinel(workspace.organizationId)) {
|
|
277
|
+
const workspace = await resolveDefaultWorkspaceRow();
|
|
278
|
+
if (!workspace) {
|
|
240
279
|
return null;
|
|
241
280
|
}
|
|
242
281
|
await bindOperatorMembership(userId);
|
|
@@ -45,13 +45,30 @@
|
|
|
45
45
|
* PII boundary: logs carry only `{ siteId, flowSlug, sessionId, outcome }` —
|
|
46
46
|
* never email/asset-key/copy values (mirrors flow-engine.ts's + abandon-
|
|
47
47
|
* email.ts's boundary).
|
|
48
|
+
*
|
|
49
|
+
* #6310 D1/D2/D4/D5/D15 — a non-`playbook:` `assetKey` now resolves against
|
|
50
|
+
* the workspace-scoped `Asset` store FIRST, via the named boundary
|
|
51
|
+
* `lib/deliverable-asset-store.ts` (never a raw `prisma.asset` call here —
|
|
52
|
+
* that access is banned by architecture.yaml's tenant-scope
|
|
53
|
+
* `forbidden_pattern`), falling back to the LEGACY `DeliverableAsset` store
|
|
54
|
+
* on a miss (D3 — existing rows keep delivering, unmigrated). The workspace
|
|
55
|
+
* is read from the magnet's own `FlowDefinition.workspaceId`, ONE
|
|
56
|
+
* `findUnique` issued only on this branch (never for a `playbook:` key, and
|
|
57
|
+
* never before the `no_entry` exit) — a `null`/absent value skips the
|
|
58
|
+
* `Asset` lookup entirely and falls straight to the legacy arm, never
|
|
59
|
+
* guessing a workspace (D5, mirroring #6155 D3's posture). The grant is
|
|
60
|
+
* minted with `workspaceId` ONLY on the `Asset` arm — the same value that
|
|
61
|
+
* authorized the resolution — restoring the download route's serve-time
|
|
62
|
+
* tenancy re-check for this new grant class (D15).
|
|
48
63
|
*/
|
|
49
64
|
|
|
50
65
|
import { sendDeliverableEmail } from "@working-theory/email";
|
|
51
66
|
import { logger } from "@working-theory/observability";
|
|
52
67
|
import { flowConfigSchema, type FlowConfig } from "@working-theory/validation";
|
|
53
68
|
|
|
69
|
+
import { findDeliverableAssetIdByKey } from "../lib/deliverable-asset-store.js";
|
|
54
70
|
import { prisma } from "../lib/prisma.js";
|
|
71
|
+
import { resolveWorkspaceIdForAlias } from "../lib/site.js";
|
|
55
72
|
|
|
56
73
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
57
74
|
|
|
@@ -194,6 +211,15 @@ async function stampOutcome(
|
|
|
194
211
|
now: Date,
|
|
195
212
|
): Promise<void> {
|
|
196
213
|
try {
|
|
214
|
+
// fence: global-key — `DeliverableGrant.id`, the capability token itself
|
|
215
|
+
// (#3961 D5: the grant IS the token). Every caller reaches this line
|
|
216
|
+
// inside an already-scoped frame — `fulfillDeliverable` has just MINTED
|
|
217
|
+
// this id under the session's workspace, and `resendDeliverableGrant` has
|
|
218
|
+
// just read the row back by the same opaque id — so the id is the row's
|
|
219
|
+
// own primary key, not a tenant selector, and a `workspaceId` term here
|
|
220
|
+
// could only re-state what the mint already fenced. It would also stop
|
|
221
|
+
// stamping LEGACY grants, whose `workspaceId` is NULL by the #6254 § 10.1
|
|
222
|
+
// Q2 carve-out, turning a post-mint audit stamp into a silent no-op.
|
|
197
223
|
await prisma.deliverableGrant.update({
|
|
198
224
|
where: { id: grantId },
|
|
199
225
|
data: { sendStatus: outcome, sendAt: now },
|
|
@@ -312,9 +338,17 @@ export async function fulfillDeliverable(
|
|
|
312
338
|
const playbookKey = playbookExperimentKey(entry.assetKey);
|
|
313
339
|
|
|
314
340
|
let assetId: string;
|
|
341
|
+
// #6310 D15 — written ONLY on the Asset arm; stays undefined (no column
|
|
342
|
+
// written) for a playbook: or legacy dasset_ grant.
|
|
343
|
+
let grantWorkspaceId: string | undefined;
|
|
344
|
+
|
|
315
345
|
if (playbookKey !== undefined) {
|
|
346
|
+
// #6257 — `siteId` is the session's own, resolved by the route that
|
|
347
|
+
// opened it (#6254/#6374 AC-7), so `workspaceId` rides beside it: a lead
|
|
348
|
+
// id and an experiment key alone would select a row whose tenant the
|
|
349
|
+
// query never stated.
|
|
316
350
|
const instance = await prisma.playbookInstance.findFirst({
|
|
317
|
-
where: { siteId, leadId, experimentKey: playbookKey },
|
|
351
|
+
where: { siteId, workspaceId: siteId, leadId, experimentKey: playbookKey },
|
|
318
352
|
select: { id: true },
|
|
319
353
|
});
|
|
320
354
|
if (!instance) {
|
|
@@ -323,24 +357,75 @@ export async function fulfillDeliverable(
|
|
|
323
357
|
}
|
|
324
358
|
assetId = entry.assetKey;
|
|
325
359
|
} else {
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
360
|
+
// #6310 D4 — resolve the workspace from the magnet's own live
|
|
361
|
+
// FlowDefinition, ONE query, issued only on this branch. D5 — a null or
|
|
362
|
+
// absent workspaceId skips the Asset lookup entirely; it is never
|
|
363
|
+
// guessed.
|
|
364
|
+
const definition = (await prisma.flowDefinition.findUnique({
|
|
365
|
+
where: { siteId_slug: { siteId, slug: flowSlug } },
|
|
366
|
+
select: { workspaceId: true },
|
|
367
|
+
})) as { workspaceId: string | null } | null;
|
|
368
|
+
const workspaceId = definition?.workspaceId ?? null;
|
|
369
|
+
|
|
370
|
+
const resolvedAssetId =
|
|
371
|
+
workspaceId !== null
|
|
372
|
+
? await findDeliverableAssetIdByKey(workspaceId, entry.assetKey)
|
|
373
|
+
: null;
|
|
374
|
+
|
|
375
|
+
if (resolvedAssetId !== null && workspaceId !== null) {
|
|
376
|
+
// #6310 D2 — Asset resolves first; D15 — the grant carries the SAME
|
|
377
|
+
// workspaceId that authorized this resolution.
|
|
378
|
+
assetId = resolvedAssetId;
|
|
379
|
+
grantWorkspaceId = workspaceId;
|
|
380
|
+
} else {
|
|
381
|
+
// #6310 D2/D3 — legacy fallback. A missing row (config references an
|
|
382
|
+
// un-uploaded key) → same skip-and-log posture as before.
|
|
383
|
+
// #6257 — the LEGACY store's read, scoped by the same resolved
|
|
384
|
+
// workspace id the `Asset` arm above already resolves through
|
|
385
|
+
// `lib/deliverable-asset-store.ts`. The compound `[siteId, key]` names
|
|
386
|
+
// one row; `workspaceId` is what makes the tenant the QUERY's statement.
|
|
387
|
+
const asset = (await prisma.deliverableAsset.findUnique({
|
|
388
|
+
where: { siteId_key: { siteId, key: entry.assetKey }, workspaceId: siteId },
|
|
389
|
+
})) as DeliverableAssetRow | null;
|
|
390
|
+
|
|
391
|
+
if (!asset) {
|
|
392
|
+
logOutcome(logCtx, "asset_missing");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
assetId = asset.id;
|
|
335
396
|
}
|
|
336
|
-
assetId = asset.id;
|
|
337
397
|
}
|
|
338
398
|
|
|
339
399
|
// D5: the grant IS the token — minted before the send is attempted so a
|
|
340
400
|
// later email-config gap or send failure never wastes it (a later
|
|
341
401
|
// manual/operator resend can reuse the same grant id).
|
|
342
402
|
const grant = (await prisma.deliverableGrant.create({
|
|
343
|
-
data: {
|
|
403
|
+
data: {
|
|
404
|
+
siteId,
|
|
405
|
+
assetId,
|
|
406
|
+
leadId,
|
|
407
|
+
sessionId,
|
|
408
|
+
// #6374 D4 / AC-8 — the SECOND DeliverableGrant mint site. The column
|
|
409
|
+
// stays NULLABLE for LEGACY rows (NULL is the serve path's fail-closed
|
|
410
|
+
// sentinel, #6254 § 10.1 Q2) and the migration touches none of them —
|
|
411
|
+
// but a grant minted HERE always knows its workspace, so it is never
|
|
412
|
+
// left to fail closed.
|
|
413
|
+
//
|
|
414
|
+
// `grantWorkspaceId` is #6310 D15's value, resolved from the magnet's
|
|
415
|
+
// own FlowDefinition on the `asset_` arm; that read is scoped by
|
|
416
|
+
// `siteId`, and the keystone's `flow_definitions_workspace_site_agree`
|
|
417
|
+
// CHECK makes `workspaceId = siteId` on that row, so the two are the
|
|
418
|
+
// SAME value and the `??` is a fallback for the legacy `dasset_` and
|
|
419
|
+
// virtual `playbook:` arms rather than a choice between two scopes.
|
|
420
|
+
// The new `deliverable_grants_workspace_site_agree` CHECK would reject
|
|
421
|
+
// a disagreement loudly either way.
|
|
422
|
+
//
|
|
423
|
+
// This does NOT change which bytes the download route serves: that
|
|
424
|
+
// route branches on the `assetId` PREFIX, and consults
|
|
425
|
+
// `grant.workspaceId` only on the `asset_` arm, where this value is
|
|
426
|
+
// what it already was.
|
|
427
|
+
workspaceId: grantWorkspaceId ?? siteId,
|
|
428
|
+
},
|
|
344
429
|
select: { id: true },
|
|
345
430
|
})) as { id: string };
|
|
346
431
|
|
|
@@ -400,6 +485,13 @@ type FlowSessionConfigRow = {
|
|
|
400
485
|
export async function resendDeliverableGrant(
|
|
401
486
|
grantId: string,
|
|
402
487
|
): Promise<ResendGrantResult> {
|
|
488
|
+
// fence: global-key — `DeliverableGrant.id`, the capability token itself
|
|
489
|
+
// (#3961 D5). This is the resolver: the operator hands the
|
|
490
|
+
// `deliverable:resend` CLI one opaque grant id and nothing else, and the
|
|
491
|
+
// WORKSPACE is what this read produces (`grant.siteId`, carried into
|
|
492
|
+
// `sendAndStamp` below), so a tenant term here would have to come from the
|
|
493
|
+
// answer it is computing. Holding the id is the capability, exactly as it
|
|
494
|
+
// is for the download route this resend re-sends a link to.
|
|
403
495
|
const grant = (await prisma.deliverableGrant.findUnique({
|
|
404
496
|
where: { id: grantId },
|
|
405
497
|
select: { id: true, siteId: true, leadId: true, sessionId: true },
|
|
@@ -514,9 +606,45 @@ type DeliverableGrantListRow = {
|
|
|
514
606
|
export async function listSendlessGrants(
|
|
515
607
|
opts: ListSendlessGrantsOptions,
|
|
516
608
|
): Promise<SendlessGrantRow[]> {
|
|
609
|
+
// #6257 — `opts.siteId` is the one value in this service that arrives as a
|
|
610
|
+
// RAW ALIAS: the `deliverable:resend` CLI passes `--site` through verbatim.
|
|
611
|
+
// It is resolved here, once, the way `dunning-policy.ts` resolves its own
|
|
612
|
+
// parameter — an alias that names no workspace lists nothing rather than
|
|
613
|
+
// falling back to the unscoped read it used to make.
|
|
614
|
+
const workspaceId = await resolveWorkspaceIdForAlias(opts.siteId);
|
|
615
|
+
if (workspaceId === null) {
|
|
616
|
+
logger.warn(
|
|
617
|
+
{ siteId: opts.siteId, outcome: "site_unresolved" },
|
|
618
|
+
"[deliverable-fulfillment] --site names no workspace — listing nothing",
|
|
619
|
+
);
|
|
620
|
+
return [];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// #6257 — LEGACY grants are NOT in this list, and the narrowing is stated
|
|
624
|
+
// rather than left for an operator to discover. Two terms exclude them: the
|
|
625
|
+
// `workspaceId` filter below (their column is NULL by the #6254 § 10.1 Q2
|
|
626
|
+
// carve-out) and the resolved `siteId` (the keystone migration deliberately
|
|
627
|
+
// does NOT normalize `deliverable_grants.site_id` — migration.sql:1059 — so
|
|
628
|
+
// a legacy row on a `"default"`-aliased deployment still carries the alias).
|
|
629
|
+
//
|
|
630
|
+
// This is a genuine TIGHTENING beyond the serve path, not parity with it:
|
|
631
|
+
// `routes/deliverables/index.ts` consults `grant.workspaceId` only on the
|
|
632
|
+
// `asset_` arm, so a legacy `dasset_` or `playbook:` grant still downloads.
|
|
633
|
+
// The fence forces it — AC-2 forbids keying this read on the raw alias the
|
|
634
|
+
// CLI was given, and a legacy row carries no workspace key to resolve — so
|
|
635
|
+
// the list is narrower by construction and the recovery path for such a row
|
|
636
|
+
// is `deliverable:resend --grant <id>`, which is unaffected.
|
|
637
|
+
logger.info(
|
|
638
|
+
{ siteId: workspaceId, outcome: "legacy_grants_excluded" },
|
|
639
|
+
"[deliverable-fulfillment] grants with no workspace key are NOT listed — resend one by id with --grant",
|
|
640
|
+
);
|
|
641
|
+
|
|
517
642
|
const rows = (await prisma.deliverableGrant.findMany({
|
|
518
643
|
where: {
|
|
519
|
-
siteId:
|
|
644
|
+
siteId: workspaceId,
|
|
645
|
+
// #6257 — the fence term. See the note above for what it excludes and
|
|
646
|
+
// why that is a tightening rather than serve-path parity.
|
|
647
|
+
workspaceId,
|
|
520
648
|
sendStatus: null,
|
|
521
649
|
...(opts.since !== undefined && { createdAt: { gte: opts.since } }),
|
|
522
650
|
},
|