@kungfu-tech/buildchain 3.0.9-alpha.9 → 4.0.0-alpha.1
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/AGENTS.md +39 -0
- package/README.md +17 -13
- package/actions/promote-buildchain-ref/README.md +0 -10
- package/actions/release-tail/README.md +6 -10
- package/architecture/internal-capabilities.json +502 -0
- package/architecture/maintainability-baseline.json +59 -0
- package/architecture/maintainability-policy.json +1384 -0
- package/architecture/release-tail-contract-inventory.json +587 -0
- package/architecture/v3-core-mechanism-inventory.json +1043 -0
- package/architecture/v3-core-mechanism-inventory.md +67 -0
- package/architecture/v4-adopter-delivery-parity.json +47 -0
- package/architecture/v4-architecture-constitution.md +210 -0
- package/architecture/v4-bootstrap-authority.json +56 -0
- package/architecture/v4-canonical-contract-fixtures.json +105 -0
- package/architecture/v4-capability-state-machine-manifest.json +480 -0
- package/architecture/v4-capability-state-machine-manifest.schema.json +202 -0
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +830 -0
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.md +223 -0
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.schema.json +318 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +227 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.schema.json +67 -0
- package/architecture/v4-exception-ledger.json +5 -0
- package/architecture/v4-exception-ledger.schema.json +56 -0
- package/architecture/v4-partial-mutation-recovery-qualification.json +65 -0
- package/architecture/v4-platform-stage-checkpoints.json +97 -0
- package/architecture/v4-provider-operation-journal-contract.json +113 -0
- package/architecture/v4-release-activation-shadow-domain.json +61 -0
- package/architecture/v4-release-train-parity.json +68 -0
- package/architecture/v4-rust-libnode-bridge-evaluation.json +107 -0
- package/architecture/v4-rust-libnode-bridge-spike.md +85 -0
- package/architecture/v4-stable-publication-fence.json +66 -0
- package/architecture/v4-stage-capsule-contract.json +90 -0
- package/architecture/v4-stage-capsule-qualification.json +99 -0
- package/architecture/v4-stage-capsule-resume-planner.json +52 -0
- package/architecture/v4-stage-capsule-store-contract.json +72 -0
- package/bin/buildchain.mjs +15 -10
- package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
- package/contracts/buildchain-v2-residuals-v1.json +2 -2
- package/contracts/fixtures/v4-delivery-warrant-trace-v1/golden.json +158 -0
- package/contracts/fixtures/v4-delivery-warrant-trace-v1/property.json +380 -0
- package/contracts/fixtures/v4-delivery-warrant-trace-v1/replay.json +163 -0
- package/contracts/fixtures/v4-partial-mutation-recovery-v1/shared.json +155 -0
- package/contracts/fixtures/v4-provider-operation-journal-v1/shared.json +457 -0
- package/contracts/fixtures/v4-provider-readback-idempotency-v1/shared.json +682 -0
- package/contracts/fixtures/v4-release-activation-shadow-v1/shared.json +53 -0
- package/contracts/fixtures/v4-stable-publication-fence-v1/shared.json +56 -0
- package/contracts/fixtures/v4-stage-capsule-resume-v1/late-platform-failure.json +176 -0
- package/contracts/fixtures/v4-stage-capsule-store-v1/shared.json +132 -0
- package/contracts/fixtures/v4-stage-capsule-v1/shared.json +111 -0
- package/contracts/v4-canonical-contracts-v1.schema.json +182 -0
- package/contracts/v4-delivery-warrant-semantic-diff-report-v1.schema.json +284 -0
- package/contracts/v4-delivery-warrant-trace-v1.schema.json +215 -0
- package/contracts/v4-host-contract-v1.schema.json +135 -0
- package/contracts/v4-partial-mutation-recovery-v1.schema.json +168 -0
- package/contracts/v4-provider-operation-journal-v1.schema.json +381 -0
- package/contracts/v4-release-activation-shadow-v1.schema.json +186 -0
- package/contracts/v4-stable-publication-fence-v1.schema.json +125 -0
- package/contracts/v4-stage-capsule-resume-v1.schema.json +225 -0
- package/contracts/v4-stage-capsule-store-v1.schema.json +154 -0
- package/contracts/v4-stage-capsule-v1.schema.json +178 -0
- package/crates/buildchain-v4-bridge/Cargo.lock +213 -0
- package/crates/buildchain-v4-bridge/Cargo.toml +12 -0
- package/crates/buildchain-v4-bridge/src/main.rs +495 -0
- package/dist/site/agent-index.json +0 -4
- package/dist/site/artifact-schemas.json +0 -8
- package/dist/site/buildchain-contract.json +103 -397
- package/dist/site/buildchain-site.json +301 -388
- package/dist/site/capability-registry.json +9 -11
- package/dist/site/cli-registry.json +8 -53
- package/dist/site/controller-registry.json +48 -8
- package/dist/site/kfd-claims.json +129 -167
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +18 -49
- package/dist/site/node-api-registry.json +4345 -7345
- package/dist/site/page-registry.json +278 -346
- package/dist/site/public-surface-audit.json +85 -404
- package/dist/site/publication-authority-registry.json +11 -1
- package/dist/site/publication-registry.json +3 -6
- package/dist/site/release-provenance.json +3 -9
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +106 -0
- package/dist/site/site-manifest.json +21 -40
- package/dist/site/workflow-registry.json +78 -35
- package/docs/MAP.md +8 -5
- package/docs/auditable-demo.md +2 -2
- package/docs/aws-us-elastic-runner-burst-plane.md +7 -59
- package/docs/cli-reference.md +15 -213
- package/docs/cli.md +14 -14
- package/docs/dev-alpha-candidate-patrol.md +3 -11
- package/docs/dev-delivery-warrant.md +29 -194
- package/docs/engineering-housekeeper.md +12 -4
- package/docs/getting-started.md +1 -1
- package/docs/install.md +5 -5
- package/docs/kfd-support.md +2 -7
- package/docs/lifecycle-protocol.md +2 -4
- package/docs/node-api-reference.md +414 -554
- package/docs/release-flow.md +31 -31
- package/docs/release-governance.md +16 -28
- package/docs/release-passport.md +2 -15
- package/docs/release-tail-contract.md +13 -6
- package/docs/release-tail-provider-plane.md +13 -19
- package/docs/release-train.md +4 -4
- package/docs/reusable-build-surface.md +14 -27
- package/docs/site-bundle-contract.md +1 -5
- package/docs/stable-candidate-patrol.md +7 -7
- package/docs/v4-canonical-contracts.md +92 -0
- package/docs/v4-delivery-warrant-read-candidate.md +73 -0
- package/docs/v4-delivery-warrant-semantic-diff.md +75 -0
- package/docs/v4-production-release.md +63 -0
- package/docs/v4-stage-capsule.md +193 -0
- package/docs/versioning.md +3 -5
- package/package.json +19 -19
- package/packages/core/adopter-delivery-gate.js +576 -0
- package/packages/core/adopter-delivery-json.js +66 -0
- package/packages/core/artifact-signing.js +0 -61
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/build-facts.js +12 -4
- package/packages/core/buildchain-agent-manuals.js +0 -2
- package/packages/core/buildchain-channel-identity.js +3 -2
- package/packages/core/buildchain-config.js +4 -69
- package/packages/core/buildchain-contract.js +232 -46
- package/packages/core/buildchain-kfd-claims.js +1 -1
- package/packages/core/channel-candidate.js +0 -8
- package/packages/core/channel-promotion-baseline.js +0 -52
- package/packages/core/controller-evidence.js +3 -4
- package/packages/core/dev-alpha-candidate-selection.js +2 -10
- package/packages/core/dev-delivery-warrant-cancellation.js +0 -1
- package/packages/core/dev-delivery-warrant-settlement.js +20 -67
- package/packages/core/dev-delivery-warrant.js +50 -138
- package/packages/core/diagnostics.js +3 -8
- package/packages/core/github-governance-authority.js +3 -1
- package/packages/core/kfd-adopter-category-driver.js +167 -0
- package/packages/core/kfd-adopter-manifest.js +47 -46
- package/packages/core/kfd-gate.js +15 -45
- package/packages/core/paper-agent-entry.js +7 -16
- package/packages/core/paper-fleet.js +2 -2
- package/packages/core/paper-repository.js +2 -3
- package/packages/core/paper-scaffold-content.js +2 -23
- package/packages/core/paper.js +11 -43
- package/packages/core/publication-reproducibility.js +4 -4
- package/packages/core/release-line-bootstrap.js +21 -8
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +20 -131
- package/packages/core/self-dogfood-version.js +48 -35
- package/packages/core/spawn-command.js +232 -8
- package/packages/core/v4-adopter-delivery-parity.js +219 -0
- package/packages/core/v4-canonical-contracts.js +284 -0
- package/packages/core/v4-delivery-warrant-fixture-runner.js +371 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +279 -0
- package/packages/core/v4-delivery-warrant-semantic-diff-gate.js +352 -0
- package/packages/core/v4-delivery-warrant-shadow-adapter.js +447 -0
- package/packages/core/v4-partial-mutation-recovery-qualification.js +395 -0
- package/packages/core/v4-platform-stage-checkpoints.js +471 -0
- package/packages/core/v4-provider-operation-journal.js +531 -0
- package/packages/core/v4-provider-readback-idempotency.js +394 -0
- package/packages/core/v4-release-activation-shadow.js +519 -0
- package/packages/core/v4-stable-publication-fence.js +357 -0
- package/packages/core/v4-stage-capsule-local-store.js +426 -0
- package/packages/core/v4-stage-capsule-qualification-campaign.js +512 -0
- package/packages/core/v4-stage-capsule-qualification.js +456 -0
- package/packages/core/v4-stage-capsule-resume-planner.js +397 -0
- package/packages/core/v4-stage-capsule-store.js +401 -0
- package/packages/core/v4-stage-capsule.js +319 -0
- package/scripts/assemble-self-publication-admission.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +5 -5
- package/scripts/auditable-demo-bundle-verification.mjs +3 -2
- package/scripts/auditable-demo-platform.mjs +2 -2
- package/scripts/auditable-demo-renditions.mjs +1 -1
- package/scripts/auditable-demo.mjs +2 -2
- package/scripts/aws-macos-jit-controller-core.mjs +13 -346
- package/scripts/aws-macos-jit-controller-runtime.mjs +2 -210
- package/scripts/aws-macos-jit-controller.mjs +37 -275
- package/scripts/aws-macos-jit-core.mjs +3 -41
- package/scripts/aws-macos-jit.mjs +0 -12
- package/scripts/aws-windows-jit-campaign.mjs +2 -2
- package/scripts/aws-windows-jit-controller-core.mjs +1 -1
- package/scripts/aws-windows-jit-controller.mjs +3 -3
- package/scripts/build-contract-core.mjs +3 -8
- package/scripts/build-standalone-binary.mjs +3 -14
- package/scripts/buildchain-channel-router.mjs +4 -0
- package/scripts/buildchain-cli-help.mjs +7 -16
- package/scripts/buildchain-contract-lock.mjs +0 -6
- package/scripts/check-action-bundles.mjs +3 -4
- package/scripts/check-internal-architecture.mjs +7 -6
- package/scripts/check-inventory.mjs +30 -25
- package/scripts/check-v4-public-dogfood-contract.mjs +270 -0
- package/scripts/create-release-bundle.mjs +8 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +1 -22
- package/scripts/dev-delivery-proof.mjs +2 -64
- package/scripts/dev-delivery-warrant.mjs +61 -86
- package/scripts/dev-pr-auto-merge.mjs +4 -30
- package/scripts/dev-pr-delivery-warrant.mjs +0 -55
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -4
- package/scripts/generate-buildchain-kfd-witnesses.mjs +94 -88
- package/scripts/generate-channel-build-workflow.mjs +54 -24
- package/scripts/generate-channel-promotion-workflow.mjs +15 -6
- package/scripts/generate-site-bundle.mjs +8 -27
- package/scripts/git-fetch-process-tree.mjs +34 -1
- package/scripts/init-repo.mjs +64 -159
- package/scripts/inspect-artifact-signing-requests.mjs +0 -6
- package/scripts/locked-source-checkout.mjs +3 -4
- package/scripts/maintainability-metrics.mjs +8 -2
- package/scripts/npm-publish-dry-run.mjs +2 -2
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/publication-commit-evidence.mjs +23 -69
- package/scripts/release-candidate-resolver.mjs +11 -17
- package/scripts/release-tail.mjs +3 -49
- package/scripts/resume-from-candidate-run.mjs +9 -123
- package/scripts/run-lifecycle-core.mjs +12 -13
- package/scripts/seal-artifact-signing-requests.mjs +17 -21
- package/scripts/site-capability-metadata.mjs +3 -20
- package/scripts/stable-candidate-qualification.mjs +10 -10
- package/scripts/v4-architecture.mjs +12 -2
- package/scripts/v4-bridge-bootstrap.mjs +141 -0
- package/scripts/v4-bridge-evidence.mjs +141 -0
- package/scripts/v4-host-adapter.mjs +182 -0
- package/scripts/v4-platform-stage-checkpoint-rehearsal.mjs +207 -0
- package/scripts/v4-stage-capsule-qualification.mjs +528 -0
- package/scripts/v4-stage-capsule-resume-rehearsal.mjs +57 -0
- package/scripts/v4-warrant-shadow-plan.mjs +601 -0
- package/scripts/verify-golden-path.mjs +5 -5
- package/scripts/web-surface-cloudfront-rewrite.mjs +15 -15
- package/scripts/web-surface-core.mjs +2 -8
- package/scripts/workflow-call-contract.mjs +5 -184
- package/contracts/dev-delivery-authority-v2.schema.json +0 -253
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +0 -16
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +0 -57
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +0 -47
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +0 -47
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +0 -40
- package/contracts/next-development-request-v1.schema.json +0 -66
- package/contracts/next-development-transition-v1.schema.json +0 -292
- package/contracts/publication-rehearsal-capsule-v1.schema.json +0 -173
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +0 -442
- package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +0 -269
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +0 -353
- package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +0 -94
- package/docs/dev-delivery-qualification-landing-adr.md +0 -137
- package/docs/next-development-transition.md +0 -119
- package/docs/publication-rehearsal.md +0 -94
- package/packages/core/buildchain-compatibility-proof.js +0 -631
- package/packages/core/dev-delivery-authority.js +0 -996
- package/packages/core/dev-delivery-native-proof.js +0 -418
- package/packages/core/dev-delivery-warrant-shadow.js +0 -502
- package/packages/core/next-development-candidate-reservation.js +0 -186
- package/packages/core/next-development-controller.js +0 -726
- package/packages/core/next-development-projection.js +0 -288
- package/packages/core/next-development-transition.js +0 -738
- package/packages/core/publication-rehearsal-projection.js +0 -173
- package/packages/core/publication-rehearsal-runtime.js +0 -921
- package/scripts/aws-macos-jit-instance-rehydrate.mjs +0 -260
- package/scripts/aws-macos-jit-source-rebind.mjs +0 -724
- package/scripts/dev-delivery-authority.mjs +0 -368
- package/scripts/dev-delivery-native-run.mjs +0 -235
- package/scripts/dev-delivery-two-phase-resume.mjs +0 -90
- package/scripts/dev-delivery-two-phase.mjs +0 -577
- package/scripts/generate-next-development-guidance.mjs +0 -49
- package/scripts/materialize-self-release-candidate-version.mjs +0 -137
- package/scripts/next-development-self-dogfood-harness.mjs +0 -409
- package/scripts/next-development-self-dogfood.mjs +0 -532
- package/scripts/next-development-transition.mjs +0 -47
- package/scripts/release-candidate-tail-reseal.mjs +0 -426
- package/templates/native-dev-delivery.yml +0 -86
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { digest } from "./aws-runner-burst-core.mjs";
|
|
4
4
|
import {
|
|
5
5
|
MACOS_EC2_JIT,
|
|
6
|
-
macosJitRegionConfig,
|
|
7
6
|
macosJitRunnerLabel,
|
|
8
7
|
macosJitRunnerLabels,
|
|
9
8
|
} from "./aws-macos-jit-core.mjs";
|
|
@@ -56,17 +55,6 @@ function source(values) {
|
|
|
56
55
|
};
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
function previousSource(values) {
|
|
60
|
-
return {
|
|
61
|
-
sha: exactSha(values.previousSourceSha),
|
|
62
|
-
ref: exact(
|
|
63
|
-
values.previousSourceRef || values.sourceRef,
|
|
64
|
-
/^refs\/heads\/[A-Za-z0-9._/-]+$/,
|
|
65
|
-
"previousSourceRef",
|
|
66
|
-
),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
58
|
function campaignId(value) {
|
|
71
59
|
return exact(value, /^[a-z0-9][a-z0-9-]{2,47}$/, "campaignId");
|
|
72
60
|
}
|
|
@@ -75,110 +63,15 @@ function qualificationId(value) {
|
|
|
75
63
|
return exact(value, /^mac-(?:smoke-0[12]|full-01)$/, "qualificationId");
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
function terminalFailureRunIds(value) {
|
|
79
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
80
|
-
throw new Error("terminalFailureRunIds must be a non-empty array");
|
|
81
|
-
}
|
|
82
|
-
const normalized = value.map((entry) =>
|
|
83
|
-
exact(entry, /^[1-9]\d*$/, "terminalFailureRunId"),
|
|
84
|
-
);
|
|
85
|
-
if (new Set(normalized).size !== normalized.length) {
|
|
86
|
-
throw new Error("terminalFailureRunIds must be unique");
|
|
87
|
-
}
|
|
88
|
-
return [...normalized].sort((left, right) => Number(left) - Number(right));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function preflightFailureRunIds(value) {
|
|
92
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
93
|
-
throw new Error("preflightFailureRunIds must be a non-empty array");
|
|
94
|
-
}
|
|
95
|
-
const normalized = value.map((entry) =>
|
|
96
|
-
exact(entry, /^[1-9]\d*$/, "preflightFailureRunId"),
|
|
97
|
-
);
|
|
98
|
-
if (new Set(normalized).size !== normalized.length) {
|
|
99
|
-
throw new Error("preflightFailureRunIds must be unique");
|
|
100
|
-
}
|
|
101
|
-
return [...normalized].sort((left, right) => Number(left) - Number(right));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function successfulRunIds(value) {
|
|
105
|
-
if (value === undefined) return [];
|
|
106
|
-
if (!Array.isArray(value)) {
|
|
107
|
-
throw new Error("successfulRunIds must be an array");
|
|
108
|
-
}
|
|
109
|
-
const normalized = value.map((entry) =>
|
|
110
|
-
exact(entry, /^[1-9]\d*$/, "successfulRunId"),
|
|
111
|
-
);
|
|
112
|
-
if (new Set(normalized).size !== normalized.length) {
|
|
113
|
-
throw new Error("successfulRunIds must be unique");
|
|
114
|
-
}
|
|
115
|
-
return [...normalized].sort((left, right) => Number(left) - Number(right));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function startupFailureRuns(value) {
|
|
119
|
-
if (value === undefined) return [];
|
|
120
|
-
if (!Array.isArray(value)) {
|
|
121
|
-
throw new Error("startupFailureRuns must be an array");
|
|
122
|
-
}
|
|
123
|
-
const normalized = value.map((entry) => ({
|
|
124
|
-
runId: exact(entry?.runId, /^[1-9]\d*$/, "startupFailureRunId"),
|
|
125
|
-
sourceSha: exactSha(entry?.sourceSha),
|
|
126
|
-
}));
|
|
127
|
-
if (
|
|
128
|
-
new Set(normalized.map(({ runId }) => runId)).size !== normalized.length
|
|
129
|
-
) {
|
|
130
|
-
throw new Error("startupFailureRuns must use unique run ids");
|
|
131
|
-
}
|
|
132
|
-
return [...normalized].sort(
|
|
133
|
-
(left, right) => Number(left.runId) - Number(right.runId),
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function historicalTerminalFailureRuns(value) {
|
|
138
|
-
if (value === undefined) return [];
|
|
139
|
-
if (!Array.isArray(value)) {
|
|
140
|
-
throw new Error("historicalTerminalFailureRuns must be an array");
|
|
141
|
-
}
|
|
142
|
-
const normalized = value.map((entry) => ({
|
|
143
|
-
runId: exact(entry?.runId, /^[1-9]\d*$/, "historicalTerminalFailureRunId"),
|
|
144
|
-
sourceSha: exactSha(entry?.sourceSha),
|
|
145
|
-
}));
|
|
146
|
-
if (
|
|
147
|
-
new Set(normalized.map(({ runId }) => runId)).size !== normalized.length
|
|
148
|
-
) {
|
|
149
|
-
throw new Error("historicalTerminalFailureRuns must use unique run ids");
|
|
150
|
-
}
|
|
151
|
-
return [...normalized].sort(
|
|
152
|
-
(left, right) => Number(left.runId) - Number(right.runId),
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function historicalPreflightFailureRuns(value) {
|
|
157
|
-
if (value === undefined) return [];
|
|
158
|
-
if (!Array.isArray(value)) {
|
|
159
|
-
throw new Error("historicalPreflightFailureRuns must be an array");
|
|
160
|
-
}
|
|
161
|
-
const normalized = value.map((entry) => ({
|
|
162
|
-
runId: exact(entry?.runId, /^[1-9]\d*$/, "historicalPreflightFailureRunId"),
|
|
163
|
-
sourceSha: exactSha(entry?.sourceSha),
|
|
164
|
-
}));
|
|
165
|
-
if (
|
|
166
|
-
new Set(normalized.map(({ runId }) => runId)).size !== normalized.length
|
|
167
|
-
) {
|
|
168
|
-
throw new Error("historicalPreflightFailureRuns must use unique run ids");
|
|
169
|
-
}
|
|
170
|
-
return [...normalized].sort(
|
|
171
|
-
(left, right) => Number(left.runId) - Number(right.runId),
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
66
|
function commonAws(values) {
|
|
176
67
|
const region = exact(
|
|
177
68
|
values.region || MACOS_EC2_JIT.region,
|
|
178
69
|
/^us-[a-z]+-\d$/,
|
|
179
70
|
"region",
|
|
180
71
|
);
|
|
181
|
-
|
|
72
|
+
if (region !== MACOS_EC2_JIT.region) {
|
|
73
|
+
throw new Error(`region must be ${MACOS_EC2_JIT.region}`);
|
|
74
|
+
}
|
|
182
75
|
const instanceType = exact(
|
|
183
76
|
values.instanceType || MACOS_EC2_JIT.instanceType,
|
|
184
77
|
/^mac2\.metal$/,
|
|
@@ -187,18 +80,13 @@ function commonAws(values) {
|
|
|
187
80
|
if (instanceType !== MACOS_EC2_JIT.instanceType) {
|
|
188
81
|
throw new Error(`instanceType must be ${MACOS_EC2_JIT.instanceType}`);
|
|
189
82
|
}
|
|
190
|
-
const availabilityZone = exact(
|
|
191
|
-
values.availabilityZone,
|
|
192
|
-
/^us-[a-z]+-\d[a-z]$/,
|
|
193
|
-
"availabilityZone",
|
|
194
|
-
);
|
|
195
|
-
if (!availabilityZone.startsWith(region)) {
|
|
196
|
-
throw new Error(`availabilityZone must belong to ${region}`);
|
|
197
|
-
}
|
|
198
83
|
return {
|
|
199
84
|
region,
|
|
200
|
-
availabilityZone
|
|
201
|
-
|
|
85
|
+
availabilityZone: exact(
|
|
86
|
+
values.availabilityZone,
|
|
87
|
+
/^us-[a-z]+-\d[a-z]$/,
|
|
88
|
+
"availabilityZone",
|
|
89
|
+
),
|
|
202
90
|
instanceType,
|
|
203
91
|
amiId: exact(values.amiId, /^ami-[0-9a-f]+$/, "amiId"),
|
|
204
92
|
amiName: exact(values.amiName, /^[A-Za-z0-9._-]+$/, "amiName"),
|
|
@@ -235,7 +123,6 @@ export function createMacosJitCampaignPlan(values = {}) {
|
|
|
235
123
|
const id = campaignId(values.campaignId);
|
|
236
124
|
const boundSource = source(values);
|
|
237
125
|
const aws = commonAws(values);
|
|
238
|
-
const regionConfig = macosJitRegionConfig(aws.region);
|
|
239
126
|
const tags = ownershipTags({ id, sourceSha: boundSource.sha });
|
|
240
127
|
const createdAt = iso(values.createdAt, "createdAt");
|
|
241
128
|
const plan = {
|
|
@@ -243,13 +130,6 @@ export function createMacosJitCampaignPlan(values = {}) {
|
|
|
243
130
|
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
244
131
|
kind: "campaign-launch-plan",
|
|
245
132
|
repository: repository(values.repository),
|
|
246
|
-
account: {
|
|
247
|
-
id: exact(values.accountId, /^\d{12}$/, "accountId"),
|
|
248
|
-
},
|
|
249
|
-
github: {
|
|
250
|
-
workflowId: MACOS_EC2_JIT.workflowId,
|
|
251
|
-
requiredState: "disabled_manually",
|
|
252
|
-
},
|
|
253
133
|
campaign: { id, createdAt },
|
|
254
134
|
source: boundSource,
|
|
255
135
|
aws: {
|
|
@@ -278,232 +158,18 @@ export function createMacosJitCampaignPlan(values = {}) {
|
|
|
278
158
|
exactSourceRequired: true,
|
|
279
159
|
activeHostCeiling: MACOS_EC2_JIT.maxAcceptedHosts,
|
|
280
160
|
activeInstanceCeiling: 1,
|
|
281
|
-
|
|
161
|
+
awsDryRunRequiredBeforeAllocation: true,
|
|
282
162
|
awsDryRunRequiredBeforeLaunch: true,
|
|
283
163
|
retainHostOnInstanceLaunchFailure: true,
|
|
284
164
|
minimumHostAllocationHours: MACOS_EC2_JIT.minimumHostAllocationHours,
|
|
285
165
|
maximumHostAllocationHours: MACOS_EC2_JIT.maximumHostAllocationHours,
|
|
286
166
|
cleanupOwner: "scheduled-card-scoped-reaper",
|
|
287
|
-
budget: {
|
|
288
|
-
name: regionConfig.budgetName,
|
|
289
|
-
limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
|
|
290
|
-
metrics: ["UnblendedCost"],
|
|
291
|
-
dimensionFilter: {
|
|
292
|
-
usageTypes: regionConfig.budgetUsageTypes,
|
|
293
|
-
operation: MACOS_EC2_JIT.budgetOperation,
|
|
294
|
-
regions: regionConfig.budgetRegions,
|
|
295
|
-
},
|
|
296
|
-
requiredActualThresholds: [80, 95],
|
|
297
|
-
},
|
|
298
|
-
},
|
|
299
|
-
};
|
|
300
|
-
return { ...plan, digest: digest(plan) };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function createMacosJitInstanceRehydratePlan(values = {}) {
|
|
304
|
-
const id = campaignId(values.campaignId);
|
|
305
|
-
const boundSource = source(values);
|
|
306
|
-
const aws = commonAws(values);
|
|
307
|
-
const regionConfig = macosJitRegionConfig(aws.region);
|
|
308
|
-
const tags = ownershipTags({ id, sourceSha: boundSource.sha });
|
|
309
|
-
const rehydrationId = exact(
|
|
310
|
-
values.rehydrationId,
|
|
311
|
-
/^r[1-9]\d{0,2}$/,
|
|
312
|
-
"rehydrationId",
|
|
313
|
-
);
|
|
314
|
-
const instanceClientToken = `kungfu-mac-rehydrate-${id}-${rehydrationId}`;
|
|
315
|
-
if (instanceClientToken.length > 64) {
|
|
316
|
-
throw new Error("rehydration client token exceeds the EC2 limit");
|
|
317
|
-
}
|
|
318
|
-
const plan = {
|
|
319
|
-
schemaVersion: 1,
|
|
320
|
-
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
321
|
-
kind: "instance-rehydrate-plan",
|
|
322
|
-
repository: repository(values.repository),
|
|
323
|
-
account: {
|
|
324
|
-
id: exact(values.accountId, /^\d{12}$/, "accountId"),
|
|
325
|
-
},
|
|
326
|
-
github: {
|
|
327
|
-
workflowId: MACOS_EC2_JIT.workflowId,
|
|
328
|
-
requiredState: "disabled_manually",
|
|
329
|
-
},
|
|
330
|
-
campaign: { id },
|
|
331
|
-
source: boundSource,
|
|
332
|
-
aws: {
|
|
333
|
-
...aws,
|
|
334
|
-
hostId: exact(values.hostId, /^h-[0-9a-f]+$/, "hostId"),
|
|
335
|
-
hostAllocatedAt: iso(values.hostAllocatedAt, "hostAllocatedAt"),
|
|
336
|
-
replacesInstanceId: exact(
|
|
337
|
-
values.replacesInstanceId,
|
|
338
|
-
/^i-[0-9a-f]+$/,
|
|
339
|
-
"replacesInstanceId",
|
|
340
|
-
),
|
|
341
|
-
rehydrationId,
|
|
342
|
-
instanceTags: tags,
|
|
343
|
-
volumeTags: tags,
|
|
344
|
-
instanceClientToken,
|
|
345
|
-
rootVolume: {
|
|
346
|
-
deviceName: "/dev/sda1",
|
|
347
|
-
deleteOnTermination: true,
|
|
348
|
-
encrypted: true,
|
|
349
|
-
volumeSizeGiB: 200,
|
|
350
|
-
volumeType: "gp3",
|
|
351
|
-
},
|
|
352
|
-
metadata: {
|
|
353
|
-
httpEndpoint: "enabled",
|
|
354
|
-
httpTokens: "required",
|
|
355
|
-
httpPutResponseHopLimit: 1,
|
|
356
|
-
instanceMetadataTags: "disabled",
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
safety: {
|
|
360
|
-
applyMode: values.execute === true ? "execute" : "dry-run",
|
|
361
|
-
exactSourceRequired: true,
|
|
362
|
-
existingCampaignHostRequired: true,
|
|
363
|
-
noHostAllocation: true,
|
|
364
|
-
zeroActiveInstanceRequired: true,
|
|
365
|
-
zeroRunnerResidueRequired: true,
|
|
366
|
-
awsDryRunRequiredBeforeLaunch: true,
|
|
367
|
-
cleanupOwner: "scheduled-card-scoped-reaper",
|
|
368
|
-
budget: {
|
|
369
|
-
name: regionConfig.budgetName,
|
|
370
|
-
limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
|
|
371
|
-
metrics: ["UnblendedCost"],
|
|
372
|
-
dimensionFilter: {
|
|
373
|
-
usageTypes: regionConfig.budgetUsageTypes,
|
|
374
|
-
operation: MACOS_EC2_JIT.budgetOperation,
|
|
375
|
-
regions: regionConfig.budgetRegions,
|
|
376
|
-
},
|
|
377
|
-
requiredActualThresholds: [80, 95],
|
|
378
|
-
},
|
|
379
167
|
},
|
|
380
168
|
};
|
|
381
169
|
return { ...plan, digest: digest(plan) };
|
|
382
170
|
}
|
|
383
171
|
|
|
384
|
-
export function
|
|
385
|
-
const id = campaignId(values.campaignId);
|
|
386
|
-
const boundSource = source(values);
|
|
387
|
-
const priorSource = previousSource(values);
|
|
388
|
-
if (boundSource.sha === priorSource.sha) {
|
|
389
|
-
throw new Error("sourceSha must differ from previousSourceSha");
|
|
390
|
-
}
|
|
391
|
-
if (boundSource.ref !== priorSource.ref) {
|
|
392
|
-
throw new Error("sourceRef must remain unchanged during campaign rebind");
|
|
393
|
-
}
|
|
394
|
-
const aws = commonAws(values);
|
|
395
|
-
const regionConfig = macosJitRegionConfig(aws.region);
|
|
396
|
-
const workflowId = exact(values.workflowId, /^\d+$/, "workflowId");
|
|
397
|
-
if (workflowId !== MACOS_EC2_JIT.workflowId) {
|
|
398
|
-
throw new Error(`workflowId must be ${MACOS_EC2_JIT.workflowId}`);
|
|
399
|
-
}
|
|
400
|
-
const priorRunPolicy = values.priorRunPolicy || "unused";
|
|
401
|
-
if (
|
|
402
|
-
!["unused", "terminal-failure", "preflight-failure"].includes(
|
|
403
|
-
priorRunPolicy,
|
|
404
|
-
)
|
|
405
|
-
) {
|
|
406
|
-
throw new Error(
|
|
407
|
-
"priorRunPolicy must be unused, terminal-failure, or preflight-failure",
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
const terminalRunIds =
|
|
411
|
-
priorRunPolicy === "terminal-failure"
|
|
412
|
-
? terminalFailureRunIds(values.terminalFailureRunIds)
|
|
413
|
-
: [];
|
|
414
|
-
const preflightRunIds =
|
|
415
|
-
priorRunPolicy === "preflight-failure"
|
|
416
|
-
? preflightFailureRunIds(values.preflightFailureRunIds)
|
|
417
|
-
: [];
|
|
418
|
-
const startupRuns = startupFailureRuns(values.startupFailureRuns);
|
|
419
|
-
const historicalTerminalRuns = historicalTerminalFailureRuns(
|
|
420
|
-
values.historicalTerminalFailureRuns,
|
|
421
|
-
);
|
|
422
|
-
const historicalPreflightRuns = historicalPreflightFailureRuns(
|
|
423
|
-
values.historicalPreflightFailureRuns,
|
|
424
|
-
);
|
|
425
|
-
const successfulRuns = successfulRunIds(values.successfulRunIds);
|
|
426
|
-
if (priorRunPolicy === "unused" && startupRuns.length !== 0) {
|
|
427
|
-
throw new Error("startupFailureRuns require a failed priorRunPolicy");
|
|
428
|
-
}
|
|
429
|
-
if (priorRunPolicy === "unused" && historicalTerminalRuns.length !== 0) {
|
|
430
|
-
throw new Error(
|
|
431
|
-
"historicalTerminalFailureRuns require a failed priorRunPolicy",
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
if (priorRunPolicy === "unused" && historicalPreflightRuns.length !== 0) {
|
|
435
|
-
throw new Error(
|
|
436
|
-
"historicalPreflightFailureRuns require a failed priorRunPolicy",
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
if (priorRunPolicy === "unused" && successfulRuns.length !== 0) {
|
|
440
|
-
throw new Error("successfulRunIds require a failed priorRunPolicy");
|
|
441
|
-
}
|
|
442
|
-
const plan = {
|
|
443
|
-
schemaVersion: 1,
|
|
444
|
-
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
445
|
-
kind: "campaign-source-rebind-plan",
|
|
446
|
-
repository: repository(values.repository),
|
|
447
|
-
account: {
|
|
448
|
-
id: exact(values.accountId, /^\d{12}$/, "accountId"),
|
|
449
|
-
},
|
|
450
|
-
campaign: { id },
|
|
451
|
-
previousSource: priorSource,
|
|
452
|
-
source: boundSource,
|
|
453
|
-
github: {
|
|
454
|
-
workflowId,
|
|
455
|
-
requiredState: "disabled_manually",
|
|
456
|
-
priorRunPolicy,
|
|
457
|
-
terminalFailureRunIds: terminalRunIds,
|
|
458
|
-
preflightFailureRunIds: preflightRunIds,
|
|
459
|
-
startupFailureRuns: startupRuns,
|
|
460
|
-
historicalTerminalFailureRuns: historicalTerminalRuns,
|
|
461
|
-
historicalPreflightFailureRuns: historicalPreflightRuns,
|
|
462
|
-
successfulRunIds: successfulRuns,
|
|
463
|
-
},
|
|
464
|
-
aws: {
|
|
465
|
-
...aws,
|
|
466
|
-
hostId: exact(values.hostId, /^h-[0-9a-f]+$/, "hostId"),
|
|
467
|
-
instanceId: exact(values.instanceId, /^i-[0-9a-f]+$/, "instanceId"),
|
|
468
|
-
},
|
|
469
|
-
safety: {
|
|
470
|
-
applyMode: values.execute === true ? "execute" : "dry-run",
|
|
471
|
-
noAllocation: true,
|
|
472
|
-
noDispatch: true,
|
|
473
|
-
forwardOnlySource: true,
|
|
474
|
-
sameSourceRefRequired: true,
|
|
475
|
-
workflowDisabledRequired: true,
|
|
476
|
-
zeroPriorJobsRequired: priorRunPolicy === "unused",
|
|
477
|
-
zeroPriorArtifactsRequired: priorRunPolicy === "unused",
|
|
478
|
-
terminalFailureEvidencePreserved: priorRunPolicy === "terminal-failure",
|
|
479
|
-
preflightFailureEvidencePreserved: priorRunPolicy === "preflight-failure",
|
|
480
|
-
startupFailureEvidencePreserved: startupRuns.length !== 0,
|
|
481
|
-
historicalTerminalFailureEvidencePreserved:
|
|
482
|
-
historicalTerminalRuns.length !== 0,
|
|
483
|
-
historicalPreflightFailureEvidencePreserved:
|
|
484
|
-
historicalPreflightRuns.length !== 0,
|
|
485
|
-
successfulRunEvidencePreserved: successfulRuns.length !== 0,
|
|
486
|
-
zeroJitResidueRequired: true,
|
|
487
|
-
zeroEvidenceRequired: true,
|
|
488
|
-
exactCampaignResourcesRequired: true,
|
|
489
|
-
compensatedRollbackRequired: true,
|
|
490
|
-
budget: {
|
|
491
|
-
name: regionConfig.budgetName,
|
|
492
|
-
limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
|
|
493
|
-
metrics: ["UnblendedCost"],
|
|
494
|
-
dimensionFilter: {
|
|
495
|
-
usageTypes: regionConfig.budgetUsageTypes,
|
|
496
|
-
operation: MACOS_EC2_JIT.budgetOperation,
|
|
497
|
-
regions: regionConfig.budgetRegions,
|
|
498
|
-
},
|
|
499
|
-
requiredActualThresholds: [80, 95],
|
|
500
|
-
},
|
|
501
|
-
},
|
|
502
|
-
};
|
|
503
|
-
return { ...plan, digest: digest(plan) };
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
export function macosAllocateHostsArgs(plan) {
|
|
172
|
+
export function macosAllocateHostsArgs(plan, { dryRun = false } = {}) {
|
|
507
173
|
if (
|
|
508
174
|
plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
|
|
509
175
|
plan.kind !== "campaign-launch-plan"
|
|
@@ -530,15 +196,16 @@ export function macosAllocateHostsArgs(plan) {
|
|
|
530
196
|
"--output",
|
|
531
197
|
"json",
|
|
532
198
|
];
|
|
199
|
+
if (dryRun) args.splice(2, 0, "--dry-run");
|
|
533
200
|
return args;
|
|
534
201
|
}
|
|
535
202
|
|
|
536
203
|
export function macosRunInstancesArgs(plan, { hostId, dryRun = false } = {}) {
|
|
537
204
|
if (
|
|
538
205
|
plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
|
|
539
|
-
|
|
206
|
+
plan.kind !== "campaign-launch-plan"
|
|
540
207
|
) {
|
|
541
|
-
throw new Error("macOS JIT
|
|
208
|
+
throw new Error("macOS JIT campaign launch plan contract is invalid");
|
|
542
209
|
}
|
|
543
210
|
const resolvedHostId = exact(hostId, /^h-[0-9a-f]+$/, "hostId");
|
|
544
211
|
const args = [
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSyncCommand } from "../packages/core/spawn-command.js";
|
|
4
4
|
|
|
5
5
|
export function commandResult(command, args, options = {}) {
|
|
6
|
-
const result =
|
|
6
|
+
const result = spawnSyncCommand(command, args, {
|
|
7
7
|
encoding: "utf8",
|
|
8
8
|
maxBuffer: 16 * 1024 * 1024,
|
|
9
9
|
stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
@@ -80,211 +80,3 @@ export function assertDryRun(result, label) {
|
|
|
80
80
|
throw new Error(`${label} did not return DryRunOperation`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
export function assertAllowedPolicySimulation(
|
|
85
|
-
plan,
|
|
86
|
-
profile,
|
|
87
|
-
principalArn,
|
|
88
|
-
actionName,
|
|
89
|
-
) {
|
|
90
|
-
if (!/^arn:aws:iam::\d{12}:(?:user|role)\/.+/.test(principalArn || "")) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
"AWS allocation policy simulation requires an IAM user or role principal ARN",
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
const contextEntries = [
|
|
96
|
-
`ContextKeyName=aws:RequestedRegion,ContextKeyValues=${plan.aws.region},ContextKeyType=string`,
|
|
97
|
-
...plan.aws.hostTags.map(
|
|
98
|
-
({ Key, Value }) =>
|
|
99
|
-
`ContextKeyName=aws:RequestTag/${Key},ContextKeyValues=${Value},ContextKeyType=string`,
|
|
100
|
-
),
|
|
101
|
-
];
|
|
102
|
-
const evaluation = awsJson(
|
|
103
|
-
plan,
|
|
104
|
-
profile,
|
|
105
|
-
[
|
|
106
|
-
"iam",
|
|
107
|
-
"simulate-principal-policy",
|
|
108
|
-
"--policy-source-arn",
|
|
109
|
-
principalArn,
|
|
110
|
-
"--action-names",
|
|
111
|
-
actionName,
|
|
112
|
-
"--resource-arns",
|
|
113
|
-
"*",
|
|
114
|
-
"--context-entries",
|
|
115
|
-
...contextEntries,
|
|
116
|
-
"--output",
|
|
117
|
-
"json",
|
|
118
|
-
],
|
|
119
|
-
`${actionName} IAM policy simulation`,
|
|
120
|
-
).EvaluationResults?.[0];
|
|
121
|
-
if (
|
|
122
|
-
evaluation?.EvalActionName !== actionName ||
|
|
123
|
-
evaluation?.EvalDecision !== "allowed" ||
|
|
124
|
-
(evaluation.MissingContextValues || []).length !== 0
|
|
125
|
-
) {
|
|
126
|
-
throw new Error(`${actionName} IAM policy simulation did not allow allocation`);
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
actionName,
|
|
130
|
-
decision: evaluation.EvalDecision,
|
|
131
|
-
principalArn,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function sameJson(left, right) {
|
|
136
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function sameDimensionSet(observed, expected) {
|
|
140
|
-
return (
|
|
141
|
-
Array.isArray(observed) &&
|
|
142
|
-
observed.length === expected.length &&
|
|
143
|
-
expected.every((entry) =>
|
|
144
|
-
observed.some((candidate) => sameJson(candidate, entry)),
|
|
145
|
-
)
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function assertMacosBudgetLaunchGate(plan, profile) {
|
|
150
|
-
const identity = awsJson(
|
|
151
|
-
plan,
|
|
152
|
-
profile,
|
|
153
|
-
["sts", "get-caller-identity", "--output", "json"],
|
|
154
|
-
"AWS account identity preflight",
|
|
155
|
-
);
|
|
156
|
-
if (identity.Account !== plan.account.id) {
|
|
157
|
-
throw new Error("AWS account identity mismatch");
|
|
158
|
-
}
|
|
159
|
-
const workflow = ghJson(
|
|
160
|
-
[
|
|
161
|
-
"api",
|
|
162
|
-
`repos/${plan.repository}/actions/workflows/${plan.github.workflowId}`,
|
|
163
|
-
],
|
|
164
|
-
undefined,
|
|
165
|
-
"GitHub macOS workflow preflight",
|
|
166
|
-
);
|
|
167
|
-
if (workflow.state !== plan.github.requiredState) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`macOS workflow must be ${plan.github.requiredState}; observed ${workflow.state || "unknown"}`,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
const stack = awsJson(
|
|
173
|
-
plan,
|
|
174
|
-
profile,
|
|
175
|
-
[
|
|
176
|
-
"cloudformation",
|
|
177
|
-
"describe-stacks",
|
|
178
|
-
"--stack-name",
|
|
179
|
-
plan.aws.controlPlaneStack,
|
|
180
|
-
"--output",
|
|
181
|
-
"json",
|
|
182
|
-
],
|
|
183
|
-
"macOS control-plane stack preflight",
|
|
184
|
-
).Stacks?.[0];
|
|
185
|
-
if (!stack || !/(?:CREATE|UPDATE)_COMPLETE$/.test(stack.StackStatus || "")) {
|
|
186
|
-
throw new Error("macOS control-plane stack is not complete");
|
|
187
|
-
}
|
|
188
|
-
const topicArn = (stack.Outputs || []).find(
|
|
189
|
-
(entry) => entry.OutputKey === "KillSwitchTopic",
|
|
190
|
-
)?.OutputValue;
|
|
191
|
-
if (!/^arn:aws:sns:/.test(String(topicArn || ""))) {
|
|
192
|
-
throw new Error("macOS Budget kill-switch topic readback failed");
|
|
193
|
-
}
|
|
194
|
-
const budget = awsJson(
|
|
195
|
-
plan,
|
|
196
|
-
profile,
|
|
197
|
-
[
|
|
198
|
-
"budgets",
|
|
199
|
-
"describe-budget",
|
|
200
|
-
"--account-id",
|
|
201
|
-
plan.account.id,
|
|
202
|
-
"--budget-name",
|
|
203
|
-
plan.safety.budget.name,
|
|
204
|
-
"--show-filter-expression",
|
|
205
|
-
"--output",
|
|
206
|
-
"json",
|
|
207
|
-
],
|
|
208
|
-
"macOS Budget preflight",
|
|
209
|
-
).Budget;
|
|
210
|
-
const expectedDimensions = [
|
|
211
|
-
["USAGE_TYPE", plan.safety.budget.dimensionFilter.usageTypes],
|
|
212
|
-
["OPERATION", [plan.safety.budget.dimensionFilter.operation]],
|
|
213
|
-
["REGION", plan.safety.budget.dimensionFilter.regions],
|
|
214
|
-
].map(([Key, Values]) => ({
|
|
215
|
-
Dimensions: { Key, Values, MatchOptions: ["EQUALS"] },
|
|
216
|
-
}));
|
|
217
|
-
if (
|
|
218
|
-
!budget ||
|
|
219
|
-
budget.BudgetName !== plan.safety.budget.name ||
|
|
220
|
-
Number(budget.BudgetLimit?.Amount) !== plan.safety.budget.limitUsd ||
|
|
221
|
-
budget.BudgetLimit?.Unit !== "USD" ||
|
|
222
|
-
budget.BudgetType !== "COST" ||
|
|
223
|
-
!sameJson(budget.Metrics, plan.safety.budget.metrics) ||
|
|
224
|
-
!sameDimensionSet(budget.FilterExpression?.And, expectedDimensions)
|
|
225
|
-
) {
|
|
226
|
-
throw new Error("macOS Budget identity or dimension filter mismatch");
|
|
227
|
-
}
|
|
228
|
-
const notifications = awsJson(
|
|
229
|
-
plan,
|
|
230
|
-
profile,
|
|
231
|
-
[
|
|
232
|
-
"budgets",
|
|
233
|
-
"describe-notifications-for-budget",
|
|
234
|
-
"--account-id",
|
|
235
|
-
plan.account.id,
|
|
236
|
-
"--budget-name",
|
|
237
|
-
plan.safety.budget.name,
|
|
238
|
-
"--output",
|
|
239
|
-
"json",
|
|
240
|
-
],
|
|
241
|
-
"macOS Budget notifications preflight",
|
|
242
|
-
).Notifications;
|
|
243
|
-
const thresholds = (notifications || [])
|
|
244
|
-
.filter(
|
|
245
|
-
(entry) =>
|
|
246
|
-
entry.NotificationType === "ACTUAL" &&
|
|
247
|
-
(entry.ThresholdType || "PERCENTAGE") === "PERCENTAGE",
|
|
248
|
-
)
|
|
249
|
-
.map((entry) => Number(entry.Threshold))
|
|
250
|
-
.sort((left, right) => left - right);
|
|
251
|
-
if (!sameJson(thresholds, plan.safety.budget.requiredActualThresholds)) {
|
|
252
|
-
throw new Error("macOS Budget notification thresholds mismatch");
|
|
253
|
-
}
|
|
254
|
-
for (const notification of notifications) {
|
|
255
|
-
const subscribers = awsJson(
|
|
256
|
-
plan,
|
|
257
|
-
profile,
|
|
258
|
-
[
|
|
259
|
-
"budgets",
|
|
260
|
-
"describe-subscribers-for-notification",
|
|
261
|
-
"--account-id",
|
|
262
|
-
plan.account.id,
|
|
263
|
-
"--budget-name",
|
|
264
|
-
plan.safety.budget.name,
|
|
265
|
-
"--notification",
|
|
266
|
-
JSON.stringify(notification),
|
|
267
|
-
"--output",
|
|
268
|
-
"json",
|
|
269
|
-
],
|
|
270
|
-
`macOS Budget ${notification.Threshold}% subscribers preflight`,
|
|
271
|
-
).Subscribers;
|
|
272
|
-
if (
|
|
273
|
-
!(subscribers || []).some(
|
|
274
|
-
(entry) =>
|
|
275
|
-
entry.SubscriptionType === "SNS" && entry.Address === topicArn,
|
|
276
|
-
)
|
|
277
|
-
) {
|
|
278
|
-
throw new Error("macOS Budget SNS subscriber mismatch");
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return {
|
|
282
|
-
accountId: identity.Account,
|
|
283
|
-
principalArn: identity.Arn,
|
|
284
|
-
workflowState: workflow.state,
|
|
285
|
-
stackStatus: stack.StackStatus,
|
|
286
|
-
budgetName: budget.BudgetName,
|
|
287
|
-
budgetDimensionFilter: plan.safety.budget.dimensionFilter,
|
|
288
|
-
budgetThresholds: thresholds,
|
|
289
|
-
};
|
|
290
|
-
}
|