@kungfu-tech/buildchain 4.0.2-alpha.5 → 4.0.2-alpha.50
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.
- package/AGENTS.md +10 -1
- package/CONTRIBUTING.md +24 -0
- package/README.md +2 -2
- package/actions/promote-buildchain-ref/README.md +1 -1
- package/actions/v4-release-candidate-promote/README.md +28 -0
- package/architecture/agent-change-map.md +70 -9
- package/architecture/ci-lane-change-budget.json +384 -0
- package/architecture/internal-capabilities.json +56 -9
- package/architecture/maintainability-debt.json +162 -107
- package/architecture/maintainability-policy.json +34 -30
- package/architecture/release-tail-contract-inventory.json +6 -6
- package/architecture/v4-architecture-constitution.md +23 -20
- package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
- package/architecture/v4-delivery-authority-parity.json +2 -2
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
- package/architecture/v4-floating-consumer-policy.json +2 -2
- package/architecture/v4-next-development-parity.json +5 -5
- package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
- package/architecture/v4-platform-stage-checkpoints.json +1 -1
- package/architecture/v4-provider-operation-journal-contract.json +5 -2
- package/architecture/v4-publication-facade-patches.json +64 -0
- package/architecture/v4-release-activation-shadow-domain.json +5 -2
- package/architecture/v4-release-topology.json +198 -38
- package/architecture/v4-rust-wasm-production-authority.json +58 -0
- package/architecture/v4-stable-publication-fence.json +5 -2
- package/architecture/v4-stage-capsule-qualification.json +2 -2
- package/architecture/v4-tail-reseal-parity.json +8 -6
- package/architecture/v4-universal-workflow-bootstrap.json +176 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/architecture/workflow-taxonomy.json +1462 -0
- package/bin/buildchain.mjs +7 -7
- package/contracts/buildchain-v2-residuals-v1.json +57 -48
- package/contracts/dev-delivery-authority-v2.schema.json +16 -2
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
- package/dist/site/buildchain-contract.json +93 -48
- package/dist/site/buildchain-site.json +276 -72
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/controller-registry.json +50 -13
- package/dist/site/kfd-claims.json +2610 -983
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +14 -14
- package/dist/site/node-api-registry.json +273 -127
- package/dist/site/page-registry.json +257 -53
- package/dist/site/public-surface-audit.json +3239 -1232
- package/dist/site/publication-authority-registry.json +1135 -363
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
- package/dist/site/site-manifest.json +18 -18
- package/dist/site/workflow-registry.json +5097 -1659
- package/docs/MAP.md +2 -0
- package/docs/auditable-demo.md +1 -1
- package/docs/cli.md +21 -8
- package/docs/dev-delivery-warrant.md +1 -1
- package/docs/engineering-housekeeper.md +3 -3
- package/docs/kfd-support.md +26 -2
- package/docs/next-development-transition.md +2 -2
- package/docs/node-api-reference.md +216 -205
- package/docs/oci-publication.md +160 -0
- package/docs/publication-authority.md +2 -2
- package/docs/publish-transaction.md +36 -3
- package/docs/release-candidate.md +3 -3
- package/docs/release-flow.md +67 -19
- package/docs/release-governance.md +8 -8
- package/docs/release-passport.md +27 -2
- package/docs/reusable-build-surface.md +16 -0
- package/docs/v4-adopter-delivery.md +2 -2
- package/docs/v4-production-release.md +71 -8
- package/docs/v4-rust-wasm-production-authority.md +58 -0
- package/docs/v4-stage-capsule.md +2 -2
- package/docs/v4-tail-reseal.md +1 -1
- package/docs/workflow-catalog.md +155 -0
- package/docs/workflow-path-migration.md +109 -0
- package/package.json +12 -7
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/buildchain-contract.js +1 -1
- package/packages/core/buildchain-kfd-claims.js +2 -2
- package/packages/core/buildchain-publication-authority.js +43 -26
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/controller-evidence.js +4 -1
- package/packages/core/dev-delivery-candidate-identity.js +40 -6
- package/packages/core/dev-delivery-warrant-state.js +3 -4
- package/packages/core/dev-delivery-warrant.js +12 -2
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/kfd-adopter-manifest.js +46 -47
- package/packages/core/next-development-projection.js +2 -2
- package/packages/core/next-development-transition.js +5 -6
- package/packages/core/oci-compose-qualification.js +140 -0
- package/packages/core/oci-publication-bundle.js +281 -0
- package/packages/core/oci-publication-graph.js +206 -0
- package/packages/core/paper-agent-entry.js +30 -4
- package/packages/core/paper-fleet.js +1 -1
- package/packages/core/paper-runtime-channels.js +348 -0
- package/packages/core/paper-scaffold-content.js +10 -0
- package/packages/core/paper.js +37 -71
- package/packages/core/publication-sealed-bundle.js +78 -13
- package/packages/core/release-line-dry-run.js +1 -1
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +5 -4
- package/packages/core/release-tail-product-capabilities.js +38 -0
- package/packages/core/release-tail-provider-plane.js +68 -1048
- package/packages/core/v4-canonical-contracts.js +15 -75
- package/packages/core/v4-domain-wasm-artifact.js +7 -0
- package/packages/core/v4-domain-wasm.js +216 -0
- package/packages/core/v4-floating-consumer-evidence.js +1 -1
- package/packages/core/v4-floating-consumer-policy.js +1 -1
- package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
- package/packages/core/v4-product-publication.js +59 -0
- package/packages/core/v4-protected-publication-source.js +19 -21
- package/packages/core/v4-provider-operation-journal.js +18 -487
- package/packages/core/v4-provider-readback-idempotency.js +12 -97
- package/packages/core/v4-release-activation-shadow.js +13 -500
- package/packages/core/v4-release-invocation.js +28 -389
- package/packages/core/v4-stable-publication-fence.js +13 -342
- package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
- package/scripts/binary-publication-evidence.mjs +135 -0
- package/scripts/build-contract-core.mjs +1 -14
- package/scripts/build-v4-domain-wasm.mjs +189 -0
- package/scripts/check-action-bundles.mjs +12 -2
- package/scripts/check-ci-lane-change-budget.mjs +4 -1
- package/scripts/check-core-mechanism-inventory.mjs +4 -3
- package/scripts/check-inventory.mjs +35 -44
- package/scripts/check-maintainability.mjs +16 -113
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
- package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
- package/scripts/check-v4-release-topology.mjs +236 -50
- package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
- package/scripts/check-workflow-taxonomy.mjs +6 -0
- package/scripts/copy-v4-domain-wasm.mjs +14 -0
- package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
- package/scripts/dev-delivery-authority.mjs +8 -8
- package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
- package/scripts/dev-delivery-warrant-options.mjs +14 -9
- package/scripts/dev-delivery-warrant-store.mjs +73 -7
- package/scripts/dev-delivery-warrant-transition.mjs +109 -0
- package/scripts/dev-delivery-warrant.mjs +76 -99
- package/scripts/dev-pr-auto-merge.mjs +15 -15
- package/scripts/generate-channel-build-workflow.mjs +7 -2
- package/scripts/generate-channel-promotion-workflow.mjs +10 -25
- package/scripts/generate-site-bundle.mjs +11 -11
- package/scripts/generate-v4-universal-workflow-facades.mjs +497 -0
- package/scripts/generate-workflow-taxonomy.mjs +25 -0
- package/scripts/github-output.mjs +31 -0
- package/scripts/maintainability-metrics.mjs +16 -5
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/next-development-review.mjs +307 -0
- package/scripts/oci-compose-preview.mjs +250 -0
- package/scripts/paper-work-fleet-cli.mjs +2 -0
- package/scripts/publication-candidate-kind.mjs +93 -0
- package/scripts/publication-candidate-sealer.mjs +16 -1
- package/scripts/release-asset-client.mjs +91 -0
- package/scripts/release-candidate-resolver.mjs +23 -35
- package/scripts/resume-from-candidate-run.mjs +11 -18
- package/scripts/site-capability-metadata.mjs +1 -0
- package/scripts/source-verification-evidence.mjs +344 -0
- package/scripts/stable-candidate-qualification.mjs +1 -1
- package/scripts/universal-facade-maintainability.mjs +91 -0
- package/scripts/v4-product-publication-intent.mjs +129 -0
- package/scripts/v4-publication-settlement.mjs +211 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +620 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/scripts/verify-version-state-delta.mjs +173 -0
- package/scripts/workflow-taxonomy.mjs +507 -0
- package/templates/native-dev-delivery.yml +4 -7
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ociPublicationTag } from "../packages/core/oci-publication-graph.js";
|
|
4
|
+
import { verifyOciPublicationBundle } from "../packages/core/oci-publication-bundle.js";
|
|
5
|
+
|
|
6
|
+
export function resolveOciCandidate({ payloadRoot, passport }) {
|
|
7
|
+
const matches = [];
|
|
8
|
+
function visit(directory) {
|
|
9
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
10
|
+
if (entry.isSymbolicLink())
|
|
11
|
+
throw new Error("OCI candidate cannot contain symlinks");
|
|
12
|
+
const file = path.join(directory, entry.name);
|
|
13
|
+
if (entry.isDirectory()) visit(file);
|
|
14
|
+
else if (entry.name === "oci-family.json") matches.push(file);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
visit(payloadRoot);
|
|
18
|
+
if (matches.length !== 1)
|
|
19
|
+
throw new Error("OCI candidate requires exactly one sealed image family");
|
|
20
|
+
const manifest = JSON.parse(fs.readFileSync(matches[0]));
|
|
21
|
+
const bundleRoot = path.dirname(matches[0]);
|
|
22
|
+
const verified = verifyOciPublicationBundle({
|
|
23
|
+
bundleRoot,
|
|
24
|
+
manifest,
|
|
25
|
+
repository: passport.repository,
|
|
26
|
+
sourceSha: passport.source.headSha,
|
|
27
|
+
version: passport.target.version,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
root: bundleRoot,
|
|
31
|
+
manifest: verified.manifest,
|
|
32
|
+
requiredArtifacts: manifest.images.map((image) => ({
|
|
33
|
+
kind: "oci",
|
|
34
|
+
name: image.repository,
|
|
35
|
+
ref: ociPublicationTag(image, manifest.version),
|
|
36
|
+
digest: image.digest,
|
|
37
|
+
platform: image.platform,
|
|
38
|
+
required: true,
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveRecoveredPublicationVersion({
|
|
44
|
+
artifactVersion,
|
|
45
|
+
channel,
|
|
46
|
+
rematerializeOnResume = false,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const version = String(artifactVersion || "").trim(),
|
|
49
|
+
match = version.match(
|
|
50
|
+
/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
|
|
51
|
+
);
|
|
52
|
+
if (!match)
|
|
53
|
+
throw new Error(
|
|
54
|
+
`recovered npm artifact has invalid publication version: ${version || "<empty>"}`,
|
|
55
|
+
);
|
|
56
|
+
return channel === "release" && rematerializeOnResume ? match[1] : version;
|
|
57
|
+
}
|
|
58
|
+
export function resolveRecoveredCandidateVersion({
|
|
59
|
+
artifactVersion,
|
|
60
|
+
publicationVersion,
|
|
61
|
+
channel,
|
|
62
|
+
rematerializeOnResume = false,
|
|
63
|
+
targetRef = "",
|
|
64
|
+
candidateRef = "",
|
|
65
|
+
} = {}) {
|
|
66
|
+
const version = String(artifactVersion || "").trim(),
|
|
67
|
+
target = String(targetRef || "").replace(/^refs\/heads\//u, ""),
|
|
68
|
+
candidate = String(candidateRef || "").replace(/^refs\/heads\//u, "");
|
|
69
|
+
if (channel !== "release" || !rematerializeOnResume) return version;
|
|
70
|
+
const prefix = `publish-gate/${target}/`;
|
|
71
|
+
if (!target || !candidate.startsWith(prefix))
|
|
72
|
+
throw new Error(
|
|
73
|
+
`stable recovery candidate ref must descend from ${prefix || "publish-gate/<target>/"}`,
|
|
74
|
+
);
|
|
75
|
+
const candidateVersion = candidate.slice(prefix.length);
|
|
76
|
+
if (!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(candidateVersion))
|
|
77
|
+
throw new Error(
|
|
78
|
+
`stable recovery candidate ref must bind an exact alpha version, got ${candidateVersion || "<empty>"}`,
|
|
79
|
+
);
|
|
80
|
+
if (candidateVersion.replace(/-alpha\.\d+$/u, "") !== publicationVersion)
|
|
81
|
+
throw new Error(
|
|
82
|
+
`stable recovery candidate ${candidateVersion} does not match publication ${publicationVersion || "<empty>"}`,
|
|
83
|
+
);
|
|
84
|
+
if (
|
|
85
|
+
(version.match(
|
|
86
|
+
/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
|
|
87
|
+
)?.[1] || "") !== publicationVersion
|
|
88
|
+
)
|
|
89
|
+
throw new Error(
|
|
90
|
+
`recovered npm artifact version ${version || "<empty>"} does not match publication ${publicationVersion}`,
|
|
91
|
+
);
|
|
92
|
+
return candidateVersion;
|
|
93
|
+
}
|
|
@@ -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";
|
|
@@ -69,7 +70,7 @@ export function createResolvedPublicationSealedBundle({
|
|
|
69
70
|
});
|
|
70
71
|
const main = resolvedNpmArtifacts.find((entry) => entry.metadata.role === "main")
|
|
71
72
|
|| (resolvedNpmArtifacts.length === 1 ? resolvedNpmArtifacts[0] : undefined);
|
|
72
|
-
if (!main) throw new Error("candidate npm payload set has no unique main package tarball");
|
|
73
|
+
if (!main || resolvedNpmArtifacts.filter((entry) => entry.metadata.role === "main").length > 1) throw new Error("candidate npm payload set has no unique main package tarball");
|
|
73
74
|
const selectedReleaseAssets = (releaseAssetPaths.length > 0
|
|
74
75
|
? releaseAssetPaths
|
|
75
76
|
: resolvedNpmArtifacts.map((entry) => entry.file.absolutePath))
|
|
@@ -88,6 +89,12 @@ export function createResolvedPublicationSealedBundle({
|
|
|
88
89
|
releaseCandidateRoot: `sha256:${normalizeSha256(releaseCandidateRoot, "releaseCandidateRoot")}`,
|
|
89
90
|
files: files.map(({ path: filePath, size, sha256 }) => ({ path: filePath, size, sha256 })),
|
|
90
91
|
};
|
|
92
|
+
if (resolvedNpmArtifacts.length > 1) {
|
|
93
|
+
candidatePayload.npmPackages = resolvedNpmArtifacts.map(({file, metadata}) => ({
|
|
94
|
+
name: metadata.name, version: metadata.ref, role: metadata.role,
|
|
95
|
+
path: file.path, size: file.size, sha256: file.sha256, integrity: metadata.integrity,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
91
98
|
const candidate = {
|
|
92
99
|
...candidatePayload,
|
|
93
100
|
candidateDigest: publicationArtifactCandidateDigest(candidatePayload),
|
|
@@ -102,3 +109,11 @@ export function createResolvedPublicationSealedBundle({
|
|
|
102
109
|
});
|
|
103
110
|
return { root: resolvedRoot, manifest, npmArtifacts: resolvedNpmArtifacts, files };
|
|
104
111
|
}
|
|
112
|
+
|
|
113
|
+
export function resolveCandidatePublicationBundle({ kind, payloadDir, passport, runtimeSha, npmArtifacts, releaseAssetPaths }) {
|
|
114
|
+
if (kind === "oci") return resolveOciCandidate({ payloadRoot: payloadDir, passport });
|
|
115
|
+
if (kind !== "npm") return undefined;
|
|
116
|
+
return createResolvedPublicationSealedBundle({ bundleRoot: payloadDir, repository: passport.repository,
|
|
117
|
+
sourceSha: passport.source?.headSha, sourceTreeSha: passport.source?.treeHash,
|
|
118
|
+
runtimeSha, releaseCandidateRoot: passport.candidateHash, npmArtifacts, releaseAssetPaths });
|
|
119
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
reconcileImmutableReleaseAssets,
|
|
6
|
+
uniqueReleaseAssets,
|
|
7
|
+
} from "../actions/promote-buildchain-ref/reuse-complete-release.js";
|
|
8
|
+
|
|
9
|
+
export function releaseAssetClient(
|
|
10
|
+
repository,
|
|
11
|
+
{ execute = execFileSync, token } = {},
|
|
12
|
+
) {
|
|
13
|
+
if (!/^[\w.-]+\/[\w.-]+$/u.test(repository))
|
|
14
|
+
throw new Error("exact repository required");
|
|
15
|
+
const [owner, repo] = repository.split("/");
|
|
16
|
+
const raw = (endpoint, args = []) =>
|
|
17
|
+
execute("gh", ["api", endpoint, ...args], {
|
|
18
|
+
maxBuffer: 128 * 1024 * 1024,
|
|
19
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20
|
+
...(token ? { env: { ...process.env, GH_TOKEN: token } } : {}),
|
|
21
|
+
});
|
|
22
|
+
const json = (endpoint) => JSON.parse(raw(endpoint));
|
|
23
|
+
const assetBytes = (asset) =>
|
|
24
|
+
raw(`repos/${repository}/releases/assets/${asset.id}`, [
|
|
25
|
+
"-H",
|
|
26
|
+
"Accept: application/octet-stream",
|
|
27
|
+
]);
|
|
28
|
+
return {
|
|
29
|
+
json,
|
|
30
|
+
assetBytes,
|
|
31
|
+
release: (tag) =>
|
|
32
|
+
json(`repos/${repository}/releases/tags/${encodeURIComponent(tag)}`),
|
|
33
|
+
async publish(release, files) {
|
|
34
|
+
const octokit = {
|
|
35
|
+
rest: {
|
|
36
|
+
repos: {
|
|
37
|
+
getReleaseAsset: async ({ asset_id }) => ({
|
|
38
|
+
data: assetBytes({ id: asset_id }),
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const result = await reconcileImmutableReleaseAssets({
|
|
44
|
+
octokit,
|
|
45
|
+
owner,
|
|
46
|
+
repo,
|
|
47
|
+
releaseId: release.id,
|
|
48
|
+
remoteAssets: uniqueReleaseAssets(release.assets || []),
|
|
49
|
+
assetPaths: files,
|
|
50
|
+
uploadReleaseAsset: async ({ name }) => {
|
|
51
|
+
const file = files.find((file) => path.basename(file) === name);
|
|
52
|
+
return JSON.parse(
|
|
53
|
+
raw(
|
|
54
|
+
`https://uploads.github.com/repos/${repository}/releases/${release.id}/assets?name=${encodeURIComponent(name)}`,
|
|
55
|
+
[
|
|
56
|
+
"--method",
|
|
57
|
+
"POST",
|
|
58
|
+
"--input",
|
|
59
|
+
file,
|
|
60
|
+
"-H",
|
|
61
|
+
"Content-Type: application/octet-stream",
|
|
62
|
+
],
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// A successful upload response alone is not the provider readback.
|
|
68
|
+
const observed = this.release(release.tag_name);
|
|
69
|
+
await reconcileImmutableReleaseAssets({
|
|
70
|
+
octokit,
|
|
71
|
+
owner,
|
|
72
|
+
repo,
|
|
73
|
+
releaseId: release.id,
|
|
74
|
+
remoteAssets: uniqueReleaseAssets(observed.assets || []),
|
|
75
|
+
assetPaths: files,
|
|
76
|
+
uploadReleaseAsset: async () => {
|
|
77
|
+
throw new Error("uploaded release asset missing on readback");
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return result.map(({ action, name, digest }) => ({
|
|
81
|
+
action,
|
|
82
|
+
name,
|
|
83
|
+
digest,
|
|
84
|
+
}));
|
|
85
|
+
},
|
|
86
|
+
write(file, value) {
|
|
87
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
88
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -7,13 +7,12 @@ 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 {
|
|
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";
|
|
14
14
|
|
|
15
|
-
const DEFAULT_WORKFLOW_FILE = "build-
|
|
16
|
-
|
|
15
|
+
const DEFAULT_WORKFLOW_FILE = "self-build-fixture.yml";
|
|
17
16
|
function env(name, fallback = "") {
|
|
18
17
|
return process.env[name] || fallback;
|
|
19
18
|
}
|
|
@@ -21,7 +20,6 @@ function env(name, fallback = "") {
|
|
|
21
20
|
export function releaseCandidateDownloadEnabled(value = "true") {
|
|
22
21
|
return String(value || "true").trim().toLowerCase() !== "false";
|
|
23
22
|
}
|
|
24
|
-
|
|
25
23
|
function splitRepository(repository) {
|
|
26
24
|
const match = String(repository || "").trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
27
25
|
if (!match) {
|
|
@@ -44,7 +42,9 @@ function assertSha(value, label = "sha") {
|
|
|
44
42
|
|
|
45
43
|
export const releaseCandidateRuntimeSha = (passport) =>
|
|
46
44
|
assertSha(passport?.buildchain?.sha, "release candidate Passport Buildchain runtime SHA").toLowerCase();
|
|
47
|
-
|
|
45
|
+
export const resolveFreshPublicationVersion = ({ sealedBundle, candidateVersion = "" } = {}) =>
|
|
46
|
+
String(sealedBundle?.manifest?.npm?.version || candidateVersion || "").trim();
|
|
47
|
+
const optionalText = (value) => String(value || "");
|
|
48
48
|
function githubHeaders(token) {
|
|
49
49
|
const headers = {
|
|
50
50
|
accept: "application/vnd.github+json",
|
|
@@ -263,17 +263,17 @@ export function selectReleaseAssetPaths({
|
|
|
263
263
|
`release candidate payload patterns matched no files: ${splitPatterns(patterns).join(", ")}`,
|
|
264
264
|
);
|
|
265
265
|
}
|
|
266
|
-
const
|
|
266
|
+
const selectedByBasename = new Map();
|
|
267
|
+
const digest = (filePath) => digestFileSync(filePath, "sha256", "hex");
|
|
267
268
|
for (const filePath of files) {
|
|
268
269
|
const basename = path.basename(filePath);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
basenames.add(basename);
|
|
270
|
+
const existing = selectedByBasename.get(basename);
|
|
271
|
+
if (!existing) selectedByBasename.set(basename, filePath);
|
|
272
|
+
else if (digest(existing) !== digest(filePath))
|
|
273
|
+
throw new Error(`release candidate public asset basename is not unique: ${basename}`);
|
|
275
274
|
}
|
|
276
|
-
|
|
275
|
+
// Repeated paths are safe only when they name the same immutable bytes.
|
|
276
|
+
return [...selectedByBasename.values()];
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
function packageNameFromArtifactPath(filePath) {
|
|
@@ -725,34 +725,21 @@ 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
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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;
|
|
743
|
-
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8")));
|
|
744
|
-
const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
|
|
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 }) })) });
|
|
732
|
+
const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8"))), publicationVersion = resolveFreshPublicationVersion({ sealedBundle, candidateVersion: passport.target?.version });
|
|
733
|
+
const generatedRequiredArtifacts = sealedBundle?.requiredArtifacts || generatePublishRequiredArtifacts({
|
|
745
734
|
manifests,
|
|
746
|
-
version:
|
|
735
|
+
version: publicationVersion,
|
|
747
736
|
kind: publishArtifactKind,
|
|
748
737
|
tarballPaths: npmTarballPaths,
|
|
749
738
|
mainPackage: publishPackageMain,
|
|
750
739
|
});
|
|
751
740
|
const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
|
|
752
741
|
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(generatedRequiredArtifacts, null, 2)}\n`);
|
|
753
|
-
const sealedBundleManifestPath = sealedBundle
|
|
754
|
-
? path.join(resolvedOutput, "sealed-bundle.json")
|
|
755
|
-
: "";
|
|
742
|
+
const sealedBundleManifestPath = sealedBundle ? path.join(resolvedOutput, "sealed-bundle.json") : "";
|
|
756
743
|
if (sealedBundleManifestPath) {
|
|
757
744
|
fs.writeFileSync(sealedBundleManifestPath, `${JSON.stringify(sealedBundle.manifest, null, 2)}\n`);
|
|
758
745
|
}
|
|
@@ -772,6 +759,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
772
759
|
sealedBundleManifest: sealedBundleManifestPath ? outputPath(sealedBundleManifestPath) : "",
|
|
773
760
|
},
|
|
774
761
|
version: passport.target?.version || "",
|
|
762
|
+
publicationVersion,
|
|
775
763
|
candidateHash: passport.candidateHash || "",
|
|
776
764
|
payloadCount: payloadArtifacts.length,
|
|
777
765
|
platformManifestCount: platformManifestPaths.length,
|
|
@@ -811,6 +799,7 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
811
799
|
"release-candidate-publication-qualification-path": result.paths?.publicationQualification || "",
|
|
812
800
|
"release-candidate-publication-qualification-root": result.publicationQualificationRoot || "",
|
|
813
801
|
"release-candidate-version": result.version || "",
|
|
802
|
+
"release-candidate-publication-version": optionalText(result.publicationVersion),
|
|
814
803
|
"release-candidate-source-sha": result.artifacts?.sourceSha || "",
|
|
815
804
|
"release-candidate-artifact": result.artifacts?.passport || "",
|
|
816
805
|
"release-candidate-build-summary-artifact": result.artifacts?.summary || "",
|
|
@@ -843,7 +832,6 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
843
832
|
console.log(JSON.stringify(result, null, 2));
|
|
844
833
|
return result;
|
|
845
834
|
}
|
|
846
|
-
|
|
847
835
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
848
836
|
try {
|
|
849
837
|
await resolveReleaseCandidateArtifactsCli();
|
|
@@ -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();
|
|
@@ -821,7 +814,7 @@ export async function resolveRecoveryTransaction({
|
|
|
821
814
|
transactionId,
|
|
822
815
|
publicationVersion,
|
|
823
816
|
}) {
|
|
824
|
-
let transaction = publicationVersion
|
|
817
|
+
let transaction = transactionId && publicationVersion
|
|
825
818
|
? await readExistingTransaction({
|
|
826
819
|
repoInfo,
|
|
827
820
|
apiUrl,
|
|
@@ -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." },
|