@kungfu-tech/buildchain 2.12.7-alpha.6 → 2.12.7-alpha.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.
- package/dist/site/buildchain-contract.json +34 -14
- package/dist/site/buildchain-site.json +6 -6
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +18 -2
- package/dist/site/kfd-claims.json +55 -7
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/node-api-registry.json +4 -4
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +62 -7
- package/dist/site/publication-authority-registry.json +25 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +4 -4
- package/dist/site/workflow-registry.json +62 -4
- package/docs/publication-artifacts.md +17 -19
- package/package.json +1 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/index.js +6 -0
- package/packages/core/publication-artifact-candidate.js +116 -0
- package/packages/core/publication-authority.js +41 -2
- package/scripts/assemble-publication-artifact-admission.mjs +190 -0
- package/scripts/publication-artifact-candidate.mjs +125 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createPublicationAdmission,
|
|
9
|
+
createPublicationGateDecision,
|
|
10
|
+
createRunnerProvenance,
|
|
11
|
+
} from "../packages/core/publication-authority.js";
|
|
12
|
+
import { buildPublicationArtifactCandidate } from "./publication-artifact-candidate.mjs";
|
|
13
|
+
|
|
14
|
+
function required(name) {
|
|
15
|
+
const value = String(process.env[name] || "").trim();
|
|
16
|
+
if (!value) throw new Error(`${name} is required`);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sha256(value) {
|
|
21
|
+
return crypto.createHash("sha256").update(String(value)).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function sourceTree(repository, sourceSha, token) {
|
|
25
|
+
const response = await fetch(
|
|
26
|
+
`${required("GITHUB_API_URL")}/repos/${repository}/git/commits/${sourceSha}`,
|
|
27
|
+
{
|
|
28
|
+
headers: {
|
|
29
|
+
accept: "application/vnd.github+json",
|
|
30
|
+
authorization: `Bearer ${token}`,
|
|
31
|
+
"x-github-api-version": "2022-11-28",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(
|
|
37
|
+
`could not resolve admitted source tree: GitHub API ${response.status}`,
|
|
38
|
+
);
|
|
39
|
+
const commit = await response.json();
|
|
40
|
+
return String(commit.tree?.sha || "").toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeBundle(outputDir, values) {
|
|
44
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
45
|
+
for (const [name, value] of Object.entries(values)) {
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
path.join(outputDir, `${name}.json`),
|
|
48
|
+
`${JSON.stringify(value, null, 2)}\n`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
52
|
+
const output = fs.createWriteStream(process.env.GITHUB_OUTPUT, {
|
|
53
|
+
flags: "a",
|
|
54
|
+
});
|
|
55
|
+
for (const [name, value] of Object.entries(values)) {
|
|
56
|
+
if (name === "candidate") continue;
|
|
57
|
+
output.write(
|
|
58
|
+
`${name.replaceAll("_", "-")}-json=${JSON.stringify(value)}\n`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
output.end();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
const repository = required("BUILDCHAIN_REPOSITORY");
|
|
67
|
+
const sourceSha = required("BUILDCHAIN_SOURCE_SHA").toLowerCase();
|
|
68
|
+
const runtimeRoot =
|
|
69
|
+
process.env.BUILDCHAIN_RUNTIME_ROOT || ".buildchain/authority-runtime";
|
|
70
|
+
const runtimeSha = execFileSync(
|
|
71
|
+
"git",
|
|
72
|
+
["-C", runtimeRoot, "rev-parse", "HEAD"],
|
|
73
|
+
{ encoding: "utf8" },
|
|
74
|
+
)
|
|
75
|
+
.trim()
|
|
76
|
+
.toLowerCase();
|
|
77
|
+
const token = required("GITHUB_TOKEN");
|
|
78
|
+
const treeSha = await sourceTree(repository, sourceSha, token);
|
|
79
|
+
const candidateBundle = buildPublicationArtifactCandidate({
|
|
80
|
+
artifactRoot:
|
|
81
|
+
process.env.BUILDCHAIN_ARTIFACT_ROOT ||
|
|
82
|
+
".buildchain/publication-evidence/artifact",
|
|
83
|
+
controllerRoot:
|
|
84
|
+
process.env.BUILDCHAIN_CONTROLLER_ROOT ||
|
|
85
|
+
".buildchain/publication-evidence/controller",
|
|
86
|
+
repository,
|
|
87
|
+
sourceSha,
|
|
88
|
+
sourceTreeSha: treeSha,
|
|
89
|
+
runtimeSha,
|
|
90
|
+
});
|
|
91
|
+
const controllerReceipt = candidateBundle.evidence.controllerReceipt;
|
|
92
|
+
const controlPlaneAudit = JSON.parse(
|
|
93
|
+
fs.readFileSync(required("BUILDCHAIN_CONTROL_PLANE_AUDIT_PATH"), "utf8"),
|
|
94
|
+
);
|
|
95
|
+
const registry = JSON.parse(
|
|
96
|
+
fs.readFileSync(
|
|
97
|
+
path.join(runtimeRoot, "dist/site/publication-authority-registry.json"),
|
|
98
|
+
"utf8",
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
const gateAggregate = createPublicationGateDecision({
|
|
102
|
+
sourceSha,
|
|
103
|
+
profile: process.env.BUILDCHAIN_GATE_PROFILE || "managed-paper-publication",
|
|
104
|
+
required: false,
|
|
105
|
+
rationale:
|
|
106
|
+
process.env.BUILDCHAIN_GATE_RATIONALE ||
|
|
107
|
+
"The managed paper repository declares no project-specific Shifu Gate registry.",
|
|
108
|
+
policy: { scope: "managed-paper-publication", repository },
|
|
109
|
+
});
|
|
110
|
+
const runnerProvenance = createRunnerProvenance({
|
|
111
|
+
runnerClass: "ephemeral",
|
|
112
|
+
os: required("RUNNER_OS"),
|
|
113
|
+
architecture: required("RUNNER_ARCH"),
|
|
114
|
+
imageDigest: sha256(
|
|
115
|
+
`${process.env.ImageOS || "unknown"}|${process.env.ImageVersion || "unknown"}`,
|
|
116
|
+
),
|
|
117
|
+
measurementDigest: sha256(
|
|
118
|
+
[
|
|
119
|
+
process.env.GITHUB_WORKFLOW,
|
|
120
|
+
process.env.GITHUB_JOB,
|
|
121
|
+
process.env.GITHUB_RUN_ID,
|
|
122
|
+
process.env.GITHUB_RUN_ATTEMPT,
|
|
123
|
+
process.env.RUNNER_ENVIRONMENT,
|
|
124
|
+
].join("|"),
|
|
125
|
+
),
|
|
126
|
+
isolation: "github-hosted-single-job",
|
|
127
|
+
});
|
|
128
|
+
const issuedAt = new Date();
|
|
129
|
+
const admission = createPublicationAdmission({
|
|
130
|
+
registryDigest: registry.registryDigest,
|
|
131
|
+
workflowPath:
|
|
132
|
+
process.env.BUILDCHAIN_AUTHORITY_WORKFLOW_PATH ||
|
|
133
|
+
".github/workflows/paper-release-sealed.yml",
|
|
134
|
+
publisherWorkflowPath: required("BUILDCHAIN_PUBLISHER_WORKFLOW_PATH"),
|
|
135
|
+
repository,
|
|
136
|
+
sourceSha,
|
|
137
|
+
runtimeSha,
|
|
138
|
+
contractDigest: controllerReceipt.runtime?.contractDigest,
|
|
139
|
+
policyDigest: gateAggregate.policyDigest,
|
|
140
|
+
controllerReceiptDigest: controllerReceipt.digest,
|
|
141
|
+
runnerProvenanceDigest: runnerProvenance.receiptDigest,
|
|
142
|
+
controlPlaneAuditDigest: controlPlaneAudit.receiptDigest,
|
|
143
|
+
gateAggregateDigest: gateAggregate.digest,
|
|
144
|
+
environment: "none",
|
|
145
|
+
product: required("BUILDCHAIN_PUBLICATION_PRODUCT"),
|
|
146
|
+
target: required("BUILDCHAIN_PUBLICATION_TARGET"),
|
|
147
|
+
version: required("BUILDCHAIN_PUBLICATION_VERSION"),
|
|
148
|
+
channel: required("BUILDCHAIN_PUBLICATION_CHANNEL"),
|
|
149
|
+
artifactDigest: candidateBundle.candidate.candidateDigest,
|
|
150
|
+
nonce: `${required("GITHUB_RUN_ID")}:${required("GITHUB_RUN_ATTEMPT")}:${sourceSha}:paper`,
|
|
151
|
+
issuedAt: issuedAt.toISOString(),
|
|
152
|
+
expiresAt: new Date(issuedAt.getTime() + 10 * 60 * 1000).toISOString(),
|
|
153
|
+
});
|
|
154
|
+
const bindingNames = [
|
|
155
|
+
"repository",
|
|
156
|
+
"publisherWorkflowPath",
|
|
157
|
+
"sourceSha",
|
|
158
|
+
"runtimeSha",
|
|
159
|
+
"contractDigest",
|
|
160
|
+
"policyDigest",
|
|
161
|
+
"controllerReceiptDigest",
|
|
162
|
+
"gateAggregateDigest",
|
|
163
|
+
"environment",
|
|
164
|
+
"product",
|
|
165
|
+
"target",
|
|
166
|
+
"version",
|
|
167
|
+
"channel",
|
|
168
|
+
"artifactDigest",
|
|
169
|
+
];
|
|
170
|
+
const expected = Object.fromEntries(
|
|
171
|
+
bindingNames.map((name) => [name, admission[name]]),
|
|
172
|
+
);
|
|
173
|
+
writeBundle(
|
|
174
|
+
process.env.BUILDCHAIN_OUTPUT_DIR ||
|
|
175
|
+
".buildchain/publication-authority/auto",
|
|
176
|
+
{
|
|
177
|
+
admission,
|
|
178
|
+
runner_provenance: runnerProvenance,
|
|
179
|
+
control_plane_audit: controlPlaneAudit,
|
|
180
|
+
gate_aggregate: gateAggregate,
|
|
181
|
+
expected,
|
|
182
|
+
candidate: candidateBundle.candidate,
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main().catch((error) => {
|
|
188
|
+
console.error(`assemble publication artifact admission: ${error.message}`);
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { createPublicationArtifactCandidate } from "../packages/core/publication-artifact-candidate.js";
|
|
8
|
+
|
|
9
|
+
function flag(name, fallback = "") {
|
|
10
|
+
const index = process.argv.indexOf(`--${name}`);
|
|
11
|
+
return index === -1 ? fallback : String(process.argv[index + 1] || "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function requiredFlag(name) {
|
|
15
|
+
const value = flag(name).trim();
|
|
16
|
+
if (!value) throw new Error(`--${name} is required`);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function filesNamed(root, name) {
|
|
21
|
+
const matches = [];
|
|
22
|
+
const pending = [path.resolve(root)];
|
|
23
|
+
while (pending.length > 0) {
|
|
24
|
+
const current = pending.pop();
|
|
25
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
26
|
+
const full = path.join(current, entry.name);
|
|
27
|
+
if (entry.isDirectory()) pending.push(full);
|
|
28
|
+
else if (entry.isFile() && entry.name === name) matches.push(full);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return matches.sort();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function oneJson(root, name) {
|
|
35
|
+
const matches = filesNamed(root, name);
|
|
36
|
+
if (matches.length !== 1)
|
|
37
|
+
throw new Error(
|
|
38
|
+
`expected exactly one ${name} under ${root}, found ${matches.length}`,
|
|
39
|
+
);
|
|
40
|
+
return JSON.parse(fs.readFileSync(matches[0], "utf8"));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectFiles(root) {
|
|
44
|
+
const absoluteRoot = path.resolve(root);
|
|
45
|
+
const files = [];
|
|
46
|
+
const pending = [absoluteRoot];
|
|
47
|
+
while (pending.length > 0) {
|
|
48
|
+
const current = pending.pop();
|
|
49
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
50
|
+
const full = path.join(current, entry.name);
|
|
51
|
+
if (entry.isDirectory()) pending.push(full);
|
|
52
|
+
else if (entry.isFile()) {
|
|
53
|
+
files.push({
|
|
54
|
+
path: path.relative(absoluteRoot, full).split(path.sep).join("/"),
|
|
55
|
+
size: fs.statSync(full).size,
|
|
56
|
+
sha256: crypto
|
|
57
|
+
.createHash("sha256")
|
|
58
|
+
.update(fs.readFileSync(full))
|
|
59
|
+
.digest("hex"),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildPublicationArtifactCandidate({
|
|
68
|
+
artifactRoot,
|
|
69
|
+
controllerRoot,
|
|
70
|
+
repository,
|
|
71
|
+
sourceSha,
|
|
72
|
+
sourceTreeSha,
|
|
73
|
+
runtimeSha,
|
|
74
|
+
} = {}) {
|
|
75
|
+
const resolvedArtifactRoot = path.resolve(artifactRoot);
|
|
76
|
+
const resolvedControllerRoot = path.resolve(controllerRoot);
|
|
77
|
+
const evidence = {
|
|
78
|
+
repository,
|
|
79
|
+
sourceSha,
|
|
80
|
+
sourceTreeSha,
|
|
81
|
+
runtimeSha,
|
|
82
|
+
manifest: oneJson(resolvedArtifactRoot, "publication-artifact.json"),
|
|
83
|
+
passport: oneJson(
|
|
84
|
+
resolvedArtifactRoot,
|
|
85
|
+
"publication-artifact-passport.json",
|
|
86
|
+
),
|
|
87
|
+
controllerReceipt: oneJson(resolvedControllerRoot, "receipt.json"),
|
|
88
|
+
files: collectFiles(resolvedArtifactRoot),
|
|
89
|
+
};
|
|
90
|
+
const candidate = createPublicationArtifactCandidate(evidence);
|
|
91
|
+
return { schemaVersion: 1, candidate, evidence };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function main() {
|
|
95
|
+
const result = buildPublicationArtifactCandidate({
|
|
96
|
+
artifactRoot: requiredFlag("artifact-root"),
|
|
97
|
+
controllerRoot: requiredFlag("controller-root"),
|
|
98
|
+
repository: requiredFlag("repository"),
|
|
99
|
+
sourceSha: requiredFlag("source-sha"),
|
|
100
|
+
sourceTreeSha: requiredFlag("source-tree-sha"),
|
|
101
|
+
runtimeSha: requiredFlag("runtime-sha"),
|
|
102
|
+
});
|
|
103
|
+
const output = flag("output");
|
|
104
|
+
if (output) {
|
|
105
|
+
fs.mkdirSync(path.dirname(path.resolve(output)), { recursive: true });
|
|
106
|
+
fs.writeFileSync(
|
|
107
|
+
path.resolve(output),
|
|
108
|
+
`${JSON.stringify(result, null, 2)}\n`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (process.argv.includes("--json") || !output)
|
|
112
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
process.argv[1] &&
|
|
117
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
118
|
+
) {
|
|
119
|
+
try {
|
|
120
|
+
main();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(`publication artifact candidate: ${error.message}`);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
}
|