@kungfu-tech/buildchain 3.0.2 → 3.0.3-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 +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +20 -2
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +56 -2
- 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 +146 -0
- 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 +188 -46
- package/dist/site/buildchain-site.json +181 -90
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +860 -98
- package/dist/site/controller-registry.json +118 -5
- package/dist/site/kfd-claims.json +233 -19
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +374 -59
- package/dist/site/page-registry.json +156 -65
- package/dist/site/public-surface-audit.json +231 -22
- 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 +575 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +54 -13
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +119 -12
- package/docs/consumer-issue-reporting.md +20 -2
- package/docs/dev-alpha-candidate-patrol.md +24 -10
- package/docs/github-governance-authority.md +24 -31
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/observed-evidence-patrol.md +28 -12
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +158 -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 +39 -32
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +218 -90
- package/docs/runtime-train-validation.md +20 -2
- package/docs/shifu-gate-profiles.md +7 -1
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +46 -24
- package/docs/web-surface-deployments.md +82 -1
- package/package.json +7 -5
- package/packages/core/README.md +60 -4
- package/packages/core/artifact-signing.js +1 -0
- package/packages/core/buildchain-contract.js +9 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/controller-evidence.js +2 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +25 -17
- package/packages/core/index.js +40 -0
- package/packages/core/paper.js +3226 -0
- package/packages/core/public-surface-audit.js +3 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-control-plane-audit.js +28 -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 +519 -228
- package/packages/core/release-propagation.js +175 -1
- package/packages/core/stable-release-gate.js +4 -1
- package/scripts/artifact-signing-delegation.mjs +268 -0
- package/scripts/audit-github-governance.mjs +32 -13
- package/scripts/audit-publication-control-plane.mjs +17 -8
- package/scripts/auditable-demo.mjs +266 -7
- 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-contract-lock.mjs +1 -1
- package/scripts/buildchain-patrol.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +70 -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 +288 -43
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
- package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
- package/scripts/gate-profile-core.mjs +6 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +84 -18
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +52 -14
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/observed-evidence.mjs +151 -33
- package/scripts/paper.mjs +645 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/reconcile-github-governance.mjs +8 -1
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +36 -4
- package/scripts/seal-artifact-signing-requests.mjs +64 -12
- package/scripts/stable-candidate-qualification.mjs +50 -0
- package/scripts/verify-artifact-signing-results.mjs +26 -1
- package/scripts/web-surface-core.mjs +272 -86
- package/scripts/web-surface-production-decision.mjs +19 -3
|
@@ -130,6 +130,16 @@ function collectArtifactFiles(root, patterns) {
|
|
|
130
130
|
return [...files].sort();
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function signingArtifactPathsForPlatform({ loadedConfig, cwd, platformId }) {
|
|
134
|
+
const declarations = loadedConfig?.config?.signing?.artifacts || [];
|
|
135
|
+
return declarations
|
|
136
|
+
.filter(
|
|
137
|
+
(entry) =>
|
|
138
|
+
entry.platforms.length === 0 || entry.platforms.includes(platformId),
|
|
139
|
+
)
|
|
140
|
+
.map((entry) => path.resolve(cwd, entry.path));
|
|
141
|
+
}
|
|
142
|
+
|
|
133
143
|
function readProcessSummaryArtifact(filePath) {
|
|
134
144
|
if (!filePath) {
|
|
135
145
|
return undefined;
|
|
@@ -435,11 +445,35 @@ export function runLifecycle({
|
|
|
435
445
|
const resolvedDiagnosticsProcessSamplesPath = path.join(diagnosticsDir, "process-samples.jsonl");
|
|
436
446
|
const resolvedSourceCheckoutPath = path.resolve(resolvedWorkspace, ".buildchain/diagnostics/source-checkout.json");
|
|
437
447
|
const resolvedDiagnosticsSourceCheckoutPath = path.join(diagnosticsDir, "source-checkout.json");
|
|
448
|
+
const resolvedCompilerCachePreparationPath = path.resolve(
|
|
449
|
+
resolvedWorkspace,
|
|
450
|
+
process.env.BUILDCHAIN_COMPILER_CACHE_PREPARATION_PATH ||
|
|
451
|
+
".buildchain/diagnostics/compiler-cache-preparation.json",
|
|
452
|
+
);
|
|
453
|
+
const compilerCachePreparationRelative = path.relative(
|
|
454
|
+
resolvedWorkspace,
|
|
455
|
+
resolvedCompilerCachePreparationPath,
|
|
456
|
+
);
|
|
457
|
+
if (
|
|
458
|
+
compilerCachePreparationRelative.startsWith("..") ||
|
|
459
|
+
path.isAbsolute(compilerCachePreparationRelative)
|
|
460
|
+
) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
"BUILDCHAIN_COMPILER_CACHE_PREPARATION_PATH must remain inside the workflow workspace",
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
const resolvedDiagnosticsCompilerCachePreparationPath = path.join(
|
|
466
|
+
diagnosticsDir,
|
|
467
|
+
"compiler-cache-preparation.json",
|
|
468
|
+
);
|
|
438
469
|
const resolvedDiagnosticsManifestPath = path.join(diagnosticsDir, "diagnostics-manifest.json");
|
|
439
470
|
const relativeDiagnosticsEventsPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsEventsPath));
|
|
440
471
|
const relativeDiagnosticsProcessSummaryPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsProcessSummaryPath));
|
|
441
472
|
const relativeDiagnosticsProcessSamplesPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsProcessSamplesPath));
|
|
442
473
|
const relativeDiagnosticsSourceCheckoutPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsSourceCheckoutPath));
|
|
474
|
+
const relativeDiagnosticsCompilerCachePreparationPath = toPosix(
|
|
475
|
+
path.relative(resolvedWorkspace, resolvedDiagnosticsCompilerCachePreparationPath),
|
|
476
|
+
);
|
|
443
477
|
const relativeDiagnosticsManifestPath = toPosix(path.relative(resolvedWorkspace, resolvedDiagnosticsManifestPath));
|
|
444
478
|
const logRunId = crypto.randomUUID();
|
|
445
479
|
const frameworkLog = createBuildchainLogger({
|
|
@@ -622,9 +656,22 @@ export function runLifecycle({
|
|
|
622
656
|
const sourceCheckoutArtifact = fs.existsSync(resolvedSourceCheckoutPath)
|
|
623
657
|
? JSON.parse(fs.readFileSync(resolvedSourceCheckoutPath, "utf8"))
|
|
624
658
|
: undefined;
|
|
659
|
+
const compilerCachePreparationArtifact = fs.existsSync(resolvedCompilerCachePreparationPath)
|
|
660
|
+
? JSON.parse(fs.readFileSync(resolvedCompilerCachePreparationPath, "utf8"))
|
|
661
|
+
: undefined;
|
|
625
662
|
fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });
|
|
626
663
|
const scanStartedAt = Date.now();
|
|
627
|
-
const
|
|
664
|
+
const signingArtifactPaths = stageName === "build"
|
|
665
|
+
? signingArtifactPathsForPlatform({
|
|
666
|
+
loadedConfig,
|
|
667
|
+
cwd: resolvedCwd,
|
|
668
|
+
platformId,
|
|
669
|
+
})
|
|
670
|
+
: [];
|
|
671
|
+
const files = collectArtifactFiles(resolvedWorkspace, [
|
|
672
|
+
...artifactPaths,
|
|
673
|
+
...signingArtifactPaths,
|
|
674
|
+
]);
|
|
628
675
|
const manifestFiles = files.map((file) => {
|
|
629
676
|
const stat = fs.statSync(file);
|
|
630
677
|
return {
|
|
@@ -743,6 +790,7 @@ export function runLifecycle({
|
|
|
743
790
|
lifecycleObservability,
|
|
744
791
|
processSummary: processSummaryArtifact?.summary,
|
|
745
792
|
sourceCheckout: sourceCheckoutArtifact,
|
|
793
|
+
compilerCachePreparation: compilerCachePreparationArtifact,
|
|
746
794
|
links: {
|
|
747
795
|
artifactName,
|
|
748
796
|
platformId,
|
|
@@ -757,6 +805,9 @@ export function runLifecycle({
|
|
|
757
805
|
...(processSummaryArtifact ? { diagnosticsProcessSummary: relativeDiagnosticsProcessSummaryPath } : {}),
|
|
758
806
|
...(processSummaryArtifact?.samplesPath ? { diagnosticsProcessSamples: relativeDiagnosticsProcessSamplesPath } : {}),
|
|
759
807
|
...(sourceCheckoutArtifact ? { sourceCheckout: relativeDiagnosticsSourceCheckoutPath } : {}),
|
|
808
|
+
...(compilerCachePreparationArtifact
|
|
809
|
+
? { compilerCachePreparation: relativeDiagnosticsCompilerCachePreparationPath }
|
|
810
|
+
: {}),
|
|
760
811
|
},
|
|
761
812
|
}),
|
|
762
813
|
);
|
|
@@ -778,6 +829,12 @@ export function runLifecycle({
|
|
|
778
829
|
if (sourceCheckoutArtifact) {
|
|
779
830
|
copyIfExists(resolvedSourceCheckoutPath, resolvedDiagnosticsSourceCheckoutPath);
|
|
780
831
|
}
|
|
832
|
+
if (compilerCachePreparationArtifact) {
|
|
833
|
+
copyIfExists(
|
|
834
|
+
resolvedCompilerCachePreparationPath,
|
|
835
|
+
resolvedDiagnosticsCompilerCachePreparationPath,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
781
838
|
writeDiagnosticsSidecarManifest(resolvedDiagnosticsManifestPath, {
|
|
782
839
|
workspace: resolvedWorkspace,
|
|
783
840
|
artifactName,
|
|
@@ -789,6 +846,10 @@ export function runLifecycle({
|
|
|
789
846
|
{ kind: "process-summary", filePath: resolvedDiagnosticsProcessSummaryPath },
|
|
790
847
|
{ kind: "process-samples", filePath: resolvedDiagnosticsProcessSamplesPath },
|
|
791
848
|
{ kind: "source-checkout", filePath: resolvedDiagnosticsSourceCheckoutPath },
|
|
849
|
+
{
|
|
850
|
+
kind: "compiler-cache-preparation",
|
|
851
|
+
filePath: resolvedDiagnosticsCompilerCachePreparationPath,
|
|
852
|
+
},
|
|
792
853
|
],
|
|
793
854
|
});
|
|
794
855
|
console.log(`buildchain_manifest=${path.relative(resolvedWorkspace, resolvedManifestPath)}`);
|
|
@@ -3,7 +3,7 @@ const TRAIN_REF_RE = /^train\/v\d+\/v\d+\.\d+\/[A-Za-z0-9._/-]+$/;
|
|
|
3
3
|
const AUTHORITY_REF_RE = /^authority\/v\d+\/v\d+\.\d+\/[A-Za-z0-9._/-]+$/;
|
|
4
4
|
const OFFICIAL_CHANNEL_REF_RE = /^v\d+(?:\.\d+)?(?:-alpha)?$/;
|
|
5
5
|
|
|
6
|
-
export function parseWorkflowShellRef(workflowRef = "", fallback = "
|
|
6
|
+
export function parseWorkflowShellRef(workflowRef = "", fallback = "v3", buildchainRepository = "kungfu-systems/buildchain") {
|
|
7
7
|
const value = String(workflowRef || "");
|
|
8
8
|
const expectedPrefix = `${buildchainRepository}/.github/workflows/`;
|
|
9
9
|
if (!value.startsWith(expectedPrefix)) {
|
|
@@ -80,7 +80,7 @@ export function normalizeRequestedRuntimeRef(requestedRef = "") {
|
|
|
80
80
|
export function resolveRuntimeSelection({
|
|
81
81
|
requestedRef = "",
|
|
82
82
|
workflowRef = "",
|
|
83
|
-
defaultStableRef = "
|
|
83
|
+
defaultStableRef = "v3",
|
|
84
84
|
buildchainRepository = "kungfu-systems/buildchain",
|
|
85
85
|
} = {}) {
|
|
86
86
|
const requested = String(requestedRef || "").trim();
|
|
@@ -98,7 +98,15 @@ export function resolveRuntimeSelection({
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
const normalized = normalizeRequestedRuntimeRef(requested);
|
|
101
|
-
const
|
|
101
|
+
const sameRepositoryWorkflow = String(workflowRef || "").startsWith(
|
|
102
|
+
`${buildchainRepository}/.github/workflows/`,
|
|
103
|
+
);
|
|
104
|
+
const pinnedSelfRuntime =
|
|
105
|
+
sameRepositoryWorkflow &&
|
|
106
|
+
normalized.exactSha &&
|
|
107
|
+
EXACT_SHA_RE.test(workflowShellRef) &&
|
|
108
|
+
normalized.ref.toLowerCase() === workflowShellRef.toLowerCase();
|
|
109
|
+
const runtimeOverride = !normalized.officialChannel && !pinnedSelfRuntime;
|
|
102
110
|
return {
|
|
103
111
|
requestedRef: requested,
|
|
104
112
|
runtimeRef: normalized.ref,
|
|
@@ -107,16 +115,23 @@ export function resolveRuntimeSelection({
|
|
|
107
115
|
runtimeOverride,
|
|
108
116
|
workflowShellRef: workflowShellRef || defaultStableRef,
|
|
109
117
|
rollbackRef: workflowShellRef || defaultStableRef,
|
|
110
|
-
trustDecision: normalized.officialChannel
|
|
118
|
+
trustDecision: normalized.officialChannel
|
|
119
|
+
? "official-channel"
|
|
120
|
+
: pinnedSelfRuntime
|
|
121
|
+
? "pinned-self"
|
|
122
|
+
: "override-requested",
|
|
111
123
|
};
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
export function validateRuntimeOverrideTrust({
|
|
115
127
|
requestedRef = "",
|
|
116
128
|
eventName = "",
|
|
129
|
+
eventAction = "",
|
|
117
130
|
actorPermission = "",
|
|
118
131
|
sameRepositoryPullRequest = false,
|
|
132
|
+
sameRepositoryWorkflow = false,
|
|
119
133
|
pullRequestHeadSha = "",
|
|
134
|
+
workflowShellSha = "",
|
|
120
135
|
} = {}) {
|
|
121
136
|
if (!String(requestedRef || "").trim()) {
|
|
122
137
|
return { ok: true, decision: "stable-default" };
|
|
@@ -126,6 +141,23 @@ export function validateRuntimeOverrideTrust({
|
|
|
126
141
|
}
|
|
127
142
|
const normalizedRequested = String(requestedRef || "").trim().toLowerCase();
|
|
128
143
|
const normalizedHeadSha = String(pullRequestHeadSha || "").trim().toLowerCase();
|
|
144
|
+
const normalizedWorkflowShellSha = String(workflowShellSha || "").trim().toLowerCase();
|
|
145
|
+
if (
|
|
146
|
+
sameRepositoryWorkflow === true &&
|
|
147
|
+
EXACT_SHA_RE.test(normalizedRequested) &&
|
|
148
|
+
EXACT_SHA_RE.test(normalizedWorkflowShellSha) &&
|
|
149
|
+
normalizedRequested === normalizedWorkflowShellSha
|
|
150
|
+
) {
|
|
151
|
+
return { ok: true, decision: "pinned-self" };
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
eventName === "pull_request" &&
|
|
155
|
+
eventAction === "closed" &&
|
|
156
|
+
EXACT_SHA_RE.test(normalizedRequested) &&
|
|
157
|
+
normalizedRequested === normalizedWorkflowShellSha
|
|
158
|
+
) {
|
|
159
|
+
return { ok: true, decision: "closed-release-pr-shell-runtime" };
|
|
160
|
+
}
|
|
129
161
|
if (
|
|
130
162
|
eventName === "pull_request" &&
|
|
131
163
|
sameRepositoryPullRequest === true &&
|
|
@@ -52,6 +52,31 @@ function assertInside(root, target, label) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function containsPath(root, target) {
|
|
56
|
+
const relative = path.relative(root, target);
|
|
57
|
+
return (
|
|
58
|
+
!relative || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resetGeneratedOutputRoot({
|
|
63
|
+
workspace,
|
|
64
|
+
cwd,
|
|
65
|
+
outputRoot,
|
|
66
|
+
protectedPaths = [],
|
|
67
|
+
}) {
|
|
68
|
+
const protectedRoots = [workspace, cwd, ...protectedPaths];
|
|
69
|
+
for (const protectedPath of protectedRoots) {
|
|
70
|
+
if (containsPath(outputRoot, protectedPath)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"signing request output must not contain the workspace, working directory, manifest, or a declared artifact",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
fs.rmSync(outputRoot, { recursive: true, force: true });
|
|
77
|
+
fs.mkdirSync(outputRoot, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
function walkSubject(subjectRoot) {
|
|
56
81
|
const entries = [];
|
|
57
82
|
function visit(current) {
|
|
@@ -208,18 +233,16 @@ function verifyLifecycleBinding({
|
|
|
208
233
|
);
|
|
209
234
|
const relativeSubject = toPosix(path.relative(workspace, subjectPath));
|
|
210
235
|
const files = descriptor.entries.filter(
|
|
211
|
-
(entry) => entry.type !== "directory",
|
|
236
|
+
(entry) => entry.type !== "directory" && entry.type !== "symlink",
|
|
212
237
|
);
|
|
238
|
+
let matchedFiles = 0;
|
|
213
239
|
for (const entry of files) {
|
|
214
|
-
if (entry.type === "symlink") continue;
|
|
215
240
|
const manifestPath = fs.statSync(subjectPath).isFile()
|
|
216
241
|
? relativeSubject
|
|
217
242
|
: `${relativeSubject}/${entry.path}`;
|
|
218
243
|
const observed = byPath.get(manifestPath);
|
|
219
|
-
if (!observed)
|
|
220
|
-
|
|
221
|
-
`signed artifact file is absent from lifecycle manifest: ${manifestPath}`,
|
|
222
|
-
);
|
|
244
|
+
if (!observed) continue;
|
|
245
|
+
matchedFiles += 1;
|
|
223
246
|
if (
|
|
224
247
|
`sha256:${observed.sha256}` !== entry.digest ||
|
|
225
248
|
Number(observed.size) !== entry.bytes
|
|
@@ -229,6 +252,15 @@ function verifyLifecycleBinding({
|
|
|
229
252
|
);
|
|
230
253
|
}
|
|
231
254
|
}
|
|
255
|
+
// A signing declaration is an independent artifact inventory and must not
|
|
256
|
+
// require consumers to repeat its path in workflow artifact-paths. When the
|
|
257
|
+
// lifecycle manifest does cover the subject, require complete coverage so a
|
|
258
|
+
// truncated or stale manifest cannot weaken the exact descriptor binding.
|
|
259
|
+
if (matchedFiles !== 0 && matchedFiles !== files.length) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`signed artifact is only partially represented in lifecycle manifest: ${relativeSubject}`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
232
264
|
}
|
|
233
265
|
|
|
234
266
|
export function sealArtifactSigningRequests({
|
|
@@ -255,8 +287,12 @@ export function sealArtifactSigningRequests({
|
|
|
255
287
|
);
|
|
256
288
|
const resolvedOutputRoot = path.resolve(resolvedWorkspace, outputRoot);
|
|
257
289
|
assertInside(resolvedWorkspace, resolvedOutputRoot, "signing request output");
|
|
258
|
-
fs.mkdirSync(resolvedOutputRoot, { recursive: true });
|
|
259
290
|
if (selected.length === 0) {
|
|
291
|
+
resetGeneratedOutputRoot({
|
|
292
|
+
workspace: resolvedWorkspace,
|
|
293
|
+
cwd: resolvedCwd,
|
|
294
|
+
outputRoot: resolvedOutputRoot,
|
|
295
|
+
});
|
|
260
296
|
const index = { schemaVersion: 1, contract: INDEX_CONTRACT, requests: [] };
|
|
261
297
|
const indexPath = path.join(resolvedOutputRoot, "index.json");
|
|
262
298
|
fs.writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}\n`);
|
|
@@ -274,8 +310,7 @@ export function sealArtifactSigningRequests({
|
|
|
274
310
|
assertInside(resolvedWorkspace, resolvedManifest, "artifact manifest");
|
|
275
311
|
const manifest = JSON.parse(fs.readFileSync(resolvedManifest, "utf8"));
|
|
276
312
|
const platform = normalizePlatform(manifest.platform?.os || process.platform);
|
|
277
|
-
const
|
|
278
|
-
for (const declaration of selected) {
|
|
313
|
+
const prepared = selected.map((declaration) => {
|
|
279
314
|
const subjectPath = path.resolve(resolvedCwd, declaration.path);
|
|
280
315
|
assertInside(
|
|
281
316
|
resolvedWorkspace,
|
|
@@ -288,7 +323,7 @@ export function sealArtifactSigningRequests({
|
|
|
288
323
|
);
|
|
289
324
|
const realSubject = fs.realpathSync(subjectPath);
|
|
290
325
|
assertInside(
|
|
291
|
-
resolvedWorkspace,
|
|
326
|
+
fs.realpathSync(resolvedWorkspace),
|
|
292
327
|
realSubject,
|
|
293
328
|
`signing artifact ${declaration.id}`,
|
|
294
329
|
);
|
|
@@ -299,8 +334,25 @@ export function sealArtifactSigningRequests({
|
|
|
299
334
|
subjectPath,
|
|
300
335
|
descriptor,
|
|
301
336
|
});
|
|
302
|
-
|
|
303
|
-
|
|
337
|
+
return {
|
|
338
|
+
declaration,
|
|
339
|
+
subjectPath,
|
|
340
|
+
descriptor,
|
|
341
|
+
id: safeId(declaration.id),
|
|
342
|
+
kind: inferKind(subjectPath, declaration.kind),
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
resetGeneratedOutputRoot({
|
|
346
|
+
workspace: resolvedWorkspace,
|
|
347
|
+
cwd: resolvedCwd,
|
|
348
|
+
outputRoot: resolvedOutputRoot,
|
|
349
|
+
protectedPaths: [
|
|
350
|
+
resolvedManifest,
|
|
351
|
+
...prepared.map((entry) => entry.subjectPath),
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
const requests = [];
|
|
355
|
+
for (const { declaration, subjectPath, descriptor, id, kind } of prepared) {
|
|
304
356
|
const transport = archiveSubject({
|
|
305
357
|
subjectPath,
|
|
306
358
|
outputRoot: resolvedOutputRoot,
|
|
@@ -41,6 +41,29 @@ function optionalSha(value, label) {
|
|
|
41
41
|
return normalized;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export function resolveStableCandidateQualificationCandidate({ eventName, inputCandidateSha = "", selfDogfoodEvidence } = {}) {
|
|
45
|
+
if (text(eventName) === "workflow_dispatch") {
|
|
46
|
+
return optionalSha(inputCandidateSha, "candidate SHA");
|
|
47
|
+
}
|
|
48
|
+
if (text(eventName) !== "workflow_run") {
|
|
49
|
+
throw new Error(`qualification candidate resolver does not admit event ${eventName || "<empty>"}`);
|
|
50
|
+
}
|
|
51
|
+
const evidence = selfDogfoodEvidence;
|
|
52
|
+
if (
|
|
53
|
+
evidence?.contract !== "kungfu-buildchain-alpha-self-dogfood"
|
|
54
|
+
|| evidence?.status !== "passed"
|
|
55
|
+
|| evidence?.observed?.alpha?.ref !== "v3-alpha"
|
|
56
|
+
) {
|
|
57
|
+
throw new Error("self-dogfood evidence is not a passing v3-alpha observation");
|
|
58
|
+
}
|
|
59
|
+
const observedSha = optionalSha(evidence.observed.alpha.sha, "observed v3-alpha SHA");
|
|
60
|
+
const expectedSha = optionalSha(evidence.observed.alpha.expectedSha, "expected v3-alpha SHA");
|
|
61
|
+
if (!observedSha || observedSha !== expectedSha) {
|
|
62
|
+
throw new Error("self-dogfood evidence does not bind observed and expected v3-alpha SHAs");
|
|
63
|
+
}
|
|
64
|
+
return observedSha;
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
function optionalRef(value, label) {
|
|
45
68
|
const normalized = text(value);
|
|
46
69
|
if (
|
|
@@ -91,8 +114,12 @@ async function ensureWorkflowEvidence({ client, repository, workflowFile, workfl
|
|
|
91
114
|
let run = await client.findWorkflowRun({ repository, workflowFile, workflowName, headSha, runName, sourceSha });
|
|
92
115
|
if (successful(run)) return { state: "existing", run };
|
|
93
116
|
|
|
117
|
+
let notBefore = "";
|
|
118
|
+
let excludeRunId = "";
|
|
94
119
|
if (!active(run)) {
|
|
95
120
|
if (options.dryRun) return { state: "planned", run };
|
|
121
|
+
notBefore = text(run?.created_at);
|
|
122
|
+
excludeRunId = text(run?.id);
|
|
96
123
|
await client.dispatchWorkflow({ repository, workflowFile, ref, inputs: runName ? { buildchain_ref: headSha } : {} });
|
|
97
124
|
}
|
|
98
125
|
|
|
@@ -104,6 +131,8 @@ async function ensureWorkflowEvidence({ client, repository, workflowFile, workfl
|
|
|
104
131
|
headSha,
|
|
105
132
|
runName,
|
|
106
133
|
sourceSha,
|
|
134
|
+
notBefore,
|
|
135
|
+
excludeRunId,
|
|
107
136
|
attempts: options.pollAttempts,
|
|
108
137
|
intervalMs: options.pollIntervalMs,
|
|
109
138
|
});
|
|
@@ -220,6 +249,8 @@ export function createGitHubQualificationClient({
|
|
|
220
249
|
|| (query.runName && (run.display_title === query.runName || run.name === query.runName))
|
|
221
250
|
)
|
|
222
251
|
&& (!query.sourceSha || run.head_sha === query.sourceSha)
|
|
252
|
+
&& (!query.notBefore || String(run.created_at || "") >= query.notBefore)
|
|
253
|
+
&& (!query.excludeRunId || String(run.id || "") !== query.excludeRunId)
|
|
223
254
|
))
|
|
224
255
|
.sort((left, right) => String(right.created_at).localeCompare(String(left.created_at)))[0];
|
|
225
256
|
}
|
|
@@ -267,6 +298,25 @@ export function createGitHubQualificationClient({
|
|
|
267
298
|
}
|
|
268
299
|
|
|
269
300
|
async function main() {
|
|
301
|
+
if (bool(process.env.BUILDCHAIN_QUALIFICATION_RESOLVE_CANDIDATE, false)) {
|
|
302
|
+
let selfDogfoodEvidence;
|
|
303
|
+
if (text(process.env.BUILDCHAIN_QUALIFICATION_EVENT_NAME) === "workflow_run") {
|
|
304
|
+
const fs = await import("node:fs");
|
|
305
|
+
const evidencePath = text(process.env.BUILDCHAIN_QUALIFICATION_SELF_DOGFOOD_EVIDENCE);
|
|
306
|
+
selfDogfoodEvidence = JSON.parse(fs.readFileSync(evidencePath, "utf8"));
|
|
307
|
+
}
|
|
308
|
+
const sha = resolveStableCandidateQualificationCandidate({
|
|
309
|
+
eventName: process.env.BUILDCHAIN_QUALIFICATION_EVENT_NAME,
|
|
310
|
+
inputCandidateSha: process.env.BUILDCHAIN_QUALIFICATION_INPUT_CANDIDATE_SHA,
|
|
311
|
+
selfDogfoodEvidence,
|
|
312
|
+
});
|
|
313
|
+
process.stdout.write(`${sha}\n`);
|
|
314
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
315
|
+
const fs = await import("node:fs");
|
|
316
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `sha=${sha}\n`);
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
270
320
|
const result = await runStableCandidateQualification();
|
|
271
321
|
const output = `${JSON.stringify(result, null, 2)}\n`;
|
|
272
322
|
process.stdout.write(output);
|
|
@@ -27,11 +27,36 @@ function resolveBelow(root, relative, label) {
|
|
|
27
27
|
return target;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function walk(root, name, output = []) {
|
|
31
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
32
|
+
const child = path.join(root, entry.name);
|
|
33
|
+
if (entry.isDirectory()) walk(child, name, output);
|
|
34
|
+
else if (entry.isFile() && entry.name === name) output.push(child);
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveRequestRoot(root) {
|
|
40
|
+
const direct = path.join(root, "index.json");
|
|
41
|
+
if (fs.existsSync(direct)) return root;
|
|
42
|
+
const candidates = walk(root, "index.json").filter((candidate) => {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(fs.readFileSync(candidate, "utf8")).contract === "kungfu-buildchain-artifact-signing-request-index/v1";
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
if (candidates.length !== 1) {
|
|
50
|
+
throw new Error(`expected exactly one artifact signing request index, found ${candidates.length}`);
|
|
51
|
+
}
|
|
52
|
+
return path.dirname(candidates[0]);
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
export function verifyArtifactSigningResults({
|
|
31
56
|
requestRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT,
|
|
32
57
|
resultRoot = process.env.BUILDCHAIN_SIGNING_RESULT_ROOT,
|
|
33
58
|
} = {}) {
|
|
34
|
-
const requests = path.resolve(required(requestRoot, "signing request root"));
|
|
59
|
+
const requests = resolveRequestRoot(path.resolve(required(requestRoot, "signing request root")));
|
|
35
60
|
const results = path.resolve(required(resultRoot, "signing result root"));
|
|
36
61
|
const requestIndex = JSON.parse(
|
|
37
62
|
fs.readFileSync(path.join(requests, "index.json"), "utf8"),
|