@kungfu-tech/buildchain 4.0.2-alpha.4 → 4.0.2-alpha.40

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 (153) hide show
  1. package/AGENTS.md +10 -1
  2. package/CONTRIBUTING.md +24 -0
  3. package/README.md +2 -2
  4. package/actions/promote-buildchain-ref/README.md +1 -1
  5. package/actions/v4-release-candidate-promote/README.md +28 -0
  6. package/architecture/agent-change-map.md +66 -7
  7. package/architecture/ci-lane-change-budget.json +366 -0
  8. package/architecture/internal-capabilities.json +52 -8
  9. package/architecture/maintainability-debt.json +148 -92
  10. package/architecture/maintainability-policy.json +34 -30
  11. package/architecture/release-tail-contract-inventory.json +6 -6
  12. package/architecture/v4-architecture-constitution.md +23 -20
  13. package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
  14. package/architecture/v4-delivery-authority-parity.json +2 -2
  15. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
  16. package/architecture/v4-floating-consumer-policy.json +2 -2
  17. package/architecture/v4-next-development-parity.json +5 -5
  18. package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
  19. package/architecture/v4-platform-stage-checkpoints.json +1 -1
  20. package/architecture/v4-provider-operation-journal-contract.json +5 -2
  21. package/architecture/v4-publication-facade-patches.json +53 -0
  22. package/architecture/v4-release-activation-shadow-domain.json +5 -2
  23. package/architecture/v4-release-topology.json +143 -35
  24. package/architecture/v4-rust-wasm-production-authority.json +58 -0
  25. package/architecture/v4-stable-publication-fence.json +5 -2
  26. package/architecture/v4-stage-capsule-qualification.json +2 -2
  27. package/architecture/v4-tail-reseal-parity.json +8 -6
  28. package/architecture/v4-universal-workflow-bootstrap.json +171 -0
  29. package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
  30. package/architecture/v4-universal-workflow-train-admission.json +40 -0
  31. package/architecture/workflow-taxonomy.json +1451 -0
  32. package/bin/buildchain.mjs +7 -7
  33. package/contracts/buildchain-v2-residuals-v1.json +57 -48
  34. package/contracts/dev-delivery-authority-v2.schema.json +16 -2
  35. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  36. package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
  37. package/dist/site/buildchain-contract.json +93 -48
  38. package/dist/site/buildchain-site.json +222 -69
  39. package/dist/site/capability-registry.json +4 -4
  40. package/dist/site/controller-registry.json +50 -13
  41. package/dist/site/kfd-claims.json +2569 -982
  42. package/dist/site/kfd-upstream-aggregate.json +1 -1
  43. package/dist/site/manual-registry.json +13 -13
  44. package/dist/site/node-api-registry.json +88 -83
  45. package/dist/site/page-registry.json +204 -51
  46. package/dist/site/public-surface-audit.json +3207 -1219
  47. package/dist/site/publication-authority-registry.json +1124 -362
  48. package/dist/site/publication-registry.json +4 -4
  49. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
  50. package/dist/site/site-manifest.json +17 -17
  51. package/dist/site/workflow-registry.json +5063 -1659
  52. package/docs/MAP.md +1 -0
  53. package/docs/auditable-demo.md +1 -1
  54. package/docs/cli.md +3 -3
  55. package/docs/dev-delivery-warrant.md +1 -1
  56. package/docs/engineering-housekeeper.md +3 -3
  57. package/docs/kfd-support.md +26 -2
  58. package/docs/next-development-transition.md +2 -2
  59. package/docs/node-api-reference.md +129 -129
  60. package/docs/publication-authority.md +2 -2
  61. package/docs/release-candidate.md +3 -3
  62. package/docs/release-flow.md +67 -19
  63. package/docs/release-governance.md +8 -8
  64. package/docs/release-passport.md +27 -2
  65. package/docs/reusable-build-surface.md +16 -0
  66. package/docs/v4-adopter-delivery.md +2 -2
  67. package/docs/v4-production-release.md +71 -8
  68. package/docs/v4-rust-wasm-production-authority.md +58 -0
  69. package/docs/v4-stage-capsule.md +2 -2
  70. package/docs/v4-tail-reseal.md +1 -1
  71. package/docs/workflow-catalog.md +154 -0
  72. package/docs/workflow-path-migration.md +109 -0
  73. package/package.json +10 -6
  74. package/packages/core/artifact-verification-envelope.js +5 -5
  75. package/packages/core/buildchain-contract.js +1 -1
  76. package/packages/core/buildchain-kfd-claims.js +2 -2
  77. package/packages/core/buildchain-publication-authority.js +42 -26
  78. package/packages/core/buildchain-v4-domain.wasm +0 -0
  79. package/packages/core/ci-lane-change-budget.js +14 -1
  80. package/packages/core/controller-evidence.js +4 -1
  81. package/packages/core/dev-delivery-candidate-identity.js +40 -6
  82. package/packages/core/dev-delivery-warrant-state.js +3 -4
  83. package/packages/core/dev-delivery-warrant.js +12 -2
  84. package/packages/core/github-governance-authority.js +4 -4
  85. package/packages/core/kfd-adopter-manifest.js +46 -47
  86. package/packages/core/next-development-projection.js +2 -2
  87. package/packages/core/release-line-dry-run.js +1 -1
  88. package/packages/core/release-passport-contract.js +5 -5
  89. package/packages/core/release-passport.js +5 -4
  90. package/packages/core/release-tail-product-capabilities.js +29 -0
  91. package/packages/core/release-tail-provider-plane.js +68 -1048
  92. package/packages/core/v4-canonical-contracts.js +14 -75
  93. package/packages/core/v4-domain-wasm-artifact.js +7 -0
  94. package/packages/core/v4-domain-wasm.js +216 -0
  95. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  96. package/packages/core/v4-floating-consumer-policy.js +1 -1
  97. package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
  98. package/packages/core/v4-product-publication.js +57 -0
  99. package/packages/core/v4-protected-publication-source.js +19 -21
  100. package/packages/core/v4-provider-operation-journal.js +18 -487
  101. package/packages/core/v4-provider-readback-idempotency.js +12 -97
  102. package/packages/core/v4-release-activation-shadow.js +13 -500
  103. package/packages/core/v4-release-invocation.js +28 -389
  104. package/packages/core/v4-stable-publication-fence.js +13 -342
  105. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  106. package/scripts/binary-publication-evidence.mjs +135 -0
  107. package/scripts/build-contract-core.mjs +1 -14
  108. package/scripts/build-v4-domain-wasm.mjs +189 -0
  109. package/scripts/check-action-bundles.mjs +12 -2
  110. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  111. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  112. package/scripts/check-inventory.mjs +35 -44
  113. package/scripts/check-maintainability.mjs +16 -113
  114. package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
  115. package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
  116. package/scripts/check-v4-release-topology.mjs +214 -50
  117. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  118. package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
  119. package/scripts/check-workflow-taxonomy.mjs +6 -0
  120. package/scripts/copy-v4-domain-wasm.mjs +14 -0
  121. package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
  122. package/scripts/dev-delivery-authority.mjs +8 -8
  123. package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
  124. package/scripts/dev-delivery-warrant-options.mjs +14 -9
  125. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  126. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  127. package/scripts/dev-delivery-warrant.mjs +76 -99
  128. package/scripts/dev-pr-auto-merge.mjs +15 -15
  129. package/scripts/generate-channel-build-workflow.mjs +7 -2
  130. package/scripts/generate-channel-promotion-workflow.mjs +7 -5
  131. package/scripts/generate-site-bundle.mjs +11 -11
  132. package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
  133. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  134. package/scripts/github-output.mjs +31 -0
  135. package/scripts/maintainability-metrics.mjs +16 -5
  136. package/scripts/maintainability-public-surface.mjs +115 -0
  137. package/scripts/next-development-review.mjs +307 -0
  138. package/scripts/release-asset-client.mjs +91 -0
  139. package/scripts/release-candidate-resolver.mjs +17 -18
  140. package/scripts/resume-from-candidate-run.mjs +1 -1
  141. package/scripts/source-verification-evidence.mjs +344 -0
  142. package/scripts/stable-candidate-qualification.mjs +1 -1
  143. package/scripts/universal-facade-maintainability.mjs +82 -0
  144. package/scripts/v4-product-publication-intent.mjs +121 -0
  145. package/scripts/v4-publication-settlement.mjs +211 -0
  146. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  147. package/scripts/v4-universal-workflow-engine.mjs +618 -0
  148. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  149. package/scripts/verify-version-state-delta.mjs +173 -0
  150. package/scripts/workflow-taxonomy.mjs +507 -0
  151. package/templates/native-dev-delivery.yml +4 -7
  152. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  153. package/templates/universal-buildchain-bootstrap.yml +28 -0
@@ -0,0 +1,344 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { pathToFileURL } from "node:url";
6
+ import { invokeV4DomainWasm } from "../packages/core/v4-domain-wasm.js";
7
+
8
+ const workflow = ".github/workflows/self-build-verify.yml";
9
+ const proofPath = ".buildchain/source-verification/evidence.json";
10
+ const hash = (bytes) =>
11
+ `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
12
+ const execute = (command, args, encoding) =>
13
+ execFileSync(command, args, {
14
+ encoding,
15
+ maxBuffer: 32 * 1024 * 1024,
16
+ stdio: ["ignore", "pipe", "pipe"],
17
+ });
18
+ const run = (command, args) => execute(command, args, "utf8").trim();
19
+ const git = (...args) => run("git", args);
20
+ const write = (file, value) => {
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
23
+ };
24
+
25
+ export function verificationIdentity({
26
+ env = process.env,
27
+ command = run,
28
+ revision = "HEAD",
29
+ } = {}) {
30
+ const sourceSha = command("git", ["rev-parse", revision]);
31
+ const sourceTree = command("git", ["rev-parse", `${revision}^{tree}`]);
32
+ const bytes = (file) => execute("git", ["show", `${sourceSha}:${file}`]);
33
+ const named = (files) =>
34
+ hash(JSON.stringify(files.map((file) => [file, hash(bytes(file))])));
35
+ for (const key of ["GITHUB_REPOSITORY", "ImageOS", "ImageVersion"])
36
+ if (!env[key]) throw new Error(`verification identity requires ${key}`);
37
+ return {
38
+ repository: env.GITHUB_REPOSITORY,
39
+ sourceSha,
40
+ sourceTree,
41
+ workflowRoot: hash(bytes(workflow)),
42
+ checkDefinitionRoot: named([
43
+ "package.json",
44
+ ".buildchain/buildchain.toml",
45
+ "scripts/source-verification-evidence.mjs",
46
+ "scripts/verify-version-state-delta.mjs",
47
+ ]),
48
+ runtimeRoot: named([
49
+ "packages/core/buildchain-v4-domain.wasm",
50
+ "packages/core/v4-domain-wasm-artifact.js",
51
+ ]),
52
+ dependencyRoot: named([
53
+ "pnpm-lock.yaml",
54
+ "pnpm-workspace.yaml",
55
+ "crates/buildchain-v4-contracts/Cargo.lock",
56
+ "crates/buildchain-v4-bridge/Cargo.lock",
57
+ ]),
58
+ toolchainRoot: hash(
59
+ JSON.stringify([
60
+ process.version,
61
+ command("corepack", ["pnpm", "--version"]),
62
+ command("go", ["version"]),
63
+ command("rustc", ["+1.96.0", "--version"]),
64
+ command("cargo", ["+1.96.0", "--version"]),
65
+ command("rustc", ["--version"]),
66
+ command("cargo", ["--version"]),
67
+ ]),
68
+ ),
69
+ environmentRoot: hash(
70
+ JSON.stringify([
71
+ env.ImageOS,
72
+ env.ImageVersion,
73
+ process.platform,
74
+ process.arch,
75
+ ...[
76
+ "CI",
77
+ "NODE_ENV",
78
+ "TZ",
79
+ "LANG",
80
+ "LC_ALL",
81
+ "SOURCE_DATE_EPOCH",
82
+ "BUILDCHAIN_SITE_GENERATED_AT",
83
+ "BUILDCHAIN_SITE_PUBLISHED_AT",
84
+ "BUILDCHAIN_SITE_TIMESTAMP_POLICY",
85
+ "BUILDCHAIN_SOURCE_SHA",
86
+ "RUSTUP_TOOLCHAIN",
87
+ "RUSTFLAGS",
88
+ "CARGO_ENCODED_RUSTFLAGS",
89
+ "NODE_OPTIONS",
90
+ "GOFLAGS",
91
+ "GOTOOLCHAIN",
92
+ ].map((key) => env[key] || ""),
93
+ ]),
94
+ ),
95
+ platform:
96
+ { linux: "linux", darwin: "macos", win32: "windows" }[process.platform] +
97
+ `-${process.arch}`,
98
+ };
99
+ }
100
+
101
+ export function planVerification(input) {
102
+ return invokeV4DomainWasm("source-verification-plan", input);
103
+ }
104
+
105
+ export function sealVerification(input) {
106
+ return invokeV4DomainWasm("source-verification-seal", input);
107
+ }
108
+
109
+ const api = (endpoint, binary = false) =>
110
+ execute("gh", ["api", endpoint], binary ? undefined : "utf8");
111
+
112
+ // Only the one bounded proof file is read; archive paths are never extracted.
113
+ export function readProofArchive(archive, digest) {
114
+ if (hash(archive) !== digest)
115
+ throw new Error("artifact archive digest mismatch");
116
+ const source = [
117
+ "import io,json,sys,zipfile",
118
+ "z=zipfile.ZipFile(io.BytesIO(sys.stdin.buffer.read()))",
119
+ "entries=[e for e in z.infolist() if e.filename=='evidence.json']",
120
+ "assert len(entries)==1 and entries[0].file_size <= 1048576, 'invalid proof archive'",
121
+ "sys.stdout.buffer.write(z.read(entries[0]))",
122
+ ].join("\n");
123
+ return JSON.parse(
124
+ execFileSync(
125
+ process.platform === "win32" ? "python" : "python3",
126
+ ["-c", source],
127
+ {
128
+ input: archive,
129
+ maxBuffer: 1048576,
130
+ encoding: "utf8",
131
+ },
132
+ ),
133
+ );
134
+ }
135
+
136
+ export async function discoverVerification({
137
+ expected,
138
+ evaluatedAtMs,
139
+ fetchJson,
140
+ fetchArchive,
141
+ }) {
142
+ const prefix = `repos/${expected.repository}/actions`;
143
+ const response = await fetchJson(
144
+ `${prefix}/workflows/self-build-verify.yml/runs?event=merge_group&status=success&head_sha=${expected.sourceSha}&per_page=10`,
145
+ );
146
+ const reasons = [];
147
+ for (const listed of response.workflow_runs || []) {
148
+ try {
149
+ const run = await fetchJson(`${prefix}/runs/${listed.id}`);
150
+ if (
151
+ run.repository?.full_name !== expected.repository ||
152
+ run.head_sha !== expected.sourceSha ||
153
+ run.path !== workflow ||
154
+ run.event !== "merge_group" ||
155
+ run.conclusion !== "success" ||
156
+ run.status !== "completed"
157
+ )
158
+ throw new Error("untrusted evidence run");
159
+ const assets = await fetchJson(
160
+ `${prefix}/runs/${run.id}/artifacts?per_page=100`,
161
+ );
162
+ const matches = (assets.artifacts || []).filter(
163
+ (a) =>
164
+ a.name === `source-verification-${run.head_sha}-${run.run_attempt}`,
165
+ );
166
+ if (
167
+ matches.length !== 1 ||
168
+ matches[0].expired ||
169
+ matches[0].size_in_bytes > 1048576
170
+ )
171
+ throw new Error("missing or expired proof artifact");
172
+ const asset = matches[0];
173
+ const candidate = readProofArchive(
174
+ await fetchArchive(`${prefix}/artifacts/${asset.id}/zip`),
175
+ asset.digest,
176
+ );
177
+ const decision = planVerification({
178
+ expected,
179
+ evaluatedAtMs,
180
+ candidate,
181
+ provider: {
182
+ repository: run.repository.full_name,
183
+ runId: String(run.id),
184
+ runAttempt: run.run_attempt,
185
+ headSha: run.head_sha,
186
+ event: run.event,
187
+ status: run.status,
188
+ conclusion: run.conclusion,
189
+ workflowPath: run.path,
190
+ artifactDigest: asset.digest,
191
+ },
192
+ });
193
+ if (decision.decision === "reuse")
194
+ return { ...decision, sourceSha: expected.sourceSha };
195
+ reasons.push(decision.reason);
196
+ } catch {
197
+ // Provider/transport or evidence failures are cache misses, never a green check.
198
+ reasons.push("unavailable-or-invalid-evidence");
199
+ }
200
+ }
201
+ return {
202
+ decision: "execute",
203
+ reason: reasons.join(",") || "missing-evidence",
204
+ };
205
+ }
206
+
207
+ function output(decision) {
208
+ write(".buildchain/source-verification/decision.json", decision);
209
+ if (process.env.GITHUB_OUTPUT)
210
+ fs.appendFileSync(
211
+ process.env.GITHUB_OUTPUT,
212
+ `decision=${decision.decision}\n`,
213
+ );
214
+ if (process.env.GITHUB_STEP_SUMMARY)
215
+ fs.appendFileSync(
216
+ process.env.GITHUB_STEP_SUMMARY,
217
+ `Source verification: **${decision.decision}** (${decision.reason}).${decision.runId ? ` Original full execution: run ${decision.runId}, attempt ${decision.runAttempt}, proof ${decision.evidenceRoot}.` : ""}\n`,
218
+ );
219
+ console.log(JSON.stringify(decision));
220
+ }
221
+
222
+ function providerDiscovery(expected) {
223
+ return discoverVerification({
224
+ expected,
225
+ evaluatedAtMs: Date.now(),
226
+ fetchJson: async (endpoint) => JSON.parse(api(endpoint)),
227
+ fetchArchive: async (endpoint) => api(endpoint, true),
228
+ });
229
+ }
230
+
231
+ export async function planVersionProjection({
232
+ baseSha,
233
+ headSha,
234
+ discover,
235
+ regenerate,
236
+ }) {
237
+ const proof = await discover(baseSha);
238
+ if (proof.decision !== "reuse")
239
+ return { decision: "execute", reason: "base-full-proof-unavailable" };
240
+ const projection = await regenerate(baseSha, headSha);
241
+ return {
242
+ ...proof,
243
+ decision: "projection",
244
+ reason: "verified-base-and-exact-regenerated-version-delta",
245
+ projection,
246
+ };
247
+ }
248
+
249
+ async function planCurrentVerification() {
250
+ if (git("status", "--porcelain", "--untracked-files=no"))
251
+ throw new Error("dirty source");
252
+ const event = process.env.GITHUB_EVENT_NAME;
253
+ const headSha = git("rev-parse", "HEAD");
254
+ if (
255
+ event === "push" &&
256
+ process.env.GITHUB_REF?.startsWith("refs/heads/dev/")
257
+ ) {
258
+ const exact = await providerDiscovery(verificationIdentity());
259
+ if (exact.decision === "reuse") return exact;
260
+ }
261
+ if (!["pull_request", "merge_group", "push"].includes(event))
262
+ return { decision: "execute", reason: "full-execution-required-for-event" };
263
+ const payload = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH));
264
+ const baseSha =
265
+ event === "merge_group"
266
+ ? payload.merge_group?.base_sha
267
+ : event === "pull_request"
268
+ ? payload.pull_request?.base.sha
269
+ : process.env.GITHUB_REF?.startsWith("refs/heads/dev/")
270
+ ? payload.before
271
+ : null;
272
+ if (!/^[a-f0-9]{40}$/u.test(baseSha || ""))
273
+ throw new Error("no exact protected base");
274
+ return planVersionProjection({
275
+ baseSha,
276
+ headSha,
277
+ discover: (revision) =>
278
+ providerDiscovery(verificationIdentity({ revision })),
279
+ regenerate: async (baseSha, headSha) => {
280
+ const { verifyVersionStateDelta } =
281
+ await import("./verify-version-state-delta.mjs");
282
+ return verifyVersionStateDelta({
283
+ baseSha,
284
+ headSha,
285
+ nodeModules: path.resolve(".buildchain/runtime/node_modules"),
286
+ });
287
+ },
288
+ });
289
+ }
290
+
291
+ export async function main(mode) {
292
+ if (mode === "plan") {
293
+ try {
294
+ output(await planCurrentVerification());
295
+ } catch {
296
+ output({
297
+ decision: "execute",
298
+ reason: "identity-provider-or-version-delta-unavailable",
299
+ });
300
+ }
301
+ } else if (mode === "start") {
302
+ write(".buildchain/source-verification/start.json", {
303
+ identity: verificationIdentity(),
304
+ startedAtMs: Date.now(),
305
+ });
306
+ } else if (mode === "seal") {
307
+ if (process.env.GITHUB_EVENT_NAME !== "merge_group")
308
+ throw new Error(
309
+ "only merge-group full executions produce reusable proof",
310
+ );
311
+ const started = JSON.parse(
312
+ fs.readFileSync(".buildchain/source-verification/start.json"),
313
+ );
314
+ if (
315
+ git("status", "--porcelain", "--untracked-files=no") ||
316
+ JSON.stringify(started.identity) !==
317
+ JSON.stringify(verificationIdentity())
318
+ )
319
+ throw new Error(
320
+ "source or verification identity changed during execution",
321
+ );
322
+ const completedAtMs = Date.now();
323
+ write(
324
+ proofPath,
325
+ sealVerification({
326
+ schema: "buildchain-v4-source-verification-evidence/v1",
327
+ ...started,
328
+ runId: process.env.GITHUB_RUN_ID,
329
+ runAttempt: Number(process.env.GITHUB_RUN_ATTEMPT),
330
+ event: process.env.GITHUB_EVENT_NAME,
331
+ validationKind: "full-source",
332
+ exitCode: 0,
333
+ completedAtMs,
334
+ expiresAtMs: completedAtMs + 6 * 60 * 60 * 1000,
335
+ }),
336
+ );
337
+ } else throw new Error(`unknown verification evidence mode: ${mode}`);
338
+ }
339
+
340
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
341
+ main(process.argv[2]).catch((error) => {
342
+ console.error(error.message);
343
+ process.exitCode = 1;
344
+ });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const DEFAULTS = {
4
- buildWorkflowFile: "build-surface-fixture.yml",
4
+ buildWorkflowFile: "self-build-fixture.yml",
5
5
  buildWorkflowName: "Build Surface Fixture",
6
6
  canaryRepository: "kungfu-systems/site-libkungfu-dev",
7
7
  canaryWorkflowFile: "buildchain-stable-canary.yml",
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+
5
+ function loadUniversalFacadeMigration(root) {
6
+ const contract = JSON.parse(
7
+ fs.readFileSync(
8
+ path.join(root, "architecture/v4-universal-workflow-bootstrap.json"),
9
+ "utf8",
10
+ ),
11
+ );
12
+ const retired = new Set(contract.retiredWorkflowSurfaces || []);
13
+ const paths = (contract.bootstrapGovernedWorkflows || []).filter(
14
+ (entry) =>
15
+ entry !== contract.bootstrap?.publicWorkflow && !retired.has(entry),
16
+ );
17
+ const sourceRevision = contract.migration?.facadeSourceRevision || "";
18
+ if (!/^[0-9a-f]{40}$/u.test(sourceRevision)) {
19
+ throw new Error(
20
+ "universal facade migration requires an exact facadeSourceRevision",
21
+ );
22
+ }
23
+ return { paths: new Set(paths), sourceRevision };
24
+ }
25
+
26
+ function verifyUniversalFacadeMigration(root) {
27
+ execFileSync(
28
+ process.execPath,
29
+ ["scripts/generate-v4-universal-workflow-facades.mjs", "--check"],
30
+ { cwd: root, stdio: "pipe" },
31
+ );
32
+ }
33
+
34
+ function governUniversalFacadeWorkflowMetrics({
35
+ root,
36
+ current,
37
+ workflowMetricsAtRevision,
38
+ }) {
39
+ const migration = loadUniversalFacadeMigration(root);
40
+ verifyUniversalFacadeMigration(root);
41
+ const frozen = workflowMetricsAtRevision(root, migration.sourceRevision);
42
+ const governed = { ...current, workflows: { ...current.workflows } };
43
+ for (const file of migration.paths) {
44
+ if (!frozen[file])
45
+ throw new Error(`frozen universal facade source is missing ${file}`);
46
+ governed.workflows[file] = frozen[file];
47
+ }
48
+ return { governed, migration };
49
+ }
50
+
51
+ function isGeneratedFacadePublicTransition({
52
+ entry,
53
+ kind,
54
+ migration,
55
+ baseContract,
56
+ currentContract,
57
+ }) {
58
+ if (kind !== "workflow" || !migration?.paths.has(entry.path) || !baseContract)
59
+ return false;
60
+ const { inputs: currentInputs, ...currentRest } = currentContract;
61
+ const { inputs: previousInputs, ...previousRest } = baseContract;
62
+ if (JSON.stringify(currentRest) !== JSON.stringify(previousRest))
63
+ return false;
64
+ const added = currentInputs.filter(
65
+ (input) => !previousInputs.includes(input),
66
+ );
67
+ const removed = previousInputs.filter(
68
+ (input) => !currentInputs.includes(input),
69
+ );
70
+ return (
71
+ removed.length === 0 &&
72
+ added.length === 1 &&
73
+ added[0] === "universal-request-json"
74
+ );
75
+ }
76
+
77
+ export {
78
+ governUniversalFacadeWorkflowMetrics,
79
+ isGeneratedFacadePublicTransition,
80
+ loadUniversalFacadeMigration,
81
+ verifyUniversalFacadeMigration,
82
+ };
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ import { spawnSyncCommand } from "../packages/core/spawn-command.js";
8
+ import { selectV4ProductPublicationIntent } from "../packages/core/v4-product-publication.js";
9
+ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
10
+
11
+ function env(name, required = false) {
12
+ const value = String(process.env[name] || "").trim();
13
+ if (required && !value) throw new Error(`${name} is required`);
14
+ return value;
15
+ }
16
+
17
+ function readJson(file) {
18
+ return JSON.parse(fs.readFileSync(path.resolve(file), "utf8"));
19
+ }
20
+
21
+ function packageName(artifactKind) {
22
+ if (artifactKind !== "npm") return "";
23
+ const declared = env("BUILDCHAIN_PUBLISH_PACKAGE_MAIN");
24
+ if (declared) return declared;
25
+ const manifestPath = env("BUILDCHAIN_SEALED_BUNDLE_MANIFEST", true);
26
+ const manifest = readJson(manifestPath);
27
+ const selected = String(manifest?.npm?.name || "").trim();
28
+ if (!selected)
29
+ throw new Error("sealed bundle manifest does not declare npm.name");
30
+ return selected;
31
+ }
32
+
33
+ function observedVersions(name) {
34
+ const result = spawnSyncCommand(
35
+ "npm",
36
+ [
37
+ "view",
38
+ name,
39
+ "versions",
40
+ "--json",
41
+ "--registry=https://registry.npmjs.org/",
42
+ ],
43
+ { encoding: "utf8" },
44
+ );
45
+ if (result.error) throw result.error;
46
+ if (result.status !== 0) {
47
+ const output = `${result.stdout || ""}\n${result.stderr || ""}`;
48
+ if (/\bE404\b|404 Not Found|is not in this registry/iu.test(output))
49
+ return [];
50
+ throw new Error(`npm version discovery failed: ${output.trim()}`);
51
+ }
52
+ const parsed = JSON.parse(String(result.stdout || "[]"));
53
+ return Array.isArray(parsed) ? parsed : [parsed].filter(Boolean);
54
+ }
55
+
56
+ function writeOutput(name, value) {
57
+ const output = env("GITHUB_OUTPUT");
58
+ if (!output) return;
59
+ fs.appendFileSync(output, `${name}=${String(value)}\n`);
60
+ }
61
+
62
+ export function resolveV4ProductPublicationIntent() {
63
+ const artifactKind = env("BUILDCHAIN_PUBLISH_ARTIFACT_KIND") || "npm";
64
+ const name = packageName(artifactKind);
65
+ const sourceSha = env("BUILDCHAIN_SOURCE_SHA", true);
66
+ const manifestPath = env(
67
+ "BUILDCHAIN_SEALED_BUNDLE_MANIFEST",
68
+ artifactKind === "npm",
69
+ );
70
+ const manifest = manifestPath ? readJson(manifestPath) : null;
71
+ const requiredArtifacts = readJson(
72
+ env("BUILDCHAIN_REQUIRED_ARTIFACTS_PATH", true),
73
+ );
74
+ const channel = env("BUILDCHAIN_CHANNEL", true);
75
+ const intent = selectV4ProductPublicationIntent({
76
+ channel,
77
+ targetRef: env("BUILDCHAIN_TARGET_REF", true),
78
+ sourceSha,
79
+ sourceTimestamp: env("BUILDCHAIN_SOURCE_TIMESTAMP", true),
80
+ repository: env("BUILDCHAIN_REPOSITORY", true),
81
+ artifactKind,
82
+ packageName: name,
83
+ distTag:
84
+ env("BUILDCHAIN_PUBLISH_DIST_TAG") ||
85
+ (channel === "alpha" ? "alpha" : "latest"),
86
+ sealedBundleRoot: manifest?.root,
87
+ requiredArtifactsRoot: v4ContentRoot(
88
+ "v4-product-required-artifacts",
89
+ requiredArtifacts,
90
+ ),
91
+ candidateVersion: env("BUILDCHAIN_CANDIDATE_VERSION", true),
92
+ recoveredVersion: env("BUILDCHAIN_RECOVERED_PUBLICATION_VERSION"),
93
+ observedVersions: artifactKind === "npm" ? observedVersions(name) : [],
94
+ });
95
+ const outputPath = path.resolve(
96
+ env("BUILDCHAIN_PRODUCT_PUBLICATION_INTENT_PATH") ||
97
+ ".buildchain/release-candidate/v4-product-publication-intent.json",
98
+ );
99
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
100
+ fs.writeFileSync(outputPath, `${JSON.stringify(intent, null, 2)}\n`);
101
+ writeOutput("version", intent.version);
102
+ writeOutput("exact-tag", intent.exactTag);
103
+ writeOutput("intent-path", outputPath);
104
+ writeOutput("intent-root", intent.intentRoot);
105
+ return { name: name || artifactKind, intent, outputPath };
106
+ }
107
+
108
+ if (
109
+ process.argv[1] &&
110
+ import.meta.url === pathToFileURL(process.argv[1]).href
111
+ ) {
112
+ try {
113
+ const result = resolveV4ProductPublicationIntent();
114
+ process.stdout.write(
115
+ `v4 product publication intent: ${result.name}@${result.intent.version} (${result.intent.mode})\n`,
116
+ );
117
+ } catch (error) {
118
+ console.error(`v4-product-publication-intent: ${error.message}`);
119
+ process.exitCode = 1;
120
+ }
121
+ }