@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,2087 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CRANE_RELEASES = exports.CRANE_VERSION = void 0;
|
|
4
|
+
exports.renderContainerReleaseCommand = renderContainerReleaseCommand;
|
|
5
|
+
const generated_ecr_collector_command_js_1 = require("./generated-ecr-collector-command.js");
|
|
6
|
+
const safe_reference_1 = require("@empire-builder-kit/runtime/safe-reference");
|
|
7
|
+
exports.CRANE_VERSION = '0.21.7';
|
|
8
|
+
exports.CRANE_RELEASES = Object.freeze({
|
|
9
|
+
'linux-arm64': {
|
|
10
|
+
archiveFormat: 'tar-gzip',
|
|
11
|
+
sha256: 'b6ee979d9411dfb05ce35ab9e156fe5de7def11a230764a7856ffa2eb971fa88',
|
|
12
|
+
source: 'https://github.com/google/go-containerregistry/releases/download/v0.21.7/go-containerregistry_Linux_arm64.tar.gz',
|
|
13
|
+
},
|
|
14
|
+
'linux-x64': {
|
|
15
|
+
archiveFormat: 'tar-gzip',
|
|
16
|
+
sha256: '1a57bc98207fa1c0d04bf760699099e26f8383499bfd55b99c1b919a928a7230',
|
|
17
|
+
source: 'https://github.com/google/go-containerregistry/releases/download/v0.21.7/go-containerregistry_Linux_x86_64.tar.gz',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
function validatedReleases(releases) {
|
|
21
|
+
if (Object.keys(releases).length === 0 ||
|
|
22
|
+
Object.entries(releases).some(([platform, release]) => !/^linux-(?:arm64|x64)$/.test(platform) ||
|
|
23
|
+
!['binary', 'tar-gzip'].includes(release.archiveFormat) ||
|
|
24
|
+
!/^[a-f0-9]{64}$/.test(release.sha256) ||
|
|
25
|
+
!/^https:\/\/github\.com\/anchore\/syft\/releases\/download\/v\d+\.\d+\.\d+\/[A-Za-z0-9._-]+$/.test(release.source))) {
|
|
26
|
+
throw new Error('Container-release Syft assets must be exact official integrity-bound releases.');
|
|
27
|
+
}
|
|
28
|
+
return releases;
|
|
29
|
+
}
|
|
30
|
+
function validatedCraneReleases(releases) {
|
|
31
|
+
if (Object.keys(releases).length === 0 ||
|
|
32
|
+
Object.entries(releases).some(([platform, release]) => !/^linux-(?:arm64|x64)$/.test(platform) ||
|
|
33
|
+
release.archiveFormat !== 'tar-gzip' ||
|
|
34
|
+
!/^[a-f0-9]{64}$/.test(release.sha256) ||
|
|
35
|
+
!/^https:\/\/github\.com\/google\/go-containerregistry\/releases\/download\/v\d+\.\d+\.\d+\/go-containerregistry_Linux_(?:arm64|x86_64)\.tar\.gz$/.test(release.source))) {
|
|
36
|
+
throw new Error('Container-release Crane assets must be exact official integrity-bound releases.');
|
|
37
|
+
}
|
|
38
|
+
return releases;
|
|
39
|
+
}
|
|
40
|
+
/** Render the protected native-platform OCI build and assembly command. */
|
|
41
|
+
function renderContainerReleaseCommand(options = {}) {
|
|
42
|
+
const moduleSpecifier = options.moduleSpecifier ?? '@empire-builder-kit/nx/supply-chain';
|
|
43
|
+
const releases = validatedReleases(options.releases ?? generated_ecr_collector_command_js_1.SYFT_COLLECTOR_RELEASES);
|
|
44
|
+
const syftVersion = options.syftVersion ?? generated_ecr_collector_command_js_1.SYFT_VERSION;
|
|
45
|
+
const craneReleases = validatedCraneReleases(options.craneReleases ?? exports.CRANE_RELEASES);
|
|
46
|
+
const craneVersion = options.craneVersion ?? exports.CRANE_VERSION;
|
|
47
|
+
if (!/^(?:@empire-builder-kit\/nx\/supply-chain|file:\/\/\/[^'"\n]+)$/.test(moduleSpecifier) ||
|
|
48
|
+
!/^\d+\.\d+\.\d+$/.test(syftVersion) ||
|
|
49
|
+
!/^\d+\.\d+\.\d+$/.test(craneVersion)) {
|
|
50
|
+
throw new Error('The generated container release configuration is invalid.');
|
|
51
|
+
}
|
|
52
|
+
return `import { createHash } from 'node:crypto';
|
|
53
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
54
|
+
import {
|
|
55
|
+
chmodSync,
|
|
56
|
+
existsSync,
|
|
57
|
+
lstatSync,
|
|
58
|
+
mkdirSync,
|
|
59
|
+
mkdtempSync,
|
|
60
|
+
readFileSync,
|
|
61
|
+
realpathSync,
|
|
62
|
+
readdirSync,
|
|
63
|
+
rmSync,
|
|
64
|
+
writeFileSync,
|
|
65
|
+
} from 'node:fs';
|
|
66
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
67
|
+
import { tmpdir } from 'node:os';
|
|
68
|
+
import {
|
|
69
|
+
CONTAINER_NATIVE_LIFECYCLE_PROOFS,
|
|
70
|
+
CONTAINER_RELEASE_PLATFORMS,
|
|
71
|
+
containerReleaseRepositoryName,
|
|
72
|
+
createContainerNativeRunProof,
|
|
73
|
+
createContainerPlatformEvidence,
|
|
74
|
+
createContainerReleaseBinding,
|
|
75
|
+
resolveContainerReleaseArtifact,
|
|
76
|
+
supplyChainDigest,
|
|
77
|
+
validateContainerArtifactIdentity,
|
|
78
|
+
validateContainerReleaseBinding,
|
|
79
|
+
validateContainerNativeRunProof,
|
|
80
|
+
validateContainerPlatformEvidence,
|
|
81
|
+
validateReviewedReleaseCheckout,
|
|
82
|
+
} from '${moduleSpecifier}';
|
|
83
|
+
|
|
84
|
+
const SYFT_VERSION = ${JSON.stringify(syftVersion)};
|
|
85
|
+
const SYFT_RELEASES = Object.freeze(${JSON.stringify(releases)});
|
|
86
|
+
const CRANE_VERSION = ${JSON.stringify(craneVersion)};
|
|
87
|
+
const CRANE_RELEASES = Object.freeze(${JSON.stringify(craneReleases)});
|
|
88
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
89
|
+
const IMAGE_DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
90
|
+
const COMMIT = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
91
|
+
const REFERENCE = new RegExp(${JSON.stringify((0, safe_reference_1.safeReferencePatternSource)())});
|
|
92
|
+
const PACKAGE_NAME =
|
|
93
|
+
/^(?:@[A-Za-z0-9][A-Za-z0-9._-]*[/])?[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
94
|
+
const AWS_REGION_NAME =
|
|
95
|
+
/^(?=.{9,32}$)[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-\\d$/;
|
|
96
|
+
const MAX_TOOL_BYTES = 100 * 1024 * 1024;
|
|
97
|
+
const MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
98
|
+
const mode = process.argv[2];
|
|
99
|
+
const requestedPlatform = process.argv[3];
|
|
100
|
+
if (
|
|
101
|
+
!['platform', 'assemble', 'promote'].includes(mode) ||
|
|
102
|
+
(mode === 'platform' &&
|
|
103
|
+
!CONTAINER_RELEASE_PLATFORMS.includes(requestedPlatform)) ||
|
|
104
|
+
(['assemble', 'promote'].includes(mode) && requestedPlatform !== undefined)
|
|
105
|
+
) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
'Usage: container-release.mjs <platform linux/amd64|platform linux/arm64|assemble|promote>',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const digestBytes = (value) =>
|
|
112
|
+
createHash('sha256').update(value).digest('hex');
|
|
113
|
+
const repositoryRoot = realpathSync(
|
|
114
|
+
execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
}).trim(),
|
|
117
|
+
);
|
|
118
|
+
const sliceRoot = realpathSync(process.cwd());
|
|
119
|
+
const slice = basename(sliceRoot);
|
|
120
|
+
if (
|
|
121
|
+
relative(repositoryRoot, sliceRoot).split(sep).join('/') !==
|
|
122
|
+
'packages/' + slice
|
|
123
|
+
) {
|
|
124
|
+
throw new Error('Container release must run from one exact Product Slice root.');
|
|
125
|
+
}
|
|
126
|
+
const deploymentSourceCommit = process.env.EBK_SOURCE_COMMIT?.trim() ?? '';
|
|
127
|
+
const sourceCommit =
|
|
128
|
+
process.env.EBK_RELEASE_SOURCE_COMMIT?.trim() ?? deploymentSourceCommit;
|
|
129
|
+
const stage = process.env.EBK_STAGE?.trim() ?? '';
|
|
130
|
+
const accountId = process.env.AWS_EXPECTED_ACCOUNT_ID?.trim() ?? '';
|
|
131
|
+
const region = process.env.AWS_REGION?.trim() ?? '';
|
|
132
|
+
const runId = process.env.GITHUB_RUN_ID?.trim() ?? '';
|
|
133
|
+
const runAttempt = process.env.GITHUB_RUN_ATTEMPT?.trim() ?? '';
|
|
134
|
+
const workflow = process.env.GITHUB_WORKFLOW_REF?.trim() ?? '';
|
|
135
|
+
if (
|
|
136
|
+
!COMMIT.test(deploymentSourceCommit) ||
|
|
137
|
+
!COMMIT.test(sourceCommit) ||
|
|
138
|
+
!['development', 'staging', 'production'].includes(stage) ||
|
|
139
|
+
!/^\\d{12}$/.test(accountId) ||
|
|
140
|
+
!AWS_REGION_NAME.test(region) ||
|
|
141
|
+
!/^[1-9]\\d*$/.test(runId) ||
|
|
142
|
+
!/^[1-9]\\d*$/.test(runAttempt) ||
|
|
143
|
+
!REFERENCE.test(workflow)
|
|
144
|
+
) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'Container release requires one exact protected source, run, account, Region, and stage.',
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const requireReviewedCheckout = () => {
|
|
150
|
+
validateReviewedReleaseCheckout({
|
|
151
|
+
deploymentSourceCommit,
|
|
152
|
+
releaseSourceCommit: sourceCommit,
|
|
153
|
+
repositoryRoot,
|
|
154
|
+
});
|
|
155
|
+
const checkoutStatus = execFileSync(
|
|
156
|
+
'git',
|
|
157
|
+
['status', '--porcelain=v1', '--untracked-files=all'],
|
|
158
|
+
{ encoding: 'utf8' },
|
|
159
|
+
);
|
|
160
|
+
if (checkoutStatus.length !== 0) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
'Container release requires an entirely clean reviewed checkout.',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
requireReviewedCheckout();
|
|
167
|
+
const safePath = (candidate, label, options = {}) => {
|
|
168
|
+
if (
|
|
169
|
+
typeof candidate !== 'string' ||
|
|
170
|
+
candidate === '' ||
|
|
171
|
+
candidate !== candidate.trim() ||
|
|
172
|
+
isAbsolute(candidate)
|
|
173
|
+
) {
|
|
174
|
+
throw new Error(label + ' must be one relative repository path.');
|
|
175
|
+
}
|
|
176
|
+
const absolute = resolve(options.base ?? repositoryRoot, candidate);
|
|
177
|
+
const fromRoot = relative(repositoryRoot, absolute);
|
|
178
|
+
if (
|
|
179
|
+
fromRoot === '..' ||
|
|
180
|
+
fromRoot.startsWith('..' + sep) ||
|
|
181
|
+
isAbsolute(fromRoot)
|
|
182
|
+
) {
|
|
183
|
+
throw new Error(label + ' escapes the repository.');
|
|
184
|
+
}
|
|
185
|
+
let walked = repositoryRoot;
|
|
186
|
+
for (const segment of fromRoot.split(sep).filter(Boolean)) {
|
|
187
|
+
walked = join(walked, segment);
|
|
188
|
+
if (!existsSync(walked)) {
|
|
189
|
+
if (options.output) return absolute;
|
|
190
|
+
throw new Error(label + ' is absent.');
|
|
191
|
+
}
|
|
192
|
+
if (lstatSync(walked).isSymbolicLink()) {
|
|
193
|
+
throw new Error(label + ' may not traverse symbolic links.');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!options.output && realpathSync(absolute) !== absolute) {
|
|
197
|
+
throw new Error(label + ' aliases another path.');
|
|
198
|
+
}
|
|
199
|
+
return absolute;
|
|
200
|
+
};
|
|
201
|
+
const digestCandidatePaths = (paths, label) => {
|
|
202
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
203
|
+
throw new Error(label + ' must name at least one candidate path.');
|
|
204
|
+
}
|
|
205
|
+
const files = [];
|
|
206
|
+
const seen = new Set();
|
|
207
|
+
const visit = (absolute) => {
|
|
208
|
+
const metadata = lstatSync(absolute);
|
|
209
|
+
if (metadata.isSymbolicLink()) {
|
|
210
|
+
throw new Error(label + ' may not traverse symbolic links.');
|
|
211
|
+
}
|
|
212
|
+
if (metadata.isDirectory()) {
|
|
213
|
+
for (const child of readdirSync(absolute).sort()) {
|
|
214
|
+
visit(
|
|
215
|
+
safePath(
|
|
216
|
+
relative(repositoryRoot, join(absolute, child)),
|
|
217
|
+
label,
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!metadata.isFile()) {
|
|
224
|
+
throw new Error(label + ' may contain only regular files.');
|
|
225
|
+
}
|
|
226
|
+
const normalized = relative(repositoryRoot, absolute)
|
|
227
|
+
.split(sep)
|
|
228
|
+
.join('/');
|
|
229
|
+
if (seen.has(normalized)) {
|
|
230
|
+
throw new Error(label + ' contains overlapping candidate paths.');
|
|
231
|
+
}
|
|
232
|
+
seen.add(normalized);
|
|
233
|
+
files.push([normalized, absolute]);
|
|
234
|
+
};
|
|
235
|
+
for (const candidate of [...paths].sort()) {
|
|
236
|
+
visit(safePath(candidate, label));
|
|
237
|
+
}
|
|
238
|
+
const hash = createHash('sha256');
|
|
239
|
+
for (const [path, absolute] of files.sort(([left], [right]) =>
|
|
240
|
+
left < right ? -1 : left > right ? 1 : 0,
|
|
241
|
+
)) {
|
|
242
|
+
const content = readFileSync(absolute);
|
|
243
|
+
hash.update(path, 'utf8');
|
|
244
|
+
hash.update('\\0');
|
|
245
|
+
hash.update(String(content.byteLength), 'utf8');
|
|
246
|
+
hash.update('\\0');
|
|
247
|
+
hash.update(content);
|
|
248
|
+
hash.update('\\0');
|
|
249
|
+
}
|
|
250
|
+
return hash.digest('hex');
|
|
251
|
+
};
|
|
252
|
+
const readJson = (path, label) => {
|
|
253
|
+
const metadata = lstatSync(path);
|
|
254
|
+
if (
|
|
255
|
+
!metadata.isFile() ||
|
|
256
|
+
metadata.isSymbolicLink() ||
|
|
257
|
+
metadata.size <= 0 ||
|
|
258
|
+
metadata.size > MAX_OUTPUT_BYTES
|
|
259
|
+
) {
|
|
260
|
+
throw new Error(label + ' must be one bounded regular file.');
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
264
|
+
} catch {
|
|
265
|
+
throw new Error(label + ' is not valid JSON.');
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const immutableWrite = (path, value) => {
|
|
269
|
+
const serialized =
|
|
270
|
+
typeof value === 'string' ? value : JSON.stringify(value, null, 2) + '\\n';
|
|
271
|
+
mkdirSync(resolve(path, '..'), { recursive: true, mode: 0o700 });
|
|
272
|
+
try {
|
|
273
|
+
writeFileSync(path, serialized, {
|
|
274
|
+
encoding: 'utf8',
|
|
275
|
+
flag: 'wx',
|
|
276
|
+
mode: 0o600,
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (readFileSync(path, 'utf8') !== serialized) throw error;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const execute = (command, args, label, options = {}) => {
|
|
283
|
+
try {
|
|
284
|
+
return execFileSync(command, args, {
|
|
285
|
+
cwd: options.cwd ?? repositoryRoot,
|
|
286
|
+
encoding: options.encoding ?? 'utf8',
|
|
287
|
+
env: { ...process.env, ...(options.env ?? {}) },
|
|
288
|
+
input: options.input,
|
|
289
|
+
maxBuffer: options.maximumBytes ?? MAX_OUTPUT_BYTES,
|
|
290
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
label + ' failed without complete evidence: ' +
|
|
295
|
+
String(error?.stderr ?? error?.message ?? error).slice(0, 2_000),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
const awsJson = (args, label) => {
|
|
300
|
+
const raw = execute('aws', args, label);
|
|
301
|
+
try {
|
|
302
|
+
return JSON.parse(raw);
|
|
303
|
+
} catch {
|
|
304
|
+
throw new Error(label + ' returned invalid JSON.');
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const describeRepository = (repositoryName) => {
|
|
308
|
+
const result = spawnSync(
|
|
309
|
+
'aws',
|
|
310
|
+
[
|
|
311
|
+
'ecr',
|
|
312
|
+
'describe-repositories',
|
|
313
|
+
'--repository-names',
|
|
314
|
+
repositoryName,
|
|
315
|
+
'--region',
|
|
316
|
+
region,
|
|
317
|
+
'--output',
|
|
318
|
+
'json',
|
|
319
|
+
],
|
|
320
|
+
{
|
|
321
|
+
cwd: repositoryRoot,
|
|
322
|
+
encoding: 'utf8',
|
|
323
|
+
env: process.env,
|
|
324
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
325
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
if (result.status === 0) {
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(result.stdout);
|
|
331
|
+
} catch {
|
|
332
|
+
throw new Error('ECR repository lookup returned invalid JSON.');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const error = String(result.stderr ?? '');
|
|
336
|
+
if (
|
|
337
|
+
!error.startsWith(
|
|
338
|
+
'An error occurred (RepositoryNotFoundException) when calling the DescribeRepositories operation:',
|
|
339
|
+
)
|
|
340
|
+
) {
|
|
341
|
+
throw new Error('ECR repository lookup failed outside the create-if-absent path.');
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
};
|
|
345
|
+
const caller = awsJson(
|
|
346
|
+
['sts', 'get-caller-identity', '--output', 'json'],
|
|
347
|
+
'AWS caller identity',
|
|
348
|
+
);
|
|
349
|
+
if (caller?.Account !== accountId) {
|
|
350
|
+
throw new Error('Container release AWS caller differs from the target account.');
|
|
351
|
+
}
|
|
352
|
+
const controls = readJson(
|
|
353
|
+
safePath('operations/release.json', 'Release controls', { base: sliceRoot }),
|
|
354
|
+
'Release controls',
|
|
355
|
+
);
|
|
356
|
+
const candidateBundle = safePath(
|
|
357
|
+
'.ebk/evidence/release-candidate/packages/' + slice +
|
|
358
|
+
'/release-artifacts.json',
|
|
359
|
+
'Release artifact evidence',
|
|
360
|
+
);
|
|
361
|
+
const artifactEvidence = readJson(candidateBundle, 'Release artifact evidence');
|
|
362
|
+
if (
|
|
363
|
+
controls?.schemaVersion !== 1 ||
|
|
364
|
+
!Array.isArray(controls.artifactInventory) ||
|
|
365
|
+
controls.artifactInventory.length === 0 ||
|
|
366
|
+
artifactEvidence?.kind !== 'ebk-release-artifact-evidence' ||
|
|
367
|
+
artifactEvidence.schemaVersion !== 1 ||
|
|
368
|
+
artifactEvidence.sourceCommit !== sourceCommit ||
|
|
369
|
+
!Array.isArray(artifactEvidence.artifacts) ||
|
|
370
|
+
artifactEvidence.inventoryDigest !==
|
|
371
|
+
digestBytes(Buffer.from(JSON.stringify(controls.artifactInventory), 'utf8'))
|
|
372
|
+
) {
|
|
373
|
+
throw new Error('Container release controls and candidate evidence are stale.');
|
|
374
|
+
}
|
|
375
|
+
const capturedByName = new Map(
|
|
376
|
+
artifactEvidence.artifacts.map((artifact) => [artifact?.name, artifact]),
|
|
377
|
+
);
|
|
378
|
+
const declaredContainers = controls.artifactInventory
|
|
379
|
+
.filter(
|
|
380
|
+
(artifact) =>
|
|
381
|
+
artifact?.finalArtifact?.support === 'supported' &&
|
|
382
|
+
artifact.finalArtifact.family === 'oci-image' &&
|
|
383
|
+
artifact.finalArtifact.imageConsumption === 'digest-qualified',
|
|
384
|
+
)
|
|
385
|
+
.sort((left, right) =>
|
|
386
|
+
String(left.id) < String(right.id)
|
|
387
|
+
? -1
|
|
388
|
+
: String(left.id) > String(right.id)
|
|
389
|
+
? 1
|
|
390
|
+
: 0,
|
|
391
|
+
);
|
|
392
|
+
if (declaredContainers.length === 0) {
|
|
393
|
+
throw new Error('Release controls contain no digest-qualified OCI artifacts.');
|
|
394
|
+
}
|
|
395
|
+
const releaseContainerInputs = declaredContainers.map((declared, index) => {
|
|
396
|
+
const identity = validateContainerArtifactIdentity(
|
|
397
|
+
declared,
|
|
398
|
+
slice,
|
|
399
|
+
'artifactInventory[' + String(index) + ']',
|
|
400
|
+
);
|
|
401
|
+
const captured = capturedByName.get(declared.name);
|
|
402
|
+
const candidateArtifactDigest =
|
|
403
|
+
captured?.classification === 'stage-neutral'
|
|
404
|
+
? captured.digest
|
|
405
|
+
: captured?.candidateDigest;
|
|
406
|
+
const candidate = declared.candidate;
|
|
407
|
+
const materialDigest = digestCandidatePaths(
|
|
408
|
+
candidate.materialPaths,
|
|
409
|
+
'OCI artifact candidate material',
|
|
410
|
+
);
|
|
411
|
+
const buildRecipeDigest = digestCandidatePaths(
|
|
412
|
+
candidate.recipePaths,
|
|
413
|
+
'OCI artifact candidate recipe',
|
|
414
|
+
);
|
|
415
|
+
const sourceInputsDigest = digestCandidatePaths(
|
|
416
|
+
candidate.sourceInputPaths,
|
|
417
|
+
'OCI artifact source inputs',
|
|
418
|
+
);
|
|
419
|
+
const container = declared.finalArtifact.container;
|
|
420
|
+
if (
|
|
421
|
+
!declared ||
|
|
422
|
+
!REFERENCE.test(declared.id ?? '') ||
|
|
423
|
+
!REFERENCE.test(declared.name ?? '') ||
|
|
424
|
+
!['job', 'service'].includes(declared.workload) ||
|
|
425
|
+
!captured ||
|
|
426
|
+
!SHA256.test(candidateArtifactDigest ?? '') ||
|
|
427
|
+
!container ||
|
|
428
|
+
Object.keys(container).sort().join('\\0') !==
|
|
429
|
+
[
|
|
430
|
+
'context',
|
|
431
|
+
'dockerfile',
|
|
432
|
+
'nativeLifecycleProof',
|
|
433
|
+
'proofEntrypoints',
|
|
434
|
+
'proofImports',
|
|
435
|
+
'role',
|
|
436
|
+
].join('\\0') ||
|
|
437
|
+
typeof container.context !== 'string' ||
|
|
438
|
+
typeof container.dockerfile !== 'string' ||
|
|
439
|
+
!['job', 'migration', 'retirement', 'service'].includes(container.role) ||
|
|
440
|
+
!CONTAINER_NATIVE_LIFECYCLE_PROOFS.includes(
|
|
441
|
+
container.nativeLifecycleProof,
|
|
442
|
+
) ||
|
|
443
|
+
!(
|
|
444
|
+
(container.nativeLifecycleProof === 'service-default-health' &&
|
|
445
|
+
container.role === 'service') ||
|
|
446
|
+
(container.nativeLifecycleProof === 'job-default-success' &&
|
|
447
|
+
container.role === 'job' &&
|
|
448
|
+
!container.proofEntrypoints?.includes(
|
|
449
|
+
'dist/jobs/data-retention/src/run.js',
|
|
450
|
+
)) ||
|
|
451
|
+
(container.nativeLifecycleProof === 'retention-default-refusal' &&
|
|
452
|
+
container.role === 'job' &&
|
|
453
|
+
container.proofEntrypoints?.includes(
|
|
454
|
+
'dist/jobs/data-retention/src/run.js',
|
|
455
|
+
)) ||
|
|
456
|
+
(container.nativeLifecycleProof === 'migration-explicit-refusal' &&
|
|
457
|
+
container.role === 'migration' &&
|
|
458
|
+
container.proofEntrypoints?.includes('jobs/migrate-database.mjs')) ||
|
|
459
|
+
(container.nativeLifecycleProof === 'retirement-explicit-refusal' &&
|
|
460
|
+
container.role === 'retirement' &&
|
|
461
|
+
container.proofEntrypoints?.includes('jobs/retire-database.mjs'))
|
|
462
|
+
) ||
|
|
463
|
+
!Array.isArray(container.proofEntrypoints) ||
|
|
464
|
+
container.proofEntrypoints.length === 0 ||
|
|
465
|
+
container.proofEntrypoints.length > 16 ||
|
|
466
|
+
container.proofEntrypoints.some(
|
|
467
|
+
(entrypoint) =>
|
|
468
|
+
typeof entrypoint !== 'string' ||
|
|
469
|
+
!/^(?!\\/)(?!.*(?:^|\\/)\\.\\.(?:\\/|$))[A-Za-z0-9._/-]{1,256}$/.test(
|
|
470
|
+
entrypoint,
|
|
471
|
+
),
|
|
472
|
+
) ||
|
|
473
|
+
!Array.isArray(container.proofImports) ||
|
|
474
|
+
container.proofImports.length === 0 ||
|
|
475
|
+
container.proofImports.length > 32 ||
|
|
476
|
+
container.proofImports.some(
|
|
477
|
+
(specifier) =>
|
|
478
|
+
typeof specifier !== 'string' ||
|
|
479
|
+
!/^(?:@[a-z0-9._-]+\\/[a-z0-9._/-]+|[a-z0-9][a-z0-9._/-]*)$/.test(
|
|
480
|
+
specifier,
|
|
481
|
+
),
|
|
482
|
+
)
|
|
483
|
+
) {
|
|
484
|
+
throw new Error(
|
|
485
|
+
'OCI artifact ' + index + ' does not match the exact container contract.',
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (
|
|
489
|
+
candidateArtifactDigest !== materialDigest ||
|
|
490
|
+
captured.buildRecipeDigest !== buildRecipeDigest ||
|
|
491
|
+
captured.sourceInputsDigest !== sourceInputsDigest ||
|
|
492
|
+
captured.workload !== declared.workload ||
|
|
493
|
+
JSON.stringify(captured.stages) !==
|
|
494
|
+
JSON.stringify(['production', 'staging'])
|
|
495
|
+
) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
'OCI artifact candidate evidence does not bind its exact material, recipe, source, and workload authority.',
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
const context = safePath(container.context, 'Container context');
|
|
501
|
+
const dockerfile = safePath(container.dockerfile, 'Container Dockerfile');
|
|
502
|
+
if (!lstatSync(context).isDirectory() || !lstatSync(dockerfile).isFile()) {
|
|
503
|
+
throw new Error('Container context or Dockerfile has the wrong file type.');
|
|
504
|
+
}
|
|
505
|
+
const packageManifestPath = safePath(
|
|
506
|
+
identity.packageManifest,
|
|
507
|
+
'Container package manifest',
|
|
508
|
+
);
|
|
509
|
+
const packageManifest = readJson(
|
|
510
|
+
packageManifestPath,
|
|
511
|
+
'Container package manifest',
|
|
512
|
+
);
|
|
513
|
+
if (
|
|
514
|
+
!PACKAGE_NAME.test(packageManifest?.name ?? '') ||
|
|
515
|
+
typeof packageManifest?.version !== 'string' ||
|
|
516
|
+
!/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/.test(packageManifest.version)
|
|
517
|
+
) {
|
|
518
|
+
throw new Error('Container package manifest has no exact package identity.');
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
buildRecipeDigest,
|
|
522
|
+
candidateArtifactDigest,
|
|
523
|
+
context,
|
|
524
|
+
declared,
|
|
525
|
+
dockerfile,
|
|
526
|
+
materialDigest,
|
|
527
|
+
packageManifestPath,
|
|
528
|
+
packageName: packageManifest.name,
|
|
529
|
+
nativeLifecycleProof: container.nativeLifecycleProof,
|
|
530
|
+
proofEntrypoints: [...new Set(container.proofEntrypoints)].sort(),
|
|
531
|
+
proofImports: [...new Set(container.proofImports)].sort(),
|
|
532
|
+
sourceInputsDigest,
|
|
533
|
+
};
|
|
534
|
+
});
|
|
535
|
+
if (
|
|
536
|
+
capturedByName.size !== artifactEvidence.artifacts.length ||
|
|
537
|
+
releaseContainerInputs.some(
|
|
538
|
+
({ declared }) =>
|
|
539
|
+
artifactEvidence.artifacts.filter(({ name }) => name === declared.name)
|
|
540
|
+
.length !== 1,
|
|
541
|
+
)
|
|
542
|
+
) {
|
|
543
|
+
throw new Error('Container release candidate artifact identities are ambiguous.');
|
|
544
|
+
}
|
|
545
|
+
const requireCandidateAuthority = (input) => {
|
|
546
|
+
if (
|
|
547
|
+
digestCandidatePaths(
|
|
548
|
+
input.declared.candidate.materialPaths,
|
|
549
|
+
'OCI artifact candidate material',
|
|
550
|
+
) !== input.materialDigest ||
|
|
551
|
+
digestCandidatePaths(
|
|
552
|
+
input.declared.candidate.recipePaths,
|
|
553
|
+
'OCI artifact candidate recipe',
|
|
554
|
+
) !== input.buildRecipeDigest ||
|
|
555
|
+
digestCandidatePaths(
|
|
556
|
+
input.declared.candidate.sourceInputPaths,
|
|
557
|
+
'OCI artifact source inputs',
|
|
558
|
+
) !== input.sourceInputsDigest
|
|
559
|
+
) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
'Container release source or candidate bytes changed after authority validation.',
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const retirementArtifactId = slice + '.job.database-retirement';
|
|
566
|
+
const containerInputs =
|
|
567
|
+
stage === 'development'
|
|
568
|
+
? releaseContainerInputs.filter(
|
|
569
|
+
({ declared }) => declared.id === retirementArtifactId,
|
|
570
|
+
)
|
|
571
|
+
: releaseContainerInputs;
|
|
572
|
+
if (
|
|
573
|
+
stage === 'development' &&
|
|
574
|
+
(mode === 'promote' ||
|
|
575
|
+
containerInputs.length !== 1 ||
|
|
576
|
+
containerInputs[0]?.declared.finalArtifact.container.role !== 'retirement' ||
|
|
577
|
+
containerInputs[0]?.nativeLifecycleProof !== 'retirement-explicit-refusal')
|
|
578
|
+
) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
'Development container release is reserved for one exact database-retirement rehearsal artifact.',
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const registrySuffix = region.startsWith('cn-') ? '.com.cn' : '.com';
|
|
584
|
+
const registry =
|
|
585
|
+
accountId + '.dkr.ecr.' + region + '.amazonaws' + registrySuffix;
|
|
586
|
+
const repositoryForStage = (artifactId, targetStage) =>
|
|
587
|
+
containerReleaseRepositoryName({ artifactId, slice, stage: targetStage });
|
|
588
|
+
const repositoryFor = (artifactId) => repositoryForStage(artifactId, stage);
|
|
589
|
+
const ensureRepository = (repositoryName) => {
|
|
590
|
+
let response = describeRepository(repositoryName);
|
|
591
|
+
if (!response) {
|
|
592
|
+
try {
|
|
593
|
+
const created = awsJson(
|
|
594
|
+
[
|
|
595
|
+
'ecr',
|
|
596
|
+
'create-repository',
|
|
597
|
+
'--repository-name',
|
|
598
|
+
repositoryName,
|
|
599
|
+
'--image-tag-mutability',
|
|
600
|
+
'IMMUTABLE',
|
|
601
|
+
'--encryption-configuration',
|
|
602
|
+
'encryptionType=AES256',
|
|
603
|
+
'--image-scanning-configuration',
|
|
604
|
+
'scanOnPush=true',
|
|
605
|
+
'--region',
|
|
606
|
+
region,
|
|
607
|
+
'--output',
|
|
608
|
+
'json',
|
|
609
|
+
],
|
|
610
|
+
'ECR repository creation',
|
|
611
|
+
);
|
|
612
|
+
response = { repositories: [created.repository] };
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (!String(error).includes('RepositoryAlreadyExistsException')) {
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
response = describeRepository(repositoryName);
|
|
618
|
+
if (!response) {
|
|
619
|
+
throw new Error('Raced ECR repository creation did not converge.');
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const repositories = response?.repositories;
|
|
624
|
+
if (
|
|
625
|
+
!Array.isArray(repositories) ||
|
|
626
|
+
repositories.length !== 1 ||
|
|
627
|
+
repositories[0]?.registryId !== accountId ||
|
|
628
|
+
repositories[0]?.repositoryName !== repositoryName ||
|
|
629
|
+
repositories[0]?.imageTagMutability !== 'IMMUTABLE' ||
|
|
630
|
+
repositories[0]?.imageScanningConfiguration?.scanOnPush !== true ||
|
|
631
|
+
repositories[0]?.encryptionConfiguration?.encryptionType !== 'AES256'
|
|
632
|
+
) {
|
|
633
|
+
throw new Error('ECR repository does not match the protected immutable contract.');
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
const login = () => {
|
|
637
|
+
const password = execute(
|
|
638
|
+
'aws',
|
|
639
|
+
['ecr', 'get-login-password', '--region', region],
|
|
640
|
+
'ECR login token',
|
|
641
|
+
);
|
|
642
|
+
const result = spawnSync(
|
|
643
|
+
'docker',
|
|
644
|
+
['login', '--username', 'AWS', '--password-stdin', registry],
|
|
645
|
+
{
|
|
646
|
+
cwd: repositoryRoot,
|
|
647
|
+
encoding: 'utf8',
|
|
648
|
+
env: process.env,
|
|
649
|
+
input: password,
|
|
650
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
651
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
652
|
+
},
|
|
653
|
+
);
|
|
654
|
+
if (result.status !== 0) {
|
|
655
|
+
throw new Error('Docker ECR login failed without exposing its credential.');
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
const exactImageByTag = (repositoryName, tag) => {
|
|
659
|
+
const response = awsJson(
|
|
660
|
+
[
|
|
661
|
+
'ecr',
|
|
662
|
+
'describe-images',
|
|
663
|
+
'--registry-id',
|
|
664
|
+
accountId,
|
|
665
|
+
'--repository-name',
|
|
666
|
+
repositoryName,
|
|
667
|
+
'--image-ids',
|
|
668
|
+
'imageTag=' + tag,
|
|
669
|
+
'--region',
|
|
670
|
+
region,
|
|
671
|
+
'--output',
|
|
672
|
+
'json',
|
|
673
|
+
],
|
|
674
|
+
'Exact ECR image lookup',
|
|
675
|
+
);
|
|
676
|
+
if (
|
|
677
|
+
!Array.isArray(response?.imageDetails) ||
|
|
678
|
+
response.imageDetails.length !== 1 ||
|
|
679
|
+
response.imageDetails[0]?.registryId !== accountId ||
|
|
680
|
+
response.imageDetails[0]?.repositoryName !== repositoryName ||
|
|
681
|
+
!IMAGE_DIGEST.test(response.imageDetails[0]?.imageDigest ?? '')
|
|
682
|
+
) {
|
|
683
|
+
throw new Error('Exact ECR image tag is absent or ambiguous.');
|
|
684
|
+
}
|
|
685
|
+
return response.imageDetails[0].imageDigest;
|
|
686
|
+
};
|
|
687
|
+
const acquireSyft = async () => {
|
|
688
|
+
const platformKey = process.platform + '-' + process.arch;
|
|
689
|
+
const release = SYFT_RELEASES[platformKey];
|
|
690
|
+
if (!release) {
|
|
691
|
+
throw new Error('No reviewed Syft release exists for ' + platformKey + '.');
|
|
692
|
+
}
|
|
693
|
+
const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-syft-'));
|
|
694
|
+
const supplied = process.env.EBK_SYFT_ARCHIVE_PATH?.trim();
|
|
695
|
+
const archive = supplied
|
|
696
|
+
? safePath(relative(repositoryRoot, resolve(repositoryRoot, supplied)), 'Supplied Syft archive')
|
|
697
|
+
: join(temporary, 'syft-archive');
|
|
698
|
+
if (!supplied) {
|
|
699
|
+
const response = await fetch(release.source, {
|
|
700
|
+
redirect: 'follow',
|
|
701
|
+
signal: AbortSignal.timeout(120_000),
|
|
702
|
+
});
|
|
703
|
+
if (
|
|
704
|
+
!response.ok ||
|
|
705
|
+
Number(response.headers.get('content-length') ?? 0) > MAX_TOOL_BYTES
|
|
706
|
+
) {
|
|
707
|
+
throw new Error('Pinned Syft archive download failed or was oversized.');
|
|
708
|
+
}
|
|
709
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
710
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_TOOL_BYTES) {
|
|
711
|
+
throw new Error('Pinned Syft archive is empty or oversized.');
|
|
712
|
+
}
|
|
713
|
+
writeFileSync(archive, bytes, { mode: 0o600 });
|
|
714
|
+
}
|
|
715
|
+
const bytes = readFileSync(archive);
|
|
716
|
+
if (
|
|
717
|
+
bytes.byteLength === 0 ||
|
|
718
|
+
bytes.byteLength > MAX_TOOL_BYTES ||
|
|
719
|
+
digestBytes(bytes) !== release.sha256
|
|
720
|
+
) {
|
|
721
|
+
throw new Error('Pinned Syft archive integrity does not match policy.');
|
|
722
|
+
}
|
|
723
|
+
const path = join(temporary, 'syft');
|
|
724
|
+
if (release.archiveFormat === 'binary') {
|
|
725
|
+
writeFileSync(path, bytes, { mode: 0o700 });
|
|
726
|
+
} else {
|
|
727
|
+
execute('tar', ['-xzf', archive, '-C', temporary, 'syft'], 'Syft extraction');
|
|
728
|
+
chmodSync(path, 0o700);
|
|
729
|
+
}
|
|
730
|
+
const version = String(execute(path, ['version'], 'Syft version'));
|
|
731
|
+
if (
|
|
732
|
+
!new RegExp('(?:Version|version):?\\\\s*' + SYFT_VERSION.replaceAll('.', '\\\\.')).test(
|
|
733
|
+
version,
|
|
734
|
+
)
|
|
735
|
+
) {
|
|
736
|
+
throw new Error('Syft runtime version differs from reviewed policy.');
|
|
737
|
+
}
|
|
738
|
+
return { path, temporary };
|
|
739
|
+
};
|
|
740
|
+
const acquireCrane = async () => {
|
|
741
|
+
const platformKey = process.platform + '-' + process.arch;
|
|
742
|
+
const release = CRANE_RELEASES[platformKey];
|
|
743
|
+
if (!release) {
|
|
744
|
+
throw new Error('No reviewed Crane release exists for ' + platformKey + '.');
|
|
745
|
+
}
|
|
746
|
+
const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-crane-'));
|
|
747
|
+
const supplied = process.env.EBK_CRANE_ARCHIVE_PATH?.trim();
|
|
748
|
+
const archive = supplied
|
|
749
|
+
? safePath(
|
|
750
|
+
relative(repositoryRoot, resolve(repositoryRoot, supplied)),
|
|
751
|
+
'Supplied Crane archive',
|
|
752
|
+
)
|
|
753
|
+
: join(temporary, 'crane-archive.tar.gz');
|
|
754
|
+
if (!supplied) {
|
|
755
|
+
const response = await fetch(release.source, {
|
|
756
|
+
redirect: 'follow',
|
|
757
|
+
signal: AbortSignal.timeout(120_000),
|
|
758
|
+
});
|
|
759
|
+
if (
|
|
760
|
+
!response.ok ||
|
|
761
|
+
Number(response.headers.get('content-length') ?? 0) > MAX_TOOL_BYTES
|
|
762
|
+
) {
|
|
763
|
+
throw new Error('Pinned Crane archive download failed or was oversized.');
|
|
764
|
+
}
|
|
765
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
766
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_TOOL_BYTES) {
|
|
767
|
+
throw new Error('Pinned Crane archive is empty or oversized.');
|
|
768
|
+
}
|
|
769
|
+
writeFileSync(archive, bytes, { mode: 0o600 });
|
|
770
|
+
}
|
|
771
|
+
const bytes = readFileSync(archive);
|
|
772
|
+
if (
|
|
773
|
+
bytes.byteLength === 0 ||
|
|
774
|
+
bytes.byteLength > MAX_TOOL_BYTES ||
|
|
775
|
+
digestBytes(bytes) !== release.sha256
|
|
776
|
+
) {
|
|
777
|
+
throw new Error('Pinned Crane archive integrity does not match policy.');
|
|
778
|
+
}
|
|
779
|
+
execute('tar', ['-xzf', archive, '-C', temporary, 'crane'], 'Crane extraction');
|
|
780
|
+
const path = join(temporary, 'crane');
|
|
781
|
+
chmodSync(path, 0o700);
|
|
782
|
+
const version = String(execute(path, ['version'], 'Crane version')).trim();
|
|
783
|
+
if (
|
|
784
|
+
!new RegExp('(?:^|\\s|v)' + CRANE_VERSION.replaceAll('.', '\\.') + '(?:$|\\s)').test(
|
|
785
|
+
version,
|
|
786
|
+
)
|
|
787
|
+
) {
|
|
788
|
+
throw new Error('Crane runtime version differs from reviewed policy.');
|
|
789
|
+
}
|
|
790
|
+
return { path, temporary };
|
|
791
|
+
};
|
|
792
|
+
const parseCycloneDx = (value, label) => {
|
|
793
|
+
if (
|
|
794
|
+
value?.bomFormat !== 'CycloneDX' ||
|
|
795
|
+
value.specVersion !== '1.6' ||
|
|
796
|
+
value.version !== 1 ||
|
|
797
|
+
!/^urn:uuid:[a-fA-F0-9-]{36}$/.test(value.serialNumber ?? '') ||
|
|
798
|
+
!Array.isArray(value.components) ||
|
|
799
|
+
value.components.length === 0 ||
|
|
800
|
+
!Array.isArray(value.dependencies) ||
|
|
801
|
+
!value.metadata ||
|
|
802
|
+
typeof value.metadata !== 'object'
|
|
803
|
+
) {
|
|
804
|
+
throw new Error(label + ' is not complete CycloneDX 1.6.');
|
|
805
|
+
}
|
|
806
|
+
return value;
|
|
807
|
+
};
|
|
808
|
+
const componentSetFor = (sbom) =>
|
|
809
|
+
sbom.components
|
|
810
|
+
.map((component) => ({
|
|
811
|
+
name: component?.name,
|
|
812
|
+
purl: component?.purl ?? '',
|
|
813
|
+
type: component?.type,
|
|
814
|
+
version: component?.version,
|
|
815
|
+
}))
|
|
816
|
+
.sort((left, right) =>
|
|
817
|
+
JSON.stringify(left) < JSON.stringify(right)
|
|
818
|
+
? -1
|
|
819
|
+
: JSON.stringify(left) > JSON.stringify(right)
|
|
820
|
+
? 1
|
|
821
|
+
: 0,
|
|
822
|
+
);
|
|
823
|
+
const npmClosureFor = (syftDocument, options = {}) => {
|
|
824
|
+
if (!Array.isArray(syftDocument?.artifacts)) {
|
|
825
|
+
throw new Error('Syft npm closure inventory is malformed.');
|
|
826
|
+
}
|
|
827
|
+
const packages = syftDocument.artifacts.flatMap((artifact) => {
|
|
828
|
+
if (artifact?.type !== 'npm') return [];
|
|
829
|
+
const locations = Array.isArray(artifact.locations)
|
|
830
|
+
? artifact.locations.map(({ path }) => {
|
|
831
|
+
const value = String(path ?? '');
|
|
832
|
+
return value.startsWith('./')
|
|
833
|
+
? value.slice(2)
|
|
834
|
+
: value.startsWith('/')
|
|
835
|
+
? value.slice(1)
|
|
836
|
+
: value;
|
|
837
|
+
})
|
|
838
|
+
: [];
|
|
839
|
+
if (
|
|
840
|
+
options.applicationOnly &&
|
|
841
|
+
!locations.some(
|
|
842
|
+
(path) =>
|
|
843
|
+
path === 'app/package.json' ||
|
|
844
|
+
path.startsWith('app/node_modules/'),
|
|
845
|
+
)
|
|
846
|
+
) {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
if (
|
|
850
|
+
!PACKAGE_NAME.test(artifact.name ?? '') ||
|
|
851
|
+
typeof artifact.version !== 'string' ||
|
|
852
|
+
artifact.version.length === 0 ||
|
|
853
|
+
artifact.version.length > 128
|
|
854
|
+
) {
|
|
855
|
+
throw new Error('Syft npm closure contains an invalid package identity.');
|
|
856
|
+
}
|
|
857
|
+
return [{ name: artifact.name, version: artifact.version }];
|
|
858
|
+
});
|
|
859
|
+
const unique = [
|
|
860
|
+
...new Map(
|
|
861
|
+
packages.map((entry) => [entry.name + '@' + entry.version, entry]),
|
|
862
|
+
).values(),
|
|
863
|
+
].sort(
|
|
864
|
+
(left, right) =>
|
|
865
|
+
(left.name < right.name ? -1 : left.name > right.name ? 1 : 0) ||
|
|
866
|
+
(left.version < right.version
|
|
867
|
+
? -1
|
|
868
|
+
: left.version > right.version
|
|
869
|
+
? 1
|
|
870
|
+
: 0),
|
|
871
|
+
);
|
|
872
|
+
if (unique.length === 0) {
|
|
873
|
+
throw new Error('Syft npm closure inventory is empty.');
|
|
874
|
+
}
|
|
875
|
+
return unique;
|
|
876
|
+
};
|
|
877
|
+
const deriveExpectedProductionClosure = (syft, input) => {
|
|
878
|
+
const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-closure-'));
|
|
879
|
+
const deployed = join(temporary, 'deployed');
|
|
880
|
+
try {
|
|
881
|
+
execute(
|
|
882
|
+
'pnpm',
|
|
883
|
+
[
|
|
884
|
+
'--filter',
|
|
885
|
+
input.packageName,
|
|
886
|
+
'deploy',
|
|
887
|
+
'--prod',
|
|
888
|
+
'--offline',
|
|
889
|
+
'--frozen-lockfile',
|
|
890
|
+
deployed,
|
|
891
|
+
],
|
|
892
|
+
'Independent production closure deployment',
|
|
893
|
+
{ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' } },
|
|
894
|
+
);
|
|
895
|
+
const raw = String(
|
|
896
|
+
execute(
|
|
897
|
+
syft.path,
|
|
898
|
+
['scan', 'dir:' + deployed, '--output', 'syft-json'],
|
|
899
|
+
'Independent production closure inventory',
|
|
900
|
+
{ env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' } },
|
|
901
|
+
),
|
|
902
|
+
);
|
|
903
|
+
let inventory;
|
|
904
|
+
try {
|
|
905
|
+
inventory = JSON.parse(raw);
|
|
906
|
+
} catch {
|
|
907
|
+
throw new Error('Independent production closure inventory is invalid JSON.');
|
|
908
|
+
}
|
|
909
|
+
const packages = npmClosureFor(inventory);
|
|
910
|
+
if (
|
|
911
|
+
!packages.some(
|
|
912
|
+
({ name, version }) =>
|
|
913
|
+
name === input.packageName &&
|
|
914
|
+
version === readJson(
|
|
915
|
+
input.packageManifestPath,
|
|
916
|
+
'Container package manifest',
|
|
917
|
+
).version,
|
|
918
|
+
)
|
|
919
|
+
) {
|
|
920
|
+
throw new Error('Independent production closure omits the workload package.');
|
|
921
|
+
}
|
|
922
|
+
return { digest: supplyChainDigest(packages), packages };
|
|
923
|
+
} finally {
|
|
924
|
+
rmSync(temporary, { force: true, recursive: true });
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
const validateRetainedSbom = (entry, label) => {
|
|
928
|
+
const path = safePath(entry.sbom.path, label, { base: repositoryRoot });
|
|
929
|
+
const bytes = readFileSync(path);
|
|
930
|
+
if (digestBytes(bytes) !== entry.sbom.sha256) {
|
|
931
|
+
throw new Error(label + ' bytes differ from retained evidence.');
|
|
932
|
+
}
|
|
933
|
+
let sbom;
|
|
934
|
+
try {
|
|
935
|
+
sbom = parseCycloneDx(JSON.parse(bytes.toString('utf8')), label);
|
|
936
|
+
} catch (error) {
|
|
937
|
+
if (error instanceof SyntaxError) {
|
|
938
|
+
throw new Error(label + ' is invalid JSON.');
|
|
939
|
+
}
|
|
940
|
+
throw error;
|
|
941
|
+
}
|
|
942
|
+
if (
|
|
943
|
+
sbom.serialNumber.toLowerCase() !== entry.sbom.serialNumber ||
|
|
944
|
+
supplyChainDigest(componentSetFor(sbom)) !== entry.sbom.componentSetDigest
|
|
945
|
+
) {
|
|
946
|
+
throw new Error(label + ' component identity differs from retained evidence.');
|
|
947
|
+
}
|
|
948
|
+
return sbom;
|
|
949
|
+
};
|
|
950
|
+
const validateRetainedRunProof = (entry, input, label) => {
|
|
951
|
+
const path = safePath(entry.runProof.path, label, { base: repositoryRoot });
|
|
952
|
+
const bytes = readFileSync(path);
|
|
953
|
+
if (digestBytes(bytes) !== entry.runProof.sha256) {
|
|
954
|
+
throw new Error(label + ' bytes differ from retained evidence.');
|
|
955
|
+
}
|
|
956
|
+
const proof = validateContainerNativeRunProof(readJson(path, label));
|
|
957
|
+
if (
|
|
958
|
+
proof.materialDigest !== entry.runProof.materialDigest ||
|
|
959
|
+
proof.accountId !== entry.accountId ||
|
|
960
|
+
proof.artifactId !== entry.artifactId ||
|
|
961
|
+
proof.artifactName !== entry.artifactName ||
|
|
962
|
+
proof.candidateArtifactDigest !== entry.candidateArtifactDigest ||
|
|
963
|
+
proof.imageDigest !== entry.imageDigest ||
|
|
964
|
+
proof.platform !== entry.platform ||
|
|
965
|
+
proof.region !== entry.region ||
|
|
966
|
+
proof.repositoryName !== entry.repositoryName ||
|
|
967
|
+
proof.slice !== entry.slice ||
|
|
968
|
+
proof.sourceCommit !== entry.sourceCommit ||
|
|
969
|
+
proof.stage !== entry.stage ||
|
|
970
|
+
proof.nativeLifecycleProof !== input.nativeLifecycleProof ||
|
|
971
|
+
proof.role !== input.declared.finalArtifact.container.role ||
|
|
972
|
+
JSON.stringify(proof.entrypoints) !==
|
|
973
|
+
JSON.stringify(input.proofEntrypoints) ||
|
|
974
|
+
JSON.stringify(proof.imports) !== JSON.stringify(input.proofImports)
|
|
975
|
+
) {
|
|
976
|
+
throw new Error(label + ' is stale, cross-paired, or incomplete.');
|
|
977
|
+
}
|
|
978
|
+
return proof;
|
|
979
|
+
};
|
|
980
|
+
const runNativeLifecycle = async (input, imageReference, platform) => {
|
|
981
|
+
const architecture = platform.split('/')[1];
|
|
982
|
+
const structuralProgram =
|
|
983
|
+
'const {spawnSync}=await import("node:child_process");' +
|
|
984
|
+
'const imports=' + JSON.stringify(input.proofImports) + ';' +
|
|
985
|
+
'for(const value of imports)await import(value);' +
|
|
986
|
+
'const entrypoints=' + JSON.stringify(input.proofEntrypoints) + ';' +
|
|
987
|
+
'for(const value of entrypoints){const checked=spawnSync(' +
|
|
988
|
+
'process.execPath,["--check",value],{encoding:"utf8"});' +
|
|
989
|
+
'if(checked.status!==0)throw new Error("entrypoint check failed");}' +
|
|
990
|
+
'process.stdout.write(JSON.stringify({architecture:process.arch,entrypoints,imports}));';
|
|
991
|
+
const structuralRaw = String(
|
|
992
|
+
execute(
|
|
993
|
+
'docker',
|
|
994
|
+
[
|
|
995
|
+
'run',
|
|
996
|
+
'--rm',
|
|
997
|
+
'--platform',
|
|
998
|
+
platform,
|
|
999
|
+
'--entrypoint',
|
|
1000
|
+
'node',
|
|
1001
|
+
imageReference,
|
|
1002
|
+
'--input-type=module',
|
|
1003
|
+
'--eval',
|
|
1004
|
+
structuralProgram,
|
|
1005
|
+
],
|
|
1006
|
+
'Native container structural proof',
|
|
1007
|
+
),
|
|
1008
|
+
);
|
|
1009
|
+
let structural;
|
|
1010
|
+
try {
|
|
1011
|
+
structural = JSON.parse(structuralRaw);
|
|
1012
|
+
} catch {
|
|
1013
|
+
throw new Error('Native container structural proof returned invalid JSON.');
|
|
1014
|
+
}
|
|
1015
|
+
const structuralArchitecture =
|
|
1016
|
+
structural?.architecture === 'x64'
|
|
1017
|
+
? 'amd64'
|
|
1018
|
+
: structural?.architecture;
|
|
1019
|
+
if (
|
|
1020
|
+
structuralArchitecture !== architecture ||
|
|
1021
|
+
JSON.stringify(structural.entrypoints) !==
|
|
1022
|
+
JSON.stringify(input.proofEntrypoints) ||
|
|
1023
|
+
JSON.stringify(structural.imports) !== JSON.stringify(input.proofImports)
|
|
1024
|
+
) {
|
|
1025
|
+
throw new Error('Native container structural proof is incomplete.');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (input.nativeLifecycleProof === 'service-default-health') {
|
|
1029
|
+
const containerId = String(
|
|
1030
|
+
execute(
|
|
1031
|
+
'docker',
|
|
1032
|
+
[
|
|
1033
|
+
'run',
|
|
1034
|
+
'--detach',
|
|
1035
|
+
'--rm',
|
|
1036
|
+
'--platform',
|
|
1037
|
+
platform,
|
|
1038
|
+
'--env',
|
|
1039
|
+
'EBK_STAGE=development',
|
|
1040
|
+
'--publish',
|
|
1041
|
+
'127.0.0.1::3000',
|
|
1042
|
+
imageReference,
|
|
1043
|
+
],
|
|
1044
|
+
'Native service default lifecycle start',
|
|
1045
|
+
),
|
|
1046
|
+
).trim();
|
|
1047
|
+
if (!/^[a-f0-9]{12,64}$/.test(containerId)) {
|
|
1048
|
+
throw new Error('Native service did not return one exact container identity.');
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
let port;
|
|
1052
|
+
let health;
|
|
1053
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
1054
|
+
const mapping = spawnSync(
|
|
1055
|
+
'docker',
|
|
1056
|
+
['port', containerId, '3000/tcp'],
|
|
1057
|
+
{
|
|
1058
|
+
cwd: repositoryRoot,
|
|
1059
|
+
encoding: 'utf8',
|
|
1060
|
+
env: process.env,
|
|
1061
|
+
maxBuffer: 1024 * 1024,
|
|
1062
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1063
|
+
},
|
|
1064
|
+
);
|
|
1065
|
+
const matched = String(mapping.stdout ?? '').trim().match(/:(\\d+)$/);
|
|
1066
|
+
if (mapping.status === 0 && matched) port = matched[1];
|
|
1067
|
+
if (port) {
|
|
1068
|
+
try {
|
|
1069
|
+
const response = await fetch('http://127.0.0.1:' + port + '/health', {
|
|
1070
|
+
signal: AbortSignal.timeout(1_000),
|
|
1071
|
+
});
|
|
1072
|
+
const body = await response.json();
|
|
1073
|
+
if (response.status === 200 && body?.status === 'ok') {
|
|
1074
|
+
health = { body, status: response.status };
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
} catch {
|
|
1078
|
+
// The real service process is still inside its bounded startup window.
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, 250));
|
|
1082
|
+
}
|
|
1083
|
+
if (!port || health?.status !== 200) {
|
|
1084
|
+
throw new Error('Native service default lifecycle did not become healthy.');
|
|
1085
|
+
}
|
|
1086
|
+
const missing = await fetch(
|
|
1087
|
+
'http://127.0.0.1:' + port + '/__ebk_missing_health_proof',
|
|
1088
|
+
{ signal: AbortSignal.timeout(1_000) },
|
|
1089
|
+
);
|
|
1090
|
+
if (missing.status !== 404) {
|
|
1091
|
+
throw new Error('Native service default lifecycle routed an unknown path.');
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
architecture,
|
|
1095
|
+
entrypoints: input.proofEntrypoints,
|
|
1096
|
+
imports: input.proofImports,
|
|
1097
|
+
nativeLifecycleProof: input.nativeLifecycleProof,
|
|
1098
|
+
role: 'service',
|
|
1099
|
+
status: 'passed',
|
|
1100
|
+
};
|
|
1101
|
+
} finally {
|
|
1102
|
+
execute(
|
|
1103
|
+
'docker',
|
|
1104
|
+
['stop', '--time', '5', containerId],
|
|
1105
|
+
'Native service proof cleanup',
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const databaseName =
|
|
1111
|
+
['development', slice, 'retirement_rehearsal']
|
|
1112
|
+
.join('_')
|
|
1113
|
+
.replaceAll('-', '_');
|
|
1114
|
+
const lifecycleArguments = ['run', '--rm', '--platform', platform];
|
|
1115
|
+
let expectedRefusal;
|
|
1116
|
+
if (input.nativeLifecycleProof === 'retirement-explicit-refusal') {
|
|
1117
|
+
const secretPrefix =
|
|
1118
|
+
'arn:aws:secretsmanager:' + region + ':' + accountId +
|
|
1119
|
+
':secret:ebk/development/' + slice +
|
|
1120
|
+
'/database/retirement_rehearsal/';
|
|
1121
|
+
for (const [name, value] of Object.entries({
|
|
1122
|
+
EBK_CANDIDATE_IDENTITY: input.candidateArtifactDigest,
|
|
1123
|
+
EBK_DATABASE_HOST: 'retirement-proof.invalid',
|
|
1124
|
+
EBK_DATABASE_IDENTIFIER: 'retirement_rehearsal',
|
|
1125
|
+
EBK_DATABASE_LIFECYCLE_CORRELATION_ID: 'native-lifecycle-proof',
|
|
1126
|
+
EBK_DATABASE_MIGRATOR_ROLE: databaseName + '_migrator',
|
|
1127
|
+
EBK_DATABASE_NAME: databaseName,
|
|
1128
|
+
EBK_DATABASE_PORT: '5432',
|
|
1129
|
+
EBK_DATABASE_READONLY_ROLE: databaseName + '_readonly',
|
|
1130
|
+
EBK_DATABASE_READWRITE_ROLE: databaseName + '_readwrite',
|
|
1131
|
+
EBK_EXECUTION_DATABASE_IDENTIFIER: 'retirement_rehearsal',
|
|
1132
|
+
EBK_EXECUTION_SLICE: slice,
|
|
1133
|
+
EBK_EXECUTION_STAGE: 'development',
|
|
1134
|
+
EBK_MASTER_SECRET_ARN: secretPrefix + 'master-proof',
|
|
1135
|
+
EBK_READONLY_SECRET_ARN: secretPrefix + 'readonly-proof',
|
|
1136
|
+
EBK_READWRITE_SECRET_ARN: secretPrefix + 'readwrite-proof',
|
|
1137
|
+
EBK_REGISTRATION_DIGEST: input.candidateArtifactDigest,
|
|
1138
|
+
EBK_SLICE: slice,
|
|
1139
|
+
EBK_STAGE: 'development',
|
|
1140
|
+
})) {
|
|
1141
|
+
lifecycleArguments.push('--env', name + '=' + value);
|
|
1142
|
+
}
|
|
1143
|
+
lifecycleArguments.push(
|
|
1144
|
+
'--entrypoint',
|
|
1145
|
+
'node',
|
|
1146
|
+
imageReference,
|
|
1147
|
+
'jobs/retire-database.mjs',
|
|
1148
|
+
);
|
|
1149
|
+
expectedRefusal = 'EBK_RETIREMENT_ID is required.';
|
|
1150
|
+
} else if (input.nativeLifecycleProof === 'migration-explicit-refusal') {
|
|
1151
|
+
lifecycleArguments.push(
|
|
1152
|
+
'--entrypoint',
|
|
1153
|
+
'node',
|
|
1154
|
+
imageReference,
|
|
1155
|
+
'jobs/migrate-database.mjs',
|
|
1156
|
+
);
|
|
1157
|
+
expectedRefusal = 'EBK_MIGRATION_ID is required.';
|
|
1158
|
+
} else {
|
|
1159
|
+
lifecycleArguments.push(imageReference);
|
|
1160
|
+
if (input.nativeLifecycleProof === 'retention-default-refusal') {
|
|
1161
|
+
expectedRefusal = 'EBK_RETENTION_MODE is required.';
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const lifecycle = spawnSync(
|
|
1165
|
+
'docker',
|
|
1166
|
+
lifecycleArguments,
|
|
1167
|
+
{
|
|
1168
|
+
cwd: repositoryRoot,
|
|
1169
|
+
encoding: 'utf8',
|
|
1170
|
+
env: process.env,
|
|
1171
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
1172
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1173
|
+
timeout: 30_000,
|
|
1174
|
+
},
|
|
1175
|
+
);
|
|
1176
|
+
if (lifecycle.error || lifecycle.signal || lifecycle.status === null) {
|
|
1177
|
+
throw new Error(
|
|
1178
|
+
'Native container lifecycle proof did not complete normally.',
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
const lifecycleOutput =
|
|
1182
|
+
String(lifecycle.stderr ?? '') + String(lifecycle.stdout ?? '');
|
|
1183
|
+
if (expectedRefusal) {
|
|
1184
|
+
if (lifecycle.status === 0 || !lifecycleOutput.includes(expectedRefusal)) {
|
|
1185
|
+
throw new Error(
|
|
1186
|
+
'Protected lifecycle command failed outside its exact missing-authority guard.',
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
} else if (
|
|
1190
|
+
input.nativeLifecycleProof !== 'job-default-success' ||
|
|
1191
|
+
lifecycle.status !== 0
|
|
1192
|
+
) {
|
|
1193
|
+
throw new Error(
|
|
1194
|
+
'Native Job default command did not complete successfully.',
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
architecture,
|
|
1199
|
+
entrypoints: input.proofEntrypoints,
|
|
1200
|
+
imports: input.proofImports,
|
|
1201
|
+
nativeLifecycleProof: input.nativeLifecycleProof,
|
|
1202
|
+
role: input.declared.finalArtifact.container.role,
|
|
1203
|
+
status: 'passed',
|
|
1204
|
+
};
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
let developmentSourceArtifact;
|
|
1208
|
+
if (stage === 'development') {
|
|
1209
|
+
const expectedSourceAccountId =
|
|
1210
|
+
process.env.EBK_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID?.trim() ?? '';
|
|
1211
|
+
const expectedSourceRegion =
|
|
1212
|
+
process.env.EBK_STAGING_PROMOTION_REGION?.trim() ?? '';
|
|
1213
|
+
if (
|
|
1214
|
+
!/^\\d{12}$/.test(expectedSourceAccountId) ||
|
|
1215
|
+
!AWS_REGION_NAME.test(expectedSourceRegion) ||
|
|
1216
|
+
expectedSourceAccountId === accountId
|
|
1217
|
+
) {
|
|
1218
|
+
throw new Error(
|
|
1219
|
+
'Development retirement promotion requires one independent staging account and Region.',
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
const sourceBindingPath = safePath(
|
|
1223
|
+
process.env.EBK_CONTAINER_PROMOTION_SOURCE_BINDING_PATH?.trim() ?? '',
|
|
1224
|
+
'Development retirement source binding',
|
|
1225
|
+
);
|
|
1226
|
+
const sourceSidecarPath = safePath(
|
|
1227
|
+
process.env.EBK_CONTAINER_PROMOTION_SOURCE_SIDECAR_PATH?.trim() ?? '',
|
|
1228
|
+
'Development retirement release-bound container sidecar',
|
|
1229
|
+
);
|
|
1230
|
+
const sourceBinding = validateContainerReleaseBinding(
|
|
1231
|
+
readJson(sourceBindingPath, 'Development retirement source binding'),
|
|
1232
|
+
);
|
|
1233
|
+
const sourceSidecar = validateContainerReleaseBinding(
|
|
1234
|
+
readJson(
|
|
1235
|
+
sourceSidecarPath,
|
|
1236
|
+
'Development retirement release-bound container sidecar',
|
|
1237
|
+
),
|
|
1238
|
+
);
|
|
1239
|
+
const sourceCandidateDigests = Object.fromEntries(
|
|
1240
|
+
releaseContainerInputs.map(({ candidateArtifactDigest, declared }) => [
|
|
1241
|
+
declared.id,
|
|
1242
|
+
candidateArtifactDigest,
|
|
1243
|
+
]),
|
|
1244
|
+
);
|
|
1245
|
+
if (
|
|
1246
|
+
sourceBindingPath === sourceSidecarPath ||
|
|
1247
|
+
JSON.stringify(sourceBinding) !== JSON.stringify(sourceSidecar) ||
|
|
1248
|
+
sourceBinding.stage !== 'staging' ||
|
|
1249
|
+
sourceBinding.accountId !== expectedSourceAccountId ||
|
|
1250
|
+
sourceBinding.region !== expectedSourceRegion ||
|
|
1251
|
+
sourceBinding.slice !== slice ||
|
|
1252
|
+
sourceBinding.sourceCommit !== sourceCommit ||
|
|
1253
|
+
sourceBinding.artifacts.length !== releaseContainerInputs.length ||
|
|
1254
|
+
sourceBinding.artifacts.some(
|
|
1255
|
+
({ artifactId, candidateArtifactDigest }) =>
|
|
1256
|
+
sourceCandidateDigests[artifactId] !== candidateArtifactDigest,
|
|
1257
|
+
)
|
|
1258
|
+
) {
|
|
1259
|
+
throw new Error(
|
|
1260
|
+
'Development retirement source is not the exact sealed staging binding.',
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
const retirementInput = containerInputs[0];
|
|
1264
|
+
developmentSourceArtifact = resolveContainerReleaseArtifact(sourceBinding, {
|
|
1265
|
+
artifactId: retirementInput.declared.id,
|
|
1266
|
+
candidateArtifactDigest: retirementInput.candidateArtifactDigest,
|
|
1267
|
+
});
|
|
1268
|
+
const dockerfileDigest = digestBytes(readFileSync(retirementInput.dockerfile));
|
|
1269
|
+
const sourceRepositoryName = repositoryForStage(
|
|
1270
|
+
retirementInput.declared.id,
|
|
1271
|
+
'staging',
|
|
1272
|
+
);
|
|
1273
|
+
if (
|
|
1274
|
+
developmentSourceArtifact.artifactName !== retirementInput.declared.name ||
|
|
1275
|
+
developmentSourceArtifact.dockerfileDigest !== dockerfileDigest ||
|
|
1276
|
+
developmentSourceArtifact.repositoryName !== sourceRepositoryName ||
|
|
1277
|
+
developmentSourceArtifact.platforms.some(
|
|
1278
|
+
(platform) =>
|
|
1279
|
+
platform.accountId !== expectedSourceAccountId ||
|
|
1280
|
+
platform.region !== expectedSourceRegion ||
|
|
1281
|
+
platform.slice !== slice ||
|
|
1282
|
+
platform.sourceCommit !== sourceCommit ||
|
|
1283
|
+
platform.stage !== 'staging' ||
|
|
1284
|
+
platform.dockerfileDigest !== dockerfileDigest ||
|
|
1285
|
+
platform.repositoryName !== sourceRepositoryName,
|
|
1286
|
+
)
|
|
1287
|
+
) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
'Development retirement source artifact is stale or cross-paired.',
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
for (const platform of developmentSourceArtifact.platforms) {
|
|
1293
|
+
validateRetainedSbom(
|
|
1294
|
+
platform,
|
|
1295
|
+
'Development retirement source platform SBOM',
|
|
1296
|
+
);
|
|
1297
|
+
validateRetainedRunProof(
|
|
1298
|
+
platform,
|
|
1299
|
+
retirementInput,
|
|
1300
|
+
'Development retirement source native run proof',
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
if (mode === 'promote') {
|
|
1306
|
+
if (stage !== 'production') {
|
|
1307
|
+
throw new Error('Exact OCI promotion targets production only.');
|
|
1308
|
+
}
|
|
1309
|
+
const expectedSourceAccountId =
|
|
1310
|
+
process.env.EBK_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID?.trim() ?? '';
|
|
1311
|
+
const expectedSourceRegion =
|
|
1312
|
+
process.env.EBK_STAGING_PROMOTION_REGION?.trim() ?? '';
|
|
1313
|
+
if (
|
|
1314
|
+
!/^\\d{12}$/.test(expectedSourceAccountId) ||
|
|
1315
|
+
!AWS_REGION_NAME.test(expectedSourceRegion) ||
|
|
1316
|
+
expectedSourceAccountId === accountId
|
|
1317
|
+
) {
|
|
1318
|
+
throw new Error(
|
|
1319
|
+
'OCI promotion requires one bounded independent staging account and Region.',
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
const sourceBindingInput =
|
|
1323
|
+
process.env.EBK_CONTAINER_PROMOTION_SOURCE_BINDING_PATH?.trim() ?? '';
|
|
1324
|
+
const sourceBindingPath = safePath(
|
|
1325
|
+
sourceBindingInput,
|
|
1326
|
+
'Promotion source binding',
|
|
1327
|
+
);
|
|
1328
|
+
const sourceBinding = validateContainerReleaseBinding(
|
|
1329
|
+
readJson(sourceBindingPath, 'Promotion source binding'),
|
|
1330
|
+
);
|
|
1331
|
+
const sourceSidecarPath = safePath(
|
|
1332
|
+
process.env.EBK_CONTAINER_PROMOTION_SOURCE_SIDECAR_PATH?.trim() ?? '',
|
|
1333
|
+
'Promotion release-bound container sidecar',
|
|
1334
|
+
);
|
|
1335
|
+
const sourceSidecar = validateContainerReleaseBinding(
|
|
1336
|
+
readJson(sourceSidecarPath, 'Promotion release-bound container sidecar'),
|
|
1337
|
+
);
|
|
1338
|
+
if (
|
|
1339
|
+
sourceSidecarPath === sourceBindingPath ||
|
|
1340
|
+
JSON.stringify(sourceSidecar) !== JSON.stringify(sourceBinding)
|
|
1341
|
+
) {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
'Promotion source binding differs from the verified staging release sidecar.',
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
const candidateDigests = Object.fromEntries(
|
|
1347
|
+
containerInputs.map(({ candidateArtifactDigest, declared }) => [
|
|
1348
|
+
declared.id,
|
|
1349
|
+
candidateArtifactDigest,
|
|
1350
|
+
]),
|
|
1351
|
+
);
|
|
1352
|
+
if (
|
|
1353
|
+
sourceBinding.stage !== 'staging' ||
|
|
1354
|
+
sourceBinding.accountId !== expectedSourceAccountId ||
|
|
1355
|
+
sourceBinding.region !== expectedSourceRegion ||
|
|
1356
|
+
sourceBinding.slice !== slice ||
|
|
1357
|
+
sourceBinding.sourceCommit !== sourceCommit ||
|
|
1358
|
+
sourceBinding.artifacts.length !== containerInputs.length ||
|
|
1359
|
+
sourceBinding.artifacts.some(
|
|
1360
|
+
({ artifactId, candidateArtifactDigest }) =>
|
|
1361
|
+
candidateDigests[artifactId] !== candidateArtifactDigest,
|
|
1362
|
+
)
|
|
1363
|
+
) {
|
|
1364
|
+
throw new Error('Promotion source is not the exact reviewed staging binding.');
|
|
1365
|
+
}
|
|
1366
|
+
const preparedSources = containerInputs.map((input) => {
|
|
1367
|
+
const sourceArtifact = resolveContainerReleaseArtifact(sourceBinding, {
|
|
1368
|
+
artifactId: input.declared.id,
|
|
1369
|
+
candidateArtifactDigest: input.candidateArtifactDigest,
|
|
1370
|
+
});
|
|
1371
|
+
const dockerfileDigest = digestBytes(readFileSync(input.dockerfile));
|
|
1372
|
+
const sourceRepositoryName = repositoryForStage(
|
|
1373
|
+
input.declared.id,
|
|
1374
|
+
'staging',
|
|
1375
|
+
);
|
|
1376
|
+
if (
|
|
1377
|
+
sourceArtifact.artifactName !== input.declared.name ||
|
|
1378
|
+
sourceArtifact.dockerfileDigest !== dockerfileDigest ||
|
|
1379
|
+
sourceArtifact.repositoryName !== sourceRepositoryName ||
|
|
1380
|
+
sourceArtifact.platforms.some(
|
|
1381
|
+
(platform) =>
|
|
1382
|
+
platform.accountId !== expectedSourceAccountId ||
|
|
1383
|
+
platform.region !== expectedSourceRegion ||
|
|
1384
|
+
platform.slice !== slice ||
|
|
1385
|
+
platform.sourceCommit !== sourceCommit ||
|
|
1386
|
+
platform.stage !== 'staging' ||
|
|
1387
|
+
platform.dockerfileDigest !== dockerfileDigest ||
|
|
1388
|
+
platform.repositoryName !== sourceRepositoryName,
|
|
1389
|
+
)
|
|
1390
|
+
) {
|
|
1391
|
+
throw new Error(
|
|
1392
|
+
'Promotion source artifact is stale or belongs to another build authority.',
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
for (const platform of sourceArtifact.platforms) {
|
|
1396
|
+
validateRetainedSbom(platform, 'Promotion source platform SBOM');
|
|
1397
|
+
validateRetainedRunProof(
|
|
1398
|
+
platform,
|
|
1399
|
+
input,
|
|
1400
|
+
'Promotion source native run proof',
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
return { input, sourceArtifact };
|
|
1404
|
+
});
|
|
1405
|
+
const crane = await acquireCrane();
|
|
1406
|
+
try {
|
|
1407
|
+
login();
|
|
1408
|
+
const artifacts = [];
|
|
1409
|
+
for (const { input, sourceArtifact } of preparedSources) {
|
|
1410
|
+
const repositoryName = repositoryFor(input.declared.id);
|
|
1411
|
+
ensureRepository(repositoryName);
|
|
1412
|
+
const tag =
|
|
1413
|
+
'promotion-' + sourceCommit + '-' + runId + '-' + runAttempt + '-index';
|
|
1414
|
+
const taggedReference = registry + '/' + repositoryName + ':' + tag;
|
|
1415
|
+
execute(
|
|
1416
|
+
crane.path,
|
|
1417
|
+
[
|
|
1418
|
+
'copy',
|
|
1419
|
+
sourceArtifact.imageReference,
|
|
1420
|
+
taggedReference,
|
|
1421
|
+
],
|
|
1422
|
+
'Recursive exact staging OCI index promotion',
|
|
1423
|
+
);
|
|
1424
|
+
const imageDigest = exactImageByTag(repositoryName, tag);
|
|
1425
|
+
if (imageDigest !== sourceArtifact.imageDigest) {
|
|
1426
|
+
throw new Error('Production OCI index digest differs from reviewed staging.');
|
|
1427
|
+
}
|
|
1428
|
+
const imageReference = registry + '/' + repositoryName + '@' + imageDigest;
|
|
1429
|
+
const rawIndex = String(
|
|
1430
|
+
execute(
|
|
1431
|
+
'docker',
|
|
1432
|
+
['buildx', 'imagetools', 'inspect', '--raw', imageReference],
|
|
1433
|
+
'Promoted OCI index inspection',
|
|
1434
|
+
),
|
|
1435
|
+
);
|
|
1436
|
+
let index;
|
|
1437
|
+
try {
|
|
1438
|
+
index = JSON.parse(rawIndex);
|
|
1439
|
+
} catch {
|
|
1440
|
+
throw new Error('Promoted OCI index inspection returned invalid JSON.');
|
|
1441
|
+
}
|
|
1442
|
+
const rawDescriptors = Array.isArray(index?.manifests)
|
|
1443
|
+
? index.manifests
|
|
1444
|
+
: [];
|
|
1445
|
+
const descriptors = rawDescriptors
|
|
1446
|
+
.map((descriptor) => ({
|
|
1447
|
+
digest: descriptor?.digest,
|
|
1448
|
+
platform:
|
|
1449
|
+
String(descriptor?.platform?.os) + '/' +
|
|
1450
|
+
String(descriptor?.platform?.architecture),
|
|
1451
|
+
}))
|
|
1452
|
+
.sort((left, right) =>
|
|
1453
|
+
left.platform < right.platform
|
|
1454
|
+
? -1
|
|
1455
|
+
: left.platform > right.platform
|
|
1456
|
+
? 1
|
|
1457
|
+
: 0,
|
|
1458
|
+
);
|
|
1459
|
+
const sourcePlatforms = [...sourceArtifact.platforms].sort((left, right) =>
|
|
1460
|
+
left.platform < right.platform
|
|
1461
|
+
? -1
|
|
1462
|
+
: left.platform > right.platform
|
|
1463
|
+
? 1
|
|
1464
|
+
: 0,
|
|
1465
|
+
);
|
|
1466
|
+
if (
|
|
1467
|
+
![
|
|
1468
|
+
'application/vnd.oci.image.index.v1+json',
|
|
1469
|
+
'application/vnd.docker.distribution.manifest.list.v2+json',
|
|
1470
|
+
].includes(index?.mediaType) ||
|
|
1471
|
+
rawDescriptors.length !== CONTAINER_RELEASE_PLATFORMS.length ||
|
|
1472
|
+
descriptors.some(
|
|
1473
|
+
({ digest, platform }) =>
|
|
1474
|
+
!IMAGE_DIGEST.test(digest) ||
|
|
1475
|
+
!CONTAINER_RELEASE_PLATFORMS.includes(platform),
|
|
1476
|
+
) ||
|
|
1477
|
+
descriptors.some(
|
|
1478
|
+
(descriptor, index) =>
|
|
1479
|
+
descriptor.platform !== sourcePlatforms[index]?.platform ||
|
|
1480
|
+
descriptor.digest !== sourcePlatforms[index]?.imageDigest,
|
|
1481
|
+
)
|
|
1482
|
+
) {
|
|
1483
|
+
throw new Error('Production OCI child manifests differ from staging.');
|
|
1484
|
+
}
|
|
1485
|
+
for (const descriptor of descriptors) {
|
|
1486
|
+
const response = awsJson(
|
|
1487
|
+
[
|
|
1488
|
+
'ecr',
|
|
1489
|
+
'batch-get-image',
|
|
1490
|
+
'--repository-name',
|
|
1491
|
+
repositoryName,
|
|
1492
|
+
'--image-ids',
|
|
1493
|
+
'imageDigest=' + descriptor.digest,
|
|
1494
|
+
'--accepted-media-types',
|
|
1495
|
+
'application/vnd.oci.image.manifest.v1+json',
|
|
1496
|
+
'application/vnd.docker.distribution.manifest.v2+json',
|
|
1497
|
+
'--region',
|
|
1498
|
+
region,
|
|
1499
|
+
'--output',
|
|
1500
|
+
'json',
|
|
1501
|
+
],
|
|
1502
|
+
'Promoted OCI child manifest lookup',
|
|
1503
|
+
);
|
|
1504
|
+
const image = response?.images?.[0];
|
|
1505
|
+
if (
|
|
1506
|
+
!Array.isArray(response?.images) ||
|
|
1507
|
+
response.images.length !== 1 ||
|
|
1508
|
+
!Array.isArray(response?.failures) ||
|
|
1509
|
+
response.failures.length !== 0 ||
|
|
1510
|
+
image?.imageId?.imageDigest !== descriptor.digest ||
|
|
1511
|
+
typeof image?.imageManifest !== 'string' ||
|
|
1512
|
+
image.imageManifest.length === 0 ||
|
|
1513
|
+
Buffer.byteLength(image.imageManifest, 'utf8') > MAX_OUTPUT_BYTES ||
|
|
1514
|
+
![
|
|
1515
|
+
'application/vnd.oci.image.manifest.v1+json',
|
|
1516
|
+
'application/vnd.docker.distribution.manifest.v2+json',
|
|
1517
|
+
].includes(image.imageManifestMediaType)
|
|
1518
|
+
) {
|
|
1519
|
+
throw new Error(
|
|
1520
|
+
'Promoted OCI child manifest is absent from the target repository.',
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
const platforms = sourcePlatforms.map((platform) =>
|
|
1525
|
+
createContainerPlatformEvidence({
|
|
1526
|
+
accountId,
|
|
1527
|
+
artifactId: platform.artifactId,
|
|
1528
|
+
artifactName: platform.artifactName,
|
|
1529
|
+
candidateArtifactDigest: platform.candidateArtifactDigest,
|
|
1530
|
+
dockerfileDigest: platform.dockerfileDigest,
|
|
1531
|
+
imageDigest: platform.imageDigest,
|
|
1532
|
+
imageReference:
|
|
1533
|
+
registry + '/' + repositoryName + '@' + platform.imageDigest,
|
|
1534
|
+
platform: platform.platform,
|
|
1535
|
+
productionClosureDigest: platform.productionClosureDigest,
|
|
1536
|
+
region,
|
|
1537
|
+
repositoryName,
|
|
1538
|
+
runProof: platform.runProof,
|
|
1539
|
+
sbom: platform.sbom,
|
|
1540
|
+
slice,
|
|
1541
|
+
sourceCommit,
|
|
1542
|
+
stage: 'production',
|
|
1543
|
+
}),
|
|
1544
|
+
);
|
|
1545
|
+
artifacts.push({
|
|
1546
|
+
...sourceArtifact,
|
|
1547
|
+
imageReference,
|
|
1548
|
+
platforms,
|
|
1549
|
+
repositoryName,
|
|
1550
|
+
sbomParityDigest: supplyChainDigest(
|
|
1551
|
+
platforms.map((entry) => ({
|
|
1552
|
+
componentSetDigest: entry.sbom.componentSetDigest,
|
|
1553
|
+
platform: entry.platform,
|
|
1554
|
+
productionClosureDigest: entry.productionClosureDigest,
|
|
1555
|
+
runProof: entry.runProof,
|
|
1556
|
+
})),
|
|
1557
|
+
),
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
const binding = createContainerReleaseBinding({
|
|
1561
|
+
accountId,
|
|
1562
|
+
artifacts,
|
|
1563
|
+
builtAt: new Date().toISOString(),
|
|
1564
|
+
provenance: { runAttempt, runId, workflow },
|
|
1565
|
+
region,
|
|
1566
|
+
slice,
|
|
1567
|
+
sourceCommit,
|
|
1568
|
+
stage: 'production',
|
|
1569
|
+
});
|
|
1570
|
+
const output = resolve(
|
|
1571
|
+
repositoryRoot,
|
|
1572
|
+
'.ebk/evidence/container-release/production/packages',
|
|
1573
|
+
slice,
|
|
1574
|
+
'binding.json',
|
|
1575
|
+
);
|
|
1576
|
+
immutableWrite(output, binding);
|
|
1577
|
+
process.stdout.write(
|
|
1578
|
+
JSON.stringify({
|
|
1579
|
+
artifactCount: artifacts.length,
|
|
1580
|
+
bindingDigest: binding.materialDigest,
|
|
1581
|
+
evidencePath: output,
|
|
1582
|
+
promotedFrom: sourceBinding.materialDigest,
|
|
1583
|
+
schemaVersion: 1,
|
|
1584
|
+
stage,
|
|
1585
|
+
}) + '\\n',
|
|
1586
|
+
);
|
|
1587
|
+
} finally {
|
|
1588
|
+
rmSync(crane.temporary, { force: true, recursive: true });
|
|
1589
|
+
}
|
|
1590
|
+
process.exit(0);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
if (mode === 'platform') {
|
|
1594
|
+
const nativeArchitecture = process.arch === 'x64' ? 'amd64' : process.arch;
|
|
1595
|
+
if (
|
|
1596
|
+
process.platform !== 'linux' ||
|
|
1597
|
+
requestedPlatform !== 'linux/' + nativeArchitecture
|
|
1598
|
+
) {
|
|
1599
|
+
throw new Error(
|
|
1600
|
+
'Platform evidence must run on the matching native Linux architecture.',
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
const syft = await acquireSyft();
|
|
1604
|
+
const crane = stage === 'development' ? await acquireCrane() : undefined;
|
|
1605
|
+
try {
|
|
1606
|
+
const expectedClosures = new Map(
|
|
1607
|
+
containerInputs.map((input) => [
|
|
1608
|
+
input.declared.id,
|
|
1609
|
+
deriveExpectedProductionClosure(syft, input),
|
|
1610
|
+
]),
|
|
1611
|
+
);
|
|
1612
|
+
requireReviewedCheckout();
|
|
1613
|
+
for (const input of containerInputs) requireCandidateAuthority(input);
|
|
1614
|
+
login();
|
|
1615
|
+
const architecture = requestedPlatform.split('/')[1];
|
|
1616
|
+
const outputRoot = resolve(
|
|
1617
|
+
repositoryRoot,
|
|
1618
|
+
'.ebk/evidence/container-release/partials',
|
|
1619
|
+
stage,
|
|
1620
|
+
slice,
|
|
1621
|
+
architecture,
|
|
1622
|
+
);
|
|
1623
|
+
for (const input of containerInputs) {
|
|
1624
|
+
const expectedClosure = expectedClosures.get(input.declared.id);
|
|
1625
|
+
if (!expectedClosure) {
|
|
1626
|
+
throw new Error('Independent production closure is absent.');
|
|
1627
|
+
}
|
|
1628
|
+
const repositoryName = repositoryFor(input.declared.id);
|
|
1629
|
+
ensureRepository(repositoryName);
|
|
1630
|
+
const tag =
|
|
1631
|
+
'candidate-' + sourceCommit + '-' + runId + '-' + runAttempt + '-' +
|
|
1632
|
+
architecture;
|
|
1633
|
+
const taggedReference = registry + '/' + repositoryName + ':' + tag;
|
|
1634
|
+
requireReviewedCheckout();
|
|
1635
|
+
requireCandidateAuthority(input);
|
|
1636
|
+
const developmentSourcePlatform =
|
|
1637
|
+
stage === 'development'
|
|
1638
|
+
? developmentSourceArtifact?.platforms.find(
|
|
1639
|
+
({ platform }) => platform === requestedPlatform,
|
|
1640
|
+
)
|
|
1641
|
+
: undefined;
|
|
1642
|
+
if (stage === 'development') {
|
|
1643
|
+
if (!crane || !developmentSourcePlatform) {
|
|
1644
|
+
throw new Error(
|
|
1645
|
+
'Development retirement source platform is absent.',
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
execute(
|
|
1649
|
+
crane.path,
|
|
1650
|
+
['copy', developmentSourcePlatform.imageReference, taggedReference],
|
|
1651
|
+
'Exact staging retirement child promotion',
|
|
1652
|
+
);
|
|
1653
|
+
} else {
|
|
1654
|
+
execute(
|
|
1655
|
+
'docker',
|
|
1656
|
+
[
|
|
1657
|
+
'buildx',
|
|
1658
|
+
'build',
|
|
1659
|
+
'--file',
|
|
1660
|
+
relative(repositoryRoot, input.dockerfile),
|
|
1661
|
+
'--label',
|
|
1662
|
+
'org.opencontainers.image.revision=' + sourceCommit,
|
|
1663
|
+
'--label',
|
|
1664
|
+
'dev.empire-builder-kit.candidate-digest=' +
|
|
1665
|
+
input.candidateArtifactDigest,
|
|
1666
|
+
'--platform',
|
|
1667
|
+
requestedPlatform,
|
|
1668
|
+
'--provenance=false',
|
|
1669
|
+
'--push',
|
|
1670
|
+
'--sbom=false',
|
|
1671
|
+
'--tag',
|
|
1672
|
+
taggedReference,
|
|
1673
|
+
relative(repositoryRoot, input.context) || '.',
|
|
1674
|
+
],
|
|
1675
|
+
'Native container build and push',
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
const imageDigest = exactImageByTag(repositoryName, tag);
|
|
1679
|
+
const imageReference = registry + '/' + repositoryName + '@' + imageDigest;
|
|
1680
|
+
if (
|
|
1681
|
+
stage === 'development' &&
|
|
1682
|
+
developmentSourcePlatform?.imageDigest !== imageDigest
|
|
1683
|
+
) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
'Development retirement child promotion differs from exact staging.',
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
const runtimeManifestRaw = String(
|
|
1689
|
+
execute(
|
|
1690
|
+
'docker',
|
|
1691
|
+
['buildx', 'imagetools', 'inspect', '--raw', imageReference],
|
|
1692
|
+
'Native runtime manifest inspection',
|
|
1693
|
+
),
|
|
1694
|
+
);
|
|
1695
|
+
let runtimeManifest;
|
|
1696
|
+
try {
|
|
1697
|
+
runtimeManifest = JSON.parse(runtimeManifestRaw);
|
|
1698
|
+
} catch {
|
|
1699
|
+
throw new Error('Native runtime manifest inspection returned invalid JSON.');
|
|
1700
|
+
}
|
|
1701
|
+
if (
|
|
1702
|
+
![
|
|
1703
|
+
'application/vnd.oci.image.manifest.v1+json',
|
|
1704
|
+
'application/vnd.docker.distribution.manifest.v2+json',
|
|
1705
|
+
].includes(runtimeManifest?.mediaType) ||
|
|
1706
|
+
Array.isArray(runtimeManifest?.manifests)
|
|
1707
|
+
) {
|
|
1708
|
+
throw new Error(
|
|
1709
|
+
'Native build must publish one plain runtime manifest; the integrity-bound Syft SBOM is retained separately.',
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
const runProof = await runNativeLifecycle(
|
|
1713
|
+
input,
|
|
1714
|
+
imageReference,
|
|
1715
|
+
requestedPlatform,
|
|
1716
|
+
);
|
|
1717
|
+
if (
|
|
1718
|
+
runProof.architecture !== architecture ||
|
|
1719
|
+
runProof.status !== 'passed' ||
|
|
1720
|
+
runProof.nativeLifecycleProof !== input.nativeLifecycleProof ||
|
|
1721
|
+
JSON.stringify(runProof.entrypoints) !==
|
|
1722
|
+
JSON.stringify(input.proofEntrypoints) ||
|
|
1723
|
+
JSON.stringify(runProof.imports) !== JSON.stringify(input.proofImports) ||
|
|
1724
|
+
runProof.role !== input.declared.finalArtifact.container.role
|
|
1725
|
+
) {
|
|
1726
|
+
throw new Error('Native production container run proof is incomplete.');
|
|
1727
|
+
}
|
|
1728
|
+
const safeArtifact = input.declared.name.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
1729
|
+
const nativeRunProof = createContainerNativeRunProof({
|
|
1730
|
+
accountId,
|
|
1731
|
+
artifactId: input.declared.id,
|
|
1732
|
+
artifactName: input.declared.name,
|
|
1733
|
+
candidateArtifactDigest: input.candidateArtifactDigest,
|
|
1734
|
+
entrypoints: runProof.entrypoints,
|
|
1735
|
+
imageDigest,
|
|
1736
|
+
imports: runProof.imports,
|
|
1737
|
+
nativeLifecycleProof: runProof.nativeLifecycleProof,
|
|
1738
|
+
platform: requestedPlatform,
|
|
1739
|
+
region,
|
|
1740
|
+
repositoryName,
|
|
1741
|
+
role: runProof.role,
|
|
1742
|
+
slice,
|
|
1743
|
+
sourceCommit,
|
|
1744
|
+
stage,
|
|
1745
|
+
status: runProof.status,
|
|
1746
|
+
});
|
|
1747
|
+
const serializedRunProof = JSON.stringify(nativeRunProof, null, 2) + '\\n';
|
|
1748
|
+
const relativeRunProofPath =
|
|
1749
|
+
'.ebk/evidence/container-release/partials/' + stage + '/' +
|
|
1750
|
+
slice + '/' + architecture + '/' + safeArtifact + '.run-proof.json';
|
|
1751
|
+
immutableWrite(
|
|
1752
|
+
resolve(repositoryRoot, relativeRunProofPath),
|
|
1753
|
+
serializedRunProof,
|
|
1754
|
+
);
|
|
1755
|
+
const closureRaw = String(
|
|
1756
|
+
execute(
|
|
1757
|
+
syft.path,
|
|
1758
|
+
['scan', 'registry:' + imageReference, '--output', 'syft-json'],
|
|
1759
|
+
'Exact application npm closure inventory',
|
|
1760
|
+
{ env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' } },
|
|
1761
|
+
),
|
|
1762
|
+
);
|
|
1763
|
+
let closureInventory;
|
|
1764
|
+
try {
|
|
1765
|
+
closureInventory = JSON.parse(closureRaw);
|
|
1766
|
+
} catch {
|
|
1767
|
+
throw new Error('Exact application npm closure inventory is invalid JSON.');
|
|
1768
|
+
}
|
|
1769
|
+
const observedClosure = npmClosureFor(closureInventory, {
|
|
1770
|
+
applicationOnly: true,
|
|
1771
|
+
});
|
|
1772
|
+
if (
|
|
1773
|
+
JSON.stringify(observedClosure) !==
|
|
1774
|
+
JSON.stringify(expectedClosure.packages)
|
|
1775
|
+
) {
|
|
1776
|
+
throw new Error(
|
|
1777
|
+
'Container application npm closure differs from the independently deployed production closure.',
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
const rawSbom = String(
|
|
1781
|
+
execute(
|
|
1782
|
+
syft.path,
|
|
1783
|
+
['scan', 'registry:' + imageReference, '--output', 'cyclonedx-json'],
|
|
1784
|
+
'Exact platform OCI SBOM',
|
|
1785
|
+
{ env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' } },
|
|
1786
|
+
),
|
|
1787
|
+
);
|
|
1788
|
+
let sbom;
|
|
1789
|
+
try {
|
|
1790
|
+
sbom = parseCycloneDx(
|
|
1791
|
+
JSON.parse(rawSbom),
|
|
1792
|
+
'Syft platform SBOM',
|
|
1793
|
+
);
|
|
1794
|
+
} catch {
|
|
1795
|
+
throw new Error('Syft returned invalid platform CycloneDX JSON.');
|
|
1796
|
+
}
|
|
1797
|
+
const componentSet = componentSetFor(sbom);
|
|
1798
|
+
if (
|
|
1799
|
+
componentSet.some(
|
|
1800
|
+
(component) =>
|
|
1801
|
+
typeof component.name !== 'string' ||
|
|
1802
|
+
typeof component.type !== 'string' ||
|
|
1803
|
+
typeof component.version !== 'string',
|
|
1804
|
+
)
|
|
1805
|
+
) {
|
|
1806
|
+
throw new Error('Platform SBOM contains invalid or development components.');
|
|
1807
|
+
}
|
|
1808
|
+
const serializedSbom = JSON.stringify(sbom, null, 2) + '\\n';
|
|
1809
|
+
const relativeSbomPath =
|
|
1810
|
+
'.ebk/evidence/container-release/partials/' + stage + '/' +
|
|
1811
|
+
slice + '/' + architecture + '/' + safeArtifact + '.cdx.json';
|
|
1812
|
+
immutableWrite(resolve(repositoryRoot, relativeSbomPath), serializedSbom);
|
|
1813
|
+
const evidence = createContainerPlatformEvidence({
|
|
1814
|
+
accountId,
|
|
1815
|
+
artifactId: input.declared.id,
|
|
1816
|
+
artifactName: input.declared.name,
|
|
1817
|
+
candidateArtifactDigest: input.candidateArtifactDigest,
|
|
1818
|
+
dockerfileDigest: digestBytes(readFileSync(input.dockerfile)),
|
|
1819
|
+
imageDigest,
|
|
1820
|
+
imageReference,
|
|
1821
|
+
platform: requestedPlatform,
|
|
1822
|
+
productionClosureDigest: expectedClosure.digest,
|
|
1823
|
+
region,
|
|
1824
|
+
repositoryName,
|
|
1825
|
+
runProof: {
|
|
1826
|
+
materialDigest: nativeRunProof.materialDigest,
|
|
1827
|
+
path: relativeRunProofPath,
|
|
1828
|
+
sha256: digestBytes(Buffer.from(serializedRunProof, 'utf8')),
|
|
1829
|
+
},
|
|
1830
|
+
sbom: {
|
|
1831
|
+
componentSetDigest: supplyChainDigest(componentSet),
|
|
1832
|
+
path: relativeSbomPath,
|
|
1833
|
+
serialNumber: sbom.serialNumber.toLowerCase(),
|
|
1834
|
+
sha256: digestBytes(Buffer.from(serializedSbom, 'utf8')),
|
|
1835
|
+
},
|
|
1836
|
+
slice,
|
|
1837
|
+
sourceCommit,
|
|
1838
|
+
stage,
|
|
1839
|
+
});
|
|
1840
|
+
immutableWrite(
|
|
1841
|
+
join(outputRoot, safeArtifact + '.platform.json'),
|
|
1842
|
+
evidence,
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
process.stdout.write(
|
|
1846
|
+
JSON.stringify({
|
|
1847
|
+
artifactCount: containerInputs.length,
|
|
1848
|
+
platform: requestedPlatform,
|
|
1849
|
+
schemaVersion: 1,
|
|
1850
|
+
stage,
|
|
1851
|
+
}) + '\\n',
|
|
1852
|
+
);
|
|
1853
|
+
} finally {
|
|
1854
|
+
rmSync(syft.temporary, { force: true, recursive: true });
|
|
1855
|
+
if (crane) {
|
|
1856
|
+
rmSync(crane.temporary, { force: true, recursive: true });
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
process.exit(0);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
const partialRoot = resolve(
|
|
1863
|
+
repositoryRoot,
|
|
1864
|
+
'.ebk/evidence/container-release/partials',
|
|
1865
|
+
stage,
|
|
1866
|
+
slice,
|
|
1867
|
+
);
|
|
1868
|
+
if (!existsSync(partialRoot) || !lstatSync(partialRoot).isDirectory()) {
|
|
1869
|
+
throw new Error('Container platform evidence directory is absent.');
|
|
1870
|
+
}
|
|
1871
|
+
const partialPaths = [];
|
|
1872
|
+
const visit = (directory) => {
|
|
1873
|
+
for (const entry of readdirSync(directory).sort()) {
|
|
1874
|
+
const path = join(directory, entry);
|
|
1875
|
+
const metadata = lstatSync(path);
|
|
1876
|
+
if (metadata.isSymbolicLink()) {
|
|
1877
|
+
throw new Error('Container platform evidence may not contain symlinks.');
|
|
1878
|
+
}
|
|
1879
|
+
if (metadata.isDirectory()) visit(path);
|
|
1880
|
+
else if (metadata.isFile() && entry.endsWith('.platform.json')) {
|
|
1881
|
+
partialPaths.push(path);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
visit(partialRoot);
|
|
1886
|
+
if (
|
|
1887
|
+
partialPaths.length !==
|
|
1888
|
+
containerInputs.length * CONTAINER_RELEASE_PLATFORMS.length
|
|
1889
|
+
) {
|
|
1890
|
+
throw new Error('Container platform evidence is incomplete or excessive.');
|
|
1891
|
+
}
|
|
1892
|
+
const partials = partialPaths.map((path) =>
|
|
1893
|
+
validateContainerPlatformEvidence(readJson(path, 'Container platform evidence')),
|
|
1894
|
+
);
|
|
1895
|
+
const preparedArtifacts = containerInputs.map((input) => {
|
|
1896
|
+
const evidence = partials
|
|
1897
|
+
.filter(({ artifactId }) => artifactId === input.declared.id)
|
|
1898
|
+
.sort((left, right) =>
|
|
1899
|
+
left.platform < right.platform
|
|
1900
|
+
? -1
|
|
1901
|
+
: left.platform > right.platform
|
|
1902
|
+
? 1
|
|
1903
|
+
: 0,
|
|
1904
|
+
);
|
|
1905
|
+
const repositoryName = repositoryFor(input.declared.id);
|
|
1906
|
+
const dockerfileDigest = digestBytes(readFileSync(input.dockerfile));
|
|
1907
|
+
const expectedPlatforms = [...CONTAINER_RELEASE_PLATFORMS].sort();
|
|
1908
|
+
if (
|
|
1909
|
+
evidence.length !== CONTAINER_RELEASE_PLATFORMS.length ||
|
|
1910
|
+
evidence.some(
|
|
1911
|
+
(entry, index) =>
|
|
1912
|
+
entry.accountId !== accountId ||
|
|
1913
|
+
entry.region !== region ||
|
|
1914
|
+
entry.slice !== slice ||
|
|
1915
|
+
entry.sourceCommit !== sourceCommit ||
|
|
1916
|
+
entry.stage !== stage ||
|
|
1917
|
+
entry.artifactId !== input.declared.id ||
|
|
1918
|
+
entry.artifactName !== input.declared.name ||
|
|
1919
|
+
entry.candidateArtifactDigest !== input.candidateArtifactDigest ||
|
|
1920
|
+
entry.dockerfileDigest !== dockerfileDigest ||
|
|
1921
|
+
entry.repositoryName !== repositoryName ||
|
|
1922
|
+
entry.platform !== expectedPlatforms[index] ||
|
|
1923
|
+
entry.productionClosureDigest !== evidence[0]?.productionClosureDigest,
|
|
1924
|
+
)
|
|
1925
|
+
) {
|
|
1926
|
+
throw new Error(
|
|
1927
|
+
'Container platform evidence is stale, cross-paired, or incomplete.',
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
for (const entry of evidence) {
|
|
1931
|
+
validateRetainedSbom(entry, 'Container platform SBOM');
|
|
1932
|
+
validateRetainedRunProof(entry, input, 'Container native run proof');
|
|
1933
|
+
}
|
|
1934
|
+
return { dockerfileDigest, evidence, input, repositoryName };
|
|
1935
|
+
});
|
|
1936
|
+
const assemblyCrane = stage === 'development' ? await acquireCrane() : undefined;
|
|
1937
|
+
try {
|
|
1938
|
+
login();
|
|
1939
|
+
const artifacts = [];
|
|
1940
|
+
for (const prepared of preparedArtifacts) {
|
|
1941
|
+
const { dockerfileDigest, evidence, input, repositoryName } = prepared;
|
|
1942
|
+
ensureRepository(repositoryName);
|
|
1943
|
+
const tag =
|
|
1944
|
+
'candidate-' + sourceCommit + '-' + runId + '-' + runAttempt + '-index';
|
|
1945
|
+
const taggedReference = registry + '/' + repositoryName + ':' + tag;
|
|
1946
|
+
if (assemblyCrane) {
|
|
1947
|
+
if (!developmentSourceArtifact) {
|
|
1948
|
+
throw new Error('Development retirement source index is absent.');
|
|
1949
|
+
}
|
|
1950
|
+
execute(
|
|
1951
|
+
assemblyCrane.path,
|
|
1952
|
+
['copy', developmentSourceArtifact.imageReference, taggedReference],
|
|
1953
|
+
'Exact staging retirement OCI index promotion',
|
|
1954
|
+
);
|
|
1955
|
+
} else {
|
|
1956
|
+
execute(
|
|
1957
|
+
'docker',
|
|
1958
|
+
[
|
|
1959
|
+
'buildx',
|
|
1960
|
+
'imagetools',
|
|
1961
|
+
'create',
|
|
1962
|
+
'--tag',
|
|
1963
|
+
taggedReference,
|
|
1964
|
+
...evidence.map(({ imageReference }) => imageReference),
|
|
1965
|
+
],
|
|
1966
|
+
'Dual-architecture OCI index assembly',
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
const imageDigest = exactImageByTag(repositoryName, tag);
|
|
1970
|
+
const imageReference = registry + '/' + repositoryName + '@' + imageDigest;
|
|
1971
|
+
if (
|
|
1972
|
+
stage === 'development' &&
|
|
1973
|
+
developmentSourceArtifact?.imageDigest !== imageDigest
|
|
1974
|
+
) {
|
|
1975
|
+
throw new Error(
|
|
1976
|
+
'Development retirement OCI index differs from exact staging.',
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
const rawIndex = String(
|
|
1980
|
+
execute(
|
|
1981
|
+
'docker',
|
|
1982
|
+
['buildx', 'imagetools', 'inspect', '--raw', imageReference],
|
|
1983
|
+
'Dual-architecture OCI index inspection',
|
|
1984
|
+
),
|
|
1985
|
+
);
|
|
1986
|
+
let index;
|
|
1987
|
+
try {
|
|
1988
|
+
index = JSON.parse(rawIndex);
|
|
1989
|
+
} catch {
|
|
1990
|
+
throw new Error('OCI index inspection returned invalid JSON.');
|
|
1991
|
+
}
|
|
1992
|
+
const rawDescriptors = Array.isArray(index?.manifests)
|
|
1993
|
+
? index.manifests
|
|
1994
|
+
: [];
|
|
1995
|
+
const descriptors = rawDescriptors
|
|
1996
|
+
.map((descriptor) => ({
|
|
1997
|
+
digest: descriptor?.digest,
|
|
1998
|
+
platform:
|
|
1999
|
+
String(descriptor?.platform?.os) + '/' +
|
|
2000
|
+
String(descriptor?.platform?.architecture),
|
|
2001
|
+
}))
|
|
2002
|
+
.sort((left, right) =>
|
|
2003
|
+
left.platform < right.platform
|
|
2004
|
+
? -1
|
|
2005
|
+
: left.platform > right.platform
|
|
2006
|
+
? 1
|
|
2007
|
+
: 0,
|
|
2008
|
+
);
|
|
2009
|
+
if (
|
|
2010
|
+
![
|
|
2011
|
+
'application/vnd.oci.image.index.v1+json',
|
|
2012
|
+
'application/vnd.docker.distribution.manifest.list.v2+json',
|
|
2013
|
+
].includes(index?.mediaType) ||
|
|
2014
|
+
rawDescriptors.length !== CONTAINER_RELEASE_PLATFORMS.length ||
|
|
2015
|
+
descriptors.some(
|
|
2016
|
+
({ digest, platform }) =>
|
|
2017
|
+
!IMAGE_DIGEST.test(digest) ||
|
|
2018
|
+
!CONTAINER_RELEASE_PLATFORMS.includes(platform),
|
|
2019
|
+
) ||
|
|
2020
|
+
descriptors.some(
|
|
2021
|
+
(descriptor, index) =>
|
|
2022
|
+
descriptor.platform !== evidence[index]?.platform ||
|
|
2023
|
+
descriptor.digest !== evidence[index]?.imageDigest,
|
|
2024
|
+
)
|
|
2025
|
+
) {
|
|
2026
|
+
throw new Error('OCI index does not contain exactly the two proven platform images.');
|
|
2027
|
+
}
|
|
2028
|
+
artifacts.push({
|
|
2029
|
+
artifactId: input.declared.id,
|
|
2030
|
+
artifactName: input.declared.name,
|
|
2031
|
+
candidateArtifactDigest: input.candidateArtifactDigest,
|
|
2032
|
+
dockerfileDigest,
|
|
2033
|
+
imageDigest,
|
|
2034
|
+
imageReference,
|
|
2035
|
+
platforms: evidence,
|
|
2036
|
+
repositoryName,
|
|
2037
|
+
sbomParityDigest: supplyChainDigest(
|
|
2038
|
+
evidence.map((entry) => ({
|
|
2039
|
+
componentSetDigest: entry.sbom.componentSetDigest,
|
|
2040
|
+
platform: entry.platform,
|
|
2041
|
+
productionClosureDigest: entry.productionClosureDigest,
|
|
2042
|
+
runProof: entry.runProof,
|
|
2043
|
+
})),
|
|
2044
|
+
),
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
const builtAtInput = process.env.EBK_CONTAINER_BUILT_AT?.trim();
|
|
2048
|
+
const builtAt = builtAtInput ?? new Date().toISOString();
|
|
2049
|
+
if (!Number.isFinite(Date.parse(builtAt)) || new Date(builtAt).toISOString() !== builtAt) {
|
|
2050
|
+
throw new Error('EBK_CONTAINER_BUILT_AT must be canonical ISO-8601 when set.');
|
|
2051
|
+
}
|
|
2052
|
+
const binding = createContainerReleaseBinding({
|
|
2053
|
+
accountId,
|
|
2054
|
+
artifacts,
|
|
2055
|
+
builtAt,
|
|
2056
|
+
provenance: { runAttempt, runId, workflow },
|
|
2057
|
+
region,
|
|
2058
|
+
slice,
|
|
2059
|
+
sourceCommit,
|
|
2060
|
+
stage,
|
|
2061
|
+
});
|
|
2062
|
+
const output = resolve(
|
|
2063
|
+
repositoryRoot,
|
|
2064
|
+
'.ebk/evidence/container-release',
|
|
2065
|
+
stage,
|
|
2066
|
+
'packages',
|
|
2067
|
+
slice,
|
|
2068
|
+
'binding.json',
|
|
2069
|
+
);
|
|
2070
|
+
immutableWrite(output, binding);
|
|
2071
|
+
process.stdout.write(
|
|
2072
|
+
JSON.stringify({
|
|
2073
|
+
artifactCount: artifacts.length,
|
|
2074
|
+
bindingDigest: binding.materialDigest,
|
|
2075
|
+
evidencePath: output,
|
|
2076
|
+
schemaVersion: 1,
|
|
2077
|
+
stage,
|
|
2078
|
+
}) + '\\n',
|
|
2079
|
+
);
|
|
2080
|
+
} finally {
|
|
2081
|
+
if (assemblyCrane) {
|
|
2082
|
+
rmSync(assemblyCrane.temporary, { force: true, recursive: true });
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
`;
|
|
2086
|
+
}
|
|
2087
|
+
//# sourceMappingURL=generated-container-release-command.js.map
|