@kungfu-tech/buildchain 3.0.6 → 3.0.7-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +4 -2
  2. package/actions/promote-buildchain-ref/README.md +10 -0
  3. package/actions/release-tail/README.md +17 -0
  4. package/bin/buildchain.mjs +11 -0
  5. package/bin/internal/command-registry.mjs +2 -0
  6. package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
  7. package/contracts/engineering-housekeeper-v1.schema.json +143 -0
  8. package/contracts/fixtures/engineering-housekeeper-v1/cases.json +68 -0
  9. package/contracts/fixtures/release-tail-capabilities-v1/kungfu-alpha.json +273 -0
  10. package/contracts/publication-rehearsal-capsule-v1.schema.json +173 -0
  11. package/contracts/release-tail-capabilities-v1.schema.json +199 -0
  12. package/contracts/release-tail-provider-bindings-v1.schema.json +56 -0
  13. package/dist/site/agent-index.json +3 -0
  14. package/dist/site/artifact-schemas.json +6 -0
  15. package/dist/site/buildchain-contract.json +42 -27
  16. package/dist/site/buildchain-site.json +384 -33
  17. package/dist/site/capability-registry.json +15 -12
  18. package/dist/site/cli-registry.json +102 -0
  19. package/dist/site/controller-registry.json +19 -3
  20. package/dist/site/kfd-claims.json +359 -17
  21. package/dist/site/kfd-upstream-aggregate.json +1 -1
  22. package/dist/site/manual-registry.json +53 -7
  23. package/dist/site/node-api-registry.json +5882 -3605
  24. package/dist/site/page-registry.json +348 -21
  25. package/dist/site/public-surface-audit.json +396 -16
  26. package/dist/site/publication-authority-registry.json +106 -1
  27. package/dist/site/publication-registry.json +4 -4
  28. package/dist/site/release-provenance.json +7 -0
  29. package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +269 -0
  30. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +353 -0
  31. package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +94 -0
  32. package/dist/site/site-manifest.json +35 -11
  33. package/dist/site/workflow-registry.json +204 -10
  34. package/docs/MAP.md +6 -2
  35. package/docs/auditable-demo.md +2 -2
  36. package/docs/cli-reference.md +136 -0
  37. package/docs/dev-delivery-warrant.md +49 -4
  38. package/docs/engineering-housekeeper.md +138 -0
  39. package/docs/lifecycle-protocol.md +4 -2
  40. package/docs/node-api-reference.md +495 -281
  41. package/docs/publication-rehearsal.md +94 -0
  42. package/docs/release-governance.md +17 -2
  43. package/docs/release-tail-contract.md +160 -0
  44. package/docs/release-tail-provider-plane.md +120 -0
  45. package/docs/reusable-build-surface.md +11 -0
  46. package/package.json +11 -3
  47. package/packages/core/artifact-signing.js +61 -0
  48. package/packages/core/buildchain-agent-manuals.js +3 -0
  49. package/packages/core/buildchain-config.js +66 -6
  50. package/packages/core/buildchain-kfd-claims.js +1 -1
  51. package/packages/core/buildchain-publication-authority.js +5 -0
  52. package/packages/core/controller-evidence.js +2 -1
  53. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  54. package/packages/core/dev-delivery-warrant-shadow.js +502 -0
  55. package/packages/core/dev-delivery-warrant.js +15 -6
  56. package/packages/core/diagnostics.js +8 -3
  57. package/packages/core/engineering-housekeeper-github-client.js +222 -0
  58. package/packages/core/engineering-housekeeper-github.js +501 -0
  59. package/packages/core/engineering-housekeeper.js +259 -0
  60. package/packages/core/index.js +42 -0
  61. package/packages/core/kfd-gate.js +45 -15
  62. package/packages/core/paper-agent-entry.js +11 -5
  63. package/packages/core/paper-repository.js +1 -0
  64. package/packages/core/paper-scaffold-content.js +21 -0
  65. package/packages/core/paper.js +28 -2
  66. package/packages/core/publication-rehearsal-projection.js +173 -0
  67. package/packages/core/publication-rehearsal-runtime.js +921 -0
  68. package/packages/core/release-passport.js +130 -20
  69. package/packages/core/release-tail-compatibility.js +60 -0
  70. package/packages/core/release-tail-provider-adapters.js +461 -0
  71. package/packages/core/release-tail-provider-plane.js +1228 -0
  72. package/scripts/assemble-publication-artifact-admission.mjs +1 -1
  73. package/scripts/assemble-self-publication-admission.mjs +2 -2
  74. package/scripts/audit-publication-control-plane.mjs +1 -1
  75. package/scripts/auditable-demo-bundle-verification.mjs +2 -3
  76. package/scripts/auditable-demo-platform.mjs +2 -2
  77. package/scripts/auditable-demo-renditions.mjs +1 -1
  78. package/scripts/auditable-demo.mjs +2 -2
  79. package/scripts/build-contract-core.mjs +8 -3
  80. package/scripts/build-standalone-binary.mjs +14 -3
  81. package/scripts/buildchain-cli-help.mjs +13 -0
  82. package/scripts/check-core-mechanism-inventory.mjs +347 -0
  83. package/scripts/check-inventory.mjs +10 -8
  84. package/scripts/check-maintainability.mjs +9 -2
  85. package/scripts/check-release-tail-contract.mjs +435 -0
  86. package/scripts/dev-delivery-warrant.mjs +31 -4
  87. package/scripts/dev-pr-auto-merge.mjs +30 -4
  88. package/scripts/dev-pr-delivery-warrant.mjs +50 -0
  89. package/scripts/engineering-housekeeper-workflow.mjs +394 -0
  90. package/scripts/generate-channel-promotion-workflow.mjs +10 -8
  91. package/scripts/generate-site-bundle.mjs +47 -4
  92. package/scripts/init-repo.mjs +26 -2
  93. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  94. package/scripts/materialize-self-release-candidate-version.mjs +137 -0
  95. package/scripts/publication-commit-evidence.mjs +69 -23
  96. package/scripts/release-candidate-resolver.mjs +16 -10
  97. package/scripts/release-tail.mjs +159 -0
  98. package/scripts/resume-from-candidate-run.mjs +123 -9
  99. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  100. package/scripts/site-capability-metadata.mjs +13 -0
  101. package/scripts/v4-architecture.mjs +600 -0
  102. package/scripts/web-surface-core.mjs +8 -2
  103. package/scripts/workflow-call-contract.mjs +184 -5
@@ -139,7 +139,7 @@ async function main() {
139
139
  sourceSha,
140
140
  runtimeSha,
141
141
  contractDigest: controllerReceipt.runtime?.contractDigest,
142
- policyDigest: gateAggregate.policyDigest,
142
+ policyDigest: gateBindings.policyDigest,
143
143
  gateRegistryDigest: gateBindings.registryDigest,
144
144
  controllerReceiptDigest: controllerReceipt.digest,
145
145
  runnerProvenanceDigest: runnerProvenance.receiptDigest,
@@ -159,7 +159,7 @@ async function main() {
159
159
  sourceSha,
160
160
  runtimeSha,
161
161
  contractDigest: controllerReceipt.runtime?.contractDigest,
162
- policyDigest: gateAggregate.policyDigest,
162
+ policyDigest: gateBindings.policyDigest,
163
163
  gateRegistryDigest: gateBindings.registryDigest,
164
164
  controllerReceiptDigest: controllerReceipt.digest,
165
165
  runnerProvenanceDigest: runnerProvenance.receiptDigest,
@@ -173,7 +173,7 @@ async function main() {
173
173
  artifactDigest: artifactSet.manifestSetDigest,
174
174
  nonce: `${required("GITHUB_RUN_ID")}:${required("GITHUB_RUN_ATTEMPT")}:${sourceSha}`,
175
175
  issuedAt: issuedAt.toISOString(),
176
- expiresAt: new Date(issuedAt.getTime() + 10 * 60 * 1000).toISOString(),
176
+ expiresAt: new Date(issuedAt.getTime() + 15 * 60 * 1000).toISOString(),
177
177
  qualification: {
178
178
  required: qualificationRequired,
179
179
  predicateId: process.env.BUILDCHAIN_CONSUMER_PREDICATE_ID || "",
@@ -453,7 +453,7 @@ function main() {
453
453
  const reviewRules = (environmentState.protection_rules || []).filter((rule) => rule.type === "required_reviewers");
454
454
  const runsOn = (block.match(/^\s{4}runs-on:\s*([^\n#]+)/m)?.[1] || "").trim().replace(/["']/g, "");
455
455
  const observedAt = new Date();
456
- const expiresAt = new Date(observedAt.getTime() + 10 * 60 * 1000);
456
+ const expiresAt = new Date(observedAt.getTime() + 15 * 60 * 1000);
457
457
  const receipt = evaluatePublicationControlPlaneSnapshot({
458
458
  repository,
459
459
  workflowPath,
@@ -4,11 +4,10 @@
4
4
  import crypto from "node:crypto";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
- import { readRendererManifest } from "./auditable-demo-renditions.mjs";
7
+ import { MAX_LONG_FORM_RENDERER_MANIFEST_BYTES, readRendererManifest } from "./auditable-demo-renditions.mjs";
8
8
 
9
9
  const DIGEST = /^sha256:[0-9a-f]{64}$/u;
10
10
  const MAX_METADATA_MEMBER_BYTES = 8 * 1024 * 1024;
11
- const MAX_LONG_FORM_MANIFEST_BYTES = 32 * 1024 * 1024;
12
11
  const MAX_TERMINAL_CAPTURE_BYTES = 4 * 1024 * 1024;
13
12
  const MAX_TERMINAL_CAPTURE_EVENTS = 10_000;
14
13
  const DIGEST_BUFFER_BYTES = 64 * 1024;
@@ -113,7 +112,7 @@ export function verifyBundleChecksums(root, label, options = {}) {
113
112
  declared.add(match[2]);
114
113
  const metadataMember = match[2].endsWith(".json") || match[2].endsWith(".sha256");
115
114
  const maximum = options.allowLongFormRendererManifest && match[2] === "manifest.json"
116
- ? MAX_LONG_FORM_MANIFEST_BYTES
115
+ ? MAX_LONG_FORM_RENDERER_MANIFEST_BYTES
117
116
  : metadataMember
118
117
  ? MAX_METADATA_MEMBER_BYTES
119
118
  : (options.maximumMemberBytes || MAX_METADATA_MEMBER_BYTES);
@@ -23,10 +23,10 @@ const NON_AUTHORITIES = [
23
23
  ];
24
24
  const RENDITIONS = [
25
25
  { id: "1080p", role: "primary", columns: 150, rows: 36, width: 1920, height: 1080 },
26
- { id: "720p", role: "responsive", columns: 100, rows: 28, width: 1280, height: 720 },
26
+ { id: "720p", role: "responsive", columns: 150, rows: 28, width: 1280, height: 720 },
27
27
  ];
28
28
  const STANDARD_MAX_SECONDS = 60;
29
- const LONG_FORM_MAX_SECONDS = 180;
29
+ const LONG_FORM_MAX_SECONDS = 360;
30
30
  const PRESENTATION_FRAMED = "presentation-framed";
31
31
  const TERMINAL_FILL = "terminal-fill";
32
32
  const MAX_EXECUTABLE_FILES = 32;
@@ -17,7 +17,7 @@ const RENDITION_SET_NON_AUTHORITIES = [
17
17
  ...TERMINAL_CAPTURE_NON_AUTHORITIES,
18
18
  ];
19
19
  const MAX_BUNDLE_MEMBER_BYTES = 8 * 1024 * 1024;
20
- const MAX_LONG_FORM_RENDERER_MANIFEST_BYTES = 32 * 1024 * 1024;
20
+ export const MAX_LONG_FORM_RENDERER_MANIFEST_BYTES = 64 * 1024 * 1024;
21
21
  const PRESENTATION_FRAMED = "presentation-framed";
22
22
  const TERMINAL_FILL = "terminal-fill";
23
23
  const GEOMETRY_TOLERANCE = 0.001;
@@ -7,7 +7,7 @@ import os from "node:os";
7
7
  import path from "node:path";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { fileURLToPath } from "node:url";
10
- import { readRendererManifest, validateRendererCompositionInputs, validateRenditionSet, validateTerminalCapture } from "./auditable-demo-renditions.mjs";
10
+ import { MAX_LONG_FORM_RENDERER_MANIFEST_BYTES, readRendererManifest, validateRendererCompositionInputs, validateRenditionSet, validateTerminalCapture } from "./auditable-demo-renditions.mjs";
11
11
 
12
12
  const UTF8 = new TextDecoder("utf-8", { fatal: true });
13
13
  const IMAGE_PATTERN = /^[a-z0-9][a-z0-9./_-]*@sha256:[0-9a-f]{64}$/;
@@ -141,7 +141,7 @@ function verifyChecksums(root, checksumName = "checksums.sha256", options = {})
141
141
  invariant(!declared.has(member), `duplicate checksum member: ${member}`);
142
142
  declared.add(member);
143
143
  const maximumBytes = options.allowLongFormRendererManifest && member === "manifest.json"
144
- ? 32 * 1024 * 1024
144
+ ? MAX_LONG_FORM_RENDERER_MANIFEST_BYTES
145
145
  : MAX_BUNDLE_MEMBER_BYTES;
146
146
  invariant(
147
147
  sha256(readRegular(target, member, maximumBytes)).slice(7) === match[1],
@@ -1209,9 +1209,14 @@ export function writeGitHubOutputs(outputs) {
1209
1209
  }
1210
1210
  return;
1211
1211
  }
1212
- const lines = Object.entries(outputs).map(
1213
- ([key, value]) => `${key}=${value}`,
1214
- );
1212
+ const lines = Object.entries(outputs).map(([key, value]) => {
1213
+ const text = String(value);
1214
+ if (!/[\r\n]/.test(text)) return `${key}=${text}`;
1215
+ const valueLines = new Set(text.split(/\r?\n/));
1216
+ let delimiter = "BUILDCHAIN_OUTPUT";
1217
+ while (valueLines.has(delimiter)) delimiter += "_";
1218
+ return `${key}<<${delimiter}\n${text}\n${delimiter}`;
1219
+ });
1215
1220
  fs.appendFileSync(outputPath, `${lines.join("\n")}\n`);
1216
1221
  }
1217
1222
 
@@ -2,7 +2,6 @@
2
2
  import { spawnSync } from "node:child_process";
3
3
  import crypto from "node:crypto";
4
4
  import fs from "node:fs";
5
- import os from "node:os";
6
5
  import path from "node:path";
7
6
  import { pathToFileURL } from "node:url";
8
7
  import { createBuildchainLogger } from "../packages/core/logging.js";
@@ -224,16 +223,16 @@ function postjectArgs(binaryPath, blobPath) {
224
223
  return args;
225
224
  }
226
225
 
227
- export function buildStandaloneBinary({
226
+ function buildStandaloneBinaryInTemp({
228
227
  cwd = process.cwd(),
229
228
  outputDir = "dist/binary",
230
229
  name = "buildchain",
231
230
  version = "",
232
231
  packageManagerInstall = false,
233
232
  logPath = undefined,
233
+ tempDir,
234
234
  } = {}) {
235
235
  const resolvedOutputDir = path.resolve(cwd, outputDir);
236
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-sea-"));
237
236
  const triple = platformTriple();
238
237
  const archiveBase = `${name}-${triple}`;
239
238
  const logger = createBuildchainLogger({
@@ -384,6 +383,18 @@ export function buildStandaloneBinary({
384
383
  return manifest;
385
384
  }
386
385
 
386
+ export function buildStandaloneBinary(options = {}) {
387
+ const cwd = path.resolve(options.cwd || process.cwd());
388
+ const tempRoot = path.join(cwd, ".buildchain", "tmp");
389
+ fs.mkdirSync(tempRoot, { recursive: true });
390
+ const tempDir = fs.mkdtempSync(path.join(tempRoot, "sea-"));
391
+ try {
392
+ return buildStandaloneBinaryInTemp({ ...options, cwd, tempDir });
393
+ } finally {
394
+ fs.rmSync(tempDir, { recursive: true, force: true });
395
+ }
396
+ }
397
+
387
398
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
388
399
  try {
389
400
  const result = buildStandaloneBinary({
@@ -2,6 +2,12 @@ export const BUILDCHAIN_USAGE = `Usage:
2
2
  buildchain --help
3
3
  buildchain version
4
4
  buildchain layout [--cwd <dir>] [--json]
5
+ buildchain architecture validate [--cwd <dir>] [--json]
6
+ buildchain architecture list [--cwd <dir>] [--json]
7
+ buildchain architecture show <capability-id> [--cwd <dir>] [--json]
8
+ buildchain architecture qualify --authority-revision <git-revision>
9
+ [--candidate-revision <git-revision>]
10
+ [--cwd <dir>] [--json]
5
11
  buildchain portable-cache plan --manifest <file-or-json> [--output <file>]
6
12
  [--github-output <file>] [--json]
7
13
  buildchain portable-cache receipt --plan <file-or-json> [--matched-key <key>]
@@ -33,6 +39,13 @@ export const BUILDCHAIN_USAGE = `Usage:
33
39
  buildchain release-governance reconcile --repository <owner/repo>
34
40
  --branch <dev|alpha|release/vN/vN.N>
35
41
  --candidate-sha <sha> [--apply] [--json]
42
+ buildchain release-tail plan --declaration <json-or-path> [--output <path>]
43
+ buildchain release-tail init --declaration <json-or-path> [--state <path>]
44
+ buildchain release-tail <status|verify> [--state <path>] [--output <path>]
45
+ buildchain release-tail compat --hooks-json <json-or-path> [--output <path>]
46
+ buildchain release-tail rehearse --capsule <path> --capsule-root <absolute-path>
47
+ --mode <simulate|replay> --state <path>
48
+ --evidence <path> [--environment-json <json-or-path>]
36
49
  buildchain github-governance <plan|apply|rollback|protection-policy-plan|ruleset-policy-plan> ...
37
50
  buildchain release <inspect|recover|finalize|abort> ...
38
51
  buildchain transaction inspect ...
@@ -0,0 +1,347 @@
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
+ const REQUIRED = [
8
+ "owner",
9
+ "writers",
10
+ "schemas",
11
+ "stores",
12
+ "states",
13
+ "events",
14
+ "transitions",
15
+ "recovery",
16
+ "effects",
17
+ "retry",
18
+ "invalidation",
19
+ "providerReadback",
20
+ "publicSurfaces",
21
+ "compatibilityCallers",
22
+ "sourcePaths",
23
+ "testPaths",
24
+ "migrationDisposition",
25
+ "unresolved",
26
+ ];
27
+
28
+ function loadJson(root, file) {
29
+ return JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
30
+ }
31
+
32
+ function registrySurfaceIds(root) {
33
+ const cli = loadJson(root, "dist/site/cli-registry.json").commands.map(
34
+ (entry) => `cli:${entry.id}`,
35
+ );
36
+ const workflows = loadJson(root, "dist/site/workflow-registry.json");
37
+ const workflowIds = workflows.workflows.map(
38
+ (entry) => `workflow:${entry.id}`,
39
+ );
40
+ const actionIds = workflows.actions.map((entry) => `action:${entry.id}`);
41
+ const exports = Object.keys(loadJson(root, "package.json").exports).map(
42
+ (entry) => `export:${entry}`,
43
+ );
44
+ return new Set([...cli, ...workflowIds, ...actionIds, ...exports]);
45
+ }
46
+
47
+ function listFiles(root, relativeRoot) {
48
+ const start = path.join(root, relativeRoot);
49
+ if (!fs.existsSync(start) || !fs.statSync(start).isDirectory()) return [];
50
+ const files = [];
51
+ const visit = (directory, prefix = "") => {
52
+ for (const entry of fs
53
+ .readdirSync(directory, { withFileTypes: true })
54
+ .sort((left, right) => left.name.localeCompare(right.name))) {
55
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
56
+ if (entry.isDirectory())
57
+ visit(path.join(directory, entry.name), relative);
58
+ else if (entry.isFile()) files.push(relative);
59
+ }
60
+ };
61
+ visit(start);
62
+ return files;
63
+ }
64
+
65
+ function validateRequiredDimensions(mechanism, issues) {
66
+ for (const field of REQUIRED) {
67
+ const value = mechanism[field];
68
+ const empty = ["owner", "migrationDisposition"].includes(field)
69
+ ? !String(value || "").trim()
70
+ : !Array.isArray(value) || value.length === 0;
71
+ if (empty) issues.push(`${mechanism.id}: ${field} is empty`);
72
+ }
73
+ }
74
+
75
+ function validateCoordinates(root, mechanism, issues) {
76
+ const writers = (mechanism.writers || []).filter((entry) =>
77
+ /^(?:actions|bin|packages|scripts)\//u.test(entry),
78
+ );
79
+ for (const file of [
80
+ ...(mechanism.sourcePaths || []),
81
+ ...(mechanism.testPaths || []),
82
+ ...writers,
83
+ ]) {
84
+ if (!fs.existsSync(path.join(root, file)))
85
+ issues.push(`${mechanism.id}: coordinate is missing: ${file}`);
86
+ }
87
+ }
88
+
89
+ function validateOwnership(mechanism, ownedSources, issues) {
90
+ for (const file of mechanism.sourcePaths || []) {
91
+ const prior = ownedSources.get(file);
92
+ if (prior)
93
+ issues.push(
94
+ `${file}: ambiguous mechanism ownership (${prior}, ${mechanism.id})`,
95
+ );
96
+ ownedSources.set(file, mechanism.id);
97
+ }
98
+ }
99
+
100
+ function validateSurfaces(mechanism, surfaces, issues) {
101
+ for (const surface of mechanism.publicSurfaces || []) {
102
+ if (!surfaces.has(surface))
103
+ issues.push(
104
+ `${mechanism.id}: public surface is absent from generated/package registries: ${surface}`,
105
+ );
106
+ }
107
+ }
108
+
109
+ function validateGitRefStore(root, mechanism, issues) {
110
+ const source = (mechanism.sourcePaths || [])
111
+ .map((file) => fs.readFileSync(path.join(root, file), "utf8"))
112
+ .join("\n");
113
+ const writesReleaseState = /refs\/heads\/buildchain\/release-state/u.test(
114
+ source,
115
+ );
116
+ const declaresGitRef = (mechanism.stores || []).some((entry) =>
117
+ /Git ref|refs\//u.test(entry),
118
+ );
119
+ if (writesReleaseState && !declaresGitRef)
120
+ issues.push(
121
+ `${mechanism.id}: Git-ref writer lacks an explicit store disposition`,
122
+ );
123
+ }
124
+
125
+ function validateReverseScanReferences(root, reverseScan, issues) {
126
+ const references = [
127
+ ...(reverseScan.registries || []),
128
+ ...(reverseScan.generatedReferences || []),
129
+ reverseScan.commandRegistry,
130
+ String(reverseScan.packageExports || "").split("#")[0],
131
+ ].filter(Boolean);
132
+ for (const file of references) {
133
+ if (!fs.existsSync(path.join(root, file)))
134
+ issues.push(`reverse scan reference is missing: ${file}`);
135
+ }
136
+ }
137
+
138
+ function validateSurfaceKinds(inventory, issues) {
139
+ const surfaceKinds = new Set(
140
+ (inventory.mechanisms || []).flatMap((mechanism) =>
141
+ (mechanism.publicSurfaces || []).map((surface) => surface.split(":")[0]),
142
+ ),
143
+ );
144
+ for (const kind of ["action", "cli", "export", "workflow"]) {
145
+ if (!surfaceKinds.has(kind))
146
+ issues.push(`reverse scan has no ${kind} public surface coverage`);
147
+ }
148
+ return surfaceKinds;
149
+ }
150
+
151
+ function discoverAuthoritySources(root, reverseScan, ids, issues) {
152
+ const discovered = new Map();
153
+ for (const [index, rule] of (
154
+ reverseScan.authoritySourceRules || []
155
+ ).entries()) {
156
+ if (!ids.has(rule?.mechanismId)) {
157
+ issues.push(
158
+ `authority source rule ${index} names an unknown mechanism: ${rule?.mechanismId || "<empty>"}`,
159
+ );
160
+ continue;
161
+ }
162
+ if (!rule.root || !rule.pattern) {
163
+ issues.push(`authority source rule ${index} is incomplete`);
164
+ continue;
165
+ }
166
+ let pattern;
167
+ try {
168
+ pattern = new RegExp(rule.pattern, "u");
169
+ } catch (error) {
170
+ issues.push(
171
+ `authority source rule ${index} has an invalid pattern: ${error.message}`,
172
+ );
173
+ continue;
174
+ }
175
+ const matches = listFiles(root, rule.root).filter((file) =>
176
+ pattern.test(file),
177
+ );
178
+ if (matches.length === 0)
179
+ issues.push(
180
+ `${rule.mechanismId}: authority source rule matched no files under ${rule.root}`,
181
+ );
182
+ for (const relative of matches) {
183
+ const file = path.posix.join(rule.root, relative);
184
+ const mechanismIds = discovered.get(file) || new Set();
185
+ mechanismIds.add(rule.mechanismId);
186
+ discovered.set(file, mechanismIds);
187
+ }
188
+ }
189
+ return discovered;
190
+ }
191
+
192
+ function validateAuthorityOwnership(discovered, ownedSources, issues) {
193
+ for (const [file, mechanismIds] of discovered) {
194
+ if (mechanismIds.size !== 1) {
195
+ issues.push(
196
+ `${file}: ambiguous reverse-scan ownership (${[...mechanismIds].join(", ")})`,
197
+ );
198
+ continue;
199
+ }
200
+ const expected = [...mechanismIds][0];
201
+ const owner = ownedSources.get(file);
202
+ if (!owner)
203
+ issues.push(`${file}: orphan authority coordinate for ${expected}`);
204
+ else if (owner !== expected)
205
+ issues.push(
206
+ `${file}: authority coordinate is owned by ${owner}, expected ${expected}`,
207
+ );
208
+ }
209
+ }
210
+
211
+ function validateGitRefStoreScan(
212
+ root,
213
+ inventory,
214
+ reverseScan,
215
+ ownedSources,
216
+ issues,
217
+ ) {
218
+ let gitRefStores = 0;
219
+ for (const [index, store] of (reverseScan.gitRefStores || []).entries()) {
220
+ const mechanism = (inventory.mechanisms || []).find(
221
+ (entry) => entry.id === store?.mechanismId,
222
+ );
223
+ if (!mechanism || !store.sourcePath || !store.marker) {
224
+ issues.push(`Git-ref store scan ${index} is incomplete`);
225
+ continue;
226
+ }
227
+ const file = path.join(root, store.sourcePath);
228
+ if (!fs.existsSync(file)) {
229
+ issues.push(
230
+ `${store.mechanismId}: Git-ref store source is missing: ${store.sourcePath}`,
231
+ );
232
+ continue;
233
+ }
234
+ if (ownedSources.get(store.sourcePath) !== store.mechanismId)
235
+ issues.push(
236
+ `${store.sourcePath}: Git-ref store source is not owned by ${store.mechanismId}`,
237
+ );
238
+ if (!fs.readFileSync(file, "utf8").includes(store.marker))
239
+ issues.push(
240
+ `${store.sourcePath}: Git-ref store marker is missing: ${store.marker}`,
241
+ );
242
+ if (
243
+ !(mechanism.stores || []).some((entry) =>
244
+ /Git ref|refs\/|ledger-ref|state ref/iu.test(entry),
245
+ )
246
+ )
247
+ issues.push(
248
+ `${store.mechanismId}: Git-ref store lacks an explicit disposition`,
249
+ );
250
+ gitRefStores += 1;
251
+ }
252
+ return gitRefStores;
253
+ }
254
+
255
+ function validateReverseScan(root, inventory, ids, ownedSources, issues) {
256
+ const reverseScan = inventory.reverseScan || {};
257
+ validateReverseScanReferences(root, reverseScan, issues);
258
+ const surfaceKinds = validateSurfaceKinds(inventory, issues);
259
+ const discovered = discoverAuthoritySources(root, reverseScan, ids, issues);
260
+ validateAuthorityOwnership(discovered, ownedSources, issues);
261
+ const gitRefStores = validateGitRefStoreScan(
262
+ root,
263
+ inventory,
264
+ reverseScan,
265
+ ownedSources,
266
+ issues,
267
+ );
268
+ return {
269
+ authorityCoordinates: discovered.size,
270
+ gitRefStores,
271
+ surfaceKinds: [...surfaceKinds].sort(),
272
+ };
273
+ }
274
+
275
+ function checkCoreMechanismInventory({
276
+ root = process.cwd(),
277
+ inventory = loadJson(root, "architecture/v3-core-mechanism-inventory.json"),
278
+ } = {}) {
279
+ const issues = [];
280
+ if (
281
+ inventory.schemaVersion !== 1 ||
282
+ inventory.contract !== "kungfu-buildchain-v3-core-mechanism-inventory"
283
+ )
284
+ issues.push("inventory contract or schemaVersion is invalid");
285
+ if (
286
+ !/^[0-9a-f]{40}$/u.test(inventory.baseline?.commit || "") ||
287
+ !/^[0-9a-f]{40}$/u.test(inventory.baseline?.tree || "")
288
+ )
289
+ issues.push("baseline must bind an exact commit and tree");
290
+ if (inventory.maintainability?.dependencyCycles !== 0)
291
+ issues.push("baseline must report the exact zero-cycle result");
292
+ const surfaces = registrySurfaceIds(root);
293
+ const ids = new Set();
294
+ const ownedSources = new Map();
295
+ for (const mechanism of inventory.mechanisms || []) {
296
+ if (!mechanism.id || ids.has(mechanism.id))
297
+ issues.push(
298
+ `mechanism id is missing or duplicated: ${mechanism.id || "<empty>"}`,
299
+ );
300
+ ids.add(mechanism.id);
301
+ validateRequiredDimensions(mechanism, issues);
302
+ validateCoordinates(root, mechanism, issues);
303
+ validateOwnership(mechanism, ownedSources, issues);
304
+ validateSurfaces(mechanism, surfaces, issues);
305
+ validateGitRefStore(root, mechanism, issues);
306
+ }
307
+ const reverseScan = validateReverseScan(
308
+ root,
309
+ inventory,
310
+ ids,
311
+ ownedSources,
312
+ issues,
313
+ );
314
+ if ((inventory.mechanisms || []).length < 10)
315
+ issues.push("inventory must retain all ten v3 core mechanism families");
316
+ if (issues.length)
317
+ throw new Error(
318
+ `v3 core mechanism inventory check failed:\n- ${issues.join("\n- ")}`,
319
+ );
320
+ return {
321
+ mechanisms: inventory.mechanisms.length,
322
+ sourceCoordinates: ownedSources.size,
323
+ publicSurfaces: inventory.mechanisms.reduce(
324
+ (sum, entry) => sum + entry.publicSurfaces.length,
325
+ 0,
326
+ ),
327
+ dependencyCycles: inventory.maintainability.dependencyCycles,
328
+ ...reverseScan,
329
+ };
330
+ }
331
+
332
+ if (
333
+ process.argv[1] &&
334
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
335
+ ) {
336
+ try {
337
+ const report = checkCoreMechanismInventory();
338
+ console.log(
339
+ `v3 core mechanism inventory check passed: ${report.mechanisms} mechanisms, ${report.sourceCoordinates} owned source coordinates, ${report.authorityCoordinates} reverse-discovered authority coordinates, ${report.publicSurfaces} public surfaces across ${report.surfaceKinds.length} registry kinds, ${report.gitRefStores} Git-ref stores, ${report.dependencyCycles} dependency cycles`,
340
+ );
341
+ } catch (error) {
342
+ console.error(error instanceof Error ? error.message : String(error));
343
+ process.exitCode = 1;
344
+ }
345
+ }
346
+
347
+ export { checkCoreMechanismInventory };
@@ -283,7 +283,7 @@ for (const requiredSnippet of [
283
283
  "authorize-promotion-runtime-override.cjs",
284
284
  "BUILDCHAIN_ROUTER_REPOSITORY: ${{ inputs.buildchain-repository }}",
285
285
  "BUILDCHAIN_RESUME_RUNTIME_SHA: ${{ inputs.resume-buildchain-runtime-sha }}",
286
- "git ls-remote",
286
+ "if [[ \"${ref}\" =~ ^[0-9A-Fa-f]{40}$ ]]; then", "sha=\"${ref,,}\"", "git ls-remote",
287
287
  "Recovery router ref does not match resume-buildchain-runtime-sha",
288
288
  ]) {
289
289
  if (!channelPromotionWorkflow.includes(requiredSnippet)) {
@@ -730,8 +730,8 @@ const registeredActionIds = (workflowRegistry.actions || []).map((entry) => entr
730
730
  const readmeActionIndex = fs.readFileSync(path.join(root, "README.md"), "utf8");
731
731
  const mapActionIndex = fs.readFileSync(path.join(root, "docs/MAP.md"), "utf8");
732
732
  const retrospectiveActionIndex = fs.readFileSync(path.join(root, ".github/retrospectives/2026-07-10-buildchain-consolidation.md"), "utf8");
733
- if (registeredActionIds.length !== 6) {
734
- throw new Error(`workflow-registry.json must expose the six current action entries, got ${registeredActionIds.length}`);
733
+ if (registeredActionIds.length !== 7) {
734
+ throw new Error(`workflow-registry.json must expose the seven current action entries, got ${registeredActionIds.length}`);
735
735
  }
736
736
  for (const actionId of registeredActionIds) {
737
737
  if (!readmeActionIndex.includes(`actions/${actionId}`) || !mapActionIndex.includes(`actions/${actionId}`)) {
@@ -992,7 +992,7 @@ for (const forbiddenSnippet of [
992
992
  for (const requiredSnippet of [
993
993
  "id-token: write",
994
994
  "actions: write",
995
- "uses: ./.github/workflows/.release-candidate-promote.yml",
995
+ "uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@769b221bad7a6b9104afad4c2628d9dca396ab0f\n",
996
996
  "github.event.workflow_run.event == 'push'",
997
997
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): prepare v')",
998
998
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): release v')",
@@ -1038,9 +1038,11 @@ for (const requiredSnippet of [
1038
1038
  "github-release-notes: ${{ inputs.github-release-notes }}",
1039
1039
  "publication-commit-command:",
1040
1040
  "BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY:",
1041
+ "BUILDCHAIN_PUBLICATION_COMMIT_CANDIDATE_SOURCE_SHA: ${{ steps.rc.outputs.release-candidate-source-sha || needs.preflight.outputs.requested-sha }}",
1041
1042
  "KUNGFU_GOVERNANCE_AUDITOR_APP_PRIVATE_KEY:",
1042
1043
  "Commit consumer publication authority last",
1043
1044
  "node .buildchain/runtime/scripts/publication-commit-evidence.mjs",
1045
+ '--candidate-source-sha "${BUILDCHAIN_PUBLICATION_COMMIT_CANDIDATE_SOURCE_SHA}"',
1044
1046
  "require-publish-source-lock: \"true\"",
1045
1047
  "publish-source-ref: ${{ steps.publish-gate.outputs.ref }}",
1046
1048
  "publish-source-sha: ${{ steps.publish-gate.outputs.sha }}",
@@ -1114,7 +1116,7 @@ for (const retiredWorkflow of [
1114
1116
  }
1115
1117
  }
1116
1118
  const promoteBuildchainRefAction = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/action.yml"), "utf8");
1117
- const promoteBuildchainRefIndex = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/index.js"), "utf8");
1119
+ const promoteBuildchainRefIndex = ["actions/promote-buildchain-ref/index.js", "actions/promote-buildchain-ref/github-release.js"].map((entry) => fs.readFileSync(path.join(root, entry), "utf8")).join("\n");
1118
1120
  for (const requiredSnippet of [
1119
1121
  "github-release:",
1120
1122
  "github-release-title:",
@@ -1140,8 +1142,8 @@ for (const requiredSnippet of [
1140
1142
  }
1141
1143
  }
1142
1144
  for (const forbiddenSnippet of [
1143
- "run: node scripts/release-candidate-resolver.mjs",
1144
- "uses: ./actions/promote-buildchain-ref",
1145
+ "uses: ./.github/workflows/.release-candidate-promote.yml",
1146
+ "uses: kungfu-systems/buildchain/.github/workflows/.release-candidate-promote.yml@",
1145
1147
  ]) {
1146
1148
  if (buildchainRefPromotionWorkflow.includes(forbiddenSnippet)) {
1147
1149
  throw new Error(`buildchain ref promotion workflow must use the declarative wrapper, found manual snippet: ${forbiddenSnippet}`);
@@ -1284,7 +1286,7 @@ if (!badgeEndpointRegistry.badges?.some((entry) => entry.id === "buildchain-rele
1284
1286
  throw new Error("badge endpoint registry must include Buildchain Release Passport badge");
1285
1287
  }
1286
1288
 
1287
- for (const siteFile of ["buildchain-site.json", "site-manifest.json", "badge-endpoint-registry.json", "publication-registry.json", "page-registry.json", "capability-registry.json", "cli-registry.json", "manual-registry.json", "node-api-registry.json", "workflow-registry.json", "controller-registry.json", "public-surface-audit.json", "release-model.json", "release-passport-check-manifest.json", "schemas/release-passport-v1.schema.json", "buildchain-contract.json"]) {
1289
+ for (const siteFile of ["buildchain-site.json", "site-manifest.json", "badge-endpoint-registry.json", "publication-registry.json", "page-registry.json", "capability-registry.json", "cli-registry.json", "manual-registry.json", "node-api-registry.json", "workflow-registry.json", "controller-registry.json", "public-surface-audit.json", "release-model.json", "release-passport-check-manifest.json", "schemas/release-passport-v1.schema.json", "schemas/publication-rehearsal-capsule-v1.schema.json", "schemas/release-tail-capabilities-v1.schema.json", "schemas/release-tail-provider-bindings-v1.schema.json", "buildchain-contract.json"]) {
1288
1290
  if (!fs.existsSync(path.join(root, "dist", "site", siteFile))) {
1289
1291
  throw new Error(`site bundle missing ${siteFile}`);
1290
1292
  }
@@ -282,9 +282,16 @@ function evaluateMaintainability({ current, baselineFiles, policy }) {
282
282
  for (const [file, metrics] of Object.entries(current.files)) {
283
283
  const baseline = baselineFiles[file];
284
284
  if (!baseline) {
285
- if (metrics.lines > budgets.newFileLines) {
285
+ const transition = policy.approvedNewFileTransitions?.[file];
286
+ if (transition && !String(transition.rationale || "").trim()) {
286
287
  issues.push(
287
- `${file}: new file has ${metrics.lines} lines; budget is ${budgets.newFileLines}`,
288
+ `${file}: approved new-file transition requires a rationale`,
289
+ );
290
+ }
291
+ const allowedFileLines = transition?.maxLines ?? budgets.newFileLines;
292
+ if (metrics.lines > allowedFileLines) {
293
+ issues.push(
294
+ `${file}: new file has ${metrics.lines} lines; budget is ${allowedFileLines}`,
288
295
  );
289
296
  }
290
297
  for (const entry of metrics.functions) {