@kungfu-tech/buildchain 4.0.1-alpha.3 → 4.0.1-alpha.31

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 (171) 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/internal-capabilities.json +117 -8
  5. package/architecture/maintainability-policy.json +43 -25
  6. package/architecture/release-tail-contract-inventory.json +5 -2
  7. package/architecture/v3-core-mechanism-inventory.json +89 -0
  8. package/architecture/v4-adopter-delivery-parity.json +32 -5
  9. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  10. package/architecture/v4-compatibility-facts-parity.json +77 -0
  11. package/architecture/v4-delivery-warrant-shadow-fixtures.json +15 -6
  12. package/architecture/v4-floating-consumer-policy.json +6 -0
  13. package/architecture/v4-next-development-parity.json +192 -0
  14. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  15. package/architecture/v4-release-train-parity.json +1 -0
  16. package/architecture/v4-tail-reseal-parity.json +150 -0
  17. package/bin/buildchain.mjs +23 -2
  18. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  19. package/bin/internal/command-registry.mjs +3 -0
  20. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  21. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  22. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  23. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -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-compatibility-facts-v1/shared.json +1129 -0
  30. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  31. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  32. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  33. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  38. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  39. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  40. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  41. package/contracts/next-development-request-v1.schema.json +66 -0
  42. package/contracts/next-development-transition-v1.schema.json +292 -0
  43. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  44. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  45. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  46. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  47. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  48. package/dist/site/agent-index.json +4 -0
  49. package/dist/site/artifact-schemas.json +8 -0
  50. package/dist/site/buildchain-contract.json +1393 -28
  51. package/dist/site/buildchain-site.json +477 -32
  52. package/dist/site/capability-registry.json +17 -14
  53. package/dist/site/cli-registry.json +159 -1
  54. package/dist/site/kfd-claims.json +455 -15
  55. package/dist/site/kfd-upstream-aggregate.json +1 -1
  56. package/dist/site/manual-registry.json +53 -7
  57. package/dist/site/node-api-registry.json +13921 -9565
  58. package/dist/site/page-registry.json +442 -21
  59. package/dist/site/public-surface-audit.json +440 -11
  60. package/dist/site/publication-authority-registry.json +44 -8
  61. package/dist/site/publication-registry.json +4 -4
  62. package/dist/site/release-provenance.json +22 -0
  63. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  64. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  65. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  66. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  67. package/dist/site/site-manifest.json +35 -11
  68. package/dist/site/workflow-registry.json +142 -8
  69. package/docs/MAP.md +7 -2
  70. package/docs/cli-reference.md +193 -0
  71. package/docs/cli.md +8 -0
  72. package/docs/dev-alpha-candidate-patrol.md +8 -0
  73. package/docs/dev-delivery-warrant.md +30 -5
  74. package/docs/next-development-transition.md +118 -0
  75. package/docs/node-api-reference.md +578 -285
  76. package/docs/release-candidate.md +7 -0
  77. package/docs/release-tail-provider-plane.md +30 -0
  78. package/docs/v4-adopter-delivery.md +133 -0
  79. package/docs/v4-compatibility-facts.md +91 -0
  80. package/docs/v4-publication-rehearsal.md +130 -0
  81. package/docs/v4-tail-reseal.md +73 -0
  82. package/docs/versioning.md +4 -3
  83. package/package.json +30 -6
  84. package/packages/core/adopter-delivery-passport.js +259 -0
  85. package/packages/core/adopter-delivery-vectors.js +158 -0
  86. package/packages/core/adopter-delivery-vectors.json +77 -0
  87. package/packages/core/buildchain-agent-manuals.js +3 -0
  88. package/packages/core/buildchain-compatibility-authority.js +336 -0
  89. package/packages/core/buildchain-compatibility-fact.js +394 -0
  90. package/packages/core/buildchain-compatibility-facts.json +217 -0
  91. package/packages/core/buildchain-compatibility-proof.js +566 -0
  92. package/packages/core/buildchain-config.js +84 -0
  93. package/packages/core/buildchain-contract.js +93 -141
  94. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  95. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  96. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  97. package/packages/core/buildchain-publication-authority.js +1 -0
  98. package/packages/core/channel-candidate.js +8 -0
  99. package/packages/core/channel-promotion-baseline.js +55 -0
  100. package/packages/core/ci-lane-change-budget.js +247 -0
  101. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  102. package/packages/core/dev-delivery-execution-transfer.js +21 -1
  103. package/packages/core/dev-delivery-native-proof.js +43 -27
  104. package/packages/core/dev-delivery-process-boundary.js +19 -7
  105. package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
  106. package/packages/core/dev-delivery-warrant-state.js +32 -10
  107. package/packages/core/dev-delivery-warrant.js +9 -3
  108. package/packages/core/github-governance-authority.js +1 -0
  109. package/packages/core/index.js +5 -0
  110. package/packages/core/kungfu-temporal-fact.js +557 -0
  111. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  112. package/packages/core/next-development-candidate-reservation.js +186 -0
  113. package/packages/core/next-development-controller.js +728 -0
  114. package/packages/core/next-development-projection.js +287 -0
  115. package/packages/core/next-development-transition.js +738 -0
  116. package/packages/core/paper-agent-entry.js +11 -5
  117. package/packages/core/paper.js +16 -5
  118. package/packages/core/published-delivery-authority.js +266 -0
  119. package/packages/core/release-candidate-recovery.js +70 -15
  120. package/packages/core/release-candidate.js +2 -2
  121. package/packages/core/release-passport.js +36 -9
  122. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  123. package/packages/core/v4-adopter-delivery.js +359 -0
  124. package/packages/core/v4-canonical-contracts.js +5 -0
  125. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  126. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  127. package/packages/core/v4-floating-consumer-policy.js +143 -20
  128. package/packages/core/v4-floating-consumer-release-passport.js +35 -8
  129. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  130. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  131. package/packages/core/v4-publication-rehearsal.js +481 -0
  132. package/packages/core/v4-tail-reseal-contract.js +26 -0
  133. package/packages/core/v4-tail-reseal-github.js +178 -0
  134. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  135. package/packages/core/v4-tail-reseal.js +594 -0
  136. package/packages/core/workflow-yaml-contract.js +24 -2
  137. package/scripts/artifact-signing-controller-core.mjs +1 -1
  138. package/scripts/buildchain-cli-help.mjs +30 -0
  139. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  140. package/scripts/check-inventory.mjs +14 -3
  141. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  142. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  143. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  144. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  145. package/scripts/dev-delivery-warrant-store.mjs +10 -6
  146. package/scripts/dev-delivery-warrant.mjs +5 -1
  147. package/scripts/dev-pr-auto-merge.mjs +1 -1
  148. package/scripts/dev-pr-delivery-warrant.mjs +1 -0
  149. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  150. package/scripts/dev-qualification-patrol.mjs +9 -9
  151. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  152. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  153. package/scripts/generate-next-development-guidance.mjs +49 -0
  154. package/scripts/generate-release-candidate-passport.mjs +152 -0
  155. package/scripts/generate-site-bundle.mjs +46 -3
  156. package/scripts/init-repo.mjs +22 -4
  157. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  158. package/scripts/next-development-self-dogfood.mjs +597 -0
  159. package/scripts/next-development-transition.mjs +47 -0
  160. package/scripts/promotion-channel-router.mjs +4 -4
  161. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  162. package/scripts/release-tail.mjs +51 -3
  163. package/scripts/release-train-self-dogfood.mjs +94 -92
  164. package/scripts/resume-from-candidate-run.mjs +560 -30
  165. package/scripts/site-capability-metadata.mjs +29 -0
  166. package/scripts/stable-candidate-qualification.mjs +9 -10
  167. package/scripts/v4-consumer-policy.mjs +5 -5
  168. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  169. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  170. package/scripts/v4-tail-reseal.mjs +416 -0
  171. package/templates/native-dev-delivery.yml +2 -0
@@ -8,6 +8,11 @@ export const BUILDCHAIN_USAGE = `Usage:
8
8
  buildchain architecture qualify --authority-revision <git-revision>
9
9
  [--candidate-revision <git-revision>]
10
10
  [--cwd <dir>] [--json]
11
+ buildchain adopter-delivery run --input <json-or-path> [--output <path>]
12
+ buildchain adopter-delivery verify --input <json-or-path>
13
+ --readback <json-or-path> [--output <path>]
14
+ buildchain adopter-delivery bootstrap --input <json-or-path> [--output <path>]
15
+ buildchain adopter-delivery archive --input <json-or-path> [--output <path>]
11
16
  buildchain portable-cache plan --manifest <file-or-json> [--output <file>]
12
17
  [--github-output <file>] [--json]
13
18
  buildchain portable-cache receipt --plan <file-or-json> [--matched-key <key>]
@@ -44,6 +49,23 @@ export const BUILDCHAIN_USAGE = `Usage:
44
49
  buildchain release-tail init --declaration <json-or-path> [--state <path>]
45
50
  buildchain release-tail <status|verify> [--state <path>] [--output <path>]
46
51
  buildchain release-tail compat --hooks-json <json-or-path> [--output <path>]
52
+ buildchain release-tail rehearse --capsule <absolute-path>
53
+ --candidate-root <absolute-path>
54
+ --mode <simulate|replay>
55
+ --state <absolute-path>
56
+ --evidence <absolute-path>
57
+ buildchain tail-reseal plan --request <request.json> [--output <plan.json>]
58
+ buildchain tail-reseal admit --request <request.json> [--output <admission.json>]
59
+ buildchain tail-reseal verify-platform --request <request.json> --platform <id>
60
+ [--artifact-root <dir>] [--mode retained|resealed]
61
+ [--provider-readback-root <sha256:...>]
62
+ [--output <readback.json>]
63
+ buildchain tail-reseal seal --request <request.json> --plan <plan.json>
64
+ --readbacks <dir> --passport <passport.json>
65
+ --protected-readback-root <sha256:...>
66
+ [--output <receipt.json>]
67
+ buildchain next-development materialize --input <request.json> [--cwd <dir>]
68
+ [--write] [--output <path>] [--json]
47
69
  buildchain github-governance <plan|apply|rollback|protection-policy-plan|ruleset-policy-plan> ...
48
70
  buildchain release <inspect|recover|finalize|abort> ...
49
71
  buildchain transaction inspect ...
@@ -154,6 +176,14 @@ export const BUILDCHAIN_USAGE = `Usage:
154
176
  [--module-fact <file>]... [--artifact <path>]...
155
177
  [--output <file>] [--json]
156
178
  buildchain facts verify [--cwd <dir>] --fact <file> [--json]
179
+ buildchain facts compatibility project [--cwd <dir>] [--registry <json-or-path>]
180
+ [--output <file>] [--json]
181
+ buildchain facts compatibility verify [--cwd <dir>] [--registry <json-or-path>] [--json]
182
+ buildchain facts compatibility query --query <json-or-path>
183
+ [--registry <json-or-path>] [--cwd <dir>] [--json]
184
+ buildchain facts compatibility query-template --fact-root <sha256:...>
185
+ [--query-id <id>] [--registry <json-or-path>]
186
+ [--cwd <dir>] [--json]
157
187
  buildchain kfd ...
158
188
  buildchain kfd hub <init|inspect|test|explain> [--cwd <dir>]
159
189
  [--declaration <path>] [--output-dir <path>]
@@ -0,0 +1,100 @@
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 process from "node:process";
6
+ import { execFileSync } from "node:child_process";
7
+ import { evaluateCiLaneChangeBudget } from "../packages/core/ci-lane-change-budget.js";
8
+
9
+ const root = process.cwd();
10
+ const workflowRoot = path.join(root, ".github", "workflows");
11
+ const policyPath = path.join(
12
+ root,
13
+ "architecture",
14
+ "ci-lane-change-budget.json",
15
+ );
16
+
17
+ const workflows = fs
18
+ .readdirSync(workflowRoot, { withFileTypes: true })
19
+ .filter((entry) => entry.isFile() && /\.(?:yaml|yml)$/u.test(entry.name))
20
+ .map((entry) => {
21
+ const workflowPath = path.posix.join(".github/workflows", entry.name);
22
+ return {
23
+ path: workflowPath,
24
+ text: fs.readFileSync(path.join(workflowRoot, entry.name), "utf8"),
25
+ };
26
+ })
27
+ .sort((left, right) => left.path.localeCompare(right.path));
28
+
29
+ const policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
30
+ const baselineRevision = String(policy.baseline?.gitRevision || "").trim();
31
+ if (!/^[0-9a-f]{40}$/u.test(baselineRevision)) {
32
+ throw new Error("baseline.gitRevision must be an exact Git commit");
33
+ }
34
+ const embeddedBaselineLanes = policy.baseline?.lanes;
35
+ if (
36
+ !Array.isArray(embeddedBaselineLanes) ||
37
+ embeddedBaselineLanes.length === 0
38
+ ) {
39
+ throw new Error("baseline.lanes must contain the exact offline lane set");
40
+ }
41
+ const embeddedLaneSetRoot = `sha256:${crypto
42
+ .createHash("sha256")
43
+ .update(`${JSON.stringify(embeddedBaselineLanes)}\n`)
44
+ .digest("hex")}`;
45
+ if (embeddedLaneSetRoot !== policy.baseline?.laneSetRoot) {
46
+ throw new Error("baseline laneSetRoot does not match baseline.lanes");
47
+ }
48
+
49
+ let baselineWorkflows = null;
50
+ try {
51
+ execFileSync("git", ["cat-file", "-e", `${baselineRevision}^{commit}`], {
52
+ cwd: root,
53
+ stdio: "ignore",
54
+ });
55
+ const baselinePaths = execFileSync(
56
+ "git",
57
+ [
58
+ "ls-tree",
59
+ "-r",
60
+ "--name-only",
61
+ baselineRevision,
62
+ "--",
63
+ ".github/workflows",
64
+ ],
65
+ { cwd: root, encoding: "utf8" },
66
+ )
67
+ .split(/\r?\n/u)
68
+ .filter((value) => /\.(?:yaml|yml)$/u.test(value))
69
+ .sort();
70
+ baselineWorkflows = baselinePaths.map((workflowPath) => ({
71
+ path: workflowPath,
72
+ text: execFileSync("git", ["show", `${baselineRevision}:${workflowPath}`], {
73
+ cwd: root,
74
+ encoding: "utf8",
75
+ }),
76
+ }));
77
+ } catch {
78
+ // Shallow and exported source cuts may not contain the pinned commit object.
79
+ // The content-rooted embedded lane set remains the exact offline authority.
80
+ }
81
+
82
+ const embeddedEvaluation = evaluateCiLaneChangeBudget({
83
+ policy,
84
+ workflows,
85
+ });
86
+ const evaluation = baselineWorkflows
87
+ ? evaluateCiLaneChangeBudget({ policy, workflows, baselineWorkflows })
88
+ : embeddedEvaluation;
89
+ if (
90
+ baselineWorkflows &&
91
+ (evaluation.baselineLaneCount !== embeddedEvaluation.baselineLaneCount ||
92
+ JSON.stringify(evaluation.newLanes) !==
93
+ JSON.stringify(embeddedEvaluation.newLanes))
94
+ ) {
95
+ throw new Error(
96
+ "embedded baseline lanes do not match the pinned baseline revision",
97
+ );
98
+ }
99
+ process.stdout.write(`${JSON.stringify(evaluation, null, 2)}\n`);
100
+ if (!evaluation.ok) process.exitCode = 1;
@@ -54,6 +54,10 @@ const requiredPaths = [
54
54
  "packages/core/homebrew.js",
55
55
  "packages/core/artifact-verification-envelope.js",
56
56
  "packages/core/anchored-version-material.js",
57
+ "packages/core/next-development-transition.js",
58
+ "packages/core/next-development-controller.js",
59
+ "packages/core/next-development-candidate-reservation.js",
60
+ "packages/core/next-development-projection.js",
57
61
  "packages/core/build-facts.js",
58
62
  "packages/core/publication-package.js",
59
63
  "packages/core/publication-reproducibility.js",
@@ -76,6 +80,8 @@ const requiredPaths = [
76
80
  "docs/auditable-demo.md",
77
81
  "contracts/auditable-demo-scenario-v1.schema.json",
78
82
  "contracts/release-candidate-recovery-v1.schema.json",
83
+ "contracts/next-development-request-v1.schema.json",
84
+ "contracts/next-development-transition-v1.schema.json",
79
85
  "contracts/auditable-demo-media-profiles-v1.json",
80
86
  "contracts/evidence/auditable-demo-web-delivery-v1.json",
81
87
  "contracts/evidence/auditable-demo-responsive-web-delivery-v1.json",
@@ -87,10 +93,15 @@ const requiredPaths = [
87
93
  "contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json",
88
94
  "contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json",
89
95
  "docs/release-propagation.md",
96
+ "docs/next-development-transition.md",
90
97
  "docs/site-bundle-contract.md",
91
98
  "docs/toolkit-observability.md",
92
99
  "docs/versioning.md",
93
100
  "scripts/release-line-dry-run.mjs",
101
+ "scripts/next-development-transition.mjs",
102
+ "scripts/next-development-self-dogfood.mjs",
103
+ "scripts/next-development-self-dogfood-harness.mjs",
104
+ "scripts/generate-next-development-guidance.mjs",
94
105
  "scripts/reconcile-release-governance.mjs",
95
106
  "scripts/buildchain-channel-router.mjs",
96
107
  "scripts/generate-channel-build-workflow.mjs",
@@ -1010,16 +1021,16 @@ for (const forbiddenSnippet of [
1010
1021
  for (const requiredSnippet of [
1011
1022
  "id-token: write",
1012
1023
  "actions: write",
1013
- "uses: ./.github/workflows/.release-candidate-promote.yml",
1024
+ "uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4-alpha",
1014
1025
  "github.event.workflow_run.event == 'push'",
1015
1026
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): prepare v')",
1016
1027
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): release v')",
1017
1028
  "resume-candidate-run-id:",
1018
1029
  "resume-expected-source-tree:",
1019
1030
  "resume-buildchain-runtime-sha:",
1020
- "github-release: true",
1031
+ "uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4",
1021
1032
  "release-passport-buildchain-self-kfd: true",
1022
- "artifact-patterns: ${{ startsWith(github.event.workflow_run.head_branch || inputs['target-ref'], 'alpha/') && 'buildchain-package-*' || '' }}",
1033
+ "buildchain-channel: alpha",
1023
1034
  "release-passport-impact-json: .buildchain/release-impact.json",
1024
1035
  ]) {
1025
1036
  if (!buildchainRefPromotionWorkflow.includes(requiredSnippet)) {
@@ -6,7 +6,6 @@ import { parseYamlUses } from "../packages/core/workflow-yaml-contract.js";
6
6
 
7
7
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
8
  const read = (relative) => fs.readFileSync(path.join(root, relative), "utf8");
9
-
10
9
  function fail(message) {
11
10
  throw new Error(`v4-floating-consumer-policy-contract: ${message}`);
12
11
  }
@@ -53,8 +52,13 @@ function assertOrdered(relative, markers) {
53
52
 
54
53
  export function assertPromotionCertificationWiring(source) {
55
54
  for (const marker of [
56
- ".buildchain/runtime/promotion-shell/scripts/v4-consumer-policy.mjs certify",
57
- '--caller-root "${{ github.workspace }}"',
55
+ "path: .buildchain/runtime/candidate-policy-runtime",
56
+ "BUILDCHAIN_EXPECTED_RUNTIME_SHA: ${{ steps.v4-policy-caller.outputs.runtime-sha }}",
57
+ "policy_runtime=.buildchain/runtime",
58
+ 'node "${policy_runtime}/scripts/v4-consumer-policy.mjs" certify',
59
+ "passport.consumerPolicy?.receipt?.caller?.sourceSha",
60
+ "path: .buildchain/runtime/policy-caller",
61
+ '--caller-root "${{ github.workspace }}/.buildchain/runtime/policy-caller"',
58
62
  '--stable-lock "${{ inputs.buildchain-stable-contract-lock-path }}"',
59
63
  '--alpha-lock "${{ inputs.buildchain-alpha-contract-lock-path }}"',
60
64
  "release-passport-v4-consumer-policy-certification-root: ${{ steps.v4-policy-certification.outputs.v4-consumer-policy-certification-root }}",
@@ -79,13 +83,22 @@ function assertPersistedSelectors() {
79
83
  );
80
84
  if (!match) continue;
81
85
  const selector = match[2];
86
+ const protectedBootstrap =
87
+ relative ===
88
+ ".github/workflows/buildchain-ref-promotion-recovery.yml" &&
89
+ match[1] === ".github/workflows/.release-candidate-promote.yml" &&
90
+ selector === "alpha/v4/v4.0";
82
91
  const isV4 =
83
92
  selector === "v4" ||
84
93
  selector === "v4-alpha" ||
85
94
  /^v4(?:[./-]|$)/u.test(selector) ||
86
95
  /^[0-9a-f]{40}$/iu.test(selector) ||
87
96
  selector.includes("${{");
88
- if (isV4 && !["v4", "v4-alpha"].includes(selector)) {
97
+ if (
98
+ (isV4 || protectedBootstrap) &&
99
+ !["v4", "v4-alpha"].includes(selector) &&
100
+ !protectedBootstrap
101
+ ) {
89
102
  offenders.push(`${relative}:${node.line} @${selector}`);
90
103
  }
91
104
  }
@@ -113,6 +126,12 @@ export function checkV4FloatingConsumerPolicyContract() {
113
126
  "Enforce v4 floating consumer policy",
114
127
  "Validate consumer package manager contract",
115
128
  ]);
129
+ if (
130
+ !read(".github/workflows/.build.yml").includes(
131
+ "BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: ${{ inputs.buildchain-expected-channel }}",
132
+ )
133
+ )
134
+ fail("channel builds must disambiguate dual-channel caller invocations");
116
135
  assertTrustGatedJobs(read(".github/workflows/.build.yml"), [
117
136
  "resolve-source",
118
137
  "resolve-contract",
@@ -20,6 +20,7 @@ import {
20
20
  candidateFromDecision,
21
21
  selectLatestQualifiedSource,
22
22
  } from "../packages/core/dev-alpha-candidate-selection.js";
23
+ import { readGitHubNextDevelopmentVersionReservation } from "../packages/core/next-development-candidate-reservation.js";
23
24
 
24
25
  export { reconcileActiveReleaseTrain, selectLatestQualifiedSource };
25
26
 
@@ -617,6 +618,7 @@ export async function runDevAlphaCandidatePatrol(
617
618
  comparison,
618
619
  workflowEvidence,
619
620
  skippedNewerCommitCount,
621
+ versionReservation,
620
622
  qualificationError,
621
623
  } = await resolvePatrolSourceInputs({
622
624
  client,
@@ -646,6 +648,7 @@ export async function runDevAlphaCandidatePatrol(
646
648
  observedSourceHeadSha,
647
649
  skippedNewerCommitCount,
648
650
  ...(targetBaseline ? { targetBaseline } : {}),
651
+ ...(versionReservation ? { versionReservation } : {}),
649
652
  },
650
653
  workflowEvidence,
651
654
  requiredWorkflowPaths,
@@ -992,6 +995,20 @@ export function createGitHubChannelCandidateClient({
992
995
  targetBranch,
993
996
  parseCandidate: managedCandidateFromPullRequest,
994
997
  }),
998
+ async readNextDevelopmentVersionReservation({
999
+ reservationSha,
1000
+ candidateSha,
1001
+ targetVersion,
1002
+ }) {
1003
+ return readGitHubNextDevelopmentVersionReservation({
1004
+ api,
1005
+ owner,
1006
+ repo,
1007
+ reservationSha,
1008
+ candidateSha,
1009
+ targetVersion,
1010
+ });
1011
+ },
995
1012
  async listCompletedWorkflowRuns(workflowPathValue, sourceBranch) {
996
1013
  const runs = [];
997
1014
  for (let page = 1; page <= 10; page += 1) {
@@ -1015,7 +1032,11 @@ export function createGitHubChannelCandidateClient({
1015
1032
  for (const commit of rows) {
1016
1033
  const commitSha = text(commit.sha);
1017
1034
  if (commitSha === targetSha) return commits;
1018
- commits.push(commitSha);
1035
+ commits.push({
1036
+ sha: commitSha,
1037
+ message: text(commit.commit?.message),
1038
+ parents: (commit.parents || []).map((parent) => text(parent.sha)),
1039
+ });
1019
1040
  }
1020
1041
  if (rows.length < 100) return commits;
1021
1042
  }
@@ -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,