@empire-builder-kit/nx 1.0.0-beta.9 → 1.0.0-rc.2
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/NOTICE +6 -0
- package/README.md +113 -3
- package/executors.json +30 -0
- package/package.json +84 -6
- package/src/api-contract/baseline-update.d.ts +82 -0
- package/src/api-contract/baseline-update.js +225 -0
- package/src/api-contract/baseline-update.js.map +1 -0
- package/src/api-contract/compatibility.d.ts +30 -0
- package/src/api-contract/compatibility.js +643 -0
- package/src/api-contract/compatibility.js.map +1 -0
- package/src/api-contract/contract-graph.d.ts +35 -0
- package/src/api-contract/contract-graph.js +141 -0
- package/src/api-contract/contract-graph.js.map +1 -0
- package/src/api-contract/evidence.d.ts +23 -0
- package/src/api-contract/evidence.js +87 -0
- package/src/api-contract/evidence.js.map +1 -0
- package/src/api-contract/index.d.ts +6 -0
- package/src/api-contract/index.js +10 -0
- package/src/api-contract/index.js.map +1 -0
- package/src/api-contract/model.d.ts +144 -0
- package/src/api-contract/model.fixtures.d.ts +2 -0
- package/src/api-contract/model.fixtures.js +81 -0
- package/src/api-contract/model.fixtures.js.map +1 -0
- package/src/api-contract/model.js +993 -0
- package/src/api-contract/model.js.map +1 -0
- package/src/api-contract/rollout.d.ts +32 -0
- package/src/api-contract/rollout.js +88 -0
- package/src/api-contract/rollout.js.map +1 -0
- package/src/authorization/compatibility.d.ts +62 -0
- package/src/authorization/compatibility.js +175 -0
- package/src/authorization/compatibility.js.map +1 -0
- package/src/authorization/index.d.ts +1 -0
- package/src/authorization/index.js +5 -0
- package/src/authorization/index.js.map +1 -0
- package/src/blueprints/execution.d.ts +25 -1
- package/src/blueprints/execution.js +246 -83
- package/src/blueprints/execution.js.map +1 -1
- package/src/blueprints/external-sandbox.d.ts +53 -0
- package/src/blueprints/external-sandbox.js +1182 -0
- package/src/blueprints/external-sandbox.js.map +1 -0
- package/src/blueprints/manifest.d.ts +42 -2
- package/src/blueprints/manifest.js +82 -27
- package/src/blueprints/manifest.js.map +1 -1
- package/src/blueprints/resolver.d.ts +14 -5
- package/src/blueprints/resolver.js +228 -68
- package/src/blueprints/resolver.js.map +1 -1
- package/src/cli/agent-command.d.ts +30 -0
- package/src/cli/agent-command.js +204 -0
- package/src/cli/agent-command.js.map +1 -0
- package/src/cli/agent-runtime.d.ts +25 -0
- package/src/cli/agent-runtime.js +488 -0
- package/src/cli/agent-runtime.js.map +1 -0
- package/src/cli/blueprint-authority.d.ts +9 -0
- package/src/cli/blueprint-authority.js +43 -0
- package/src/cli/blueprint-authority.js.map +1 -0
- package/src/cli/blueprint-command.d.ts +24 -0
- package/src/cli/blueprint-command.js +63 -0
- package/src/cli/blueprint-command.js.map +1 -0
- package/src/cli/bootstrap.d.ts +31 -0
- package/src/cli/bootstrap.js +165 -0
- package/src/cli/bootstrap.js.map +1 -0
- package/src/cli/dev-command.d.ts +92 -0
- package/src/cli/dev-command.js +561 -0
- package/src/cli/dev-command.js.map +1 -0
- package/src/cli/main.d.ts +131 -0
- package/src/cli/main.js +1199 -0
- package/src/cli/main.js.map +1 -0
- package/src/cli/output.d.ts +19 -0
- package/src/cli/output.js +51 -0
- package/src/cli/output.js.map +1 -0
- package/src/cli/personal-secret-runtime.d.ts +81 -0
- package/src/cli/personal-secret-runtime.js +567 -0
- package/src/cli/personal-secret-runtime.js.map +1 -0
- package/src/cli/secret-terminal.d.ts +12 -0
- package/src/cli/secret-terminal.js +128 -0
- package/src/cli/secret-terminal.js.map +1 -0
- package/src/container-images.d.ts +1 -0
- package/src/container-images.js +5 -0
- package/src/container-images.js.map +1 -0
- package/src/data-lifecycle/index.d.ts +3 -0
- package/src/data-lifecycle/index.js +7 -0
- package/src/data-lifecycle/index.js.map +1 -0
- package/src/data-lifecycle/model.d.ts +172 -0
- package/src/data-lifecycle/model.fixtures.d.ts +2 -0
- package/src/data-lifecycle/model.fixtures.js +244 -0
- package/src/data-lifecycle/model.fixtures.js.map +1 -0
- package/src/data-lifecycle/model.js +95 -0
- package/src/data-lifecycle/model.js.map +1 -0
- package/src/data-lifecycle/sensitive-artifacts.d.ts +28 -0
- package/src/data-lifecycle/sensitive-artifacts.js +146 -0
- package/src/data-lifecycle/sensitive-artifacts.js.map +1 -0
- package/src/data-lifecycle/validation.d.ts +32 -0
- package/src/data-lifecycle/validation.js +837 -0
- package/src/data-lifecycle/validation.js.map +1 -0
- package/src/database-retirement/contract.d.ts +68 -0
- package/src/database-retirement/contract.js +166 -0
- package/src/database-retirement/contract.js.map +1 -0
- package/src/database-retirement/index.d.ts +1 -0
- package/src/database-retirement/index.js +5 -0
- package/src/database-retirement/index.js.map +1 -0
- package/src/deterministic-order.d.ts +2 -0
- package/src/deterministic-order.js +7 -0
- package/src/deterministic-order.js.map +1 -0
- package/src/developer-agent/context.d.ts +27 -0
- package/src/developer-agent/context.js +497 -0
- package/src/developer-agent/context.js.map +1 -0
- package/src/developer-agent/generator-planning.d.ts +34 -0
- package/src/developer-agent/generator-planning.js +645 -0
- package/src/developer-agent/generator-planning.js.map +1 -0
- package/src/developer-agent/generator-preview.d.ts +8 -0
- package/src/developer-agent/generator-preview.js +650 -0
- package/src/developer-agent/generator-preview.js.map +1 -0
- package/src/developer-agent/git-inspection.d.ts +108 -0
- package/src/developer-agent/git-inspection.js +815 -0
- package/src/developer-agent/git-inspection.js.map +1 -0
- package/src/developer-agent/handoff.d.ts +43 -0
- package/src/developer-agent/handoff.js +698 -0
- package/src/developer-agent/handoff.js.map +1 -0
- package/src/developer-agent/index.d.ts +10 -0
- package/src/developer-agent/index.js +14 -0
- package/src/developer-agent/index.js.map +1 -0
- package/src/developer-agent/model.d.ts +481 -0
- package/src/developer-agent/model.js +143 -0
- package/src/developer-agent/model.js.map +1 -0
- package/src/developer-agent/operation-registry.d.ts +45 -0
- package/src/developer-agent/operation-registry.js +1255 -0
- package/src/developer-agent/operation-registry.js.map +1 -0
- package/src/developer-agent/ordering.d.ts +9 -0
- package/src/developer-agent/ordering.js +15 -0
- package/src/developer-agent/ordering.js.map +1 -0
- package/src/developer-agent/planning.d.ts +18 -0
- package/src/developer-agent/planning.js +447 -0
- package/src/developer-agent/planning.js.map +1 -0
- package/src/developer-agent/preview-changes.d.ts +26 -0
- package/src/developer-agent/preview-changes.js +203 -0
- package/src/developer-agent/preview-changes.js.map +1 -0
- package/src/developer-agent/preview-source.d.ts +2 -0
- package/src/developer-agent/preview-source.js +16 -0
- package/src/developer-agent/preview-source.js.map +1 -0
- package/src/developer-agent/validation.d.ts +18 -0
- package/src/developer-agent/validation.js +2617 -0
- package/src/developer-agent/validation.js.map +1 -0
- package/src/developer-agent/workspace-inspection.d.ts +20 -0
- package/src/developer-agent/workspace-inspection.js +1134 -0
- package/src/developer-agent/workspace-inspection.js.map +1 -0
- package/src/developer-agent/workspace-path.d.ts +6 -0
- package/src/developer-agent/workspace-path.js +22 -0
- package/src/developer-agent/workspace-path.js.map +1 -0
- package/src/executors/blueprint-trust/blueprint-trust.d.ts +29 -0
- package/src/executors/blueprint-trust/blueprint-trust.js +115 -0
- package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -0
- package/src/executors/blueprint-trust/schema.json +27 -0
- package/src/executors/blueprint-verification/blueprint-verification.d.ts +13 -0
- package/src/executors/blueprint-verification/blueprint-verification.js +110 -0
- package/src/executors/blueprint-verification/blueprint-verification.js.map +1 -0
- package/src/executors/blueprint-verification/schema.json +44 -0
- package/src/executors/deployment-plan/deployment-plan.js +1 -0
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/dev-doctor/dev-doctor.d.ts +19 -5
- package/src/executors/dev-doctor/dev-doctor.js +186 -32
- package/src/executors/dev-doctor/dev-doctor.js.map +1 -1
- package/src/executors/dev-doctor/schema.json +1 -6
- package/src/executors/dev-setup/dev-setup.d.ts +7 -1
- package/src/executors/dev-setup/dev-setup.js +14 -3
- package/src/executors/dev-setup/dev-setup.js.map +1 -1
- package/src/executors/developer-context/developer-context.d.ts +38 -0
- package/src/executors/developer-context/developer-context.js +78 -0
- package/src/executors/developer-context/developer-context.js.map +1 -0
- package/src/executors/developer-context/schema.json +87 -0
- package/src/executors/fleet/fleet.d.ts +12 -43
- package/src/executors/fleet/fleet.js +120 -500
- package/src/executors/fleet/fleet.js.map +1 -1
- package/src/executors/fleet/schema.json +64 -14
- package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
- package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
- package/src/executors/github-bootstrap/schema.json +5 -0
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +144 -0
- package/src/executors/infrastructure-plan/infrastructure-plan.js +3320 -0
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -0
- package/src/executors/infrastructure-plan/schema.json +78 -0
- package/src/executors/secret-guardrails/schema.json +6 -0
- package/src/executors/secret-guardrails/secret-guardrails.d.ts +3 -0
- package/src/executors/secret-guardrails/secret-guardrails.js +44 -0
- package/src/executors/secret-guardrails/secret-guardrails.js.map +1 -0
- package/src/executors/secret-metadata/schema.json +31 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +32 -0
- package/src/executors/secret-metadata/secret-metadata.js +131 -0
- package/src/executors/secret-metadata/secret-metadata.js.map +1 -0
- package/src/executors/sst-lifecycle/logging-preflight.d.ts +17 -0
- package/src/executors/sst-lifecycle/logging-preflight.js +196 -0
- package/src/executors/sst-lifecycle/logging-preflight.js.map +1 -0
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +23 -3
- package/src/executors/sst-lifecycle/run-sst-command.js +113 -10
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
- package/src/executors/sst-lifecycle/schema.json +9 -5
- package/src/executors/sst-lifecycle/secret-preflight.d.ts +25 -0
- package/src/executors/sst-lifecycle/secret-preflight.js +119 -0
- package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -0
- package/src/foundation/discovery.d.ts +17 -1
- package/src/foundation/discovery.js +16 -1
- package/src/foundation/discovery.js.map +1 -1
- package/src/foundation/index.d.ts +3 -0
- package/src/foundation/index.js +3 -0
- package/src/foundation/index.js.map +1 -1
- package/src/foundation/local-database-contract.d.ts +1 -0
- package/src/foundation/local-database-contract.js +3 -0
- package/src/foundation/local-database-contract.js.map +1 -1
- package/src/foundation/logging-preflight.d.ts +30 -0
- package/src/foundation/logging-preflight.js +138 -0
- package/src/foundation/logging-preflight.js.map +1 -0
- package/src/foundation/logging.d.ts +70 -0
- package/src/foundation/logging.js +133 -0
- package/src/foundation/logging.js.map +1 -0
- package/src/foundation/preflight.d.ts +9 -0
- package/src/foundation/preflight.js +58 -2
- package/src/foundation/preflight.js.map +1 -1
- package/src/foundation/provision-database-bindings.d.ts +9 -1
- package/src/foundation/provision-database-bindings.js +25 -5
- package/src/foundation/provision-database-bindings.js.map +1 -1
- package/src/foundation/provision-logical-database.d.ts +10 -2
- package/src/foundation/provision-logical-database.js +68 -113
- package/src/foundation/provision-logical-database.js.map +1 -1
- package/src/foundation/router-origin-proof.d.ts +2 -0
- package/src/foundation/router-origin-proof.js +25 -0
- package/src/foundation/router-origin-proof.js.map +1 -0
- package/src/foundation/stages.d.ts +1 -0
- package/src/foundation/stages.js +9 -1
- package/src/foundation/stages.js.map +1 -1
- package/src/frontend-security/index.d.ts +1 -0
- package/src/frontend-security/index.js +5 -0
- package/src/frontend-security/index.js.map +1 -0
- package/src/frontend-security/security-headers.d.ts +50 -0
- package/src/frontend-security/security-headers.js +418 -0
- package/src/frontend-security/security-headers.js.map +1 -0
- package/src/generators/feature-registry.d.ts +13 -0
- package/src/generators/feature-registry.js +73 -0
- package/src/generators/feature-registry.js.map +1 -0
- package/src/generators/function/function.d.ts +1 -1
- package/src/generators/function/function.js +51 -6
- package/src/generators/function/function.js.map +1 -1
- package/src/generators/function/schema.d.ts +1 -0
- package/src/generators/function/schema.json +6 -1
- package/src/generators/logging-registry.d.ts +13 -0
- package/src/generators/logging-registry.js +62 -0
- package/src/generators/logging-registry.js.map +1 -0
- package/src/generators/post-generation-install.d.ts +67 -0
- package/src/generators/post-generation-install.js +3308 -0
- package/src/generators/post-generation-install.js.map +1 -0
- package/src/generators/preset/generator.js +3 -0
- package/src/generators/preset/generator.js.map +1 -1
- package/src/generators/preset/tool-versions.d.ts +18 -0
- package/src/generators/preset/tool-versions.js +22 -0
- package/src/generators/preset/tool-versions.js.map +1 -0
- package/src/generators/preset/workspace-files-developer-agent.d.ts +14 -0
- package/src/generators/preset/workspace-files-developer-agent.js +511 -0
- package/src/generators/preset/workspace-files-developer-agent.js.map +1 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.d.ts +6 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.js +406 -0
- package/src/generators/preset/workspace-files-foundation-workload-roles.js.map +1 -0
- package/src/generators/preset/workspace-files-performance-cost.d.ts +66 -0
- package/src/generators/preset/workspace-files-performance-cost.js +866 -0
- package/src/generators/preset/workspace-files-performance-cost.js.map +1 -0
- package/src/generators/preset/workspace-files-performance-load.d.ts +73 -0
- package/src/generators/preset/workspace-files-performance-load.js +801 -0
- package/src/generators/preset/workspace-files-performance-load.js.map +1 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.d.ts +9 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.js +1102 -0
- package/src/generators/preset/workspace-files-workload-role-baseline.js.map +1 -0
- package/src/generators/preset/workspace-files.d.ts +14 -1
- package/src/generators/preset/workspace-files.js +8189 -245
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/repository-policy/repository-policy.js +3 -47
- package/src/generators/repository-policy/repository-policy.js.map +1 -1
- package/src/generators/slice/built-in-plan-api-contract-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js +2656 -0
- package/src/generators/slice/built-in-plan-api-contract-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-api-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-api-files.js +1186 -0
- package/src/generators/slice/built-in-plan-api-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-async-work-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-async-work-files.js +7903 -0
- package/src/generators/slice/built-in-plan-async-work-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-authorization-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-authorization-files.js +2314 -0
- package/src/generators/slice/built-in-plan-authorization-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +559 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.js +1101 -0
- package/src/generators/slice/built-in-plan-data-deletion-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +249 -0
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-files.js +1530 -0
- package/src/generators/slice/built-in-plan-data-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-files.js +750 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +828 -0
- package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-retention-job-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +2604 -0
- package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +144 -0
- package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-data-rights-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-data-rights-files.js +4628 -0
- package/src/generators/slice/built-in-plan-data-rights-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +1901 -0
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +485 -0
- package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-foundation-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-foundation-files.js +184 -0
- package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-frontend-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-frontend-files.js +2233 -0
- package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js +3038 -0
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js +312 -0
- package/src/generators/slice/built-in-plan-infrastructure-safety-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-metadata-files.d.ts +3 -0
- package/src/generators/slice/built-in-plan-metadata-files.js +219 -0
- package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-operations-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-operations-files.js +630 -0
- package/src/generators/slice/built-in-plan-operations-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-performance-files.d.ts +3 -0
- package/src/generators/slice/built-in-plan-performance-files.js +3293 -0
- package/src/generators/slice/built-in-plan-performance-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-project-files.d.ts +3 -0
- package/src/generators/slice/built-in-plan-project-files.js +979 -0
- package/src/generators/slice/built-in-plan-project-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-release-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-release-files.js +6387 -0
- package/src/generators/slice/built-in-plan-release-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-reliability-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-reliability-files.js +4499 -0
- package/src/generators/slice/built-in-plan-reliability-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-secret-runtime-files.js +1004 -0
- package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-shared.d.ts +53 -0
- package/src/generators/slice/built-in-plan-shared.js +113 -0
- package/src/generators/slice/built-in-plan-shared.js.map +1 -0
- package/src/generators/slice/built-in-plan-testing-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-testing-files.js +2356 -0
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-verification-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-verification-files.js +358 -0
- package/src/generators/slice/built-in-plan-verification-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-web-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-web-files.js +430 -0
- package/src/generators/slice/built-in-plan-web-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-workload-role-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-workload-role-files.js +2356 -0
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -0
- package/src/generators/slice/built-in-plan.d.ts +8 -0
- package/src/generators/slice/built-in-plan.js +264 -0
- package/src/generators/slice/built-in-plan.js.map +1 -0
- package/src/generators/slice/schema.json +1 -1
- package/src/generators/slice/slice.d.ts +1 -22
- package/src/generators/slice/slice.js +185 -1510
- package/src/generators/slice/slice.js.map +1 -1
- package/src/generators/upgrade/schema.d.ts +2 -0
- package/src/generators/upgrade/schema.json +11 -1
- package/src/generators/upgrade/upgrade.d.ts +51 -1
- package/src/generators/upgrade/upgrade.js +820 -82
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/generators/workload/frozen-container.d.ts +8 -0
- package/src/generators/workload/frozen-container.js +36 -0
- package/src/generators/workload/frozen-container.js.map +1 -0
- package/src/generators/workload/performance-registration.d.ts +32 -0
- package/src/generators/workload/performance-registration.js +226 -0
- package/src/generators/workload/performance-registration.js.map +1 -0
- package/src/generators/workload/reliability-registration.d.ts +16 -0
- package/src/generators/workload/reliability-registration.js +150 -0
- package/src/generators/workload/reliability-registration.js.map +1 -0
- package/src/generators/workload/schema.d.ts +1 -0
- package/src/generators/workload/schema.json +6 -1
- package/src/generators/workload/service-performance-files.d.ts +4 -0
- package/src/generators/workload/service-performance-files.js +1846 -0
- package/src/generators/workload/service-performance-files.js.map +1 -0
- package/src/generators/workload/workload.d.ts +3 -2
- package/src/generators/workload/workload.js +1083 -18
- package/src/generators/workload/workload.js.map +1 -1
- package/src/git-policy/action-pins.d.ts +10 -0
- package/src/git-policy/action-pins.js +10 -0
- package/src/git-policy/action-pins.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +14 -1
- package/src/git-policy/github-bootstrap.js +274 -46
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/index.js.map +1 -1
- package/src/infrastructure-change/convergence.d.ts +13 -0
- package/src/infrastructure-change/convergence.js +85 -0
- package/src/infrastructure-change/convergence.js.map +1 -0
- package/src/infrastructure-change/drift.d.ts +13 -0
- package/src/infrastructure-change/drift.js +112 -0
- package/src/infrastructure-change/drift.js.map +1 -0
- package/src/infrastructure-change/index.d.ts +7 -0
- package/src/infrastructure-change/index.js +11 -0
- package/src/infrastructure-change/index.js.map +1 -0
- package/src/infrastructure-change/model.d.ts +253 -0
- package/src/infrastructure-change/model.js +28 -0
- package/src/infrastructure-change/model.js.map +1 -0
- package/src/infrastructure-change/normalize.d.ts +12 -0
- package/src/infrastructure-change/normalize.js +245 -0
- package/src/infrastructure-change/normalize.js.map +1 -0
- package/src/infrastructure-change/plan.d.ts +40 -0
- package/src/infrastructure-change/plan.js +361 -0
- package/src/infrastructure-change/plan.js.map +1 -0
- package/src/infrastructure-change/risk.d.ts +7 -0
- package/src/infrastructure-change/risk.js +269 -0
- package/src/infrastructure-change/risk.js.map +1 -0
- package/src/infrastructure-change/summary.d.ts +20 -0
- package/src/infrastructure-change/summary.js +130 -0
- package/src/infrastructure-change/summary.js.map +1 -0
- package/src/local-dev/context-discovery.d.ts +2 -0
- package/src/local-dev/context-discovery.js +59 -0
- package/src/local-dev/context-discovery.js.map +1 -0
- package/src/local-dev/contract-validation.d.ts +15 -0
- package/src/local-dev/contract-validation.js +84 -0
- package/src/local-dev/contract-validation.js.map +1 -0
- package/src/local-dev/contracts.d.ts +10 -1
- package/src/local-dev/contracts.js +57 -5
- package/src/local-dev/contracts.js.map +1 -1
- package/src/local-dev/control-contract.d.ts +67 -0
- package/src/local-dev/control-contract.js +180 -0
- package/src/local-dev/control-contract.js.map +1 -0
- package/src/local-dev/dev-command-core.d.ts +60 -0
- package/src/local-dev/dev-command-core.js +89 -0
- package/src/local-dev/dev-command-core.js.map +1 -0
- package/src/local-dev/effects.d.ts +16 -0
- package/src/local-dev/effects.js +63 -0
- package/src/local-dev/effects.js.map +1 -0
- package/src/local-dev/environment-inspector.d.ts +125 -0
- package/src/local-dev/environment-inspector.js +687 -0
- package/src/local-dev/environment-inspector.js.map +1 -0
- package/src/local-dev/environment-state.d.ts +113 -0
- package/src/local-dev/environment-state.js +463 -0
- package/src/local-dev/environment-state.js.map +1 -0
- package/src/local-dev/errors.d.ts +6 -0
- package/src/local-dev/errors.js +41 -0
- package/src/local-dev/errors.js.map +1 -0
- package/src/local-dev/fleet-client.d.ts +33 -0
- package/src/local-dev/fleet-client.js +156 -0
- package/src/local-dev/fleet-client.js.map +1 -0
- package/src/local-dev/fleet-configuration.d.ts +13 -0
- package/src/local-dev/fleet-configuration.js +71 -0
- package/src/local-dev/fleet-configuration.js.map +1 -0
- package/src/local-dev/fleet-inventory.d.ts +56 -0
- package/src/local-dev/fleet-inventory.js +469 -0
- package/src/local-dev/fleet-inventory.js.map +1 -0
- package/src/local-dev/fleet-ipc.d.ts +28 -0
- package/src/local-dev/fleet-ipc.js +267 -0
- package/src/local-dev/fleet-ipc.js.map +1 -0
- package/src/local-dev/fleet-launcher.d.ts +53 -0
- package/src/local-dev/fleet-launcher.js +379 -0
- package/src/local-dev/fleet-launcher.js.map +1 -0
- package/src/local-dev/fleet-lock.d.ts +41 -0
- package/src/local-dev/fleet-lock.js +135 -0
- package/src/local-dev/fleet-lock.js.map +1 -0
- package/src/local-dev/fleet-logs.d.ts +66 -0
- package/src/local-dev/fleet-logs.js +542 -0
- package/src/local-dev/fleet-logs.js.map +1 -0
- package/src/local-dev/fleet-process.d.ts +50 -0
- package/src/local-dev/fleet-process.js +289 -0
- package/src/local-dev/fleet-process.js.map +1 -0
- package/src/local-dev/fleet-runtime.d.ts +22 -0
- package/src/local-dev/fleet-runtime.js +368 -0
- package/src/local-dev/fleet-runtime.js.map +1 -0
- package/src/local-dev/fleet-session.d.ts +70 -0
- package/src/local-dev/fleet-session.js +301 -0
- package/src/local-dev/fleet-session.js.map +1 -0
- package/src/local-dev/fleet-slice-runner-daemon.d.ts +1 -0
- package/src/local-dev/fleet-slice-runner-daemon.js +21 -0
- package/src/local-dev/fleet-slice-runner-daemon.js.map +1 -0
- package/src/local-dev/fleet-startup.d.ts +22 -0
- package/src/local-dev/fleet-startup.js +114 -0
- package/src/local-dev/fleet-startup.js.map +1 -0
- package/src/local-dev/fleet-state.d.ts +62 -0
- package/src/local-dev/fleet-state.js +297 -0
- package/src/local-dev/fleet-state.js.map +1 -0
- package/src/local-dev/fleet-supervisor-daemon.d.ts +1 -0
- package/src/local-dev/fleet-supervisor-daemon.js +41 -0
- package/src/local-dev/fleet-supervisor-daemon.js.map +1 -0
- package/src/local-dev/fleet-supervisor.d.ts +52 -0
- package/src/local-dev/fleet-supervisor.js +384 -0
- package/src/local-dev/fleet-supervisor.js.map +1 -0
- package/src/local-dev/gateway.js +1 -1
- package/src/local-dev/gateway.js.map +1 -1
- package/src/local-dev/identity.js +4 -6
- package/src/local-dev/identity.js.map +1 -1
- package/src/local-dev/index.d.ts +1 -0
- package/src/local-dev/index.js +1 -0
- package/src/local-dev/index.js.map +1 -1
- package/src/local-dev/local-context.d.ts +62 -0
- package/src/local-dev/local-context.js +299 -0
- package/src/local-dev/local-context.js.map +1 -0
- package/src/local-dev/nx-workspace.d.ts +45 -0
- package/src/local-dev/nx-workspace.js +228 -0
- package/src/local-dev/nx-workspace.js.map +1 -0
- package/src/local-dev/personal-secret-ledger.d.ts +75 -0
- package/src/local-dev/personal-secret-ledger.js +391 -0
- package/src/local-dev/personal-secret-ledger.js.map +1 -0
- package/src/local-dev/personal-stage.d.ts +18 -0
- package/src/local-dev/personal-stage.js +129 -0
- package/src/local-dev/personal-stage.js.map +1 -0
- package/src/local-dev/private-state.d.ts +17 -0
- package/src/local-dev/private-state.js +254 -0
- package/src/local-dev/private-state.js.map +1 -0
- package/src/local-dev/process-identity.d.ts +9 -0
- package/src/local-dev/process-identity.js +79 -0
- package/src/local-dev/process-identity.js.map +1 -0
- package/src/local-dev/runtime-paths.d.ts +17 -0
- package/src/local-dev/runtime-paths.js +33 -0
- package/src/local-dev/runtime-paths.js.map +1 -0
- package/src/local-dev/secret-slice-claim.d.ts +17 -0
- package/src/local-dev/secret-slice-claim.js +75 -0
- package/src/local-dev/secret-slice-claim.js.map +1 -0
- package/src/local-dev/slice-claim.d.ts +36 -0
- package/src/local-dev/slice-claim.js +199 -0
- package/src/local-dev/slice-claim.js.map +1 -0
- package/src/local-dev/slice-runner.d.ts +121 -0
- package/src/local-dev/slice-runner.js +715 -0
- package/src/local-dev/slice-runner.js.map +1 -0
- package/src/local-dev/tls.js +51 -17
- package/src/local-dev/tls.js.map +1 -1
- package/src/ownership/composition.d.ts +5 -2
- package/src/ownership/composition.js +26 -3
- package/src/ownership/composition.js.map +1 -1
- package/src/ownership/hash.d.ts +1 -1
- package/src/ownership/hash.js +3 -1
- package/src/ownership/hash.js.map +1 -1
- package/src/ownership/record.d.ts +32 -1
- package/src/ownership/record.js +139 -2
- package/src/ownership/record.js.map +1 -1
- package/src/performance/cost-collection.d.ts +225 -0
- package/src/performance/cost-collection.js +825 -0
- package/src/performance/cost-collection.js.map +1 -0
- package/src/performance/cost-controls.d.ts +131 -0
- package/src/performance/cost-controls.js +336 -0
- package/src/performance/cost-controls.js.map +1 -0
- package/src/performance/evidence.d.ts +140 -0
- package/src/performance/evidence.js +599 -0
- package/src/performance/evidence.js.map +1 -0
- package/src/performance/index.d.ts +10 -0
- package/src/performance/index.js +14 -0
- package/src/performance/index.js.map +1 -0
- package/src/performance/load-safety.d.ts +84 -0
- package/src/performance/load-safety.js +346 -0
- package/src/performance/load-safety.js.map +1 -0
- package/src/performance/model.d.ts +129 -0
- package/src/performance/model.js +452 -0
- package/src/performance/model.js.map +1 -0
- package/src/performance/pr-artifact-budget.d.ts +42 -0
- package/src/performance/pr-artifact-budget.js +151 -0
- package/src/performance/pr-artifact-budget.js.map +1 -0
- package/src/performance/runner.d.ts +75 -0
- package/src/performance/runner.js +227 -0
- package/src/performance/runner.js.map +1 -0
- package/src/performance/scenario-endpoint.d.ts +1 -0
- package/src/performance/scenario-endpoint.js +5 -0
- package/src/performance/scenario-endpoint.js.map +1 -0
- package/src/performance/scenario.d.ts +193 -0
- package/src/performance/scenario.js +736 -0
- package/src/performance/scenario.js.map +1 -0
- package/src/performance/test-fixtures.d.ts +7 -0
- package/src/performance/test-fixtures.js +169 -0
- package/src/performance/test-fixtures.js.map +1 -0
- package/src/performance/unit-cost.d.ts +105 -0
- package/src/performance/unit-cost.js +364 -0
- package/src/performance/unit-cost.js.map +1 -0
- package/src/release/index.d.ts +4 -0
- package/src/release/index.js +8 -0
- package/src/release/index.js.map +1 -0
- package/src/release/lifecycle.d.ts +132 -0
- package/src/release/lifecycle.js +411 -0
- package/src/release/lifecycle.js.map +1 -0
- package/src/release/manifest.d.ts +148 -0
- package/src/release/manifest.js +503 -0
- package/src/release/manifest.js.map +1 -0
- package/src/release/rollout.d.ts +170 -0
- package/src/release/rollout.js +578 -0
- package/src/release/rollout.js.map +1 -0
- package/src/release/source-authority.d.ts +19 -0
- package/src/release/source-authority.js +195 -0
- package/src/release/source-authority.js.map +1 -0
- package/src/repository-policy/deployment-plan.d.ts +2 -1
- package/src/repository-policy/deployment-plan.js +2 -0
- package/src/repository-policy/deployment-plan.js.map +1 -1
- package/src/repository-policy/policy.d.ts +1 -1
- package/src/repository-policy/policy.js +29 -25
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/aggregate-count-evidence-v1.json +135 -0
- package/src/schemas/api-contract-baseline-v1.json +674 -0
- package/src/schemas/api-contract-compatibility-report-v1.json +126 -0
- package/src/schemas/aws-billing-actual-evidence-v1.json +407 -0
- package/src/schemas/blueprint-v1.json +18 -1
- package/src/schemas/data-lifecycle-policy-v1.json +517 -0
- package/src/schemas/generated-ownership-v2.json +22 -0
- package/src/schemas/performance-policy-v1.json +335 -0
- package/src/schemas/repository-policy-v1.json +1 -0
- package/src/schemas/testing-evidence-quarantine-v1.json +168 -0
- package/src/schemas/testing-evidence-result-v1.json +319 -0
- package/src/secrets/adapters.d.ts +49 -0
- package/src/secrets/adapters.js +184 -0
- package/src/secrets/adapters.js.map +1 -0
- package/src/secrets/aws-control-plane.d.ts +20 -0
- package/src/secrets/aws-control-plane.js +255 -0
- package/src/secrets/aws-control-plane.js.map +1 -0
- package/src/secrets/declaration-verification.d.ts +11 -0
- package/src/secrets/declaration-verification.js +68 -0
- package/src/secrets/declaration-verification.js.map +1 -0
- package/src/secrets/guardrails.d.ts +26 -0
- package/src/secrets/guardrails.js +328 -0
- package/src/secrets/guardrails.js.map +1 -0
- package/src/secrets/index.d.ts +12 -0
- package/src/secrets/index.js +16 -0
- package/src/secrets/index.js.map +1 -0
- package/src/secrets/metadata.d.ts +48 -0
- package/src/secrets/metadata.js +171 -0
- package/src/secrets/metadata.js.map +1 -0
- package/src/secrets/model.d.ts +49 -0
- package/src/secrets/model.js +125 -0
- package/src/secrets/model.js.map +1 -0
- package/src/secrets/operator.d.ts +101 -0
- package/src/secrets/operator.js +229 -0
- package/src/secrets/operator.js.map +1 -0
- package/src/secrets/permissions.d.ts +34 -0
- package/src/secrets/permissions.js +86 -0
- package/src/secrets/permissions.js.map +1 -0
- package/src/secrets/personal-command.d.ts +83 -0
- package/src/secrets/personal-command.js +197 -0
- package/src/secrets/personal-command.js.map +1 -0
- package/src/secrets/personal-ledger.d.ts +21 -0
- package/src/secrets/personal-ledger.js +135 -0
- package/src/secrets/personal-ledger.js.map +1 -0
- package/src/secrets/registry.d.ts +9 -0
- package/src/secrets/registry.js +86 -0
- package/src/secrets/registry.js.map +1 -0
- package/src/secrets/verification.d.ts +18 -0
- package/src/secrets/verification.js +139 -0
- package/src/secrets/verification.js.map +1 -0
- package/src/secrets/workload-access.d.ts +46 -0
- package/src/secrets/workload-access.js +163 -0
- package/src/secrets/workload-access.js.map +1 -0
- package/src/supply-chain/action-pins.d.ts +26 -0
- package/src/supply-chain/action-pins.js +208 -0
- package/src/supply-chain/action-pins.js.map +1 -0
- package/src/supply-chain/blueprint-trust-git.d.ts +130 -0
- package/src/supply-chain/blueprint-trust-git.js +1581 -0
- package/src/supply-chain/blueprint-trust-git.js.map +1 -0
- package/src/supply-chain/blueprint-trust-process.d.ts +4 -0
- package/src/supply-chain/blueprint-trust-process.js +88 -0
- package/src/supply-chain/blueprint-trust-process.js.map +1 -0
- package/src/supply-chain/blueprint-trust.d.ts +354 -0
- package/src/supply-chain/blueprint-trust.js +1558 -0
- package/src/supply-chain/blueprint-trust.js.map +1 -0
- package/src/supply-chain/blueprint-verification-authority.d.ts +62 -0
- package/src/supply-chain/blueprint-verification-authority.js +588 -0
- package/src/supply-chain/blueprint-verification-authority.js.map +1 -0
- package/src/supply-chain/blueprint-verification-execution.d.ts +68 -0
- package/src/supply-chain/blueprint-verification-execution.js +624 -0
- package/src/supply-chain/blueprint-verification-execution.js.map +1 -0
- package/src/supply-chain/build-output.d.ts +35 -0
- package/src/supply-chain/build-output.js +398 -0
- package/src/supply-chain/build-output.js.map +1 -0
- package/src/supply-chain/common.d.ts +21 -0
- package/src/supply-chain/common.js +107 -0
- package/src/supply-chain/common.js.map +1 -0
- package/src/supply-chain/container-artifact-contract.d.ts +23 -0
- package/src/supply-chain/container-artifact-contract.js +208 -0
- package/src/supply-chain/container-artifact-contract.js.map +1 -0
- package/src/supply-chain/container-release.d.ts +119 -0
- package/src/supply-chain/container-release.js +496 -0
- package/src/supply-chain/container-release.js.map +1 -0
- package/src/supply-chain/ecr-applicability.d.ts +19 -0
- package/src/supply-chain/ecr-applicability.js +103 -0
- package/src/supply-chain/ecr-applicability.js.map +1 -0
- package/src/supply-chain/ecr-scanning.d.ts +83 -0
- package/src/supply-chain/ecr-scanning.js +321 -0
- package/src/supply-chain/ecr-scanning.js.map +1 -0
- package/src/supply-chain/ecs-deployed-image.d.ts +85 -0
- package/src/supply-chain/ecs-deployed-image.js +300 -0
- package/src/supply-chain/ecs-deployed-image.js.map +1 -0
- package/src/supply-chain/errors.d.ts +6 -0
- package/src/supply-chain/errors.js +15 -0
- package/src/supply-chain/errors.js.map +1 -0
- package/src/supply-chain/generated-blueprint-trust-command.d.ts +4 -0
- package/src/supply-chain/generated-blueprint-trust-command.js +1362 -0
- package/src/supply-chain/generated-blueprint-trust-command.js.map +1 -0
- package/src/supply-chain/generated-command.d.ts +1 -0
- package/src/supply-chain/generated-command.js +687 -0
- package/src/supply-chain/generated-command.js.map +1 -0
- package/src/supply-chain/generated-container-release-command.d.ts +16 -0
- package/src/supply-chain/generated-container-release-command.js +2087 -0
- package/src/supply-chain/generated-container-release-command.js.map +1 -0
- package/src/supply-chain/generated-ecr-collector-command.d.ts +21 -0
- package/src/supply-chain/generated-ecr-collector-command.js +1338 -0
- package/src/supply-chain/generated-ecr-collector-command.js.map +1 -0
- package/src/supply-chain/generated-files.d.ts +13 -0
- package/src/supply-chain/generated-files.js +401 -0
- package/src/supply-chain/generated-files.js.map +1 -0
- package/src/supply-chain/generated-scanner-command.d.ts +1 -0
- package/src/supply-chain/generated-scanner-command.js +718 -0
- package/src/supply-chain/generated-scanner-command.js.map +1 -0
- package/src/supply-chain/index.d.ts +28 -0
- package/src/supply-chain/index.js +35 -0
- package/src/supply-chain/index.js.map +1 -0
- package/src/supply-chain/inventory.d.ts +79 -0
- package/src/supply-chain/inventory.js +717 -0
- package/src/supply-chain/inventory.js.map +1 -0
- package/src/supply-chain/license-expression.d.ts +7 -0
- package/src/supply-chain/license-expression.js +128 -0
- package/src/supply-chain/license-expression.js.map +1 -0
- package/src/supply-chain/pnpm-toolchain-authority.d.ts +31 -0
- package/src/supply-chain/pnpm-toolchain-authority.js +316 -0
- package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -0
- package/src/supply-chain/policy.d.ts +105 -0
- package/src/supply-chain/policy.js +301 -0
- package/src/supply-chain/policy.js.map +1 -0
- package/src/supply-chain/publication.d.ts +48 -0
- package/src/supply-chain/publication.js +85 -0
- package/src/supply-chain/publication.js.map +1 -0
- package/src/supply-chain/sbom.d.ts +228 -0
- package/src/supply-chain/sbom.js +826 -0
- package/src/supply-chain/sbom.js.map +1 -0
- package/src/supply-chain/scanner.d.ts +77 -0
- package/src/supply-chain/scanner.js +502 -0
- package/src/supply-chain/scanner.js.map +1 -0
- package/src/supply-chain/vendored-source.d.ts +95 -0
- package/src/supply-chain/vendored-source.js +315 -0
- package/src/supply-chain/vendored-source.js.map +1 -0
- package/src/testing-evidence/fixture-scope.d.ts +52 -0
- package/src/testing-evidence/fixture-scope.js +140 -0
- package/src/testing-evidence/fixture-scope.js.map +1 -0
- package/src/testing-evidence/index.d.ts +5 -0
- package/src/testing-evidence/index.js +9 -0
- package/src/testing-evidence/index.js.map +1 -0
- package/src/testing-evidence/quarantine.d.ts +48 -0
- package/src/testing-evidence/quarantine.js +253 -0
- package/src/testing-evidence/quarantine.js.map +1 -0
- package/src/testing-evidence/result.d.ts +108 -0
- package/src/testing-evidence/result.js +571 -0
- package/src/testing-evidence/result.js.map +1 -0
- package/src/testing-evidence/runner-adapter.d.ts +51 -0
- package/src/testing-evidence/runner-adapter.js +288 -0
- package/src/testing-evidence/runner-adapter.js.map +1 -0
- package/src/testing-evidence/sensitive-artifacts.d.ts +13 -0
- package/src/testing-evidence/sensitive-artifacts.js +93 -0
- package/src/testing-evidence/sensitive-artifacts.js.map +1 -0
- package/src/work-markers/work-markers.js +4 -3
- package/src/work-markers/work-markers.js.map +1 -1
- package/src/workload-roles/account-baseline.d.ts +132 -0
- package/src/workload-roles/account-baseline.js +743 -0
- package/src/workload-roles/account-baseline.js.map +1 -0
- package/src/workload-roles/authority.d.ts +38 -0
- package/src/workload-roles/authority.js +156 -0
- package/src/workload-roles/authority.js.map +1 -0
- package/src/workload-roles/aws-adapter.d.ts +99 -0
- package/src/workload-roles/aws-adapter.js +416 -0
- package/src/workload-roles/aws-adapter.js.map +1 -0
- package/src/workload-roles/aws-internal-handler.d.ts +27 -0
- package/src/workload-roles/aws-internal-handler.js +58 -0
- package/src/workload-roles/aws-internal-handler.js.map +1 -0
- package/src/workload-roles/aws-s3-state.d.ts +52 -0
- package/src/workload-roles/aws-s3-state.js +164 -0
- package/src/workload-roles/aws-s3-state.js.map +1 -0
- package/src/workload-roles/aws-sdk-control-plane.d.ts +12 -0
- package/src/workload-roles/aws-sdk-control-plane.js +291 -0
- package/src/workload-roles/aws-sdk-control-plane.js.map +1 -0
- package/src/workload-roles/boundary.d.ts +33 -0
- package/src/workload-roles/boundary.js +130 -0
- package/src/workload-roles/boundary.js.map +1 -0
- package/src/workload-roles/convergence.d.ts +66 -0
- package/src/workload-roles/convergence.js +186 -0
- package/src/workload-roles/convergence.js.map +1 -0
- package/src/workload-roles/effective-policy.d.ts +51 -0
- package/src/workload-roles/effective-policy.js +183 -0
- package/src/workload-roles/effective-policy.js.map +1 -0
- package/src/workload-roles/handler.d.ts +43 -0
- package/src/workload-roles/handler.js +130 -0
- package/src/workload-roles/handler.js.map +1 -0
- package/src/workload-roles/history.d.ts +47 -0
- package/src/workload-roles/history.js +324 -0
- package/src/workload-roles/history.js.map +1 -0
- package/src/workload-roles/index.d.ts +17 -0
- package/src/workload-roles/index.js +21 -0
- package/src/workload-roles/index.js.map +1 -0
- package/src/workload-roles/injection.d.ts +119 -0
- package/src/workload-roles/injection.js +495 -0
- package/src/workload-roles/injection.js.map +1 -0
- package/src/workload-roles/manifest.d.ts +54 -0
- package/src/workload-roles/manifest.js +343 -0
- package/src/workload-roles/manifest.js.map +1 -0
- package/src/workload-roles/provisioner.d.ts +70 -0
- package/src/workload-roles/provisioner.js +231 -0
- package/src/workload-roles/provisioner.js.map +1 -0
- package/src/workload-roles/registration.d.ts +101 -0
- package/src/workload-roles/registration.js +457 -0
- package/src/workload-roles/registration.js.map +1 -0
- package/src/workload-roles/stage-boundary.d.ts +31 -0
- package/src/workload-roles/stage-boundary.js +208 -0
- package/src/workload-roles/stage-boundary.js.map +1 -0
- package/src/workload-roles/standard-slice.d.ts +54 -0
- package/src/workload-roles/standard-slice.js +252 -0
- package/src/workload-roles/standard-slice.js.map +1 -0
- package/migrations/standard-pre-v1-to-v1.md +0 -11
- package/src/foundation/aurora-logical-database.d.ts +0 -76
- package/src/foundation/aurora-logical-database.js +0 -419
- package/src/foundation/aurora-logical-database.js.map +0 -1
|
@@ -0,0 +1,2356 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderProductTestingFiles = renderProductTestingFiles;
|
|
4
|
+
const built_in_plan_shared_js_1 = require("./built-in-plan-shared.js");
|
|
5
|
+
/** @internal */
|
|
6
|
+
function renderProductTestingFiles(name, pascalName) {
|
|
7
|
+
return {
|
|
8
|
+
'database/test-compose.yml': `services:
|
|
9
|
+
database:
|
|
10
|
+
image: postgres:17-alpine
|
|
11
|
+
environment:
|
|
12
|
+
POSTGRES_DB: postgres
|
|
13
|
+
POSTGRES_PASSWORD: ebk_test
|
|
14
|
+
POSTGRES_USER: ebk_test
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: [CMD-SHELL, "pg_isready -U ebk_test -d postgres"]
|
|
17
|
+
interval: 1s
|
|
18
|
+
timeout: 3s
|
|
19
|
+
retries: 30
|
|
20
|
+
labels:
|
|
21
|
+
dev.empirebuilderkit.owner: test-integration
|
|
22
|
+
dev.empirebuilderkit.run: \${EBK_TEST_RUN_ID}
|
|
23
|
+
dev.empirebuilderkit.slice: ${name}
|
|
24
|
+
ports:
|
|
25
|
+
- '127.0.0.1::5432'
|
|
26
|
+
tmpfs:
|
|
27
|
+
- /var/lib/postgresql/data
|
|
28
|
+
`,
|
|
29
|
+
'docs/testing.md': `# ${name} testing
|
|
30
|
+
|
|
31
|
+
Use the narrowest proof layer that can establish the behavior. Source tests are
|
|
32
|
+
hermetic and credential-free. The \`test-integration\` target owns a disposable
|
|
33
|
+
PostgreSQL container, creates one database per worker, applies the committed
|
|
34
|
+
migrations, runs deterministic fixtures, and removes every resource it creates.
|
|
35
|
+
It never uses the persistent database started by \`ebk dev\`.
|
|
36
|
+
|
|
37
|
+
Every fixture run records its seed and run scope. Keep fixture data synthetic.
|
|
38
|
+
Do not copy customer data, credentials, browser storage, tokens, request bodies,
|
|
39
|
+
or protected identifiers into source, test output, or retained artifacts.
|
|
40
|
+
|
|
41
|
+
The published testing-evidence JSON Schemas validate structural interchange
|
|
42
|
+
only. Promotion and quarantine decisions must use
|
|
43
|
+
\`validateTestingEvidenceResult\` and \`validateTestingEvidenceQuarantine\`;
|
|
44
|
+
those validators enforce cross-field outcome rules and trusted time/workflow
|
|
45
|
+
context that JSON Schema cannot establish alone.
|
|
46
|
+
|
|
47
|
+
\`smoke-deployed-check\` only proves that Playwright can discover the suite. It
|
|
48
|
+
does not contact or prove a deployment. Governed staging smoke runs through the
|
|
49
|
+
protected \`Deployed smoke\` GitHub workflow and emits a candidate-, commit-,
|
|
50
|
+
stage-, and cleanup-attributed result. Configure each protected environment's
|
|
51
|
+
\`EBK_SMOKE_TARGETS\` variable as a JSON object from exact Slice name to its
|
|
52
|
+
credential-free HTTPS \`webUrl\` and \`apiUrl\`, Cognito \`userPoolId\`, and
|
|
53
|
+
\`clientId\`. Set \`EBK_SMOKE_AUTHORITY_VERSION\` to
|
|
54
|
+
\`cognito-admin-cloudwatch-observe-v1\` only after the protected role is reviewed for
|
|
55
|
+
\`DescribeUserPoolClient\`, \`AdminCreateUser\`, \`AdminSetUserPassword\`,
|
|
56
|
+
\`AdminAddUserToGroup\`, and \`AdminDeleteUser\` on those pools and
|
|
57
|
+
\`FilterLogEvents\` on each exact
|
|
58
|
+
\`/ebk/<stage>/<slice>/event-consumer\` log group. The workflow creates one
|
|
59
|
+
run-scoped synthetic customer in the Slice's narrow \`<slice>-test-writer\`
|
|
60
|
+
group and one distinct synthetic reviewer in \`<slice>-admin\`. It never grants
|
|
61
|
+
the customer administrator authority, strips AWS credentials from the browser
|
|
62
|
+
child,
|
|
63
|
+
observes the published event only from the credential-bearing parent, removes
|
|
64
|
+
both principals and all raw browser and log state, and retains only allowlisted
|
|
65
|
+
sanitized evidence.
|
|
66
|
+
|
|
67
|
+
The environment-only \`AWS_SMOKE_ROLE_ARN\` is a setup/observation role, never
|
|
68
|
+
a deployment role. Its GitHub OIDC trust is limited to this repository and the
|
|
69
|
+
matching protected environment. Its policy names only the reviewed user-pool
|
|
70
|
+
ARNs for the five Cognito actions above and the exact generated event-consumer
|
|
71
|
+
log-group ARNs for \`logs:FilterLogEvents\`. It has no CloudFormation, SST,
|
|
72
|
+
application mutation, wildcard Logs query, \`GetLogEvents\`, \`StartQuery\`, or
|
|
73
|
+
\`logs:Unmask\` authority. GitHub bootstrap verifies that the secret name exists
|
|
74
|
+
only in each protected environment; it never creates the secret or role and
|
|
75
|
+
cannot attest to the hidden role policy. Review that policy before setting the
|
|
76
|
+
authority-version acknowledgement.
|
|
77
|
+
|
|
78
|
+
A successful \`develop\` deployment automatically starts staging smoke for
|
|
79
|
+
every registered Slice target at that exact commit. The credential-free
|
|
80
|
+
\`staging-evidence\` promotion check downloads only artifacts from the managed
|
|
81
|
+
smoke workflow and rejects missing, mismatched, flaky, failed, incomplete, or
|
|
82
|
+
cleanup-deficient evidence.
|
|
83
|
+
|
|
84
|
+
One optional evidence-level retry replays the complete, identical browser or
|
|
85
|
+
live-integration proof. Consequently \`counts.retried\` is always zero or one.
|
|
86
|
+
The evidence adapter retains both attempts; any pass-after-fail outcome is
|
|
87
|
+
\`flaky\`, never \`passed\`. Runner-internal retries are forbidden. Source and
|
|
88
|
+
contract tests receive no automatic retry.
|
|
89
|
+
|
|
90
|
+
Skipped integration and Playwright tests fail unless their stable hashed test
|
|
91
|
+
ID matches one active runner-specific quarantine record. Matched skips count as
|
|
92
|
+
quarantined, and a record that is not observed by its runner also fails closed.
|
|
93
|
+
Local quarantine uses reviewed date expiry. A run-count record binds an
|
|
94
|
+
absolute \`.github/workflows/*.yml\` path and \`expiresAfterRun\`; it is accepted
|
|
95
|
+
only when GitHub supplies a matching \`GITHUB_WORKFLOW_REF\` and positive
|
|
96
|
+
\`GITHUB_RUN_NUMBER\`, and it must expire within 25 trusted workflow runs.
|
|
97
|
+
`,
|
|
98
|
+
'integration/record.spec.ts': `import { Client } from 'pg';
|
|
99
|
+
import { describe, expect, it } from 'vitest';
|
|
100
|
+
import { build${pascalName}RecordFixture } from '../testing/fixtures/records.js';
|
|
101
|
+
|
|
102
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
103
|
+
if (!databaseUrl) {
|
|
104
|
+
throw new Error('DATABASE_URL is required for ${name} local integration proof.');
|
|
105
|
+
}
|
|
106
|
+
const seed = process.env.EBK_TEST_SEED;
|
|
107
|
+
if (!seed) {
|
|
108
|
+
throw new Error('EBK_TEST_SEED is required for reproducible integration fixtures.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
describe('${name} database integration', () => {
|
|
112
|
+
it('writes and reads one deterministic record in its worker database', async () => {
|
|
113
|
+
const fixture = build${pascalName}RecordFixture(seed);
|
|
114
|
+
const client = new Client({ connectionString: databaseUrl });
|
|
115
|
+
await client.connect();
|
|
116
|
+
try {
|
|
117
|
+
await client.query(
|
|
118
|
+
\`INSERT INTO records
|
|
119
|
+
(id, name, owner_id, run_scope, test_expires_at)
|
|
120
|
+
VALUES ($1::uuid, $2, $3, $4, now() + INTERVAL '1 day')\`,
|
|
121
|
+
[fixture.id, fixture.name, 'synthetic-test-owner', seed],
|
|
122
|
+
);
|
|
123
|
+
const result = await client.query(
|
|
124
|
+
'SELECT id::text AS id, name FROM records WHERE id = $1::uuid',
|
|
125
|
+
[fixture.id],
|
|
126
|
+
);
|
|
127
|
+
expect(result.rows).toEqual([fixture]);
|
|
128
|
+
} finally {
|
|
129
|
+
await client.end();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
`,
|
|
134
|
+
'integration/migrations.spec.ts': `import { Client } from 'pg';
|
|
135
|
+
import { describe, expect, it } from 'vitest';
|
|
136
|
+
|
|
137
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
138
|
+
if (!databaseUrl) {
|
|
139
|
+
throw new Error('DATABASE_URL is required for ${name} local integration proof.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
describe('${name} migration integration', () => {
|
|
143
|
+
it('exposes the committed records table contract', async () => {
|
|
144
|
+
const requiredColumns = [
|
|
145
|
+
{ column_name: 'id', data_type: 'uuid' },
|
|
146
|
+
{ column_name: 'name', data_type: 'text' },
|
|
147
|
+
{ column_name: 'owner_id', data_type: 'text' },
|
|
148
|
+
{ column_name: 'run_scope', data_type: 'text' },
|
|
149
|
+
{ column_name: 'created_at', data_type: 'timestamp with time zone' },
|
|
150
|
+
];
|
|
151
|
+
const client = new Client({ connectionString: databaseUrl });
|
|
152
|
+
await client.connect();
|
|
153
|
+
try {
|
|
154
|
+
const result = await client.query(
|
|
155
|
+
\`SELECT column_name, data_type
|
|
156
|
+
FROM information_schema.columns
|
|
157
|
+
WHERE table_schema = 'public'
|
|
158
|
+
AND table_name = 'records'
|
|
159
|
+
AND column_name = ANY($1::text[])
|
|
160
|
+
ORDER BY ordinal_position\`,
|
|
161
|
+
[requiredColumns.map(({ column_name }) => column_name)],
|
|
162
|
+
);
|
|
163
|
+
expect(result.rows).toEqual(requiredColumns);
|
|
164
|
+
} finally {
|
|
165
|
+
await client.end();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
`,
|
|
170
|
+
'testing/fixtures/records.ts': `import { createTypedFixtureBuilder } from '@empire-builder-kit/nx/testing-evidence';
|
|
171
|
+
|
|
172
|
+
export interface ${pascalName}RecordFixture {
|
|
173
|
+
id: string;
|
|
174
|
+
name: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const build${pascalName}RecordFixture =
|
|
178
|
+
createTypedFixtureBuilder<${pascalName}RecordFixture>((fixture) => {
|
|
179
|
+
const token = fixture.token('record-id', 32);
|
|
180
|
+
return {
|
|
181
|
+
id: [
|
|
182
|
+
token.slice(0, 8),
|
|
183
|
+
token.slice(8, 12),
|
|
184
|
+
'4' + token.slice(13, 16),
|
|
185
|
+
'8' + token.slice(17, 20),
|
|
186
|
+
token.slice(20, 32),
|
|
187
|
+
].join('-'),
|
|
188
|
+
name: \`Synthetic ${pascalName} \${fixture.token('record-name', 12)}\`,
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
`,
|
|
192
|
+
'testing/quarantine.json': (0, built_in_plan_shared_js_1.json)({
|
|
193
|
+
records: [],
|
|
194
|
+
schemaVersion: 1,
|
|
195
|
+
}),
|
|
196
|
+
'testing/quarantine-runtime.mjs': `import { createHash } from 'node:crypto';
|
|
197
|
+
import { isAbsolute, relative } from 'node:path';
|
|
198
|
+
|
|
199
|
+
function portableTestPath(projectRoot, value) {
|
|
200
|
+
const path = isAbsolute(value)
|
|
201
|
+
? relative(projectRoot, value)
|
|
202
|
+
: value;
|
|
203
|
+
const normalized = path.replaceAll('\\\\', '/').replace(/^\\.\\//, '');
|
|
204
|
+
if (
|
|
205
|
+
!normalized ||
|
|
206
|
+
normalized === '..' ||
|
|
207
|
+
normalized.startsWith('../') ||
|
|
208
|
+
normalized.includes('/../')
|
|
209
|
+
) {
|
|
210
|
+
throw new Error('Runner report contains a test outside the Product Slice.');
|
|
211
|
+
}
|
|
212
|
+
return normalized;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function testId(runner, parts, classification = 'general') {
|
|
216
|
+
return (
|
|
217
|
+
runner +
|
|
218
|
+
':' +
|
|
219
|
+
classification +
|
|
220
|
+
':' +
|
|
221
|
+
createHash('sha256').update(parts.join('\\0')).digest('hex')
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function githubWorkflowRun(environment = process.env) {
|
|
226
|
+
const reference = environment.GITHUB_WORKFLOW_REF;
|
|
227
|
+
const number = Number(environment.GITHUB_RUN_NUMBER);
|
|
228
|
+
if (!reference || !Number.isSafeInteger(number) || number < 1) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const match =
|
|
232
|
+
/\\/(\\.github\\/workflows\\/[A-Za-z0-9._/-]+\\.ya?ml)@/.exec(reference);
|
|
233
|
+
return match ? { number, workflow: match[1] } : undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function playwrightSkippedTestIds(report, projectRoot) {
|
|
237
|
+
const ids = [];
|
|
238
|
+
const visit = (suite, titles = []) => {
|
|
239
|
+
const nextTitles = suite.title ? [...titles, suite.title] : titles;
|
|
240
|
+
for (const spec of suite.specs ?? []) {
|
|
241
|
+
const file = portableTestPath(
|
|
242
|
+
projectRoot,
|
|
243
|
+
spec.file ?? suite.file ?? '',
|
|
244
|
+
);
|
|
245
|
+
for (const test of spec.tests ?? []) {
|
|
246
|
+
const results = test.results ?? [];
|
|
247
|
+
if (
|
|
248
|
+
test.expectedStatus === 'skipped' ||
|
|
249
|
+
(results.length > 0 &&
|
|
250
|
+
results.every(({ status }) => status === 'skipped'))
|
|
251
|
+
) {
|
|
252
|
+
ids.push(
|
|
253
|
+
testId('playwright', [
|
|
254
|
+
file,
|
|
255
|
+
...nextTitles,
|
|
256
|
+
spec.title ?? '',
|
|
257
|
+
test.projectName ?? test.projectId ?? '',
|
|
258
|
+
], file === 'e2e/frontend-accessibility.spec.ts'
|
|
259
|
+
? 'accessibility'
|
|
260
|
+
: 'general'),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (const child of suite.suites ?? []) visit(child, nextTitles);
|
|
266
|
+
};
|
|
267
|
+
for (const suite of report?.suites ?? []) visit(suite);
|
|
268
|
+
return [...new Set(ids)].sort();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function vitestIntegrationSkippedTestIds(reports, projectRoot) {
|
|
272
|
+
const ids = [];
|
|
273
|
+
for (const report of reports) {
|
|
274
|
+
for (const fileResult of report?.testResults ?? []) {
|
|
275
|
+
const file = portableTestPath(
|
|
276
|
+
projectRoot,
|
|
277
|
+
fileResult.name ?? '',
|
|
278
|
+
);
|
|
279
|
+
for (const assertion of fileResult.assertionResults ?? []) {
|
|
280
|
+
if (['pending', 'skipped', 'todo'].includes(assertion.status)) {
|
|
281
|
+
ids.push(
|
|
282
|
+
testId('vitest-integration', [
|
|
283
|
+
file,
|
|
284
|
+
assertion.fullName ?? assertion.title ?? '',
|
|
285
|
+
]),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return [...new Set(ids)].sort();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function reconcileRunnerQuarantine(options) {
|
|
295
|
+
const prefix = options.runner + ':';
|
|
296
|
+
const records = options.records.filter(({ testId }) =>
|
|
297
|
+
testId.startsWith(prefix),
|
|
298
|
+
);
|
|
299
|
+
const declared = new Map(records.map((record) => [record.testId, record]));
|
|
300
|
+
const skipped = [...new Set(options.skippedTestIds)].sort();
|
|
301
|
+
const matched = skipped.filter((id) => declared.has(id));
|
|
302
|
+
const unmatched = skipped.filter((id) => !declared.has(id));
|
|
303
|
+
const observed = new Set(skipped);
|
|
304
|
+
const unobserved = [...declared.keys()]
|
|
305
|
+
.filter((id) => !observed.has(id))
|
|
306
|
+
.sort();
|
|
307
|
+
const findings = [];
|
|
308
|
+
if (unmatched.length > 0) {
|
|
309
|
+
findings.push({
|
|
310
|
+
classification: 'environment-drift',
|
|
311
|
+
summary:
|
|
312
|
+
'Skipped tests lack active quarantine records: ' +
|
|
313
|
+
unmatched.slice(0, 8).join(', ') +
|
|
314
|
+
(unmatched.length > 8 ? ', ...' : ''),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (unobserved.length > 0) {
|
|
318
|
+
findings.push({
|
|
319
|
+
classification: 'environment-drift',
|
|
320
|
+
summary:
|
|
321
|
+
'Active quarantine records did not match skipped tests: ' +
|
|
322
|
+
unobserved.slice(0, 8).join(', ') +
|
|
323
|
+
(unobserved.length > 8 ? ', ...' : ''),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
findings,
|
|
328
|
+
quarantined: matched.length,
|
|
329
|
+
skipped: unmatched.length,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
`,
|
|
333
|
+
'scripts/run-integration-tests.mjs': `import { spawn, spawnSync } from 'node:child_process';
|
|
334
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
335
|
+
import {
|
|
336
|
+
existsSync,
|
|
337
|
+
mkdirSync,
|
|
338
|
+
readFileSync,
|
|
339
|
+
readdirSync,
|
|
340
|
+
rmSync,
|
|
341
|
+
statSync,
|
|
342
|
+
writeFileSync,
|
|
343
|
+
} from 'node:fs';
|
|
344
|
+
import { join, relative, resolve } from 'node:path';
|
|
345
|
+
import { createRequire } from 'node:module';
|
|
346
|
+
import { TextDecoder } from 'node:util';
|
|
347
|
+
import pg from 'pg';
|
|
348
|
+
import {
|
|
349
|
+
githubWorkflowRun,
|
|
350
|
+
reconcileRunnerQuarantine,
|
|
351
|
+
vitestIntegrationSkippedTestIds,
|
|
352
|
+
} from '../testing/quarantine-runtime.mjs';
|
|
353
|
+
|
|
354
|
+
const require = createRequire(import.meta.url);
|
|
355
|
+
const {
|
|
356
|
+
createLocalTestScope,
|
|
357
|
+
createTestingEvidenceFromRunnerAttempts,
|
|
358
|
+
inspectTestingEvidenceArtifact,
|
|
359
|
+
normalizeEbkIntegrationEvidenceAttempt,
|
|
360
|
+
sanitizeTestingEvidenceSummary,
|
|
361
|
+
validateTestingEvidenceQuarantine,
|
|
362
|
+
} = require('@empire-builder-kit/nx/testing-evidence');
|
|
363
|
+
const { Client } = pg;
|
|
364
|
+
const fatalUtf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
365
|
+
const projectRoot = process.cwd();
|
|
366
|
+
const workspaceRoot = resolve(projectRoot, '../..');
|
|
367
|
+
const evidenceRoot = join(projectRoot, '.ebk', 'evidence');
|
|
368
|
+
const rawRoot = join(evidenceRoot, 'raw');
|
|
369
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
370
|
+
mkdirSync(rawRoot, { recursive: true });
|
|
371
|
+
const sanitizedIntegrationReportPath = join(
|
|
372
|
+
evidenceRoot,
|
|
373
|
+
'test-integration-report.json',
|
|
374
|
+
);
|
|
375
|
+
rmSync(sanitizedIntegrationReportPath, { force: true });
|
|
376
|
+
|
|
377
|
+
const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'));
|
|
378
|
+
const run = (command, args, options = {}) => {
|
|
379
|
+
const result = spawnSync(command, args, {
|
|
380
|
+
cwd: options.cwd ?? projectRoot,
|
|
381
|
+
encoding: 'utf8',
|
|
382
|
+
env: options.env ?? process.env,
|
|
383
|
+
shell: false,
|
|
384
|
+
stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
|
|
385
|
+
windowsHide: true,
|
|
386
|
+
});
|
|
387
|
+
if (result.error || result.status !== 0) {
|
|
388
|
+
throw new Error(
|
|
389
|
+
sanitizeTestingEvidenceSummary(
|
|
390
|
+
result.error?.message ||
|
|
391
|
+
result.stderr ||
|
|
392
|
+
result.stdout ||
|
|
393
|
+
command + ' failed.',
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return result.stdout.trim();
|
|
398
|
+
};
|
|
399
|
+
const gitCommit =
|
|
400
|
+
process.env.EBK_SOURCE_COMMIT ??
|
|
401
|
+
run('git', ['rev-parse', 'HEAD'], { cwd: workspaceRoot });
|
|
402
|
+
const workspacePackage = readJson(join(workspaceRoot, 'package.json'));
|
|
403
|
+
const ownership = readJson(join(projectRoot, '.ebk', 'ownership.json'));
|
|
404
|
+
const frameworkCandidate =
|
|
405
|
+
workspacePackage.dependencies?.['@empire-builder-kit/runtime'];
|
|
406
|
+
if (typeof frameworkCandidate !== 'string' || !frameworkCandidate) {
|
|
407
|
+
throw new Error('The workspace runtime candidate is unavailable.');
|
|
408
|
+
}
|
|
409
|
+
const blueprintCandidates = [
|
|
410
|
+
ownership.blueprint.name + '@' + ownership.blueprint.version,
|
|
411
|
+
];
|
|
412
|
+
const requestedRunId = process.env.EBK_TEST_RUN_ID;
|
|
413
|
+
const runId = requestedRunId ?? randomUUID().replaceAll('-', '');
|
|
414
|
+
const seed = process.env.EBK_TEST_SEED ?? runId;
|
|
415
|
+
const validRunId = /^[a-z0-9]{1,32}$/.test(runId);
|
|
416
|
+
const validSeed = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(seed);
|
|
417
|
+
const evidenceRunId = validRunId ? runId : 'invalid-run-id';
|
|
418
|
+
const evidenceSeed = validSeed ? seed : 'invalid-seed';
|
|
419
|
+
const workers = Number(process.env.EBK_TEST_WORKERS ?? 1);
|
|
420
|
+
const validWorkers =
|
|
421
|
+
Number.isSafeInteger(workers) && workers >= 1 && workers <= 8;
|
|
422
|
+
const composeProject = 'ebk-test-${name}-' + runId;
|
|
423
|
+
const composeArgs = [
|
|
424
|
+
'compose',
|
|
425
|
+
'-f',
|
|
426
|
+
'database/test-compose.yml',
|
|
427
|
+
'--project-name',
|
|
428
|
+
composeProject,
|
|
429
|
+
];
|
|
430
|
+
const resultPath = join(evidenceRoot, 'test-integration.json');
|
|
431
|
+
const source = {
|
|
432
|
+
blueprintCandidates,
|
|
433
|
+
commit: gitCommit,
|
|
434
|
+
frameworkCandidate,
|
|
435
|
+
};
|
|
436
|
+
const operation = {
|
|
437
|
+
commandVersion: '1',
|
|
438
|
+
project: '${name}',
|
|
439
|
+
proofLayer: 'local-integration',
|
|
440
|
+
target: 'test-integration',
|
|
441
|
+
};
|
|
442
|
+
let quarantineRecords = [];
|
|
443
|
+
|
|
444
|
+
function writeResult(result) {
|
|
445
|
+
writeFileSync(resultPath, JSON.stringify(result, null, 2) + '\\n', 'utf8');
|
|
446
|
+
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function preflightFailure(classification, summary, startedAt) {
|
|
450
|
+
const completedAt = new Date().toISOString();
|
|
451
|
+
const result = createTestingEvidenceFromRunnerAttempts({
|
|
452
|
+
attempts: [],
|
|
453
|
+
cleanup: { outcome: 'not-required' },
|
|
454
|
+
environment: { class: 'local-docker', deployed: false },
|
|
455
|
+
operation,
|
|
456
|
+
preflight: {
|
|
457
|
+
completedAt,
|
|
458
|
+
findings: [{ classification, summary }],
|
|
459
|
+
startedAt,
|
|
460
|
+
},
|
|
461
|
+
proofStarted: false,
|
|
462
|
+
runScope: evidenceRunId,
|
|
463
|
+
seed: evidenceSeed,
|
|
464
|
+
slice: '${name}',
|
|
465
|
+
source,
|
|
466
|
+
workspace: workspacePackage.name,
|
|
467
|
+
});
|
|
468
|
+
rmSync(sanitizedIntegrationReportPath, { force: true });
|
|
469
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
470
|
+
writeResult(result);
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function pruneStaleTestProjects() {
|
|
475
|
+
const candidateIds = run('docker', [
|
|
476
|
+
'ps',
|
|
477
|
+
'-aq',
|
|
478
|
+
'--filter',
|
|
479
|
+
'label=dev.empirebuilderkit.owner=test-integration',
|
|
480
|
+
'--filter',
|
|
481
|
+
'label=dev.empirebuilderkit.slice=${name}',
|
|
482
|
+
]);
|
|
483
|
+
if (!candidateIds) return;
|
|
484
|
+
const candidates = JSON.parse(
|
|
485
|
+
run('docker', ['inspect', ...candidateIds.split(/\\s+/)]),
|
|
486
|
+
);
|
|
487
|
+
const now = Date.now();
|
|
488
|
+
const cutoff = now - 24 * 60 * 60 * 1000;
|
|
489
|
+
const projects = new Set();
|
|
490
|
+
for (const observation of candidates) {
|
|
491
|
+
const labels = observation.Config?.Labels ?? {};
|
|
492
|
+
const project = labels['com.docker.compose.project'];
|
|
493
|
+
const observedRunId = labels['dev.empirebuilderkit.run'];
|
|
494
|
+
const createdAt = Date.parse(observation.Created);
|
|
495
|
+
if (
|
|
496
|
+
labels['dev.empirebuilderkit.owner'] !== 'test-integration' ||
|
|
497
|
+
labels['dev.empirebuilderkit.slice'] !== '${name}' ||
|
|
498
|
+
typeof project !== 'string' ||
|
|
499
|
+
typeof observedRunId !== 'string' ||
|
|
500
|
+
!/^[a-z0-9]{1,32}$/.test(observedRunId) ||
|
|
501
|
+
project !== 'ebk-test-${name}-' + observedRunId ||
|
|
502
|
+
!Number.isFinite(createdAt) ||
|
|
503
|
+
createdAt > now ||
|
|
504
|
+
project === composeProject
|
|
505
|
+
) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
'A stale integration resource has ambiguous ownership or age.',
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
projects.add(project);
|
|
511
|
+
}
|
|
512
|
+
for (const project of [...projects].sort()) {
|
|
513
|
+
const memberIds = run('docker', [
|
|
514
|
+
'ps',
|
|
515
|
+
'-aq',
|
|
516
|
+
'--filter',
|
|
517
|
+
'label=com.docker.compose.project=' + project,
|
|
518
|
+
]);
|
|
519
|
+
if (!memberIds) {
|
|
520
|
+
throw new Error(
|
|
521
|
+
'A stale integration project has no inspectable members.',
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
const members = JSON.parse(
|
|
525
|
+
run('docker', ['inspect', ...memberIds.split(/\\s+/)]),
|
|
526
|
+
);
|
|
527
|
+
const ages = [];
|
|
528
|
+
let running = false;
|
|
529
|
+
for (const observation of members) {
|
|
530
|
+
const labels = observation.Config?.Labels ?? {};
|
|
531
|
+
const observedRunId = labels['dev.empirebuilderkit.run'];
|
|
532
|
+
const createdAt = Date.parse(observation.Created);
|
|
533
|
+
if (
|
|
534
|
+
labels['com.docker.compose.project'] !== project ||
|
|
535
|
+
labels['dev.empirebuilderkit.owner'] !== 'test-integration' ||
|
|
536
|
+
labels['dev.empirebuilderkit.slice'] !== '${name}' ||
|
|
537
|
+
typeof observedRunId !== 'string' ||
|
|
538
|
+
!/^[a-z0-9]{1,32}$/.test(observedRunId) ||
|
|
539
|
+
project !== 'ebk-test-${name}-' + observedRunId ||
|
|
540
|
+
project === composeProject ||
|
|
541
|
+
!Number.isFinite(createdAt) ||
|
|
542
|
+
createdAt > now ||
|
|
543
|
+
typeof observation.State?.Running !== 'boolean'
|
|
544
|
+
) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
'A stale integration project has ambiguous members or age.',
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
ages.push(createdAt);
|
|
550
|
+
running ||= observation.State.Running;
|
|
551
|
+
}
|
|
552
|
+
const oldMembers = ages.filter((createdAt) => createdAt <= cutoff).length;
|
|
553
|
+
if (oldMembers > 0 && oldMembers !== ages.length) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
'A stale integration project contains mixed-age members.',
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
if (oldMembers === 0 || running) continue;
|
|
559
|
+
run('docker', [
|
|
560
|
+
'compose',
|
|
561
|
+
'-f',
|
|
562
|
+
'database/test-compose.yml',
|
|
563
|
+
'--project-name',
|
|
564
|
+
project,
|
|
565
|
+
'down',
|
|
566
|
+
'--remove-orphans',
|
|
567
|
+
'--volumes',
|
|
568
|
+
]);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function prepareDatabase(adminUrl, databaseName) {
|
|
573
|
+
const admin = new Client({ connectionString: adminUrl });
|
|
574
|
+
await admin.connect();
|
|
575
|
+
try {
|
|
576
|
+
await admin.query('CREATE DATABASE "' + databaseName + '"');
|
|
577
|
+
} finally {
|
|
578
|
+
await admin.end();
|
|
579
|
+
}
|
|
580
|
+
const databaseUrl = adminUrl.replace(/\\/postgres$/, '/' + databaseName);
|
|
581
|
+
const client = new Client({ connectionString: databaseUrl });
|
|
582
|
+
await client.connect();
|
|
583
|
+
try {
|
|
584
|
+
for (const file of readdirSync(join(projectRoot, 'database', 'migrations'))
|
|
585
|
+
.filter((path) => path.endsWith('.sql'))
|
|
586
|
+
.sort()) {
|
|
587
|
+
await client.query(
|
|
588
|
+
readFileSync(join(projectRoot, 'database', 'migrations', file), 'utf8'),
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
} finally {
|
|
592
|
+
await client.end();
|
|
593
|
+
}
|
|
594
|
+
return databaseUrl;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function runWorker(worker, databaseUrl, reportPath, attempt) {
|
|
598
|
+
const executable = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
599
|
+
const arguments_ = [
|
|
600
|
+
'exec',
|
|
601
|
+
'vitest',
|
|
602
|
+
'run',
|
|
603
|
+
'--config',
|
|
604
|
+
'vitest.integration.config.ts',
|
|
605
|
+
'--maxWorkers=1',
|
|
606
|
+
'--reporter=./testing/strict-json-reporter.mjs',
|
|
607
|
+
'--outputFile=' + reportPath,
|
|
608
|
+
...(workers > 1 ? ['--shard=' + (worker + 1) + '/' + workers] : []),
|
|
609
|
+
];
|
|
610
|
+
return new Promise((resolveWorker) => {
|
|
611
|
+
const workerEnvironmentNames = new Set([
|
|
612
|
+
'APPDATA',
|
|
613
|
+
'CI',
|
|
614
|
+
'ComSpec',
|
|
615
|
+
'COMSPEC',
|
|
616
|
+
'HOME',
|
|
617
|
+
'LANG',
|
|
618
|
+
'LC_ALL',
|
|
619
|
+
'LOCALAPPDATA',
|
|
620
|
+
'PATH',
|
|
621
|
+
'PATHEXT',
|
|
622
|
+
'SystemRoot',
|
|
623
|
+
'SYSTEMROOT',
|
|
624
|
+
'TEMP',
|
|
625
|
+
'TMP',
|
|
626
|
+
'TMPDIR',
|
|
627
|
+
'TZ',
|
|
628
|
+
'USERPROFILE',
|
|
629
|
+
]);
|
|
630
|
+
const childEnvironment = Object.fromEntries(
|
|
631
|
+
Object.entries(process.env).filter(
|
|
632
|
+
([name, value]) =>
|
|
633
|
+
workerEnvironmentNames.has(name) && typeof value === 'string',
|
|
634
|
+
),
|
|
635
|
+
);
|
|
636
|
+
const child = spawn(executable, arguments_, {
|
|
637
|
+
cwd: projectRoot,
|
|
638
|
+
env: {
|
|
639
|
+
...childEnvironment,
|
|
640
|
+
DATABASE_URL: databaseUrl,
|
|
641
|
+
EBK_TEST_ATTEMPT: String(attempt),
|
|
642
|
+
EBK_TEST_RUN_ID: runId,
|
|
643
|
+
EBK_TEST_SEED: seed,
|
|
644
|
+
EBK_TEST_WORKER: String(worker),
|
|
645
|
+
},
|
|
646
|
+
shell: false,
|
|
647
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
648
|
+
windowsHide: true,
|
|
649
|
+
});
|
|
650
|
+
const maximumDiagnosticCharacters = 64 * 1024;
|
|
651
|
+
let diagnostic = '';
|
|
652
|
+
const appendDiagnostic = (chunk) => {
|
|
653
|
+
if (diagnostic.length >= maximumDiagnosticCharacters) return;
|
|
654
|
+
diagnostic += chunk
|
|
655
|
+
.toString()
|
|
656
|
+
.slice(0, maximumDiagnosticCharacters - diagnostic.length);
|
|
657
|
+
};
|
|
658
|
+
child.stdout.on('data', (chunk) => {
|
|
659
|
+
appendDiagnostic(chunk);
|
|
660
|
+
});
|
|
661
|
+
child.stderr.on('data', (chunk) => {
|
|
662
|
+
appendDiagnostic(chunk);
|
|
663
|
+
});
|
|
664
|
+
child.once('error', (error) => {
|
|
665
|
+
resolveWorker({ diagnostic: error.message, status: 1, worker });
|
|
666
|
+
});
|
|
667
|
+
child.once('close', (status) => {
|
|
668
|
+
resolveWorker({ diagnostic, status: status ?? 1, worker });
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function readBoundedVitestReport(reportPath) {
|
|
674
|
+
if (!existsSync(reportPath)) {
|
|
675
|
+
return {
|
|
676
|
+
finding: {
|
|
677
|
+
classification: 'evidence-incomplete',
|
|
678
|
+
summary: 'A Vitest worker did not emit its machine-readable report.',
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const size = statSync(reportPath).size;
|
|
683
|
+
if (!Number.isSafeInteger(size) || size < 2 || size > 5 * 1024 * 1024) {
|
|
684
|
+
return {
|
|
685
|
+
finding: {
|
|
686
|
+
classification: 'evidence-incomplete',
|
|
687
|
+
summary:
|
|
688
|
+
'A Vitest worker report was empty or exceeded the five-megabyte inspection bound.',
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
let report;
|
|
693
|
+
try {
|
|
694
|
+
const bytes = readFileSync(reportPath);
|
|
695
|
+
if (bytes.byteLength !== size) {
|
|
696
|
+
throw new Error('vitest-report-size-changed');
|
|
697
|
+
}
|
|
698
|
+
report = JSON.parse(fatalUtf8Decoder.decode(bytes));
|
|
699
|
+
} catch {
|
|
700
|
+
return {
|
|
701
|
+
finding: {
|
|
702
|
+
classification: 'evidence-incomplete',
|
|
703
|
+
summary: 'A Vitest worker emitted malformed machine-readable output.',
|
|
704
|
+
},
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
const counts = [
|
|
708
|
+
report?.numTotalTests,
|
|
709
|
+
report?.numFailedTests,
|
|
710
|
+
report?.numPassedTests,
|
|
711
|
+
report?.numPendingTests,
|
|
712
|
+
report?.numTodoTests,
|
|
713
|
+
];
|
|
714
|
+
if (
|
|
715
|
+
!report ||
|
|
716
|
+
typeof report !== 'object' ||
|
|
717
|
+
Array.isArray(report) ||
|
|
718
|
+
!Array.isArray(report.testResults) ||
|
|
719
|
+
!report.ebkPolicy ||
|
|
720
|
+
typeof report.ebkPolicy !== 'object' ||
|
|
721
|
+
Array.isArray(report.ebkPolicy) ||
|
|
722
|
+
JSON.stringify(Object.keys(report.ebkPolicy).sort()) !==
|
|
723
|
+
JSON.stringify([
|
|
724
|
+
'expectedFailures',
|
|
725
|
+
'focusedTests',
|
|
726
|
+
'retryConfiguredTests',
|
|
727
|
+
]) ||
|
|
728
|
+
counts.some(
|
|
729
|
+
(count) =>
|
|
730
|
+
!Number.isSafeInteger(count) ||
|
|
731
|
+
count < 0,
|
|
732
|
+
) ||
|
|
733
|
+
!Number.isSafeInteger(report.ebkPolicy.expectedFailures) ||
|
|
734
|
+
report.ebkPolicy.expectedFailures < 0 ||
|
|
735
|
+
!Number.isSafeInteger(report.ebkPolicy.focusedTests) ||
|
|
736
|
+
report.ebkPolicy.focusedTests < 0 ||
|
|
737
|
+
!Number.isSafeInteger(report.ebkPolicy.retryConfiguredTests) ||
|
|
738
|
+
report.ebkPolicy.retryConfiguredTests < 0
|
|
739
|
+
) {
|
|
740
|
+
return {
|
|
741
|
+
finding: {
|
|
742
|
+
classification: 'evidence-incomplete',
|
|
743
|
+
summary:
|
|
744
|
+
'A Vitest worker report did not satisfy the bounded JSON result contract.',
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const observed = {
|
|
749
|
+
failed: 0,
|
|
750
|
+
passed: 0,
|
|
751
|
+
pending: 0,
|
|
752
|
+
todo: 0,
|
|
753
|
+
total: 0,
|
|
754
|
+
};
|
|
755
|
+
for (const testResult of report.testResults) {
|
|
756
|
+
if (
|
|
757
|
+
!testResult ||
|
|
758
|
+
typeof testResult !== 'object' ||
|
|
759
|
+
Array.isArray(testResult) ||
|
|
760
|
+
!Array.isArray(testResult.assertionResults)
|
|
761
|
+
) {
|
|
762
|
+
return {
|
|
763
|
+
finding: {
|
|
764
|
+
classification: 'evidence-incomplete',
|
|
765
|
+
summary:
|
|
766
|
+
'A Vitest worker report did not contain inspectable assertion results.',
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
for (const assertion of testResult.assertionResults) {
|
|
771
|
+
const status = assertion?.status;
|
|
772
|
+
if (
|
|
773
|
+
!assertion ||
|
|
774
|
+
typeof assertion !== 'object' ||
|
|
775
|
+
Array.isArray(assertion) ||
|
|
776
|
+
!['failed', 'passed', 'pending', 'skipped', 'todo'].includes(status)
|
|
777
|
+
) {
|
|
778
|
+
return {
|
|
779
|
+
finding: {
|
|
780
|
+
classification: 'evidence-incomplete',
|
|
781
|
+
summary:
|
|
782
|
+
'A Vitest worker report contained an unsupported assertion result.',
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
observed.total += 1;
|
|
787
|
+
if (status === 'failed') observed.failed += 1;
|
|
788
|
+
if (status === 'passed') observed.passed += 1;
|
|
789
|
+
if (status === 'pending' || status === 'skipped') observed.pending += 1;
|
|
790
|
+
if (status === 'todo') observed.todo += 1;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (
|
|
794
|
+
observed.total !== report.numTotalTests ||
|
|
795
|
+
observed.failed !== report.numFailedTests ||
|
|
796
|
+
observed.passed !== report.numPassedTests ||
|
|
797
|
+
observed.pending !== report.numPendingTests ||
|
|
798
|
+
observed.todo !== report.numTodoTests
|
|
799
|
+
) {
|
|
800
|
+
return {
|
|
801
|
+
finding: {
|
|
802
|
+
classification: 'evidence-incomplete',
|
|
803
|
+
summary:
|
|
804
|
+
'A Vitest worker report aggregate did not reconcile to its assertion results.',
|
|
805
|
+
},
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (
|
|
809
|
+
report.ebkPolicy.expectedFailures > 0 ||
|
|
810
|
+
report.ebkPolicy.focusedTests > 0 ||
|
|
811
|
+
report.ebkPolicy.retryConfiguredTests > 0
|
|
812
|
+
) {
|
|
813
|
+
return {
|
|
814
|
+
finding: {
|
|
815
|
+
classification: 'assertion-failed',
|
|
816
|
+
summary:
|
|
817
|
+
'Vitest focused tests, expected-failure semantics, and runner-internal retries are forbidden in integration proof.',
|
|
818
|
+
},
|
|
819
|
+
report,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
return { report };
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async function runAttempt(attempt, adminUrl) {
|
|
826
|
+
const attemptStartedAt = new Date().toISOString();
|
|
827
|
+
const findings = [];
|
|
828
|
+
const reports = [];
|
|
829
|
+
const reportPaths = [];
|
|
830
|
+
let workerResults = [];
|
|
831
|
+
try {
|
|
832
|
+
const databaseUrls = [];
|
|
833
|
+
for (let worker = 0; worker < workers; worker += 1) {
|
|
834
|
+
const scope = createLocalTestScope({
|
|
835
|
+
capabilities: {
|
|
836
|
+
canCreateDatabase: true,
|
|
837
|
+
canForceDropDatabase: true,
|
|
838
|
+
migrationSchemaIsolationProven: false,
|
|
839
|
+
},
|
|
840
|
+
identity: {
|
|
841
|
+
attempt,
|
|
842
|
+
commit: gitCommit,
|
|
843
|
+
runId,
|
|
844
|
+
seed,
|
|
845
|
+
stage: 'development',
|
|
846
|
+
worker,
|
|
847
|
+
workspace: workspacePackage.name,
|
|
848
|
+
},
|
|
849
|
+
sharedDatabaseName: 'postgres',
|
|
850
|
+
});
|
|
851
|
+
databaseUrls.push(
|
|
852
|
+
await prepareDatabase(adminUrl, scope.databaseName),
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
workerResults = await Promise.all(
|
|
856
|
+
databaseUrls.map((databaseUrl, worker) => {
|
|
857
|
+
const reportPath = join(
|
|
858
|
+
rawRoot,
|
|
859
|
+
'vitest-attempt-' + attempt + '-worker-' + worker + '.json',
|
|
860
|
+
);
|
|
861
|
+
reportPaths.push(reportPath);
|
|
862
|
+
return runWorker(worker, databaseUrl, reportPath, attempt);
|
|
863
|
+
}),
|
|
864
|
+
);
|
|
865
|
+
const workerDiagnostics = [];
|
|
866
|
+
for (const workerResult of workerResults) {
|
|
867
|
+
const reportPath = join(
|
|
868
|
+
rawRoot,
|
|
869
|
+
'vitest-attempt-' +
|
|
870
|
+
attempt +
|
|
871
|
+
'-worker-' +
|
|
872
|
+
workerResult.worker +
|
|
873
|
+
'.json',
|
|
874
|
+
);
|
|
875
|
+
const observedReport = readBoundedVitestReport(reportPath);
|
|
876
|
+
if (observedReport.report) reports.push(observedReport.report);
|
|
877
|
+
if (observedReport.finding) findings.push(observedReport.finding);
|
|
878
|
+
if (workerResult.status !== 0) {
|
|
879
|
+
workerDiagnostics.push({
|
|
880
|
+
classification: 'assertion-failed',
|
|
881
|
+
summary: sanitizeTestingEvidenceSummary(workerResult.diagnostic),
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const failedTestNames = [
|
|
886
|
+
...new Set(
|
|
887
|
+
reports
|
|
888
|
+
.flatMap((report) => report.testResults ?? [])
|
|
889
|
+
.flatMap((fileResult) => fileResult.assertionResults ?? [])
|
|
890
|
+
.filter(({ status }) => status === 'failed')
|
|
891
|
+
.map(
|
|
892
|
+
({ fullName, title }) => fullName || title || 'unnamed test',
|
|
893
|
+
),
|
|
894
|
+
),
|
|
895
|
+
].sort();
|
|
896
|
+
// The named finding must precede worker diagnostics: retained evidence
|
|
897
|
+
// keeps the first finding per classification, so a leading raw-report
|
|
898
|
+
// pointer would mask the test identities.
|
|
899
|
+
if (failedTestNames.length > 0) {
|
|
900
|
+
findings.push({
|
|
901
|
+
classification: 'assertion-failed',
|
|
902
|
+
summary: sanitizeTestingEvidenceSummary(
|
|
903
|
+
'Failed integration tests: ' + failedTestNames.join(' | '),
|
|
904
|
+
),
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
findings.push(...workerDiagnostics);
|
|
908
|
+
if (
|
|
909
|
+
reports.some(
|
|
910
|
+
(report) =>
|
|
911
|
+
!inspectTestingEvidenceArtifact({
|
|
912
|
+
contents: JSON.stringify(report),
|
|
913
|
+
}).safe,
|
|
914
|
+
)
|
|
915
|
+
) {
|
|
916
|
+
findings.push({
|
|
917
|
+
classification: 'sensitive-artifact',
|
|
918
|
+
summary:
|
|
919
|
+
'A raw runner report matched protected artifact data and was not retained.',
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
} catch (error) {
|
|
923
|
+
findings.push({
|
|
924
|
+
classification: 'infrastructure-unavailable',
|
|
925
|
+
summary: error instanceof Error ? error.message : String(error),
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const totals = reports.reduce(
|
|
929
|
+
(sum, report) => ({
|
|
930
|
+
failed: sum.failed + Number(report.numFailedTests ?? 0),
|
|
931
|
+
passed: sum.passed + Number(report.numPassedTests ?? 0),
|
|
932
|
+
skipped:
|
|
933
|
+
sum.skipped +
|
|
934
|
+
Number(report.numPendingTests ?? 0) +
|
|
935
|
+
Number(report.numTodoTests ?? 0),
|
|
936
|
+
}),
|
|
937
|
+
{ failed: 0, passed: 0, skipped: 0 },
|
|
938
|
+
);
|
|
939
|
+
const reportedSkipped = totals.skipped;
|
|
940
|
+
const quarantine = reconcileRunnerQuarantine({
|
|
941
|
+
records: quarantineRecords,
|
|
942
|
+
runner: 'vitest-integration',
|
|
943
|
+
skippedTestIds: vitestIntegrationSkippedTestIds(
|
|
944
|
+
reports,
|
|
945
|
+
projectRoot,
|
|
946
|
+
),
|
|
947
|
+
});
|
|
948
|
+
findings.push(...quarantine.findings);
|
|
949
|
+
if (reportedSkipped !== quarantine.quarantined + quarantine.skipped) {
|
|
950
|
+
findings.push({
|
|
951
|
+
classification: 'evidence-incomplete',
|
|
952
|
+
summary:
|
|
953
|
+
'Vitest skipped-test counts could not be reconciled to stable test identities.',
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
totals.skipped = quarantine.skipped;
|
|
957
|
+
if (
|
|
958
|
+
(workerResults.some(({ status }) => status !== 0) ||
|
|
959
|
+
findings.length > 0) &&
|
|
960
|
+
totals.failed === 0
|
|
961
|
+
) {
|
|
962
|
+
totals.failed = 1;
|
|
963
|
+
}
|
|
964
|
+
return {
|
|
965
|
+
normalized: normalizeEbkIntegrationEvidenceAttempt({
|
|
966
|
+
attempt,
|
|
967
|
+
completedAt: new Date().toISOString(),
|
|
968
|
+
failed: totals.failed,
|
|
969
|
+
findings,
|
|
970
|
+
...(attempt === 2 ? { identicalToPrevious: true } : {}),
|
|
971
|
+
passed: totals.passed,
|
|
972
|
+
quarantined: quarantine.quarantined,
|
|
973
|
+
skipped: totals.skipped,
|
|
974
|
+
startedAt: attemptStartedAt,
|
|
975
|
+
}),
|
|
976
|
+
reportPaths,
|
|
977
|
+
reports,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function failClosedArtifact(attempts, classification, sanitizedSummary) {
|
|
982
|
+
const last = attempts.at(-1);
|
|
983
|
+
if (!last) {
|
|
984
|
+
throw new Error('A proof artifact failed without an owning attempt.');
|
|
985
|
+
}
|
|
986
|
+
const findings = [
|
|
987
|
+
...last.findings.filter(
|
|
988
|
+
(finding) => finding.classification !== classification,
|
|
989
|
+
),
|
|
990
|
+
{ classification, sanitizedSummary },
|
|
991
|
+
];
|
|
992
|
+
if (
|
|
993
|
+
!findings.some(({ classification: observed }) =>
|
|
994
|
+
[
|
|
995
|
+
'assertion-failed',
|
|
996
|
+
'authorization-denied',
|
|
997
|
+
'contract-incompatible',
|
|
998
|
+
'environment-drift',
|
|
999
|
+
'infrastructure-unavailable',
|
|
1000
|
+
'prerequisite-missing',
|
|
1001
|
+
'sensitive-artifact',
|
|
1002
|
+
].includes(observed),
|
|
1003
|
+
)
|
|
1004
|
+
) {
|
|
1005
|
+
findings.push({
|
|
1006
|
+
classification: 'assertion-failed',
|
|
1007
|
+
sanitizedSummary: 'The bounded runner artifact was not retained.',
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
attempts[attempts.length - 1] = {
|
|
1011
|
+
...last,
|
|
1012
|
+
failed: Math.max(last.failed, 1),
|
|
1013
|
+
findings: findings.sort((left, right) =>
|
|
1014
|
+
left.classification < right.classification
|
|
1015
|
+
? -1
|
|
1016
|
+
: left.classification > right.classification
|
|
1017
|
+
? 1
|
|
1018
|
+
: 0,
|
|
1019
|
+
),
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const startedAt = new Date().toISOString();
|
|
1024
|
+
if (!validRunId || !validSeed || !validWorkers) {
|
|
1025
|
+
preflightFailure(
|
|
1026
|
+
'prerequisite-missing',
|
|
1027
|
+
'Integration proof requires a non-empty normalized run id, bounded printable seed, and one through eight workers.',
|
|
1028
|
+
startedAt,
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
if (process.exitCode === undefined) {
|
|
1032
|
+
let quarantine;
|
|
1033
|
+
try {
|
|
1034
|
+
quarantine = validateTestingEvidenceQuarantine(
|
|
1035
|
+
readJson(join(projectRoot, 'testing', 'quarantine.json')),
|
|
1036
|
+
{
|
|
1037
|
+
now: startedAt,
|
|
1038
|
+
workflowRun: githubWorkflowRun(),
|
|
1039
|
+
},
|
|
1040
|
+
);
|
|
1041
|
+
} catch {
|
|
1042
|
+
preflightFailure(
|
|
1043
|
+
'environment-drift',
|
|
1044
|
+
'The testing quarantine file is missing or malformed.',
|
|
1045
|
+
startedAt,
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
if (process.exitCode === undefined && quarantine && !quarantine.valid) {
|
|
1049
|
+
preflightFailure(
|
|
1050
|
+
'environment-drift',
|
|
1051
|
+
quarantine.issues
|
|
1052
|
+
.map(({ message, path }) => path + ' ' + message)
|
|
1053
|
+
.join('; '),
|
|
1054
|
+
startedAt,
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
if (process.exitCode === undefined && quarantine?.valid) {
|
|
1058
|
+
quarantineRecords = quarantine.value.records;
|
|
1059
|
+
try {
|
|
1060
|
+
run('docker', ['version', '--format', '{{.Server.Version}}']);
|
|
1061
|
+
pruneStaleTestProjects();
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
preflightFailure(
|
|
1064
|
+
'prerequisite-missing',
|
|
1065
|
+
error instanceof Error ? error.message : String(error),
|
|
1066
|
+
startedAt,
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (process.exitCode === undefined) {
|
|
1073
|
+
let cleanup = { outcome: 'not-required' };
|
|
1074
|
+
let proofStarted = false;
|
|
1075
|
+
let preflightSummary = 'The integration proof did not start.';
|
|
1076
|
+
const attempts = [];
|
|
1077
|
+
const reports = [];
|
|
1078
|
+
const reportPaths = [];
|
|
1079
|
+
try {
|
|
1080
|
+
run('docker', [...composeArgs, 'up', '--detach', '--wait'], {
|
|
1081
|
+
env: { ...process.env, EBK_TEST_RUN_ID: runId },
|
|
1082
|
+
});
|
|
1083
|
+
proofStarted = true;
|
|
1084
|
+
const address = run('docker', [
|
|
1085
|
+
...composeArgs,
|
|
1086
|
+
'port',
|
|
1087
|
+
'database',
|
|
1088
|
+
'5432',
|
|
1089
|
+
]);
|
|
1090
|
+
const port = /:(\\d+)\\s*$/.exec(address)?.[1];
|
|
1091
|
+
if (!port) throw new Error('Docker did not report the integration port.');
|
|
1092
|
+
const adminUrl =
|
|
1093
|
+
'postgresql://ebk_test:ebk_test@127.0.0.1:' + port + '/postgres';
|
|
1094
|
+
const first = await runAttempt(1, adminUrl);
|
|
1095
|
+
attempts.push(first.normalized);
|
|
1096
|
+
reports.push(...first.reports);
|
|
1097
|
+
reportPaths.push(...first.reportPaths);
|
|
1098
|
+
if (first.normalized.failed > 0) {
|
|
1099
|
+
const second = await runAttempt(2, adminUrl);
|
|
1100
|
+
attempts.push(second.normalized);
|
|
1101
|
+
reports.push(...second.reports);
|
|
1102
|
+
reportPaths.push(...second.reportPaths);
|
|
1103
|
+
}
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
if (proofStarted) {
|
|
1106
|
+
attempts.push(
|
|
1107
|
+
normalizeEbkIntegrationEvidenceAttempt({
|
|
1108
|
+
attempt: 1,
|
|
1109
|
+
completedAt: new Date().toISOString(),
|
|
1110
|
+
failed: 1,
|
|
1111
|
+
findings: [
|
|
1112
|
+
{
|
|
1113
|
+
classification: 'infrastructure-unavailable',
|
|
1114
|
+
summary: error instanceof Error ? error.message : String(error),
|
|
1115
|
+
},
|
|
1116
|
+
],
|
|
1117
|
+
passed: 0,
|
|
1118
|
+
quarantined: 0,
|
|
1119
|
+
skipped: 0,
|
|
1120
|
+
startedAt,
|
|
1121
|
+
}),
|
|
1122
|
+
);
|
|
1123
|
+
} else {
|
|
1124
|
+
preflightSummary =
|
|
1125
|
+
error instanceof Error ? error.message : String(error);
|
|
1126
|
+
}
|
|
1127
|
+
} finally {
|
|
1128
|
+
try {
|
|
1129
|
+
if (proofStarted) {
|
|
1130
|
+
run('docker', [
|
|
1131
|
+
...composeArgs,
|
|
1132
|
+
'down',
|
|
1133
|
+
'--remove-orphans',
|
|
1134
|
+
'--volumes',
|
|
1135
|
+
]);
|
|
1136
|
+
cleanup = { outcome: 'passed' };
|
|
1137
|
+
}
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
cleanup = {
|
|
1140
|
+
outcome: 'failed',
|
|
1141
|
+
sanitizedSummary:
|
|
1142
|
+
error instanceof Error ? error.message : String(error),
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (!proofStarted) {
|
|
1148
|
+
preflightFailure(
|
|
1149
|
+
'prerequisite-missing',
|
|
1150
|
+
preflightSummary,
|
|
1151
|
+
startedAt,
|
|
1152
|
+
);
|
|
1153
|
+
} else {
|
|
1154
|
+
const artifacts = [];
|
|
1155
|
+
try {
|
|
1156
|
+
if (reports.length > 0) {
|
|
1157
|
+
const combinedReport = JSON.stringify({ reports }, null, 2) + '\\n';
|
|
1158
|
+
const combinedReportBytes = Buffer.byteLength(
|
|
1159
|
+
combinedReport,
|
|
1160
|
+
'utf8',
|
|
1161
|
+
);
|
|
1162
|
+
if (combinedReportBytes > 5 * 1024 * 1024) {
|
|
1163
|
+
failClosedArtifact(
|
|
1164
|
+
attempts,
|
|
1165
|
+
'evidence-incomplete',
|
|
1166
|
+
'The combined Vitest report exceeded the five-megabyte retention bound.',
|
|
1167
|
+
);
|
|
1168
|
+
rmSync(sanitizedIntegrationReportPath, { force: true });
|
|
1169
|
+
} else {
|
|
1170
|
+
let inspection;
|
|
1171
|
+
try {
|
|
1172
|
+
inspection = inspectTestingEvidenceArtifact({
|
|
1173
|
+
contents: combinedReport,
|
|
1174
|
+
});
|
|
1175
|
+
} catch {
|
|
1176
|
+
inspection = { safe: false };
|
|
1177
|
+
}
|
|
1178
|
+
if (!inspection.safe) {
|
|
1179
|
+
failClosedArtifact(
|
|
1180
|
+
attempts,
|
|
1181
|
+
'sensitive-artifact',
|
|
1182
|
+
'The combined Vitest report matched protected artifact data and was removed.',
|
|
1183
|
+
);
|
|
1184
|
+
rmSync(sanitizedIntegrationReportPath, { force: true });
|
|
1185
|
+
} else {
|
|
1186
|
+
writeFileSync(
|
|
1187
|
+
sanitizedIntegrationReportPath,
|
|
1188
|
+
combinedReport,
|
|
1189
|
+
'utf8',
|
|
1190
|
+
);
|
|
1191
|
+
artifacts.push({
|
|
1192
|
+
digest:
|
|
1193
|
+
'sha256:' +
|
|
1194
|
+
createHash('sha256').update(combinedReport).digest('hex'),
|
|
1195
|
+
name: relative(projectRoot, sanitizedIntegrationReportPath)
|
|
1196
|
+
.replaceAll('\\\\', '/'),
|
|
1197
|
+
retentionClass: 'source-result',
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
} finally {
|
|
1203
|
+
for (const reportPath of reportPaths) {
|
|
1204
|
+
rmSync(reportPath, { force: true });
|
|
1205
|
+
}
|
|
1206
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
1207
|
+
}
|
|
1208
|
+
const result = createTestingEvidenceFromRunnerAttempts({
|
|
1209
|
+
artifacts,
|
|
1210
|
+
attempts,
|
|
1211
|
+
cleanup,
|
|
1212
|
+
environment: { class: 'local-docker', deployed: false },
|
|
1213
|
+
operation,
|
|
1214
|
+
proofStarted: true,
|
|
1215
|
+
runScope: runId,
|
|
1216
|
+
seed,
|
|
1217
|
+
slice: '${name}',
|
|
1218
|
+
source,
|
|
1219
|
+
workspace: workspacePackage.name,
|
|
1220
|
+
});
|
|
1221
|
+
writeResult(result);
|
|
1222
|
+
if (result.outcome !== 'passed') process.exitCode = 1;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
`,
|
|
1226
|
+
'scripts/run-smoke-deployed.mjs': `import {
|
|
1227
|
+
AdminAddUserToGroupCommand,
|
|
1228
|
+
AdminCreateUserCommand,
|
|
1229
|
+
AdminDeleteUserCommand,
|
|
1230
|
+
AdminSetUserPasswordCommand,
|
|
1231
|
+
CognitoIdentityProviderClient,
|
|
1232
|
+
DescribeUserPoolClientCommand,
|
|
1233
|
+
} from '@aws-sdk/client-cognito-identity-provider';
|
|
1234
|
+
import {
|
|
1235
|
+
CloudWatchLogsClient,
|
|
1236
|
+
FilterLogEventsCommand,
|
|
1237
|
+
} from '@aws-sdk/client-cloudwatch-logs';
|
|
1238
|
+
import { spawnSync } from 'node:child_process';
|
|
1239
|
+
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
1240
|
+
import {
|
|
1241
|
+
existsSync,
|
|
1242
|
+
mkdirSync,
|
|
1243
|
+
readFileSync,
|
|
1244
|
+
rmSync,
|
|
1245
|
+
statSync,
|
|
1246
|
+
writeFileSync,
|
|
1247
|
+
} from 'node:fs';
|
|
1248
|
+
import { join, relative, resolve } from 'node:path';
|
|
1249
|
+
import { createRequire } from 'node:module';
|
|
1250
|
+
import { TextDecoder } from 'node:util';
|
|
1251
|
+
import {
|
|
1252
|
+
githubWorkflowRun,
|
|
1253
|
+
playwrightSkippedTestIds,
|
|
1254
|
+
reconcileRunnerQuarantine,
|
|
1255
|
+
} from '../testing/quarantine-runtime.mjs';
|
|
1256
|
+
|
|
1257
|
+
const require = createRequire(import.meta.url);
|
|
1258
|
+
const {
|
|
1259
|
+
createTestingEvidenceFromRunnerAttempts,
|
|
1260
|
+
inspectTestingEvidenceArtifact,
|
|
1261
|
+
normalizePlaywrightEvidenceAttempt,
|
|
1262
|
+
sanitizeTestingEvidenceSummary,
|
|
1263
|
+
validateTestingEvidenceQuarantine,
|
|
1264
|
+
} = require('@empire-builder-kit/nx/testing-evidence');
|
|
1265
|
+
const fatalUtf8Decoder = new TextDecoder('utf-8', { fatal: true });
|
|
1266
|
+
const projectRoot = process.cwd();
|
|
1267
|
+
const workspaceRoot = resolve(projectRoot, '../..');
|
|
1268
|
+
const evidenceRoot = join(projectRoot, '.ebk', 'evidence');
|
|
1269
|
+
const rawRoot = join(evidenceRoot, 'raw');
|
|
1270
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
1271
|
+
mkdirSync(rawRoot, { recursive: true });
|
|
1272
|
+
const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'));
|
|
1273
|
+
const run = (command, args, options = {}) => {
|
|
1274
|
+
const result = spawnSync(command, args, {
|
|
1275
|
+
cwd: options.cwd ?? projectRoot,
|
|
1276
|
+
encoding: 'utf8',
|
|
1277
|
+
env: options.env ?? process.env,
|
|
1278
|
+
shell: false,
|
|
1279
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1280
|
+
windowsHide: true,
|
|
1281
|
+
});
|
|
1282
|
+
if (result.error || result.status !== 0) {
|
|
1283
|
+
throw new Error(
|
|
1284
|
+
sanitizeTestingEvidenceSummary(
|
|
1285
|
+
result.error?.message ||
|
|
1286
|
+
result.stderr ||
|
|
1287
|
+
result.stdout ||
|
|
1288
|
+
command + ' failed.',
|
|
1289
|
+
),
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
return result.stdout.trim();
|
|
1293
|
+
};
|
|
1294
|
+
const gitCommit =
|
|
1295
|
+
process.env.EBK_SOURCE_COMMIT ??
|
|
1296
|
+
run('git', ['rev-parse', 'HEAD'], { cwd: workspaceRoot });
|
|
1297
|
+
const workspacePackage = readJson(join(workspaceRoot, 'package.json'));
|
|
1298
|
+
const ownership = readJson(join(projectRoot, '.ebk', 'ownership.json'));
|
|
1299
|
+
const frameworkCandidate =
|
|
1300
|
+
workspacePackage.dependencies?.['@empire-builder-kit/runtime'];
|
|
1301
|
+
if (typeof frameworkCandidate !== 'string' || !frameworkCandidate) {
|
|
1302
|
+
throw new Error('The workspace runtime candidate is unavailable.');
|
|
1303
|
+
}
|
|
1304
|
+
const stage = process.env.EBK_STAGE ?? '';
|
|
1305
|
+
const validSourceCommit =
|
|
1306
|
+
/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(gitCommit);
|
|
1307
|
+
const smokeUrl = process.env.EBK_SMOKE_URL ?? '';
|
|
1308
|
+
const smokeApiUrl = process.env.EBK_SMOKE_API_URL ?? '';
|
|
1309
|
+
const userPoolId = process.env.EBK_SMOKE_USER_POOL_ID ?? '';
|
|
1310
|
+
const clientId = process.env.EBK_SMOKE_CLIENT_ID ?? '';
|
|
1311
|
+
const smokeAuthority =
|
|
1312
|
+
process.env.EBK_SMOKE_AUTHORITY_VERSION ?? '';
|
|
1313
|
+
const requestedRunId = process.env.EBK_TEST_RUN_ID;
|
|
1314
|
+
const runId = requestedRunId ?? randomUUID().replaceAll('-', '');
|
|
1315
|
+
const seed = process.env.EBK_TEST_SEED ?? runId;
|
|
1316
|
+
const validRunId = /^[a-z0-9]{1,32}$/.test(runId);
|
|
1317
|
+
const validSeed = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(seed);
|
|
1318
|
+
const evidenceRunId = validRunId ? runId : 'invalid-run-id';
|
|
1319
|
+
const evidenceSeed = validSeed ? seed : 'invalid-seed';
|
|
1320
|
+
const resultPath = join(evidenceRoot, 'smoke-deployed.json');
|
|
1321
|
+
const sanitizedReportPath = join(evidenceRoot, 'playwright-smoke.json');
|
|
1322
|
+
rmSync(resultPath, { force: true });
|
|
1323
|
+
rmSync(sanitizedReportPath, { force: true });
|
|
1324
|
+
const rawPath = (attempt) =>
|
|
1325
|
+
join(rawRoot, 'playwright-smoke-' + attempt + '.json');
|
|
1326
|
+
const dataCleanupPath = (attempt) =>
|
|
1327
|
+
join(rawRoot, 'data-cleanup-' + attempt + '.json');
|
|
1328
|
+
const source = {
|
|
1329
|
+
blueprintCandidates: [
|
|
1330
|
+
ownership.blueprint.name + '@' + ownership.blueprint.version,
|
|
1331
|
+
],
|
|
1332
|
+
commit: validSourceCommit ? gitCommit : '0'.repeat(40),
|
|
1333
|
+
frameworkCandidate,
|
|
1334
|
+
};
|
|
1335
|
+
const operation = {
|
|
1336
|
+
commandVersion: '1',
|
|
1337
|
+
project: '${name}',
|
|
1338
|
+
proofLayer: 'deployed-smoke',
|
|
1339
|
+
target: 'smoke-deployed',
|
|
1340
|
+
};
|
|
1341
|
+
const startedAt = new Date().toISOString();
|
|
1342
|
+
let quarantineRecords = [];
|
|
1343
|
+
|
|
1344
|
+
function writeResult(result) {
|
|
1345
|
+
writeFileSync(resultPath, JSON.stringify(result, null, 2) + '\\n', 'utf8');
|
|
1346
|
+
process.stdout.write(JSON.stringify(result) + '\\n');
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function readBoundedUtf8(path, minimumBytes, maximumBytes) {
|
|
1350
|
+
const size = statSync(path).size;
|
|
1351
|
+
if (
|
|
1352
|
+
!Number.isSafeInteger(size) ||
|
|
1353
|
+
size < minimumBytes ||
|
|
1354
|
+
size > maximumBytes
|
|
1355
|
+
) {
|
|
1356
|
+
throw new Error('invalid-bounded-file-size');
|
|
1357
|
+
}
|
|
1358
|
+
const bytes = readFileSync(path);
|
|
1359
|
+
if (bytes.byteLength !== size) {
|
|
1360
|
+
throw new Error('bounded-file-size-changed');
|
|
1361
|
+
}
|
|
1362
|
+
return fatalUtf8Decoder.decode(bytes);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function readDataCleanupOutcome(path) {
|
|
1366
|
+
try {
|
|
1367
|
+
if (!existsSync(path)) throw new Error('missing-cleanup-outcome');
|
|
1368
|
+
const value = JSON.parse(readBoundedUtf8(path, 2, 1024));
|
|
1369
|
+
if (
|
|
1370
|
+
!value ||
|
|
1371
|
+
typeof value !== 'object' ||
|
|
1372
|
+
Array.isArray(value) ||
|
|
1373
|
+
JSON.stringify(Object.keys(value).sort()) !==
|
|
1374
|
+
JSON.stringify(['outcome', 'schemaVersion']) ||
|
|
1375
|
+
value.schemaVersion !== 1 ||
|
|
1376
|
+
!['not-required', 'passed'].includes(value.outcome)
|
|
1377
|
+
) {
|
|
1378
|
+
throw new Error('invalid-cleanup-outcome');
|
|
1379
|
+
}
|
|
1380
|
+
return { outcome: value.outcome };
|
|
1381
|
+
} catch {
|
|
1382
|
+
return {
|
|
1383
|
+
outcome: 'failed',
|
|
1384
|
+
sanitizedSummary:
|
|
1385
|
+
'The deployed smoke record cleanup was not proved.',
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function failPreflight(
|
|
1391
|
+
classification,
|
|
1392
|
+
summary,
|
|
1393
|
+
cleanup = { outcome: 'not-required' },
|
|
1394
|
+
) {
|
|
1395
|
+
const result = createTestingEvidenceFromRunnerAttempts({
|
|
1396
|
+
attempts: [],
|
|
1397
|
+
cleanup,
|
|
1398
|
+
environment: {
|
|
1399
|
+
class: 'protected-github',
|
|
1400
|
+
deployed: true,
|
|
1401
|
+
stage: stage || 'unresolved',
|
|
1402
|
+
},
|
|
1403
|
+
operation,
|
|
1404
|
+
preflight: {
|
|
1405
|
+
completedAt: new Date().toISOString(),
|
|
1406
|
+
findings: [{ classification, summary }],
|
|
1407
|
+
startedAt,
|
|
1408
|
+
},
|
|
1409
|
+
proofStarted: false,
|
|
1410
|
+
runScope: evidenceRunId,
|
|
1411
|
+
seed: evidenceSeed,
|
|
1412
|
+
slice: '${name}',
|
|
1413
|
+
source,
|
|
1414
|
+
workspace: workspacePackage.name,
|
|
1415
|
+
});
|
|
1416
|
+
rmSync(sanitizedReportPath, { force: true });
|
|
1417
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
1418
|
+
writeResult(result);
|
|
1419
|
+
process.exitCode = 1;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function credentialFreeHttpsUrl(value) {
|
|
1423
|
+
let parsed;
|
|
1424
|
+
try {
|
|
1425
|
+
parsed = new URL(value);
|
|
1426
|
+
} catch {
|
|
1427
|
+
return undefined;
|
|
1428
|
+
}
|
|
1429
|
+
return parsed.protocol === 'https:' &&
|
|
1430
|
+
!parsed.username &&
|
|
1431
|
+
!parsed.password &&
|
|
1432
|
+
!parsed.search &&
|
|
1433
|
+
!parsed.hash
|
|
1434
|
+
? parsed
|
|
1435
|
+
: undefined;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function permissionFailure(error, operation, authority = 'Cognito principal') {
|
|
1439
|
+
const name =
|
|
1440
|
+
error && typeof error === 'object' && 'name' in error
|
|
1441
|
+
? String(error.name)
|
|
1442
|
+
: 'UnknownAwsError';
|
|
1443
|
+
const denied = [
|
|
1444
|
+
'AccessDeniedException',
|
|
1445
|
+
'NotAuthorizedException',
|
|
1446
|
+
'UnauthorizedException',
|
|
1447
|
+
].includes(name);
|
|
1448
|
+
return {
|
|
1449
|
+
classification: denied
|
|
1450
|
+
? 'authorization-denied'
|
|
1451
|
+
: 'infrastructure-unavailable',
|
|
1452
|
+
summary: denied
|
|
1453
|
+
? 'The protected smoke role lacks the declared ' +
|
|
1454
|
+
authority +
|
|
1455
|
+
' permission for ' +
|
|
1456
|
+
operation +
|
|
1457
|
+
'.'
|
|
1458
|
+
: authority +
|
|
1459
|
+
' ' +
|
|
1460
|
+
operation +
|
|
1461
|
+
' failed with ' +
|
|
1462
|
+
name.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80) +
|
|
1463
|
+
'.',
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function isCognitoUserAbsent(error) {
|
|
1468
|
+
return (
|
|
1469
|
+
error &&
|
|
1470
|
+
typeof error === 'object' &&
|
|
1471
|
+
'name' in error &&
|
|
1472
|
+
error.name === 'UserNotFoundException'
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function structuredLogRecord(message) {
|
|
1477
|
+
let candidate = message;
|
|
1478
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
1479
|
+
if (typeof candidate === 'string') {
|
|
1480
|
+
try {
|
|
1481
|
+
candidate = JSON.parse(candidate);
|
|
1482
|
+
} catch {
|
|
1483
|
+
return undefined;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
1487
|
+
return undefined;
|
|
1488
|
+
}
|
|
1489
|
+
if (
|
|
1490
|
+
candidate.schemaVersion === 1 &&
|
|
1491
|
+
typeof candidate.component === 'string'
|
|
1492
|
+
) {
|
|
1493
|
+
return candidate;
|
|
1494
|
+
}
|
|
1495
|
+
candidate = candidate.message ?? candidate.record;
|
|
1496
|
+
}
|
|
1497
|
+
return undefined;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function validObservationIntent(value) {
|
|
1501
|
+
return (
|
|
1502
|
+
value &&
|
|
1503
|
+
typeof value === 'object' &&
|
|
1504
|
+
!Array.isArray(value) &&
|
|
1505
|
+
JSON.stringify(Object.keys(value).sort()) ===
|
|
1506
|
+
JSON.stringify(['correlationId', 'eventId', 'schemaVersion']) &&
|
|
1507
|
+
value.schemaVersion === 1 &&
|
|
1508
|
+
/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(value.eventId) &&
|
|
1509
|
+
/^synthetic-[0-9a-f]{32}$/.test(value.correlationId)
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function inspectPlaywrightReportPolicy(report) {
|
|
1514
|
+
const config = report?.config;
|
|
1515
|
+
const stats = report?.stats;
|
|
1516
|
+
const countNames = ['expected', 'flaky', 'skipped', 'unexpected'];
|
|
1517
|
+
if (
|
|
1518
|
+
!report ||
|
|
1519
|
+
typeof report !== 'object' ||
|
|
1520
|
+
Array.isArray(report) ||
|
|
1521
|
+
!Array.isArray(report.suites) ||
|
|
1522
|
+
!config ||
|
|
1523
|
+
typeof config !== 'object' ||
|
|
1524
|
+
Array.isArray(config) ||
|
|
1525
|
+
typeof config.forbidOnly !== 'boolean' ||
|
|
1526
|
+
!stats ||
|
|
1527
|
+
typeof stats !== 'object' ||
|
|
1528
|
+
Array.isArray(stats) ||
|
|
1529
|
+
countNames.some(
|
|
1530
|
+
(name) =>
|
|
1531
|
+
!Number.isSafeInteger(stats[name]) ||
|
|
1532
|
+
stats[name] < 0,
|
|
1533
|
+
)
|
|
1534
|
+
) {
|
|
1535
|
+
return { invalid: true };
|
|
1536
|
+
}
|
|
1537
|
+
const observed = {
|
|
1538
|
+
expected: 0,
|
|
1539
|
+
flaky: 0,
|
|
1540
|
+
skipped: 0,
|
|
1541
|
+
unexpected: 0,
|
|
1542
|
+
};
|
|
1543
|
+
let expectedFailureCount = 0;
|
|
1544
|
+
let testCount = 0;
|
|
1545
|
+
const suites = [...report.suites];
|
|
1546
|
+
while (suites.length > 0) {
|
|
1547
|
+
const suite = suites.pop();
|
|
1548
|
+
if (
|
|
1549
|
+
!suite ||
|
|
1550
|
+
typeof suite !== 'object' ||
|
|
1551
|
+
Array.isArray(suite) ||
|
|
1552
|
+
(suite.suites !== undefined && !Array.isArray(suite.suites)) ||
|
|
1553
|
+
(suite.specs !== undefined && !Array.isArray(suite.specs))
|
|
1554
|
+
) {
|
|
1555
|
+
return { invalid: true };
|
|
1556
|
+
}
|
|
1557
|
+
suites.push(...(suite.suites ?? []));
|
|
1558
|
+
for (const spec of suite.specs ?? []) {
|
|
1559
|
+
if (
|
|
1560
|
+
!spec ||
|
|
1561
|
+
typeof spec !== 'object' ||
|
|
1562
|
+
Array.isArray(spec) ||
|
|
1563
|
+
!Array.isArray(spec.tests)
|
|
1564
|
+
) {
|
|
1565
|
+
return { invalid: true };
|
|
1566
|
+
}
|
|
1567
|
+
for (const testCase of spec.tests) {
|
|
1568
|
+
if (
|
|
1569
|
+
!testCase ||
|
|
1570
|
+
typeof testCase !== 'object' ||
|
|
1571
|
+
Array.isArray(testCase) ||
|
|
1572
|
+
!['expected', 'flaky', 'skipped', 'unexpected'].includes(
|
|
1573
|
+
testCase.status,
|
|
1574
|
+
) ||
|
|
1575
|
+
!['failed', 'passed', 'skipped', 'timedOut'].includes(
|
|
1576
|
+
testCase.expectedStatus,
|
|
1577
|
+
)
|
|
1578
|
+
) {
|
|
1579
|
+
return { invalid: true };
|
|
1580
|
+
}
|
|
1581
|
+
observed[testCase.status] += 1;
|
|
1582
|
+
testCount += 1;
|
|
1583
|
+
if (
|
|
1584
|
+
testCase.expectedStatus === 'failed' ||
|
|
1585
|
+
testCase.expectedStatus === 'timedOut'
|
|
1586
|
+
) {
|
|
1587
|
+
expectedFailureCount += 1;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (
|
|
1593
|
+
testCount === 0 ||
|
|
1594
|
+
countNames.some((name) => observed[name] !== stats[name])
|
|
1595
|
+
) {
|
|
1596
|
+
return { invalid: true };
|
|
1597
|
+
}
|
|
1598
|
+
return {
|
|
1599
|
+
expectedFailureCount,
|
|
1600
|
+
flakyCount: stats.flaky,
|
|
1601
|
+
focusedConfiguration: !config.forbidOnly,
|
|
1602
|
+
invalid: false,
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
async function observePublishedEvent(logs, observationPath, attemptStartedAt) {
|
|
1607
|
+
let intent;
|
|
1608
|
+
try {
|
|
1609
|
+
const contents = readBoundedUtf8(observationPath, 2, 1024);
|
|
1610
|
+
intent = JSON.parse(contents);
|
|
1611
|
+
} catch {
|
|
1612
|
+
return {
|
|
1613
|
+
classification: 'evidence-incomplete',
|
|
1614
|
+
summary:
|
|
1615
|
+
'The browser did not emit a valid credential-free event observation intent.',
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
if (!validObservationIntent(intent)) {
|
|
1619
|
+
return {
|
|
1620
|
+
classification: 'evidence-incomplete',
|
|
1621
|
+
summary:
|
|
1622
|
+
'The browser emitted an invalid credential-free event observation intent.',
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
const logGroupName = '/ebk/' + stage + '/${name}/event-consumer';
|
|
1626
|
+
const startTime = Math.max(0, Date.parse(attemptStartedAt) - 5_000);
|
|
1627
|
+
for (let poll = 0; poll < 8; poll += 1) {
|
|
1628
|
+
let response;
|
|
1629
|
+
try {
|
|
1630
|
+
response = await logs.send(
|
|
1631
|
+
new FilterLogEventsCommand({
|
|
1632
|
+
filterPattern: '"' + intent.eventId + '"',
|
|
1633
|
+
limit: 100,
|
|
1634
|
+
logGroupName,
|
|
1635
|
+
startTime,
|
|
1636
|
+
}),
|
|
1637
|
+
);
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
return permissionFailure(
|
|
1640
|
+
error,
|
|
1641
|
+
'event-consumer observation',
|
|
1642
|
+
'CloudWatch Logs',
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
const observed = (response.events ?? []).some(({ message }) => {
|
|
1646
|
+
const record = structuredLogRecord(message);
|
|
1647
|
+
return (
|
|
1648
|
+
record?.component === 'event-consumer' &&
|
|
1649
|
+
record?.correlationId === intent.correlationId &&
|
|
1650
|
+
record?.eventId === intent.eventId &&
|
|
1651
|
+
record?.eventType === '${pascalName}Created' &&
|
|
1652
|
+
record?.feature === 'events' &&
|
|
1653
|
+
record?.operation === 'consume-created' &&
|
|
1654
|
+
record?.outcome === 'success' &&
|
|
1655
|
+
record?.slice === '${name}' &&
|
|
1656
|
+
record?.stage === stage
|
|
1657
|
+
);
|
|
1658
|
+
});
|
|
1659
|
+
if (observed) return undefined;
|
|
1660
|
+
if (poll < 7) {
|
|
1661
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, 3_000));
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
classification: 'assertion-failed',
|
|
1666
|
+
summary:
|
|
1667
|
+
'The exact published event and correlation were not observed in the managed event-consumer log group within the bounded window.',
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
const url = credentialFreeHttpsUrl(smokeUrl);
|
|
1672
|
+
const apiUrl = credentialFreeHttpsUrl(smokeApiUrl);
|
|
1673
|
+
const expectedProductPath = '/${name}';
|
|
1674
|
+
const normalizedProductPath = url?.pathname.replace(/\\/$/, '') || '/';
|
|
1675
|
+
const validIdentity =
|
|
1676
|
+
/^[A-Za-z0-9_-]{1,128}_[A-Za-z0-9]{1,128}$/.test(userPoolId) &&
|
|
1677
|
+
/^[a-z0-9]{1,128}$/.test(clientId);
|
|
1678
|
+
if (
|
|
1679
|
+
!['staging', 'production'].includes(stage) ||
|
|
1680
|
+
!url ||
|
|
1681
|
+
!apiUrl ||
|
|
1682
|
+
normalizedProductPath !== expectedProductPath ||
|
|
1683
|
+
apiUrl.pathname !== '/' ||
|
|
1684
|
+
!validIdentity ||
|
|
1685
|
+
!validSourceCommit ||
|
|
1686
|
+
!validRunId ||
|
|
1687
|
+
!validSeed ||
|
|
1688
|
+
smokeAuthority !== 'cognito-admin-cloudwatch-observe-v1' ||
|
|
1689
|
+
!process.env.AWS_REGION
|
|
1690
|
+
) {
|
|
1691
|
+
failPreflight(
|
|
1692
|
+
'prerequisite-missing',
|
|
1693
|
+
'Protected smoke requires staging or production, one exact source commit, reviewed credential-free HTTPS web and API targets, exact Cognito identity, bounded run/seed identity, AWS region, and cognito-admin-cloudwatch-observe-v1 authority acknowledgement.',
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
if (process.exitCode === undefined) {
|
|
1697
|
+
let quarantine;
|
|
1698
|
+
try {
|
|
1699
|
+
quarantine = validateTestingEvidenceQuarantine(
|
|
1700
|
+
readJson(join(projectRoot, 'testing', 'quarantine.json')),
|
|
1701
|
+
{
|
|
1702
|
+
now: startedAt,
|
|
1703
|
+
workflowRun: githubWorkflowRun(),
|
|
1704
|
+
},
|
|
1705
|
+
);
|
|
1706
|
+
} catch {
|
|
1707
|
+
failPreflight(
|
|
1708
|
+
'environment-drift',
|
|
1709
|
+
'The testing quarantine file is missing or malformed.',
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
if (process.exitCode === undefined && quarantine && !quarantine.valid) {
|
|
1713
|
+
failPreflight(
|
|
1714
|
+
'environment-drift',
|
|
1715
|
+
quarantine.issues
|
|
1716
|
+
.map(({ message, path }) => path + ' ' + message)
|
|
1717
|
+
.join('; '),
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
if (process.exitCode === undefined && quarantine?.valid) {
|
|
1721
|
+
quarantineRecords = quarantine.value.records;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if (process.exitCode === undefined) {
|
|
1726
|
+
const cognito = new CognitoIdentityProviderClient({
|
|
1727
|
+
region: process.env.AWS_REGION,
|
|
1728
|
+
});
|
|
1729
|
+
const logs = new CloudWatchLogsClient({
|
|
1730
|
+
region: process.env.AWS_REGION,
|
|
1731
|
+
});
|
|
1732
|
+
const principalEmail =
|
|
1733
|
+
('ebk-smoke-${name}-' + runId).slice(0, 60) + '@example.invalid';
|
|
1734
|
+
const reviewerEmail =
|
|
1735
|
+
('ebk-review-${name}-' + runId).slice(0, 60) + '@example.invalid';
|
|
1736
|
+
let principalPassword =
|
|
1737
|
+
'Ebk!' + randomBytes(30).toString('base64url') + '9aA';
|
|
1738
|
+
let reviewerPassword =
|
|
1739
|
+
'Ebk!' + randomBytes(30).toString('base64url') + '9aA';
|
|
1740
|
+
let principalCleanupRequired = false;
|
|
1741
|
+
let reviewerCleanupRequired = false;
|
|
1742
|
+
let setupFailure;
|
|
1743
|
+
let preflightCleanup = { outcome: 'not-required' };
|
|
1744
|
+
try {
|
|
1745
|
+
await cognito.send(
|
|
1746
|
+
new DescribeUserPoolClientCommand({
|
|
1747
|
+
ClientId: clientId,
|
|
1748
|
+
UserPoolId: userPoolId,
|
|
1749
|
+
}),
|
|
1750
|
+
);
|
|
1751
|
+
principalCleanupRequired = true;
|
|
1752
|
+
await cognito.send(
|
|
1753
|
+
new AdminCreateUserCommand({
|
|
1754
|
+
MessageAction: 'SUPPRESS',
|
|
1755
|
+
UserAttributes: [
|
|
1756
|
+
{ Name: 'email', Value: principalEmail },
|
|
1757
|
+
{ Name: 'email_verified', Value: 'true' },
|
|
1758
|
+
],
|
|
1759
|
+
Username: principalEmail,
|
|
1760
|
+
UserPoolId: userPoolId,
|
|
1761
|
+
}),
|
|
1762
|
+
);
|
|
1763
|
+
await cognito.send(
|
|
1764
|
+
new AdminSetUserPasswordCommand({
|
|
1765
|
+
Password: principalPassword,
|
|
1766
|
+
Permanent: true,
|
|
1767
|
+
Username: principalEmail,
|
|
1768
|
+
UserPoolId: userPoolId,
|
|
1769
|
+
}),
|
|
1770
|
+
);
|
|
1771
|
+
await cognito.send(
|
|
1772
|
+
new AdminAddUserToGroupCommand({
|
|
1773
|
+
GroupName: '${name}-test-writer',
|
|
1774
|
+
Username: principalEmail,
|
|
1775
|
+
UserPoolId: userPoolId,
|
|
1776
|
+
}),
|
|
1777
|
+
);
|
|
1778
|
+
reviewerCleanupRequired = true;
|
|
1779
|
+
await cognito.send(
|
|
1780
|
+
new AdminCreateUserCommand({
|
|
1781
|
+
MessageAction: 'SUPPRESS',
|
|
1782
|
+
UserAttributes: [
|
|
1783
|
+
{ Name: 'email', Value: reviewerEmail },
|
|
1784
|
+
{ Name: 'email_verified', Value: 'true' },
|
|
1785
|
+
],
|
|
1786
|
+
Username: reviewerEmail,
|
|
1787
|
+
UserPoolId: userPoolId,
|
|
1788
|
+
}),
|
|
1789
|
+
);
|
|
1790
|
+
await cognito.send(
|
|
1791
|
+
new AdminSetUserPasswordCommand({
|
|
1792
|
+
Password: reviewerPassword,
|
|
1793
|
+
Permanent: true,
|
|
1794
|
+
Username: reviewerEmail,
|
|
1795
|
+
UserPoolId: userPoolId,
|
|
1796
|
+
}),
|
|
1797
|
+
);
|
|
1798
|
+
await cognito.send(
|
|
1799
|
+
new AdminAddUserToGroupCommand({
|
|
1800
|
+
GroupName: '${name}-admin',
|
|
1801
|
+
Username: reviewerEmail,
|
|
1802
|
+
UserPoolId: userPoolId,
|
|
1803
|
+
}),
|
|
1804
|
+
);
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
setupFailure = permissionFailure(error, 'setup');
|
|
1807
|
+
}
|
|
1808
|
+
if (setupFailure && reviewerCleanupRequired) {
|
|
1809
|
+
try {
|
|
1810
|
+
await cognito.send(
|
|
1811
|
+
new AdminDeleteUserCommand({
|
|
1812
|
+
Username: reviewerEmail,
|
|
1813
|
+
UserPoolId: userPoolId,
|
|
1814
|
+
}),
|
|
1815
|
+
);
|
|
1816
|
+
reviewerCleanupRequired = false;
|
|
1817
|
+
if (preflightCleanup.outcome !== 'failed') {
|
|
1818
|
+
preflightCleanup = { outcome: 'passed' };
|
|
1819
|
+
}
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
if (isCognitoUserAbsent(error)) {
|
|
1822
|
+
reviewerCleanupRequired = false;
|
|
1823
|
+
if (preflightCleanup.outcome !== 'failed') {
|
|
1824
|
+
preflightCleanup = { outcome: 'passed' };
|
|
1825
|
+
}
|
|
1826
|
+
} else {
|
|
1827
|
+
preflightCleanup = {
|
|
1828
|
+
outcome: 'failed',
|
|
1829
|
+
sanitizedSummary: permissionFailure(error, 'cleanup').summary,
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
if (setupFailure && principalCleanupRequired) {
|
|
1835
|
+
try {
|
|
1836
|
+
await cognito.send(
|
|
1837
|
+
new AdminDeleteUserCommand({
|
|
1838
|
+
Username: principalEmail,
|
|
1839
|
+
UserPoolId: userPoolId,
|
|
1840
|
+
}),
|
|
1841
|
+
);
|
|
1842
|
+
principalCleanupRequired = false;
|
|
1843
|
+
if (preflightCleanup.outcome !== 'failed') {
|
|
1844
|
+
preflightCleanup = { outcome: 'passed' };
|
|
1845
|
+
}
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
if (isCognitoUserAbsent(error)) {
|
|
1848
|
+
principalCleanupRequired = false;
|
|
1849
|
+
if (preflightCleanup.outcome !== 'failed') {
|
|
1850
|
+
preflightCleanup = { outcome: 'passed' };
|
|
1851
|
+
}
|
|
1852
|
+
} else {
|
|
1853
|
+
preflightCleanup = {
|
|
1854
|
+
outcome: 'failed',
|
|
1855
|
+
sanitizedSummary: permissionFailure(error, 'cleanup').summary,
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
if (setupFailure) {
|
|
1861
|
+
principalPassword = undefined;
|
|
1862
|
+
reviewerPassword = undefined;
|
|
1863
|
+
failPreflight(
|
|
1864
|
+
setupFailure.classification,
|
|
1865
|
+
setupFailure.summary,
|
|
1866
|
+
preflightCleanup,
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
if (process.exitCode !== undefined) {
|
|
1871
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
1872
|
+
} else {
|
|
1873
|
+
const executable =
|
|
1874
|
+
process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
1875
|
+
const executeAttempt = async (attempt) => {
|
|
1876
|
+
const attemptStartedAt = new Date().toISOString();
|
|
1877
|
+
const path = rawPath(attempt);
|
|
1878
|
+
const observationPath = join(
|
|
1879
|
+
rawRoot,
|
|
1880
|
+
'event-observation-' + attempt + '.json',
|
|
1881
|
+
);
|
|
1882
|
+
const cleanupPath = dataCleanupPath(attempt);
|
|
1883
|
+
rmSync(observationPath, { force: true });
|
|
1884
|
+
rmSync(cleanupPath, { force: true });
|
|
1885
|
+
let dataCleanup = {
|
|
1886
|
+
outcome: 'failed',
|
|
1887
|
+
sanitizedSummary:
|
|
1888
|
+
'The deployed smoke record cleanup was not proved.',
|
|
1889
|
+
};
|
|
1890
|
+
let execution;
|
|
1891
|
+
let observationFinding;
|
|
1892
|
+
let reportPolicyFinding;
|
|
1893
|
+
let report;
|
|
1894
|
+
let reportCredentialLeak = false;
|
|
1895
|
+
let reportInvalid = false;
|
|
1896
|
+
try {
|
|
1897
|
+
const browserEnvironmentNames = new Set([
|
|
1898
|
+
'APPDATA',
|
|
1899
|
+
'CI',
|
|
1900
|
+
'ComSpec',
|
|
1901
|
+
'COMSPEC',
|
|
1902
|
+
'HOME',
|
|
1903
|
+
'LANG',
|
|
1904
|
+
'LC_ALL',
|
|
1905
|
+
'LOCALAPPDATA',
|
|
1906
|
+
'PATH',
|
|
1907
|
+
'PATHEXT',
|
|
1908
|
+
'PLAYWRIGHT_BROWSERS_PATH',
|
|
1909
|
+
'SystemRoot',
|
|
1910
|
+
'SYSTEMROOT',
|
|
1911
|
+
'TEMP',
|
|
1912
|
+
'TMP',
|
|
1913
|
+
'TMPDIR',
|
|
1914
|
+
'USERPROFILE',
|
|
1915
|
+
]);
|
|
1916
|
+
const childEnvironment = Object.fromEntries(
|
|
1917
|
+
Object.entries(process.env).filter(
|
|
1918
|
+
([name, value]) =>
|
|
1919
|
+
browserEnvironmentNames.has(name) &&
|
|
1920
|
+
typeof value === 'string',
|
|
1921
|
+
),
|
|
1922
|
+
);
|
|
1923
|
+
execution = spawnSync(
|
|
1924
|
+
executable,
|
|
1925
|
+
['exec', 'playwright', 'test', '--reporter=json'],
|
|
1926
|
+
{
|
|
1927
|
+
cwd: projectRoot,
|
|
1928
|
+
encoding: 'utf8',
|
|
1929
|
+
env: {
|
|
1930
|
+
...childEnvironment,
|
|
1931
|
+
EBK_SOURCE_COMMIT: gitCommit,
|
|
1932
|
+
EBK_STAGE: stage,
|
|
1933
|
+
EBK_SMOKE_API_URL: apiUrl.href,
|
|
1934
|
+
EBK_SMOKE_DATA_CLEANUP_PATH: cleanupPath,
|
|
1935
|
+
EBK_SMOKE_EMAIL: principalEmail,
|
|
1936
|
+
EBK_SMOKE_PASSWORD: principalPassword,
|
|
1937
|
+
EBK_SMOKE_REVIEWER_EMAIL: reviewerEmail,
|
|
1938
|
+
EBK_SMOKE_REVIEWER_PASSWORD: reviewerPassword,
|
|
1939
|
+
EBK_SMOKE_OBSERVATION_PATH: observationPath,
|
|
1940
|
+
EBK_SMOKE_URL: url.href,
|
|
1941
|
+
EBK_TEST_RUN_ID: runId,
|
|
1942
|
+
EBK_TEST_SEED: seed,
|
|
1943
|
+
PLAYWRIGHT_JSON_OUTPUT_NAME: path,
|
|
1944
|
+
},
|
|
1945
|
+
shell: false,
|
|
1946
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
1947
|
+
windowsHide: true,
|
|
1948
|
+
},
|
|
1949
|
+
);
|
|
1950
|
+
if (existsSync(path)) {
|
|
1951
|
+
try {
|
|
1952
|
+
const reportContents = readBoundedUtf8(
|
|
1953
|
+
path,
|
|
1954
|
+
2,
|
|
1955
|
+
5 * 1024 * 1024,
|
|
1956
|
+
);
|
|
1957
|
+
if (
|
|
1958
|
+
(typeof principalPassword === 'string' &&
|
|
1959
|
+
reportContents.includes(principalPassword)) ||
|
|
1960
|
+
(typeof reviewerPassword === 'string' &&
|
|
1961
|
+
reportContents.includes(reviewerPassword))
|
|
1962
|
+
) {
|
|
1963
|
+
reportCredentialLeak = true;
|
|
1964
|
+
} else {
|
|
1965
|
+
report = JSON.parse(reportContents);
|
|
1966
|
+
const policy = inspectPlaywrightReportPolicy(report);
|
|
1967
|
+
if (policy.invalid) {
|
|
1968
|
+
report = undefined;
|
|
1969
|
+
reportInvalid = true;
|
|
1970
|
+
} else if (policy.flakyCount > 0) {
|
|
1971
|
+
reportPolicyFinding = {
|
|
1972
|
+
classification: 'assertion-failed',
|
|
1973
|
+
summary:
|
|
1974
|
+
'Playwright runner-internal retries are forbidden in deployed proof.',
|
|
1975
|
+
};
|
|
1976
|
+
} else if (
|
|
1977
|
+
policy.expectedFailureCount > 0 ||
|
|
1978
|
+
policy.focusedConfiguration
|
|
1979
|
+
) {
|
|
1980
|
+
reportPolicyFinding = {
|
|
1981
|
+
classification: 'assertion-failed',
|
|
1982
|
+
summary:
|
|
1983
|
+
'Playwright focused tests and expected-failure semantics are forbidden in deployed proof.',
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
} catch {
|
|
1988
|
+
reportInvalid = true;
|
|
1989
|
+
}
|
|
1990
|
+
} else {
|
|
1991
|
+
reportInvalid = true;
|
|
1992
|
+
}
|
|
1993
|
+
if (execution?.status === 0) {
|
|
1994
|
+
observationFinding = await observePublishedEvent(
|
|
1995
|
+
logs,
|
|
1996
|
+
observationPath,
|
|
1997
|
+
attemptStartedAt,
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
} finally {
|
|
2001
|
+
dataCleanup = readDataCleanupOutcome(cleanupPath);
|
|
2002
|
+
rmSync(path, { force: true });
|
|
2003
|
+
rmSync(observationPath, { force: true });
|
|
2004
|
+
rmSync(cleanupPath, { force: true });
|
|
2005
|
+
rmSync(join(projectRoot, 'test-results'), {
|
|
2006
|
+
force: true,
|
|
2007
|
+
recursive: true,
|
|
2008
|
+
});
|
|
2009
|
+
rmSync(join(projectRoot, 'playwright-report'), {
|
|
2010
|
+
force: true,
|
|
2011
|
+
recursive: true,
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
const stats = report?.stats ?? {};
|
|
2015
|
+
const reportedSkipped = Number(stats.skipped ?? 0);
|
|
2016
|
+
const quarantine = reconcileRunnerQuarantine({
|
|
2017
|
+
records: quarantineRecords,
|
|
2018
|
+
runner: 'playwright',
|
|
2019
|
+
skippedTestIds: report
|
|
2020
|
+
? playwrightSkippedTestIds(report, projectRoot)
|
|
2021
|
+
: [],
|
|
2022
|
+
});
|
|
2023
|
+
const reportedUnexpected = Number(stats.unexpected ?? 0);
|
|
2024
|
+
const processSucceeded =
|
|
2025
|
+
execution?.status === 0 &&
|
|
2026
|
+
execution.signal === null &&
|
|
2027
|
+
execution.error === undefined;
|
|
2028
|
+
const runnerFailed = Math.max(
|
|
2029
|
+
Number.isSafeInteger(reportedUnexpected) &&
|
|
2030
|
+
reportedUnexpected >= 0
|
|
2031
|
+
? reportedUnexpected
|
|
2032
|
+
: 1,
|
|
2033
|
+
processSucceeded &&
|
|
2034
|
+
!reportCredentialLeak &&
|
|
2035
|
+
!reportInvalid &&
|
|
2036
|
+
report
|
|
2037
|
+
? 0
|
|
2038
|
+
: 1,
|
|
2039
|
+
);
|
|
2040
|
+
const failed = Math.max(
|
|
2041
|
+
runnerFailed,
|
|
2042
|
+
observationFinding ? 1 : 0,
|
|
2043
|
+
reportPolicyFinding ? 1 : 0,
|
|
2044
|
+
);
|
|
2045
|
+
return {
|
|
2046
|
+
attempt,
|
|
2047
|
+
completedAt: new Date().toISOString(),
|
|
2048
|
+
dataCleanup,
|
|
2049
|
+
diagnostic:
|
|
2050
|
+
(reportInvalid
|
|
2051
|
+
? 'Playwright emitted malformed machine-readable output.'
|
|
2052
|
+
: undefined) ||
|
|
2053
|
+
(reportCredentialLeak
|
|
2054
|
+
? 'Playwright output contained the synthetic principal credential and was removed.'
|
|
2055
|
+
: undefined) ||
|
|
2056
|
+
(execution?.error
|
|
2057
|
+
? 'The Playwright process could not start.'
|
|
2058
|
+
: 'Playwright smoke assertions failed.'),
|
|
2059
|
+
failed,
|
|
2060
|
+
artifactSensitive: reportCredentialLeak,
|
|
2061
|
+
observationFindings: observationFinding
|
|
2062
|
+
? [observationFinding]
|
|
2063
|
+
: [],
|
|
2064
|
+
policyFindings: reportPolicyFinding
|
|
2065
|
+
? [reportPolicyFinding]
|
|
2066
|
+
: [],
|
|
2067
|
+
passed: Number(stats.expected ?? 0),
|
|
2068
|
+
report,
|
|
2069
|
+
reportInvalid,
|
|
2070
|
+
runnerFailed,
|
|
2071
|
+
quarantineFindings: [
|
|
2072
|
+
...quarantine.findings,
|
|
2073
|
+
...(reportedSkipped !==
|
|
2074
|
+
quarantine.quarantined + quarantine.skipped
|
|
2075
|
+
? [
|
|
2076
|
+
{
|
|
2077
|
+
classification: 'evidence-incomplete',
|
|
2078
|
+
summary:
|
|
2079
|
+
'Playwright skipped-test counts could not be reconciled to stable test identities.',
|
|
2080
|
+
},
|
|
2081
|
+
]
|
|
2082
|
+
: []),
|
|
2083
|
+
],
|
|
2084
|
+
quarantined: quarantine.quarantined,
|
|
2085
|
+
skipped: quarantine.skipped,
|
|
2086
|
+
startedAt: attemptStartedAt,
|
|
2087
|
+
};
|
|
2088
|
+
};
|
|
2089
|
+
const executions = [];
|
|
2090
|
+
const artifacts = [];
|
|
2091
|
+
let cleanup = { outcome: 'passed' };
|
|
2092
|
+
try {
|
|
2093
|
+
executions.push(await executeAttempt(1));
|
|
2094
|
+
if (executions[0].failed > 0) {
|
|
2095
|
+
executions.push(await executeAttempt(2));
|
|
2096
|
+
}
|
|
2097
|
+
const reports = executions
|
|
2098
|
+
.filter(({ report }) => report !== undefined)
|
|
2099
|
+
.map(({ attempt, report }) => ({ attempt, report }));
|
|
2100
|
+
if (reports.length > 0) {
|
|
2101
|
+
const raw = JSON.stringify({ reports }, null, 2) + '\\n';
|
|
2102
|
+
let inspection;
|
|
2103
|
+
try {
|
|
2104
|
+
inspection =
|
|
2105
|
+
(typeof principalPassword === 'string' &&
|
|
2106
|
+
raw.includes(principalPassword)) ||
|
|
2107
|
+
(typeof reviewerPassword === 'string' &&
|
|
2108
|
+
raw.includes(reviewerPassword))
|
|
2109
|
+
? { safe: false }
|
|
2110
|
+
: inspectTestingEvidenceArtifact({
|
|
2111
|
+
contents: raw,
|
|
2112
|
+
});
|
|
2113
|
+
} catch {
|
|
2114
|
+
inspection = { safe: false };
|
|
2115
|
+
}
|
|
2116
|
+
if (inspection.safe) {
|
|
2117
|
+
writeFileSync(sanitizedReportPath, raw, 'utf8');
|
|
2118
|
+
artifacts.push({
|
|
2119
|
+
digest:
|
|
2120
|
+
'sha256:' + createHash('sha256').update(raw).digest('hex'),
|
|
2121
|
+
name: relative(projectRoot, sanitizedReportPath)
|
|
2122
|
+
.replaceAll('\\\\', '/'),
|
|
2123
|
+
retentionClass: 'protected-promotion-proof',
|
|
2124
|
+
});
|
|
2125
|
+
} else {
|
|
2126
|
+
rmSync(sanitizedReportPath, { force: true });
|
|
2127
|
+
executions[executions.length - 1].artifactSensitive = true;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
} catch {
|
|
2131
|
+
rmSync(sanitizedReportPath, { force: true });
|
|
2132
|
+
const execution = executions.at(-1);
|
|
2133
|
+
if (execution) {
|
|
2134
|
+
execution.diagnostic =
|
|
2135
|
+
'The deployed browser runner failed before producing safe evidence.';
|
|
2136
|
+
execution.failed = Math.max(execution.failed, 1);
|
|
2137
|
+
execution.reportInvalid = true;
|
|
2138
|
+
} else {
|
|
2139
|
+
const failedAt = new Date().toISOString();
|
|
2140
|
+
executions.push({
|
|
2141
|
+
attempt: 1,
|
|
2142
|
+
completedAt: failedAt,
|
|
2143
|
+
dataCleanup: {
|
|
2144
|
+
outcome: 'failed',
|
|
2145
|
+
sanitizedSummary:
|
|
2146
|
+
'The deployed smoke record cleanup was not proved.',
|
|
2147
|
+
},
|
|
2148
|
+
diagnostic:
|
|
2149
|
+
'The deployed browser runner failed before producing safe evidence.',
|
|
2150
|
+
failed: 1,
|
|
2151
|
+
passed: 0,
|
|
2152
|
+
reportInvalid: true,
|
|
2153
|
+
skipped: 0,
|
|
2154
|
+
startedAt: failedAt,
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
} finally {
|
|
2158
|
+
principalPassword = undefined;
|
|
2159
|
+
reviewerPassword = undefined;
|
|
2160
|
+
if (artifacts.length === 0) {
|
|
2161
|
+
rmSync(sanitizedReportPath, { force: true });
|
|
2162
|
+
}
|
|
2163
|
+
rmSync(rawRoot, { force: true, recursive: true });
|
|
2164
|
+
rmSync(join(projectRoot, 'test-results'), {
|
|
2165
|
+
force: true,
|
|
2166
|
+
recursive: true,
|
|
2167
|
+
});
|
|
2168
|
+
rmSync(join(projectRoot, 'playwright-report'), {
|
|
2169
|
+
force: true,
|
|
2170
|
+
recursive: true,
|
|
2171
|
+
});
|
|
2172
|
+
if (principalCleanupRequired) {
|
|
2173
|
+
try {
|
|
2174
|
+
await cognito.send(
|
|
2175
|
+
new AdminDeleteUserCommand({
|
|
2176
|
+
Username: principalEmail,
|
|
2177
|
+
UserPoolId: userPoolId,
|
|
2178
|
+
}),
|
|
2179
|
+
);
|
|
2180
|
+
principalCleanupRequired = false;
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
if (isCognitoUserAbsent(error)) {
|
|
2183
|
+
principalCleanupRequired = false;
|
|
2184
|
+
} else {
|
|
2185
|
+
cleanup = {
|
|
2186
|
+
outcome: 'failed',
|
|
2187
|
+
sanitizedSummary: permissionFailure(error, 'cleanup').summary,
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
if (reviewerCleanupRequired) {
|
|
2193
|
+
try {
|
|
2194
|
+
await cognito.send(
|
|
2195
|
+
new AdminDeleteUserCommand({
|
|
2196
|
+
Username: reviewerEmail,
|
|
2197
|
+
UserPoolId: userPoolId,
|
|
2198
|
+
}),
|
|
2199
|
+
);
|
|
2200
|
+
reviewerCleanupRequired = false;
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
if (isCognitoUserAbsent(error)) {
|
|
2203
|
+
reviewerCleanupRequired = false;
|
|
2204
|
+
} else {
|
|
2205
|
+
cleanup = {
|
|
2206
|
+
outcome: 'failed',
|
|
2207
|
+
sanitizedSummary: permissionFailure(error, 'cleanup').summary,
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
if (
|
|
2214
|
+
executions.some(
|
|
2215
|
+
({ dataCleanup }) => dataCleanup?.outcome === 'failed',
|
|
2216
|
+
)
|
|
2217
|
+
) {
|
|
2218
|
+
cleanup = {
|
|
2219
|
+
outcome: 'failed',
|
|
2220
|
+
sanitizedSummary:
|
|
2221
|
+
cleanup.outcome === 'failed'
|
|
2222
|
+
? 'Synthetic principal or deployed record cleanup failed.'
|
|
2223
|
+
: 'The deployed smoke record cleanup was not proved.',
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
const attempts = executions.map((execution) =>
|
|
2227
|
+
normalizePlaywrightEvidenceAttempt({
|
|
2228
|
+
attempt: execution.attempt,
|
|
2229
|
+
completedAt: execution.completedAt,
|
|
2230
|
+
failed: execution.failed,
|
|
2231
|
+
findings: [
|
|
2232
|
+
...(execution.runnerFailed > 0
|
|
2233
|
+
? [
|
|
2234
|
+
{
|
|
2235
|
+
classification: 'assertion-failed',
|
|
2236
|
+
summary: sanitizeTestingEvidenceSummary(
|
|
2237
|
+
execution.diagnostic,
|
|
2238
|
+
),
|
|
2239
|
+
},
|
|
2240
|
+
]
|
|
2241
|
+
: []),
|
|
2242
|
+
...(execution.artifactSensitive
|
|
2243
|
+
? [
|
|
2244
|
+
{
|
|
2245
|
+
classification: 'sensitive-artifact',
|
|
2246
|
+
summary:
|
|
2247
|
+
'The Playwright report matched protected artifact data and was not retained.',
|
|
2248
|
+
},
|
|
2249
|
+
]
|
|
2250
|
+
: []),
|
|
2251
|
+
...(execution.reportInvalid
|
|
2252
|
+
? [
|
|
2253
|
+
{
|
|
2254
|
+
classification: 'evidence-incomplete',
|
|
2255
|
+
summary:
|
|
2256
|
+
'Playwright emitted malformed machine-readable output; raw output was removed.',
|
|
2257
|
+
},
|
|
2258
|
+
]
|
|
2259
|
+
: []),
|
|
2260
|
+
...(execution.quarantineFindings ?? []),
|
|
2261
|
+
...(execution.observationFindings ?? []),
|
|
2262
|
+
...(execution.policyFindings ?? []),
|
|
2263
|
+
],
|
|
2264
|
+
...(execution.attempt === 2
|
|
2265
|
+
? { identicalToPrevious: true }
|
|
2266
|
+
: {}),
|
|
2267
|
+
passed: execution.passed,
|
|
2268
|
+
quarantined: execution.quarantined ?? 0,
|
|
2269
|
+
skipped: execution.skipped,
|
|
2270
|
+
startedAt: execution.startedAt,
|
|
2271
|
+
}),
|
|
2272
|
+
);
|
|
2273
|
+
const result = createTestingEvidenceFromRunnerAttempts({
|
|
2274
|
+
artifacts,
|
|
2275
|
+
attempts,
|
|
2276
|
+
cleanup,
|
|
2277
|
+
environment: {
|
|
2278
|
+
class: 'protected-github',
|
|
2279
|
+
deployed: true,
|
|
2280
|
+
stage,
|
|
2281
|
+
},
|
|
2282
|
+
operation,
|
|
2283
|
+
proofStarted: true,
|
|
2284
|
+
runScope: runId,
|
|
2285
|
+
seed,
|
|
2286
|
+
slice: '${name}',
|
|
2287
|
+
source,
|
|
2288
|
+
workspace: workspacePackage.name,
|
|
2289
|
+
});
|
|
2290
|
+
writeResult(result);
|
|
2291
|
+
if (result.outcome !== 'passed') process.exitCode = 1;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
`,
|
|
2295
|
+
'testing/strict-json-reporter.mjs': `import { JsonReporter } from 'vitest/node';
|
|
2296
|
+
|
|
2297
|
+
export default class StrictJsonReporter extends JsonReporter {
|
|
2298
|
+
expectedFailures = 0;
|
|
2299
|
+
focusedTests = 0;
|
|
2300
|
+
retryConfiguredTests = 0;
|
|
2301
|
+
|
|
2302
|
+
async onTestRunEnd(testModules, unhandledErrors, reason) {
|
|
2303
|
+
for (const testModule of testModules) {
|
|
2304
|
+
for (const testCase of testModule.children.allTests()) {
|
|
2305
|
+
if (testCase.options.fails) this.expectedFailures += 1;
|
|
2306
|
+
if (testCase.options.mode === 'only') this.focusedTests += 1;
|
|
2307
|
+
if ((testCase.options.retry ?? 0) > 0) {
|
|
2308
|
+
this.retryConfiguredTests += 1;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
for (const testSuite of testModule.children.allSuites()) {
|
|
2312
|
+
if (testSuite.options.mode === 'only') this.focusedTests += 1;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
await super.onTestRunEnd(testModules, unhandledErrors, reason);
|
|
2316
|
+
if (
|
|
2317
|
+
this.expectedFailures > 0 ||
|
|
2318
|
+
this.focusedTests > 0 ||
|
|
2319
|
+
this.retryConfiguredTests > 0
|
|
2320
|
+
) {
|
|
2321
|
+
process.exitCode = 1;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
async writeReport(serializedReport) {
|
|
2326
|
+
const report = JSON.parse(serializedReport);
|
|
2327
|
+
report.ebkPolicy = {
|
|
2328
|
+
expectedFailures: this.expectedFailures,
|
|
2329
|
+
focusedTests: this.focusedTests,
|
|
2330
|
+
retryConfiguredTests: this.retryConfiguredTests,
|
|
2331
|
+
};
|
|
2332
|
+
await super.writeReport(JSON.stringify(report));
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
`,
|
|
2336
|
+
'vitest.integration.config.ts': `import { defineConfig } from 'vitest/config';
|
|
2337
|
+
|
|
2338
|
+
export default defineConfig({
|
|
2339
|
+
test: {
|
|
2340
|
+
allowOnly: false,
|
|
2341
|
+
environment: 'node',
|
|
2342
|
+
env: { EBK_STAGE: 'development' },
|
|
2343
|
+
// Hosted runners share four cores between Vitest, the proof registry,
|
|
2344
|
+
// and the dockerized database; rendezvous-orchestrated integration
|
|
2345
|
+
// proofs need a loaded-host budget, not the five-second idle default.
|
|
2346
|
+
hookTimeout: 30_000,
|
|
2347
|
+
include: ['integration/**/*.spec.ts'],
|
|
2348
|
+
maxWorkers: 1,
|
|
2349
|
+
retry: 0,
|
|
2350
|
+
testTimeout: 30_000,
|
|
2351
|
+
},
|
|
2352
|
+
});
|
|
2353
|
+
`,
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
//# sourceMappingURL=built-in-plan-testing-files.js.map
|