@kungfu-tech/buildchain 3.0.9-alpha.0 → 3.0.9-alpha.10
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/bin/buildchain.mjs +9 -1
- package/contracts/dev-delivery-authority-v2.schema.json +253 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +16 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +57 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/release-cut-v1.schema.json +1 -0
- package/contracts/release-train-transition-v1.schema.json +1 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +91 -32
- package/dist/site/buildchain-site.json +353 -45
- package/dist/site/capability-registry.json +7 -6
- package/dist/site/cli-registry.json +41 -2
- package/dist/site/controller-registry.json +8 -4
- package/dist/site/kfd-claims.json +193 -9
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +26 -10
- package/dist/site/node-api-registry.json +8012 -4574
- package/dist/site/page-registry.json +331 -31
- package/dist/site/public-surface-audit.json +353 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +1 -0
- package/dist/site/release-provenance.json +12 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +442 -0
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +22 -14
- package/dist/site/workflow-registry.json +15 -7
- package/docs/MAP.md +5 -2
- package/docs/adopter-delivery-gate.md +106 -0
- package/docs/aws-us-elastic-runner-burst-plane.md +30 -3
- package/docs/cli-reference.md +200 -13
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +137 -0
- package/docs/dev-delivery-warrant.md +164 -27
- package/docs/kfd-support.md +7 -2
- package/docs/next-development-transition.md +119 -0
- package/docs/node-api-reference.md +464 -277
- package/docs/release-governance.md +10 -7
- package/docs/release-passport.md +15 -2
- package/docs/release-train.md +46 -4
- package/docs/reusable-build-surface.md +26 -9
- package/docs/runtime-train-validation.md +6 -0
- package/docs/versioning.md +6 -2
- package/package.json +21 -6
- package/packages/core/README.md +25 -0
- package/packages/core/adopter-delivery-gate.js +576 -0
- package/packages/core/adopter-delivery-json.js +66 -0
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/buildchain-agent-manuals.js +1 -0
- package/packages/core/buildchain-channel-identity.js +118 -0
- package/packages/core/buildchain-compatibility-proof.js +54 -0
- package/packages/core/buildchain-contract.js +7 -0
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +52 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority.js +996 -0
- package/packages/core/dev-delivery-native-proof.js +418 -0
- package/packages/core/dev-delivery-warrant-settlement.js +67 -20
- package/packages/core/dev-delivery-warrant.js +141 -48
- package/packages/core/kfd-adopter-category-driver.js +167 -0
- package/packages/core/kfd-adopter-manifest.js +49 -50
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +726 -0
- package/packages/core/next-development-projection.js +288 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +7 -4
- package/packages/core/paper.js +14 -7
- package/packages/core/release-blocker-priority.js +192 -0
- package/packages/core/release-passport-contract.js +10 -6
- package/packages/core/release-passport.js +29 -12
- package/packages/core/release-train.js +220 -0
- package/scripts/aws-macos-jit-controller-core.mjs +239 -4
- package/scripts/aws-macos-jit-controller.mjs +198 -2
- package/scripts/aws-macos-jit-instance-rehydrate.mjs +260 -0
- package/scripts/aws-macos-jit-source-rebind.mjs +235 -14
- package/scripts/aws-macos-jit.mjs +12 -0
- package/scripts/buildchain-channel-router.mjs +18 -2
- package/scripts/buildchain-cli-help.mjs +9 -2
- package/scripts/buildchain-contract-lock.mjs +11 -1
- package/scripts/check-internal-architecture.mjs +1 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority.mjs +368 -0
- package/scripts/dev-delivery-native-run.mjs +235 -0
- package/scripts/dev-delivery-proof.mjs +64 -2
- package/scripts/dev-delivery-two-phase-resume.mjs +90 -0
- package/scripts/dev-delivery-two-phase.mjs +577 -0
- package/scripts/dev-delivery-warrant.mjs +47 -5
- package/scripts/dev-pr-delivery-warrant.mjs +19 -14
- package/scripts/generate-buildchain-kfd-witnesses.mjs +88 -94
- package/scripts/generate-channel-build-workflow.mjs +82 -31
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-site-bundle.mjs +8 -3
- package/scripts/init-repo.mjs +135 -62
- package/scripts/next-development-self-dogfood-harness.mjs +409 -0
- package/scripts/next-development-self-dogfood.mjs +532 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/promotion-channel-router.mjs +5 -3
- package/scripts/release-candidate-tail-reseal.mjs +426 -0
- package/scripts/release-train-self-dogfood.mjs +579 -0
- package/scripts/runtime-ref-core.mjs +4 -17
- package/scripts/seal-artifact-signing-requests.mjs +7 -4
- package/scripts/site-capability-metadata.mjs +23 -1
- package/scripts/stable-candidate-qualification.mjs +7 -7
- package/scripts/workflow-call-contract.mjs +1 -1
- package/templates/native-dev-delivery.yml +86 -0
|
@@ -8,6 +8,8 @@ export const RELEASE_TRAIN_TRANSITION_CONTRACT =
|
|
|
8
8
|
"kungfu-buildchain-release-train-transition/v1";
|
|
9
9
|
export const RELEASE_TRAIN_OBSERVATION_CONTRACT =
|
|
10
10
|
"kungfu-buildchain-release-train-observation/v1";
|
|
11
|
+
export const RELEASE_BLOCKER_REPAIR_CONTRACT =
|
|
12
|
+
"kungfu-buildchain-release-blocker-repair/v1";
|
|
11
13
|
export const LEGACY_DEV_ALPHA_CANDIDATE_STATE_SCHEMA =
|
|
12
14
|
"kungfu-buildchain-dev-alpha-candidate-state/v1";
|
|
13
15
|
|
|
@@ -22,6 +24,7 @@ export const RELEASE_TRAIN_STATES = Object.freeze([
|
|
|
22
24
|
]);
|
|
23
25
|
|
|
24
26
|
export const RELEASE_TRAIN_SUPERSESSION_CAUSES = Object.freeze([
|
|
27
|
+
"release-blocker-repair",
|
|
25
28
|
"incompatible-semantics",
|
|
26
29
|
"alpha-base-incompatibility",
|
|
27
30
|
"invalid-authority",
|
|
@@ -518,3 +521,220 @@ export function readReleaseTrain(input) {
|
|
|
518
521
|
}
|
|
519
522
|
throw new Error("unsupported Release Train record");
|
|
520
523
|
}
|
|
524
|
+
|
|
525
|
+
function releaseBlockerLanding(input, label, { allowConflict = false } = {}) {
|
|
526
|
+
const status = text(input?.status, `${label}.status`);
|
|
527
|
+
if (status !== "landed" && !(allowConflict && status === "conflict")) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
`${label}.status must be ${allowConflict ? "landed or conflict" : "landed"}`,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
const landing = {
|
|
533
|
+
status,
|
|
534
|
+
baseSha: exactSha(input.baseSha, `${label}.baseSha`),
|
|
535
|
+
landedSha:
|
|
536
|
+
status === "landed"
|
|
537
|
+
? exactSha(input.landedSha, `${label}.landedSha`)
|
|
538
|
+
: "",
|
|
539
|
+
patchRoot: contentRoot(input.patchRoot, `${label}.patchRoot`),
|
|
540
|
+
evidenceRoot: contentRoot(input.evidenceRoot, `${label}.evidenceRoot`),
|
|
541
|
+
};
|
|
542
|
+
return landing;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function releaseBlockerPublication(patchRoot, devLanding) {
|
|
546
|
+
const eligible =
|
|
547
|
+
devLanding.status === "landed" && devLanding.patchRoot === patchRoot;
|
|
548
|
+
const body = {
|
|
549
|
+
eligible,
|
|
550
|
+
reason:
|
|
551
|
+
devLanding.status === "conflict"
|
|
552
|
+
? "dev-forward-port-conflict"
|
|
553
|
+
: eligible
|
|
554
|
+
? "exact-cut-dev-patch-settled"
|
|
555
|
+
: "cut-dev-patch-root-mismatch",
|
|
556
|
+
requiredPatchRoot: patchRoot,
|
|
557
|
+
observedDevPatchRoot: devLanding.patchRoot,
|
|
558
|
+
devLandingStatus: devLanding.status,
|
|
559
|
+
};
|
|
560
|
+
return { ...body, gateRoot: releaseTrainRoot(body) };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function buildReleaseBlockerRepair(activeTrainInput, input = {}) {
|
|
564
|
+
const activeTrain = validateReleaseTrain(activeTrainInput);
|
|
565
|
+
if (activeTrain.state.status !== "repair-required") {
|
|
566
|
+
throw new Error(
|
|
567
|
+
"release-blocker repair requires the active train to be repair-required",
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
const expectedStateRoot = contentRoot(
|
|
571
|
+
input.expectedStateRoot,
|
|
572
|
+
"expectedStateRoot",
|
|
573
|
+
);
|
|
574
|
+
if (expectedStateRoot !== activeTrain.state.stateRoot) {
|
|
575
|
+
throw new Error("release-blocker repair compare-and-swap failed");
|
|
576
|
+
}
|
|
577
|
+
const patchRoot = contentRoot(input.patchRoot, "patchRoot");
|
|
578
|
+
const blockerRoot = contentRoot(input.blockerRoot, "blockerRoot");
|
|
579
|
+
const authorityRoots = sortedRoots(input.authorityRoots, "authorityRoots");
|
|
580
|
+
const createdAt = timestamp(input.createdAt, "createdAt");
|
|
581
|
+
const cutLanding = releaseBlockerLanding(
|
|
582
|
+
{
|
|
583
|
+
status: "landed",
|
|
584
|
+
baseSha: activeTrain.releaseCut.candidateSha,
|
|
585
|
+
landedSha: input.cutCandidateSha,
|
|
586
|
+
patchRoot: input.cutPatchRoot,
|
|
587
|
+
evidenceRoot: input.cutLandingEvidenceRoot,
|
|
588
|
+
},
|
|
589
|
+
"cutLanding",
|
|
590
|
+
);
|
|
591
|
+
if (cutLanding.patchRoot !== patchRoot) {
|
|
592
|
+
throw new Error(
|
|
593
|
+
"cut landing must preserve the declared release-blocker patch root",
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
const devLanding = releaseBlockerLanding(
|
|
597
|
+
{
|
|
598
|
+
status: input.devLandingStatus,
|
|
599
|
+
baseSha: input.devBaseSha,
|
|
600
|
+
landedSha: input.devLandingSha,
|
|
601
|
+
patchRoot: input.devPatchRoot,
|
|
602
|
+
evidenceRoot: input.devLandingEvidenceRoot,
|
|
603
|
+
},
|
|
604
|
+
"devLanding",
|
|
605
|
+
{ allowConflict: true },
|
|
606
|
+
);
|
|
607
|
+
const priorCut = activeTrain.releaseCut;
|
|
608
|
+
const successorTrain = createReleaseTrain({
|
|
609
|
+
repository: priorCut.repository,
|
|
610
|
+
sourceBranch: priorCut.sourceBranch,
|
|
611
|
+
targetBranch: priorCut.targetBranch,
|
|
612
|
+
originDevSha: priorCut.originDevSha,
|
|
613
|
+
candidateSha: cutLanding.landedSha,
|
|
614
|
+
candidateTreeSha: exactSha(
|
|
615
|
+
input.cutCandidateTreeSha,
|
|
616
|
+
"cutCandidateTreeSha",
|
|
617
|
+
),
|
|
618
|
+
alphaBaseSha: priorCut.alphaBaseSha,
|
|
619
|
+
buildchainRuntimeSha: priorCut.buildchainRuntimeSha,
|
|
620
|
+
generation: priorCut.generation + 1,
|
|
621
|
+
authorityRoots: [
|
|
622
|
+
...new Set([...priorCut.authorityRoots, ...authorityRoots]),
|
|
623
|
+
].sort(),
|
|
624
|
+
supersession: {
|
|
625
|
+
cause: "release-blocker-repair",
|
|
626
|
+
priorCutRoot: priorCut.cutRoot,
|
|
627
|
+
},
|
|
628
|
+
createdAt,
|
|
629
|
+
});
|
|
630
|
+
const supersededTrain = transitionReleaseTrain(activeTrain, {
|
|
631
|
+
to: "superseded",
|
|
632
|
+
expectedStateRoot,
|
|
633
|
+
event: "release-blocker-successor-cut",
|
|
634
|
+
reason:
|
|
635
|
+
"a rooted release-blocker repair advanced the active candidate generation",
|
|
636
|
+
authorityRoots,
|
|
637
|
+
supersessionCause: "release-blocker-repair",
|
|
638
|
+
replacementCutRoot: successorTrain.releaseCut.cutRoot,
|
|
639
|
+
replacementCandidateSha: successorTrain.releaseCut.candidateSha,
|
|
640
|
+
recordedAt: createdAt,
|
|
641
|
+
});
|
|
642
|
+
const publication = releaseBlockerPublication(patchRoot, devLanding);
|
|
643
|
+
const body = {
|
|
644
|
+
schemaVersion: 1,
|
|
645
|
+
contract: RELEASE_BLOCKER_REPAIR_CONTRACT,
|
|
646
|
+
blockerRoot,
|
|
647
|
+
patchRoot,
|
|
648
|
+
priorTrain: activeTrain,
|
|
649
|
+
supersededTrain,
|
|
650
|
+
successorTrain,
|
|
651
|
+
cutLanding,
|
|
652
|
+
devLanding,
|
|
653
|
+
candidateBuild: {
|
|
654
|
+
eligible: true,
|
|
655
|
+
candidateSha: successorTrain.releaseCut.candidateSha,
|
|
656
|
+
candidateTreeSha: successorTrain.releaseCut.candidateTreeSha,
|
|
657
|
+
generation: successorTrain.releaseCut.generation,
|
|
658
|
+
cutRoot: successorTrain.releaseCut.cutRoot,
|
|
659
|
+
reason: "rooted-cut-landing",
|
|
660
|
+
},
|
|
661
|
+
publication,
|
|
662
|
+
authorityRoots,
|
|
663
|
+
createdAt,
|
|
664
|
+
};
|
|
665
|
+
return { ...body, repairRoot: releaseTrainRoot(body) };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function createReleaseBlockerRepair(activeTrainInput, input = {}) {
|
|
669
|
+
return buildReleaseBlockerRepair(activeTrainInput, input);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function validateReleaseBlockerRepair(input) {
|
|
673
|
+
if (
|
|
674
|
+
input?.contract !== RELEASE_BLOCKER_REPAIR_CONTRACT ||
|
|
675
|
+
Number(input?.schemaVersion) !== 1
|
|
676
|
+
) {
|
|
677
|
+
throw new Error(
|
|
678
|
+
`release-blocker repair must use ${RELEASE_BLOCKER_REPAIR_CONTRACT}`,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
const rebuilt = buildReleaseBlockerRepair(input.priorTrain, {
|
|
682
|
+
expectedStateRoot: input.priorTrain?.state?.stateRoot,
|
|
683
|
+
blockerRoot: input.blockerRoot,
|
|
684
|
+
patchRoot: input.patchRoot,
|
|
685
|
+
cutCandidateSha: input.cutLanding?.landedSha,
|
|
686
|
+
cutCandidateTreeSha: input.successorTrain?.releaseCut?.candidateTreeSha,
|
|
687
|
+
cutPatchRoot: input.cutLanding?.patchRoot,
|
|
688
|
+
cutLandingEvidenceRoot: input.cutLanding?.evidenceRoot,
|
|
689
|
+
devLandingStatus: input.devLanding?.status,
|
|
690
|
+
devBaseSha: input.devLanding?.baseSha,
|
|
691
|
+
devLandingSha: input.devLanding?.landedSha,
|
|
692
|
+
devPatchRoot: input.devLanding?.patchRoot,
|
|
693
|
+
devLandingEvidenceRoot: input.devLanding?.evidenceRoot,
|
|
694
|
+
authorityRoots: input.authorityRoots,
|
|
695
|
+
createdAt: input.createdAt,
|
|
696
|
+
});
|
|
697
|
+
if (input.repairRoot !== rebuilt.repairRoot) {
|
|
698
|
+
throw new Error("release-blocker repair root does not match its content");
|
|
699
|
+
}
|
|
700
|
+
if (releaseTrainRoot(input) !== releaseTrainRoot(rebuilt)) {
|
|
701
|
+
throw new Error(
|
|
702
|
+
"release-blocker repair content drifted from its rooted contract",
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
return clone(input);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export function settleReleaseBlockerDevLanding(repairInput, input = {}) {
|
|
709
|
+
const repair = validateReleaseBlockerRepair(repairInput);
|
|
710
|
+
if (
|
|
711
|
+
contentRoot(input.expectedRepairRoot, "expectedRepairRoot") !==
|
|
712
|
+
repair.repairRoot
|
|
713
|
+
) {
|
|
714
|
+
throw new Error(
|
|
715
|
+
"release-blocker repair settlement compare-and-swap failed",
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const patchRoot = contentRoot(input.patchRoot, "patchRoot");
|
|
719
|
+
if (patchRoot !== repair.patchRoot) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"dev landing settlement must preserve the release-blocker patch root",
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return buildReleaseBlockerRepair(repair.priorTrain, {
|
|
725
|
+
expectedStateRoot: repair.priorTrain.state.stateRoot,
|
|
726
|
+
blockerRoot: repair.blockerRoot,
|
|
727
|
+
patchRoot: repair.patchRoot,
|
|
728
|
+
cutCandidateSha: repair.cutLanding.landedSha,
|
|
729
|
+
cutCandidateTreeSha: repair.successorTrain.releaseCut.candidateTreeSha,
|
|
730
|
+
cutPatchRoot: repair.cutLanding.patchRoot,
|
|
731
|
+
cutLandingEvidenceRoot: repair.cutLanding.evidenceRoot,
|
|
732
|
+
devLandingStatus: "landed",
|
|
733
|
+
devBaseSha: input.devBaseSha,
|
|
734
|
+
devLandingSha: input.devLandingSha,
|
|
735
|
+
devPatchRoot: patchRoot,
|
|
736
|
+
devLandingEvidenceRoot: input.devLandingEvidenceRoot,
|
|
737
|
+
authorityRoots: repair.authorityRoots,
|
|
738
|
+
createdAt: repair.createdAt,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
@@ -75,6 +75,103 @@ function qualificationId(value) {
|
|
|
75
75
|
return exact(value, /^mac-(?:smoke-0[12]|full-01)$/, "qualificationId");
|
|
76
76
|
}
|
|
77
77
|
|
|
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
|
+
|
|
78
175
|
function commonAws(values) {
|
|
79
176
|
const region = exact(
|
|
80
177
|
values.region || MACOS_EC2_JIT.region,
|
|
@@ -203,6 +300,87 @@ export function createMacosJitCampaignPlan(values = {}) {
|
|
|
203
300
|
return { ...plan, digest: digest(plan) };
|
|
204
301
|
}
|
|
205
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
|
+
},
|
|
380
|
+
};
|
|
381
|
+
return { ...plan, digest: digest(plan) };
|
|
382
|
+
}
|
|
383
|
+
|
|
206
384
|
export function createMacosJitSourceRebindPlan(values = {}) {
|
|
207
385
|
const id = campaignId(values.campaignId);
|
|
208
386
|
const boundSource = source(values);
|
|
@@ -219,6 +397,48 @@ export function createMacosJitSourceRebindPlan(values = {}) {
|
|
|
219
397
|
if (workflowId !== MACOS_EC2_JIT.workflowId) {
|
|
220
398
|
throw new Error(`workflowId must be ${MACOS_EC2_JIT.workflowId}`);
|
|
221
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
|
+
}
|
|
222
442
|
const plan = {
|
|
223
443
|
schemaVersion: 1,
|
|
224
444
|
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
@@ -233,6 +453,13 @@ export function createMacosJitSourceRebindPlan(values = {}) {
|
|
|
233
453
|
github: {
|
|
234
454
|
workflowId,
|
|
235
455
|
requiredState: "disabled_manually",
|
|
456
|
+
priorRunPolicy,
|
|
457
|
+
terminalFailureRunIds: terminalRunIds,
|
|
458
|
+
preflightFailureRunIds: preflightRunIds,
|
|
459
|
+
startupFailureRuns: startupRuns,
|
|
460
|
+
historicalTerminalFailureRuns: historicalTerminalRuns,
|
|
461
|
+
historicalPreflightFailureRuns: historicalPreflightRuns,
|
|
462
|
+
successfulRunIds: successfulRuns,
|
|
236
463
|
},
|
|
237
464
|
aws: {
|
|
238
465
|
...aws,
|
|
@@ -246,8 +473,16 @@ export function createMacosJitSourceRebindPlan(values = {}) {
|
|
|
246
473
|
forwardOnlySource: true,
|
|
247
474
|
sameSourceRefRequired: true,
|
|
248
475
|
workflowDisabledRequired: true,
|
|
249
|
-
zeroPriorJobsRequired:
|
|
250
|
-
zeroPriorArtifactsRequired:
|
|
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,
|
|
251
486
|
zeroJitResidueRequired: true,
|
|
252
487
|
zeroEvidenceRequired: true,
|
|
253
488
|
exactCampaignResourcesRequired: true,
|
|
@@ -301,9 +536,9 @@ export function macosAllocateHostsArgs(plan) {
|
|
|
301
536
|
export function macosRunInstancesArgs(plan, { hostId, dryRun = false } = {}) {
|
|
302
537
|
if (
|
|
303
538
|
plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
|
|
304
|
-
|
|
539
|
+
!["campaign-launch-plan", "instance-rehydrate-plan"].includes(plan.kind)
|
|
305
540
|
) {
|
|
306
|
-
throw new Error("macOS JIT
|
|
541
|
+
throw new Error("macOS JIT instance launch plan contract is invalid");
|
|
307
542
|
}
|
|
308
543
|
const resolvedHostId = exact(hostId, /^h-[0-9a-f]+$/, "hostId");
|
|
309
544
|
const args = [
|