@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.
Files changed (228) hide show
  1. package/AGENTS.md +13 -5
  2. package/actions/promote-buildchain-ref/README.md +11 -6
  3. package/actions/release-tail/README.md +8 -0
  4. package/architecture/ci-lane-change-budget.json +187 -0
  5. package/architecture/decisions/0002-next-development-transition.md +101 -0
  6. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  7. package/architecture/internal-capabilities.json +219 -7
  8. package/architecture/maintainability-policy.json +246 -29
  9. package/architecture/release-tail-contract-inventory.json +5 -2
  10. package/architecture/v3-core-mechanism-inventory.json +91 -0
  11. package/architecture/v4-adopter-delivery-parity.json +32 -5
  12. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  13. package/architecture/v4-compatibility-facts-parity.json +77 -0
  14. package/architecture/v4-delivery-authority-parity.json +250 -0
  15. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  16. package/architecture/v4-floating-consumer-policy.json +81 -0
  17. package/architecture/v4-floating-consumer-policy.md +32 -0
  18. package/architecture/v4-next-development-parity.json +192 -0
  19. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  20. package/architecture/v4-release-train-parity.json +1 -0
  21. package/architecture/v4-runtime-ref-resume-authority.json +64 -0
  22. package/architecture/v4-stage-capsule-qualification.json +2 -2
  23. package/architecture/v4-tail-reseal-parity.json +150 -0
  24. package/bin/buildchain.mjs +32 -3
  25. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  26. package/bin/internal/command-registry.mjs +3 -0
  27. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  28. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  29. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  30. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  31. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  32. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  33. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  34. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  35. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  36. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  37. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  38. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  39. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +59 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  43. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  44. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  45. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  46. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  47. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  48. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  49. package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
  50. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  51. package/contracts/next-development-request-v1.schema.json +66 -0
  52. package/contracts/next-development-transition-v1.schema.json +292 -0
  53. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  54. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  55. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +107 -0
  56. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  57. package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
  58. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  59. package/dist/site/agent-index.json +5 -0
  60. package/dist/site/artifact-schemas.json +10 -0
  61. package/dist/site/buildchain-contract.json +1562 -44
  62. package/dist/site/buildchain-site.json +684 -42
  63. package/dist/site/capability-registry.json +17 -14
  64. package/dist/site/cli-registry.json +205 -3
  65. package/dist/site/controller-registry.json +62 -6
  66. package/dist/site/kfd-claims.json +551 -22
  67. package/dist/site/kfd-upstream-aggregate.json +1 -1
  68. package/dist/site/manual-registry.json +55 -9
  69. package/dist/site/node-api-registry.json +15247 -8690
  70. package/dist/site/page-registry.json +647 -29
  71. package/dist/site/public-surface-audit.json +876 -55
  72. package/dist/site/publication-authority-registry.json +44 -8
  73. package/dist/site/publication-registry.json +4 -4
  74. package/dist/site/release-provenance.json +26 -0
  75. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  76. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  77. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  78. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  79. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  80. package/dist/site/site-manifest.json +37 -13
  81. package/dist/site/workflow-registry.json +200 -22
  82. package/docs/MAP.md +10 -2
  83. package/docs/cli-reference.md +404 -13
  84. package/docs/cli.md +8 -0
  85. package/docs/dev-alpha-candidate-patrol.md +8 -0
  86. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  87. package/docs/dev-delivery-warrant.md +361 -30
  88. package/docs/lifecycle-protocol.md +41 -0
  89. package/docs/next-development-transition.md +118 -0
  90. package/docs/node-api-reference.md +748 -322
  91. package/docs/release-tail-provider-plane.md +30 -0
  92. package/docs/reusable-build-surface.md +41 -0
  93. package/docs/runtime-train-validation.md +10 -0
  94. package/docs/v4-adopter-delivery.md +133 -0
  95. package/docs/v4-compatibility-facts.md +91 -0
  96. package/docs/v4-publication-rehearsal.md +130 -0
  97. package/docs/v4-runtime-ref-resume-authority.md +69 -0
  98. package/docs/v4-tail-reseal.md +73 -0
  99. package/docs/versioning.md +4 -2
  100. package/package.json +36 -6
  101. package/packages/core/adopter-delivery-passport.js +259 -0
  102. package/packages/core/adopter-delivery-vectors.js +158 -0
  103. package/packages/core/adopter-delivery-vectors.json +77 -0
  104. package/packages/core/artifact-signing.js +61 -0
  105. package/packages/core/buildchain-agent-manuals.js +3 -0
  106. package/packages/core/buildchain-compatibility-authority.js +336 -0
  107. package/packages/core/buildchain-compatibility-fact.js +394 -0
  108. package/packages/core/buildchain-compatibility-facts.json +217 -0
  109. package/packages/core/buildchain-compatibility-proof.js +566 -0
  110. package/packages/core/buildchain-config.js +150 -1
  111. package/packages/core/buildchain-contract.js +96 -143
  112. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  113. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  114. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  115. package/packages/core/buildchain-publication-authority.js +1 -0
  116. package/packages/core/channel-candidate.js +8 -0
  117. package/packages/core/channel-promotion-baseline.js +55 -0
  118. package/packages/core/ci-lane-change-budget.js +247 -0
  119. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  120. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  121. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  122. package/packages/core/dev-delivery-authority-landing.js +461 -0
  123. package/packages/core/dev-delivery-authority-observation.js +48 -0
  124. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  125. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  126. package/packages/core/dev-delivery-authority-state.js +583 -0
  127. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  128. package/packages/core/dev-delivery-contract-surface.js +76 -0
  129. package/packages/core/dev-delivery-execution-failure.js +133 -0
  130. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  131. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  132. package/packages/core/dev-delivery-landing-readback.js +598 -0
  133. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  134. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  135. package/packages/core/dev-delivery-native-execution.js +110 -0
  136. package/packages/core/dev-delivery-native-proof.js +562 -0
  137. package/packages/core/dev-delivery-process-boundary.js +563 -0
  138. package/packages/core/dev-delivery-proof.js +37 -3
  139. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  140. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  141. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  142. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  143. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  144. package/packages/core/dev-delivery-warrant-state.js +587 -0
  145. package/packages/core/dev-delivery-warrant.js +328 -370
  146. package/packages/core/github-governance-authority.js +1 -0
  147. package/packages/core/index.js +7 -0
  148. package/packages/core/kungfu-temporal-fact.js +557 -0
  149. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  150. package/packages/core/next-development-candidate-reservation.js +186 -0
  151. package/packages/core/next-development-controller.js +728 -0
  152. package/packages/core/next-development-projection.js +287 -0
  153. package/packages/core/next-development-transition.js +738 -0
  154. package/packages/core/paper-agent-entry.js +11 -5
  155. package/packages/core/paper.js +16 -5
  156. package/packages/core/publication-authority.js +5 -5
  157. package/packages/core/published-delivery-authority.js +266 -0
  158. package/packages/core/release-candidate-recovery.js +66 -11
  159. package/packages/core/release-candidate.js +79 -19
  160. package/packages/core/release-passport.js +271 -38
  161. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  162. package/packages/core/v4-adopter-delivery.js +359 -0
  163. package/packages/core/v4-canonical-contracts.js +5 -0
  164. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  165. package/packages/core/v4-floating-consumer-evidence.js +338 -0
  166. package/packages/core/v4-floating-consumer-policy.js +569 -0
  167. package/packages/core/v4-floating-consumer-release-passport.js +153 -0
  168. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  169. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  170. package/packages/core/v4-publication-rehearsal.js +481 -0
  171. package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
  172. package/packages/core/v4-runtime-selector-persistence.js +228 -0
  173. package/packages/core/v4-tail-reseal-contract.js +26 -0
  174. package/packages/core/v4-tail-reseal-github.js +178 -0
  175. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  176. package/packages/core/v4-tail-reseal.js +594 -0
  177. package/packages/core/workflow-yaml-contract.js +72 -2
  178. package/scripts/audit-publication-control-plane.mjs +31 -31
  179. package/scripts/buildchain-cli-help.mjs +41 -2
  180. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  181. package/scripts/check-inventory.mjs +14 -3
  182. package/scripts/check-v4-floating-consumer-policy-contract.mjs +217 -0
  183. package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
  184. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  185. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  186. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  187. package/scripts/dev-delivery-authority.mjs +490 -0
  188. package/scripts/dev-delivery-native-run.mjs +177 -0
  189. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  190. package/scripts/dev-delivery-proof.mjs +67 -2
  191. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  192. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  193. package/scripts/dev-delivery-source-proof-reuse.mjs +547 -0
  194. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  195. package/scripts/dev-delivery-two-phase.mjs +573 -0
  196. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  197. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  198. package/scripts/dev-delivery-warrant.mjs +232 -194
  199. package/scripts/dev-pr-auto-merge.mjs +37 -48
  200. package/scripts/dev-pr-delivery-warrant.mjs +216 -2
  201. package/scripts/dev-pr-prequeue-guard.mjs +399 -0
  202. package/scripts/dev-qualification-patrol.mjs +9 -9
  203. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  204. package/scripts/ensure-github-release.mjs +3 -3
  205. package/scripts/generate-channel-build-workflow.mjs +3 -0
  206. package/scripts/generate-channel-promotion-workflow.mjs +114 -14
  207. package/scripts/generate-next-development-guidance.mjs +49 -0
  208. package/scripts/generate-release-candidate-passport.mjs +192 -32
  209. package/scripts/generate-site-bundle.mjs +69 -4
  210. package/scripts/init-repo.mjs +26 -4
  211. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  212. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  213. package/scripts/next-development-self-dogfood.mjs +597 -0
  214. package/scripts/next-development-transition.mjs +47 -0
  215. package/scripts/npm-publish-transaction.mjs +103 -91
  216. package/scripts/promotion-channel-router.mjs +4 -4
  217. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  218. package/scripts/release-tail.mjs +51 -3
  219. package/scripts/release-train-self-dogfood.mjs +94 -92
  220. package/scripts/resume-from-candidate-run.mjs +565 -41
  221. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  222. package/scripts/site-capability-metadata.mjs +43 -0
  223. package/scripts/stable-candidate-qualification.mjs +9 -10
  224. package/scripts/v4-consumer-policy.mjs +231 -0
  225. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  226. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  227. package/scripts/v4-tail-reseal.mjs +416 -0
  228. package/templates/native-dev-delivery.yml +143 -0
@@ -0,0 +1,153 @@
1
+ import crypto from "node:crypto";
2
+ import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+
5
+ const SHA = /^[0-9a-f]{40}$/u;
6
+
7
+ function exactSha(value, label) {
8
+ const normalized = String(value || "")
9
+ .trim()
10
+ .toLowerCase();
11
+ if (!SHA.test(normalized))
12
+ throw new Error(`${label} must be an exact 40-hex Git SHA`);
13
+ return normalized;
14
+ }
15
+
16
+ function git(args, { cwd, encoding = "utf8" }) {
17
+ const result = spawnSync("git", args, {
18
+ cwd,
19
+ encoding,
20
+ maxBuffer: 64 * 1024 * 1024,
21
+ });
22
+ if (result.status !== 0)
23
+ throw new Error(
24
+ `git ${args.join(" ")} failed: ${String(result.stderr || "").trim()}`,
25
+ );
26
+ return encoding === null ? result.stdout : String(result.stdout || "").trim();
27
+ }
28
+
29
+ function patchRoot(base, head, cwd) {
30
+ const patch = git(
31
+ [
32
+ "diff",
33
+ "--binary",
34
+ "--full-index",
35
+ "--no-ext-diff",
36
+ `${base}..${head}`,
37
+ ],
38
+ { cwd, encoding: null },
39
+ );
40
+ return `sha256:${crypto.createHash("sha256").update(patch).digest("hex")}`;
41
+ }
42
+
43
+ function linearRange(base, head, cwd) {
44
+ try {
45
+ git(["merge-base", "--is-ancestor", base, head], { cwd });
46
+ } catch {
47
+ return null;
48
+ }
49
+ const commits = git(
50
+ ["rev-list", "--reverse", "--topo-order", `${base}..${head}`],
51
+ { cwd },
52
+ )
53
+ .split(/\s+/u)
54
+ .filter(Boolean);
55
+ if (commits.length === 0) return null;
56
+ let expectedParent = base;
57
+ const trees = [];
58
+ const patchRoots = [];
59
+ for (const rangeCommit of commits) {
60
+ const [observedCommit, ...parents] = git(
61
+ ["rev-list", "--parents", "-n", "1", rangeCommit],
62
+ { cwd },
63
+ ).split(/\s+/u);
64
+ if (
65
+ observedCommit !== rangeCommit ||
66
+ parents.length !== 1 ||
67
+ parents[0] !== expectedParent
68
+ ) {
69
+ return null;
70
+ }
71
+ trees.push(
72
+ exactSha(
73
+ git(["rev-parse", `${rangeCommit}^{tree}`], { cwd }),
74
+ "replayedCommitTree",
75
+ ),
76
+ );
77
+ patchRoots.push(patchRoot(expectedParent, rangeCommit, cwd));
78
+ expectedParent = rangeCommit;
79
+ }
80
+ return { commits, trees, patchRoots };
81
+ }
82
+
83
+ export function exactMergeGroupBinding(input = {}) {
84
+ const cwd = path.resolve(input.cwd || process.cwd());
85
+ const mergeGroupHead = exactSha(input.mergeGroupHead, "mergeGroupHead");
86
+ const expectedTree = exactSha(input.mergeGroupTree, "mergeGroupTree");
87
+ const qualifiedBase = exactSha(input.qualifiedBase, "qualifiedBase");
88
+ const currentBase = exactSha(input.currentBase, "currentBase");
89
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
90
+ const sourcePatchRoot = String(input.sourcePatchRoot || "").trim();
91
+ const actualTree = exactSha(
92
+ git(["rev-parse", `${mergeGroupHead}^{tree}`], { cwd }),
93
+ "mergeGroupTree",
94
+ );
95
+ if (actualTree !== expectedTree)
96
+ return { ok: false, reason: "merge-group-tree-mismatch" };
97
+ try {
98
+ git(["merge-base", "--is-ancestor", qualifiedBase, currentBase], { cwd });
99
+ } catch {
100
+ return {
101
+ ok: false,
102
+ reason: "current-base-not-descendant-of-qualified-base",
103
+ };
104
+ }
105
+ if (!/^sha256:[0-9a-f]{64}$/u.test(sourcePatchRoot))
106
+ throw new Error("sourcePatchRoot must be an exact SHA-256 root");
107
+ if (patchRoot(currentBase, mergeGroupHead, cwd) !== sourcePatchRoot)
108
+ return { ok: false, reason: "merge-group-source-patch-mismatch" };
109
+
110
+ const [commit, ...parents] = git(
111
+ ["rev-list", "--parents", "-n", "1", mergeGroupHead],
112
+ { cwd },
113
+ ).split(/\s+/u);
114
+ if (commit !== mergeGroupHead)
115
+ return { ok: false, reason: "merge-group-parent-mismatch" };
116
+ if (
117
+ parents.length === 2 &&
118
+ parents[0] === currentBase &&
119
+ parents[1] === sourceHead
120
+ ) {
121
+ return {
122
+ ok: true,
123
+ mergeGroupHead,
124
+ mergeGroupTree: actualTree,
125
+ parents,
126
+ compositionMode: "two-parent-merge",
127
+ replayedCommitTrees: [],
128
+ replayedCommitPatchRoots: [],
129
+ };
130
+ }
131
+
132
+ const sourceRange = linearRange(qualifiedBase, sourceHead, cwd);
133
+ const mergeGroupRange = linearRange(currentBase, mergeGroupHead, cwd);
134
+ if (
135
+ !sourceRange ||
136
+ !mergeGroupRange ||
137
+ sourceRange.commits.length !== mergeGroupRange.commits.length ||
138
+ sourceRange.patchRoots.some(
139
+ (root, index) => root !== mergeGroupRange.patchRoots[index],
140
+ )
141
+ ) {
142
+ return { ok: false, reason: "merge-group-parent-mismatch" };
143
+ }
144
+ return {
145
+ ok: true,
146
+ mergeGroupHead,
147
+ mergeGroupTree: actualTree,
148
+ parents,
149
+ compositionMode: "linear-replay",
150
+ replayedCommitTrees: mergeGroupRange.trees,
151
+ replayedCommitPatchRoots: mergeGroupRange.patchRoots,
152
+ };
153
+ }
@@ -0,0 +1,547 @@
1
+ #!/usr/bin/env node
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { pathToFileURL } from "node:url";
7
+ import {
8
+ classifyDevDeliveryDelta,
9
+ createSourceQualificationProofV2,
10
+ devDeliveryContentRoot,
11
+ verifySourceQualificationProof,
12
+ } from "../packages/core/dev-delivery-warrant.js";
13
+ import { validateControllerReceipt } from "../packages/core/controller-evidence.js";
14
+ import { exactMergeGroupBinding } from "./dev-delivery-source-proof-replay.mjs";
15
+
16
+ const REUSE_DECISION_SCHEMA =
17
+ "kungfu.buildchain.source-qualification-reuse-decision/v1";
18
+ const SHA = /^[0-9a-f]{40}$/u;
19
+
20
+ function flag(args, name, fallback = "") {
21
+ const index = args.indexOf(`--${name}`);
22
+ return index === -1 ? fallback : args[index + 1] || "";
23
+ }
24
+
25
+ function required(value, label) {
26
+ const normalized = String(value || "").trim();
27
+ if (!normalized) throw new Error(`${label} is required`);
28
+ return normalized;
29
+ }
30
+
31
+ function exactSha(value, label) {
32
+ const normalized = required(value, label).toLowerCase();
33
+ if (!SHA.test(normalized))
34
+ throw new Error(`${label} must be an exact 40-hex Git SHA`);
35
+ return normalized;
36
+ }
37
+
38
+ function jsonList(value, label) {
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(value || "[]");
42
+ } catch (error) {
43
+ throw new Error(`${label} must be a JSON array: ${error.message}`);
44
+ }
45
+ if (!Array.isArray(parsed) || parsed.length === 0)
46
+ throw new Error(`${label} must be a non-empty JSON array`);
47
+ return [
48
+ ...new Set(
49
+ parsed.map((entry) => String(entry || "").trim()).filter(Boolean),
50
+ ),
51
+ ].sort();
52
+ }
53
+
54
+ function readJson(filePath, label) {
55
+ try {
56
+ return JSON.parse(fs.readFileSync(required(filePath, label), "utf8"));
57
+ } catch (error) {
58
+ throw new Error(`could not read ${label}: ${error.message}`);
59
+ }
60
+ }
61
+
62
+ function writeJson(filePath, value) {
63
+ const target = path.resolve(required(filePath, "output"));
64
+ fs.mkdirSync(path.dirname(target), { recursive: true });
65
+ fs.writeFileSync(target, `${JSON.stringify(value, null, 2)}\n`);
66
+ }
67
+
68
+ function git(args, { cwd = process.cwd(), encoding = "utf8" } = {}) {
69
+ const result = spawnSync("git", args, {
70
+ cwd,
71
+ encoding,
72
+ maxBuffer: 64 * 1024 * 1024,
73
+ });
74
+ if (result.status !== 0)
75
+ throw new Error(
76
+ `git ${args.join(" ")} failed: ${String(result.stderr || "").trim()}`,
77
+ );
78
+ return encoding === null ? result.stdout : String(result.stdout || "").trim();
79
+ }
80
+
81
+ function sha256(bytes) {
82
+ return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
83
+ }
84
+
85
+ function fileFact(filePath, workspace) {
86
+ const absolute = path.resolve(filePath);
87
+ return {
88
+ path: path
89
+ .relative(path.resolve(workspace), absolute)
90
+ .split(path.sep)
91
+ .join("/"),
92
+ size: fs.statSync(absolute).size,
93
+ sha256: sha256(fs.readFileSync(absolute)),
94
+ };
95
+ }
96
+
97
+ function gitPathRoot(ref, paths, label, cwd) {
98
+ const files = paths.map((filePath) => {
99
+ const normalized = filePath.replace(/^\.\//u, "");
100
+ if (!normalized || normalized.includes("\n"))
101
+ throw new Error(`${label} contains an invalid path`);
102
+ return {
103
+ path: normalized,
104
+ blob: exactSha(
105
+ git(["rev-parse", `${ref}:${normalized}`], { cwd }),
106
+ `${label} blob for ${normalized}`,
107
+ ),
108
+ };
109
+ });
110
+ return devDeliveryContentRoot({
111
+ schema: "kungfu.buildchain.git-path-set/v1",
112
+ label,
113
+ files,
114
+ });
115
+ }
116
+
117
+ export function sourceQualificationPredicates(input = {}) {
118
+ const cwd = path.resolve(input.cwd || process.cwd());
119
+ const repository = required(input.repository, "repository");
120
+ const protectedBase = required(input.protectedBase, "protectedBase");
121
+ const qualifiedBase = exactSha(input.qualifiedBase, "qualifiedBase");
122
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
123
+ const sourceTree = exactSha(
124
+ git(["rev-parse", `${sourceHead}^{tree}`], { cwd }),
125
+ "sourceTree",
126
+ );
127
+ const policyPaths = jsonList(input.policyPaths, "policy paths");
128
+ const closurePaths = jsonList(input.closurePaths, "closure paths");
129
+ const dependencyPaths = jsonList(input.dependencyPaths, "dependency paths");
130
+ const requiredContexts = jsonList(
131
+ input.requiredContexts,
132
+ "required contexts",
133
+ );
134
+ const affectedPaths = git(
135
+ ["diff", "--name-only", "--no-renames", `${qualifiedBase}...${sourceHead}`],
136
+ { cwd },
137
+ )
138
+ .split("\n")
139
+ .map((entry) => entry.trim())
140
+ .filter(Boolean)
141
+ .sort();
142
+ const patch = git(
143
+ [
144
+ "diff",
145
+ "--binary",
146
+ "--full-index",
147
+ "--no-ext-diff",
148
+ `${qualifiedBase}...${sourceHead}`,
149
+ ],
150
+ {
151
+ cwd,
152
+ encoding: null,
153
+ },
154
+ );
155
+ const runtimeRef = required(input.runtimeRef, "runtimeRef");
156
+ const runtimeSha = exactSha(input.runtimeSha, "runtimeSha");
157
+ const contractDigest = required(input.contractDigest, "contractDigest");
158
+ const nodeVersion = required(input.nodeVersion, "nodeVersion");
159
+ return {
160
+ repository,
161
+ protectedBase,
162
+ qualifiedBase,
163
+ sourceHead,
164
+ sourceTree,
165
+ sourceIdentityRoot: devDeliveryContentRoot({
166
+ schema: "kungfu.buildchain.source-identity/v1",
167
+ repository,
168
+ protectedBase,
169
+ qualifiedBase,
170
+ sourceHead,
171
+ sourceTree,
172
+ }),
173
+ sourcePatchRoot: sha256(patch),
174
+ planRoot: devDeliveryContentRoot({
175
+ schema: "kungfu.buildchain.source-qualification-plan/v1",
176
+ mode: "source",
177
+ workingDirectory: required(
178
+ input.workingDirectory || ".",
179
+ "workingDirectory",
180
+ ),
181
+ nodeVersion,
182
+ policyPaths,
183
+ closurePaths,
184
+ dependencyPaths,
185
+ requiredContexts,
186
+ }),
187
+ closureRoot: gitPathRoot(sourceHead, closurePaths, "source-closure", cwd),
188
+ dependencyRoot: gitPathRoot(
189
+ sourceHead,
190
+ dependencyPaths,
191
+ "source-dependencies",
192
+ cwd,
193
+ ),
194
+ toolchainRoot: devDeliveryContentRoot({
195
+ schema: "kungfu.buildchain.source-toolchain/v1",
196
+ runtimeRef,
197
+ runtimeSha,
198
+ contractDigest,
199
+ nodeVersion,
200
+ }),
201
+ policyRoot: gitPathRoot(sourceHead, policyPaths, "source-policy", cwd),
202
+ requiredContextRoot: devDeliveryContentRoot({
203
+ schema: "kungfu.buildchain.required-context-set/v1",
204
+ contexts: requiredContexts,
205
+ }),
206
+ affectedPaths,
207
+ };
208
+ }
209
+
210
+ export function sealSourceQualificationProof(input = {}) {
211
+ const receipt = readJson(input.controllerReceiptPath, "controller receipt");
212
+ const expectedSourceSha = exactSha(input.sourceHead, "sourceHead");
213
+ const expectedRuntimeSha = exactSha(input.runtimeSha, "runtimeSha");
214
+ const expectedRepository = required(input.repository, "repository");
215
+ const validation = validateControllerReceipt(receipt, {
216
+ expectedSourceSha,
217
+ expectedRuntimeSha,
218
+ });
219
+ if (
220
+ !validation.ok ||
221
+ !validation.qualifying ||
222
+ receipt.controller?.id !== "source-check" ||
223
+ receipt.source?.repository !== expectedRepository
224
+ ) {
225
+ throw new Error(
226
+ `controller receipt is not qualifying: ${validation.issues.join("; ") || receipt.status || "unknown"}`,
227
+ );
228
+ }
229
+ const predicates = sourceQualificationPredicates(input);
230
+ return createSourceQualificationProofV2({
231
+ ...predicates,
232
+ controllerReceiptRoot: receipt.digest,
233
+ sourceWorkflowRunId: input.sourceWorkflowRunId,
234
+ shardEvidenceRoots: [receipt.digest],
235
+ qualifiedAt: input.qualifiedAt,
236
+ });
237
+ }
238
+
239
+ function rootedDecision(body) {
240
+ return { ...body, decisionRoot: devDeliveryContentRoot(body) };
241
+ }
242
+
243
+ export function materializeReuseLifecycleEvidence({
244
+ decision,
245
+ sourceProofPath,
246
+ decisionPath,
247
+ manifestPath,
248
+ summaryPath,
249
+ workspace = process.cwd(),
250
+ repository = "",
251
+ sourceRef = "",
252
+ runId = "",
253
+ runAttempt = "",
254
+ platformId = process.platform,
255
+ platformName = platformId,
256
+ runnerOs = process.platform,
257
+ runnerArch = process.arch,
258
+ } = {}) {
259
+ if (!decision?.reusable || decision.action !== "reuse-source-qualification") {
260
+ throw new Error(
261
+ "reuse lifecycle evidence requires a reusable source-proof decision",
262
+ );
263
+ }
264
+ const files = [
265
+ fileFact(required(sourceProofPath, "source proof"), workspace),
266
+ fileFact(required(decisionPath, "reuse decision"), workspace),
267
+ ].sort((left, right) => left.path.localeCompare(right.path));
268
+ const summary = {
269
+ contract: "kungfu-buildchain-artifact-summary",
270
+ artifactName: "buildchain-check",
271
+ platform: String(platformId),
272
+ fileCount: files.length,
273
+ totalBytes: files.reduce((total, entry) => total + entry.size, 0),
274
+ digest: crypto
275
+ .createHash("sha256")
276
+ .update(JSON.stringify(files))
277
+ .digest("hex"),
278
+ };
279
+ const manifest = {
280
+ schemaVersion: 1,
281
+ contract: "kungfu-buildchain-artifact",
282
+ artifactName: "buildchain-check",
283
+ platform: {
284
+ id: String(platformId),
285
+ name: String(platformName),
286
+ os: String(runnerOs),
287
+ arch: String(runnerArch),
288
+ },
289
+ git: {
290
+ repository: String(repository),
291
+ sha: decision.mergeGroupHead,
292
+ ref: String(sourceRef),
293
+ runId: String(runId),
294
+ runAttempt: String(runAttempt),
295
+ },
296
+ lifecycle: {
297
+ stage: "check",
298
+ commandSource: "exact-source-proof-reuse",
299
+ executed: false,
300
+ satisfiedBy: decision.sourceProofRoot,
301
+ },
302
+ qualification: {
303
+ schema: REUSE_DECISION_SCHEMA,
304
+ authority: decision.finalAuthority,
305
+ sourceProofRoot: decision.sourceProofRoot,
306
+ decisionRoot: decision.decisionRoot,
307
+ sourceWorkflowRunId: decision.sourceWorkflowRunId,
308
+ sourceHead: decision.sourceHead,
309
+ qualifiedBase: decision.qualifiedBase,
310
+ currentBase: decision.currentBase,
311
+ mergeGroupHead: decision.mergeGroupHead,
312
+ mergeGroupTree: decision.mergeGroupTree,
313
+ },
314
+ summary,
315
+ expectedArtifacts: {
316
+ ok: true,
317
+ source: "exact-source-proof-reuse",
318
+ checks: [
319
+ {
320
+ name: "source-proof-verification",
321
+ ok: true,
322
+ detail: decision.sourceProofRoot,
323
+ },
324
+ ],
325
+ },
326
+ files,
327
+ };
328
+ writeJson(manifestPath, manifest);
329
+ writeJson(summaryPath, summary);
330
+ return { manifest, summary };
331
+ }
332
+
333
+ export function verifySourceQualificationReuse(input = {}) {
334
+ const proof = readJson(input.sourceProofPath, "source proof");
335
+ const receipt = readJson(input.controllerReceiptPath, "controller receipt");
336
+ const expectedSourceSha = exactSha(input.sourceHead, "sourceHead");
337
+ const expectedRuntimeSha = exactSha(input.runtimeSha, "runtimeSha");
338
+ const expectedRepository = required(input.repository, "repository");
339
+ const receiptValidation = validateControllerReceipt(receipt, {
340
+ expectedSourceSha,
341
+ expectedRuntimeSha,
342
+ });
343
+ if (
344
+ !receiptValidation.ok ||
345
+ !receiptValidation.qualifying ||
346
+ receipt.controller?.id !== "source-check" ||
347
+ receipt.source?.repository !== expectedRepository
348
+ ) {
349
+ return rootedDecision({
350
+ schema: REUSE_DECISION_SCHEMA,
351
+ reusable: false,
352
+ action: "rerun-full-source-qualification",
353
+ reason: "producer-controller-receipt-not-qualifying",
354
+ });
355
+ }
356
+ const verification = verifySourceQualificationProof(proof, {
357
+ repository: required(input.repository, "repository"),
358
+ protectedBase: required(input.protectedBase, "protectedBase"),
359
+ qualifiedBase: exactSha(proof.qualifiedBase, "qualifiedBase"),
360
+ sourceHead: exactSha(input.sourceHead, "sourceHead"),
361
+ sourceWorkflowRunId: Number(input.sourceWorkflowRunId),
362
+ controllerReceiptRoot: receipt.digest,
363
+ });
364
+ if (!verification.ok) {
365
+ return rootedDecision({
366
+ schema: REUSE_DECISION_SCHEMA,
367
+ reusable: false,
368
+ action: "rerun-full-source-qualification",
369
+ reason: verification.reason,
370
+ ...(verification.error ? { diagnostic: verification.error } : {}),
371
+ });
372
+ }
373
+ const current = sourceQualificationPredicates({
374
+ ...input,
375
+ qualifiedBase: proof.qualifiedBase,
376
+ });
377
+ const classification = classifyDevDeliveryDelta({
378
+ proof,
379
+ current: { ...current, graphKnown: true, changedPaths: [] },
380
+ });
381
+ if (!classification.reusable) {
382
+ return rootedDecision({
383
+ schema: REUSE_DECISION_SCHEMA,
384
+ reusable: false,
385
+ action: "rerun-full-source-qualification",
386
+ reason: classification.reason,
387
+ });
388
+ }
389
+ const mergeGroup = exactMergeGroupBinding({
390
+ ...input,
391
+ qualifiedBase: proof.qualifiedBase,
392
+ sourcePatchRoot: proof.sourcePatchRoot,
393
+ });
394
+ if (!mergeGroup.ok) {
395
+ return rootedDecision({
396
+ schema: REUSE_DECISION_SCHEMA,
397
+ reusable: false,
398
+ action: "rerun-full-source-qualification",
399
+ reason: mergeGroup.reason,
400
+ });
401
+ }
402
+ return rootedDecision({
403
+ schema: REUSE_DECISION_SCHEMA,
404
+ reusable: true,
405
+ action: "reuse-source-qualification",
406
+ reason: "exact-source-proof",
407
+ sourceProofRoot: proof.proofRoot,
408
+ sourceWorkflowRunId: proof.sourceWorkflowRunId,
409
+ sourceHead: proof.sourceHead,
410
+ qualifiedBase: proof.qualifiedBase,
411
+ currentBase: exactSha(input.currentBase, "currentBase"),
412
+ mergeGroupHead: mergeGroup.mergeGroupHead,
413
+ mergeGroupTree: mergeGroup.mergeGroupTree,
414
+ mergeGroupParents: mergeGroup.parents,
415
+ mergeGroupCompositionMode: mergeGroup.compositionMode,
416
+ mergeGroupReplayTrees: mergeGroup.replayedCommitTrees,
417
+ mergeGroupReplayPatchRoots: mergeGroup.replayedCommitPatchRoots,
418
+ predicateRoots: {
419
+ sourceIdentityRoot: proof.sourceIdentityRoot,
420
+ sourcePatchRoot: proof.sourcePatchRoot,
421
+ planRoot: proof.planRoot,
422
+ closureRoot: proof.closureRoot,
423
+ dependencyRoot: proof.dependencyRoot,
424
+ toolchainRoot: proof.toolchainRoot,
425
+ policyRoot: proof.policyRoot,
426
+ requiredContextRoot: proof.requiredContextRoot,
427
+ },
428
+ warrantBinding: "sourceProofRoot",
429
+ finalAuthority: "exact-merge-group-source-proof-verification",
430
+ verifiedAt: required(input.verifiedAt, "verifiedAt"),
431
+ });
432
+ }
433
+
434
+ function writeOutputs(values) {
435
+ const output = process.env.GITHUB_OUTPUT;
436
+ if (!output) return;
437
+ fs.appendFileSync(
438
+ output,
439
+ Object.entries(values)
440
+ .map(([key, value]) => `${key}=${String(value)}\n`)
441
+ .join(""),
442
+ );
443
+ }
444
+
445
+ function cliInput(args) {
446
+ return {
447
+ cwd: flag(args, "cwd", process.cwd()),
448
+ repository: flag(args, "repository", process.env.GITHUB_REPOSITORY),
449
+ protectedBase: flag(args, "branch", process.env.GITHUB_BASE_REF),
450
+ qualifiedBase: flag(args, "qualified-base"),
451
+ currentBase: flag(args, "current-base"),
452
+ sourceHead: flag(args, "source-head"),
453
+ sourceWorkflowRunId: flag(
454
+ args,
455
+ "source-workflow-run-id",
456
+ process.env.GITHUB_RUN_ID,
457
+ ),
458
+ runtimeRef: flag(args, "runtime-ref"),
459
+ runtimeSha: flag(args, "runtime-sha"),
460
+ contractDigest: flag(args, "contract-digest"),
461
+ nodeVersion: flag(args, "node-version", "24"),
462
+ workingDirectory: flag(args, "working-directory", "."),
463
+ policyPaths: flag(args, "policy-paths-json"),
464
+ closurePaths: flag(args, "closure-paths-json"),
465
+ dependencyPaths: flag(args, "dependency-paths-json"),
466
+ requiredContexts: flag(args, "required-contexts-json"),
467
+ controllerReceiptPath: flag(args, "controller-receipt"),
468
+ sourceProofPath: flag(args, "source-proof"),
469
+ mergeGroupHead: flag(args, "merge-group-head"),
470
+ mergeGroupTree: flag(args, "merge-group-tree"),
471
+ qualifiedAt: flag(args, "qualified-at"),
472
+ verifiedAt: flag(args, "verified-at"),
473
+ outputPath: flag(args, "output"),
474
+ manifestPath: flag(args, "manifest-output"),
475
+ summaryPath: flag(args, "summary-output"),
476
+ };
477
+ }
478
+
479
+ export function sourceProofReuseCli(args = process.argv.slice(2)) {
480
+ const [command = "", ...rest] = args;
481
+ const input = cliInput(rest);
482
+ if (command === "seal") {
483
+ const proof = sealSourceQualificationProof(input);
484
+ writeJson(input.outputPath, proof);
485
+ writeOutputs({ "proof-root": proof.proofRoot, "proof-reused": "false" });
486
+ return proof;
487
+ }
488
+ if (command === "verify") {
489
+ let decision;
490
+ try {
491
+ decision = verifySourceQualificationReuse(input);
492
+ } catch (error) {
493
+ decision = rootedDecision({
494
+ schema: REUSE_DECISION_SCHEMA,
495
+ reusable: false,
496
+ action: "rerun-full-source-qualification",
497
+ reason: "unverifiable-proof",
498
+ diagnostic: String(error.message || error),
499
+ });
500
+ }
501
+ writeJson(input.outputPath, decision);
502
+ if (decision.reusable && input.manifestPath && input.summaryPath) {
503
+ materializeReuseLifecycleEvidence({
504
+ decision,
505
+ sourceProofPath: input.sourceProofPath,
506
+ decisionPath: input.outputPath,
507
+ manifestPath: input.manifestPath,
508
+ summaryPath: input.summaryPath,
509
+ workspace: input.cwd,
510
+ repository: input.repository,
511
+ sourceRef: process.env.GITHUB_REF,
512
+ runId: process.env.GITHUB_RUN_ID,
513
+ runAttempt: process.env.GITHUB_RUN_ATTEMPT,
514
+ platformId: process.env.RUNNER_OS || process.platform,
515
+ platformName:
516
+ process.env.RUNNER_NAME || process.env.RUNNER_OS || process.platform,
517
+ runnerOs: process.env.RUNNER_OS || process.platform,
518
+ runnerArch: process.env.RUNNER_ARCH || process.arch,
519
+ });
520
+ }
521
+ writeOutputs({
522
+ reuse: String(decision.reusable),
523
+ "proof-root": decision.sourceProofRoot || "",
524
+ reason: decision.reason,
525
+ "decision-root": decision.decisionRoot,
526
+ });
527
+ return decision;
528
+ }
529
+ throw new Error(
530
+ "usage: dev-delivery-source-proof-reuse.mjs <seal|verify> [options]",
531
+ );
532
+ }
533
+
534
+ if (
535
+ process.argv[1] &&
536
+ import.meta.url === pathToFileURL(process.argv[1]).href
537
+ ) {
538
+ try {
539
+ const result = sourceProofReuseCli();
540
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
541
+ } catch (error) {
542
+ console.error(
543
+ `::error::${String(error.message || error).replace(/\r?\n/gu, "%0A")}`,
544
+ );
545
+ process.exitCode = 1;
546
+ }
547
+ }