@kungfu-tech/buildchain 4.0.0 → 4.0.1-alpha.10
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 +565 -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,373 @@ 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(
|
|
316
|
+
".buildchain/release-candidate/v4-runtime-authorization.json",
|
|
317
|
+
);
|
|
318
|
+
if (!fs.existsSync(authorizationPath)) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
"cross-runtime recovery requires a fresh runtime authorization receipt",
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
const delegated = readOnlyJson([{ absolutePath: authorizationPath }], "runtime authorization");
|
|
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 }) {
|
|
105
481
|
const response = await githubJson({
|
|
106
482
|
apiUrl,
|
|
107
483
|
token,
|
|
@@ -110,10 +486,65 @@ async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, ver
|
|
|
110
486
|
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/state.json?ref=${encodeURIComponent(stateRef)}`,
|
|
111
487
|
});
|
|
112
488
|
if (!response) return undefined;
|
|
113
|
-
if (
|
|
114
|
-
|
|
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
|
+
);
|
|
115
497
|
}
|
|
116
|
-
return JSON.parse(
|
|
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;
|
|
546
|
+
}
|
|
547
|
+
throw new Error(`durable publication transaction ${transactionId} was not found`);
|
|
117
548
|
}
|
|
118
549
|
|
|
119
550
|
function outputPath(filePath) {
|
|
@@ -156,7 +587,7 @@ async function downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir,
|
|
|
156
587
|
};
|
|
157
588
|
}
|
|
158
589
|
|
|
159
|
-
function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
|
|
590
|
+
export function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
|
|
160
591
|
const names = new Set([selected.passport.name, selected.summary.name]);
|
|
161
592
|
for (const reference of passport.controllerReceipts || []) {
|
|
162
593
|
if (!reference.artifact) throw new Error(`Passport controller receipt ${reference.controllerId} has no artifact identity`);
|
|
@@ -171,11 +602,9 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
|
|
|
171
602
|
for (const artifact of artifacts) {
|
|
172
603
|
if (manifestPattern.test(String(artifact.name || ""))) names.add(artifact.name);
|
|
173
604
|
}
|
|
174
|
-
const
|
|
175
|
-
for (const
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
return names;
|
|
605
|
+
const publicationNames = new Set(selectPayloadArtifacts({ artifacts, artifactName: selected.prefix, sourceSha: selected.sourceSha, patterns: artifactPatterns }).map((artifact) => artifact.name));
|
|
606
|
+
for (const name of publicationNames) names.add(name);
|
|
607
|
+
return { names, publicationNames };
|
|
179
608
|
}
|
|
180
609
|
|
|
181
610
|
export function normalizePlatformManifests(downloads, passport) {
|
|
@@ -352,7 +781,15 @@ async function recoverCandidateEvidence({
|
|
|
352
781
|
for (const artifact of [selected.passport, selected.summary]) initialDownloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
353
782
|
const passport = readOnlyJson(initialDownloads[0].files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "release-candidate-passport.json");
|
|
354
783
|
const buildSummary = readOnlyJson(initialDownloads[1].files.filter((file) => path.basename(file.path) === "build-summary.json"), "build-summary.json");
|
|
355
|
-
const
|
|
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;
|
|
792
|
+
const { names: requiredNames, publicationNames } = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
|
|
356
793
|
const chosen = artifacts.filter((artifact) => requiredNames.has(artifact.name));
|
|
357
794
|
if (chosen.length !== requiredNames.size) {
|
|
358
795
|
const found = new Set(chosen.map((artifact) => artifact.name));
|
|
@@ -363,7 +800,7 @@ async function recoverCandidateEvidence({
|
|
|
363
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 }));
|
|
364
801
|
return {
|
|
365
802
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
366
|
-
passport, buildSummary, chosen, downloads,
|
|
803
|
+
passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
|
|
367
804
|
};
|
|
368
805
|
}
|
|
369
806
|
|
|
@@ -373,6 +810,52 @@ async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionI
|
|
|
373
810
|
return { status: comparison.status, mergeIsAncestor: ["ahead", "identical"].includes(comparison.status) };
|
|
374
811
|
}
|
|
375
812
|
|
|
813
|
+
async function resolveRecoveryTransaction({
|
|
814
|
+
repoInfo,
|
|
815
|
+
apiUrl,
|
|
816
|
+
token,
|
|
817
|
+
fetchImpl,
|
|
818
|
+
transactionId,
|
|
819
|
+
publicationVersion,
|
|
820
|
+
}) {
|
|
821
|
+
const byId = await readExistingTransactionById({
|
|
822
|
+
repoInfo,
|
|
823
|
+
apiUrl,
|
|
824
|
+
token,
|
|
825
|
+
fetchImpl,
|
|
826
|
+
transactionId,
|
|
827
|
+
});
|
|
828
|
+
const version = byId?.version || publicationVersion;
|
|
829
|
+
const transaction = byId || await readExistingTransaction({
|
|
830
|
+
repoInfo,
|
|
831
|
+
apiUrl,
|
|
832
|
+
token,
|
|
833
|
+
fetchImpl,
|
|
834
|
+
version,
|
|
835
|
+
});
|
|
836
|
+
return { version, 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
|
+
|
|
376
859
|
export async function resumeFromCandidateRun({
|
|
377
860
|
repository, targetRepository = repository, candidateRunId,
|
|
378
861
|
expectedWorkflowFile, expectedWorkflowName,
|
|
@@ -387,21 +870,23 @@ export async function resumeFromCandidateRun({
|
|
|
387
870
|
recoveryRunId = env("GITHUB_RUN_ID"), fetchImpl = globalThis.fetch,
|
|
388
871
|
} = {}) {
|
|
389
872
|
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
|
-
}
|
|
873
|
+
const runId = validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot);
|
|
395
874
|
const archiveDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-resume-"));
|
|
396
875
|
try {
|
|
397
876
|
const {
|
|
398
877
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
399
|
-
passport, buildSummary, chosen, downloads,
|
|
878
|
+
passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
|
|
400
879
|
} = await recoverCandidateEvidence({
|
|
401
880
|
repoInfo, runId, artifactName, artifactPatterns, requiredArtifactCount,
|
|
402
881
|
outputDir, apiUrl, token, fetchImpl, archiveDir,
|
|
403
882
|
});
|
|
404
|
-
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);
|
|
405
890
|
if (!prNumber) throw new Error("Release Candidate Passport has no PR identity");
|
|
406
891
|
const pullRequest = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/pulls/${prNumber}` });
|
|
407
892
|
const targetCommit = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/commits/${targetSha}` });
|
|
@@ -412,7 +897,7 @@ export async function resumeFromCandidateRun({
|
|
|
412
897
|
const controllerReceipts = normalizeControllerReceipts(downloads, passport);
|
|
413
898
|
const productPayloadManifests = normalizeProductPayloadManifests(downloads);
|
|
414
899
|
const publication = createRecoveredPublication({
|
|
415
|
-
downloads,
|
|
900
|
+
downloads: downloads.filter(({ artifact }) => publicationNames.has(artifact.name)),
|
|
416
901
|
bundleRoot,
|
|
417
902
|
repository: repoInfo.fullName,
|
|
418
903
|
passport,
|
|
@@ -422,8 +907,24 @@ export async function resumeFromCandidateRun({
|
|
|
422
907
|
releasePatterns,
|
|
423
908
|
platformManifests: platformManifestEvidence.manifests,
|
|
424
909
|
});
|
|
425
|
-
const
|
|
426
|
-
|
|
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
|
+
});
|
|
427
928
|
const targetAdvance = await resolveTargetAdvance({
|
|
428
929
|
observedTargetSha, targetSha, transactionId, existingTransaction,
|
|
429
930
|
repoInfo, apiUrl, token, fetchImpl,
|
|
@@ -451,20 +952,9 @@ export async function resumeFromCandidateRun({
|
|
|
451
952
|
expectedRuntimeSha: candidateRuntimeSha,
|
|
452
953
|
expectedTransactionId: transactionId,
|
|
453
954
|
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
|
-
},
|
|
955
|
+
run: normalizeCandidateRun(run, repoInfo.fullName),
|
|
467
956
|
workflow: { path: workflow.path, name: workflow.name, state: workflow.state },
|
|
957
|
+
anchorProvenance: normalizeAnchorProvenance(anchorProvenance, repoInfo.fullName),
|
|
468
958
|
pullRequest: {
|
|
469
959
|
number: pullRequest.number,
|
|
470
960
|
merged: pullRequest.merged === true,
|
|
@@ -494,6 +984,19 @@ export async function resumeFromCandidateRun({
|
|
|
494
984
|
if (publication.manifest) fs.writeFileSync(sealedManifestPath, `${JSON.stringify(publication.manifest, null, 2)}\n`);
|
|
495
985
|
const publishRequiredArtifacts = publication.publishRequiredArtifacts;
|
|
496
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
|
+
: "";
|
|
497
1000
|
const tarballs = publication.npmArtifacts.map((entry) => outputPath(entry.file.absolutePath));
|
|
498
1001
|
return {
|
|
499
1002
|
enabled: true,
|
|
@@ -517,6 +1020,9 @@ export async function resumeFromCandidateRun({
|
|
|
517
1020
|
sealedBundleRoot: publication.manifest ? outputPath(bundleRoot) : "",
|
|
518
1021
|
sealedBundleManifest: publication.manifest ? outputPath(sealedManifestPath) : "",
|
|
519
1022
|
recoveryReceipt: outputPath(recoveryReceiptPath),
|
|
1023
|
+
runtimeResumeEvidence: runtimeResumeEvidencePath
|
|
1024
|
+
? outputPath(runtimeResumeEvidencePath)
|
|
1025
|
+
: "",
|
|
520
1026
|
},
|
|
521
1027
|
};
|
|
522
1028
|
} finally {
|
|
@@ -568,6 +1074,10 @@ export async function resumeFromCandidateRunCli() {
|
|
|
568
1074
|
"release-candidate-run-url": result.run.url,
|
|
569
1075
|
"release-candidate-recovery-receipt-path": result.paths.recoveryReceipt,
|
|
570
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
|
+
: "",
|
|
571
1081
|
"release-candidate-root": result.candidateRoot,
|
|
572
1082
|
"release-candidate-artifact-root": result.artifactRoot,
|
|
573
1083
|
"publish-sealed-bundle-root": result.paths.sealedBundleRoot,
|
|
@@ -589,7 +1099,21 @@ export async function resumeFromCandidateRunCli() {
|
|
|
589
1099
|
}
|
|
590
1100
|
|
|
591
1101
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
592
|
-
|
|
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) => {
|
|
593
1117
|
const failure = error.recoveryFailure || recoveryFailure(error);
|
|
594
1118
|
console.error(`candidate recovery rejected [${failure.code}]: ${failure.reason}`);
|
|
595
1119
|
console.error(`next action: ${failure.nextAction}`);
|