@empire-builder-kit/nx 1.0.0-rc.38 → 1.0.0-rc.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/executors.json +5 -0
- package/generators.json +10 -0
- package/package.json +20 -3
- package/src/aws-authority/index.d.ts +1 -0
- package/src/aws-authority/index.js +1 -0
- package/src/aws-authority/lambda-policy-ordering.d.ts +38 -0
- package/src/aws-authority/lambda-policy-ordering.js +56 -0
- package/src/aws-authority/stage-authority.d.ts +11 -3
- package/src/aws-authority/stage-authority.js +615 -29
- package/src/blueprints/external-sandbox.js +21 -9
- package/src/blueprints/manifest.d.ts +1 -1
- package/src/blueprints/resolver.js +28 -15
- package/src/cli/bootstrap.d.ts +1 -1
- package/src/cli/deploy-runtime.d.ts +6 -0
- package/src/cli/deploy-runtime.js +58 -0
- package/src/cli/dev-command.d.ts +16 -4
- package/src/cli/dev-command.js +63 -0
- package/src/cli/main.d.ts +7 -1
- package/src/cli/main.js +128 -20
- package/src/cli/output.d.ts +1 -1
- package/src/developer-agent/context.js +41 -9
- package/src/developer-agent/generator-planning.js +16 -2
- package/src/developer-agent/git-inspection.d.ts +6 -2
- package/src/developer-agent/git-inspection.js +78 -6
- package/src/developer-agent/model.d.ts +4 -2
- package/src/developer-agent/operation-registry.d.ts +5 -0
- package/src/developer-agent/operation-registry.js +179 -23
- package/src/developer-agent/preview-changes.d.ts +2 -0
- package/src/developer-agent/preview-changes.js +14 -2
- package/src/developer-agent/preview-source.js +3 -0
- package/src/developer-agent/validation.js +20 -8
- package/src/developer-agent/workspace-inspection.js +37 -3
- package/src/executors/deployment-plan/deployment-plan.d.ts +2 -0
- package/src/executors/deployment-plan/deployment-plan.js +51 -4
- package/src/executors/dev-doctor/dev-doctor.js +13 -6
- package/src/executors/dev-task-cleanup/dev-task-cleanup.d.ts +23 -0
- package/src/executors/dev-task-cleanup/dev-task-cleanup.js +145 -0
- package/src/executors/dev-task-cleanup/schema.json +9 -0
- package/src/executors/dev-task-cleanup/task-stack-absence.d.ts +27 -0
- package/src/executors/dev-task-cleanup/task-stack-absence.js +281 -0
- package/src/executors/dev-task-cleanup/task-stack-checkpoint.d.ts +12 -0
- package/src/executors/dev-task-cleanup/task-stack-checkpoint.js +92 -0
- package/src/executors/fleet/fleet.js +1 -1
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +28 -1
- package/src/executors/local-deploy/local-deploy.d.ts +8 -1
- package/src/executors/local-deploy/local-deploy.js +97 -24
- package/src/executors/local-deploy/schema.json +8 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +30 -18
- package/src/executors/sst-lifecycle/governed-native-operation.d.ts +2 -0
- package/src/executors/sst-lifecycle/governed-native-operation.js +9 -0
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +5 -0
- package/src/executors/sst-lifecycle/run-sst-command.js +220 -32
- package/src/executors/sst-lifecycle/sst-environment-source.d.ts +8 -0
- package/src/executors/sst-lifecycle/sst-environment-source.js +54 -0
- package/src/executors/sst-lifecycle/task-unlock.d.ts +15 -0
- package/src/executors/sst-lifecycle/task-unlock.js +126 -0
- package/src/foundation/capability-plan.d.ts +43 -0
- package/src/foundation/capability-plan.js +168 -0
- package/src/foundation/discovery.d.ts +14 -11
- package/src/foundation/discovery.js +32 -6
- package/src/foundation/index.d.ts +1 -0
- package/src/foundation/index.js +1 -0
- package/src/foundation/local-database-contract.d.ts +20 -0
- package/src/foundation/local-database-contract.js +262 -13
- package/src/foundation/local-private-smoke-access.d.ts +20 -0
- package/src/foundation/local-private-smoke-access.js +197 -0
- package/src/foundation/preflight.js +37 -18
- package/src/foundation/private-smoke-access.js +10 -2
- package/src/foundation/private-smoke-admission.d.ts +3 -1
- package/src/foundation/private-smoke-admission.js +29 -8
- package/src/foundation/private-smoke-control-plane.js +17 -6
- package/src/foundation/provision-database-bindings.d.ts +5 -0
- package/src/foundation/provision-database-bindings.js +41 -8
- package/src/foundation/provision-logical-database.js +13 -8
- package/src/foundation/requirements.d.ts +3 -3
- package/src/foundation/requirements.js +24 -12
- package/src/foundation/router-waf.d.ts +1 -0
- package/src/foundation/router-waf.js +14 -1
- package/src/generators/blueprint/blueprint.d.ts +5 -0
- package/src/generators/blueprint/blueprint.js +102 -0
- package/src/generators/blueprint/files.d.ts +6 -0
- package/src/generators/blueprint/files.js +217 -0
- package/src/generators/blueprint/schema.d.ts +4 -0
- package/src/generators/blueprint/schema.json +18 -0
- package/src/generators/formatting.js +24 -1
- package/src/generators/logging-registry.d.ts +2 -0
- package/src/generators/logging-registry.js +22 -0
- package/src/generators/preset/workspace-files-adopter-skills.d.ts +7 -0
- package/src/generators/preset/workspace-files-adopter-skills.js +301 -0
- package/src/generators/preset/workspace-files-ci-install.d.ts +2 -0
- package/src/generators/preset/workspace-files-ci-install.js +196 -0
- package/src/generators/preset/workspace-files-developer-agent.js +40 -13
- package/src/generators/preset/workspace-files-performance-load.js +8 -0
- package/src/generators/preset/workspace-files-stage-authority.js +40 -1
- package/src/generators/preset/workspace-files-thin-deploy.js +66 -34
- package/src/generators/preset/workspace-files-thin-docs.js +64 -18
- package/src/generators/preset/workspace-files.d.ts +4 -0
- package/src/generators/preset/workspace-files.js +159 -91
- package/src/generators/repository-policy/repository-policy.js +37 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js +42 -7
- package/src/generators/slice/built-in-plan-api-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-api-files.js +47 -389
- package/src/generators/slice/built-in-plan-async-work-files.js +19 -1912
- package/src/generators/slice/built-in-plan-authorization-files.js +92 -329
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +36 -2
- package/src/generators/slice/built-in-plan-authorization-storage.d.ts +3 -0
- package/src/generators/slice/built-in-plan-authorization-storage.js +119 -0
- package/src/generators/slice/built-in-plan-background-work-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-background-work-files.js +15 -5
- package/src/generators/slice/built-in-plan-data-deletion-files.js +2 -830
- package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-data-files.js +61 -1164
- package/src/generators/slice/built-in-plan-data-lifecycle-files.js +53 -28
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +18 -2
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +7 -2446
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +30 -6
- package/src/generators/slice/built-in-plan-data-rights-files.js +6 -4482
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +4 -15
- package/src/generators/slice/built-in-plan-foundation-files.d.ts +2 -1
- package/src/generators/slice/built-in-plan-foundation-files.js +34 -10
- package/src/generators/slice/built-in-plan-frontend-files.js +2 -2
- package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js +180 -522
- package/src/generators/slice/built-in-plan-key-value-cleanup.d.ts +4 -0
- package/src/generators/slice/built-in-plan-key-value-cleanup.js +100 -0
- package/src/generators/slice/built-in-plan-key-value-consumer.d.ts +4 -0
- package/src/generators/slice/built-in-plan-key-value-consumer.js +151 -0
- package/src/generators/slice/built-in-plan-key-value-deletion.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-deletion.js +377 -0
- package/src/generators/slice/built-in-plan-key-value-delivery.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-delivery.js +120 -0
- package/src/generators/slice/built-in-plan-key-value-export.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-export.js +595 -0
- package/src/generators/slice/built-in-plan-key-value-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-files.js +31 -0
- package/src/generators/slice/built-in-plan-key-value-holds.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-holds.js +189 -0
- package/src/generators/slice/built-in-plan-key-value-infrastructure.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-infrastructure.js +102 -0
- package/src/generators/slice/built-in-plan-key-value-performance.d.ts +5 -0
- package/src/generators/slice/built-in-plan-key-value-performance.js +530 -0
- package/src/generators/slice/built-in-plan-key-value-provider.d.ts +4 -0
- package/src/generators/slice/built-in-plan-key-value-provider.js +366 -0
- package/src/generators/slice/built-in-plan-key-value-records.d.ts +3 -0
- package/src/generators/slice/built-in-plan-key-value-records.js +232 -0
- package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-retention-infrastructure.js +103 -0
- package/src/generators/slice/built-in-plan-key-value-retention-job-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-retention-job-files.js +160 -0
- package/src/generators/slice/built-in-plan-key-value-retention-maintenance.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-retention-maintenance.js +329 -0
- package/src/generators/slice/built-in-plan-key-value-retention-worker.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-retention-worker.js +477 -0
- package/src/generators/slice/built-in-plan-key-value-retention.d.ts +2 -0
- package/src/generators/slice/built-in-plan-key-value-retention.js +184 -0
- package/src/generators/slice/built-in-plan-metadata-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-metadata-files.js +140 -28
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +36 -19
- package/src/generators/slice/built-in-plan-operations-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-operations-files.js +89 -50
- package/src/generators/slice/built-in-plan-performance-files.js +84 -1010
- package/src/generators/slice/built-in-plan-project-files.js +116 -67
- package/src/generators/slice/built-in-plan-release-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-release-files.js +64 -15
- package/src/generators/slice/built-in-plan-reliability-files.js +54 -28
- package/src/generators/slice/built-in-plan-shared.d.ts +3 -1
- package/src/generators/slice/built-in-plan-shared.js +40 -18
- package/src/generators/slice/built-in-plan-sql-events.d.ts +2 -0
- package/src/generators/slice/built-in-plan-sql-events.js +257 -0
- package/src/generators/slice/built-in-plan-testing-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +184 -132
- package/src/generators/slice/built-in-plan-testing-persistence-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-testing-persistence-files.js +178 -0
- package/src/generators/slice/built-in-plan-thin-release-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-thin-release-files.js +35 -2
- package/src/generators/slice/built-in-plan-verification-files.js +70 -0
- package/src/generators/slice/built-in-plan-web-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-web-files.js +191 -14
- package/src/generators/slice/built-in-plan-workload-role-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-workload-role-files.js +113 -274
- package/src/generators/slice/built-in-plan.d.ts +9 -1
- package/src/generators/slice/built-in-plan.js +49 -21
- package/src/generators/slice/data-policy-review-seed.d.ts +2 -0
- package/src/generators/slice/data-policy-review-seed.js +22 -0
- package/src/generators/slice/deployed-smoke-readiness.d.ts +2 -0
- package/src/generators/slice/deployed-smoke-readiness.js +214 -0
- package/src/generators/slice/local-source-identity.d.ts +2 -0
- package/src/generators/slice/local-source-identity.js +31 -0
- package/src/generators/slice/persistence-contract.d.ts +14 -0
- package/src/generators/slice/persistence-contract.js +32 -0
- package/src/generators/slice/slice.js +29 -24
- package/src/generators/sql/schema.d.ts +7 -0
- package/src/generators/sql/schema.json +38 -0
- package/src/generators/sql/sql.d.ts +6 -0
- package/src/generators/sql/sql.js +53 -0
- package/src/generators/upgrade/sql-seeded-reconciliation.d.ts +2 -0
- package/src/generators/upgrade/sql-seeded-reconciliation.js +170 -0
- package/src/generators/upgrade/upgrade.d.ts +2 -0
- package/src/generators/upgrade/upgrade.js +136 -9
- package/src/generators/workload/workload.js +51 -2
- package/src/local-dev/browser-verification.d.ts +37 -0
- package/src/local-dev/browser-verification.js +281 -0
- package/src/local-dev/contracts.d.ts +10 -1
- package/src/local-dev/contracts.js +114 -13
- package/src/local-dev/dev-command-core.js +24 -5
- package/src/local-dev/docker-credential-health.d.ts +14 -0
- package/src/local-dev/docker-credential-health.js +94 -0
- package/src/local-dev/environment-inspector.d.ts +7 -0
- package/src/local-dev/environment-inspector.js +42 -15
- package/src/local-dev/environment-state.js +2 -2
- package/src/local-dev/errors.d.ts +1 -1
- package/src/local-dev/errors.js +2 -0
- package/src/local-dev/fleet-client.js +2 -2
- package/src/local-dev/fleet-configuration.js +57 -25
- package/src/local-dev/fleet-inventory.js +46 -18
- package/src/local-dev/fleet-ipc.d.ts +7 -0
- package/src/local-dev/fleet-ipc.js +14 -2
- package/src/local-dev/fleet-launcher.d.ts +6 -0
- package/src/local-dev/fleet-launcher.js +14 -2
- package/src/local-dev/fleet-logs.js +50 -22
- package/src/local-dev/fleet-process.d.ts +20 -1
- package/src/local-dev/fleet-process.js +197 -41
- package/src/local-dev/fleet-runner-evidence.d.ts +5 -0
- package/src/local-dev/fleet-runner-evidence.js +56 -0
- package/src/local-dev/fleet-runtime.js +268 -229
- package/src/local-dev/fleet-session.d.ts +2 -0
- package/src/local-dev/fleet-session.js +28 -11
- package/src/local-dev/fleet-slice-runner-daemon.js +4 -0
- package/src/local-dev/fleet-state.d.ts +5 -2
- package/src/local-dev/fleet-state.js +25 -4
- package/src/local-dev/fleet-supervisor.d.ts +3 -0
- package/src/local-dev/fleet-supervisor.js +66 -11
- package/src/local-dev/fleet-web-start.d.ts +12 -0
- package/src/local-dev/fleet-web-start.js +48 -0
- package/src/local-dev/identity.js +3 -14
- package/src/local-dev/index.d.ts +1 -0
- package/src/local-dev/index.js +1 -0
- package/src/local-dev/native-completion.d.ts +9 -0
- package/src/local-dev/native-completion.js +130 -0
- package/src/local-dev/nx-workspace.d.ts +4 -2
- package/src/local-dev/nx-workspace.js +11 -6
- package/src/local-dev/personal-stage.d.ts +4 -5
- package/src/local-dev/personal-stage.js +6 -54
- package/src/local-dev/port-claims.d.ts +13 -0
- package/src/local-dev/port-claims.js +46 -0
- package/src/local-dev/ports.d.ts +14 -2
- package/src/local-dev/ports.js +78 -3
- package/src/local-dev/private-state.d.ts +6 -0
- package/src/local-dev/private-state.js +126 -34
- package/src/local-dev/process-group.d.ts +4 -0
- package/src/local-dev/process-group.js +29 -0
- package/src/local-dev/process-identity.d.ts +8 -0
- package/src/local-dev/process-identity.js +31 -1
- package/src/local-dev/proxy.js +11 -12
- package/src/local-dev/runtime-paths.js +1 -1
- package/src/local-dev/slice-claim.js +1 -1
- package/src/local-dev/slice-runner.d.ts +15 -6
- package/src/local-dev/slice-runner.js +189 -54
- package/src/local-dev/task-attachment.d.ts +17 -0
- package/src/local-dev/task-attachment.js +73 -0
- package/src/local-dev/task-authority.d.ts +10 -0
- package/src/local-dev/task-authority.js +27 -0
- package/src/local-dev/task-identity.d.ts +33 -0
- package/src/local-dev/task-identity.js +418 -0
- package/src/local-dev/task-native-writer.d.ts +18 -0
- package/src/local-dev/task-native-writer.js +329 -0
- package/src/local-dev/tls.js +3 -1
- package/src/local-dev/web-launch.d.ts +9 -0
- package/src/local-dev/web-launch.js +130 -0
- package/src/ownership/composition.d.ts +2 -1
- package/src/ownership/composition.js +1 -0
- package/src/performance/scenario.d.ts +22 -0
- package/src/performance/scenario.js +100 -92
- package/src/repository-policy/deployment-plan.d.ts +2 -2
- package/src/repository-policy/deployment-plan.js +2 -1
- package/src/repository-policy/policy.js +38 -3
- package/src/schemas/blueprint-v1.json +5 -4
- package/src/schemas/foundation-requirement-v1.json +5 -4
- package/src/schemas/testing-evidence-result-v1.json +1 -1
- package/src/secrets/operator.d.ts +3 -1
- package/src/secrets/operator.js +22 -3
- package/src/sst-environment-source-format.d.ts +3 -0
- package/src/sst-environment-source-format.js +20 -0
- package/src/supply-chain/blueprint-trust-git.js +13 -3
- package/src/supply-chain/build-output.js +15 -0
- package/src/supply-chain/generated-blueprint-trust-command.js +18 -1
- package/src/testing-evidence/fixture-scope.d.ts +2 -1
- package/src/testing-evidence/fixture-scope.js +4 -2
- package/src/testing-evidence/result.d.ts +2 -1
- package/src/testing-evidence/result.js +18 -4
- package/src/workload-roles/standard-slice.js +2 -3
- package/src/api-contract/baseline-update.js.map +0 -1
- package/src/api-contract/compatibility.js.map +0 -1
- package/src/api-contract/contract-graph.js.map +0 -1
- package/src/api-contract/evidence.js.map +0 -1
- package/src/api-contract/index.js.map +0 -1
- package/src/api-contract/model.fixtures.d.ts +0 -2
- package/src/api-contract/model.fixtures.js +0 -81
- package/src/api-contract/model.fixtures.js.map +0 -1
- package/src/api-contract/model.js.map +0 -1
- package/src/api-contract/rollout.js.map +0 -1
- package/src/authorization/compatibility.js.map +0 -1
- package/src/authorization/index.js.map +0 -1
- package/src/aws/child-environment.js.map +0 -1
- package/src/aws/identity.js.map +0 -1
- package/src/aws-authority/index.js.map +0 -1
- package/src/aws-authority/stage-authority.js.map +0 -1
- package/src/blueprints/execution.js.map +0 -1
- package/src/blueprints/external-sandbox.js.map +0 -1
- package/src/blueprints/index.js.map +0 -1
- package/src/blueprints/inputs.js.map +0 -1
- package/src/blueprints/manifest.js.map +0 -1
- package/src/blueprints/resolver.js.map +0 -1
- package/src/blueprints/transitions.js.map +0 -1
- package/src/cli/agent-command.js.map +0 -1
- package/src/cli/agent-runtime.js.map +0 -1
- package/src/cli/blueprint-authority.js.map +0 -1
- package/src/cli/blueprint-command.js.map +0 -1
- package/src/cli/bootstrap.js.map +0 -1
- package/src/cli/dev-command.js.map +0 -1
- package/src/cli/main.js.map +0 -1
- package/src/cli/output.js.map +0 -1
- package/src/cli/personal-secret-runtime.js.map +0 -1
- package/src/cli/secret-terminal.js.map +0 -1
- package/src/container-images.js.map +0 -1
- package/src/data-lifecycle/index.js.map +0 -1
- package/src/data-lifecycle/model.fixtures.d.ts +0 -2
- package/src/data-lifecycle/model.fixtures.js +0 -244
- package/src/data-lifecycle/model.fixtures.js.map +0 -1
- package/src/data-lifecycle/model.js.map +0 -1
- package/src/data-lifecycle/sensitive-artifacts.js.map +0 -1
- package/src/data-lifecycle/validation.js.map +0 -1
- package/src/database-retirement/contract.js.map +0 -1
- package/src/database-retirement/index.js.map +0 -1
- package/src/deterministic-order.js.map +0 -1
- package/src/developer-agent/context.js.map +0 -1
- package/src/developer-agent/generator-planning.js.map +0 -1
- package/src/developer-agent/generator-preview.js.map +0 -1
- package/src/developer-agent/git-inspection.js.map +0 -1
- package/src/developer-agent/handoff.js.map +0 -1
- package/src/developer-agent/index.js.map +0 -1
- package/src/developer-agent/model.js.map +0 -1
- package/src/developer-agent/operation-registry.js.map +0 -1
- package/src/developer-agent/ordering.js.map +0 -1
- package/src/developer-agent/planning.js.map +0 -1
- package/src/developer-agent/preview-changes.js.map +0 -1
- package/src/developer-agent/preview-source.js.map +0 -1
- package/src/developer-agent/validation.js.map +0 -1
- package/src/developer-agent/workspace-inspection.js.map +0 -1
- package/src/developer-agent/workspace-path.js.map +0 -1
- package/src/executors/blueprint-trust/blueprint-trust.js.map +0 -1
- package/src/executors/blueprint-verification/blueprint-verification.js.map +0 -1
- package/src/executors/deploy/deploy.js.map +0 -1
- package/src/executors/deployment-plan/deployment-plan.js.map +0 -1
- package/src/executors/dev/dev.js.map +0 -1
- package/src/executors/dev-doctor/dev-doctor.js.map +0 -1
- package/src/executors/dev-setup/dev-setup.js.map +0 -1
- package/src/executors/developer-context/developer-context.js.map +0 -1
- package/src/executors/fleet/fleet.js.map +0 -1
- package/src/executors/foundation-preflight/foundation-preflight.js.map +0 -1
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +0 -1
- package/src/executors/foundation-profile-preflight/production-database-safeguard.js.map +0 -1
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +0 -1
- package/src/executors/github-bootstrap/github-bootstrap.js.map +0 -1
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +0 -1
- package/src/executors/local-deploy/federated-credential-transport.js.map +0 -1
- package/src/executors/local-deploy/fixtures/local-deploy-state-machine.mjs +0 -53
- package/src/executors/local-deploy/local-approval.js.map +0 -1
- package/src/executors/local-deploy/local-deploy.js.map +0 -1
- package/src/executors/refresh/refresh.js.map +0 -1
- package/src/executors/remove/remove.js.map +0 -1
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +0 -1
- package/src/executors/secret-guardrails/secret-guardrails.js.map +0 -1
- package/src/executors/secret-initialize/secret-initialize.js.map +0 -1
- package/src/executors/secret-metadata/secret-metadata.js.map +0 -1
- package/src/executors/sst-lifecycle/generated-secret-retirement.js.map +0 -1
- package/src/executors/sst-lifecycle/governed-native-operation.js.map +0 -1
- package/src/executors/sst-lifecycle/logging-preflight.js.map +0 -1
- package/src/executors/sst-lifecycle/run-sst-command.js.map +0 -1
- package/src/executors/sst-lifecycle/secret-preflight.js.map +0 -1
- package/src/executors/sst-lifecycle/sst-environment-source.js.map +0 -1
- package/src/executors/unlock/unlock.js.map +0 -1
- package/src/executors/work-markers/work-markers.js.map +0 -1
- package/src/foundation/discovery.js.map +0 -1
- package/src/foundation/index.js.map +0 -1
- package/src/foundation/local-database-contract.js.map +0 -1
- package/src/foundation/logging-preflight.js.map +0 -1
- package/src/foundation/logging.js.map +0 -1
- package/src/foundation/partial-state.js.map +0 -1
- package/src/foundation/preflight.js.map +0 -1
- package/src/foundation/private-smoke-access.js.map +0 -1
- package/src/foundation/private-smoke-admission.js.map +0 -1
- package/src/foundation/private-smoke-control-plane.js.map +0 -1
- package/src/foundation/provision-database-bindings.js.map +0 -1
- package/src/foundation/provision-logical-database.js.map +0 -1
- package/src/foundation/requirements.js.map +0 -1
- package/src/foundation/router-origin-proof.js.map +0 -1
- package/src/foundation/router-waf-rate-rehearsal.js.map +0 -1
- package/src/foundation/router-waf.js.map +0 -1
- package/src/foundation/stages.js.map +0 -1
- package/src/frontend-security/index.js.map +0 -1
- package/src/frontend-security/security-headers.js.map +0 -1
- package/src/generators/feature-registry.js.map +0 -1
- package/src/generators/formatting.js.map +0 -1
- package/src/generators/function/function.js.map +0 -1
- package/src/generators/job/job.js.map +0 -1
- package/src/generators/logging-registry.js.map +0 -1
- package/src/generators/post-generation-install.js.map +0 -1
- package/src/generators/preset/dependency-overrides.js.map +0 -1
- package/src/generators/preset/generator.js.map +0 -1
- package/src/generators/preset/tool-versions.js.map +0 -1
- package/src/generators/preset/workspace-files-ci-acceptance.js.map +0 -1
- package/src/generators/preset/workspace-files-developer-agent.js.map +0 -1
- package/src/generators/preset/workspace-files-ecr-inspector.js.map +0 -1
- package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +0 -1
- package/src/generators/preset/workspace-files-performance-cost.js.map +0 -1
- package/src/generators/preset/workspace-files-performance-load.js.map +0 -1
- package/src/generators/preset/workspace-files-stage-authority.js.map +0 -1
- package/src/generators/preset/workspace-files-thin-deploy.js.map +0 -1
- package/src/generators/preset/workspace-files-thin-docs.js.map +0 -1
- package/src/generators/preset/workspace-files-thin-release.js.map +0 -1
- package/src/generators/preset/workspace-files-workload-role-baseline.js.map +0 -1
- package/src/generators/preset/workspace-files.js.map +0 -1
- package/src/generators/repository-policy/repository-policy.js.map +0 -1
- package/src/generators/repository-policy/tree-policy.js.map +0 -1
- package/src/generators/service/service.js.map +0 -1
- package/src/generators/slice/built-in-plan-api-contract-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-api-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-async-work-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-authorization-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-background-work-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-deletion-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-lifecycle-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-data-rights-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-foundation-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-frontend-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-metadata-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-operations-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-performance-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-project-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-release-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-reliability-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-shared.js.map +0 -1
- package/src/generators/slice/built-in-plan-testing-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-thin-release-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-verification-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-web-files.js.map +0 -1
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +0 -1
- package/src/generators/slice/built-in-plan.js.map +0 -1
- package/src/generators/slice/slice.js.map +0 -1
- package/src/generators/upgrade/upgrade.js.map +0 -1
- package/src/generators/workload/frozen-container.js.map +0 -1
- package/src/generators/workload/performance-registration.js.map +0 -1
- package/src/generators/workload/reliability-registration.js.map +0 -1
- package/src/generators/workload/service-performance-files.js.map +0 -1
- package/src/generators/workload/workload.js.map +0 -1
- package/src/git-policy/action-pins.js.map +0 -1
- package/src/git-policy/github-bootstrap.js.map +0 -1
- package/src/git-policy/index.js.map +0 -1
- package/src/git-policy/routing.js.map +0 -1
- package/src/index.js.map +0 -1
- package/src/infrastructure-change/convergence.js.map +0 -1
- package/src/infrastructure-change/drift.js.map +0 -1
- package/src/infrastructure-change/index.js.map +0 -1
- package/src/infrastructure-change/local-capability.js.map +0 -1
- package/src/infrastructure-change/model.js.map +0 -1
- package/src/infrastructure-change/normalize.js.map +0 -1
- package/src/infrastructure-change/plan.js.map +0 -1
- package/src/infrastructure-change/risk.js.map +0 -1
- package/src/infrastructure-change/summary.js.map +0 -1
- package/src/local-dev/context-discovery.js.map +0 -1
- package/src/local-dev/contract-validation.js.map +0 -1
- package/src/local-dev/contracts.js.map +0 -1
- package/src/local-dev/control-contract.js.map +0 -1
- package/src/local-dev/dev-command-core.js.map +0 -1
- package/src/local-dev/effects.js.map +0 -1
- package/src/local-dev/environment-inspector.js.map +0 -1
- package/src/local-dev/environment-state.js.map +0 -1
- package/src/local-dev/errors.js.map +0 -1
- package/src/local-dev/fleet-client.js.map +0 -1
- package/src/local-dev/fleet-configuration.js.map +0 -1
- package/src/local-dev/fleet-inventory.js.map +0 -1
- package/src/local-dev/fleet-ipc.js.map +0 -1
- package/src/local-dev/fleet-launcher.js.map +0 -1
- package/src/local-dev/fleet-lock.js.map +0 -1
- package/src/local-dev/fleet-logs.js.map +0 -1
- package/src/local-dev/fleet-process.js.map +0 -1
- package/src/local-dev/fleet-runtime.js.map +0 -1
- package/src/local-dev/fleet-session.js.map +0 -1
- package/src/local-dev/fleet-slice-runner-daemon.js.map +0 -1
- package/src/local-dev/fleet-startup.js.map +0 -1
- package/src/local-dev/fleet-state.js.map +0 -1
- package/src/local-dev/fleet-supervisor-daemon.js.map +0 -1
- package/src/local-dev/fleet-supervisor.js.map +0 -1
- package/src/local-dev/gateway-daemon.js.map +0 -1
- package/src/local-dev/gateway.js.map +0 -1
- package/src/local-dev/identity.js.map +0 -1
- package/src/local-dev/index.js.map +0 -1
- package/src/local-dev/local-context.js.map +0 -1
- package/src/local-dev/nx-workspace.js.map +0 -1
- package/src/local-dev/personal-secret-ledger.js.map +0 -1
- package/src/local-dev/personal-stage.js.map +0 -1
- package/src/local-dev/portless.js.map +0 -1
- package/src/local-dev/ports.js.map +0 -1
- package/src/local-dev/private-state.js.map +0 -1
- package/src/local-dev/process-identity.js.map +0 -1
- package/src/local-dev/proxy.js.map +0 -1
- package/src/local-dev/runtime-paths.js.map +0 -1
- package/src/local-dev/secret-slice-claim.js.map +0 -1
- package/src/local-dev/slice-claim.js.map +0 -1
- package/src/local-dev/slice-runner.js.map +0 -1
- package/src/local-dev/tls.js.map +0 -1
- package/src/ownership/composition.js.map +0 -1
- package/src/ownership/foundation-record.js.map +0 -1
- package/src/ownership/hash.js.map +0 -1
- package/src/ownership/index.js.map +0 -1
- package/src/ownership/json-ownership.js.map +0 -1
- package/src/ownership/record.js.map +0 -1
- package/src/ownership/workspace-record.js.map +0 -1
- package/src/performance/cost-collection.js.map +0 -1
- package/src/performance/cost-controls.js.map +0 -1
- package/src/performance/evidence.js.map +0 -1
- package/src/performance/index.js.map +0 -1
- package/src/performance/load-safety.js.map +0 -1
- package/src/performance/model.js.map +0 -1
- package/src/performance/pr-artifact-budget.js.map +0 -1
- package/src/performance/runner.js.map +0 -1
- package/src/performance/scenario-endpoint.js.map +0 -1
- package/src/performance/scenario.js.map +0 -1
- package/src/performance/test-fixtures.d.ts +0 -7
- package/src/performance/test-fixtures.js +0 -167
- package/src/performance/test-fixtures.js.map +0 -1
- package/src/performance/unit-cost.js.map +0 -1
- package/src/release/index.js.map +0 -1
- package/src/release/lifecycle.js.map +0 -1
- package/src/release/manifest.js.map +0 -1
- package/src/release/rollout.js.map +0 -1
- package/src/release/source-authority.js.map +0 -1
- package/src/repository-policy/aurora-preflight.js.map +0 -1
- package/src/repository-policy/capacity-migration.js.map +0 -1
- package/src/repository-policy/deployment-plan.js.map +0 -1
- package/src/repository-policy/foundation-profile.js.map +0 -1
- package/src/repository-policy/index.js.map +0 -1
- package/src/repository-policy/policy.js.map +0 -1
- package/src/secrets/adapters.js.map +0 -1
- package/src/secrets/aws-control-plane.js.map +0 -1
- package/src/secrets/declaration-verification.js.map +0 -1
- package/src/secrets/generated-aws-control-plane.js.map +0 -1
- package/src/secrets/guardrails.js.map +0 -1
- package/src/secrets/index.js.map +0 -1
- package/src/secrets/metadata.js.map +0 -1
- package/src/secrets/model.js.map +0 -1
- package/src/secrets/operator.js.map +0 -1
- package/src/secrets/permissions.js.map +0 -1
- package/src/secrets/personal-command.js.map +0 -1
- package/src/secrets/personal-ledger.js.map +0 -1
- package/src/secrets/registry.js.map +0 -1
- package/src/secrets/verification.js.map +0 -1
- package/src/secrets/workload-access.js.map +0 -1
- package/src/sst-environment-source-format.js.map +0 -1
- package/src/supply-chain/action-pins.js.map +0 -1
- package/src/supply-chain/blueprint-trust-git.js.map +0 -1
- package/src/supply-chain/blueprint-trust-process.js.map +0 -1
- package/src/supply-chain/blueprint-trust.js.map +0 -1
- package/src/supply-chain/blueprint-verification-authority.js.map +0 -1
- package/src/supply-chain/blueprint-verification-execution.js.map +0 -1
- package/src/supply-chain/build-output-bounds.js.map +0 -1
- package/src/supply-chain/build-output.js.map +0 -1
- package/src/supply-chain/common.js.map +0 -1
- package/src/supply-chain/container-artifact-contract.js.map +0 -1
- package/src/supply-chain/container-release.js.map +0 -1
- package/src/supply-chain/ecr-applicability.js.map +0 -1
- package/src/supply-chain/ecr-scanning.js.map +0 -1
- package/src/supply-chain/ecs-deployed-image.js.map +0 -1
- package/src/supply-chain/errors.js.map +0 -1
- package/src/supply-chain/generated-blueprint-trust-command.js.map +0 -1
- package/src/supply-chain/generated-command.js.map +0 -1
- package/src/supply-chain/generated-container-release-command.js.map +0 -1
- package/src/supply-chain/generated-ecr-collector-command.js.map +0 -1
- package/src/supply-chain/generated-files.js.map +0 -1
- package/src/supply-chain/generated-scanner-command.js.map +0 -1
- package/src/supply-chain/index.js.map +0 -1
- package/src/supply-chain/inventory.js.map +0 -1
- package/src/supply-chain/license-expression.js.map +0 -1
- package/src/supply-chain/pnpm-toolchain-authority.js.map +0 -1
- package/src/supply-chain/policy.js.map +0 -1
- package/src/supply-chain/publication.js.map +0 -1
- package/src/supply-chain/sbom.js.map +0 -1
- package/src/supply-chain/scanner.js.map +0 -1
- package/src/supply-chain/vendored-source.js.map +0 -1
- package/src/testing-evidence/fixture-scope.js.map +0 -1
- package/src/testing-evidence/index.js.map +0 -1
- package/src/testing-evidence/quarantine.js.map +0 -1
- package/src/testing-evidence/result.js.map +0 -1
- package/src/testing-evidence/runner-adapter.js.map +0 -1
- package/src/testing-evidence/sensitive-artifacts.js.map +0 -1
- package/src/validation.js.map +0 -1
- package/src/work-markers/index.js.map +0 -1
- package/src/work-markers/work-markers.js.map +0 -1
- package/src/workload-roles/account-baseline.js.map +0 -1
- package/src/workload-roles/authority.js.map +0 -1
- package/src/workload-roles/aws-adapter.js.map +0 -1
- package/src/workload-roles/aws-internal-handler.js.map +0 -1
- package/src/workload-roles/aws-s3-state.js.map +0 -1
- package/src/workload-roles/aws-sdk-control-plane.js.map +0 -1
- package/src/workload-roles/boundary.js.map +0 -1
- package/src/workload-roles/convergence.js.map +0 -1
- package/src/workload-roles/effective-policy.js.map +0 -1
- package/src/workload-roles/handler.js.map +0 -1
- package/src/workload-roles/history.js.map +0 -1
- package/src/workload-roles/index.js.map +0 -1
- package/src/workload-roles/injection.js.map +0 -1
- package/src/workload-roles/manifest.js.map +0 -1
- package/src/workload-roles/provisioner.js.map +0 -1
- package/src/workload-roles/registration.js.map +0 -1
- package/src/workload-roles/stage-boundary.js.map +0 -1
- package/src/workload-roles/standard-slice.js.map +0 -1
- package/src/workspace-deployment/affected-lifecycle.js.map +0 -1
- package/src/workspace-deployment/identity.js.map +0 -1
- package/src/workspace-deployment/index.js.map +0 -1
- package/src/workspace-deployment/migration-evidence.js.map +0 -1
- package/src/workspace-deployment/migration.js.map +0 -1
- package/src/workspace-deployment/ownership.js.map +0 -1
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderProductAsyncWorkFiles = renderProductAsyncWorkFiles;
|
|
4
4
|
const built_in_plan_shared_js_1 = require("./built-in-plan-shared.js");
|
|
5
|
+
const built_in_plan_key_value_provider_js_1 = require("./built-in-plan-key-value-provider.js");
|
|
6
|
+
const built_in_plan_key_value_performance_js_1 = require("./built-in-plan-key-value-performance.js");
|
|
7
|
+
const built_in_plan_key_value_cleanup_js_1 = require("./built-in-plan-key-value-cleanup.js");
|
|
5
8
|
/** @internal */
|
|
6
9
|
function renderProductAsyncWorkFiles(name, pascalName) {
|
|
7
10
|
const recordExportWorkflowName = (0, built_in_plan_shared_js_1.stableGeneratedName)(40, name);
|
|
8
11
|
return {
|
|
12
|
+
...(0, built_in_plan_key_value_provider_js_1.renderKeyValueProviderFiles)(name),
|
|
13
|
+
...(0, built_in_plan_key_value_performance_js_1.renderKeyValuePerformanceFiles)(name),
|
|
14
|
+
...(0, built_in_plan_key_value_cleanup_js_1.renderKeyValueCleanupFiles)(name),
|
|
9
15
|
'async/work-contract.json': (0, built_in_plan_shared_js_1.json)({
|
|
10
16
|
capabilities: [
|
|
11
17
|
{
|
|
12
18
|
id: 'domain-events',
|
|
13
19
|
owner: name,
|
|
14
20
|
primitive: 'eventbridge-event',
|
|
15
|
-
recovery: '
|
|
21
|
+
recovery: 'streams-and-bounded-reconciliation',
|
|
16
22
|
},
|
|
17
23
|
{
|
|
18
24
|
id: 'durable-work',
|
|
@@ -40,15 +46,15 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
40
46
|
},
|
|
41
47
|
],
|
|
42
48
|
consistency: {
|
|
43
|
-
inboxTable: '
|
|
44
|
-
outboxTable: '
|
|
45
|
-
transactionBoundary: '
|
|
49
|
+
inboxTable: 'slice-key-value',
|
|
50
|
+
outboxTable: 'slice-key-value',
|
|
51
|
+
transactionBoundary: 'native-dynamodb-transaction',
|
|
46
52
|
},
|
|
47
53
|
queue: {
|
|
48
54
|
batchItemFailures: true,
|
|
49
55
|
conflictPolicy: 'same-id-different-payload-fails',
|
|
50
56
|
idempotencyWindowSeconds: 2_592_000,
|
|
51
|
-
inboxCleanupBatchSize:
|
|
57
|
+
inboxCleanupBatchSize: 20,
|
|
52
58
|
inboxCleanupIntervalSeconds: 3_600,
|
|
53
59
|
inboxCleanupInvocationLimit: 10_000,
|
|
54
60
|
performanceCleanupDeadlineSeconds: 86_400,
|
|
@@ -912,535 +918,6 @@ export function createAsyncRecoveryAdapters(): AsyncRecoveryAdapters {
|
|
|
912
918
|
}
|
|
913
919
|
|
|
914
920
|
export type { BackfillReportV1 };
|
|
915
|
-
`,
|
|
916
|
-
'async/provider-worker.ts': `import { randomUUID } from 'node:crypto';
|
|
917
|
-
import {
|
|
918
|
-
asyncWorkDigest,
|
|
919
|
-
processSqsBatch,
|
|
920
|
-
runProviderDelivery,
|
|
921
|
-
type ProviderDeliveryV1,
|
|
922
|
-
} from '@empire-builder-kit/runtime/async-work';
|
|
923
|
-
import { deriveOperationalRolloutStableKey } from '@empire-builder-kit/runtime/configuration';
|
|
924
|
-
import { createLogger, type Logger } from '@empire-builder-kit/runtime/logging';
|
|
925
|
-
import type { Client } from 'pg';
|
|
926
|
-
import {
|
|
927
|
-
createSliceAwsConfigurationProvider,
|
|
928
|
-
createSliceLocalConfigurationProvider,
|
|
929
|
-
} from '../configuration/provider.js';
|
|
930
|
-
import { evaluateLambdaProviderDelivery } from '../configuration/rollout.js';
|
|
931
|
-
import { withPrimaryDatabase } from '../database/runtime-client.js';
|
|
932
|
-
import {
|
|
933
|
-
asyncAdapterDeclaration,
|
|
934
|
-
createAsyncRecoveryAdapters,
|
|
935
|
-
} from './recovery-adapters.js';
|
|
936
|
-
|
|
937
|
-
const providerWorkerRolloutStableKey =
|
|
938
|
-
deriveOperationalRolloutStableKey({
|
|
939
|
-
kind: 'lambda',
|
|
940
|
-
slice: '${name}',
|
|
941
|
-
workload: 'provider-worker',
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
interface QueueRecord { body: string; messageId: string }
|
|
945
|
-
interface QueueEvent { Records: QueueRecord[] }
|
|
946
|
-
|
|
947
|
-
interface InboxRow {
|
|
948
|
-
attempts: number;
|
|
949
|
-
lastAttemptAt: Date;
|
|
950
|
-
payloadDigest: string;
|
|
951
|
-
resultReference: string | null;
|
|
952
|
-
status: 'claimed' | 'failed' | 'succeeded';
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
const HANDLER_ID = '${name}.provider-worker';
|
|
956
|
-
const HANDLER_VERSION = '1';
|
|
957
|
-
const CLAIM_LEASE_MILLISECONDS = 120_000;
|
|
958
|
-
const INBOX_RETENTION_DAYS = 30;
|
|
959
|
-
const protectedStage =
|
|
960
|
-
process.env.EBK_STAGE === 'staging' ||
|
|
961
|
-
process.env.EBK_STAGE === 'production';
|
|
962
|
-
|
|
963
|
-
function requiredConfigurationIdentifier(name: string): string {
|
|
964
|
-
const value = process.env[name]?.trim();
|
|
965
|
-
if (!value) throw new Error(name + ' is required.');
|
|
966
|
-
return value;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
const configurationProvider = protectedStage
|
|
970
|
-
? createSliceAwsConfigurationProvider({
|
|
971
|
-
applicationIdentifier: requiredConfigurationIdentifier(
|
|
972
|
-
'EBK_APPCONFIG_APPLICATION_ID',
|
|
973
|
-
),
|
|
974
|
-
environmentIdentifier: requiredConfigurationIdentifier(
|
|
975
|
-
'EBK_APPCONFIG_ENVIRONMENT_ID',
|
|
976
|
-
),
|
|
977
|
-
profileIdentifier: requiredConfigurationIdentifier(
|
|
978
|
-
'EBK_APPCONFIG_PROFILE_ID',
|
|
979
|
-
),
|
|
980
|
-
})
|
|
981
|
-
: createSliceLocalConfigurationProvider();
|
|
982
|
-
|
|
983
|
-
function expiresAt(createdAt: string) {
|
|
984
|
-
const value = new Date(createdAt);
|
|
985
|
-
value.setUTCDate(value.getUTCDate() + INBOX_RETENTION_DAYS);
|
|
986
|
-
return value.toISOString();
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function parseDelivery(body: string): ProviderDeliveryV1<unknown> {
|
|
990
|
-
const delivery = JSON.parse(body) as ProviderDeliveryV1<unknown>;
|
|
991
|
-
if (
|
|
992
|
-
delivery?.version !== 1 ||
|
|
993
|
-
typeof delivery.deliveryId !== 'string' ||
|
|
994
|
-
typeof delivery.requestDigest !== 'string' ||
|
|
995
|
-
asyncWorkDigest(delivery.request) !== delivery.requestDigest
|
|
996
|
-
) {
|
|
997
|
-
throw new Error('provider-delivery-invalid');
|
|
998
|
-
}
|
|
999
|
-
return delivery;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
export type ProviderClaimResult =
|
|
1003
|
-
| { attemptToken: string; outcome: 'claimed' }
|
|
1004
|
-
| {
|
|
1005
|
-
outcome:
|
|
1006
|
-
| 'conflict'
|
|
1007
|
-
| 'duplicate'
|
|
1008
|
-
| 'retry'
|
|
1009
|
-
| 'terminal-permanent'
|
|
1010
|
-
| 'terminal-reconciliation';
|
|
1011
|
-
};
|
|
1012
|
-
|
|
1013
|
-
function attemptReference(
|
|
1014
|
-
state: 'effect-started' | 'prepared',
|
|
1015
|
-
attemptToken: string,
|
|
1016
|
-
): string {
|
|
1017
|
-
return state + '-' + attemptToken;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
export async function claimProviderDelivery(
|
|
1021
|
-
client: Client,
|
|
1022
|
-
delivery: ProviderDeliveryV1<unknown>,
|
|
1023
|
-
now: string,
|
|
1024
|
-
createAttemptToken: () => string = randomUUID,
|
|
1025
|
-
): Promise<ProviderClaimResult> {
|
|
1026
|
-
const attemptToken = createAttemptToken();
|
|
1027
|
-
const preparedReference = attemptReference('prepared', attemptToken);
|
|
1028
|
-
await client.query('BEGIN');
|
|
1029
|
-
try {
|
|
1030
|
-
const inserted = await client.query(
|
|
1031
|
-
\`INSERT INTO ebk_inbox
|
|
1032
|
-
(handler_id, handler_version, message_id, payload_digest, status,
|
|
1033
|
-
attempts, first_seen_at, last_attempt_at, expires_at,
|
|
1034
|
-
result_reference)
|
|
1035
|
-
VALUES ($1, $2, $3, $4, 'claimed', 1, $5, $5, $6, $7)
|
|
1036
|
-
ON CONFLICT (handler_id, message_id) DO NOTHING
|
|
1037
|
-
RETURNING message_id\`,
|
|
1038
|
-
[
|
|
1039
|
-
HANDLER_ID,
|
|
1040
|
-
HANDLER_VERSION,
|
|
1041
|
-
delivery.deliveryId,
|
|
1042
|
-
delivery.requestDigest,
|
|
1043
|
-
now,
|
|
1044
|
-
expiresAt(delivery.createdAt),
|
|
1045
|
-
preparedReference,
|
|
1046
|
-
],
|
|
1047
|
-
);
|
|
1048
|
-
if (inserted.rowCount === 1) {
|
|
1049
|
-
await client.query('COMMIT');
|
|
1050
|
-
return { attemptToken, outcome: 'claimed' };
|
|
1051
|
-
}
|
|
1052
|
-
const existing = await client.query<InboxRow>(
|
|
1053
|
-
\`SELECT attempts, last_attempt_at AS "lastAttemptAt",
|
|
1054
|
-
payload_digest AS "payloadDigest",
|
|
1055
|
-
result_reference AS "resultReference", status
|
|
1056
|
-
FROM ebk_inbox
|
|
1057
|
-
WHERE handler_id = $1 AND message_id = $2
|
|
1058
|
-
FOR UPDATE\`,
|
|
1059
|
-
[HANDLER_ID, delivery.deliveryId],
|
|
1060
|
-
);
|
|
1061
|
-
const row = existing.rows[0];
|
|
1062
|
-
if (!row || row.payloadDigest !== delivery.requestDigest) {
|
|
1063
|
-
await client.query('COMMIT');
|
|
1064
|
-
return { outcome: 'conflict' };
|
|
1065
|
-
}
|
|
1066
|
-
if (row.status === 'succeeded') {
|
|
1067
|
-
await client.query('COMMIT');
|
|
1068
|
-
return { outcome: 'duplicate' };
|
|
1069
|
-
}
|
|
1070
|
-
if (row.status === 'failed' && row.resultReference) {
|
|
1071
|
-
if (row.resultReference.startsWith('terminal-reconciliation-')) {
|
|
1072
|
-
await client.query('COMMIT');
|
|
1073
|
-
return { outcome: 'terminal-reconciliation' };
|
|
1074
|
-
}
|
|
1075
|
-
if (row.resultReference.startsWith('terminal-permanent-')) {
|
|
1076
|
-
await client.query('COMMIT');
|
|
1077
|
-
return { outcome: 'terminal-permanent' };
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
if (
|
|
1081
|
-
row.status === 'claimed' &&
|
|
1082
|
-
Date.parse(now) - row.lastAttemptAt.getTime() <
|
|
1083
|
-
CLAIM_LEASE_MILLISECONDS
|
|
1084
|
-
) {
|
|
1085
|
-
await client.query('COMMIT');
|
|
1086
|
-
return { outcome: 'retry' };
|
|
1087
|
-
}
|
|
1088
|
-
if (
|
|
1089
|
-
row.status === 'claimed' &&
|
|
1090
|
-
row.resultReference?.startsWith('effect-started-') &&
|
|
1091
|
-
delivery.contract.idempotencyRepeatSafety === 'unproven'
|
|
1092
|
-
) {
|
|
1093
|
-
const terminal = await client.query(
|
|
1094
|
-
\`UPDATE ebk_inbox
|
|
1095
|
-
SET handler_version = $3, last_attempt_at = $4, status = 'failed',
|
|
1096
|
-
result_reference = $5
|
|
1097
|
-
WHERE handler_id = $1 AND message_id = $2
|
|
1098
|
-
AND status = 'claimed' AND result_reference = $6\`,
|
|
1099
|
-
[
|
|
1100
|
-
HANDLER_ID,
|
|
1101
|
-
delivery.deliveryId,
|
|
1102
|
-
HANDLER_VERSION,
|
|
1103
|
-
now,
|
|
1104
|
-
'terminal-reconciliation-' +
|
|
1105
|
-
asyncWorkDigest({
|
|
1106
|
-
deliveryId: delivery.deliveryId,
|
|
1107
|
-
result: 'provider-effect-outcome-unknown',
|
|
1108
|
-
}),
|
|
1109
|
-
row.resultReference,
|
|
1110
|
-
],
|
|
1111
|
-
);
|
|
1112
|
-
if (terminal.rowCount !== 1) {
|
|
1113
|
-
throw new Error('provider-reconciliation-transition-lost');
|
|
1114
|
-
}
|
|
1115
|
-
await client.query('COMMIT');
|
|
1116
|
-
return { outcome: 'terminal-reconciliation' };
|
|
1117
|
-
}
|
|
1118
|
-
await client.query(
|
|
1119
|
-
\`UPDATE ebk_inbox
|
|
1120
|
-
SET attempts = attempts + 1, handler_version = $3,
|
|
1121
|
-
last_attempt_at = $4, result_reference = $5, status = 'claimed'
|
|
1122
|
-
WHERE handler_id = $1 AND message_id = $2\`,
|
|
1123
|
-
[
|
|
1124
|
-
HANDLER_ID,
|
|
1125
|
-
delivery.deliveryId,
|
|
1126
|
-
HANDLER_VERSION,
|
|
1127
|
-
now,
|
|
1128
|
-
preparedReference,
|
|
1129
|
-
],
|
|
1130
|
-
);
|
|
1131
|
-
await client.query('COMMIT');
|
|
1132
|
-
return { attemptToken, outcome: 'claimed' };
|
|
1133
|
-
} catch (error) {
|
|
1134
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
1135
|
-
throw error;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
export async function markProviderEffectStarted(
|
|
1140
|
-
client: Client,
|
|
1141
|
-
deliveryId: string,
|
|
1142
|
-
attemptToken: string,
|
|
1143
|
-
now: string,
|
|
1144
|
-
): Promise<boolean> {
|
|
1145
|
-
const result = await client.query(
|
|
1146
|
-
\`UPDATE ebk_inbox
|
|
1147
|
-
SET result_reference = $4, last_attempt_at = $5
|
|
1148
|
-
WHERE handler_id = $1 AND message_id = $2 AND status = 'claimed'
|
|
1149
|
-
AND result_reference = $3\`,
|
|
1150
|
-
[
|
|
1151
|
-
HANDLER_ID,
|
|
1152
|
-
deliveryId,
|
|
1153
|
-
attemptReference('prepared', attemptToken),
|
|
1154
|
-
attemptReference('effect-started', attemptToken),
|
|
1155
|
-
now,
|
|
1156
|
-
],
|
|
1157
|
-
);
|
|
1158
|
-
return result.rowCount === 1;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
export async function finishProviderDelivery(
|
|
1162
|
-
client: Client,
|
|
1163
|
-
deliveryId: string,
|
|
1164
|
-
attemptToken: string,
|
|
1165
|
-
input: {
|
|
1166
|
-
expectedState?: 'effect-started' | 'prepared';
|
|
1167
|
-
resultReference?: string;
|
|
1168
|
-
status: 'failed' | 'succeeded';
|
|
1169
|
-
},
|
|
1170
|
-
): Promise<boolean> {
|
|
1171
|
-
const result = await client.query(
|
|
1172
|
-
\`UPDATE ebk_inbox
|
|
1173
|
-
SET completed_at = CASE WHEN $3 = 'succeeded' THEN now() ELSE NULL END,
|
|
1174
|
-
result_reference = $4, status = $3, last_attempt_at = now()
|
|
1175
|
-
WHERE handler_id = $1 AND message_id = $2
|
|
1176
|
-
AND status = 'claimed' AND result_reference = $5\`,
|
|
1177
|
-
[
|
|
1178
|
-
HANDLER_ID,
|
|
1179
|
-
deliveryId,
|
|
1180
|
-
input.status,
|
|
1181
|
-
input.resultReference ?? null,
|
|
1182
|
-
attemptReference(input.expectedState ?? 'effect-started', attemptToken),
|
|
1183
|
-
],
|
|
1184
|
-
);
|
|
1185
|
-
return result.rowCount === 1;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
interface ProviderWorkerDependencies {
|
|
1189
|
-
createAttemptToken(): string;
|
|
1190
|
-
deliver: typeof runProviderDelivery;
|
|
1191
|
-
evaluateRollout: typeof evaluateLambdaProviderDelivery;
|
|
1192
|
-
logger: Logger;
|
|
1193
|
-
now(): string;
|
|
1194
|
-
withDatabase: typeof withPrimaryDatabase;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
export function createProviderWorkerHandler(
|
|
1198
|
-
dependencies: ProviderWorkerDependencies,
|
|
1199
|
-
) {
|
|
1200
|
-
const processRecord = async (record: QueueRecord) => {
|
|
1201
|
-
let delivery: ProviderDeliveryV1<unknown>;
|
|
1202
|
-
try {
|
|
1203
|
-
delivery = parseDelivery(record.body);
|
|
1204
|
-
} catch (error) {
|
|
1205
|
-
dependencies.logger.warn('Provider delivery rejected', {
|
|
1206
|
-
attributes: {
|
|
1207
|
-
errorCode: 'provider-delivery-invalid',
|
|
1208
|
-
messageIdDigest: asyncWorkDigest(record.messageId),
|
|
1209
|
-
},
|
|
1210
|
-
error,
|
|
1211
|
-
outcome: 'failure',
|
|
1212
|
-
});
|
|
1213
|
-
return { errorCode: 'provider-delivery-invalid', outcome: 'terminal' as const };
|
|
1214
|
-
}
|
|
1215
|
-
const correlationId = delivery.deliveryId;
|
|
1216
|
-
const claimResult = await dependencies.withDatabase(
|
|
1217
|
-
{ correlationId },
|
|
1218
|
-
'async-provider-worker',
|
|
1219
|
-
(client) =>
|
|
1220
|
-
claimProviderDelivery(
|
|
1221
|
-
client,
|
|
1222
|
-
delivery,
|
|
1223
|
-
dependencies.now(),
|
|
1224
|
-
dependencies.createAttemptToken,
|
|
1225
|
-
),
|
|
1226
|
-
);
|
|
1227
|
-
if (claimResult.outcome === 'conflict') {
|
|
1228
|
-
dependencies.logger.error('Provider delivery idempotency conflict', {
|
|
1229
|
-
attributes: { deliveryIdDigest: asyncWorkDigest(delivery.deliveryId) },
|
|
1230
|
-
context: { correlationId },
|
|
1231
|
-
outcome: 'failure',
|
|
1232
|
-
});
|
|
1233
|
-
return { errorCode: 'idempotency-payload-conflict', outcome: 'terminal' as const };
|
|
1234
|
-
}
|
|
1235
|
-
if (claimResult.outcome === 'duplicate') {
|
|
1236
|
-
dependencies.logger.info('Provider delivery deduplicated', {
|
|
1237
|
-
attributes: { deliveryIdDigest: asyncWorkDigest(delivery.deliveryId) },
|
|
1238
|
-
context: { correlationId },
|
|
1239
|
-
outcome: 'success',
|
|
1240
|
-
});
|
|
1241
|
-
return { outcome: 'succeeded' as const };
|
|
1242
|
-
}
|
|
1243
|
-
if (claimResult.outcome === 'terminal-permanent') {
|
|
1244
|
-
return {
|
|
1245
|
-
errorCode: 'provider-permanent-failure',
|
|
1246
|
-
outcome: 'terminal' as const,
|
|
1247
|
-
};
|
|
1248
|
-
}
|
|
1249
|
-
if (claimResult.outcome === 'terminal-reconciliation') {
|
|
1250
|
-
dependencies.logger.error('Provider delivery requires reconciliation', {
|
|
1251
|
-
attributes: { deliveryIdDigest: asyncWorkDigest(delivery.deliveryId) },
|
|
1252
|
-
context: { correlationId },
|
|
1253
|
-
outcome: 'failure',
|
|
1254
|
-
});
|
|
1255
|
-
return {
|
|
1256
|
-
errorCode: 'provider-reconciliation-required',
|
|
1257
|
-
outcome: 'terminal' as const,
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
if (claimResult.outcome === 'retry') {
|
|
1261
|
-
return { errorCode: 'idempotency-claim-active', outcome: 'retry' as const };
|
|
1262
|
-
}
|
|
1263
|
-
if (claimResult.outcome !== 'claimed') {
|
|
1264
|
-
throw new Error('provider-delivery-claim-outcome-unhandled');
|
|
1265
|
-
}
|
|
1266
|
-
const attemptToken = claimResult.attemptToken;
|
|
1267
|
-
let rollout;
|
|
1268
|
-
try {
|
|
1269
|
-
rollout = await dependencies.evaluateRollout(
|
|
1270
|
-
configurationProvider,
|
|
1271
|
-
providerWorkerRolloutStableKey,
|
|
1272
|
-
);
|
|
1273
|
-
} catch (error) {
|
|
1274
|
-
await dependencies.withDatabase(
|
|
1275
|
-
{ correlationId },
|
|
1276
|
-
'async-provider-worker',
|
|
1277
|
-
(client) =>
|
|
1278
|
-
finishProviderDelivery(
|
|
1279
|
-
client,
|
|
1280
|
-
delivery.deliveryId,
|
|
1281
|
-
attemptToken,
|
|
1282
|
-
{
|
|
1283
|
-
expectedState: 'prepared',
|
|
1284
|
-
resultReference: 'configuration-invalid',
|
|
1285
|
-
status: 'failed',
|
|
1286
|
-
},
|
|
1287
|
-
),
|
|
1288
|
-
);
|
|
1289
|
-
dependencies.logger.error('Provider delivery configuration failed', {
|
|
1290
|
-
context: { correlationId },
|
|
1291
|
-
error,
|
|
1292
|
-
outcome: 'failure',
|
|
1293
|
-
});
|
|
1294
|
-
return {
|
|
1295
|
-
errorCode: 'provider-delivery-configuration-invalid',
|
|
1296
|
-
outcome: 'retry' as const,
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
if (!rollout.assigned) {
|
|
1300
|
-
await dependencies.withDatabase(
|
|
1301
|
-
{ correlationId },
|
|
1302
|
-
'async-provider-worker',
|
|
1303
|
-
(client) =>
|
|
1304
|
-
finishProviderDelivery(
|
|
1305
|
-
client,
|
|
1306
|
-
delivery.deliveryId,
|
|
1307
|
-
attemptToken,
|
|
1308
|
-
{
|
|
1309
|
-
expectedState: 'prepared',
|
|
1310
|
-
resultReference: 'configuration-disabled',
|
|
1311
|
-
status: 'failed',
|
|
1312
|
-
},
|
|
1313
|
-
),
|
|
1314
|
-
);
|
|
1315
|
-
return {
|
|
1316
|
-
errorCode: 'provider-delivery-disabled',
|
|
1317
|
-
outcome: 'retry' as const,
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
const effectStarted = await dependencies.withDatabase(
|
|
1321
|
-
{ correlationId },
|
|
1322
|
-
'async-provider-worker',
|
|
1323
|
-
(client) =>
|
|
1324
|
-
markProviderEffectStarted(
|
|
1325
|
-
client,
|
|
1326
|
-
delivery.deliveryId,
|
|
1327
|
-
attemptToken,
|
|
1328
|
-
dependencies.now(),
|
|
1329
|
-
),
|
|
1330
|
-
);
|
|
1331
|
-
if (!effectStarted) {
|
|
1332
|
-
return {
|
|
1333
|
-
errorCode: 'idempotency-claim-lost',
|
|
1334
|
-
outcome: 'retry' as const,
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
const result = await dependencies.deliver({
|
|
1338
|
-
adapter: createAsyncRecoveryAdapters().providerDelivery,
|
|
1339
|
-
delivery,
|
|
1340
|
-
});
|
|
1341
|
-
if (result.outcome === 'confirmed') {
|
|
1342
|
-
const persisted = await dependencies.withDatabase(
|
|
1343
|
-
{ correlationId },
|
|
1344
|
-
'async-provider-worker',
|
|
1345
|
-
(client) =>
|
|
1346
|
-
finishProviderDelivery(
|
|
1347
|
-
client,
|
|
1348
|
-
delivery.deliveryId,
|
|
1349
|
-
attemptToken,
|
|
1350
|
-
{
|
|
1351
|
-
resultReference: result.providerReference,
|
|
1352
|
-
status: 'succeeded',
|
|
1353
|
-
},
|
|
1354
|
-
),
|
|
1355
|
-
);
|
|
1356
|
-
if (!persisted) {
|
|
1357
|
-
throw new Error('provider-delivery-confirmation-state-lost');
|
|
1358
|
-
}
|
|
1359
|
-
dependencies.logger.info('Provider delivery completed', {
|
|
1360
|
-
attributes: {
|
|
1361
|
-
attempts: result.attempts,
|
|
1362
|
-
deliveryIdDigest: asyncWorkDigest(delivery.deliveryId),
|
|
1363
|
-
},
|
|
1364
|
-
context: { correlationId },
|
|
1365
|
-
outcome: 'success',
|
|
1366
|
-
});
|
|
1367
|
-
return { outcome: 'succeeded' as const };
|
|
1368
|
-
}
|
|
1369
|
-
await dependencies.withDatabase(
|
|
1370
|
-
{ correlationId },
|
|
1371
|
-
'async-provider-worker',
|
|
1372
|
-
(client) =>
|
|
1373
|
-
finishProviderDelivery(
|
|
1374
|
-
client,
|
|
1375
|
-
delivery.deliveryId,
|
|
1376
|
-
attemptToken,
|
|
1377
|
-
{
|
|
1378
|
-
resultReference:
|
|
1379
|
-
result.outcome === 'permanent-failure'
|
|
1380
|
-
? 'terminal-permanent-' + result.reconciliationId
|
|
1381
|
-
: 'terminal-reconciliation-' + result.reconciliationId,
|
|
1382
|
-
status: 'failed',
|
|
1383
|
-
},
|
|
1384
|
-
),
|
|
1385
|
-
);
|
|
1386
|
-
dependencies.logger.error('Provider delivery terminated', {
|
|
1387
|
-
attributes: {
|
|
1388
|
-
deliveryIdDigest: asyncWorkDigest(delivery.deliveryId),
|
|
1389
|
-
errorCode: result.errorCode,
|
|
1390
|
-
requiresReconciliation:
|
|
1391
|
-
result.outcome === 'reconciliation-required',
|
|
1392
|
-
},
|
|
1393
|
-
context: { correlationId },
|
|
1394
|
-
outcome: 'failure',
|
|
1395
|
-
});
|
|
1396
|
-
return {
|
|
1397
|
-
errorCode: result.errorCode,
|
|
1398
|
-
outcome: 'terminal' as const,
|
|
1399
|
-
};
|
|
1400
|
-
};
|
|
1401
|
-
|
|
1402
|
-
return async (event: QueueEvent) => {
|
|
1403
|
-
if (!asyncAdapterDeclaration.configured) {
|
|
1404
|
-
throw new Error('async-provider-adapter-disabled');
|
|
1405
|
-
}
|
|
1406
|
-
const result = await processSqsBatch({
|
|
1407
|
-
handler: async (record) => {
|
|
1408
|
-
try {
|
|
1409
|
-
return await processRecord(record);
|
|
1410
|
-
} catch (error) {
|
|
1411
|
-
dependencies.logger.error('Provider delivery handler failed', {
|
|
1412
|
-
attributes: {
|
|
1413
|
-
messageIdDigest: asyncWorkDigest(record.messageId),
|
|
1414
|
-
},
|
|
1415
|
-
error,
|
|
1416
|
-
outcome: 'failure',
|
|
1417
|
-
});
|
|
1418
|
-
throw error;
|
|
1419
|
-
}
|
|
1420
|
-
},
|
|
1421
|
-
records: event.Records,
|
|
1422
|
-
});
|
|
1423
|
-
return result.response;
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
const logger = createLogger({
|
|
1428
|
-
component: 'provider-worker',
|
|
1429
|
-
feature: 'async-work',
|
|
1430
|
-
operation: 'provider-delivery',
|
|
1431
|
-
slice: '${name}',
|
|
1432
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1433
|
-
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
1434
|
-
});
|
|
1435
|
-
|
|
1436
|
-
export const handler = createProviderWorkerHandler({
|
|
1437
|
-
createAttemptToken: randomUUID,
|
|
1438
|
-
deliver: runProviderDelivery,
|
|
1439
|
-
evaluateRollout: evaluateLambdaProviderDelivery,
|
|
1440
|
-
logger,
|
|
1441
|
-
now: () => new Date().toISOString(),
|
|
1442
|
-
withDatabase: withPrimaryDatabase,
|
|
1443
|
-
});
|
|
1444
921
|
`,
|
|
1445
922
|
'async/webhook-ingress.ts': `import {
|
|
1446
923
|
SendMessageCommand,
|
|
@@ -1774,771 +1251,6 @@ export async function handler(event: { time?: string }) {
|
|
|
1774
1251
|
});
|
|
1775
1252
|
return { accepted: true, scheduledAt };
|
|
1776
1253
|
}
|
|
1777
|
-
`,
|
|
1778
|
-
'async/outbox-relay.ts': `import { randomUUID } from 'node:crypto';
|
|
1779
|
-
import {
|
|
1780
|
-
EventBridgeClient,
|
|
1781
|
-
PutEventsCommand,
|
|
1782
|
-
type PutEventsRequestEntry,
|
|
1783
|
-
} from '@aws-sdk/client-eventbridge';
|
|
1784
|
-
import { createLogger, type Logger } from '@empire-builder-kit/runtime/logging';
|
|
1785
|
-
import {
|
|
1786
|
-
createMetricEmitter,
|
|
1787
|
-
type MetricEmitter,
|
|
1788
|
-
} from '@empire-builder-kit/runtime/observability';
|
|
1789
|
-
import { withPrimaryDatabase } from '../database/runtime-client.js';
|
|
1790
|
-
|
|
1791
|
-
interface OutboxRow {
|
|
1792
|
-
eventJson: PutEventsRequestEntry;
|
|
1793
|
-
idempotencyKey: string;
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
const eventBridge = new EventBridgeClient({ maxAttempts: 2 });
|
|
1797
|
-
|
|
1798
|
-
function emitOutboxBacklog(metrics: MetricEmitter, value: number) {
|
|
1799
|
-
metrics.emit({
|
|
1800
|
-
dimensions: {
|
|
1801
|
-
component: 'outbox-relay',
|
|
1802
|
-
feature: 'async-work',
|
|
1803
|
-
operation: 'outbox-relay',
|
|
1804
|
-
slice: '${name}',
|
|
1805
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1806
|
-
},
|
|
1807
|
-
metricName: 'OutboxBacklog',
|
|
1808
|
-
namespace: 'EBK/Async',
|
|
1809
|
-
unit: 'Count',
|
|
1810
|
-
value,
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
interface OutboxRelayDependencies {
|
|
1815
|
-
logger: Logger;
|
|
1816
|
-
metrics: MetricEmitter;
|
|
1817
|
-
publish(entries: PutEventsRequestEntry[]): Promise<{
|
|
1818
|
-
Entries?: Array<{ ErrorCode?: string; EventId?: string }>;
|
|
1819
|
-
}>;
|
|
1820
|
-
withDatabase: typeof withPrimaryDatabase;
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
export function createOutboxRelayHandler(
|
|
1824
|
-
dependencies: OutboxRelayDependencies,
|
|
1825
|
-
) {
|
|
1826
|
-
return async (event: { id?: string } = {}) => {
|
|
1827
|
-
const correlationId = event.id ?? randomUUID();
|
|
1828
|
-
return dependencies.withDatabase(
|
|
1829
|
-
{ correlationId },
|
|
1830
|
-
'async-outbox-relay',
|
|
1831
|
-
async (client) => {
|
|
1832
|
-
await client.query('BEGIN');
|
|
1833
|
-
try {
|
|
1834
|
-
const isolatedPerformance = await client.query<{ effectId: string }>(
|
|
1835
|
-
\`WITH claimed AS (
|
|
1836
|
-
SELECT effect_id
|
|
1837
|
-
FROM performance_load_async_effects
|
|
1838
|
-
WHERE processed_at IS NULL AND expires_at > now()
|
|
1839
|
-
ORDER BY created_at, effect_id
|
|
1840
|
-
LIMIT 10
|
|
1841
|
-
FOR UPDATE SKIP LOCKED
|
|
1842
|
-
)
|
|
1843
|
-
UPDATE performance_load_async_effects AS effects
|
|
1844
|
-
SET processed_at = now()
|
|
1845
|
-
FROM claimed
|
|
1846
|
-
WHERE effects.effect_id = claimed.effect_id
|
|
1847
|
-
RETURNING effects.effect_id AS "effectId"\`,
|
|
1848
|
-
);
|
|
1849
|
-
const processedPerformance = isolatedPerformance.rows.length;
|
|
1850
|
-
const pending = await client.query<OutboxRow>(
|
|
1851
|
-
\`SELECT idempotency_key AS "idempotencyKey",
|
|
1852
|
-
event_json AS "eventJson"
|
|
1853
|
-
FROM data_lifecycle_outbox
|
|
1854
|
-
WHERE published_at IS NULL AND expires_at > now()
|
|
1855
|
-
ORDER BY created_at, idempotency_key
|
|
1856
|
-
LIMIT 10
|
|
1857
|
-
FOR UPDATE SKIP LOCKED\`,
|
|
1858
|
-
);
|
|
1859
|
-
if (pending.rows.length === 0) {
|
|
1860
|
-
emitOutboxBacklog(dependencies.metrics, 0);
|
|
1861
|
-
await client.query('COMMIT');
|
|
1862
|
-
dependencies.logger.info('Outbox relay converged', {
|
|
1863
|
-
attributes: { processedPerformance },
|
|
1864
|
-
context: { correlationId },
|
|
1865
|
-
outcome: 'success',
|
|
1866
|
-
});
|
|
1867
|
-
return {
|
|
1868
|
-
failed: 0,
|
|
1869
|
-
outcome: 'converged',
|
|
1870
|
-
processedPerformance,
|
|
1871
|
-
published: 0,
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
const result = await dependencies.publish(
|
|
1875
|
-
pending.rows.map(({ eventJson }) => eventJson),
|
|
1876
|
-
);
|
|
1877
|
-
let failed = 0;
|
|
1878
|
-
let published = 0;
|
|
1879
|
-
for (const [index, row] of pending.rows.entries()) {
|
|
1880
|
-
const entry = result.Entries?.[index];
|
|
1881
|
-
if (!entry?.EventId || entry.ErrorCode) {
|
|
1882
|
-
failed += 1;
|
|
1883
|
-
await client.query(
|
|
1884
|
-
\`UPDATE data_lifecycle_outbox
|
|
1885
|
-
SET attempts = attempts + 1,
|
|
1886
|
-
last_error_code = $2
|
|
1887
|
-
WHERE idempotency_key = $1\`,
|
|
1888
|
-
[row.idempotencyKey, entry?.ErrorCode ?? 'event-publication-failed'],
|
|
1889
|
-
);
|
|
1890
|
-
continue;
|
|
1891
|
-
}
|
|
1892
|
-
published += 1;
|
|
1893
|
-
await client.query(
|
|
1894
|
-
\`UPDATE data_lifecycle_outbox
|
|
1895
|
-
SET attempts = attempts + 1, last_error_code = NULL,
|
|
1896
|
-
published_at = now()
|
|
1897
|
-
WHERE idempotency_key = $1 AND published_at IS NULL\`,
|
|
1898
|
-
[row.idempotencyKey],
|
|
1899
|
-
);
|
|
1900
|
-
}
|
|
1901
|
-
const remaining = await client.query<{ count: string }>(
|
|
1902
|
-
\`SELECT count(*)::text AS count
|
|
1903
|
-
FROM data_lifecycle_outbox
|
|
1904
|
-
WHERE published_at IS NULL AND expires_at > now()\`,
|
|
1905
|
-
);
|
|
1906
|
-
emitOutboxBacklog(
|
|
1907
|
-
dependencies.metrics,
|
|
1908
|
-
Number(remaining.rows[0]?.count ?? 0),
|
|
1909
|
-
);
|
|
1910
|
-
await client.query('COMMIT');
|
|
1911
|
-
dependencies.logger.info('Outbox relay batch completed', {
|
|
1912
|
-
attributes: { failed, processedPerformance, published },
|
|
1913
|
-
context: { correlationId },
|
|
1914
|
-
outcome: failed === 0 ? 'success' : 'failure',
|
|
1915
|
-
});
|
|
1916
|
-
return {
|
|
1917
|
-
failed,
|
|
1918
|
-
outcome: failed === 0 ? 'converged' : 'retry-required',
|
|
1919
|
-
processedPerformance,
|
|
1920
|
-
published,
|
|
1921
|
-
};
|
|
1922
|
-
} catch (error) {
|
|
1923
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
1924
|
-
dependencies.logger.error('Outbox relay batch failed', {
|
|
1925
|
-
context: { correlationId },
|
|
1926
|
-
error,
|
|
1927
|
-
outcome: 'failure',
|
|
1928
|
-
});
|
|
1929
|
-
throw error;
|
|
1930
|
-
}
|
|
1931
|
-
},
|
|
1932
|
-
);
|
|
1933
|
-
};
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
const logger = createLogger({
|
|
1937
|
-
component: 'outbox-relay',
|
|
1938
|
-
feature: 'async-work',
|
|
1939
|
-
operation: 'outbox-relay',
|
|
1940
|
-
slice: '${name}',
|
|
1941
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1942
|
-
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
1943
|
-
});
|
|
1944
|
-
|
|
1945
|
-
export const handler = createOutboxRelayHandler({
|
|
1946
|
-
logger,
|
|
1947
|
-
metrics: createMetricEmitter(),
|
|
1948
|
-
publish: (Entries) => eventBridge.send(new PutEventsCommand({ Entries })),
|
|
1949
|
-
withDatabase: withPrimaryDatabase,
|
|
1950
|
-
});
|
|
1951
|
-
`,
|
|
1952
|
-
'async/inbox-cleanup.ts': `import { randomUUID } from 'node:crypto';
|
|
1953
|
-
import { createLogger, type Logger } from '@empire-builder-kit/runtime/logging';
|
|
1954
|
-
import {
|
|
1955
|
-
createMetricEmitter,
|
|
1956
|
-
type MetricEmitter,
|
|
1957
|
-
} from '@empire-builder-kit/runtime/observability';
|
|
1958
|
-
import type { Client as PgClient } from 'pg';
|
|
1959
|
-
import { withPrimaryDatabase } from '../database/runtime-client.js';
|
|
1960
|
-
|
|
1961
|
-
const CLEANUP_BATCH_SIZE = 1_000;
|
|
1962
|
-
const CLEANUP_MAXIMUM_ROWS = 10_000;
|
|
1963
|
-
const CLEANUP_MAXIMUM_RUNTIME_MILLISECONDS = 4 * 60_000;
|
|
1964
|
-
const PERFORMANCE_CLEANUP_BATCH_SIZE = 500;
|
|
1965
|
-
const PERFORMANCE_CLEANUP_MAXIMUM_ROWS = 5_000;
|
|
1966
|
-
const PERFORMANCE_CLEANUP_DEADLINE_HOURS = 24;
|
|
1967
|
-
const PERFORMANCE_CLEANUP_FEATURE = 'performance-load';
|
|
1968
|
-
const PERFORMANCE_CLEANUP_MAXIMUM_PENDING_REPORTS = 10_000;
|
|
1969
|
-
const PERFORMANCE_AUDIT_RETENTION_DAYS = 30;
|
|
1970
|
-
|
|
1971
|
-
interface InboxCleanupDependencies {
|
|
1972
|
-
logger: Logger;
|
|
1973
|
-
metrics: MetricEmitter;
|
|
1974
|
-
now(): number;
|
|
1975
|
-
withDatabase: typeof withPrimaryDatabase;
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
function emitInboxCleanupBacklog(metrics: MetricEmitter, value: number) {
|
|
1979
|
-
metrics.emit({
|
|
1980
|
-
dimensions: {
|
|
1981
|
-
component: 'inbox-cleanup',
|
|
1982
|
-
feature: 'async-work',
|
|
1983
|
-
operation: 'inbox-cleanup',
|
|
1984
|
-
slice: '${name}',
|
|
1985
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1986
|
-
},
|
|
1987
|
-
metricName: 'InboxCleanupBacklog',
|
|
1988
|
-
namespace: 'EBK/Async',
|
|
1989
|
-
unit: 'Count',
|
|
1990
|
-
value,
|
|
1991
|
-
});
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
function emitPerformanceCleanupBacklog(
|
|
1995
|
-
metrics: MetricEmitter,
|
|
1996
|
-
value: number,
|
|
1997
|
-
) {
|
|
1998
|
-
metrics.emit({
|
|
1999
|
-
dimensions: {
|
|
2000
|
-
component: 'performance-load-cleanup',
|
|
2001
|
-
feature: 'performance',
|
|
2002
|
-
operation: 'expiry-cleanup',
|
|
2003
|
-
slice: '${name}',
|
|
2004
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
2005
|
-
},
|
|
2006
|
-
metricName: 'PerformanceLoadCleanupBacklog',
|
|
2007
|
-
namespace: 'EBK/Performance',
|
|
2008
|
-
unit: 'Count',
|
|
2009
|
-
value,
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
function emitPerformanceCleanupDeadlineBreaches(
|
|
2014
|
-
metrics: MetricEmitter,
|
|
2015
|
-
value: number,
|
|
2016
|
-
) {
|
|
2017
|
-
return metrics.emit({
|
|
2018
|
-
dimensions: {
|
|
2019
|
-
component: 'performance-load-cleanup',
|
|
2020
|
-
feature: 'performance',
|
|
2021
|
-
operation: 'expiry-cleanup',
|
|
2022
|
-
slice: '${name}',
|
|
2023
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
2024
|
-
},
|
|
2025
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
2026
|
-
namespace: 'EBK/Performance',
|
|
2027
|
-
unit: 'Count',
|
|
2028
|
-
value,
|
|
2029
|
-
});
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
async function reportPerformanceCleanupDeadlineBreaches(
|
|
2033
|
-
client: PgClient,
|
|
2034
|
-
metrics: MetricEmitter,
|
|
2035
|
-
): Promise<{ continuationRequired: boolean; reported: number }> {
|
|
2036
|
-
await client.query('BEGIN');
|
|
2037
|
-
try {
|
|
2038
|
-
const pending = await client.query<{ resourceRef: string }>(
|
|
2039
|
-
\`SELECT resource_ref AS "resourceRef"
|
|
2040
|
-
FROM cleanup_deadline_reports
|
|
2041
|
-
WHERE feature = $1
|
|
2042
|
-
ORDER BY observed_at, resource_ref
|
|
2043
|
-
LIMIT $2
|
|
2044
|
-
FOR UPDATE\`,
|
|
2045
|
-
[PERFORMANCE_CLEANUP_FEATURE, PERFORMANCE_CLEANUP_MAXIMUM_ROWS + 1],
|
|
2046
|
-
);
|
|
2047
|
-
const selected = pending.rows.slice(0, PERFORMANCE_CLEANUP_MAXIMUM_ROWS);
|
|
2048
|
-
if (
|
|
2049
|
-
emitPerformanceCleanupDeadlineBreaches(
|
|
2050
|
-
metrics,
|
|
2051
|
-
selected.length,
|
|
2052
|
-
) === undefined
|
|
2053
|
-
) {
|
|
2054
|
-
throw new Error(
|
|
2055
|
-
'Performance cleanup deadline metric delivery failed.',
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
if (selected.length > 0) {
|
|
2059
|
-
await client.query(
|
|
2060
|
-
\`DELETE FROM cleanup_deadline_reports
|
|
2061
|
-
WHERE feature = $1 AND resource_ref = ANY($2::text[])\`,
|
|
2062
|
-
[
|
|
2063
|
-
PERFORMANCE_CLEANUP_FEATURE,
|
|
2064
|
-
selected.map(({ resourceRef }) => resourceRef),
|
|
2065
|
-
],
|
|
2066
|
-
);
|
|
2067
|
-
}
|
|
2068
|
-
await client.query('COMMIT');
|
|
2069
|
-
return {
|
|
2070
|
-
continuationRequired:
|
|
2071
|
-
pending.rows.length > PERFORMANCE_CLEANUP_MAXIMUM_ROWS,
|
|
2072
|
-
reported: selected.length,
|
|
2073
|
-
};
|
|
2074
|
-
} catch (error) {
|
|
2075
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
2076
|
-
throw error;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
async function recordPerformanceCleanupDeadlineReports(
|
|
2081
|
-
client: PgClient,
|
|
2082
|
-
resourceKind: 'async-effect' | 'record',
|
|
2083
|
-
resources: readonly { expiresAt: Date; resourceId: string }[],
|
|
2084
|
-
): Promise<void> {
|
|
2085
|
-
if (resources.length === 0) return;
|
|
2086
|
-
const recorded = await client.query<{
|
|
2087
|
-
candidateCount: string;
|
|
2088
|
-
recordedCount: string;
|
|
2089
|
-
}>(
|
|
2090
|
-
\`WITH deadline_candidates AS (
|
|
2091
|
-
SELECT
|
|
2092
|
-
$1 || ':' || resource_id AS resource_ref,
|
|
2093
|
-
expires_at + make_interval(hours => $2) AS deadline_at
|
|
2094
|
-
FROM unnest($3::text[], $4::timestamptz[])
|
|
2095
|
-
AS deleted(resource_id, expires_at)
|
|
2096
|
-
WHERE expires_at + make_interval(hours => $2) <= clock_timestamp()
|
|
2097
|
-
), recorded AS (
|
|
2098
|
-
INSERT INTO cleanup_deadline_reports
|
|
2099
|
-
(deadline_at, feature, resource_ref)
|
|
2100
|
-
SELECT deadline_at, $5, resource_ref
|
|
2101
|
-
FROM deadline_candidates
|
|
2102
|
-
ON CONFLICT (feature, resource_ref) DO UPDATE
|
|
2103
|
-
SET deadline_at = cleanup_deadline_reports.deadline_at
|
|
2104
|
-
WHERE cleanup_deadline_reports.deadline_at = EXCLUDED.deadline_at
|
|
2105
|
-
RETURNING resource_ref
|
|
2106
|
-
)
|
|
2107
|
-
SELECT
|
|
2108
|
-
(SELECT count(*)::text FROM deadline_candidates)
|
|
2109
|
-
AS "candidateCount",
|
|
2110
|
-
(SELECT count(*)::text FROM recorded) AS "recordedCount"\`,
|
|
2111
|
-
[
|
|
2112
|
-
resourceKind,
|
|
2113
|
-
PERFORMANCE_CLEANUP_DEADLINE_HOURS,
|
|
2114
|
-
resources.map(({ resourceId }) => resourceId),
|
|
2115
|
-
resources.map(({ expiresAt }) => expiresAt),
|
|
2116
|
-
PERFORMANCE_CLEANUP_FEATURE,
|
|
2117
|
-
],
|
|
2118
|
-
);
|
|
2119
|
-
if (
|
|
2120
|
-
recorded.rows[0]?.candidateCount !== recorded.rows[0]?.recordedCount
|
|
2121
|
-
) {
|
|
2122
|
-
throw new Error(
|
|
2123
|
-
'Persisted performance cleanup deadline report is inconsistent.',
|
|
2124
|
-
);
|
|
2125
|
-
}
|
|
2126
|
-
const pending = await client.query<{ count: string }>(
|
|
2127
|
-
\`SELECT count(*)::text AS count
|
|
2128
|
-
FROM (
|
|
2129
|
-
SELECT 1
|
|
2130
|
-
FROM cleanup_deadline_reports
|
|
2131
|
-
WHERE feature = $1
|
|
2132
|
-
LIMIT $2
|
|
2133
|
-
) AS bounded_pending_reports\`,
|
|
2134
|
-
[
|
|
2135
|
-
PERFORMANCE_CLEANUP_FEATURE,
|
|
2136
|
-
PERFORMANCE_CLEANUP_MAXIMUM_PENDING_REPORTS + 1,
|
|
2137
|
-
],
|
|
2138
|
-
);
|
|
2139
|
-
if (
|
|
2140
|
-
Number(pending.rows[0]?.count ?? 0) >
|
|
2141
|
-
PERFORMANCE_CLEANUP_MAXIMUM_PENDING_REPORTS
|
|
2142
|
-
) {
|
|
2143
|
-
throw new Error(
|
|
2144
|
-
'Performance cleanup pending deadline report inventory was exceeded.',
|
|
2145
|
-
);
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
export function createInboxCleanupHandler(
|
|
2150
|
-
dependencies: InboxCleanupDependencies,
|
|
2151
|
-
) {
|
|
2152
|
-
return async (event: { id?: string } = {}) => {
|
|
2153
|
-
const correlationId = event.id ?? randomUUID();
|
|
2154
|
-
const startedAt = dependencies.now();
|
|
2155
|
-
return dependencies.withDatabase(
|
|
2156
|
-
{ correlationId },
|
|
2157
|
-
'async-inbox-cleanup',
|
|
2158
|
-
async (client) => {
|
|
2159
|
-
let remainingExpired = 0;
|
|
2160
|
-
let remainingPerformanceExpired = 0;
|
|
2161
|
-
let reportedPerformanceDeadlineBreaches = 0;
|
|
2162
|
-
let reportContinuationRequired = false;
|
|
2163
|
-
let reportFailure: unknown;
|
|
2164
|
-
let removedTotal = 0;
|
|
2165
|
-
let removedPerformanceTotal = 0;
|
|
2166
|
-
try {
|
|
2167
|
-
const reportPendingBreaches = async () => {
|
|
2168
|
-
try {
|
|
2169
|
-
const result =
|
|
2170
|
-
await reportPerformanceCleanupDeadlineBreaches(
|
|
2171
|
-
client,
|
|
2172
|
-
dependencies.metrics,
|
|
2173
|
-
);
|
|
2174
|
-
reportedPerformanceDeadlineBreaches += result.reported;
|
|
2175
|
-
reportContinuationRequired ||=
|
|
2176
|
-
result.continuationRequired;
|
|
2177
|
-
} catch (error) {
|
|
2178
|
-
reportFailure ??= error;
|
|
2179
|
-
}
|
|
2180
|
-
};
|
|
2181
|
-
await reportPendingBreaches();
|
|
2182
|
-
for (;;) {
|
|
2183
|
-
await client.query('BEGIN');
|
|
2184
|
-
const inboxLimit = Math.min(
|
|
2185
|
-
CLEANUP_BATCH_SIZE,
|
|
2186
|
-
CLEANUP_MAXIMUM_ROWS - removedTotal,
|
|
2187
|
-
);
|
|
2188
|
-
const removed =
|
|
2189
|
-
inboxLimit > 0
|
|
2190
|
-
? await client.query<{ messageId: string }>(
|
|
2191
|
-
\`WITH expired AS (
|
|
2192
|
-
SELECT handler_id, message_id
|
|
2193
|
-
FROM ebk_inbox
|
|
2194
|
-
WHERE expires_at <= now()
|
|
2195
|
-
AND (
|
|
2196
|
-
status = 'succeeded'
|
|
2197
|
-
OR (
|
|
2198
|
-
status = 'failed'
|
|
2199
|
-
AND (
|
|
2200
|
-
result_reference LIKE 'terminal-permanent-%'
|
|
2201
|
-
OR result_reference LIKE 'terminal-reconciliation-%'
|
|
2202
|
-
OR result_reference IN (
|
|
2203
|
-
'configuration-disabled',
|
|
2204
|
-
'configuration-invalid'
|
|
2205
|
-
)
|
|
2206
|
-
)
|
|
2207
|
-
)
|
|
2208
|
-
)
|
|
2209
|
-
ORDER BY expires_at, handler_id, message_id
|
|
2210
|
-
LIMIT $1
|
|
2211
|
-
FOR UPDATE SKIP LOCKED
|
|
2212
|
-
)
|
|
2213
|
-
DELETE FROM ebk_inbox AS inbox
|
|
2214
|
-
USING expired
|
|
2215
|
-
WHERE inbox.handler_id = expired.handler_id
|
|
2216
|
-
AND inbox.message_id = expired.message_id
|
|
2217
|
-
RETURNING inbox.message_id AS "messageId"\`,
|
|
2218
|
-
[inboxLimit],
|
|
2219
|
-
)
|
|
2220
|
-
: { rowCount: 0, rows: [] };
|
|
2221
|
-
|
|
2222
|
-
let removedPerformanceThisPage = 0;
|
|
2223
|
-
let performanceLimit = Math.min(
|
|
2224
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2225
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2226
|
-
removedPerformanceTotal -
|
|
2227
|
-
removedPerformanceThisPage,
|
|
2228
|
-
);
|
|
2229
|
-
if (performanceLimit > 0) {
|
|
2230
|
-
const expiredEffects = await client.query<{
|
|
2231
|
-
expiresAt: Date;
|
|
2232
|
-
resourceId: string;
|
|
2233
|
-
}>(
|
|
2234
|
-
\`WITH expired AS (
|
|
2235
|
-
SELECT effect_id, expires_at
|
|
2236
|
-
FROM performance_load_async_effects
|
|
2237
|
-
WHERE expires_at <= now()
|
|
2238
|
-
ORDER BY expires_at, effect_id
|
|
2239
|
-
LIMIT $1
|
|
2240
|
-
FOR UPDATE SKIP LOCKED
|
|
2241
|
-
)
|
|
2242
|
-
DELETE FROM performance_load_async_effects AS effects
|
|
2243
|
-
USING expired
|
|
2244
|
-
WHERE effects.effect_id = expired.effect_id
|
|
2245
|
-
RETURNING effects.effect_id AS "resourceId",
|
|
2246
|
-
effects.expires_at AS "expiresAt"\`,
|
|
2247
|
-
[performanceLimit],
|
|
2248
|
-
);
|
|
2249
|
-
await recordPerformanceCleanupDeadlineReports(
|
|
2250
|
-
client,
|
|
2251
|
-
'async-effect',
|
|
2252
|
-
expiredEffects.rows,
|
|
2253
|
-
);
|
|
2254
|
-
removedPerformanceThisPage += expiredEffects.rows.length;
|
|
2255
|
-
}
|
|
2256
|
-
performanceLimit = Math.min(
|
|
2257
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2258
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2259
|
-
removedPerformanceTotal -
|
|
2260
|
-
removedPerformanceThisPage,
|
|
2261
|
-
);
|
|
2262
|
-
if (performanceLimit > 0) {
|
|
2263
|
-
const expiredRecords = await client.query<{
|
|
2264
|
-
expiresAt: Date;
|
|
2265
|
-
resourceId: string;
|
|
2266
|
-
}>(
|
|
2267
|
-
\`WITH expired AS (
|
|
2268
|
-
SELECT id, test_expires_at
|
|
2269
|
-
FROM records
|
|
2270
|
-
WHERE owner_id = 'ebk-performance'
|
|
2271
|
-
AND name = 'ebk-protected-performance-load'
|
|
2272
|
-
AND test_expires_at <= now()
|
|
2273
|
-
ORDER BY test_expires_at, id
|
|
2274
|
-
LIMIT $1
|
|
2275
|
-
FOR UPDATE SKIP LOCKED
|
|
2276
|
-
)
|
|
2277
|
-
DELETE FROM records AS records
|
|
2278
|
-
USING expired
|
|
2279
|
-
WHERE records.id = expired.id
|
|
2280
|
-
AND records.owner_id = 'ebk-performance'
|
|
2281
|
-
AND records.name = 'ebk-protected-performance-load'
|
|
2282
|
-
AND records.test_expires_at <= now()
|
|
2283
|
-
RETURNING records.id::text AS "resourceId",
|
|
2284
|
-
records.test_expires_at AS "expiresAt"\`,
|
|
2285
|
-
[performanceLimit],
|
|
2286
|
-
);
|
|
2287
|
-
await recordPerformanceCleanupDeadlineReports(
|
|
2288
|
-
client,
|
|
2289
|
-
'record',
|
|
2290
|
-
expiredRecords.rows,
|
|
2291
|
-
);
|
|
2292
|
-
removedPerformanceThisPage += expiredRecords.rows.length;
|
|
2293
|
-
}
|
|
2294
|
-
performanceLimit = Math.min(
|
|
2295
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2296
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2297
|
-
removedPerformanceTotal -
|
|
2298
|
-
removedPerformanceThisPage,
|
|
2299
|
-
);
|
|
2300
|
-
if (performanceLimit > 0) {
|
|
2301
|
-
const expiredRequests = await client.query(
|
|
2302
|
-
\`WITH expired AS (
|
|
2303
|
-
SELECT request.plan_digest, request.request_ordinal
|
|
2304
|
-
FROM performance_load_requests AS request
|
|
2305
|
-
INNER JOIN performance_load_runs AS run
|
|
2306
|
-
ON run.plan_digest = request.plan_digest
|
|
2307
|
-
WHERE run.expires_at <=
|
|
2308
|
-
now() - make_interval(days => $2)
|
|
2309
|
-
ORDER BY run.expires_at, request.plan_digest,
|
|
2310
|
-
request.request_ordinal
|
|
2311
|
-
LIMIT $1
|
|
2312
|
-
FOR UPDATE OF request SKIP LOCKED
|
|
2313
|
-
)
|
|
2314
|
-
DELETE FROM performance_load_requests AS request
|
|
2315
|
-
USING expired
|
|
2316
|
-
WHERE request.plan_digest = expired.plan_digest
|
|
2317
|
-
AND request.request_ordinal = expired.request_ordinal\`,
|
|
2318
|
-
[performanceLimit, PERFORMANCE_AUDIT_RETENTION_DAYS],
|
|
2319
|
-
);
|
|
2320
|
-
removedPerformanceThisPage += expiredRequests.rowCount ?? 0;
|
|
2321
|
-
}
|
|
2322
|
-
performanceLimit = Math.min(
|
|
2323
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2324
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2325
|
-
removedPerformanceTotal -
|
|
2326
|
-
removedPerformanceThisPage,
|
|
2327
|
-
);
|
|
2328
|
-
if (performanceLimit > 0) {
|
|
2329
|
-
const expiredRuns = await client.query(
|
|
2330
|
-
\`WITH expired AS (
|
|
2331
|
-
SELECT run.plan_digest
|
|
2332
|
-
FROM performance_load_runs AS run
|
|
2333
|
-
WHERE run.expires_at <=
|
|
2334
|
-
now() - make_interval(days => $2)
|
|
2335
|
-
AND NOT EXISTS (
|
|
2336
|
-
SELECT 1
|
|
2337
|
-
FROM performance_load_requests AS request
|
|
2338
|
-
WHERE request.plan_digest = run.plan_digest
|
|
2339
|
-
)
|
|
2340
|
-
AND NOT EXISTS (
|
|
2341
|
-
SELECT 1
|
|
2342
|
-
FROM performance_load_async_effects AS effects
|
|
2343
|
-
WHERE effects.plan_digest = run.plan_digest
|
|
2344
|
-
)
|
|
2345
|
-
AND NOT EXISTS (
|
|
2346
|
-
SELECT 1
|
|
2347
|
-
FROM records AS performance_record
|
|
2348
|
-
WHERE performance_record.owner_id = 'ebk-performance'
|
|
2349
|
-
AND performance_record.name =
|
|
2350
|
-
'ebk-protected-performance-load'
|
|
2351
|
-
AND performance_record.run_scope = run.plan_digest
|
|
2352
|
-
)
|
|
2353
|
-
ORDER BY run.expires_at, run.plan_digest
|
|
2354
|
-
LIMIT $1
|
|
2355
|
-
FOR UPDATE OF run SKIP LOCKED
|
|
2356
|
-
)
|
|
2357
|
-
DELETE FROM performance_load_runs AS run
|
|
2358
|
-
USING expired
|
|
2359
|
-
WHERE run.plan_digest = expired.plan_digest\`,
|
|
2360
|
-
[performanceLimit, PERFORMANCE_AUDIT_RETENTION_DAYS],
|
|
2361
|
-
);
|
|
2362
|
-
removedPerformanceThisPage += expiredRuns.rowCount ?? 0;
|
|
2363
|
-
}
|
|
2364
|
-
const remaining = await client.query<{ count: string }>(
|
|
2365
|
-
\`SELECT count(*)::text AS count
|
|
2366
|
-
FROM ebk_inbox
|
|
2367
|
-
WHERE expires_at <= now()
|
|
2368
|
-
AND (
|
|
2369
|
-
status = 'succeeded'
|
|
2370
|
-
OR (
|
|
2371
|
-
status = 'failed'
|
|
2372
|
-
AND (
|
|
2373
|
-
result_reference LIKE 'terminal-permanent-%'
|
|
2374
|
-
OR result_reference LIKE 'terminal-reconciliation-%'
|
|
2375
|
-
OR result_reference IN (
|
|
2376
|
-
'configuration-disabled',
|
|
2377
|
-
'configuration-invalid'
|
|
2378
|
-
)
|
|
2379
|
-
)
|
|
2380
|
-
)
|
|
2381
|
-
)\`,
|
|
2382
|
-
);
|
|
2383
|
-
const remainingPerformance = await client.query<{
|
|
2384
|
-
count: string;
|
|
2385
|
-
}>(
|
|
2386
|
-
\`SELECT count(*)::text AS count
|
|
2387
|
-
FROM (
|
|
2388
|
-
SELECT 1
|
|
2389
|
-
FROM performance_load_async_effects
|
|
2390
|
-
WHERE expires_at <= now()
|
|
2391
|
-
UNION ALL
|
|
2392
|
-
SELECT 1
|
|
2393
|
-
FROM records
|
|
2394
|
-
WHERE owner_id = 'ebk-performance'
|
|
2395
|
-
AND name = 'ebk-protected-performance-load'
|
|
2396
|
-
AND test_expires_at <= now()
|
|
2397
|
-
UNION ALL
|
|
2398
|
-
SELECT 1
|
|
2399
|
-
FROM performance_load_requests AS request
|
|
2400
|
-
INNER JOIN performance_load_runs AS run
|
|
2401
|
-
ON run.plan_digest = request.plan_digest
|
|
2402
|
-
WHERE run.expires_at <=
|
|
2403
|
-
now() - make_interval(days => $1)
|
|
2404
|
-
UNION ALL
|
|
2405
|
-
SELECT 1
|
|
2406
|
-
FROM performance_load_runs AS run
|
|
2407
|
-
WHERE run.expires_at <=
|
|
2408
|
-
now() - make_interval(days => $1)
|
|
2409
|
-
AND NOT EXISTS (
|
|
2410
|
-
SELECT 1
|
|
2411
|
-
FROM performance_load_requests AS request
|
|
2412
|
-
WHERE request.plan_digest = run.plan_digest
|
|
2413
|
-
)
|
|
2414
|
-
AND NOT EXISTS (
|
|
2415
|
-
SELECT 1
|
|
2416
|
-
FROM performance_load_async_effects AS effects
|
|
2417
|
-
WHERE effects.plan_digest = run.plan_digest
|
|
2418
|
-
)
|
|
2419
|
-
AND NOT EXISTS (
|
|
2420
|
-
SELECT 1
|
|
2421
|
-
FROM records AS performance_record
|
|
2422
|
-
WHERE performance_record.owner_id = 'ebk-performance'
|
|
2423
|
-
AND performance_record.name =
|
|
2424
|
-
'ebk-protected-performance-load'
|
|
2425
|
-
AND performance_record.run_scope = run.plan_digest
|
|
2426
|
-
)
|
|
2427
|
-
LIMIT $2
|
|
2428
|
-
) AS bounded_backlog\`,
|
|
2429
|
-
[
|
|
2430
|
-
PERFORMANCE_AUDIT_RETENTION_DAYS,
|
|
2431
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS + 1,
|
|
2432
|
-
],
|
|
2433
|
-
);
|
|
2434
|
-
await client.query('COMMIT');
|
|
2435
|
-
const removedThisBatch = removed.rowCount ?? 0;
|
|
2436
|
-
removedTotal += removedThisBatch;
|
|
2437
|
-
removedPerformanceTotal += removedPerformanceThisPage;
|
|
2438
|
-
remainingExpired = Number(remaining.rows[0]?.count ?? 0);
|
|
2439
|
-
remainingPerformanceExpired = Number(
|
|
2440
|
-
remainingPerformance.rows[0]?.count ?? 0,
|
|
2441
|
-
);
|
|
2442
|
-
const inboxCanContinue =
|
|
2443
|
-
remainingExpired > 0 &&
|
|
2444
|
-
removedTotal < CLEANUP_MAXIMUM_ROWS;
|
|
2445
|
-
const performanceCanContinue =
|
|
2446
|
-
remainingPerformanceExpired > 0 &&
|
|
2447
|
-
removedPerformanceTotal <
|
|
2448
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS;
|
|
2449
|
-
if (
|
|
2450
|
-
(!inboxCanContinue && !performanceCanContinue) ||
|
|
2451
|
-
removedThisBatch + removedPerformanceThisPage === 0 ||
|
|
2452
|
-
dependencies.now() - startedAt >=
|
|
2453
|
-
CLEANUP_MAXIMUM_RUNTIME_MILLISECONDS
|
|
2454
|
-
) {
|
|
2455
|
-
break;
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
emitInboxCleanupBacklog(dependencies.metrics, remainingExpired);
|
|
2459
|
-
emitPerformanceCleanupBacklog(
|
|
2460
|
-
dependencies.metrics,
|
|
2461
|
-
remainingPerformanceExpired,
|
|
2462
|
-
);
|
|
2463
|
-
await reportPendingBreaches();
|
|
2464
|
-
if (reportFailure !== undefined) {
|
|
2465
|
-
throw reportFailure;
|
|
2466
|
-
}
|
|
2467
|
-
if (
|
|
2468
|
-
reportedPerformanceDeadlineBreaches > 0 ||
|
|
2469
|
-
reportContinuationRequired
|
|
2470
|
-
) {
|
|
2471
|
-
throw new Error('performance-load-cleanup-deadline-breached');
|
|
2472
|
-
}
|
|
2473
|
-
dependencies.logger.info('Async inbox cleanup completed', {
|
|
2474
|
-
attributes: {
|
|
2475
|
-
batchLimit: CLEANUP_BATCH_SIZE,
|
|
2476
|
-
invocationLimit: CLEANUP_MAXIMUM_ROWS,
|
|
2477
|
-
performanceAuditRetentionDays:
|
|
2478
|
-
PERFORMANCE_AUDIT_RETENTION_DAYS,
|
|
2479
|
-
performanceBatchLimit: PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2480
|
-
performanceCleanupDeadlineHours:
|
|
2481
|
-
PERFORMANCE_CLEANUP_DEADLINE_HOURS,
|
|
2482
|
-
performanceInvocationLimit:
|
|
2483
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS,
|
|
2484
|
-
reportedPerformanceDeadlineBreaches,
|
|
2485
|
-
remainingPerformanceExpired,
|
|
2486
|
-
remainingExpired,
|
|
2487
|
-
removedPerformance: removedPerformanceTotal,
|
|
2488
|
-
removed: removedTotal,
|
|
2489
|
-
},
|
|
2490
|
-
context: { correlationId },
|
|
2491
|
-
outcome:
|
|
2492
|
-
remainingExpired === 0 &&
|
|
2493
|
-
remainingPerformanceExpired === 0
|
|
2494
|
-
? 'success'
|
|
2495
|
-
: 'unknown',
|
|
2496
|
-
});
|
|
2497
|
-
return {
|
|
2498
|
-
batchLimit: CLEANUP_BATCH_SIZE,
|
|
2499
|
-
invocationLimit: CLEANUP_MAXIMUM_ROWS,
|
|
2500
|
-
performanceAuditRetentionDays:
|
|
2501
|
-
PERFORMANCE_AUDIT_RETENTION_DAYS,
|
|
2502
|
-
performanceBatchLimit: PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2503
|
-
performanceCleanupDeadlineHours:
|
|
2504
|
-
PERFORMANCE_CLEANUP_DEADLINE_HOURS,
|
|
2505
|
-
performanceInvocationLimit:
|
|
2506
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS,
|
|
2507
|
-
reportedPerformanceDeadlineBreaches,
|
|
2508
|
-
remainingPerformanceExpired,
|
|
2509
|
-
remainingExpired,
|
|
2510
|
-
removedPerformance: removedPerformanceTotal,
|
|
2511
|
-
removed: removedTotal,
|
|
2512
|
-
};
|
|
2513
|
-
} catch (error) {
|
|
2514
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
2515
|
-
dependencies.logger.error('Async inbox cleanup failed', {
|
|
2516
|
-
context: { correlationId },
|
|
2517
|
-
error,
|
|
2518
|
-
outcome: 'failure',
|
|
2519
|
-
});
|
|
2520
|
-
throw error;
|
|
2521
|
-
}
|
|
2522
|
-
},
|
|
2523
|
-
);
|
|
2524
|
-
};
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
const logger = createLogger({
|
|
2528
|
-
component: 'inbox-cleanup',
|
|
2529
|
-
feature: 'async-work',
|
|
2530
|
-
operation: 'inbox-cleanup',
|
|
2531
|
-
slice: '${name}',
|
|
2532
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
2533
|
-
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
2534
|
-
});
|
|
2535
|
-
|
|
2536
|
-
export const handler = createInboxCleanupHandler({
|
|
2537
|
-
logger,
|
|
2538
|
-
metrics: createMetricEmitter(),
|
|
2539
|
-
now: Date.now,
|
|
2540
|
-
withDatabase: withPrimaryDatabase,
|
|
2541
|
-
});
|
|
2542
1254
|
`,
|
|
2543
1255
|
'async/workflow-task.ts': `import {
|
|
2544
1256
|
DeleteItemCommand,
|
|
@@ -3298,6 +2010,7 @@ describe('generated async failure fixture', () => {
|
|
|
3298
2010
|
it,
|
|
3299
2011
|
vi,
|
|
3300
2012
|
} from 'vitest';
|
|
2013
|
+
${"import { DynamoDBClient, GetItemCommand, TransactWriteItemsCommand, type AttributeValue } from '@aws-sdk/client-dynamodb';\nimport { createDynamoPersistence } from '@empire-builder-kit/runtime/key-value';\nimport { markProviderEffectStarted } from '../async/provider-inbox.js';\n"}\
|
|
3301
2014
|
import { createInboxCleanupHandler } from '../async/inbox-cleanup.js';
|
|
3302
2015
|
import { createOutboxRelayHandler } from '../async/outbox-relay.js';
|
|
3303
2016
|
import {
|
|
@@ -3335,175 +2048,7 @@ afterEach(() => {
|
|
|
3335
2048
|
});
|
|
3336
2049
|
|
|
3337
2050
|
describe('generated async handlers', () => {
|
|
3338
|
-
|
|
3339
|
-
const delivery = createQueuedProviderDelivery({
|
|
3340
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3341
|
-
operationId: 'operation-one',
|
|
3342
|
-
request: { value: true },
|
|
3343
|
-
});
|
|
3344
|
-
const deliver = vi.fn();
|
|
3345
|
-
const client = {
|
|
3346
|
-
async query(statement: string) {
|
|
3347
|
-
if (statement === 'BEGIN' || statement === 'COMMIT') {
|
|
3348
|
-
return { rowCount: null, rows: [] };
|
|
3349
|
-
}
|
|
3350
|
-
if (statement.includes('INSERT INTO ebk_inbox')) {
|
|
3351
|
-
return { rowCount: 0, rows: [] };
|
|
3352
|
-
}
|
|
3353
|
-
if (statement.includes('SELECT attempts')) {
|
|
3354
|
-
return {
|
|
3355
|
-
rowCount: 1,
|
|
3356
|
-
rows: [{
|
|
3357
|
-
attempts: 1,
|
|
3358
|
-
lastAttemptAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
3359
|
-
payloadDigest: delivery.requestDigest,
|
|
3360
|
-
resultReference: 'effect-started-prior-attempt',
|
|
3361
|
-
status: 'claimed',
|
|
3362
|
-
}],
|
|
3363
|
-
};
|
|
3364
|
-
}
|
|
3365
|
-
if (statement.includes("status = 'failed'")) {
|
|
3366
|
-
return { rowCount: 1, rows: [] };
|
|
3367
|
-
}
|
|
3368
|
-
throw new Error('Unexpected provider query: ' + statement);
|
|
3369
|
-
},
|
|
3370
|
-
};
|
|
3371
|
-
const handler = createProviderWorkerHandler({
|
|
3372
|
-
createAttemptToken: () => 'new-attempt',
|
|
3373
|
-
deliver: deliver as never,
|
|
3374
|
-
evaluateRollout: vi.fn() as never,
|
|
3375
|
-
logger: logger as never,
|
|
3376
|
-
now: () => '2026-01-01T00:03:00.000Z',
|
|
3377
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
3378
|
-
work(client as never)) as never,
|
|
3379
|
-
});
|
|
3380
|
-
|
|
3381
|
-
const response = await handler({
|
|
3382
|
-
Records: [{
|
|
3383
|
-
body: JSON.stringify(delivery),
|
|
3384
|
-
messageId: 'queue-message-one',
|
|
3385
|
-
}],
|
|
3386
|
-
});
|
|
3387
|
-
|
|
3388
|
-
expect(response.batchItemFailures).toEqual([
|
|
3389
|
-
{ itemIdentifier: 'queue-message-one' },
|
|
3390
|
-
]);
|
|
3391
|
-
expect(deliver).not.toHaveBeenCalled();
|
|
3392
|
-
});
|
|
3393
|
-
|
|
3394
|
-
it('rolls back when an unproven terminal reconciliation transition is lost', async () => {
|
|
3395
|
-
const delivery = createQueuedProviderDelivery({
|
|
3396
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3397
|
-
operationId: 'operation-transition-lost',
|
|
3398
|
-
request: { value: true },
|
|
3399
|
-
});
|
|
3400
|
-
const statements: string[] = [];
|
|
3401
|
-
const client = {
|
|
3402
|
-
async query(statement: string) {
|
|
3403
|
-
statements.push(statement);
|
|
3404
|
-
if (statement === 'BEGIN' || statement === 'ROLLBACK') {
|
|
3405
|
-
return { rowCount: null, rows: [] };
|
|
3406
|
-
}
|
|
3407
|
-
if (statement.includes('INSERT INTO ebk_inbox')) {
|
|
3408
|
-
return { rowCount: 0, rows: [] };
|
|
3409
|
-
}
|
|
3410
|
-
if (statement.includes('SELECT attempts')) {
|
|
3411
|
-
return {
|
|
3412
|
-
rowCount: 1,
|
|
3413
|
-
rows: [{
|
|
3414
|
-
attempts: 1,
|
|
3415
|
-
lastAttemptAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
3416
|
-
payloadDigest: delivery.requestDigest,
|
|
3417
|
-
resultReference: 'effect-started-prior-attempt',
|
|
3418
|
-
status: 'claimed',
|
|
3419
|
-
}],
|
|
3420
|
-
};
|
|
3421
|
-
}
|
|
3422
|
-
if (statement.includes("status = 'failed'")) {
|
|
3423
|
-
return { rowCount: 0, rows: [] };
|
|
3424
|
-
}
|
|
3425
|
-
throw new Error('Unexpected provider query: ' + statement);
|
|
3426
|
-
},
|
|
3427
|
-
};
|
|
3428
|
-
|
|
3429
|
-
await expect(
|
|
3430
|
-
claimProviderDelivery(
|
|
3431
|
-
client as never,
|
|
3432
|
-
delivery,
|
|
3433
|
-
'2026-01-01T00:03:00.000Z',
|
|
3434
|
-
),
|
|
3435
|
-
).rejects.toThrow('provider-reconciliation-transition-lost');
|
|
3436
|
-
expect(statements).toContain('ROLLBACK');
|
|
3437
|
-
expect(statements).not.toContain('COMMIT');
|
|
3438
|
-
});
|
|
3439
|
-
|
|
3440
|
-
it('retries a stale effect only when provider repeat safety is guaranteed', async () => {
|
|
3441
|
-
const unproven = createQueuedProviderDelivery({
|
|
3442
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3443
|
-
operationId: 'operation-guaranteed',
|
|
3444
|
-
request: { value: true },
|
|
3445
|
-
});
|
|
3446
|
-
const delivery = {
|
|
3447
|
-
...unproven,
|
|
3448
|
-
contract: {
|
|
3449
|
-
...unproven.contract,
|
|
3450
|
-
idempotencyRepeatSafety: 'provider-guaranteed' as const,
|
|
3451
|
-
},
|
|
3452
|
-
};
|
|
3453
|
-
const client = {
|
|
3454
|
-
async query(statement: string) {
|
|
3455
|
-
if (statement === 'BEGIN' || statement === 'COMMIT') {
|
|
3456
|
-
return { rowCount: null, rows: [] };
|
|
3457
|
-
}
|
|
3458
|
-
if (statement.includes('INSERT INTO ebk_inbox')) {
|
|
3459
|
-
return { rowCount: 0, rows: [] };
|
|
3460
|
-
}
|
|
3461
|
-
if (statement.includes('SELECT attempts')) {
|
|
3462
|
-
return {
|
|
3463
|
-
rowCount: 1,
|
|
3464
|
-
rows: [{
|
|
3465
|
-
attempts: 1,
|
|
3466
|
-
lastAttemptAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
3467
|
-
payloadDigest: delivery.requestDigest,
|
|
3468
|
-
resultReference: 'effect-started-prior-attempt',
|
|
3469
|
-
status: 'claimed',
|
|
3470
|
-
}],
|
|
3471
|
-
};
|
|
3472
|
-
}
|
|
3473
|
-
if (statement.includes('UPDATE ebk_inbox')) {
|
|
3474
|
-
return { rowCount: 1, rows: [] };
|
|
3475
|
-
}
|
|
3476
|
-
throw new Error('Unexpected provider query: ' + statement);
|
|
3477
|
-
},
|
|
3478
|
-
};
|
|
3479
|
-
const deliver = vi.fn().mockResolvedValue({
|
|
3480
|
-
attempts: 1,
|
|
3481
|
-
deliveryId: delivery.deliveryId,
|
|
3482
|
-
outcome: 'confirmed',
|
|
3483
|
-
providerReference: 'provider-reference',
|
|
3484
|
-
statusCode: 202,
|
|
3485
|
-
});
|
|
3486
|
-
const handler = createProviderWorkerHandler({
|
|
3487
|
-
createAttemptToken: () => 'guaranteed-attempt',
|
|
3488
|
-
deliver: deliver as never,
|
|
3489
|
-
evaluateRollout: vi.fn().mockResolvedValue({ assigned: true }) as never,
|
|
3490
|
-
logger: logger as never,
|
|
3491
|
-
now: () => '2026-01-01T00:03:00.000Z',
|
|
3492
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
3493
|
-
work(client as never)) as never,
|
|
3494
|
-
});
|
|
3495
|
-
|
|
3496
|
-
await expect(
|
|
3497
|
-
handler({
|
|
3498
|
-
Records: [{
|
|
3499
|
-
body: JSON.stringify(delivery),
|
|
3500
|
-
messageId: 'queue-message-guaranteed',
|
|
3501
|
-
}],
|
|
3502
|
-
}),
|
|
3503
|
-
).resolves.toEqual({ batchItemFailures: [] });
|
|
3504
|
-
expect(deliver).toHaveBeenCalledTimes(1);
|
|
3505
|
-
});
|
|
3506
|
-
|
|
2051
|
+
${(0, built_in_plan_key_value_provider_js_1.renderKeyValueProviderTestCases)(name)}
|
|
3507
2052
|
it('treats a retry after a workflow effect marker as compensation-required', async () => {
|
|
3508
2053
|
const runStep = vi.fn();
|
|
3509
2054
|
const transition = vi.fn();
|
|
@@ -4093,446 +2638,8 @@ describe('generated async handlers', () => {
|
|
|
4093
2638
|
);
|
|
4094
2639
|
});
|
|
4095
2640
|
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
const client = {
|
|
4099
|
-
async query(statement: string) {
|
|
4100
|
-
statements.push(statement);
|
|
4101
|
-
if (statement.includes('FROM data_lifecycle_outbox') &&
|
|
4102
|
-
statement.includes('FOR UPDATE')) {
|
|
4103
|
-
return {
|
|
4104
|
-
rows: [{
|
|
4105
|
-
eventJson: { Detail: '{}', DetailType: 'Fixture' },
|
|
4106
|
-
idempotencyKey: 'outbox-one',
|
|
4107
|
-
}],
|
|
4108
|
-
};
|
|
4109
|
-
}
|
|
4110
|
-
if (statement.includes('count(*)')) {
|
|
4111
|
-
return { rows: [{ count: '0' }] };
|
|
4112
|
-
}
|
|
4113
|
-
return { rowCount: 1, rows: [] };
|
|
4114
|
-
},
|
|
4115
|
-
};
|
|
4116
|
-
const publish = vi.fn().mockResolvedValue({
|
|
4117
|
-
Entries: [{ EventId: 'event-one' }],
|
|
4118
|
-
});
|
|
4119
|
-
const handler = createOutboxRelayHandler({
|
|
4120
|
-
logger: logger as never,
|
|
4121
|
-
metrics: { emit: vi.fn() },
|
|
4122
|
-
publish: publish as never,
|
|
4123
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4124
|
-
work(client as never)) as never,
|
|
4125
|
-
});
|
|
4126
|
-
|
|
4127
|
-
await expect(handler({ id: 'correlation-one' })).resolves.toMatchObject({
|
|
4128
|
-
failed: 0,
|
|
4129
|
-
outcome: 'converged',
|
|
4130
|
-
published: 1,
|
|
4131
|
-
});
|
|
4132
|
-
expect(publish).toHaveBeenCalledTimes(1);
|
|
4133
|
-
expect(statements.some((value) => value.includes('published_at = now()')))
|
|
4134
|
-
.toBe(true);
|
|
4135
|
-
});
|
|
4136
|
-
|
|
4137
|
-
it('processes protected performance work through an isolated sink', async () => {
|
|
4138
|
-
const client = {
|
|
4139
|
-
async query(statement: string) {
|
|
4140
|
-
if (statement.includes('UPDATE performance_load_async_effects')) {
|
|
4141
|
-
return { rows: [{ effectId: 'effect-one' }] };
|
|
4142
|
-
}
|
|
4143
|
-
if (
|
|
4144
|
-
statement.includes('FROM data_lifecycle_outbox') &&
|
|
4145
|
-
statement.includes('FOR UPDATE')
|
|
4146
|
-
) {
|
|
4147
|
-
return { rows: [] };
|
|
4148
|
-
}
|
|
4149
|
-
return { rowCount: 1, rows: [] };
|
|
4150
|
-
},
|
|
4151
|
-
};
|
|
4152
|
-
const publish = vi.fn();
|
|
4153
|
-
const handler = createOutboxRelayHandler({
|
|
4154
|
-
logger: logger as never,
|
|
4155
|
-
metrics: { emit: vi.fn() },
|
|
4156
|
-
publish: publish as never,
|
|
4157
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4158
|
-
work(client as never)) as never,
|
|
4159
|
-
});
|
|
4160
|
-
|
|
4161
|
-
await expect(handler({ id: 'performance-one' })).resolves.toMatchObject({
|
|
4162
|
-
failed: 0,
|
|
4163
|
-
outcome: 'converged',
|
|
4164
|
-
processedPerformance: 1,
|
|
4165
|
-
published: 0,
|
|
4166
|
-
});
|
|
4167
|
-
expect(publish).not.toHaveBeenCalled();
|
|
4168
|
-
});
|
|
4169
|
-
|
|
4170
|
-
it('drains terminal inbox rows in bounded pages and never selects active claims', async () => {
|
|
4171
|
-
const metrics = { emit: vi.fn(() => ({} as never)) };
|
|
4172
|
-
const deleteCounts = [1_000, 500];
|
|
4173
|
-
const remainingCounts = ['500', '0'];
|
|
4174
|
-
const performanceDeleteCounts = new Map([
|
|
4175
|
-
['performance_load_async_effects', [1, 0]],
|
|
4176
|
-
['records', [1, 0]],
|
|
4177
|
-
['performance_load_requests', [1, 0]],
|
|
4178
|
-
['performance_load_runs', [1, 0]],
|
|
4179
|
-
]);
|
|
4180
|
-
const performanceStatements: string[] = [];
|
|
4181
|
-
const client = {
|
|
4182
|
-
async query(statement: string, parameters?: readonly unknown[]) {
|
|
4183
|
-
if (
|
|
4184
|
-
statement === 'BEGIN' ||
|
|
4185
|
-
statement === 'COMMIT' ||
|
|
4186
|
-
statement === 'ROLLBACK'
|
|
4187
|
-
) {
|
|
4188
|
-
return { rowCount: null, rows: [] };
|
|
4189
|
-
}
|
|
4190
|
-
if (
|
|
4191
|
-
statement.includes('SELECT resource_ref AS "resourceRef"') &&
|
|
4192
|
-
statement.includes('FROM cleanup_deadline_reports')
|
|
4193
|
-
) {
|
|
4194
|
-
return { rows: [] };
|
|
4195
|
-
}
|
|
4196
|
-
if (statement.includes('DELETE FROM cleanup_deadline_reports')) {
|
|
4197
|
-
return { rowCount: 0, rows: [] };
|
|
4198
|
-
}
|
|
4199
|
-
if (statement.includes('WITH deadline_candidates AS')) {
|
|
4200
|
-
expect(statement).toContain('clock_timestamp()');
|
|
4201
|
-
expect(statement).toContain(
|
|
4202
|
-
'ON CONFLICT (feature, resource_ref) DO UPDATE',
|
|
4203
|
-
);
|
|
4204
|
-
return {
|
|
4205
|
-
rows: [{ candidateCount: '0', recordedCount: '0' }],
|
|
4206
|
-
};
|
|
4207
|
-
}
|
|
4208
|
-
if (statement.includes('bounded_pending_reports')) {
|
|
4209
|
-
return { rows: [{ count: '0' }] };
|
|
4210
|
-
}
|
|
4211
|
-
if (statement.includes('DELETE FROM ebk_inbox')) {
|
|
4212
|
-
expect(statement).toContain("status = 'succeeded'");
|
|
4213
|
-
expect(statement).toContain(
|
|
4214
|
-
"result_reference LIKE 'terminal-permanent-%'",
|
|
4215
|
-
);
|
|
4216
|
-
expect(statement).toContain(
|
|
4217
|
-
"result_reference LIKE 'terminal-reconciliation-%'",
|
|
4218
|
-
);
|
|
4219
|
-
expect(statement).toContain("'configuration-disabled'");
|
|
4220
|
-
expect(statement).toContain("'configuration-invalid'");
|
|
4221
|
-
return { rowCount: deleteCounts.shift() ?? 0, rows: [] };
|
|
4222
|
-
}
|
|
4223
|
-
if (
|
|
4224
|
-
statement.includes('SELECT count(*)') &&
|
|
4225
|
-
statement.includes('FROM ebk_inbox')
|
|
4226
|
-
) {
|
|
4227
|
-
expect(statement).toContain("status = 'succeeded'");
|
|
4228
|
-
expect(statement).toContain(
|
|
4229
|
-
"result_reference LIKE 'terminal-permanent-%'",
|
|
4230
|
-
);
|
|
4231
|
-
expect(statement).toContain("'configuration-disabled'");
|
|
4232
|
-
expect(statement).toContain("'configuration-invalid'");
|
|
4233
|
-
expect(statement).not.toContain("status = 'claimed'");
|
|
4234
|
-
return { rows: [{ count: remainingCounts.shift() ?? '0' }] };
|
|
4235
|
-
}
|
|
4236
|
-
if (
|
|
4237
|
-
statement.includes('DELETE FROM performance_load_async_effects')
|
|
4238
|
-
) {
|
|
4239
|
-
performanceStatements.push(statement);
|
|
4240
|
-
expect(statement).toContain('expires_at <= now()');
|
|
4241
|
-
const count =
|
|
4242
|
-
performanceDeleteCounts
|
|
4243
|
-
.get('performance_load_async_effects')
|
|
4244
|
-
?.shift() ?? 0;
|
|
4245
|
-
return {
|
|
4246
|
-
rowCount: count,
|
|
4247
|
-
rows: Array.from({ length: count }, (_, index) => ({
|
|
4248
|
-
expiresAt: new Date('2026-08-03T00:00:00.000Z'),
|
|
4249
|
-
resourceId: 'effect-' + String(index),
|
|
4250
|
-
})),
|
|
4251
|
-
};
|
|
4252
|
-
}
|
|
4253
|
-
if (statement.includes('DELETE FROM records')) {
|
|
4254
|
-
performanceStatements.push(statement);
|
|
4255
|
-
expect(statement).toContain("owner_id = 'ebk-performance'");
|
|
4256
|
-
expect(statement).toContain(
|
|
4257
|
-
"name = 'ebk-protected-performance-load'",
|
|
4258
|
-
);
|
|
4259
|
-
expect(statement).toContain('test_expires_at <= now()');
|
|
4260
|
-
const count =
|
|
4261
|
-
performanceDeleteCounts.get('records')?.shift() ?? 0;
|
|
4262
|
-
return {
|
|
4263
|
-
rowCount: count,
|
|
4264
|
-
rows: Array.from({ length: count }, (_, index) => ({
|
|
4265
|
-
expiresAt: new Date('2026-08-03T00:00:00.000Z'),
|
|
4266
|
-
resourceId: 'record-' + String(index),
|
|
4267
|
-
})),
|
|
4268
|
-
};
|
|
4269
|
-
}
|
|
4270
|
-
if (statement.includes('DELETE FROM performance_load_requests')) {
|
|
4271
|
-
performanceStatements.push(statement);
|
|
4272
|
-
expect(statement).toContain(
|
|
4273
|
-
'now() - make_interval(days => $2)',
|
|
4274
|
-
);
|
|
4275
|
-
expect(parameters?.[1]).toBe(30);
|
|
4276
|
-
return {
|
|
4277
|
-
rowCount:
|
|
4278
|
-
performanceDeleteCounts
|
|
4279
|
-
.get('performance_load_requests')
|
|
4280
|
-
?.shift() ?? 0,
|
|
4281
|
-
rows: [],
|
|
4282
|
-
};
|
|
4283
|
-
}
|
|
4284
|
-
if (statement.includes('DELETE FROM performance_load_runs')) {
|
|
4285
|
-
performanceStatements.push(statement);
|
|
4286
|
-
expect(statement).toContain(
|
|
4287
|
-
'FROM performance_load_requests AS request',
|
|
4288
|
-
);
|
|
4289
|
-
expect(statement).toContain(
|
|
4290
|
-
'FROM performance_load_async_effects AS effects',
|
|
4291
|
-
);
|
|
4292
|
-
expect(statement).toContain('FROM records AS performance_record');
|
|
4293
|
-
expect(parameters?.[1]).toBe(30);
|
|
4294
|
-
return {
|
|
4295
|
-
rowCount:
|
|
4296
|
-
performanceDeleteCounts
|
|
4297
|
-
.get('performance_load_runs')
|
|
4298
|
-
?.shift() ?? 0,
|
|
4299
|
-
rows: [],
|
|
4300
|
-
};
|
|
4301
|
-
}
|
|
4302
|
-
if (
|
|
4303
|
-
statement.includes('AS count') &&
|
|
4304
|
-
statement.includes('performance_load_async_effects')
|
|
4305
|
-
) {
|
|
4306
|
-
expect(statement).toContain("owner_id = 'ebk-performance'");
|
|
4307
|
-
expect(statement).toContain(
|
|
4308
|
-
"name = 'ebk-protected-performance-load'",
|
|
4309
|
-
);
|
|
4310
|
-
expect(statement).toContain('FROM records AS performance_record');
|
|
4311
|
-
expect(statement).toContain('LIMIT $2');
|
|
4312
|
-
expect(parameters).toEqual([30, 5_001]);
|
|
4313
|
-
return { rows: [{ count: '0' }] };
|
|
4314
|
-
}
|
|
4315
|
-
throw new Error('Unexpected cleanup query: ' + statement);
|
|
4316
|
-
},
|
|
4317
|
-
};
|
|
4318
|
-
const handler = createInboxCleanupHandler({
|
|
4319
|
-
logger: logger as never,
|
|
4320
|
-
metrics,
|
|
4321
|
-
now: () => 0,
|
|
4322
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4323
|
-
work(client as never)) as never,
|
|
4324
|
-
});
|
|
4325
|
-
|
|
4326
|
-
await expect(handler({ id: 'cleanup-one' })).resolves.toMatchObject({
|
|
4327
|
-
performanceCleanupDeadlineHours: 24,
|
|
4328
|
-
remainingExpired: 0,
|
|
4329
|
-
reportedPerformanceDeadlineBreaches: 0,
|
|
4330
|
-
remainingPerformanceExpired: 0,
|
|
4331
|
-
removed: 1_500,
|
|
4332
|
-
removedPerformance: 4,
|
|
4333
|
-
});
|
|
4334
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4335
|
-
expect.objectContaining({
|
|
4336
|
-
metricName: 'InboxCleanupBacklog',
|
|
4337
|
-
value: 0,
|
|
4338
|
-
}),
|
|
4339
|
-
);
|
|
4340
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4341
|
-
expect.objectContaining({
|
|
4342
|
-
metricName: 'PerformanceLoadCleanupBacklog',
|
|
4343
|
-
value: 0,
|
|
4344
|
-
}),
|
|
4345
|
-
);
|
|
4346
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4347
|
-
expect.objectContaining({
|
|
4348
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4349
|
-
value: 0,
|
|
4350
|
-
}),
|
|
4351
|
-
);
|
|
4352
|
-
expect(performanceStatements).toHaveLength(8);
|
|
4353
|
-
});
|
|
4354
|
-
|
|
4355
|
-
it('fails the scheduled invocation when protected load residue exceeds the cleanup deadline', async () => {
|
|
4356
|
-
const metrics = { emit: vi.fn(() => ({} as never)) };
|
|
4357
|
-
const pendingReports = new Set(['async-effect:effect-one']);
|
|
4358
|
-
const client = {
|
|
4359
|
-
async query(statement: string, parameters?: readonly unknown[]) {
|
|
4360
|
-
if (
|
|
4361
|
-
statement === 'BEGIN' ||
|
|
4362
|
-
statement === 'COMMIT' ||
|
|
4363
|
-
statement === 'ROLLBACK'
|
|
4364
|
-
) {
|
|
4365
|
-
return { rowCount: null, rows: [] };
|
|
4366
|
-
}
|
|
4367
|
-
if (statement.includes('SELECT resource_ref AS "resourceRef"')) {
|
|
4368
|
-
return {
|
|
4369
|
-
rows: [...pendingReports].map((resourceRef) => ({ resourceRef })),
|
|
4370
|
-
};
|
|
4371
|
-
}
|
|
4372
|
-
if (statement.includes('DELETE FROM cleanup_deadline_reports')) {
|
|
4373
|
-
for (const resourceRef of parameters?.[1] as readonly string[]) {
|
|
4374
|
-
pendingReports.delete(resourceRef);
|
|
4375
|
-
}
|
|
4376
|
-
return { rowCount: 1, rows: [] };
|
|
4377
|
-
}
|
|
4378
|
-
if (
|
|
4379
|
-
statement.includes('DELETE FROM performance_load_async_effects') ||
|
|
4380
|
-
statement.includes('DELETE FROM records')
|
|
4381
|
-
) {
|
|
4382
|
-
return { rowCount: 0, rows: [] };
|
|
4383
|
-
}
|
|
4384
|
-
if (statement.includes('DELETE FROM')) {
|
|
4385
|
-
return { rowCount: 0, rows: [] };
|
|
4386
|
-
}
|
|
4387
|
-
if (statement.includes('SELECT count(*)')) {
|
|
4388
|
-
return { rows: [{ count: '0' }] };
|
|
4389
|
-
}
|
|
4390
|
-
throw new Error('Unexpected cleanup deadline query: ' + statement);
|
|
4391
|
-
},
|
|
4392
|
-
};
|
|
4393
|
-
const handler = createInboxCleanupHandler({
|
|
4394
|
-
logger: { error: vi.fn(), info: vi.fn() } as never,
|
|
4395
|
-
metrics,
|
|
4396
|
-
now: () => 0,
|
|
4397
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4398
|
-
work(client as never)) as never,
|
|
4399
|
-
});
|
|
4400
|
-
|
|
4401
|
-
await expect(handler({ id: 'cleanup-deadline' })).rejects.toThrow(
|
|
4402
|
-
'performance-load-cleanup-deadline-breached',
|
|
4403
|
-
);
|
|
4404
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4405
|
-
expect.objectContaining({
|
|
4406
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4407
|
-
value: 1,
|
|
4408
|
-
}),
|
|
4409
|
-
);
|
|
4410
|
-
expect(pendingReports.size).toBe(0);
|
|
4411
|
-
});
|
|
4412
|
-
|
|
4413
|
-
it('retries deadline telemetry when its post-emission commit fails', async () => {
|
|
4414
|
-
const pendingReports = new Set(['record:record-one']);
|
|
4415
|
-
let failReportCommits = true;
|
|
4416
|
-
let reportTransaction = false;
|
|
4417
|
-
let snapshot = new Set<string>();
|
|
4418
|
-
const client = {
|
|
4419
|
-
async query(statement: string, parameters?: readonly unknown[]) {
|
|
4420
|
-
if (statement === 'BEGIN') {
|
|
4421
|
-
snapshot = new Set(pendingReports);
|
|
4422
|
-
reportTransaction = false;
|
|
4423
|
-
return { rowCount: null, rows: [] };
|
|
4424
|
-
}
|
|
4425
|
-
if (statement === 'COMMIT') {
|
|
4426
|
-
if (reportTransaction && failReportCommits) {
|
|
4427
|
-
throw new Error('simulated report commit crash');
|
|
4428
|
-
}
|
|
4429
|
-
return { rowCount: null, rows: [] };
|
|
4430
|
-
}
|
|
4431
|
-
if (statement === 'ROLLBACK') {
|
|
4432
|
-
pendingReports.clear();
|
|
4433
|
-
for (const resourceRef of snapshot) pendingReports.add(resourceRef);
|
|
4434
|
-
return { rowCount: null, rows: [] };
|
|
4435
|
-
}
|
|
4436
|
-
if (statement.includes('SELECT resource_ref AS "resourceRef"')) {
|
|
4437
|
-
reportTransaction = true;
|
|
4438
|
-
return {
|
|
4439
|
-
rows: [...pendingReports].map((resourceRef) => ({ resourceRef })),
|
|
4440
|
-
};
|
|
4441
|
-
}
|
|
4442
|
-
if (statement.includes('DELETE FROM cleanup_deadline_reports')) {
|
|
4443
|
-
for (const resourceRef of parameters?.[1] as readonly string[]) {
|
|
4444
|
-
pendingReports.delete(resourceRef);
|
|
4445
|
-
}
|
|
4446
|
-
return { rowCount: 1, rows: [] };
|
|
4447
|
-
}
|
|
4448
|
-
if (
|
|
4449
|
-
statement.includes('DELETE FROM performance_load_async_effects') ||
|
|
4450
|
-
statement.includes('DELETE FROM records')
|
|
4451
|
-
) {
|
|
4452
|
-
return { rowCount: 0, rows: [] };
|
|
4453
|
-
}
|
|
4454
|
-
if (statement.includes('DELETE FROM')) {
|
|
4455
|
-
return { rowCount: 0, rows: [] };
|
|
4456
|
-
}
|
|
4457
|
-
if (statement.includes('SELECT count(*)')) {
|
|
4458
|
-
return { rows: [{ count: '0' }] };
|
|
4459
|
-
}
|
|
4460
|
-
throw new Error('Unexpected cleanup retry query: ' + statement);
|
|
4461
|
-
},
|
|
4462
|
-
};
|
|
4463
|
-
const firstMetrics = { emit: vi.fn(() => ({} as never)) };
|
|
4464
|
-
const createHandler = (metrics: typeof firstMetrics) =>
|
|
4465
|
-
createInboxCleanupHandler({
|
|
4466
|
-
logger: { error: vi.fn(), info: vi.fn() } as never,
|
|
4467
|
-
metrics,
|
|
4468
|
-
now: () => 0,
|
|
4469
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4470
|
-
work(client as never)) as never,
|
|
4471
|
-
});
|
|
4472
|
-
|
|
4473
|
-
await expect(
|
|
4474
|
-
createHandler(firstMetrics)({ id: 'cleanup-report-crash' }),
|
|
4475
|
-
).rejects.toThrow('simulated report commit crash');
|
|
4476
|
-
expect(pendingReports.size).toBe(1);
|
|
4477
|
-
expect(firstMetrics.emit).toHaveBeenCalledWith(
|
|
4478
|
-
expect.objectContaining({
|
|
4479
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4480
|
-
value: 1,
|
|
4481
|
-
}),
|
|
4482
|
-
);
|
|
4483
|
-
|
|
4484
|
-
failReportCommits = false;
|
|
4485
|
-
const retryMetrics = { emit: vi.fn(() => ({} as never)) };
|
|
4486
|
-
await expect(
|
|
4487
|
-
createHandler(retryMetrics)({ id: 'cleanup-report-retry' }),
|
|
4488
|
-
).rejects.toThrow('performance-load-cleanup-deadline-breached');
|
|
4489
|
-
expect(pendingReports.size).toBe(0);
|
|
4490
|
-
expect(retryMetrics.emit).toHaveBeenCalledWith(
|
|
4491
|
-
expect.objectContaining({
|
|
4492
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4493
|
-
value: 1,
|
|
4494
|
-
}),
|
|
4495
|
-
);
|
|
4496
|
-
});
|
|
4497
|
-
|
|
4498
|
-
it('surfaces publish-before-database-ack as retryable at-least-once work', async () => {
|
|
4499
|
-
const client = {
|
|
4500
|
-
async query(statement: string) {
|
|
4501
|
-
if (statement.includes('FROM data_lifecycle_outbox') &&
|
|
4502
|
-
statement.includes('FOR UPDATE')) {
|
|
4503
|
-
return {
|
|
4504
|
-
rows: [{
|
|
4505
|
-
eventJson: { Detail: '{}', DetailType: 'Fixture' },
|
|
4506
|
-
idempotencyKey: 'outbox-crash',
|
|
4507
|
-
}],
|
|
4508
|
-
};
|
|
4509
|
-
}
|
|
4510
|
-
if (statement.includes('published_at = now()')) {
|
|
4511
|
-
throw new Error('database unavailable after publish');
|
|
4512
|
-
}
|
|
4513
|
-
return { rowCount: 1, rows: [] };
|
|
4514
|
-
},
|
|
4515
|
-
};
|
|
4516
|
-
const publish = vi.fn().mockResolvedValue({
|
|
4517
|
-
Entries: [{ EventId: 'event-one' }],
|
|
4518
|
-
});
|
|
4519
|
-
const handler = createOutboxRelayHandler({
|
|
4520
|
-
logger: logger as never,
|
|
4521
|
-
metrics: { emit: vi.fn() },
|
|
4522
|
-
publish: publish as never,
|
|
4523
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4524
|
-
work(client as never)) as never,
|
|
4525
|
-
});
|
|
4526
|
-
|
|
4527
|
-
await expect(handler({ id: 'correlation-crash' })).rejects.toThrow(
|
|
4528
|
-
'database unavailable after publish',
|
|
4529
|
-
);
|
|
4530
|
-
await expect(handler({ id: 'correlation-retry' })).rejects.toThrow(
|
|
4531
|
-
'database unavailable after publish',
|
|
4532
|
-
);
|
|
4533
|
-
expect(publish).toHaveBeenCalledTimes(2);
|
|
4534
|
-
});
|
|
4535
|
-
|
|
2641
|
+
${(0, built_in_plan_key_value_performance_js_1.renderKeyValueOutboxTests)()}
|
|
2642
|
+
${(0, built_in_plan_key_value_cleanup_js_1.renderKeyValueCleanupTests)()}
|
|
4536
2643
|
it('enforces archive hard caps and cancels an active unsafe replay', async () => {
|
|
4537
2644
|
expect(() =>
|
|
4538
2645
|
assertReplayArchiveHardCaps({
|
|
@@ -4579,9 +2686,9 @@ if (contract.schemaVersion !== 1 || contract.slice !== '${name}') {
|
|
|
4579
2686
|
fail('work contract identity is invalid.');
|
|
4580
2687
|
}
|
|
4581
2688
|
if (
|
|
4582
|
-
contract.consistency?.inboxTable !== '
|
|
4583
|
-
|
|
4584
|
-
contract.consistency?.transactionBoundary !== '
|
|
2689
|
+
contract.consistency?.inboxTable !== '${'slice-key-value'}' ||
|
|
2690
|
+
contract.consistency?.outboxTable !== '${'slice-key-value'}' ||
|
|
2691
|
+
contract.consistency?.transactionBoundary !== '${'native-dynamodb-transaction'}'
|
|
4585
2692
|
) {
|
|
4586
2693
|
fail('the explicit inbox/outbox transaction contract is missing.');
|
|
4587
2694
|
}
|
|
@@ -4589,7 +2696,7 @@ if (
|
|
|
4589
2696
|
contract.queue?.batchItemFailures !== true ||
|
|
4590
2697
|
contract.queue?.conflictPolicy !== 'same-id-different-payload-fails' ||
|
|
4591
2698
|
contract.queue?.idempotencyWindowSeconds !== 2_592_000 ||
|
|
4592
|
-
contract.queue?.inboxCleanupBatchSize !==
|
|
2699
|
+
contract.queue?.inboxCleanupBatchSize !== ${20} ||
|
|
4593
2700
|
contract.queue?.inboxCleanupIntervalSeconds !== 3_600 ||
|
|
4594
2701
|
contract.queue?.inboxCleanupInvocationLimit !== 10_000 ||
|
|
4595
2702
|
contract.queue?.performanceCleanupDeadlineSeconds !== 86_400 ||
|