@wildo-ai/saas-backend-lib 1.1.8 → 1.1.9
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/dist/esm/data-seeding/models/data-seed.shared.schemas.d.ts +8 -0
- package/dist/esm/data-seeding/models/data-seed.shared.schemas.d.ts.map +1 -1
- package/dist/esm/data-seeding/models/data-seed.shared.schemas.js +17 -0
- package/dist/esm/data-seeding/models/data-seed.shared.schemas.js.map +1 -1
- package/dist/esm/data-seeding/services/data-seed-application-bootstrap.backend.d.ts.map +1 -1
- package/dist/esm/data-seeding/services/data-seed-application-bootstrap.backend.js +9 -1
- package/dist/esm/data-seeding/services/data-seed-application-bootstrap.backend.js.map +1 -1
- package/dist/esm/data-seeding/services/data-seed-environment-admission.backend.d.ts +23 -0
- package/dist/esm/data-seeding/services/data-seed-environment-admission.backend.d.ts.map +1 -0
- package/dist/esm/data-seeding/services/data-seed-environment-admission.backend.js +25 -0
- package/dist/esm/data-seeding/services/data-seed-environment-admission.backend.js.map +1 -0
- package/dist/esm/databases/services/postgresql-migration-runner.backend.d.ts +6 -0
- package/dist/esm/databases/services/postgresql-migration-runner.backend.d.ts.map +1 -1
- package/dist/esm/databases/services/postgresql-migration-runner.backend.js +8 -1
- package/dist/esm/databases/services/postgresql-migration-runner.backend.js.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +0 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +4 -39
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts +15 -4
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.js +21 -11
- package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +47 -2
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +56 -8
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
- package/dist/esm/queue/job-consumer.backend.service.d.ts.map +1 -1
- package/dist/esm/queue/job-consumer.backend.service.js +21 -8
- package/dist/esm/queue/job-consumer.backend.service.js.map +1 -1
- package/dist/esm/queue/job-status-tracking.backend.service.d.ts.map +1 -1
- package/dist/esm/queue/job-status-tracking.backend.service.js +4 -1
- package/dist/esm/queue/job-status-tracking.backend.service.js.map +1 -1
- package/dist/esm/queue/queue.backend.service.d.ts +14 -0
- package/dist/esm/queue/queue.backend.service.d.ts.map +1 -1
- package/dist/esm/queue/queue.backend.service.js +68 -12
- package/dist/esm/queue/queue.backend.service.js.map +1 -1
- package/dist/esm/queue/scheduled-evidence-coverage.backend.d.ts +8 -0
- package/dist/esm/queue/scheduled-evidence-coverage.backend.d.ts.map +1 -1
- package/dist/esm/queue/scheduled-evidence-coverage.backend.js +20 -2
- package/dist/esm/queue/scheduled-evidence-coverage.backend.js.map +1 -1
- package/dist/esm/queue/scheduled-execution.backend.service.d.ts +44 -1
- package/dist/esm/queue/scheduled-execution.backend.service.d.ts.map +1 -1
- package/dist/esm/queue/scheduled-execution.backend.service.js +180 -10
- package/dist/esm/queue/scheduled-execution.backend.service.js.map +1 -1
- package/dist/esm/queue/scheduled-message-verifier.backend.service.d.ts +73 -21
- package/dist/esm/queue/scheduled-message-verifier.backend.service.d.ts.map +1 -1
- package/dist/esm/queue/scheduled-message-verifier.backend.service.js +162 -88
- package/dist/esm/queue/scheduled-message-verifier.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/start-application.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/start-application.backend.service.js +16 -1
- package/dist/esm/runtime/services/start-application.backend.service.js.map +1 -1
- package/package.json +10 -10
- package/dist/esm/__tests__/fixture-deep-partial.d.ts +0 -18
- package/dist/esm/__tests__/fixture-deep-partial.d.ts.map +0 -1
- package/dist/esm/__tests__/fixture-deep-partial.js +0 -1
- package/dist/esm/__tests__/fixture-deep-partial.js.map +0 -1
- package/dist/esm/__tests__/integration/redis-integration-gate.d.ts +0 -26
- package/dist/esm/__tests__/integration/redis-integration-gate.d.ts.map +0 -1
- package/dist/esm/__tests__/integration/redis-integration-gate.js +0 -105
- package/dist/esm/__tests__/integration/redis-integration-gate.js.map +0 -1
- package/dist/esm/__tests__/setup.d.ts +0 -7
- package/dist/esm/__tests__/setup.d.ts.map +0 -1
- package/dist/esm/__tests__/setup.js +0 -14
- package/dist/esm/__tests__/setup.js.map +0 -1
- package/dist/esm/__tests__/test-container.d.ts +0 -41
- package/dist/esm/__tests__/test-container.d.ts.map +0 -1
- package/dist/esm/__tests__/test-container.js +0 -98
- package/dist/esm/__tests__/test-container.js.map +0 -1
- package/dist/esm/base-classes/services/__tests__/mock-resource-operation-helper.service.d.ts +0 -47
- package/dist/esm/base-classes/services/__tests__/mock-resource-operation-helper.service.d.ts.map +0 -1
- package/dist/esm/base-classes/services/__tests__/mock-resource-operation-helper.service.js +0 -82
- package/dist/esm/base-classes/services/__tests__/mock-resource-operation-helper.service.js.map +0 -1
- package/dist/esm/base-classes/services/repositories/__tests__/method-span-scan.guard-helpers.d.ts +0 -57
- package/dist/esm/base-classes/services/repositories/__tests__/method-span-scan.guard-helpers.d.ts.map +0 -1
- package/dist/esm/base-classes/services/repositories/__tests__/method-span-scan.guard-helpers.js +0 -172
- package/dist/esm/base-classes/services/repositories/__tests__/method-span-scan.guard-helpers.js.map +0 -1
- package/dist/esm/base-classes/services/repositories/__tests__/repositories-registry-infrastructure-read.double.d.ts +0 -41
- package/dist/esm/base-classes/services/repositories/__tests__/repositories-registry-infrastructure-read.double.d.ts.map +0 -1
- package/dist/esm/base-classes/services/repositories/__tests__/repositories-registry-infrastructure-read.double.js +0 -54
- package/dist/esm/base-classes/services/repositories/__tests__/repositories-registry-infrastructure-read.double.js.map +0 -1
- package/dist/esm/billing/services/__tests__/subscription-provider-projection.test-double.d.ts +0 -20
- package/dist/esm/billing/services/__tests__/subscription-provider-projection.test-double.d.ts.map +0 -1
- package/dist/esm/billing/services/__tests__/subscription-provider-projection.test-double.js +0 -33
- package/dist/esm/billing/services/__tests__/subscription-provider-projection.test-double.js.map +0 -1
- package/dist/esm/billing/services/custom-impl/__tests__/plan-change-confirmation.fixture.d.ts +0 -22
- package/dist/esm/billing/services/custom-impl/__tests__/plan-change-confirmation.fixture.d.ts.map +0 -1
- package/dist/esm/billing/services/custom-impl/__tests__/plan-change-confirmation.fixture.js +0 -92
- package/dist/esm/billing/services/custom-impl/__tests__/plan-change-confirmation.fixture.js.map +0 -1
- package/dist/esm/billing/services/custom-impl/__tests__/subscription-keyed-mutation.fixture.d.ts +0 -15
- package/dist/esm/billing/services/custom-impl/__tests__/subscription-keyed-mutation.fixture.d.ts.map +0 -1
- package/dist/esm/billing/services/custom-impl/__tests__/subscription-keyed-mutation.fixture.js +0 -61
- package/dist/esm/billing/services/custom-impl/__tests__/subscription-keyed-mutation.fixture.js.map +0 -1
- package/dist/esm/billing/services/webhooks/__tests__/billing-event-authority.test-double.d.ts +0 -21
- package/dist/esm/billing/services/webhooks/__tests__/billing-event-authority.test-double.d.ts.map +0 -1
- package/dist/esm/billing/services/webhooks/__tests__/billing-event-authority.test-double.js +0 -32
- package/dist/esm/billing/services/webhooks/__tests__/billing-event-authority.test-double.js.map +0 -1
- package/dist/esm/billing/services/webhooks/__tests__/settlement-ledger.test-double.d.ts +0 -52
- package/dist/esm/billing/services/webhooks/__tests__/settlement-ledger.test-double.d.ts.map +0 -1
- package/dist/esm/billing/services/webhooks/__tests__/settlement-ledger.test-double.js +0 -74
- package/dist/esm/billing/services/webhooks/__tests__/settlement-ledger.test-double.js.map +0 -1
- package/dist/esm/config/services/__tests__/mock-app-configuration.service.d.ts +0 -60
- package/dist/esm/config/services/__tests__/mock-app-configuration.service.d.ts.map +0 -1
- package/dist/esm/config/services/__tests__/mock-app-configuration.service.js +0 -156
- package/dist/esm/config/services/__tests__/mock-app-configuration.service.js.map +0 -1
- package/dist/esm/config/services/__tests__/startup-validator-resources-registry.double.d.ts +0 -31
- package/dist/esm/config/services/__tests__/startup-validator-resources-registry.double.d.ts.map +0 -1
- package/dist/esm/config/services/__tests__/startup-validator-resources-registry.double.js +0 -47
- package/dist/esm/config/services/__tests__/startup-validator-resources-registry.double.js.map +0 -1
- package/dist/esm/databases/services/__tests__/mock-mongoose-model.service.d.ts +0 -20
- package/dist/esm/databases/services/__tests__/mock-mongoose-model.service.d.ts.map +0 -1
- package/dist/esm/databases/services/__tests__/mock-mongoose-model.service.js +0 -424
- package/dist/esm/databases/services/__tests__/mock-mongoose-model.service.js.map +0 -1
- package/dist/esm/databases/services/__tests__/mock-zod-mongoose-schema-convertor.service.d.ts +0 -19
- package/dist/esm/databases/services/__tests__/mock-zod-mongoose-schema-convertor.service.d.ts.map +0 -1
- package/dist/esm/databases/services/__tests__/mock-zod-mongoose-schema-convertor.service.js +0 -49
- package/dist/esm/databases/services/__tests__/mock-zod-mongoose-schema-convertor.service.js.map +0 -1
- package/dist/esm/flows-actors/services/__tests__/pending-decision.fixtures.d.ts +0 -43
- package/dist/esm/flows-actors/services/__tests__/pending-decision.fixtures.d.ts.map +0 -1
- package/dist/esm/flows-actors/services/__tests__/pending-decision.fixtures.js +0 -61
- package/dist/esm/flows-actors/services/__tests__/pending-decision.fixtures.js.map +0 -1
- package/dist/esm/inbound/services/__tests__/support/in-memory-infrastructure-repositories.d.ts +0 -25
- package/dist/esm/inbound/services/__tests__/support/in-memory-infrastructure-repositories.d.ts.map +0 -1
- package/dist/esm/inbound/services/__tests__/support/in-memory-infrastructure-repositories.js +0 -92
- package/dist/esm/inbound/services/__tests__/support/in-memory-infrastructure-repositories.js.map +0 -1
- package/dist/esm/meta-workflows/services/__tests__/fixtures/meta-workflow-evaluation-treatment.test-fixture.d.ts +0 -7
- package/dist/esm/meta-workflows/services/__tests__/fixtures/meta-workflow-evaluation-treatment.test-fixture.d.ts.map +0 -1
- package/dist/esm/meta-workflows/services/__tests__/fixtures/meta-workflow-evaluation-treatment.test-fixture.js +0 -194
- package/dist/esm/meta-workflows/services/__tests__/fixtures/meta-workflow-evaluation-treatment.test-fixture.js.map +0 -1
- package/dist/esm/monitoring/logger/services/__tests__/mock-logger.service.d.ts +0 -34
- package/dist/esm/monitoring/logger/services/__tests__/mock-logger.service.d.ts.map +0 -1
- package/dist/esm/monitoring/logger/services/__tests__/mock-logger.service.js +0 -59
- package/dist/esm/monitoring/logger/services/__tests__/mock-logger.service.js.map +0 -1
- package/dist/esm/monitoring/observability/services/__tests__/mock-metrics.service.d.ts +0 -48
- package/dist/esm/monitoring/observability/services/__tests__/mock-metrics.service.d.ts.map +0 -1
- package/dist/esm/monitoring/observability/services/__tests__/mock-metrics.service.js +0 -53
- package/dist/esm/monitoring/observability/services/__tests__/mock-metrics.service.js.map +0 -1
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/bundle.d.ts +0 -3
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/bundle.d.ts.map +0 -1
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/bundle.js +0 -19
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/bundle.js.map +0 -1
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/host.d.ts +0 -51
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/host.d.ts.map +0 -1
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/host.js +0 -24
- package/dist/esm/notifications/services/__tests__/fixtures/personal-notification-browser/host.js.map +0 -1
- package/dist/esm/notifications/services/__tests__/recipient-language.test-double.d.ts +0 -8
- package/dist/esm/notifications/services/__tests__/recipient-language.test-double.d.ts.map +0 -1
- package/dist/esm/notifications/services/__tests__/recipient-language.test-double.js +0 -9
- package/dist/esm/notifications/services/__tests__/recipient-language.test-double.js.map +0 -1
- package/dist/esm/pdf-templates/__tests__/fixtures/tiny-pdf-template.fixture.d.ts +0 -10
- package/dist/esm/pdf-templates/__tests__/fixtures/tiny-pdf-template.fixture.d.ts.map +0 -1
- package/dist/esm/pdf-templates/__tests__/fixtures/tiny-pdf-template.fixture.js +0 -18
- package/dist/esm/pdf-templates/__tests__/fixtures/tiny-pdf-template.fixture.js.map +0 -1
- package/dist/esm/security/api-keys/__tests__/api-key-usage-redis.double.d.ts +0 -11
- package/dist/esm/security/api-keys/__tests__/api-key-usage-redis.double.d.ts.map +0 -1
- package/dist/esm/security/api-keys/__tests__/api-key-usage-redis.double.js +0 -34
- package/dist/esm/security/api-keys/__tests__/api-key-usage-redis.double.js.map +0 -1
- package/dist/esm/security/authentications/__tests__/registration-onboarding.harness.d.ts +0 -105
- package/dist/esm/security/authentications/__tests__/registration-onboarding.harness.d.ts.map +0 -1
- package/dist/esm/security/authentications/__tests__/registration-onboarding.harness.js +0 -140
- package/dist/esm/security/authentications/__tests__/registration-onboarding.harness.js.map +0 -1
- package/dist/esm/security/authentications/services/__tests__/oauth-provider-audit-spies.mock.d.ts +0 -35
- package/dist/esm/security/authentications/services/__tests__/oauth-provider-audit-spies.mock.d.ts.map +0 -1
- package/dist/esm/security/authentications/services/__tests__/oauth-provider-audit-spies.mock.js +0 -46
- package/dist/esm/security/authentications/services/__tests__/oauth-provider-audit-spies.mock.js.map +0 -1
- package/dist/esm/security/connected-accounts/__tests__/support/real-connection-client-methods.d.ts +0 -2
- package/dist/esm/security/connected-accounts/__tests__/support/real-connection-client-methods.d.ts.map +0 -1
- package/dist/esm/security/connected-accounts/__tests__/support/real-connection-client-methods.js +0 -18
- package/dist/esm/security/connected-accounts/__tests__/support/real-connection-client-methods.js.map +0 -1
- package/dist/esm/systems-of-record/credentials/__tests__/certificate-fixtures.d.ts +0 -20
- package/dist/esm/systems-of-record/credentials/__tests__/certificate-fixtures.d.ts.map +0 -1
- package/dist/esm/systems-of-record/credentials/__tests__/certificate-fixtures.js +0 -56
- package/dist/esm/systems-of-record/credentials/__tests__/certificate-fixtures.js.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-seed-application-bootstrap.backend.js","sourceRoot":"","sources":["../../../../../src/data-seeding/services/data-seed-application-bootstrap.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The data-seeding subsystem's startup wiring, in the two halves `StartApplicationBackendService.initialize()`\n * calls at different steps:\n * - {@link wireDataSeedingDeclarations} (Step 5.9, BEFORE the HTTP server listens) — register the seed\n * corpus and scope resolvers, wire the Level-B resource-lifecycle hooks and the LAZY guards;\n * - {@link runDataSeedingApplicationLifecyclePasses} (Step 7.7) — the application-lifecycle passes.\n *\n * Extracted from `initialize()` so the (small) seeding contract can be exercised with mocked dependencies,\n * without the 30+ dependency boot chain. There is deliberately no one-call composite: the split is what\n * keeps a request from reaching the services registry before its seed hooks exist, and a composite would\n * invite a caller to run both after its server had started.\n *\n * See `.cursor/plans/seeding-first-class.md` §2.F for the full rationale\n * (including why platform-mode boots must NOT dispatch and why dispatcher\n * failures here are intentionally fatal rather than swallowed).\n */\nimport { CoreResourceOperation } from '@wildo-ai/saas-models';\nimport { ENGINE_OWNED_DATA_SEEDS } from '~wildo-backend-lib/data-seeding/services/engine-owned-data-seeds.backend';\nimport {\n DataSeedResourceLifecyclePhase,\n DataSeedTriggerKind,\n type DataSeedDefinition,\n} from '~wildo-backend-lib/data-seeding/models/data-seed.shared.schemas';\nimport { DataSeedRunStatus } from '~wildo-backend-lib/data-seeding/models/data-seed-run.shared.schemas';\nimport { describeCaughtError } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\n\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { narrowExecutionContextForDataSeeding } from '~wildo-backend-lib/data-seeding/models/data-seed-execution-context.shared.types';\nimport type { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport type { DataSeedRegistryBackendService } from '~wildo-backend-lib/data-seeding/services/data-seed-registry.backend.service';\nimport type {\n DataSeedScopeResolver,\n DataSeedScopeResolverRegistryBackendService,\n} from '~wildo-backend-lib/data-seeding/services/data-seed-scope-resolver-registry.backend.service';\nimport type { DataSeedDispatcherBackendService } from '~wildo-backend-lib/data-seeding/services/data-seed-dispatcher.backend.service';\nimport type { ServicesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/services/services-registry-handler.backend.service';\n\n/** @wildo_source:part:start saas.data-seeding.bootstrap-wiring facet:kind:framework-api facet:audience:operator */\n/**\n * Inputs to the data-seeding bootstrap halves ({@link wireDataSeedingDeclarations},\n * {@link runDataSeedingApplicationLifecyclePasses}). Kept as a typed\n * record (instead of positional args) so callers from different startup\n * orchestrators stay readable when the surface grows in later phases.\n */\nexport interface DataSeedApplicationBootstrapInput {\n /**\n * Declarative seed definitions collected from `BackendDomainModule.seeds`\n * and forwarded by the orchestrator. `undefined` is treated as `[]`\n * (registration still runs to keep the registry's \"initialized\" semantics\n * consistent across boots).\n */\n dataSeeds: readonly DataSeedDefinition[] | undefined;\n\n /**\n * Phase 7 / Item 47 (cron scheduler) — Slice 3.\n *\n * Aggregated `customResolverKey → DataSeedScopeResolver` map collected\n * from `BackendDomainModule.dataSeedScopeResolvers` (cross-module\n * duplicate keys already rejected by `mergeBackendDomainModules`).\n * `undefined` / `{}` is treated identically — a no-op registration\n * that leaves the registry empty so future cron seeds with\n * `iterationStrategy === CUSTOM_RESOLVER` fail loudly at first\n * invocation rather than silently iterating zero rows.\n */\n dataSeedScopeResolvers?: Readonly<Record<string, DataSeedScopeResolver>>;\n\n /**\n * `true` when the caller is the platform apps-manager (or another\n * profile that bootstraps tenant apps later through their own\n * `StartApplicationBackendService.initialize()` call). Dispatch is\n * skipped in that case — see plan §2.F.\n */\n isPlatformMode: boolean;\n\n logger: LoggerService;\n /** Used to log Step 7.7 progress under the same channel as the host service. */\n logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n registry: DataSeedRegistryBackendService;\n /**\n * Phase 7 / Item 47 — Slice 3. Hydrated from\n * {@link DataSeedApplicationBootstrapInput.dataSeedScopeResolvers}\n * BEFORE Level-A dispatch / Level-B + LAZY hook wiring (registration\n * is dispatch-independent — keeping it before the platform-mode\n * skip ensures both the platform process AND tenant boots see the\n * same registry state).\n */\n scopeResolverRegistry: DataSeedScopeResolverRegistryBackendService;\n dispatcher: DataSeedDispatcherBackendService;\n\n /**\n * Required for Level-B auto-wiring (Phase 3, plan decision P3-B=B1).\n * After registering seeds, the bootstrap walks the registry and\n * registers pre/post-operation hooks on this services registry so\n * that any resource-lifecycle trigger (`beforeCreate`/`afterCreate`/\n * `beforeUpdate`/`afterUpdate`/`beforeDelete`/`afterDelete`) fires\n * the dispatcher's `runResourceLifecycle` when the matching\n * operation runs.\n */\n servicesRegistry: ServicesRegistryHandlerBackendService;\n}\n\n/**\n * The half of the bootstrap that must be live before ANY request can reach the services registry:\n * register the seed corpus and the scope resolvers, then wire the Level-B resource-lifecycle hooks and\n * the LAZY-access guards. Pure in-memory wiring — no read, no write, no remote call.\n *\n * ## Contract of the two halves, taken together\n *\n * - Registration ALWAYS runs (even in platform mode) so future Level-B\n * and lazy-access dispatch paths can resolve seed declarations.\n * - Dispatcher is invoked twice in order: `onAppInit` MUST precede\n * `onAppVersionUpgrade` because the upgrade pass may mutate rows\n * created by the init pass.\n * - Any throw from the dispatcher is rethrown unchanged. Booting with\n * a half-applied seed corpus would silently break downstream ops, so\n * the dispatcher's FAILED `DataSeedRun` ledger entries (which the\n * next boot retries) are the intended recovery surface — not a try/\n * catch here.\n *\n * Operational invariants (operator-facing):\n * 1. Seed registry hydrated unconditionally (platform mode + tenant boot)\n * so Level-B + LAZY hooks can resolve declarations regardless of who\n * runs the Level-A pass.\n * 2. Custom-resolver registry hydrated next (Item 47 / Slice 3) — empty\n * map is a no-op call, so any future cron seed declaring\n * `iterationStrategy === CUSTOM_RESOLVER` against an unregistered key\n * fails loudly at first invocation rather than silently iterating\n * zero rows.\n * 3. Level-B hooks wired BEFORE Level-A dispatch (P3-B=B1) — per-tenant\n * resource-lifecycle seeds must be live before any boot pass, so\n * cross-tenant boots see consistent hook state.\n * 4. LAZY guards wired symmetric with Level-B (P5-D=D1) — pre-only\n * hooks per `(resourceType, operation)` pair, coalesced to one hook\n * per resource type with internal route map.\n * 5. Platform-mode skip: dispatch is suppressed for the apps-manager\n * profile (it bootstraps tenant apps which run their own pass).\n *\n * ## Why it is its own step, and runs before the HTTP server listens\n *\n * The HTTP server starts listening inside Step 6, and this wiring used to run inside Step 7.7, after\n * queue-worker initialization and an AWAITED cron-job sync. Every request in between reached a services\n * registry with no seed hooks: an organization created then got none of its `afterCreate` defaults\n * (SSO, SCIM, SIEM-export and webhook configuration singletons), silently and permanently, because a\n * post-create hook that was not registered has nothing to retry. Measured 2026-09-14 on Wonder Todos:\n * the cron sync waited ~20 minutes on a platform that was not ready, the backend answered\n * `/health/ready` 200 throughout, and every organization registered in that window had no\n * `organizationSsoConfig` row.\n *\n * So the declarations are wired where the registry becomes reachable, and only the application-lifecycle\n * passes ({@link runDataSeedingApplicationLifecyclePasses}) stay at Step 7.7.\n */\nexport function wireDataSeedingDeclarations(\n input: Pick<\n DataSeedApplicationBootstrapInput,\n 'dataSeeds' | 'dataSeedScopeResolvers' | 'logDebug' | 'registry' | 'scopeResolverRegistry' | 'dispatcher' | 'servicesRegistry'\n > & {\n /**\n * Whether this runtime owns the application's DATA LIFECYCLE, and therefore whether the\n * framework's own seeds belong in its registry.\n *\n * Only the application backend does. A MINION or WORKER runs the same bootstrap with a SCOPED\n * services registry that refuses unscoped writes, and registering a Level-B seed there makes\n * `wireLevelBResourceLifecycleHooks` ask for exactly that access — measured 2026-09-18, when\n * registering the engine's set unconditionally crash-looped the marketing-scrapper minion with\n * `Minion 'marketingScrapper' is not allowed to write resource '<unscoped-registry-access>'`.\n *\n * The application's OWN `dataSeeds` are registered regardless, exactly as before: what an\n * application passes is its business, and this flag governs only what the FRAMEWORK adds.\n */\n readonly registersEngineOwnedSeeds: boolean;\n },\n): void {\n const { dataSeeds, dataSeedScopeResolvers, logDebug, registry, scopeResolverRegistry, dispatcher, servicesRegistry, registersEngineOwnedSeeds } = input;\n\n /*\n * FRAMEWORK-owned seeds first, then the application's.\n *\n * These used to be the application's job: each one spread `enterpriseAuthConfigOrgSeedDefinitions`\n * and its siblings into a module of its own, and an application that did not know to do so had no\n * per-scope configuration rows at all — which is exactly what the newer dogfood application did,\n * leaving every SSO / SCIM / SIEM settings panel answering a 404. The framework's own defaults for\n * the framework's own resources are registered by the framework.\n *\n * Order matters only for the duplicate refusal below: registering the engine's set FIRST means an\n * application that still spreads one of them fails naming the framework's key, which is the half\n * of the collision a reader can act on.\n */\n const engineOwned = registersEngineOwnedSeeds ? ENGINE_OWNED_DATA_SEEDS : [];\n const seeds = [...engineOwned, ...(dataSeeds ?? [])];\n logDebug('🌱 Registering data-seed definitions...', {\n count: seeds.length,\n engineOwned: engineOwned.length,\n applicationOwned: (dataSeeds ?? []).length,\n });\n registry.registerSeeds(seeds);\n logDebug('✅ Data-seed registry populated');\n\n // Phase 7 / Item 47 — Slice 3 — register custom-resolver scope iterators.\n // Runs unconditionally (even in platform mode + when the map is empty)\n // so the registry's lifecycle is symmetric with `DataSeedRegistry`'s\n // (always-initialized at boot). The empty-map case is a no-op call\n // that registers zero entries; future cron seeds (Slice 5) probing\n // the registry via `hasResolver` get a clean `false` instead of a\n // \"registry not initialized\" error.\n const resolversMap = dataSeedScopeResolvers ?? {};\n const resolverCount = Object.keys(resolversMap).length;\n logDebug('🌱 Registering data-seed scope resolvers...', { count: resolverCount });\n scopeResolverRegistry.registerResolvers(resolversMap);\n logDebug('✅ Data-seed scope-resolver registry populated', { count: resolverCount });\n\n // Phase 3 (P3-B=B1) — auto-wire Level-B hooks into the services\n // registry. Runs BEFORE the Level-A dispatch (and in platform mode\n // too) so Level-B seeds fire on per-tenant resource lifecycle events\n // regardless of who runs the Level-A pass.\n wireLevelBResourceLifecycleHooks({ registry, dispatcher, servicesRegistry, logDebug });\n\n // Phase 5 (P5-D=D1) — auto-wire LAZY-access guards into the services\n // registry. Symmetric with Level-B wiring (registers pre-operation\n // hooks per `(resourceType, operation)` pair declared by LAZY\n // seeds). Runs BEFORE the Level-A dispatch (and in platform mode\n // too — same rationale as the Level-B wiring above: lazy guards\n // are per-tenant request hooks, the host running the Level-A pass\n // is irrelevant to their availability at request time).\n wireLazyAccessGuards({ registry, dispatcher, servicesRegistry, logDebug });\n}\n\n/**\n * Step 7.7 — the application-lifecycle passes (`onAppInit`, then `onAppVersionUpgrade`, then the\n * non-fatal missing-defaults repair). Requires {@link wireDataSeedingDeclarations} to have run: the\n * dispatcher resolves seeds from the registry it populated. Skipped entirely in platform mode.\n */\nexport async function runDataSeedingApplicationLifecyclePasses(\n input: Pick<DataSeedApplicationBootstrapInput, 'isPlatformMode' | 'logger' | 'logDebug' | 'dispatcher'>,\n): Promise<void> {\n const { isPlatformMode, logger, logDebug, dispatcher } = input;\n\n if (isPlatformMode) {\n logDebug('🌱 Skipping application-lifecycle data-seed dispatch in platform mode (tenant apps will run their own pass via apps-manager)');\n return;\n }\n\n logger.info('🌱 Running application-lifecycle data seeds (onAppInit + onAppVersionUpgrade)...');\n try {\n const initReport = await dispatcher.runApplicationLifecycle('onAppInit');\n logDebug('✅ onAppInit data-seed pass complete', {\n seedsProcessed: initReport.seedsProcessed,\n completed: initReport.runs.filter((r) => r.status === DataSeedRunStatus.COMPLETED).length,\n partial: initReport.runs.filter((r) => r.status === DataSeedRunStatus.PARTIAL).length,\n failed: initReport.runs.filter((r) => r.status === DataSeedRunStatus.FAILED).length,\n });\n\n const upgradeReport = await dispatcher.runApplicationLifecycle('onAppVersionUpgrade');\n logDebug('✅ onAppVersionUpgrade data-seed pass complete', {\n seedsProcessed: upgradeReport.seedsProcessed,\n completed: upgradeReport.runs.filter((r) => r.status === DataSeedRunStatus.COMPLETED).length,\n partial: upgradeReport.runs.filter((r) => r.status === DataSeedRunStatus.PARTIAL).length,\n failed: upgradeReport.runs.filter((r) => r.status === DataSeedRunStatus.FAILED).length,\n });\n } catch (error) {\n logger.error('❌ Data-seed application-lifecycle pass failed (fatal):', error);\n throw error;\n }\n\n // Missing-defaults repair (#554) — after both lifecycle passes, and NON-fatal, which is the opposite\n // of the passes above on purpose. Those passes create rows downstream operations need, so booting\n // without them is worse than not booting. This pass only completes rows that are already readable,\n // and its per-seed failures are on the ledger and retried next boot; refusing to start over it\n // would make a defaults gap into an outage.\n try {\n const repairReport = await dispatcher.repairMissingDefaultsOnLifecycleSeededRows();\n logDebug('✅ Missing-defaults repair pass complete', {\n repaired: repairReport.runs.length,\n alreadyRepaired: repairReport.seedsAlreadyRepaired,\n failedSeedKeys: repairReport.failedSeedKeys,\n });\n } catch (error) {\n logger.error('❌ Missing-defaults repair pass failed (non-fatal — retried on the next boot):', error);\n }\n}\n/** @wildo_source:part:end saas.data-seeding.bootstrap-wiring */\n\n// ---------------------------------------------------------------------------\n// Level-B hook wiring (Phase 3, plan decision P3-B=B1)\n// ---------------------------------------------------------------------------\n\n/**\n * Walk the data-seed registry and register pre/post-operation hooks on\n * the services registry so that every `resourceLifecycle` trigger\n * declared by a seed fires the dispatcher's `runResourceLifecycle`\n * when the matching resource operation runs.\n *\n * One pre-hook and one post-hook are registered per resource type that\n * appears in any Level-B trigger. Inside the hook, we match the\n * `operationIdentifier` against the registered seed's phase so that a\n * `beforeCreate` seed only fires on CREATE / CREATE_MANY, `beforeDelete`\n * only on DELETE / DELETE_MANY, etc. This grouping means:\n * - The services registry stays at most one entry per `(resourceType,\n * pre|post)` regardless of how many seeds target that resource.\n * - Adding a new seed on an already-wired resource does not re-\n * register a duplicate hook.\n *\n * Bulk operations (`CREATE_MANY` / `UPDATE_MANY` / `DELETE_MANY`) are\n * mapped to the same phase as their singular counterpart and dispatched\n * **per row** rather than once per batch (plan decision P3-F=F1: each\n * `DataSeedRun` ledger row anchors to a single triggering row's `_id`,\n * so a single bulk op produces N ledger rows). This preserves the\n * \"one anchor → one ledger\" invariant and keeps idempotency keys\n * identical across the singular and bulk callers of an operation.\n *\n * Failure propagation:\n * - PRE-hooks (beforeCreate/Update/Delete + bulk variants) throw into\n * `dispatchPreOperationHooks` which does NOT catch — errors bubble\n * to the triggering op (plan decision P3-A=A1, short-circuit). For\n * bulk pre-hooks, the loop is intentionally NOT wrapped in try/catch:\n * a failure on row N short-circuits rows N+1..M and aborts the\n * parent operation BEFORE any side effect (this matches the\n * semantics required by `executePreOperationLogic`'s callers).\n * - POST-hooks (afterCreate/Update/Delete + bulk variants) throw into\n * `dispatchPostOperationHooks` which catches + logs (plan decision\n * P3-D=D1, failure contained to the `DataSeedRun` ledger). For bulk\n * post-hooks each row is wrapped in its OWN try/catch so a single\n * failed row's `runResourceLifecycle` does not skip the remaining\n * rows in the batch.\n *\n * NOTE — BEFORE_CREATE limitation: the pre-hook receives the incoming\n * create payload which does NOT yet carry an `_id`. The dispatcher\n * asserts `triggeringRow._id` presence and throws `CONFIGURATION`\n * otherwise, meaning a Level-B seed on `BEFORE_CREATE` that tries to\n * actually upsert entries will fail loudly with a descriptive error.\n * BEFORE_CREATE is still useful as a pure VETO point (a seed whose\n * computed `entry()` validates cross-resource state and throws to\n * short-circuit) — the throw happens before `runAdditiveSeed` ever\n * reaches the `_id` check. Enforcement lives in the dispatcher rather\n * than here so a single error shape covers both direct and wired\n * callers of `runResourceLifecycle`.\n */\nfunction wireLevelBResourceLifecycleHooks(input: {\n registry: DataSeedRegistryBackendService;\n dispatcher: DataSeedDispatcherBackendService;\n servicesRegistry: ServicesRegistryHandlerBackendService;\n logDebug: (message: string, context?: Record<string, unknown>) => void;\n}): void {\n const { registry, dispatcher, servicesRegistry, logDebug } = input;\n\n const levelBSeeds = registry.getSeedsByTriggerKind(\n DataSeedTriggerKind.RESOURCE_LIFECYCLE,\n );\n if (levelBSeeds.length === 0) {\n logDebug('🌱 No Level-B (resource-lifecycle) data-seeds registered — skipping hook wiring');\n return;\n }\n\n // Collect the set of resource types that need hooks. `Set` is enough\n // here — we register exactly one pre-hook + one post-hook per\n // resource type and let the dispatcher filter per-phase internally\n // via `selectLevelBSeedsForPhase`. This keeps wiring O(distinct\n // resource types) regardless of seed count.\n const wiredResourceTypes = new Set<string>();\n for (const seed of levelBSeeds) {\n for (const trigger of seed.triggers) {\n if (trigger.triggerKind === DataSeedTriggerKind.RESOURCE_LIFECYCLE) {\n wiredResourceTypes.add(trigger.resourceType);\n }\n }\n }\n\n for (const resourceType of wiredResourceTypes) {\n servicesRegistry.registerPreOperationHook(\n resourceType,\n async (rt, operationId, data, executionContext) => {\n const phase = preOperationIdToPhase(operationId);\n if (!phase) return;\n\n if (isBulkOperationId(operationId)) {\n // Per-row dispatch (P3-F=F1). Pre-hook failures intentionally\n // short-circuit the remaining rows (no try/catch) — a veto\n // from row N must abort the entire bulk op.\n //\n // We apply `normalizeTriggeringRow` symmetrically with the\n // post-hook path so that DELETE_MANY's id-only payload\n // (lifted to `{_id}`) and any future bulk-op payload\n // contracts are handled in one place. The pre-hook payload\n // for `UPDATE_MANY` is the per-row shape `{...patch, _id}`\n // materialised by `executePreOperationLogic` (Phase 3.2 R2-1\n // / R2-2 fix); this loop iterates over those rows.\n const rows = ensureRowArray(data, operationId).map((row) =>\n normalizeTriggeringRow(row, operationId),\n );\n for (const row of rows) {\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n row as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext),\n );\n }\n return;\n }\n\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n normalizeTriggeringRow(data, operationId) as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext),\n );\n },\n );\n\n servicesRegistry.registerPostOperationHook(\n resourceType,\n async (rt, operationId, result, executionContext) => {\n const phase = postOperationIdToPhase(operationId);\n if (!phase) return;\n\n if (isBulkOperationId(operationId)) {\n const rows = ensureRowArray(result, operationId).map((row) =>\n normalizeTriggeringRow(row, operationId),\n );\n // Each row gets its own try/catch so a single failed seed\n // dispatch does not silently skip the remaining rows in the\n // batch. The outer `dispatchPostOperationHooks` already\n // contains failures for the SINGLE-call case (P3-D=D1); we\n // replicate that containment per-row for bulk so partial\n // failures still let the rest of the batch run.\n for (const row of rows) {\n try {\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n row as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext as ExecutionContext<any>),\n );\n } catch (error) {\n logDebug('🌱 Level-B per-row post-hook dispatch failed (contained — see DataSeedRun ledger)', {\n resourceType: rt,\n operationId,\n phase,\n error: describeCaughtError(error),\n });\n }\n }\n return;\n }\n\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n normalizeTriggeringRow(result, operationId) as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext as ExecutionContext<any>),\n );\n },\n );\n\n logDebug('🌱 Wired Level-B resource-lifecycle hooks', { resourceType });\n }\n}\n\n// NOTE — Boundary type narrowing\n//\n// The wired hook callbacks consume payloads typed as `unknown` from the\n// `dispatchPre/PostOperationHooks` contract, but the dispatcher's\n// `runResourceLifecycle` declares `triggeringRow: { _id: string } &\n// Record<string, unknown>` (strict, by design — direct in-process\n// callers like custom resource impls benefit from the precise shape).\n// We bridge the two with a plain TypeScript cast at each call site\n// rather than a runtime asserter for two reasons:\n//\n// 1. The dispatcher already runs the canonical `_id` runtime check\n// (`runResourceLifecycle` line ~315) and throws `CONFIGURATION`\n// with a descriptive message that names the resource and phase.\n// A redundant assertion here would either duplicate that error\n// message (drift risk) or shadow it with a less-informative one.\n// 2. The cost of the dispatcher's existing check is negligible\n// relative to the Level-B dispatch path it gates (internal exec\n// context creation, seed selection, ledger write); moving the\n// check earlier would not measurably change the cost envelope.\n//\n// `normalizeTriggeringRow` (below) handles the only legitimate shape\n// transform required at the boundary: lifting `DELETE` / `DELETE_MANY`\n// `string` ids into `{ _id: string }` row stubs so the dispatcher's\n// runtime check has something to match.\n\n/**\n * Map a `CoreResourceOperation` identifier observed by the pre-hook\n * bucket to the matching Level-B `BEFORE_*` phase. Bulk variants\n * (`CREATE_MANY`/`UPDATE_MANY`/`DELETE_MANY`) map to the SAME phase as\n * their singular counterpart — the bulk vs single distinction is\n * resolved by {@link isBulkOperationId} and per-row fan-out happens at\n * the wired callback. Returns `null` for operations the seed layer\n * does not observe (READ, LIST, RELATE_*, custom operations whose id\n * does not match a `CoreResourceOperation`).\n */\nfunction preOperationIdToPhase(\n operationId: string,\n): DataSeedResourceLifecyclePhase | null {\n switch (operationId) {\n case CoreResourceOperation.CREATE:\n case CoreResourceOperation.CREATE_MANY:\n return DataSeedResourceLifecyclePhase.BEFORE_CREATE;\n case CoreResourceOperation.UPDATE:\n case CoreResourceOperation.UPDATE_MANY:\n return DataSeedResourceLifecyclePhase.BEFORE_UPDATE;\n case CoreResourceOperation.DELETE:\n case CoreResourceOperation.DELETE_MANY:\n return DataSeedResourceLifecyclePhase.BEFORE_DELETE;\n default:\n return null;\n }\n}\n\n/**\n * Map a `CoreResourceOperation` identifier observed by the post-hook\n * bucket to the matching Level-B `AFTER_*` phase. Bulk variants map to\n * the same phase as their singular counterpart — see\n * {@link preOperationIdToPhase} for rationale.\n */\nfunction postOperationIdToPhase(\n operationId: string,\n): DataSeedResourceLifecyclePhase | null {\n switch (operationId) {\n case CoreResourceOperation.CREATE:\n case CoreResourceOperation.CREATE_MANY:\n return DataSeedResourceLifecyclePhase.AFTER_CREATE;\n case CoreResourceOperation.UPDATE:\n case CoreResourceOperation.UPDATE_MANY:\n return DataSeedResourceLifecyclePhase.AFTER_UPDATE;\n case CoreResourceOperation.DELETE:\n case CoreResourceOperation.DELETE_MANY:\n return DataSeedResourceLifecyclePhase.AFTER_DELETE;\n default:\n return null;\n }\n}\n\n/**\n * `true` when `operationId` is one of the bulk core operations whose\n * payload is an array of rows (or array of ids for `DELETE_MANY`).\n * Centralised here so the wired callbacks have a single place to ask\n * \"should I fan out per-row?\".\n */\nfunction isBulkOperationId(operationId: string): boolean {\n return (\n operationId === CoreResourceOperation.CREATE_MANY\n || operationId === CoreResourceOperation.UPDATE_MANY\n || operationId === CoreResourceOperation.DELETE_MANY\n );\n}\n\n/**\n * Defensive: assert that the bulk-op payload is actually an array, with\n * a `CONFIGURATION`-shaped error message that points at the offending\n * operation. This guards the wired callback against an upstream\n * regression where a `*_MANY` op accidentally passes a single object\n * (which would silently iterate over its own keys via `for..of` on a\n * non-iterable and explode in confusing ways).\n */\nfunction ensureRowArray(payload: unknown, operationId: string): unknown[] {\n if (!Array.isArray(payload)) {\n throw new Error(\n `[data-seeding] Level-B hook for bulk operation '${operationId}' expected an array payload but received ${\n payload === null ? 'null' : typeof payload\n }. This indicates an upstream contract regression in ServicesRegistryHandlerBackendService — singular ops should not be routed through the bulk path.`,\n );\n }\n return payload;\n}\n\n/**\n * Normalise the post-hook payload for `DELETE` / `DELETE_MANY` whose\n * underlying repository contract returns the deleted id(s) as\n * `string` (singular) or `string[]` (bulk) rather than the full row.\n * The dispatcher's `runResourceLifecycle` requires\n * `triggeringRow._id` to anchor the `DataSeedRun` ledger, so we lift\n * the id back into a synthetic `{ _id: string }` shape on the way in.\n *\n * For all non-delete operations the payload is already a row-shaped\n * object (or array of row-shaped objects) so we pass it through\n * unchanged. F-11 (review): centralising here means `runResourceLifecycle`\n * itself never has to know about the delete-id-only quirk and stays\n * uniform across all phases.\n */\nfunction normalizeTriggeringRow(\n payload: unknown,\n operationId: string,\n): unknown {\n if (\n operationId === CoreResourceOperation.DELETE\n || operationId === CoreResourceOperation.DELETE_MANY\n ) {\n if (typeof payload === 'string') {\n return { _id: payload };\n }\n }\n return payload;\n}\n\n// ---------------------------------------------------------------------------\n// LAZY-access guard wiring (Phase 5, plan decision P5-D=D1)\n// ---------------------------------------------------------------------------\n\n/**\n * Walk the data-seed registry and register pre-operation hooks on the\n * services registry so that every `lazyAccess` trigger declared by a\n * LAZY seed fires the dispatcher's `runLazy` when the matching\n * `(resourceType, operation)` is invoked.\n *\n * Symmetric companion to {@link wireLevelBResourceLifecycleHooks},\n * with three deliberate differences:\n *\n * 1. **Pre-only**: LAZY guards must materialise the seed row BEFORE\n * the triggering op reads it. There is no symmetric POST hook —\n * a post-op LAZY guard would be useless (the user already saw an\n * empty result).\n * 2. **Operation-scoped routing**: a single resource may declare\n * multiple LAZY triggers on different operations (READ + LIST +\n * a mutation). One pre-hook per resource is registered, and the\n * callback routes via an internal `Set<operationId>` lookup so\n * that operations NOT covered by any LAZY trigger short-circuit\n * in O(1) without invoking the dispatcher.\n * 3. **Re-entrancy is the dispatcher's job**: the registered hook\n * calls `dispatcher.runLazy` unconditionally; the dispatcher\n * itself short-circuits on `INTERNALLY_INITIATED_REQUEST` /\n * `subCallDepth > 0` (Phase 5.B re-entrancy hardening). Pulling\n * that check up here would only duplicate the guard.\n *\n * Failure propagation (plan decision P5-A=A1): pre-hook errors\n * propagate unchanged, aborting the triggering operation. The\n * services registry's `dispatchPreOperationHooks` does NOT swallow\n * pre-hook failures — symmetric with the Phase-3 BEFORE_* veto\n * contract. This is intentional: a LAZY seed whose body fails\n * during a USER-facing READ must surface the error, not silently\n * return an incomplete dataset.\n *\n * **Coalescing**: if multiple LAZY seeds target the same `resourceType`\n * (regardless of operation), exactly ONE pre-hook is registered for\n * that resource. The internal route map is per-resource so the\n * callback can quickly decide whether the firing op is covered.\n * This keeps the services-registry footprint at O(distinct LAZY\n * resource types) regardless of seed count.\n *\n * **Read-side hook prerequisite (P5-H=H1)**: this helper assumes the\n * services registry's pre-hook chain fires for `READ` and `LIST` (in\n * addition to the mutating ops). The READ/LIST widening lives in\n * `services-registry-handler.backend.service.ts` per Phase 5 P5-H\n * decision. Wiring here without that widening would yield a silently\n * inert LAZY feature (the canonical first-access trigger is a READ\n * or LIST).\n */\nfunction wireLazyAccessGuards(input: {\n registry: DataSeedRegistryBackendService;\n dispatcher: DataSeedDispatcherBackendService;\n servicesRegistry: ServicesRegistryHandlerBackendService;\n logDebug: (message: string, context?: Record<string, unknown>) => void;\n}): void {\n const { registry, dispatcher, servicesRegistry, logDebug } = input;\n\n const lazySeeds = registry.getSeedsByTriggerKind(\n DataSeedTriggerKind.LAZY_ACCESS,\n );\n if (lazySeeds.length === 0) {\n logDebug('🌱 No LAZY (lazy-access) data-seeds registered — skipping guard wiring');\n return;\n }\n\n // Aggregate the distinct `(resourceType, operation)` pairs declared\n // across all LAZY seeds. The grouping key is `resourceType` (one\n // pre-hook per resource), and the inner `Set<string>` carries the\n // operations that resource opts into. A single pre-hook callback\n // can then route in O(1) on `operationId in operations` instead\n // of re-walking the registry on every op.\n //\n // Defensive note: the factory invariant \"exactly one lazyAccess\n // trigger per LAZY seed\" means the inner loop usually only runs\n // once per seed, but we still iterate the trigger array so a\n // future loosening of the invariant does not silently miss\n // additional triggers.\n const routeMap = new Map<string, Set<string>>();\n for (const seed of lazySeeds) {\n for (const trigger of seed.triggers) {\n if (trigger.triggerKind !== DataSeedTriggerKind.LAZY_ACCESS) continue;\n const operations = routeMap.get(trigger.resourceType) ?? new Set<string>();\n operations.add(trigger.operation);\n routeMap.set(trigger.resourceType, operations);\n }\n }\n\n // The `lazySeeds.length > 0` guard above does not preclude a\n // degenerate registry where every LAZY seed somehow exposes zero\n // matching `lazyAccess` triggers (e.g. a future trigger schema\n // change that drops the discriminator). Surface that as a no-op\n // wiring with a debug log — registering a zero-route hook would\n // pay the per-call dispatch cost forever for nothing.\n if (routeMap.size === 0) {\n logDebug('🌱 LAZY seeds present but contributed no lazyAccess triggers — skipping guard wiring');\n return;\n }\n\n for (const [resourceType, operations] of routeMap) {\n servicesRegistry.registerPreOperationHook(\n resourceType,\n async (rt, operationId, _data, executionContext) => {\n // O(1) route decision. Operations on this resource that are\n // NOT in the LAZY operation set short-circuit here without\n // touching the dispatcher — keeps unrelated traffic on this\n // resource (e.g. CREATEs on a resource whose LAZY trigger is\n // LIST) free of any seeding cost.\n if (!operations.has(operationId)) return;\n\n // P5-A=A1: any throw from `runLazy` (including CONFIGURATION\n // errors from `resolveLazyMatchScopeAnchor` and per-seed\n // failures rethrown by the dispatcher) propagates to the\n // services-registry pre-hook chain, which propagates to the\n // triggering op. The wired callback intentionally adds NO\n // try/catch — see the helper's header JSDoc for the rationale.\n await dispatcher.runLazy(\n rt,\n operationId,\n narrowExecutionContextForDataSeeding(executionContext),\n );\n },\n );\n\n logDebug('🌱 Wired LAZY-access guard', {\n resourceType,\n operations: Array.from(operations),\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"data-seed-application-bootstrap.backend.js","sourceRoot":"","sources":["../../../../../src/data-seeding/services/data-seed-application-bootstrap.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The data-seeding subsystem's startup wiring, in the two halves `StartApplicationBackendService.initialize()`\n * calls at different steps:\n * - {@link wireDataSeedingDeclarations} (Step 5.9, BEFORE the HTTP server listens) — register the seed\n * corpus and scope resolvers, wire the Level-B resource-lifecycle hooks and the LAZY guards;\n * - {@link runDataSeedingApplicationLifecyclePasses} (Step 7.7) — the application-lifecycle passes.\n *\n * Extracted from `initialize()` so the (small) seeding contract can be exercised with mocked dependencies,\n * without the 30+ dependency boot chain. There is deliberately no one-call composite: the split is what\n * keeps a request from reaching the services registry before its seed hooks exist, and a composite would\n * invite a caller to run both after its server had started.\n *\n * See `.cursor/plans/seeding-first-class.md` §2.F for the full rationale\n * (including why platform-mode boots must NOT dispatch and why dispatcher\n * failures here are intentionally fatal rather than swallowed).\n */\nimport { CoreResourceOperation } from '@wildo-ai/saas-models';\nimport { ENGINE_OWNED_DATA_SEEDS } from '~wildo-backend-lib/data-seeding/services/engine-owned-data-seeds.backend';\nimport { admitDataSeedsForApplicationEnvironment } from '~wildo-backend-lib/data-seeding/services/data-seed-environment-admission.backend';\nimport { readDeclaredApplicationEnvironment } from '~wildo-backend-lib/config/models/app-env-config.backend.schemas';\nimport {\n DataSeedResourceLifecyclePhase,\n DataSeedTriggerKind,\n type DataSeedDefinition,\n} from '~wildo-backend-lib/data-seeding/models/data-seed.shared.schemas';\nimport { DataSeedRunStatus } from '~wildo-backend-lib/data-seeding/models/data-seed-run.shared.schemas';\nimport { describeCaughtError } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\n\nimport type { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { narrowExecutionContextForDataSeeding } from '~wildo-backend-lib/data-seeding/models/data-seed-execution-context.shared.types';\nimport type { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport type { DataSeedRegistryBackendService } from '~wildo-backend-lib/data-seeding/services/data-seed-registry.backend.service';\nimport type {\n DataSeedScopeResolver,\n DataSeedScopeResolverRegistryBackendService,\n} from '~wildo-backend-lib/data-seeding/services/data-seed-scope-resolver-registry.backend.service';\nimport type { DataSeedDispatcherBackendService } from '~wildo-backend-lib/data-seeding/services/data-seed-dispatcher.backend.service';\nimport type { ServicesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/services/services-registry-handler.backend.service';\n\n/** @wildo_source:part:start saas.data-seeding.bootstrap-wiring facet:kind:framework-api facet:audience:operator */\n/**\n * Inputs to the data-seeding bootstrap halves ({@link wireDataSeedingDeclarations},\n * {@link runDataSeedingApplicationLifecyclePasses}). Kept as a typed\n * record (instead of positional args) so callers from different startup\n * orchestrators stay readable when the surface grows in later phases.\n */\nexport interface DataSeedApplicationBootstrapInput {\n /**\n * Declarative seed definitions collected from `BackendDomainModule.seeds`\n * and forwarded by the orchestrator. `undefined` is treated as `[]`\n * (registration still runs to keep the registry's \"initialized\" semantics\n * consistent across boots).\n */\n dataSeeds: readonly DataSeedDefinition[] | undefined;\n\n /**\n * Phase 7 / Item 47 (cron scheduler) — Slice 3.\n *\n * Aggregated `customResolverKey → DataSeedScopeResolver` map collected\n * from `BackendDomainModule.dataSeedScopeResolvers` (cross-module\n * duplicate keys already rejected by `mergeBackendDomainModules`).\n * `undefined` / `{}` is treated identically — a no-op registration\n * that leaves the registry empty so future cron seeds with\n * `iterationStrategy === CUSTOM_RESOLVER` fail loudly at first\n * invocation rather than silently iterating zero rows.\n */\n dataSeedScopeResolvers?: Readonly<Record<string, DataSeedScopeResolver>>;\n\n /**\n * `true` when the caller is the platform apps-manager (or another\n * profile that bootstraps tenant apps later through their own\n * `StartApplicationBackendService.initialize()` call). Dispatch is\n * skipped in that case — see plan §2.F.\n */\n isPlatformMode: boolean;\n\n logger: LoggerService;\n /** Used to log Step 7.7 progress under the same channel as the host service. */\n logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n registry: DataSeedRegistryBackendService;\n /**\n * Phase 7 / Item 47 — Slice 3. Hydrated from\n * {@link DataSeedApplicationBootstrapInput.dataSeedScopeResolvers}\n * BEFORE Level-A dispatch / Level-B + LAZY hook wiring (registration\n * is dispatch-independent — keeping it before the platform-mode\n * skip ensures both the platform process AND tenant boots see the\n * same registry state).\n */\n scopeResolverRegistry: DataSeedScopeResolverRegistryBackendService;\n dispatcher: DataSeedDispatcherBackendService;\n\n /**\n * Required for Level-B auto-wiring (Phase 3, plan decision P3-B=B1).\n * After registering seeds, the bootstrap walks the registry and\n * registers pre/post-operation hooks on this services registry so\n * that any resource-lifecycle trigger (`beforeCreate`/`afterCreate`/\n * `beforeUpdate`/`afterUpdate`/`beforeDelete`/`afterDelete`) fires\n * the dispatcher's `runResourceLifecycle` when the matching\n * operation runs.\n */\n servicesRegistry: ServicesRegistryHandlerBackendService;\n}\n\n/**\n * The half of the bootstrap that must be live before ANY request can reach the services registry:\n * register the seed corpus and the scope resolvers, then wire the Level-B resource-lifecycle hooks and\n * the LAZY-access guards. Pure in-memory wiring — no read, no write, no remote call.\n *\n * ## Contract of the two halves, taken together\n *\n * - Registration ALWAYS runs (even in platform mode) so future Level-B\n * and lazy-access dispatch paths can resolve seed declarations.\n * - Dispatcher is invoked twice in order: `onAppInit` MUST precede\n * `onAppVersionUpgrade` because the upgrade pass may mutate rows\n * created by the init pass.\n * - Any throw from the dispatcher is rethrown unchanged. Booting with\n * a half-applied seed corpus would silently break downstream ops, so\n * the dispatcher's FAILED `DataSeedRun` ledger entries (which the\n * next boot retries) are the intended recovery surface — not a try/\n * catch here.\n *\n * Operational invariants (operator-facing):\n * 1. Seed registry hydrated unconditionally (platform mode + tenant boot)\n * so Level-B + LAZY hooks can resolve declarations regardless of who\n * runs the Level-A pass.\n * 2. Custom-resolver registry hydrated next (Item 47 / Slice 3) — empty\n * map is a no-op call, so any future cron seed declaring\n * `iterationStrategy === CUSTOM_RESOLVER` against an unregistered key\n * fails loudly at first invocation rather than silently iterating\n * zero rows.\n * 3. Level-B hooks wired BEFORE Level-A dispatch (P3-B=B1) — per-tenant\n * resource-lifecycle seeds must be live before any boot pass, so\n * cross-tenant boots see consistent hook state.\n * 4. LAZY guards wired symmetric with Level-B (P5-D=D1) — pre-only\n * hooks per `(resourceType, operation)` pair, coalesced to one hook\n * per resource type with internal route map.\n * 5. Platform-mode skip: dispatch is suppressed for the apps-manager\n * profile (it bootstraps tenant apps which run their own pass).\n *\n * ## Why it is its own step, and runs before the HTTP server listens\n *\n * The HTTP server starts listening inside Step 6, and this wiring used to run inside Step 7.7, after\n * queue-worker initialization and an AWAITED cron-job sync. Every request in between reached a services\n * registry with no seed hooks: an organization created then got none of its `afterCreate` defaults\n * (SSO, SCIM, SIEM-export and webhook configuration singletons), silently and permanently, because a\n * post-create hook that was not registered has nothing to retry. Measured 2026-09-14 on Wonder Todos:\n * the cron sync waited ~20 minutes on a platform that was not ready, the backend answered\n * `/health/ready` 200 throughout, and every organization registered in that window had no\n * `organizationSsoConfig` row.\n *\n * So the declarations are wired where the registry becomes reachable, and only the application-lifecycle\n * passes ({@link runDataSeedingApplicationLifecyclePasses}) stay at Step 7.7.\n */\nexport function wireDataSeedingDeclarations(\n input: Pick<\n DataSeedApplicationBootstrapInput,\n 'dataSeeds' | 'dataSeedScopeResolvers' | 'logDebug' | 'registry' | 'scopeResolverRegistry' | 'dispatcher' | 'servicesRegistry'\n > & {\n /**\n * Whether this runtime owns the application's DATA LIFECYCLE, and therefore whether the\n * framework's own seeds belong in its registry.\n *\n * Only the application backend does. A MINION or WORKER runs the same bootstrap with a SCOPED\n * services registry that refuses unscoped writes, and registering a Level-B seed there makes\n * `wireLevelBResourceLifecycleHooks` ask for exactly that access — measured 2026-09-18, when\n * registering the engine's set unconditionally crash-looped the marketing-scrapper minion with\n * `Minion 'marketingScrapper' is not allowed to write resource '<unscoped-registry-access>'`.\n *\n * The application's OWN `dataSeeds` are registered regardless, exactly as before: what an\n * application passes is its business, and this flag governs only what the FRAMEWORK adds.\n */\n readonly registersEngineOwnedSeeds: boolean;\n },\n): void {\n const { dataSeeds, dataSeedScopeResolvers, logDebug, registry, scopeResolverRegistry, dispatcher, servicesRegistry, registersEngineOwnedSeeds } = input;\n\n /*\n * FRAMEWORK-owned seeds first, then the application's.\n *\n * These used to be the application's job: each one spread `enterpriseAuthConfigOrgSeedDefinitions`\n * and its siblings into a module of its own, and an application that did not know to do so had no\n * per-scope configuration rows at all — which is exactly what the newer dogfood application did,\n * leaving every SSO / SCIM / SIEM settings panel answering a 404. The framework's own defaults for\n * the framework's own resources are registered by the framework.\n *\n * Order matters only for the duplicate refusal below: registering the engine's set FIRST means an\n * application that still spreads one of them fails naming the framework's key, which is the half\n * of the collision a reader can act on.\n */\n const engineOwned = registersEngineOwnedSeeds ? ENGINE_OWNED_DATA_SEEDS : [];\n /*\n * Admitted over the MERGED list (#2234), so a restricted seed is withheld at registration whoever owns it — the\n * promise `allowedApplicationEnvironments` makes. The application's own seeds were already admitted where\n * `start-application` took them (its seeding gates read that set); re-admitting them here is idempotent, and it is\n * what reaches the engine's.\n */\n const seeds = admitDataSeedsForApplicationEnvironment([...engineOwned, ...(dataSeeds ?? [])], readDeclaredApplicationEnvironment(process.env)).admitted;\n logDebug('🌱 Registering data-seed definitions...', {\n count: seeds.length,\n engineOwned: engineOwned.length,\n applicationOwned: (dataSeeds ?? []).length,\n });\n registry.registerSeeds(seeds);\n logDebug('✅ Data-seed registry populated');\n\n // Phase 7 / Item 47 — Slice 3 — register custom-resolver scope iterators.\n // Runs unconditionally (even in platform mode + when the map is empty)\n // so the registry's lifecycle is symmetric with `DataSeedRegistry`'s\n // (always-initialized at boot). The empty-map case is a no-op call\n // that registers zero entries; future cron seeds (Slice 5) probing\n // the registry via `hasResolver` get a clean `false` instead of a\n // \"registry not initialized\" error.\n const resolversMap = dataSeedScopeResolvers ?? {};\n const resolverCount = Object.keys(resolversMap).length;\n logDebug('🌱 Registering data-seed scope resolvers...', { count: resolverCount });\n scopeResolverRegistry.registerResolvers(resolversMap);\n logDebug('✅ Data-seed scope-resolver registry populated', { count: resolverCount });\n\n // Phase 3 (P3-B=B1) — auto-wire Level-B hooks into the services\n // registry. Runs BEFORE the Level-A dispatch (and in platform mode\n // too) so Level-B seeds fire on per-tenant resource lifecycle events\n // regardless of who runs the Level-A pass.\n wireLevelBResourceLifecycleHooks({ registry, dispatcher, servicesRegistry, logDebug });\n\n // Phase 5 (P5-D=D1) — auto-wire LAZY-access guards into the services\n // registry. Symmetric with Level-B wiring (registers pre-operation\n // hooks per `(resourceType, operation)` pair declared by LAZY\n // seeds). Runs BEFORE the Level-A dispatch (and in platform mode\n // too — same rationale as the Level-B wiring above: lazy guards\n // are per-tenant request hooks, the host running the Level-A pass\n // is irrelevant to their availability at request time).\n wireLazyAccessGuards({ registry, dispatcher, servicesRegistry, logDebug });\n}\n\n/**\n * Step 7.7 — the application-lifecycle passes (`onAppInit`, then `onAppVersionUpgrade`, then the\n * non-fatal missing-defaults repair). Requires {@link wireDataSeedingDeclarations} to have run: the\n * dispatcher resolves seeds from the registry it populated. Skipped entirely in platform mode.\n */\nexport async function runDataSeedingApplicationLifecyclePasses(\n input: Pick<DataSeedApplicationBootstrapInput, 'isPlatformMode' | 'logger' | 'logDebug' | 'dispatcher'>,\n): Promise<void> {\n const { isPlatformMode, logger, logDebug, dispatcher } = input;\n\n if (isPlatformMode) {\n logDebug('🌱 Skipping application-lifecycle data-seed dispatch in platform mode (tenant apps will run their own pass via apps-manager)');\n return;\n }\n\n logger.info('🌱 Running application-lifecycle data seeds (onAppInit + onAppVersionUpgrade)...');\n try {\n const initReport = await dispatcher.runApplicationLifecycle('onAppInit');\n logDebug('✅ onAppInit data-seed pass complete', {\n seedsProcessed: initReport.seedsProcessed,\n completed: initReport.runs.filter((r) => r.status === DataSeedRunStatus.COMPLETED).length,\n partial: initReport.runs.filter((r) => r.status === DataSeedRunStatus.PARTIAL).length,\n failed: initReport.runs.filter((r) => r.status === DataSeedRunStatus.FAILED).length,\n });\n\n const upgradeReport = await dispatcher.runApplicationLifecycle('onAppVersionUpgrade');\n logDebug('✅ onAppVersionUpgrade data-seed pass complete', {\n seedsProcessed: upgradeReport.seedsProcessed,\n completed: upgradeReport.runs.filter((r) => r.status === DataSeedRunStatus.COMPLETED).length,\n partial: upgradeReport.runs.filter((r) => r.status === DataSeedRunStatus.PARTIAL).length,\n failed: upgradeReport.runs.filter((r) => r.status === DataSeedRunStatus.FAILED).length,\n });\n } catch (error) {\n logger.error('❌ Data-seed application-lifecycle pass failed (fatal):', error);\n throw error;\n }\n\n // Missing-defaults repair (#554) — after both lifecycle passes, and NON-fatal, which is the opposite\n // of the passes above on purpose. Those passes create rows downstream operations need, so booting\n // without them is worse than not booting. This pass only completes rows that are already readable,\n // and its per-seed failures are on the ledger and retried next boot; refusing to start over it\n // would make a defaults gap into an outage.\n try {\n const repairReport = await dispatcher.repairMissingDefaultsOnLifecycleSeededRows();\n logDebug('✅ Missing-defaults repair pass complete', {\n repaired: repairReport.runs.length,\n alreadyRepaired: repairReport.seedsAlreadyRepaired,\n failedSeedKeys: repairReport.failedSeedKeys,\n });\n } catch (error) {\n logger.error('❌ Missing-defaults repair pass failed (non-fatal — retried on the next boot):', error);\n }\n}\n/** @wildo_source:part:end saas.data-seeding.bootstrap-wiring */\n\n// ---------------------------------------------------------------------------\n// Level-B hook wiring (Phase 3, plan decision P3-B=B1)\n// ---------------------------------------------------------------------------\n\n/**\n * Walk the data-seed registry and register pre/post-operation hooks on\n * the services registry so that every `resourceLifecycle` trigger\n * declared by a seed fires the dispatcher's `runResourceLifecycle`\n * when the matching resource operation runs.\n *\n * One pre-hook and one post-hook are registered per resource type that\n * appears in any Level-B trigger. Inside the hook, we match the\n * `operationIdentifier` against the registered seed's phase so that a\n * `beforeCreate` seed only fires on CREATE / CREATE_MANY, `beforeDelete`\n * only on DELETE / DELETE_MANY, etc. This grouping means:\n * - The services registry stays at most one entry per `(resourceType,\n * pre|post)` regardless of how many seeds target that resource.\n * - Adding a new seed on an already-wired resource does not re-\n * register a duplicate hook.\n *\n * Bulk operations (`CREATE_MANY` / `UPDATE_MANY` / `DELETE_MANY`) are\n * mapped to the same phase as their singular counterpart and dispatched\n * **per row** rather than once per batch (plan decision P3-F=F1: each\n * `DataSeedRun` ledger row anchors to a single triggering row's `_id`,\n * so a single bulk op produces N ledger rows). This preserves the\n * \"one anchor → one ledger\" invariant and keeps idempotency keys\n * identical across the singular and bulk callers of an operation.\n *\n * Failure propagation:\n * - PRE-hooks (beforeCreate/Update/Delete + bulk variants) throw into\n * `dispatchPreOperationHooks` which does NOT catch — errors bubble\n * to the triggering op (plan decision P3-A=A1, short-circuit). For\n * bulk pre-hooks, the loop is intentionally NOT wrapped in try/catch:\n * a failure on row N short-circuits rows N+1..M and aborts the\n * parent operation BEFORE any side effect (this matches the\n * semantics required by `executePreOperationLogic`'s callers).\n * - POST-hooks (afterCreate/Update/Delete + bulk variants) throw into\n * `dispatchPostOperationHooks` which catches + logs (plan decision\n * P3-D=D1, failure contained to the `DataSeedRun` ledger). For bulk\n * post-hooks each row is wrapped in its OWN try/catch so a single\n * failed row's `runResourceLifecycle` does not skip the remaining\n * rows in the batch.\n *\n * NOTE — BEFORE_CREATE limitation: the pre-hook receives the incoming\n * create payload which does NOT yet carry an `_id`. The dispatcher\n * asserts `triggeringRow._id` presence and throws `CONFIGURATION`\n * otherwise, meaning a Level-B seed on `BEFORE_CREATE` that tries to\n * actually upsert entries will fail loudly with a descriptive error.\n * BEFORE_CREATE is still useful as a pure VETO point (a seed whose\n * computed `entry()` validates cross-resource state and throws to\n * short-circuit) — the throw happens before `runAdditiveSeed` ever\n * reaches the `_id` check. Enforcement lives in the dispatcher rather\n * than here so a single error shape covers both direct and wired\n * callers of `runResourceLifecycle`.\n */\nfunction wireLevelBResourceLifecycleHooks(input: {\n registry: DataSeedRegistryBackendService;\n dispatcher: DataSeedDispatcherBackendService;\n servicesRegistry: ServicesRegistryHandlerBackendService;\n logDebug: (message: string, context?: Record<string, unknown>) => void;\n}): void {\n const { registry, dispatcher, servicesRegistry, logDebug } = input;\n\n const levelBSeeds = registry.getSeedsByTriggerKind(\n DataSeedTriggerKind.RESOURCE_LIFECYCLE,\n );\n if (levelBSeeds.length === 0) {\n logDebug('🌱 No Level-B (resource-lifecycle) data-seeds registered — skipping hook wiring');\n return;\n }\n\n // Collect the set of resource types that need hooks. `Set` is enough\n // here — we register exactly one pre-hook + one post-hook per\n // resource type and let the dispatcher filter per-phase internally\n // via `selectLevelBSeedsForPhase`. This keeps wiring O(distinct\n // resource types) regardless of seed count.\n const wiredResourceTypes = new Set<string>();\n for (const seed of levelBSeeds) {\n for (const trigger of seed.triggers) {\n if (trigger.triggerKind === DataSeedTriggerKind.RESOURCE_LIFECYCLE) {\n wiredResourceTypes.add(trigger.resourceType);\n }\n }\n }\n\n for (const resourceType of wiredResourceTypes) {\n servicesRegistry.registerPreOperationHook(\n resourceType,\n async (rt, operationId, data, executionContext) => {\n const phase = preOperationIdToPhase(operationId);\n if (!phase) return;\n\n if (isBulkOperationId(operationId)) {\n // Per-row dispatch (P3-F=F1). Pre-hook failures intentionally\n // short-circuit the remaining rows (no try/catch) — a veto\n // from row N must abort the entire bulk op.\n //\n // We apply `normalizeTriggeringRow` symmetrically with the\n // post-hook path so that DELETE_MANY's id-only payload\n // (lifted to `{_id}`) and any future bulk-op payload\n // contracts are handled in one place. The pre-hook payload\n // for `UPDATE_MANY` is the per-row shape `{...patch, _id}`\n // materialised by `executePreOperationLogic` (Phase 3.2 R2-1\n // / R2-2 fix); this loop iterates over those rows.\n const rows = ensureRowArray(data, operationId).map((row) =>\n normalizeTriggeringRow(row, operationId),\n );\n for (const row of rows) {\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n row as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext),\n );\n }\n return;\n }\n\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n normalizeTriggeringRow(data, operationId) as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext),\n );\n },\n );\n\n servicesRegistry.registerPostOperationHook(\n resourceType,\n async (rt, operationId, result, executionContext) => {\n const phase = postOperationIdToPhase(operationId);\n if (!phase) return;\n\n if (isBulkOperationId(operationId)) {\n const rows = ensureRowArray(result, operationId).map((row) =>\n normalizeTriggeringRow(row, operationId),\n );\n // Each row gets its own try/catch so a single failed seed\n // dispatch does not silently skip the remaining rows in the\n // batch. The outer `dispatchPostOperationHooks` already\n // contains failures for the SINGLE-call case (P3-D=D1); we\n // replicate that containment per-row for bulk so partial\n // failures still let the rest of the batch run.\n for (const row of rows) {\n try {\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n row as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext as ExecutionContext<any>),\n );\n } catch (error) {\n logDebug('🌱 Level-B per-row post-hook dispatch failed (contained — see DataSeedRun ledger)', {\n resourceType: rt,\n operationId,\n phase,\n error: describeCaughtError(error),\n });\n }\n }\n return;\n }\n\n await dispatcher.runResourceLifecycle(\n rt,\n phase,\n normalizeTriggeringRow(result, operationId) as { _id: string } & Record<string, unknown>,\n narrowExecutionContextForDataSeeding(executionContext as ExecutionContext<any>),\n );\n },\n );\n\n logDebug('🌱 Wired Level-B resource-lifecycle hooks', { resourceType });\n }\n}\n\n// NOTE — Boundary type narrowing\n//\n// The wired hook callbacks consume payloads typed as `unknown` from the\n// `dispatchPre/PostOperationHooks` contract, but the dispatcher's\n// `runResourceLifecycle` declares `triggeringRow: { _id: string } &\n// Record<string, unknown>` (strict, by design — direct in-process\n// callers like custom resource impls benefit from the precise shape).\n// We bridge the two with a plain TypeScript cast at each call site\n// rather than a runtime asserter for two reasons:\n//\n// 1. The dispatcher already runs the canonical `_id` runtime check\n// (`runResourceLifecycle` line ~315) and throws `CONFIGURATION`\n// with a descriptive message that names the resource and phase.\n// A redundant assertion here would either duplicate that error\n// message (drift risk) or shadow it with a less-informative one.\n// 2. The cost of the dispatcher's existing check is negligible\n// relative to the Level-B dispatch path it gates (internal exec\n// context creation, seed selection, ledger write); moving the\n// check earlier would not measurably change the cost envelope.\n//\n// `normalizeTriggeringRow` (below) handles the only legitimate shape\n// transform required at the boundary: lifting `DELETE` / `DELETE_MANY`\n// `string` ids into `{ _id: string }` row stubs so the dispatcher's\n// runtime check has something to match.\n\n/**\n * Map a `CoreResourceOperation` identifier observed by the pre-hook\n * bucket to the matching Level-B `BEFORE_*` phase. Bulk variants\n * (`CREATE_MANY`/`UPDATE_MANY`/`DELETE_MANY`) map to the SAME phase as\n * their singular counterpart — the bulk vs single distinction is\n * resolved by {@link isBulkOperationId} and per-row fan-out happens at\n * the wired callback. Returns `null` for operations the seed layer\n * does not observe (READ, LIST, RELATE_*, custom operations whose id\n * does not match a `CoreResourceOperation`).\n */\nfunction preOperationIdToPhase(\n operationId: string,\n): DataSeedResourceLifecyclePhase | null {\n switch (operationId) {\n case CoreResourceOperation.CREATE:\n case CoreResourceOperation.CREATE_MANY:\n return DataSeedResourceLifecyclePhase.BEFORE_CREATE;\n case CoreResourceOperation.UPDATE:\n case CoreResourceOperation.UPDATE_MANY:\n return DataSeedResourceLifecyclePhase.BEFORE_UPDATE;\n case CoreResourceOperation.DELETE:\n case CoreResourceOperation.DELETE_MANY:\n return DataSeedResourceLifecyclePhase.BEFORE_DELETE;\n default:\n return null;\n }\n}\n\n/**\n * Map a `CoreResourceOperation` identifier observed by the post-hook\n * bucket to the matching Level-B `AFTER_*` phase. Bulk variants map to\n * the same phase as their singular counterpart — see\n * {@link preOperationIdToPhase} for rationale.\n */\nfunction postOperationIdToPhase(\n operationId: string,\n): DataSeedResourceLifecyclePhase | null {\n switch (operationId) {\n case CoreResourceOperation.CREATE:\n case CoreResourceOperation.CREATE_MANY:\n return DataSeedResourceLifecyclePhase.AFTER_CREATE;\n case CoreResourceOperation.UPDATE:\n case CoreResourceOperation.UPDATE_MANY:\n return DataSeedResourceLifecyclePhase.AFTER_UPDATE;\n case CoreResourceOperation.DELETE:\n case CoreResourceOperation.DELETE_MANY:\n return DataSeedResourceLifecyclePhase.AFTER_DELETE;\n default:\n return null;\n }\n}\n\n/**\n * `true` when `operationId` is one of the bulk core operations whose\n * payload is an array of rows (or array of ids for `DELETE_MANY`).\n * Centralised here so the wired callbacks have a single place to ask\n * \"should I fan out per-row?\".\n */\nfunction isBulkOperationId(operationId: string): boolean {\n return (\n operationId === CoreResourceOperation.CREATE_MANY\n || operationId === CoreResourceOperation.UPDATE_MANY\n || operationId === CoreResourceOperation.DELETE_MANY\n );\n}\n\n/**\n * Defensive: assert that the bulk-op payload is actually an array, with\n * a `CONFIGURATION`-shaped error message that points at the offending\n * operation. This guards the wired callback against an upstream\n * regression where a `*_MANY` op accidentally passes a single object\n * (which would silently iterate over its own keys via `for..of` on a\n * non-iterable and explode in confusing ways).\n */\nfunction ensureRowArray(payload: unknown, operationId: string): unknown[] {\n if (!Array.isArray(payload)) {\n throw new Error(\n `[data-seeding] Level-B hook for bulk operation '${operationId}' expected an array payload but received ${\n payload === null ? 'null' : typeof payload\n }. This indicates an upstream contract regression in ServicesRegistryHandlerBackendService — singular ops should not be routed through the bulk path.`,\n );\n }\n return payload;\n}\n\n/**\n * Normalise the post-hook payload for `DELETE` / `DELETE_MANY` whose\n * underlying repository contract returns the deleted id(s) as\n * `string` (singular) or `string[]` (bulk) rather than the full row.\n * The dispatcher's `runResourceLifecycle` requires\n * `triggeringRow._id` to anchor the `DataSeedRun` ledger, so we lift\n * the id back into a synthetic `{ _id: string }` shape on the way in.\n *\n * For all non-delete operations the payload is already a row-shaped\n * object (or array of row-shaped objects) so we pass it through\n * unchanged. F-11 (review): centralising here means `runResourceLifecycle`\n * itself never has to know about the delete-id-only quirk and stays\n * uniform across all phases.\n */\nfunction normalizeTriggeringRow(\n payload: unknown,\n operationId: string,\n): unknown {\n if (\n operationId === CoreResourceOperation.DELETE\n || operationId === CoreResourceOperation.DELETE_MANY\n ) {\n if (typeof payload === 'string') {\n return { _id: payload };\n }\n }\n return payload;\n}\n\n// ---------------------------------------------------------------------------\n// LAZY-access guard wiring (Phase 5, plan decision P5-D=D1)\n// ---------------------------------------------------------------------------\n\n/**\n * Walk the data-seed registry and register pre-operation hooks on the\n * services registry so that every `lazyAccess` trigger declared by a\n * LAZY seed fires the dispatcher's `runLazy` when the matching\n * `(resourceType, operation)` is invoked.\n *\n * Symmetric companion to {@link wireLevelBResourceLifecycleHooks},\n * with three deliberate differences:\n *\n * 1. **Pre-only**: LAZY guards must materialise the seed row BEFORE\n * the triggering op reads it. There is no symmetric POST hook —\n * a post-op LAZY guard would be useless (the user already saw an\n * empty result).\n * 2. **Operation-scoped routing**: a single resource may declare\n * multiple LAZY triggers on different operations (READ + LIST +\n * a mutation). One pre-hook per resource is registered, and the\n * callback routes via an internal `Set<operationId>` lookup so\n * that operations NOT covered by any LAZY trigger short-circuit\n * in O(1) without invoking the dispatcher.\n * 3. **Re-entrancy is the dispatcher's job**: the registered hook\n * calls `dispatcher.runLazy` unconditionally; the dispatcher\n * itself short-circuits on `INTERNALLY_INITIATED_REQUEST` /\n * `subCallDepth > 0` (Phase 5.B re-entrancy hardening). Pulling\n * that check up here would only duplicate the guard.\n *\n * Failure propagation (plan decision P5-A=A1): pre-hook errors\n * propagate unchanged, aborting the triggering operation. The\n * services registry's `dispatchPreOperationHooks` does NOT swallow\n * pre-hook failures — symmetric with the Phase-3 BEFORE_* veto\n * contract. This is intentional: a LAZY seed whose body fails\n * during a USER-facing READ must surface the error, not silently\n * return an incomplete dataset.\n *\n * **Coalescing**: if multiple LAZY seeds target the same `resourceType`\n * (regardless of operation), exactly ONE pre-hook is registered for\n * that resource. The internal route map is per-resource so the\n * callback can quickly decide whether the firing op is covered.\n * This keeps the services-registry footprint at O(distinct LAZY\n * resource types) regardless of seed count.\n *\n * **Read-side hook prerequisite (P5-H=H1)**: this helper assumes the\n * services registry's pre-hook chain fires for `READ` and `LIST` (in\n * addition to the mutating ops). The READ/LIST widening lives in\n * `services-registry-handler.backend.service.ts` per Phase 5 P5-H\n * decision. Wiring here without that widening would yield a silently\n * inert LAZY feature (the canonical first-access trigger is a READ\n * or LIST).\n */\nfunction wireLazyAccessGuards(input: {\n registry: DataSeedRegistryBackendService;\n dispatcher: DataSeedDispatcherBackendService;\n servicesRegistry: ServicesRegistryHandlerBackendService;\n logDebug: (message: string, context?: Record<string, unknown>) => void;\n}): void {\n const { registry, dispatcher, servicesRegistry, logDebug } = input;\n\n const lazySeeds = registry.getSeedsByTriggerKind(\n DataSeedTriggerKind.LAZY_ACCESS,\n );\n if (lazySeeds.length === 0) {\n logDebug('🌱 No LAZY (lazy-access) data-seeds registered — skipping guard wiring');\n return;\n }\n\n // Aggregate the distinct `(resourceType, operation)` pairs declared\n // across all LAZY seeds. The grouping key is `resourceType` (one\n // pre-hook per resource), and the inner `Set<string>` carries the\n // operations that resource opts into. A single pre-hook callback\n // can then route in O(1) on `operationId in operations` instead\n // of re-walking the registry on every op.\n //\n // Defensive note: the factory invariant \"exactly one lazyAccess\n // trigger per LAZY seed\" means the inner loop usually only runs\n // once per seed, but we still iterate the trigger array so a\n // future loosening of the invariant does not silently miss\n // additional triggers.\n const routeMap = new Map<string, Set<string>>();\n for (const seed of lazySeeds) {\n for (const trigger of seed.triggers) {\n if (trigger.triggerKind !== DataSeedTriggerKind.LAZY_ACCESS) continue;\n const operations = routeMap.get(trigger.resourceType) ?? new Set<string>();\n operations.add(trigger.operation);\n routeMap.set(trigger.resourceType, operations);\n }\n }\n\n // The `lazySeeds.length > 0` guard above does not preclude a\n // degenerate registry where every LAZY seed somehow exposes zero\n // matching `lazyAccess` triggers (e.g. a future trigger schema\n // change that drops the discriminator). Surface that as a no-op\n // wiring with a debug log — registering a zero-route hook would\n // pay the per-call dispatch cost forever for nothing.\n if (routeMap.size === 0) {\n logDebug('🌱 LAZY seeds present but contributed no lazyAccess triggers — skipping guard wiring');\n return;\n }\n\n for (const [resourceType, operations] of routeMap) {\n servicesRegistry.registerPreOperationHook(\n resourceType,\n async (rt, operationId, _data, executionContext) => {\n // O(1) route decision. Operations on this resource that are\n // NOT in the LAZY operation set short-circuit here without\n // touching the dispatcher — keeps unrelated traffic on this\n // resource (e.g. CREATEs on a resource whose LAZY trigger is\n // LIST) free of any seeding cost.\n if (!operations.has(operationId)) return;\n\n // P5-A=A1: any throw from `runLazy` (including CONFIGURATION\n // errors from `resolveLazyMatchScopeAnchor` and per-seed\n // failures rethrown by the dispatcher) propagates to the\n // services-registry pre-hook chain, which propagates to the\n // triggering op. The wired callback intentionally adds NO\n // try/catch — see the helper's header JSDoc for the rationale.\n await dispatcher.runLazy(\n rt,\n operationId,\n narrowExecutionContextForDataSeeding(executionContext),\n );\n },\n );\n\n logDebug('🌱 Wired LAZY-access guard', {\n resourceType,\n operations: Array.from(operations),\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RuntimeEnvironment } from '@wildo-ai/saas-models';
|
|
2
|
+
import type { DataSeedDefinition } from '../../data-seeding/models/data-seed.shared.schemas';
|
|
3
|
+
/** Which seeds a deployment runs, and which it withholds because their declared environments exclude it. */
|
|
4
|
+
export interface DataSeedEnvironmentAdmission {
|
|
5
|
+
/** The seeds this deployment registers — every unrestricted seed, and every restricted one that lists its environment. */
|
|
6
|
+
readonly admitted: readonly DataSeedDefinition[];
|
|
7
|
+
/** The seeds withheld, by key, so the boot log can say what did not run and why. */
|
|
8
|
+
readonly withheldSeedKeys: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Select the seeds this deployment may run, from `allowedApplicationEnvironments` (#2234).
|
|
12
|
+
*
|
|
13
|
+
* Applied ONCE, where the application backend first takes its seeds, so every consumer downstream —
|
|
14
|
+
* registration, the scheduled-rerun gate, the any-seeds gate — reads the same admitted set. Filtering
|
|
15
|
+
* at registration alone would leave those gates counting seeds that never register.
|
|
16
|
+
*
|
|
17
|
+
* Fails CLOSED: `environment` is the DECLARED runtime environment (`readDeclaredApplicationEnvironment`),
|
|
18
|
+
* and when there is none a restricted seed is withheld rather than assumed to be development. That is
|
|
19
|
+
* the direction that matters: the point of a restriction is that demonstration rows never reach a
|
|
20
|
+
* production database, and an unknown environment may be production.
|
|
21
|
+
*/
|
|
22
|
+
export declare function admitDataSeedsForApplicationEnvironment(seeds: readonly DataSeedDefinition[], environment: RuntimeEnvironment | undefined): DataSeedEnvironmentAdmission;
|
|
23
|
+
//# sourceMappingURL=data-seed-environment-admission.backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-seed-environment-admission.backend.d.ts","sourceRoot":"","sources":["../../../../../src/data-seeding/services/data-seed-environment-admission.backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iEAAiE,CAAC;AAE1G,4GAA4G;AAC5G,MAAM,WAAW,4BAA4B;IAC3C,0HAA0H;IAC1H,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACjD,oFAAoF;IACpF,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uCAAuC,CACrD,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,WAAW,EAAE,kBAAkB,GAAG,SAAS,GAC1C,4BAA4B,CAS9B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select the seeds this deployment may run, from `allowedApplicationEnvironments` (#2234).
|
|
3
|
+
*
|
|
4
|
+
* Applied ONCE, where the application backend first takes its seeds, so every consumer downstream —
|
|
5
|
+
* registration, the scheduled-rerun gate, the any-seeds gate — reads the same admitted set. Filtering
|
|
6
|
+
* at registration alone would leave those gates counting seeds that never register.
|
|
7
|
+
*
|
|
8
|
+
* Fails CLOSED: `environment` is the DECLARED runtime environment (`readDeclaredApplicationEnvironment`),
|
|
9
|
+
* and when there is none a restricted seed is withheld rather than assumed to be development. That is
|
|
10
|
+
* the direction that matters: the point of a restriction is that demonstration rows never reach a
|
|
11
|
+
* production database, and an unknown environment may be production.
|
|
12
|
+
*/
|
|
13
|
+
export function admitDataSeedsForApplicationEnvironment(seeds, environment) {
|
|
14
|
+
const admitted = [];
|
|
15
|
+
const withheldSeedKeys = [];
|
|
16
|
+
for (const seed of seeds) {
|
|
17
|
+
const allowed = seed.allowedApplicationEnvironments;
|
|
18
|
+
if (allowed === undefined || (environment !== undefined && allowed.includes(environment)))
|
|
19
|
+
admitted.push(seed);
|
|
20
|
+
else
|
|
21
|
+
withheldSeedKeys.push(seed.seedKey);
|
|
22
|
+
}
|
|
23
|
+
return { admitted, withheldSeedKeys };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=data-seed-environment-admission.backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-seed-environment-admission.backend.js","sourceRoot":"","sources":["../../../../../src/data-seeding/services/data-seed-environment-admission.backend.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uCAAuC,CACrD,KAAoC,EACpC,WAA2C;IAE3C,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAC1C,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,8BAA8B,CAAC;QACpD,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YAC1G,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;AACxC,CAAC","sourcesContent":["import type { RuntimeEnvironment } from '@wildo-ai/saas-models';\n\nimport type { DataSeedDefinition } from '~wildo-backend-lib/data-seeding/models/data-seed.shared.schemas';\n\n/** Which seeds a deployment runs, and which it withholds because their declared environments exclude it. */\nexport interface DataSeedEnvironmentAdmission {\n /** The seeds this deployment registers — every unrestricted seed, and every restricted one that lists its environment. */\n readonly admitted: readonly DataSeedDefinition[];\n /** The seeds withheld, by key, so the boot log can say what did not run and why. */\n readonly withheldSeedKeys: readonly string[];\n}\n\n/**\n * Select the seeds this deployment may run, from `allowedApplicationEnvironments` (#2234).\n *\n * Applied ONCE, where the application backend first takes its seeds, so every consumer downstream —\n * registration, the scheduled-rerun gate, the any-seeds gate — reads the same admitted set. Filtering\n * at registration alone would leave those gates counting seeds that never register.\n *\n * Fails CLOSED: `environment` is the DECLARED runtime environment (`readDeclaredApplicationEnvironment`),\n * and when there is none a restricted seed is withheld rather than assumed to be development. That is\n * the direction that matters: the point of a restriction is that demonstration rows never reach a\n * production database, and an unknown environment may be production.\n */\nexport function admitDataSeedsForApplicationEnvironment(\n seeds: readonly DataSeedDefinition[],\n environment: RuntimeEnvironment | undefined,\n): DataSeedEnvironmentAdmission {\n const admitted: DataSeedDefinition[] = [];\n const withheldSeedKeys: string[] = [];\n for (const seed of seeds) {\n const allowed = seed.allowedApplicationEnvironments;\n if (allowed === undefined || (environment !== undefined && allowed.includes(environment))) admitted.push(seed);\n else withheldSeedKeys.push(seed.seedKey);\n }\n return { admitted, withheldSeedKeys };\n}\n"]}
|
|
@@ -131,6 +131,11 @@ export declare function loadMigrationsFromDirectory(migrationsDirectory: string)
|
|
|
131
131
|
* @param appliedBy identifies the applying process in the ledger (defaults to host:pid) — forensic
|
|
132
132
|
* value only, never consulted for logic.
|
|
133
133
|
* @param waitDeadlineMs how long a NON-holder waits for the holder to finish before failing loudly.
|
|
134
|
+
* @param resolveCommand the ledger-correction command that REACHES this database, named in the
|
|
135
|
+
* edited-after-apply refusal. Defaults to `wildo db resolve`, which needs an application root —
|
|
136
|
+
* so a caller migrating a database that is not an application's (the platform's `wildo_platform`)
|
|
137
|
+
* must pass its own door. Before this existed the platform refusal named a command that refuses
|
|
138
|
+
* there, and INIT stayed down until someone edited the ledger by hand (2026-10-01).
|
|
134
139
|
*/
|
|
135
140
|
export declare function applyPostgresMigrations(input: {
|
|
136
141
|
db: Kysely<any>;
|
|
@@ -138,6 +143,7 @@ export declare function applyPostgresMigrations(input: {
|
|
|
138
143
|
appliedBy?: string;
|
|
139
144
|
waitDeadlineMs?: number;
|
|
140
145
|
pollIntervalMs?: number;
|
|
146
|
+
resolveCommand?: string;
|
|
141
147
|
}): Promise<MigrationApplyReport>;
|
|
142
148
|
/**
|
|
143
149
|
* BASELINE: record migrations as APPLIED without executing them (critique #1).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresql-migration-runner.backend.d.ts","sourceRoot":"","sources":["../../../../../src/databases/services/postgresql-migration-runner.backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAMH,OAAO,EAAE,MAAM,EAAwB,MAAM,QAAQ,CAAC;AAMtD,iHAAiH;AACjH,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAE1D;;;;;GAKG;AACH,oBAAY,qBAAqB;IAC/B,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB;;;;;;;OAOG;IACH,WAAW,gBAAgB;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,oBAAY,kBAAkB;IAC5B,0EAA0E;IAC1E,OAAO,YAAY;IACnB,4FAA4F;IAC5F,MAAM,WAAW;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAKD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,mBAAmB,EAAE,MAAM,GAAG,aAAa,EAAE,CAUxF;AAsED
|
|
1
|
+
{"version":3,"file":"postgresql-migration-runner.backend.d.ts","sourceRoot":"","sources":["../../../../../src/databases/services/postgresql-migration-runner.backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -187,9 +187,15 @@ function assertMigrationIsApplicable(migration) {
|
|
|
187
187
|
* @param appliedBy identifies the applying process in the ledger (defaults to host:pid) — forensic
|
|
188
188
|
* value only, never consulted for logic.
|
|
189
189
|
* @param waitDeadlineMs how long a NON-holder waits for the holder to finish before failing loudly.
|
|
190
|
+
* @param resolveCommand the ledger-correction command that REACHES this database, named in the
|
|
191
|
+
* edited-after-apply refusal. Defaults to `wildo db resolve`, which needs an application root —
|
|
192
|
+
* so a caller migrating a database that is not an application's (the platform's `wildo_platform`)
|
|
193
|
+
* must pass its own door. Before this existed the platform refusal named a command that refuses
|
|
194
|
+
* there, and INIT stayed down until someone edited the ledger by hand (2026-10-01).
|
|
190
195
|
*/
|
|
191
196
|
export async function applyPostgresMigrations(input) {
|
|
192
197
|
const { db, migrations } = input;
|
|
198
|
+
const resolveCommand = input.resolveCommand ?? 'wildo db resolve';
|
|
193
199
|
const appliedBy = input.appliedBy ?? `${hostname()}:${process.pid}`;
|
|
194
200
|
const waitDeadlineMs = input.waitDeadlineMs ?? 120_000;
|
|
195
201
|
const pollIntervalMs = input.pollIntervalMs ?? 1_000;
|
|
@@ -241,7 +247,8 @@ export async function applyPostgresMigrations(input) {
|
|
|
241
247
|
if (existing.sha256 !== sha256) {
|
|
242
248
|
throw new Error(`Migration "${migration.name}" was APPLIED with sha256 ${existing.sha256.slice(0, 12)}… but the file now `
|
|
243
249
|
+ `hashes ${sha256.slice(0, 12)}… — the repository and the database disagree about history. Restore the `
|
|
244
|
-
+
|
|
250
|
+
+ `original file, or re-accept the edit deliberately with \`${resolveCommand} --applied ${migration.name}\` `
|
|
251
|
+
+ '(recorded, with who).');
|
|
245
252
|
}
|
|
246
253
|
report.verified.push(migration.name);
|
|
247
254
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresql-migration-runner.backend.js","sourceRoot":"","sources":["../../../../../src/databases/services/postgresql-migration-runner.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The migration RUNNER — ledger, exclusion, application. Born concurrent-safe (Phase 2, K9).\n *\n * ## The two authorities, and why neither is Redis\n *\n * - **State** lives in `_wildo_migrations`, a table IN THE DATABASE BEING MIGRATED. Deliberately\n * NOT a resource: it must be readable BEFORE the resource system boots (migrations run ahead of\n * S2–S4), must be unreachable from the API and authorization planes, and must not be governed by\n * the thing it records. Same shape as `_version_db_doc` — framework-owned storage no app schema\n * declares. The boot gate ignores extra tables by design, so the two coexist.\n * - **Exclusion** is `pg_advisory_lock`, in that same database. A lock must live in the same\n * failure domain as the writes it guards (a Redis TTL lapsing mid-migration silently readmits a\n * second writer), and SESSION-scoped advisory locks self-release when their holder dies — the\n * crashed-migrator remediation, for free, with no stale-lock protocol. Redis, where present, is\n * PROPAGATION only (Phase 5, deferred): it may echo state, it never decides it.\n *\n * ## The instance protocol (C6 — N instances, dist-only, simultaneous starts)\n *\n * 1. `pg_try_advisory_lock(hash)` — holder applies every pending migration IN ORDER, one\n * transaction each, records each ledger row, releases, done.\n * 2. Non-holder waits: poll the ledger until nothing is pending (deadline-bounded), then proceeds.\n * 3. EVERY instance — holder or waiter — still runs `verifyPostgreSqlTableSchemas` afterwards; the\n * gate stays the fleet's backstop (K6), so a runner bug cannot silently become a served schema.\n *\n * ## What one transaction per migration buys (K8 — \"remediation should be easy\")\n *\n * PostgreSQL DDL is TRANSACTIONAL. A failed migration therefore rolls back WHOLE: the database\n * lands exactly at the previous migration, the ledger records FAILED with the error, and recovery\n * is \"fix the draft, run again\" — no forensic reconstruction of a half-applied state. This is also\n * why `create index concurrently` is REFUSED: it cannot run inside a transaction, and supporting a\n * non-transactional lane badly would cost the property everything else here leans on.\n *\n * ## Refusals (all fail closed, all name their remedy)\n *\n * - **Edited-after-apply**: an APPLIED ledger row whose SHA-256 no longer matches the file. The\n * repo and the database disagree about history; restore the file, or re-accept deliberately with\n * `wildo db resolve --applied`.\n * - **Applied-but-not-shipped is TOLERATED, not refused** — deliberately. A deployment that ships\n * FEWER migrations than the ledger records is a ROLLBACK deploy, and the additive-biased change\n * vocabulary exists precisely so old code can serve against the newer schema; refusing here\n * would break the one recovery every fleet depends on. The extra rows sit as history. When an\n * operator has genuinely reverted a change by hand, `wildo db resolve --rolled-back` records\n * never-happened — which also clears the sha-refusal a returning same-name file would otherwise\n * hit. (An earlier revision of this header listed this case as a refusal; the code never refused,\n * and the code was right.)\n * - **Destructive without acknowledgement**: a `drop table`/`drop column` statement with no\n * {@link DESTRUCTIVE_ACKNOWLEDGEMENT_MARKER} in the file (K5/Phase 3′ — the acknowledgement is a\n * reviewed, recorded artifact, not a CLI flag).\n * - **`create index concurrently`** — see above.\n *\n * A FAILED row does NOT lock its bytes: the transaction rolled back, so nothing the file says has\n * happened — the draft may be edited and re-run (draft-until-APPLIED, exactly).\n */\n\nimport { createHash } from 'node:crypto';\nimport { readdirSync, readFileSync, statSync } from 'node:fs';\nimport { hostname } from 'node:os';\nimport { join } from 'node:path';\nimport { Kysely, PostgresDialect, sql } from 'kysely';\nimport { Pool, PoolClient} from 'pg';\n\nimport { DESTRUCTIVE_ACKNOWLEDGEMENT_MARKER,\n DESTRUCTIVE_ACKNOWLEDGEMENT_PATTERN } from '~wildo-backend-lib/databases/services/postgresql-schema-migration-sql.backend';\n\n/** The ledger. Quoted with a leading underscore like `_version_db_doc` — framework-owned, never app-authored. */\nexport const MIGRATION_LEDGER_TABLE = '_wildo_migrations';\n\n/**\n * One migration's terminal states. RUNNING is deliberately NOT a persisted state: the status write\n * happens inside the migration's own transaction, so a crash mid-apply rolls the marker back with\n * the work — an observer never sees a RUNNING row from a dead holder, which is one less zombie\n * state to remediate. Liveness is signalled by the advisory lock, not by a row.\n */\nexport enum MigrationLedgerStatus {\n APPLIED = 'APPLIED',\n FAILED = 'FAILED',\n /**\n * Declared never-happened by an operator (`wildo db resolve --rolled-back`) — the remedy for an\n * APPLIED row whose file the code rolled back past. A ROLLED_BACK row is TERMINAL EVIDENCE, not a\n * deletion: the pending computation treats it exactly like an absent row (a re-shipped migration\n * under the same name applies fresh), while the ledger keeps who resolved it and when. Deleting\n * the row would have been the same behaviour minus the evidence, which is never the right trade\n * for the one table whose whole job is being the record.\n */\n ROLLED_BACK = 'ROLLED_BACK',\n}\n\nexport interface MigrationFile {\n /** The folder name — `<timestamp>_<name>` — which is also the global ordering key. */\n name: string;\n sql: string;\n}\n\n/**\n * Which part this process played in a migration run.\n *\n * Two members rather than a boolean because the distinction survives into operator-facing\n * reporting: exactly one process in a cluster APPLIES, and every other one WAITED on the\n * advisory lock and then verified. Reading \"waited\" in a log is a statement that this node\n * did nothing wrong, which a bare `applied: false` does not convey.\n */\nexport enum MigrationApplyRole {\n /** This process held the advisory lock and ran the pending migrations. */\n APPLIED = 'applied',\n /** Another process held the lock; this one waited, then verified the recorded checksums. */\n WAITED = 'waited',\n}\n\nexport interface MigrationApplyReport {\n /** Did THIS process hold the lock and apply, or wait for another holder? */\n role: MigrationApplyRole;\n applied: string[];\n /** Already-APPLIED migrations verified against their recorded SHA-256. */\n verified: string[];\n failed?: { name: string; error: string };\n /**\n * Pending migrations whose name sorts BEFORE an already-APPLIED one — they were still applied,\n * and this field is why that is safe to allow: it makes the event VISIBLE instead of refused.\n *\n * The decision (R2 review note, resolved 2026-08-11): out-of-order arrival is the NORMAL result\n * of two branches generating migrations and merging — the older-stamped, later-merged migration\n * is legitimate, and refusing it at BOOT would brick a fleet over a merge-order artifact.\n * `prisma migrate deploy` applies in filename order for the same reason. What is NOT acceptable\n * is silence, because an out-of-order migration was drafted against an older accepted state and\n * may assume schema a newer applied migration changed — the boot gate still verifies the END\n * state, but the author deserves the pointer when it does. Callers log this; the boot door warns.\n */\n outOfOrder?: string[];\n}\n\nconst DESTRUCTIVE_STATEMENT_PATTERN = /^\\s*(drop\\s+table|alter\\s+table\\s+.*\\bdrop\\s+column|truncate)\\b/im;\nconst CONCURRENT_INDEX_PATTERN = /\\bcreate\\s+(unique\\s+)?index\\s+concurrently\\b/i;\n\nexport function sha256OfMigration(sqlText: string): string {\n return createHash('sha256').update(sqlText, 'utf8').digest('hex');\n}\n\n/**\n * Read a migrations directory into ordered {@link MigrationFile}s.\n *\n * Layout: `migrations/<timestamp>_<name>/migration.sql` (K4). Lexicographic folder order IS the\n * application order — the timestamp prefix makes that chronological, and making the ORDER visible\n * in `ls` is half the point of the layout.\n */\nexport function loadMigrationsFromDirectory(migrationsDirectory: string): MigrationFile[] {\n const entries = readdirSync(migrationsDirectory)\n .filter((entry) => {\n try { return statSync(join(migrationsDirectory, entry)).isDirectory(); } catch { return false; }\n })\n .sort((a, b) => a.localeCompare(b));\n return entries.map((name) => ({\n name,\n sql: readFileSync(join(migrationsDirectory, name, 'migration.sql'), 'utf-8'),\n }));\n}\n\nasync function ensureLedgerTable(db: Kysely<any>): Promise<void> {\n try {\n await sql.raw(\n `create table if not exists \"${MIGRATION_LEDGER_TABLE}\" (\n \"name\" text primary key,\n \"sha256\" text not null,\n \"status\" text not null,\n \"appliedAt\" timestamptz,\n \"appliedBy\" text,\n \"error\" text\n)`,\n ).execute(db);\n } catch (error) {\n /**\n * `IF NOT EXISTS` does not fully serialize concurrent creators: two sessions racing this exact\n * statement can BOTH pass the existence check and one then fails on the catalog's own unique\n * index (`pg_type_typname_nsp_index`, a 23505) — a known PostgreSQL sharp edge, and the ledger\n * is created by exactly the code path that races by design (C6: simultaneous starts). The loser\n * of that race is fine precisely when the table now exists; anything else rethrows.\n */\n const exists = await sql<{ ok: number }>`select 1 as ok from ${sql.ref(MIGRATION_LEDGER_TABLE)} limit 0`\n .execute(db)\n .then(() => true)\n .catch(() => false);\n if (!exists) throw error;\n }\n}\n\ninterface LedgerRow { name: string; sha256: string; status: string }\n\nasync function readLedger(db: Kysely<any>): Promise<Map<string, LedgerRow>> {\n const rows = await sql<LedgerRow>`select \"name\", \"sha256\", \"status\" from ${sql.ref(MIGRATION_LEDGER_TABLE)}`.execute(db);\n return new Map(rows.rows.map((row) => [row.name, row]));\n}\n\n/**\n * Statements split on semicolon-at-line-end — the renderer's own emission shape. NOT a SQL parser:\n * a semicolon inside a string literal at end-of-line would split wrongly, which is why the runner\n * executes inside one transaction per migration — a mis-split fails the whole migration loudly and\n * rolls back, rather than half-applying.\n */\nfunction splitStatements(sqlText: string): string[] {\n return sqlText\n .split(/;\\s*$/m)\n .map((statement) => statement.trim())\n .filter((statement) => statement.length > 0 && !statement.split('\\n').every((line) => line.trim() === '' || line.trim().startsWith('--')));\n}\n\nfunction assertMigrationIsApplicable(migration: MigrationFile): void {\n if (CONCURRENT_INDEX_PATTERN.test(migration.sql)) {\n throw new Error(\n `Migration \"${migration.name}\" uses CREATE INDEX CONCURRENTLY, which cannot run inside a transaction. `\n + 'The runner applies each migration transactionally so a failure rolls back whole — that property is '\n + 'worth more than a lock-free index build. Use a plain CREATE INDEX (brief write-lock during the build), '\n + 'or wait for the dedicated non-transactional lane.',\n );\n }\n if (DESTRUCTIVE_STATEMENT_PATTERN.test(migration.sql) && !DESTRUCTIVE_ACKNOWLEDGEMENT_PATTERN.test(migration.sql)) {\n throw new Error(\n `Migration \"${migration.name}\" contains DESTRUCTIVE statements (drop table / drop column / truncate) `\n + `without the acknowledgement annotation. Add a line starting with\\n ${DESTRUCTIVE_ACKNOWLEDGEMENT_MARKER} <reason>\\n`\n + 'to the file — in the file, not on the command line, so the acknowledgement is reviewed with the PR and '\n + 'recorded with the migration. It also acknowledges deploy order: destroying schema is only safe once no '\n + 'old instance still reads it.',\n );\n }\n}\n\n/**\n * Apply every pending migration, exactly once across any number of concurrent callers.\n *\n * @param appliedBy identifies the applying process in the ledger (defaults to host:pid) — forensic\n * value only, never consulted for logic.\n * @param waitDeadlineMs how long a NON-holder waits for the holder to finish before failing loudly.\n */\nexport async function applyPostgresMigrations(input: {\n db: Kysely<any>;\n migrations: readonly MigrationFile[];\n appliedBy?: string;\n waitDeadlineMs?: number;\n pollIntervalMs?: number;\n}): Promise<MigrationApplyReport> {\n const { db, migrations } = input;\n const appliedBy = input.appliedBy ?? `${hostname()}:${process.pid}`;\n const waitDeadlineMs = input.waitDeadlineMs ?? 120_000;\n const pollIntervalMs = input.pollIntervalMs ?? 1_000;\n\n await ensureLedgerTable(db);\n\n /**\n * EVERYTHING below runs on ONE PINNED CONNECTION (`db.connection()`), and this is load-bearing,\n * not style: an advisory lock is SESSION-scoped, and a Kysely pool hands successive statements to\n * arbitrary pooled sessions. Without pinning, the lock lands on whichever client ran the\n * `pg_try_advisory_lock`, the migrations run on OTHERS, and the unlock probably reaches a session\n * that never held anything — the exclusion would be decorative while looking implemented.\n * (Caught in design review of this very function, before the racing lane could catch it live.)\n */\n return db.connection().execute(async (session) => {\n /**\n * The lock key is derived from the DATABASE's own name, in SQL, so every process keying \"the\n * migrations of this database\" lands on the same int without sharing any configuration —\n * `hashtext` is stable within a PostgreSQL major version, and both racers by definition run\n * against the same server.\n */\n const lock = await sql<{ acquired: boolean }>`\n select pg_try_advisory_lock(hashtext('wildo-migrations:' || current_database())::bigint) as acquired\n `.execute(session);\n const isHolder = lock.rows[0]?.acquired === true;\n\n if (!isHolder) {\n return waitForHolder({ db, migrations, waitDeadlineMs, pollIntervalMs });\n }\n\n try {\n const report: MigrationApplyReport = { role: MigrationApplyRole.APPLIED, applied: [], verified: [] };\n // Ledger read AFTER the lock: the holder must see the previous holder's completed writes.\n const ledger = await readLedger(session);\n\n // See `MigrationApplyReport.outOfOrder` — computed against what the LEDGER already holds,\n // because \"out of order\" is a statement about arrival relative to history, not about the\n // shipped list's own internal order (which is always applied sorted).\n const appliedNames = [...ledger.values()]\n .filter((row) => row.status === MigrationLedgerStatus.APPLIED)\n .map((row) => row.name)\n .sort((a, b) => a.localeCompare(b));\n const newestApplied = appliedNames.length > 0 ? appliedNames[appliedNames.length - 1] : undefined;\n const outOfOrder = newestApplied === undefined ? [] : migrations\n .filter((migration) => ledger.get(migration.name)?.status !== MigrationLedgerStatus.APPLIED)\n .filter((migration) => migration.name.localeCompare(newestApplied) < 0)\n .map((migration) => migration.name);\n if (outOfOrder.length > 0) report.outOfOrder = outOfOrder;\n\n for (const migration of migrations) {\n const sha256 = sha256OfMigration(migration.sql);\n const existing = ledger.get(migration.name);\n\n if (existing?.status === MigrationLedgerStatus.APPLIED) {\n if (existing.sha256 !== sha256) {\n throw new Error(\n `Migration \"${migration.name}\" was APPLIED with sha256 ${existing.sha256.slice(0, 12)}… but the file now `\n + `hashes ${sha256.slice(0, 12)}… — the repository and the database disagree about history. Restore the `\n + 'original file, or re-accept the edit deliberately with `wildo db resolve --applied` (recorded, with who).',\n );\n }\n report.verified.push(migration.name);\n continue;\n }\n\n // FAILED rows re-run with WHATEVER bytes the file now has: the failed transaction rolled\n // back, so nothing those bytes claim has happened — draft-until-APPLIED, exactly.\n assertMigrationIsApplicable(migration);\n\n try {\n // The pool-level transaction is fine here: DDL inside is transactional regardless of which\n // session runs it, and the LEDGER row commits with the DDL it records. Only the LOCK needs\n // the pinned session.\n await db.transaction().execute(async (transaction) => {\n for (const statement of splitStatements(migration.sql)) {\n await sql.raw(statement).execute(transaction);\n }\n await sql`\n insert into ${sql.ref(MIGRATION_LEDGER_TABLE)} (\"name\", \"sha256\", \"status\", \"appliedAt\", \"appliedBy\")\n values (${migration.name}, ${sha256}, ${MigrationLedgerStatus.APPLIED}, now(), ${appliedBy})\n on conflict (\"name\") do update set\n \"sha256\" = excluded.\"sha256\", \"status\" = excluded.\"status\",\n \"appliedAt\" = excluded.\"appliedAt\", \"appliedBy\" = excluded.\"appliedBy\", \"error\" = null\n `.execute(transaction);\n });\n report.applied.push(migration.name);\n } catch (error) {\n const message = String((error as Error)?.message ?? error);\n // OUTSIDE the failed transaction, so the evidence survives the rollback that just erased\n // everything else the migration did.\n await sql`\n insert into ${sql.ref(MIGRATION_LEDGER_TABLE)} (\"name\", \"sha256\", \"status\", \"appliedAt\", \"appliedBy\", \"error\")\n values (${migration.name}, ${sha256}, ${MigrationLedgerStatus.FAILED}, now(), ${appliedBy}, ${message})\n on conflict (\"name\") do update set\n \"sha256\" = excluded.\"sha256\", \"status\" = excluded.\"status\",\n \"appliedAt\" = excluded.\"appliedAt\", \"appliedBy\" = excluded.\"appliedBy\", \"error\" = excluded.\"error\"\n `.execute(db);\n report.failed = { name: migration.name, error: message };\n // STOP at the first failure — later migrations may depend on this one, and \"apply what you\n // can\" converts one legible failure into a cascade of misleading ones.\n throw new Error(\n `Migration \"${migration.name}\" FAILED and rolled back whole (the database is exactly at the previous `\n + `migration): ${message}\\n Fix the draft and run again; FAILED rows re-run freely.`,\n );\n }\n }\n\n return report;\n } finally {\n // On the SAME pinned session that took the lock — anywhere else unlocks nothing.\n await sql`select pg_advisory_unlock(hashtext('wildo-migrations:' || current_database())::bigint)`.execute(session)\n .catch(() => undefined /* the session dying releases it anyway — that is the point of session locks */);\n }\n });\n}\n\n/** The non-holder half: poll the ledger until nothing is pending, bounded by the deadline. */\nasync function waitForHolder(input: {\n db: Kysely<any>;\n migrations: readonly MigrationFile[];\n waitDeadlineMs: number;\n pollIntervalMs: number;\n}): Promise<MigrationApplyReport> {\n const { db, migrations, waitDeadlineMs, pollIntervalMs } = input;\n\n /**\n * The deadline RESETS on ledger progress. A fixed deadline measured a property nobody cares\n * about — total elapsed time — and false-failed every waiting instance whenever one migration in\n * a long chain outlived it, even though the holder was demonstrably alive and working. What a\n * waiter actually needs bounded is STALLED time: no ledger change for the whole window means the\n * holder hangs inside one migration (or died between two), and that is when failing loudly is\n * right. (Review finding F2 of the Phase 1+2 review.)\n */\n let deadline = Date.now() + waitDeadlineMs;\n let lastLedgerFingerprint = '';\n\n for (;;) {\n const ledger = await readLedger(db);\n const fingerprint = [...ledger.entries()].map(([name, row]) => `${name}:${row.status}:${row.sha256}`).sort().join('|');\n if (fingerprint !== lastLedgerFingerprint) {\n lastLedgerFingerprint = fingerprint;\n deadline = Date.now() + waitDeadlineMs;\n }\n\n const pending = migrations.filter((migration) => {\n const row = ledger.get(migration.name);\n return row?.status !== MigrationLedgerStatus.APPLIED;\n });\n if (pending.length === 0) {\n return { role: MigrationApplyRole.WAITED, applied: [], verified: migrations.map((migration) => migration.name) };\n }\n /**\n * Only a FAILED row for a migration THIS deployment actually ships may stop a waiter. The first\n * version scanned the whole ledger, and that bricked every waiting instance on a STALE row: a\n * migration that failed once, was renamed while being fixed, and left its FAILED row behind —\n * the holder ignores stale rows (it walks the shipped set), so the fleet split into a booting\n * holder and refusing waiters over a row nothing would ever clean. The two halves must judge\n * the SAME population. (Review finding F1.)\n */\n const pendingNames = new Set(pending.map((migration) => migration.name));\n const failedRow = [...ledger.values()].find((row) => row.status === MigrationLedgerStatus.FAILED && pendingNames.has(row.name));\n if (failedRow) {\n throw new Error(\n `Waiting on the migration holder, but \"${failedRow.name}\" is recorded FAILED — the holder stopped there. `\n + 'This instance cannot proceed against a schema behind its code; fix the migration and redeploy.',\n );\n }\n if (Date.now() >= deadline) {\n throw new Error(\n `No migration-ledger progress for ${input.waitDeadlineMs}ms while waiting for the holder; ${pending.length} migration(s) still `\n + `pending (first: \"${pending[0].name}\"). The holder either hangs or died between migrations — `\n + `pg_locks/pg_stat_activity names the session holding the wildo-migrations advisory lock.`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n }\n}\n\n/**\n * BASELINE: record migrations as APPLIED without executing them (critique #1).\n *\n * For a database whose tables ALREADY exist — every dev-provisioned database on the day the runner\n * ships, or a production database adopted into the system — executing migration 0 is usually\n * harmless (the renderer emits idempotent DDL) but recording the truth is better than re-running\n * it. Guarded by the caller's explicit choice (`wildo db migrate --baseline`); never automatic,\n * because \"these migrations have effectively run\" is a claim about the DATABASE only its operator\n * can make.\n */\nexport async function baselinePostgresMigrations(input: {\n db: Kysely<any>;\n migrations: readonly MigrationFile[];\n baselinedBy?: string;\n}): Promise<string[]> {\n const { db, migrations } = input;\n const baselinedBy = input.baselinedBy ?? `baseline:${hostname()}:${process.pid}`;\n await ensureLedgerTable(db);\n const ledger = await readLedger(db);\n\n const recorded: string[] = [];\n for (const migration of migrations) {\n if (ledger.get(migration.name)?.status === MigrationLedgerStatus.APPLIED) continue;\n await sql`\n insert into ${sql.ref(MIGRATION_LEDGER_TABLE)} (\"name\", \"sha256\", \"status\", \"appliedAt\", \"appliedBy\")\n values (${migration.name}, ${sha256OfMigration(migration.sql)}, ${MigrationLedgerStatus.APPLIED}, now(), ${baselinedBy})\n on conflict (\"name\") do update set\n \"sha256\" = excluded.\"sha256\", \"status\" = excluded.\"status\",\n \"appliedAt\" = excluded.\"appliedAt\", \"appliedBy\" = excluded.\"appliedBy\", \"error\" = null\n `.execute(db);\n recorded.push(migration.name);\n }\n return recorded;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The manual door's plumbing (`wildo db migrate` / `wildo db resolve`)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PostgresMigrationConnectionInput {\n host: string;\n port: number;\n database: string;\n user: string;\n password: string;\n /** `require`-style ssl toggle; local self-hosted PostgreSQL runs without it. */\n ssl?: boolean;\n /**\n * Create objects as the DATABASE'S OWNER rather than as the connecting role.\n *\n * ## Why an operator door needs this\n *\n * There are two doors onto this runner: the application's own boot, which connects as the\n * application's runtime role, and `wildo db migrate`, which connects as the PostgreSQL ADMIN\n * because it is an operator tool that must work before any application boots. Both are reasonable\n * about CONNECTIVITY and the second is wrong about OWNERSHIP: every table it creates is owned by\n * the admin, and the application — a different role — is then denied on its own schema.\n *\n * Measured on 2026-08-19: `wildo db migrate` created all 94 Wonder Todos tables as `wildo_admin`,\n * and the backend failed to start with `permission denied for table _wildo_migrations` while the\n * DATABASE itself was correctly owned by the application role.\n *\n * ## Why the database owner, and not a supplied name\n *\n * The owner is read from the server (`pg_database.datdba`), so the runner never re-derives the\n * provisioner's role-naming convention. Re-deriving it would create the same second naming\n * authority that had already produced `wildo_wonder_todos` beside `wonder-todos-db`.\n *\n * Applied per pooled connection, because `SET ROLE` is session state. It requires the connecting\n * role to be a member of the owner (a superuser always is); a role without that membership fails\n * loudly here rather than silently creating admin-owned objects.\n */\n createObjectsAsDatabaseOwner?: boolean;\n}\n\nexport interface PostgresMigrationConnection {\n db: Kysely<any>;\n close(): Promise<void>;\n}\n\n/**\n * A Kysely handle for the MANUAL door, constructed HERE so the CLI needs no `pg`/`kysely`\n * dependency of its own. Under pnpm's strict layout the CLI cannot resolve packages it does not\n * declare — and declaring them would create a second place the driver's version is chosen. The\n * engine already owns that choice for the boot door; the manual door borrows the same one.\n *\n * Pool of 5, not 1: the runner pins its advisory lock to ONE connection itself\n * (`db.connection()`), and the waiter's ledger polls are free to use the rest.\n */\nexport function openPostgresMigrationConnection(input: PostgresMigrationConnectionInput): PostgresMigrationConnection {\n const pool = new Pool({\n host: input.host,\n port: input.port,\n database: input.database,\n user: input.user,\n password: input.password,\n ...(input.ssl ? { ssl: { rejectUnauthorized: false } } : {}),\n max: 5,\n });\n\n if (input.createObjectsAsDatabaseOwner) {\n /**\n * Per CONNECTION, and GATED — the caller must not receive a session before `SET ROLE` has run.\n *\n * ## Why this cannot be `pool.on('connect')`\n *\n * That is what it was, and it is a RACE. The `connect` event's handlers are synchronous: a\n * handler that issues a query can only fire it and return, so the pool hands the client to the\n * caller while the `SET ROLE` is still in flight. Whichever statement the caller sends first may\n * win.\n *\n * On a database that has been migrated before, the race is invisible — the first statement only\n * READS the ledger, and reading is unaffected by which role you are. On a COLD database the\n * first statement CREATES it, so the loser of the race is the ownership of\n * `_wildo_migrations` itself.\n *\n * Measured 2026-08-23, first cold install of the acceptance cycle: `wildo db migrate` created\n * `_wildo_migrations` owned by `wildo_admin` in a database owned by\n * `wildo_wonder_todos_user`, then failed on its very next statement with\n * `permission denied for table _wildo_migrations`. That is the SAME symptom this option was\n * introduced to fix, reappearing one table earlier than the 94 it did fix — the ledger is\n * created by the runner itself, before any migration statement, and so was the only object the\n * original handler could lose the race to.\n *\n * Wrapping `connect` makes the assumption a precondition of holding the session rather than a\n * hope about ordering. Kysely acquires through the promise form, which is the form wrapped; the\n * callback form is left alone deliberately — nothing in this runner uses it, and quietly\n * half-supporting it would be worse than not supporting it.\n */\n const acquire = pool.connect.bind(pool);\n (pool as unknown as { connect: () => Promise<PoolClient> }).connect = async (): Promise<PoolClient> => {\n const client = await acquire();\n try {\n const result = await client.query(\n 'select pg_get_userbyid(datdba) as owner from pg_database where datname = current_database()',\n );\n const owner = result.rows?.[0]?.owner;\n if (typeof owner === 'string' && owner.length > 0) {\n // Quoted so an owner containing a hyphen — legal, and what an authored database name\n // produces — is not a syntax error.\n await client.query(`SET ROLE \"${owner.replace(/\"/g, '\"\"')}\"`);\n }\n } catch (error) {\n // Release before rethrowing: a client acquired and abandoned is a pool slot leaked for the\n // lifetime of the process, and this path runs once per connection.\n client.release();\n throw error;\n }\n return client;\n };\n }\n\n const db = new Kysely<any>({ dialect: new PostgresDialect({ pool }) });\n\n return { db, close: () => db.destroy() };\n}\n\n/** The closed resolution vocabulary — each member is one refusal's documented remedy. */\nexport enum MigrationResolution {\n /**\n * Re-accept a migration's CURRENT bytes as the applied truth — the remedy for the\n * edited-after-apply refusal, when the edit is deliberate (a comment fixed, a reviewed\n * reformat). Records the new SHA-256 WITHOUT executing anything: resolving is a statement about\n * history, never a way to run SQL.\n */\n APPLIED = 'applied',\n /** Declare an applied migration never-happened — see {@link MigrationLedgerStatus.ROLLED_BACK}. */\n ROLLED_BACK = 'rolled-back',\n}\n\n/**\n * `wildo db resolve` — the K8 easy-remediation verbs, as one recorded ledger mutation.\n *\n * Fails closed on a name the ledger does not know: resolving is always a correction OF a recorded\n * fact, and \"correcting\" an absent row would mint history instead. (`--applied` for a\n * never-recorded migration is what BASELINE is for, and the error says so.)\n */\nexport async function resolvePostgresMigrationLedgerEntry(input: {\n db: Kysely<any>;\n name: string;\n resolution: MigrationResolution;\n /** REQUIRED for {@link MigrationResolution.APPLIED} — the sha256 of the bytes being accepted. */\n sha256?: string;\n resolvedBy: string;\n}): Promise<void> {\n const { db, name, resolution, resolvedBy } = input;\n\n const existing = await sql<{ name: string }>`\n select \"name\" from ${sql.ref(MIGRATION_LEDGER_TABLE)} where \"name\" = ${name}\n `.execute(db);\n if (existing.rows.length === 0) {\n throw new Error(\n `Cannot resolve \"${name}\": the ledger has no row for it. Resolving corrects a RECORDED fact; `\n + 'for a migration that was applied outside the ledger entirely, use baseline '\n + '(`wildo db migrate --baseline`), which records without executing.',\n );\n }\n\n if (resolution === MigrationResolution.APPLIED) {\n if (!input.sha256) {\n throw new Error(`Resolving \"${name}\" as applied requires the sha256 of the bytes being accepted.`);\n }\n await sql`\n update ${sql.ref(MIGRATION_LEDGER_TABLE)}\n set \"sha256\" = ${input.sha256}, \"status\" = ${MigrationLedgerStatus.APPLIED},\n \"appliedAt\" = now(), \"appliedBy\" = ${`resolve:${resolvedBy}`}, \"error\" = null\n where \"name\" = ${name}\n `.execute(db);\n return;\n }\n\n await sql`\n update ${sql.ref(MIGRATION_LEDGER_TABLE)}\n set \"status\" = ${MigrationLedgerStatus.ROLLED_BACK},\n \"appliedAt\" = now(), \"appliedBy\" = ${`resolve:${resolvedBy}`}, \"error\" = null\n where \"name\" = ${name}\n `.execute(db);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"postgresql-migration-runner.backend.js","sourceRoot":"","sources":["../../../../../src/databases/services/postgresql-migration-runner.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The migration RUNNER — ledger, exclusion, application. Born concurrent-safe (Phase 2, K9).\n *\n * ## The two authorities, and why neither is Redis\n *\n * - **State** lives in `_wildo_migrations`, a table IN THE DATABASE BEING MIGRATED. Deliberately\n * NOT a resource: it must be readable BEFORE the resource system boots (migrations run ahead of\n * S2–S4), must be unreachable from the API and authorization planes, and must not be governed by\n * the thing it records. Same shape as `_version_db_doc` — framework-owned storage no app schema\n * declares. The boot gate ignores extra tables by design, so the two coexist.\n * - **Exclusion** is `pg_advisory_lock`, in that same database. A lock must live in the same\n * failure domain as the writes it guards (a Redis TTL lapsing mid-migration silently readmits a\n * second writer), and SESSION-scoped advisory locks self-release when their holder dies — the\n * crashed-migrator remediation, for free, with no stale-lock protocol. Redis, where present, is\n * PROPAGATION only (Phase 5, deferred): it may echo state, it never decides it.\n *\n * ## The instance protocol (C6 — N instances, dist-only, simultaneous starts)\n *\n * 1. `pg_try_advisory_lock(hash)` — holder applies every pending migration IN ORDER, one\n * transaction each, records each ledger row, releases, done.\n * 2. Non-holder waits: poll the ledger until nothing is pending (deadline-bounded), then proceeds.\n * 3. EVERY instance — holder or waiter — still runs `verifyPostgreSqlTableSchemas` afterwards; the\n * gate stays the fleet's backstop (K6), so a runner bug cannot silently become a served schema.\n *\n * ## What one transaction per migration buys (K8 — \"remediation should be easy\")\n *\n * PostgreSQL DDL is TRANSACTIONAL. A failed migration therefore rolls back WHOLE: the database\n * lands exactly at the previous migration, the ledger records FAILED with the error, and recovery\n * is \"fix the draft, run again\" — no forensic reconstruction of a half-applied state. This is also\n * why `create index concurrently` is REFUSED: it cannot run inside a transaction, and supporting a\n * non-transactional lane badly would cost the property everything else here leans on.\n *\n * ## Refusals (all fail closed, all name their remedy)\n *\n * - **Edited-after-apply**: an APPLIED ledger row whose SHA-256 no longer matches the file. The\n * repo and the database disagree about history; restore the file, or re-accept deliberately with\n * `wildo db resolve --applied`.\n * - **Applied-but-not-shipped is TOLERATED, not refused** — deliberately. A deployment that ships\n * FEWER migrations than the ledger records is a ROLLBACK deploy, and the additive-biased change\n * vocabulary exists precisely so old code can serve against the newer schema; refusing here\n * would break the one recovery every fleet depends on. The extra rows sit as history. When an\n * operator has genuinely reverted a change by hand, `wildo db resolve --rolled-back` records\n * never-happened — which also clears the sha-refusal a returning same-name file would otherwise\n * hit. (An earlier revision of this header listed this case as a refusal; the code never refused,\n * and the code was right.)\n * - **Destructive without acknowledgement**: a `drop table`/`drop column` statement with no\n * {@link DESTRUCTIVE_ACKNOWLEDGEMENT_MARKER} in the file (K5/Phase 3′ — the acknowledgement is a\n * reviewed, recorded artifact, not a CLI flag).\n * - **`create index concurrently`** — see above.\n *\n * A FAILED row does NOT lock its bytes: the transaction rolled back, so nothing the file says has\n * happened — the draft may be edited and re-run (draft-until-APPLIED, exactly).\n */\n\nimport { createHash } from 'node:crypto';\nimport { readdirSync, readFileSync, statSync } from 'node:fs';\nimport { hostname } from 'node:os';\nimport { join } from 'node:path';\nimport { Kysely, PostgresDialect, sql } from 'kysely';\nimport { Pool, PoolClient} from 'pg';\n\nimport { DESTRUCTIVE_ACKNOWLEDGEMENT_MARKER,\n DESTRUCTIVE_ACKNOWLEDGEMENT_PATTERN } from '~wildo-backend-lib/databases/services/postgresql-schema-migration-sql.backend';\n\n/** The ledger. Quoted with a leading underscore like `_version_db_doc` — framework-owned, never app-authored. */\nexport const MIGRATION_LEDGER_TABLE = '_wildo_migrations';\n\n/**\n * One migration's terminal states. RUNNING is deliberately NOT a persisted state: the status write\n * happens inside the migration's own transaction, so a crash mid-apply rolls the marker back with\n * the work — an observer never sees a RUNNING row from a dead holder, which is one less zombie\n * state to remediate. Liveness is signalled by the advisory lock, not by a row.\n */\nexport enum MigrationLedgerStatus {\n APPLIED = 'APPLIED',\n FAILED = 'FAILED',\n /**\n * Declared never-happened by an operator (`wildo db resolve --rolled-back`) — the remedy for an\n * APPLIED row whose file the code rolled back past. A ROLLED_BACK row is TERMINAL EVIDENCE, not a\n * deletion: the pending computation treats it exactly like an absent row (a re-shipped migration\n * under the same name applies fresh), while the ledger keeps who resolved it and when. Deleting\n * the row would have been the same behaviour minus the evidence, which is never the right trade\n * for the one table whose whole job is being the record.\n */\n ROLLED_BACK = 'ROLLED_BACK',\n}\n\nexport interface MigrationFile {\n /** The folder name — `<timestamp>_<name>` — which is also the global ordering key. */\n name: string;\n sql: string;\n}\n\n/**\n * Which part this process played in a migration run.\n *\n * Two members rather than a boolean because the distinction survives into operator-facing\n * reporting: exactly one process in a cluster APPLIES, and every other one WAITED on the\n * advisory lock and then verified. Reading \"waited\" in a log is a statement that this node\n * did nothing wrong, which a bare `applied: false` does not convey.\n */\nexport enum MigrationApplyRole {\n /** This process held the advisory lock and ran the pending migrations. */\n APPLIED = 'applied',\n /** Another process held the lock; this one waited, then verified the recorded checksums. */\n WAITED = 'waited',\n}\n\nexport interface MigrationApplyReport {\n /** Did THIS process hold the lock and apply, or wait for another holder? */\n role: MigrationApplyRole;\n applied: string[];\n /** Already-APPLIED migrations verified against their recorded SHA-256. */\n verified: string[];\n failed?: { name: string; error: string };\n /**\n * Pending migrations whose name sorts BEFORE an already-APPLIED one — they were still applied,\n * and this field is why that is safe to allow: it makes the event VISIBLE instead of refused.\n *\n * The decision (R2 review note, resolved 2026-08-11): out-of-order arrival is the NORMAL result\n * of two branches generating migrations and merging — the older-stamped, later-merged migration\n * is legitimate, and refusing it at BOOT would brick a fleet over a merge-order artifact.\n * `prisma migrate deploy` applies in filename order for the same reason. What is NOT acceptable\n * is silence, because an out-of-order migration was drafted against an older accepted state and\n * may assume schema a newer applied migration changed — the boot gate still verifies the END\n * state, but the author deserves the pointer when it does. Callers log this; the boot door warns.\n */\n outOfOrder?: string[];\n}\n\nconst DESTRUCTIVE_STATEMENT_PATTERN = /^\\s*(drop\\s+table|alter\\s+table\\s+.*\\bdrop\\s+column|truncate)\\b/im;\nconst CONCURRENT_INDEX_PATTERN = /\\bcreate\\s+(unique\\s+)?index\\s+concurrently\\b/i;\n\nexport function sha256OfMigration(sqlText: string): string {\n return createHash('sha256').update(sqlText, 'utf8').digest('hex');\n}\n\n/**\n * Read a migrations directory into ordered {@link MigrationFile}s.\n *\n * Layout: `migrations/<timestamp>_<name>/migration.sql` (K4). Lexicographic folder order IS the\n * application order — the timestamp prefix makes that chronological, and making the ORDER visible\n * in `ls` is half the point of the layout.\n */\nexport function loadMigrationsFromDirectory(migrationsDirectory: string): MigrationFile[] {\n const entries = readdirSync(migrationsDirectory)\n .filter((entry) => {\n try { return statSync(join(migrationsDirectory, entry)).isDirectory(); } catch { return false; }\n })\n .sort((a, b) => a.localeCompare(b));\n return entries.map((name) => ({\n name,\n sql: readFileSync(join(migrationsDirectory, name, 'migration.sql'), 'utf-8'),\n }));\n}\n\nasync function ensureLedgerTable(db: Kysely<any>): Promise<void> {\n try {\n await sql.raw(\n `create table if not exists \"${MIGRATION_LEDGER_TABLE}\" (\n \"name\" text primary key,\n \"sha256\" text not null,\n \"status\" text not null,\n \"appliedAt\" timestamptz,\n \"appliedBy\" text,\n \"error\" text\n)`,\n ).execute(db);\n } catch (error) {\n /**\n * `IF NOT EXISTS` does not fully serialize concurrent creators: two sessions racing this exact\n * statement can BOTH pass the existence check and one then fails on the catalog's own unique\n * index (`pg_type_typname_nsp_index`, a 23505) — a known PostgreSQL sharp edge, and the ledger\n * is created by exactly the code path that races by design (C6: simultaneous starts). The loser\n * of that race is fine precisely when the table now exists; anything else rethrows.\n */\n const exists = await sql<{ ok: number }>`select 1 as ok from ${sql.ref(MIGRATION_LEDGER_TABLE)} limit 0`\n .execute(db)\n .then(() => true)\n .catch(() => false);\n if (!exists) throw error;\n }\n}\n\ninterface LedgerRow { name: string; sha256: string; status: string }\n\nasync function readLedger(db: Kysely<any>): Promise<Map<string, LedgerRow>> {\n const rows = await sql<LedgerRow>`select \"name\", \"sha256\", \"status\" from ${sql.ref(MIGRATION_LEDGER_TABLE)}`.execute(db);\n return new Map(rows.rows.map((row) => [row.name, row]));\n}\n\n/**\n * Statements split on semicolon-at-line-end — the renderer's own emission shape. NOT a SQL parser:\n * a semicolon inside a string literal at end-of-line would split wrongly, which is why the runner\n * executes inside one transaction per migration — a mis-split fails the whole migration loudly and\n * rolls back, rather than half-applying.\n */\nfunction splitStatements(sqlText: string): string[] {\n return sqlText\n .split(/;\\s*$/m)\n .map((statement) => statement.trim())\n .filter((statement) => statement.length > 0 && !statement.split('\\n').every((line) => line.trim() === '' || line.trim().startsWith('--')));\n}\n\nfunction assertMigrationIsApplicable(migration: MigrationFile): void {\n if (CONCURRENT_INDEX_PATTERN.test(migration.sql)) {\n throw new Error(\n `Migration \"${migration.name}\" uses CREATE INDEX CONCURRENTLY, which cannot run inside a transaction. `\n + 'The runner applies each migration transactionally so a failure rolls back whole — that property is '\n + 'worth more than a lock-free index build. Use a plain CREATE INDEX (brief write-lock during the build), '\n + 'or wait for the dedicated non-transactional lane.',\n );\n }\n if (DESTRUCTIVE_STATEMENT_PATTERN.test(migration.sql) && !DESTRUCTIVE_ACKNOWLEDGEMENT_PATTERN.test(migration.sql)) {\n throw new Error(\n `Migration \"${migration.name}\" contains DESTRUCTIVE statements (drop table / drop column / truncate) `\n + `without the acknowledgement annotation. Add a line starting with\\n ${DESTRUCTIVE_ACKNOWLEDGEMENT_MARKER} <reason>\\n`\n + 'to the file — in the file, not on the command line, so the acknowledgement is reviewed with the PR and '\n + 'recorded with the migration. It also acknowledges deploy order: destroying schema is only safe once no '\n + 'old instance still reads it.',\n );\n }\n}\n\n/**\n * Apply every pending migration, exactly once across any number of concurrent callers.\n *\n * @param appliedBy identifies the applying process in the ledger (defaults to host:pid) — forensic\n * value only, never consulted for logic.\n * @param waitDeadlineMs how long a NON-holder waits for the holder to finish before failing loudly.\n * @param resolveCommand the ledger-correction command that REACHES this database, named in the\n * edited-after-apply refusal. Defaults to `wildo db resolve`, which needs an application root —\n * so a caller migrating a database that is not an application's (the platform's `wildo_platform`)\n * must pass its own door. Before this existed the platform refusal named a command that refuses\n * there, and INIT stayed down until someone edited the ledger by hand (2026-10-01).\n */\nexport async function applyPostgresMigrations(input: {\n db: Kysely<any>;\n migrations: readonly MigrationFile[];\n appliedBy?: string;\n waitDeadlineMs?: number;\n pollIntervalMs?: number;\n resolveCommand?: string;\n}): Promise<MigrationApplyReport> {\n const { db, migrations } = input;\n const resolveCommand = input.resolveCommand ?? 'wildo db resolve';\n const appliedBy = input.appliedBy ?? `${hostname()}:${process.pid}`;\n const waitDeadlineMs = input.waitDeadlineMs ?? 120_000;\n const pollIntervalMs = input.pollIntervalMs ?? 1_000;\n\n await ensureLedgerTable(db);\n\n /**\n * EVERYTHING below runs on ONE PINNED CONNECTION (`db.connection()`), and this is load-bearing,\n * not style: an advisory lock is SESSION-scoped, and a Kysely pool hands successive statements to\n * arbitrary pooled sessions. Without pinning, the lock lands on whichever client ran the\n * `pg_try_advisory_lock`, the migrations run on OTHERS, and the unlock probably reaches a session\n * that never held anything — the exclusion would be decorative while looking implemented.\n * (Caught in design review of this very function, before the racing lane could catch it live.)\n */\n return db.connection().execute(async (session) => {\n /**\n * The lock key is derived from the DATABASE's own name, in SQL, so every process keying \"the\n * migrations of this database\" lands on the same int without sharing any configuration —\n * `hashtext` is stable within a PostgreSQL major version, and both racers by definition run\n * against the same server.\n */\n const lock = await sql<{ acquired: boolean }>`\n select pg_try_advisory_lock(hashtext('wildo-migrations:' || current_database())::bigint) as acquired\n `.execute(session);\n const isHolder = lock.rows[0]?.acquired === true;\n\n if (!isHolder) {\n return waitForHolder({ db, migrations, waitDeadlineMs, pollIntervalMs });\n }\n\n try {\n const report: MigrationApplyReport = { role: MigrationApplyRole.APPLIED, applied: [], verified: [] };\n // Ledger read AFTER the lock: the holder must see the previous holder's completed writes.\n const ledger = await readLedger(session);\n\n // See `MigrationApplyReport.outOfOrder` — computed against what the LEDGER already holds,\n // because \"out of order\" is a statement about arrival relative to history, not about the\n // shipped list's own internal order (which is always applied sorted).\n const appliedNames = [...ledger.values()]\n .filter((row) => row.status === MigrationLedgerStatus.APPLIED)\n .map((row) => row.name)\n .sort((a, b) => a.localeCompare(b));\n const newestApplied = appliedNames.length > 0 ? appliedNames[appliedNames.length - 1] : undefined;\n const outOfOrder = newestApplied === undefined ? [] : migrations\n .filter((migration) => ledger.get(migration.name)?.status !== MigrationLedgerStatus.APPLIED)\n .filter((migration) => migration.name.localeCompare(newestApplied) < 0)\n .map((migration) => migration.name);\n if (outOfOrder.length > 0) report.outOfOrder = outOfOrder;\n\n for (const migration of migrations) {\n const sha256 = sha256OfMigration(migration.sql);\n const existing = ledger.get(migration.name);\n\n if (existing?.status === MigrationLedgerStatus.APPLIED) {\n if (existing.sha256 !== sha256) {\n throw new Error(\n `Migration \"${migration.name}\" was APPLIED with sha256 ${existing.sha256.slice(0, 12)}… but the file now `\n + `hashes ${sha256.slice(0, 12)}… — the repository and the database disagree about history. Restore the `\n + `original file, or re-accept the edit deliberately with \\`${resolveCommand} --applied ${migration.name}\\` `\n + '(recorded, with who).',\n );\n }\n report.verified.push(migration.name);\n continue;\n }\n\n // FAILED rows re-run with WHATEVER bytes the file now has: the failed transaction rolled\n // back, so nothing those bytes claim has happened — draft-until-APPLIED, exactly.\n assertMigrationIsApplicable(migration);\n\n try {\n // The pool-level transaction is fine here: DDL inside is transactional regardless of which\n // session runs it, and the LEDGER row commits with the DDL it records. Only the LOCK needs\n // the pinned session.\n await db.transaction().execute(async (transaction) => {\n for (const statement of splitStatements(migration.sql)) {\n await sql.raw(statement).execute(transaction);\n }\n await sql`\n insert into ${sql.ref(MIGRATION_LEDGER_TABLE)} (\"name\", \"sha256\", \"status\", \"appliedAt\", \"appliedBy\")\n values (${migration.name}, ${sha256}, ${MigrationLedgerStatus.APPLIED}, now(), ${appliedBy})\n on conflict (\"name\") do update set\n \"sha256\" = excluded.\"sha256\", \"status\" = excluded.\"status\",\n \"appliedAt\" = excluded.\"appliedAt\", \"appliedBy\" = excluded.\"appliedBy\", \"error\" = null\n `.execute(transaction);\n });\n report.applied.push(migration.name);\n } catch (error) {\n const message = String((error as Error)?.message ?? error);\n // OUTSIDE the failed transaction, so the evidence survives the rollback that just erased\n // everything else the migration did.\n await sql`\n insert into ${sql.ref(MIGRATION_LEDGER_TABLE)} (\"name\", \"sha256\", \"status\", \"appliedAt\", \"appliedBy\", \"error\")\n values (${migration.name}, ${sha256}, ${MigrationLedgerStatus.FAILED}, now(), ${appliedBy}, ${message})\n on conflict (\"name\") do update set\n \"sha256\" = excluded.\"sha256\", \"status\" = excluded.\"status\",\n \"appliedAt\" = excluded.\"appliedAt\", \"appliedBy\" = excluded.\"appliedBy\", \"error\" = excluded.\"error\"\n `.execute(db);\n report.failed = { name: migration.name, error: message };\n // STOP at the first failure — later migrations may depend on this one, and \"apply what you\n // can\" converts one legible failure into a cascade of misleading ones.\n throw new Error(\n `Migration \"${migration.name}\" FAILED and rolled back whole (the database is exactly at the previous `\n + `migration): ${message}\\n Fix the draft and run again; FAILED rows re-run freely.`,\n );\n }\n }\n\n return report;\n } finally {\n // On the SAME pinned session that took the lock — anywhere else unlocks nothing.\n await sql`select pg_advisory_unlock(hashtext('wildo-migrations:' || current_database())::bigint)`.execute(session)\n .catch(() => undefined /* the session dying releases it anyway — that is the point of session locks */);\n }\n });\n}\n\n/** The non-holder half: poll the ledger until nothing is pending, bounded by the deadline. */\nasync function waitForHolder(input: {\n db: Kysely<any>;\n migrations: readonly MigrationFile[];\n waitDeadlineMs: number;\n pollIntervalMs: number;\n}): Promise<MigrationApplyReport> {\n const { db, migrations, waitDeadlineMs, pollIntervalMs } = input;\n\n /**\n * The deadline RESETS on ledger progress. A fixed deadline measured a property nobody cares\n * about — total elapsed time — and false-failed every waiting instance whenever one migration in\n * a long chain outlived it, even though the holder was demonstrably alive and working. What a\n * waiter actually needs bounded is STALLED time: no ledger change for the whole window means the\n * holder hangs inside one migration (or died between two), and that is when failing loudly is\n * right. (Review finding F2 of the Phase 1+2 review.)\n */\n let deadline = Date.now() + waitDeadlineMs;\n let lastLedgerFingerprint = '';\n\n for (;;) {\n const ledger = await readLedger(db);\n const fingerprint = [...ledger.entries()].map(([name, row]) => `${name}:${row.status}:${row.sha256}`).sort().join('|');\n if (fingerprint !== lastLedgerFingerprint) {\n lastLedgerFingerprint = fingerprint;\n deadline = Date.now() + waitDeadlineMs;\n }\n\n const pending = migrations.filter((migration) => {\n const row = ledger.get(migration.name);\n return row?.status !== MigrationLedgerStatus.APPLIED;\n });\n if (pending.length === 0) {\n return { role: MigrationApplyRole.WAITED, applied: [], verified: migrations.map((migration) => migration.name) };\n }\n /**\n * Only a FAILED row for a migration THIS deployment actually ships may stop a waiter. The first\n * version scanned the whole ledger, and that bricked every waiting instance on a STALE row: a\n * migration that failed once, was renamed while being fixed, and left its FAILED row behind —\n * the holder ignores stale rows (it walks the shipped set), so the fleet split into a booting\n * holder and refusing waiters over a row nothing would ever clean. The two halves must judge\n * the SAME population. (Review finding F1.)\n */\n const pendingNames = new Set(pending.map((migration) => migration.name));\n const failedRow = [...ledger.values()].find((row) => row.status === MigrationLedgerStatus.FAILED && pendingNames.has(row.name));\n if (failedRow) {\n throw new Error(\n `Waiting on the migration holder, but \"${failedRow.name}\" is recorded FAILED — the holder stopped there. `\n + 'This instance cannot proceed against a schema behind its code; fix the migration and redeploy.',\n );\n }\n if (Date.now() >= deadline) {\n throw new Error(\n `No migration-ledger progress for ${input.waitDeadlineMs}ms while waiting for the holder; ${pending.length} migration(s) still `\n + `pending (first: \"${pending[0].name}\"). The holder either hangs or died between migrations — `\n + `pg_locks/pg_stat_activity names the session holding the wildo-migrations advisory lock.`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));\n }\n}\n\n/**\n * BASELINE: record migrations as APPLIED without executing them (critique #1).\n *\n * For a database whose tables ALREADY exist — every dev-provisioned database on the day the runner\n * ships, or a production database adopted into the system — executing migration 0 is usually\n * harmless (the renderer emits idempotent DDL) but recording the truth is better than re-running\n * it. Guarded by the caller's explicit choice (`wildo db migrate --baseline`); never automatic,\n * because \"these migrations have effectively run\" is a claim about the DATABASE only its operator\n * can make.\n */\nexport async function baselinePostgresMigrations(input: {\n db: Kysely<any>;\n migrations: readonly MigrationFile[];\n baselinedBy?: string;\n}): Promise<string[]> {\n const { db, migrations } = input;\n const baselinedBy = input.baselinedBy ?? `baseline:${hostname()}:${process.pid}`;\n await ensureLedgerTable(db);\n const ledger = await readLedger(db);\n\n const recorded: string[] = [];\n for (const migration of migrations) {\n if (ledger.get(migration.name)?.status === MigrationLedgerStatus.APPLIED) continue;\n await sql`\n insert into ${sql.ref(MIGRATION_LEDGER_TABLE)} (\"name\", \"sha256\", \"status\", \"appliedAt\", \"appliedBy\")\n values (${migration.name}, ${sha256OfMigration(migration.sql)}, ${MigrationLedgerStatus.APPLIED}, now(), ${baselinedBy})\n on conflict (\"name\") do update set\n \"sha256\" = excluded.\"sha256\", \"status\" = excluded.\"status\",\n \"appliedAt\" = excluded.\"appliedAt\", \"appliedBy\" = excluded.\"appliedBy\", \"error\" = null\n `.execute(db);\n recorded.push(migration.name);\n }\n return recorded;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The manual door's plumbing (`wildo db migrate` / `wildo db resolve`)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PostgresMigrationConnectionInput {\n host: string;\n port: number;\n database: string;\n user: string;\n password: string;\n /** `require`-style ssl toggle; local self-hosted PostgreSQL runs without it. */\n ssl?: boolean;\n /**\n * Create objects as the DATABASE'S OWNER rather than as the connecting role.\n *\n * ## Why an operator door needs this\n *\n * There are two doors onto this runner: the application's own boot, which connects as the\n * application's runtime role, and `wildo db migrate`, which connects as the PostgreSQL ADMIN\n * because it is an operator tool that must work before any application boots. Both are reasonable\n * about CONNECTIVITY and the second is wrong about OWNERSHIP: every table it creates is owned by\n * the admin, and the application — a different role — is then denied on its own schema.\n *\n * Measured on 2026-08-19: `wildo db migrate` created all 94 Wonder Todos tables as `wildo_admin`,\n * and the backend failed to start with `permission denied for table _wildo_migrations` while the\n * DATABASE itself was correctly owned by the application role.\n *\n * ## Why the database owner, and not a supplied name\n *\n * The owner is read from the server (`pg_database.datdba`), so the runner never re-derives the\n * provisioner's role-naming convention. Re-deriving it would create the same second naming\n * authority that had already produced `wildo_wonder_todos` beside `wonder-todos-db`.\n *\n * Applied per pooled connection, because `SET ROLE` is session state. It requires the connecting\n * role to be a member of the owner (a superuser always is); a role without that membership fails\n * loudly here rather than silently creating admin-owned objects.\n */\n createObjectsAsDatabaseOwner?: boolean;\n}\n\nexport interface PostgresMigrationConnection {\n db: Kysely<any>;\n close(): Promise<void>;\n}\n\n/**\n * A Kysely handle for the MANUAL door, constructed HERE so the CLI needs no `pg`/`kysely`\n * dependency of its own. Under pnpm's strict layout the CLI cannot resolve packages it does not\n * declare — and declaring them would create a second place the driver's version is chosen. The\n * engine already owns that choice for the boot door; the manual door borrows the same one.\n *\n * Pool of 5, not 1: the runner pins its advisory lock to ONE connection itself\n * (`db.connection()`), and the waiter's ledger polls are free to use the rest.\n */\nexport function openPostgresMigrationConnection(input: PostgresMigrationConnectionInput): PostgresMigrationConnection {\n const pool = new Pool({\n host: input.host,\n port: input.port,\n database: input.database,\n user: input.user,\n password: input.password,\n ...(input.ssl ? { ssl: { rejectUnauthorized: false } } : {}),\n max: 5,\n });\n\n if (input.createObjectsAsDatabaseOwner) {\n /**\n * Per CONNECTION, and GATED — the caller must not receive a session before `SET ROLE` has run.\n *\n * ## Why this cannot be `pool.on('connect')`\n *\n * That is what it was, and it is a RACE. The `connect` event's handlers are synchronous: a\n * handler that issues a query can only fire it and return, so the pool hands the client to the\n * caller while the `SET ROLE` is still in flight. Whichever statement the caller sends first may\n * win.\n *\n * On a database that has been migrated before, the race is invisible — the first statement only\n * READS the ledger, and reading is unaffected by which role you are. On a COLD database the\n * first statement CREATES it, so the loser of the race is the ownership of\n * `_wildo_migrations` itself.\n *\n * Measured 2026-08-23, first cold install of the acceptance cycle: `wildo db migrate` created\n * `_wildo_migrations` owned by `wildo_admin` in a database owned by\n * `wildo_wonder_todos_user`, then failed on its very next statement with\n * `permission denied for table _wildo_migrations`. That is the SAME symptom this option was\n * introduced to fix, reappearing one table earlier than the 94 it did fix — the ledger is\n * created by the runner itself, before any migration statement, and so was the only object the\n * original handler could lose the race to.\n *\n * Wrapping `connect` makes the assumption a precondition of holding the session rather than a\n * hope about ordering. Kysely acquires through the promise form, which is the form wrapped; the\n * callback form is left alone deliberately — nothing in this runner uses it, and quietly\n * half-supporting it would be worse than not supporting it.\n */\n const acquire = pool.connect.bind(pool);\n (pool as unknown as { connect: () => Promise<PoolClient> }).connect = async (): Promise<PoolClient> => {\n const client = await acquire();\n try {\n const result = await client.query(\n 'select pg_get_userbyid(datdba) as owner from pg_database where datname = current_database()',\n );\n const owner = result.rows?.[0]?.owner;\n if (typeof owner === 'string' && owner.length > 0) {\n // Quoted so an owner containing a hyphen — legal, and what an authored database name\n // produces — is not a syntax error.\n await client.query(`SET ROLE \"${owner.replace(/\"/g, '\"\"')}\"`);\n }\n } catch (error) {\n // Release before rethrowing: a client acquired and abandoned is a pool slot leaked for the\n // lifetime of the process, and this path runs once per connection.\n client.release();\n throw error;\n }\n return client;\n };\n }\n\n const db = new Kysely<any>({ dialect: new PostgresDialect({ pool }) });\n\n return { db, close: () => db.destroy() };\n}\n\n/** The closed resolution vocabulary — each member is one refusal's documented remedy. */\nexport enum MigrationResolution {\n /**\n * Re-accept a migration's CURRENT bytes as the applied truth — the remedy for the\n * edited-after-apply refusal, when the edit is deliberate (a comment fixed, a reviewed\n * reformat). Records the new SHA-256 WITHOUT executing anything: resolving is a statement about\n * history, never a way to run SQL.\n */\n APPLIED = 'applied',\n /** Declare an applied migration never-happened — see {@link MigrationLedgerStatus.ROLLED_BACK}. */\n ROLLED_BACK = 'rolled-back',\n}\n\n/**\n * `wildo db resolve` — the K8 easy-remediation verbs, as one recorded ledger mutation.\n *\n * Fails closed on a name the ledger does not know: resolving is always a correction OF a recorded\n * fact, and \"correcting\" an absent row would mint history instead. (`--applied` for a\n * never-recorded migration is what BASELINE is for, and the error says so.)\n */\nexport async function resolvePostgresMigrationLedgerEntry(input: {\n db: Kysely<any>;\n name: string;\n resolution: MigrationResolution;\n /** REQUIRED for {@link MigrationResolution.APPLIED} — the sha256 of the bytes being accepted. */\n sha256?: string;\n resolvedBy: string;\n}): Promise<void> {\n const { db, name, resolution, resolvedBy } = input;\n\n const existing = await sql<{ name: string }>`\n select \"name\" from ${sql.ref(MIGRATION_LEDGER_TABLE)} where \"name\" = ${name}\n `.execute(db);\n if (existing.rows.length === 0) {\n throw new Error(\n `Cannot resolve \"${name}\": the ledger has no row for it. Resolving corrects a RECORDED fact; `\n + 'for a migration that was applied outside the ledger entirely, use baseline '\n + '(`wildo db migrate --baseline`), which records without executing.',\n );\n }\n\n if (resolution === MigrationResolution.APPLIED) {\n if (!input.sha256) {\n throw new Error(`Resolving \"${name}\" as applied requires the sha256 of the bytes being accepted.`);\n }\n await sql`\n update ${sql.ref(MIGRATION_LEDGER_TABLE)}\n set \"sha256\" = ${input.sha256}, \"status\" = ${MigrationLedgerStatus.APPLIED},\n \"appliedAt\" = now(), \"appliedBy\" = ${`resolve:${resolvedBy}`}, \"error\" = null\n where \"name\" = ${name}\n `.execute(db);\n return;\n }\n\n await sql`\n update ${sql.ref(MIGRATION_LEDGER_TABLE)}\n set \"status\" = ${MigrationLedgerStatus.ROLLED_BACK},\n \"appliedAt\" = now(), \"appliedBy\" = ${`resolve:${resolvedBy}`}, \"error\" = null\n where \"name\" = ${name}\n `.execute(db);\n}\n"]}
|
package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts
CHANGED
|
@@ -681,7 +681,6 @@ export declare class ExternalProvidersRegistryBackendService {
|
|
|
681
681
|
getAgentClientProtocolExecutionProfileSecret(providerRef: string, executionProfileRef: string): string | undefined;
|
|
682
682
|
private toSemanticOperationKeys;
|
|
683
683
|
private getDefaultProviderConfiguration;
|
|
684
|
-
private secretRefForRuntimeContract;
|
|
685
684
|
/**
|
|
686
685
|
* Map a runtime contract's `kind` to the canonical
|
|
687
686
|
* `ExternalProvider_Auth_Kind` for the `authConfig` projection.
|