@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
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ export const V4_RUNTIME_SEMANTIC_CLOSURE_CONTRACT =
8
+ "kungfu-buildchain-v4-runtime-semantic-closure";
9
+ export const V4_RUNTIME_SEMANTIC_CLOSURE_PATH =
10
+ "architecture/v4-runtime-semantic-closure.json";
11
+ export const REQUIRED_RUNTIME_EVIDENCE = Object.freeze([
12
+ "positive",
13
+ "negative",
14
+ "failure",
15
+ "recovery",
16
+ "idempotence",
17
+ ]);
18
+
19
+ function loadJson(root, relativePath) {
20
+ return JSON.parse(fs.readFileSync(path.join(root, relativePath), "utf8"));
21
+ }
22
+
23
+ function sorted(values) {
24
+ return [...values].sort((left, right) => left.localeCompare(right));
25
+ }
26
+
27
+ function sameMembers(left, right) {
28
+ return JSON.stringify(sorted(left)) === JSON.stringify(sorted(right));
29
+ }
30
+
31
+ function publicSurfaceIds(root) {
32
+ const packageJson = loadJson(root, "package.json");
33
+ const cli = loadJson(root, "dist/site/cli-registry.json");
34
+ const workflows = loadJson(root, "dist/site/workflow-registry.json");
35
+ return new Set([
36
+ ...Object.keys(packageJson.exports || {}).map((id) => `export:${id}`),
37
+ ...(cli.commands || []).map((entry) => `cli:${entry.id}`),
38
+ ...(workflows.workflows || []).map((entry) => `workflow:${entry.id}`),
39
+ ...(workflows.actions || []).map((entry) => `action:${entry.id}`),
40
+ ]);
41
+ }
42
+
43
+ function validateEvidence(root, mechanism, capability, issues) {
44
+ const sourceTests = new Set(mechanism.testPaths || []);
45
+ for (const dimension of REQUIRED_RUNTIME_EVIDENCE) {
46
+ const entries = capability.evidence?.[dimension];
47
+ if (!Array.isArray(entries) || entries.length === 0) {
48
+ issues.push(`${capability.id}: ${dimension} evidence is empty`);
49
+ continue;
50
+ }
51
+ for (const entry of entries) {
52
+ if (!entry?.path || !entry?.name) {
53
+ issues.push(`${capability.id}: ${dimension} evidence is incomplete`);
54
+ continue;
55
+ }
56
+ if (!sourceTests.has(entry.path))
57
+ issues.push(
58
+ `${capability.id}: ${dimension} evidence is outside the reverse-scanned test set: ${entry.path}`,
59
+ );
60
+ const absolute = path.join(root, entry.path);
61
+ if (!fs.existsSync(absolute)) {
62
+ issues.push(
63
+ `${capability.id}: evidence path is missing: ${entry.path}`,
64
+ );
65
+ continue;
66
+ }
67
+ const source = fs.readFileSync(absolute, "utf8");
68
+ if (!source.includes(`test(${JSON.stringify(entry.name)}`))
69
+ issues.push(
70
+ `${capability.id}: evidence test is missing from ${entry.path}: ${entry.name}`,
71
+ );
72
+ }
73
+ }
74
+ }
75
+
76
+ function validateResidualClosure(mechanism, capability, issues) {
77
+ const sourceResiduals = mechanism.unresolved || [];
78
+ const closed = capability.resolvedResiduals || [];
79
+ if (
80
+ !sameMembers(
81
+ sourceResiduals,
82
+ closed.map((entry) => entry?.source || ""),
83
+ )
84
+ )
85
+ issues.push(
86
+ `${capability.id}: resolved residual identities do not match the reverse scan`,
87
+ );
88
+ for (const entry of closed) {
89
+ if (!String(entry?.resolution || "").trim())
90
+ issues.push(`${capability.id}: residual resolution is empty`);
91
+ }
92
+ }
93
+
94
+ function validateImplementation(root, mechanism, capability, issues) {
95
+ if (
96
+ !Array.isArray(capability.implementation) ||
97
+ capability.implementation.length === 0
98
+ ) {
99
+ issues.push(`${capability.id}: implementation route is empty`);
100
+ return;
101
+ }
102
+ const reverseScanned = new Set(mechanism.sourcePaths || []);
103
+ let boundToReverseScan = false;
104
+ for (const relativePath of capability.implementation) {
105
+ if (!fs.existsSync(path.join(root, relativePath)))
106
+ issues.push(
107
+ `${capability.id}: implementation path is missing: ${relativePath}`,
108
+ );
109
+ if (reverseScanned.has(relativePath)) boundToReverseScan = true;
110
+ }
111
+ if (!boundToReverseScan)
112
+ issues.push(
113
+ `${capability.id}: implementation is not bound to a reverse-scanned v3 mechanism route`,
114
+ );
115
+ }
116
+
117
+ function validateAuthority(capability, stateMachine, issues) {
118
+ for (const field of [
119
+ "exactHeadFence",
120
+ "immutableRootFence",
121
+ "providerReadbackBeforeRetry",
122
+ "idempotentReplay",
123
+ ]) {
124
+ if (capability.invariants?.[field] !== true)
125
+ issues.push(`${capability.id}: ${field} must remain true`);
126
+ }
127
+ if (capability.routeKind !== "v4-native")
128
+ issues.push(`${capability.id}: routeKind must be v4-native`);
129
+ if (capability.legacyFallbackAllowed !== false)
130
+ issues.push(`${capability.id}: legacy fallback must remain disabled`);
131
+ if (capability.providerDecisionAuthorityAllowed !== false)
132
+ issues.push(
133
+ `${capability.id}: provider decision authority must remain disabled`,
134
+ );
135
+ if (stateMachine) {
136
+ if (capability.stateAuthority !== "single-writer-state-machine")
137
+ issues.push(
138
+ `${capability.id}: state-machine authority classification drifted`,
139
+ );
140
+ if (
141
+ stateMachine.writer?.authoritative !== true ||
142
+ stateMachine.writer?.secondWriterBudget !== 0
143
+ )
144
+ issues.push(`${capability.id}: v4 state machine is not single-writer`);
145
+ if (
146
+ !Array.isArray(stateMachine.recovery) ||
147
+ stateMachine.recovery.length === 0
148
+ )
149
+ issues.push(`${capability.id}: v4 state machine recovery is empty`);
150
+ } else if (capability.stateAuthority !== "bounded-protocol") {
151
+ issues.push(
152
+ `${capability.id}: bounded protocol authority classification drifted`,
153
+ );
154
+ }
155
+ }
156
+
157
+ export function validateRuntimeSemanticClosure({
158
+ root = process.cwd(),
159
+ manifest = loadJson(root, V4_RUNTIME_SEMANTIC_CLOSURE_PATH),
160
+ v3Inventory = loadJson(root, "architecture/v3-core-mechanism-inventory.json"),
161
+ v4Manifest = loadJson(
162
+ root,
163
+ "architecture/v4-capability-state-machine-manifest.json",
164
+ ),
165
+ } = {}) {
166
+ const issues = [];
167
+ if (
168
+ manifest?.schemaVersion !== 1 ||
169
+ manifest?.contract !== V4_RUNTIME_SEMANTIC_CLOSURE_CONTRACT
170
+ )
171
+ issues.push(
172
+ "runtime semantic closure contract or schemaVersion is invalid",
173
+ );
174
+ if (!sameMembers(manifest?.requiredEvidence || [], REQUIRED_RUNTIME_EVIDENCE))
175
+ issues.push("runtime semantic closure evidence dimensions drifted");
176
+
177
+ const mechanisms = new Map(
178
+ (v3Inventory.mechanisms || []).map((entry) => [entry.id, entry]),
179
+ );
180
+ const v4Capabilities = new Map(
181
+ (v4Manifest.capabilities || []).map((entry) => [entry.id, entry]),
182
+ );
183
+ const stateMachines = new Map(
184
+ (v4Manifest.stateMachines || []).map((entry) => [entry.id, entry]),
185
+ );
186
+ const capabilities = Array.isArray(manifest?.capabilities)
187
+ ? manifest.capabilities
188
+ : [];
189
+ if (
190
+ !sameMembers(
191
+ capabilities.map((entry) => entry.id),
192
+ mechanisms.keys(),
193
+ )
194
+ )
195
+ issues.push(
196
+ "runtime semantic closure does not cover every v3 mechanism exactly once",
197
+ );
198
+ if (
199
+ !sameMembers(
200
+ capabilities.map((entry) => entry.id),
201
+ v4Capabilities.keys(),
202
+ )
203
+ )
204
+ issues.push(
205
+ "runtime semantic closure does not match the v4 capability manifest",
206
+ );
207
+
208
+ const surfaces = publicSurfaceIds(root);
209
+ const ids = new Set();
210
+ for (const capability of capabilities) {
211
+ if (!capability?.id || ids.has(capability.id)) {
212
+ issues.push(
213
+ `runtime semantic capability id is missing or duplicated: ${capability?.id || "<empty>"}`,
214
+ );
215
+ continue;
216
+ }
217
+ ids.add(capability.id);
218
+ const mechanism = mechanisms.get(capability.id);
219
+ const v4Capability = v4Capabilities.get(capability.id);
220
+ if (!mechanism || !v4Capability) continue;
221
+ if (capability.sourceMechanismId !== capability.id)
222
+ issues.push(`${capability.id}: source mechanism identity drifted`);
223
+ validateImplementation(root, mechanism, capability, issues);
224
+ validateResidualClosure(mechanism, capability, issues);
225
+ validateEvidence(root, mechanism, capability, issues);
226
+ validateAuthority(capability, stateMachines.get(capability.id), issues);
227
+ for (const surface of v4Capability.surfaces || []) {
228
+ if (!surfaces.has(surface))
229
+ issues.push(
230
+ `${capability.id}: executable v4 surface is absent: ${surface}`,
231
+ );
232
+ }
233
+ }
234
+
235
+ if (issues.length)
236
+ throw new Error(
237
+ `v4 runtime semantic closure failed:\n- ${issues.join("\n- ")}`,
238
+ );
239
+ return {
240
+ capabilities: capabilities.length,
241
+ evidenceDimensions: capabilities.length * REQUIRED_RUNTIME_EVIDENCE.length,
242
+ residualsClosed: capabilities.reduce(
243
+ (sum, entry) => sum + entry.resolvedResiduals.length,
244
+ 0,
245
+ ),
246
+ legacyFallbacks: 0,
247
+ providerDecisionAuthorities: 0,
248
+ };
249
+ }
250
+
251
+ if (
252
+ process.argv[1] &&
253
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
254
+ ) {
255
+ try {
256
+ const report = validateRuntimeSemanticClosure();
257
+ console.log(
258
+ `v4 runtime semantic closure passed: ${report.capabilities} capabilities, ` +
259
+ `${report.evidenceDimensions} evidence dimensions, ${report.residualsClosed} reverse-scan residuals closed, ` +
260
+ "0 legacy fallbacks, 0 provider decision authorities",
261
+ );
262
+ } catch (error) {
263
+ console.error(error instanceof Error ? error.message : String(error));
264
+ process.exitCode = 1;
265
+ }
266
+ }
@@ -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,
@@ -7,6 +7,32 @@ export function hasFlag(args, name) {
7
7
  return args.includes(`--${name}`);
8
8
  }
9
9
 
10
+ function terminalEvidenceOptions(rest, environment) {
11
+ return {
12
+ outcome: flag(rest, "outcome", environment.BUILDCHAIN_DEV_DELIVERY_OUTCOME),
13
+ eventAction: flag(
14
+ rest,
15
+ "event-action",
16
+ environment.BUILDCHAIN_DEV_DELIVERY_EVENT_ACTION,
17
+ ),
18
+ evidenceRoot: flag(
19
+ rest,
20
+ "evidence-root",
21
+ environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT,
22
+ ),
23
+ expectedPriorEvidenceRoot: flag(
24
+ rest,
25
+ "expected-prior-evidence-root",
26
+ environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_PRIOR_EVIDENCE_ROOT,
27
+ ),
28
+ integrationProofPath: flag(
29
+ rest,
30
+ "integration-proof",
31
+ environment.BUILDCHAIN_DEV_DELIVERY_INTEGRATION_PROOF,
32
+ ),
33
+ };
34
+ }
35
+
10
36
  export function devDeliveryCliOptions(args = [], environment = process.env) {
11
37
  const [command = "", ...rest] = args;
12
38
  return {
@@ -196,17 +222,7 @@ export function devDeliveryCliOptions(args = [], environment = process.env) {
196
222
  "lease-seconds",
197
223
  environment.BUILDCHAIN_DEV_DELIVERY_LEASE_SECONDS,
198
224
  ),
199
- outcome: flag(rest, "outcome", environment.BUILDCHAIN_DEV_DELIVERY_OUTCOME),
200
- eventAction: flag(
201
- rest,
202
- "event-action",
203
- environment.BUILDCHAIN_DEV_DELIVERY_EVENT_ACTION,
204
- ),
205
- evidenceRoot: flag(
206
- rest,
207
- "evidence-root",
208
- environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT,
209
- ),
225
+ ...terminalEvidenceOptions(rest, environment),
210
226
  transferRoot: flag(rest, "transfer-root"),
211
227
  finalizerBoundaryRoot: flag(rest, "finalizer-boundary-root"),
212
228
  nativeJobId: flag(rest, "native-job-id"),
@@ -46,7 +46,7 @@ function decodeBlob(blob) {
46
46
  ).toString("utf8");
47
47
  }
48
48
 
49
- function validateStoredState(queue) {
49
+ function validateStoredState(queue, { allowLegacyV3Readback = false } = {}) {
50
50
  const body = structuredClone(queue || {});
51
51
  const embeddedStateRoot = body.stateRoot;
52
52
  delete body.stateRoot;
@@ -59,7 +59,7 @@ function validateStoredState(queue) {
59
59
  ) {
60
60
  normalizeDevDeliveryAuthorityState(queue);
61
61
  } else {
62
- normalizeDevDeliveryQueue(queue);
62
+ normalizeDevDeliveryQueue(queue, { allowLegacyV3Readback });
63
63
  }
64
64
  return queue;
65
65
  }
@@ -108,7 +108,7 @@ export class GitHubDevDeliveryStore {
108
108
  return data;
109
109
  }
110
110
 
111
- async read({ stateRef, protectedBase, now }) {
111
+ async read({ stateRef, protectedBase, now, allowLegacyV3Readback = false }) {
112
112
  let ref;
113
113
  try {
114
114
  ref = await this.request(
@@ -128,11 +128,13 @@ export class GitHubDevDeliveryStore {
128
128
  };
129
129
  }
130
130
  const commitSha = exactSha(ref?.object?.sha, "state ref commit");
131
- const readback = await this.readCommit(commitSha);
131
+ const readback = await this.readCommit(commitSha, {
132
+ allowLegacyV3Readback,
133
+ });
132
134
  return { exists: true, ...readback };
133
135
  }
134
136
 
135
- async readCommit(commitShaInput) {
137
+ async readCommit(commitShaInput, { allowLegacyV3Readback = false } = {}) {
136
138
  const commitSha = exactSha(commitShaInput, "state commit");
137
139
  const commit = await this.request(
138
140
  "GET",
@@ -152,7 +154,9 @@ export class GitHubDevDeliveryStore {
152
154
  `/repos/${this.repository.fullName}/git/blobs/${entry.sha}`,
153
155
  );
154
156
  const bytes = decodeBlob(blob);
155
- const queue = validateStoredState(JSON.parse(bytes));
157
+ const queue = validateStoredState(JSON.parse(bytes), {
158
+ allowLegacyV3Readback,
159
+ });
156
160
  return { commitSha, queue, bytes };
157
161
  }
158
162