@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.
Files changed (203) hide show
  1. package/actions/release-tail/README.md +8 -0
  2. package/architecture/ci-lane-change-budget.json +187 -0
  3. package/architecture/decisions/0002-next-development-transition.md +101 -0
  4. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  5. package/architecture/internal-capabilities.json +211 -8
  6. package/architecture/maintainability-policy.json +113 -28
  7. package/architecture/release-tail-contract-inventory.json +5 -2
  8. package/architecture/v3-core-mechanism-inventory.json +91 -0
  9. package/architecture/v4-adopter-delivery-parity.json +32 -5
  10. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  11. package/architecture/v4-compatibility-facts-parity.json +77 -0
  12. package/architecture/v4-delivery-authority-parity.json +250 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  14. package/architecture/v4-floating-consumer-policy.json +6 -0
  15. package/architecture/v4-next-development-parity.json +192 -0
  16. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  17. package/architecture/v4-release-train-parity.json +1 -0
  18. package/architecture/v4-tail-reseal-parity.json +150 -0
  19. package/bin/buildchain.mjs +32 -3
  20. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  21. package/bin/internal/command-registry.mjs +3 -0
  22. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  23. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  24. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  25. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  26. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  29. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  30. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  31. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  32. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  33. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  38. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  39. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  43. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  44. package/contracts/next-development-request-v1.schema.json +66 -0
  45. package/contracts/next-development-transition-v1.schema.json +292 -0
  46. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  47. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  48. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  49. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  50. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  51. package/dist/site/agent-index.json +5 -0
  52. package/dist/site/artifact-schemas.json +10 -0
  53. package/dist/site/buildchain-contract.json +1475 -27
  54. package/dist/site/buildchain-site.json +627 -31
  55. package/dist/site/capability-registry.json +17 -14
  56. package/dist/site/cli-registry.json +205 -3
  57. package/dist/site/kfd-claims.json +490 -16
  58. package/dist/site/kfd-upstream-aggregate.json +1 -1
  59. package/dist/site/manual-registry.json +52 -6
  60. package/dist/site/node-api-registry.json +14736 -8851
  61. package/dist/site/page-registry.json +593 -21
  62. package/dist/site/public-surface-audit.json +837 -48
  63. package/dist/site/publication-authority-registry.json +44 -8
  64. package/dist/site/publication-registry.json +4 -4
  65. package/dist/site/release-provenance.json +24 -0
  66. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  67. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  68. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  69. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  70. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  71. package/dist/site/site-manifest.json +34 -10
  72. package/dist/site/workflow-registry.json +160 -10
  73. package/docs/MAP.md +8 -2
  74. package/docs/cli-reference.md +404 -13
  75. package/docs/cli.md +8 -0
  76. package/docs/dev-alpha-candidate-patrol.md +8 -0
  77. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  78. package/docs/dev-delivery-warrant.md +351 -38
  79. package/docs/next-development-transition.md +118 -0
  80. package/docs/node-api-reference.md +646 -293
  81. package/docs/release-tail-provider-plane.md +30 -0
  82. package/docs/v4-adopter-delivery.md +133 -0
  83. package/docs/v4-compatibility-facts.md +91 -0
  84. package/docs/v4-publication-rehearsal.md +130 -0
  85. package/docs/v4-tail-reseal.md +73 -0
  86. package/docs/versioning.md +4 -3
  87. package/package.json +34 -6
  88. package/packages/core/adopter-delivery-passport.js +259 -0
  89. package/packages/core/adopter-delivery-vectors.js +158 -0
  90. package/packages/core/adopter-delivery-vectors.json +77 -0
  91. package/packages/core/buildchain-agent-manuals.js +3 -0
  92. package/packages/core/buildchain-compatibility-authority.js +336 -0
  93. package/packages/core/buildchain-compatibility-fact.js +394 -0
  94. package/packages/core/buildchain-compatibility-facts.json +217 -0
  95. package/packages/core/buildchain-compatibility-proof.js +566 -0
  96. package/packages/core/buildchain-config.js +84 -0
  97. package/packages/core/buildchain-contract.js +96 -143
  98. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  99. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  100. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  101. package/packages/core/buildchain-publication-authority.js +1 -0
  102. package/packages/core/channel-candidate.js +8 -0
  103. package/packages/core/channel-promotion-baseline.js +55 -0
  104. package/packages/core/ci-lane-change-budget.js +247 -0
  105. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  106. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  107. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  108. package/packages/core/dev-delivery-authority-landing.js +461 -0
  109. package/packages/core/dev-delivery-authority-observation.js +48 -0
  110. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  111. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  112. package/packages/core/dev-delivery-authority-state.js +583 -0
  113. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  114. package/packages/core/dev-delivery-contract-surface.js +76 -0
  115. package/packages/core/dev-delivery-execution-failure.js +133 -0
  116. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  117. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  118. package/packages/core/dev-delivery-landing-readback.js +598 -0
  119. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  120. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  121. package/packages/core/dev-delivery-native-execution.js +110 -0
  122. package/packages/core/dev-delivery-native-proof.js +562 -0
  123. package/packages/core/dev-delivery-process-boundary.js +563 -0
  124. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  125. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  126. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  127. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  128. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  129. package/packages/core/dev-delivery-warrant-state.js +587 -0
  130. package/packages/core/dev-delivery-warrant.js +328 -370
  131. package/packages/core/github-governance-authority.js +1 -0
  132. package/packages/core/index.js +5 -0
  133. package/packages/core/kungfu-temporal-fact.js +557 -0
  134. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  135. package/packages/core/next-development-candidate-reservation.js +186 -0
  136. package/packages/core/next-development-controller.js +728 -0
  137. package/packages/core/next-development-projection.js +287 -0
  138. package/packages/core/next-development-transition.js +738 -0
  139. package/packages/core/paper-agent-entry.js +11 -5
  140. package/packages/core/paper.js +16 -5
  141. package/packages/core/published-delivery-authority.js +266 -0
  142. package/packages/core/release-candidate-recovery.js +70 -15
  143. package/packages/core/release-candidate.js +2 -2
  144. package/packages/core/release-passport.js +36 -9
  145. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  146. package/packages/core/v4-adopter-delivery.js +359 -0
  147. package/packages/core/v4-canonical-contracts.js +5 -0
  148. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  149. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  150. package/packages/core/v4-floating-consumer-policy.js +143 -20
  151. package/packages/core/v4-floating-consumer-release-passport.js +32 -5
  152. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  153. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  154. package/packages/core/v4-publication-rehearsal.js +481 -0
  155. package/packages/core/v4-tail-reseal-contract.js +26 -0
  156. package/packages/core/v4-tail-reseal-github.js +178 -0
  157. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  158. package/packages/core/v4-tail-reseal.js +594 -0
  159. package/packages/core/workflow-yaml-contract.js +24 -2
  160. package/scripts/buildchain-cli-help.mjs +41 -2
  161. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  162. package/scripts/check-inventory.mjs +14 -3
  163. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  164. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  165. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  166. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  167. package/scripts/dev-delivery-authority.mjs +490 -0
  168. package/scripts/dev-delivery-native-run.mjs +177 -0
  169. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  170. package/scripts/dev-delivery-proof.mjs +67 -2
  171. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  172. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  173. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  174. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  175. package/scripts/dev-delivery-two-phase.mjs +573 -0
  176. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  177. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  178. package/scripts/dev-delivery-warrant.mjs +232 -194
  179. package/scripts/dev-pr-auto-merge.mjs +1 -1
  180. package/scripts/dev-pr-delivery-warrant.mjs +11 -0
  181. package/scripts/dev-qualification-patrol.mjs +9 -9
  182. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  183. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  184. package/scripts/generate-next-development-guidance.mjs +49 -0
  185. package/scripts/generate-release-candidate-passport.mjs +152 -0
  186. package/scripts/generate-site-bundle.mjs +69 -4
  187. package/scripts/init-repo.mjs +22 -4
  188. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  189. package/scripts/next-development-self-dogfood.mjs +597 -0
  190. package/scripts/next-development-transition.mjs +47 -0
  191. package/scripts/npm-publish-transaction.mjs +2 -2
  192. package/scripts/promotion-channel-router.mjs +4 -4
  193. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  194. package/scripts/release-tail.mjs +51 -3
  195. package/scripts/release-train-self-dogfood.mjs +94 -92
  196. package/scripts/resume-from-candidate-run.mjs +557 -30
  197. package/scripts/site-capability-metadata.mjs +41 -0
  198. package/scripts/stable-candidate-qualification.mjs +9 -10
  199. package/scripts/v4-consumer-policy.mjs +5 -5
  200. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  201. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  202. package/scripts/v4-tail-reseal.mjs +416 -0
  203. 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
+ }
@@ -11,6 +11,7 @@ import {
11
11
  verifySourceQualificationProof,
12
12
  } from "../packages/core/dev-delivery-warrant.js";
13
13
  import { validateControllerReceipt } from "../packages/core/controller-evidence.js";
14
+ import { exactMergeGroupBinding } from "./dev-delivery-source-proof-replay.mjs";
14
15
 
15
16
  const REUSE_DECISION_SCHEMA =
16
17
  "kungfu.buildchain.source-qualification-reuse-decision/v1";
@@ -113,98 +114,6 @@ function gitPathRoot(ref, paths, label, cwd) {
113
114
  });
114
115
  }
115
116
 
116
- function exactMergeGroupBinding(input = {}) {
117
- const cwd = path.resolve(input.cwd || process.cwd());
118
- const mergeGroupHead = exactSha(input.mergeGroupHead, "mergeGroupHead");
119
- const expectedTree = exactSha(input.mergeGroupTree, "mergeGroupTree");
120
- const currentBase = exactSha(input.currentBase, "currentBase");
121
- const sourceHead = exactSha(input.sourceHead, "sourceHead");
122
- const actualTree = exactSha(
123
- git(["rev-parse", `${mergeGroupHead}^{tree}`], { cwd }),
124
- "mergeGroupTree",
125
- );
126
- if (actualTree !== expectedTree)
127
- return { ok: false, reason: "merge-group-tree-mismatch" };
128
- const [commit, ...parents] = git(
129
- ["rev-list", "--parents", "-n", "1", mergeGroupHead],
130
- { cwd },
131
- ).split(/\s+/u);
132
- if (commit !== mergeGroupHead) {
133
- return { ok: false, reason: "merge-group-parent-mismatch" };
134
- }
135
- if (
136
- parents.length === 2 &&
137
- parents[0] === currentBase &&
138
- parents[1] === sourceHead
139
- ) {
140
- return {
141
- ok: true,
142
- mergeGroupHead,
143
- mergeGroupTree: actualTree,
144
- parents,
145
- compositionMode: "two-parent-merge",
146
- replayedCommitTrees: [],
147
- };
148
- }
149
-
150
- const linearRange = (head) => {
151
- try {
152
- git(["merge-base", "--is-ancestor", currentBase, head], { cwd });
153
- } catch {
154
- return null;
155
- }
156
- const commits = git(
157
- ["rev-list", "--reverse", "--topo-order", `${currentBase}..${head}`],
158
- { cwd },
159
- )
160
- .split(/\s+/u)
161
- .filter(Boolean);
162
- if (commits.length === 0) return null;
163
- let expectedParent = currentBase;
164
- const trees = [];
165
- for (const rangeCommit of commits) {
166
- const [observedCommit, ...rangeParents] = git(
167
- ["rev-list", "--parents", "-n", "1", rangeCommit],
168
- { cwd },
169
- ).split(/\s+/u);
170
- if (
171
- observedCommit !== rangeCommit ||
172
- rangeParents.length !== 1 ||
173
- rangeParents[0] !== expectedParent
174
- ) {
175
- return null;
176
- }
177
- trees.push(
178
- exactSha(
179
- git(["rev-parse", `${rangeCommit}^{tree}`], { cwd }),
180
- "replayedCommitTree",
181
- ),
182
- );
183
- expectedParent = rangeCommit;
184
- }
185
- return trees;
186
- };
187
-
188
- const sourceTrees = linearRange(sourceHead);
189
- const mergeGroupTrees = linearRange(mergeGroupHead);
190
- if (
191
- !sourceTrees ||
192
- !mergeGroupTrees ||
193
- sourceTrees.length !== mergeGroupTrees.length ||
194
- sourceTrees.some((tree, index) => tree !== mergeGroupTrees[index])
195
- ) {
196
- return { ok: false, reason: "merge-group-parent-mismatch" };
197
- }
198
- return {
199
- ok: true,
200
- mergeGroupHead,
201
- mergeGroupTree: actualTree,
202
- parents,
203
- compositionMode: "linear-replay",
204
- replayedCommitTrees: mergeGroupTrees,
205
- };
206
- }
207
-
208
117
  export function sourceQualificationPredicates(input = {}) {
209
118
  const cwd = path.resolve(input.cwd || process.cwd());
210
119
  const repository = required(input.repository, "repository");
@@ -397,7 +306,8 @@ export function materializeReuseLifecycleEvidence({
397
306
  decisionRoot: decision.decisionRoot,
398
307
  sourceWorkflowRunId: decision.sourceWorkflowRunId,
399
308
  sourceHead: decision.sourceHead,
400
- qualifiedBase: decision.currentBase,
309
+ qualifiedBase: decision.qualifiedBase,
310
+ currentBase: decision.currentBase,
401
311
  mergeGroupHead: decision.mergeGroupHead,
402
312
  mergeGroupTree: decision.mergeGroupTree,
403
313
  },
@@ -446,7 +356,7 @@ export function verifySourceQualificationReuse(input = {}) {
446
356
  const verification = verifySourceQualificationProof(proof, {
447
357
  repository: required(input.repository, "repository"),
448
358
  protectedBase: required(input.protectedBase, "protectedBase"),
449
- qualifiedBase: exactSha(input.currentBase, "currentBase"),
359
+ qualifiedBase: exactSha(proof.qualifiedBase, "qualifiedBase"),
450
360
  sourceHead: exactSha(input.sourceHead, "sourceHead"),
451
361
  sourceWorkflowRunId: Number(input.sourceWorkflowRunId),
452
362
  controllerReceiptRoot: receipt.digest,
@@ -462,7 +372,7 @@ export function verifySourceQualificationReuse(input = {}) {
462
372
  }
463
373
  const current = sourceQualificationPredicates({
464
374
  ...input,
465
- qualifiedBase: input.currentBase,
375
+ qualifiedBase: proof.qualifiedBase,
466
376
  });
467
377
  const classification = classifyDevDeliveryDelta({
468
378
  proof,
@@ -476,7 +386,11 @@ export function verifySourceQualificationReuse(input = {}) {
476
386
  reason: classification.reason,
477
387
  });
478
388
  }
479
- const mergeGroup = exactMergeGroupBinding(input);
389
+ const mergeGroup = exactMergeGroupBinding({
390
+ ...input,
391
+ qualifiedBase: proof.qualifiedBase,
392
+ sourcePatchRoot: proof.sourcePatchRoot,
393
+ });
480
394
  if (!mergeGroup.ok) {
481
395
  return rootedDecision({
482
396
  schema: REUSE_DECISION_SCHEMA,
@@ -493,12 +407,14 @@ export function verifySourceQualificationReuse(input = {}) {
493
407
  sourceProofRoot: proof.proofRoot,
494
408
  sourceWorkflowRunId: proof.sourceWorkflowRunId,
495
409
  sourceHead: proof.sourceHead,
496
- currentBase: proof.qualifiedBase,
410
+ qualifiedBase: proof.qualifiedBase,
411
+ currentBase: exactSha(input.currentBase, "currentBase"),
497
412
  mergeGroupHead: mergeGroup.mergeGroupHead,
498
413
  mergeGroupTree: mergeGroup.mergeGroupTree,
499
414
  mergeGroupParents: mergeGroup.parents,
500
415
  mergeGroupCompositionMode: mergeGroup.compositionMode,
501
416
  mergeGroupReplayTrees: mergeGroup.replayedCommitTrees,
417
+ mergeGroupReplayPatchRoots: mergeGroup.replayedCommitPatchRoots,
502
418
  predicateRoots: {
503
419
  sourceIdentityRoot: proof.sourceIdentityRoot,
504
420
  sourcePatchRoot: proof.sourcePatchRoot,
@@ -0,0 +1,345 @@
1
+ import { execFileSync } from "node:child_process";
2
+
3
+ import {
4
+ createNativeCommandContract,
5
+ createNativeProofReuseDecision,
6
+ createNativeQualificationProof,
7
+ } from "../packages/core/dev-delivery-warrant.js";
8
+
9
+ const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/u;
10
+
11
+ function exactLocalSha(value, label) {
12
+ const normalized = String(value || "")
13
+ .trim()
14
+ .toLowerCase();
15
+ if (!/^[0-9a-f]{40}$/u.test(normalized))
16
+ throw new Error(`${label} must be an exact lowercase Git SHA`);
17
+ return normalized;
18
+ }
19
+
20
+ export class LocalTwoPhaseClient {
21
+ constructor({ candidateDirectory } = {}) {
22
+ this.candidateDirectory = candidateDirectory;
23
+ }
24
+
25
+ git(args, options = {}) {
26
+ return execFileSync("git", ["-C", this.candidateDirectory, ...args], {
27
+ encoding: "utf8",
28
+ ...options,
29
+ }).trim();
30
+ }
31
+
32
+ async baseSha(branch) {
33
+ return exactLocalSha(
34
+ this.git(["rev-parse", `refs/remotes/origin/${branch}`]),
35
+ "protected base SHA",
36
+ );
37
+ }
38
+
39
+ async exactPullRequestHead(_pullRequestNumber, expectedHead) {
40
+ const observed = exactLocalSha(this.git(["rev-parse", "HEAD"]), "PR head");
41
+ if (observed !== expectedHead)
42
+ throw new Error(
43
+ `semantic source head changed: ${observed} != ${expectedHead}`,
44
+ );
45
+ return observed;
46
+ }
47
+
48
+ async baseDelta(previousBase, currentBase) {
49
+ if (previousBase === currentBase)
50
+ return {
51
+ graphKnown: true,
52
+ attributionComplete: true,
53
+ changedPaths: [],
54
+ renames: [],
55
+ };
56
+ try {
57
+ this.git(["merge-base", "--is-ancestor", previousBase, currentBase]);
58
+ } catch {
59
+ return {
60
+ graphKnown: false,
61
+ attributionComplete: false,
62
+ changedPaths: [],
63
+ renames: [],
64
+ };
65
+ }
66
+ const entries = this.git([
67
+ "diff",
68
+ "--name-status",
69
+ "-M",
70
+ previousBase,
71
+ currentBase,
72
+ ])
73
+ .split("\n")
74
+ .filter(Boolean)
75
+ .map((line) => line.split("\t"));
76
+ const renames = entries
77
+ .filter(([status]) => status.startsWith("R"))
78
+ .map(([, from, to]) => ({ from, to }));
79
+ return {
80
+ graphKnown: true,
81
+ attributionComplete: renames.every(({ from, to }) => from && to),
82
+ changedPaths: [
83
+ ...new Set(entries.flatMap(([, ...paths]) => paths).filter(Boolean)),
84
+ ].sort(),
85
+ renames,
86
+ };
87
+ }
88
+ }
89
+
90
+ export class GitHubTwoPhaseClient {
91
+ constructor({
92
+ repository,
93
+ token,
94
+ apiUrl = "https://api.github.com",
95
+ fetchImpl = globalThis.fetch,
96
+ } = {}) {
97
+ if (!token) throw new Error("GITHUB_TOKEN is required");
98
+ this.repository = repository;
99
+ this.token = token;
100
+ this.apiUrl = apiUrl.replace(/\/+$/u, "");
101
+ this.fetch = fetchImpl;
102
+ }
103
+
104
+ async request(requestPath, { method = "GET", body } = {}) {
105
+ const response = await this.fetch(`${this.apiUrl}${requestPath}`, {
106
+ method,
107
+ headers: {
108
+ accept: "application/vnd.github+json",
109
+ authorization: `Bearer ${this.token}`,
110
+ "content-type": "application/json",
111
+ "x-github-api-version": "2022-11-28",
112
+ },
113
+ body: body === undefined ? undefined : JSON.stringify(body),
114
+ });
115
+ const raw = await response.text();
116
+ const data = raw ? JSON.parse(raw) : null;
117
+ if (!response.ok) throw new Error(data?.message || `${requestPath} failed`);
118
+ return data;
119
+ }
120
+
121
+ async baseSha(branch) {
122
+ const data = await this.request(
123
+ `/repos/${this.repository}/git/ref/heads/${branch
124
+ .split("/")
125
+ .map(encodeURIComponent)
126
+ .join("/")}`,
127
+ );
128
+ return exactLocalSha(data?.object?.sha, "protected base SHA");
129
+ }
130
+
131
+ async exactPullRequestHead(pullRequestNumber, expectedHead) {
132
+ const data = await this.request(
133
+ `/repos/${this.repository}/pulls/${pullRequestNumber}`,
134
+ );
135
+ const observed = exactLocalSha(data?.head?.sha, "observed PR head");
136
+ if (observed !== expectedHead)
137
+ throw new Error(
138
+ `semantic source head changed: ${observed} != ${expectedHead}`,
139
+ );
140
+ return observed;
141
+ }
142
+
143
+ async baseDelta(previousBase, currentBase) {
144
+ if (previousBase === currentBase)
145
+ return {
146
+ graphKnown: true,
147
+ attributionComplete: true,
148
+ changedPaths: [],
149
+ renames: [],
150
+ };
151
+ const data = await this.request(
152
+ `/repos/${this.repository}/compare/${previousBase}...${currentBase}`,
153
+ );
154
+ return attributedGitHubBaseDelta(data, previousBase);
155
+ }
156
+
157
+ async wake(eventType, candidate) {
158
+ await this.request(`/repos/${this.repository}/dispatches`, {
159
+ method: "POST",
160
+ body: { event_type: eventType, client_payload: { candidate } },
161
+ });
162
+ }
163
+ }
164
+
165
+ export function attributedGitHubBaseDelta(data, previousBase) {
166
+ const files = Array.isArray(data?.files) ? data.files : [];
167
+ const graphKnown =
168
+ data?.status === "ahead" &&
169
+ data?.merge_base_commit?.sha === previousBase &&
170
+ files.length < 300;
171
+ const renames = files
172
+ .filter((entry) => entry.status === "renamed")
173
+ .map((entry) => ({
174
+ from: String(entry.previous_filename || ""),
175
+ to: String(entry.filename || ""),
176
+ }));
177
+ const attributionComplete =
178
+ graphKnown && renames.every((entry) => entry.from && entry.to);
179
+ return {
180
+ graphKnown,
181
+ attributionComplete,
182
+ changedPaths: attributionComplete
183
+ ? [
184
+ ...new Set(
185
+ files
186
+ .flatMap((entry) => [
187
+ String(entry.filename || ""),
188
+ String(entry.previous_filename || ""),
189
+ ])
190
+ .filter(Boolean),
191
+ ),
192
+ ].sort()
193
+ : [],
194
+ renames: attributionComplete ? renames : [],
195
+ };
196
+ }
197
+
198
+ export async function replayQualifiedNativeWarrant({
199
+ warrant,
200
+ pullRequestNumber,
201
+ expectedHead,
202
+ exactPullRequestHead,
203
+ }) {
204
+ if (warrant.phase !== "qualified") return null;
205
+ if (
206
+ !ROOT_PATTERN.test(warrant.nativeProofRoot || "") ||
207
+ !ROOT_PATTERN.test(warrant.nativeProofReuseRoot || "") ||
208
+ !ROOT_PATTERN.test(warrant.qualificationReceiptRoot || "")
209
+ ) {
210
+ throw new Error(
211
+ "qualified Warrant replay is missing rooted native or qualification evidence",
212
+ );
213
+ }
214
+ await exactPullRequestHead(pullRequestNumber, expectedHead);
215
+ return {
216
+ schema: "kungfu.buildchain.two-phase-delivery-result/v1",
217
+ ok: true,
218
+ outcome: "already-qualified-warrant",
219
+ nativeAttempts: 0,
220
+ nativeProofRoot: warrant.nativeProofRoot,
221
+ nativeReuseDecisionRoot: warrant.nativeProofReuseRoot,
222
+ qualificationReceiptRoot: warrant.qualificationReceiptRoot,
223
+ landingAuthority: false,
224
+ qualifiedWarrant: warrant,
225
+ };
226
+ }
227
+
228
+ export async function classifyNativeProofAgainstCurrent(
229
+ proof,
230
+ options,
231
+ client,
232
+ ) {
233
+ const currentBase = await client.baseSha(options.branch);
234
+ const delta = await client.baseDelta(proof.qualifiedBase, currentBase);
235
+ const current = {
236
+ sourceHead: options.expectedHead,
237
+ sourceIdentityRoot: options.sourceIdentityRoot,
238
+ sourcePatchRoot: options.sourcePatchRoot,
239
+ planRoot: options.planRoot,
240
+ closureRoot: options.closureRoot,
241
+ dependencyRoot: options.dependencyRoot,
242
+ toolchainRoot: options.toolchainRoot,
243
+ environmentRoot: options.environmentRoot,
244
+ nativeCommandRoot: options.nativeCommandRoot,
245
+ currentBase,
246
+ graphKnown: delta.graphKnown,
247
+ attributionComplete: delta.attributionComplete,
248
+ changedPaths: delta.changedPaths,
249
+ renames: delta.renames,
250
+ };
251
+ return {
252
+ current,
253
+ decision: createNativeProofReuseDecision({ proof, current }),
254
+ };
255
+ }
256
+
257
+ export async function runNativeQualificationAttempt({
258
+ options,
259
+ warrant,
260
+ attempt,
261
+ client,
262
+ runCommand,
263
+ runNative,
264
+ composeCandidate,
265
+ writeEvidence,
266
+ }) {
267
+ await client.exactPullRequestHead(
268
+ options.pullRequestNumber,
269
+ options.expectedHead,
270
+ );
271
+ const qualifiedBase = await client.baseSha(options.branch);
272
+ composeCandidate(
273
+ options.candidateDirectory,
274
+ options.expectedHead,
275
+ qualifiedBase,
276
+ );
277
+ const commandContract = createNativeCommandContract(options.nativeCommand);
278
+ if (
279
+ commandContract.commandRoot !==
280
+ warrant.nativeCommandContract?.commandRoot ||
281
+ commandContract.commandRoot !== options.nativeCommandRoot
282
+ ) {
283
+ throw new Error(
284
+ "native command does not match the authorized Warrant contract",
285
+ );
286
+ }
287
+ const nativeExecutionReceipt = await runNative({
288
+ command: options.nativeCommand,
289
+ cwd: options.candidateDirectory,
290
+ intervalMs: options.heartbeatSeconds * 1000,
291
+ executionBinding: {
292
+ repository: options.repository,
293
+ protectedBase: options.branch,
294
+ sourceHead: options.expectedHead,
295
+ qualifiedBase,
296
+ nativeCommandRoot: options.nativeCommandRoot,
297
+ toolchainRoot: options.toolchainRoot,
298
+ environmentRoot: options.environmentRoot,
299
+ },
300
+ heartbeat: async () => {
301
+ await runCommand({
302
+ command: "heartbeat",
303
+ repository: options.repository,
304
+ branch: options.branch,
305
+ fencingToken: warrant.fencingToken,
306
+ leaseGeneration: warrant.generation,
307
+ leaseSeconds: options.leaseSeconds,
308
+ execute: true,
309
+ token: options.token,
310
+ apiUrl: options.apiUrl,
311
+ });
312
+ },
313
+ });
314
+ writeEvidence(
315
+ `native-heartbeat-attempt-${attempt}.json`,
316
+ nativeExecutionReceipt,
317
+ );
318
+ await client.exactPullRequestHead(
319
+ options.pullRequestNumber,
320
+ options.expectedHead,
321
+ );
322
+ const proof = createNativeQualificationProof({
323
+ repository: options.repository,
324
+ protectedBase: options.branch,
325
+ sourceIdentityRoot: options.sourceIdentityRoot,
326
+ sourcePatchRoot: options.sourcePatchRoot,
327
+ planRoot: options.planRoot,
328
+ closureRoot: options.closureRoot,
329
+ dependencyRoot: options.dependencyRoot,
330
+ toolchainRoot: options.toolchainRoot,
331
+ environmentRoot: options.environmentRoot,
332
+ sourceHead: options.expectedHead,
333
+ qualifiedBase,
334
+ nativeCommandRoot: options.nativeCommandRoot,
335
+ nativeExecutionReceipt,
336
+ affectedPaths: options.affectedPaths,
337
+ shardEvidenceRoots: [
338
+ ...options.shardEvidenceRoots,
339
+ nativeExecutionReceipt.receiptRoot,
340
+ ],
341
+ qualifiedAt: new Date().toISOString(),
342
+ });
343
+ writeEvidence("native-proof.json", proof);
344
+ return proof;
345
+ }