@kungfu-tech/buildchain 3.0.3-alpha.0 → 3.0.3-alpha.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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +32 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +19 -1
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +41 -241
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +0 -45
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +161 -42
- package/dist/site/buildchain-site.json +180 -84
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +932 -98
- package/dist/site/controller-registry.json +97 -5
- package/dist/site/kfd-claims.json +316 -23
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +371 -56
- package/dist/site/page-registry.json +155 -59
- package/dist/site/public-surface-audit.json +286 -24
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +570 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +35 -12
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +143 -12
- package/docs/consumer-issue-reporting.md +19 -1
- package/docs/github-governance-authority.md +2 -2
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +185 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +31 -31
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +95 -27
- package/docs/runtime-train-validation.md +20 -2
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +6 -4
- package/docs/web-surface-deployments.md +62 -0
- package/package.json +7 -5
- package/packages/core/README.md +61 -4
- package/packages/core/buildchain-contract.js +8 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +10 -25
- package/packages/core/index.js +40 -0
- package/packages/core/paper-fleet.js +260 -0
- package/packages/core/paper-repository.js +210 -0
- package/packages/core/paper-work.js +365 -0
- package/packages/core/paper.js +3180 -0
- package/packages/core/public-surface-audit.js +26 -75
- package/packages/core/public-surface-cli.js +117 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +483 -201
- package/packages/core/release-propagation.js +175 -1
- package/scripts/auditable-demo.mjs +119 -5
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-cli-help.mjs +256 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +69 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +268 -43
- package/scripts/finalize-native-artifact-signing-result.mjs +75 -4
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +68 -188
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +7 -0
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/paper-work-fleet-cli.mjs +563 -0
- package/scripts/paper.mjs +631 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +23 -2
- package/scripts/seal-artifact-signing-requests.mjs +13 -6
- package/scripts/site-capability-metadata.mjs +202 -0
- package/scripts/stable-candidate-qualification.mjs +42 -0
- package/scripts/web-surface-core.mjs +272 -86
|
@@ -12,6 +12,8 @@ const STATE_MARKER_START = "<!-- buildchain-dev-alpha-candidate-state";
|
|
|
12
12
|
const STATE_MARKER_END = "-->";
|
|
13
13
|
const LEGACY_BODY_MARKER = "Buildchain exact-source channel candidate.";
|
|
14
14
|
const EXACT_SHA = /^[0-9a-f]{40}$/u;
|
|
15
|
+
const EVIDENCE_ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
16
|
+
const ABSENT_STATE_ROOT = "absent";
|
|
15
17
|
|
|
16
18
|
function text(value = "") {
|
|
17
19
|
return String(value ?? "").trim();
|
|
@@ -72,6 +74,18 @@ function optionalExactSha(value, name) {
|
|
|
72
74
|
return normalized;
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
function optionalStateRoot(value, name) {
|
|
78
|
+
const normalized = text(value);
|
|
79
|
+
if (
|
|
80
|
+
normalized &&
|
|
81
|
+
normalized !== ABSENT_STATE_ROOT &&
|
|
82
|
+
!EVIDENCE_ROOT.test(normalized)
|
|
83
|
+
) {
|
|
84
|
+
throw new Error(`${name} must be absent or an exact sha256 root`);
|
|
85
|
+
}
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
function canonical(value) {
|
|
76
90
|
if (Array.isArray(value)) return value.map(canonical);
|
|
77
91
|
if (value && typeof value === "object") {
|
|
@@ -237,6 +251,31 @@ export function normalizeDevAlphaPatrolOptions(options = {}) {
|
|
|
237
251
|
process.env.BUILDCHAIN_CHANNEL_PATROL_EXPECTED_SELECTED_SHA,
|
|
238
252
|
"expectedSelectedSha",
|
|
239
253
|
),
|
|
254
|
+
expectedPriorStateRoot: optionalStateRoot(
|
|
255
|
+
options.expectedPriorStateRoot ??
|
|
256
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_EXPECTED_PRIOR_STATE_ROOT,
|
|
257
|
+
"expectedPriorStateRoot",
|
|
258
|
+
),
|
|
259
|
+
reactivationAuthorized: bool(
|
|
260
|
+
options.reactivationAuthorized ??
|
|
261
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_REACTIVATION_AUTHORIZED,
|
|
262
|
+
false,
|
|
263
|
+
),
|
|
264
|
+
transitionAuthority: {
|
|
265
|
+
actor: text(
|
|
266
|
+
options.transitionAuthority?.actor ?? process.env.GITHUB_ACTOR,
|
|
267
|
+
),
|
|
268
|
+
workflow: text(
|
|
269
|
+
options.transitionAuthority?.workflow ?? process.env.GITHUB_WORKFLOW,
|
|
270
|
+
),
|
|
271
|
+
runId: text(
|
|
272
|
+
options.transitionAuthority?.runId ?? process.env.GITHUB_RUN_ID,
|
|
273
|
+
),
|
|
274
|
+
runAttempt: text(
|
|
275
|
+
options.transitionAuthority?.runAttempt ??
|
|
276
|
+
process.env.GITHUB_RUN_ATTEMPT,
|
|
277
|
+
),
|
|
278
|
+
},
|
|
240
279
|
createPullRequest,
|
|
241
280
|
settlementAuthorized: bool(
|
|
242
281
|
options.settlementAuthorized ??
|
|
@@ -396,6 +435,10 @@ function candidateFromDecision(decision) {
|
|
|
396
435
|
sourceLockRef: decision.sourceLockRef,
|
|
397
436
|
decisionRoot: decision.decisionRoot,
|
|
398
437
|
workflowEvidence: decision.workflowEvidence,
|
|
438
|
+
qualificationRoot: evidenceRoot({
|
|
439
|
+
sourceSha: decision.source.sha,
|
|
440
|
+
workflowEvidence: decision.workflowEvidence,
|
|
441
|
+
}),
|
|
399
442
|
};
|
|
400
443
|
}
|
|
401
444
|
|
|
@@ -406,6 +449,9 @@ function candidateStateBody({
|
|
|
406
449
|
activeCandidate,
|
|
407
450
|
nextCandidate,
|
|
408
451
|
supersededCandidate,
|
|
452
|
+
priorStateRoot = ABSENT_STATE_ROOT,
|
|
453
|
+
generation = 1,
|
|
454
|
+
tombstones = [],
|
|
409
455
|
}) {
|
|
410
456
|
const body = {
|
|
411
457
|
schema: DEV_ALPHA_CANDIDATE_STATE_SCHEMA,
|
|
@@ -413,8 +459,11 @@ function candidateStateBody({
|
|
|
413
459
|
sourceBranch: options.sourceBranch,
|
|
414
460
|
targetBranch: options.targetBranch,
|
|
415
461
|
targetSha,
|
|
462
|
+
generation,
|
|
463
|
+
priorStateRoot,
|
|
416
464
|
activeCandidate,
|
|
417
465
|
nextCandidate,
|
|
466
|
+
tombstones,
|
|
418
467
|
observationDecisionRoot: decision.decisionRoot || null,
|
|
419
468
|
observedAt: options.now,
|
|
420
469
|
...(supersededCandidate ? { supersededCandidate } : {}),
|
|
@@ -422,6 +471,50 @@ function candidateStateBody({
|
|
|
422
471
|
return { ...body, stateRoot: evidenceRoot(body) };
|
|
423
472
|
}
|
|
424
473
|
|
|
474
|
+
function persistedStateRoot(candidate) {
|
|
475
|
+
return text(candidate?.state?.stateRoot) || ABSENT_STATE_ROOT;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function candidateTombstone({
|
|
479
|
+
options,
|
|
480
|
+
candidate,
|
|
481
|
+
generation,
|
|
482
|
+
priorStateRoot,
|
|
483
|
+
reason,
|
|
484
|
+
pullRequest,
|
|
485
|
+
}) {
|
|
486
|
+
const body = {
|
|
487
|
+
schema: "kungfu-buildchain-dev-alpha-candidate-tombstone/v1",
|
|
488
|
+
repository: options.repository,
|
|
489
|
+
targetBranch: options.targetBranch,
|
|
490
|
+
candidateSha: candidate.sourceSha,
|
|
491
|
+
qualificationRoot:
|
|
492
|
+
candidate.qualificationRoot ||
|
|
493
|
+
evidenceRoot({
|
|
494
|
+
sourceSha: candidate.sourceSha,
|
|
495
|
+
workflowEvidence: candidate.workflowEvidence || [],
|
|
496
|
+
}),
|
|
497
|
+
generation,
|
|
498
|
+
priorStateRoot,
|
|
499
|
+
reason,
|
|
500
|
+
transitionAuthority: options.transitionAuthority,
|
|
501
|
+
pullRequest: pullRequest
|
|
502
|
+
? {
|
|
503
|
+
number: Number(pullRequest.number),
|
|
504
|
+
url: text(pullRequest.url || pullRequest.html_url),
|
|
505
|
+
}
|
|
506
|
+
: null,
|
|
507
|
+
workflowRuns: (candidate.workflowEvidence || []).map((row) => ({
|
|
508
|
+
workflowPath: row.workflowPath,
|
|
509
|
+
runId: row.runId,
|
|
510
|
+
runAttempt: row.runAttempt,
|
|
511
|
+
url: row.url,
|
|
512
|
+
})),
|
|
513
|
+
recordedAt: options.now,
|
|
514
|
+
};
|
|
515
|
+
return { ...body, tombstoneRoot: evidenceRoot(body) };
|
|
516
|
+
}
|
|
517
|
+
|
|
425
518
|
function pullRequestBody({
|
|
426
519
|
options,
|
|
427
520
|
observedSourceHeadSha,
|
|
@@ -573,9 +666,24 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
573
666
|
);
|
|
574
667
|
}
|
|
575
668
|
let activeCandidate = managedCandidates[0] || null;
|
|
669
|
+
const priorStateRoot = persistedStateRoot(activeCandidate);
|
|
670
|
+
if (
|
|
671
|
+
options.expectedPriorStateRoot &&
|
|
672
|
+
options.expectedPriorStateRoot !== priorStateRoot
|
|
673
|
+
) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
`candidate controller compare-and-swap failed: expected prior state root ${options.expectedPriorStateRoot}, observed ${priorStateRoot}`,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
const priorGeneration = Number(activeCandidate?.state?.generation || 0);
|
|
679
|
+
let generation = Math.max(priorGeneration, 1);
|
|
680
|
+
const priorTombstones = Array.isArray(activeCandidate?.state?.tombstones)
|
|
681
|
+
? activeCandidate.state.tombstones
|
|
682
|
+
: [];
|
|
576
683
|
const observedCandidate = candidateFromDecision(decision);
|
|
577
684
|
let nextCandidate = null;
|
|
578
685
|
let supersededCandidate = null;
|
|
686
|
+
let tombstones = [...priorTombstones];
|
|
579
687
|
let controllerState = decision.eligible
|
|
580
688
|
? "eligible-for-settlement"
|
|
581
689
|
: qualificationError
|
|
@@ -585,43 +693,100 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
585
693
|
: "observed";
|
|
586
694
|
if (activeCandidate) {
|
|
587
695
|
controllerState = "active";
|
|
696
|
+
nextCandidate = activeCandidate.nextCandidate || null;
|
|
588
697
|
if (
|
|
589
698
|
observedCandidate &&
|
|
590
699
|
observedCandidate.sourceSha !== activeCandidate.sourceSha
|
|
591
700
|
) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
701
|
+
const matchingTombstone = priorTombstones.find(
|
|
702
|
+
(row) => row.candidateSha === observedCandidate.sourceSha,
|
|
703
|
+
);
|
|
704
|
+
if (matchingTombstone && !options.reactivationAuthorized) {
|
|
705
|
+
controllerState = "tombstone-blocked";
|
|
706
|
+
} else {
|
|
707
|
+
if (
|
|
708
|
+
nextCandidate &&
|
|
709
|
+
nextCandidate.sourceSha !== observedCandidate.sourceSha
|
|
710
|
+
) {
|
|
711
|
+
supersededCandidate = nextCandidate;
|
|
712
|
+
generation = priorGeneration + 1;
|
|
713
|
+
tombstones.push(
|
|
714
|
+
candidateTombstone({
|
|
715
|
+
options,
|
|
716
|
+
candidate: supersededCandidate,
|
|
717
|
+
generation,
|
|
718
|
+
priorStateRoot,
|
|
719
|
+
reason: "newer-qualified-next-candidate",
|
|
720
|
+
pullRequest: activeCandidate,
|
|
721
|
+
}),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
nextCandidate = observedCandidate;
|
|
725
|
+
controllerState = "retained-next";
|
|
726
|
+
}
|
|
727
|
+
} else if (
|
|
728
|
+
!decision.eligible &&
|
|
729
|
+
nextCandidate &&
|
|
730
|
+
decision.source.sha === nextCandidate.sourceSha
|
|
598
731
|
) {
|
|
599
|
-
supersededCandidate =
|
|
732
|
+
supersededCandidate = nextCandidate;
|
|
733
|
+
nextCandidate = null;
|
|
734
|
+
generation = priorGeneration + 1;
|
|
735
|
+
tombstones.push(
|
|
736
|
+
candidateTombstone({
|
|
737
|
+
options,
|
|
738
|
+
candidate: supersededCandidate,
|
|
739
|
+
generation,
|
|
740
|
+
priorStateRoot,
|
|
741
|
+
reason: "qualification-evidence-rejected",
|
|
742
|
+
pullRequest: activeCandidate,
|
|
743
|
+
}),
|
|
744
|
+
);
|
|
745
|
+
controllerState = "rejected-next";
|
|
600
746
|
}
|
|
601
747
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
:
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
748
|
+
const persistedNextSha = text(activeCandidate?.nextCandidate?.sourceSha);
|
|
749
|
+
const desiredNextSha = text(nextCandidate?.sourceSha);
|
|
750
|
+
const transitionNeeded =
|
|
751
|
+
!activeCandidate?.state ||
|
|
752
|
+
persistedNextSha !== desiredNextSha ||
|
|
753
|
+
tombstones.length !== priorTombstones.length;
|
|
754
|
+
if (transitionNeeded && activeCandidate && generation === priorGeneration) {
|
|
755
|
+
generation = priorGeneration + 1;
|
|
756
|
+
}
|
|
757
|
+
let state =
|
|
758
|
+
activeCandidate?.state && !transitionNeeded
|
|
759
|
+
? activeCandidate.state
|
|
760
|
+
: candidateStateBody({
|
|
761
|
+
options,
|
|
762
|
+
targetSha,
|
|
763
|
+
decision,
|
|
764
|
+
activeCandidate: activeCandidate
|
|
765
|
+
? {
|
|
766
|
+
sourceSha: activeCandidate.sourceSha,
|
|
767
|
+
sourceLockRef: activeCandidate.sourceLockRef,
|
|
768
|
+
decisionRoot: activeCandidate.decisionRoot || null,
|
|
769
|
+
pullRequestNumber: activeCandidate.number,
|
|
770
|
+
pullRequestUrl: activeCandidate.url,
|
|
771
|
+
}
|
|
772
|
+
: null,
|
|
773
|
+
nextCandidate,
|
|
774
|
+
supersededCandidate,
|
|
775
|
+
priorStateRoot,
|
|
776
|
+
generation,
|
|
777
|
+
tombstones,
|
|
778
|
+
});
|
|
618
779
|
let pullRequest;
|
|
619
780
|
let settlementAction = "none";
|
|
620
781
|
if (options.settlementAuthorized && !options.dryRun) {
|
|
621
782
|
if (activeCandidate) {
|
|
622
783
|
const nextBody = replaceCandidateStateMarker(activeCandidate.body, state);
|
|
623
784
|
if (nextBody !== activeCandidate.body) {
|
|
624
|
-
await client.updatePullRequestBody(
|
|
785
|
+
await client.updatePullRequestBody(
|
|
786
|
+
activeCandidate.number,
|
|
787
|
+
nextBody,
|
|
788
|
+
priorStateRoot,
|
|
789
|
+
);
|
|
625
790
|
settlementAction = nextCandidate
|
|
626
791
|
? supersededCandidate
|
|
627
792
|
? "supersede-next-candidate"
|
|
@@ -656,15 +821,53 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
656
821
|
state,
|
|
657
822
|
}),
|
|
658
823
|
});
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
824
|
+
if (pullRequest.state && pullRequest.state !== "open") {
|
|
825
|
+
const tombstone = candidateTombstone({
|
|
826
|
+
options,
|
|
827
|
+
candidate: observedCandidate,
|
|
828
|
+
generation,
|
|
829
|
+
priorStateRoot,
|
|
830
|
+
reason: pullRequest.merged_at
|
|
831
|
+
? "candidate-pr-merged"
|
|
832
|
+
: "candidate-pr-closed",
|
|
833
|
+
pullRequest,
|
|
834
|
+
});
|
|
835
|
+
tombstones = [...priorTombstones, tombstone];
|
|
836
|
+
state = candidateStateBody({
|
|
837
|
+
options,
|
|
838
|
+
targetSha,
|
|
839
|
+
decision,
|
|
840
|
+
activeCandidate: null,
|
|
841
|
+
nextCandidate: null,
|
|
842
|
+
supersededCandidate: null,
|
|
843
|
+
priorStateRoot,
|
|
844
|
+
generation,
|
|
845
|
+
tombstones,
|
|
846
|
+
});
|
|
847
|
+
const nextBody = replaceCandidateStateMarker(pullRequest.body, state);
|
|
848
|
+
await client.updatePullRequestBody(
|
|
849
|
+
pullRequest.number,
|
|
850
|
+
nextBody,
|
|
851
|
+
persistedStateRoot({
|
|
852
|
+
state: parseCandidateStateMarker(pullRequest.body),
|
|
853
|
+
}),
|
|
854
|
+
);
|
|
855
|
+
settlementAction = "reuse-known-candidate-tombstone";
|
|
856
|
+
controllerState = "tombstoned";
|
|
857
|
+
activeCandidate = null;
|
|
858
|
+
} else {
|
|
859
|
+
settlementAction = pullRequest.reused
|
|
860
|
+
? "reuse-active-candidate"
|
|
861
|
+
: "create-active-candidate";
|
|
862
|
+
controllerState = "active";
|
|
863
|
+
activeCandidate = {
|
|
864
|
+
number: Number(pullRequest.number || 0),
|
|
865
|
+
url: text(pullRequest.html_url),
|
|
866
|
+
sourceSha,
|
|
867
|
+
sourceLockRef: decision.sourceLockRef,
|
|
868
|
+
decisionRoot: decision.decisionRoot,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
668
871
|
}
|
|
669
872
|
}
|
|
670
873
|
return {
|
|
@@ -681,6 +884,9 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
681
884
|
supersededCandidate,
|
|
682
885
|
settlementAction,
|
|
683
886
|
stateRoot: state.stateRoot,
|
|
887
|
+
priorStateRoot,
|
|
888
|
+
generation: state.generation,
|
|
889
|
+
tombstones: state.tombstones || [],
|
|
684
890
|
},
|
|
685
891
|
pullRequest: pullRequest || null,
|
|
686
892
|
};
|
|
@@ -809,18 +1015,36 @@ export function createGitHubChannelCandidateClient({
|
|
|
809
1015
|
});
|
|
810
1016
|
},
|
|
811
1017
|
async ensurePullRequest({ head, base, title, body }) {
|
|
812
|
-
const
|
|
813
|
-
`/repos/${owner}/${repo}/pulls?state=
|
|
814
|
-
);
|
|
815
|
-
return (
|
|
816
|
-
open[0] ||
|
|
817
|
-
api(`/repos/${owner}/${repo}/pulls`, {
|
|
818
|
-
method: "POST",
|
|
819
|
-
body: { head, base, title, body },
|
|
820
|
-
})
|
|
1018
|
+
const known = await api(
|
|
1019
|
+
`/repos/${owner}/${repo}/pulls?state=all&head=${encodeURIComponent(`${owner}:${head}`)}&base=${encodeURIComponent(base)}&per_page=20`,
|
|
821
1020
|
);
|
|
1021
|
+
if (known.length > 1) {
|
|
1022
|
+
throw new Error(
|
|
1023
|
+
`multiple known candidate pull requests bind ${head}: ${known
|
|
1024
|
+
.map((row) => `#${row.number}`)
|
|
1025
|
+
.join(", ")}`,
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
if (known[0]) return { ...known[0], reused: true };
|
|
1029
|
+
return api(`/repos/${owner}/${repo}/pulls`, {
|
|
1030
|
+
method: "POST",
|
|
1031
|
+
body: { head, base, title, body },
|
|
1032
|
+
});
|
|
822
1033
|
},
|
|
823
|
-
async updatePullRequestBody(
|
|
1034
|
+
async updatePullRequestBody(
|
|
1035
|
+
number,
|
|
1036
|
+
body,
|
|
1037
|
+
expectedPriorStateRoot = ABSENT_STATE_ROOT,
|
|
1038
|
+
) {
|
|
1039
|
+
const current = await api(`/repos/${owner}/${repo}/pulls/${number}`);
|
|
1040
|
+
const observedPriorStateRoot = persistedStateRoot({
|
|
1041
|
+
state: parseCandidateStateMarker(current.body),
|
|
1042
|
+
});
|
|
1043
|
+
if (observedPriorStateRoot !== expectedPriorStateRoot) {
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
`candidate controller compare-and-swap failed: expected prior state root ${expectedPriorStateRoot}, observed ${observedPriorStateRoot}`,
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
824
1048
|
return api(`/repos/${owner}/${repo}/pulls/${number}`, {
|
|
825
1049
|
method: "PATCH",
|
|
826
1050
|
body: { body },
|
|
@@ -866,6 +1090,7 @@ async function main() {
|
|
|
866
1090
|
"active-candidate-pr": result.controller.activeCandidate?.url || "",
|
|
867
1091
|
"next-candidate-sha": result.controller.nextCandidate?.sourceSha || "",
|
|
868
1092
|
"settlement-action": result.controller.settlementAction,
|
|
1093
|
+
"prior-state-root": result.controller.priorStateRoot,
|
|
869
1094
|
};
|
|
870
1095
|
fs.appendFileSync(
|
|
871
1096
|
process.env.GITHUB_OUTPUT,
|
|
@@ -37,6 +37,8 @@ export function finalizeNativeArtifactSigningResult({
|
|
|
37
37
|
requestPath = process.env.BUILDCHAIN_SIGNING_REQUEST_PATH,
|
|
38
38
|
signedPayload = process.env.BUILDCHAIN_SIGNED_PAYLOAD,
|
|
39
39
|
evidencePath = process.env.BUILDCHAIN_SIGNING_EVIDENCE,
|
|
40
|
+
credentialArtifactRoot =
|
|
41
|
+
process.env.BUILDCHAIN_SIGNING_CREDENTIAL_ARTIFACT_ROOT,
|
|
40
42
|
outputRoot = process.env.BUILDCHAIN_SIGNING_RESULT_ROOT,
|
|
41
43
|
checks = process.env.BUILDCHAIN_SIGNING_VERIFICATION_CHECKS,
|
|
42
44
|
} = {}) {
|
|
@@ -74,20 +76,79 @@ export function finalizeNativeArtifactSigningResult({
|
|
|
74
76
|
const evidenceOutput = path.join(resultDirectory, "provider-evidence.json");
|
|
75
77
|
fs.copyFileSync(evidenceSource, evidenceOutput, fs.constants.COPYFILE_EXCL);
|
|
76
78
|
const evidenceDocument = JSON.parse(fs.readFileSync(evidenceOutput, "utf8"));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
evidenceDocument.
|
|
80
|
-
|
|
79
|
+
const appBundleResult = request.artifact.kind === "app-bundle";
|
|
80
|
+
const providerEvidencePassed = appBundleResult
|
|
81
|
+
? evidenceDocument.schema ===
|
|
82
|
+
"buildchain.macos-credential-island-evidence/v1" &&
|
|
83
|
+
evidenceDocument.status === "accepted" &&
|
|
84
|
+
evidenceDocument.source?.repository === request.source.repository &&
|
|
85
|
+
evidenceDocument.source?.sha === request.source.sha &&
|
|
86
|
+
evidenceDocument.source?.treeSha === request.source.treeSha &&
|
|
87
|
+
evidenceDocument.buildchain?.runtimeSha === request.runtime.sha &&
|
|
88
|
+
evidenceDocument.app?.architecture === request.artifact.arch &&
|
|
89
|
+
evidenceDocument.notarization?.application?.status === "Accepted" &&
|
|
90
|
+
evidenceDocument.notarization?.diskImage?.status === "Accepted"
|
|
91
|
+
: evidenceDocument.status === "passed" &&
|
|
92
|
+
evidenceDocument.provider === request.signature.provider;
|
|
93
|
+
if (!providerEvidencePassed) {
|
|
81
94
|
throw new Error(
|
|
82
95
|
"provider evidence does not prove the requested native signature",
|
|
83
96
|
);
|
|
84
97
|
}
|
|
98
|
+
let credentialOutput = "";
|
|
99
|
+
let credentialEvidence = [];
|
|
100
|
+
if (appBundleResult) {
|
|
101
|
+
const credentialSource = path.resolve(
|
|
102
|
+
required(
|
|
103
|
+
credentialArtifactRoot,
|
|
104
|
+
"credential artifact root for app-bundle result",
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
const credentialManifest = path.join(credentialSource, "manifest.json");
|
|
108
|
+
if (!fs.statSync(credentialManifest).isFile())
|
|
109
|
+
throw new Error("credential artifact manifest is missing");
|
|
110
|
+
credentialOutput = path.join(resultDirectory, "credential-artifact");
|
|
111
|
+
fs.cpSync(credentialSource, credentialOutput, {
|
|
112
|
+
recursive: true,
|
|
113
|
+
force: false,
|
|
114
|
+
errorOnExist: true,
|
|
115
|
+
});
|
|
116
|
+
const manifestOutput = path.join(credentialOutput, "manifest.json");
|
|
117
|
+
const manifest = JSON.parse(fs.readFileSync(manifestOutput, "utf8"));
|
|
118
|
+
if (!Array.isArray(manifest.files) || manifest.files.length === 0)
|
|
119
|
+
throw new Error("credential artifact manifest contains no files");
|
|
120
|
+
credentialEvidence = [
|
|
121
|
+
{
|
|
122
|
+
kind: "credential-artifact-manifest",
|
|
123
|
+
path: "credential-artifact/manifest.json",
|
|
124
|
+
digest: sha256File(manifestOutput),
|
|
125
|
+
},
|
|
126
|
+
...manifest.files.map((file, index) => {
|
|
127
|
+
const filePath = resolveBelow(
|
|
128
|
+
credentialOutput,
|
|
129
|
+
file.path,
|
|
130
|
+
`credential artifact file ${index}`,
|
|
131
|
+
);
|
|
132
|
+
const observed = sha256File(filePath);
|
|
133
|
+
if (observed !== `sha256:${String(file.sha256 || "").toLowerCase()}`)
|
|
134
|
+
throw new Error(
|
|
135
|
+
`credential artifact file digest mismatch: ${file.path}`,
|
|
136
|
+
);
|
|
137
|
+
return {
|
|
138
|
+
kind: `credential-artifact-${index}`,
|
|
139
|
+
path: `credential-artifact/${String(file.path).replaceAll("\\", "/")}`,
|
|
140
|
+
digest: observed,
|
|
141
|
+
};
|
|
142
|
+
}),
|
|
143
|
+
];
|
|
144
|
+
}
|
|
85
145
|
const evidence = [
|
|
86
146
|
{
|
|
87
147
|
kind: `${request.signature.profile}-verification`,
|
|
88
148
|
path: "provider-evidence.json",
|
|
89
149
|
digest: sha256File(evidenceOutput),
|
|
90
150
|
},
|
|
151
|
+
...credentialEvidence,
|
|
91
152
|
];
|
|
92
153
|
const payloadDigest = sha256File(payloadPath);
|
|
93
154
|
const receipt = createArtifactSigningReceipt({
|
|
@@ -118,6 +179,15 @@ export function finalizeNativeArtifactSigningResult({
|
|
|
118
179
|
.filter(Boolean),
|
|
119
180
|
);
|
|
120
181
|
}
|
|
182
|
+
if (verificationChecks.length === 0 && appBundleResult) {
|
|
183
|
+
verificationChecks.push(
|
|
184
|
+
...Object.entries(evidenceDocument.verification || {})
|
|
185
|
+
.filter(([, passed]) => passed === true)
|
|
186
|
+
.map(([name]) => name),
|
|
187
|
+
"application-notarytool-accepted",
|
|
188
|
+
"disk-image-notarytool-accepted",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
121
191
|
if (verificationChecks.length === 0)
|
|
122
192
|
throw new Error("provider evidence contains no verification checks");
|
|
123
193
|
const result = createArtifactSigningResult({
|
|
@@ -160,6 +230,7 @@ export function finalizeNativeArtifactSigningResult({
|
|
|
160
230
|
writeGitHubOutputs({
|
|
161
231
|
"result-root": resultDirectory,
|
|
162
232
|
"result-index": path.join(resultDirectory, "index.json"),
|
|
233
|
+
"credential-artifact-root": credentialOutput,
|
|
163
234
|
});
|
|
164
235
|
return index;
|
|
165
236
|
}
|
|
@@ -284,24 +284,15 @@ jobs:
|
|
|
284
284
|
--publication-channel "\${{ inputs.channel }}"
|
|
285
285
|
--target-ref "\${{ inputs.target-ref || github.ref_name }}"
|
|
286
286
|
--router-ref "\${{ steps.router.outputs.ref }}"
|
|
287
|
+
--router-sha "\${{ steps.router.outputs.sha }}"
|
|
287
288
|
|
|
288
289
|
- name: Authorize trusted runtime override
|
|
289
290
|
if: \${{ steps.route.outputs.override-used == 'true' }}
|
|
290
291
|
uses: actions/github-script@v8
|
|
291
292
|
with:
|
|
292
293
|
script: |
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
|
|
297
|
-
owner: context.repo.owner,
|
|
298
|
-
repo: context.repo.repo,
|
|
299
|
-
username: context.actor,
|
|
300
|
-
});
|
|
301
|
-
const level = permission.data.user?.permissions || permission.data.permission || "none";
|
|
302
|
-
if (!["write", "maintain", "admin"].includes(level)) {
|
|
303
|
-
throw new Error(\`promotion runtime override requires write, maintain, or admin permission; actor has \${level}\`);
|
|
304
|
-
}
|
|
294
|
+
const script = require(process.env.GITHUB_WORKSPACE + "/.buildchain/router/scripts/authorize-promotion-runtime-override.cjs");
|
|
295
|
+
await script.authorizePromotionRuntimeOverride({ github, context });
|
|
305
296
|
|
|
306
297
|
- name: Resolve immutable promotion identities
|
|
307
298
|
id: identities
|
|
@@ -29,6 +29,8 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
29
29
|
const gateAggregate = gateAggregateJson ? JSON.parse(gateAggregateJson) : undefined;
|
|
30
30
|
const controllerReceiptJson = env("BUILDCHAIN_CONTROLLER_RECEIPT_JSON");
|
|
31
31
|
const controllerReceipts = controllerReceiptJson ? [JSON.parse(controllerReceiptJson)] : [];
|
|
32
|
+
const familyEvidenceJson = env("BUILDCHAIN_RC_FAMILY_EVIDENCE_JSON");
|
|
33
|
+
const familyEvidence = familyEvidenceJson ? JSON.parse(familyEvidenceJson) : undefined;
|
|
32
34
|
const passport = createReleaseCandidatePassport({
|
|
33
35
|
repository: env("GITHUB_REPOSITORY", buildSummary.git?.repository || ""),
|
|
34
36
|
pullRequest: {
|
|
@@ -51,6 +53,7 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
51
53
|
workflowShellRef: env("BUILDCHAIN_WORKFLOW_SHELL_REF", buildSummary.runtime?.workflowShellRef || ""),
|
|
52
54
|
},
|
|
53
55
|
gateAggregate,
|
|
56
|
+
familyEvidence,
|
|
54
57
|
controllerReceipts,
|
|
55
58
|
workflow: {
|
|
56
59
|
name: env("GITHUB_WORKFLOW"),
|
|
@@ -80,6 +83,7 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
80
83
|
candidateHash: passport.candidateHash,
|
|
81
84
|
platformCount: passport.platformMatrix.length,
|
|
82
85
|
gateProfileEvidence: passport.gateProfileEvidence,
|
|
86
|
+
familyEvidence: passport.familyEvidence,
|
|
83
87
|
controllerReceipts: passport.controllerReceipts || [],
|
|
84
88
|
}),
|
|
85
89
|
});
|