@kungfu-tech/buildchain 3.0.5-alpha.8 → 3.0.6-alpha.0

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 (51) hide show
  1. package/contracts/auditable-demo-scenario-v1.schema.json +11 -0
  2. package/dist/site/buildchain-contract.json +42 -22
  3. package/dist/site/buildchain-site.json +73 -27
  4. package/dist/site/capability-registry.json +2 -2
  5. package/dist/site/cli-registry.json +5 -3
  6. package/dist/site/controller-registry.json +19 -3
  7. package/dist/site/kfd-claims.json +58 -14
  8. package/dist/site/kfd-upstream-aggregate.json +1 -1
  9. package/dist/site/manual-registry.json +7 -6
  10. package/dist/site/node-api-registry.json +335 -8
  11. package/dist/site/page-registry.json +63 -17
  12. package/dist/site/public-surface-audit.json +94 -11
  13. package/dist/site/publication-authority-registry.json +21 -1
  14. package/dist/site/publication-registry.json +4 -4
  15. package/dist/site/site-manifest.json +10 -10
  16. package/dist/site/workflow-registry.json +53 -4
  17. package/docs/MAP.md +4 -1
  18. package/docs/auditable-demo.md +19 -3
  19. package/docs/cli-reference.md +15 -4
  20. package/docs/dev-alpha-candidate-patrol.md +14 -5
  21. package/docs/dev-qualification-patrol.md +108 -0
  22. package/docs/node-api-reference.md +42 -9
  23. package/docs/release-propagation.md +93 -0
  24. package/docs/reusable-build-surface.md +44 -12
  25. package/docs/stable-candidate-patrol.md +5 -2
  26. package/package.json +1 -1
  27. package/packages/core/buildchain-publication-authority.js +1 -0
  28. package/packages/core/release-propagation-agent-entry.js +185 -0
  29. package/packages/core/release-propagation-push.js +293 -0
  30. package/packages/core/release-propagation-stage-evidence.js +36 -0
  31. package/packages/core/release-propagation-work.js +1 -1
  32. package/packages/core/release-propagation.js +15 -0
  33. package/scripts/artifact-signing-controller-core.mjs +525 -0
  34. package/scripts/artifact-signing-controller.mjs +302 -0
  35. package/scripts/artifact-signing-delegation.mjs +51 -3
  36. package/scripts/auditable-demo-platform.mjs +59 -1
  37. package/scripts/auditable-demo-transport-smoke.mjs +176 -0
  38. package/scripts/build-standalone-binary.mjs +3 -1
  39. package/scripts/buildchain-cli-help.mjs +1 -1
  40. package/scripts/check-inventory.mjs +1 -0
  41. package/scripts/compiler-cache-evidence.mjs +90 -7
  42. package/scripts/dev-alpha-candidate-patrol.mjs +80 -5
  43. package/scripts/dev-pr-auto-merge.mjs +4 -4
  44. package/scripts/dev-qualification-patrol.mjs +594 -0
  45. package/scripts/dispatch-artifact-signing-authority.mjs +312 -61
  46. package/scripts/generate-site-bundle.mjs +3 -0
  47. package/scripts/inspect-artifact-signing-requests.mjs +12 -0
  48. package/scripts/release-propagation.mjs +73 -0
  49. package/scripts/run-lifecycle-core.mjs +25 -0
  50. package/scripts/site-capability-metadata.mjs +1 -1
  51. package/scripts/stable-candidate-patrol.mjs +30 -4
@@ -0,0 +1,293 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import {
3
+ assertPlainObject,
4
+ assertString,
5
+ sha256Json,
6
+ } from "./release-propagation-common.js";
7
+ import {
8
+ assertCommitSha,
9
+ assertContentRoot,
10
+ contentRoot,
11
+ } from "./release-propagation-work-control.js";
12
+ import { verifyReleasePropagationWork } from "./release-propagation-work.js";
13
+
14
+ export const RELEASE_PROPAGATION_PUSH_PLAN_CONTRACT =
15
+ "kungfu-buildchain-release-propagation-push-plan";
16
+ export const RELEASE_PROPAGATION_PUSH_RESULT_CONTRACT =
17
+ "kungfu-buildchain-release-propagation-push-result";
18
+
19
+ function branchName(value, label) {
20
+ const branch = assertString(value, label);
21
+ if (
22
+ branch.startsWith("-") ||
23
+ branch.startsWith("refs/") ||
24
+ branch.includes("..") ||
25
+ branch.includes("@{") ||
26
+ /[\s~^:?*[\\]/.test(branch) ||
27
+ branch.endsWith(".") ||
28
+ branch.endsWith("/") ||
29
+ branch.includes("//")
30
+ ) {
31
+ throw new Error(`${label} is not a safe Git branch name`);
32
+ }
33
+ return branch;
34
+ }
35
+
36
+ function remoteName(value) {
37
+ const name = assertString(value, "push repository state.remoteName");
38
+ if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-")) {
39
+ throw new Error("push repository state.remoteName is unsafe");
40
+ }
41
+ return name;
42
+ }
43
+
44
+ function optionalCommit(value, label) {
45
+ if (value === "") return "";
46
+ return assertCommitSha(value, label);
47
+ }
48
+
49
+ function normalizeRepositoryState(value) {
50
+ const state = assertPlainObject(value, "push repository state");
51
+ return {
52
+ repository: assertString(
53
+ state.repository,
54
+ "push repository state.repository",
55
+ ),
56
+ remoteName: remoteName(state.remoteName),
57
+ currentBranch: branchName(
58
+ state.currentBranch,
59
+ "push repository state.currentBranch",
60
+ ),
61
+ sourceRevision: assertCommitSha(
62
+ state.sourceRevision,
63
+ "push repository state.sourceRevision",
64
+ ),
65
+ remoteBaseRevision: assertCommitSha(
66
+ state.remoteBaseRevision,
67
+ "push repository state.remoteBaseRevision",
68
+ ),
69
+ remoteBranchRevision: optionalCommit(
70
+ state.remoteBranchRevision,
71
+ "push repository state.remoteBranchRevision",
72
+ ),
73
+ expectedBaseIsAncestor: state.expectedBaseIsAncestor === true,
74
+ };
75
+ }
76
+
77
+ export function createReleasePropagationPushPlan({
78
+ work: workInput,
79
+ expectedWorkRoot,
80
+ repositoryState: stateInput,
81
+ } = {}) {
82
+ const status = verifyReleasePropagationWork(workInput);
83
+ const expectedRoot = assertContentRoot(expectedWorkRoot, "expectedWorkRoot");
84
+ if (status.contentRoot !== expectedRoot) {
85
+ throw new Error("propagation work changed before push planning");
86
+ }
87
+ if (
88
+ status.lifecycle !== "ready" ||
89
+ status.currentStage !== "push-branch" ||
90
+ status.work.authority.mode !== "execute"
91
+ ) {
92
+ throw new Error("propagation work is not ready for the push-branch stage");
93
+ }
94
+ const state = normalizeRepositoryState(stateInput);
95
+ const expectedBranch = branchName(
96
+ status.work.downstream.branch,
97
+ "propagation work downstream.branch",
98
+ );
99
+ if (state.repository !== status.work.downstream.repository) {
100
+ throw new Error(
101
+ "push remote repository disagrees with the propagation target",
102
+ );
103
+ }
104
+ if (state.currentBranch !== expectedBranch) {
105
+ throw new Error(
106
+ "current branch disagrees with the exact propagation branch",
107
+ );
108
+ }
109
+ if (state.remoteBaseRevision !== status.work.downstream.expectedBaseSha) {
110
+ throw new Error("downstream base advanced after the propagation capture");
111
+ }
112
+ if (!state.expectedBaseIsAncestor) {
113
+ throw new Error(
114
+ "propagation branch is not based on the expected downstream base",
115
+ );
116
+ }
117
+ const destinationRef = `refs/heads/${expectedBranch}`;
118
+ const body = {
119
+ schemaVersion: 1,
120
+ contract: RELEASE_PROPAGATION_PUSH_PLAN_CONTRACT,
121
+ workId: status.workId,
122
+ expectedWorkRoot: expectedRoot,
123
+ repository: state.repository,
124
+ remoteName: state.remoteName,
125
+ sourceRef: "HEAD",
126
+ sourceRevision: state.sourceRevision,
127
+ destinationRef,
128
+ expectedBaseRevision: status.work.downstream.expectedBaseSha,
129
+ expectedOldRevision: state.remoteBranchRevision,
130
+ pushMode: "fast-forward-only-exact-refspec",
131
+ argv: ["push", "--porcelain", state.remoteName, `HEAD:${destinationRef}`],
132
+ };
133
+ return { ...body, planRoot: sha256Json(body) };
134
+ }
135
+
136
+ function git(cwd, args) {
137
+ return execFileSync("git", args, {
138
+ cwd,
139
+ encoding: "utf8",
140
+ maxBuffer: 16 * 1024 * 1024,
141
+ }).trim();
142
+ }
143
+
144
+ function repositoryFromRemoteUrl(value) {
145
+ const remoteUrl = String(value || "").trim();
146
+ if (!remoteUrl || /[?#]/.test(remoteUrl)) {
147
+ throw new Error("push remote URL must not contain query or fragment data");
148
+ }
149
+ const ssh = remoteUrl.match(
150
+ /^[^@\s]+@github\.com:([^/\s]+\/[^/\s]+?)(?:\.git)?$/,
151
+ );
152
+ if (ssh) return ssh[1];
153
+ const https = remoteUrl.match(
154
+ /^https:\/\/github\.com\/([^/\s]+\/[^/\s]+?)(?:\.git)?$/,
155
+ );
156
+ if (https) return https[1];
157
+ throw new Error("push remote must be an exact GitHub repository URL");
158
+ }
159
+
160
+ function lsRemote(cwd, remote, ref) {
161
+ const output = git(cwd, ["ls-remote", "--refs", remote, ref]);
162
+ if (!output) return "";
163
+ const rows = output.split("\n").filter(Boolean);
164
+ if (rows.length !== 1) {
165
+ throw new Error(`expected exactly one remote ref for ${ref}`);
166
+ }
167
+ const [revision, observedRef] = rows[0].split(/\s+/);
168
+ if (observedRef !== ref) {
169
+ throw new Error(`remote ref lookup disagrees with ${ref}`);
170
+ }
171
+ return assertCommitSha(revision, `remote ${ref}`);
172
+ }
173
+
174
+ export function inspectReleasePropagationPushState({
175
+ work: workInput,
176
+ cwd = process.cwd(),
177
+ remote = "origin",
178
+ } = {}) {
179
+ const status = verifyReleasePropagationWork(workInput);
180
+ const expectedBranch = branchName(
181
+ status.work.downstream.branch,
182
+ "propagation work downstream.branch",
183
+ );
184
+ const baseRef = branchName(
185
+ status.work.downstream.baseRef,
186
+ "propagation work downstream.baseRef",
187
+ );
188
+ const normalizedRemote = remoteName(remote);
189
+ const repository = repositoryFromRemoteUrl(
190
+ git(cwd, ["remote", "get-url", normalizedRemote]),
191
+ );
192
+ const sourceRevision = assertCommitSha(
193
+ git(cwd, ["rev-parse", "HEAD"]),
194
+ "push source revision",
195
+ );
196
+ const ancestor = spawnSync(
197
+ "git",
198
+ [
199
+ "merge-base",
200
+ "--is-ancestor",
201
+ status.work.downstream.expectedBaseSha,
202
+ sourceRevision,
203
+ ],
204
+ { cwd, encoding: "utf8" },
205
+ );
206
+ if (ancestor.error) throw ancestor.error;
207
+ if (ancestor.status !== 0) {
208
+ throw new Error(
209
+ "propagation branch is not based on the expected downstream base",
210
+ );
211
+ }
212
+ return {
213
+ repository,
214
+ remoteName: normalizedRemote,
215
+ currentBranch: git(cwd, ["branch", "--show-current"]),
216
+ sourceRevision,
217
+ remoteBaseRevision: lsRemote(
218
+ cwd,
219
+ normalizedRemote,
220
+ `refs/heads/${baseRef}`,
221
+ ),
222
+ remoteBranchRevision: lsRemote(
223
+ cwd,
224
+ normalizedRemote,
225
+ `refs/heads/${expectedBranch}`,
226
+ ),
227
+ expectedBaseIsAncestor: true,
228
+ };
229
+ }
230
+
231
+ export function executeReleasePropagationPush({
232
+ work,
233
+ expectedWorkRoot,
234
+ cwd = process.cwd(),
235
+ remote = "origin",
236
+ } = {}) {
237
+ const repositoryState = inspectReleasePropagationPushState({
238
+ work,
239
+ cwd,
240
+ remote,
241
+ });
242
+ const plan = createReleasePropagationPushPlan({
243
+ work,
244
+ expectedWorkRoot,
245
+ repositoryState,
246
+ });
247
+ let mutation = true;
248
+ let output = "";
249
+ if (plan.expectedOldRevision === plan.sourceRevision) {
250
+ mutation = false;
251
+ } else {
252
+ output = git(cwd, plan.argv);
253
+ }
254
+ const observedRevision = lsRemote(cwd, plan.remoteName, plan.destinationRef);
255
+ if (observedRevision !== plan.sourceRevision) {
256
+ throw new Error(
257
+ "remote branch readback disagrees with the pushed source revision",
258
+ );
259
+ }
260
+ const claims = {
261
+ provider: "git",
262
+ repository: plan.repository,
263
+ remoteName: plan.remoteName,
264
+ sourceRef: plan.sourceRef,
265
+ sourceRevision: plan.sourceRevision,
266
+ destinationRef: plan.destinationRef,
267
+ expectedBaseRevision: plan.expectedBaseRevision,
268
+ expectedOldRevision: plan.expectedOldRevision,
269
+ observedRevision,
270
+ pushMode: plan.pushMode,
271
+ argv: plan.argv,
272
+ mutation,
273
+ };
274
+ const evidence = {
275
+ kind: "git-branch-reconciliation",
276
+ root: contentRoot(claims),
277
+ locator: "buildchain://release-propagation/branch-reconciliation",
278
+ repository: plan.repository,
279
+ revision: observedRevision,
280
+ httpStatus: null,
281
+ bytes: Buffer.byteLength(output),
282
+ claims,
283
+ };
284
+ const body = {
285
+ schemaVersion: 1,
286
+ contract: RELEASE_PROPAGATION_PUSH_RESULT_CONTRACT,
287
+ status: mutation ? "pushed" : "already-current",
288
+ mutation,
289
+ plan,
290
+ evidence,
291
+ };
292
+ return { ...body, resultRoot: sha256Json(body) };
293
+ }
@@ -4,6 +4,7 @@ import {
4
4
  optionalString,
5
5
  } from "./release-propagation-common.js";
6
6
  import {
7
+ assertCommitSha,
7
8
  assertContentRoot,
8
9
  assertExactFields,
9
10
  assertIsoTimestamp,
@@ -107,6 +108,40 @@ function verifyIndependentReview(work, receipt) {
107
108
  }
108
109
  }
109
110
 
111
+ function verifyPushedBranch(work, receipt) {
112
+ const reconciliation = receipt.evidence.find((entry) => entry.kind === "git-branch-reconciliation");
113
+ const claims = exactClaims(reconciliation, [
114
+ "provider", "repository", "remoteName", "sourceRef", "sourceRevision",
115
+ "destinationRef", "expectedBaseRevision", "expectedOldRevision",
116
+ "observedRevision", "pushMode", "argv", "mutation",
117
+ ], "push-branch evidence claims");
118
+ if (claims.provider !== "git"
119
+ || claims.repository !== work.downstream.repository
120
+ || claims.sourceRef !== "HEAD"
121
+ || claims.destinationRef !== `refs/heads/${work.downstream.branch}`
122
+ || claims.expectedBaseRevision !== work.downstream.expectedBaseSha
123
+ || claims.pushMode !== "fast-forward-only-exact-refspec") {
124
+ throw new Error("push-branch evidence does not bind the exact propagation target");
125
+ }
126
+ assertCommitSha(claims.sourceRevision, "push-branch source revision");
127
+ assertCommitSha(claims.observedRevision, "push-branch observed revision");
128
+ if (claims.expectedOldRevision !== "") {
129
+ assertCommitSha(claims.expectedOldRevision, "push-branch expected-old revision");
130
+ }
131
+ const expectedArgv = ["push", "--porcelain", claims.remoteName, `HEAD:${claims.destinationRef}`];
132
+ if (JSON.stringify(claims.argv) !== JSON.stringify(expectedArgv)
133
+ || claims.argv.some((value) => value === "--force" || value.startsWith("--force-"))) {
134
+ throw new Error("push-branch evidence must use the exact non-force refspec");
135
+ }
136
+ if (claims.sourceRevision !== claims.observedRevision
137
+ || reconciliation.revision !== claims.observedRevision) {
138
+ throw new Error("push-branch evidence readback disagrees with the source revision");
139
+ }
140
+ if (typeof claims.mutation !== "boolean") {
141
+ throw new Error("push-branch evidence mutation must be boolean");
142
+ }
143
+ }
144
+
110
145
  function verifyProductionDeployment(work, receipt) {
111
146
  const deployment = receipt.evidence.find((entry) => entry.kind === "production-deployment");
112
147
  const claims = exactClaims(deployment, [
@@ -237,6 +272,7 @@ export function validateStageEvidence({ work, receipt }) {
237
272
  ]).has(receipt.stage)) {
238
273
  for (const entry of receipt.evidence) assertHttpsLocator(entry.locator, receipt.stage);
239
274
  }
275
+ if (receipt.stage === "push-branch") verifyPushedBranch(work, receipt);
240
276
  if (receipt.stage === "independent-review") verifyIndependentReview(work, receipt);
241
277
  if (receipt.stage === "production-deploy") verifyProductionDeployment(work, receipt);
242
278
  if (receipt.stage === "online-readback") verifyOnlineReadback(work, receipt.evidence);
@@ -77,7 +77,7 @@ function workCommands(executionProfile = null, repository = "", branch = "", bas
77
77
  stages: {
78
78
  materialize: profile.updateCommand || "<consumer update command from the exact execution profile>",
79
79
  "verify-release": profile.verifyCommand || "<consumer verification command from the exact execution profile>",
80
- "push-branch": `git push --force-with-lease=<exact-expected-old> origin HEAD:refs/heads/${branch || "<managed-branch>"}`,
80
+ "push-branch": "buildchain release-propagation work push-branch --work <work.json> --expected-work-root <sha256:...> --cwd <downstream-worktree> --remote origin --execute --json",
81
81
  "pull-request": `gh pr create --repo ${repository || "<downstream-repository>"} --base ${baseRef || "<base-ref>"} --head ${branch || "<managed-branch>"}`,
82
82
  preview: `gh run list --repo ${repository || "<downstream-repository>"} --workflow ${workflow} --branch ${branch || "<managed-branch>"} --event pull_request --json databaseId,status,conclusion,url,headSha`,
83
83
  "independent-review": `gh pr review <pr-number> --repo ${repository || "<downstream-repository>"} --approve`,
@@ -486,3 +486,18 @@ export {
486
486
  normalizeManualUpstreamPickupConfig,
487
487
  resolveNpmRegistryRelease,
488
488
  } from "./release-propagation-pickup.js";
489
+ export {
490
+ RELEASE_PROPAGATION_FAILURE_MATRIX,
491
+ RELEASE_PROPAGATION_FAILURE_MATRIX_CONTRACT,
492
+ SITE_UPSTREAM_AGENT_ENTRY_CONTRACT,
493
+ classifyReleasePropagationCondition,
494
+ normalizeSiteUpstreamIntent,
495
+ planSiteUpstreamAgentEntry,
496
+ } from "./release-propagation-agent-entry.js";
497
+ export {
498
+ RELEASE_PROPAGATION_PUSH_PLAN_CONTRACT,
499
+ RELEASE_PROPAGATION_PUSH_RESULT_CONTRACT,
500
+ createReleasePropagationPushPlan,
501
+ executeReleasePropagationPush,
502
+ inspectReleasePropagationPushState,
503
+ } from "./release-propagation-push.js";