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