@kungfu-tech/buildchain 2.13.0 → 2.14.0-alpha.0
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/bin/buildchain.mjs +13 -0
- package/dist/site/buildchain-contract.json +23 -18
- package/dist/site/buildchain-site.json +33 -28
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +12 -0
- package/dist/site/controller-registry.json +6 -2
- package/dist/site/kfd-claims.json +21 -8
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +8 -8
- package/dist/site/node-api-registry.json +5 -5
- package/dist/site/page-registry.json +21 -16
- package/dist/site/public-surface-audit.json +27 -6
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +12 -12
- package/dist/site/workflow-registry.json +2 -1
- package/docs/MAP.md +2 -1
- package/docs/cli.md +19 -0
- package/docs/publication-authority.md +24 -0
- package/docs/release-flow.md +2 -1
- package/docs/release-governance.md +49 -2
- package/docs/release-propagation.md +9 -0
- package/docs/versioning.md +1 -0
- package/docs/web-surface-deployments.md +18 -4
- package/package.json +2 -2
- package/packages/core/index.js +8 -0
- package/packages/core/publication-authority.js +52 -2
- package/packages/core/release-line-bootstrap.js +5 -0
- package/packages/core/web-surface-publication-candidate.js +162 -0
- package/scripts/assemble-web-surface-publication-admission.mjs +204 -0
- package/scripts/check-action-bundles.mjs +50 -0
- package/scripts/dev-merge-queue.mjs +257 -0
- package/scripts/generate-site-bundle.mjs +2 -0
- package/scripts/verify-web-surface-publication-capability.mjs +78 -0
- package/scripts/web-surface-production-decision.mjs +138 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
import { publicationAuthorityDigest } from "../packages/core/publication-authority.js";
|
|
6
|
+
import { webSurfacePublicationDigest } from "../packages/core/web-surface-publication-candidate.js";
|
|
7
|
+
|
|
8
|
+
function required(name) {
|
|
9
|
+
const value = String(process.env[name] || "").trim();
|
|
10
|
+
if (!value) throw new Error(`${name} is required`);
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function main() {
|
|
15
|
+
const capability = JSON.parse(required("BUILDCHAIN_PUBLICATION_CAPABILITY_JSON"));
|
|
16
|
+
const { capabilityDigest, ...payload } = capability;
|
|
17
|
+
if (publicationAuthorityDigest(payload) !== capabilityDigest) {
|
|
18
|
+
throw new Error("web-surface publication capability digest mismatch");
|
|
19
|
+
}
|
|
20
|
+
if (capability.decision !== "allow" || Date.parse(capability.expiresAt) <= Date.now()) {
|
|
21
|
+
throw new Error("web-surface publication capability is not live and authorizing");
|
|
22
|
+
}
|
|
23
|
+
const candidate = JSON.parse(required("BUILDCHAIN_WEB_SURFACE_CANDIDATE_JSON"));
|
|
24
|
+
const { candidateDigest, ...candidatePayload } = candidate;
|
|
25
|
+
if (
|
|
26
|
+
webSurfacePublicationDigest(candidatePayload) !== candidateDigest ||
|
|
27
|
+
candidateDigest !== capability.artifactDigest
|
|
28
|
+
) {
|
|
29
|
+
throw new Error("web-surface publication candidate capability binding mismatch");
|
|
30
|
+
}
|
|
31
|
+
const plan = JSON.parse(fs.readFileSync(required("BUILDCHAIN_WEB_SURFACE_PLAN_PATH"), "utf8"));
|
|
32
|
+
const planDigest = crypto
|
|
33
|
+
.createHash("sha256")
|
|
34
|
+
.update(`${JSON.stringify(plan, null, 2)}\n`)
|
|
35
|
+
.digest("hex");
|
|
36
|
+
if (
|
|
37
|
+
candidate.planDigest !== planDigest ||
|
|
38
|
+
candidate.artifactHash !== plan.artifact?.hash ||
|
|
39
|
+
candidate.deployTarget !== plan.manifest?.deployTarget
|
|
40
|
+
) {
|
|
41
|
+
throw new Error("web-surface publication candidate plan binding mismatch");
|
|
42
|
+
}
|
|
43
|
+
const roleArn = required("BUILDCHAIN_PRODUCTION_ROLE_ARN");
|
|
44
|
+
const exact = {
|
|
45
|
+
workflowPath: ".github/workflows/.web-surface.yml",
|
|
46
|
+
repository: required("BUILDCHAIN_REPOSITORY"),
|
|
47
|
+
sourceSha: required("BUILDCHAIN_SOURCE_SHA").toLowerCase(),
|
|
48
|
+
runtimeSha: required("BUILDCHAIN_RUNTIME_SHA").toLowerCase(),
|
|
49
|
+
environment: required("BUILDCHAIN_PUBLICATION_ENVIRONMENT"),
|
|
50
|
+
product: String(plan.manifest?.site || ""),
|
|
51
|
+
target: `aws-role:${roleArn}#deploy:${String(plan.manifest?.deployTarget || "")}`,
|
|
52
|
+
version: required("BUILDCHAIN_SOURCE_SHA").toLowerCase(),
|
|
53
|
+
channel: "production",
|
|
54
|
+
};
|
|
55
|
+
for (const [name, value] of Object.entries(exact)) {
|
|
56
|
+
if (capability[name] !== value) throw new Error(`web-surface publication capability ${name} binding mismatch`);
|
|
57
|
+
}
|
|
58
|
+
for (const [name, value] of Object.entries({
|
|
59
|
+
repository: exact.repository,
|
|
60
|
+
sourceSha: exact.sourceSha,
|
|
61
|
+
runtimeSha: exact.runtimeSha,
|
|
62
|
+
site: exact.product,
|
|
63
|
+
environment: exact.environment,
|
|
64
|
+
})) {
|
|
65
|
+
if (candidate[name] !== value) throw new Error(`web-surface publication candidate ${name} binding mismatch`);
|
|
66
|
+
}
|
|
67
|
+
if (capability.capabilityIds?.includes("web-production") !== true) {
|
|
68
|
+
throw new Error("web-surface publication capability lacks web-production authority");
|
|
69
|
+
}
|
|
70
|
+
process.stdout.write(`${JSON.stringify({ ok: true, capabilityDigest })}\n`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
main();
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`verify web-surface publication capability: ${error.message}`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { createWebSurfaceProductionDecision } from "../packages/core/web-surface-publication-candidate.js";
|
|
6
|
+
|
|
7
|
+
const TRUSTED_PERMISSIONS = new Set(["write", "maintain", "admin"]);
|
|
8
|
+
|
|
9
|
+
export function resolveWebSurfaceProductionDecision({
|
|
10
|
+
eventName,
|
|
11
|
+
refName,
|
|
12
|
+
repository,
|
|
13
|
+
sourceSha,
|
|
14
|
+
actor,
|
|
15
|
+
productionApply,
|
|
16
|
+
productionApproved,
|
|
17
|
+
productionReleaseOnMain,
|
|
18
|
+
actorPermission = "",
|
|
19
|
+
releaseApproved = false,
|
|
20
|
+
releasePr = 0,
|
|
21
|
+
releaseSource = "",
|
|
22
|
+
} = {}) {
|
|
23
|
+
if (
|
|
24
|
+
eventName === "workflow_dispatch" &&
|
|
25
|
+
productionApply === true &&
|
|
26
|
+
productionApproved === true
|
|
27
|
+
) {
|
|
28
|
+
const trusted = TRUSTED_PERMISSIONS.has(String(actorPermission));
|
|
29
|
+
return createWebSurfaceProductionDecision({
|
|
30
|
+
approved: trusted,
|
|
31
|
+
kind: trusted ? "manual-dispatch" : "none",
|
|
32
|
+
repository,
|
|
33
|
+
sourceSha,
|
|
34
|
+
actor,
|
|
35
|
+
actorPermission,
|
|
36
|
+
reason: trusted ? "trusted-manual-dispatch" : "manual-actor-permission-insufficient",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
eventName === "push" &&
|
|
41
|
+
refName === "main" &&
|
|
42
|
+
productionApply === true &&
|
|
43
|
+
productionReleaseOnMain === true &&
|
|
44
|
+
releaseApproved === true
|
|
45
|
+
) {
|
|
46
|
+
return createWebSurfaceProductionDecision({
|
|
47
|
+
approved: true,
|
|
48
|
+
kind: "release-pr",
|
|
49
|
+
repository,
|
|
50
|
+
sourceSha,
|
|
51
|
+
actor,
|
|
52
|
+
releasePr,
|
|
53
|
+
releaseSource,
|
|
54
|
+
reason: "reviewed-release-pr-merged",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return createWebSurfaceProductionDecision({
|
|
58
|
+
approved: false,
|
|
59
|
+
kind: "none",
|
|
60
|
+
repository,
|
|
61
|
+
sourceSha,
|
|
62
|
+
actor,
|
|
63
|
+
actorPermission,
|
|
64
|
+
releasePr,
|
|
65
|
+
releaseSource,
|
|
66
|
+
reason: "no-authorizing-production-event",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function actorPermission(repository, actor, token, apiUrl) {
|
|
71
|
+
const response = await fetch(
|
|
72
|
+
`${apiUrl}/repos/${repository}/collaborators/${encodeURIComponent(actor)}/permission`,
|
|
73
|
+
{
|
|
74
|
+
headers: {
|
|
75
|
+
accept: "application/vnd.github+json",
|
|
76
|
+
authorization: `Bearer ${token}`,
|
|
77
|
+
"x-github-api-version": "2022-11-28",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`could not resolve production actor permission: GitHub API ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
return String((await response.json()).permission || "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function bool(name) {
|
|
88
|
+
return String(process.env[name] || "") === "true";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function main() {
|
|
92
|
+
const eventName = String(process.env.GITHUB_EVENT_NAME || "");
|
|
93
|
+
const repository = String(process.env.GITHUB_REPOSITORY || "");
|
|
94
|
+
const actor = String(process.env.GITHUB_ACTOR || "");
|
|
95
|
+
let permission = "";
|
|
96
|
+
if (eventName === "workflow_dispatch" && bool("BUILDCHAIN_PRODUCTION_APPLY")) {
|
|
97
|
+
permission = await actorPermission(
|
|
98
|
+
repository,
|
|
99
|
+
actor,
|
|
100
|
+
String(process.env.GITHUB_TOKEN || ""),
|
|
101
|
+
String(process.env.GITHUB_API_URL || "https://api.github.com"),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const decision = resolveWebSurfaceProductionDecision({
|
|
105
|
+
eventName,
|
|
106
|
+
refName: process.env.GITHUB_REF_NAME,
|
|
107
|
+
repository,
|
|
108
|
+
sourceSha: process.env.GITHUB_SHA,
|
|
109
|
+
actor,
|
|
110
|
+
productionApply: bool("BUILDCHAIN_PRODUCTION_APPLY"),
|
|
111
|
+
productionApproved: bool("BUILDCHAIN_PRODUCTION_APPROVED"),
|
|
112
|
+
productionReleaseOnMain: bool("BUILDCHAIN_PRODUCTION_RELEASE_ON_MAIN"),
|
|
113
|
+
actorPermission: permission,
|
|
114
|
+
releaseApproved: bool("BUILDCHAIN_PRODUCTION_RELEASE_APPROVED"),
|
|
115
|
+
releasePr: Number(process.env.BUILDCHAIN_PRODUCTION_RELEASE_PR || 0),
|
|
116
|
+
releaseSource: process.env.BUILDCHAIN_PRODUCTION_RELEASE_SOURCE,
|
|
117
|
+
});
|
|
118
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
119
|
+
fs.appendFileSync(
|
|
120
|
+
process.env.GITHUB_OUTPUT,
|
|
121
|
+
`approved=${decision.approved}\ndecision-json=${JSON.stringify(decision)}\n`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
125
|
+
fs.appendFileSync(
|
|
126
|
+
process.env.GITHUB_STEP_SUMMARY,
|
|
127
|
+
`## Web production decision\n\n- approved: \`${decision.approved}\`\n- kind: \`${decision.kind}\`\n- reason: \`${decision.reason}\`\n`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
process.stdout.write(`${JSON.stringify(decision)}\n`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
134
|
+
main().catch((error) => {
|
|
135
|
+
console.error(`web-surface production decision: ${error.message}`);
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
});
|
|
138
|
+
}
|