@kungfu-tech/buildchain 4.0.1-alpha.0 → 4.0.1-alpha.11
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 +13 -5
- package/actions/promote-buildchain-ref/README.md +11 -6
- package/actions/release-tail/README.md +8 -0
- package/architecture/ci-lane-change-budget.json +187 -0
- package/architecture/decisions/0002-next-development-transition.md +101 -0
- package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +219 -7
- package/architecture/maintainability-policy.json +246 -29
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +91 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-delivery-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
- package/architecture/v4-floating-consumer-policy.json +81 -0
- package/architecture/v4-floating-consumer-policy.md +32 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-runtime-ref-resume-authority.json +64 -0
- package/architecture/v4-stage-capsule-qualification.json +2 -2
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +32 -3
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/dev-delivery-authority-v2.schema.json +662 -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/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +59 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +107 -0
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +5 -0
- package/dist/site/artifact-schemas.json +10 -0
- package/dist/site/buildchain-contract.json +1562 -44
- package/dist/site/buildchain-site.json +684 -42
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +205 -3
- package/dist/site/controller-registry.json +62 -6
- package/dist/site/kfd-claims.json +551 -22
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +55 -9
- package/dist/site/node-api-registry.json +15247 -8690
- package/dist/site/page-registry.json +647 -29
- package/dist/site/public-surface-audit.json +876 -55
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +26 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +37 -13
- package/dist/site/workflow-registry.json +200 -22
- package/docs/MAP.md +10 -2
- package/docs/cli-reference.md +404 -13
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +361 -30
- package/docs/lifecycle-protocol.md +41 -0
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +748 -322
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/reusable-build-surface.md +41 -0
- package/docs/runtime-train-validation.md +10 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-runtime-ref-resume-authority.md +69 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -2
- package/package.json +36 -6
- 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-signing.js +61 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +150 -1
- package/packages/core/buildchain-contract.js +96 -143
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +592 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +562 -0
- package/packages/core/dev-delivery-process-boundary.js +563 -0
- package/packages/core/dev-delivery-proof.js +37 -3
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +587 -0
- package/packages/core/dev-delivery-warrant.js +328 -370
- package/packages/core/github-governance-authority.js +1 -0
- package/packages/core/index.js +7 -0
- package/packages/core/kungfu-temporal-fact.js +557 -0
- 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 +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/publication-authority.js +5 -5
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +66 -11
- package/packages/core/release-candidate.js +79 -19
- package/packages/core/release-passport.js +271 -38
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +338 -0
- package/packages/core/v4-floating-consumer-policy.js +569 -0
- package/packages/core/v4-floating-consumer-release-passport.js +153 -0
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
- package/packages/core/v4-runtime-selector-persistence.js +228 -0
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/packages/core/workflow-yaml-contract.js +72 -2
- package/scripts/audit-publication-control-plane.mjs +31 -31
- package/scripts/buildchain-cli-help.mjs +41 -2
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +217 -0
- package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +547 -0
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +231 -0
- package/scripts/dev-delivery-warrant.mjs +232 -194
- package/scripts/dev-pr-auto-merge.mjs +37 -48
- package/scripts/dev-pr-delivery-warrant.mjs +216 -2
- package/scripts/dev-pr-prequeue-guard.mjs +399 -0
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/ensure-github-release.mjs +3 -3
- package/scripts/generate-channel-build-workflow.mjs +3 -0
- package/scripts/generate-channel-promotion-workflow.mjs +114 -14
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +192 -32
- package/scripts/generate-site-bundle.mjs +69 -4
- package/scripts/init-repo.mjs +26 -4
- package/scripts/inspect-artifact-signing-requests.mjs +6 -0
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/npm-publish-transaction.mjs +103 -91
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/release-train-self-dogfood.mjs +94 -92
- package/scripts/resume-from-candidate-run.mjs +563 -41
- package/scripts/seal-artifact-signing-requests.mjs +6 -0
- package/scripts/site-capability-metadata.mjs +43 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v4-consumer-policy.mjs +231 -0
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
- package/templates/native-dev-delivery.yml +143 -0
|
@@ -3,17 +3,19 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
6
7
|
import { pathToFileURL } from "node:url";
|
|
7
8
|
|
|
8
9
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
10
|
+
import { normalizeAnchorProvenance, normalizeCandidateRun, recoverCandidateProvenance } from "./release-candidate-anchor-provenance.mjs";
|
|
11
|
+
export { resolveAnchorRecoveryRequest } from "./release-candidate-anchor-provenance.mjs";
|
|
9
12
|
import {
|
|
10
13
|
generatePublishRequiredArtifacts,
|
|
11
|
-
readNpmPackageArtifact,
|
|
12
|
-
selectReleaseCandidateArtifacts,
|
|
13
|
-
} from "./release-candidate-resolver.mjs";
|
|
14
|
-
import {
|
|
15
14
|
githubDownload,
|
|
16
15
|
githubJson,
|
|
16
|
+
readNpmPackageArtifact,
|
|
17
|
+
selectPayloadArtifacts,
|
|
18
|
+
selectReleaseCandidateArtifacts,
|
|
17
19
|
unzip,
|
|
18
20
|
verifyArtifactArchive,
|
|
19
21
|
} from "./release-candidate-resolver.mjs";
|
|
@@ -28,6 +30,15 @@ import {
|
|
|
28
30
|
} from "../packages/core/publication-artifact-candidate.js";
|
|
29
31
|
import { createPublicationSealedBundle } from "../packages/core/publication-sealed-bundle.js";
|
|
30
32
|
import { releaseTransactionStateRef } from "../packages/core/publish-transaction.js";
|
|
33
|
+
import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
34
|
+
import { validateV4StageCapsule } from "../packages/core/v4-stage-capsule.js";
|
|
35
|
+
import {
|
|
36
|
+
authorizeV4RuntimeSelection,
|
|
37
|
+
createV4RuntimeResumeLineage,
|
|
38
|
+
scanV4RuntimeSelectorPersistence,
|
|
39
|
+
v4RuntimeResumeDocumentRoot,
|
|
40
|
+
verifyV4RuntimeAuthorizationReceipt,
|
|
41
|
+
} from "../packages/core/v4-runtime-ref-resume-authority.js";
|
|
31
42
|
|
|
32
43
|
function env(name, fallback = "") {
|
|
33
44
|
return process.env[name] || fallback;
|
|
@@ -100,8 +111,371 @@ function readOnlyJson(files, label) {
|
|
|
100
111
|
return JSON.parse(fs.readFileSync(files[0].absolutePath, "utf8"));
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
const
|
|
114
|
+
function trackedRuntimePersistenceScan() {
|
|
115
|
+
const paths = execFileSync(
|
|
116
|
+
"git",
|
|
117
|
+
["ls-files", ".github/workflows", ".github/actions", ".buildchain"],
|
|
118
|
+
{ encoding: "utf8" },
|
|
119
|
+
)
|
|
120
|
+
.split(/\r?\n/)
|
|
121
|
+
.filter((entry) => /\.(?:json|toml|ya?ml)$/u.test(entry));
|
|
122
|
+
return scanV4RuntimeSelectorPersistence({ root: process.cwd(), paths });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads }) {
|
|
126
|
+
if (
|
|
127
|
+
!sidecar ||
|
|
128
|
+
sidecar.contract !==
|
|
129
|
+
"kungfu-buildchain-v4-release-candidate-stage-capsules/v1" ||
|
|
130
|
+
sidecar.status !== "sealed"
|
|
131
|
+
) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
"cross-runtime recovery requires original sealed release-candidate Stage Capsules",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const payload = { ...sidecar };
|
|
137
|
+
delete payload.root;
|
|
138
|
+
const expectedAttempt =
|
|
139
|
+
`github-run:${passport.workflow.runId}:attempt:${passport.workflow.runAttempt}`;
|
|
140
|
+
if (
|
|
141
|
+
sidecar.root !== v4RuntimeResumeDocumentRoot(payload) ||
|
|
142
|
+
sidecar.repository !== passport.repository ||
|
|
143
|
+
sidecar.source?.sha !== passport.source.headSha ||
|
|
144
|
+
sidecar.source?.treeSha !== passport.source.treeHash ||
|
|
145
|
+
sidecar.buildAttempt?.id !== expectedAttempt ||
|
|
146
|
+
sidecar.buildAttempt?.runtimeSha !== passport.buildchain.sha ||
|
|
147
|
+
sidecar.consumerPolicyReceiptRoot !== passport.consumerPolicy?.receiptRoot
|
|
148
|
+
) {
|
|
149
|
+
throw new Error("release-candidate Stage Capsule sidecar identity mismatch");
|
|
150
|
+
}
|
|
151
|
+
const artifactByName = new Map(downloads.map((entry) => [entry.artifact.name, entry]));
|
|
152
|
+
const required = [...passport.platformMatrix.map((entry) => entry.platformId)].sort();
|
|
153
|
+
const entries = [...(sidecar.capsules || [])].sort((left, right) =>
|
|
154
|
+
left.platform.localeCompare(right.platform),
|
|
155
|
+
);
|
|
156
|
+
if (
|
|
157
|
+
entries.length !== required.length ||
|
|
158
|
+
entries.some((entry, index) => entry.platform !== required[index])
|
|
159
|
+
) {
|
|
160
|
+
throw new Error("release-candidate Stage Capsule platform set is incomplete");
|
|
161
|
+
}
|
|
162
|
+
return entries.map((entry) => {
|
|
163
|
+
validateV4StageCapsule(entry.capsule);
|
|
164
|
+
const download = artifactByName.get(entry.artifactName);
|
|
165
|
+
const artifact = entry.artifact;
|
|
166
|
+
if (
|
|
167
|
+
!download ||
|
|
168
|
+
artifact?.platformId !== entry.platform ||
|
|
169
|
+
artifact?.id !== String(download.artifact.id) ||
|
|
170
|
+
artifact?.name !== download.artifact.name ||
|
|
171
|
+
artifact?.digest !== entry.artifactDigest ||
|
|
172
|
+
artifact?.digest !== download.artifact.digest ||
|
|
173
|
+
artifact?.digest !== download.record.digest ||
|
|
174
|
+
artifact?.digest !== download.record.downloadedDigest ||
|
|
175
|
+
artifact?.expiresAt !==
|
|
176
|
+
new Date(download.artifact.expires_at).toISOString() ||
|
|
177
|
+
entry.capsule.identity.policyRoot !== sidecar.consumerPolicyReceiptRoot ||
|
|
178
|
+
entry.capsule.identity.sourceRoot !==
|
|
179
|
+
v4ContentRoot("candidate-identity", passport.source) ||
|
|
180
|
+
entry.capsule.identity.runtimeRoot !==
|
|
181
|
+
v4ContentRoot("candidate-identity", { sha: passport.buildchain.sha })
|
|
182
|
+
) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Stage Capsule ${entry.platform} does not bind the verified provider artifact`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
platform: entry.platform,
|
|
189
|
+
capsuleRoot: entry.capsule.capsuleRoot,
|
|
190
|
+
identityRoot: entry.capsule.identityRoot,
|
|
191
|
+
artifactDigest: entry.artifactDigest,
|
|
192
|
+
sourceSha: sidecar.source.sha,
|
|
193
|
+
sourceTreeSha: sidecar.source.treeSha,
|
|
194
|
+
policyRoot: sidecar.consumerPolicyReceiptRoot,
|
|
195
|
+
buildRuntimeSha: sidecar.buildAttempt.runtimeSha,
|
|
196
|
+
sealed: true,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function validateV4RuntimeResumePublicReadback({
|
|
202
|
+
targetRef,
|
|
203
|
+
targetSha,
|
|
204
|
+
alphaSha,
|
|
205
|
+
exactTagSha,
|
|
206
|
+
tagLineage,
|
|
207
|
+
runtimeSha,
|
|
208
|
+
version,
|
|
209
|
+
transaction,
|
|
210
|
+
main,
|
|
211
|
+
npm,
|
|
212
|
+
}) {
|
|
213
|
+
if (
|
|
214
|
+
transaction?.target_ref !== targetRef ||
|
|
215
|
+
transaction?.version !== version ||
|
|
216
|
+
exactTagSha !== transaction?.source_sha ||
|
|
217
|
+
!["ahead", "identical"].includes(tagLineage?.status) ||
|
|
218
|
+
alphaSha !== runtimeSha ||
|
|
219
|
+
!targetSha ||
|
|
220
|
+
npm["dist-tags"]?.alpha !== version ||
|
|
221
|
+
npm.versions?.[version]?.dist?.integrity !== main.digest
|
|
222
|
+
) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"cross-runtime final public readback does not match durable publication bytes and runtime",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function readPublicResumeState({
|
|
230
|
+
repoInfo,
|
|
231
|
+
targetRef,
|
|
232
|
+
runtimeSha,
|
|
233
|
+
version,
|
|
234
|
+
transaction,
|
|
235
|
+
token,
|
|
236
|
+
apiUrl,
|
|
237
|
+
fetchImpl,
|
|
238
|
+
}) {
|
|
239
|
+
const readRef = async (ref) =>
|
|
240
|
+
(await githubJson({
|
|
241
|
+
apiUrl,
|
|
242
|
+
token,
|
|
243
|
+
fetchImpl,
|
|
244
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/ref/${ref}`,
|
|
245
|
+
})).object?.sha || "";
|
|
246
|
+
const main = transaction?.artifacts?.find((entry) => entry.kind === "npm" && entry.required !== false);
|
|
247
|
+
if (!main || main.ref !== version || !main.digest) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
"cross-runtime recovery requires exact durable npm publication evidence",
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const packageUrl =
|
|
253
|
+
`https://registry.npmjs.org/${encodeURIComponent(main.name)}`;
|
|
254
|
+
const exactTag = transaction.exact_tag || `v${version}`;
|
|
255
|
+
const [targetSha, alphaSha, exactTagSha, npmResponse] = await Promise.all([
|
|
256
|
+
readRef(`heads/${targetRef}`),
|
|
257
|
+
readRef("tags/v4-alpha"),
|
|
258
|
+
readRef(`tags/${exactTag}`),
|
|
259
|
+
fetchImpl(packageUrl),
|
|
260
|
+
]);
|
|
261
|
+
if (!npmResponse.ok) {
|
|
262
|
+
throw new Error(`npm public readback failed with HTTP ${npmResponse.status}`);
|
|
263
|
+
}
|
|
264
|
+
const npm = await npmResponse.json();
|
|
265
|
+
const tagLineage = await githubJson({
|
|
266
|
+
apiUrl,
|
|
267
|
+
token,
|
|
268
|
+
fetchImpl,
|
|
269
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${exactTagSha}...${targetSha}`,
|
|
270
|
+
});
|
|
271
|
+
validateV4RuntimeResumePublicReadback({
|
|
272
|
+
targetRef,
|
|
273
|
+
targetSha,
|
|
274
|
+
alphaSha,
|
|
275
|
+
exactTagSha,
|
|
276
|
+
tagLineage,
|
|
277
|
+
runtimeSha,
|
|
278
|
+
version,
|
|
279
|
+
transaction,
|
|
280
|
+
main,
|
|
281
|
+
npm,
|
|
282
|
+
});
|
|
283
|
+
const body = {
|
|
284
|
+
schemaVersion: 1,
|
|
285
|
+
contract: "kungfu-buildchain-v4-runtime-resume-public-readback/v1",
|
|
286
|
+
observedAt: new Date().toISOString(),
|
|
287
|
+
repository: repoInfo.fullName,
|
|
288
|
+
version,
|
|
289
|
+
refs: {
|
|
290
|
+
target: { ref: targetRef, sha: targetSha },
|
|
291
|
+
floating: { ref: "v4-alpha", sha: alphaSha },
|
|
292
|
+
exactTag: { ref: exactTag, sha: exactTagSha },
|
|
293
|
+
},
|
|
294
|
+
npm: {
|
|
295
|
+
package: main.name,
|
|
296
|
+
version,
|
|
297
|
+
distTag: "alpha",
|
|
298
|
+
integrity: npm.versions[version].dist.integrity,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
return { ...body, root: v4RuntimeResumeDocumentRoot(body) };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function prepareRuntimeResumeEvidence({
|
|
305
|
+
repoInfo,
|
|
306
|
+
targetRef,
|
|
307
|
+
runtimeSha,
|
|
308
|
+
version,
|
|
309
|
+
passport,
|
|
310
|
+
sidecar,
|
|
311
|
+
stageCapsules,
|
|
312
|
+
recovery,
|
|
313
|
+
outputDir,
|
|
314
|
+
}) {
|
|
315
|
+
const authorizationPath = path.resolve(".buildchain/release-candidate/v4-runtime-authorization.json");
|
|
316
|
+
const delegatedRaw = env("BUILDCHAIN_RUNTIME_AUTHORIZATION_JSON").trim();
|
|
317
|
+
const delegatedRoot = env("BUILDCHAIN_RUNTIME_AUTHORIZATION_ROOT").trim();
|
|
318
|
+
const authorizationFileExists = fs.existsSync(authorizationPath);
|
|
319
|
+
if (!authorizationFileExists && (!delegatedRaw || !delegatedRoot)) throw new Error("cross-runtime recovery requires a fresh runtime authorization receipt");
|
|
320
|
+
const delegated = authorizationFileExists ? readOnlyJson([{ absolutePath: authorizationPath }], "runtime authorization") : JSON.parse(delegatedRaw);
|
|
321
|
+
if (!authorizationFileExists && delegated.receiptRoot !== delegatedRoot) throw new Error("fresh recovery runtime authorization handoff root mismatch");
|
|
322
|
+
const delegatedVerification = verifyV4RuntimeAuthorizationReceipt({
|
|
323
|
+
receipt: delegated.receipt,
|
|
324
|
+
receiptRoot: delegated.receiptRoot,
|
|
325
|
+
repository: repoInfo.fullName,
|
|
326
|
+
runtimeSha,
|
|
327
|
+
});
|
|
328
|
+
if (!delegatedVerification.ok) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`fresh recovery runtime authorization rejected: ${delegatedVerification.failures.join(", ")}`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const policy = passport.consumerPolicy.receipt;
|
|
334
|
+
const authorization = authorizeV4RuntimeSelection({
|
|
335
|
+
repository: repoInfo.fullName,
|
|
336
|
+
eventName: "workflow_dispatch",
|
|
337
|
+
mode: "resume",
|
|
338
|
+
actor: delegated.receipt.actor.login,
|
|
339
|
+
actorPermission: delegated.receipt.actor.permission,
|
|
340
|
+
reason:
|
|
341
|
+
`resume sealed candidate run ${passport.workflow.runId} from a tree-equivalent protected source`,
|
|
342
|
+
authorizedAt: new Date().toISOString(),
|
|
343
|
+
sourceSha: sidecar.source.sha,
|
|
344
|
+
sourceTreeSha: sidecar.source.treeSha,
|
|
345
|
+
requestedRef: delegated.receipt.request.ref,
|
|
346
|
+
resolvedRuntimeSha: runtimeSha,
|
|
347
|
+
approvedRefReadbacks: delegated.receipt.runtime.reachableFrom,
|
|
348
|
+
stableContractLockRoot: policy.contractLocks.stable.root,
|
|
349
|
+
alphaContractLockRoot: policy.contractLocks.alpha.root,
|
|
350
|
+
consumerPolicyReceiptRoot: sidecar.consumerPolicyReceiptRoot,
|
|
351
|
+
persistenceScan: trackedRuntimePersistenceScan(),
|
|
352
|
+
});
|
|
353
|
+
const planBody = {
|
|
354
|
+
schemaVersion: 1,
|
|
355
|
+
contract: "kungfu-buildchain-v4-runtime-resume-plan/v1",
|
|
356
|
+
originalRunId: String(passport.workflow.runId),
|
|
357
|
+
resumeRunId: String(env("GITHUB_RUN_ID")),
|
|
358
|
+
recoveryReceiptRoot: recovery.receipt.root,
|
|
359
|
+
requiredPlatforms: stageCapsules.map((entry) => entry.platform),
|
|
360
|
+
reusedCapsuleRoots: stageCapsules.map((entry) => entry.capsuleRoot),
|
|
361
|
+
rebuildPlatforms: [],
|
|
362
|
+
skippedBuildStages: recovery.receipt.skippedBuildStages,
|
|
363
|
+
};
|
|
364
|
+
const plan = { ...planBody, root: v4RuntimeResumeDocumentRoot(planBody) };
|
|
365
|
+
const materialBody = {
|
|
366
|
+
schemaVersion: 1,
|
|
367
|
+
contract: "kungfu-buildchain-v4-runtime-resume-material/v1",
|
|
368
|
+
repository: repoInfo.fullName,
|
|
369
|
+
targetRef,
|
|
370
|
+
runtimeSha,
|
|
371
|
+
version,
|
|
372
|
+
authorization: authorization.receipt,
|
|
373
|
+
authorizationRoot: authorization.receiptRoot,
|
|
374
|
+
buildAttempt: sidecar.buildAttempt,
|
|
375
|
+
resumeAttempt: {
|
|
376
|
+
id: `github-run:${env("GITHUB_RUN_ID")}:attempt:${env("GITHUB_RUN_ATTEMPT", "1")}`,
|
|
377
|
+
runtimeSha,
|
|
378
|
+
},
|
|
379
|
+
source: sidecar.source,
|
|
380
|
+
consumerPolicyReceiptRoot: sidecar.consumerPolicyReceiptRoot,
|
|
381
|
+
requiredPlatforms: stageCapsules.map((entry) => entry.platform),
|
|
382
|
+
stageCapsules,
|
|
383
|
+
resumePlanRoot: plan.root,
|
|
384
|
+
floatingRefBefore: {
|
|
385
|
+
ref: "v4-alpha",
|
|
386
|
+
sha: sidecar.buildAttempt.runtimeSha,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
const material = {
|
|
390
|
+
...materialBody,
|
|
391
|
+
root: v4RuntimeResumeDocumentRoot(materialBody),
|
|
392
|
+
};
|
|
393
|
+
for (const [name, value] of [
|
|
394
|
+
["v4-runtime-resume-plan.json", plan],
|
|
395
|
+
["v4-runtime-resume-material.json", material],
|
|
396
|
+
]) {
|
|
397
|
+
fs.writeFileSync(
|
|
398
|
+
path.join(outputDir, name),
|
|
399
|
+
`${JSON.stringify(value, null, 2)}\n`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return path.join(outputDir, "v4-runtime-resume-evidence.json");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export async function finalizeRuntimeResumeEvidence({
|
|
406
|
+
materialPath,
|
|
407
|
+
transaction,
|
|
408
|
+
token = env("GITHUB_TOKEN", env("INPUT_TOKEN")),
|
|
409
|
+
apiUrl = env("GITHUB_API_URL", "https://api.github.com"),
|
|
410
|
+
fetchImpl = globalThis.fetch,
|
|
411
|
+
outputDir = path.dirname(materialPath),
|
|
412
|
+
}) {
|
|
413
|
+
const material = JSON.parse(fs.readFileSync(materialPath, "utf8"));
|
|
414
|
+
const body = { ...material };
|
|
415
|
+
delete body.root;
|
|
416
|
+
if (
|
|
417
|
+
material.contract !== "kungfu-buildchain-v4-runtime-resume-material/v1" ||
|
|
418
|
+
material.root !== v4RuntimeResumeDocumentRoot(body)
|
|
419
|
+
) {
|
|
420
|
+
throw new Error("cross-runtime resume material root mismatch");
|
|
421
|
+
}
|
|
422
|
+
const repoInfo = splitRepository(material.repository);
|
|
423
|
+
const version = env("BUILDCHAIN_RELEASE_VERSION", material.version);
|
|
424
|
+
const targetRef = env("BUILDCHAIN_RELEASE_TARGET_REF", material.targetRef);
|
|
425
|
+
if (
|
|
426
|
+
version !== material.version ||
|
|
427
|
+
targetRef !== material.targetRef ||
|
|
428
|
+
transaction?.version !== version ||
|
|
429
|
+
transaction?.state !== "complete"
|
|
430
|
+
) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
"cross-runtime resume finalization does not match the completed publication transaction",
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const readback = await readPublicResumeState({
|
|
436
|
+
repoInfo,
|
|
437
|
+
targetRef,
|
|
438
|
+
runtimeSha: material.runtimeSha,
|
|
439
|
+
version,
|
|
440
|
+
transaction,
|
|
441
|
+
token,
|
|
442
|
+
apiUrl,
|
|
443
|
+
fetchImpl,
|
|
444
|
+
});
|
|
445
|
+
const resumed = createV4RuntimeResumeLineage({
|
|
446
|
+
authorization: material.authorization,
|
|
447
|
+
authorizationRoot: material.authorizationRoot,
|
|
448
|
+
buildAttempt: material.buildAttempt,
|
|
449
|
+
resumeAttempt: material.resumeAttempt,
|
|
450
|
+
source: material.source,
|
|
451
|
+
consumerPolicyReceiptRoot: material.consumerPolicyReceiptRoot,
|
|
452
|
+
requiredPlatforms: material.requiredPlatforms,
|
|
453
|
+
stageCapsules: material.stageCapsules,
|
|
454
|
+
resumePlanRoot: material.resumePlanRoot,
|
|
455
|
+
finalPublicReadbackRoot: readback.root,
|
|
456
|
+
floatingRefBefore: material.floatingRefBefore,
|
|
457
|
+
floatingRefAfter: { ref: "v4-alpha", sha: material.runtimeSha },
|
|
458
|
+
});
|
|
459
|
+
const evidence = {
|
|
460
|
+
authorization: material.authorization,
|
|
461
|
+
authorizationRoot: material.authorizationRoot,
|
|
462
|
+
lineage: resumed.lineage,
|
|
463
|
+
lineageRoot: resumed.lineageRoot,
|
|
464
|
+
};
|
|
465
|
+
const evidencePath = path.join(outputDir, "v4-runtime-resume-evidence.json");
|
|
466
|
+
for (const [name, value] of [
|
|
467
|
+
["v4-runtime-resume-public-readback.json", readback],
|
|
468
|
+
[path.basename(evidencePath), evidence],
|
|
469
|
+
]) {
|
|
470
|
+
fs.writeFileSync(
|
|
471
|
+
path.join(outputDir, name),
|
|
472
|
+
`${JSON.stringify(value, null, 2)}\n`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
return { evidence, path: evidencePath, readback };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function readTransactionAtRef({ repoInfo, apiUrl, token, fetchImpl, stateRef }) {
|
|
105
479
|
const response = await githubJson({
|
|
106
480
|
apiUrl,
|
|
107
481
|
token,
|
|
@@ -110,10 +484,65 @@ async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, ver
|
|
|
110
484
|
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/state.json?ref=${encodeURIComponent(stateRef)}`,
|
|
111
485
|
});
|
|
112
486
|
if (!response) return undefined;
|
|
113
|
-
if (
|
|
114
|
-
|
|
487
|
+
if (
|
|
488
|
+
response.type !== "file" ||
|
|
489
|
+
response.encoding !== "base64" ||
|
|
490
|
+
!response.content
|
|
491
|
+
) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`durable transaction ${stateRef} did not expose a base64 state.json file`,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return JSON.parse(
|
|
497
|
+
Buffer.from(
|
|
498
|
+
String(response.content).replace(/\s/g, ""),
|
|
499
|
+
"base64",
|
|
500
|
+
).toString("utf8"),
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function readExistingTransaction({
|
|
505
|
+
repoInfo,
|
|
506
|
+
apiUrl,
|
|
507
|
+
token,
|
|
508
|
+
fetchImpl,
|
|
509
|
+
version,
|
|
510
|
+
}) {
|
|
511
|
+
return readTransactionAtRef({
|
|
512
|
+
repoInfo,
|
|
513
|
+
apiUrl,
|
|
514
|
+
token,
|
|
515
|
+
fetchImpl,
|
|
516
|
+
stateRef: releaseTransactionStateRef(version),
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function readExistingTransactionById({
|
|
521
|
+
repoInfo,
|
|
522
|
+
apiUrl,
|
|
523
|
+
token,
|
|
524
|
+
fetchImpl,
|
|
525
|
+
transactionId,
|
|
526
|
+
}) {
|
|
527
|
+
if (!transactionId) return undefined;
|
|
528
|
+
const statePrefix = "buildchain/release-state/";
|
|
529
|
+
const refs = await githubJson({
|
|
530
|
+
apiUrl,
|
|
531
|
+
token,
|
|
532
|
+
fetchImpl,
|
|
533
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/matching-refs/heads/${statePrefix}`,
|
|
534
|
+
});
|
|
535
|
+
for (const ref of refs) {
|
|
536
|
+
const transaction = await readTransactionAtRef({
|
|
537
|
+
repoInfo,
|
|
538
|
+
apiUrl,
|
|
539
|
+
token,
|
|
540
|
+
fetchImpl,
|
|
541
|
+
stateRef: String(ref.ref).replace(/^refs\/heads\//, ""),
|
|
542
|
+
});
|
|
543
|
+
if (transaction?.id === transactionId) return transaction;
|
|
115
544
|
}
|
|
116
|
-
|
|
545
|
+
throw new Error(`durable publication transaction ${transactionId} was not found`);
|
|
117
546
|
}
|
|
118
547
|
|
|
119
548
|
function outputPath(filePath) {
|
|
@@ -156,7 +585,7 @@ async function downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir,
|
|
|
156
585
|
};
|
|
157
586
|
}
|
|
158
587
|
|
|
159
|
-
function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
|
|
588
|
+
export function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
|
|
160
589
|
const names = new Set([selected.passport.name, selected.summary.name]);
|
|
161
590
|
for (const reference of passport.controllerReceipts || []) {
|
|
162
591
|
if (!reference.artifact) throw new Error(`Passport controller receipt ${reference.controllerId} has no artifact identity`);
|
|
@@ -171,11 +600,9 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
|
|
|
171
600
|
for (const artifact of artifacts) {
|
|
172
601
|
if (manifestPattern.test(String(artifact.name || ""))) names.add(artifact.name);
|
|
173
602
|
}
|
|
174
|
-
const
|
|
175
|
-
for (const
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
return names;
|
|
603
|
+
const publicationNames = new Set(selectPayloadArtifacts({ artifacts, artifactName: selected.prefix, sourceSha: selected.sourceSha, patterns: artifactPatterns }).map((artifact) => artifact.name));
|
|
604
|
+
for (const name of publicationNames) names.add(name);
|
|
605
|
+
return { names, publicationNames };
|
|
179
606
|
}
|
|
180
607
|
|
|
181
608
|
export function normalizePlatformManifests(downloads, passport) {
|
|
@@ -352,7 +779,15 @@ async function recoverCandidateEvidence({
|
|
|
352
779
|
for (const artifact of [selected.passport, selected.summary]) initialDownloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
353
780
|
const passport = readOnlyJson(initialDownloads[0].files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "release-candidate-passport.json");
|
|
354
781
|
const buildSummary = readOnlyJson(initialDownloads[1].files.filter((file) => path.basename(file.path) === "build-summary.json"), "build-summary.json");
|
|
355
|
-
const
|
|
782
|
+
const sidecarFiles = initialDownloads[0].files.filter(
|
|
783
|
+
(file) =>
|
|
784
|
+
path.basename(file.path) === "release-candidate-stage-capsules.json",
|
|
785
|
+
);
|
|
786
|
+
const stageCapsuleSidecar =
|
|
787
|
+
sidecarFiles.length === 1
|
|
788
|
+
? readOnlyJson(sidecarFiles, "release-candidate-stage-capsules.json")
|
|
789
|
+
: undefined;
|
|
790
|
+
const { names: requiredNames, publicationNames } = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
|
|
356
791
|
const chosen = artifacts.filter((artifact) => requiredNames.has(artifact.name));
|
|
357
792
|
if (chosen.length !== requiredNames.size) {
|
|
358
793
|
const found = new Set(chosen.map((artifact) => artifact.name));
|
|
@@ -363,7 +798,7 @@ async function recoverCandidateEvidence({
|
|
|
363
798
|
for (const artifact of chosen.filter((entry) => ![selected.passport.id, selected.summary.id].includes(entry.id))) downloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
364
799
|
return {
|
|
365
800
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
366
|
-
passport, buildSummary, chosen, downloads,
|
|
801
|
+
passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
|
|
367
802
|
};
|
|
368
803
|
}
|
|
369
804
|
|
|
@@ -373,6 +808,52 @@ async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionI
|
|
|
373
808
|
return { status: comparison.status, mergeIsAncestor: ["ahead", "identical"].includes(comparison.status) };
|
|
374
809
|
}
|
|
375
810
|
|
|
811
|
+
async function resolveRecoveryTransaction({
|
|
812
|
+
repoInfo,
|
|
813
|
+
apiUrl,
|
|
814
|
+
token,
|
|
815
|
+
fetchImpl,
|
|
816
|
+
transactionId,
|
|
817
|
+
publicationVersion,
|
|
818
|
+
}) {
|
|
819
|
+
const byId = await readExistingTransactionById({
|
|
820
|
+
repoInfo,
|
|
821
|
+
apiUrl,
|
|
822
|
+
token,
|
|
823
|
+
fetchImpl,
|
|
824
|
+
transactionId,
|
|
825
|
+
});
|
|
826
|
+
const version = byId?.version || publicationVersion;
|
|
827
|
+
const transaction = byId || await readExistingTransaction({
|
|
828
|
+
repoInfo,
|
|
829
|
+
apiUrl,
|
|
830
|
+
token,
|
|
831
|
+
fetchImpl,
|
|
832
|
+
version,
|
|
833
|
+
});
|
|
834
|
+
return { version, transaction };
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function resolveRecoveredStageCapsules({
|
|
838
|
+
candidateRuntimeSha,
|
|
839
|
+
runtimeSha,
|
|
840
|
+
sidecar,
|
|
841
|
+
passport,
|
|
842
|
+
downloads,
|
|
843
|
+
}) {
|
|
844
|
+
if (candidateRuntimeSha === runtimeSha) return [];
|
|
845
|
+
return verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads });
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot) {
|
|
849
|
+
const runId = String(candidateRunId || "").trim();
|
|
850
|
+
if (!/^\d+$/.test(runId)) throw new Error("candidate run ID must be numeric");
|
|
851
|
+
if (!String(expectedSourceTree || "").trim() && !String(expectedCandidateRoot || "").trim()) {
|
|
852
|
+
throw new Error("candidate recovery requires expectedSourceTree or expectedCandidateRoot");
|
|
853
|
+
}
|
|
854
|
+
return runId;
|
|
855
|
+
}
|
|
856
|
+
|
|
376
857
|
export async function resumeFromCandidateRun({
|
|
377
858
|
repository, targetRepository = repository, candidateRunId,
|
|
378
859
|
expectedWorkflowFile, expectedWorkflowName,
|
|
@@ -387,21 +868,23 @@ export async function resumeFromCandidateRun({
|
|
|
387
868
|
recoveryRunId = env("GITHUB_RUN_ID"), fetchImpl = globalThis.fetch,
|
|
388
869
|
} = {}) {
|
|
389
870
|
const repoInfo = splitRepository(repository);
|
|
390
|
-
const runId =
|
|
391
|
-
if (!/^\d+$/.test(runId)) throw new Error("candidate run ID must be numeric");
|
|
392
|
-
if (!String(expectedSourceTree || "").trim() && !String(expectedCandidateRoot || "").trim()) {
|
|
393
|
-
throw new Error("candidate recovery requires expectedSourceTree or expectedCandidateRoot");
|
|
394
|
-
}
|
|
871
|
+
const runId = validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot);
|
|
395
872
|
const archiveDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-resume-"));
|
|
396
873
|
try {
|
|
397
874
|
const {
|
|
398
875
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
399
|
-
passport, buildSummary, chosen, downloads,
|
|
876
|
+
passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
|
|
400
877
|
} = await recoverCandidateEvidence({
|
|
401
878
|
repoInfo, runId, artifactName, artifactPatterns, requiredArtifactCount,
|
|
402
879
|
outputDir, apiUrl, token, fetchImpl, archiveDir,
|
|
403
880
|
});
|
|
404
|
-
const
|
|
881
|
+
const { anchorProvenance, provenancePassport } = await recoverCandidateProvenance({
|
|
882
|
+
passport, buildSummary, transactionId, repoInfo, artifactName,
|
|
883
|
+
expectedWorkflowFile, expectedWorkflowName, channel, apiUrl, token,
|
|
884
|
+
fetchImpl, archiveDir, bundleRoot, githubJson, selectReleaseCandidateArtifacts,
|
|
885
|
+
downloadArtifact, readOnlyJson,
|
|
886
|
+
});
|
|
887
|
+
const prNumber = Number(provenancePassport.pullRequest?.number || 0);
|
|
405
888
|
if (!prNumber) throw new Error("Release Candidate Passport has no PR identity");
|
|
406
889
|
const pullRequest = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/pulls/${prNumber}` });
|
|
407
890
|
const targetCommit = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/commits/${targetSha}` });
|
|
@@ -412,7 +895,7 @@ export async function resumeFromCandidateRun({
|
|
|
412
895
|
const controllerReceipts = normalizeControllerReceipts(downloads, passport);
|
|
413
896
|
const productPayloadManifests = normalizeProductPayloadManifests(downloads);
|
|
414
897
|
const publication = createRecoveredPublication({
|
|
415
|
-
downloads,
|
|
898
|
+
downloads: downloads.filter(({ artifact }) => publicationNames.has(artifact.name)),
|
|
416
899
|
bundleRoot,
|
|
417
900
|
repository: repoInfo.fullName,
|
|
418
901
|
passport,
|
|
@@ -422,8 +905,24 @@ export async function resumeFromCandidateRun({
|
|
|
422
905
|
releasePatterns,
|
|
423
906
|
platformManifests: platformManifestEvidence.manifests,
|
|
424
907
|
});
|
|
425
|
-
const
|
|
426
|
-
|
|
908
|
+
const stageCapsules = resolveRecoveredStageCapsules({
|
|
909
|
+
candidateRuntimeSha,
|
|
910
|
+
runtimeSha,
|
|
911
|
+
sidecar: stageCapsuleSidecar,
|
|
912
|
+
passport,
|
|
913
|
+
downloads,
|
|
914
|
+
});
|
|
915
|
+
const {
|
|
916
|
+
version: candidateVersion,
|
|
917
|
+
transaction: existingTransaction,
|
|
918
|
+
} = await resolveRecoveryTransaction({
|
|
919
|
+
repoInfo,
|
|
920
|
+
apiUrl,
|
|
921
|
+
token,
|
|
922
|
+
fetchImpl,
|
|
923
|
+
transactionId,
|
|
924
|
+
publicationVersion: publication.version,
|
|
925
|
+
});
|
|
427
926
|
const targetAdvance = await resolveTargetAdvance({
|
|
428
927
|
observedTargetSha, targetSha, transactionId, existingTransaction,
|
|
429
928
|
repoInfo, apiUrl, token, fetchImpl,
|
|
@@ -451,20 +950,9 @@ export async function resumeFromCandidateRun({
|
|
|
451
950
|
expectedRuntimeSha: candidateRuntimeSha,
|
|
452
951
|
expectedTransactionId: transactionId,
|
|
453
952
|
existingTransaction,
|
|
454
|
-
run:
|
|
455
|
-
id: String(run.id),
|
|
456
|
-
repository: repoInfo.fullName,
|
|
457
|
-
headRepository: run.head_repository?.full_name || "",
|
|
458
|
-
status: run.status,
|
|
459
|
-
conclusion: run.conclusion,
|
|
460
|
-
event: run.event,
|
|
461
|
-
path: run.path,
|
|
462
|
-
name: run.name,
|
|
463
|
-
headSha: run.head_sha || "",
|
|
464
|
-
headBranch: run.head_branch || "",
|
|
465
|
-
pullRequestNumbers: (run.pull_requests || []).map((entry) => Number(entry.number)),
|
|
466
|
-
},
|
|
953
|
+
run: normalizeCandidateRun(run, repoInfo.fullName),
|
|
467
954
|
workflow: { path: workflow.path, name: workflow.name, state: workflow.state },
|
|
955
|
+
anchorProvenance: normalizeAnchorProvenance(anchorProvenance, repoInfo.fullName),
|
|
468
956
|
pullRequest: {
|
|
469
957
|
number: pullRequest.number,
|
|
470
958
|
merged: pullRequest.merged === true,
|
|
@@ -494,6 +982,19 @@ export async function resumeFromCandidateRun({
|
|
|
494
982
|
if (publication.manifest) fs.writeFileSync(sealedManifestPath, `${JSON.stringify(publication.manifest, null, 2)}\n`);
|
|
495
983
|
const publishRequiredArtifacts = publication.publishRequiredArtifacts;
|
|
496
984
|
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(publishRequiredArtifacts, null, 2)}\n`);
|
|
985
|
+
const runtimeResumeEvidencePath = stageCapsules.length
|
|
986
|
+
? prepareRuntimeResumeEvidence({
|
|
987
|
+
repoInfo,
|
|
988
|
+
targetRef,
|
|
989
|
+
runtimeSha,
|
|
990
|
+
version: candidateVersion,
|
|
991
|
+
passport,
|
|
992
|
+
sidecar: stageCapsuleSidecar,
|
|
993
|
+
stageCapsules,
|
|
994
|
+
recovery,
|
|
995
|
+
outputDir: resolvedOutput,
|
|
996
|
+
})
|
|
997
|
+
: "";
|
|
497
998
|
const tarballs = publication.npmArtifacts.map((entry) => outputPath(entry.file.absolutePath));
|
|
498
999
|
return {
|
|
499
1000
|
enabled: true,
|
|
@@ -517,6 +1018,9 @@ export async function resumeFromCandidateRun({
|
|
|
517
1018
|
sealedBundleRoot: publication.manifest ? outputPath(bundleRoot) : "",
|
|
518
1019
|
sealedBundleManifest: publication.manifest ? outputPath(sealedManifestPath) : "",
|
|
519
1020
|
recoveryReceipt: outputPath(recoveryReceiptPath),
|
|
1021
|
+
runtimeResumeEvidence: runtimeResumeEvidencePath
|
|
1022
|
+
? outputPath(runtimeResumeEvidencePath)
|
|
1023
|
+
: "",
|
|
520
1024
|
},
|
|
521
1025
|
};
|
|
522
1026
|
} finally {
|
|
@@ -568,6 +1072,10 @@ export async function resumeFromCandidateRunCli() {
|
|
|
568
1072
|
"release-candidate-run-url": result.run.url,
|
|
569
1073
|
"release-candidate-recovery-receipt-path": result.paths.recoveryReceipt,
|
|
570
1074
|
"release-candidate-recovery-root": result.receipt.root,
|
|
1075
|
+
"v4-runtime-resume-evidence-path": result.paths.runtimeResumeEvidence,
|
|
1076
|
+
"v4-runtime-resume-finalize-command": result.paths.runtimeResumeEvidence
|
|
1077
|
+
? "node .buildchain/runtime/promotion-shell/scripts/resume-from-candidate-run.mjs finalize"
|
|
1078
|
+
: "",
|
|
571
1079
|
"release-candidate-root": result.candidateRoot,
|
|
572
1080
|
"release-candidate-artifact-root": result.artifactRoot,
|
|
573
1081
|
"publish-sealed-bundle-root": result.paths.sealedBundleRoot,
|
|
@@ -589,7 +1097,21 @@ export async function resumeFromCandidateRunCli() {
|
|
|
589
1097
|
}
|
|
590
1098
|
|
|
591
1099
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
592
|
-
|
|
1100
|
+
const operation = process.argv[2] === "finalize"
|
|
1101
|
+
? finalizeRuntimeResumeEvidence({
|
|
1102
|
+
materialPath: requiredEnv("BUILDCHAIN_V4_RUNTIME_RESUME_MATERIAL"),
|
|
1103
|
+
transaction: JSON.parse(
|
|
1104
|
+
requiredEnv("BUILDCHAIN_RELEASE_TRANSACTION_JSON"),
|
|
1105
|
+
),
|
|
1106
|
+
outputDir: env(
|
|
1107
|
+
"BUILDCHAIN_RELEASE_PASSPORT_OUTPUT_DIR",
|
|
1108
|
+
path.dirname(requiredEnv("BUILDCHAIN_V4_RUNTIME_RESUME_MATERIAL")),
|
|
1109
|
+
),
|
|
1110
|
+
}).then((result) =>
|
|
1111
|
+
console.log(JSON.stringify({ files: [outputPath(result.path)] })),
|
|
1112
|
+
)
|
|
1113
|
+
: resumeFromCandidateRunCli();
|
|
1114
|
+
operation.catch((error) => {
|
|
593
1115
|
const failure = error.recoveryFailure || recoveryFailure(error);
|
|
594
1116
|
console.error(`candidate recovery rejected [${failure.code}]: ${failure.reason}`);
|
|
595
1117
|
console.error(`next action: ${failure.nextAction}`);
|