create-ailk 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -8
- package/component-catalog.json +336 -27
- package/component-catalog.md +25 -12
- package/dist/lib/api-boot-assertion.d.ts +46 -0
- package/dist/lib/api-boot-assertion.js +70 -0
- package/dist/lib/import-graph.d.ts +29 -0
- package/dist/lib/import-graph.js +140 -0
- package/dist/module-architecture.d.ts +1 -0
- package/dist/module-architecture.js +253 -127
- 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/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/TEMPLATE-MANIFEST.json +2 -2
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +1 -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/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/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/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__/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/telemetry-scrub.ts +5 -1
- package/templates/apps/web/messages/en.json +187 -0
- 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 +61 -0
- 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 +142 -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 +1 -1
- 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/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
|
@@ -10,10 +10,12 @@ module.exports = {
|
|
|
10
10
|
// ts-jest compiles it directly (#3958) — mirrors apps/api/jest.config.cjs's
|
|
11
11
|
// identical mapping. The published dist is ESM-only and this package's
|
|
12
12
|
// CJS ts-jest transform can't load it; database/flows/presets.ts's test
|
|
13
|
-
// needs the real flowConfigSchema value at runtime
|
|
14
|
-
// type-only one — unlike ../tenancy.ts's `import type { Id }` from
|
|
15
|
-
// @working-theory/ids, which erases to nothing and needs no mapping).
|
|
13
|
+
// needs the real flowConfigSchema value at runtime.
|
|
16
14
|
"^@working-theory/validation$": "<rootDir>/../packages/validation/src/index.ts",
|
|
15
|
+
// ../tenancy.ts's `isSentinel` import from @working-theory/ids became a
|
|
16
|
+
// VALUE import (#5876 spec D2/D11) — no longer type-only, so it DOES need
|
|
17
|
+
// a mapping now, same reason as the validation mapping above.
|
|
18
|
+
"^@working-theory/ids$": "<rootDir>/../packages/ids/src/index.ts",
|
|
17
19
|
"^(\\.{1,2}/.*)\\.js$": "$1",
|
|
18
20
|
},
|
|
19
21
|
transform: {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- The founder's "how are you solving it?" answer on `project_listings` (#5096).
|
|
2
|
+
--
|
|
3
|
+
-- `problem` (step five) has had a column since the surface shipped; its
|
|
4
|
+
-- narrative other half has not, so samhenry PR #324's step six had nowhere to
|
|
5
|
+
-- write.
|
|
6
|
+
--
|
|
7
|
+
-- ONE NULLABLE COLUMN, NO DEFAULT, NO BACKFILL, NO CHANGE TO ANY EXISTING ONE.
|
|
8
|
+
-- Additive and opt-in: the field is absent from the platform's default
|
|
9
|
+
-- `collectedFields` mask (#5096 DD1), so no existing site can have it written
|
|
10
|
+
-- until that site names it. Every row written before this migration keeps a
|
|
11
|
+
-- NULL here, which the row schema already allows for every application field
|
|
12
|
+
-- (#5036 D1 — a half-filled draft is a legitimate row).
|
|
13
|
+
--
|
|
14
|
+
-- Rollback: ALTER TABLE "project_listings" DROP COLUMN "solution";
|
|
15
|
+
ALTER TABLE "project_listings" ADD COLUMN "solution" TEXT;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
-- #5800 — the Artifact content store: roadmap D1 (generalize `Artifact`) and
|
|
2
|
+
-- D7 (`Asset`, workspace-scoped).
|
|
3
|
+
--
|
|
4
|
+
-- D1's one sentence: THE SESSION BECOMES PROVENANCE, THE WORKSPACE OWNS THE
|
|
5
|
+
-- ARTIFACT. Today `artifacts.session_id` is NOT NULL behind an ON DELETE
|
|
6
|
+
-- CASCADE, so deleting a conversation deletes the pages it produced. After this
|
|
7
|
+
-- migration the column is nullable behind ON DELETE SET NULL: the session is
|
|
8
|
+
-- where the artifact came from, not what keeps it alive.
|
|
9
|
+
--
|
|
10
|
+
-- The relaxation is bracketed by ZERO-ROW ASSERTIONS. It applies to an empty
|
|
11
|
+
-- database and to a populated one, and each assertion names a specific way the
|
|
12
|
+
-- relaxation could have lost data. A failed assertion aborts the transaction
|
|
13
|
+
-- with a message that says which rows to look at — the migration never proceeds
|
|
14
|
+
-- on a silent surprise, and it never backfills its way past one.
|
|
15
|
+
--
|
|
16
|
+
-- The rest is additive: four nullable-or-defaulted columns, two indexes, and a
|
|
17
|
+
-- new `assets` table. `content_type` is BACKFILLED from the legacy `type`
|
|
18
|
+
-- column rather than replacing it, because
|
|
19
|
+
-- apps/api/src/routes/workspaces/sessions.ts (a hard-floor surface) reads
|
|
20
|
+
-- `type` and must keep working unchanged; the two agree row-for-row when this
|
|
21
|
+
-- migration commits, and retiring `type` belongs to the card that moves that
|
|
22
|
+
-- route.
|
|
23
|
+
--
|
|
24
|
+
-- Rollback: drop the two indexes and the four columns, drop "assets", restore
|
|
25
|
+
-- the FK as NOT NULL + ON DELETE CASCADE. That restore is only safe while no
|
|
26
|
+
-- artifact has yet outlived its session.
|
|
27
|
+
|
|
28
|
+
-- ── PRE-ASSERTIONS (zero rows, or this migration does not run) ──────────────
|
|
29
|
+
DO $$
|
|
30
|
+
DECLARE
|
|
31
|
+
already_null bigint;
|
|
32
|
+
orphaned bigint;
|
|
33
|
+
BEGIN
|
|
34
|
+
-- "session_id" is NOT NULL today. A non-zero count here means the column is
|
|
35
|
+
-- not the shape this migration was written against — stop rather than guess.
|
|
36
|
+
SELECT count(*) INTO already_null FROM "artifacts" WHERE "session_id" IS NULL;
|
|
37
|
+
IF already_null <> 0 THEN
|
|
38
|
+
RAISE EXCEPTION
|
|
39
|
+
'#5800 pre-assertion: % artifact row(s) already carry a NULL session_id before the FK was relaxed; the column was expected NOT NULL',
|
|
40
|
+
already_null;
|
|
41
|
+
END IF;
|
|
42
|
+
|
|
43
|
+
-- The outgoing FK is what guarantees this is zero. Proving it BEFORE the
|
|
44
|
+
-- swap is what lets the post-assertion below attribute any orphan to the
|
|
45
|
+
-- swap itself.
|
|
46
|
+
SELECT count(*) INTO orphaned
|
|
47
|
+
FROM "artifacts" a
|
|
48
|
+
LEFT JOIN "agent_sessions" s ON s."id" = a."session_id"
|
|
49
|
+
WHERE s."id" IS NULL;
|
|
50
|
+
IF orphaned <> 0 THEN
|
|
51
|
+
RAISE EXCEPTION
|
|
52
|
+
'#5800 pre-assertion: % artifact row(s) point at an agent_sessions row that does not exist',
|
|
53
|
+
orphaned;
|
|
54
|
+
END IF;
|
|
55
|
+
END $$;
|
|
56
|
+
|
|
57
|
+
-- The census the post-assertion compares against. ON COMMIT DROP: `prisma
|
|
58
|
+
-- migrate` runs each migration in one transaction, so this disappears with it.
|
|
59
|
+
CREATE TEMP TABLE "_5800_artifact_census" ON COMMIT DROP AS
|
|
60
|
+
SELECT count(*) AS n FROM "artifacts";
|
|
61
|
+
|
|
62
|
+
-- ── The FK relaxation (D1) ─────────────────────────────────────────────────
|
|
63
|
+
-- DropForeignKey
|
|
64
|
+
ALTER TABLE "artifacts" DROP CONSTRAINT "artifacts_session_id_fkey";
|
|
65
|
+
|
|
66
|
+
-- AlterTable
|
|
67
|
+
ALTER TABLE "artifacts" ADD COLUMN "content_type" TEXT NOT NULL DEFAULT 'text/document',
|
|
68
|
+
ADD COLUMN "locale" TEXT NOT NULL DEFAULT 'en',
|
|
69
|
+
ADD COLUMN "published_version_id" TEXT,
|
|
70
|
+
ADD COLUMN "slug" TEXT,
|
|
71
|
+
ALTER COLUMN "session_id" DROP NOT NULL;
|
|
72
|
+
|
|
73
|
+
-- AddForeignKey
|
|
74
|
+
ALTER TABLE "artifacts" ADD CONSTRAINT "artifacts_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "agent_sessions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
75
|
+
|
|
76
|
+
-- Backfill: the new discriminator starts life agreeing with the legacy one, so
|
|
77
|
+
-- a pre-existing chat artifact reads identically through either column.
|
|
78
|
+
UPDATE "artifacts" SET "content_type" = "type";
|
|
79
|
+
|
|
80
|
+
-- ── POST-ASSERTIONS (zero rows lost, zero rows nulled, zero drift) ─────────
|
|
81
|
+
DO $$
|
|
82
|
+
DECLARE
|
|
83
|
+
before_n bigint;
|
|
84
|
+
after_n bigint;
|
|
85
|
+
nulled bigint;
|
|
86
|
+
drift bigint;
|
|
87
|
+
BEGIN
|
|
88
|
+
SELECT n INTO before_n FROM "_5800_artifact_census";
|
|
89
|
+
SELECT count(*) INTO after_n FROM "artifacts";
|
|
90
|
+
IF before_n <> after_n THEN
|
|
91
|
+
RAISE EXCEPTION
|
|
92
|
+
'#5800 post-assertion: artifact count changed across the FK relaxation (% -> %)',
|
|
93
|
+
before_n, after_n;
|
|
94
|
+
END IF;
|
|
95
|
+
|
|
96
|
+
-- Relaxing the constraint must not itself null anyone's provenance. Rows
|
|
97
|
+
-- become NULL later, one at a time, as sessions are deleted.
|
|
98
|
+
SELECT count(*) INTO nulled FROM "artifacts" WHERE "session_id" IS NULL;
|
|
99
|
+
IF nulled <> 0 THEN
|
|
100
|
+
RAISE EXCEPTION
|
|
101
|
+
'#5800 post-assertion: relaxing the FK left % artifact row(s) with a NULL session_id',
|
|
102
|
+
nulled;
|
|
103
|
+
END IF;
|
|
104
|
+
|
|
105
|
+
SELECT count(*) INTO drift
|
|
106
|
+
FROM "artifacts" WHERE "content_type" IS DISTINCT FROM "type";
|
|
107
|
+
IF drift <> 0 THEN
|
|
108
|
+
RAISE EXCEPTION
|
|
109
|
+
'#5800 post-assertion: % artifact row(s) disagree between content_type and the legacy type column',
|
|
110
|
+
drift;
|
|
111
|
+
END IF;
|
|
112
|
+
END $$;
|
|
113
|
+
|
|
114
|
+
-- ── Content-store identity + the list index (D1) ───────────────────────────
|
|
115
|
+
-- CreateIndex
|
|
116
|
+
CREATE INDEX "artifacts_workspace_id_content_type_updated_at_idx" ON "artifacts"("workspace_id", "content_type", "updated_at");
|
|
117
|
+
|
|
118
|
+
-- CreateIndex
|
|
119
|
+
-- `slug` is NULL until publish and NULLs never collide in a Postgres unique
|
|
120
|
+
-- index, so a workspace may hold any number of unpublished drafts while
|
|
121
|
+
-- exactly one artifact may claim a given published path.
|
|
122
|
+
CREATE UNIQUE INDEX "artifacts_workspace_id_content_type_locale_slug_key" ON "artifacts"("workspace_id", "content_type", "locale", "slug");
|
|
123
|
+
|
|
124
|
+
-- ── Asset (D7) ─────────────────────────────────────────────────────────────
|
|
125
|
+
-- Shaped after `deliverable_assets` (20260811050000_add_deliverable_models):
|
|
126
|
+
-- the bytes live in the row. Same CreateTable + CreateIndex + ADR-0031 rule-3
|
|
127
|
+
-- id-format completion in ONE migration as
|
|
128
|
+
-- 20260911180000_consent_grants, since the table is new and needs no retrofit.
|
|
129
|
+
-- CreateTable
|
|
130
|
+
CREATE TABLE "assets" (
|
|
131
|
+
"id" TEXT NOT NULL DEFAULT ('asset_'::text || (uuidv7())::text),
|
|
132
|
+
"workspace_id" TEXT NOT NULL,
|
|
133
|
+
"key" TEXT NOT NULL,
|
|
134
|
+
"filename" TEXT NOT NULL,
|
|
135
|
+
"content_type" TEXT NOT NULL,
|
|
136
|
+
"bytes" BYTEA NOT NULL,
|
|
137
|
+
"size" INTEGER NOT NULL,
|
|
138
|
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
139
|
+
|
|
140
|
+
CONSTRAINT "assets_pkey" PRIMARY KEY ("id")
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
-- CreateIndex
|
|
144
|
+
CREATE INDEX "assets_workspace_id_idx" ON "assets"("workspace_id");
|
|
145
|
+
|
|
146
|
+
-- CreateIndex
|
|
147
|
+
CREATE UNIQUE INDEX "assets_workspace_id_key_key" ON "assets"("workspace_id", "key");
|
|
148
|
+
|
|
149
|
+
-- ADR-0031 rule 3 — assets: asset_ (registered in
|
|
150
|
+
-- packages/ids/src/registry.ts ENTITY_PREFIX.asset; this migration's path is
|
|
151
|
+
-- added to migration-sync.test.ts's PK_CHECK_MIGRATIONS). workspace_id is a
|
|
152
|
+
-- relation-free scope column (ADR-0009 invariant 1) and gets the same CHECK
|
|
153
|
+
-- every other content-namespace table's does.
|
|
154
|
+
ALTER TABLE "assets" ALTER COLUMN "id" TYPE text COLLATE "C";
|
|
155
|
+
ALTER TABLE "assets" ALTER COLUMN "id" SET DEFAULT ('asset_'::text || (uuidv7())::text);
|
|
156
|
+
ALTER TABLE "assets" ADD CONSTRAINT "assets_id_format" CHECK ("id" ~ '^asset_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
157
|
+
ALTER TABLE "assets" ALTER COLUMN "workspace_id" TYPE text COLLATE "C";
|
|
158
|
+
ALTER TABLE "assets" ADD CONSTRAINT "assets_workspace_id_format" CHECK ("workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- #5970 — request-and-confirm: the callback row gains a confirm/pay lifecycle
|
|
2
|
+
-- (D-67 / SUB-100, spec docs/specs/5970-request-and-confirm-spec.md § 4.3).
|
|
3
|
+
--
|
|
4
|
+
-- Eleven nullable columns and two indexes on "callbacks". Every column is
|
|
5
|
+
-- NULL-able with no default, so on Postgres this ALTER TABLE is metadata-only:
|
|
6
|
+
-- no table rewrite, no backfill, no lock held for the length of a scan.
|
|
7
|
+
--
|
|
8
|
+
-- Nothing here relaxes a constraint and nothing here backfills, so — unlike
|
|
9
|
+
-- 20260916120000_artifact_content_store — this migration carries no zero-row
|
|
10
|
+
-- pre-assertion block: there is no way for it to lose a row.
|
|
11
|
+
--
|
|
12
|
+
-- The two indexes are the webhook stamp's `where` (spec § D8). Without them
|
|
13
|
+
-- every checkout.session.completed carrying a payment_link would sequential-
|
|
14
|
+
-- scan "callbacks" on a hot path. "stripe_checkout_session_id" carries no
|
|
15
|
+
-- index deliberately: nothing queries by it — it is a support-lookup column.
|
|
16
|
+
--
|
|
17
|
+
-- Rollback: drop the two indexes and the eleven columns. Safe unconditionally
|
|
18
|
+
-- while nothing but #5970's own code reads them.
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "callbacks"
|
|
21
|
+
ADD COLUMN "confirm_token_hash" TEXT,
|
|
22
|
+
ADD COLUMN "confirm_token_expires_at" TIMESTAMP(3),
|
|
23
|
+
ADD COLUMN "confirmed_at" TIMESTAMP(3),
|
|
24
|
+
ADD COLUMN "confirmed_time" TEXT,
|
|
25
|
+
ADD COLUMN "buyer_email" TEXT,
|
|
26
|
+
ADD COLUMN "amount_minor" INTEGER,
|
|
27
|
+
ADD COLUMN "currency" TEXT,
|
|
28
|
+
ADD COLUMN "stripe_payment_link_id" TEXT,
|
|
29
|
+
ADD COLUMN "stripe_checkout_session_id" TEXT,
|
|
30
|
+
ADD COLUMN "stripe_invoice_id" TEXT,
|
|
31
|
+
ADD COLUMN "paid_at" TIMESTAMP(3);
|
|
32
|
+
|
|
33
|
+
-- The digest is unique: two rows sharing one is a mint bug, and the constraint
|
|
34
|
+
-- is what turns that bug into a write failure instead of a silent cross-row
|
|
35
|
+
-- credential.
|
|
36
|
+
CREATE UNIQUE INDEX "callbacks_confirm_token_hash_key" ON "callbacks"("confirm_token_hash");
|
|
37
|
+
|
|
38
|
+
CREATE INDEX "callbacks_stripe_payment_link_id_idx" ON "callbacks"("stripe_payment_link_id");
|
|
39
|
+
|
|
40
|
+
CREATE INDEX "callbacks_stripe_invoice_id_idx" ON "callbacks"("stripe_invoice_id");
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- #6085 — the Appointment model: a Cal.com booking normalized from the new
|
|
2
|
+
-- webhook receiver (brief H7, spec docs/specs/6085-appointments-from-cal-com-webhooks-spec.md § 4.3).
|
|
3
|
+
--
|
|
4
|
+
-- One new table, three indexes, in the house style of
|
|
5
|
+
-- 20260903010000_add_playbook_instance (CreateTable + CreateIndex immediately
|
|
6
|
+
-- followed by the ADR-0031 rule-3 id-format completion, in ONE migration
|
|
7
|
+
-- since the table is new and needs no retrofit).
|
|
8
|
+
--
|
|
9
|
+
-- The unique index over (site_id, provider, provider_booking_uid) is the
|
|
10
|
+
-- LOAD-BEARING line of this migration: it is the spec's own idempotency key
|
|
11
|
+
-- (§ D4) — a table without it makes "exactly one Appointment row per booking
|
|
12
|
+
-- uid" unenforceable, and § D7(a)'s guarded-write shape races on precisely
|
|
13
|
+
-- this index.
|
|
14
|
+
--
|
|
15
|
+
-- No backfill, no constraint relaxed, no existing table touched, so this
|
|
16
|
+
-- migration carries no zero-row pre-assertion block — there is no way for it
|
|
17
|
+
-- to lose a row.
|
|
18
|
+
--
|
|
19
|
+
-- Rollback: DROP TABLE "appointments". Safe unconditionally while nothing but
|
|
20
|
+
-- #6085's own code reads it.
|
|
21
|
+
|
|
22
|
+
-- CreateTable
|
|
23
|
+
CREATE TABLE "appointments" (
|
|
24
|
+
"id" TEXT NOT NULL DEFAULT ('appt_'::text || (uuidv7())::text),
|
|
25
|
+
"site_id" VARCHAR(200) NOT NULL,
|
|
26
|
+
"provider_booking_uid" VARCHAR(200) NOT NULL,
|
|
27
|
+
"provider" VARCHAR(32) NOT NULL DEFAULT 'cal',
|
|
28
|
+
"start_time" TIMESTAMP(3) NOT NULL,
|
|
29
|
+
"end_time" TIMESTAMP(3) NOT NULL,
|
|
30
|
+
"title" VARCHAR(300),
|
|
31
|
+
"attendee_name" VARCHAR(120) NOT NULL,
|
|
32
|
+
"attendee_email" VARCHAR(254) NOT NULL,
|
|
33
|
+
"attendee_time_zone" VARCHAR(64),
|
|
34
|
+
"status" VARCHAR(32) NOT NULL,
|
|
35
|
+
"paid" BOOLEAN NOT NULL DEFAULT false,
|
|
36
|
+
"payment_initiated_at" TIMESTAMP(3),
|
|
37
|
+
"no_show" BOOLEAN NOT NULL DEFAULT false,
|
|
38
|
+
"cancelled_at" TIMESTAMP(3),
|
|
39
|
+
"lifecycle_event_at" TIMESTAMP(3) NOT NULL,
|
|
40
|
+
"payment_event_at" TIMESTAMP(3),
|
|
41
|
+
"no_show_event_at" TIMESTAMP(3),
|
|
42
|
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
43
|
+
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
44
|
+
|
|
45
|
+
CONSTRAINT "appointments_pkey" PRIMARY KEY ("id")
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
-- CreateIndex
|
|
49
|
+
-- The idempotency key (§ D4) — see the file header. `provider` is included
|
|
50
|
+
-- per § 9 SEC-10 so a second scheduling provider's uid space cannot collide
|
|
51
|
+
-- with this one's (#4288).
|
|
52
|
+
CREATE UNIQUE INDEX "appointments_site_id_provider_provider_booking_uid_key" ON "appointments"("site_id", "provider", "provider_booking_uid");
|
|
53
|
+
|
|
54
|
+
-- CreateIndex
|
|
55
|
+
-- The read's `where` + `order by` (§ D6). Without it every appointments page
|
|
56
|
+
-- load sequential-scans "appointments".
|
|
57
|
+
CREATE INDEX "appointments_site_id_start_time_idx" ON "appointments"("site_id", "start_time");
|
|
58
|
+
|
|
59
|
+
-- CreateIndex
|
|
60
|
+
CREATE INDEX "appointments_created_at_idx" ON "appointments"("created_at");
|
|
61
|
+
|
|
62
|
+
-- ADR-0031 rule 3 — appointments: appt_ (registered in
|
|
63
|
+
-- packages/ids/src/registry.ts ENTITY_PREFIX.appointment; this migration's
|
|
64
|
+
-- path is added to migration-sync.test.ts's PK_CHECK_MIGRATIONS).
|
|
65
|
+
ALTER TABLE "appointments" ALTER COLUMN "id" TYPE text COLLATE "C";
|
|
66
|
+
ALTER TABLE "appointments" ALTER COLUMN "id" SET DEFAULT ('appt_'::text || (uuidv7())::text);
|
|
67
|
+
ALTER TABLE "appointments" ADD CONSTRAINT "appointments_id_format" CHECK ("id" ~ '^appt_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
-- #6092 — S4's data tier: an asset carries title, description and tags.
|
|
2
|
+
--
|
|
3
|
+
-- Today `assets` holds bytes and a filename and nothing the owner wrote
|
|
4
|
+
-- (decisions.html D10: "an asset has a filename and dimensions only"). The
|
|
5
|
+
-- lightbox edits three things in place — a title, a description and a set of
|
|
6
|
+
-- tags — so the table gains exactly those three columns.
|
|
7
|
+
--
|
|
8
|
+
-- STRICTLY ADDITIVE. Nothing is dropped, renamed, relaxed or backfilled. The
|
|
9
|
+
-- `(workspace_id, key)` identity, the `assets_id_format` and
|
|
10
|
+
-- `assets_workspace_id_format` CHECKs and the `asset_`+UUIDv7 default are all
|
|
11
|
+
-- untouched, so a pre-existing row stays valid and an INSERT written before
|
|
12
|
+
-- this migration keeps working unchanged.
|
|
13
|
+
--
|
|
14
|
+
-- `tags` is TEXT[] with an empty-array default rather than a nullable column:
|
|
15
|
+
-- an untagged asset is an empty set, not an unknown one, and a NULL would give
|
|
16
|
+
-- two spellings of the same state. It carries tag SLUGS, never the human
|
|
17
|
+
-- labels, and it is deliberately UNCONSTRAINED — two vocabularies ride it (the
|
|
18
|
+
-- twelve asset tags and D9's four document tags, since a document is a row in
|
|
19
|
+
-- this same store), and the "fixed set" of D10 is a surface affordance. A tag
|
|
20
|
+
-- whose slug a surface cannot resolve is that surface's problem to render, not
|
|
21
|
+
-- a reason to reject an upload.
|
|
22
|
+
--
|
|
23
|
+
-- `title` and `description` are nullable with no default: NULL means the owner
|
|
24
|
+
-- has not written one, which is distinct from an empty string meaning they
|
|
25
|
+
-- cleared it. `title` is NOT backfilled from `filename` — they are different
|
|
26
|
+
-- facts and the lightbox shows both.
|
|
27
|
+
--
|
|
28
|
+
-- Rollback: ALTER TABLE "assets" DROP COLUMN "tags", DROP COLUMN
|
|
29
|
+
-- "description", DROP COLUMN "title". Safe unconditionally; the columns carry
|
|
30
|
+
-- no constraint and nothing references them.
|
|
31
|
+
|
|
32
|
+
-- The census the post-assertion compares against. ON COMMIT DROP: `prisma
|
|
33
|
+
-- migrate` runs each migration in one transaction, so this disappears with it.
|
|
34
|
+
CREATE TEMP TABLE "_6092_asset_census" ON COMMIT DROP AS
|
|
35
|
+
SELECT count(*) AS n FROM "assets";
|
|
36
|
+
|
|
37
|
+
-- AlterTable
|
|
38
|
+
ALTER TABLE "assets" ADD COLUMN "title" TEXT,
|
|
39
|
+
ADD COLUMN "description" TEXT,
|
|
40
|
+
ADD COLUMN "tags" TEXT[] DEFAULT ARRAY[]::TEXT[];
|
|
41
|
+
|
|
42
|
+
-- ── POST-ASSERTIONS (no row lost; every pre-existing row reads back empty) ──
|
|
43
|
+
-- This is AC-1's second half, asserted against the rows the target database
|
|
44
|
+
-- actually holds rather than against a fixture's.
|
|
45
|
+
DO $$
|
|
46
|
+
DECLARE
|
|
47
|
+
before_n bigint;
|
|
48
|
+
after_n bigint;
|
|
49
|
+
written bigint;
|
|
50
|
+
BEGIN
|
|
51
|
+
SELECT n INTO before_n FROM "_6092_asset_census";
|
|
52
|
+
SELECT count(*) INTO after_n FROM "assets";
|
|
53
|
+
IF before_n <> after_n THEN
|
|
54
|
+
RAISE EXCEPTION
|
|
55
|
+
'#6092 post-assertion: asset count changed across a purely additive migration (% -> %)',
|
|
56
|
+
before_n, after_n;
|
|
57
|
+
END IF;
|
|
58
|
+
|
|
59
|
+
-- Adding the columns must not itself author metadata for anyone. Rows gain a
|
|
60
|
+
-- title, a description or a tag later, one at a time, from the lightbox.
|
|
61
|
+
SELECT count(*) INTO written FROM "assets"
|
|
62
|
+
WHERE "title" IS NOT NULL
|
|
63
|
+
OR "description" IS NOT NULL
|
|
64
|
+
OR "tags" IS DISTINCT FROM ARRAY[]::TEXT[];
|
|
65
|
+
IF written <> 0 THEN
|
|
66
|
+
RAISE EXCEPTION
|
|
67
|
+
'#6092 post-assertion: % pre-existing asset row(s) came out of the migration with metadata already set',
|
|
68
|
+
written;
|
|
69
|
+
END IF;
|
|
70
|
+
END $$;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
-- #6155 — FlowDefinition and FlowSession gain a workspace isolation key.
|
|
2
|
+
--
|
|
3
|
+
-- The ownership brief (docs/internal/decisions/workspace-owns-data-surfaces-render.md)
|
|
4
|
+
-- and AD-2 (docs/internal/decisions/flow-engine-admin-surface.md): a workspace
|
|
5
|
+
-- owns data, and the flow engine's tenant-scope fence grows to cover it.
|
|
6
|
+
-- `siteId` stays a legacy alias for the public path (unchanged); `workspaceId`
|
|
7
|
+
-- is the isolation key every authenticated read/write will go through
|
|
8
|
+
-- (apps/api/src/lib/tenant-db.ts's new `flows`/`flowSessions` accessors).
|
|
9
|
+
--
|
|
10
|
+
-- NULLABLE, no default (spec D3) — a row whose `siteId` matches no workspace
|
|
11
|
+
-- is left UNSET rather than guessed, and stays invisible to every accessor
|
|
12
|
+
-- rather than visible to the wrong tenant (the same "fails empty, never
|
|
13
|
+
-- wide" posture apps/api/src/services/leads-inbox.ts already takes).
|
|
14
|
+
--
|
|
15
|
+
-- RE-RUNNABLE (NFR3): `ADD COLUMN IF NOT EXISTS` / `CREATE INDEX IF NOT
|
|
16
|
+
-- EXISTS` cover the column and index. Postgres has no
|
|
17
|
+
-- `ADD CONSTRAINT IF NOT EXISTS`, so each format CHECK is wrapped in its own
|
|
18
|
+
-- existence-guarded `DO $$` block — the stated exception to NFR3's "in the
|
|
19
|
+
-- house style of 20260918120000_asset_metadata_columns" note: that
|
|
20
|
+
-- precedent (and 20260713192429_prefixed_uuid_entity_ids's own CHECK
|
|
21
|
+
-- additions) rely on `prisma migrate`'s per-migration transaction and
|
|
22
|
+
-- ledger, not on idempotent DDL — the guard below is this migration's own
|
|
23
|
+
-- addition.
|
|
24
|
+
--
|
|
25
|
+
-- Rollback: DROP INDEX "flow_definitions_workspace_id_idx",
|
|
26
|
+
-- "flow_sessions_workspace_id_idx"; ALTER TABLE "flow_definitions" DROP
|
|
27
|
+
-- CONSTRAINT "flow_definitions_workspace_id_format", DROP COLUMN
|
|
28
|
+
-- "workspace_id"; ALTER TABLE "flow_sessions" DROP CONSTRAINT
|
|
29
|
+
-- "flow_sessions_workspace_id_format", DROP COLUMN "workspace_id". Safe
|
|
30
|
+
-- unconditionally at this pin — nothing reads the column until #6156+.
|
|
31
|
+
|
|
32
|
+
-- The census the post-assertions compare against. ON COMMIT DROP: `prisma
|
|
33
|
+
-- migrate` runs each migration in one transaction, so this disappears with it.
|
|
34
|
+
CREATE TEMP TABLE "_6155_flow_census" ON COMMIT DROP AS
|
|
35
|
+
SELECT (SELECT count(*) FROM "flow_definitions") AS d,
|
|
36
|
+
(SELECT count(*) FROM "flow_sessions") AS s;
|
|
37
|
+
|
|
38
|
+
-- AlterTable
|
|
39
|
+
ALTER TABLE "flow_definitions" ADD COLUMN IF NOT EXISTS "workspace_id" TEXT;
|
|
40
|
+
ALTER TABLE "flow_sessions" ADD COLUMN IF NOT EXISTS "workspace_id" TEXT;
|
|
41
|
+
|
|
42
|
+
-- Format CHECKs — NULL-permitting, in the exact shape of the CHECKs
|
|
43
|
+
-- 20260713192429_prefixed_uuid_entity_ids/migration.sql:170-197 already
|
|
44
|
+
-- applies to every other `workspace_id` column. Existence-guarded because
|
|
45
|
+
-- Postgres has no `ADD CONSTRAINT IF NOT EXISTS`.
|
|
46
|
+
DO $$ BEGIN
|
|
47
|
+
IF NOT EXISTS (
|
|
48
|
+
SELECT 1 FROM pg_constraint WHERE conname = 'flow_definitions_workspace_id_format'
|
|
49
|
+
) THEN
|
|
50
|
+
ALTER TABLE "flow_definitions" ADD CONSTRAINT "flow_definitions_workspace_id_format"
|
|
51
|
+
CHECK ("workspace_id" IS NULL OR "workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
52
|
+
END IF;
|
|
53
|
+
END $$;
|
|
54
|
+
|
|
55
|
+
DO $$ BEGIN
|
|
56
|
+
IF NOT EXISTS (
|
|
57
|
+
SELECT 1 FROM pg_constraint WHERE conname = 'flow_sessions_workspace_id_format'
|
|
58
|
+
) THEN
|
|
59
|
+
ALTER TABLE "flow_sessions" ADD CONSTRAINT "flow_sessions_workspace_id_format"
|
|
60
|
+
CHECK ("workspace_id" IS NULL OR "workspace_id" ~ '^wksp_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$');
|
|
61
|
+
END IF;
|
|
62
|
+
END $$;
|
|
63
|
+
|
|
64
|
+
-- CreateIndex
|
|
65
|
+
CREATE INDEX IF NOT EXISTS "flow_definitions_workspace_id_idx" ON "flow_definitions"("workspace_id");
|
|
66
|
+
CREATE INDEX IF NOT EXISTS "flow_sessions_workspace_id_idx" ON "flow_sessions"("workspace_id");
|
|
67
|
+
|
|
68
|
+
-- ── Backfill (spec D2) ──────────────────────────────────────────────────────
|
|
69
|
+
-- Both spellings of the alias, one statement per table. The literal alias
|
|
70
|
+
-- "default" resolves to the seeded sentinel workspace
|
|
71
|
+
-- (database/tenancy.ts DEFAULT_WORKSPACE_ID); every other alias resolves
|
|
72
|
+
-- only by exact workspaces.id equality. An inner join to "workspaces" means a
|
|
73
|
+
-- row whose alias resolves to nothing is simply not updated and stays NULL
|
|
74
|
+
-- — the correct, stated behavior (D3), not an error.
|
|
75
|
+
--
|
|
76
|
+
-- The sentinel id is written as a literal because SQL cannot import
|
|
77
|
+
-- database/tenancy.ts; database/__tests__/tenancy.test.ts pins the same
|
|
78
|
+
-- literal against the exported constant so the two cannot drift.
|
|
79
|
+
UPDATE "flow_definitions" f
|
|
80
|
+
SET "workspace_id" = w."id"
|
|
81
|
+
FROM "workspaces" w
|
|
82
|
+
WHERE f."workspace_id" IS NULL
|
|
83
|
+
AND w."id" = CASE WHEN f."site_id" = 'default'
|
|
84
|
+
THEN 'wksp_00000000-0000-7000-8000-000000000001'
|
|
85
|
+
ELSE f."site_id" END;
|
|
86
|
+
|
|
87
|
+
UPDATE "flow_sessions" f
|
|
88
|
+
SET "workspace_id" = w."id"
|
|
89
|
+
FROM "workspaces" w
|
|
90
|
+
WHERE f."workspace_id" IS NULL
|
|
91
|
+
AND w."id" = CASE WHEN f."site_id" = 'default'
|
|
92
|
+
THEN 'wksp_00000000-0000-7000-8000-000000000001'
|
|
93
|
+
ELSE f."site_id" END;
|
|
94
|
+
|
|
95
|
+
-- ── POST-ASSERTIONS ─────────────────────────────────────────────────────────
|
|
96
|
+
-- AC-1's own enforcement: nothing lost, nothing guessed, nothing missed that
|
|
97
|
+
-- was resolvable. The fourth check is a NOTICE, not an exception — an
|
|
98
|
+
-- unpinned/unseeded deployment leaving rows NULL is the expected, documented
|
|
99
|
+
-- case (spec § 1.3), never an error this migration should refuse to run over.
|
|
100
|
+
DO $$
|
|
101
|
+
DECLARE
|
|
102
|
+
before_d bigint;
|
|
103
|
+
before_s bigint;
|
|
104
|
+
after_d bigint;
|
|
105
|
+
after_s bigint;
|
|
106
|
+
guessed bigint;
|
|
107
|
+
missed bigint;
|
|
108
|
+
null_d bigint;
|
|
109
|
+
null_s bigint;
|
|
110
|
+
BEGIN
|
|
111
|
+
SELECT d, s INTO before_d, before_s FROM "_6155_flow_census";
|
|
112
|
+
SELECT count(*) INTO after_d FROM "flow_definitions";
|
|
113
|
+
SELECT count(*) INTO after_s FROM "flow_sessions";
|
|
114
|
+
|
|
115
|
+
-- 1. Nothing lost: row counts unchanged on both tables.
|
|
116
|
+
IF before_d <> after_d OR before_s <> after_s THEN
|
|
117
|
+
RAISE EXCEPTION
|
|
118
|
+
'#6155 post-assertion: row count changed across a purely additive migration (flow_definitions % -> %, flow_sessions % -> %)',
|
|
119
|
+
before_d, after_d, before_s, after_s;
|
|
120
|
+
END IF;
|
|
121
|
+
|
|
122
|
+
-- 2. Nothing guessed: no row's workspace_id was set to a value absent
|
|
123
|
+
-- from "workspaces" (the inner-join backfill above cannot produce this
|
|
124
|
+
-- on its own, but a re-run against a database missing the sentinel row
|
|
125
|
+
-- must never leave a dangling reference).
|
|
126
|
+
SELECT count(*) INTO guessed FROM "flow_definitions" f
|
|
127
|
+
WHERE f."workspace_id" IS NOT NULL
|
|
128
|
+
AND NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."id" = f."workspace_id");
|
|
129
|
+
IF guessed <> 0 THEN
|
|
130
|
+
RAISE EXCEPTION
|
|
131
|
+
'#6155 post-assertion: % flow_definitions row(s) carry a workspace_id absent from workspaces',
|
|
132
|
+
guessed;
|
|
133
|
+
END IF;
|
|
134
|
+
SELECT count(*) INTO guessed FROM "flow_sessions" f
|
|
135
|
+
WHERE f."workspace_id" IS NOT NULL
|
|
136
|
+
AND NOT EXISTS (SELECT 1 FROM "workspaces" w WHERE w."id" = f."workspace_id");
|
|
137
|
+
IF guessed <> 0 THEN
|
|
138
|
+
RAISE EXCEPTION
|
|
139
|
+
'#6155 post-assertion: % flow_sessions row(s) carry a workspace_id absent from workspaces',
|
|
140
|
+
guessed;
|
|
141
|
+
END IF;
|
|
142
|
+
|
|
143
|
+
-- 3. Nothing missed: no row whose site_id names a workspace that actually
|
|
144
|
+
-- exists came out of the backfill with workspace_id still NULL.
|
|
145
|
+
SELECT count(*) INTO missed FROM "flow_definitions" f
|
|
146
|
+
WHERE f."workspace_id" IS NULL
|
|
147
|
+
AND EXISTS (
|
|
148
|
+
SELECT 1 FROM "workspaces" w
|
|
149
|
+
WHERE w."id" = CASE WHEN f."site_id" = 'default'
|
|
150
|
+
THEN 'wksp_00000000-0000-7000-8000-000000000001'
|
|
151
|
+
ELSE f."site_id" END
|
|
152
|
+
);
|
|
153
|
+
IF missed <> 0 THEN
|
|
154
|
+
RAISE EXCEPTION
|
|
155
|
+
'#6155 post-assertion: % flow_definitions row(s) had a resolvable site_id but came out of the backfill NULL',
|
|
156
|
+
missed;
|
|
157
|
+
END IF;
|
|
158
|
+
SELECT count(*) INTO missed FROM "flow_sessions" f
|
|
159
|
+
WHERE f."workspace_id" IS NULL
|
|
160
|
+
AND EXISTS (
|
|
161
|
+
SELECT 1 FROM "workspaces" w
|
|
162
|
+
WHERE w."id" = CASE WHEN f."site_id" = 'default'
|
|
163
|
+
THEN 'wksp_00000000-0000-7000-8000-000000000001'
|
|
164
|
+
ELSE f."site_id" END
|
|
165
|
+
);
|
|
166
|
+
IF missed <> 0 THEN
|
|
167
|
+
RAISE EXCEPTION
|
|
168
|
+
'#6155 post-assertion: % flow_sessions row(s) had a resolvable site_id but came out of the backfill NULL',
|
|
169
|
+
missed;
|
|
170
|
+
END IF;
|
|
171
|
+
|
|
172
|
+
-- 4. A NOTICE, never an exception: an unpinned/unseeded deployment leaving
|
|
173
|
+
-- rows NULL is the expected, documented case (spec § 1.3).
|
|
174
|
+
SELECT count(*) INTO null_d FROM "flow_definitions" WHERE "workspace_id" IS NULL;
|
|
175
|
+
SELECT count(*) INTO null_s FROM "flow_sessions" WHERE "workspace_id" IS NULL;
|
|
176
|
+
IF null_d <> 0 OR null_s <> 0 THEN
|
|
177
|
+
RAISE NOTICE
|
|
178
|
+
'#6155: % flow_definitions row(s) and % flow_sessions row(s) left workspace_id unset — their site_id did not resolve to a workspace this database holds',
|
|
179
|
+
null_d, null_s;
|
|
180
|
+
END IF;
|
|
181
|
+
END $$;
|