@kungfu-tech/buildchain 3.0.7-alpha.0 → 3.0.7

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 (67) hide show
  1. package/actions/promote-buildchain-ref/README.md +10 -0
  2. package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
  3. package/contracts/engineering-housekeeper-v1.schema.json +143 -0
  4. package/contracts/fixtures/engineering-housekeeper-v1/cases.json +68 -0
  5. package/dist/site/buildchain-contract.json +24 -24
  6. package/dist/site/buildchain-site.json +91 -30
  7. package/dist/site/capability-registry.json +3 -3
  8. package/dist/site/controller-registry.json +6 -2
  9. package/dist/site/kfd-claims.json +122 -11
  10. package/dist/site/kfd-upstream-aggregate.json +1 -1
  11. package/dist/site/manual-registry.json +8 -7
  12. package/dist/site/node-api-registry.json +683 -105
  13. package/dist/site/page-registry.json +80 -19
  14. package/dist/site/public-surface-audit.json +98 -7
  15. package/dist/site/publication-authority-registry.json +81 -1
  16. package/dist/site/publication-registry.json +4 -4
  17. package/dist/site/release-provenance.json +2 -0
  18. package/dist/site/site-manifest.json +11 -11
  19. package/dist/site/workflow-registry.json +119 -2
  20. package/docs/MAP.md +1 -0
  21. package/docs/auditable-demo.md +2 -2
  22. package/docs/dev-delivery-warrant.md +49 -4
  23. package/docs/engineering-housekeeper.md +138 -0
  24. package/docs/lifecycle-protocol.md +4 -2
  25. package/docs/node-api-reference.md +277 -212
  26. package/docs/release-governance.md +17 -2
  27. package/docs/release-tail-provider-plane.md +1 -1
  28. package/docs/reusable-build-surface.md +11 -0
  29. package/package.json +4 -1
  30. package/packages/core/artifact-signing.js +61 -0
  31. package/packages/core/buildchain-config.js +66 -6
  32. package/packages/core/buildchain-publication-authority.js +4 -0
  33. package/packages/core/controller-evidence.js +2 -1
  34. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  35. package/packages/core/dev-delivery-warrant-shadow.js +502 -0
  36. package/packages/core/dev-delivery-warrant.js +15 -6
  37. package/packages/core/diagnostics.js +8 -3
  38. package/packages/core/engineering-housekeeper-github-client.js +222 -0
  39. package/packages/core/engineering-housekeeper-github.js +501 -0
  40. package/packages/core/engineering-housekeeper.js +259 -0
  41. package/packages/core/index.js +3 -0
  42. package/packages/core/kfd-gate.js +45 -15
  43. package/packages/core/publication-rehearsal-runtime.js +13 -1
  44. package/packages/core/release-passport.js +130 -20
  45. package/scripts/assemble-self-publication-admission.mjs +1 -1
  46. package/scripts/audit-publication-control-plane.mjs +1 -1
  47. package/scripts/auditable-demo-bundle-verification.mjs +2 -3
  48. package/scripts/auditable-demo-platform.mjs +2 -2
  49. package/scripts/auditable-demo-renditions.mjs +1 -1
  50. package/scripts/auditable-demo.mjs +2 -2
  51. package/scripts/build-contract-core.mjs +8 -3
  52. package/scripts/build-standalone-binary.mjs +14 -3
  53. package/scripts/check-inventory.mjs +3 -1
  54. package/scripts/dev-delivery-warrant.mjs +31 -4
  55. package/scripts/dev-pr-auto-merge.mjs +30 -4
  56. package/scripts/dev-pr-delivery-warrant.mjs +50 -0
  57. package/scripts/engineering-housekeeper-workflow.mjs +394 -0
  58. package/scripts/generate-site-bundle.mjs +23 -4
  59. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  60. package/scripts/materialize-self-release-candidate-version.mjs +6 -0
  61. package/scripts/publication-commit-evidence.mjs +69 -23
  62. package/scripts/release-candidate-resolver.mjs +16 -10
  63. package/scripts/resume-from-candidate-run.mjs +123 -9
  64. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  65. package/scripts/site-capability-metadata.mjs +2 -0
  66. package/scripts/web-surface-core.mjs +8 -2
  67. package/scripts/workflow-call-contract.mjs +1 -1
@@ -351,16 +351,22 @@ export function generatePublishRequiredArtifacts({
351
351
  const files = Array.isArray(manifest.files) ? manifest.files : [];
352
352
  return files
353
353
  .filter((file) => file?.sha256)
354
- .map((file) => ({
355
- kind,
356
- name: packageNameFromArtifactPath(file.path || file.name || manifest.artifactName || platform),
357
- ref,
358
- digest: String(file.sha256).startsWith("sha256:")
359
- ? String(file.sha256)
360
- : `sha256:${file.sha256}`,
361
- role: "platform",
362
- platform,
363
- }));
354
+ .map((file) => {
355
+ const name = String(file.path || file.name || manifest.artifactName || platform)
356
+ .replaceAll("\\", "/")
357
+ .replace(/^\.\//, "");
358
+ return {
359
+ ...(platform ? { group: platform } : {}),
360
+ kind,
361
+ name,
362
+ ref,
363
+ digest: String(file.sha256).startsWith("sha256:")
364
+ ? String(file.sha256)
365
+ : `sha256:${file.sha256}`,
366
+ role: "platform",
367
+ platform,
368
+ };
369
+ });
364
370
  });
365
371
  }
366
372
 
@@ -95,12 +95,18 @@ function findFiles(root, predicate) {
95
95
  return collectFiles(root).filter((file) => predicate(file.path, file.absolutePath));
96
96
  }
97
97
 
98
+ export function recoveredArtifactPathsByBasename(downloads, filename) {
99
+ return downloads.flatMap((download) => download.files
100
+ .filter((file) => path.basename(file.path) === filename)
101
+ .map((file) => outputPath(file.absolutePath)));
102
+ }
103
+
98
104
  function readOnlyJson(files, label) {
99
105
  if (files.length !== 1) throw new Error(`expected exactly one ${label}, found ${files.length}`);
100
106
  return JSON.parse(fs.readFileSync(files[0].absolutePath, "utf8"));
101
107
  }
102
108
 
103
- async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version }) {
109
+ export async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version }) {
104
110
  const stateRef = releaseTransactionStateRef(version);
105
111
  const response = await githubJson({
106
112
  apiUrl,
@@ -110,10 +116,31 @@ async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, ver
110
116
  path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/state.json?ref=${encodeURIComponent(stateRef)}`,
111
117
  });
112
118
  if (!response) return undefined;
113
- if (response.type !== "file" || response.encoding !== "base64" || !response.content) {
114
- throw new Error(`durable transaction ${stateRef} did not expose a base64 state.json file`);
119
+ if (response.type !== "file") {
120
+ throw new Error(`durable transaction ${stateRef} did not expose a state.json file`);
121
+ }
122
+ let encoded = response.encoding === "base64" && response.content
123
+ ? response
124
+ : undefined;
125
+ if (!encoded) {
126
+ const blobSha = String(response.sha || "").trim();
127
+ if (!/^[0-9a-f]{40}$/i.test(blobSha)) {
128
+ throw new Error(`durable transaction ${stateRef} did not expose inline content or an exact blob identity`);
129
+ }
130
+ encoded = await githubJson({
131
+ apiUrl,
132
+ token,
133
+ fetchImpl,
134
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/blobs/${blobSha}`,
135
+ });
136
+ if (String(encoded?.sha || "").trim() !== blobSha) {
137
+ throw new Error(`durable transaction ${stateRef} blob identity drifted from ${blobSha}`);
138
+ }
139
+ }
140
+ if (encoded?.encoding !== "base64" || !encoded.content) {
141
+ throw new Error(`durable transaction ${stateRef} did not expose base64 state.json content`);
115
142
  }
116
- return JSON.parse(Buffer.from(String(response.content).replace(/\s/g, ""), "base64").toString("utf8"));
143
+ return JSON.parse(Buffer.from(String(encoded.content).replace(/\s/g, ""), "base64").toString("utf8"));
117
144
  }
118
145
 
119
146
  function outputPath(filePath) {
@@ -180,6 +207,7 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
180
207
 
181
208
  export function normalizePlatformManifests(downloads, passport) {
182
209
  const manifests = [];
210
+ const paths = [];
183
211
  const evidenceByArtifact = new Map();
184
212
  const platformById = new Map((passport.platformMatrix || []).map((entry) => [String(entry.platformId || ""), entry]));
185
213
  const seenPlatformIds = new Set();
@@ -208,6 +236,7 @@ export function normalizePlatformManifests(downloads, passport) {
208
236
  manifest.artifactName = expectedPlatform.artifactName;
209
237
  seenPlatformIds.add(platformId);
210
238
  manifests.push(manifest);
239
+ paths.push(file.absolutePath);
211
240
  addEvidence(manifest.artifactName, download.record.files);
212
241
  }
213
242
  if (String(download.artifact.name).includes("-diagnostics-")) {
@@ -222,7 +251,7 @@ export function normalizePlatformManifests(downloads, passport) {
222
251
  artifactName,
223
252
  files: [...files.values()].sort((left, right) => left.path.localeCompare(right.path)),
224
253
  }));
225
- return { manifests, evidence };
254
+ return { manifests, paths, evidence };
226
255
  }
227
256
 
228
257
  function normalizeControllerReceipts(downloads, passport) {
@@ -268,16 +297,53 @@ export function createRecoveredPublicationCandidate({
268
297
  return { ...payload, candidateDigest: publicationArtifactCandidateDigest(payload) };
269
298
  }
270
299
 
300
+ export function deduplicateReleaseAssets(files = []) {
301
+ const assets = new Map();
302
+ for (const file of files) {
303
+ const name = path.basename(file.path);
304
+ const existing = assets.get(name);
305
+ if (!existing) {
306
+ assets.set(name, file);
307
+ continue;
308
+ }
309
+ const digest = String(file.sha256 || "").replace(/^sha256:/, "");
310
+ const existingDigest = String(existing.sha256 || "").replace(/^sha256:/, "");
311
+ if (digest !== existingDigest || Number(file.size) !== Number(existing.size)) {
312
+ throw new Error(
313
+ `recovered GitHub Release asset basename collision: ${name} maps to different sealed bytes`,
314
+ );
315
+ }
316
+ if (
317
+ String(file.path).length < String(existing.path).length ||
318
+ (String(file.path).length === String(existing.path).length &&
319
+ String(file.path).localeCompare(String(existing.path)) < 0)
320
+ ) {
321
+ assets.set(name, file);
322
+ }
323
+ }
324
+ return [...assets.values()].sort((left, right) =>
325
+ path.basename(left.path).localeCompare(path.basename(right.path)),
326
+ );
327
+ }
328
+
271
329
  export function createRecoveredPublication({ downloads, bundleRoot, repository, passport, candidateRuntimeSha, publishArtifactKind, publishPackageMain, releasePatterns, platformManifests }) {
272
330
  const allFiles = downloads.flatMap((download) => download.files.map((file) => ({
273
331
  path: path.relative(bundleRoot, file.absolutePath).split(path.sep).join("/"),
274
332
  size: file.size,
275
333
  sha256: file.sha256.replace(/^sha256:/, ""),
276
334
  absolutePath: file.absolutePath,
335
+ artifactName: String(download.artifact?.name || ""),
277
336
  }))).sort((left, right) => left.path.localeCompare(right.path));
278
337
  const kind = String(publishArtifactKind || "npm");
279
338
  const releaseMatchers = splitPatterns(releasePatterns).map(patternMatcher);
280
- const releaseAssets = allFiles.filter((file) => releaseMatchers.some((matcher) => matcher.test(path.basename(file.path))));
339
+ const platformArtifactNames = new Set(
340
+ platformManifests.map((manifest) => String(manifest.artifactName || "")).filter(Boolean),
341
+ );
342
+ const releaseAssetFiles = kind !== "npm" && platformArtifactNames.size > 0
343
+ ? allFiles.filter((file) => platformArtifactNames.has(file.artifactName))
344
+ : allFiles;
345
+ const releaseAssets = deduplicateReleaseAssets(releaseAssetFiles.filter((file) =>
346
+ releaseMatchers.some((matcher) => matcher.test(path.basename(file.path)))));
281
347
  if (kind !== "npm") {
282
348
  createRecoveredPublicationCandidate({ allFiles, repository, passport, candidateRuntimeSha });
283
349
  const version = String(passport.target?.version || "").trim();
@@ -367,6 +433,42 @@ async function recoverCandidateEvidence({
367
433
  };
368
434
  }
369
435
 
436
+ export function exposeRecoveredPayloadRoot({ resolvedOutput, bundleRoot }) {
437
+ const recoveredPayloadRoot = path.join(bundleRoot, "artifacts");
438
+ if (!fs.existsSync(recoveredPayloadRoot) || !fs.statSync(recoveredPayloadRoot).isDirectory()) {
439
+ throw new Error(`recovered candidate payload root is missing: ${recoveredPayloadRoot}`);
440
+ }
441
+ const compatibilityRoot = path.join(resolvedOutput, "payloads");
442
+ try {
443
+ fs.lstatSync(compatibilityRoot);
444
+ if (fs.realpathSync(compatibilityRoot) !== fs.realpathSync(recoveredPayloadRoot)) {
445
+ throw new Error(`recovered candidate payload compatibility path is already occupied: ${compatibilityRoot}`);
446
+ }
447
+ } catch (error) {
448
+ if (error?.code !== "ENOENT") throw error;
449
+ fs.symlinkSync(path.relative(resolvedOutput, recoveredPayloadRoot), compatibilityRoot, "dir");
450
+ }
451
+ return compatibilityRoot;
452
+ }
453
+
454
+ export function exposeRecoveredPassportPath({ resolvedOutput, recoveredPassportPath }) {
455
+ if (!fs.existsSync(recoveredPassportPath) || !fs.statSync(recoveredPassportPath).isFile()) {
456
+ throw new Error(`recovered candidate passport is missing: ${recoveredPassportPath}`);
457
+ }
458
+ const recoveredPassportRoot = path.dirname(recoveredPassportPath);
459
+ const compatibilityRoot = path.join(resolvedOutput, "passport");
460
+ try {
461
+ fs.lstatSync(compatibilityRoot);
462
+ if (fs.realpathSync(compatibilityRoot) !== fs.realpathSync(recoveredPassportRoot)) {
463
+ throw new Error(`recovered candidate passport compatibility path is already occupied: ${compatibilityRoot}`);
464
+ }
465
+ } catch (error) {
466
+ if (error?.code !== "ENOENT") throw error;
467
+ fs.symlinkSync(path.relative(resolvedOutput, recoveredPassportRoot), compatibilityRoot, "dir");
468
+ }
469
+ return path.join(compatibilityRoot, path.basename(recoveredPassportPath));
470
+ }
471
+
370
472
  async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionId, existingTransaction, repoInfo, apiUrl, token, fetchImpl }) {
371
473
  if (observedTargetSha === targetSha || !transactionId || existingTransaction?.id !== transactionId) return undefined;
372
474
  const comparison = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${targetSha}...${observedTargetSha}` });
@@ -494,7 +596,16 @@ export async function resumeFromCandidateRun({
494
596
  if (publication.manifest) fs.writeFileSync(sealedManifestPath, `${JSON.stringify(publication.manifest, null, 2)}\n`);
495
597
  const publishRequiredArtifacts = publication.publishRequiredArtifacts;
496
598
  fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(publishRequiredArtifacts, null, 2)}\n`);
599
+ const payloadRoot = exposeRecoveredPayloadRoot({ resolvedOutput, bundleRoot });
600
+ const recoveredPassportPath = initialDownloads[0].files.find(
601
+ (file) => path.basename(file.path) === "release-candidate-passport.json",
602
+ ).absolutePath;
603
+ const passportPath = exposeRecoveredPassportPath({ resolvedOutput, recoveredPassportPath });
497
604
  const tarballs = publication.npmArtifacts.map((entry) => outputPath(entry.file.absolutePath));
605
+ const githubArtifactAttestationPolicies = recoveredArtifactPathsByBasename(
606
+ downloads,
607
+ "github-artifact-attestation-policy.json",
608
+ );
498
609
  return {
499
610
  enabled: true,
500
611
  action: "reused",
@@ -507,10 +618,11 @@ export async function resumeFromCandidateRun({
507
618
  receipt: recovery.receipt,
508
619
  publishRequiredArtifacts,
509
620
  paths: {
510
- passport: outputPath(initialDownloads[0].files.find((file) => path.basename(file.path) === "release-candidate-passport.json").absolutePath),
621
+ passport: outputPath(passportPath),
511
622
  buildSummary: outputPath(initialDownloads[1].files.find((file) => path.basename(file.path) === "build-summary.json").absolutePath),
512
- payloads: outputPath(path.join(bundleRoot, "artifacts")),
513
- platformManifests: downloads.flatMap((download) => download.files.filter((file) => path.basename(file.path) === "manifest.json").map((file) => outputPath(file.absolutePath))),
623
+ payloads: outputPath(payloadRoot),
624
+ platformManifests: platformManifestEvidence.paths.map(outputPath),
625
+ githubArtifactAttestationPolicies,
514
626
  npmTarballs: tarballs,
515
627
  releaseAssets: publication.releaseAssets.map((asset) => outputPath(asset.absolutePath)),
516
628
  publishRequiredArtifacts: outputPath(requiredArtifactsPath),
@@ -560,6 +672,8 @@ export async function resumeFromCandidateRunCli() {
560
672
  "release-candidate-payload-artifacts": result.artifacts.payloads.join(","),
561
673
  "release-candidate-payload-dir": result.paths.payloads,
562
674
  "release-candidate-platform-manifest-paths": result.paths.platformManifests.join(","),
675
+ "release-candidate-github-artifact-attestation-policy-paths": result.paths.githubArtifactAttestationPolicies.join(","),
676
+ "release-candidate-github-artifact-attestation-policy-count": String(result.paths.githubArtifactAttestationPolicies.length),
563
677
  "release-candidate-npm-tarball-paths": result.paths.npmTarballs.join(","),
564
678
  "release-candidate-github-release-artifact-paths": result.paths.releaseAssets.join("\n"),
565
679
  "publish-required-artifacts-json": JSON.stringify(result.publishRequiredArtifacts),
@@ -376,6 +376,12 @@ export function sealArtifactSigningRequests({
376
376
  signature: {
377
377
  profile: declaration.profile,
378
378
  required: declaration.required,
379
+ ...(declaration.entitlementsProfile !== "none"
380
+ ? {
381
+ entitlementsProfile: declaration.entitlementsProfile,
382
+ entitlementsPaths: declaration.entitlementsPaths,
383
+ }
384
+ : {}),
379
385
  },
380
386
  });
381
387
  const check = validateArtifactSigningRequest(request);
@@ -188,6 +188,8 @@ export function nodeApiMeta(exportName) {
188
188
  "./publication-control-plane-audit": { group: "release-passport-trust", summary: "Read-only publication control-plane snapshot evaluation APIs." },
189
189
  "./buildchain-publication-authority": { group: "release-passport-trust", summary: "Buildchain-owned closed-world publication authority descriptor registry." },
190
190
  "./github-governance-authority": { group: "governance-versioning", summary: "Fail-closed GitHub ownership, effective-policy, managed-zone admission, rollout-plan, and immutable receipt APIs." },
191
+ "./engineering-housekeeper": { group: "governance-versioning", summary: "Provider-neutral repository hygiene classification, deterministic plan and receipt, apply-time revalidation, and replay APIs." },
192
+ "./engineering-housekeeper-github": { group: "governance-versioning", summary: "Paginated GitHub repository hygiene inventory, default-dry-run execution, exact-head mutation fences, and rooted provider receipts." },
191
193
  "./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
192
194
  "./artifact-verification-envelope": { group: "release-passport-trust", summary: "Sealed exact-root, lifecycle, identity, and existing KFD assessment inputs for KFX admission." },
193
195
  "./artifact-signing": { group: "reusable-build", summary: "Credential-free artifact signing declarations, source-bound requests, authority receipts, profile resolution, and fail-closed validation APIs." },
@@ -1725,12 +1725,13 @@ async function fetchWithRetry(url, {
1725
1725
  attempts = 3,
1726
1726
  intervalMs = 5000,
1727
1727
  shouldRetry = () => false,
1728
+ requestOptions = { redirect: "follow" },
1728
1729
  } = {}) {
1729
1730
  const maxAttempts = Math.max(1, Number(attempts) || 1);
1730
1731
  let lastError = null;
1731
1732
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1732
1733
  try {
1733
- const response = await fetchImpl(url, { redirect: "follow" });
1734
+ const response = await fetchImpl(url, requestOptions);
1734
1735
  if (attempt >= maxAttempts || !shouldRetry(response)) {
1735
1736
  return { response, attempts: attempt };
1736
1737
  }
@@ -1751,6 +1752,11 @@ function retryableHealthResponse(response) {
1751
1752
  return response.status === 403 || response.status === 404 || response.status >= 500;
1752
1753
  }
1753
1754
 
1755
+ function retryingHealthFetch(fetchImpl, attempts, intervalMs) {
1756
+ return (url, requestOptions) => fetchWithRetry(url, { fetchImpl, attempts, intervalMs,
1757
+ shouldRetry: retryableHealthResponse, requestOptions }).then(({ response }) => response);
1758
+ }
1759
+
1754
1760
  const DEFAULT_HEALTH_HTTP_RETRY_ATTEMPTS = 12;
1755
1761
  const DEFAULT_HEALTH_HTTP_RETRY_INTERVAL_MS = 10000;
1756
1762
 
@@ -1984,7 +1990,7 @@ export async function checkWebSurfaceHealth({
1984
1990
  };
1985
1991
  const evidence = await verifyInstallerPublicReadback({
1986
1992
  publication: projected,
1987
- fetchImpl,
1993
+ fetchImpl: retryingHealthFetch(fetchImpl, httpRetryAttempts, httpRetryIntervalMs),
1988
1994
  });
1989
1995
  checks.push({
1990
1996
  surface: "__installer__",
@@ -6,7 +6,7 @@ import { pathToFileURL } from "node:url";
6
6
  import { evaluateWorkflowCallContract } from "../packages/core/workflow-call-contract.js";
7
7
  import { parseWorkflowDocument } from "../packages/core/workflow-yaml-contract.js";
8
8
 
9
- export const SELF_RELEASE_REF = "9a0cdf8d84aacf8c7daaac82efa43d1b34696a03";
9
+ export const SELF_RELEASE_REF = "769b221bad7a6b9104afad4c2628d9dca396ab0f";
10
10
  export const SELF_RELEASE_PUBLIC_WORKFLOW = `kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@${SELF_RELEASE_REF}`;
11
11
 
12
12
  function parityFailure(message) {