@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.
Files changed (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -1
  4. package/actions/promote-buildchain-ref/README.md +90 -19
  5. package/actions/report-buildchain-issue/README.md +20 -2
  6. package/actions/run-lifecycle/README.md +5 -0
  7. package/actions/validate-config/README.md +19 -1
  8. package/bin/buildchain.mjs +56 -2
  9. package/bin/internal/command-registry.mjs +82 -0
  10. package/bin/internal/trust-release-cli.mjs +1 -0
  11. package/contracts/auditable-demo-media-profiles-v1.json +116 -1
  12. package/contracts/buildchain-v2-residuals-v1.json +146 -0
  13. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +143 -0
  14. package/contracts/evidence/auditable-demo-web-delivery-v1.json +7 -3
  15. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/complete-transcript.txt +2 -0
  16. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json +16 -0
  17. package/contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json +12 -0
  18. package/dist/site/buildchain-contract.json +188 -46
  19. package/dist/site/buildchain-site.json +181 -90
  20. package/dist/site/capability-registry.json +5 -5
  21. package/dist/site/cli-registry.json +860 -98
  22. package/dist/site/controller-registry.json +118 -5
  23. package/dist/site/kfd-claims.json +233 -19
  24. package/dist/site/kfd-upstream-aggregate.json +1 -1
  25. package/dist/site/manual-registry.json +20 -20
  26. package/dist/site/node-api-registry.json +374 -59
  27. package/dist/site/page-registry.json +156 -65
  28. package/dist/site/public-surface-audit.json +231 -22
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/release-provenance.json +3 -0
  31. package/dist/site/site-manifest.json +24 -24
  32. package/dist/site/workflow-registry.json +575 -79
  33. package/docs/MAP.md +22 -4
  34. package/docs/auditable-demo.md +54 -13
  35. package/docs/aws-us-elastic-runner-burst-plane.md +381 -0
  36. package/docs/cli.md +119 -12
  37. package/docs/consumer-issue-reporting.md +20 -2
  38. package/docs/dev-alpha-candidate-patrol.md +24 -10
  39. package/docs/github-governance-authority.md +24 -31
  40. package/docs/lifecycle-protocol.md +20 -2
  41. package/docs/migration-inventory.md +24 -5
  42. package/docs/observed-evidence-patrol.md +28 -12
  43. package/docs/ownership.md +19 -1
  44. package/docs/publication-artifacts.md +158 -18
  45. package/docs/publication-authority.md +4 -4
  46. package/docs/publish-transaction.md +78 -31
  47. package/docs/release-candidate.md +24 -0
  48. package/docs/release-flow.md +50 -32
  49. package/docs/release-governance.md +39 -32
  50. package/docs/release-passport.md +4 -0
  51. package/docs/release-propagation.md +57 -12
  52. package/docs/reusable-build-surface.md +218 -90
  53. package/docs/runtime-train-validation.md +20 -2
  54. package/docs/shifu-gate-profiles.md +7 -1
  55. package/docs/stable-candidate-patrol.md +6 -6
  56. package/docs/toolkit-observability.md +22 -0
  57. package/docs/versioning.md +46 -24
  58. package/docs/web-surface-deployments.md +82 -1
  59. package/package.json +7 -5
  60. package/packages/core/README.md +60 -4
  61. package/packages/core/artifact-signing.js +1 -0
  62. package/packages/core/buildchain-contract.js +9 -4
  63. package/packages/core/cache-evidence.js +1 -0
  64. package/packages/core/controller-evidence.js +2 -0
  65. package/packages/core/diagnostics.js +105 -30
  66. package/packages/core/github-governance-authority.js +25 -17
  67. package/packages/core/index.js +40 -0
  68. package/packages/core/paper.js +3226 -0
  69. package/packages/core/public-surface-audit.js +3 -0
  70. package/packages/core/publication-artifact.js +1 -6
  71. package/packages/core/publication-authority.js +1 -0
  72. package/packages/core/publication-control-plane-audit.js +28 -0
  73. package/packages/core/publication-reproducibility.js +947 -0
  74. package/packages/core/publication-sealed-bundle.js +190 -0
  75. package/packages/core/publish-transaction.js +147 -17
  76. package/packages/core/release-candidate.js +234 -0
  77. package/packages/core/release-passport.js +519 -228
  78. package/packages/core/release-propagation.js +175 -1
  79. package/packages/core/stable-release-gate.js +4 -1
  80. package/scripts/artifact-signing-delegation.mjs +268 -0
  81. package/scripts/audit-github-governance.mjs +32 -13
  82. package/scripts/audit-publication-control-plane.mjs +17 -8
  83. package/scripts/auditable-demo.mjs +266 -7
  84. package/scripts/aws-codebuild-toolchain.mjs +285 -0
  85. package/scripts/aws-macos-jit-core.mjs +378 -0
  86. package/scripts/aws-macos-jit.mjs +123 -0
  87. package/scripts/aws-runner-burst-core.mjs +248 -0
  88. package/scripts/aws-runner-burst.mjs +79 -0
  89. package/scripts/aws-windows-jit-core.mjs +374 -0
  90. package/scripts/aws-windows-jit.mjs +121 -0
  91. package/scripts/build-contract-core.mjs +69 -6
  92. package/scripts/build-standalone-binary.mjs +23 -0
  93. package/scripts/buildchain-contract-lock.mjs +1 -1
  94. package/scripts/buildchain-patrol.mjs +1 -1
  95. package/scripts/check-internal-architecture.mjs +69 -2
  96. package/scripts/check-inventory.mjs +70 -2
  97. package/scripts/check-javascript-syntax.mjs +31 -0
  98. package/scripts/check-maintainability.mjs +371 -0
  99. package/scripts/compiler-cache-evidence.mjs +221 -0
  100. package/scripts/dev-alpha-candidate-patrol.mjs +288 -43
  101. package/scripts/dispatch-artifact-signing-authority.mjs +7 -1
  102. package/scripts/finalize-native-artifact-signing-result.mjs +185 -31
  103. package/scripts/gate-profile-core.mjs +6 -1
  104. package/scripts/generate-channel-promotion-workflow.mjs +3 -12
  105. package/scripts/generate-release-candidate-passport.mjs +4 -0
  106. package/scripts/generate-site-bundle.mjs +84 -18
  107. package/scripts/import-artifact-signing-results.mjs +64 -5
  108. package/scripts/inspect-artifact-signing-requests.mjs +52 -14
  109. package/scripts/maintainability-metrics.mjs +339 -0
  110. package/scripts/npm-publish-transaction.mjs +57 -6
  111. package/scripts/observed-evidence.mjs +151 -33
  112. package/scripts/paper.mjs +645 -0
  113. package/scripts/promotion-channel-router.mjs +16 -4
  114. package/scripts/publication-reproducibility.mjs +62 -0
  115. package/scripts/reconcile-github-governance.mjs +8 -1
  116. package/scripts/release-propagation.mjs +39 -0
  117. package/scripts/resolve-artifact-signing-upload-route.mjs +55 -0
  118. package/scripts/resolve-build-contract.mjs +6 -0
  119. package/scripts/route-offline-runners.mjs +236 -0
  120. package/scripts/run-candidate-body-prefix-renderer.mjs +187 -0
  121. package/scripts/run-lifecycle-core.mjs +62 -1
  122. package/scripts/runtime-ref-core.mjs +36 -4
  123. package/scripts/seal-artifact-signing-requests.mjs +64 -12
  124. package/scripts/stable-candidate-qualification.mjs +50 -0
  125. package/scripts/verify-artifact-signing-results.mjs +26 -1
  126. package/scripts/web-surface-core.mjs +272 -86
  127. 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 entries = [["immutable", immutable], ["latest", latest]].map(([kind, entry]) => {
40
- const source = safeRelative(entry.source, `${kind}.source`);
41
- const key = safeRelative(entry.key, `${kind}.key`);
42
- const file = path.resolve(root, source);
43
- if (file !== root && !file.startsWith(`${root}${path.sep}`)) throw new Error(`${kind}.source escapes artifact root`);
44
- if (!fs.statSync(file).isFile()) throw new Error(`${kind}.source is not a file: ${source}`);
45
- assertHex(entry.sha256, `${kind}.sha256`);
46
- const actual = sha256(file);
47
- if (actual !== entry.sha256) throw new Error(`${kind}.sha256 does not match ${source}`);
48
- const document = readJson(file);
49
- if (document.snapshotId !== snapshotId) throw new Error(`${kind} document snapshotId does not match manifest`);
50
- return { kind, source, key, file, sha256: actual, contentType: entry.contentType || "application/json" };
51
- });
52
- if (!entries[0].key.includes(`/${snapshotId}.json`)) throw new Error("immutable key must bind snapshot.id below a versioned path");
53
- if (entries[0].key === entries[1].key) throw new Error("immutable and latest keys must differ");
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 { manifest, manifestPath: resolvedManifest, artifactRoot: root, snapshotId, entries, invalidationPaths };
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 [immutable, latest] = bundle.entries;
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
- receipt.previousLatest = headEvidence(headObject(commandRunner, bucket, latest.key));
151
- const latestPut = commandRunner(putArgs({ bucket, entry: latest, snapshotId: bundle.snapshotId, immutable: false }));
152
- receipt.operations.push({ action: "advance-latest", status: latestPut.status });
153
- if (latestPut.status !== 0) throw new Error(`latest alias update failed: ${latestPut.stderr || latestPut.stdout}`);
154
- const verifiedLatest = headEvidence(headObject(commandRunner, bucket, latest.key));
155
- assertHeadMatches(verifiedLatest, latest, bundle.snapshotId, "verified latest");
156
- receipt.latest = { ...receipt.latest, status: "written", verification: verifiedLatest };
157
-
158
- if (distributionId && bundle.invalidationPaths.length > 0) {
159
- const invalidation = commandRunner([
160
- "cloudfront", "create-invalidation", "--distribution-id", distributionId,
161
- "--paths", ...bundle.invalidationPaths,
162
- ]);
163
- receipt.operations.push({ action: "invalidate-cdn", status: invalidation.status, result: jsonOutput(invalidation) });
164
- if (invalidation.status !== 0) throw new Error(`CloudFront invalidation failed: ${invalidation.stderr || invalidation.stdout}`);
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();