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