@empire-builder-kit/nx 1.0.0-rc.4 → 1.0.0-rc.41
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 +44 -10
- package/executors.json +21 -1
- package/generators.json +10 -0
- package/package.json +31 -7
- package/src/aws/child-environment.d.ts +10 -0
- package/src/aws/child-environment.js +77 -0
- package/src/aws/identity.d.ts +13 -0
- package/src/aws/identity.js +41 -0
- package/src/aws-authority/index.d.ts +2 -0
- package/src/aws-authority/index.js +6 -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 +248 -0
- package/src/aws-authority/stage-authority.js +3558 -0
- package/src/blueprints/external-sandbox.d.ts +1 -1
- 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 +30 -0
- package/src/cli/deploy-runtime.js +174 -0
- package/src/cli/dev-command.d.ts +18 -4
- package/src/cli/dev-command.js +71 -0
- package/src/cli/main.d.ts +9 -1
- package/src/cli/main.js +157 -24
- package/src/cli/output.d.ts +1 -1
- package/src/cli/output.js +1 -1
- package/src/cli/personal-secret-runtime.d.ts +12 -0
- package/src/cli/personal-secret-runtime.js +18 -3
- package/src/cli/secret-terminal.js +1 -1
- package/src/container-images.d.ts +1 -1
- package/src/container-images.js +1 -1
- package/src/developer-agent/context.js +41 -9
- package/src/developer-agent/generator-planning.js +20 -6
- 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 +5 -3
- package/src/developer-agent/model.js +4 -4
- package/src/developer-agent/operation-registry.d.ts +15 -0
- package/src/developer-agent/operation-registry.js +342 -49
- package/src/developer-agent/planning.d.ts +8 -1
- package/src/developer-agent/planning.js +39 -16
- 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 +25 -9
- package/src/developer-agent/workspace-inspection.js +60 -5
- package/src/executors/blueprint-trust/blueprint-trust.js +6 -0
- package/src/executors/deployment-plan/deployment-plan.d.ts +2 -0
- package/src/executors/deployment-plan/deployment-plan.js +57 -2
- 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-preflight/foundation-preflight.d.ts +1 -0
- package/src/executors/foundation-preflight/foundation-preflight.js +6 -2
- package/src/executors/foundation-preflight/schema.json +5 -0
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +13 -2
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +95 -31
- package/src/executors/foundation-profile-preflight/production-database-safeguard.d.ts +65 -0
- package/src/executors/foundation-profile-preflight/production-database-safeguard.js +223 -0
- package/src/executors/foundation-profile-preflight/schema.json +15 -4
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +11 -1
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +70 -20
- package/src/executors/foundation-remove-preflight/schema.json +6 -1
- package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js +3 -0
- package/src/executors/github-bootstrap/schema.json +7 -1
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +29 -136
- package/src/executors/infrastructure-plan/infrastructure-plan.js +230 -3282
- package/src/executors/infrastructure-plan/schema.json +12 -52
- package/src/executors/local-deploy/federated-credential-transport.d.ts +48 -0
- package/src/executors/local-deploy/federated-credential-transport.js +252 -0
- package/src/executors/local-deploy/local-approval.d.ts +18 -0
- package/src/executors/local-deploy/local-approval.js +39 -0
- package/src/executors/local-deploy/local-deploy.d.ts +112 -0
- package/src/executors/local-deploy/local-deploy.js +1404 -0
- package/src/executors/local-deploy/schema.json +70 -0
- package/src/executors/refresh/refresh.js +2 -2
- package/src/executors/remove/remove.js +2 -2
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +42 -0
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +1229 -0
- package/src/executors/router-waf-rehearsal/schema.json +29 -0
- package/src/executors/secret-initialize/schema.json +42 -0
- package/src/executors/secret-initialize/secret-initialize.d.ts +34 -0
- package/src/executors/secret-initialize/secret-initialize.js +297 -0
- package/src/executors/secret-metadata/schema.json +5 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +16 -2
- package/src/executors/secret-metadata/secret-metadata.js +129 -30
- package/src/executors/sst-lifecycle/generated-secret-retirement.d.ts +53 -0
- package/src/executors/sst-lifecycle/generated-secret-retirement.js +192 -0
- package/src/executors/sst-lifecycle/governed-native-operation.d.ts +48 -0
- package/src/executors/sst-lifecycle/governed-native-operation.js +163 -0
- package/src/executors/sst-lifecycle/logging-preflight.d.ts +5 -0
- package/src/executors/sst-lifecycle/logging-preflight.js +23 -13
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +46 -6
- package/src/executors/sst-lifecycle/run-sst-command.js +750 -71
- package/src/executors/sst-lifecycle/schema.json +8 -1
- package/src/executors/sst-lifecycle/secret-preflight.d.ts +2 -0
- package/src/executors/sst-lifecycle/secret-preflight.js +13 -7
- package/src/executors/sst-lifecycle/sst-environment-source.d.ts +33 -0
- package/src/executors/sst-lifecycle/sst-environment-source.js +267 -0
- package/src/executors/sst-lifecycle/task-unlock.d.ts +15 -0
- package/src/executors/sst-lifecycle/task-unlock.js +126 -0
- package/src/executors/unlock/unlock.js +2 -2
- package/src/foundation/capability-plan.d.ts +43 -0
- package/src/foundation/capability-plan.js +168 -0
- package/src/foundation/cost-estimate.d.ts +170 -0
- package/src/foundation/cost-estimate.js +683 -0
- package/src/foundation/discovery.d.ts +14 -11
- package/src/foundation/discovery.js +32 -6
- package/src/foundation/index.d.ts +4 -0
- package/src/foundation/index.js +11 -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/logging-preflight.js +1 -1
- package/src/foundation/logging.d.ts +3 -1
- package/src/foundation/logging.js +1 -1
- package/src/foundation/partial-state.d.ts +10 -0
- package/src/foundation/partial-state.js +21 -0
- package/src/foundation/preflight.js +37 -18
- package/src/foundation/private-smoke-access.d.ts +25 -0
- package/src/foundation/private-smoke-access.js +135 -0
- package/src/foundation/private-smoke-admission.d.ts +45 -0
- package/src/foundation/private-smoke-admission.js +180 -0
- package/src/foundation/private-smoke-control-plane.d.ts +18 -0
- package/src/foundation/private-smoke-control-plane.js +184 -0
- 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 +15 -8
- package/src/foundation/requirements.d.ts +3 -3
- package/src/foundation/requirements.js +24 -12
- package/src/foundation/router-waf-rate-rehearsal.d.ts +97 -0
- package/src/foundation/router-waf-rate-rehearsal.js +210 -0
- package/src/foundation/router-waf.d.ts +149 -0
- package/src/foundation/router-waf.js +809 -0
- package/src/foundation/stages.d.ts +1 -0
- package/src/foundation/stages.js +11 -0
- 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.d.ts +8 -0
- package/src/generators/formatting.js +141 -0
- package/src/generators/function/function.js +38 -17
- package/src/generators/logging-registry.d.ts +2 -0
- package/src/generators/logging-registry.js +22 -0
- package/src/generators/post-generation-install.js +2 -2
- package/src/generators/preset/dependency-overrides.d.ts +8 -0
- package/src/generators/preset/dependency-overrides.js +55 -0
- package/src/generators/preset/generator.js +43 -3
- package/src/generators/preset/schema.d.ts +10 -0
- package/src/generators/preset/schema.json +47 -0
- package/src/generators/preset/workspace-files-adopter-skills.d.ts +7 -0
- package/src/generators/preset/workspace-files-adopter-skills.js +324 -0
- package/src/generators/preset/workspace-files-ci-acceptance.d.ts +1 -0
- package/src/generators/preset/workspace-files-ci-acceptance.js +791 -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.d.ts +2 -0
- package/src/generators/preset/workspace-files-developer-agent.js +162 -44
- package/src/generators/preset/workspace-files-ecr-inspector.d.ts +18 -0
- package/src/generators/preset/workspace-files-ecr-inspector.js +298 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.js +5 -2
- package/src/generators/preset/workspace-files-performance-cost.d.ts +2 -2
- package/src/generators/preset/workspace-files-performance-cost.js +8 -8
- package/src/generators/preset/workspace-files-performance-load.d.ts +1 -1
- package/src/generators/preset/workspace-files-performance-load.js +39 -25
- package/src/generators/preset/workspace-files-shared-operation-claims.d.ts +10 -0
- package/src/generators/preset/workspace-files-shared-operation-claims.js +59 -0
- package/src/generators/preset/workspace-files-stage-authority.d.ts +6 -0
- package/src/generators/preset/workspace-files-stage-authority.js +529 -0
- package/src/generators/preset/workspace-files-thin-deploy.d.ts +65 -0
- package/src/generators/preset/workspace-files-thin-deploy.js +2507 -0
- package/src/generators/preset/workspace-files-thin-docs.d.ts +7 -0
- package/src/generators/preset/workspace-files-thin-docs.js +370 -0
- package/src/generators/preset/workspace-files-thin-release.d.ts +14 -0
- package/src/generators/preset/workspace-files-thin-release.js +26 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.js +254 -42
- package/src/generators/preset/workspace-files.d.ts +18 -2
- package/src/generators/preset/workspace-files.js +1525 -609
- package/src/generators/repository-policy/repository-policy.js +235 -5
- package/src/generators/repository-policy/schema.d.ts +10 -0
- package/src/generators/repository-policy/schema.json +80 -4
- package/src/generators/repository-policy/tree-policy.d.ts +3 -0
- package/src/generators/repository-policy/tree-policy.js +25 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js +49 -11
- package/src/generators/slice/built-in-plan-api-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-api-files.js +130 -414
- package/src/generators/slice/built-in-plan-async-work-files.js +166 -1969
- package/src/generators/slice/built-in-plan-authorization-files.js +102 -334
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +975 -233
- 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 +2 -0
- package/src/generators/slice/built-in-plan-background-work-files.js +99 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.js +2 -830
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +5 -31
- package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-data-files.js +176 -1200
- 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 +37 -2
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +7 -2443
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +50 -8
- package/src/generators/slice/built-in-plan-data-rights-files.js +6 -4482
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +44 -25
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +25 -2
- package/src/generators/slice/built-in-plan-foundation-files.d.ts +2 -1
- package/src/generators/slice/built-in-plan-foundation-files.js +39 -18
- package/src/generators/slice/built-in-plan-frontend-files.js +14 -3
- package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js +933 -730
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +75 -14
- 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 +137 -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 +159 -29
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +324 -0
- package/src/generators/slice/built-in-plan-operations-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-operations-files.js +585 -97
- package/src/generators/slice/built-in-plan-performance-files.d.ts +2 -1
- package/src/generators/slice/built-in-plan-performance-files.js +220 -1074
- package/src/generators/slice/built-in-plan-project-files.js +141 -135
- package/src/generators/slice/built-in-plan-release-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-release-files.js +310 -70
- package/src/generators/slice/built-in-plan-reliability-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-reliability-files.js +283 -44
- package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-secret-runtime-files.js +87 -76
- package/src/generators/slice/built-in-plan-shared.d.ts +4 -2
- package/src/generators/slice/built-in-plan-shared.js +41 -19
- package/src/generators/slice/built-in-plan-sql-events.d.ts +2 -0
- package/src/generators/slice/built-in-plan-sql-events.js +276 -0
- package/src/generators/slice/built-in-plan-testing-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +304 -172
- package/src/generators/slice/built-in-plan-testing-persistence-event-files.d.ts +7 -0
- package/src/generators/slice/built-in-plan-testing-persistence-event-files.js +487 -0
- 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 +180 -0
- package/src/generators/slice/built-in-plan-thin-release-files.d.ts +11 -0
- package/src/generators/slice/built-in-plan-thin-release-files.js +162 -0
- package/src/generators/slice/built-in-plan-verification-files.js +76 -2
- package/src/generators/slice/built-in-plan-web-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-web-files.js +223 -16
- 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 +143 -279
- package/src/generators/slice/built-in-plan.d.ts +11 -1
- package/src/generators/slice/built-in-plan.js +83 -30
- 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 +61 -50
- 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 +15 -1
- package/src/generators/upgrade/upgrade.js +435 -44
- package/src/generators/workload/frozen-container.d.ts +1 -1
- package/src/generators/workload/performance-registration.d.ts +1 -0
- package/src/generators/workload/performance-registration.js +9 -11
- package/src/generators/workload/service-performance-files.d.ts +1 -0
- package/src/generators/workload/service-performance-files.js +27 -7
- package/src/generators/workload/workload.d.ts +1 -1
- package/src/generators/workload/workload.js +105 -29
- package/src/git-policy/action-pins.d.ts +5 -0
- package/src/git-policy/action-pins.js +5 -0
- package/src/git-policy/github-bootstrap.d.ts +48 -8
- package/src/git-policy/github-bootstrap.js +344 -176
- package/src/git-policy/routing.d.ts +7 -1
- package/src/git-policy/routing.js +62 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/infrastructure-change/local-capability.d.ts +73 -0
- package/src/infrastructure-change/local-capability.js +114 -0
- package/src/infrastructure-change/model.d.ts +25 -4
- package/src/infrastructure-change/model.js +1 -0
- package/src/infrastructure-change/normalize.d.ts +4 -0
- package/src/infrastructure-change/normalize.js +47 -7
- package/src/infrastructure-change/plan.d.ts +30 -3
- package/src/infrastructure-change/plan.js +323 -13
- package/src/infrastructure-change/risk.d.ts +3 -1
- package/src/infrastructure-change/risk.js +96 -16
- 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 +58 -22
- 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 +16 -4
- 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 +11 -0
- package/src/local-dev/private-state.js +146 -40
- 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 +8 -2
- 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/cost-collection.d.ts +1 -1
- package/src/performance/cost-collection.js +4 -2
- package/src/performance/cost-controls.d.ts +8 -4
- package/src/performance/cost-controls.js +39 -28
- package/src/performance/evidence.d.ts +1 -1
- package/src/performance/evidence.js +5 -3
- package/src/performance/load-safety.js +5 -3
- package/src/performance/model.d.ts +4 -4
- package/src/performance/model.js +4 -5
- package/src/performance/scenario.d.ts +22 -0
- package/src/performance/scenario.js +100 -92
- package/src/release/index.d.ts +0 -2
- package/src/release/index.js +0 -2
- package/src/release/source-authority.d.ts +5 -5
- package/src/release/source-authority.js +141 -124
- package/src/repository-policy/aurora-preflight.d.ts +1 -1
- package/src/repository-policy/aurora-preflight.js +4 -1
- package/src/repository-policy/capacity-migration.d.ts +3 -0
- package/src/repository-policy/capacity-migration.js +21 -0
- package/src/repository-policy/deployment-plan.d.ts +4 -1
- package/src/repository-policy/deployment-plan.js +9 -0
- package/src/repository-policy/foundation-profile.js +10 -1
- package/src/repository-policy/policy.d.ts +57 -3
- package/src/repository-policy/policy.js +497 -11
- package/src/schemas/blueprint-v1.json +5 -4
- package/src/schemas/foundation-requirement-v1.json +5 -4
- package/src/schemas/performance-policy-v1.json +3 -3
- package/src/schemas/repository-policy-v1.json +130 -6
- package/src/schemas/testing-evidence-result-v1.json +1 -1
- package/src/secrets/adapters.d.ts +7 -2
- package/src/secrets/adapters.js +34 -3
- package/src/secrets/aws-control-plane.d.ts +10 -1
- package/src/secrets/aws-control-plane.js +67 -0
- package/src/secrets/generated-aws-control-plane.d.ts +23 -0
- package/src/secrets/generated-aws-control-plane.js +259 -0
- package/src/secrets/metadata.js +38 -10
- package/src/secrets/model.d.ts +8 -1
- package/src/secrets/model.js +24 -3
- package/src/secrets/operator.d.ts +91 -3
- package/src/secrets/operator.js +463 -13
- package/src/secrets/personal-command.d.ts +18 -2
- package/src/secrets/personal-command.js +66 -12
- package/src/secrets/registry.d.ts +2 -1
- package/src/secrets/registry.js +18 -1
- package/src/secrets/verification.js +15 -2
- package/src/secrets/workload-access.js +10 -2
- package/src/sst-environment-source-format.d.ts +6 -0
- package/src/sst-environment-source-format.js +199 -0
- package/src/supply-chain/action-pins.js +61 -7
- package/src/supply-chain/blueprint-trust-git.js +85 -24
- package/src/supply-chain/blueprint-verification-authority.js +55 -3
- package/src/supply-chain/build-output-bounds.d.ts +2 -0
- package/src/supply-chain/build-output-bounds.js +12 -0
- package/src/supply-chain/build-output.d.ts +7 -1
- package/src/supply-chain/build-output.js +177 -24
- package/src/supply-chain/ecr-scanning.d.ts +5 -3
- package/src/supply-chain/ecr-scanning.js +11 -7
- package/src/supply-chain/ecs-deployed-image.d.ts +5 -3
- package/src/supply-chain/ecs-deployed-image.js +6 -4
- package/src/supply-chain/generated-blueprint-trust-command.js +18 -1
- package/src/supply-chain/generated-command.js +1 -0
- package/src/supply-chain/generated-container-release-command.js +1285 -124
- package/src/supply-chain/generated-ecr-collector-command.d.ts +0 -1
- package/src/supply-chain/generated-ecr-collector-command.js +200 -93
- package/src/supply-chain/generated-files.js +25 -14
- package/src/supply-chain/generated-scanner-command.js +2 -1
- package/src/supply-chain/index.d.ts +1 -1
- package/src/supply-chain/index.js +2 -1
- package/src/supply-chain/sbom.d.ts +1 -1
- package/src/supply-chain/sbom.js +30 -5
- package/src/supply-chain/scanner.js +3 -0
- 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/account-baseline.d.ts +11 -0
- package/src/workload-roles/account-baseline.js +253 -16
- package/src/workload-roles/authority.d.ts +3 -1
- package/src/workload-roles/authority.js +48 -2
- package/src/workload-roles/aws-s3-state.js +27 -0
- package/src/workload-roles/boundary.d.ts +2 -1
- package/src/workload-roles/boundary.js +9 -2
- package/src/workload-roles/handler.js +52 -24
- package/src/workload-roles/index.d.ts +10 -17
- package/src/workload-roles/index.js +5 -18
- package/src/workload-roles/injection.js +1 -1
- package/src/workload-roles/provisioner.js +12 -4
- package/src/workload-roles/registration.d.ts +1 -0
- package/src/workload-roles/registration.js +5 -0
- package/src/workload-roles/stage-boundary.js +1 -1
- package/src/workload-roles/standard-slice.js +3 -3
- package/src/workspace-deployment/affected-lifecycle.d.ts +53 -0
- package/src/workspace-deployment/affected-lifecycle.js +84 -0
- package/src/workspace-deployment/identity.d.ts +3 -0
- package/src/workspace-deployment/identity.js +22 -0
- package/src/workspace-deployment/index.d.ts +4 -0
- package/src/workspace-deployment/index.js +8 -0
- package/src/workspace-deployment/migration-evidence.d.ts +29 -0
- package/src/workspace-deployment/migration-evidence.js +83 -0
- package/src/workspace-deployment/migration.d.ts +27 -0
- package/src/workspace-deployment/migration.js +109 -0
- package/src/workspace-deployment/ownership.d.ts +81 -0
- package/src/workspace-deployment/ownership.js +381 -0
- 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/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-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/refresh/refresh.js.map +0 -1
- package/src/executors/remove/remove.js.map +0 -1
- package/src/executors/secret-guardrails/secret-guardrails.js.map +0 -1
- package/src/executors/secret-metadata/secret-metadata.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/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/preflight.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/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/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/generator.js.map +0 -1
- package/src/generators/preset/tool-versions.js.map +0 -1
- package/src/generators/preset/workspace-files-developer-agent.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-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/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-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-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-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/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 -169
- 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/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/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/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.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
|
@@ -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,
|
|
@@ -217,6 +223,7 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
217
223
|
'sqs:ListQueueTags',
|
|
218
224
|
'sqs:ReceiveMessage',
|
|
219
225
|
'sqs:SendMessage',
|
|
226
|
+
'ssm:GetParameter',
|
|
220
227
|
],
|
|
221
228
|
destination: 'generated-provider-queue-only',
|
|
222
229
|
source: 'generated-provider-dlq-only',
|
|
@@ -229,6 +236,7 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
229
236
|
'dynamodb:PutItem',
|
|
230
237
|
'dynamodb:Scan',
|
|
231
238
|
'dynamodb:UpdateItem',
|
|
239
|
+
'ssm:GetParameter',
|
|
232
240
|
],
|
|
233
241
|
scope: 'exact-retained-protected-report over the generated async recovery ledger audit path',
|
|
234
242
|
},
|
|
@@ -241,6 +249,7 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
241
249
|
'events:DescribeRule',
|
|
242
250
|
'events:ListTargetsByRule',
|
|
243
251
|
'events:StartReplay',
|
|
252
|
+
'ssm:GetParameter',
|
|
244
253
|
],
|
|
245
254
|
destination: 'the exact source Foundation event bus and rule allowlist',
|
|
246
255
|
source: 'the deterministic canonical Foundation event archive',
|
|
@@ -252,6 +261,7 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
252
261
|
'dynamodb:ListTagsOfResource',
|
|
253
262
|
'dynamodb:PutItem',
|
|
254
263
|
'dynamodb:UpdateItem',
|
|
264
|
+
'ssm:GetParameter',
|
|
255
265
|
],
|
|
256
266
|
scope: 'one exact metadata-only provider account, operation, and time window through a configured Product adapter',
|
|
257
267
|
},
|
|
@@ -264,6 +274,7 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
264
274
|
'states:DescribeStateMachine',
|
|
265
275
|
'states:ListTagsForResource',
|
|
266
276
|
'states:RedriveExecution',
|
|
277
|
+
'ssm:GetParameter',
|
|
267
278
|
],
|
|
268
279
|
scope: 'one exact generated record-export execution, version, and cancel-or-redrive action',
|
|
269
280
|
},
|
|
@@ -275,6 +286,7 @@ function renderProductAsyncWorkFiles(name, pascalName) {
|
|
|
275
286
|
source: 'generated logical source allowlist resolved from deployed outputs',
|
|
276
287
|
state: 'generated tagged DynamoDB recovery ledger',
|
|
277
288
|
stage: 'GitHub environment and workflow input',
|
|
289
|
+
workspaceOwner: 'exact /ebk/<stage>/workspace-owner parameter read before recovery AWS access',
|
|
278
290
|
},
|
|
279
291
|
localProtectedMutation: 'forbidden',
|
|
280
292
|
schemaVersion: 1,
|
|
@@ -906,535 +918,6 @@ export function createAsyncRecoveryAdapters(): AsyncRecoveryAdapters {
|
|
|
906
918
|
}
|
|
907
919
|
|
|
908
920
|
export type { BackfillReportV1 };
|
|
909
|
-
`,
|
|
910
|
-
'async/provider-worker.ts': `import { randomUUID } from 'node:crypto';
|
|
911
|
-
import {
|
|
912
|
-
asyncWorkDigest,
|
|
913
|
-
processSqsBatch,
|
|
914
|
-
runProviderDelivery,
|
|
915
|
-
type ProviderDeliveryV1,
|
|
916
|
-
} from '@empire-builder-kit/runtime/async-work';
|
|
917
|
-
import { deriveOperationalRolloutStableKey } from '@empire-builder-kit/runtime/configuration';
|
|
918
|
-
import { createLogger, type Logger } from '@empire-builder-kit/runtime/logging';
|
|
919
|
-
import type { Client } from 'pg';
|
|
920
|
-
import {
|
|
921
|
-
createSliceAwsConfigurationProvider,
|
|
922
|
-
createSliceLocalConfigurationProvider,
|
|
923
|
-
} from '../configuration/provider.js';
|
|
924
|
-
import { evaluateLambdaProviderDelivery } from '../configuration/rollout.js';
|
|
925
|
-
import { withPrimaryDatabase } from '../database/runtime-client.js';
|
|
926
|
-
import {
|
|
927
|
-
asyncAdapterDeclaration,
|
|
928
|
-
createAsyncRecoveryAdapters,
|
|
929
|
-
} from './recovery-adapters.js';
|
|
930
|
-
|
|
931
|
-
const providerWorkerRolloutStableKey =
|
|
932
|
-
deriveOperationalRolloutStableKey({
|
|
933
|
-
kind: 'lambda',
|
|
934
|
-
slice: '${name}',
|
|
935
|
-
workload: 'provider-worker',
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
interface QueueRecord { body: string; messageId: string }
|
|
939
|
-
interface QueueEvent { Records: QueueRecord[] }
|
|
940
|
-
|
|
941
|
-
interface InboxRow {
|
|
942
|
-
attempts: number;
|
|
943
|
-
lastAttemptAt: Date;
|
|
944
|
-
payloadDigest: string;
|
|
945
|
-
resultReference: string | null;
|
|
946
|
-
status: 'claimed' | 'failed' | 'succeeded';
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
const HANDLER_ID = '${name}.provider-worker';
|
|
950
|
-
const HANDLER_VERSION = '1';
|
|
951
|
-
const CLAIM_LEASE_MILLISECONDS = 120_000;
|
|
952
|
-
const INBOX_RETENTION_DAYS = 30;
|
|
953
|
-
const protectedStage =
|
|
954
|
-
process.env.EBK_STAGE === 'staging' ||
|
|
955
|
-
process.env.EBK_STAGE === 'production';
|
|
956
|
-
|
|
957
|
-
function requiredConfigurationIdentifier(name: string): string {
|
|
958
|
-
const value = process.env[name]?.trim();
|
|
959
|
-
if (!value) throw new Error(name + ' is required.');
|
|
960
|
-
return value;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
const configurationProvider = protectedStage
|
|
964
|
-
? createSliceAwsConfigurationProvider({
|
|
965
|
-
applicationIdentifier: requiredConfigurationIdentifier(
|
|
966
|
-
'EBK_APPCONFIG_APPLICATION_ID',
|
|
967
|
-
),
|
|
968
|
-
environmentIdentifier: requiredConfigurationIdentifier(
|
|
969
|
-
'EBK_APPCONFIG_ENVIRONMENT_ID',
|
|
970
|
-
),
|
|
971
|
-
profileIdentifier: requiredConfigurationIdentifier(
|
|
972
|
-
'EBK_APPCONFIG_PROFILE_ID',
|
|
973
|
-
),
|
|
974
|
-
})
|
|
975
|
-
: createSliceLocalConfigurationProvider();
|
|
976
|
-
|
|
977
|
-
function expiresAt(createdAt: string) {
|
|
978
|
-
const value = new Date(createdAt);
|
|
979
|
-
value.setUTCDate(value.getUTCDate() + INBOX_RETENTION_DAYS);
|
|
980
|
-
return value.toISOString();
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
function parseDelivery(body: string): ProviderDeliveryV1<unknown> {
|
|
984
|
-
const delivery = JSON.parse(body) as ProviderDeliveryV1<unknown>;
|
|
985
|
-
if (
|
|
986
|
-
delivery?.version !== 1 ||
|
|
987
|
-
typeof delivery.deliveryId !== 'string' ||
|
|
988
|
-
typeof delivery.requestDigest !== 'string' ||
|
|
989
|
-
asyncWorkDigest(delivery.request) !== delivery.requestDigest
|
|
990
|
-
) {
|
|
991
|
-
throw new Error('provider-delivery-invalid');
|
|
992
|
-
}
|
|
993
|
-
return delivery;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
export type ProviderClaimResult =
|
|
997
|
-
| { attemptToken: string; outcome: 'claimed' }
|
|
998
|
-
| {
|
|
999
|
-
outcome:
|
|
1000
|
-
| 'conflict'
|
|
1001
|
-
| 'duplicate'
|
|
1002
|
-
| 'retry'
|
|
1003
|
-
| 'terminal-permanent'
|
|
1004
|
-
| 'terminal-reconciliation';
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
function attemptReference(
|
|
1008
|
-
state: 'effect-started' | 'prepared',
|
|
1009
|
-
attemptToken: string,
|
|
1010
|
-
): string {
|
|
1011
|
-
return state + '-' + attemptToken;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
export async function claimProviderDelivery(
|
|
1015
|
-
client: Client,
|
|
1016
|
-
delivery: ProviderDeliveryV1<unknown>,
|
|
1017
|
-
now: string,
|
|
1018
|
-
createAttemptToken: () => string = randomUUID,
|
|
1019
|
-
): Promise<ProviderClaimResult> {
|
|
1020
|
-
const attemptToken = createAttemptToken();
|
|
1021
|
-
const preparedReference = attemptReference('prepared', attemptToken);
|
|
1022
|
-
await client.query('BEGIN');
|
|
1023
|
-
try {
|
|
1024
|
-
const inserted = await client.query(
|
|
1025
|
-
\`INSERT INTO ebk_inbox
|
|
1026
|
-
(handler_id, handler_version, message_id, payload_digest, status,
|
|
1027
|
-
attempts, first_seen_at, last_attempt_at, expires_at,
|
|
1028
|
-
result_reference)
|
|
1029
|
-
VALUES ($1, $2, $3, $4, 'claimed', 1, $5, $5, $6, $7)
|
|
1030
|
-
ON CONFLICT (handler_id, message_id) DO NOTHING
|
|
1031
|
-
RETURNING message_id\`,
|
|
1032
|
-
[
|
|
1033
|
-
HANDLER_ID,
|
|
1034
|
-
HANDLER_VERSION,
|
|
1035
|
-
delivery.deliveryId,
|
|
1036
|
-
delivery.requestDigest,
|
|
1037
|
-
now,
|
|
1038
|
-
expiresAt(delivery.createdAt),
|
|
1039
|
-
preparedReference,
|
|
1040
|
-
],
|
|
1041
|
-
);
|
|
1042
|
-
if (inserted.rowCount === 1) {
|
|
1043
|
-
await client.query('COMMIT');
|
|
1044
|
-
return { attemptToken, outcome: 'claimed' };
|
|
1045
|
-
}
|
|
1046
|
-
const existing = await client.query<InboxRow>(
|
|
1047
|
-
\`SELECT attempts, last_attempt_at AS "lastAttemptAt",
|
|
1048
|
-
payload_digest AS "payloadDigest",
|
|
1049
|
-
result_reference AS "resultReference", status
|
|
1050
|
-
FROM ebk_inbox
|
|
1051
|
-
WHERE handler_id = $1 AND message_id = $2
|
|
1052
|
-
FOR UPDATE\`,
|
|
1053
|
-
[HANDLER_ID, delivery.deliveryId],
|
|
1054
|
-
);
|
|
1055
|
-
const row = existing.rows[0];
|
|
1056
|
-
if (!row || row.payloadDigest !== delivery.requestDigest) {
|
|
1057
|
-
await client.query('COMMIT');
|
|
1058
|
-
return { outcome: 'conflict' };
|
|
1059
|
-
}
|
|
1060
|
-
if (row.status === 'succeeded') {
|
|
1061
|
-
await client.query('COMMIT');
|
|
1062
|
-
return { outcome: 'duplicate' };
|
|
1063
|
-
}
|
|
1064
|
-
if (row.status === 'failed' && row.resultReference) {
|
|
1065
|
-
if (row.resultReference.startsWith('terminal-reconciliation-')) {
|
|
1066
|
-
await client.query('COMMIT');
|
|
1067
|
-
return { outcome: 'terminal-reconciliation' };
|
|
1068
|
-
}
|
|
1069
|
-
if (row.resultReference.startsWith('terminal-permanent-')) {
|
|
1070
|
-
await client.query('COMMIT');
|
|
1071
|
-
return { outcome: 'terminal-permanent' };
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
if (
|
|
1075
|
-
row.status === 'claimed' &&
|
|
1076
|
-
Date.parse(now) - row.lastAttemptAt.getTime() <
|
|
1077
|
-
CLAIM_LEASE_MILLISECONDS
|
|
1078
|
-
) {
|
|
1079
|
-
await client.query('COMMIT');
|
|
1080
|
-
return { outcome: 'retry' };
|
|
1081
|
-
}
|
|
1082
|
-
if (
|
|
1083
|
-
row.status === 'claimed' &&
|
|
1084
|
-
row.resultReference?.startsWith('effect-started-') &&
|
|
1085
|
-
delivery.contract.idempotencyRepeatSafety === 'unproven'
|
|
1086
|
-
) {
|
|
1087
|
-
const terminal = await client.query(
|
|
1088
|
-
\`UPDATE ebk_inbox
|
|
1089
|
-
SET handler_version = $3, last_attempt_at = $4, status = 'failed',
|
|
1090
|
-
result_reference = $5
|
|
1091
|
-
WHERE handler_id = $1 AND message_id = $2
|
|
1092
|
-
AND status = 'claimed' AND result_reference = $6\`,
|
|
1093
|
-
[
|
|
1094
|
-
HANDLER_ID,
|
|
1095
|
-
delivery.deliveryId,
|
|
1096
|
-
HANDLER_VERSION,
|
|
1097
|
-
now,
|
|
1098
|
-
'terminal-reconciliation-' +
|
|
1099
|
-
asyncWorkDigest({
|
|
1100
|
-
deliveryId: delivery.deliveryId,
|
|
1101
|
-
result: 'provider-effect-outcome-unknown',
|
|
1102
|
-
}),
|
|
1103
|
-
row.resultReference,
|
|
1104
|
-
],
|
|
1105
|
-
);
|
|
1106
|
-
if (terminal.rowCount !== 1) {
|
|
1107
|
-
throw new Error('provider-reconciliation-transition-lost');
|
|
1108
|
-
}
|
|
1109
|
-
await client.query('COMMIT');
|
|
1110
|
-
return { outcome: 'terminal-reconciliation' };
|
|
1111
|
-
}
|
|
1112
|
-
await client.query(
|
|
1113
|
-
\`UPDATE ebk_inbox
|
|
1114
|
-
SET attempts = attempts + 1, handler_version = $3,
|
|
1115
|
-
last_attempt_at = $4, result_reference = $5, status = 'claimed'
|
|
1116
|
-
WHERE handler_id = $1 AND message_id = $2\`,
|
|
1117
|
-
[
|
|
1118
|
-
HANDLER_ID,
|
|
1119
|
-
delivery.deliveryId,
|
|
1120
|
-
HANDLER_VERSION,
|
|
1121
|
-
now,
|
|
1122
|
-
preparedReference,
|
|
1123
|
-
],
|
|
1124
|
-
);
|
|
1125
|
-
await client.query('COMMIT');
|
|
1126
|
-
return { attemptToken, outcome: 'claimed' };
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
1129
|
-
throw error;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
export async function markProviderEffectStarted(
|
|
1134
|
-
client: Client,
|
|
1135
|
-
deliveryId: string,
|
|
1136
|
-
attemptToken: string,
|
|
1137
|
-
now: string,
|
|
1138
|
-
): Promise<boolean> {
|
|
1139
|
-
const result = await client.query(
|
|
1140
|
-
\`UPDATE ebk_inbox
|
|
1141
|
-
SET result_reference = $4, last_attempt_at = $5
|
|
1142
|
-
WHERE handler_id = $1 AND message_id = $2 AND status = 'claimed'
|
|
1143
|
-
AND result_reference = $3\`,
|
|
1144
|
-
[
|
|
1145
|
-
HANDLER_ID,
|
|
1146
|
-
deliveryId,
|
|
1147
|
-
attemptReference('prepared', attemptToken),
|
|
1148
|
-
attemptReference('effect-started', attemptToken),
|
|
1149
|
-
now,
|
|
1150
|
-
],
|
|
1151
|
-
);
|
|
1152
|
-
return result.rowCount === 1;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
export async function finishProviderDelivery(
|
|
1156
|
-
client: Client,
|
|
1157
|
-
deliveryId: string,
|
|
1158
|
-
attemptToken: string,
|
|
1159
|
-
input: {
|
|
1160
|
-
expectedState?: 'effect-started' | 'prepared';
|
|
1161
|
-
resultReference?: string;
|
|
1162
|
-
status: 'failed' | 'succeeded';
|
|
1163
|
-
},
|
|
1164
|
-
): Promise<boolean> {
|
|
1165
|
-
const result = await client.query(
|
|
1166
|
-
\`UPDATE ebk_inbox
|
|
1167
|
-
SET completed_at = CASE WHEN $3 = 'succeeded' THEN now() ELSE NULL END,
|
|
1168
|
-
result_reference = $4, status = $3, last_attempt_at = now()
|
|
1169
|
-
WHERE handler_id = $1 AND message_id = $2
|
|
1170
|
-
AND status = 'claimed' AND result_reference = $5\`,
|
|
1171
|
-
[
|
|
1172
|
-
HANDLER_ID,
|
|
1173
|
-
deliveryId,
|
|
1174
|
-
input.status,
|
|
1175
|
-
input.resultReference ?? null,
|
|
1176
|
-
attemptReference(input.expectedState ?? 'effect-started', attemptToken),
|
|
1177
|
-
],
|
|
1178
|
-
);
|
|
1179
|
-
return result.rowCount === 1;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
interface ProviderWorkerDependencies {
|
|
1183
|
-
createAttemptToken(): string;
|
|
1184
|
-
deliver: typeof runProviderDelivery;
|
|
1185
|
-
evaluateRollout: typeof evaluateLambdaProviderDelivery;
|
|
1186
|
-
logger: Logger;
|
|
1187
|
-
now(): string;
|
|
1188
|
-
withDatabase: typeof withPrimaryDatabase;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
export function createProviderWorkerHandler(
|
|
1192
|
-
dependencies: ProviderWorkerDependencies,
|
|
1193
|
-
) {
|
|
1194
|
-
const processRecord = async (record: QueueRecord) => {
|
|
1195
|
-
let delivery: ProviderDeliveryV1<unknown>;
|
|
1196
|
-
try {
|
|
1197
|
-
delivery = parseDelivery(record.body);
|
|
1198
|
-
} catch (error) {
|
|
1199
|
-
dependencies.logger.warn('Provider delivery rejected', {
|
|
1200
|
-
attributes: {
|
|
1201
|
-
errorCode: 'provider-delivery-invalid',
|
|
1202
|
-
messageIdDigest: asyncWorkDigest(record.messageId),
|
|
1203
|
-
},
|
|
1204
|
-
error,
|
|
1205
|
-
outcome: 'failure',
|
|
1206
|
-
});
|
|
1207
|
-
return { errorCode: 'provider-delivery-invalid', outcome: 'terminal' as const };
|
|
1208
|
-
}
|
|
1209
|
-
const correlationId = delivery.deliveryId;
|
|
1210
|
-
const claimResult = await dependencies.withDatabase(
|
|
1211
|
-
{ correlationId },
|
|
1212
|
-
'async-provider-worker',
|
|
1213
|
-
(client) =>
|
|
1214
|
-
claimProviderDelivery(
|
|
1215
|
-
client,
|
|
1216
|
-
delivery,
|
|
1217
|
-
dependencies.now(),
|
|
1218
|
-
dependencies.createAttemptToken,
|
|
1219
|
-
),
|
|
1220
|
-
);
|
|
1221
|
-
if (claimResult.outcome === 'conflict') {
|
|
1222
|
-
dependencies.logger.error('Provider delivery idempotency conflict', {
|
|
1223
|
-
attributes: { deliveryIdDigest: asyncWorkDigest(delivery.deliveryId) },
|
|
1224
|
-
context: { correlationId },
|
|
1225
|
-
outcome: 'failure',
|
|
1226
|
-
});
|
|
1227
|
-
return { errorCode: 'idempotency-payload-conflict', outcome: 'terminal' as const };
|
|
1228
|
-
}
|
|
1229
|
-
if (claimResult.outcome === 'duplicate') {
|
|
1230
|
-
dependencies.logger.info('Provider delivery deduplicated', {
|
|
1231
|
-
attributes: { deliveryIdDigest: asyncWorkDigest(delivery.deliveryId) },
|
|
1232
|
-
context: { correlationId },
|
|
1233
|
-
outcome: 'success',
|
|
1234
|
-
});
|
|
1235
|
-
return { outcome: 'succeeded' as const };
|
|
1236
|
-
}
|
|
1237
|
-
if (claimResult.outcome === 'terminal-permanent') {
|
|
1238
|
-
return {
|
|
1239
|
-
errorCode: 'provider-permanent-failure',
|
|
1240
|
-
outcome: 'terminal' as const,
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
if (claimResult.outcome === 'terminal-reconciliation') {
|
|
1244
|
-
dependencies.logger.error('Provider delivery requires reconciliation', {
|
|
1245
|
-
attributes: { deliveryIdDigest: asyncWorkDigest(delivery.deliveryId) },
|
|
1246
|
-
context: { correlationId },
|
|
1247
|
-
outcome: 'failure',
|
|
1248
|
-
});
|
|
1249
|
-
return {
|
|
1250
|
-
errorCode: 'provider-reconciliation-required',
|
|
1251
|
-
outcome: 'terminal' as const,
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
if (claimResult.outcome === 'retry') {
|
|
1255
|
-
return { errorCode: 'idempotency-claim-active', outcome: 'retry' as const };
|
|
1256
|
-
}
|
|
1257
|
-
if (claimResult.outcome !== 'claimed') {
|
|
1258
|
-
throw new Error('provider-delivery-claim-outcome-unhandled');
|
|
1259
|
-
}
|
|
1260
|
-
const attemptToken = claimResult.attemptToken;
|
|
1261
|
-
let rollout;
|
|
1262
|
-
try {
|
|
1263
|
-
rollout = await dependencies.evaluateRollout(
|
|
1264
|
-
configurationProvider,
|
|
1265
|
-
providerWorkerRolloutStableKey,
|
|
1266
|
-
);
|
|
1267
|
-
} catch (error) {
|
|
1268
|
-
await dependencies.withDatabase(
|
|
1269
|
-
{ correlationId },
|
|
1270
|
-
'async-provider-worker',
|
|
1271
|
-
(client) =>
|
|
1272
|
-
finishProviderDelivery(
|
|
1273
|
-
client,
|
|
1274
|
-
delivery.deliveryId,
|
|
1275
|
-
attemptToken,
|
|
1276
|
-
{
|
|
1277
|
-
expectedState: 'prepared',
|
|
1278
|
-
resultReference: 'configuration-invalid',
|
|
1279
|
-
status: 'failed',
|
|
1280
|
-
},
|
|
1281
|
-
),
|
|
1282
|
-
);
|
|
1283
|
-
dependencies.logger.error('Provider delivery configuration failed', {
|
|
1284
|
-
context: { correlationId },
|
|
1285
|
-
error,
|
|
1286
|
-
outcome: 'failure',
|
|
1287
|
-
});
|
|
1288
|
-
return {
|
|
1289
|
-
errorCode: 'provider-delivery-configuration-invalid',
|
|
1290
|
-
outcome: 'retry' as const,
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
if (!rollout.assigned) {
|
|
1294
|
-
await dependencies.withDatabase(
|
|
1295
|
-
{ correlationId },
|
|
1296
|
-
'async-provider-worker',
|
|
1297
|
-
(client) =>
|
|
1298
|
-
finishProviderDelivery(
|
|
1299
|
-
client,
|
|
1300
|
-
delivery.deliveryId,
|
|
1301
|
-
attemptToken,
|
|
1302
|
-
{
|
|
1303
|
-
expectedState: 'prepared',
|
|
1304
|
-
resultReference: 'configuration-disabled',
|
|
1305
|
-
status: 'failed',
|
|
1306
|
-
},
|
|
1307
|
-
),
|
|
1308
|
-
);
|
|
1309
|
-
return {
|
|
1310
|
-
errorCode: 'provider-delivery-disabled',
|
|
1311
|
-
outcome: 'retry' as const,
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
const effectStarted = await dependencies.withDatabase(
|
|
1315
|
-
{ correlationId },
|
|
1316
|
-
'async-provider-worker',
|
|
1317
|
-
(client) =>
|
|
1318
|
-
markProviderEffectStarted(
|
|
1319
|
-
client,
|
|
1320
|
-
delivery.deliveryId,
|
|
1321
|
-
attemptToken,
|
|
1322
|
-
dependencies.now(),
|
|
1323
|
-
),
|
|
1324
|
-
);
|
|
1325
|
-
if (!effectStarted) {
|
|
1326
|
-
return {
|
|
1327
|
-
errorCode: 'idempotency-claim-lost',
|
|
1328
|
-
outcome: 'retry' as const,
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
const result = await dependencies.deliver({
|
|
1332
|
-
adapter: createAsyncRecoveryAdapters().providerDelivery,
|
|
1333
|
-
delivery,
|
|
1334
|
-
});
|
|
1335
|
-
if (result.outcome === 'confirmed') {
|
|
1336
|
-
const persisted = await dependencies.withDatabase(
|
|
1337
|
-
{ correlationId },
|
|
1338
|
-
'async-provider-worker',
|
|
1339
|
-
(client) =>
|
|
1340
|
-
finishProviderDelivery(
|
|
1341
|
-
client,
|
|
1342
|
-
delivery.deliveryId,
|
|
1343
|
-
attemptToken,
|
|
1344
|
-
{
|
|
1345
|
-
resultReference: result.providerReference,
|
|
1346
|
-
status: 'succeeded',
|
|
1347
|
-
},
|
|
1348
|
-
),
|
|
1349
|
-
);
|
|
1350
|
-
if (!persisted) {
|
|
1351
|
-
throw new Error('provider-delivery-confirmation-state-lost');
|
|
1352
|
-
}
|
|
1353
|
-
dependencies.logger.info('Provider delivery completed', {
|
|
1354
|
-
attributes: {
|
|
1355
|
-
attempts: result.attempts,
|
|
1356
|
-
deliveryIdDigest: asyncWorkDigest(delivery.deliveryId),
|
|
1357
|
-
},
|
|
1358
|
-
context: { correlationId },
|
|
1359
|
-
outcome: 'success',
|
|
1360
|
-
});
|
|
1361
|
-
return { outcome: 'succeeded' as const };
|
|
1362
|
-
}
|
|
1363
|
-
await dependencies.withDatabase(
|
|
1364
|
-
{ correlationId },
|
|
1365
|
-
'async-provider-worker',
|
|
1366
|
-
(client) =>
|
|
1367
|
-
finishProviderDelivery(
|
|
1368
|
-
client,
|
|
1369
|
-
delivery.deliveryId,
|
|
1370
|
-
attemptToken,
|
|
1371
|
-
{
|
|
1372
|
-
resultReference:
|
|
1373
|
-
result.outcome === 'permanent-failure'
|
|
1374
|
-
? 'terminal-permanent-' + result.reconciliationId
|
|
1375
|
-
: 'terminal-reconciliation-' + result.reconciliationId,
|
|
1376
|
-
status: 'failed',
|
|
1377
|
-
},
|
|
1378
|
-
),
|
|
1379
|
-
);
|
|
1380
|
-
dependencies.logger.error('Provider delivery terminated', {
|
|
1381
|
-
attributes: {
|
|
1382
|
-
deliveryIdDigest: asyncWorkDigest(delivery.deliveryId),
|
|
1383
|
-
errorCode: result.errorCode,
|
|
1384
|
-
requiresReconciliation:
|
|
1385
|
-
result.outcome === 'reconciliation-required',
|
|
1386
|
-
},
|
|
1387
|
-
context: { correlationId },
|
|
1388
|
-
outcome: 'failure',
|
|
1389
|
-
});
|
|
1390
|
-
return {
|
|
1391
|
-
errorCode: result.errorCode,
|
|
1392
|
-
outcome: 'terminal' as const,
|
|
1393
|
-
};
|
|
1394
|
-
};
|
|
1395
|
-
|
|
1396
|
-
return async (event: QueueEvent) => {
|
|
1397
|
-
if (!asyncAdapterDeclaration.configured) {
|
|
1398
|
-
throw new Error('async-provider-adapter-disabled');
|
|
1399
|
-
}
|
|
1400
|
-
const result = await processSqsBatch({
|
|
1401
|
-
handler: async (record) => {
|
|
1402
|
-
try {
|
|
1403
|
-
return await processRecord(record);
|
|
1404
|
-
} catch (error) {
|
|
1405
|
-
dependencies.logger.error('Provider delivery handler failed', {
|
|
1406
|
-
attributes: {
|
|
1407
|
-
messageIdDigest: asyncWorkDigest(record.messageId),
|
|
1408
|
-
},
|
|
1409
|
-
error,
|
|
1410
|
-
outcome: 'failure',
|
|
1411
|
-
});
|
|
1412
|
-
throw error;
|
|
1413
|
-
}
|
|
1414
|
-
},
|
|
1415
|
-
records: event.Records,
|
|
1416
|
-
});
|
|
1417
|
-
return result.response;
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
const logger = createLogger({
|
|
1422
|
-
component: 'provider-worker',
|
|
1423
|
-
feature: 'async-work',
|
|
1424
|
-
operation: 'provider-delivery',
|
|
1425
|
-
slice: '${name}',
|
|
1426
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1427
|
-
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
1428
|
-
});
|
|
1429
|
-
|
|
1430
|
-
export const handler = createProviderWorkerHandler({
|
|
1431
|
-
createAttemptToken: randomUUID,
|
|
1432
|
-
deliver: runProviderDelivery,
|
|
1433
|
-
evaluateRollout: evaluateLambdaProviderDelivery,
|
|
1434
|
-
logger,
|
|
1435
|
-
now: () => new Date().toISOString(),
|
|
1436
|
-
withDatabase: withPrimaryDatabase,
|
|
1437
|
-
});
|
|
1438
921
|
`,
|
|
1439
922
|
'async/webhook-ingress.ts': `import {
|
|
1440
923
|
SendMessageCommand,
|
|
@@ -1703,836 +1186,71 @@ export const handler = createWebhookIngressHandler({
|
|
|
1703
1186
|
EbkTenant: {
|
|
1704
1187
|
DataType: 'String',
|
|
1705
1188
|
StringValue: delivery.contract.account,
|
|
1706
|
-
},
|
|
1707
|
-
EbkWorkType: {
|
|
1708
|
-
DataType: 'String',
|
|
1709
|
-
StringValue: 'provider-delivery-v1',
|
|
1710
|
-
},
|
|
1711
|
-
},
|
|
1712
|
-
QueueUrl: Resource.${pascalName}ProviderQueue.url,
|
|
1713
|
-
}),
|
|
1714
|
-
),
|
|
1715
|
-
verify: verifyProviderWebhook,
|
|
1716
|
-
});
|
|
1717
|
-
`,
|
|
1718
|
-
'async/schedule-target.ts': `import {
|
|
1719
|
-
SendMessageCommand,
|
|
1720
|
-
SQSClient,
|
|
1721
|
-
} from '@aws-sdk/client-sqs';
|
|
1722
|
-
import { createLogger } from '@empire-builder-kit/runtime/logging';
|
|
1723
|
-
import { Resource } from 'sst/resource';
|
|
1724
|
-
import {
|
|
1725
|
-
asyncAdapterDeclaration,
|
|
1726
|
-
createAsyncRecoveryAdapters,
|
|
1727
|
-
} from './recovery-adapters.js';
|
|
1728
|
-
|
|
1729
|
-
const sqs = new SQSClient({ maxAttempts: 2 });
|
|
1730
|
-
const logger = createLogger({
|
|
1731
|
-
component: 'schedule-target',
|
|
1732
|
-
feature: 'async-work',
|
|
1733
|
-
operation: 'schedule-target',
|
|
1734
|
-
slice: '${name}',
|
|
1735
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1736
|
-
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
1737
|
-
});
|
|
1738
|
-
|
|
1739
|
-
export async function handler(event: { time?: string }) {
|
|
1740
|
-
if (!asyncAdapterDeclaration.configured) {
|
|
1741
|
-
return { accepted: false, reason: 'schedule-not-configured' };
|
|
1742
|
-
}
|
|
1743
|
-
const scheduledAt = event.time ?? new Date().toISOString();
|
|
1744
|
-
const delivery =
|
|
1745
|
-
await createAsyncRecoveryAdapters().createScheduledDelivery({
|
|
1746
|
-
scheduleId: '${name}.provider-reconciliation',
|
|
1747
|
-
scheduledAt,
|
|
1748
|
-
});
|
|
1749
|
-
await sqs.send(
|
|
1750
|
-
new SendMessageCommand({
|
|
1751
|
-
MessageBody: JSON.stringify(delivery),
|
|
1752
|
-
MessageAttributes: {
|
|
1753
|
-
EbkTenant: {
|
|
1754
|
-
DataType: 'String',
|
|
1755
|
-
StringValue: delivery.contract.account,
|
|
1756
|
-
},
|
|
1757
|
-
EbkWorkType: {
|
|
1758
|
-
DataType: 'String',
|
|
1759
|
-
StringValue: 'provider-delivery-v1',
|
|
1760
|
-
},
|
|
1761
|
-
},
|
|
1762
|
-
QueueUrl: Resource.${pascalName}ProviderQueue.url,
|
|
1763
|
-
}),
|
|
1764
|
-
);
|
|
1765
|
-
logger.info('Scheduled provider reconciliation delivery queued', {
|
|
1766
|
-
attributes: { scheduledAt },
|
|
1767
|
-
outcome: 'success',
|
|
1768
|
-
});
|
|
1769
|
-
return { accepted: true, scheduledAt };
|
|
1770
|
-
}
|
|
1771
|
-
`,
|
|
1772
|
-
'async/outbox-relay.ts': `import { randomUUID } from 'node:crypto';
|
|
1773
|
-
import {
|
|
1774
|
-
EventBridgeClient,
|
|
1775
|
-
PutEventsCommand,
|
|
1776
|
-
type PutEventsRequestEntry,
|
|
1777
|
-
} from '@aws-sdk/client-eventbridge';
|
|
1778
|
-
import { createLogger, type Logger } from '@empire-builder-kit/runtime/logging';
|
|
1779
|
-
import {
|
|
1780
|
-
createMetricEmitter,
|
|
1781
|
-
type MetricEmitter,
|
|
1782
|
-
} from '@empire-builder-kit/runtime/observability';
|
|
1783
|
-
import { withPrimaryDatabase } from '../database/runtime-client.js';
|
|
1784
|
-
|
|
1785
|
-
interface OutboxRow {
|
|
1786
|
-
eventJson: PutEventsRequestEntry;
|
|
1787
|
-
idempotencyKey: string;
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
const eventBridge = new EventBridgeClient({ maxAttempts: 2 });
|
|
1791
|
-
|
|
1792
|
-
function emitOutboxBacklog(metrics: MetricEmitter, value: number) {
|
|
1793
|
-
metrics.emit({
|
|
1794
|
-
dimensions: {
|
|
1795
|
-
component: 'outbox-relay',
|
|
1796
|
-
feature: 'async-work',
|
|
1797
|
-
operation: 'outbox-relay',
|
|
1798
|
-
slice: '${name}',
|
|
1799
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1800
|
-
},
|
|
1801
|
-
metricName: 'OutboxBacklog',
|
|
1802
|
-
namespace: 'EBK/Async',
|
|
1803
|
-
unit: 'Count',
|
|
1804
|
-
value,
|
|
1805
|
-
});
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
interface OutboxRelayDependencies {
|
|
1809
|
-
logger: Logger;
|
|
1810
|
-
metrics: MetricEmitter;
|
|
1811
|
-
publish(entries: PutEventsRequestEntry[]): Promise<{
|
|
1812
|
-
Entries?: Array<{ ErrorCode?: string; EventId?: string }>;
|
|
1813
|
-
}>;
|
|
1814
|
-
withDatabase: typeof withPrimaryDatabase;
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
export function createOutboxRelayHandler(
|
|
1818
|
-
dependencies: OutboxRelayDependencies,
|
|
1819
|
-
) {
|
|
1820
|
-
return async (event: { id?: string } = {}) => {
|
|
1821
|
-
const correlationId = event.id ?? randomUUID();
|
|
1822
|
-
return dependencies.withDatabase(
|
|
1823
|
-
{ correlationId },
|
|
1824
|
-
'async-outbox-relay',
|
|
1825
|
-
async (client) => {
|
|
1826
|
-
await client.query('BEGIN');
|
|
1827
|
-
try {
|
|
1828
|
-
const isolatedPerformance = await client.query<{ effectId: string }>(
|
|
1829
|
-
\`WITH claimed AS (
|
|
1830
|
-
SELECT effect_id
|
|
1831
|
-
FROM performance_load_async_effects
|
|
1832
|
-
WHERE processed_at IS NULL AND expires_at > now()
|
|
1833
|
-
ORDER BY created_at, effect_id
|
|
1834
|
-
LIMIT 10
|
|
1835
|
-
FOR UPDATE SKIP LOCKED
|
|
1836
|
-
)
|
|
1837
|
-
UPDATE performance_load_async_effects AS effects
|
|
1838
|
-
SET processed_at = now()
|
|
1839
|
-
FROM claimed
|
|
1840
|
-
WHERE effects.effect_id = claimed.effect_id
|
|
1841
|
-
RETURNING effects.effect_id AS "effectId"\`,
|
|
1842
|
-
);
|
|
1843
|
-
const processedPerformance = isolatedPerformance.rows.length;
|
|
1844
|
-
const pending = await client.query<OutboxRow>(
|
|
1845
|
-
\`SELECT idempotency_key AS "idempotencyKey",
|
|
1846
|
-
event_json AS "eventJson"
|
|
1847
|
-
FROM data_lifecycle_outbox
|
|
1848
|
-
WHERE published_at IS NULL AND expires_at > now()
|
|
1849
|
-
ORDER BY created_at, idempotency_key
|
|
1850
|
-
LIMIT 10
|
|
1851
|
-
FOR UPDATE SKIP LOCKED\`,
|
|
1852
|
-
);
|
|
1853
|
-
if (pending.rows.length === 0) {
|
|
1854
|
-
emitOutboxBacklog(dependencies.metrics, 0);
|
|
1855
|
-
await client.query('COMMIT');
|
|
1856
|
-
dependencies.logger.info('Outbox relay converged', {
|
|
1857
|
-
attributes: { processedPerformance },
|
|
1858
|
-
context: { correlationId },
|
|
1859
|
-
outcome: 'success',
|
|
1860
|
-
});
|
|
1861
|
-
return {
|
|
1862
|
-
failed: 0,
|
|
1863
|
-
outcome: 'converged',
|
|
1864
|
-
processedPerformance,
|
|
1865
|
-
published: 0,
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
const result = await dependencies.publish(
|
|
1869
|
-
pending.rows.map(({ eventJson }) => eventJson),
|
|
1870
|
-
);
|
|
1871
|
-
let failed = 0;
|
|
1872
|
-
let published = 0;
|
|
1873
|
-
for (const [index, row] of pending.rows.entries()) {
|
|
1874
|
-
const entry = result.Entries?.[index];
|
|
1875
|
-
if (!entry?.EventId || entry.ErrorCode) {
|
|
1876
|
-
failed += 1;
|
|
1877
|
-
await client.query(
|
|
1878
|
-
\`UPDATE data_lifecycle_outbox
|
|
1879
|
-
SET attempts = attempts + 1,
|
|
1880
|
-
last_error_code = $2
|
|
1881
|
-
WHERE idempotency_key = $1\`,
|
|
1882
|
-
[row.idempotencyKey, entry?.ErrorCode ?? 'event-publication-failed'],
|
|
1883
|
-
);
|
|
1884
|
-
continue;
|
|
1885
|
-
}
|
|
1886
|
-
published += 1;
|
|
1887
|
-
await client.query(
|
|
1888
|
-
\`UPDATE data_lifecycle_outbox
|
|
1889
|
-
SET attempts = attempts + 1, last_error_code = NULL,
|
|
1890
|
-
published_at = now()
|
|
1891
|
-
WHERE idempotency_key = $1 AND published_at IS NULL\`,
|
|
1892
|
-
[row.idempotencyKey],
|
|
1893
|
-
);
|
|
1894
|
-
}
|
|
1895
|
-
const remaining = await client.query<{ count: string }>(
|
|
1896
|
-
\`SELECT count(*)::text AS count
|
|
1897
|
-
FROM data_lifecycle_outbox
|
|
1898
|
-
WHERE published_at IS NULL AND expires_at > now()\`,
|
|
1899
|
-
);
|
|
1900
|
-
emitOutboxBacklog(
|
|
1901
|
-
dependencies.metrics,
|
|
1902
|
-
Number(remaining.rows[0]?.count ?? 0),
|
|
1903
|
-
);
|
|
1904
|
-
await client.query('COMMIT');
|
|
1905
|
-
dependencies.logger.info('Outbox relay batch completed', {
|
|
1906
|
-
attributes: { failed, processedPerformance, published },
|
|
1907
|
-
context: { correlationId },
|
|
1908
|
-
outcome: failed === 0 ? 'success' : 'failure',
|
|
1909
|
-
});
|
|
1910
|
-
return {
|
|
1911
|
-
failed,
|
|
1912
|
-
outcome: failed === 0 ? 'converged' : 'retry-required',
|
|
1913
|
-
processedPerformance,
|
|
1914
|
-
published,
|
|
1915
|
-
};
|
|
1916
|
-
} catch (error) {
|
|
1917
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
1918
|
-
dependencies.logger.error('Outbox relay batch failed', {
|
|
1919
|
-
context: { correlationId },
|
|
1920
|
-
error,
|
|
1921
|
-
outcome: 'failure',
|
|
1922
|
-
});
|
|
1923
|
-
throw error;
|
|
1924
|
-
}
|
|
1925
|
-
},
|
|
1926
|
-
);
|
|
1927
|
-
};
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
const logger = createLogger({
|
|
1931
|
-
component: 'outbox-relay',
|
|
1932
|
-
feature: 'async-work',
|
|
1933
|
-
operation: 'outbox-relay',
|
|
1934
|
-
slice: '${name}',
|
|
1935
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1936
|
-
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
1937
|
-
});
|
|
1938
|
-
|
|
1939
|
-
export const handler = createOutboxRelayHandler({
|
|
1940
|
-
logger,
|
|
1941
|
-
metrics: createMetricEmitter(),
|
|
1942
|
-
publish: (Entries) => eventBridge.send(new PutEventsCommand({ Entries })),
|
|
1943
|
-
withDatabase: withPrimaryDatabase,
|
|
1944
|
-
});
|
|
1945
|
-
`,
|
|
1946
|
-
'async/inbox-cleanup.ts': `import { randomUUID } from 'node:crypto';
|
|
1947
|
-
import { createLogger, type Logger } from '@empire-builder-kit/runtime/logging';
|
|
1948
|
-
import {
|
|
1949
|
-
createMetricEmitter,
|
|
1950
|
-
type MetricEmitter,
|
|
1951
|
-
} from '@empire-builder-kit/runtime/observability';
|
|
1952
|
-
import type { Client as PgClient } from 'pg';
|
|
1953
|
-
import { withPrimaryDatabase } from '../database/runtime-client.js';
|
|
1954
|
-
|
|
1955
|
-
const CLEANUP_BATCH_SIZE = 1_000;
|
|
1956
|
-
const CLEANUP_MAXIMUM_ROWS = 10_000;
|
|
1957
|
-
const CLEANUP_MAXIMUM_RUNTIME_MILLISECONDS = 4 * 60_000;
|
|
1958
|
-
const PERFORMANCE_CLEANUP_BATCH_SIZE = 500;
|
|
1959
|
-
const PERFORMANCE_CLEANUP_MAXIMUM_ROWS = 5_000;
|
|
1960
|
-
const PERFORMANCE_CLEANUP_DEADLINE_HOURS = 24;
|
|
1961
|
-
const PERFORMANCE_CLEANUP_FEATURE = 'performance-load';
|
|
1962
|
-
const PERFORMANCE_CLEANUP_MAXIMUM_PENDING_REPORTS = 10_000;
|
|
1963
|
-
const PERFORMANCE_AUDIT_RETENTION_DAYS = 30;
|
|
1964
|
-
|
|
1965
|
-
interface InboxCleanupDependencies {
|
|
1966
|
-
logger: Logger;
|
|
1967
|
-
metrics: MetricEmitter;
|
|
1968
|
-
now(): number;
|
|
1969
|
-
withDatabase: typeof withPrimaryDatabase;
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
function emitInboxCleanupBacklog(metrics: MetricEmitter, value: number) {
|
|
1973
|
-
metrics.emit({
|
|
1974
|
-
dimensions: {
|
|
1975
|
-
component: 'inbox-cleanup',
|
|
1976
|
-
feature: 'async-work',
|
|
1977
|
-
operation: 'inbox-cleanup',
|
|
1978
|
-
slice: '${name}',
|
|
1979
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1980
|
-
},
|
|
1981
|
-
metricName: 'InboxCleanupBacklog',
|
|
1982
|
-
namespace: 'EBK/Async',
|
|
1983
|
-
unit: 'Count',
|
|
1984
|
-
value,
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
function emitPerformanceCleanupBacklog(
|
|
1989
|
-
metrics: MetricEmitter,
|
|
1990
|
-
value: number,
|
|
1991
|
-
) {
|
|
1992
|
-
metrics.emit({
|
|
1993
|
-
dimensions: {
|
|
1994
|
-
component: 'performance-load-cleanup',
|
|
1995
|
-
feature: 'performance',
|
|
1996
|
-
operation: 'expiry-cleanup',
|
|
1997
|
-
slice: '${name}',
|
|
1998
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
1999
|
-
},
|
|
2000
|
-
metricName: 'PerformanceLoadCleanupBacklog',
|
|
2001
|
-
namespace: 'EBK/Performance',
|
|
2002
|
-
unit: 'Count',
|
|
2003
|
-
value,
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
function emitPerformanceCleanupDeadlineBreaches(
|
|
2008
|
-
metrics: MetricEmitter,
|
|
2009
|
-
value: number,
|
|
2010
|
-
) {
|
|
2011
|
-
return metrics.emit({
|
|
2012
|
-
dimensions: {
|
|
2013
|
-
component: 'performance-load-cleanup',
|
|
2014
|
-
feature: 'performance',
|
|
2015
|
-
operation: 'expiry-cleanup',
|
|
2016
|
-
slice: '${name}',
|
|
2017
|
-
stage: process.env.EBK_STAGE ?? 'local',
|
|
2018
|
-
},
|
|
2019
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
2020
|
-
namespace: 'EBK/Performance',
|
|
2021
|
-
unit: 'Count',
|
|
2022
|
-
value,
|
|
2023
|
-
});
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
async function reportPerformanceCleanupDeadlineBreaches(
|
|
2027
|
-
client: PgClient,
|
|
2028
|
-
metrics: MetricEmitter,
|
|
2029
|
-
): Promise<{ continuationRequired: boolean; reported: number }> {
|
|
2030
|
-
await client.query('BEGIN');
|
|
2031
|
-
try {
|
|
2032
|
-
const pending = await client.query<{ resourceRef: string }>(
|
|
2033
|
-
\`SELECT resource_ref AS "resourceRef"
|
|
2034
|
-
FROM cleanup_deadline_reports
|
|
2035
|
-
WHERE feature = $1
|
|
2036
|
-
ORDER BY observed_at, resource_ref
|
|
2037
|
-
LIMIT $2
|
|
2038
|
-
FOR UPDATE\`,
|
|
2039
|
-
[PERFORMANCE_CLEANUP_FEATURE, PERFORMANCE_CLEANUP_MAXIMUM_ROWS + 1],
|
|
2040
|
-
);
|
|
2041
|
-
const selected = pending.rows.slice(0, PERFORMANCE_CLEANUP_MAXIMUM_ROWS);
|
|
2042
|
-
if (
|
|
2043
|
-
emitPerformanceCleanupDeadlineBreaches(
|
|
2044
|
-
metrics,
|
|
2045
|
-
selected.length,
|
|
2046
|
-
) === undefined
|
|
2047
|
-
) {
|
|
2048
|
-
throw new Error(
|
|
2049
|
-
'Performance cleanup deadline metric delivery failed.',
|
|
2050
|
-
);
|
|
2051
|
-
}
|
|
2052
|
-
if (selected.length > 0) {
|
|
2053
|
-
await client.query(
|
|
2054
|
-
\`DELETE FROM cleanup_deadline_reports
|
|
2055
|
-
WHERE feature = $1 AND resource_ref = ANY($2::text[])\`,
|
|
2056
|
-
[
|
|
2057
|
-
PERFORMANCE_CLEANUP_FEATURE,
|
|
2058
|
-
selected.map(({ resourceRef }) => resourceRef),
|
|
2059
|
-
],
|
|
2060
|
-
);
|
|
2061
|
-
}
|
|
2062
|
-
await client.query('COMMIT');
|
|
2063
|
-
return {
|
|
2064
|
-
continuationRequired:
|
|
2065
|
-
pending.rows.length > PERFORMANCE_CLEANUP_MAXIMUM_ROWS,
|
|
2066
|
-
reported: selected.length,
|
|
2067
|
-
};
|
|
2068
|
-
} catch (error) {
|
|
2069
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
2070
|
-
throw error;
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
async function recordPerformanceCleanupDeadlineReports(
|
|
2075
|
-
client: PgClient,
|
|
2076
|
-
resourceKind: 'async-effect' | 'record',
|
|
2077
|
-
resources: readonly { expiresAt: Date; resourceId: string }[],
|
|
2078
|
-
): Promise<void> {
|
|
2079
|
-
if (resources.length === 0) return;
|
|
2080
|
-
const recorded = await client.query<{
|
|
2081
|
-
candidateCount: string;
|
|
2082
|
-
recordedCount: string;
|
|
2083
|
-
}>(
|
|
2084
|
-
\`WITH deadline_candidates AS (
|
|
2085
|
-
SELECT
|
|
2086
|
-
$1 || ':' || resource_id AS resource_ref,
|
|
2087
|
-
expires_at + make_interval(hours => $2) AS deadline_at
|
|
2088
|
-
FROM unnest($3::text[], $4::timestamptz[])
|
|
2089
|
-
AS deleted(resource_id, expires_at)
|
|
2090
|
-
WHERE expires_at + make_interval(hours => $2) <= clock_timestamp()
|
|
2091
|
-
), recorded AS (
|
|
2092
|
-
INSERT INTO cleanup_deadline_reports
|
|
2093
|
-
(deadline_at, feature, resource_ref)
|
|
2094
|
-
SELECT deadline_at, $5, resource_ref
|
|
2095
|
-
FROM deadline_candidates
|
|
2096
|
-
ON CONFLICT (feature, resource_ref) DO UPDATE
|
|
2097
|
-
SET deadline_at = cleanup_deadline_reports.deadline_at
|
|
2098
|
-
WHERE cleanup_deadline_reports.deadline_at = EXCLUDED.deadline_at
|
|
2099
|
-
RETURNING resource_ref
|
|
2100
|
-
)
|
|
2101
|
-
SELECT
|
|
2102
|
-
(SELECT count(*)::text FROM deadline_candidates)
|
|
2103
|
-
AS "candidateCount",
|
|
2104
|
-
(SELECT count(*)::text FROM recorded) AS "recordedCount"\`,
|
|
2105
|
-
[
|
|
2106
|
-
resourceKind,
|
|
2107
|
-
PERFORMANCE_CLEANUP_DEADLINE_HOURS,
|
|
2108
|
-
resources.map(({ resourceId }) => resourceId),
|
|
2109
|
-
resources.map(({ expiresAt }) => expiresAt),
|
|
2110
|
-
PERFORMANCE_CLEANUP_FEATURE,
|
|
2111
|
-
],
|
|
2112
|
-
);
|
|
2113
|
-
if (
|
|
2114
|
-
recorded.rows[0]?.candidateCount !== recorded.rows[0]?.recordedCount
|
|
2115
|
-
) {
|
|
2116
|
-
throw new Error(
|
|
2117
|
-
'Persisted performance cleanup deadline report is inconsistent.',
|
|
2118
|
-
);
|
|
2119
|
-
}
|
|
2120
|
-
const pending = await client.query<{ count: string }>(
|
|
2121
|
-
\`SELECT count(*)::text AS count
|
|
2122
|
-
FROM (
|
|
2123
|
-
SELECT 1
|
|
2124
|
-
FROM cleanup_deadline_reports
|
|
2125
|
-
WHERE feature = $1
|
|
2126
|
-
LIMIT $2
|
|
2127
|
-
) AS bounded_pending_reports\`,
|
|
2128
|
-
[
|
|
2129
|
-
PERFORMANCE_CLEANUP_FEATURE,
|
|
2130
|
-
PERFORMANCE_CLEANUP_MAXIMUM_PENDING_REPORTS + 1,
|
|
2131
|
-
],
|
|
2132
|
-
);
|
|
2133
|
-
if (
|
|
2134
|
-
Number(pending.rows[0]?.count ?? 0) >
|
|
2135
|
-
PERFORMANCE_CLEANUP_MAXIMUM_PENDING_REPORTS
|
|
2136
|
-
) {
|
|
2137
|
-
throw new Error(
|
|
2138
|
-
'Performance cleanup pending deadline report inventory was exceeded.',
|
|
2139
|
-
);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
export function createInboxCleanupHandler(
|
|
2144
|
-
dependencies: InboxCleanupDependencies,
|
|
2145
|
-
) {
|
|
2146
|
-
return async (event: { id?: string } = {}) => {
|
|
2147
|
-
const correlationId = event.id ?? randomUUID();
|
|
2148
|
-
const startedAt = dependencies.now();
|
|
2149
|
-
return dependencies.withDatabase(
|
|
2150
|
-
{ correlationId },
|
|
2151
|
-
'async-inbox-cleanup',
|
|
2152
|
-
async (client) => {
|
|
2153
|
-
let remainingExpired = 0;
|
|
2154
|
-
let remainingPerformanceExpired = 0;
|
|
2155
|
-
let reportedPerformanceDeadlineBreaches = 0;
|
|
2156
|
-
let reportContinuationRequired = false;
|
|
2157
|
-
let reportFailure: unknown;
|
|
2158
|
-
let removedTotal = 0;
|
|
2159
|
-
let removedPerformanceTotal = 0;
|
|
2160
|
-
try {
|
|
2161
|
-
const reportPendingBreaches = async () => {
|
|
2162
|
-
try {
|
|
2163
|
-
const result =
|
|
2164
|
-
await reportPerformanceCleanupDeadlineBreaches(
|
|
2165
|
-
client,
|
|
2166
|
-
dependencies.metrics,
|
|
2167
|
-
);
|
|
2168
|
-
reportedPerformanceDeadlineBreaches += result.reported;
|
|
2169
|
-
reportContinuationRequired ||=
|
|
2170
|
-
result.continuationRequired;
|
|
2171
|
-
} catch (error) {
|
|
2172
|
-
reportFailure ??= error;
|
|
2173
|
-
}
|
|
2174
|
-
};
|
|
2175
|
-
await reportPendingBreaches();
|
|
2176
|
-
for (;;) {
|
|
2177
|
-
await client.query('BEGIN');
|
|
2178
|
-
const inboxLimit = Math.min(
|
|
2179
|
-
CLEANUP_BATCH_SIZE,
|
|
2180
|
-
CLEANUP_MAXIMUM_ROWS - removedTotal,
|
|
2181
|
-
);
|
|
2182
|
-
const removed =
|
|
2183
|
-
inboxLimit > 0
|
|
2184
|
-
? await client.query<{ messageId: string }>(
|
|
2185
|
-
\`WITH expired AS (
|
|
2186
|
-
SELECT handler_id, message_id
|
|
2187
|
-
FROM ebk_inbox
|
|
2188
|
-
WHERE expires_at <= now()
|
|
2189
|
-
AND (
|
|
2190
|
-
status = 'succeeded'
|
|
2191
|
-
OR (
|
|
2192
|
-
status = 'failed'
|
|
2193
|
-
AND (
|
|
2194
|
-
result_reference LIKE 'terminal-permanent-%'
|
|
2195
|
-
OR result_reference LIKE 'terminal-reconciliation-%'
|
|
2196
|
-
OR result_reference IN (
|
|
2197
|
-
'configuration-disabled',
|
|
2198
|
-
'configuration-invalid'
|
|
2199
|
-
)
|
|
2200
|
-
)
|
|
2201
|
-
)
|
|
2202
|
-
)
|
|
2203
|
-
ORDER BY expires_at, handler_id, message_id
|
|
2204
|
-
LIMIT $1
|
|
2205
|
-
FOR UPDATE SKIP LOCKED
|
|
2206
|
-
)
|
|
2207
|
-
DELETE FROM ebk_inbox AS inbox
|
|
2208
|
-
USING expired
|
|
2209
|
-
WHERE inbox.handler_id = expired.handler_id
|
|
2210
|
-
AND inbox.message_id = expired.message_id
|
|
2211
|
-
RETURNING inbox.message_id AS "messageId"\`,
|
|
2212
|
-
[inboxLimit],
|
|
2213
|
-
)
|
|
2214
|
-
: { rowCount: 0, rows: [] };
|
|
2215
|
-
|
|
2216
|
-
let removedPerformanceThisPage = 0;
|
|
2217
|
-
let performanceLimit = Math.min(
|
|
2218
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2219
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2220
|
-
removedPerformanceTotal -
|
|
2221
|
-
removedPerformanceThisPage,
|
|
2222
|
-
);
|
|
2223
|
-
if (performanceLimit > 0) {
|
|
2224
|
-
const expiredEffects = await client.query<{
|
|
2225
|
-
expiresAt: Date;
|
|
2226
|
-
resourceId: string;
|
|
2227
|
-
}>(
|
|
2228
|
-
\`WITH expired AS (
|
|
2229
|
-
SELECT effect_id, expires_at
|
|
2230
|
-
FROM performance_load_async_effects
|
|
2231
|
-
WHERE expires_at <= now()
|
|
2232
|
-
ORDER BY expires_at, effect_id
|
|
2233
|
-
LIMIT $1
|
|
2234
|
-
FOR UPDATE SKIP LOCKED
|
|
2235
|
-
)
|
|
2236
|
-
DELETE FROM performance_load_async_effects AS effects
|
|
2237
|
-
USING expired
|
|
2238
|
-
WHERE effects.effect_id = expired.effect_id
|
|
2239
|
-
RETURNING effects.effect_id AS "resourceId",
|
|
2240
|
-
effects.expires_at AS "expiresAt"\`,
|
|
2241
|
-
[performanceLimit],
|
|
2242
|
-
);
|
|
2243
|
-
await recordPerformanceCleanupDeadlineReports(
|
|
2244
|
-
client,
|
|
2245
|
-
'async-effect',
|
|
2246
|
-
expiredEffects.rows,
|
|
2247
|
-
);
|
|
2248
|
-
removedPerformanceThisPage += expiredEffects.rows.length;
|
|
2249
|
-
}
|
|
2250
|
-
performanceLimit = Math.min(
|
|
2251
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2252
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2253
|
-
removedPerformanceTotal -
|
|
2254
|
-
removedPerformanceThisPage,
|
|
2255
|
-
);
|
|
2256
|
-
if (performanceLimit > 0) {
|
|
2257
|
-
const expiredRecords = await client.query<{
|
|
2258
|
-
expiresAt: Date;
|
|
2259
|
-
resourceId: string;
|
|
2260
|
-
}>(
|
|
2261
|
-
\`WITH expired AS (
|
|
2262
|
-
SELECT id, test_expires_at
|
|
2263
|
-
FROM records
|
|
2264
|
-
WHERE owner_id = 'ebk-performance'
|
|
2265
|
-
AND name = 'ebk-protected-performance-load'
|
|
2266
|
-
AND test_expires_at <= now()
|
|
2267
|
-
ORDER BY test_expires_at, id
|
|
2268
|
-
LIMIT $1
|
|
2269
|
-
FOR UPDATE SKIP LOCKED
|
|
2270
|
-
)
|
|
2271
|
-
DELETE FROM records AS records
|
|
2272
|
-
USING expired
|
|
2273
|
-
WHERE records.id = expired.id
|
|
2274
|
-
AND records.owner_id = 'ebk-performance'
|
|
2275
|
-
AND records.name = 'ebk-protected-performance-load'
|
|
2276
|
-
AND records.test_expires_at <= now()
|
|
2277
|
-
RETURNING records.id::text AS "resourceId",
|
|
2278
|
-
records.test_expires_at AS "expiresAt"\`,
|
|
2279
|
-
[performanceLimit],
|
|
2280
|
-
);
|
|
2281
|
-
await recordPerformanceCleanupDeadlineReports(
|
|
2282
|
-
client,
|
|
2283
|
-
'record',
|
|
2284
|
-
expiredRecords.rows,
|
|
2285
|
-
);
|
|
2286
|
-
removedPerformanceThisPage += expiredRecords.rows.length;
|
|
2287
|
-
}
|
|
2288
|
-
performanceLimit = Math.min(
|
|
2289
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2290
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2291
|
-
removedPerformanceTotal -
|
|
2292
|
-
removedPerformanceThisPage,
|
|
2293
|
-
);
|
|
2294
|
-
if (performanceLimit > 0) {
|
|
2295
|
-
const expiredRequests = await client.query(
|
|
2296
|
-
\`WITH expired AS (
|
|
2297
|
-
SELECT request.plan_digest, request.request_ordinal
|
|
2298
|
-
FROM performance_load_requests AS request
|
|
2299
|
-
INNER JOIN performance_load_runs AS run
|
|
2300
|
-
ON run.plan_digest = request.plan_digest
|
|
2301
|
-
WHERE run.expires_at <=
|
|
2302
|
-
now() - make_interval(days => $2)
|
|
2303
|
-
ORDER BY run.expires_at, request.plan_digest,
|
|
2304
|
-
request.request_ordinal
|
|
2305
|
-
LIMIT $1
|
|
2306
|
-
FOR UPDATE OF request SKIP LOCKED
|
|
2307
|
-
)
|
|
2308
|
-
DELETE FROM performance_load_requests AS request
|
|
2309
|
-
USING expired
|
|
2310
|
-
WHERE request.plan_digest = expired.plan_digest
|
|
2311
|
-
AND request.request_ordinal = expired.request_ordinal\`,
|
|
2312
|
-
[performanceLimit, PERFORMANCE_AUDIT_RETENTION_DAYS],
|
|
2313
|
-
);
|
|
2314
|
-
removedPerformanceThisPage += expiredRequests.rowCount ?? 0;
|
|
2315
|
-
}
|
|
2316
|
-
performanceLimit = Math.min(
|
|
2317
|
-
PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2318
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS -
|
|
2319
|
-
removedPerformanceTotal -
|
|
2320
|
-
removedPerformanceThisPage,
|
|
2321
|
-
);
|
|
2322
|
-
if (performanceLimit > 0) {
|
|
2323
|
-
const expiredRuns = await client.query(
|
|
2324
|
-
\`WITH expired AS (
|
|
2325
|
-
SELECT run.plan_digest
|
|
2326
|
-
FROM performance_load_runs AS run
|
|
2327
|
-
WHERE run.expires_at <=
|
|
2328
|
-
now() - make_interval(days => $2)
|
|
2329
|
-
AND NOT EXISTS (
|
|
2330
|
-
SELECT 1
|
|
2331
|
-
FROM performance_load_requests AS request
|
|
2332
|
-
WHERE request.plan_digest = run.plan_digest
|
|
2333
|
-
)
|
|
2334
|
-
AND NOT EXISTS (
|
|
2335
|
-
SELECT 1
|
|
2336
|
-
FROM performance_load_async_effects AS effects
|
|
2337
|
-
WHERE effects.plan_digest = run.plan_digest
|
|
2338
|
-
)
|
|
2339
|
-
AND NOT EXISTS (
|
|
2340
|
-
SELECT 1
|
|
2341
|
-
FROM records AS performance_record
|
|
2342
|
-
WHERE performance_record.owner_id = 'ebk-performance'
|
|
2343
|
-
AND performance_record.name =
|
|
2344
|
-
'ebk-protected-performance-load'
|
|
2345
|
-
AND performance_record.run_scope = run.plan_digest
|
|
2346
|
-
)
|
|
2347
|
-
ORDER BY run.expires_at, run.plan_digest
|
|
2348
|
-
LIMIT $1
|
|
2349
|
-
FOR UPDATE OF run SKIP LOCKED
|
|
2350
|
-
)
|
|
2351
|
-
DELETE FROM performance_load_runs AS run
|
|
2352
|
-
USING expired
|
|
2353
|
-
WHERE run.plan_digest = expired.plan_digest\`,
|
|
2354
|
-
[performanceLimit, PERFORMANCE_AUDIT_RETENTION_DAYS],
|
|
2355
|
-
);
|
|
2356
|
-
removedPerformanceThisPage += expiredRuns.rowCount ?? 0;
|
|
2357
|
-
}
|
|
2358
|
-
const remaining = await client.query<{ count: string }>(
|
|
2359
|
-
\`SELECT count(*)::text AS count
|
|
2360
|
-
FROM ebk_inbox
|
|
2361
|
-
WHERE expires_at <= now()
|
|
2362
|
-
AND (
|
|
2363
|
-
status = 'succeeded'
|
|
2364
|
-
OR (
|
|
2365
|
-
status = 'failed'
|
|
2366
|
-
AND (
|
|
2367
|
-
result_reference LIKE 'terminal-permanent-%'
|
|
2368
|
-
OR result_reference LIKE 'terminal-reconciliation-%'
|
|
2369
|
-
OR result_reference IN (
|
|
2370
|
-
'configuration-disabled',
|
|
2371
|
-
'configuration-invalid'
|
|
2372
|
-
)
|
|
2373
|
-
)
|
|
2374
|
-
)
|
|
2375
|
-
)\`,
|
|
2376
|
-
);
|
|
2377
|
-
const remainingPerformance = await client.query<{
|
|
2378
|
-
count: string;
|
|
2379
|
-
}>(
|
|
2380
|
-
\`SELECT count(*)::text AS count
|
|
2381
|
-
FROM (
|
|
2382
|
-
SELECT 1
|
|
2383
|
-
FROM performance_load_async_effects
|
|
2384
|
-
WHERE expires_at <= now()
|
|
2385
|
-
UNION ALL
|
|
2386
|
-
SELECT 1
|
|
2387
|
-
FROM records
|
|
2388
|
-
WHERE owner_id = 'ebk-performance'
|
|
2389
|
-
AND name = 'ebk-protected-performance-load'
|
|
2390
|
-
AND test_expires_at <= now()
|
|
2391
|
-
UNION ALL
|
|
2392
|
-
SELECT 1
|
|
2393
|
-
FROM performance_load_requests AS request
|
|
2394
|
-
INNER JOIN performance_load_runs AS run
|
|
2395
|
-
ON run.plan_digest = request.plan_digest
|
|
2396
|
-
WHERE run.expires_at <=
|
|
2397
|
-
now() - make_interval(days => $1)
|
|
2398
|
-
UNION ALL
|
|
2399
|
-
SELECT 1
|
|
2400
|
-
FROM performance_load_runs AS run
|
|
2401
|
-
WHERE run.expires_at <=
|
|
2402
|
-
now() - make_interval(days => $1)
|
|
2403
|
-
AND NOT EXISTS (
|
|
2404
|
-
SELECT 1
|
|
2405
|
-
FROM performance_load_requests AS request
|
|
2406
|
-
WHERE request.plan_digest = run.plan_digest
|
|
2407
|
-
)
|
|
2408
|
-
AND NOT EXISTS (
|
|
2409
|
-
SELECT 1
|
|
2410
|
-
FROM performance_load_async_effects AS effects
|
|
2411
|
-
WHERE effects.plan_digest = run.plan_digest
|
|
2412
|
-
)
|
|
2413
|
-
AND NOT EXISTS (
|
|
2414
|
-
SELECT 1
|
|
2415
|
-
FROM records AS performance_record
|
|
2416
|
-
WHERE performance_record.owner_id = 'ebk-performance'
|
|
2417
|
-
AND performance_record.name =
|
|
2418
|
-
'ebk-protected-performance-load'
|
|
2419
|
-
AND performance_record.run_scope = run.plan_digest
|
|
2420
|
-
)
|
|
2421
|
-
LIMIT $2
|
|
2422
|
-
) AS bounded_backlog\`,
|
|
2423
|
-
[
|
|
2424
|
-
PERFORMANCE_AUDIT_RETENTION_DAYS,
|
|
2425
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS + 1,
|
|
2426
|
-
],
|
|
2427
|
-
);
|
|
2428
|
-
await client.query('COMMIT');
|
|
2429
|
-
const removedThisBatch = removed.rowCount ?? 0;
|
|
2430
|
-
removedTotal += removedThisBatch;
|
|
2431
|
-
removedPerformanceTotal += removedPerformanceThisPage;
|
|
2432
|
-
remainingExpired = Number(remaining.rows[0]?.count ?? 0);
|
|
2433
|
-
remainingPerformanceExpired = Number(
|
|
2434
|
-
remainingPerformance.rows[0]?.count ?? 0,
|
|
2435
|
-
);
|
|
2436
|
-
const inboxCanContinue =
|
|
2437
|
-
remainingExpired > 0 &&
|
|
2438
|
-
removedTotal < CLEANUP_MAXIMUM_ROWS;
|
|
2439
|
-
const performanceCanContinue =
|
|
2440
|
-
remainingPerformanceExpired > 0 &&
|
|
2441
|
-
removedPerformanceTotal <
|
|
2442
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS;
|
|
2443
|
-
if (
|
|
2444
|
-
(!inboxCanContinue && !performanceCanContinue) ||
|
|
2445
|
-
removedThisBatch + removedPerformanceThisPage === 0 ||
|
|
2446
|
-
dependencies.now() - startedAt >=
|
|
2447
|
-
CLEANUP_MAXIMUM_RUNTIME_MILLISECONDS
|
|
2448
|
-
) {
|
|
2449
|
-
break;
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
emitInboxCleanupBacklog(dependencies.metrics, remainingExpired);
|
|
2453
|
-
emitPerformanceCleanupBacklog(
|
|
2454
|
-
dependencies.metrics,
|
|
2455
|
-
remainingPerformanceExpired,
|
|
2456
|
-
);
|
|
2457
|
-
await reportPendingBreaches();
|
|
2458
|
-
if (reportFailure !== undefined) {
|
|
2459
|
-
throw reportFailure;
|
|
2460
|
-
}
|
|
2461
|
-
if (
|
|
2462
|
-
reportedPerformanceDeadlineBreaches > 0 ||
|
|
2463
|
-
reportContinuationRequired
|
|
2464
|
-
) {
|
|
2465
|
-
throw new Error('performance-load-cleanup-deadline-breached');
|
|
2466
|
-
}
|
|
2467
|
-
dependencies.logger.info('Async inbox cleanup completed', {
|
|
2468
|
-
attributes: {
|
|
2469
|
-
batchLimit: CLEANUP_BATCH_SIZE,
|
|
2470
|
-
invocationLimit: CLEANUP_MAXIMUM_ROWS,
|
|
2471
|
-
performanceAuditRetentionDays:
|
|
2472
|
-
PERFORMANCE_AUDIT_RETENTION_DAYS,
|
|
2473
|
-
performanceBatchLimit: PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2474
|
-
performanceCleanupDeadlineHours:
|
|
2475
|
-
PERFORMANCE_CLEANUP_DEADLINE_HOURS,
|
|
2476
|
-
performanceInvocationLimit:
|
|
2477
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS,
|
|
2478
|
-
reportedPerformanceDeadlineBreaches,
|
|
2479
|
-
remainingPerformanceExpired,
|
|
2480
|
-
remainingExpired,
|
|
2481
|
-
removedPerformance: removedPerformanceTotal,
|
|
2482
|
-
removed: removedTotal,
|
|
2483
|
-
},
|
|
2484
|
-
context: { correlationId },
|
|
2485
|
-
outcome:
|
|
2486
|
-
remainingExpired === 0 &&
|
|
2487
|
-
remainingPerformanceExpired === 0
|
|
2488
|
-
? 'success'
|
|
2489
|
-
: 'unknown',
|
|
2490
|
-
});
|
|
2491
|
-
return {
|
|
2492
|
-
batchLimit: CLEANUP_BATCH_SIZE,
|
|
2493
|
-
invocationLimit: CLEANUP_MAXIMUM_ROWS,
|
|
2494
|
-
performanceAuditRetentionDays:
|
|
2495
|
-
PERFORMANCE_AUDIT_RETENTION_DAYS,
|
|
2496
|
-
performanceBatchLimit: PERFORMANCE_CLEANUP_BATCH_SIZE,
|
|
2497
|
-
performanceCleanupDeadlineHours:
|
|
2498
|
-
PERFORMANCE_CLEANUP_DEADLINE_HOURS,
|
|
2499
|
-
performanceInvocationLimit:
|
|
2500
|
-
PERFORMANCE_CLEANUP_MAXIMUM_ROWS,
|
|
2501
|
-
reportedPerformanceDeadlineBreaches,
|
|
2502
|
-
remainingPerformanceExpired,
|
|
2503
|
-
remainingExpired,
|
|
2504
|
-
removedPerformance: removedPerformanceTotal,
|
|
2505
|
-
removed: removedTotal,
|
|
2506
|
-
};
|
|
2507
|
-
} catch (error) {
|
|
2508
|
-
await client.query('ROLLBACK').catch(() => undefined);
|
|
2509
|
-
dependencies.logger.error('Async inbox cleanup failed', {
|
|
2510
|
-
context: { correlationId },
|
|
2511
|
-
error,
|
|
2512
|
-
outcome: 'failure',
|
|
2513
|
-
});
|
|
2514
|
-
throw error;
|
|
2515
|
-
}
|
|
2516
|
-
},
|
|
2517
|
-
);
|
|
2518
|
-
};
|
|
2519
|
-
}
|
|
1189
|
+
},
|
|
1190
|
+
EbkWorkType: {
|
|
1191
|
+
DataType: 'String',
|
|
1192
|
+
StringValue: 'provider-delivery-v1',
|
|
1193
|
+
},
|
|
1194
|
+
},
|
|
1195
|
+
QueueUrl: Resource.${pascalName}ProviderQueue.url,
|
|
1196
|
+
}),
|
|
1197
|
+
),
|
|
1198
|
+
verify: verifyProviderWebhook,
|
|
1199
|
+
});
|
|
1200
|
+
`,
|
|
1201
|
+
'async/schedule-target.ts': `import {
|
|
1202
|
+
SendMessageCommand,
|
|
1203
|
+
SQSClient,
|
|
1204
|
+
} from '@aws-sdk/client-sqs';
|
|
1205
|
+
import { createLogger } from '@empire-builder-kit/runtime/logging';
|
|
1206
|
+
import { Resource } from 'sst/resource';
|
|
1207
|
+
import {
|
|
1208
|
+
asyncAdapterDeclaration,
|
|
1209
|
+
createAsyncRecoveryAdapters,
|
|
1210
|
+
} from './recovery-adapters.js';
|
|
2520
1211
|
|
|
1212
|
+
const sqs = new SQSClient({ maxAttempts: 2 });
|
|
2521
1213
|
const logger = createLogger({
|
|
2522
|
-
component: '
|
|
1214
|
+
component: 'schedule-target',
|
|
2523
1215
|
feature: 'async-work',
|
|
2524
|
-
operation: '
|
|
1216
|
+
operation: 'schedule-target',
|
|
2525
1217
|
slice: '${name}',
|
|
2526
1218
|
stage: process.env.EBK_STAGE ?? 'local',
|
|
2527
1219
|
workspace: process.env.EBK_WORKSPACE ?? '${name}',
|
|
2528
1220
|
});
|
|
2529
1221
|
|
|
2530
|
-
export
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
1222
|
+
export async function handler(event: { time?: string }) {
|
|
1223
|
+
if (!asyncAdapterDeclaration.configured) {
|
|
1224
|
+
return { accepted: false, reason: 'schedule-not-configured' };
|
|
1225
|
+
}
|
|
1226
|
+
const scheduledAt = event.time ?? new Date().toISOString();
|
|
1227
|
+
const delivery =
|
|
1228
|
+
await createAsyncRecoveryAdapters().createScheduledDelivery({
|
|
1229
|
+
scheduleId: '${name}.provider-reconciliation',
|
|
1230
|
+
scheduledAt,
|
|
1231
|
+
});
|
|
1232
|
+
await sqs.send(
|
|
1233
|
+
new SendMessageCommand({
|
|
1234
|
+
MessageBody: JSON.stringify(delivery),
|
|
1235
|
+
MessageAttributes: {
|
|
1236
|
+
EbkTenant: {
|
|
1237
|
+
DataType: 'String',
|
|
1238
|
+
StringValue: delivery.contract.account,
|
|
1239
|
+
},
|
|
1240
|
+
EbkWorkType: {
|
|
1241
|
+
DataType: 'String',
|
|
1242
|
+
StringValue: 'provider-delivery-v1',
|
|
1243
|
+
},
|
|
1244
|
+
},
|
|
1245
|
+
QueueUrl: Resource.${pascalName}ProviderQueue.url,
|
|
1246
|
+
}),
|
|
1247
|
+
);
|
|
1248
|
+
logger.info('Scheduled provider reconciliation delivery queued', {
|
|
1249
|
+
attributes: { scheduledAt },
|
|
1250
|
+
outcome: 'success',
|
|
1251
|
+
});
|
|
1252
|
+
return { accepted: true, scheduledAt };
|
|
1253
|
+
}
|
|
2536
1254
|
`,
|
|
2537
1255
|
'async/workflow-task.ts': `import {
|
|
2538
1256
|
DeleteItemCommand,
|
|
@@ -3292,6 +2010,7 @@ describe('generated async failure fixture', () => {
|
|
|
3292
2010
|
it,
|
|
3293
2011
|
vi,
|
|
3294
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"}\
|
|
3295
2014
|
import { createInboxCleanupHandler } from '../async/inbox-cleanup.js';
|
|
3296
2015
|
import { createOutboxRelayHandler } from '../async/outbox-relay.js';
|
|
3297
2016
|
import {
|
|
@@ -3329,175 +2048,7 @@ afterEach(() => {
|
|
|
3329
2048
|
});
|
|
3330
2049
|
|
|
3331
2050
|
describe('generated async handlers', () => {
|
|
3332
|
-
|
|
3333
|
-
const delivery = createQueuedProviderDelivery({
|
|
3334
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3335
|
-
operationId: 'operation-one',
|
|
3336
|
-
request: { value: true },
|
|
3337
|
-
});
|
|
3338
|
-
const deliver = vi.fn();
|
|
3339
|
-
const client = {
|
|
3340
|
-
async query(statement: string) {
|
|
3341
|
-
if (statement === 'BEGIN' || statement === 'COMMIT') {
|
|
3342
|
-
return { rowCount: null, rows: [] };
|
|
3343
|
-
}
|
|
3344
|
-
if (statement.includes('INSERT INTO ebk_inbox')) {
|
|
3345
|
-
return { rowCount: 0, rows: [] };
|
|
3346
|
-
}
|
|
3347
|
-
if (statement.includes('SELECT attempts')) {
|
|
3348
|
-
return {
|
|
3349
|
-
rowCount: 1,
|
|
3350
|
-
rows: [{
|
|
3351
|
-
attempts: 1,
|
|
3352
|
-
lastAttemptAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
3353
|
-
payloadDigest: delivery.requestDigest,
|
|
3354
|
-
resultReference: 'effect-started-prior-attempt',
|
|
3355
|
-
status: 'claimed',
|
|
3356
|
-
}],
|
|
3357
|
-
};
|
|
3358
|
-
}
|
|
3359
|
-
if (statement.includes("status = 'failed'")) {
|
|
3360
|
-
return { rowCount: 1, rows: [] };
|
|
3361
|
-
}
|
|
3362
|
-
throw new Error('Unexpected provider query: ' + statement);
|
|
3363
|
-
},
|
|
3364
|
-
};
|
|
3365
|
-
const handler = createProviderWorkerHandler({
|
|
3366
|
-
createAttemptToken: () => 'new-attempt',
|
|
3367
|
-
deliver: deliver as never,
|
|
3368
|
-
evaluateRollout: vi.fn() as never,
|
|
3369
|
-
logger: logger as never,
|
|
3370
|
-
now: () => '2026-01-01T00:03:00.000Z',
|
|
3371
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
3372
|
-
work(client as never)) as never,
|
|
3373
|
-
});
|
|
3374
|
-
|
|
3375
|
-
const response = await handler({
|
|
3376
|
-
Records: [{
|
|
3377
|
-
body: JSON.stringify(delivery),
|
|
3378
|
-
messageId: 'queue-message-one',
|
|
3379
|
-
}],
|
|
3380
|
-
});
|
|
3381
|
-
|
|
3382
|
-
expect(response.batchItemFailures).toEqual([
|
|
3383
|
-
{ itemIdentifier: 'queue-message-one' },
|
|
3384
|
-
]);
|
|
3385
|
-
expect(deliver).not.toHaveBeenCalled();
|
|
3386
|
-
});
|
|
3387
|
-
|
|
3388
|
-
it('rolls back when an unproven terminal reconciliation transition is lost', async () => {
|
|
3389
|
-
const delivery = createQueuedProviderDelivery({
|
|
3390
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3391
|
-
operationId: 'operation-transition-lost',
|
|
3392
|
-
request: { value: true },
|
|
3393
|
-
});
|
|
3394
|
-
const statements: string[] = [];
|
|
3395
|
-
const client = {
|
|
3396
|
-
async query(statement: string) {
|
|
3397
|
-
statements.push(statement);
|
|
3398
|
-
if (statement === 'BEGIN' || statement === 'ROLLBACK') {
|
|
3399
|
-
return { rowCount: null, rows: [] };
|
|
3400
|
-
}
|
|
3401
|
-
if (statement.includes('INSERT INTO ebk_inbox')) {
|
|
3402
|
-
return { rowCount: 0, rows: [] };
|
|
3403
|
-
}
|
|
3404
|
-
if (statement.includes('SELECT attempts')) {
|
|
3405
|
-
return {
|
|
3406
|
-
rowCount: 1,
|
|
3407
|
-
rows: [{
|
|
3408
|
-
attempts: 1,
|
|
3409
|
-
lastAttemptAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
3410
|
-
payloadDigest: delivery.requestDigest,
|
|
3411
|
-
resultReference: 'effect-started-prior-attempt',
|
|
3412
|
-
status: 'claimed',
|
|
3413
|
-
}],
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
|
-
if (statement.includes("status = 'failed'")) {
|
|
3417
|
-
return { rowCount: 0, rows: [] };
|
|
3418
|
-
}
|
|
3419
|
-
throw new Error('Unexpected provider query: ' + statement);
|
|
3420
|
-
},
|
|
3421
|
-
};
|
|
3422
|
-
|
|
3423
|
-
await expect(
|
|
3424
|
-
claimProviderDelivery(
|
|
3425
|
-
client as never,
|
|
3426
|
-
delivery,
|
|
3427
|
-
'2026-01-01T00:03:00.000Z',
|
|
3428
|
-
),
|
|
3429
|
-
).rejects.toThrow('provider-reconciliation-transition-lost');
|
|
3430
|
-
expect(statements).toContain('ROLLBACK');
|
|
3431
|
-
expect(statements).not.toContain('COMMIT');
|
|
3432
|
-
});
|
|
3433
|
-
|
|
3434
|
-
it('retries a stale effect only when provider repeat safety is guaranteed', async () => {
|
|
3435
|
-
const unproven = createQueuedProviderDelivery({
|
|
3436
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
3437
|
-
operationId: 'operation-guaranteed',
|
|
3438
|
-
request: { value: true },
|
|
3439
|
-
});
|
|
3440
|
-
const delivery = {
|
|
3441
|
-
...unproven,
|
|
3442
|
-
contract: {
|
|
3443
|
-
...unproven.contract,
|
|
3444
|
-
idempotencyRepeatSafety: 'provider-guaranteed' as const,
|
|
3445
|
-
},
|
|
3446
|
-
};
|
|
3447
|
-
const client = {
|
|
3448
|
-
async query(statement: string) {
|
|
3449
|
-
if (statement === 'BEGIN' || statement === 'COMMIT') {
|
|
3450
|
-
return { rowCount: null, rows: [] };
|
|
3451
|
-
}
|
|
3452
|
-
if (statement.includes('INSERT INTO ebk_inbox')) {
|
|
3453
|
-
return { rowCount: 0, rows: [] };
|
|
3454
|
-
}
|
|
3455
|
-
if (statement.includes('SELECT attempts')) {
|
|
3456
|
-
return {
|
|
3457
|
-
rowCount: 1,
|
|
3458
|
-
rows: [{
|
|
3459
|
-
attempts: 1,
|
|
3460
|
-
lastAttemptAt: new Date('2026-01-01T00:00:00.000Z'),
|
|
3461
|
-
payloadDigest: delivery.requestDigest,
|
|
3462
|
-
resultReference: 'effect-started-prior-attempt',
|
|
3463
|
-
status: 'claimed',
|
|
3464
|
-
}],
|
|
3465
|
-
};
|
|
3466
|
-
}
|
|
3467
|
-
if (statement.includes('UPDATE ebk_inbox')) {
|
|
3468
|
-
return { rowCount: 1, rows: [] };
|
|
3469
|
-
}
|
|
3470
|
-
throw new Error('Unexpected provider query: ' + statement);
|
|
3471
|
-
},
|
|
3472
|
-
};
|
|
3473
|
-
const deliver = vi.fn().mockResolvedValue({
|
|
3474
|
-
attempts: 1,
|
|
3475
|
-
deliveryId: delivery.deliveryId,
|
|
3476
|
-
outcome: 'confirmed',
|
|
3477
|
-
providerReference: 'provider-reference',
|
|
3478
|
-
statusCode: 202,
|
|
3479
|
-
});
|
|
3480
|
-
const handler = createProviderWorkerHandler({
|
|
3481
|
-
createAttemptToken: () => 'guaranteed-attempt',
|
|
3482
|
-
deliver: deliver as never,
|
|
3483
|
-
evaluateRollout: vi.fn().mockResolvedValue({ assigned: true }) as never,
|
|
3484
|
-
logger: logger as never,
|
|
3485
|
-
now: () => '2026-01-01T00:03:00.000Z',
|
|
3486
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
3487
|
-
work(client as never)) as never,
|
|
3488
|
-
});
|
|
3489
|
-
|
|
3490
|
-
await expect(
|
|
3491
|
-
handler({
|
|
3492
|
-
Records: [{
|
|
3493
|
-
body: JSON.stringify(delivery),
|
|
3494
|
-
messageId: 'queue-message-guaranteed',
|
|
3495
|
-
}],
|
|
3496
|
-
}),
|
|
3497
|
-
).resolves.toEqual({ batchItemFailures: [] });
|
|
3498
|
-
expect(deliver).toHaveBeenCalledTimes(1);
|
|
3499
|
-
});
|
|
3500
|
-
|
|
2051
|
+
${(0, built_in_plan_key_value_provider_js_1.renderKeyValueProviderTestCases)(name)}
|
|
3501
2052
|
it('treats a retry after a workflow effect marker as compensation-required', async () => {
|
|
3502
2053
|
const runStep = vi.fn();
|
|
3503
2054
|
const transition = vi.fn();
|
|
@@ -4087,446 +2638,8 @@ describe('generated async handlers', () => {
|
|
|
4087
2638
|
);
|
|
4088
2639
|
});
|
|
4089
2640
|
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
const client = {
|
|
4093
|
-
async query(statement: string) {
|
|
4094
|
-
statements.push(statement);
|
|
4095
|
-
if (statement.includes('FROM data_lifecycle_outbox') &&
|
|
4096
|
-
statement.includes('FOR UPDATE')) {
|
|
4097
|
-
return {
|
|
4098
|
-
rows: [{
|
|
4099
|
-
eventJson: { Detail: '{}', DetailType: 'Fixture' },
|
|
4100
|
-
idempotencyKey: 'outbox-one',
|
|
4101
|
-
}],
|
|
4102
|
-
};
|
|
4103
|
-
}
|
|
4104
|
-
if (statement.includes('count(*)')) {
|
|
4105
|
-
return { rows: [{ count: '0' }] };
|
|
4106
|
-
}
|
|
4107
|
-
return { rowCount: 1, rows: [] };
|
|
4108
|
-
},
|
|
4109
|
-
};
|
|
4110
|
-
const publish = vi.fn().mockResolvedValue({
|
|
4111
|
-
Entries: [{ EventId: 'event-one' }],
|
|
4112
|
-
});
|
|
4113
|
-
const handler = createOutboxRelayHandler({
|
|
4114
|
-
logger: logger as never,
|
|
4115
|
-
metrics: { emit: vi.fn() },
|
|
4116
|
-
publish: publish as never,
|
|
4117
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4118
|
-
work(client as never)) as never,
|
|
4119
|
-
});
|
|
4120
|
-
|
|
4121
|
-
await expect(handler({ id: 'correlation-one' })).resolves.toMatchObject({
|
|
4122
|
-
failed: 0,
|
|
4123
|
-
outcome: 'converged',
|
|
4124
|
-
published: 1,
|
|
4125
|
-
});
|
|
4126
|
-
expect(publish).toHaveBeenCalledTimes(1);
|
|
4127
|
-
expect(statements.some((value) => value.includes('published_at = now()')))
|
|
4128
|
-
.toBe(true);
|
|
4129
|
-
});
|
|
4130
|
-
|
|
4131
|
-
it('processes protected performance work through an isolated sink', async () => {
|
|
4132
|
-
const client = {
|
|
4133
|
-
async query(statement: string) {
|
|
4134
|
-
if (statement.includes('UPDATE performance_load_async_effects')) {
|
|
4135
|
-
return { rows: [{ effectId: 'effect-one' }] };
|
|
4136
|
-
}
|
|
4137
|
-
if (
|
|
4138
|
-
statement.includes('FROM data_lifecycle_outbox') &&
|
|
4139
|
-
statement.includes('FOR UPDATE')
|
|
4140
|
-
) {
|
|
4141
|
-
return { rows: [] };
|
|
4142
|
-
}
|
|
4143
|
-
return { rowCount: 1, rows: [] };
|
|
4144
|
-
},
|
|
4145
|
-
};
|
|
4146
|
-
const publish = vi.fn();
|
|
4147
|
-
const handler = createOutboxRelayHandler({
|
|
4148
|
-
logger: logger as never,
|
|
4149
|
-
metrics: { emit: vi.fn() },
|
|
4150
|
-
publish: publish as never,
|
|
4151
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4152
|
-
work(client as never)) as never,
|
|
4153
|
-
});
|
|
4154
|
-
|
|
4155
|
-
await expect(handler({ id: 'performance-one' })).resolves.toMatchObject({
|
|
4156
|
-
failed: 0,
|
|
4157
|
-
outcome: 'converged',
|
|
4158
|
-
processedPerformance: 1,
|
|
4159
|
-
published: 0,
|
|
4160
|
-
});
|
|
4161
|
-
expect(publish).not.toHaveBeenCalled();
|
|
4162
|
-
});
|
|
4163
|
-
|
|
4164
|
-
it('drains terminal inbox rows in bounded pages and never selects active claims', async () => {
|
|
4165
|
-
const metrics = { emit: vi.fn(() => ({} as never)) };
|
|
4166
|
-
const deleteCounts = [1_000, 500];
|
|
4167
|
-
const remainingCounts = ['500', '0'];
|
|
4168
|
-
const performanceDeleteCounts = new Map([
|
|
4169
|
-
['performance_load_async_effects', [1, 0]],
|
|
4170
|
-
['records', [1, 0]],
|
|
4171
|
-
['performance_load_requests', [1, 0]],
|
|
4172
|
-
['performance_load_runs', [1, 0]],
|
|
4173
|
-
]);
|
|
4174
|
-
const performanceStatements: string[] = [];
|
|
4175
|
-
const client = {
|
|
4176
|
-
async query(statement: string, parameters?: readonly unknown[]) {
|
|
4177
|
-
if (
|
|
4178
|
-
statement === 'BEGIN' ||
|
|
4179
|
-
statement === 'COMMIT' ||
|
|
4180
|
-
statement === 'ROLLBACK'
|
|
4181
|
-
) {
|
|
4182
|
-
return { rowCount: null, rows: [] };
|
|
4183
|
-
}
|
|
4184
|
-
if (
|
|
4185
|
-
statement.includes('SELECT resource_ref AS "resourceRef"') &&
|
|
4186
|
-
statement.includes('FROM cleanup_deadline_reports')
|
|
4187
|
-
) {
|
|
4188
|
-
return { rows: [] };
|
|
4189
|
-
}
|
|
4190
|
-
if (statement.includes('DELETE FROM cleanup_deadline_reports')) {
|
|
4191
|
-
return { rowCount: 0, rows: [] };
|
|
4192
|
-
}
|
|
4193
|
-
if (statement.includes('WITH deadline_candidates AS')) {
|
|
4194
|
-
expect(statement).toContain('clock_timestamp()');
|
|
4195
|
-
expect(statement).toContain(
|
|
4196
|
-
'ON CONFLICT (feature, resource_ref) DO UPDATE',
|
|
4197
|
-
);
|
|
4198
|
-
return {
|
|
4199
|
-
rows: [{ candidateCount: '0', recordedCount: '0' }],
|
|
4200
|
-
};
|
|
4201
|
-
}
|
|
4202
|
-
if (statement.includes('bounded_pending_reports')) {
|
|
4203
|
-
return { rows: [{ count: '0' }] };
|
|
4204
|
-
}
|
|
4205
|
-
if (statement.includes('DELETE FROM ebk_inbox')) {
|
|
4206
|
-
expect(statement).toContain("status = 'succeeded'");
|
|
4207
|
-
expect(statement).toContain(
|
|
4208
|
-
"result_reference LIKE 'terminal-permanent-%'",
|
|
4209
|
-
);
|
|
4210
|
-
expect(statement).toContain(
|
|
4211
|
-
"result_reference LIKE 'terminal-reconciliation-%'",
|
|
4212
|
-
);
|
|
4213
|
-
expect(statement).toContain("'configuration-disabled'");
|
|
4214
|
-
expect(statement).toContain("'configuration-invalid'");
|
|
4215
|
-
return { rowCount: deleteCounts.shift() ?? 0, rows: [] };
|
|
4216
|
-
}
|
|
4217
|
-
if (
|
|
4218
|
-
statement.includes('SELECT count(*)') &&
|
|
4219
|
-
statement.includes('FROM ebk_inbox')
|
|
4220
|
-
) {
|
|
4221
|
-
expect(statement).toContain("status = 'succeeded'");
|
|
4222
|
-
expect(statement).toContain(
|
|
4223
|
-
"result_reference LIKE 'terminal-permanent-%'",
|
|
4224
|
-
);
|
|
4225
|
-
expect(statement).toContain("'configuration-disabled'");
|
|
4226
|
-
expect(statement).toContain("'configuration-invalid'");
|
|
4227
|
-
expect(statement).not.toContain("status = 'claimed'");
|
|
4228
|
-
return { rows: [{ count: remainingCounts.shift() ?? '0' }] };
|
|
4229
|
-
}
|
|
4230
|
-
if (
|
|
4231
|
-
statement.includes('DELETE FROM performance_load_async_effects')
|
|
4232
|
-
) {
|
|
4233
|
-
performanceStatements.push(statement);
|
|
4234
|
-
expect(statement).toContain('expires_at <= now()');
|
|
4235
|
-
const count =
|
|
4236
|
-
performanceDeleteCounts
|
|
4237
|
-
.get('performance_load_async_effects')
|
|
4238
|
-
?.shift() ?? 0;
|
|
4239
|
-
return {
|
|
4240
|
-
rowCount: count,
|
|
4241
|
-
rows: Array.from({ length: count }, (_, index) => ({
|
|
4242
|
-
expiresAt: new Date('2026-08-03T00:00:00.000Z'),
|
|
4243
|
-
resourceId: 'effect-' + String(index),
|
|
4244
|
-
})),
|
|
4245
|
-
};
|
|
4246
|
-
}
|
|
4247
|
-
if (statement.includes('DELETE FROM records')) {
|
|
4248
|
-
performanceStatements.push(statement);
|
|
4249
|
-
expect(statement).toContain("owner_id = 'ebk-performance'");
|
|
4250
|
-
expect(statement).toContain(
|
|
4251
|
-
"name = 'ebk-protected-performance-load'",
|
|
4252
|
-
);
|
|
4253
|
-
expect(statement).toContain('test_expires_at <= now()');
|
|
4254
|
-
const count =
|
|
4255
|
-
performanceDeleteCounts.get('records')?.shift() ?? 0;
|
|
4256
|
-
return {
|
|
4257
|
-
rowCount: count,
|
|
4258
|
-
rows: Array.from({ length: count }, (_, index) => ({
|
|
4259
|
-
expiresAt: new Date('2026-08-03T00:00:00.000Z'),
|
|
4260
|
-
resourceId: 'record-' + String(index),
|
|
4261
|
-
})),
|
|
4262
|
-
};
|
|
4263
|
-
}
|
|
4264
|
-
if (statement.includes('DELETE FROM performance_load_requests')) {
|
|
4265
|
-
performanceStatements.push(statement);
|
|
4266
|
-
expect(statement).toContain(
|
|
4267
|
-
'now() - make_interval(days => $2)',
|
|
4268
|
-
);
|
|
4269
|
-
expect(parameters?.[1]).toBe(30);
|
|
4270
|
-
return {
|
|
4271
|
-
rowCount:
|
|
4272
|
-
performanceDeleteCounts
|
|
4273
|
-
.get('performance_load_requests')
|
|
4274
|
-
?.shift() ?? 0,
|
|
4275
|
-
rows: [],
|
|
4276
|
-
};
|
|
4277
|
-
}
|
|
4278
|
-
if (statement.includes('DELETE FROM performance_load_runs')) {
|
|
4279
|
-
performanceStatements.push(statement);
|
|
4280
|
-
expect(statement).toContain(
|
|
4281
|
-
'FROM performance_load_requests AS request',
|
|
4282
|
-
);
|
|
4283
|
-
expect(statement).toContain(
|
|
4284
|
-
'FROM performance_load_async_effects AS effects',
|
|
4285
|
-
);
|
|
4286
|
-
expect(statement).toContain('FROM records AS performance_record');
|
|
4287
|
-
expect(parameters?.[1]).toBe(30);
|
|
4288
|
-
return {
|
|
4289
|
-
rowCount:
|
|
4290
|
-
performanceDeleteCounts
|
|
4291
|
-
.get('performance_load_runs')
|
|
4292
|
-
?.shift() ?? 0,
|
|
4293
|
-
rows: [],
|
|
4294
|
-
};
|
|
4295
|
-
}
|
|
4296
|
-
if (
|
|
4297
|
-
statement.includes('AS count') &&
|
|
4298
|
-
statement.includes('performance_load_async_effects')
|
|
4299
|
-
) {
|
|
4300
|
-
expect(statement).toContain("owner_id = 'ebk-performance'");
|
|
4301
|
-
expect(statement).toContain(
|
|
4302
|
-
"name = 'ebk-protected-performance-load'",
|
|
4303
|
-
);
|
|
4304
|
-
expect(statement).toContain('FROM records AS performance_record');
|
|
4305
|
-
expect(statement).toContain('LIMIT $2');
|
|
4306
|
-
expect(parameters).toEqual([30, 5_001]);
|
|
4307
|
-
return { rows: [{ count: '0' }] };
|
|
4308
|
-
}
|
|
4309
|
-
throw new Error('Unexpected cleanup query: ' + statement);
|
|
4310
|
-
},
|
|
4311
|
-
};
|
|
4312
|
-
const handler = createInboxCleanupHandler({
|
|
4313
|
-
logger: logger as never,
|
|
4314
|
-
metrics,
|
|
4315
|
-
now: () => 0,
|
|
4316
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4317
|
-
work(client as never)) as never,
|
|
4318
|
-
});
|
|
4319
|
-
|
|
4320
|
-
await expect(handler({ id: 'cleanup-one' })).resolves.toMatchObject({
|
|
4321
|
-
performanceCleanupDeadlineHours: 24,
|
|
4322
|
-
remainingExpired: 0,
|
|
4323
|
-
reportedPerformanceDeadlineBreaches: 0,
|
|
4324
|
-
remainingPerformanceExpired: 0,
|
|
4325
|
-
removed: 1_500,
|
|
4326
|
-
removedPerformance: 4,
|
|
4327
|
-
});
|
|
4328
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4329
|
-
expect.objectContaining({
|
|
4330
|
-
metricName: 'InboxCleanupBacklog',
|
|
4331
|
-
value: 0,
|
|
4332
|
-
}),
|
|
4333
|
-
);
|
|
4334
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4335
|
-
expect.objectContaining({
|
|
4336
|
-
metricName: 'PerformanceLoadCleanupBacklog',
|
|
4337
|
-
value: 0,
|
|
4338
|
-
}),
|
|
4339
|
-
);
|
|
4340
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4341
|
-
expect.objectContaining({
|
|
4342
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4343
|
-
value: 0,
|
|
4344
|
-
}),
|
|
4345
|
-
);
|
|
4346
|
-
expect(performanceStatements).toHaveLength(8);
|
|
4347
|
-
});
|
|
4348
|
-
|
|
4349
|
-
it('fails the scheduled invocation when protected load residue exceeds the cleanup deadline', async () => {
|
|
4350
|
-
const metrics = { emit: vi.fn(() => ({} as never)) };
|
|
4351
|
-
const pendingReports = new Set(['async-effect:effect-one']);
|
|
4352
|
-
const client = {
|
|
4353
|
-
async query(statement: string, parameters?: readonly unknown[]) {
|
|
4354
|
-
if (
|
|
4355
|
-
statement === 'BEGIN' ||
|
|
4356
|
-
statement === 'COMMIT' ||
|
|
4357
|
-
statement === 'ROLLBACK'
|
|
4358
|
-
) {
|
|
4359
|
-
return { rowCount: null, rows: [] };
|
|
4360
|
-
}
|
|
4361
|
-
if (statement.includes('SELECT resource_ref AS "resourceRef"')) {
|
|
4362
|
-
return {
|
|
4363
|
-
rows: [...pendingReports].map((resourceRef) => ({ resourceRef })),
|
|
4364
|
-
};
|
|
4365
|
-
}
|
|
4366
|
-
if (statement.includes('DELETE FROM cleanup_deadline_reports')) {
|
|
4367
|
-
for (const resourceRef of parameters?.[1] as readonly string[]) {
|
|
4368
|
-
pendingReports.delete(resourceRef);
|
|
4369
|
-
}
|
|
4370
|
-
return { rowCount: 1, rows: [] };
|
|
4371
|
-
}
|
|
4372
|
-
if (
|
|
4373
|
-
statement.includes('DELETE FROM performance_load_async_effects') ||
|
|
4374
|
-
statement.includes('DELETE FROM records')
|
|
4375
|
-
) {
|
|
4376
|
-
return { rowCount: 0, rows: [] };
|
|
4377
|
-
}
|
|
4378
|
-
if (statement.includes('DELETE FROM')) {
|
|
4379
|
-
return { rowCount: 0, rows: [] };
|
|
4380
|
-
}
|
|
4381
|
-
if (statement.includes('SELECT count(*)')) {
|
|
4382
|
-
return { rows: [{ count: '0' }] };
|
|
4383
|
-
}
|
|
4384
|
-
throw new Error('Unexpected cleanup deadline query: ' + statement);
|
|
4385
|
-
},
|
|
4386
|
-
};
|
|
4387
|
-
const handler = createInboxCleanupHandler({
|
|
4388
|
-
logger: { error: vi.fn(), info: vi.fn() } as never,
|
|
4389
|
-
metrics,
|
|
4390
|
-
now: () => 0,
|
|
4391
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4392
|
-
work(client as never)) as never,
|
|
4393
|
-
});
|
|
4394
|
-
|
|
4395
|
-
await expect(handler({ id: 'cleanup-deadline' })).rejects.toThrow(
|
|
4396
|
-
'performance-load-cleanup-deadline-breached',
|
|
4397
|
-
);
|
|
4398
|
-
expect(metrics.emit).toHaveBeenCalledWith(
|
|
4399
|
-
expect.objectContaining({
|
|
4400
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4401
|
-
value: 1,
|
|
4402
|
-
}),
|
|
4403
|
-
);
|
|
4404
|
-
expect(pendingReports.size).toBe(0);
|
|
4405
|
-
});
|
|
4406
|
-
|
|
4407
|
-
it('retries deadline telemetry when its post-emission commit fails', async () => {
|
|
4408
|
-
const pendingReports = new Set(['record:record-one']);
|
|
4409
|
-
let failReportCommits = true;
|
|
4410
|
-
let reportTransaction = false;
|
|
4411
|
-
let snapshot = new Set<string>();
|
|
4412
|
-
const client = {
|
|
4413
|
-
async query(statement: string, parameters?: readonly unknown[]) {
|
|
4414
|
-
if (statement === 'BEGIN') {
|
|
4415
|
-
snapshot = new Set(pendingReports);
|
|
4416
|
-
reportTransaction = false;
|
|
4417
|
-
return { rowCount: null, rows: [] };
|
|
4418
|
-
}
|
|
4419
|
-
if (statement === 'COMMIT') {
|
|
4420
|
-
if (reportTransaction && failReportCommits) {
|
|
4421
|
-
throw new Error('simulated report commit crash');
|
|
4422
|
-
}
|
|
4423
|
-
return { rowCount: null, rows: [] };
|
|
4424
|
-
}
|
|
4425
|
-
if (statement === 'ROLLBACK') {
|
|
4426
|
-
pendingReports.clear();
|
|
4427
|
-
for (const resourceRef of snapshot) pendingReports.add(resourceRef);
|
|
4428
|
-
return { rowCount: null, rows: [] };
|
|
4429
|
-
}
|
|
4430
|
-
if (statement.includes('SELECT resource_ref AS "resourceRef"')) {
|
|
4431
|
-
reportTransaction = true;
|
|
4432
|
-
return {
|
|
4433
|
-
rows: [...pendingReports].map((resourceRef) => ({ resourceRef })),
|
|
4434
|
-
};
|
|
4435
|
-
}
|
|
4436
|
-
if (statement.includes('DELETE FROM cleanup_deadline_reports')) {
|
|
4437
|
-
for (const resourceRef of parameters?.[1] as readonly string[]) {
|
|
4438
|
-
pendingReports.delete(resourceRef);
|
|
4439
|
-
}
|
|
4440
|
-
return { rowCount: 1, rows: [] };
|
|
4441
|
-
}
|
|
4442
|
-
if (
|
|
4443
|
-
statement.includes('DELETE FROM performance_load_async_effects') ||
|
|
4444
|
-
statement.includes('DELETE FROM records')
|
|
4445
|
-
) {
|
|
4446
|
-
return { rowCount: 0, rows: [] };
|
|
4447
|
-
}
|
|
4448
|
-
if (statement.includes('DELETE FROM')) {
|
|
4449
|
-
return { rowCount: 0, rows: [] };
|
|
4450
|
-
}
|
|
4451
|
-
if (statement.includes('SELECT count(*)')) {
|
|
4452
|
-
return { rows: [{ count: '0' }] };
|
|
4453
|
-
}
|
|
4454
|
-
throw new Error('Unexpected cleanup retry query: ' + statement);
|
|
4455
|
-
},
|
|
4456
|
-
};
|
|
4457
|
-
const firstMetrics = { emit: vi.fn(() => ({} as never)) };
|
|
4458
|
-
const createHandler = (metrics: typeof firstMetrics) =>
|
|
4459
|
-
createInboxCleanupHandler({
|
|
4460
|
-
logger: { error: vi.fn(), info: vi.fn() } as never,
|
|
4461
|
-
metrics,
|
|
4462
|
-
now: () => 0,
|
|
4463
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4464
|
-
work(client as never)) as never,
|
|
4465
|
-
});
|
|
4466
|
-
|
|
4467
|
-
await expect(
|
|
4468
|
-
createHandler(firstMetrics)({ id: 'cleanup-report-crash' }),
|
|
4469
|
-
).rejects.toThrow('simulated report commit crash');
|
|
4470
|
-
expect(pendingReports.size).toBe(1);
|
|
4471
|
-
expect(firstMetrics.emit).toHaveBeenCalledWith(
|
|
4472
|
-
expect.objectContaining({
|
|
4473
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4474
|
-
value: 1,
|
|
4475
|
-
}),
|
|
4476
|
-
);
|
|
4477
|
-
|
|
4478
|
-
failReportCommits = false;
|
|
4479
|
-
const retryMetrics = { emit: vi.fn(() => ({} as never)) };
|
|
4480
|
-
await expect(
|
|
4481
|
-
createHandler(retryMetrics)({ id: 'cleanup-report-retry' }),
|
|
4482
|
-
).rejects.toThrow('performance-load-cleanup-deadline-breached');
|
|
4483
|
-
expect(pendingReports.size).toBe(0);
|
|
4484
|
-
expect(retryMetrics.emit).toHaveBeenCalledWith(
|
|
4485
|
-
expect.objectContaining({
|
|
4486
|
-
metricName: 'PerformanceLoadCleanupDeadlineBreaches',
|
|
4487
|
-
value: 1,
|
|
4488
|
-
}),
|
|
4489
|
-
);
|
|
4490
|
-
});
|
|
4491
|
-
|
|
4492
|
-
it('surfaces publish-before-database-ack as retryable at-least-once work', async () => {
|
|
4493
|
-
const client = {
|
|
4494
|
-
async query(statement: string) {
|
|
4495
|
-
if (statement.includes('FROM data_lifecycle_outbox') &&
|
|
4496
|
-
statement.includes('FOR UPDATE')) {
|
|
4497
|
-
return {
|
|
4498
|
-
rows: [{
|
|
4499
|
-
eventJson: { Detail: '{}', DetailType: 'Fixture' },
|
|
4500
|
-
idempotencyKey: 'outbox-crash',
|
|
4501
|
-
}],
|
|
4502
|
-
};
|
|
4503
|
-
}
|
|
4504
|
-
if (statement.includes('published_at = now()')) {
|
|
4505
|
-
throw new Error('database unavailable after publish');
|
|
4506
|
-
}
|
|
4507
|
-
return { rowCount: 1, rows: [] };
|
|
4508
|
-
},
|
|
4509
|
-
};
|
|
4510
|
-
const publish = vi.fn().mockResolvedValue({
|
|
4511
|
-
Entries: [{ EventId: 'event-one' }],
|
|
4512
|
-
});
|
|
4513
|
-
const handler = createOutboxRelayHandler({
|
|
4514
|
-
logger: logger as never,
|
|
4515
|
-
metrics: { emit: vi.fn() },
|
|
4516
|
-
publish: publish as never,
|
|
4517
|
-
withDatabase: (async (_context, _workload, work) =>
|
|
4518
|
-
work(client as never)) as never,
|
|
4519
|
-
});
|
|
4520
|
-
|
|
4521
|
-
await expect(handler({ id: 'correlation-crash' })).rejects.toThrow(
|
|
4522
|
-
'database unavailable after publish',
|
|
4523
|
-
);
|
|
4524
|
-
await expect(handler({ id: 'correlation-retry' })).rejects.toThrow(
|
|
4525
|
-
'database unavailable after publish',
|
|
4526
|
-
);
|
|
4527
|
-
expect(publish).toHaveBeenCalledTimes(2);
|
|
4528
|
-
});
|
|
4529
|
-
|
|
2641
|
+
${(0, built_in_plan_key_value_performance_js_1.renderKeyValueOutboxTests)()}
|
|
2642
|
+
${(0, built_in_plan_key_value_cleanup_js_1.renderKeyValueCleanupTests)()}
|
|
4530
2643
|
it('enforces archive hard caps and cancels an active unsafe replay', async () => {
|
|
4531
2644
|
expect(() =>
|
|
4532
2645
|
assertReplayArchiveHardCaps({
|
|
@@ -4573,9 +2686,9 @@ if (contract.schemaVersion !== 1 || contract.slice !== '${name}') {
|
|
|
4573
2686
|
fail('work contract identity is invalid.');
|
|
4574
2687
|
}
|
|
4575
2688
|
if (
|
|
4576
|
-
contract.consistency?.inboxTable !== '
|
|
4577
|
-
|
|
4578
|
-
contract.consistency?.transactionBoundary !== '
|
|
2689
|
+
contract.consistency?.inboxTable !== '${'slice-key-value'}' ||
|
|
2690
|
+
contract.consistency?.outboxTable !== '${'slice-key-value'}' ||
|
|
2691
|
+
contract.consistency?.transactionBoundary !== '${'native-dynamodb-transaction'}'
|
|
4579
2692
|
) {
|
|
4580
2693
|
fail('the explicit inbox/outbox transaction contract is missing.');
|
|
4581
2694
|
}
|
|
@@ -4583,7 +2696,7 @@ if (
|
|
|
4583
2696
|
contract.queue?.batchItemFailures !== true ||
|
|
4584
2697
|
contract.queue?.conflictPolicy !== 'same-id-different-payload-fails' ||
|
|
4585
2698
|
contract.queue?.idempotencyWindowSeconds !== 2_592_000 ||
|
|
4586
|
-
contract.queue?.inboxCleanupBatchSize !==
|
|
2699
|
+
contract.queue?.inboxCleanupBatchSize !== ${20} ||
|
|
4587
2700
|
contract.queue?.inboxCleanupIntervalSeconds !== 3_600 ||
|
|
4588
2701
|
contract.queue?.inboxCleanupInvocationLimit !== 10_000 ||
|
|
4589
2702
|
contract.queue?.performanceCleanupDeadlineSeconds !== 86_400 ||
|
|
@@ -4705,6 +2818,8 @@ import {
|
|
|
4705
2818
|
GetCallerIdentityCommand,
|
|
4706
2819
|
STSClient,
|
|
4707
2820
|
} from '@aws-sdk/client-sts';
|
|
2821
|
+
import { assertWorkspaceStageOwnershipForWorkspace } from '@empire-builder-kit/nx/workspace-deployment';
|
|
2822
|
+
import { resolve } from 'node:path';
|
|
4708
2823
|
import {
|
|
4709
2824
|
createBackfillReport,
|
|
4710
2825
|
runBackfillPage,
|
|
@@ -4748,6 +2863,23 @@ function recoveryStage() {
|
|
|
4748
2863
|
return stage;
|
|
4749
2864
|
}
|
|
4750
2865
|
|
|
2866
|
+
if (action === 'apply') {
|
|
2867
|
+
const ownership = await assertWorkspaceStageOwnershipForWorkspace(
|
|
2868
|
+
resolve(import.meta.dirname, '../../..'),
|
|
2869
|
+
recoveryStage(),
|
|
2870
|
+
{
|
|
2871
|
+
...(process.env.AWS_PROFILE ? { profile: process.env.AWS_PROFILE } : {}),
|
|
2872
|
+
...(process.env.AWS_CONFIG_FILE ? { configFilepath: process.env.AWS_CONFIG_FILE } : {}),
|
|
2873
|
+
...(process.env.AWS_SHARED_CREDENTIALS_FILE
|
|
2874
|
+
? { credentialsFilepath: process.env.AWS_SHARED_CREDENTIALS_FILE }
|
|
2875
|
+
: {}),
|
|
2876
|
+
},
|
|
2877
|
+
);
|
|
2878
|
+
if (required('AWS_REGION') !== ownership.region) {
|
|
2879
|
+
throw new Error('Async recovery Region differs from its exact workspace owner tuple.');
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
|
|
4751
2883
|
function digest(value) {
|
|
4752
2884
|
return createHash('sha256')
|
|
4753
2885
|
.update(JSON.stringify(value), 'utf8')
|
|
@@ -5783,10 +3915,12 @@ import {
|
|
|
5783
3915
|
assertAsyncAdapterCapabilityReady,
|
|
5784
3916
|
createAsyncRecoveryAdapters,
|
|
5785
3917
|
} from '../async/recovery-adapters.ts';
|
|
3918
|
+
import { assertWorkspaceStageOwnershipForWorkspace } from '@empire-builder-kit/nx/workspace-deployment';
|
|
5786
3919
|
import {
|
|
5787
3920
|
assertReplayArchiveHardCaps as assertReplayArchiveHardCapsContract,
|
|
5788
3921
|
cancelReplayIfRequired,
|
|
5789
3922
|
} from '../async/replay-protection.ts';
|
|
3923
|
+
import { resolve } from 'node:path';
|
|
5790
3924
|
|
|
5791
3925
|
function required(name) {
|
|
5792
3926
|
const value = process.env[name]?.trim();
|
|
@@ -5889,6 +4023,13 @@ if (
|
|
|
5889
4023
|
}
|
|
5890
4024
|
const accountId = required('AWS_EXPECTED_ACCOUNT_ID');
|
|
5891
4025
|
const region = required('AWS_REGION');
|
|
4026
|
+
const ownership = await assertWorkspaceStageOwnershipForWorkspace(
|
|
4027
|
+
resolve(import.meta.dirname, '../../..'),
|
|
4028
|
+
stage,
|
|
4029
|
+
);
|
|
4030
|
+
if (ownership.region !== region) {
|
|
4031
|
+
throw new Error('Protected recovery Region differs from its exact workspace owner tuple.');
|
|
4032
|
+
}
|
|
5892
4033
|
const accountReferenceDigest = referenceDigest(accountId, 'aws-account');
|
|
5893
4034
|
const sqs = new SQSClient({ region });
|
|
5894
4035
|
const dynamo = new DynamoDBClient({ region });
|
|
@@ -7884,6 +6025,13 @@ owner. A same-ID/different-payload delivery is a conflict, not a duplicate.
|
|
|
7884
6025
|
Reconcile unknown provider outcomes before retrying. Webhook bodies are
|
|
7885
6026
|
authenticated as raw bytes before parsing or normalization.
|
|
7886
6027
|
|
|
6028
|
+
The dedicated protected recovery role must allow only
|
|
6029
|
+
\`ssm:GetParameter\` on the exact
|
|
6030
|
+
\`arn:<partition>:ssm:<region>:<account>:parameter/ebk/<stage>/workspace-owner\`
|
|
6031
|
+
in addition to its declared capability
|
|
6032
|
+
permissions. Every report, apply, and status run checks that retained claim
|
|
6033
|
+
before its first recovery-service request. The role must not mutate the owner.
|
|
6034
|
+
|
|
7887
6035
|
Automatic missed-schedule catch-up is not enabled by clean generation. Use the
|
|
7888
6036
|
checkpointed backfill report/apply/status path for one bounded range. For a
|
|
7889
6037
|
workflow, use its recorded version and cooperative cancellation boundary.
|
|
@@ -7897,6 +6045,55 @@ capacity/idempotency/provider-effect evidence, stop conditions, and the forward
|
|
|
7897
6045
|
compensation plan. Archive-wide observed size is evidence, not an immutable
|
|
7898
6046
|
range estimate, because the canonical archive continues receiving events while
|
|
7899
6047
|
approval is pending.
|
|
6048
|
+
|
|
6049
|
+
## Event delivery alarms
|
|
6050
|
+
|
|
6051
|
+
Domain events are delivered by the key-value stream Function and recovered by
|
|
6052
|
+
the scheduled key-value reconciliation Function. The \`async/outbox-relay\`
|
|
6053
|
+
Function drains only the isolated performance sink; \`outbox-relay-errors\`
|
|
6054
|
+
does not describe domain delivery. These alarms observe delivery only. No alarm
|
|
6055
|
+
pauses, drops, quarantines, caps, or retries an event intent, and reconciliation
|
|
6056
|
+
does not fail because some intents remain undelivered.
|
|
6057
|
+
|
|
6058
|
+
- \`key-value-stream-delivery-errors\` reports a failed invocation of the
|
|
6059
|
+
stream delivery Function: the stream identity or record shape was rejected,
|
|
6060
|
+
or the handler crashed. Per-record publish failures are partial batch
|
|
6061
|
+
failures, not Lambda errors. They surface through
|
|
6062
|
+
\`key-value-stream-iterator-age\` while retries hold the shard back, and
|
|
6063
|
+
through \`outbox-backlog\` and \`event-delivery-attempts\` after the mapping
|
|
6064
|
+
discards them (after three retries or one hour).
|
|
6065
|
+
- \`key-value-reconciliation-errors\` reports Lambda invocation errors for the
|
|
6066
|
+
exact reconciliation Function.
|
|
6067
|
+
- \`key-value-stream-iterator-age\` reports a stream iterator at least ten
|
|
6068
|
+
minutes old. The mapping discards records older than one hour; their retained
|
|
6069
|
+
intents then wait for reconciliation.
|
|
6070
|
+
- \`outbox-backlog\` reports \`OutboxBacklog\`: intents a reconciliation run
|
|
6071
|
+
attempted but could not deliver, plus rejected intents, in two consecutive
|
|
6072
|
+
hours. Each run reads at most one 25-intent page per shard, so the value is a
|
|
6073
|
+
lower bound rather than a global count. Intents leased by another delivery
|
|
6074
|
+
owner are in flight and are not counted.
|
|
6075
|
+
- \`event-intent-rejected\` reports retained intents that fail validation.
|
|
6076
|
+
They are never dispatched or deleted, so the alarm recurs until an owner
|
|
6077
|
+
investigates the content-free \`ebk-key-value-invalid-intents\` record.
|
|
6078
|
+
- \`event-delivery-attempts\` reports \`EventDeliveryMaxAttempts\` of at least
|
|
6079
|
+
ten delivery leases on one due intent. Attempts are reported, never enforced.
|
|
6080
|
+
|
|
6081
|
+
SQL Slices only (see docs/runbooks/sql-events.md):
|
|
6082
|
+
|
|
6083
|
+
- \`sql-outbox-relay-errors\` reports Lambda errors for the exact SQL outbox
|
|
6084
|
+
relay Function, which fails every run that retains a failed delivery.
|
|
6085
|
+
- \`sql-outbox-backlog\` reports the Minimum of \`SqlOutboxBacklog\`, the
|
|
6086
|
+
global unpublished-intent count, over two consecutive hours (3600 s x 2): it
|
|
6087
|
+
alarms only when no relay run in either hour drained the outbox. A paused
|
|
6088
|
+
relay reports nothing, so the alarm is absent rather than breaching while
|
|
6089
|
+
paused.
|
|
6090
|
+
- \`sql-outbox-attempts\` reports \`SqlOutboxMaxAttempts\` of at least ten
|
|
6091
|
+
relay attempts on one unpublished intent. It is observational; no intent is
|
|
6092
|
+
capped or dropped.
|
|
6093
|
+
|
|
6094
|
+
Diagnose with the content-free \`ebk-key-value-reconciliation\` log summary and
|
|
6095
|
+
the EventBridge target before any recovery. Do not delete or edit retained
|
|
6096
|
+
intents to clear an alarm.
|
|
7900
6097
|
`,
|
|
7901
6098
|
};
|
|
7902
6099
|
}
|