@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
@@ -12,37 +12,110 @@ function required(value, label) {
12
12
 
13
13
  function repository(value, label) {
14
14
  const normalized = required(value, label);
15
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized)) throw new Error(`${label} must be owner/repository`);
15
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(normalized))
16
+ throw new Error(`${label} must be owner/repository`);
16
17
  return normalized;
17
18
  }
18
19
 
19
20
  const TRANSIENT_GITHUB_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
20
- const DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF = "authority/v3/v3.0/artifact-signing";
21
+ const DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF =
22
+ "authority/v3/v3.0/artifact-signing";
23
+ const AUTHORITY_WORKFLOW = "artifact-signing-authority.yml";
21
24
 
22
25
  export function resolveAuthorityDispatchRef(value) {
23
26
  const ref = required(value, "authority ref");
24
- return /^[0-9a-f]{40}$/u.test(ref) ? DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF : ref;
27
+ return /^[0-9a-f]{40}$/u.test(ref)
28
+ ? DEFAULT_ARTIFACT_SIGNING_AUTHORITY_REF
29
+ : ref;
30
+ }
31
+
32
+ export async function resolveArtifactSigningAuthorityRuntime({
33
+ authorityRepository,
34
+ authorityRef,
35
+ token,
36
+ requestImpl = githubRequest,
37
+ }) {
38
+ const authorityRepo = repository(authorityRepository, "authority repository");
39
+ const ref = resolveAuthorityDispatchRef(authorityRef);
40
+ const encodedRef = ref.split("/").map(encodeURIComponent).join("/");
41
+ const response = await requestImpl(
42
+ `/repos/${authorityRepo}/git/ref/heads/${encodedRef}`,
43
+ { token: required(token, "Buildchain authority dispatch token") },
44
+ );
45
+ if (response?.object?.type !== "commit") {
46
+ throw new Error(
47
+ "Buildchain signing authority ref does not resolve to a commit",
48
+ );
49
+ }
50
+ const sha = String(response.object.sha || "");
51
+ if (!/^[0-9a-f]{40}$/u.test(sha)) {
52
+ throw new Error("Buildchain signing authority runtime SHA must be exact");
53
+ }
54
+ return { ref, sha };
25
55
  }
26
56
 
27
57
  function retryDelayMs(attempt) {
28
- return Math.min(1_000 * (2 ** (attempt - 1)), 10_000);
58
+ return Math.min(1_000 * 2 ** (attempt - 1), 10_000);
29
59
  }
30
60
 
31
- export async function githubRequest(url, {
32
- token,
33
- method = "GET",
34
- body,
35
- fetchImpl = fetch,
36
- delayImpl = delay,
37
- maxAttempts = 5,
38
- warnImpl = console.warn,
39
- } = {}) {
61
+ function authorityError(message, outputs = {}) {
62
+ const error = new Error(message);
63
+ error.authorityOutputs = outputs;
64
+ return error;
65
+ }
66
+
67
+ export function validateArtifactSigningAuthorityRun(
68
+ run,
69
+ { authorityRepository, authorityRuntimeSha, expectedTitle },
70
+ ) {
71
+ if (!run || typeof run !== "object") {
72
+ throw new Error("Buildchain signing authority run is missing");
73
+ }
74
+ if (String(run.display_title || "") !== expectedTitle) {
75
+ throw new Error("Buildchain signing authority correlation mismatch");
76
+ }
77
+ if (String(run.event || "") !== "workflow_dispatch") {
78
+ throw new Error("Buildchain signing authority event mismatch");
79
+ }
80
+ if (String(run.head_sha || "") !== authorityRuntimeSha) {
81
+ throw new Error("Buildchain signing authority runtime SHA mismatch");
82
+ }
83
+ if (
84
+ run.repository?.full_name &&
85
+ String(run.repository.full_name) !== authorityRepository
86
+ ) {
87
+ throw new Error("Buildchain signing authority repository mismatch");
88
+ }
89
+ if (
90
+ run.path &&
91
+ !String(run.path).startsWith(`.github/workflows/${AUTHORITY_WORKFLOW}@`)
92
+ ) {
93
+ throw new Error("Buildchain signing authority workflow path mismatch");
94
+ }
95
+ return run;
96
+ }
97
+
98
+ export async function githubRequest(
99
+ url,
100
+ {
101
+ token,
102
+ method = "GET",
103
+ body,
104
+ fetchImpl = fetch,
105
+ delayImpl = delay,
106
+ maxAttempts = 5,
107
+ warnImpl = console.warn,
108
+ } = {},
109
+ ) {
40
110
  const methodName = String(method).toUpperCase();
41
111
  const retrySafe = methodName === "GET";
42
112
  const requestedAttempts = Number(maxAttempts);
43
- const attemptLimit = retrySafe && Number.isSafeInteger(requestedAttempts) && requestedAttempts > 0
44
- ? requestedAttempts
45
- : 1;
113
+ const attemptLimit =
114
+ retrySafe &&
115
+ Number.isSafeInteger(requestedAttempts) &&
116
+ requestedAttempts > 0
117
+ ? requestedAttempts
118
+ : 1;
46
119
  for (let attempt = 1; attempt <= attemptLimit; attempt += 1) {
47
120
  let response;
48
121
  try {
@@ -59,7 +132,9 @@ export async function githubRequest(url, {
59
132
  });
60
133
  } catch (error) {
61
134
  if (retrySafe && attempt < attemptLimit) {
62
- warnImpl(`Buildchain signing authority: GitHub API GET transport failure; retry ${attempt + 1}/${attemptLimit}`);
135
+ warnImpl(
136
+ `Buildchain signing authority: GitHub API GET transport failure; retry ${attempt + 1}/${attemptLimit}`,
137
+ );
63
138
  await delayImpl(retryDelayMs(attempt));
64
139
  continue;
65
140
  }
@@ -67,19 +142,29 @@ export async function githubRequest(url, {
67
142
  }
68
143
  if (!response.ok) {
69
144
  const text = await response.text();
70
- if (retrySafe && TRANSIENT_GITHUB_STATUSES.has(response.status) && attempt < attemptLimit) {
71
- warnImpl(`Buildchain signing authority: GitHub API GET returned ${response.status}; retry ${attempt + 1}/${attemptLimit}`);
145
+ if (
146
+ retrySafe &&
147
+ TRANSIENT_GITHUB_STATUSES.has(response.status) &&
148
+ attempt < attemptLimit
149
+ ) {
150
+ warnImpl(
151
+ `Buildchain signing authority: GitHub API GET returned ${response.status}; retry ${attempt + 1}/${attemptLimit}`,
152
+ );
72
153
  await delayImpl(retryDelayMs(attempt));
73
154
  continue;
74
155
  }
75
- throw new Error(`GitHub API ${methodName} ${url} failed (${response.status}): ${text.slice(0, 500)}`);
156
+ throw new Error(
157
+ `GitHub API ${methodName} ${url} failed (${response.status}): ${text.slice(0, 500)}`,
158
+ );
76
159
  }
77
160
  if (response.status === 204) return {};
78
161
  try {
79
162
  return await response.json();
80
163
  } catch (error) {
81
164
  if (retrySafe && attempt < attemptLimit) {
82
- warnImpl(`Buildchain signing authority: GitHub API GET response failed to decode; retry ${attempt + 1}/${attemptLimit}`);
165
+ warnImpl(
166
+ `Buildchain signing authority: GitHub API GET response failed to decode; retry ${attempt + 1}/${attemptLimit}`,
167
+ );
83
168
  await delayImpl(retryDelayMs(attempt));
84
169
  continue;
85
170
  }
@@ -89,70 +174,236 @@ export async function githubRequest(url, {
89
174
  throw new Error("GitHub API retry loop exhausted");
90
175
  }
91
176
 
177
+ async function pollArtifactSigningAuthorityRun({
178
+ authorityRepository,
179
+ token,
180
+ authorityRuntimeSha,
181
+ expectedTitle,
182
+ startedAtMs,
183
+ deadline,
184
+ baseOutputs,
185
+ requestImpl,
186
+ delayImpl,
187
+ nowImpl,
188
+ }) {
189
+ let run;
190
+ while (nowImpl() < deadline) {
191
+ const response = await requestImpl(
192
+ `/repos/${authorityRepository}/actions/workflows/${AUTHORITY_WORKFLOW}/runs?event=workflow_dispatch&per_page=50`,
193
+ { token },
194
+ );
195
+ const matches = (response.workflow_runs || []).filter(
196
+ (entry) =>
197
+ new Date(entry.created_at).getTime() >= startedAtMs - 30_000 &&
198
+ String(entry.display_title || "") === expectedTitle,
199
+ );
200
+ if (matches.length > 1) {
201
+ throw authorityError(
202
+ "multiple Buildchain signing authority runs share one correlation",
203
+ { ...baseOutputs, "authority-status": "failed" },
204
+ );
205
+ }
206
+ if (matches.length === 1) {
207
+ try {
208
+ run = validateArtifactSigningAuthorityRun(matches[0], {
209
+ authorityRepository,
210
+ authorityRuntimeSha,
211
+ expectedTitle,
212
+ });
213
+ } catch (error) {
214
+ throw authorityError(error.message, {
215
+ ...baseOutputs,
216
+ "authority-run-id": String(matches[0].id || ""),
217
+ "authority-run-url": String(matches[0].html_url || ""),
218
+ "authority-status": "failed",
219
+ });
220
+ }
221
+ }
222
+ if (run?.status === "completed") return run;
223
+ await delayImpl(10_000);
224
+ }
225
+ throw authorityError("timed out waiting for Buildchain signing authority", {
226
+ ...baseOutputs,
227
+ "authority-run-id": String(run?.id || ""),
228
+ "authority-run-url": String(run?.html_url || ""),
229
+ "authority-status": "timed-out",
230
+ "authority-conclusion": String(run?.conclusion || "timed-out"),
231
+ "controller-completed-at": new Date(nowImpl()).toISOString(),
232
+ });
233
+ }
234
+
235
+ function successfulAuthorityResult({
236
+ run,
237
+ baseOutputs,
238
+ resultArtifact,
239
+ correlationId,
240
+ controllerStartedAt,
241
+ completedAt,
242
+ }) {
243
+ if (run.conclusion !== "success") {
244
+ const status = run.conclusion === "cancelled" ? "cancelled" : "failed";
245
+ throw authorityError(
246
+ `Buildchain signing authority failed: ${run.html_url}`,
247
+ {
248
+ ...baseOutputs,
249
+ "authority-run-id": String(run.id),
250
+ "authority-run-url": String(run.html_url || ""),
251
+ "authority-status": status,
252
+ "authority-conclusion": String(run.conclusion || status),
253
+ "controller-completed-at": completedAt,
254
+ },
255
+ );
256
+ }
257
+ return {
258
+ outputs: {
259
+ ...baseOutputs,
260
+ "authority-run-id": String(run.id),
261
+ "authority-run-url": String(run.html_url || ""),
262
+ "authority-status": "succeeded",
263
+ "authority-conclusion": "success",
264
+ "controller-completed-at": completedAt,
265
+ },
266
+ result: {
267
+ runId: run.id,
268
+ runUrl: run.html_url,
269
+ authorityRuntimeSha: baseOutputs["authority-runtime-sha"],
270
+ resultArtifact,
271
+ correlationId,
272
+ status: "succeeded",
273
+ startedAt: controllerStartedAt,
274
+ completedAt,
275
+ },
276
+ };
277
+ }
278
+
92
279
  export async function dispatchArtifactSigningAuthority({
93
280
  token = process.env.BUILDCHAIN_AUTHORITY_DISPATCH_TOKEN,
94
- authorityRepository = process.env.BUILDCHAIN_AUTHORITY_REPOSITORY || "kungfu-systems/buildchain",
281
+ authorityRepository = process.env.BUILDCHAIN_AUTHORITY_REPOSITORY ||
282
+ "kungfu-systems/buildchain",
95
283
  authorityRef = process.env.BUILDCHAIN_AUTHORITY_REF,
96
284
  sourceRepository = process.env.GITHUB_REPOSITORY,
97
285
  sourceRunId = process.env.GITHUB_RUN_ID,
98
286
  sourceRunAttempt = process.env.GITHUB_RUN_ATTEMPT || "1",
99
287
  requestArtifact = process.env.BUILDCHAIN_SIGNING_REQUEST_ARTIFACT,
288
+ requestRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT_DIGEST,
100
289
  runtimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
101
290
  resultArtifact = process.env.BUILDCHAIN_SIGNING_RESULT_ARTIFACT,
291
+ correlationId = process.env.BUILDCHAIN_AUTHORITY_CORRELATION_ID || "",
102
292
  timeoutSeconds = process.env.BUILDCHAIN_SIGNING_TIMEOUT_SECONDS || "7200",
293
+ requestImpl = githubRequest,
294
+ delayImpl = delay,
295
+ nowImpl = () => Date.now(),
103
296
  } = {}) {
104
297
  const authToken = required(token, "Buildchain authority dispatch token");
105
298
  const authorityRepo = repository(authorityRepository, "authority repository");
106
299
  const sourceRepo = repository(sourceRepository, "source repository");
107
300
  const ref = resolveAuthorityDispatchRef(authorityRef);
108
301
  const runtime = required(runtimeSha, "Buildchain runtime SHA");
109
- if (!/^[0-9a-f]{40}$/u.test(runtime)) throw new Error("Buildchain runtime SHA must be exact");
110
- const correlationId = `${required(sourceRunId, "source run ID")}-${required(sourceRunAttempt, "source run attempt")}-${runtime.slice(0, 12)}-${required(requestArtifact, "request artifact").replace(/[^A-Za-z0-9._-]+/gu, "-")}`;
302
+ if (!/^[0-9a-f]{40}$/u.test(runtime))
303
+ throw new Error("Buildchain runtime SHA must be exact");
304
+ const runId = required(sourceRunId, "source run ID");
305
+ const runAttempt = required(sourceRunAttempt, "source run attempt");
306
+ if (!/^[1-9][0-9]*$/u.test(runAttempt)) {
307
+ throw new Error("source run attempt must be a positive integer");
308
+ }
309
+ const requestName = required(requestArtifact, "request artifact");
310
+ const requestRootDigest = required(requestRoot, "request root");
311
+ if (!/^sha256:[0-9a-f]{64}$/u.test(requestRootDigest)) {
312
+ throw new Error("request root must be a canonical sha256 root");
313
+ }
314
+ const correlation = required(
315
+ correlationId ||
316
+ `${runId}-${runAttempt}-${runtime.slice(0, 12)}-${requestName.replace(/[^A-Za-z0-9._-]+/gu, "-")}`,
317
+ "correlation ID",
318
+ );
111
319
  const resultName = required(resultArtifact, "result artifact");
112
- const workflow = "artifact-signing-authority.yml";
113
- const startedAt = Date.now() - 30_000;
114
- await githubRequest(`/repos/${authorityRepo}/actions/workflows/${workflow}/dispatches`, {
115
- token: authToken,
116
- method: "POST",
117
- body: {
118
- ref,
119
- inputs: {
120
- "source-repository": sourceRepo,
121
- "source-run-id": String(sourceRunId),
122
- "request-artifact-pattern": required(requestArtifact, "request artifact"),
123
- "result-artifact-name": resultName,
124
- "correlation-id": correlationId,
125
- "expected-runtime-sha": runtime,
320
+ const timeout = Number(timeoutSeconds);
321
+ if (!Number.isFinite(timeout) || timeout <= 0) {
322
+ throw new Error("Buildchain signing timeout must be positive");
323
+ }
324
+ const controllerStartedAtMs = nowImpl();
325
+ const controllerStartedAt = new Date(controllerStartedAtMs).toISOString();
326
+ const baseOutputs = {
327
+ "result-artifact": resultName,
328
+ "correlation-id": correlation,
329
+ "controller-started-at": controllerStartedAt,
330
+ };
331
+ try {
332
+ const authorityRuntime = await resolveArtifactSigningAuthorityRuntime({
333
+ authorityRepository: authorityRepo,
334
+ authorityRef: ref,
335
+ token: authToken,
336
+ requestImpl,
337
+ });
338
+ baseOutputs["authority-runtime-sha"] = authorityRuntime.sha;
339
+ await requestImpl(
340
+ `/repos/${authorityRepo}/actions/workflows/${AUTHORITY_WORKFLOW}/dispatches`,
341
+ {
342
+ token: authToken,
343
+ method: "POST",
344
+ body: {
345
+ ref: authorityRuntime.ref,
346
+ inputs: {
347
+ "source-repository": sourceRepo,
348
+ "source-run-id": runId,
349
+ "source-run-attempt": runAttempt,
350
+ "request-artifact-pattern": requestName,
351
+ "expected-request-root": requestRootDigest,
352
+ "result-artifact-name": resultName,
353
+ "correlation-id": correlation,
354
+ "expected-runtime-sha": runtime,
355
+ },
356
+ },
126
357
  },
127
- },
128
- });
129
- const deadline = Date.now() + Number(timeoutSeconds) * 1000;
130
- let run;
131
- while (Date.now() < deadline) {
132
- const response = await githubRequest(`/repos/${authorityRepo}/actions/workflows/${workflow}/runs?event=workflow_dispatch&per_page=50`, { token: authToken });
133
- run = (response.workflow_runs || []).find((entry) =>
134
- new Date(entry.created_at).getTime() >= startedAt &&
135
- String(entry.display_title || "").includes(correlationId),
136
358
  );
137
- if (run?.status === "completed") break;
138
- await delay(10_000);
139
- }
140
- if (!run || run.status !== "completed") throw new Error("timed out waiting for Buildchain signing authority");
141
- if (run.conclusion !== "success") throw new Error(`Buildchain signing authority failed: ${run.html_url}`);
142
- writeGitHubOutputs({
143
- "authority-run-id": String(run.id),
144
- "authority-run-url": run.html_url,
145
- "result-artifact": resultName,
146
- "correlation-id": correlationId,
147
- });
148
- return { runId: run.id, runUrl: run.html_url, resultArtifact: resultName, correlationId };
359
+ const expectedTitle = `Sign ${sourceRepo} run ${runId} (${correlation})`;
360
+ const run = await pollArtifactSigningAuthorityRun({
361
+ authorityRepository: authorityRepo,
362
+ token: authToken,
363
+ authorityRuntimeSha: authorityRuntime.sha,
364
+ expectedTitle,
365
+ startedAtMs: controllerStartedAtMs,
366
+ deadline: controllerStartedAtMs + timeout * 1000,
367
+ baseOutputs,
368
+ requestImpl,
369
+ delayImpl,
370
+ nowImpl,
371
+ });
372
+ const completedAt = new Date(nowImpl()).toISOString();
373
+ const completed = successfulAuthorityResult({
374
+ run,
375
+ baseOutputs,
376
+ resultArtifact: resultName,
377
+ correlationId: correlation,
378
+ controllerStartedAt,
379
+ completedAt,
380
+ });
381
+ writeGitHubOutputs(completed.outputs);
382
+ return completed.result;
383
+ } catch (error) {
384
+ if (!error.authorityOutputs) {
385
+ error.authorityOutputs = {
386
+ ...baseOutputs,
387
+ "authority-status": "failed",
388
+ "authority-conclusion": "controller-error",
389
+ "controller-completed-at": new Date(nowImpl()).toISOString(),
390
+ };
391
+ }
392
+ throw error;
393
+ }
149
394
  }
150
395
 
151
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
396
+ if (
397
+ process.argv[1] &&
398
+ import.meta.url === pathToFileURL(process.argv[1]).href
399
+ ) {
152
400
  try {
153
401
  await dispatchArtifactSigningAuthority();
154
402
  } catch (error) {
155
- console.error(`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`);
403
+ if (error?.authorityOutputs) writeGitHubOutputs(error.authorityOutputs);
404
+ console.error(
405
+ `::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
406
+ );
156
407
  process.exitCode = 1;
157
408
  }
158
409
  }
@@ -364,6 +364,7 @@ const manualMetaById = new Map(Object.entries({
364
364
  "release-activation-transaction": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 125 },
365
365
  "release-candidate": { capabilityGroup: "reusable-build", audience: ["release-operator", "consumer"], maturity: "stable", order: 130 },
366
366
  "stable-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer"], maturity: "preview", order: 135 },
367
+ "dev-qualification-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 136 },
367
368
  "dev-alpha-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 137 },
368
369
  "observed-evidence-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 140 },
369
370
  "reusable-build-surface": { capabilityGroup: "reusable-build", audience: ["consumer", "release-operator"], maturity: "stable", order: 200 },
@@ -724,6 +725,7 @@ function buildSiteBundle() {
724
725
  "docs/reusable-build-surface.md",
725
726
  "docs/release-candidate.md",
726
727
  "docs/stable-candidate-patrol.md",
728
+ "docs/dev-qualification-patrol.md",
727
729
  "docs/dev-alpha-candidate-patrol.md",
728
730
  "docs/observed-evidence-patrol.md",
729
731
  "docs/release-governance.md",
@@ -784,6 +786,7 @@ function buildSiteBundle() {
784
786
  ["buildchain-patrol-weekly", "repository-patrol"],
785
787
  ["buildchain-patrol-monthly", "repository-patrol"],
786
788
  ["stable-candidate-patrol", "repository-patrol"],
789
+ ["dev-qualification-patrol", "repository-patrol"],
787
790
  ["dev-alpha-candidate-patrol", "repository-patrol"],
788
791
  ["buildchain-stable-candidate-patrol", "repository-patrol"],
789
792
  ["buildchain-stable-candidate-qualification", "repository-patrol"],
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
 
6
6
  import { validateArtifactSigningRequest } from "../packages/core/artifact-signing.js";
7
+ import { artifactSigningRequestRoot } from "./artifact-signing-controller-core.mjs";
7
8
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
8
9
 
9
10
  function required(value, label) {
@@ -35,12 +36,14 @@ export function inspectArtifactSigningRequests({
35
36
  inputRoot = process.env.BUILDCHAIN_SIGNING_REQUEST_ROOT,
36
37
  expectedRepository = process.env.BUILDCHAIN_SIGNING_SOURCE_REPOSITORY,
37
38
  expectedRuntimeSha = process.env.BUILDCHAIN_RUNTIME_SHA,
39
+ expectedRequestRoot = process.env.BUILDCHAIN_SIGNING_EXPECTED_REQUEST_ROOT,
38
40
  } = {}) {
39
41
  const root = path.resolve(required(inputRoot, "signing request root"));
40
42
  const indexes = walk(root, "index.json");
41
43
  if (indexes.length === 0)
42
44
  throw new Error("no artifact signing request indexes found");
43
45
  const seen = new Set();
46
+ const observedRoots = [];
44
47
  const matrices = {
45
48
  detached: [],
46
49
  macos: [],
@@ -52,6 +55,7 @@ export function inspectArtifactSigningRequests({
52
55
  index.contract !== "kungfu-buildchain-artifact-signing-request-index/v1"
53
56
  )
54
57
  continue;
58
+ observedRoots.push(artifactSigningRequestRoot(index));
55
59
  for (const entry of index.requests || []) {
56
60
  const requestPath = path.resolve(path.dirname(indexPath), entry.path);
57
61
  const relative = path.relative(root, requestPath);
@@ -113,6 +117,14 @@ export function inspectArtifactSigningRequests({
113
117
  );
114
118
  }
115
119
  }
120
+ if (expectedRequestRoot) {
121
+ if (observedRoots.length !== 1) {
122
+ throw new Error("signing authority expected exactly one request root");
123
+ }
124
+ if (observedRoots[0] !== expectedRequestRoot) {
125
+ throw new Error("signing authority request root mismatch");
126
+ }
127
+ }
116
128
  for (const entries of Object.values(matrices))
117
129
  entries.sort((a, b) => a.request.localeCompare(b.request));
118
130
  writeGitHubOutputs({
@@ -18,7 +18,12 @@ import {
18
18
  writeReleasePropagationLock,
19
19
  createManualUpstreamPickupCapture,
20
20
  createManualUpstreamPickupPlan,
21
+ createReleasePropagationPushPlan,
22
+ executeReleasePropagationPush,
23
+ inspectReleasePropagationPushState,
21
24
  normalizeManualUpstreamPickupConfig,
25
+ planSiteUpstreamAgentEntry,
26
+ RELEASE_PROPAGATION_FAILURE_MATRIX,
22
27
  resolveNpmRegistryRelease,
23
28
  } from "../packages/core/release-propagation.js";
24
29
 
@@ -66,6 +71,19 @@ function usage() {
66
71
  --completion-decision <json-or-path>
67
72
  --expected-work-root <sha256:...>
68
73
  [--output <file>] [--json]
74
+ buildchain release-propagation work push-plan --work <json-or-path>
75
+ --expected-work-root <sha256:...>
76
+ [--cwd <dir>] [--remote <name>]
77
+ [--output <file>] [--json]
78
+ buildchain release-propagation work push-branch --work <json-or-path>
79
+ --expected-work-root <sha256:...>
80
+ [--cwd <dir>] [--remote <name>]
81
+ --execute [--output <file>] [--json]
82
+ buildchain release-propagation entry plan --source-id <paper|kfd|buildchain|kungfu-core>
83
+ [--channel <alpha|release>]
84
+ [--handoff-work <json-or-path>]
85
+ [--output <file>] [--json]
86
+ buildchain release-propagation entry fault-matrix [--json]
69
87
  buildchain release-propagation pickup plan --config <json-or-path>
70
88
  --source-id <id> --channel <alpha|release>
71
89
  --current-version <version>
@@ -182,9 +200,60 @@ function runWorkCli(args) {
182
200
  emitWorkResult(workArgs, result, `release propagation work complete: ${result.contentRoot}`);
183
201
  return;
184
202
  }
203
+ if (mode === "push-plan" || mode === "push-branch") {
204
+ const work = readJsonFlag(workArgs, "work");
205
+ const cwd = readFlag(workArgs, "cwd", process.cwd());
206
+ const remote = readFlag(workArgs, "remote", "origin");
207
+ let result;
208
+ if (mode === "push-plan") {
209
+ const repositoryState = inspectReleasePropagationPushState({ work, cwd, remote });
210
+ result = createReleasePropagationPushPlan({
211
+ work,
212
+ expectedWorkRoot: readFlag(workArgs, "expected-work-root"),
213
+ repositoryState,
214
+ });
215
+ } else {
216
+ if (!hasFlag(workArgs, "execute")) throw new Error("work push-branch requires --execute");
217
+ result = executeReleasePropagationPush({
218
+ work,
219
+ expectedWorkRoot: readFlag(workArgs, "expected-work-root"),
220
+ cwd,
221
+ remote,
222
+ });
223
+ }
224
+ emitWorkResult(workArgs, result, mode === "push-plan"
225
+ ? `release propagation push plan: ${result.planRoot}`
226
+ : `release propagation push result: ${result.resultRoot}`);
227
+ return;
228
+ }
185
229
  throw new Error(`unsupported release-propagation work command: ${mode}`);
186
230
  }
187
231
 
232
+ function runEntryCli(args) {
233
+ const [mode = "", ...entryArgs] = args;
234
+ if (!mode || mode === "--help" || mode === "-h") {
235
+ process.stdout.write(usage());
236
+ return;
237
+ }
238
+ let result;
239
+ if (mode === "fault-matrix") {
240
+ result = RELEASE_PROPAGATION_FAILURE_MATRIX;
241
+ } else if (mode === "plan") {
242
+ result = planSiteUpstreamAgentEntry({
243
+ sourceId: readFlag(entryArgs, "source-id"),
244
+ channel: readFlag(entryArgs, "channel"),
245
+ handoffWork: entryArgs.includes("--handoff-work")
246
+ ? readJsonFlag(entryArgs, "handoff-work")
247
+ : null,
248
+ });
249
+ } else {
250
+ throw new Error(`unsupported release-propagation entry command: ${mode}`);
251
+ }
252
+ writeOutput(readFlag(entryArgs, "output", ""), result);
253
+ if (hasFlag(entryArgs, "json")) printJson(result);
254
+ else process.stdout.write(`${result.contract}\n`);
255
+ }
256
+
188
257
  async function resolvePickupPlan(args) {
189
258
  const config = normalizeManualUpstreamPickupConfig(readJsonFlag(args, "config"));
190
259
  const sourceId = readFlag(args, "source-id");
@@ -274,6 +343,10 @@ export async function runReleasePropagationCli(argv = process.argv.slice(2)) {
274
343
  runWorkCli(args);
275
344
  return;
276
345
  }
346
+ if (mode === "entry") {
347
+ runEntryCli(args);
348
+ return;
349
+ }
277
350
  if (mode === "pickup") {
278
351
  await runPickupCli(args);
279
352
  return;
@@ -28,6 +28,7 @@ import {
28
28
  parseExpectedArtifactsJson,
29
29
  validateExpectedArtifacts,
30
30
  } from "./build-contract-core.mjs";
31
+ import { verifyCompilerCacheActivity } from "./compiler-cache-evidence.mjs";
31
32
 
32
33
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
33
34
  const buildchainCliCandidates = [
@@ -391,6 +392,22 @@ function writeDiagnosticsSidecarManifest(filePath, {
391
392
  return manifest;
392
393
  }
393
394
 
395
+ export function verifyBuildLifecycleCompilerCacheActivity({
396
+ stageName = "",
397
+ executed = false,
398
+ cwd = process.cwd(),
399
+ env = process.env,
400
+ verifier = verifyCompilerCacheActivity,
401
+ frameworkLog,
402
+ } = {}) {
403
+ if (stageName !== "build" || !executed) return undefined;
404
+ const activity = verifier({ cwd, env });
405
+ if (activity) {
406
+ frameworkLog?.info("compiler-cache.activity", { attributes: activity });
407
+ }
408
+ return activity;
409
+ }
410
+
394
411
  export function runLifecycle({
395
412
  cwd = process.cwd(),
396
413
  stageName = "",
@@ -646,6 +663,13 @@ export function runLifecycle({
646
663
  throw new Error(`required lifecycle stage did not run: ${stageName || "command"}`);
647
664
  }
648
665
 
666
+ const compilerCacheActivity = verifyBuildLifecycleCompilerCacheActivity({
667
+ stageName,
668
+ executed,
669
+ cwd: resolvedCwd,
670
+ frameworkLog,
671
+ });
672
+
649
673
  const shouldReadProcessSummary = Boolean(
650
674
  resolvedProcessSummaryPath
651
675
  && (fs.existsSync(resolvedProcessSummaryPath) || processSummaryRequired),
@@ -739,6 +763,7 @@ export function runLifecycle({
739
763
  fileCount: summary.fileCount,
740
764
  });
741
765
  observability.lifecycle = lifecycleObservability;
766
+ Object.assign(observability, { compilerCacheActivity });
742
767
  observability.diagnostics = {
743
768
  contract: BUILDCHAIN_DIAGNOSTICS_CONTRACT,
744
769
  path: relativeDiagnosticsPath,