@kungfu-tech/buildchain 4.0.1-alpha.9 → 4.0.1
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/README.md +9 -6
- package/actions/v4-release-candidate-promote/README.md +3 -0
- package/architecture/agent-change-map.md +1015 -0
- package/architecture/ci-lane-change-budget.json +70 -1
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/internal-capabilities.json +156 -11
- package/architecture/maintainability-debt.json +3229 -0
- package/architecture/maintainability-policy.json +480 -129
- package/architecture/v3-core-mechanism-inventory.json +4 -3
- package/architecture/v3-v4-capability-closure.md +59 -0
- package/architecture/v3-v4-live-capability-inventory.json +102404 -0
- package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
- package/architecture/v4-runtime-semantic-closure.json +710 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +123 -32
- package/dist/site/buildchain-site.json +96 -28
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +5 -2
- package/dist/site/controller-registry.json +41 -5
- package/dist/site/kfd-claims.json +170 -21
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +1474 -215
- package/dist/site/page-registry.json +85 -17
- package/dist/site/public-surface-audit.json +85 -20
- package/dist/site/publication-authority-registry.json +28 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +5 -0
- package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +94 -18
- package/docs/MAP.md +1 -1
- package/docs/cli-reference.md +4 -4
- package/docs/dev-delivery-warrant.md +12 -9
- package/docs/node-api-reference.md +291 -206
- package/docs/publish-transaction.md +11 -4
- package/docs/release-candidate.md +7 -0
- package/docs/v4-adopter-delivery.md +23 -4
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/package.json +8 -3
- package/packages/core/adopter-delivery-passport.js +36 -0
- package/packages/core/buildchain-contract.js +31 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/dev-delivery-warrant-settlement.js +239 -3
- package/packages/core/dev-delivery-warrant-state.js +15 -2
- package/packages/core/dev-delivery-warrant.js +21 -4
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/index.js +1 -0
- package/packages/core/paper-scaffold-content.js +401 -0
- package/packages/core/paper.js +12 -378
- package/packages/core/publication-control-plane-audit.js +135 -111
- package/packages/core/publication-rehearsal-projection.js +104 -0
- package/packages/core/publication-rehearsal-runtime.js +190 -0
- package/packages/core/release-candidate-recovery.js +6 -5
- package/packages/core/release-passport.js +82 -184
- package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
- package/packages/core/v4-floating-consumer-policy.js +3 -2
- package/packages/core/v4-floating-consumer-release-passport.js +134 -15
- package/packages/core/v4-publication-qualification.js +222 -0
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +17 -17
- package/scripts/buildchain-cli-help.mjs +3 -0
- package/scripts/check-internal-architecture.mjs +23 -3
- package/scripts/check-inventory.mjs +23 -5
- package/scripts/check-maintainability.mjs +145 -4
- package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
- package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -3
- package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
- package/scripts/dev-delivery-warrant-options.mjs +27 -11
- package/scripts/dev-delivery-warrant.mjs +45 -5
- package/scripts/dev-pr-auto-merge.mjs +25 -11
- package/scripts/dev-pr-delivery-warrant.mjs +44 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-agent-change-map.mjs +118 -0
- package/scripts/generate-release-candidate-passport.mjs +26 -1
- package/scripts/generate-site-bundle.mjs +6 -0
- package/scripts/maintainability-domain-metrics.mjs +237 -0
- package/scripts/maintainability-governance.mjs +473 -0
- package/scripts/maintainability-metrics.mjs +28 -6
- package/scripts/project-cut-merge-queue-admission.mjs +394 -0
- package/scripts/promotion-routing-evidence.mjs +35 -6
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/release-line-policy.mjs +25 -15
- package/scripts/release-tail.mjs +19 -2
- package/scripts/resume-from-candidate-run.mjs +106 -103
- package/scripts/run-lifecycle-core.mjs +341 -376
- package/scripts/site-capability-metadata.mjs +4 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v3-v4-capability-catalog.mjs +439 -0
- package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
- package/scripts/v4-declarative-promotion-admission.mjs +42 -0
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
v4FloatingConsumerDocumentRoot,
|
|
5
5
|
verifyV4FloatingConsumerPolicyCertification,
|
|
6
6
|
} from "./v4-floating-consumer-evidence.js";
|
|
7
|
-
|
|
7
|
+
import {
|
|
8
|
+
verifyV4RuntimeAuthorizationReceipt,
|
|
9
|
+
verifyV4RuntimeResumeLineage,
|
|
10
|
+
} from "./v4-runtime-ref-resume-authority.js";
|
|
8
11
|
const SHA256_ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
9
|
-
|
|
10
12
|
export function isV4PromotionRouting(value = undefined) {
|
|
11
13
|
return [
|
|
12
14
|
value?.router?.ref,
|
|
@@ -14,7 +16,6 @@ export function isV4PromotionRouting(value = undefined) {
|
|
|
14
16
|
value?.runtime?.requestedRef,
|
|
15
17
|
].some((ref) => /^v4(?:-alpha)?$/u.test(String(ref || "")));
|
|
16
18
|
}
|
|
17
|
-
|
|
18
19
|
function resolveInside(root, relative, label) {
|
|
19
20
|
const resolvedRoot = path.resolve(root);
|
|
20
21
|
const file = path.resolve(resolvedRoot, relative);
|
|
@@ -23,7 +24,6 @@ function resolveInside(root, relative, label) {
|
|
|
23
24
|
}
|
|
24
25
|
return file;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
27
|
function resolveCallerLock(root, relative, label) {
|
|
28
28
|
const file = resolveInside(root, relative, `${label} contract lock`);
|
|
29
29
|
if (!fs.existsSync(file)) {
|
|
@@ -76,27 +76,31 @@ function normalizeCertification(value, expected) {
|
|
|
76
76
|
}
|
|
77
77
|
return { certificationRoot: verification.certificationRoot, certification };
|
|
78
78
|
}
|
|
79
|
-
|
|
80
79
|
export function resolveV4ConsumerPolicyCertificationIdentity({
|
|
81
80
|
release = {},
|
|
82
81
|
routing = {},
|
|
83
82
|
runtimeResume = undefined,
|
|
84
83
|
sourceSha = "",
|
|
85
84
|
} = {}) {
|
|
86
|
-
const
|
|
87
|
-
release?.
|
|
85
|
+
const certification =
|
|
86
|
+
release?.certification?.certification || release?.certification;
|
|
87
|
+
const certifiedSourceSha = certification?.caller?.sourceSha || "";
|
|
88
|
+
const treeEquivalentSources = [
|
|
89
|
+
release?.builtSourceSha || release?.built_source_sha,
|
|
90
|
+
release?.promotionChannelSha || release?.promotion_channel_sha,
|
|
91
|
+
];
|
|
92
|
+
const buildSha = runtimeResume?.lineage?.attempts?.build?.runtimeSha;
|
|
93
|
+
const certifiedSha = certification?.invocation?.resolvedRuntimeSha || "";
|
|
88
94
|
return {
|
|
89
95
|
sourceSha:
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
certifiedSourceSha === sourceSha ||
|
|
97
|
+
(release?.treeEquivalent === true &&
|
|
98
|
+
treeEquivalentSources.includes(certifiedSourceSha))
|
|
99
|
+
? certifiedSourceSha
|
|
92
100
|
: sourceSha,
|
|
93
|
-
runtimeSha:
|
|
94
|
-
runtimeResume?.lineage?.attempts?.build?.runtimeSha ||
|
|
95
|
-
routing?.runtime?.resolvedSha ||
|
|
96
|
-
"",
|
|
101
|
+
runtimeSha: buildSha || certifiedSha || routing?.runtime?.resolvedSha || "",
|
|
97
102
|
};
|
|
98
103
|
}
|
|
99
|
-
|
|
100
104
|
export function requireV4ConsumerPolicyCertification({
|
|
101
105
|
value,
|
|
102
106
|
repository,
|
|
@@ -134,7 +138,7 @@ export function releasePassportCertificationVerificationOptions({
|
|
|
134
138
|
}) {
|
|
135
139
|
const authority = evidence?.certification?.authority || {};
|
|
136
140
|
const identity = resolveV4ConsumerPolicyCertificationIdentity({
|
|
137
|
-
release: passport?.release,
|
|
141
|
+
release: { ...passport?.release, certification: evidence?.certification },
|
|
138
142
|
routing,
|
|
139
143
|
runtimeResume: passport?.v4RuntimeResume,
|
|
140
144
|
sourceSha: passport?.release?.sourceSha || "",
|
|
@@ -151,3 +155,118 @@ export function releasePassportCertificationVerificationOptions({
|
|
|
151
155
|
alphaLockPath: authority.contractLocks?.alpha?.path || "",
|
|
152
156
|
};
|
|
153
157
|
}
|
|
158
|
+
export function normalizePromotionRouting(value = undefined) {
|
|
159
|
+
if (value === undefined) return undefined;
|
|
160
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
161
|
+
throw new Error("release.promotionRouting must be a JSON object");
|
|
162
|
+
}
|
|
163
|
+
if (value.contract !== "buildchain.promotion-routing/v1") {
|
|
164
|
+
throw new Error(
|
|
165
|
+
"release.promotionRouting contract must be buildchain.promotion-routing/v1",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
for (const [label, field] of [
|
|
169
|
+
["router.ref", value.router?.ref],
|
|
170
|
+
["router.sha", value.router?.sha],
|
|
171
|
+
["shell.ref", value.shell?.ref],
|
|
172
|
+
["shell.sha", value.shell?.sha],
|
|
173
|
+
["runtime.requestedRef", value.runtime?.requestedRef],
|
|
174
|
+
["runtime.resolvedSha", value.runtime?.resolvedSha],
|
|
175
|
+
["contractLock.path", value.contractLock?.path],
|
|
176
|
+
["contractLock.digest", value.contractLock?.digest],
|
|
177
|
+
["publication.channel", value.publication?.channel],
|
|
178
|
+
["publication.targetRef", value.publication?.targetRef],
|
|
179
|
+
]) {
|
|
180
|
+
if (!String(field || "").trim())
|
|
181
|
+
throw new Error(`release.promotionRouting.${label} is required`);
|
|
182
|
+
}
|
|
183
|
+
for (const [label, sha] of [
|
|
184
|
+
["router.sha", value.router.sha],
|
|
185
|
+
["shell.sha", value.shell.sha],
|
|
186
|
+
["runtime.resolvedSha", value.runtime.resolvedSha],
|
|
187
|
+
]) {
|
|
188
|
+
if (!/^[0-9a-f]{40}$/i.test(String(sha))) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`release.promotionRouting.${label} must be a 40-character Git SHA`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!/^sha256:[0-9a-f]{64}$/i.test(String(value.contractLock.digest))) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
"release.promotionRouting.contractLock.digest must be a sha256 digest",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return JSON.parse(JSON.stringify(value));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function normalizePromotionEvidence({
|
|
203
|
+
release = {},
|
|
204
|
+
v4ConsumerPolicyCertification: certification,
|
|
205
|
+
v4ConsumerPolicyCertificationRoot: certificationRoot,
|
|
206
|
+
v4RuntimeResumeEvidence,
|
|
207
|
+
repository,
|
|
208
|
+
sourceSha,
|
|
209
|
+
cwd,
|
|
210
|
+
}) {
|
|
211
|
+
const routing = normalizePromotionRouting(release.promotionRouting);
|
|
212
|
+
const identity = resolveV4ConsumerPolicyCertificationIdentity({
|
|
213
|
+
release: { ...release, certification },
|
|
214
|
+
routing,
|
|
215
|
+
runtimeResume: v4RuntimeResumeEvidence,
|
|
216
|
+
sourceSha,
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
routing,
|
|
220
|
+
consumerPolicy: requireV4ConsumerPolicyCertification({
|
|
221
|
+
value: certification,
|
|
222
|
+
repository,
|
|
223
|
+
sourceSha: identity.sourceSha,
|
|
224
|
+
runtimeSha: identity.runtimeSha,
|
|
225
|
+
routing,
|
|
226
|
+
cwd,
|
|
227
|
+
certificationRoot,
|
|
228
|
+
}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function normalizeV4RuntimeResumeEvidence(value, expected = {}) {
|
|
233
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
234
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
235
|
+
throw new Error("v4RuntimeResumeEvidence must be a JSON object");
|
|
236
|
+
}
|
|
237
|
+
const authorizationVerification = verifyV4RuntimeAuthorizationReceipt({
|
|
238
|
+
receipt: value.authorization,
|
|
239
|
+
receiptRoot: value.authorizationRoot,
|
|
240
|
+
repository: expected.repository,
|
|
241
|
+
sourceSha: expected.sourceSha,
|
|
242
|
+
runtimeSha: expected.resumeRuntimeSha,
|
|
243
|
+
consumerPolicyReceiptRoot: expected.consumerPolicyReceiptRoot,
|
|
244
|
+
});
|
|
245
|
+
if (!authorizationVerification.ok) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`v4 runtime authorization invalid: ${authorizationVerification.failures.join(", ")}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const lineageVerification = verifyV4RuntimeResumeLineage({
|
|
251
|
+
lineage: value.lineage,
|
|
252
|
+
lineageRoot: value.lineageRoot,
|
|
253
|
+
repository: expected.repository,
|
|
254
|
+
sourceSha: expected.sourceSha,
|
|
255
|
+
resumeRuntimeSha: expected.resumeRuntimeSha,
|
|
256
|
+
consumerPolicyReceiptRoot: expected.consumerPolicyReceiptRoot,
|
|
257
|
+
});
|
|
258
|
+
if (!lineageVerification.ok) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`v4 runtime resume lineage invalid: ${lineageVerification.failures.join(", ")}`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (value.lineage?.authorizationRoot !== value.authorizationRoot) {
|
|
264
|
+
throw new Error("v4 runtime resume lineage authorization root mismatch");
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
authorizationRoot: value.authorizationRoot,
|
|
268
|
+
authorization: structuredClone(value.authorization),
|
|
269
|
+
lineageRoot: value.lineageRoot,
|
|
270
|
+
lineage: structuredClone(value.lineage),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const V4_PUBLICATION_QUALIFICATION_SCHEMA =
|
|
4
|
+
"kungfu.buildchain.v4-publication-qualification/v1";
|
|
5
|
+
|
|
6
|
+
const ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
7
|
+
const SHA = /^[0-9a-f]{40}$/u;
|
|
8
|
+
const FORBIDDEN_EXECUTION_KEYS = new Set([
|
|
9
|
+
"cmd",
|
|
10
|
+
"command",
|
|
11
|
+
"eval",
|
|
12
|
+
"executable",
|
|
13
|
+
"javascript",
|
|
14
|
+
"run",
|
|
15
|
+
"script",
|
|
16
|
+
"shell",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export class V4PublicationQualificationError extends Error {
|
|
20
|
+
constructor(code, message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "V4PublicationQualificationError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fail(code, message) {
|
|
28
|
+
throw new V4PublicationQualificationError(code, message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function canonical(value) {
|
|
32
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
33
|
+
if (value && typeof value === "object") {
|
|
34
|
+
return `{${Object.keys(value)
|
|
35
|
+
.sort()
|
|
36
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
37
|
+
.join(",")}}`;
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function v4PublicationQualificationRoot(value) {
|
|
43
|
+
return `sha256:${crypto.createHash("sha256").update(canonical(value)).digest("hex")}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function assertNoV4ExecutionFields(value, location = "$") {
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
value.forEach((entry, index) =>
|
|
49
|
+
assertNoV4ExecutionFields(entry, `${location}[${index}]`),
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!value || typeof value !== "object") return;
|
|
54
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
55
|
+
if (FORBIDDEN_EXECUTION_KEYS.has(key.toLowerCase())) {
|
|
56
|
+
fail(
|
|
57
|
+
"execution-field-forbidden",
|
|
58
|
+
`v4 declarative publication forbids ${location}.${key}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
assertNoV4ExecutionFields(entry, `${location}.${key}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function exactRoot(value, label) {
|
|
66
|
+
const root = String(value || "").toLowerCase();
|
|
67
|
+
if (!ROOT.test(root))
|
|
68
|
+
fail("schema-invalid", `${label} must be a sha256 root`);
|
|
69
|
+
return root;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function exactSha(value, label) {
|
|
73
|
+
const sha = String(value || "").toLowerCase();
|
|
74
|
+
if (!SHA.test(sha))
|
|
75
|
+
fail("schema-invalid", `${label} must be an exact Git SHA`);
|
|
76
|
+
return sha;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizedArtifacts(artifacts) {
|
|
80
|
+
if (!Array.isArray(artifacts) || artifacts.length === 0) {
|
|
81
|
+
fail(
|
|
82
|
+
"schema-invalid",
|
|
83
|
+
"publication qualification requires typed artifacts",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const normalized = artifacts
|
|
87
|
+
.map((artifact) => {
|
|
88
|
+
const value = {
|
|
89
|
+
role: String(artifact?.role || ""),
|
|
90
|
+
platform: String(artifact?.platform || ""),
|
|
91
|
+
artifactRoot: exactRoot(artifact?.artifactRoot, "artifactRoot"),
|
|
92
|
+
manifestRoot: exactRoot(artifact?.manifestRoot, "manifestRoot"),
|
|
93
|
+
};
|
|
94
|
+
if (!value.role || !value.platform)
|
|
95
|
+
fail("schema-invalid", "artifact role and platform are required");
|
|
96
|
+
return value;
|
|
97
|
+
})
|
|
98
|
+
.sort((left, right) =>
|
|
99
|
+
`${left.role}\0${left.platform}`.localeCompare(
|
|
100
|
+
`${right.role}\0${right.platform}`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
if (
|
|
104
|
+
new Set(normalized.map(({ role, platform }) => `${role}\0${platform}`))
|
|
105
|
+
.size !== normalized.length
|
|
106
|
+
) {
|
|
107
|
+
fail("schema-invalid", "artifact role/platform bindings must be unique");
|
|
108
|
+
}
|
|
109
|
+
return normalized;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createV4PublicationQualificationReceipt({
|
|
113
|
+
repository,
|
|
114
|
+
candidateRoot,
|
|
115
|
+
sourceSha,
|
|
116
|
+
sourceRoot,
|
|
117
|
+
policyDigest,
|
|
118
|
+
artifacts,
|
|
119
|
+
issuedAt,
|
|
120
|
+
expiresAt,
|
|
121
|
+
}) {
|
|
122
|
+
const body = {
|
|
123
|
+
schema: V4_PUBLICATION_QUALIFICATION_SCHEMA,
|
|
124
|
+
repository: String(repository || ""),
|
|
125
|
+
candidateRoot: exactRoot(candidateRoot, "candidateRoot"),
|
|
126
|
+
sourceSha: exactSha(sourceSha, "sourceSha"),
|
|
127
|
+
sourceRoot: exactRoot(sourceRoot, "sourceRoot"),
|
|
128
|
+
policyDigest: exactRoot(policyDigest, "policyDigest"),
|
|
129
|
+
artifacts: normalizedArtifacts(artifacts),
|
|
130
|
+
issuedAt: String(issuedAt || ""),
|
|
131
|
+
expiresAt: String(expiresAt || ""),
|
|
132
|
+
};
|
|
133
|
+
if (!/^[^/\s]+\/[^/\s]+$/u.test(body.repository))
|
|
134
|
+
fail("schema-invalid", "repository must be owner/repo");
|
|
135
|
+
const issued = Date.parse(body.issuedAt);
|
|
136
|
+
const expires = Date.parse(body.expiresAt);
|
|
137
|
+
if (
|
|
138
|
+
!Number.isFinite(issued) ||
|
|
139
|
+
!Number.isFinite(expires) ||
|
|
140
|
+
expires <= issued
|
|
141
|
+
)
|
|
142
|
+
fail("schema-invalid", "qualification freshness interval is invalid");
|
|
143
|
+
assertNoV4ExecutionFields(body);
|
|
144
|
+
const artifactRoot = v4PublicationQualificationRoot(body.artifacts);
|
|
145
|
+
const receipt = { ...body, artifactRoot };
|
|
146
|
+
return {
|
|
147
|
+
...receipt,
|
|
148
|
+
receiptRoot: v4PublicationQualificationRoot(receipt),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function validateV4PublicationQualificationReceipt(
|
|
153
|
+
receipt,
|
|
154
|
+
{
|
|
155
|
+
repository,
|
|
156
|
+
candidateRoot,
|
|
157
|
+
sourceSha,
|
|
158
|
+
sourceRoot,
|
|
159
|
+
artifactRoot,
|
|
160
|
+
policyDigest,
|
|
161
|
+
evaluatedAt = new Date().toISOString(),
|
|
162
|
+
} = {},
|
|
163
|
+
) {
|
|
164
|
+
if (!receipt)
|
|
165
|
+
fail("qualification-missing", "qualification receipt is required");
|
|
166
|
+
assertNoV4ExecutionFields(receipt);
|
|
167
|
+
const recreated = createV4PublicationQualificationReceipt(receipt);
|
|
168
|
+
if (
|
|
169
|
+
receipt.artifactRoot !== recreated.artifactRoot ||
|
|
170
|
+
receipt.receiptRoot !== recreated.receiptRoot
|
|
171
|
+
) {
|
|
172
|
+
fail(
|
|
173
|
+
"qualification-tampered",
|
|
174
|
+
"qualification receipt root does not verify",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (repository && receipt.repository !== repository)
|
|
178
|
+
fail("repository-mismatch", "qualification repository does not match");
|
|
179
|
+
if (candidateRoot && receipt.candidateRoot !== candidateRoot)
|
|
180
|
+
fail("candidate-mismatch", "qualification candidate root does not match");
|
|
181
|
+
if (sourceSha && receipt.sourceSha !== sourceSha)
|
|
182
|
+
fail("source-mismatch", "qualification source SHA does not match");
|
|
183
|
+
if (sourceRoot && receipt.sourceRoot !== sourceRoot)
|
|
184
|
+
fail("source-mismatch", "qualification source root does not match");
|
|
185
|
+
if (artifactRoot && receipt.artifactRoot !== artifactRoot)
|
|
186
|
+
fail("artifact-mismatch", "qualification artifact root does not match");
|
|
187
|
+
if (policyDigest && receipt.policyDigest !== policyDigest)
|
|
188
|
+
fail("policy-mismatch", "qualification policy digest does not match");
|
|
189
|
+
const evaluated = Date.parse(evaluatedAt);
|
|
190
|
+
if (!Number.isFinite(evaluated))
|
|
191
|
+
fail("schema-invalid", "evaluatedAt is invalid");
|
|
192
|
+
if (
|
|
193
|
+
evaluated < Date.parse(receipt.issuedAt) ||
|
|
194
|
+
evaluated >= Date.parse(receipt.expiresAt)
|
|
195
|
+
)
|
|
196
|
+
fail(
|
|
197
|
+
"qualification-stale",
|
|
198
|
+
"qualification receipt is outside its freshness window",
|
|
199
|
+
);
|
|
200
|
+
return Object.freeze({
|
|
201
|
+
ok: true,
|
|
202
|
+
receiptRoot: receipt.receiptRoot,
|
|
203
|
+
artifactRoot: receipt.artifactRoot,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function assertV4DeclarativePromotionInputs(inputs) {
|
|
208
|
+
const forbidden = Object.entries(inputs || {}).filter(
|
|
209
|
+
([name, value]) =>
|
|
210
|
+
/(command|cmd|script|shell|run)$/iu.test(name) &&
|
|
211
|
+
String(value || "").trim() !== "",
|
|
212
|
+
);
|
|
213
|
+
if (forbidden.length > 0) {
|
|
214
|
+
fail(
|
|
215
|
+
"legacy-command-input-forbidden",
|
|
216
|
+
`v4 declarative publication rejects: ${forbidden
|
|
217
|
+
.map(([name]) => name)
|
|
218
|
+
.sort()
|
|
219
|
+
.join(", ")}`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -374,6 +374,7 @@ export function assertArtifactSigningControlRequestContext(
|
|
|
374
374
|
export function artifactSigningControlRequestOutputs(request) {
|
|
375
375
|
const value = validateArtifactSigningControlRequest(request);
|
|
376
376
|
return {
|
|
377
|
+
"source-run-attempt": String(value.source.runAttempt),
|
|
377
378
|
"request-count": String(value.request.count),
|
|
378
379
|
"request-artifact": value.request.artifact,
|
|
379
380
|
"request-root": value.request.root,
|
|
@@ -384,7 +385,6 @@ export function artifactSigningControlRequestOutputs(request) {
|
|
|
384
385
|
"runtime-sha": value.runtime.sha,
|
|
385
386
|
};
|
|
386
387
|
}
|
|
387
|
-
|
|
388
388
|
export function normalizeControllerStatus(value) {
|
|
389
389
|
const normalized = required(value, "authority status");
|
|
390
390
|
if (
|
|
@@ -11,10 +11,9 @@ import {
|
|
|
11
11
|
|
|
12
12
|
const GITHUB_JSON_MAX_BUFFER = 16 * 1024 * 1024;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
14
|
+
const flag = (name, fallback = "") => process.argv.includes(`--${name}`)
|
|
15
|
+
? String(process.argv[process.argv.indexOf(`--${name}`) + 1] || "")
|
|
16
|
+
: fallback;
|
|
18
17
|
|
|
19
18
|
function commandJson(command, args, label, { publicReadFallback = false } = {}) {
|
|
20
19
|
const options = {
|
|
@@ -42,13 +41,9 @@ function commandJson(command, args, label, { publicReadFallback = false } = {})
|
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
return commandJson("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], label, { publicReadFallback });
|
|
47
|
-
}
|
|
44
|
+
const githubJson = (apiPath, label, { publicReadFallback = false } = {}) => commandJson("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], label, { publicReadFallback });
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
return githubJson(apiPath, label, { publicReadFallback: true });
|
|
51
|
-
}
|
|
46
|
+
const githubPublicJson = (apiPath, label) => githubJson(apiPath, label, { publicReadFallback: true });
|
|
52
47
|
|
|
53
48
|
function githubJsonOptional(apiPath, label, fallback, fallbackPattern = /404|not found/i) {
|
|
54
49
|
const result = spawnSyncCommand("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], {
|
|
@@ -68,9 +63,7 @@ function githubJsonOptional(apiPath, label, fallback, fallbackPattern = /404|not
|
|
|
68
63
|
}
|
|
69
64
|
}
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
return githubJsonOptional(apiPath, label, fallback, /401|403|404|unauthorized|forbidden|not found/i);
|
|
73
|
-
}
|
|
66
|
+
const githubJsonReadLimited = (apiPath, label, fallback) => githubJsonOptional(apiPath, label, fallback, /401|403|404|unauthorized|forbidden|not found/i);
|
|
74
67
|
|
|
75
68
|
function rulesetIncludesBranch(ruleset, branch, defaultBranch) {
|
|
76
69
|
const includes = ruleset.conditions?.ref_name?.include || [];
|
|
@@ -102,7 +95,7 @@ function normalizeRulesetBranchPolicy(rulesets, branch, defaultBranch) {
|
|
|
102
95
|
};
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
function jobBlock(workflowText, jobId) {
|
|
98
|
+
function jobBlock(workflowText, jobId, delegated = false) {
|
|
106
99
|
const lines = String(workflowText).split(/\r?\n/);
|
|
107
100
|
const jobsIndex = lines.findIndex((line) => /^jobs:\s*$/.test(line));
|
|
108
101
|
if (jobsIndex === -1) return "";
|
|
@@ -115,14 +108,21 @@ function jobBlock(workflowText, jobId) {
|
|
|
115
108
|
break;
|
|
116
109
|
}
|
|
117
110
|
}
|
|
118
|
-
|
|
111
|
+
const block = lines.slice(start, end).join("\n");
|
|
112
|
+
if (delegated) return block;
|
|
113
|
+
const declarations = [...block.matchAll(/^ {4}# buildchain-publication-authority-job:\s*([A-Za-z0-9_-]+)\s*$/gm)];
|
|
114
|
+
if (declarations.length > 1 || declarations[0]?.[1] === jobId) throw new Error(`publication authority job delegation is invalid: ${jobId}`);
|
|
115
|
+
if (!declarations.length) return block;
|
|
116
|
+
const delegatedJobId = declarations[0][1];
|
|
117
|
+
const delegatedBlock = jobBlock(workflowText, delegatedJobId, true);
|
|
118
|
+
if (!delegatedBlock) throw new Error(`delegated publication workflow job is missing: ${delegatedJobId}`);
|
|
119
|
+
return delegatedBlock;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
function first(value, keys) {
|
|
122
123
|
for (const key of keys) {
|
|
123
|
-
const parts = key.split(".");
|
|
124
124
|
let current = value;
|
|
125
|
-
for (const part of
|
|
125
|
+
for (const part of key.split(".")) current = current && typeof current === "object" ? current[part] : undefined;
|
|
126
126
|
if (current !== undefined && current !== null && current !== "") return current;
|
|
127
127
|
}
|
|
128
128
|
return undefined;
|
|
@@ -51,6 +51,8 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
51
51
|
buildchain release-tail compat --hooks-json <json-or-path> [--output <path>]
|
|
52
52
|
buildchain release-tail rehearse --capsule <absolute-path>
|
|
53
53
|
--candidate-root <absolute-path>
|
|
54
|
+
[--capsule-root <absolute-path>]
|
|
55
|
+
[--environment-json <empty-json-or-path>]
|
|
54
56
|
--mode <simulate|replay>
|
|
55
57
|
--state <absolute-path>
|
|
56
58
|
--evidence <absolute-path>
|
|
@@ -70,6 +72,7 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
70
72
|
buildchain release <inspect|recover|finalize|abort> ...
|
|
71
73
|
buildchain transaction inspect ...
|
|
72
74
|
buildchain collect github-release --tag <tag> [--repository <owner/repo>]
|
|
75
|
+
[--adopter-delivery-json <legacy-json-or-path>]
|
|
73
76
|
[--assets-dir <dir>] [--assets-json <json-or-path>]
|
|
74
77
|
[--release-json <json-or-path>] [--package-set-json <json-or-path>]
|
|
75
78
|
[--product-name <name>]
|
|
@@ -100,8 +100,8 @@ function dependencyCycles(graph) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function assertIndexShape(index) {
|
|
103
|
-
if (index?.schemaVersion !==
|
|
104
|
-
throw new Error("internal architecture index schemaVersion must be
|
|
103
|
+
if (index?.schemaVersion !== 2) {
|
|
104
|
+
throw new Error("internal architecture index schemaVersion must be 2");
|
|
105
105
|
}
|
|
106
106
|
for (const field of [
|
|
107
107
|
"coverageRoots",
|
|
@@ -209,7 +209,27 @@ function evaluateCapabilities(root, index) {
|
|
|
209
209
|
if (!Array.isArray(capability.tests) || capability.tests.length === 0) {
|
|
210
210
|
issues.push(`${capability.id}: test mapping is empty`);
|
|
211
211
|
}
|
|
212
|
-
|
|
212
|
+
if (!Array.isArray(capability.generatedOutputs)) {
|
|
213
|
+
issues.push(`${capability.id}: generated output mapping is missing`);
|
|
214
|
+
}
|
|
215
|
+
if (
|
|
216
|
+
!Array.isArray(capability.validationCommands) ||
|
|
217
|
+
capability.validationCommands.length === 0
|
|
218
|
+
) {
|
|
219
|
+
issues.push(`${capability.id}: validation command mapping is empty`);
|
|
220
|
+
} else if (
|
|
221
|
+
capability.validationCommands.some(
|
|
222
|
+
(command) => !String(command || "").trim(),
|
|
223
|
+
)
|
|
224
|
+
) {
|
|
225
|
+
issues.push(`${capability.id}: validation command is empty`);
|
|
226
|
+
}
|
|
227
|
+
for (const field of [
|
|
228
|
+
"implementation",
|
|
229
|
+
"tests",
|
|
230
|
+
"contracts",
|
|
231
|
+
"generatedOutputs",
|
|
232
|
+
]) {
|
|
213
233
|
for (const relativePath of capability[field] || []) {
|
|
214
234
|
if (!fs.existsSync(path.resolve(root, relativePath))) {
|
|
215
235
|
issues.push(
|
|
@@ -285,6 +285,10 @@ const channelPromotionWorkflow = fs.readFileSync(
|
|
|
285
285
|
path.join(root, ".github/workflows/release-candidate-promote.yml"),
|
|
286
286
|
"utf8",
|
|
287
287
|
);
|
|
288
|
+
const boundedAlphaRecoveryWorkflow = fs.readFileSync(
|
|
289
|
+
path.join(root, ".github/workflows/buildchain-ref-promotion-recovery.yml"),
|
|
290
|
+
"utf8",
|
|
291
|
+
);
|
|
288
292
|
const promotionShellRouting = parsePromotionShellRouting(
|
|
289
293
|
fs.readFileSync(path.join(root, ".buildchain/promotion-shell-routing.json"), "utf8"),
|
|
290
294
|
{ major: Number(selfDogfoodMajor) },
|
|
@@ -295,6 +299,20 @@ if (channelPromotionWorkflow !== generateChannelPromotionWorkflow(advancedPromot
|
|
|
295
299
|
})) {
|
|
296
300
|
throw new Error("generated channel promotion workflow is stale");
|
|
297
301
|
}
|
|
302
|
+
for (const requiredSnippet of [
|
|
303
|
+
"uses: kungfu-systems/buildchain/.github/workflows/.release-candidate-promote.yml@alpha/v4/v4.0",
|
|
304
|
+
"promotion-shell-ref: ${{ needs.consumer-admission.outputs.shell-call-ref }}",
|
|
305
|
+
"declarative-release-tail: true",
|
|
306
|
+
]) {
|
|
307
|
+
if (!boundedAlphaRecoveryWorkflow.includes(requiredSnippet)) {
|
|
308
|
+
throw new Error(`bounded alpha recovery missing bootstrap contract: ${requiredSnippet}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (const workflowSource of [boundedAlphaRecoveryWorkflow, channelPromotionWorkflow]) {
|
|
312
|
+
if (workflowSource.includes("channel-finalization-recovery")) {
|
|
313
|
+
throw new Error("Buildchain-only alpha bootstrap must not add a public or recovery workflow input");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
298
316
|
for (const requiredSnippet of [
|
|
299
317
|
"buildchain-channel:",
|
|
300
318
|
`/${promotionShellRouting.alpha.workflowPath}@${promotionShellRouting.alpha.callRef}`,
|
|
@@ -759,8 +777,8 @@ const registeredActionIds = (workflowRegistry.actions || []).map((entry) => entr
|
|
|
759
777
|
const readmeActionIndex = fs.readFileSync(path.join(root, "README.md"), "utf8");
|
|
760
778
|
const mapActionIndex = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
|
|
761
779
|
const retrospectiveActionIndex = fs.readFileSync(path.join(root, ".github/retrospectives/2026-07-10-buildchain-consolidation.md"), "utf8");
|
|
762
|
-
if (registeredActionIds.length !==
|
|
763
|
-
throw new Error(`workflow-registry.json must expose the
|
|
780
|
+
if (registeredActionIds.length !== 8) {
|
|
781
|
+
throw new Error(`workflow-registry.json must expose the eight current action entries, got ${registeredActionIds.length}`);
|
|
764
782
|
}
|
|
765
783
|
for (const actionId of registeredActionIds) {
|
|
766
784
|
if (!readmeActionIndex.includes(`actions/${actionId}`) || !mapActionIndex.includes(`actions/${actionId}`)) {
|
|
@@ -1021,16 +1039,16 @@ for (const forbiddenSnippet of [
|
|
|
1021
1039
|
for (const requiredSnippet of [
|
|
1022
1040
|
"id-token: write",
|
|
1023
1041
|
"actions: write",
|
|
1024
|
-
"uses: kungfu-systems/buildchain/.github/workflows
|
|
1042
|
+
"uses: kungfu-systems/buildchain/.github/workflows/.release-candidate-promote.yml@v4-alpha",
|
|
1025
1043
|
"github.event.workflow_run.event == 'push'",
|
|
1026
1044
|
"!startsWith(github.event.workflow_run.display_title, 'chore(release): prepare v')",
|
|
1027
1045
|
"!startsWith(github.event.workflow_run.display_title, 'chore(release): release v')",
|
|
1028
1046
|
"resume-candidate-run-id:",
|
|
1029
1047
|
"resume-expected-source-tree:",
|
|
1030
|
-
"resume-buildchain-runtime-
|
|
1048
|
+
"resume-buildchain-runtime-ref:",
|
|
1031
1049
|
"uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4",
|
|
1032
1050
|
"release-passport-buildchain-self-kfd: true",
|
|
1033
|
-
"
|
|
1051
|
+
"declarative-release-tail: true",
|
|
1034
1052
|
"release-passport-impact-json: .buildchain/release-impact.json",
|
|
1035
1053
|
]) {
|
|
1036
1054
|
if (!buildchainRefPromotionWorkflow.includes(requiredSnippet)) {
|