@kungfu-tech/buildchain 3.0.2 → 3.0.3-alpha.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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -1
- package/actions/promote-buildchain-ref/README.md +90 -19
- package/actions/report-buildchain-issue/README.md +20 -2
- package/actions/run-lifecycle/README.md +5 -0
- package/actions/validate-config/README.md +19 -1
- package/bin/buildchain.mjs +56 -2
- package/bin/internal/command-registry.mjs +82 -0
- package/bin/internal/trust-release-cli.mjs +1 -0
- package/contracts/auditable-demo-media-profiles-v1.json +116 -1
- package/contracts/buildchain-v2-residuals-v1.json +146 -0
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
- package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
- package/dist/site/buildchain-contract.json +188 -46
- package/dist/site/buildchain-site.json +181 -90
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/cli-registry.json +860 -98
- package/dist/site/controller-registry.json +118 -5
- package/dist/site/kfd-claims.json +233 -19
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -20
- package/dist/site/node-api-registry.json +374 -59
- package/dist/site/page-registry.json +156 -65
- package/dist/site/public-surface-audit.json +231 -22
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +3 -0
- package/dist/site/site-manifest.json +24 -24
- package/dist/site/workflow-registry.json +575 -79
- package/docs/MAP.md +22 -4
- package/docs/auditable-demo.md +54 -13
- package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
- package/docs/cli.md +119 -12
- package/docs/consumer-issue-reporting.md +20 -2
- package/docs/dev-alpha-candidate-patrol.md +24 -10
- package/docs/github-governance-authority.md +24 -31
- package/docs/lifecycle-protocol.md +20 -2
- package/docs/migration-inventory.md +24 -5
- package/docs/observed-evidence-patrol.md +28 -12
- package/docs/ownership.md +19 -1
- package/docs/publication-artifacts.md +158 -18
- package/docs/publication-authority.md +4 -4
- package/docs/publish-transaction.md +78 -31
- package/docs/release-candidate.md +24 -0
- package/docs/release-flow.md +50 -32
- package/docs/release-governance.md +39 -32
- package/docs/release-passport.md +4 -0
- package/docs/release-propagation.md +57 -12
- package/docs/reusable-build-surface.md +218 -90
- package/docs/runtime-train-validation.md +20 -2
- package/docs/shifu-gate-profiles.md +7 -1
- package/docs/stable-candidate-patrol.md +6 -6
- package/docs/toolkit-observability.md +22 -0
- package/docs/versioning.md +46 -24
- package/docs/web-surface-deployments.md +82 -1
- package/package.json +7 -5
- package/packages/core/README.md +60 -4
- package/packages/core/artifact-signing.js +1 -0
- package/packages/core/buildchain-contract.js +9 -4
- package/packages/core/cache-evidence.js +1 -0
- package/packages/core/controller-evidence.js +2 -0
- package/packages/core/diagnostics.js +105 -30
- package/packages/core/github-governance-authority.js +25 -17
- package/packages/core/index.js +40 -0
- package/packages/core/paper.js +3226 -0
- package/packages/core/public-surface-audit.js +3 -0
- package/packages/core/publication-artifact.js +1 -6
- package/packages/core/publication-authority.js +1 -0
- package/packages/core/publication-control-plane-audit.js +28 -0
- package/packages/core/publication-reproducibility.js +947 -0
- package/packages/core/publication-sealed-bundle.js +190 -0
- package/packages/core/publish-transaction.js +147 -17
- package/packages/core/release-candidate.js +234 -0
- package/packages/core/release-passport.js +519 -228
- package/packages/core/release-propagation.js +175 -1
- package/packages/core/stable-release-gate.js +4 -1
- package/scripts/artifact-signing-delegation.mjs +268 -0
- package/scripts/audit-github-governance.mjs +32 -13
- package/scripts/audit-publication-control-plane.mjs +17 -8
- package/scripts/auditable-demo.mjs +266 -7
- package/scripts/aws-codebuild-toolchain.mjs +285 -0
- package/scripts/aws-macos-jit-core.mjs +378 -0
- package/scripts/aws-macos-jit.mjs +123 -0
- package/scripts/aws-runner-burst-core.mjs +248 -0
- package/scripts/aws-runner-burst.mjs +79 -0
- package/scripts/aws-windows-jit-core.mjs +374 -0
- package/scripts/aws-windows-jit.mjs +121 -0
- package/scripts/build-contract-core.mjs +69 -6
- package/scripts/build-standalone-binary.mjs +23 -0
- package/scripts/buildchain-contract-lock.mjs +1 -1
- package/scripts/buildchain-patrol.mjs +1 -1
- package/scripts/check-internal-architecture.mjs +69 -2
- package/scripts/check-inventory.mjs +70 -2
- package/scripts/check-javascript-syntax.mjs +31 -0
- package/scripts/check-maintainability.mjs +371 -0
- package/scripts/compiler-cache-evidence.mjs +221 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
- package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
- package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
- package/scripts/gate-profile-core.mjs +6 -1
- package/scripts/generate-channel-promotion-workflow.mjs +3 -12
- package/scripts/generate-release-candidate-passport.mjs +4 -0
- package/scripts/generate-site-bundle.mjs +84 -18
- package/scripts/import-artifact-signing-results.mjs +64 -5
- package/scripts/inspect-artifact-signing-requests.mjs +52 -14
- package/scripts/maintainability-metrics.mjs +339 -0
- package/scripts/npm-publish-transaction.mjs +57 -6
- package/scripts/observed-evidence.mjs +151 -33
- package/scripts/paper.mjs +645 -0
- package/scripts/promotion-channel-router.mjs +16 -4
- package/scripts/publication-reproducibility.mjs +62 -0
- package/scripts/reconcile-github-governance.mjs +8 -1
- package/scripts/release-propagation.mjs +39 -0
- package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
- package/scripts/resolve-build-contract.mjs +6 -0
- package/scripts/route-offline-runners.mjs +236 -0
- package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
- package/scripts/run-lifecycle-core.mjs +62 -1
- package/scripts/runtime-ref-core.mjs +36 -4
- package/scripts/seal-artifact-signing-requests.mjs +64 -12
- package/scripts/stable-candidate-qualification.mjs +50 -0
- package/scripts/verify-artifact-signing-results.mjs +26 -1
- package/scripts/web-surface-core.mjs +272 -86
- package/scripts/web-surface-production-decision.mjs +19 -3
|
@@ -24,6 +24,40 @@ function assertHex(value, label) {
|
|
|
24
24
|
if (!/^[0-9a-f]{64}$/.test(String(value || ""))) throw new Error(`${label} must be a lowercase sha256 hex digest`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function requiredHeader(value, label) {
|
|
28
|
+
const normalized = String(value || "").trim();
|
|
29
|
+
if (!normalized || /[\r\n]/.test(normalized)) throw new Error(`${label} must be a non-empty single-line value`);
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function validatePublicationEntry({ entry, kind, root, snapshotId, requireSnapshotDocument = false, requireHeaders = false }) {
|
|
34
|
+
const source = safeRelative(entry.source, `${kind}.source`);
|
|
35
|
+
const key = safeRelative(entry.key, `${kind}.key`);
|
|
36
|
+
const file = path.resolve(root, source);
|
|
37
|
+
if (file !== root && !file.startsWith(`${root}${path.sep}`)) throw new Error(`${kind}.source escapes artifact root`);
|
|
38
|
+
if (!fs.statSync(file).isFile()) throw new Error(`${kind}.source is not a file: ${source}`);
|
|
39
|
+
assertHex(entry.sha256, `${kind}.sha256`);
|
|
40
|
+
const actual = sha256(file);
|
|
41
|
+
if (actual !== entry.sha256) throw new Error(`${kind}.sha256 does not match ${source}`);
|
|
42
|
+
if (requireSnapshotDocument) {
|
|
43
|
+
const document = readJson(file);
|
|
44
|
+
if (document.snapshotId !== snapshotId) throw new Error(`${kind} document snapshotId does not match manifest`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
kind,
|
|
48
|
+
source,
|
|
49
|
+
key,
|
|
50
|
+
file,
|
|
51
|
+
sha256: actual,
|
|
52
|
+
contentType: requireHeaders
|
|
53
|
+
? requiredHeader(entry.contentType, `${kind}.contentType`)
|
|
54
|
+
: String(entry.contentType || "application/json"),
|
|
55
|
+
cacheControl: requireHeaders
|
|
56
|
+
? requiredHeader(entry.cacheControl, `${kind}.cacheControl`)
|
|
57
|
+
: String(entry.cacheControl || ""),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
export function validateObservedEvidenceBundle({ manifestPath, artifactRoot = path.dirname(manifestPath) }) {
|
|
28
62
|
const resolvedManifest = path.resolve(manifestPath);
|
|
29
63
|
const root = path.resolve(artifactRoot);
|
|
@@ -36,26 +70,39 @@ export function validateObservedEvidenceBundle({ manifestPath, artifactRoot = pa
|
|
|
36
70
|
if (!Number.isFinite(Date.parse(manifest.snapshot?.observedAt || ""))) throw new Error("snapshot.observedAt must be ISO-8601");
|
|
37
71
|
const immutable = manifest.publication?.immutable || {};
|
|
38
72
|
const latest = manifest.publication?.latest || {};
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
if (
|
|
73
|
+
const immutableEntry = validatePublicationEntry({ entry: immutable, kind: "immutable", root, snapshotId, requireSnapshotDocument: true });
|
|
74
|
+
const latestEntry = validatePublicationEntry({ entry: latest, kind: "latest", root, snapshotId, requireSnapshotDocument: true });
|
|
75
|
+
const projectionInput = manifest.publication?.projections ?? [];
|
|
76
|
+
if (!Array.isArray(projectionInput) || projectionInput.length > 16) {
|
|
77
|
+
throw new Error("publication.projections must be an array with at most 16 entries");
|
|
78
|
+
}
|
|
79
|
+
const projections = projectionInput.map((entry, index) => validatePublicationEntry({
|
|
80
|
+
entry,
|
|
81
|
+
kind: `projection[${index}]`,
|
|
82
|
+
root,
|
|
83
|
+
snapshotId,
|
|
84
|
+
requireHeaders: true,
|
|
85
|
+
}));
|
|
86
|
+
const entries = [immutableEntry, ...projections, latestEntry];
|
|
87
|
+
if (!immutableEntry.key.includes(`/${snapshotId}.json`)) throw new Error("immutable key must bind snapshot.id below a versioned path");
|
|
88
|
+
if (new Set(entries.map((entry) => entry.key)).size !== entries.length) {
|
|
89
|
+
throw new Error("immutable, projection, and latest keys must be unique");
|
|
90
|
+
}
|
|
54
91
|
const invalidationPaths = [...new Set(manifest.publication?.invalidationPaths || [])];
|
|
55
92
|
if (invalidationPaths.some((entry) => typeof entry !== "string" || !entry.startsWith("/") || entry.includes(".."))) {
|
|
56
93
|
throw new Error("invalidation paths must be absolute viewer paths without traversal");
|
|
57
94
|
}
|
|
58
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
manifest,
|
|
97
|
+
manifestPath: resolvedManifest,
|
|
98
|
+
artifactRoot: root,
|
|
99
|
+
snapshotId,
|
|
100
|
+
entries,
|
|
101
|
+
immutable: immutableEntry,
|
|
102
|
+
latest: latestEntry,
|
|
103
|
+
projections,
|
|
104
|
+
invalidationPaths,
|
|
105
|
+
};
|
|
59
106
|
}
|
|
60
107
|
|
|
61
108
|
function defaultRunner(args) {
|
|
@@ -79,6 +126,8 @@ function headEvidence(result) {
|
|
|
79
126
|
snapshotId: String(value.Metadata?.["snapshot-id"] || ""),
|
|
80
127
|
etag: String(value.ETag || "").replaceAll('"', ""),
|
|
81
128
|
versionId: String(value.VersionId || ""),
|
|
129
|
+
contentType: String(value.ContentType || ""),
|
|
130
|
+
cacheControl: String(value.CacheControl || ""),
|
|
82
131
|
};
|
|
83
132
|
}
|
|
84
133
|
|
|
@@ -90,7 +139,7 @@ function putArgs({ bucket, entry, snapshotId, immutable }) {
|
|
|
90
139
|
const args = [
|
|
91
140
|
"s3api", "put-object", "--bucket", bucket, "--key", entry.key, "--body", entry.file,
|
|
92
141
|
"--content-type", entry.contentType,
|
|
93
|
-
"--cache-control", immutable ? "public,max-age=31536000,immutable" : "public,max-age=0,must-revalidate",
|
|
142
|
+
"--cache-control", entry.cacheControl || (immutable ? "public,max-age=31536000,immutable" : "public,max-age=0,must-revalidate"),
|
|
94
143
|
"--checksum-sha256", base64Digest(entry.sha256),
|
|
95
144
|
"--metadata", `snapshot-id=${snapshotId},sha256=${entry.sha256}`,
|
|
96
145
|
];
|
|
@@ -98,10 +147,39 @@ function putArgs({ bucket, entry, snapshotId, immutable }) {
|
|
|
98
147
|
return args;
|
|
99
148
|
}
|
|
100
149
|
|
|
150
|
+
function copySource(bucket, key, versionId) {
|
|
151
|
+
const encoded = `${encodeURIComponent(bucket)}/${key.split("/").map(encodeURIComponent).join("/")}`;
|
|
152
|
+
return `${encoded}?versionId=${encodeURIComponent(versionId)}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function rollbackMutableEntries({ commandRunner, bucket, applied }) {
|
|
156
|
+
const operations = [];
|
|
157
|
+
for (const { entry, previous } of [...applied].reverse()) {
|
|
158
|
+
const result = previous
|
|
159
|
+
? commandRunner([
|
|
160
|
+
"s3api", "copy-object", "--bucket", bucket, "--key", entry.key,
|
|
161
|
+
"--copy-source", copySource(bucket, entry.key, previous.versionId),
|
|
162
|
+
"--metadata-directive", "COPY",
|
|
163
|
+
])
|
|
164
|
+
: commandRunner(["s3api", "delete-object", "--bucket", bucket, "--key", entry.key]);
|
|
165
|
+
operations.push({ action: previous ? "restore-previous-version" : "remove-new-projection", key: entry.key, status: result.status });
|
|
166
|
+
if (result.status !== 0) {
|
|
167
|
+
throw new Error(`rollback failed for ${entry.key}: ${result.stderr || result.stdout}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return operations;
|
|
171
|
+
}
|
|
172
|
+
|
|
101
173
|
function assertHeadMatches(head, entry, snapshotId, label) {
|
|
102
174
|
if (!head || head.sha256 !== entry.sha256 || head.snapshotId !== snapshotId) {
|
|
103
175
|
throw new Error(`${label} object does not match the admitted snapshot and sha256`);
|
|
104
176
|
}
|
|
177
|
+
if (
|
|
178
|
+
entry.kind.startsWith("projection[") &&
|
|
179
|
+
(head.contentType !== entry.contentType || head.cacheControl !== entry.cacheControl)
|
|
180
|
+
) {
|
|
181
|
+
throw new Error(`${label} object does not match the admitted content type and cache control`);
|
|
182
|
+
}
|
|
105
183
|
}
|
|
106
184
|
|
|
107
185
|
export function publishObservedEvidence(options, { commandRunner = defaultRunner } = {}) {
|
|
@@ -110,7 +188,7 @@ export function publishObservedEvidence(options, { commandRunner = defaultRunner
|
|
|
110
188
|
if (!bucket) throw new Error("bucket is required");
|
|
111
189
|
const distributionId = String(options.distributionId || "").trim();
|
|
112
190
|
const dryRun = options.dryRun !== false;
|
|
113
|
-
const
|
|
191
|
+
const { immutable, latest, projections } = bundle;
|
|
114
192
|
const receipt = {
|
|
115
193
|
schemaVersion: 1,
|
|
116
194
|
contract: "kungfu-buildchain-observed-evidence-publication-receipt",
|
|
@@ -121,8 +199,17 @@ export function publishObservedEvidence(options, { commandRunner = defaultRunner
|
|
|
121
199
|
bucket,
|
|
122
200
|
distributionId,
|
|
123
201
|
immutable: { key: immutable.key, sha256: immutable.sha256, status: "planned" },
|
|
202
|
+
projections: projections.map((entry) => ({
|
|
203
|
+
key: entry.key,
|
|
204
|
+
sha256: entry.sha256,
|
|
205
|
+
contentType: entry.contentType,
|
|
206
|
+
cacheControl: entry.cacheControl,
|
|
207
|
+
status: "planned",
|
|
208
|
+
})),
|
|
124
209
|
latest: { key: latest.key, sha256: latest.sha256, status: "planned" },
|
|
210
|
+
previousProjections: [],
|
|
125
211
|
previousLatest: null,
|
|
212
|
+
rollback: { status: "not-needed", operations: [] },
|
|
126
213
|
invalidationPaths: bundle.invalidationPaths,
|
|
127
214
|
operations: [],
|
|
128
215
|
};
|
|
@@ -147,21 +234,52 @@ export function publishObservedEvidence(options, { commandRunner = defaultRunner
|
|
|
147
234
|
assertHeadMatches(verifiedImmutable, immutable, bundle.snapshotId, "verified immutable");
|
|
148
235
|
receipt.immutable.verification = verifiedImmutable;
|
|
149
236
|
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
237
|
+
const mutableEntries = [...projections, latest];
|
|
238
|
+
const previousByKey = new Map(mutableEntries.map((entry) => [entry.key, headEvidence(headObject(commandRunner, bucket, entry.key))]));
|
|
239
|
+
if (projections.length > 0) {
|
|
240
|
+
for (const [key, previous] of previousByKey) {
|
|
241
|
+
if (previous && !previous.versionId) {
|
|
242
|
+
throw new Error(`mutable projection transaction requires bucket versioning before replacing ${key}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
receipt.previousProjections = projections.map((entry) => ({ key: entry.key, previous: previousByKey.get(entry.key) }));
|
|
247
|
+
receipt.previousLatest = previousByKey.get(latest.key);
|
|
248
|
+
const applied = [];
|
|
249
|
+
try {
|
|
250
|
+
for (const [index, projection] of projections.entries()) {
|
|
251
|
+
const put = commandRunner(putArgs({ bucket, entry: projection, snapshotId: bundle.snapshotId, immutable: false }));
|
|
252
|
+
receipt.operations.push({ action: "advance-projection", key: projection.key, status: put.status });
|
|
253
|
+
if (put.status !== 0) throw new Error(`projection update failed for ${projection.key}: ${put.stderr || put.stdout}`);
|
|
254
|
+
applied.push({ entry: projection, previous: previousByKey.get(projection.key) });
|
|
255
|
+
const verified = headEvidence(headObject(commandRunner, bucket, projection.key));
|
|
256
|
+
assertHeadMatches(verified, projection, bundle.snapshotId, `verified projection ${projection.key}`);
|
|
257
|
+
receipt.projections[index] = { ...receipt.projections[index], status: "written", verification: verified };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const latestPut = commandRunner(putArgs({ bucket, entry: latest, snapshotId: bundle.snapshotId, immutable: false }));
|
|
261
|
+
receipt.operations.push({ action: "advance-latest", status: latestPut.status });
|
|
262
|
+
if (latestPut.status !== 0) throw new Error(`latest alias update failed: ${latestPut.stderr || latestPut.stdout}`);
|
|
263
|
+
applied.push({ entry: latest, previous: previousByKey.get(latest.key) });
|
|
264
|
+
const verifiedLatest = headEvidence(headObject(commandRunner, bucket, latest.key));
|
|
265
|
+
assertHeadMatches(verifiedLatest, latest, bundle.snapshotId, "verified latest");
|
|
266
|
+
receipt.latest = { ...receipt.latest, status: "written", verification: verifiedLatest };
|
|
267
|
+
|
|
268
|
+
if (distributionId && bundle.invalidationPaths.length > 0) {
|
|
269
|
+
const invalidation = commandRunner([
|
|
270
|
+
"cloudfront", "create-invalidation", "--distribution-id", distributionId,
|
|
271
|
+
"--paths", ...bundle.invalidationPaths,
|
|
272
|
+
]);
|
|
273
|
+
receipt.operations.push({ action: "invalidate-cdn", status: invalidation.status, result: jsonOutput(invalidation) });
|
|
274
|
+
if (invalidation.status !== 0) throw new Error(`CloudFront invalidation failed: ${invalidation.stderr || invalidation.stdout}`);
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (projections.length > 0 && applied.length > 0) {
|
|
278
|
+
receipt.rollback.status = "applying";
|
|
279
|
+
receipt.rollback.operations = rollbackMutableEntries({ commandRunner, bucket, applied });
|
|
280
|
+
receipt.rollback.status = "restored";
|
|
281
|
+
}
|
|
282
|
+
throw error;
|
|
165
283
|
}
|
|
166
284
|
receipt.status = "published";
|
|
167
285
|
receipt.publishedAt = new Date().toISOString();
|