@kungfu-tech/buildchain 4.0.2-alpha.44 → 4.0.2-alpha.45

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 (37) hide show
  1. package/architecture/maintainability-debt.json +18 -19
  2. package/architecture/maintainability-policy.json +4 -4
  3. package/architecture/v4-release-topology.json +9 -6
  4. package/architecture/v4-universal-workflow-train-admission.json +1 -1
  5. package/dist/site/buildchain-contract.json +7 -7
  6. package/dist/site/buildchain-site.json +47 -11
  7. package/dist/site/capability-registry.json +2 -2
  8. package/dist/site/kfd-claims.json +29 -5
  9. package/dist/site/kfd-upstream-aggregate.json +1 -1
  10. package/dist/site/manual-registry.json +2 -2
  11. package/dist/site/node-api-registry.json +142 -1
  12. package/dist/site/page-registry.json +41 -5
  13. package/dist/site/public-surface-audit.json +7 -3
  14. package/dist/site/publication-authority-registry.json +5 -17
  15. package/dist/site/publication-registry.json +4 -4
  16. package/dist/site/release-provenance.json +2 -1
  17. package/dist/site/site-manifest.json +6 -6
  18. package/dist/site/workflow-registry.json +5 -5
  19. package/docs/MAP.md +1 -0
  20. package/docs/node-api-reference.md +11 -0
  21. package/docs/oci-publication.md +82 -0
  22. package/package.json +3 -2
  23. package/packages/core/buildchain-v4-domain.wasm +0 -0
  24. package/packages/core/oci-publication-bundle.js +241 -0
  25. package/packages/core/release-tail-product-capabilities.js +9 -0
  26. package/packages/core/v4-canonical-contracts.js +1 -0
  27. package/packages/core/v4-domain-wasm-artifact.js +2 -2
  28. package/scripts/check-v4-release-topology.mjs +14 -4
  29. package/scripts/generate-channel-promotion-workflow.mjs +2 -19
  30. package/scripts/generate-v4-universal-workflow-facades.mjs +8 -2
  31. package/scripts/publication-candidate-kind.mjs +92 -0
  32. package/scripts/publication-candidate-sealer.mjs +9 -0
  33. package/scripts/release-candidate-resolver.mjs +6 -17
  34. package/scripts/resume-from-candidate-run.mjs +10 -17
  35. package/scripts/site-capability-metadata.mjs +1 -0
  36. package/scripts/v4-product-publication-intent.mjs +1 -1
  37. package/scripts/v4-universal-workflow-engine.mjs +2 -0
@@ -316,16 +316,7 @@ ${secrets.trimEnd()}
316
316
  outputs:
317
317
  ${publicOutputs(outputs)}
318
318
 
319
- permissions:
320
- actions: write
321
- artifact-metadata: write
322
- attestations: write
323
- checks: write
324
- contents: write
325
- id-token: write
326
- issues: write
327
- pull-requests: write
328
-
319
+ # Provider authority is inherited from the explicit caller envelope.
329
320
  jobs:
330
321
  resolve-promotion:
331
322
  name: Resolve promotion workflow shell and runtime
@@ -503,15 +494,7 @@ ${consumerAdmissionJob()}
503
494
  name: Invoke the single v4 publisher adapter
504
495
  needs: [resolve-promotion, consumer-admission]
505
496
  uses: kungfu-systems/buildchain/${alphaRoute.workflowPath}@${alphaRoute.callRef}
506
- permissions:
507
- actions: write
508
- artifact-metadata: write
509
- attestations: write
510
- checks: write
511
- contents: write
512
- id-token: write
513
- issues: write
514
- pull-requests: write
497
+ # Provider authority is inherited from the explicit caller envelope.
515
498
  with:
516
499
  ${invokeForwarded}
517
500
  secrets: inherit
@@ -216,11 +216,17 @@ function applyPublicationFacadePatches(source, relative) {
216
216
  return source;
217
217
  }
218
218
 
219
+ function inheritPublicationProviderAuthority(source, relative) {
220
+ if (relative !== ".github/workflows/.release-candidate-promote.yml") return source;
221
+ return source.replace("permissions:\n contents: read\n", "# Provider authority is inherited from the explicit caller envelope.\n")
222
+ .replace(" permissions:\n actions: read\n checks: write\n contents: write\n id-token: write\n pull-requests: write\n", " # Provider authority is inherited from the explicit caller envelope.\n");
223
+ }
224
+
219
225
  export function migrateV4UniversalWorkflowFacade(source, relative) {
220
- return rewriteRepositoryWorkflowPaths(root, applyPublicationFacadePatches(guardCompatibilityJobs(
226
+ return rewriteRepositoryWorkflowPaths(root, inheritPublicationProviderAuthority(applyPublicationFacadePatches(guardCompatibilityJobs(
221
227
  addUniversalInput(addRuntimeBootstrapDependencies(source, relative), relative),
222
228
  relative,
223
- ), relative));
229
+ ), relative), relative));
224
230
  }
225
231
 
226
232
  function verify(source, relative) {
@@ -0,0 +1,92 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { verifyOciPublicationBundle } from "../packages/core/oci-publication-bundle.js";
4
+
5
+ export function resolveOciCandidate({ payloadRoot, passport }) {
6
+ const matches = [];
7
+ function visit(directory) {
8
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
9
+ if (entry.isSymbolicLink())
10
+ throw new Error("OCI candidate cannot contain symlinks");
11
+ const file = path.join(directory, entry.name);
12
+ if (entry.isDirectory()) visit(file);
13
+ else if (entry.name === "oci-family.json") matches.push(file);
14
+ }
15
+ }
16
+ visit(payloadRoot);
17
+ if (matches.length !== 1)
18
+ throw new Error("OCI candidate requires exactly one sealed image family");
19
+ const manifest = JSON.parse(fs.readFileSync(matches[0]));
20
+ const bundleRoot = path.dirname(matches[0]);
21
+ const verified = verifyOciPublicationBundle({
22
+ bundleRoot,
23
+ manifest,
24
+ repository: passport.repository,
25
+ sourceSha: passport.source.headSha,
26
+ version: passport.target.version,
27
+ });
28
+ return {
29
+ root: bundleRoot,
30
+ manifest: verified.manifest,
31
+ requiredArtifacts: manifest.images.map((image) => ({
32
+ kind: "oci",
33
+ name: image.repository,
34
+ ref: `v${manifest.version}`,
35
+ digest: image.digest,
36
+ platform: image.platform,
37
+ required: true,
38
+ })),
39
+ };
40
+ }
41
+
42
+ export function resolveRecoveredPublicationVersion({
43
+ artifactVersion,
44
+ channel,
45
+ rematerializeOnResume = false,
46
+ } = {}) {
47
+ const version = String(artifactVersion || "").trim(),
48
+ match = version.match(
49
+ /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
50
+ );
51
+ if (!match)
52
+ throw new Error(
53
+ `recovered npm artifact has invalid publication version: ${version || "<empty>"}`,
54
+ );
55
+ return channel === "release" && rematerializeOnResume ? match[1] : version;
56
+ }
57
+ export function resolveRecoveredCandidateVersion({
58
+ artifactVersion,
59
+ publicationVersion,
60
+ channel,
61
+ rematerializeOnResume = false,
62
+ targetRef = "",
63
+ candidateRef = "",
64
+ } = {}) {
65
+ const version = String(artifactVersion || "").trim(),
66
+ target = String(targetRef || "").replace(/^refs\/heads\//u, ""),
67
+ candidate = String(candidateRef || "").replace(/^refs\/heads\//u, "");
68
+ if (channel !== "release" || !rematerializeOnResume) return version;
69
+ const prefix = `publish-gate/${target}/`;
70
+ if (!target || !candidate.startsWith(prefix))
71
+ throw new Error(
72
+ `stable recovery candidate ref must descend from ${prefix || "publish-gate/<target>/"}`,
73
+ );
74
+ const candidateVersion = candidate.slice(prefix.length);
75
+ if (!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(candidateVersion))
76
+ throw new Error(
77
+ `stable recovery candidate ref must bind an exact alpha version, got ${candidateVersion || "<empty>"}`,
78
+ );
79
+ if (candidateVersion.replace(/-alpha\.\d+$/u, "") !== publicationVersion)
80
+ throw new Error(
81
+ `stable recovery candidate ${candidateVersion} does not match publication ${publicationVersion || "<empty>"}`,
82
+ );
83
+ if (
84
+ (version.match(
85
+ /^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
86
+ )?.[1] || "") !== publicationVersion
87
+ )
88
+ throw new Error(
89
+ `recovered npm artifact version ${version || "<empty>"} does not match publication ${publicationVersion}`,
90
+ );
91
+ return candidateVersion;
92
+ }
@@ -1,3 +1,4 @@
1
+ import { resolveOciCandidate } from "./publication-candidate-kind.mjs";
1
2
  import crypto from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
@@ -102,3 +103,11 @@ export function createResolvedPublicationSealedBundle({
102
103
  });
103
104
  return { root: resolvedRoot, manifest, npmArtifacts: resolvedNpmArtifacts, files };
104
105
  }
106
+
107
+ export function resolveCandidatePublicationBundle({ kind, payloadDir, passport, runtimeSha, npmArtifacts, releaseAssetPaths }) {
108
+ if (kind === "oci") return resolveOciCandidate({ payloadRoot: payloadDir, passport });
109
+ if (kind !== "npm") return undefined;
110
+ return createResolvedPublicationSealedBundle({ bundleRoot: payloadDir, repository: passport.repository,
111
+ sourceSha: passport.source?.headSha, sourceTreeSha: passport.source?.treeHash,
112
+ runtimeSha, releaseCandidateRoot: passport.candidateHash, npmArtifacts, releaseAssetPaths });
113
+ }
@@ -7,7 +7,7 @@ import { execFileSync } from "node:child_process";
7
7
  import { Readable } from "node:stream";
8
8
  import { pipeline } from "node:stream/promises";
9
9
  import { pathToFileURL } from "node:url";
10
- import { createResolvedPublicationSealedBundle } from "./publication-candidate-sealer.mjs";
10
+ import { resolveCandidatePublicationBundle } from "./publication-candidate-sealer.mjs";
11
11
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
12
12
  import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
13
13
  import { v4PublicationQualificationRoot, validateV4PublicationQualificationReceipt } from "../packages/core/v4-publication-qualification.js";
@@ -725,23 +725,12 @@ export async function resolveReleaseCandidateArtifacts({
725
725
  const noun = publishArtifactKind === "npm" ? "npm package tarballs" : "platform manifests";
726
726
  throw new Error(`expected at least ${minimumPayloadCount} downloaded ${noun}, found ${downloadedRequiredArtifactCount}`);
727
727
  }
728
- const sealedBundle = publishArtifactKind === "npm"
729
- ? createResolvedPublicationSealedBundle({
730
- bundleRoot: payloadDir,
731
- repository: repoInfo.fullName,
732
- sourceSha: passport.source?.headSha,
733
- sourceTreeSha: passport.source?.treeHash,
734
- runtimeSha: releaseCandidateRuntimeSha(passport),
735
- releaseCandidateRoot: passport.candidateHash,
736
- npmArtifacts: npmTarballPaths.map((tarballPath) => ({
737
- path: tarballPath,
738
- ...readNpmPackageArtifact({ tarballPath, mainPackage: publishPackageMain }),
739
- })),
740
- releaseAssetPaths,
741
- })
742
- : undefined;
728
+ const sealedBundle = resolveCandidatePublicationBundle({ kind: publishArtifactKind, payloadDir, passport,
729
+ runtimeSha: releaseCandidateRuntimeSha(passport), releaseAssetPaths,
730
+ npmArtifacts: npmTarballPaths.map((tarballPath) => ({ path: tarballPath,
731
+ ...readNpmPackageArtifact({ tarballPath, mainPackage: publishPackageMain }) })) });
743
732
  const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8"))), publicationVersion = resolveFreshPublicationVersion({ sealedBundle, candidateVersion: passport.target?.version });
744
- const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
733
+ const generatedRequiredArtifacts = sealedBundle?.requiredArtifacts || generatePublishRequiredArtifacts({
745
734
  manifests,
746
735
  version: publicationVersion,
747
736
  kind: publishArtifactKind,
@@ -6,6 +6,8 @@ import path from "node:path";
6
6
  import { execFileSync } from "node:child_process";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
8
 
9
+ import { resolveOciCandidate, resolveRecoveredPublicationVersion, resolveRecoveredCandidateVersion } from "./publication-candidate-kind.mjs";
10
+ export { resolveRecoveredPublicationVersion, resolveRecoveredCandidateVersion } from "./publication-candidate-kind.mjs";
9
11
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
10
12
  import { compareSemver } from "./publication-registry-hydrate.mjs";
11
13
  import { normalizeAnchorProvenance, normalizeCandidateRun, recoverCandidateProvenance } from "./release-candidate-anchor-provenance.mjs";
@@ -695,22 +697,6 @@ export function createRecoveredPublicationCandidate({
695
697
  };
696
698
  return { ...payload, candidateDigest: publicationArtifactCandidateDigest(payload) };
697
699
  }
698
- export function resolveRecoveredPublicationVersion({ artifactVersion, channel, rematerializeOnResume = false } = {}) {
699
- const version = String(artifactVersion || "").trim(), match = version.match(/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u);
700
- if (!match) throw new Error(`recovered npm artifact has invalid publication version: ${version || "<empty>"}`);
701
- return channel === "release" && rematerializeOnResume ? match[1] : version;
702
- }
703
- export function resolveRecoveredCandidateVersion({ artifactVersion, publicationVersion, channel, rematerializeOnResume = false, targetRef = "", candidateRef = "" } = {}) {
704
- const version = String(artifactVersion || "").trim(), target = String(targetRef || "").replace(/^refs\/heads\//u, ""), candidate = String(candidateRef || "").replace(/^refs\/heads\//u, "");
705
- if (channel !== "release" || !rematerializeOnResume) return version;
706
- const prefix = `publish-gate/${target}/`;
707
- if (!target || !candidate.startsWith(prefix)) throw new Error(`stable recovery candidate ref must descend from ${prefix || "publish-gate/<target>/"}`);
708
- const candidateVersion = candidate.slice(prefix.length);
709
- if (!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(candidateVersion)) throw new Error(`stable recovery candidate ref must bind an exact alpha version, got ${candidateVersion || "<empty>"}`);
710
- if (candidateVersion.replace(/-alpha\.\d+$/u, "") !== publicationVersion) throw new Error(`stable recovery candidate ${candidateVersion} does not match publication ${publicationVersion || "<empty>"}`);
711
- if ((version.match(/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u)?.[1] || "") !== publicationVersion) throw new Error(`recovered npm artifact version ${version || "<empty>"} does not match publication ${publicationVersion}`);
712
- return candidateVersion;
713
- }
714
700
  export function createRecoveredPublication({ downloads, bundleRoot, repository, passport, candidateRuntimeSha, publishArtifactKind, publishPackageMain, releasePatterns, platformManifests, channel, targetRef = "", candidateRef = "", rematerializeOnResume = false }) {
715
701
  const allFiles = downloads.flatMap((download) => download.files.map((file) => ({ path: path.relative(bundleRoot, file.absolutePath).split(path.sep).join("/"),
716
702
  size: file.size, sha256: file.sha256.replace(/^sha256:/, ""), absolutePath: file.absolutePath })))
@@ -718,6 +704,13 @@ export function createRecoveredPublication({ downloads, bundleRoot, repository,
718
704
  const kind = String(publishArtifactKind || "npm");
719
705
  const releaseMatchers = splitPatterns(releasePatterns).map(patternMatcher);
720
706
  const releaseAssets = allFiles.filter((file) => releaseMatchers.some((matcher) => matcher.test(path.basename(file.path))));
707
+ if (kind === "oci") {
708
+ createRecoveredPublicationCandidate({ allFiles, repository, passport, candidateRuntimeSha });
709
+ const sealed = resolveOciCandidate({ payloadRoot: bundleRoot, passport });
710
+ return { manifest: sealed.manifest, bundleRoot: sealed.root, npmArtifacts: [], allFiles, releaseAssets,
711
+ version: sealed.manifest.version, candidateVersion: sealed.manifest.version,
712
+ publishRequiredArtifacts: sealed.requiredArtifacts };
713
+ }
721
714
  if (kind !== "npm") {
722
715
  createRecoveredPublicationCandidate({ allFiles, repository, passport, candidateRuntimeSha });
723
716
  const version = String(passport.target?.version || "").trim();
@@ -1019,7 +1012,7 @@ export async function resumeFromCandidateRun({
1019
1012
  npmTarballs: tarballs,
1020
1013
  releaseAssets: publication.releaseAssets.map((asset) => outputPath(asset.absolutePath)),
1021
1014
  publishRequiredArtifacts: outputPath(requiredArtifactsPath),
1022
- sealedBundleRoot: publication.manifest ? outputPath(bundleRoot) : "",
1015
+ sealedBundleRoot: publication.manifest ? outputPath(publication.bundleRoot || bundleRoot) : "",
1023
1016
  sealedBundleManifest: publication.manifest ? outputPath(sealedManifestPath) : "",
1024
1017
  recoveryReceipt: outputPath(recoveryReceiptPath),
1025
1018
  stageCapsules: stageCapsuleFile ? outputPath(stageCapsuleFile.absolutePath) : "",
@@ -206,6 +206,7 @@ export function nodeApiMeta(exportName) {
206
206
  "./publication-artifact": { group: "reusable-build", summary: "Publication artifact manifest, source bundle, and publication passport APIs." },
207
207
  "./publication-package": { group: "reusable-build", summary: "Publication npm package synthesis APIs for Buildchain-managed paper release presets." },
208
208
  "./publication-reproducibility": { group: "reusable-build", summary: "Two-clean-build publication byte reproducibility receipt APIs." },
209
+ "./oci-publication": { group: "reusable-build", summary: "Sealed OCI image family verification, provenance, and smoke evidence APIs." },
209
210
  "./publication-sealed-bundle": { group: "reusable-build", summary: "Build-once publication bundle manifests and exact-byte resume verification APIs." },
210
211
  "./paper": { group: "reusable-build", summary: "Unified Paper work, fleet, scaffold, preflight, npm bootstrap, build, Alpha, status, and resume planning APIs." },
211
212
  "./publication-authority": { group: "release-passport-trust", summary: "Sealed publication authority registry, runner provenance, control-plane audit, admission, and independent verification APIs." },
@@ -65,7 +65,7 @@ export function resolveV4ProductPublicationIntent() {
65
65
  const sourceSha = env("BUILDCHAIN_SOURCE_SHA", true);
66
66
  const manifestPath = env(
67
67
  "BUILDCHAIN_SEALED_BUNDLE_MANIFEST",
68
- artifactKind === "npm",
68
+ artifactKind !== "custom",
69
69
  );
70
70
  const manifest = manifestPath ? readJson(manifestPath) : null;
71
71
  const requiredArtifacts = readJson(
@@ -306,6 +306,7 @@ async function materializeProductPublicationIntent({
306
306
  BUILDCHAIN_SEALED_BUNDLE_MANIFEST: candidate.paths.sealedBundleManifest,
307
307
  BUILDCHAIN_REQUIRED_ARTIFACTS_PATH:
308
308
  candidate.paths.publishRequiredArtifacts,
309
+ BUILDCHAIN_PUBLISH_ARTIFACT_KIND: inputs["publish-artifact-kind"] || "npm",
309
310
  BUILDCHAIN_PUBLISH_PACKAGE_MAIN: inputs["publish-package-main"] || "",
310
311
  BUILDCHAIN_PUBLISH_DIST_TAG: inputs["publish-dist-tag"] || "",
311
312
  BUILDCHAIN_PRODUCT_PUBLICATION_INTENT_PATH: intentPath,
@@ -449,6 +450,7 @@ async function executeReleasePromotion(request, admission) {
449
450
  "candidate-build-summary-path": candidate.paths.buildSummary,
450
451
  "stage-capsules-path": candidate.paths.stageCapsules,
451
452
  "publication-qualification-path": candidate.paths.publicationQualification,
453
+ "publish-artifact-kind": payload.inputs["publish-artifact-kind"] || "npm",
452
454
  "sealed-bundle-root": candidate.paths.sealedBundleRoot,
453
455
  "sealed-bundle-manifest": candidate.paths.sealedBundleManifest,
454
456
  "required-artifacts-path": candidate.paths.publishRequiredArtifacts,