@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.55

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.
Files changed (159) hide show
  1. package/README.md +9 -6
  2. package/actions/release-tail/README.md +8 -0
  3. package/actions/v4-release-candidate-promote/README.md +3 -0
  4. package/architecture/agent-change-map.md +1015 -0
  5. package/architecture/ci-lane-change-budget.json +256 -0
  6. package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
  7. package/architecture/internal-capabilities.json +220 -15
  8. package/architecture/maintainability-debt.json +3229 -0
  9. package/architecture/maintainability-policy.json +489 -132
  10. package/architecture/release-tail-contract-inventory.json +5 -2
  11. package/architecture/v3-core-mechanism-inventory.json +4 -3
  12. package/architecture/v3-v4-capability-closure.md +59 -0
  13. package/architecture/v3-v4-live-capability-inventory.json +102404 -0
  14. package/architecture/v4-adopter-delivery-parity.json +32 -5
  15. package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
  16. package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
  17. package/architecture/v4-floating-consumer-policy.json +6 -0
  18. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  19. package/architecture/v4-runtime-semantic-closure.json +710 -0
  20. package/bin/buildchain.mjs +7 -1
  21. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  22. package/bin/internal/command-registry.mjs +1 -0
  23. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  24. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  25. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  26. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  29. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  30. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  31. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  32. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  33. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  38. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  39. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  40. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  41. package/contracts/v4-publication-qualification-v1.schema.json +51 -0
  42. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  43. package/dist/site/agent-index.json +4 -0
  44. package/dist/site/artifact-schemas.json +8 -0
  45. package/dist/site/buildchain-contract.json +125 -34
  46. package/dist/site/buildchain-site.json +308 -33
  47. package/dist/site/capability-registry.json +13 -12
  48. package/dist/site/cli-registry.json +57 -2
  49. package/dist/site/controller-registry.json +41 -5
  50. package/dist/site/kfd-claims.json +438 -24
  51. package/dist/site/kfd-upstream-aggregate.json +1 -1
  52. package/dist/site/manual-registry.json +22 -7
  53. package/dist/site/node-api-registry.json +12843 -9314
  54. package/dist/site/page-registry.json +288 -21
  55. package/dist/site/public-surface-audit.json +281 -21
  56. package/dist/site/publication-authority-registry.json +66 -8
  57. package/dist/site/publication-registry.json +4 -4
  58. package/dist/site/release-provenance.json +20 -0
  59. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  60. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  61. package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
  62. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  63. package/dist/site/site-manifest.json +19 -11
  64. package/dist/site/workflow-registry.json +195 -17
  65. package/docs/MAP.md +4 -2
  66. package/docs/cli-reference.md +70 -2
  67. package/docs/cli.md +8 -0
  68. package/docs/dev-delivery-warrant.md +34 -10
  69. package/docs/node-api-reference.md +524 -279
  70. package/docs/publish-transaction.md +11 -4
  71. package/docs/release-candidate.md +7 -0
  72. package/docs/release-tail-provider-plane.md +30 -0
  73. package/docs/v4-adopter-delivery.md +152 -0
  74. package/docs/v4-declarative-release-tail-migration.md +43 -0
  75. package/docs/v4-publication-rehearsal.md +130 -0
  76. package/package.json +24 -4
  77. package/packages/core/adopter-delivery-passport.js +295 -0
  78. package/packages/core/adopter-delivery-vectors.js +158 -0
  79. package/packages/core/adopter-delivery-vectors.json +77 -0
  80. package/packages/core/buildchain-agent-manuals.js +1 -0
  81. package/packages/core/buildchain-config.js +84 -0
  82. package/packages/core/buildchain-contract.js +31 -1
  83. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  84. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  85. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  86. package/packages/core/buildchain-publication-authority.js +2 -0
  87. package/packages/core/ci-lane-change-budget.js +247 -0
  88. package/packages/core/dev-delivery-native-proof.js +43 -27
  89. package/packages/core/dev-delivery-warrant-settlement.js +239 -3
  90. package/packages/core/dev-delivery-warrant-state.js +46 -10
  91. package/packages/core/dev-delivery-warrant.js +29 -7
  92. package/packages/core/github-governance-authority.js +5 -4
  93. package/packages/core/index.js +6 -0
  94. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  95. package/packages/core/paper-scaffold-content.js +401 -0
  96. package/packages/core/paper.js +12 -378
  97. package/packages/core/publication-control-plane-audit.js +135 -111
  98. package/packages/core/publication-rehearsal-projection.js +104 -0
  99. package/packages/core/publication-rehearsal-runtime.js +190 -0
  100. package/packages/core/published-delivery-authority.js +266 -0
  101. package/packages/core/release-candidate-recovery.js +71 -15
  102. package/packages/core/release-candidate.js +2 -2
  103. package/packages/core/release-passport.js +95 -170
  104. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  105. package/packages/core/v4-adopter-delivery.js +359 -0
  106. package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
  107. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  108. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  109. package/packages/core/v4-floating-consumer-policy.js +139 -35
  110. package/packages/core/v4-floating-consumer-release-passport.js +154 -8
  111. package/packages/core/v4-publication-qualification.js +222 -0
  112. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  113. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  114. package/packages/core/v4-publication-rehearsal.js +481 -0
  115. package/packages/core/workflow-yaml-contract.js +24 -2
  116. package/scripts/artifact-signing-controller-core.mjs +1 -1
  117. package/scripts/audit-publication-control-plane.mjs +17 -17
  118. package/scripts/buildchain-cli-help.mjs +13 -0
  119. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  120. package/scripts/check-internal-architecture.mjs +23 -3
  121. package/scripts/check-inventory.mjs +24 -6
  122. package/scripts/check-maintainability.mjs +145 -4
  123. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  124. package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
  125. package/scripts/check-v4-floating-consumer-policy-contract.mjs +30 -4
  126. package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
  127. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  128. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  129. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  130. package/scripts/dev-delivery-warrant-store.mjs +10 -6
  131. package/scripts/dev-delivery-warrant.mjs +50 -6
  132. package/scripts/dev-pr-auto-merge.mjs +25 -11
  133. package/scripts/dev-pr-delivery-warrant.mjs +45 -0
  134. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  135. package/scripts/dev-qualification-patrol.mjs +9 -9
  136. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  137. package/scripts/generate-agent-change-map.mjs +118 -0
  138. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  139. package/scripts/generate-release-candidate-passport.mjs +177 -0
  140. package/scripts/generate-site-bundle.mjs +36 -0
  141. package/scripts/maintainability-domain-metrics.mjs +237 -0
  142. package/scripts/maintainability-governance.mjs +473 -0
  143. package/scripts/maintainability-metrics.mjs +28 -6
  144. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  145. package/scripts/promotion-channel-router.mjs +4 -4
  146. package/scripts/promotion-routing-evidence.mjs +35 -6
  147. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  148. package/scripts/release-candidate-resolver.mjs +44 -0
  149. package/scripts/release-line-policy.mjs +25 -15
  150. package/scripts/release-tail.mjs +68 -3
  151. package/scripts/resume-from-candidate-run.mjs +595 -65
  152. package/scripts/run-lifecycle-core.mjs +341 -376
  153. package/scripts/site-capability-metadata.mjs +21 -0
  154. package/scripts/stable-candidate-qualification.mjs +9 -10
  155. package/scripts/v3-v4-capability-catalog.mjs +439 -0
  156. package/scripts/v4-consumer-policy.mjs +4 -0
  157. package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
  158. package/scripts/v4-declarative-promotion-admission.mjs +42 -0
  159. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
@@ -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
+ }
@@ -47,6 +47,7 @@ const TRUSTED_ASSOCIATIONS = new Set(["COLLABORATOR", "MEMBER", "OWNER"]);
47
47
  const RESUMABLE_TRANSACTION_STATES = new Set([
48
48
  "sealed",
49
49
  "publishing",
50
+ "publish_failed",
50
51
  "published",
51
52
  "package-published",
52
53
  "finalizing",
@@ -308,6 +309,42 @@ function validateProductPayloads({ passport, artifacts, productPayloadManifests,
308
309
  }
309
310
  }
310
311
 
312
+ function assertAnchorEqual(actual, expected, label) {
313
+ assertEqual(actual, expected, "anchored-provenance-invalid", label, "Preserve both candidate runs and repair the explicit anchor provenance before retrying.");
314
+ }
315
+
316
+ function validateAnchorProvenance({ run, passport, anchorProvenance, expectedTransactionId, workflowFile, expectedWorkflowName, repository, targetRef }) {
317
+ const request = anchorProvenance.request;
318
+ const provenanceRun = anchorProvenance.run;
319
+ const provenanceWorkflow = anchorProvenance.workflow;
320
+ const provenancePassport = anchorProvenance.passport;
321
+ assertAnchorEqual(run?.status, "completed", "anchored candidate run status");
322
+ assertAnchorEqual(run?.conclusion, "success", "anchored candidate run conclusion");
323
+ assertAnchorEqual(run?.event, "workflow_dispatch", "anchored candidate run event");
324
+ assertAnchorEqual(passport?.target?.channel, "anchor", "anchored candidate channel");
325
+ assertAnchorEqual(passport?.target?.ref, "publish-gate/anchor", "anchored candidate ref");
326
+ assertAnchorEqual(run?.headSha, passport?.buildchain?.sha, "anchored candidate runtime");
327
+ assertAnchorEqual(request?.schema, 1, "anchor request schema");
328
+ assertAnchorEqual(request?.contract, "kungfu-buildchain-explicit-publish-anchor-request/v1", "anchor request contract");
329
+ assertAnchorEqual(Boolean(request?.assignmentId), true, "anchor request Assignment");
330
+ assertAnchorEqual(/^native:[0-9a-f-]+$/u.test(String(request?.attemptId || "")), true, "anchor request Attempt");
331
+ assertAnchorEqual(request?.transactionId, expectedTransactionId, "anchor request transaction");
332
+ assertAnchorEqual(String(provenanceRun?.id || ""), String(request?.supersededCandidate?.workflowRunId || ""), "superseded candidate run");
333
+ assertAnchorEqual(provenancePassport?.workflow?.runId, String(request?.supersededCandidate?.workflowRunId || ""), "superseded candidate Passport run");
334
+ assertAnchorEqual(request?.source?.sha, passport?.source?.headSha, "anchor request source SHA");
335
+ assertAnchorEqual(request?.source?.tree, passport?.source?.treeHash, "anchor request source tree");
336
+ assertAnchorEqual(request?.supersededCandidate?.sha, provenancePassport?.source?.headSha, "superseded candidate source SHA");
337
+ assertAnchorEqual(request?.supersededCandidate?.root, `sha256:${provenancePassport?.candidateHash || ""}`, "superseded candidate root");
338
+ assertAnchorEqual(request?.runtime?.sha, passport?.buildchain?.sha, "anchor request runtime");
339
+ const provenanceWorkflowPath = String(provenanceWorkflow?.path || provenanceRun?.path || "").split("@")[0].replace(/^\.github\/workflows\//, "");
340
+ assertEqual(provenanceWorkflowPath, workflowFile, "workflow-mismatch", "superseded candidate workflow file", "Use the exact trusted original candidate workflow.");
341
+ assertEqual(String(provenanceWorkflow?.name || provenanceRun?.name || ""), expectedWorkflowName, "workflow-mismatch", "superseded candidate workflow name", "Use the exact trusted original candidate workflow.");
342
+ 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.");
343
+ const validation = validateReleaseCandidatePassport({ passport: provenancePassport, repository, targetChannel: normalizedRef(targetRef).split("/")[0], buildSummary: anchorProvenance.buildSummary });
344
+ 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.");
345
+ return { provenanceRun, provenanceWorkflow, provenancePassport };
346
+ }
347
+
311
348
  function validateCandidateProvenance({
312
349
  candidateRepository,
313
350
  targetRepository,
@@ -318,29 +355,36 @@ function validateCandidateProvenance({
318
355
  run,
319
356
  workflow,
320
357
  pullRequest,
321
- ancestry,
358
+ ancestry, targetTree,
359
+ passport,
360
+ anchorProvenance,
361
+ expectedTransactionId,
322
362
  }) {
323
363
  const repository = required(candidateRepository, "candidateRepository");
324
364
  assertEqual(repository, required(targetRepository, "targetRepository"), "repository-mismatch", "candidate repository", "Dispatch recovery in the repository that created the candidate.");
325
365
  assertEqual(String(run?.id || ""), required(expectedRunId, "expectedRunId"), "run-mismatch", "candidate run ID", "Select the exact successful candidate run.");
326
366
  assertEqual(run?.repository, repository, "repository-mismatch", "run repository", "Use a candidate run from the target repository.");
327
367
  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
368
  const workflowFile = required(expectedWorkflowFile, "expectedWorkflowFile").replace(/^\.github\/workflows\//, "");
332
369
  const actualWorkflowPath = String(workflow?.path || run?.path || "").split("@")[0].replace(/^\.github\/workflows\//, "");
333
370
  assertEqual(actualWorkflowPath, workflowFile, "workflow-mismatch", "candidate workflow file", "Select a run from the documented trusted Build workflow.");
334
371
  assertEqual(String(workflow?.name || run?.name || ""), required(expectedWorkflowName, "expectedWorkflowName"), "workflow-mismatch", "candidate workflow name", "Select a run from the documented trusted Build workflow.");
335
372
  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.");
373
+ const anchor = anchorProvenance ? validateAnchorProvenance({ run, passport, anchorProvenance, expectedTransactionId, workflowFile, expectedWorkflowName: required(expectedWorkflowName, "expectedWorkflowName"), repository, targetRef }) : undefined;
374
+ const provenanceRun = anchor?.provenanceRun || run;
375
+ if (provenanceRun?.status !== "completed" || provenanceRun?.conclusion !== "success" || provenanceRun?.event !== "pull_request") {
376
+ fail("untrusted-build-run", "candidate provenance run must be a successful completed pull_request build", "Choose a successful allowed Build workflow run.");
377
+ }
378
+ assertEqual(provenanceRun?.repository, repository, "repository-mismatch", "provenance run repository", "Use a candidate run from the target repository.");
379
+ assertEqual(provenanceRun?.headRepository, repository, "provenance-insufficient", "provenance run head repository", "Fork candidates are not recoverable; create a same-repository candidate.");
336
380
  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
381
  assertEqual(pullRequest?.headRepository, repository, "provenance-insufficient", "candidate PR head repository", "Fork candidates are outside the recovery permission boundary.");
338
382
  if (!pullRequest?.merged) fail("pr-identity-invalid", "candidate PR is not merged", "Use a merged channel candidate PR.");
339
383
  assertEqual(normalizedRef(pullRequest?.baseRef), normalizedRef(targetRef), "target-mismatch", "candidate PR target ref", "Use the original release channel target.");
340
- const boundByNumber = Array.isArray(run?.pullRequestNumbers) && run.pullRequestNumbers.includes(Number(pullRequest?.number));
341
- const boundByHead = run?.headSha === pullRequest?.headSha && normalizedRef(run?.headBranch) === normalizedRef(pullRequest?.headRef);
384
+ const boundByNumber = Array.isArray(provenanceRun?.pullRequestNumbers) && provenanceRun.pullRequestNumbers.includes(Number(pullRequest?.number));
385
+ const boundByHead = provenanceRun?.headSha === pullRequest?.headSha && normalizedRef(provenanceRun?.headBranch) === normalizedRef(pullRequest?.headRef);
342
386
  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 not an ancestor of the promotion SHA", "Promote a descendant of the verified merged candidate identity.");
387
+ 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
388
  return { repository, workflowFile };
345
389
  }
346
390
 
@@ -354,12 +398,13 @@ function validateCandidateIdentity({
354
398
  currentToolingSha,
355
399
  passport,
356
400
  buildSummary,
401
+ anchorProvenance,
357
402
  }) {
358
403
  const sha = exactSha(targetSha, "targetSha");
359
404
  const tree = exactSha(targetTree, "targetTree");
360
405
  const runtimeSha = exactSha(expectedRuntimeSha, "expectedRuntimeSha");
361
406
  const toolingSha = exactSha(currentToolingSha, "currentToolingSha");
362
- const validation = validateReleaseCandidatePassport({ passport, repository, targetChannel: channel, buildSummary });
407
+ const validation = validateReleaseCandidatePassport({ passport, repository, targetChannel: anchorProvenance ? "" : channel, buildSummary });
363
408
  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
409
  const expectedPassportHash = sha256Json({
365
410
  repository: passport.repository,
@@ -369,6 +414,7 @@ function validateCandidateIdentity({
369
414
  buildchain: passport.buildchain,
370
415
  ...(passport.gateProfileEvidence ? { gateProfileEvidence: passport.gateProfileEvidence } : {}),
371
416
  ...(passport.familyEvidence ? { familyEvidence: passport.familyEvidence } : {}),
417
+ ...(passport.consumerPolicy ? { consumerPolicy: passport.consumerPolicy } : {}),
372
418
  ...(passport.controllerReceipts ? { controllerReceipts: passport.controllerReceipts } : {}),
373
419
  });
374
420
  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 +424,18 @@ function validateCandidateIdentity({
378
424
  return { sha, tree, runtimeSha, toolingSha };
379
425
  }
380
426
 
381
- function validateRecoveryTransaction({ existingTransaction, expectedTransactionId, repository, version, passport, sha, targetRef, candidateRoot }) {
427
+ function validateRecoveryTransaction({ existingTransaction, expectedTransactionId, repository, version, passport, channel, sha, targetRef, candidateRoot }) {
382
428
  const actualTransactionId = String(existingTransaction?.id || "");
383
429
  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
430
  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
431
  if (existingTransaction) {
386
- const expectedIdentity = releaseTransactionId({ repository, version, sourceSha: sha, targetRef: normalizedRef(targetRef) });
432
+ const expectedIdentity = releaseTransactionId({ repository, version, sourceSha: exactSha(existingTransaction.source_sha, "existingTransaction.source_sha"), targetRef: normalizedRef(targetRef) });
387
433
  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
434
  assertEqual(existingTransaction.repository, repository, "transaction-identity-conflict", "durable transaction repository", "Enter repair_required; never cross repository transaction boundaries.");
389
435
  assertEqual(normalizedRef(existingTransaction.target_ref), normalizedRef(targetRef), "transaction-identity-conflict", "durable transaction target ref", "Enter repair_required; never retarget a sealed transaction.");
390
- assertEqual(existingTransaction.source_sha, sha, "transaction-identity-conflict", "durable transaction source SHA", "Resume with the transaction's exact promotion SHA or enter repair_required.");
436
+ 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
437
  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.");
438
+ assertEqual(existingTransaction.channel, channel || passport.target.channel, "transaction-identity-conflict", "durable transaction channel", "Enter repair_required; never move a transaction between channels.");
393
439
  }
394
440
  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
441
  return actualTransactionId;
@@ -415,6 +461,7 @@ export function verifyReleaseCandidateRecovery({
415
461
  workflow,
416
462
  pullRequest,
417
463
  ancestry,
464
+ anchorProvenance = undefined,
418
465
  passport,
419
466
  buildSummary,
420
467
  controllerReceipts = [],
@@ -430,10 +477,11 @@ export function verifyReleaseCandidateRecovery({
430
477
  const { repository, workflowFile } = validateCandidateProvenance({
431
478
  candidateRepository, targetRepository, expectedRunId, expectedWorkflowFile,
432
479
  expectedWorkflowName, targetRef, run, workflow, pullRequest, ancestry,
480
+ targetTree, passport, anchorProvenance, expectedTransactionId,
433
481
  });
434
482
  const { sha, tree, runtimeSha, toolingSha } = validateCandidateIdentity({
435
483
  repository, channel, targetSha, targetTree, expectedSourceTree,
436
- expectedRuntimeSha, currentToolingSha, passport, buildSummary,
484
+ expectedRuntimeSha, currentToolingSha, passport, buildSummary, anchorProvenance,
437
485
  });
438
486
 
439
487
  const recoveredArtifacts = artifacts.map(normalizeArtifact).sort((left, right) => left.name.localeCompare(right.name));
@@ -478,7 +526,7 @@ export function verifyReleaseCandidateRecovery({
478
526
 
479
527
  const version = required(publicationVersion || passport.target?.version, "publicationVersion");
480
528
  const actualTransactionId = validateRecoveryTransaction({
481
- existingTransaction, expectedTransactionId, repository, version, passport, sha, targetRef, candidateRoot,
529
+ existingTransaction, expectedTransactionId, repository, version, passport, channel, sha, targetRef, candidateRoot,
482
530
  });
483
531
 
484
532
  const receipt = {
@@ -496,9 +544,17 @@ export function verifyReleaseCandidateRecovery({
496
544
  sourceSha: passport.source.headSha,
497
545
  mergeSha: String(pullRequest.mergeSha || ""),
498
546
  tree: passport.source.treeHash,
547
+ ...(anchorProvenance ? {
548
+ provenance: "anchored-rematerialization",
549
+ supersededCandidate: {
550
+ runId: String(anchorProvenance.run.id),
551
+ candidateRoot: String(anchorProvenance.request.supersededCandidate.root),
552
+ sourceSha: String(anchorProvenance.request.supersededCandidate.sha),
553
+ },
554
+ } : {}),
499
555
  },
500
556
  target: {
501
- channel: passport.target.channel,
557
+ channel,
502
558
  ref: normalizedRef(targetRef),
503
559
  sha,
504
560
  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) {