@kungfu-tech/buildchain 4.0.2-alpha.5 → 4.0.2-alpha.50

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 (171) 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 +70 -9
  7. package/architecture/ci-lane-change-budget.json +384 -0
  8. package/architecture/internal-capabilities.json +56 -9
  9. package/architecture/maintainability-debt.json +162 -107
  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 +64 -0
  22. package/architecture/v4-release-activation-shadow-domain.json +5 -2
  23. package/architecture/v4-release-topology.json +198 -38
  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 +176 -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 +1462 -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 +276 -72
  39. package/dist/site/capability-registry.json +5 -5
  40. package/dist/site/controller-registry.json +50 -13
  41. package/dist/site/kfd-claims.json +2610 -983
  42. package/dist/site/kfd-upstream-aggregate.json +1 -1
  43. package/dist/site/manual-registry.json +14 -14
  44. package/dist/site/node-api-registry.json +273 -127
  45. package/dist/site/page-registry.json +257 -53
  46. package/dist/site/public-surface-audit.json +3239 -1232
  47. package/dist/site/publication-authority-registry.json +1135 -363
  48. package/dist/site/publication-registry.json +4 -4
  49. package/dist/site/release-provenance.json +2 -1
  50. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
  51. package/dist/site/site-manifest.json +18 -18
  52. package/dist/site/workflow-registry.json +5097 -1659
  53. package/docs/MAP.md +2 -0
  54. package/docs/auditable-demo.md +1 -1
  55. package/docs/cli.md +21 -8
  56. package/docs/dev-delivery-warrant.md +1 -1
  57. package/docs/engineering-housekeeper.md +3 -3
  58. package/docs/kfd-support.md +26 -2
  59. package/docs/next-development-transition.md +2 -2
  60. package/docs/node-api-reference.md +216 -205
  61. package/docs/oci-publication.md +160 -0
  62. package/docs/publication-authority.md +2 -2
  63. package/docs/publish-transaction.md +36 -3
  64. package/docs/release-candidate.md +3 -3
  65. package/docs/release-flow.md +67 -19
  66. package/docs/release-governance.md +8 -8
  67. package/docs/release-passport.md +27 -2
  68. package/docs/reusable-build-surface.md +16 -0
  69. package/docs/v4-adopter-delivery.md +2 -2
  70. package/docs/v4-production-release.md +71 -8
  71. package/docs/v4-rust-wasm-production-authority.md +58 -0
  72. package/docs/v4-stage-capsule.md +2 -2
  73. package/docs/v4-tail-reseal.md +1 -1
  74. package/docs/workflow-catalog.md +155 -0
  75. package/docs/workflow-path-migration.md +109 -0
  76. package/package.json +12 -7
  77. package/packages/core/artifact-verification-envelope.js +5 -5
  78. package/packages/core/buildchain-contract.js +1 -1
  79. package/packages/core/buildchain-kfd-claims.js +2 -2
  80. package/packages/core/buildchain-publication-authority.js +43 -26
  81. package/packages/core/buildchain-v4-domain.wasm +0 -0
  82. package/packages/core/ci-lane-change-budget.js +14 -1
  83. package/packages/core/controller-evidence.js +4 -1
  84. package/packages/core/dev-delivery-candidate-identity.js +40 -6
  85. package/packages/core/dev-delivery-warrant-state.js +3 -4
  86. package/packages/core/dev-delivery-warrant.js +12 -2
  87. package/packages/core/github-governance-authority.js +4 -4
  88. package/packages/core/kfd-adopter-manifest.js +46 -47
  89. package/packages/core/next-development-projection.js +2 -2
  90. package/packages/core/next-development-transition.js +5 -6
  91. package/packages/core/oci-compose-qualification.js +140 -0
  92. package/packages/core/oci-publication-bundle.js +281 -0
  93. package/packages/core/oci-publication-graph.js +206 -0
  94. package/packages/core/paper-agent-entry.js +30 -4
  95. package/packages/core/paper-fleet.js +1 -1
  96. package/packages/core/paper-runtime-channels.js +348 -0
  97. package/packages/core/paper-scaffold-content.js +10 -0
  98. package/packages/core/paper.js +37 -71
  99. package/packages/core/publication-sealed-bundle.js +78 -13
  100. package/packages/core/release-line-dry-run.js +1 -1
  101. package/packages/core/release-passport-contract.js +5 -5
  102. package/packages/core/release-passport.js +5 -4
  103. package/packages/core/release-tail-product-capabilities.js +38 -0
  104. package/packages/core/release-tail-provider-plane.js +68 -1048
  105. package/packages/core/v4-canonical-contracts.js +15 -75
  106. package/packages/core/v4-domain-wasm-artifact.js +7 -0
  107. package/packages/core/v4-domain-wasm.js +216 -0
  108. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  109. package/packages/core/v4-floating-consumer-policy.js +1 -1
  110. package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
  111. package/packages/core/v4-product-publication.js +59 -0
  112. package/packages/core/v4-protected-publication-source.js +19 -21
  113. package/packages/core/v4-provider-operation-journal.js +18 -487
  114. package/packages/core/v4-provider-readback-idempotency.js +12 -97
  115. package/packages/core/v4-release-activation-shadow.js +13 -500
  116. package/packages/core/v4-release-invocation.js +28 -389
  117. package/packages/core/v4-stable-publication-fence.js +13 -342
  118. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  119. package/scripts/binary-publication-evidence.mjs +135 -0
  120. package/scripts/build-contract-core.mjs +1 -14
  121. package/scripts/build-v4-domain-wasm.mjs +189 -0
  122. package/scripts/check-action-bundles.mjs +12 -2
  123. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  124. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  125. package/scripts/check-inventory.mjs +35 -44
  126. package/scripts/check-maintainability.mjs +16 -113
  127. package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
  128. package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
  129. package/scripts/check-v4-release-topology.mjs +236 -50
  130. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  131. package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
  132. package/scripts/check-workflow-taxonomy.mjs +6 -0
  133. package/scripts/copy-v4-domain-wasm.mjs +14 -0
  134. package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
  135. package/scripts/dev-delivery-authority.mjs +8 -8
  136. package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
  137. package/scripts/dev-delivery-warrant-options.mjs +14 -9
  138. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  139. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  140. package/scripts/dev-delivery-warrant.mjs +76 -99
  141. package/scripts/dev-pr-auto-merge.mjs +15 -15
  142. package/scripts/generate-channel-build-workflow.mjs +7 -2
  143. package/scripts/generate-channel-promotion-workflow.mjs +10 -25
  144. package/scripts/generate-site-bundle.mjs +11 -11
  145. package/scripts/generate-v4-universal-workflow-facades.mjs +497 -0
  146. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  147. package/scripts/github-output.mjs +31 -0
  148. package/scripts/maintainability-metrics.mjs +16 -5
  149. package/scripts/maintainability-public-surface.mjs +115 -0
  150. package/scripts/next-development-review.mjs +307 -0
  151. package/scripts/oci-compose-preview.mjs +250 -0
  152. package/scripts/paper-work-fleet-cli.mjs +2 -0
  153. package/scripts/publication-candidate-kind.mjs +93 -0
  154. package/scripts/publication-candidate-sealer.mjs +16 -1
  155. package/scripts/release-asset-client.mjs +91 -0
  156. package/scripts/release-candidate-resolver.mjs +23 -35
  157. package/scripts/resume-from-candidate-run.mjs +11 -18
  158. package/scripts/site-capability-metadata.mjs +1 -0
  159. package/scripts/source-verification-evidence.mjs +344 -0
  160. package/scripts/stable-candidate-qualification.mjs +1 -1
  161. package/scripts/universal-facade-maintainability.mjs +91 -0
  162. package/scripts/v4-product-publication-intent.mjs +129 -0
  163. package/scripts/v4-publication-settlement.mjs +211 -0
  164. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  165. package/scripts/v4-universal-workflow-engine.mjs +620 -0
  166. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  167. package/scripts/verify-version-state-delta.mjs +173 -0
  168. package/scripts/workflow-taxonomy.mjs +507 -0
  169. package/templates/native-dev-delivery.yml +4 -7
  170. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  171. package/templates/universal-buildchain-bootstrap.yml +28 -0
@@ -19,6 +19,8 @@ import {
19
19
  evaluateTestBudgets,
20
20
  evaluateWorkflowBudgets,
21
21
  } from "./maintainability-governance.mjs";
22
+ import { governUniversalFacadeWorkflowMetrics } from "./universal-facade-maintainability.mjs";
23
+ import { evaluatePublicSurface } from "./maintainability-public-surface.mjs";
22
24
 
23
25
  function collectHotspots(
24
26
  root,
@@ -162,116 +164,6 @@ function selectedFunction(metrics, file, name) {
162
164
  return metrics.files[file]?.functions.find((entry) => entry.name === name);
163
165
  }
164
166
 
165
- function readJsonAtRevision(root, revision, file) {
166
- return JSON.parse(gitOutput(root, ["show", `${revision}:${file}`]));
167
- }
168
-
169
- function publicSurfaceContract(entry, kind) {
170
- if (kind === "cli") return { id: entry.id, usage: entry.usage };
171
- if (kind === "node")
172
- return {
173
- export: entry.export,
174
- specifier: entry.specifier,
175
- target: entry.target,
176
- };
177
- return {
178
- id: entry.id,
179
- path: entry.path,
180
- reusable: entry.reusable,
181
- inputs: entry.inputs || [],
182
- secrets: entry.secrets || [],
183
- outputs: entry.outputs || [],
184
- };
185
- }
186
-
187
- function evaluatePublicSurface({ root, revision, policy }) {
188
- const issues = [];
189
- const lifecycleFields = policy.publicSurfacePolicy.requiredLifecycleFields;
190
- const capabilityGroups = new Set(
191
- readJson(root, "dist/site/capability-registry.json").groups.map(
192
- (entry) => entry.id,
193
- ),
194
- );
195
- const definitions = [
196
- {
197
- file: "dist/site/cli-registry.json",
198
- collection: "commands",
199
- kind: "cli",
200
- key: "id",
201
- },
202
- {
203
- file: "dist/site/node-api-registry.json",
204
- collection: "exports",
205
- kind: "node",
206
- key: "export",
207
- },
208
- {
209
- file: "dist/site/workflow-registry.json",
210
- collection: "workflows",
211
- kind: "workflow",
212
- key: "id",
213
- },
214
- {
215
- file: "dist/site/workflow-registry.json",
216
- collection: "actions",
217
- kind: "action",
218
- key: "id",
219
- },
220
- ];
221
- for (const definition of definitions) {
222
- const current =
223
- readJson(root, definition.file)[definition.collection] || [];
224
- const baseline =
225
- readJsonAtRevision(root, revision, definition.file)[
226
- definition.collection
227
- ] || [];
228
- const baselineByKey = new Map(
229
- baseline.map((entry) => [entry[definition.key], entry]),
230
- );
231
- for (const entry of current) {
232
- const label = `${definition.kind}:${entry[definition.key]}`;
233
- for (const field of lifecycleFields) {
234
- if (
235
- !Object.prototype.hasOwnProperty.call(entry, field) ||
236
- typeof entry[field] !== "string"
237
- ) {
238
- issues.push(`${label}: lifecycle field ${field} is missing`);
239
- }
240
- }
241
- if (!capabilityGroups.has(entry.capabilityGroup)) {
242
- issues.push(
243
- `${label}: capability group ${entry.capabilityGroup || "<empty>"} is not registered`,
244
- );
245
- }
246
- const previous = baselineByKey.get(entry[definition.key]);
247
- const currentContract = publicSurfaceContract(entry, definition.kind);
248
- if (
249
- previous &&
250
- JSON.stringify(currentContract) !==
251
- JSON.stringify(publicSurfaceContract(previous, definition.kind))
252
- ) {
253
- const approval = policy.approvedPublicSurfaceTransitions?.[label];
254
- const approved =
255
- approval?.fromRevision === revision &&
256
- String(approval?.rationale || "").trim() &&
257
- JSON.stringify(approval?.contract) ===
258
- JSON.stringify(currentContract);
259
- if (!approved) {
260
- issues.push(
261
- `${label}: existing public contract drifted from ${revision}`,
262
- );
263
- }
264
- }
265
- if (!previous && !entry.nonDuplicationRationale) {
266
- issues.push(
267
- `${label}: new public surface requires a non-duplication rationale`,
268
- );
269
- }
270
- }
271
- }
272
- return issues;
273
- }
274
-
275
167
  function evaluateAddedFunctionBudgets({
276
168
  file,
277
169
  metrics,
@@ -497,12 +389,18 @@ function checkMaintainability({ root = process.cwd() } = {}) {
497
389
  root,
498
390
  extendedCoverageRevision,
499
391
  );
392
+ const { governed: governedCurrent, migration: facadeMigration } =
393
+ governUniversalFacadeWorkflowMetrics({
394
+ root,
395
+ current,
396
+ workflowMetricsAtRevision,
397
+ });
500
398
  const issues = evaluateExceptionGovernance({ policy });
501
399
  issues.push(...evaluateExceptionBudget({ policy }));
502
400
  const hotspots = collectHotspots(root, current, 20, debt.hotspots || []);
503
401
  issues.push(
504
402
  ...evaluateDebtAuthority({
505
- current,
403
+ current: governedCurrent,
506
404
  policy,
507
405
  debt,
508
406
  capabilityIds: new Set(
@@ -518,14 +416,19 @@ function checkMaintainability({ root = process.cwd() } = {}) {
518
416
  );
519
417
  issues.push(
520
418
  ...evaluateWorkflowBudgets({
521
- current,
419
+ current: governedCurrent,
522
420
  baselineFiles: baselineWorkflows,
523
421
  policy,
524
422
  }),
525
423
  );
526
424
  issues.push(...evaluateRepositoryBudgets({ current, policy }));
527
425
  issues.push(
528
- ...evaluatePublicSurface({ root, revision: enforcementRevision, policy }),
426
+ ...evaluatePublicSurface({
427
+ root,
428
+ revision: enforcementRevision,
429
+ policy,
430
+ migration: facadeMigration,
431
+ }),
529
432
  );
530
433
  if (issues.length > 0) {
531
434
  throw new Error(`maintainability check failed:\n- ${issues.join("\n- ")}`);
@@ -80,7 +80,7 @@ function assertPersistedSelectors() {
80
80
  const selector = match[2];
81
81
  const protectedBootstrap =
82
82
  relative ===
83
- ".github/workflows/buildchain-ref-promotion-recovery.yml" &&
83
+ ".github/workflows/self-ops-promotion-recovery.yml" &&
84
84
  match[1] === ".github/workflows/.release-candidate-promote.yml" &&
85
85
  selector === "alpha/v4/v4.0";
86
86
  const isV4 =
@@ -148,7 +148,7 @@ export function checkV4FloatingConsumerPolicyContract() {
148
148
  "Resolve controller identities",
149
149
  "Install Buildchain runtime dependencies",
150
150
  ]);
151
- const stageCanary = read(".github/workflows/v4-stage-capsule-canary.yml");
151
+ const stageCanary = read(".github/workflows/public-build-stage-capsule-canary.yml");
152
152
  if (
153
153
  !stageCanary.includes("consumer-admission:") ||
154
154
  !stageCanary.includes("needs: consumer-admission")
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from "node:fs";
4
+ import { readWorkflowTaxonomy, workflowPath } from "./workflow-taxonomy.mjs";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
 
@@ -8,8 +9,8 @@ const DEFAULT_ROOT = path.resolve(
8
9
  path.dirname(fileURLToPath(import.meta.url)),
9
10
  "..",
10
11
  );
11
- const CALLER_PATH = ".github/workflows/v4-public-consumer-dogfood.yml";
12
- const REUSABLE_PATH = ".github/workflows/v4-stage-capsule-canary.yml";
12
+ const CALLER_PATH = ".github/workflows/self-build-public-consumer-dogfood.yml";
13
+ const REUSABLE_PATH = ".github/workflows/public-build-stage-capsule-canary.yml";
13
14
  export const V4_PUBLIC_DOGFOOD_ALPHA_REF = "v4-alpha";
14
15
  const PRIVATE_CONSUMER = ["buildchain", "self", "dogfood"].join("-");
15
16
  const PRIVATE_SHADOW = ["kungfu", "shadow"].join("-");
@@ -26,7 +27,7 @@ permissions:
26
27
 
27
28
  jobs:
28
29
  dogfood:
29
- uses: kungfu-systems/buildchain/.github/workflows/v4-stage-capsule-canary.yml@${validationRef}
30
+ uses: kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml@${validationRef}
30
31
  with:
31
32
  consumer: buildchain
32
33
  node-version: "24"
@@ -93,10 +94,19 @@ function assertReusableWorkflow(root) {
93
94
 
94
95
  function assertWorkflowInventory(root) {
95
96
  const workflowRoot = path.join(root, ".github/workflows");
97
+ const taxonomy = readWorkflowTaxonomy(root);
96
98
  for (const entry of fs.readdirSync(workflowRoot, { withFileTypes: true })) {
97
99
  if (!entry.isFile() || !/\.ya?ml$/u.test(entry.name)) continue;
98
- const relative = `.github/workflows/${entry.name}`;
100
+ let relative = `.github/workflows/${entry.name}`;
99
101
  const text = read(root, relative);
102
+ const canonical = taxonomy?.entries.find(
103
+ (item) => workflowPath(item) === relative && item.compatibility,
104
+ );
105
+ if (canonical) {
106
+ if (read(root, canonical.compatibility.path) !== text)
107
+ fail(`${relative} differs from its public compatibility identity`);
108
+ relative = canonical.compatibility.path;
109
+ }
100
110
  if (
101
111
  relative !== REUSABLE_PATH &&
102
112
  text.includes("scripts/v4-stage-capsule-qualification.mjs")
@@ -104,7 +114,7 @@ function assertWorkflowInventory(root) {
104
114
  fail(`${relative} invokes the private qualification script directly`);
105
115
  if (
106
116
  relative !== CALLER_PATH &&
107
- text.includes(".github/workflows/v4-stage-capsule-canary.yml@")
117
+ text.includes(".github/workflows/public-build-stage-capsule-canary.yml@")
108
118
  )
109
119
  fail(`${relative} creates a second v4 dogfood caller`);
110
120
  if (relative !== REUSABLE_PATH && relative !== CALLER_PATH)
@@ -113,11 +123,13 @@ function assertWorkflowInventory(root) {
113
123
  }
114
124
 
115
125
  function assertProtectedVerify(root) {
116
- const verify = read(root, ".github/workflows/verify.yml");
126
+ const verify = read(root, ".github/workflows/self-build-verify.yml");
117
127
  for (const required of [
118
128
  "needs: stage-capsule-checkpoints",
119
- "name: Enforce public consumer-only v4 dogfood",
120
- "run: node scripts/check-v4-public-dogfood-contract.mjs",
129
+ "name: Run declared verify lifecycle (full source tests and generated artifact checks)",
130
+ "node .buildchain/runtime/bin/buildchain.mjs lifecycle run verify",
131
+ "run: node scripts/source-verification-evidence.mjs plan",
132
+ "run: node scripts/source-verification-evidence.mjs seal",
121
133
  ])
122
134
  if (!verify.includes(required))
123
135
  fail(`Verify is missing protected gate ${required}`);
@@ -150,7 +162,7 @@ function assertArchitecture(root) {
150
162
  !dogfood ||
151
163
  dogfood.callerWorkflow !== CALLER_PATH ||
152
164
  dogfood.reusableWorkflow !==
153
- "kungfu-systems/buildchain/.github/workflows/v4-stage-capsule-canary.yml" ||
165
+ "kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml" ||
154
166
  dogfood.runtimeBinding !== "job.workflow_sha" ||
155
167
  dogfood.consumerSourceBinding !== "github.sha" ||
156
168
  JSON.stringify(dogfood.executableStages) !==
@@ -193,6 +205,7 @@ function assertConsumerLifecycle(root) {
193
205
  '[lifecycle.install]\ncommand = "corepack enable pnpm && corepack pnpm@11.7.0 install --frozen-lockfile"',
194
206
  '[lifecycle.build]\ncommand = "corepack pnpm@11.7.0 run build && corepack pnpm@11.7.0 run generate:site"',
195
207
  "[lifecycle.verify]\ncommands = [",
208
+ '"corepack pnpm@11.7.0 run check",',
196
209
  ])
197
210
  if (!lifecycle.includes(declaration))
198
211
  fail(
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import assert from "node:assert/strict";
4
+ import crypto from "node:crypto";
4
5
  import fs from "node:fs";
6
+ import ts from "typescript";
7
+ import { workflowCompatibilityIdentity } from "./workflow-taxonomy.mjs";
5
8
  import path from "node:path";
6
- import { fileURLToPath } from "node:url";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
10
 
8
11
  import {
9
12
  parseWorkflowDocument,
@@ -17,6 +20,101 @@ function read(relative) {
17
20
  return fs.readFileSync(path.join(root, relative), "utf8");
18
21
  }
19
22
 
23
+ const LOCAL_MODULE_EXTENSIONS = ["", ".js", ".mjs", ".cjs"];
24
+ const PRIVILEGED_ENTRYPOINTS = [
25
+ "actions/v4-release-candidate-promote/index.js",
26
+ "scripts/binary-publication-evidence.mjs",
27
+ "scripts/next-development-review.mjs",
28
+ "scripts/oci-compose-preview.mjs",
29
+ "scripts/v4-publication-settlement.mjs",
30
+ ];
31
+
32
+ export function localModuleSpecifiers(source) {
33
+ const specifiers = [];
34
+ const visit = (node) => {
35
+ let value;
36
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
37
+ value = node.moduleSpecifier;
38
+ else if (
39
+ ts.isCallExpression(node) &&
40
+ node.expression.kind === ts.SyntaxKind.ImportKeyword
41
+ )
42
+ value = node.arguments[0];
43
+ if (value && ts.isStringLiteral(value) && value.text.startsWith("."))
44
+ specifiers.push(value.text);
45
+ ts.forEachChild(node, visit);
46
+ };
47
+ visit(
48
+ ts.createSourceFile(
49
+ "module.mjs",
50
+ source,
51
+ ts.ScriptTarget.Latest,
52
+ true,
53
+ ts.ScriptKind.JS,
54
+ ),
55
+ );
56
+ return [...new Set(specifiers)].sort();
57
+ }
58
+
59
+ function resolveLocalModule(importer, specifier) {
60
+ const unresolved = path.posix.normalize(
61
+ path.posix.join(path.posix.dirname(importer), specifier),
62
+ );
63
+ assert.ok(
64
+ !unresolved.startsWith("../") && !path.posix.isAbsolute(unresolved),
65
+ `local module import escapes the repository: ${importer} -> ${specifier}`,
66
+ );
67
+ const candidates = [
68
+ ...LOCAL_MODULE_EXTENSIONS.map((extension) => `${unresolved}${extension}`),
69
+ ...LOCAL_MODULE_EXTENSIONS.slice(1).map((extension) =>
70
+ path.posix.join(unresolved, `index${extension}`),
71
+ ),
72
+ ];
73
+ const matches = candidates.filter((relative) => {
74
+ const absolute = path.join(root, relative);
75
+ return fs.existsSync(absolute) && fs.statSync(absolute).isFile();
76
+ });
77
+ assert.equal(
78
+ matches.length,
79
+ 1,
80
+ `local module import must resolve exactly once: ${importer} -> ${specifier}`,
81
+ );
82
+ return matches[0];
83
+ }
84
+
85
+ export function discoverStaticModuleClosure(entrypoints) {
86
+ const pending = [...entrypoints];
87
+ const visited = new Set();
88
+ while (pending.length > 0) {
89
+ const relative = pending.pop();
90
+ if (visited.has(relative)) continue;
91
+ assert.ok(
92
+ fs.statSync(path.join(root, relative)).isFile(),
93
+ `privileged entry is not a file: ${relative}`,
94
+ );
95
+ visited.add(relative);
96
+ for (const specifier of localModuleSpecifiers(read(relative))) {
97
+ const dependency = resolveLocalModule(relative, specifier);
98
+ if (!visited.has(dependency)) pending.push(dependency);
99
+ }
100
+ }
101
+ return [...visited].sort();
102
+ }
103
+
104
+ function rootedPathSet(paths) {
105
+ return `sha256:${crypto
106
+ .createHash("sha256")
107
+ .update(JSON.stringify([...paths].sort()))
108
+ .digest("hex")}`;
109
+ }
110
+
111
+ function fileSha256(relative) {
112
+ return `sha256:${crypto
113
+ .createHash("sha256")
114
+ .update(fs.readFileSync(path.join(root, relative)))
115
+ .digest("hex")}`;
116
+ }
117
+
20
118
  function productionFiles(relative, extensions) {
21
119
  const absolute = path.join(root, relative);
22
120
  if (!fs.existsSync(absolute)) return [];
@@ -61,6 +159,23 @@ export function discoverV4ReleaseAuthorityClosure() {
61
159
  [".yml", ".yaml", ".js", ".mjs", ".cjs"],
62
160
  /(?:createV4ReleaseReceipt|release-receipt\.json|V4_RELEASE_RECEIPT_CONTRACT)/u,
63
161
  );
162
+ const privilegedModules = discoverStaticModuleClosure(PRIVILEGED_ENTRYPOINTS);
163
+ const rustWasmArtifact = "packages/core/buildchain-v4-domain.wasm";
164
+ const rustWasmDistributions = [
165
+ "actions/promote-buildchain-ref/dist/buildchain-v4-domain.wasm",
166
+ "actions/release-tail/dist/buildchain-v4-domain.wasm",
167
+ "actions/v4-release-candidate-promote/dist/buildchain-v4-domain.wasm",
168
+ ];
169
+ const legacyEngineModules = productionFiles(
170
+ "actions/promote-buildchain-ref",
171
+ [".js", ".mjs", ".cjs"],
172
+ )
173
+ .filter((relative) =>
174
+ /(?:^|\/)(?:lib|promote-(?:alpha|release|major)-channel|durable-transaction-operations)\.js$/u.test(
175
+ relative,
176
+ ),
177
+ )
178
+ .sort();
64
179
  return {
65
180
  providerAdapters: [
66
181
  ...new Set([
@@ -71,6 +186,23 @@ export function discoverV4ReleaseAuthorityClosure() {
71
186
  runtimeSelectors,
72
187
  runtimeEngines: ["actions/v4-release-candidate-promote/index.js"],
73
188
  terminalProjections,
189
+ privilegedExecutableClosure: {
190
+ entrypoints: PRIVILEGED_ENTRYPOINTS,
191
+ modules: privilegedModules,
192
+ root: rootedPathSet(privilegedModules),
193
+ },
194
+ rustWasmAuthority: {
195
+ loader: "packages/core/v4-domain-wasm.js",
196
+ metadata: "packages/core/v4-domain-wasm-artifact.js",
197
+ artifact: rustWasmArtifact,
198
+ artifactRoot: fileSha256(rustWasmArtifact),
199
+ distributedArtifacts: rustWasmDistributions,
200
+ byteIdenticalDistributions: rustWasmDistributions.every(
201
+ (relative) => fileSha256(relative) === fileSha256(rustWasmArtifact),
202
+ ),
203
+ fallbackWriterCount: 0,
204
+ },
205
+ legacyEngineModules,
74
206
  };
75
207
  }
76
208
 
@@ -97,33 +229,46 @@ function permission(block, name) {
97
229
  function workflowSnapshot(relative) {
98
230
  const source = read(relative);
99
231
  const document = parseWorkflowDocument(source);
100
- const jobs = document.jobs.map((job) => {
101
- const block = jobBlock(source, job.id);
102
- const uses = job.uses || null;
103
- return {
104
- id: job.id,
105
- kind: uses ? "reusable-call" : "runner",
106
- uses,
107
- permissions: {
108
- contents: permission(block, "contents"),
109
- idToken: permission(block, "id-token"),
110
- },
111
- carriers: {
112
- artifactDownload: /uses:\s+actions\/download-artifact@/u.test(block),
113
- artifactUpload: /uses:\s+actions\/upload-artifact@/u.test(block),
114
- jobOutput: /GITHUB_OUTPUT/u.test(block),
115
- },
116
- mutationSignals: [
117
- /contents:\s*write/u.test(block) && "contents-write",
118
- /id-token:\s*write/u.test(block) && "oidc-write",
119
- /(?:promote-buildchain-ref|v4-release-candidate-promote)/u.test(
120
- block,
121
- ) && "promotion-runtime",
122
- /(?:git push|npm publish|gh release (?:create|upload))/u.test(block) &&
123
- "direct-publication-command",
124
- ].filter(Boolean),
125
- };
126
- });
232
+ const jobs = document.jobs
233
+ .filter((job) => job.id !== "universal-bootstrap")
234
+ .map((job) => {
235
+ const block = jobBlock(source, job.id);
236
+ const uses = job.uses || null;
237
+ // A built-in publisher still requires mutation authority when the caller supplies its envelope.
238
+ const inheritedPublisher =
239
+ !/^ permissions:/mu.test(block) &&
240
+ /uses:.*actions\/v4-release-candidate-promote/u.test(block);
241
+ return {
242
+ id: job.id,
243
+ kind: uses ? "reusable-call" : "runner",
244
+ uses,
245
+ permissions: {
246
+ contents:
247
+ permission(block, "contents") ||
248
+ (inheritedPublisher ? "inherited" : null),
249
+ idToken:
250
+ permission(block, "id-token") ||
251
+ (inheritedPublisher ? "inherited" : null),
252
+ },
253
+ carriers: {
254
+ artifactDownload: /uses:\s+actions\/download-artifact@/u.test(block),
255
+ artifactUpload: /uses:\s+actions\/upload-artifact@/u.test(block),
256
+ jobOutput: /GITHUB_OUTPUT/u.test(block),
257
+ },
258
+ mutationSignals: [
259
+ (/contents:\s*write/u.test(block) || inheritedPublisher) &&
260
+ "contents-write",
261
+ (/id-token:\s*write/u.test(block) || inheritedPublisher) &&
262
+ "oidc-write",
263
+ /(?:promote-buildchain-ref|v4-release-candidate-promote)/u.test(
264
+ block,
265
+ ) && "promotion-runtime",
266
+ /(?:git push|npm publish|gh release (?:create|upload))/u.test(
267
+ block,
268
+ ) && "direct-publication-command",
269
+ ].filter(Boolean),
270
+ };
271
+ });
127
272
  return {
128
273
  path: relative,
129
274
  triggers: document.triggers,
@@ -131,6 +276,7 @@ function workflowSnapshot(relative) {
131
276
  reusableEdges: parseYamlUses(source)
132
277
  .map(({ value }) => value)
133
278
  .filter((value) => /\.github\/workflows\//u.test(value))
279
+ .filter((value) => !/\/bootstrap\.yml(?:@v4)?$/u.test(value))
134
280
  .sort(),
135
281
  };
136
282
  }
@@ -230,7 +376,12 @@ export function findUnknownV4ReleaseTopology(
230
376
  ) {
231
377
  const declared = new Set(workflowPaths);
232
378
  return allWorkflowPaths
233
- .filter((relative) => !declared.has(relative))
379
+ .filter(
380
+ (relative) =>
381
+ !declared.has(
382
+ workflowCompatibilityIdentity(root, relative, readWorkflow(relative)),
383
+ ),
384
+ )
234
385
  .filter((relative) => {
235
386
  const source = readWorkflow(relative);
236
387
  const usesReleaseAuthority = parseYamlUses(source).some(({ value }) =>
@@ -274,6 +425,7 @@ function assertAuthorityClosure(ledger) {
274
425
  "runtimeSelectors",
275
426
  "runtimeEngines",
276
427
  "terminalProjections",
428
+ "legacyEngineModules",
277
429
  ]) {
278
430
  assert.ok(
279
431
  Array.isArray(closure[className]) && closure[className].length > 0,
@@ -292,6 +444,24 @@ function assertAuthorityClosure(ledger) {
292
444
  discovered[className],
293
445
  `authority closure class drifted: ${className}`,
294
446
  );
447
+ assert.deepEqual(
448
+ closure.privilegedExecutableClosure,
449
+ discovered.privilegedExecutableClosure,
450
+ "privileged executable transitive closure drifted",
451
+ );
452
+ assert.match(
453
+ closure.privilegedExecutableClosure.root,
454
+ /^sha256:[0-9a-f]{64}$/u,
455
+ );
456
+ const reachableLegacyEngines = closure.legacyEngineModules.filter(
457
+ (relative) =>
458
+ closure.privilegedExecutableClosure.modules.includes(relative),
459
+ );
460
+ assert.deepEqual(
461
+ reachableLegacyEngines,
462
+ [],
463
+ `legacy release engines remain reachable from APPLY: ${reachableLegacyEngines.join(", ")}`,
464
+ );
295
465
  assert.deepEqual(closure.runtimeEngines, [
296
466
  "actions/v4-release-candidate-promote/index.js",
297
467
  ]);
@@ -301,11 +471,11 @@ function assertAuthorityClosure(ledger) {
301
471
  );
302
472
  assert.equal(
303
473
  closure.recoveryEntry,
304
- ".github/workflows/buildchain-ref-promotion-recovery.yml",
474
+ ".github/workflows/self-ops-promotion-recovery.yml",
305
475
  );
306
476
  const engineSurface = [
307
477
  ".github/workflows/.release-candidate-promote.yml",
308
- ...closure.runtimeEngines,
478
+ ...closure.privilegedExecutableClosure.modules,
309
479
  ]
310
480
  .map(read)
311
481
  .join("\n");
@@ -333,6 +503,17 @@ export function checkV4ReleaseTopology() {
333
503
  assert.equal(ledger.contract, "kungfu-buildchain-v4-release-topology/v1");
334
504
  assertClosedWorld(ledger.closedWorld.workflowPaths);
335
505
  assertAuthorityClosure(ledger);
506
+ const preview = ledger.postPublicationScope;
507
+ assert.deepEqual(preview.workflowPaths, [
508
+ ".github/workflows/public-release-oci-compose-preview.yml",
509
+ ]);
510
+ const previewTopology = discoverV4ReleaseTopology(
511
+ preview.workflowPaths,
512
+ preview.workflowPaths,
513
+ );
514
+ assert.equal(previewTopology.semanticMetrics.contentsWriteJobCount, 1);
515
+ assert.equal(previewTopology.semanticMetrics.oidcWriteJobCount, 0);
516
+ assert.deepEqual(preview.observedTopology, previewTopology);
336
517
  const actual = discoverV4ReleaseTopology(
337
518
  ledger.closedWorld.workflowPaths,
338
519
  ledger.semanticScope.workflowPaths,
@@ -361,23 +542,28 @@ export function checkV4ReleaseTopology() {
361
542
  return actual;
362
543
  }
363
544
 
364
- if (process.argv.includes("--print-closure")) {
365
- process.stdout.write(
366
- `${JSON.stringify(discoverV4ReleaseAuthorityClosure(), null, 2)}\n`,
367
- );
368
- } else if (process.argv.includes("--print")) {
369
- const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
370
- process.stdout.write(
371
- `${JSON.stringify(
372
- discoverV4ReleaseTopology(
373
- ledger.closedWorld.workflowPaths,
374
- ledger.semanticScope.workflowPaths,
375
- ),
376
- null,
377
- 2,
378
- )}\n`,
379
- );
380
- } else {
381
- checkV4ReleaseTopology();
382
- process.stdout.write("v4 release topology: ok\n");
545
+ if (
546
+ process.argv[1] &&
547
+ import.meta.url === pathToFileURL(process.argv[1]).href
548
+ ) {
549
+ if (process.argv.includes("--print-closure")) {
550
+ process.stdout.write(
551
+ `${JSON.stringify(discoverV4ReleaseAuthorityClosure(), null, 2)}\n`,
552
+ );
553
+ } else if (process.argv.includes("--print")) {
554
+ const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
555
+ process.stdout.write(
556
+ `${JSON.stringify(
557
+ discoverV4ReleaseTopology(
558
+ ledger.closedWorld.workflowPaths,
559
+ ledger.semanticScope.workflowPaths,
560
+ ),
561
+ null,
562
+ 2,
563
+ )}\n`,
564
+ );
565
+ } else {
566
+ checkV4ReleaseTopology();
567
+ process.stdout.write("v4 release topology: ok\n");
568
+ }
383
569
  }
@@ -35,7 +35,10 @@ function publicSurfaceIds(root) {
35
35
  return new Set([
36
36
  ...Object.keys(packageJson.exports || {}).map((id) => `export:${id}`),
37
37
  ...(cli.commands || []).map((entry) => `cli:${entry.id}`),
38
- ...(workflows.workflows || []).map((entry) => `workflow:${entry.id}`),
38
+ ...(workflows.workflows || []).flatMap((entry) => [
39
+ `workflow:${entry.id}`,
40
+ ...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
41
+ ]),
39
42
  ...(workflows.actions || []).map((entry) => `action:${entry.id}`),
40
43
  ]);
41
44
  }