@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actions/release-tail/README.md +8 -0
- package/architecture/ci-lane-change-budget.json +187 -0
- package/architecture/decisions/0002-next-development-transition.md +101 -0
- package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +211 -8
- package/architecture/maintainability-policy.json +113 -28
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +91 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-delivery-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +32 -3
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/dev-delivery-authority-v2.schema.json +662 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +5 -0
- package/dist/site/artifact-schemas.json +10 -0
- package/dist/site/buildchain-contract.json +1475 -27
- package/dist/site/buildchain-site.json +627 -31
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +205 -3
- package/dist/site/kfd-claims.json +490 -16
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +52 -6
- package/dist/site/node-api-registry.json +14736 -8851
- package/dist/site/page-registry.json +593 -21
- package/dist/site/public-surface-audit.json +837 -48
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +24 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +34 -10
- package/dist/site/workflow-registry.json +160 -10
- package/docs/MAP.md +8 -2
- package/docs/cli-reference.md +404 -13
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +351 -38
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +646 -293
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +34 -6
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +96 -143
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +592 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +562 -0
- package/packages/core/dev-delivery-process-boundary.js +563 -0
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +587 -0
- package/packages/core/dev-delivery-warrant.js +328 -370
- package/packages/core/github-governance-authority.js +1 -0
- package/packages/core/index.js +5 -0
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +70 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +36 -9
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +18 -4
- package/packages/core/v4-floating-consumer-policy.js +143 -20
- package/packages/core/v4-floating-consumer-release-passport.js +32 -5
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/packages/core/workflow-yaml-contract.js +24 -2
- package/scripts/buildchain-cli-help.mjs +41 -2
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +231 -0
- package/scripts/dev-delivery-warrant.mjs +232 -194
- package/scripts/dev-pr-auto-merge.mjs +1 -1
- package/scripts/dev-pr-delivery-warrant.mjs +11 -0
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +69 -4
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/release-train-self-dogfood.mjs +94 -92
- package/scripts/resume-from-candidate-run.mjs +557 -30
- package/scripts/site-capability-metadata.mjs +41 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v4-consumer-policy.mjs +5 -5
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
- package/templates/native-dev-delivery.yml +143 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
stableJson,
|
|
13
13
|
workCheck,
|
|
14
14
|
} from "./paper-repository.js";
|
|
15
|
+
import { mergeNextDevelopmentAgentInstructions } from "./next-development-projection.js";
|
|
15
16
|
|
|
16
17
|
export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
|
|
17
18
|
export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
|
|
@@ -167,9 +168,11 @@ export function paperAgentEntryFiles({
|
|
|
167
168
|
[PAPER_PATHS.agentEntry, jsonText(entry)],
|
|
168
169
|
[
|
|
169
170
|
PAPER_PATHS.agentInstructions,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
mergeNextDevelopmentAgentInstructions(
|
|
172
|
+
mergePaperAgentEntryInstructions(currentAgents, {
|
|
173
|
+
developmentRef: resolvedDevelopmentRef,
|
|
174
|
+
}),
|
|
175
|
+
),
|
|
173
176
|
],
|
|
174
177
|
]);
|
|
175
178
|
}
|
|
@@ -205,8 +208,11 @@ function ciContext({ env, developmentRef }) {
|
|
|
205
208
|
sourceBranch.startsWith(versionStatePrefix) &&
|
|
206
209
|
/^[0-9a-f]{12}$/i.test(sourceBranch.slice(versionStatePrefix.length));
|
|
207
210
|
const branchOk = pullRequest
|
|
208
|
-
? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
209
|
-
|
|
211
|
+
? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
212
|
+
targetBranch === developmentRef) ||
|
|
213
|
+
(channelIndex > 0 &&
|
|
214
|
+
sourceBranch === channelBranches[channelIndex - 1]) ||
|
|
215
|
+
generatedVersionState
|
|
210
216
|
: channelBranches.includes(refName);
|
|
211
217
|
return {
|
|
212
218
|
mode: "ci",
|
package/packages/core/paper.js
CHANGED
|
@@ -46,6 +46,11 @@ import {
|
|
|
46
46
|
scaffoldReadme,
|
|
47
47
|
} from "./paper-scaffold-content.js";
|
|
48
48
|
import { executePaperNpmBootstrap as executePaperNpmBootstrapOperation } from "./paper-npm-bootstrap.js";
|
|
49
|
+
import {
|
|
50
|
+
appendNextDevelopmentToml,
|
|
51
|
+
nextDevelopmentWorkflowHeader,
|
|
52
|
+
projectNextDevelopmentToml,
|
|
53
|
+
} from "./next-development-projection.js";
|
|
49
54
|
|
|
50
55
|
export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
|
|
51
56
|
export {
|
|
@@ -350,7 +355,7 @@ immutable_base_url = ${tomlString(joinUrl(siteBaseUrl, "archive").replace(/\/$/,
|
|
|
350
355
|
registry_path = ".buildchain/publication/publication-registry.json"
|
|
351
356
|
`
|
|
352
357
|
: "";
|
|
353
|
-
return `schema = 1
|
|
358
|
+
return appendNextDevelopmentToml(`schema = 1
|
|
354
359
|
|
|
355
360
|
[project]
|
|
356
361
|
type = "publication-artifact"
|
|
@@ -384,14 +389,14 @@ command = "make pdf"
|
|
|
384
389
|
|
|
385
390
|
[lifecycle.verify]
|
|
386
391
|
command = "make check"
|
|
387
|
-
|
|
392
|
+
`);
|
|
388
393
|
}
|
|
389
394
|
|
|
390
395
|
function scaffoldBuildWorkflow(
|
|
391
396
|
buildchainSha,
|
|
392
397
|
{ artifactName = "paper-publication" } = {},
|
|
393
398
|
) {
|
|
394
|
-
return
|
|
399
|
+
return `${nextDevelopmentWorkflowHeader()}name: Build
|
|
395
400
|
|
|
396
401
|
on:
|
|
397
402
|
workflow_dispatch:
|
|
@@ -419,7 +424,7 @@ jobs:
|
|
|
419
424
|
}
|
|
420
425
|
|
|
421
426
|
function scaffoldVerifyWorkflow(buildchainSha) {
|
|
422
|
-
return
|
|
427
|
+
return `${nextDevelopmentWorkflowHeader()}name: Verify
|
|
423
428
|
|
|
424
429
|
on:
|
|
425
430
|
pull_request:
|
|
@@ -450,7 +455,7 @@ function scaffoldReleaseWorkflow(
|
|
|
450
455
|
const passportInput = releasePassportProductName
|
|
451
456
|
? ` release-passport-product-name: ${JSON.stringify(releasePassportProductName)}\n`
|
|
452
457
|
: "";
|
|
453
|
-
return
|
|
458
|
+
return `${nextDevelopmentWorkflowHeader()}name: Paper Release
|
|
454
459
|
|
|
455
460
|
on:
|
|
456
461
|
workflow_dispatch:
|
|
@@ -997,6 +1002,12 @@ function migrationFiles({
|
|
|
997
1002
|
runtimeIdentity.version,
|
|
998
1003
|
);
|
|
999
1004
|
const files = new Map([
|
|
1005
|
+
[
|
|
1006
|
+
PAPER_PATHS.config,
|
|
1007
|
+
projectNextDevelopmentToml(
|
|
1008
|
+
fs.readFileSync(path.resolve(cwd, PAPER_PATHS.config), "utf8"),
|
|
1009
|
+
),
|
|
1010
|
+
],
|
|
1000
1011
|
[PAPER_PATHS.contractLock, contractLockText],
|
|
1001
1012
|
[PAPER_PATHS.versionPin, `${runtimeIdentity.version}\n`],
|
|
1002
1013
|
[PAPER_PATHS.buildWorkflow, buildWorkflow],
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
copyFile,
|
|
5
|
+
mkdir,
|
|
6
|
+
mkdtemp,
|
|
7
|
+
readFile,
|
|
8
|
+
realpath,
|
|
9
|
+
rm,
|
|
10
|
+
} from "node:fs/promises";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
import { pathToFileURL } from "node:url";
|
|
15
|
+
import { execFile } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { adopterDeliveryGateDigest } from "./adopter-delivery-gate.js";
|
|
18
|
+
|
|
19
|
+
export const PUBLISHED_DELIVERY_AUTHORITY_CONTRACT =
|
|
20
|
+
"kungfu-buildchain-published-delivery-authority/v1";
|
|
21
|
+
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
24
|
+
const PACKAGES = [
|
|
25
|
+
{
|
|
26
|
+
key: "buildchain",
|
|
27
|
+
name: "@kungfu-tech/buildchain",
|
|
28
|
+
packageRoot: "archive",
|
|
29
|
+
modules: {
|
|
30
|
+
adopterDeliveryGate: "packages/core/adopter-delivery-gate.js",
|
|
31
|
+
bootstrap: "packages/core/buildchain-delivery-bootstrap.js",
|
|
32
|
+
infrastructure: "packages/core/buildchain-delivery-infrastructure.js",
|
|
33
|
+
kfdCategoryDriver: "packages/core/kfd-adopter-category-driver.js",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "kfd",
|
|
38
|
+
name: "@kungfu-tech/kfd",
|
|
39
|
+
packageRoot: "profiles/adopter-conformance/adopters/kfd/manifest.json",
|
|
40
|
+
modules: {},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
async function sha256File(file) {
|
|
45
|
+
const hash = createHash("sha256");
|
|
46
|
+
for await (const chunk of createReadStream(file)) hash.update(chunk);
|
|
47
|
+
return `sha256:${hash.digest("hex")}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function exactDeclaration(value, expectedName, label) {
|
|
51
|
+
if (
|
|
52
|
+
value?.name !== expectedName ||
|
|
53
|
+
typeof value.version !== "string" ||
|
|
54
|
+
value.version.length === 0 ||
|
|
55
|
+
typeof value.archivePath !== "string" ||
|
|
56
|
+
value.archivePath.length === 0 ||
|
|
57
|
+
!ROOT_PATTERN.test(value.archiveRoot ?? "") ||
|
|
58
|
+
!ROOT_PATTERN.test(value.artifactRoot ?? "")
|
|
59
|
+
) {
|
|
60
|
+
throw new TypeError(`${label} package declaration is incomplete`);
|
|
61
|
+
}
|
|
62
|
+
return structuredClone(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function safeArchiveEntries(listing, verboseListing, label) {
|
|
66
|
+
const entries = listing.split("\n").filter(Boolean);
|
|
67
|
+
const types = verboseListing.split("\n").filter(Boolean);
|
|
68
|
+
if (entries.length === 0 || entries.length !== types.length) {
|
|
69
|
+
throw new Error(`${label} archive listing is incomplete`);
|
|
70
|
+
}
|
|
71
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
72
|
+
const entry = entries[index];
|
|
73
|
+
const parts = entry.split("/");
|
|
74
|
+
if (
|
|
75
|
+
parts[0] !== "package" ||
|
|
76
|
+
parts.some((part, partIndex) =>
|
|
77
|
+
partIndex === parts.length - 1 && part === ""
|
|
78
|
+
? false
|
|
79
|
+
: part === "" || part === "." || part === "..",
|
|
80
|
+
)
|
|
81
|
+
) {
|
|
82
|
+
throw new Error(`${label} archive contains an unsafe path`);
|
|
83
|
+
}
|
|
84
|
+
if (!["-", "d"].includes(types[index][0])) {
|
|
85
|
+
throw new Error(`${label} archive contains a non-file entry`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function extractPackage(declaration, destination, definition, label) {
|
|
91
|
+
const archivePath = path.resolve(declaration.archivePath);
|
|
92
|
+
const observedRoot = await sha256File(archivePath);
|
|
93
|
+
if (observedRoot !== declaration.archiveRoot) {
|
|
94
|
+
throw new Error(`${label} archive bytes do not match archiveRoot`);
|
|
95
|
+
}
|
|
96
|
+
await mkdir(destination, { recursive: true });
|
|
97
|
+
const stagedArchiveName = `.buildchain-${definition.key}-authority.tgz`;
|
|
98
|
+
const stagedArchivePath = path.join(
|
|
99
|
+
path.dirname(destination),
|
|
100
|
+
stagedArchiveName,
|
|
101
|
+
);
|
|
102
|
+
const stagedArchiveFile = `../${stagedArchiveName}`;
|
|
103
|
+
await copyFile(archivePath, stagedArchivePath);
|
|
104
|
+
try {
|
|
105
|
+
if ((await sha256File(stagedArchivePath)) !== declaration.archiveRoot) {
|
|
106
|
+
throw new Error(`${label} staged archive bytes do not match archiveRoot`);
|
|
107
|
+
}
|
|
108
|
+
const [{ stdout: listing }, { stdout: verboseListing }] = await Promise.all(
|
|
109
|
+
[
|
|
110
|
+
execFileAsync("tar", ["-tzf", stagedArchiveFile], {
|
|
111
|
+
cwd: destination,
|
|
112
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
113
|
+
}),
|
|
114
|
+
execFileAsync("tar", ["-tvzf", stagedArchiveFile], {
|
|
115
|
+
cwd: destination,
|
|
116
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
117
|
+
}),
|
|
118
|
+
],
|
|
119
|
+
);
|
|
120
|
+
safeArchiveEntries(listing, verboseListing, label);
|
|
121
|
+
await execFileAsync(
|
|
122
|
+
"tar",
|
|
123
|
+
["-xzf", stagedArchiveFile, "--strip-components", "1"],
|
|
124
|
+
{ cwd: destination, maxBuffer: 16 * 1024 * 1024 },
|
|
125
|
+
);
|
|
126
|
+
} finally {
|
|
127
|
+
await rm(stagedArchivePath, { force: true });
|
|
128
|
+
}
|
|
129
|
+
const packageJsonPath = path.join(destination, "package.json");
|
|
130
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
131
|
+
if (
|
|
132
|
+
packageJson.name !== declaration.name ||
|
|
133
|
+
packageJson.version !== declaration.version
|
|
134
|
+
) {
|
|
135
|
+
throw new Error(`${label} extracted package identity does not match`);
|
|
136
|
+
}
|
|
137
|
+
if (definition.packageRoot === "archive") {
|
|
138
|
+
if (declaration.artifactRoot !== declaration.archiveRoot) {
|
|
139
|
+
throw new Error(`${label} package artifactRoot must match archiveRoot`);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
const packageManifest = JSON.parse(
|
|
143
|
+
await readFile(path.join(destination, definition.packageRoot), "utf8"),
|
|
144
|
+
);
|
|
145
|
+
const packageCut = packageManifest?.kfdCut?.package;
|
|
146
|
+
if (
|
|
147
|
+
packageCut?.name !== declaration.name ||
|
|
148
|
+
packageCut?.version !== declaration.version ||
|
|
149
|
+
packageCut?.artifactRoot !== declaration.artifactRoot
|
|
150
|
+
) {
|
|
151
|
+
throw new Error(`${label} semantic package identity does not match`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const destinationRoot = await realpath(destination);
|
|
155
|
+
if (!destinationRoot.endsWith(path.join(...declaration.name.split("/")))) {
|
|
156
|
+
throw new Error(`${label} extraction escaped its package coordinate`);
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
name: declaration.name,
|
|
160
|
+
version: declaration.version,
|
|
161
|
+
archiveRoot: declaration.archiveRoot,
|
|
162
|
+
artifactRoot: declaration.artifactRoot,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function importAuthorityRuntime(buildchainRoot) {
|
|
167
|
+
const definition = PACKAGES[0];
|
|
168
|
+
const imported = {};
|
|
169
|
+
for (const [key, relativePath] of Object.entries(definition.modules)) {
|
|
170
|
+
imported[key] = await import(
|
|
171
|
+
pathToFileURL(path.join(buildchainRoot, relativePath)).href
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const runtime = {
|
|
175
|
+
createAdopterDeliveryGate:
|
|
176
|
+
imported.adopterDeliveryGate.createAdopterDeliveryGate,
|
|
177
|
+
createPackageArtifactProfile:
|
|
178
|
+
imported.adopterDeliveryGate.createPackageArtifactProfile,
|
|
179
|
+
qualifyBuildchainDeliveryInfrastructureBootstrap:
|
|
180
|
+
imported.bootstrap.qualifyBuildchainDeliveryInfrastructureBootstrap,
|
|
181
|
+
createBuildchainDeliveryInfrastructureInstanceManifest:
|
|
182
|
+
imported.infrastructure
|
|
183
|
+
.createBuildchainDeliveryInfrastructureInstanceManifest,
|
|
184
|
+
verifyBuildchainDeliveryInfrastructureInstance:
|
|
185
|
+
imported.infrastructure.verifyBuildchainDeliveryInfrastructureInstance,
|
|
186
|
+
createKfdAdopterCategoryProtocolDriver:
|
|
187
|
+
imported.kfdCategoryDriver.createKfdAdopterCategoryProtocolDriver,
|
|
188
|
+
};
|
|
189
|
+
for (const [name, implementation] of Object.entries(runtime)) {
|
|
190
|
+
if (typeof implementation !== "function") {
|
|
191
|
+
throw new Error(`published Buildchain authority is missing ${name}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return runtime;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export async function loadPublishedBuildchainDeliveryAuthority({
|
|
198
|
+
buildchain,
|
|
199
|
+
kfd,
|
|
200
|
+
} = {}) {
|
|
201
|
+
const declarations = {
|
|
202
|
+
buildchain: exactDeclaration(
|
|
203
|
+
buildchain,
|
|
204
|
+
"@kungfu-tech/buildchain",
|
|
205
|
+
"Buildchain authority",
|
|
206
|
+
),
|
|
207
|
+
kfd: exactDeclaration(kfd, "@kungfu-tech/kfd", "KFD authority"),
|
|
208
|
+
};
|
|
209
|
+
const temporaryRoot = await mkdtemp(
|
|
210
|
+
path.join(tmpdir(), "buildchain-published-delivery-authority-"),
|
|
211
|
+
);
|
|
212
|
+
let retained = false;
|
|
213
|
+
try {
|
|
214
|
+
const identities = {};
|
|
215
|
+
for (const definition of PACKAGES) {
|
|
216
|
+
identities[definition.key] = await extractPackage(
|
|
217
|
+
declarations[definition.key],
|
|
218
|
+
path.join(temporaryRoot, "node_modules", ...definition.name.split("/")),
|
|
219
|
+
definition,
|
|
220
|
+
`${definition.key} authority`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const authorityRuntime = await importAuthorityRuntime(
|
|
224
|
+
path.join(temporaryRoot, "node_modules", ...PACKAGES[0].name.split("/")),
|
|
225
|
+
);
|
|
226
|
+
const authority = {
|
|
227
|
+
schemaVersion: 1,
|
|
228
|
+
contract: PUBLISHED_DELIVERY_AUTHORITY_CONTRACT,
|
|
229
|
+
packages: identities,
|
|
230
|
+
moduleCoordinates: structuredClone(PACKAGES[0].modules),
|
|
231
|
+
qualifying: false,
|
|
232
|
+
selfCertified: false,
|
|
233
|
+
releaseAuthorized: false,
|
|
234
|
+
finalAuthority:
|
|
235
|
+
"caller-bound-public-npm-archive-bytes-and-package-identity",
|
|
236
|
+
};
|
|
237
|
+
authority.authorityRoot = adopterDeliveryGateDigest(authority);
|
|
238
|
+
retained = true;
|
|
239
|
+
return {
|
|
240
|
+
...authority,
|
|
241
|
+
authorityRuntime,
|
|
242
|
+
temporaryRoot,
|
|
243
|
+
async dispose() {
|
|
244
|
+
await rm(temporaryRoot, { recursive: true, force: true });
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
} finally {
|
|
248
|
+
if (!retained) await rm(temporaryRoot, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function withPublishedBuildchainDeliveryAuthority(
|
|
253
|
+
declarations,
|
|
254
|
+
operation,
|
|
255
|
+
) {
|
|
256
|
+
if (typeof operation !== "function") {
|
|
257
|
+
throw new TypeError("published authority operation must be a function");
|
|
258
|
+
}
|
|
259
|
+
const authority =
|
|
260
|
+
await loadPublishedBuildchainDeliveryAuthority(declarations);
|
|
261
|
+
try {
|
|
262
|
+
return await operation(authority);
|
|
263
|
+
} finally {
|
|
264
|
+
await authority.dispose();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -308,6 +308,42 @@ function validateProductPayloads({ passport, artifacts, productPayloadManifests,
|
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
function assertAnchorEqual(actual, expected, label) {
|
|
312
|
+
assertEqual(actual, expected, "anchored-provenance-invalid", label, "Preserve both candidate runs and repair the explicit anchor provenance before retrying.");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function validateAnchorProvenance({ run, passport, anchorProvenance, expectedTransactionId, workflowFile, expectedWorkflowName, repository, targetRef }) {
|
|
316
|
+
const request = anchorProvenance.request;
|
|
317
|
+
const provenanceRun = anchorProvenance.run;
|
|
318
|
+
const provenanceWorkflow = anchorProvenance.workflow;
|
|
319
|
+
const provenancePassport = anchorProvenance.passport;
|
|
320
|
+
assertAnchorEqual(run?.status, "completed", "anchored candidate run status");
|
|
321
|
+
assertAnchorEqual(run?.conclusion, "success", "anchored candidate run conclusion");
|
|
322
|
+
assertAnchorEqual(run?.event, "workflow_dispatch", "anchored candidate run event");
|
|
323
|
+
assertAnchorEqual(passport?.target?.channel, "anchor", "anchored candidate channel");
|
|
324
|
+
assertAnchorEqual(passport?.target?.ref, "publish-gate/anchor", "anchored candidate ref");
|
|
325
|
+
assertAnchorEqual(run?.headSha, passport?.buildchain?.sha, "anchored candidate runtime");
|
|
326
|
+
assertAnchorEqual(request?.schema, 1, "anchor request schema");
|
|
327
|
+
assertAnchorEqual(request?.contract, "kungfu-buildchain-explicit-publish-anchor-request/v1", "anchor request contract");
|
|
328
|
+
assertAnchorEqual(Boolean(request?.assignmentId), true, "anchor request Assignment");
|
|
329
|
+
assertAnchorEqual(/^native:[0-9a-f-]+$/u.test(String(request?.attemptId || "")), true, "anchor request Attempt");
|
|
330
|
+
assertAnchorEqual(request?.transactionId, expectedTransactionId, "anchor request transaction");
|
|
331
|
+
assertAnchorEqual(String(provenanceRun?.id || ""), String(request?.supersededCandidate?.workflowRunId || ""), "superseded candidate run");
|
|
332
|
+
assertAnchorEqual(provenancePassport?.workflow?.runId, String(request?.supersededCandidate?.workflowRunId || ""), "superseded candidate Passport run");
|
|
333
|
+
assertAnchorEqual(request?.source?.sha, passport?.source?.headSha, "anchor request source SHA");
|
|
334
|
+
assertAnchorEqual(request?.source?.tree, passport?.source?.treeHash, "anchor request source tree");
|
|
335
|
+
assertAnchorEqual(request?.supersededCandidate?.sha, provenancePassport?.source?.headSha, "superseded candidate source SHA");
|
|
336
|
+
assertAnchorEqual(request?.supersededCandidate?.root, `sha256:${provenancePassport?.candidateHash || ""}`, "superseded candidate root");
|
|
337
|
+
assertAnchorEqual(request?.runtime?.sha, passport?.buildchain?.sha, "anchor request runtime");
|
|
338
|
+
const provenanceWorkflowPath = String(provenanceWorkflow?.path || provenanceRun?.path || "").split("@")[0].replace(/^\.github\/workflows\//, "");
|
|
339
|
+
assertEqual(provenanceWorkflowPath, workflowFile, "workflow-mismatch", "superseded candidate workflow file", "Use the exact trusted original candidate workflow.");
|
|
340
|
+
assertEqual(String(provenanceWorkflow?.name || provenanceRun?.name || ""), expectedWorkflowName, "workflow-mismatch", "superseded candidate workflow name", "Use the exact trusted original candidate workflow.");
|
|
341
|
+
if (provenanceWorkflow?.state && provenanceWorkflow.state !== "active") fail("untrusted-build-run", `superseded candidate workflow is not active: ${provenanceWorkflow.state}`, "Restore or select an allowed active Build workflow.");
|
|
342
|
+
const validation = validateReleaseCandidatePassport({ passport: provenancePassport, repository, targetChannel: normalizedRef(targetRef).split("/")[0], buildSummary: anchorProvenance.buildSummary });
|
|
343
|
+
if (!validation.ok) fail("anchored-provenance-invalid", `superseded candidate Passport validation failed: ${validation.errors.join("; ")}`, "Preserve both candidate runs and repair the explicit anchor provenance before retrying.");
|
|
344
|
+
return { provenanceRun, provenanceWorkflow, provenancePassport };
|
|
345
|
+
}
|
|
346
|
+
|
|
311
347
|
function validateCandidateProvenance({
|
|
312
348
|
candidateRepository,
|
|
313
349
|
targetRepository,
|
|
@@ -318,29 +354,36 @@ function validateCandidateProvenance({
|
|
|
318
354
|
run,
|
|
319
355
|
workflow,
|
|
320
356
|
pullRequest,
|
|
321
|
-
ancestry,
|
|
357
|
+
ancestry, targetTree,
|
|
358
|
+
passport,
|
|
359
|
+
anchorProvenance,
|
|
360
|
+
expectedTransactionId,
|
|
322
361
|
}) {
|
|
323
362
|
const repository = required(candidateRepository, "candidateRepository");
|
|
324
363
|
assertEqual(repository, required(targetRepository, "targetRepository"), "repository-mismatch", "candidate repository", "Dispatch recovery in the repository that created the candidate.");
|
|
325
364
|
assertEqual(String(run?.id || ""), required(expectedRunId, "expectedRunId"), "run-mismatch", "candidate run ID", "Select the exact successful candidate run.");
|
|
326
365
|
assertEqual(run?.repository, repository, "repository-mismatch", "run repository", "Use a candidate run from the target repository.");
|
|
327
366
|
assertEqual(run?.headRepository, repository, "provenance-insufficient", "run head repository", "Fork candidates are not recoverable; create a same-repository candidate.");
|
|
328
|
-
if (run?.status !== "completed" || run?.conclusion !== "success" || run?.event !== "pull_request") {
|
|
329
|
-
fail("untrusted-build-run", "candidate run must be a successful completed pull_request build", "Choose a successful allowed Build workflow run.");
|
|
330
|
-
}
|
|
331
367
|
const workflowFile = required(expectedWorkflowFile, "expectedWorkflowFile").replace(/^\.github\/workflows\//, "");
|
|
332
368
|
const actualWorkflowPath = String(workflow?.path || run?.path || "").split("@")[0].replace(/^\.github\/workflows\//, "");
|
|
333
369
|
assertEqual(actualWorkflowPath, workflowFile, "workflow-mismatch", "candidate workflow file", "Select a run from the documented trusted Build workflow.");
|
|
334
370
|
assertEqual(String(workflow?.name || run?.name || ""), required(expectedWorkflowName, "expectedWorkflowName"), "workflow-mismatch", "candidate workflow name", "Select a run from the documented trusted Build workflow.");
|
|
335
371
|
if (workflow?.state && workflow.state !== "active") fail("untrusted-build-run", `candidate workflow is not active: ${workflow.state}`, "Restore or select an allowed active Build workflow.");
|
|
372
|
+
const anchor = anchorProvenance ? validateAnchorProvenance({ run, passport, anchorProvenance, expectedTransactionId, workflowFile, expectedWorkflowName: required(expectedWorkflowName, "expectedWorkflowName"), repository, targetRef }) : undefined;
|
|
373
|
+
const provenanceRun = anchor?.provenanceRun || run;
|
|
374
|
+
if (provenanceRun?.status !== "completed" || provenanceRun?.conclusion !== "success" || provenanceRun?.event !== "pull_request") {
|
|
375
|
+
fail("untrusted-build-run", "candidate provenance run must be a successful completed pull_request build", "Choose a successful allowed Build workflow run.");
|
|
376
|
+
}
|
|
377
|
+
assertEqual(provenanceRun?.repository, repository, "repository-mismatch", "provenance run repository", "Use a candidate run from the target repository.");
|
|
378
|
+
assertEqual(provenanceRun?.headRepository, repository, "provenance-insufficient", "provenance run head repository", "Fork candidates are not recoverable; create a same-repository candidate.");
|
|
336
379
|
if (!TRUSTED_ASSOCIATIONS.has(String(pullRequest?.authorAssociation || ""))) fail("permission-evidence-insufficient", "candidate PR author lacks trusted repository association evidence", "Have a repository member create or approve a same-repository candidate PR.");
|
|
337
380
|
assertEqual(pullRequest?.headRepository, repository, "provenance-insufficient", "candidate PR head repository", "Fork candidates are outside the recovery permission boundary.");
|
|
338
381
|
if (!pullRequest?.merged) fail("pr-identity-invalid", "candidate PR is not merged", "Use a merged channel candidate PR.");
|
|
339
382
|
assertEqual(normalizedRef(pullRequest?.baseRef), normalizedRef(targetRef), "target-mismatch", "candidate PR target ref", "Use the original release channel target.");
|
|
340
|
-
const boundByNumber = Array.isArray(
|
|
341
|
-
const boundByHead =
|
|
383
|
+
const boundByNumber = Array.isArray(provenanceRun?.pullRequestNumbers) && provenanceRun.pullRequestNumbers.includes(Number(pullRequest?.number));
|
|
384
|
+
const boundByHead = provenanceRun?.headSha === pullRequest?.headSha && normalizedRef(provenanceRun?.headBranch) === normalizedRef(pullRequest?.headRef);
|
|
342
385
|
if (!boundByNumber && !boundByHead) fail("pr-identity-invalid", "candidate run is not bound to the merged candidate PR", "Select the exact PR-stage Build run.");
|
|
343
|
-
if (!ancestry?.mergeIsAncestor && ancestry?.status !== "identical") fail("ancestry-invalid", "candidate merge is
|
|
386
|
+
if (!ancestry?.mergeIsAncestor && ancestry?.status !== "identical" && exactSha(targetTree, "targetTree") !== exactSha(passport?.source?.treeHash, "passport.source.treeHash")) fail("ancestry-invalid", "candidate merge is neither an ancestor nor tree-equivalent to the promotion SHA", "Promote a descendant of the verified merged candidate identity or its exact tree-equivalent protected merge result.");
|
|
344
387
|
return { repository, workflowFile };
|
|
345
388
|
}
|
|
346
389
|
|
|
@@ -354,12 +397,13 @@ function validateCandidateIdentity({
|
|
|
354
397
|
currentToolingSha,
|
|
355
398
|
passport,
|
|
356
399
|
buildSummary,
|
|
400
|
+
anchorProvenance,
|
|
357
401
|
}) {
|
|
358
402
|
const sha = exactSha(targetSha, "targetSha");
|
|
359
403
|
const tree = exactSha(targetTree, "targetTree");
|
|
360
404
|
const runtimeSha = exactSha(expectedRuntimeSha, "expectedRuntimeSha");
|
|
361
405
|
const toolingSha = exactSha(currentToolingSha, "currentToolingSha");
|
|
362
|
-
const validation = validateReleaseCandidatePassport({ passport, repository, targetChannel: channel, buildSummary });
|
|
406
|
+
const validation = validateReleaseCandidatePassport({ passport, repository, targetChannel: anchorProvenance ? "" : channel, buildSummary });
|
|
363
407
|
if (!validation.ok) fail("passport-invalid", `Release Candidate Passport validation failed: ${validation.errors.join("; ")}`, "Preserve the candidate evidence and explicitly create a new candidate after fixing the producer.");
|
|
364
408
|
const expectedPassportHash = sha256Json({
|
|
365
409
|
repository: passport.repository,
|
|
@@ -369,6 +413,7 @@ function validateCandidateIdentity({
|
|
|
369
413
|
buildchain: passport.buildchain,
|
|
370
414
|
...(passport.gateProfileEvidence ? { gateProfileEvidence: passport.gateProfileEvidence } : {}),
|
|
371
415
|
...(passport.familyEvidence ? { familyEvidence: passport.familyEvidence } : {}),
|
|
416
|
+
...(passport.consumerPolicy ? { consumerPolicy: passport.consumerPolicy } : {}),
|
|
372
417
|
...(passport.controllerReceipts ? { controllerReceipts: passport.controllerReceipts } : {}),
|
|
373
418
|
});
|
|
374
419
|
if (passport.candidateHash !== expectedPassportHash) fail("candidate-root-mismatch", "Release Candidate Passport candidate hash does not match its content", "Preserve the run evidence and explicitly create a new candidate after fixing the producer.");
|
|
@@ -378,18 +423,18 @@ function validateCandidateIdentity({
|
|
|
378
423
|
return { sha, tree, runtimeSha, toolingSha };
|
|
379
424
|
}
|
|
380
425
|
|
|
381
|
-
function validateRecoveryTransaction({ existingTransaction, expectedTransactionId, repository, version, passport, sha, targetRef, candidateRoot }) {
|
|
426
|
+
function validateRecoveryTransaction({ existingTransaction, expectedTransactionId, repository, version, passport, channel, sha, targetRef, candidateRoot }) {
|
|
382
427
|
const actualTransactionId = String(existingTransaction?.id || "");
|
|
383
428
|
if (expectedTransactionId && !actualTransactionId) fail("transaction-identity-conflict", `expected transaction ${expectedTransactionId} does not exist`, "Remove the stale transaction identity only if no durable transaction was ever sealed; otherwise preserve evidence and enter repair_required.");
|
|
384
429
|
if (expectedTransactionId && expectedTransactionId !== actualTransactionId) fail("transaction-identity-conflict", `existing transaction ${actualTransactionId} conflicts with expected ${expectedTransactionId}`, "Enter repair_required and inspect the durable transaction before any retry.");
|
|
385
430
|
if (existingTransaction) {
|
|
386
|
-
const expectedIdentity = releaseTransactionId({ repository, version, sourceSha:
|
|
431
|
+
const expectedIdentity = releaseTransactionId({ repository, version, sourceSha: exactSha(existingTransaction.source_sha, "existingTransaction.source_sha"), targetRef: normalizedRef(targetRef) });
|
|
387
432
|
assertEqual(existingTransaction.id, expectedIdentity, "transaction-identity-conflict", "durable transaction identity", "Enter repair_required; the durable transaction does not belong to this exact publication target.");
|
|
388
433
|
assertEqual(existingTransaction.repository, repository, "transaction-identity-conflict", "durable transaction repository", "Enter repair_required; never cross repository transaction boundaries.");
|
|
389
434
|
assertEqual(normalizedRef(existingTransaction.target_ref), normalizedRef(targetRef), "transaction-identity-conflict", "durable transaction target ref", "Enter repair_required; never retarget a sealed transaction.");
|
|
390
|
-
|
|
435
|
+
if (![existingTransaction.source_sha, existingTransaction.release_sha, existingTransaction.release_material_sha].filter(Boolean).includes(sha)) fail("transaction-identity-conflict", "recovery promotion SHA is not bound to the durable transaction", "Resume with the transaction's exact source, release, or release material SHA, or enter repair_required.");
|
|
391
436
|
assertEqual(existingTransaction.version, version, "transaction-identity-conflict", "durable transaction version", "Enter repair_required; never change a sealed publication version.");
|
|
392
|
-
assertEqual(existingTransaction.channel, passport.target.channel, "transaction-identity-conflict", "durable transaction channel", "Enter repair_required; never move a transaction between channels.");
|
|
437
|
+
assertEqual(existingTransaction.channel, channel || passport.target.channel, "transaction-identity-conflict", "durable transaction channel", "Enter repair_required; never move a transaction between channels.");
|
|
393
438
|
}
|
|
394
439
|
if (existingTransaction?.candidateRoot && existingTransaction.candidateRoot !== candidateRoot) fail("transaction-identity-conflict", "existing transaction is sealed to a different candidate root", "Enter repair_required; never reuse the conflicting transaction.");
|
|
395
440
|
return actualTransactionId;
|
|
@@ -415,6 +460,7 @@ export function verifyReleaseCandidateRecovery({
|
|
|
415
460
|
workflow,
|
|
416
461
|
pullRequest,
|
|
417
462
|
ancestry,
|
|
463
|
+
anchorProvenance = undefined,
|
|
418
464
|
passport,
|
|
419
465
|
buildSummary,
|
|
420
466
|
controllerReceipts = [],
|
|
@@ -430,10 +476,11 @@ export function verifyReleaseCandidateRecovery({
|
|
|
430
476
|
const { repository, workflowFile } = validateCandidateProvenance({
|
|
431
477
|
candidateRepository, targetRepository, expectedRunId, expectedWorkflowFile,
|
|
432
478
|
expectedWorkflowName, targetRef, run, workflow, pullRequest, ancestry,
|
|
479
|
+
targetTree, passport, anchorProvenance, expectedTransactionId,
|
|
433
480
|
});
|
|
434
481
|
const { sha, tree, runtimeSha, toolingSha } = validateCandidateIdentity({
|
|
435
482
|
repository, channel, targetSha, targetTree, expectedSourceTree,
|
|
436
|
-
expectedRuntimeSha, currentToolingSha, passport, buildSummary,
|
|
483
|
+
expectedRuntimeSha, currentToolingSha, passport, buildSummary, anchorProvenance,
|
|
437
484
|
});
|
|
438
485
|
|
|
439
486
|
const recoveredArtifacts = artifacts.map(normalizeArtifact).sort((left, right) => left.name.localeCompare(right.name));
|
|
@@ -478,7 +525,7 @@ export function verifyReleaseCandidateRecovery({
|
|
|
478
525
|
|
|
479
526
|
const version = required(publicationVersion || passport.target?.version, "publicationVersion");
|
|
480
527
|
const actualTransactionId = validateRecoveryTransaction({
|
|
481
|
-
existingTransaction, expectedTransactionId, repository, version, passport, sha, targetRef, candidateRoot,
|
|
528
|
+
existingTransaction, expectedTransactionId, repository, version, passport, channel, sha, targetRef, candidateRoot,
|
|
482
529
|
});
|
|
483
530
|
|
|
484
531
|
const receipt = {
|
|
@@ -496,9 +543,17 @@ export function verifyReleaseCandidateRecovery({
|
|
|
496
543
|
sourceSha: passport.source.headSha,
|
|
497
544
|
mergeSha: String(pullRequest.mergeSha || ""),
|
|
498
545
|
tree: passport.source.treeHash,
|
|
546
|
+
...(anchorProvenance ? {
|
|
547
|
+
provenance: "anchored-rematerialization",
|
|
548
|
+
supersededCandidate: {
|
|
549
|
+
runId: String(anchorProvenance.run.id),
|
|
550
|
+
candidateRoot: String(anchorProvenance.request.supersededCandidate.root),
|
|
551
|
+
sourceSha: String(anchorProvenance.request.supersededCandidate.sha),
|
|
552
|
+
},
|
|
553
|
+
} : {}),
|
|
499
554
|
},
|
|
500
555
|
target: {
|
|
501
|
-
channel
|
|
556
|
+
channel,
|
|
502
557
|
ref: normalizedRef(targetRef),
|
|
503
558
|
sha,
|
|
504
559
|
observedRefSha: exactSha(targetRefSha, "targetRefSha"),
|
|
@@ -206,7 +206,7 @@ function validateConsumerPolicyEvidence(passport, check) {
|
|
|
206
206
|
receipt: passport.consumerPolicy.receipt,
|
|
207
207
|
receiptRoot: passport.consumerPolicy.receiptRoot,
|
|
208
208
|
repository: passport.repository,
|
|
209
|
-
sourceSha: passport.source?.headSha,
|
|
209
|
+
sourceSha: passport.target?.ref === "publish-gate/anchor" ? passport.consumerPolicy.receipt?.caller?.sourceSha : passport.source?.headSha,
|
|
210
210
|
resolvedRuntimeSha: passport.buildchain?.sha,
|
|
211
211
|
});
|
|
212
212
|
for (const failure of verification.failures) {
|
|
@@ -403,7 +403,7 @@ export function createReleaseCandidatePassport({
|
|
|
403
403
|
};
|
|
404
404
|
const normalizedConsumerPolicy = normalizeConsumerPolicyEvidence(consumerPolicyReceipt, {
|
|
405
405
|
repository: repository || normalizedSummary.git?.repository || "",
|
|
406
|
-
sourceSha,
|
|
406
|
+
sourceSha: normalizedSummary.publishSource?.ref === "publish-gate/anchor" ? (consumerPolicyReceipt?.receipt || consumerPolicyReceipt)?.caller?.sourceSha : sourceSha,
|
|
407
407
|
runtimeSha: resolvedBuildchain.sha,
|
|
408
408
|
});
|
|
409
409
|
if (isV4BuildchainRuntime(resolvedBuildchain) && !normalizedConsumerPolicy) {
|