@kungfu-tech/buildchain 4.0.0 → 4.0.1-alpha.2
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 +13 -5
- package/actions/promote-buildchain-ref/README.md +11 -6
- package/architecture/internal-capabilities.json +11 -2
- package/architecture/maintainability-policy.json +149 -17
- package/architecture/v4-floating-consumer-policy.json +75 -0
- package/architecture/v4-floating-consumer-policy.md +32 -0
- package/architecture/v4-runtime-ref-resume-authority.json +64 -0
- package/architecture/v4-stage-capsule-qualification.json +2 -2
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +53 -0
- package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +105 -0
- package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
- package/dist/site/buildchain-contract.json +101 -31
- package/dist/site/buildchain-site.json +73 -27
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/controller-registry.json +62 -6
- package/dist/site/kfd-claims.json +68 -13
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +799 -127
- package/dist/site/page-registry.json +63 -17
- package/dist/site/public-surface-audit.json +49 -17
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +46 -18
- package/docs/MAP.md +2 -0
- package/docs/dev-delivery-warrant.md +19 -1
- package/docs/lifecycle-protocol.md +41 -0
- package/docs/node-api-reference.md +207 -134
- package/docs/reusable-build-surface.md +41 -0
- package/docs/runtime-train-validation.md +10 -0
- package/docs/v4-runtime-ref-resume-authority.md +69 -0
- package/docs/versioning.md +1 -0
- package/package.json +6 -4
- package/packages/core/artifact-signing.js +61 -0
- package/packages/core/buildchain-config.js +66 -1
- package/packages/core/dev-delivery-proof.js +37 -3
- package/packages/core/dev-delivery-warrant.js +3 -3
- package/packages/core/index.js +2 -0
- package/packages/core/publication-authority.js +5 -5
- package/packages/core/release-candidate.js +79 -19
- package/packages/core/release-passport.js +239 -33
- package/packages/core/v4-floating-consumer-evidence.js +324 -0
- package/packages/core/v4-floating-consumer-policy.js +446 -0
- package/packages/core/v4-floating-consumer-release-passport.js +126 -0
- package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
- package/packages/core/v4-runtime-selector-persistence.js +228 -0
- package/packages/core/workflow-yaml-contract.js +48 -0
- package/scripts/audit-publication-control-plane.mjs +31 -31
- package/scripts/check-inventory.mjs +1 -1
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +198 -0
- package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
- package/scripts/dev-delivery-source-proof-reuse.mjs +631 -0
- package/scripts/dev-pr-auto-merge.mjs +37 -48
- package/scripts/dev-pr-delivery-warrant.mjs +205 -2
- package/scripts/dev-pr-prequeue-guard.mjs +399 -0
- package/scripts/ensure-github-release.mjs +3 -3
- package/scripts/generate-channel-build-workflow.mjs +3 -0
- package/scripts/generate-channel-promotion-workflow.mjs +112 -12
- package/scripts/generate-release-candidate-passport.mjs +41 -33
- package/scripts/init-repo.mjs +5 -1
- package/scripts/inspect-artifact-signing-requests.mjs +6 -0
- package/scripts/npm-publish-transaction.mjs +101 -89
- package/scripts/resume-from-candidate-run.mjs +11 -14
- package/scripts/seal-artifact-signing-requests.mjs +6 -0
- package/scripts/site-capability-metadata.mjs +2 -0
- package/scripts/v4-consumer-policy.mjs +231 -0
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
validateReleaseCandidatePassport,
|
|
8
|
-
} from "../packages/core/release-candidate.js";
|
|
6
|
+
import { createReleaseCandidatePassport, validateReleaseCandidatePassport } from "../packages/core/release-candidate.js";
|
|
7
|
+
import { scanV4FloatingConsumerPolicy, v4ConsumerPolicyScannerRoot } from "../packages/core/v4-floating-consumer-policy.js";
|
|
9
8
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
10
|
+
const env = (name, fallback = "") => process.env[name] || fallback;
|
|
11
|
+
const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
14
12
|
|
|
15
|
-
function
|
|
16
|
-
return JSON.parse(
|
|
13
|
+
export function resolveLegacyConsumerPolicyReceipt(options = {}) {
|
|
14
|
+
if (options.json) return { receipt: JSON.parse(options.json), receiptRoot: options.receiptRoot };
|
|
15
|
+
if (options.repository !== "kungfu-systems/buildchain" || options.targetChannel !== "alpha" || options.workflowShellRef !== "v4-alpha" || options.runtimeOverride !== true) return undefined;
|
|
16
|
+
if (!/^[0-9a-f]{40}$/u.test(options.runtimeRef || "") || options.runtimeRef !== options.runtimeSha || options.sourceTreeHash !== options.runtimeTreeHash()) return undefined;
|
|
17
|
+
const root = path.resolve(options.root || ".buildchain/runtime");
|
|
18
|
+
const alphaLock = readJsonFile(path.join(root, ".buildchain/alpha-contract-lock.json"));
|
|
19
|
+
const result = scanV4FloatingConsumerPolicy({
|
|
20
|
+
root, repository: options.repository, sourceSha: options.sourceSha,
|
|
21
|
+
invokedWorkflow: options.invokedWorkflow || ".github/workflows/build.yml", invocationSourcePath: options.invocationSourcePath,
|
|
22
|
+
expectedInvocationChannel: "alpha", resolvedWorkflowSha: alphaLock.buildchain?.resolvedSha,
|
|
23
|
+
resolvedRuntimeSha: options.runtimeSha, scannerRoot: v4ConsumerPolicyScannerRoot(),
|
|
24
|
+
policy: readJsonFile(path.resolve(import.meta.dirname, "../architecture/v4-floating-consumer-policy.json")),
|
|
25
|
+
});
|
|
26
|
+
if (!result.ok) throw new Error(`legacy floating-shell policy receipt invalid: ${result.failures.map(({ code }) => code).join(", ")}`);
|
|
27
|
+
return { receipt: result.receipt, receiptRoot: result.receiptRoot };
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
export function generateReleaseCandidatePassportCli() {
|
|
@@ -21,23 +32,28 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
21
32
|
const outputPath = path.resolve(env("BUILDCHAIN_RC_PASSPORT_PATH", ".buildchain/artifacts/release-candidate-passport.json"));
|
|
22
33
|
const buildSummary = readJsonFile(buildSummaryPath);
|
|
23
34
|
const sourceSha = env("BUILDCHAIN_RC_SOURCE_HEAD_SHA", buildSummary.publishSource?.sha || buildSummary.git?.sha || "");
|
|
24
|
-
const version = env(
|
|
25
|
-
"BUILDCHAIN_RC_VERSION",
|
|
26
|
-
buildSummary.publishSource?.consumerVersion || "",
|
|
27
|
-
);
|
|
35
|
+
const version = env("BUILDCHAIN_RC_VERSION", buildSummary.publishSource?.consumerVersion || "");
|
|
28
36
|
const gateAggregateJson = env("BUILDCHAIN_GATE_PROFILE_AGGREGATE_JSON");
|
|
29
37
|
const gateAggregate = gateAggregateJson ? JSON.parse(gateAggregateJson) : undefined;
|
|
30
38
|
const controllerReceiptJson = env("BUILDCHAIN_CONTROLLER_RECEIPT_JSON");
|
|
31
39
|
const controllerReceipts = controllerReceiptJson ? [JSON.parse(controllerReceiptJson)] : [];
|
|
32
40
|
const familyEvidenceJson = env("BUILDCHAIN_RC_FAMILY_EVIDENCE_JSON");
|
|
33
41
|
const familyEvidence = familyEvidenceJson ? JSON.parse(familyEvidenceJson) : undefined;
|
|
42
|
+
const consumerPolicyJson = env("BUILDCHAIN_V4_POLICY_RECEIPT_JSON");
|
|
43
|
+
const consumerPolicyReceipt = resolveLegacyConsumerPolicyReceipt({
|
|
44
|
+
json: consumerPolicyJson, receiptRoot: env("BUILDCHAIN_V4_POLICY_RECEIPT_ROOT"),
|
|
45
|
+
repository: env("GITHUB_REPOSITORY", buildSummary.git?.repository || ""), sourceSha,
|
|
46
|
+
targetChannel: env("BUILDCHAIN_RC_TARGET_CHANNEL", buildSummary.publishSource?.channel || buildSummary.publishGate?.channel || ""),
|
|
47
|
+
runtimeRef: env("BUILDCHAIN_RUNTIME_REF", buildSummary.runtime?.ref || ""), runtimeSha: env("BUILDCHAIN_RUNTIME_SHA", buildSummary.runtime?.sha || ""),
|
|
48
|
+
workflowShellRef: env("BUILDCHAIN_WORKFLOW_SHELL_REF", buildSummary.runtime?.workflowShellRef || ""), runtimeOverride: buildSummary.runtime?.override === true,
|
|
49
|
+
root: ".buildchain/runtime", invocationSourcePath: env("GITHUB_WORKFLOW_REF"), sourceTreeHash: env("BUILDCHAIN_RC_SOURCE_TREE_HASH"),
|
|
50
|
+
runtimeTreeHash: () => execFileSync("git", ["-C", ".buildchain/runtime", "rev-parse", "HEAD^{tree}"], { encoding: "utf8" }).trim(),
|
|
51
|
+
});
|
|
34
52
|
const passport = createReleaseCandidatePassport({
|
|
35
53
|
repository: env("GITHUB_REPOSITORY", buildSummary.git?.repository || ""),
|
|
36
54
|
pullRequest: {
|
|
37
|
-
number: env("BUILDCHAIN_PULL_REQUEST_NUMBER"),
|
|
38
|
-
|
|
39
|
-
headRef: env("BUILDCHAIN_PULL_REQUEST_HEAD_REF"),
|
|
40
|
-
baseRef: env("BUILDCHAIN_PULL_REQUEST_BASE_REF"),
|
|
55
|
+
number: env("BUILDCHAIN_PULL_REQUEST_NUMBER"), url: env("BUILDCHAIN_PULL_REQUEST_URL"),
|
|
56
|
+
headRef: env("BUILDCHAIN_PULL_REQUEST_HEAD_REF"), baseRef: env("BUILDCHAIN_PULL_REQUEST_BASE_REF"),
|
|
41
57
|
},
|
|
42
58
|
targetChannel: env("BUILDCHAIN_RC_TARGET_CHANNEL", buildSummary.publishSource?.channel || buildSummary.publishGate?.channel || ""),
|
|
43
59
|
version,
|
|
@@ -47,30 +63,21 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
47
63
|
sourceTreeHash: env("BUILDCHAIN_RC_SOURCE_TREE_HASH"),
|
|
48
64
|
buildSummary,
|
|
49
65
|
buildchain: {
|
|
50
|
-
ref: env("BUILDCHAIN_RUNTIME_REF", buildSummary.runtime?.ref || ""),
|
|
51
|
-
|
|
52
|
-
version: env("BUILDCHAIN_RUNTIME_VERSION"),
|
|
53
|
-
workflowShellRef: env("BUILDCHAIN_WORKFLOW_SHELL_REF", buildSummary.runtime?.workflowShellRef || ""),
|
|
66
|
+
ref: env("BUILDCHAIN_RUNTIME_REF", buildSummary.runtime?.ref || ""), sha: env("BUILDCHAIN_RUNTIME_SHA", buildSummary.runtime?.sha || ""),
|
|
67
|
+
version: env("BUILDCHAIN_RUNTIME_VERSION"), workflowShellRef: env("BUILDCHAIN_WORKFLOW_SHELL_REF", buildSummary.runtime?.workflowShellRef || ""),
|
|
54
68
|
},
|
|
55
69
|
gateAggregate,
|
|
56
70
|
familyEvidence,
|
|
71
|
+
consumerPolicyReceipt,
|
|
57
72
|
controllerReceipts,
|
|
58
73
|
workflow: {
|
|
59
|
-
name: env("GITHUB_WORKFLOW"),
|
|
60
|
-
|
|
61
|
-
runAttempt: env("GITHUB_RUN_ATTEMPT", buildSummary.git?.runAttempt || ""),
|
|
62
|
-
url: env("BUILDCHAIN_WORKFLOW_RUN_URL"),
|
|
74
|
+
name: env("GITHUB_WORKFLOW"), runId: env("GITHUB_RUN_ID", buildSummary.git?.runId || ""),
|
|
75
|
+
runAttempt: env("GITHUB_RUN_ATTEMPT", buildSummary.git?.runAttempt || ""), url: env("BUILDCHAIN_WORKFLOW_RUN_URL"),
|
|
63
76
|
},
|
|
64
77
|
});
|
|
65
78
|
const validation = validateReleaseCandidatePassport({
|
|
66
|
-
passport,
|
|
67
|
-
|
|
68
|
-
sourceHeadSha: sourceSha,
|
|
69
|
-
buildSummary,
|
|
70
|
-
});
|
|
71
|
-
if (!validation.ok) {
|
|
72
|
-
throw new Error(`release candidate passport invalid: ${validation.errors.join("; ")}`);
|
|
73
|
-
}
|
|
79
|
+
passport, repository: env("GITHUB_REPOSITORY", buildSummary.git?.repository || ""), sourceHeadSha: sourceSha, buildSummary });
|
|
80
|
+
if (!validation.ok) throw new Error(`release candidate passport invalid: ${validation.errors.join("; ")}`);
|
|
74
81
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
75
82
|
fs.writeFileSync(outputPath, `${JSON.stringify(passport, null, 2)}\n`);
|
|
76
83
|
writeGitHubOutputs({
|
|
@@ -84,6 +91,7 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
84
91
|
platformCount: passport.platformMatrix.length,
|
|
85
92
|
gateProfileEvidence: passport.gateProfileEvidence,
|
|
86
93
|
familyEvidence: passport.familyEvidence,
|
|
94
|
+
consumerPolicy: passport.consumerPolicy,
|
|
87
95
|
controllerReceipts: passport.controllerReceipts || [],
|
|
88
96
|
}),
|
|
89
97
|
});
|
package/scripts/init-repo.mjs
CHANGED
|
@@ -327,7 +327,9 @@ function workflowArtifactPaths(type) {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
function publicationWorkflowYaml() {
|
|
330
|
-
return
|
|
330
|
+
return `# Commit both .buildchain/contract-lock.json and .buildchain/alpha-contract-lock.json.
|
|
331
|
+
# Persist only @v4 or @v4-alpha; pass temporary train/SHA runtimes through workflow_dispatch.
|
|
332
|
+
name: Build
|
|
331
333
|
|
|
332
334
|
on:
|
|
333
335
|
workflow_dispatch:
|
|
@@ -361,6 +363,8 @@ jobs:
|
|
|
361
363
|
function workflowYaml({ type, runnerPreset, artifactName }) {
|
|
362
364
|
return `# Buildchain v4 lifecycle checkpoints are governed by architecture/v4-platform-stage-checkpoints.json.
|
|
363
365
|
# Do not add undeclared runner-only inputs, outputs, environment, or provider effects.
|
|
366
|
+
# Commit both .buildchain/contract-lock.json and .buildchain/alpha-contract-lock.json.
|
|
367
|
+
# Persist only @v4 or @v4-alpha; pass temporary train/SHA runtimes through workflow_dispatch.
|
|
364
368
|
name: Build
|
|
365
369
|
|
|
366
370
|
on:
|
|
@@ -82,6 +82,10 @@ export function inspectArtifactSigningRequests({
|
|
|
82
82
|
if (seen.has(key))
|
|
83
83
|
throw new Error(`duplicate artifact signing request: ${key}`);
|
|
84
84
|
seen.add(key);
|
|
85
|
+
const {
|
|
86
|
+
entitlementsProfile = "none",
|
|
87
|
+
entitlementsPaths = [],
|
|
88
|
+
} = request.signature;
|
|
85
89
|
const item = {
|
|
86
90
|
id: request.artifact.id,
|
|
87
91
|
slug: safeId(request.artifact.id),
|
|
@@ -104,6 +108,8 @@ export function inspectArtifactSigningRequests({
|
|
|
104
108
|
sourceSha: request.source.sha,
|
|
105
109
|
sourceTreeSha: request.source.treeSha,
|
|
106
110
|
transportFormat: request.artifact.transport?.format || "",
|
|
111
|
+
entitlementsProfile,
|
|
112
|
+
entitlementsPaths: entitlementsPaths.join(","),
|
|
107
113
|
};
|
|
108
114
|
if (request.signature.profile === "detached-signature-v1")
|
|
109
115
|
matrices.detached.push(item);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
6
7
|
import { spawnSyncCommand } from "../packages/core/spawn-command.js";
|
|
@@ -105,6 +106,20 @@ function sealedPackResult() {
|
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
function materializedPackResult({ cwd, registry }) {
|
|
110
|
+
const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-npm-pack-"));
|
|
111
|
+
try {
|
|
112
|
+
const pack = parsePackResult(runNpm({
|
|
113
|
+
cwd,
|
|
114
|
+
args: ["pack", "--json", "--pack-destination", temporaryRoot, `--registry=${registry}`],
|
|
115
|
+
}).stdout);
|
|
116
|
+
return { ...pack, tarballPath: path.join(temporaryRoot, pack.filename), temporaryRoot };
|
|
117
|
+
} catch (error) {
|
|
118
|
+
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
function parseNpmView(stdout) {
|
|
109
124
|
const raw = String(stdout || "").trim();
|
|
110
125
|
if (!raw) {
|
|
@@ -188,46 +203,11 @@ export function npmPublishTransaction({
|
|
|
188
203
|
const exactTag = assertPackageVersion({ pkg, expectedVersion });
|
|
189
204
|
const distTag = readEnv("BUILDCHAIN_NPM_DIST_TAG", pkg.version.includes("-") ? "alpha" : "latest");
|
|
190
205
|
|
|
191
|
-
const pack =
|
|
192
|
-
sealedPackResult() ||
|
|
193
|
-
parsePackResult(
|
|
194
|
-
runNpm({
|
|
195
|
-
cwd: resolvedCwd,
|
|
196
|
-
args: ["pack", "--dry-run", "--json", `--registry=${registry}`],
|
|
197
|
-
}).stdout,
|
|
198
|
-
);
|
|
206
|
+
const pack = sealedPackResult() || materializedPackResult({ cwd: resolvedCwd, registry });
|
|
199
207
|
const digest = artifactDigest(pack);
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
: publishedDigest({
|
|
204
|
-
cwd: resolvedCwd,
|
|
205
|
-
name: pkg.name,
|
|
206
|
-
version: pkg.version,
|
|
207
|
-
registry,
|
|
208
|
-
});
|
|
209
|
-
let publishAction = "already-published";
|
|
210
|
-
if (existingDigest) {
|
|
211
|
-
if (existingDigest !== digest) {
|
|
212
|
-
throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
|
|
213
|
-
}
|
|
214
|
-
} else if (dryRunPublish) {
|
|
215
|
-
publishAction = "dry-run";
|
|
216
|
-
} else {
|
|
217
|
-
runNpm({
|
|
218
|
-
cwd: resolvedCwd,
|
|
219
|
-
args: [
|
|
220
|
-
"publish",
|
|
221
|
-
...(pack.tarballPath ? [pack.tarballPath] : []),
|
|
222
|
-
"--access",
|
|
223
|
-
access,
|
|
224
|
-
"--tag",
|
|
225
|
-
distTag,
|
|
226
|
-
`--registry=${registry}`,
|
|
227
|
-
],
|
|
228
|
-
});
|
|
229
|
-
publishAction = "published";
|
|
230
|
-
const registryDigest = skipRegistryLookup
|
|
209
|
+
try {
|
|
210
|
+
const existingDigest = skipRegistryLookup
|
|
231
211
|
? undefined
|
|
232
212
|
: publishedDigest({
|
|
233
213
|
cwd: resolvedCwd,
|
|
@@ -235,60 +215,92 @@ export function npmPublishTransaction({
|
|
|
235
215
|
version: pkg.version,
|
|
236
216
|
registry,
|
|
237
217
|
});
|
|
238
|
-
|
|
239
|
-
|
|
218
|
+
let publishAction = "already-published";
|
|
219
|
+
if (existingDigest) {
|
|
220
|
+
if (existingDigest !== digest) {
|
|
221
|
+
throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
|
|
222
|
+
}
|
|
223
|
+
} else if (dryRunPublish) {
|
|
224
|
+
publishAction = "dry-run";
|
|
225
|
+
} else {
|
|
226
|
+
runNpm({
|
|
227
|
+
cwd: resolvedCwd,
|
|
228
|
+
args: [
|
|
229
|
+
"publish",
|
|
230
|
+
...(pack.tarballPath ? [pack.tarballPath] : []),
|
|
231
|
+
"--access",
|
|
232
|
+
access,
|
|
233
|
+
"--tag",
|
|
234
|
+
distTag,
|
|
235
|
+
`--registry=${registry}`,
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
publishAction = "published";
|
|
239
|
+
const registryDigest = skipRegistryLookup
|
|
240
|
+
? undefined
|
|
241
|
+
: publishedDigest({
|
|
242
|
+
cwd: resolvedCwd,
|
|
243
|
+
name: pkg.name,
|
|
244
|
+
version: pkg.version,
|
|
245
|
+
registry,
|
|
246
|
+
});
|
|
247
|
+
if (registryDigest && registryDigest !== digest) {
|
|
248
|
+
throw new Error(`artifact digest mismatch: npm:${pkg.name}@${pkg.version}`);
|
|
249
|
+
}
|
|
240
250
|
}
|
|
241
|
-
}
|
|
242
251
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
version: pkg.version,
|
|
271
|
-
"exact-tag": exactTag,
|
|
272
|
-
"dist-tag": distTag,
|
|
273
|
-
"artifact-digest": digest,
|
|
274
|
-
"publish-action": publishAction,
|
|
275
|
-
"publish-evidence": resolvedEvidencePath,
|
|
276
|
-
});
|
|
277
|
-
return {
|
|
278
|
-
schemaVersion: 1,
|
|
279
|
-
package: {
|
|
280
|
-
name: pkg.name,
|
|
252
|
+
const evidencePath = readEnv("BUILDCHAIN_PUBLISH_EVIDENCE");
|
|
253
|
+
if (!evidencePath) {
|
|
254
|
+
throw new Error("BUILDCHAIN_PUBLISH_EVIDENCE is required");
|
|
255
|
+
}
|
|
256
|
+
const evidence = {
|
|
257
|
+
schema: 1,
|
|
258
|
+
version: expectedVersion || pkg.version,
|
|
259
|
+
channel: readEnv("BUILDCHAIN_CHANNEL"),
|
|
260
|
+
source_sha: readEnv("BUILDCHAIN_SOURCE_SHA"),
|
|
261
|
+
release_sha: readEnv("BUILDCHAIN_RELEASE_SHA"),
|
|
262
|
+
target_ref: readEnv("BUILDCHAIN_TARGET_REF"),
|
|
263
|
+
release_material_sha: readEnv("BUILDCHAIN_RELEASE_MATERIAL_SHA", readEnv("BUILDCHAIN_RELEASE_SHA")),
|
|
264
|
+
publish_tooling_sha: readEnv("BUILDCHAIN_PUBLISH_TOOLING_SHA", readEnv("BUILDCHAIN_RELEASE_SHA")),
|
|
265
|
+
artifacts: [{
|
|
266
|
+
group: "node",
|
|
267
|
+
kind: "npm",
|
|
268
|
+
name: pkg.name,
|
|
269
|
+
ref: pkg.version,
|
|
270
|
+
digest,
|
|
271
|
+
}],
|
|
272
|
+
};
|
|
273
|
+
const resolvedEvidencePath = writeEvidence({
|
|
274
|
+
cwd: resolvedCwd,
|
|
275
|
+
evidencePath,
|
|
276
|
+
evidence,
|
|
277
|
+
});
|
|
278
|
+
writeGitHubOutputs({
|
|
281
279
|
version: pkg.version,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
280
|
+
"exact-tag": exactTag,
|
|
281
|
+
"dist-tag": distTag,
|
|
282
|
+
"artifact-digest": digest,
|
|
283
|
+
"publish-action": publishAction,
|
|
284
|
+
"publish-evidence": resolvedEvidencePath,
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
schemaVersion: 1,
|
|
288
|
+
package: {
|
|
289
|
+
name: pkg.name,
|
|
290
|
+
version: pkg.version,
|
|
291
|
+
},
|
|
292
|
+
exactTag,
|
|
293
|
+
distTag,
|
|
294
|
+
registry,
|
|
295
|
+
publishAction,
|
|
296
|
+
pack: { ...pack, temporaryRoot: undefined },
|
|
297
|
+
sealedBundleRoot: readEnv("BUILDCHAIN_SEALED_BUNDLE_ROOT"),
|
|
298
|
+
evidencePath: resolvedEvidencePath,
|
|
299
|
+
evidence,
|
|
300
|
+
};
|
|
301
|
+
} finally {
|
|
302
|
+
if (pack.temporaryRoot) fs.rmSync(pack.temporaryRoot, { recursive: true, force: true });
|
|
303
|
+
}
|
|
292
304
|
}
|
|
293
305
|
|
|
294
306
|
function usage() {
|
|
@@ -8,12 +8,11 @@ import { pathToFileURL } from "node:url";
|
|
|
8
8
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
9
9
|
import {
|
|
10
10
|
generatePublishRequiredArtifacts,
|
|
11
|
-
readNpmPackageArtifact,
|
|
12
|
-
selectReleaseCandidateArtifacts,
|
|
13
|
-
} from "./release-candidate-resolver.mjs";
|
|
14
|
-
import {
|
|
15
11
|
githubDownload,
|
|
16
12
|
githubJson,
|
|
13
|
+
readNpmPackageArtifact,
|
|
14
|
+
selectPayloadArtifacts,
|
|
15
|
+
selectReleaseCandidateArtifacts,
|
|
17
16
|
unzip,
|
|
18
17
|
verifyArtifactArchive,
|
|
19
18
|
} from "./release-candidate-resolver.mjs";
|
|
@@ -156,7 +155,7 @@ async function downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir,
|
|
|
156
155
|
};
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
|
|
158
|
+
export function candidateArtifactNames({ passport, selected, artifacts, artifactPatterns }) {
|
|
160
159
|
const names = new Set([selected.passport.name, selected.summary.name]);
|
|
161
160
|
for (const reference of passport.controllerReceipts || []) {
|
|
162
161
|
if (!reference.artifact) throw new Error(`Passport controller receipt ${reference.controllerId} has no artifact identity`);
|
|
@@ -171,11 +170,9 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
|
|
|
171
170
|
for (const artifact of artifacts) {
|
|
172
171
|
if (manifestPattern.test(String(artifact.name || ""))) names.add(artifact.name);
|
|
173
172
|
}
|
|
174
|
-
const
|
|
175
|
-
for (const
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
return names;
|
|
173
|
+
const publicationNames = new Set(selectPayloadArtifacts({ artifacts, artifactName: selected.prefix, sourceSha: selected.sourceSha, patterns: artifactPatterns }).map((artifact) => artifact.name));
|
|
174
|
+
for (const name of publicationNames) names.add(name);
|
|
175
|
+
return { names, publicationNames };
|
|
179
176
|
}
|
|
180
177
|
|
|
181
178
|
export function normalizePlatformManifests(downloads, passport) {
|
|
@@ -352,7 +349,7 @@ async function recoverCandidateEvidence({
|
|
|
352
349
|
for (const artifact of [selected.passport, selected.summary]) initialDownloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
353
350
|
const passport = readOnlyJson(initialDownloads[0].files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "release-candidate-passport.json");
|
|
354
351
|
const buildSummary = readOnlyJson(initialDownloads[1].files.filter((file) => path.basename(file.path) === "build-summary.json"), "build-summary.json");
|
|
355
|
-
const requiredNames = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
|
|
352
|
+
const { names: requiredNames, publicationNames } = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
|
|
356
353
|
const chosen = artifacts.filter((artifact) => requiredNames.has(artifact.name));
|
|
357
354
|
if (chosen.length !== requiredNames.size) {
|
|
358
355
|
const found = new Set(chosen.map((artifact) => artifact.name));
|
|
@@ -363,7 +360,7 @@ async function recoverCandidateEvidence({
|
|
|
363
360
|
for (const artifact of chosen.filter((entry) => ![selected.passport.id, selected.summary.id].includes(entry.id))) downloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
|
|
364
361
|
return {
|
|
365
362
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
366
|
-
passport, buildSummary, chosen, downloads,
|
|
363
|
+
passport, buildSummary, chosen, downloads, publicationNames,
|
|
367
364
|
};
|
|
368
365
|
}
|
|
369
366
|
|
|
@@ -396,7 +393,7 @@ export async function resumeFromCandidateRun({
|
|
|
396
393
|
try {
|
|
397
394
|
const {
|
|
398
395
|
run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
|
|
399
|
-
passport, buildSummary, chosen, downloads,
|
|
396
|
+
passport, buildSummary, chosen, downloads, publicationNames,
|
|
400
397
|
} = await recoverCandidateEvidence({
|
|
401
398
|
repoInfo, runId, artifactName, artifactPatterns, requiredArtifactCount,
|
|
402
399
|
outputDir, apiUrl, token, fetchImpl, archiveDir,
|
|
@@ -412,7 +409,7 @@ export async function resumeFromCandidateRun({
|
|
|
412
409
|
const controllerReceipts = normalizeControllerReceipts(downloads, passport);
|
|
413
410
|
const productPayloadManifests = normalizeProductPayloadManifests(downloads);
|
|
414
411
|
const publication = createRecoveredPublication({
|
|
415
|
-
downloads,
|
|
412
|
+
downloads: downloads.filter(({ artifact }) => publicationNames.has(artifact.name)),
|
|
416
413
|
bundleRoot,
|
|
417
414
|
repository: repoInfo.fullName,
|
|
418
415
|
passport,
|
|
@@ -381,6 +381,12 @@ export function sealArtifactSigningRequests({
|
|
|
381
381
|
signature: {
|
|
382
382
|
profile: declaration.profile,
|
|
383
383
|
required: declaration.required,
|
|
384
|
+
...(declaration.entitlementsProfile !== "none"
|
|
385
|
+
? {
|
|
386
|
+
entitlementsProfile: declaration.entitlementsProfile,
|
|
387
|
+
entitlementsPaths: declaration.entitlementsPaths,
|
|
388
|
+
}
|
|
389
|
+
: {}),
|
|
384
390
|
},
|
|
385
391
|
});
|
|
386
392
|
const check = validateArtifactSigningRequest(request);
|
|
@@ -201,6 +201,8 @@ export function nodeApiMeta(exportName) {
|
|
|
201
201
|
"./kfd-agent-hub": { group: "kfd-trust", summary: "Declarative Agent Hub adapter inspection, fixed-suite execution, exact KFD cut locking, and agent explanation APIs." },
|
|
202
202
|
"./release-passport-contract": { group: "release-passport-trust", summary: "Standalone release passport JSON Schema, ownership/check manifest, and structural validation APIs." },
|
|
203
203
|
"./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
|
|
204
|
+
"./v4-floating-consumer-policy": { group: "governance-versioning", summary: "Semantic floating-selector admission, dual-lock receipt, and independent v4 consumer certification APIs." },
|
|
205
|
+
"./v4-runtime-ref-resume-authority": { group: "release-passport-trust", summary: "Transient exact-runtime authorization, persisted-selector rejection, fresh-attempt Stage Capsule reuse, and A+B Passport lineage APIs." },
|
|
204
206
|
"./release-candidate-recovery": { group: "release-passport-trust", summary: "Fail-closed verification and immutable receipts for reusing a successful sealed candidate run without rebuilding product payloads." },
|
|
205
207
|
"./release-train": { group: "governance-versioning", summary: "Frozen Release Cut identity, rooted Release Train transitions, Dev observations, supersession, replay, and legacy read compatibility APIs." },
|
|
206
208
|
"./stable-candidate-ledger": { group: "governance-versioning", summary: "Immutable alpha candidate ledger, qualification, revocation, selection, and exact stable source-lock APIs." },
|