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

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 +64 -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
@@ -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,100 @@ 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/v4-publication-settlement.mjs",
29
+ ];
30
+
31
+ export function localModuleSpecifiers(source) {
32
+ const specifiers = [];
33
+ const visit = (node) => {
34
+ let value;
35
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
36
+ value = node.moduleSpecifier;
37
+ else if (
38
+ ts.isCallExpression(node) &&
39
+ node.expression.kind === ts.SyntaxKind.ImportKeyword
40
+ )
41
+ value = node.arguments[0];
42
+ if (value && ts.isStringLiteral(value) && value.text.startsWith("."))
43
+ specifiers.push(value.text);
44
+ ts.forEachChild(node, visit);
45
+ };
46
+ visit(
47
+ ts.createSourceFile(
48
+ "module.mjs",
49
+ source,
50
+ ts.ScriptTarget.Latest,
51
+ true,
52
+ ts.ScriptKind.JS,
53
+ ),
54
+ );
55
+ return [...new Set(specifiers)].sort();
56
+ }
57
+
58
+ function resolveLocalModule(importer, specifier) {
59
+ const unresolved = path.posix.normalize(
60
+ path.posix.join(path.posix.dirname(importer), specifier),
61
+ );
62
+ assert.ok(
63
+ !unresolved.startsWith("../") && !path.posix.isAbsolute(unresolved),
64
+ `local module import escapes the repository: ${importer} -> ${specifier}`,
65
+ );
66
+ const candidates = [
67
+ ...LOCAL_MODULE_EXTENSIONS.map((extension) => `${unresolved}${extension}`),
68
+ ...LOCAL_MODULE_EXTENSIONS.slice(1).map((extension) =>
69
+ path.posix.join(unresolved, `index${extension}`),
70
+ ),
71
+ ];
72
+ const matches = candidates.filter((relative) => {
73
+ const absolute = path.join(root, relative);
74
+ return fs.existsSync(absolute) && fs.statSync(absolute).isFile();
75
+ });
76
+ assert.equal(
77
+ matches.length,
78
+ 1,
79
+ `local module import must resolve exactly once: ${importer} -> ${specifier}`,
80
+ );
81
+ return matches[0];
82
+ }
83
+
84
+ export function discoverStaticModuleClosure(entrypoints) {
85
+ const pending = [...entrypoints];
86
+ const visited = new Set();
87
+ while (pending.length > 0) {
88
+ const relative = pending.pop();
89
+ if (visited.has(relative)) continue;
90
+ assert.ok(
91
+ fs.statSync(path.join(root, relative)).isFile(),
92
+ `privileged entry is not a file: ${relative}`,
93
+ );
94
+ visited.add(relative);
95
+ for (const specifier of localModuleSpecifiers(read(relative))) {
96
+ const dependency = resolveLocalModule(relative, specifier);
97
+ if (!visited.has(dependency)) pending.push(dependency);
98
+ }
99
+ }
100
+ return [...visited].sort();
101
+ }
102
+
103
+ function rootedPathSet(paths) {
104
+ return `sha256:${crypto
105
+ .createHash("sha256")
106
+ .update(JSON.stringify([...paths].sort()))
107
+ .digest("hex")}`;
108
+ }
109
+
110
+ function fileSha256(relative) {
111
+ return `sha256:${crypto
112
+ .createHash("sha256")
113
+ .update(fs.readFileSync(path.join(root, relative)))
114
+ .digest("hex")}`;
115
+ }
116
+
20
117
  function productionFiles(relative, extensions) {
21
118
  const absolute = path.join(root, relative);
22
119
  if (!fs.existsSync(absolute)) return [];
@@ -61,6 +158,23 @@ export function discoverV4ReleaseAuthorityClosure() {
61
158
  [".yml", ".yaml", ".js", ".mjs", ".cjs"],
62
159
  /(?:createV4ReleaseReceipt|release-receipt\.json|V4_RELEASE_RECEIPT_CONTRACT)/u,
63
160
  );
161
+ const privilegedModules = discoverStaticModuleClosure(PRIVILEGED_ENTRYPOINTS);
162
+ const rustWasmArtifact = "packages/core/buildchain-v4-domain.wasm";
163
+ const rustWasmDistributions = [
164
+ "actions/promote-buildchain-ref/dist/buildchain-v4-domain.wasm",
165
+ "actions/release-tail/dist/buildchain-v4-domain.wasm",
166
+ "actions/v4-release-candidate-promote/dist/buildchain-v4-domain.wasm",
167
+ ];
168
+ const legacyEngineModules = productionFiles(
169
+ "actions/promote-buildchain-ref",
170
+ [".js", ".mjs", ".cjs"],
171
+ )
172
+ .filter((relative) =>
173
+ /(?:^|\/)(?:lib|promote-(?:alpha|release|major)-channel|durable-transaction-operations)\.js$/u.test(
174
+ relative,
175
+ ),
176
+ )
177
+ .sort();
64
178
  return {
65
179
  providerAdapters: [
66
180
  ...new Set([
@@ -71,6 +185,23 @@ export function discoverV4ReleaseAuthorityClosure() {
71
185
  runtimeSelectors,
72
186
  runtimeEngines: ["actions/v4-release-candidate-promote/index.js"],
73
187
  terminalProjections,
188
+ privilegedExecutableClosure: {
189
+ entrypoints: PRIVILEGED_ENTRYPOINTS,
190
+ modules: privilegedModules,
191
+ root: rootedPathSet(privilegedModules),
192
+ },
193
+ rustWasmAuthority: {
194
+ loader: "packages/core/v4-domain-wasm.js",
195
+ metadata: "packages/core/v4-domain-wasm-artifact.js",
196
+ artifact: rustWasmArtifact,
197
+ artifactRoot: fileSha256(rustWasmArtifact),
198
+ distributedArtifacts: rustWasmDistributions,
199
+ byteIdenticalDistributions: rustWasmDistributions.every(
200
+ (relative) => fileSha256(relative) === fileSha256(rustWasmArtifact),
201
+ ),
202
+ fallbackWriterCount: 0,
203
+ },
204
+ legacyEngineModules,
74
205
  };
75
206
  }
76
207
 
@@ -97,33 +228,36 @@ function permission(block, name) {
97
228
  function workflowSnapshot(relative) {
98
229
  const source = read(relative);
99
230
  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
- });
231
+ const jobs = document.jobs
232
+ .filter((job) => job.id !== "universal-bootstrap")
233
+ .map((job) => {
234
+ const block = jobBlock(source, job.id);
235
+ const uses = job.uses || null;
236
+ return {
237
+ id: job.id,
238
+ kind: uses ? "reusable-call" : "runner",
239
+ uses,
240
+ permissions: {
241
+ contents: permission(block, "contents"),
242
+ idToken: permission(block, "id-token"),
243
+ },
244
+ carriers: {
245
+ artifactDownload: /uses:\s+actions\/download-artifact@/u.test(block),
246
+ artifactUpload: /uses:\s+actions\/upload-artifact@/u.test(block),
247
+ jobOutput: /GITHUB_OUTPUT/u.test(block),
248
+ },
249
+ mutationSignals: [
250
+ /contents:\s*write/u.test(block) && "contents-write",
251
+ /id-token:\s*write/u.test(block) && "oidc-write",
252
+ /(?:promote-buildchain-ref|v4-release-candidate-promote)/u.test(
253
+ block,
254
+ ) && "promotion-runtime",
255
+ /(?:git push|npm publish|gh release (?:create|upload))/u.test(
256
+ block,
257
+ ) && "direct-publication-command",
258
+ ].filter(Boolean),
259
+ };
260
+ });
127
261
  return {
128
262
  path: relative,
129
263
  triggers: document.triggers,
@@ -131,6 +265,7 @@ function workflowSnapshot(relative) {
131
265
  reusableEdges: parseYamlUses(source)
132
266
  .map(({ value }) => value)
133
267
  .filter((value) => /\.github\/workflows\//u.test(value))
268
+ .filter((value) => !/\/bootstrap\.yml(?:@v4)?$/u.test(value))
134
269
  .sort(),
135
270
  };
136
271
  }
@@ -230,7 +365,12 @@ export function findUnknownV4ReleaseTopology(
230
365
  ) {
231
366
  const declared = new Set(workflowPaths);
232
367
  return allWorkflowPaths
233
- .filter((relative) => !declared.has(relative))
368
+ .filter(
369
+ (relative) =>
370
+ !declared.has(
371
+ workflowCompatibilityIdentity(root, relative, readWorkflow(relative)),
372
+ ),
373
+ )
234
374
  .filter((relative) => {
235
375
  const source = readWorkflow(relative);
236
376
  const usesReleaseAuthority = parseYamlUses(source).some(({ value }) =>
@@ -274,6 +414,7 @@ function assertAuthorityClosure(ledger) {
274
414
  "runtimeSelectors",
275
415
  "runtimeEngines",
276
416
  "terminalProjections",
417
+ "legacyEngineModules",
277
418
  ]) {
278
419
  assert.ok(
279
420
  Array.isArray(closure[className]) && closure[className].length > 0,
@@ -292,6 +433,24 @@ function assertAuthorityClosure(ledger) {
292
433
  discovered[className],
293
434
  `authority closure class drifted: ${className}`,
294
435
  );
436
+ assert.deepEqual(
437
+ closure.privilegedExecutableClosure,
438
+ discovered.privilegedExecutableClosure,
439
+ "privileged executable transitive closure drifted",
440
+ );
441
+ assert.match(
442
+ closure.privilegedExecutableClosure.root,
443
+ /^sha256:[0-9a-f]{64}$/u,
444
+ );
445
+ const reachableLegacyEngines = closure.legacyEngineModules.filter(
446
+ (relative) =>
447
+ closure.privilegedExecutableClosure.modules.includes(relative),
448
+ );
449
+ assert.deepEqual(
450
+ reachableLegacyEngines,
451
+ [],
452
+ `legacy release engines remain reachable from APPLY: ${reachableLegacyEngines.join(", ")}`,
453
+ );
295
454
  assert.deepEqual(closure.runtimeEngines, [
296
455
  "actions/v4-release-candidate-promote/index.js",
297
456
  ]);
@@ -301,11 +460,11 @@ function assertAuthorityClosure(ledger) {
301
460
  );
302
461
  assert.equal(
303
462
  closure.recoveryEntry,
304
- ".github/workflows/buildchain-ref-promotion-recovery.yml",
463
+ ".github/workflows/self-ops-promotion-recovery.yml",
305
464
  );
306
465
  const engineSurface = [
307
466
  ".github/workflows/.release-candidate-promote.yml",
308
- ...closure.runtimeEngines,
467
+ ...closure.privilegedExecutableClosure.modules,
309
468
  ]
310
469
  .map(read)
311
470
  .join("\n");
@@ -361,23 +520,28 @@ export function checkV4ReleaseTopology() {
361
520
  return actual;
362
521
  }
363
522
 
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");
523
+ if (
524
+ process.argv[1] &&
525
+ import.meta.url === pathToFileURL(process.argv[1]).href
526
+ ) {
527
+ if (process.argv.includes("--print-closure")) {
528
+ process.stdout.write(
529
+ `${JSON.stringify(discoverV4ReleaseAuthorityClosure(), null, 2)}\n`,
530
+ );
531
+ } else if (process.argv.includes("--print")) {
532
+ const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
533
+ process.stdout.write(
534
+ `${JSON.stringify(
535
+ discoverV4ReleaseTopology(
536
+ ledger.closedWorld.workflowPaths,
537
+ ledger.semanticScope.workflowPaths,
538
+ ),
539
+ null,
540
+ 2,
541
+ )}\n`,
542
+ );
543
+ } else {
544
+ checkV4ReleaseTopology();
545
+ process.stdout.write("v4 release topology: ok\n");
546
+ }
383
547
  }
@@ -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
  }