@kungfu-tech/buildchain 4.0.1-alpha.41 → 4.0.1-alpha.42
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/ci-lane-change-budget.json +53 -1
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/maintainability-policy.json +15 -13
- 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 +70 -19
- package/dist/site/buildchain-site.json +62 -14
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/controller-registry.json +6 -2
- package/dist/site/kfd-claims.json +99 -12
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +261 -17
- package/dist/site/page-registry.json +55 -7
- package/dist/site/public-surface-audit.json +43 -7
- package/dist/site/publication-authority-registry.json +28 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +53 -6
- package/docs/MAP.md +1 -1
- package/docs/node-api-reference.md +60 -32
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/package.json +3 -1
- package/packages/core/buildchain-contract.js +31 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/index.js +1 -0
- package/packages/core/v4-publication-qualification.js +222 -0
- package/scripts/check-inventory.mjs +2 -2
- package/scripts/generate-release-candidate-passport.mjs +26 -1
- package/scripts/generate-site-bundle.mjs +6 -0
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/site-capability-metadata.mjs +1 -0
- package/scripts/v4-declarative-promotion-admission.mjs +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "4.0.1-alpha.
|
|
3
|
+
"version": "4.0.1-alpha.42",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"./v4-tail-reseal": "./packages/core/v4-tail-reseal.js",
|
|
49
49
|
"./v4-tail-reseal-receipt": "./packages/core/v4-tail-reseal-receipt.js",
|
|
50
50
|
"./v4-publication-rehearsal": "./packages/core/v4-publication-rehearsal.js",
|
|
51
|
+
"./v4-publication-qualification": "./packages/core/v4-publication-qualification.js",
|
|
51
52
|
"./adopter-delivery-gate": "./packages/core/adopter-delivery-gate.js",
|
|
52
53
|
"./kfd-adopter-category-driver": "./packages/core/kfd-adopter-category-driver.js",
|
|
53
54
|
"./buildchain-delivery-infrastructure": "./packages/core/buildchain-delivery-infrastructure.js",
|
|
@@ -114,6 +115,7 @@
|
|
|
114
115
|
"./site/schemas/v4-publication-rehearsal-capsule-v1.schema.json": "./dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json",
|
|
115
116
|
"./site/schemas/v4-adopter-delivery-v1.schema.json": "./dist/site/schemas/v4-adopter-delivery-v1.schema.json",
|
|
116
117
|
"./site/schemas/release-tail-capabilities-v1.schema.json": "./dist/site/schemas/release-tail-capabilities-v1.schema.json",
|
|
118
|
+
"./site/schemas/v4-publication-qualification-v1.schema.json": "./dist/site/schemas/v4-publication-qualification-v1.schema.json",
|
|
117
119
|
"./site/buildchain-contract.json": "./dist/site/buildchain-contract.json",
|
|
118
120
|
"./site/product-mechanism.json": "./dist/site/product-mechanism.json",
|
|
119
121
|
"./site/release-provenance.json": "./dist/site/release-provenance.json",
|
|
@@ -166,6 +166,36 @@ function declarativeAuditableDemoSurface(root, pkg, majorLine) {
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function v4ReleaseCandidatePromoteSurface(root, majorLine) {
|
|
170
|
+
return surface(root, {
|
|
171
|
+
id: "v4-release-candidate-promote-action",
|
|
172
|
+
kind: "action",
|
|
173
|
+
path: "actions/v4-release-candidate-promote/action.yml",
|
|
174
|
+
publicRef: `kungfu-systems/buildchain/actions/v4-release-candidate-promote@${majorLine}`,
|
|
175
|
+
requiredInputs: [
|
|
176
|
+
"token", "repository", "source-sha", "version", "tag", "channel",
|
|
177
|
+
"candidate-passport-path", "stage-capsules-path",
|
|
178
|
+
"publication-qualification-path",
|
|
179
|
+
],
|
|
180
|
+
requiredOutputs: [
|
|
181
|
+
"release-passport-path", "release-passport-root", "transaction-state",
|
|
182
|
+
"declaration-root", "transaction-root", "state-root",
|
|
183
|
+
"receipt-roots-json",
|
|
184
|
+
],
|
|
185
|
+
breakingDefaults: {
|
|
186
|
+
statePath: ".buildchain/release-tail/state.json",
|
|
187
|
+
executionBoundary: "built-in-provider-plane-only",
|
|
188
|
+
},
|
|
189
|
+
optionalInputs: ["artifact-paths", "state-path", "failure-after-capability"],
|
|
190
|
+
guarantees: [
|
|
191
|
+
"v4 promotion consumes only sealed candidate, Stage Capsule, qualification, and artifact evidence",
|
|
192
|
+
"GitHub Release, signed-channel commit, activation, readback, and released-evidence synthesis use built-in provider adapters",
|
|
193
|
+
"durable provider checkpoints resume completed capabilities without replay",
|
|
194
|
+
"no consumer command, script, shell, checkout, or executable tail hook is accepted",
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
169
199
|
export function createBuildchainContractWorld({
|
|
170
200
|
root = process.cwd(),
|
|
171
201
|
packageJson = undefined,
|
|
@@ -484,7 +514,7 @@ export function createBuildchainContractWorld({
|
|
|
484
514
|
"Build Images owns encoding, Buildchain owns qualification and receipts, and site repositories own browser loading and accessibility behavior",
|
|
485
515
|
"media qualification does not claim browser playback, responsive layout, reduced-motion behavior, accessibility, or production deployment",
|
|
486
516
|
],
|
|
487
|
-
}), declarativeAuditableDemoSurface(root, pkg, majorLine),
|
|
517
|
+
}), declarativeAuditableDemoSurface(root, pkg, majorLine), v4ReleaseCandidatePromoteSurface(root, majorLine),
|
|
488
518
|
surface(root, {
|
|
489
519
|
id: "promote-buildchain-ref-action",
|
|
490
520
|
kind: "action",
|
|
@@ -58,6 +58,7 @@ const DESCRIPTORS = Object.freeze([
|
|
|
58
58
|
[".github/workflows/release-tail.yml", "product-publication", true, ["artifact.publish", "signed-channel.commit", "release.activate", "released-evidence.synthesize"], "caller-secrets", "caller-bound", "caller-bound", "fixed"],
|
|
59
59
|
[".github/workflows/release-verify.yml", "governance-write"],
|
|
60
60
|
[".github/workflows/stable-candidate-patrol.yml", "governance-write"],
|
|
61
|
+
[".github/workflows/v4-declarative-release-tail-dogfood.yml", "product-publication", true, ["artifact.publish", "signed-channel.commit", "release.activate", "released-evidence.synthesize"], "caller-secrets", "none", "fixed", "fixed"],
|
|
61
62
|
]);
|
|
62
63
|
|
|
63
64
|
export function buildchainPublicationAuthorityDescriptors() {
|
package/packages/core/index.js
CHANGED
|
@@ -646,6 +646,7 @@ export {
|
|
|
646
646
|
export * from "./release-propagation.js";
|
|
647
647
|
export * from "./v4-floating-consumer-policy.js";
|
|
648
648
|
export * from "./v4-runtime-ref-resume-authority.js";
|
|
649
|
+
export * from "./v4-publication-qualification.js";
|
|
649
650
|
|
|
650
651
|
export {
|
|
651
652
|
RELEASE_ACTIVATION_CONTRACT,
|
|
@@ -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
|
+
}
|
|
@@ -759,8 +759,8 @@ const registeredActionIds = (workflowRegistry.actions || []).map((entry) => entr
|
|
|
759
759
|
const readmeActionIndex = fs.readFileSync(path.join(root, "README.md"), "utf8");
|
|
760
760
|
const mapActionIndex = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
|
|
761
761
|
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
|
|
762
|
+
if (registeredActionIds.length !== 8) {
|
|
763
|
+
throw new Error(`workflow-registry.json must expose the eight current action entries, got ${registeredActionIds.length}`);
|
|
764
764
|
}
|
|
765
765
|
for (const actionId of registeredActionIds) {
|
|
766
766
|
if (!readmeActionIndex.includes(`actions/${actionId}`) || !mapActionIndex.includes(`actions/${actionId}`)) {
|
|
@@ -8,6 +8,7 @@ import { scanV4FloatingConsumerPolicy, v4ConsumerPolicyScannerRoot } from "../pa
|
|
|
8
8
|
import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
9
9
|
import { V4_STAGE_CAPSULE_CONTRACT, V4_STAGE_CAPSULE_IDENTITY_CONTRACT, v4StageCapsuleIdentityRoot, v4StageCapsuleRoot, validateV4StageCapsule } from "../packages/core/v4-stage-capsule.js";
|
|
10
10
|
import { v4RuntimeResumeDocumentRoot } from "../packages/core/v4-runtime-ref-resume-authority.js";
|
|
11
|
+
import { createV4PublicationQualificationReceipt } from "../packages/core/v4-publication-qualification.js";
|
|
11
12
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
12
13
|
|
|
13
14
|
const env = (name, fallback = "") => process.env[name] || fallback;
|
|
@@ -32,7 +33,20 @@ export function createReleaseCandidateStageCapsules({
|
|
|
32
33
|
const coordinateByPlatform = new Map(
|
|
33
34
|
coordinates.artifacts.map((entry) => [entry.platformId, entry]),
|
|
34
35
|
);
|
|
35
|
-
const
|
|
36
|
+
const qualificationArtifacts = passport.platformMatrix.map((platform) => {
|
|
37
|
+
const coordinate = coordinateByPlatform.get(platform.platformId);
|
|
38
|
+
if (!coordinate || coordinate.name !== platform.artifactName) throw new Error(`Stage Capsule coordinate missing for ${platform.platformId}`);
|
|
39
|
+
const manifest = readJsonFile(platform.manifestPath);
|
|
40
|
+
return { role: platform.artifactName, platform: platform.platformId, artifactRoot: coordinate.digest, manifestRoot: v4ContentRoot("stage-capsule-artifact-manifest", manifest) };
|
|
41
|
+
});
|
|
42
|
+
const qualificationExpiresAt = coordinates.artifacts.map((entry) => entry.expiresAt).sort((left, right) => Date.parse(left) - Date.parse(right))[0];
|
|
43
|
+
const qualificationReceipt = createV4PublicationQualificationReceipt({
|
|
44
|
+
repository: passport.repository, candidateRoot: `sha256:${passport.candidateHash}`,
|
|
45
|
+
sourceSha: passport.source.headSha, sourceRoot: v4ContentRoot("candidate-identity", passport.source),
|
|
46
|
+
policyDigest: passport.consumerPolicy.receiptRoot, artifacts: qualificationArtifacts,
|
|
47
|
+
issuedAt: passport.createdAt || new Date(Date.parse(qualificationExpiresAt) - 86_400_000).toISOString(), expiresAt: qualificationExpiresAt,
|
|
48
|
+
});
|
|
49
|
+
const qualificationRoot = qualificationReceipt.receiptRoot;
|
|
36
50
|
const entries = passport.platformMatrix
|
|
37
51
|
.map((platform) => {
|
|
38
52
|
const coordinate = coordinateByPlatform.get(platform.platformId);
|
|
@@ -93,6 +107,9 @@ export function createReleaseCandidateStageCapsules({
|
|
|
93
107
|
platform: platform.platformId,
|
|
94
108
|
artifactName: coordinate.name,
|
|
95
109
|
artifactDigest: coordinate.digest,
|
|
110
|
+
publicationArtifact: qualificationArtifacts.find(
|
|
111
|
+
({ platform: platformId }) => platformId === platform.platformId,
|
|
112
|
+
),
|
|
96
113
|
artifact: coordinate,
|
|
97
114
|
capsule,
|
|
98
115
|
};
|
|
@@ -112,6 +129,8 @@ export function createReleaseCandidateStageCapsules({
|
|
|
112
129
|
treeSha: passport.source.treeHash,
|
|
113
130
|
},
|
|
114
131
|
consumerPolicyReceiptRoot: passport.consumerPolicy.receiptRoot,
|
|
132
|
+
publicationQualificationRoot: qualificationRoot,
|
|
133
|
+
publicationQualificationReceipt: qualificationReceipt,
|
|
115
134
|
capsules: entries,
|
|
116
135
|
};
|
|
117
136
|
return { ...body, root: v4RuntimeResumeDocumentRoot(body) };
|
|
@@ -150,12 +169,16 @@ function writeReleaseCandidateStageCapsules({
|
|
|
150
169
|
stageCapsulesPath,
|
|
151
170
|
`${JSON.stringify(stageCapsules, null, 2)}\n`,
|
|
152
171
|
);
|
|
172
|
+
const qualificationPath = path.join(path.dirname(outputPath), "release-candidate-publication-qualification.json");
|
|
173
|
+
fs.writeFileSync(qualificationPath, `${JSON.stringify(stageCapsules.publicationQualificationReceipt, null, 2)}\n`);
|
|
153
174
|
return {
|
|
154
175
|
path: path
|
|
155
176
|
.relative(process.cwd(), stageCapsulesPath)
|
|
156
177
|
.split(path.sep)
|
|
157
178
|
.join("/"),
|
|
158
179
|
root: stageCapsules.root,
|
|
180
|
+
qualificationPath: path.relative(process.cwd(), qualificationPath).split(path.sep).join("/"),
|
|
181
|
+
qualificationRoot: stageCapsules.publicationQualificationRoot,
|
|
159
182
|
};
|
|
160
183
|
}
|
|
161
184
|
|
|
@@ -246,6 +269,8 @@ export function generateReleaseCandidatePassportCli() {
|
|
|
246
269
|
}),
|
|
247
270
|
"release-candidate-stage-capsules-path": stageCapsules.path,
|
|
248
271
|
"release-candidate-stage-capsules-root": stageCapsules.root,
|
|
272
|
+
"release-candidate-publication-qualification-path": stageCapsules.qualificationPath,
|
|
273
|
+
"release-candidate-publication-qualification-root": stageCapsules.qualificationRoot,
|
|
249
274
|
});
|
|
250
275
|
return passport;
|
|
251
276
|
}
|
|
@@ -66,10 +66,15 @@ const RELEASE_TAIL_CAPABILITIES_SCHEMA =
|
|
|
66
66
|
"schemas/release-tail-capabilities-v1.schema.json";
|
|
67
67
|
const RELEASE_TAIL_CAPABILITIES_SCHEMA_SOURCE =
|
|
68
68
|
"contracts/release-tail-capabilities-v1.schema.json";
|
|
69
|
+
const V4_PUBLICATION_QUALIFICATION_SCHEMA =
|
|
70
|
+
"schemas/v4-publication-qualification-v1.schema.json";
|
|
71
|
+
const V4_PUBLICATION_QUALIFICATION_SCHEMA_SOURCE =
|
|
72
|
+
"contracts/v4-publication-qualification-v1.schema.json";
|
|
69
73
|
const PUBLICATION_REHEARSAL_SITE_SCHEMAS = [
|
|
70
74
|
V4_PUBLICATION_REHEARSAL_SCHEMA,
|
|
71
75
|
V4_ADOPTER_DELIVERY_SCHEMA,
|
|
72
76
|
RELEASE_TAIL_CAPABILITIES_SCHEMA,
|
|
77
|
+
V4_PUBLICATION_QUALIFICATION_SCHEMA,
|
|
73
78
|
];
|
|
74
79
|
const root = path.resolve(import.meta.dirname, "..");
|
|
75
80
|
const outputDir = path.join(root, "dist", "site");
|
|
@@ -102,6 +107,7 @@ function siteFileBytes(name, value) {
|
|
|
102
107
|
if (name === DEV_DELIVERY_AUTHORITY_SCHEMA) return readText(DEV_DELIVERY_AUTHORITY_SCHEMA_SOURCE);
|
|
103
108
|
if (name === V4_COMPATIBILITY_FACTS_SCHEMA) return readText(V4_COMPATIBILITY_FACTS_SCHEMA_SOURCE);
|
|
104
109
|
if (name === V4_PUBLICATION_REHEARSAL_SCHEMA) return readText(V4_PUBLICATION_REHEARSAL_SCHEMA_SOURCE);
|
|
110
|
+
if (name === V4_PUBLICATION_QUALIFICATION_SCHEMA) return readText(V4_PUBLICATION_QUALIFICATION_SCHEMA_SOURCE);
|
|
105
111
|
if (name === V4_ADOPTER_DELIVERY_SCHEMA) return readText(V4_ADOPTER_DELIVERY_SCHEMA_SOURCE);
|
|
106
112
|
if (name === RELEASE_TAIL_CAPABILITIES_SCHEMA) return readText(RELEASE_TAIL_CAPABILITIES_SCHEMA_SOURCE);
|
|
107
113
|
return stableJson(value);
|
|
@@ -9,6 +9,8 @@ import { pipeline } from "node:stream/promises";
|
|
|
9
9
|
import { pathToFileURL } from "node:url";
|
|
10
10
|
import { createResolvedPublicationSealedBundle } from "./publication-candidate-sealer.mjs";
|
|
11
11
|
import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
12
|
+
import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
13
|
+
import { v4PublicationQualificationRoot, validateV4PublicationQualificationReceipt } from "../packages/core/v4-publication-qualification.js";
|
|
12
14
|
|
|
13
15
|
const DEFAULT_WORKFLOW_FILE = "build-surface-fixture.yml";
|
|
14
16
|
|
|
@@ -434,6 +436,42 @@ function findDownloadedFile(root, filename) {
|
|
|
434
436
|
return "";
|
|
435
437
|
}
|
|
436
438
|
|
|
439
|
+
function resolveV4PublicationEvidence(passportDir, passport, outputPath) {
|
|
440
|
+
const stageCapsulesPath = findDownloadedFile(passportDir, "release-candidate-stage-capsules.json");
|
|
441
|
+
const publicationQualificationPath = findDownloadedFile(passportDir, "release-candidate-publication-qualification.json");
|
|
442
|
+
if (!passport.consumerPolicy?.receiptRoot) {
|
|
443
|
+
return {
|
|
444
|
+
paths: {
|
|
445
|
+
stageCapsules: stageCapsulesPath ? outputPath(stageCapsulesPath) : "",
|
|
446
|
+
publicationQualification: publicationQualificationPath ? outputPath(publicationQualificationPath) : "",
|
|
447
|
+
},
|
|
448
|
+
publicationQualificationRoot: "",
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (!stageCapsulesPath || !publicationQualificationPath) {
|
|
452
|
+
throw new Error("v4 release candidate is missing Stage Capsules or publication qualification receipt");
|
|
453
|
+
}
|
|
454
|
+
const stageCapsules = JSON.parse(fs.readFileSync(stageCapsulesPath, "utf8"));
|
|
455
|
+
const qualification = JSON.parse(fs.readFileSync(publicationQualificationPath, "utf8"));
|
|
456
|
+
validateV4PublicationQualificationReceipt(qualification, {
|
|
457
|
+
repository: passport.repository,
|
|
458
|
+
candidateRoot: `sha256:${passport.candidateHash}`,
|
|
459
|
+
sourceSha: passport.source.headSha,
|
|
460
|
+
sourceRoot: v4ContentRoot("candidate-identity", passport.source),
|
|
461
|
+
artifactRoot: v4PublicationQualificationRoot(stageCapsules.capsules.map(({ publicationArtifact }) => publicationArtifact)),
|
|
462
|
+
policyDigest: passport.consumerPolicy.receiptRoot,
|
|
463
|
+
});
|
|
464
|
+
if (stageCapsules.publicationQualificationRoot !== qualification.receiptRoot) throw new Error("Stage Capsules do not bind the publication qualification receipt");
|
|
465
|
+
if (stageCapsules.capsules.some(({ capsule }) => capsule.identity.qualificationRoot !== qualification.receiptRoot)) throw new Error("a Stage Capsule does not bind the publication qualification receipt");
|
|
466
|
+
return {
|
|
467
|
+
paths: {
|
|
468
|
+
stageCapsules: outputPath(stageCapsulesPath),
|
|
469
|
+
publicationQualification: outputPath(publicationQualificationPath),
|
|
470
|
+
},
|
|
471
|
+
publicationQualificationRoot: qualification.receiptRoot,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
437
475
|
export async function resolveReleaseCandidateArtifacts({
|
|
438
476
|
repository,
|
|
439
477
|
targetRef,
|
|
@@ -667,6 +705,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
667
705
|
throw new Error("downloaded release-candidate artifacts did not contain release-candidate-passport.json and build-summary.json");
|
|
668
706
|
}
|
|
669
707
|
const passport = JSON.parse(fs.readFileSync(passportPath, "utf8"));
|
|
708
|
+
const v4Publication = resolveV4PublicationEvidence(passportDir, passport, outputPath);
|
|
670
709
|
const platformManifestPaths = findDownloadedFiles(payloadDir, "manifest.json");
|
|
671
710
|
const githubArtifactAttestationPolicyPaths = findDownloadedFiles(
|
|
672
711
|
payloadDir,
|
|
@@ -722,6 +761,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
722
761
|
paths: {
|
|
723
762
|
passport: outputPath(passportPath),
|
|
724
763
|
buildSummary: outputPath(buildSummaryPath),
|
|
764
|
+
...v4Publication.paths,
|
|
725
765
|
payloads: outputPath(payloadDir),
|
|
726
766
|
platformManifests: platformManifestPaths.map(outputPath),
|
|
727
767
|
githubArtifactAttestationPolicies: githubArtifactAttestationPolicyPaths.map(outputPath),
|
|
@@ -738,6 +778,7 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
738
778
|
githubArtifactAttestationPolicyCount: githubArtifactAttestationPolicyPaths.length,
|
|
739
779
|
npmTarballCount: npmTarballPaths.length,
|
|
740
780
|
publishRequiredArtifacts: generatedRequiredArtifacts,
|
|
781
|
+
publicationQualificationRoot: v4Publication.publicationQualificationRoot,
|
|
741
782
|
};
|
|
742
783
|
}
|
|
743
784
|
|
|
@@ -766,6 +807,9 @@ export async function resolveReleaseCandidateArtifactsCli() {
|
|
|
766
807
|
"promote-only-release-candidate": String(result.enabled === true),
|
|
767
808
|
"release-candidate-passport-path": result.paths?.passport || "",
|
|
768
809
|
"release-candidate-build-summary-path": result.paths?.buildSummary || "",
|
|
810
|
+
"release-candidate-stage-capsules-path": result.paths?.stageCapsules || "",
|
|
811
|
+
"release-candidate-publication-qualification-path": result.paths?.publicationQualification || "",
|
|
812
|
+
"release-candidate-publication-qualification-root": result.publicationQualificationRoot || "",
|
|
769
813
|
"release-candidate-version": result.version || "",
|
|
770
814
|
"release-candidate-source-sha": result.artifacts?.sourceSha || "",
|
|
771
815
|
"release-candidate-artifact": result.artifacts?.passport || "",
|
|
@@ -230,6 +230,7 @@ export function nodeApiMeta(exportName) {
|
|
|
230
230
|
"./v4-tail-reseal": { group: "release-passport-trust", summary: "Effect-pure retained Stage Capsule validation and explicit Warrant-, credential-, and release-tail-fenced macOS reseal planning APIs." },
|
|
231
231
|
"./v4-tail-reseal-receipt": { group: "release-passport-trust", summary: "Four-platform byte readback, standard candidate Passport binding, and terminal v4 tail-reseal receipt APIs." },
|
|
232
232
|
"./v4-publication-rehearsal": { group: "release-passport-trust", summary: "Content-addressed v4 publication rehearsal capsules, shared production release-tail execution, replay, provider observation, and fail-closed verification APIs." },
|
|
233
|
+
"./v4-publication-qualification": { group: "release-passport-trust", summary: "Sealed v4 candidate, source, artifact, policy, freshness, and command-free promotion admission APIs." },
|
|
233
234
|
"./adopter-delivery-gate": { group: "reusable-build", summary: "Protocol-neutral adopter delivery gate, driver interface, artifact profiles, and canonical result APIs." },
|
|
234
235
|
"./kfd-adopter-category-driver": { group: "kfd-trust", summary: "Published KFD adopter-category driver and exact package-cut verification APIs." },
|
|
235
236
|
"./buildchain-delivery-infrastructure": { group: "reusable-build", summary: "Public delivery-infrastructure instance creation and KFD verification APIs." },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { assertV4DeclarativePromotionInputs } from "../packages/core/v4-publication-qualification.js";
|
|
5
|
+
|
|
6
|
+
export function admitV4DeclarativePromotion({
|
|
7
|
+
inputs,
|
|
8
|
+
runtimeRef,
|
|
9
|
+
declarative,
|
|
10
|
+
}) {
|
|
11
|
+
if (!/^v4(?:$|[-./])/u.test(String(runtimeRef || ""))) {
|
|
12
|
+
return Object.freeze({ mode: "legacy", admitted: true });
|
|
13
|
+
}
|
|
14
|
+
if (declarative !== true) {
|
|
15
|
+
const error = new Error(
|
|
16
|
+
"Buildchain v4 release-candidate-promote requires declarative-release-tail=true",
|
|
17
|
+
);
|
|
18
|
+
error.code = "v4-declarative-release-tail-required";
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
assertV4DeclarativePromotionInputs(inputs);
|
|
22
|
+
return Object.freeze({ mode: "v4-declarative", admitted: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
process.argv[1] &&
|
|
27
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
28
|
+
) {
|
|
29
|
+
try {
|
|
30
|
+
const result = admitV4DeclarativePromotion({
|
|
31
|
+
inputs: JSON.parse(process.env.BUILDCHAIN_PROMOTION_INPUTS_JSON || "{}"),
|
|
32
|
+
runtimeRef: process.env.BUILDCHAIN_RUNTIME_REF,
|
|
33
|
+
declarative: process.env.BUILDCHAIN_DECLARATIVE_RELEASE_TAIL === "true",
|
|
34
|
+
});
|
|
35
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
process.stderr.write(
|
|
38
|
+
`v4-declarative-promotion-admission: ${error.code || "rejected"}: ${error.message}\n`,
|
|
39
|
+
);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
}
|