@kungfu-tech/buildchain 4.0.1-alpha.9 → 4.0.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 (98) hide show
  1. package/README.md +9 -6
  2. package/actions/v4-release-candidate-promote/README.md +3 -0
  3. package/architecture/agent-change-map.md +1015 -0
  4. package/architecture/ci-lane-change-budget.json +70 -1
  5. package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
  6. package/architecture/internal-capabilities.json +156 -11
  7. package/architecture/maintainability-debt.json +3229 -0
  8. package/architecture/maintainability-policy.json +480 -129
  9. package/architecture/v3-core-mechanism-inventory.json +4 -3
  10. package/architecture/v3-v4-capability-closure.md +59 -0
  11. package/architecture/v3-v4-live-capability-inventory.json +102404 -0
  12. package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
  14. package/architecture/v4-runtime-semantic-closure.json +710 -0
  15. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  16. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  17. package/contracts/v4-publication-qualification-v1.schema.json +51 -0
  18. package/dist/site/agent-index.json +1 -0
  19. package/dist/site/artifact-schemas.json +2 -0
  20. package/dist/site/buildchain-contract.json +123 -32
  21. package/dist/site/buildchain-site.json +96 -28
  22. package/dist/site/capability-registry.json +6 -6
  23. package/dist/site/cli-registry.json +5 -2
  24. package/dist/site/controller-registry.json +41 -5
  25. package/dist/site/kfd-claims.json +170 -21
  26. package/dist/site/kfd-upstream-aggregate.json +1 -1
  27. package/dist/site/manual-registry.json +6 -6
  28. package/dist/site/node-api-registry.json +1474 -215
  29. package/dist/site/page-registry.json +85 -17
  30. package/dist/site/public-surface-audit.json +85 -20
  31. package/dist/site/publication-authority-registry.json +28 -1
  32. package/dist/site/publication-registry.json +4 -4
  33. package/dist/site/release-provenance.json +5 -0
  34. package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
  35. package/dist/site/site-manifest.json +10 -10
  36. package/dist/site/workflow-registry.json +94 -18
  37. package/docs/MAP.md +1 -1
  38. package/docs/cli-reference.md +4 -4
  39. package/docs/dev-delivery-warrant.md +12 -9
  40. package/docs/node-api-reference.md +291 -206
  41. package/docs/publish-transaction.md +11 -4
  42. package/docs/release-candidate.md +7 -0
  43. package/docs/v4-adopter-delivery.md +23 -4
  44. package/docs/v4-declarative-release-tail-migration.md +43 -0
  45. package/package.json +8 -3
  46. package/packages/core/adopter-delivery-passport.js +36 -0
  47. package/packages/core/buildchain-contract.js +31 -1
  48. package/packages/core/buildchain-publication-authority.js +1 -0
  49. package/packages/core/dev-delivery-warrant-settlement.js +239 -3
  50. package/packages/core/dev-delivery-warrant-state.js +15 -2
  51. package/packages/core/dev-delivery-warrant.js +21 -4
  52. package/packages/core/github-governance-authority.js +4 -4
  53. package/packages/core/index.js +1 -0
  54. package/packages/core/paper-scaffold-content.js +401 -0
  55. package/packages/core/paper.js +12 -378
  56. package/packages/core/publication-control-plane-audit.js +135 -111
  57. package/packages/core/publication-rehearsal-projection.js +104 -0
  58. package/packages/core/publication-rehearsal-runtime.js +190 -0
  59. package/packages/core/release-candidate-recovery.js +6 -5
  60. package/packages/core/release-passport.js +82 -184
  61. package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
  62. package/packages/core/v4-floating-consumer-policy.js +3 -2
  63. package/packages/core/v4-floating-consumer-release-passport.js +134 -15
  64. package/packages/core/v4-publication-qualification.js +222 -0
  65. package/scripts/artifact-signing-controller-core.mjs +1 -1
  66. package/scripts/audit-publication-control-plane.mjs +17 -17
  67. package/scripts/buildchain-cli-help.mjs +3 -0
  68. package/scripts/check-internal-architecture.mjs +23 -3
  69. package/scripts/check-inventory.mjs +23 -5
  70. package/scripts/check-maintainability.mjs +145 -4
  71. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  72. package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
  73. package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -3
  74. package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
  75. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  76. package/scripts/dev-delivery-warrant.mjs +45 -5
  77. package/scripts/dev-pr-auto-merge.mjs +25 -11
  78. package/scripts/dev-pr-delivery-warrant.mjs +44 -0
  79. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  80. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  81. package/scripts/generate-agent-change-map.mjs +118 -0
  82. package/scripts/generate-release-candidate-passport.mjs +26 -1
  83. package/scripts/generate-site-bundle.mjs +6 -0
  84. package/scripts/maintainability-domain-metrics.mjs +237 -0
  85. package/scripts/maintainability-governance.mjs +473 -0
  86. package/scripts/maintainability-metrics.mjs +28 -6
  87. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  88. package/scripts/promotion-routing-evidence.mjs +35 -6
  89. package/scripts/release-candidate-resolver.mjs +44 -0
  90. package/scripts/release-line-policy.mjs +25 -15
  91. package/scripts/release-tail.mjs +19 -2
  92. package/scripts/resume-from-candidate-run.mjs +106 -103
  93. package/scripts/run-lifecycle-core.mjs +341 -376
  94. package/scripts/site-capability-metadata.mjs +4 -0
  95. package/scripts/stable-candidate-qualification.mjs +9 -10
  96. package/scripts/v3-v4-capability-catalog.mjs +439 -0
  97. package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
  98. package/scripts/v4-declarative-promotion-admission.mjs +42 -0
@@ -230,6 +230,9 @@ export function nodeApiMeta(exportName) {
230
230
  "./v4-tail-reseal": { group: "release-passport-trust", summary: "Effect-pure retained Stage Capsule validation and explicit Warrant-, credential-, and release-tail-fenced macOS reseal planning APIs." },
231
231
  "./v4-tail-reseal-receipt": { group: "release-passport-trust", summary: "Four-platform byte readback, standard candidate Passport binding, and terminal v4 tail-reseal receipt APIs." },
232
232
  "./v4-publication-rehearsal": { group: "release-passport-trust", summary: "Content-addressed v4 publication rehearsal capsules, shared production release-tail execution, replay, provider observation, and fail-closed verification APIs." },
233
+ "./publication-rehearsal-runtime": { group: "release-passport-trust", summary: "Compatibility names for the source-bound v4 publication rehearsal runtime and explicit rooted migration diagnostics." },
234
+ "./publication-rehearsal-projection": { group: "release-passport-trust", summary: "Compatibility projection helpers that emit only the current source-bound v4 publication rehearsal configuration and workflow." },
235
+ "./v4-publication-qualification": { group: "release-passport-trust", summary: "Sealed v4 candidate, source, artifact, policy, freshness, and command-free promotion admission APIs." },
233
236
  "./adopter-delivery-gate": { group: "reusable-build", summary: "Protocol-neutral adopter delivery gate, driver interface, artifact profiles, and canonical result APIs." },
234
237
  "./kfd-adopter-category-driver": { group: "kfd-trust", summary: "Published KFD adopter-category driver and exact package-cut verification APIs." },
235
238
  "./buildchain-delivery-infrastructure": { group: "reusable-build", summary: "Public delivery-infrastructure instance creation and KFD verification APIs." },
@@ -240,6 +243,7 @@ export function nodeApiMeta(exportName) {
240
243
  "./adopter-delivery-passport": { group: "release-passport-trust", summary: "Non-authoritative adopter delivery Release Passport binding and substitution checks." },
241
244
  "./adopter-delivery-vectors": { group: "reusable-build", summary: "Rooted public adopter delivery conformance vector suite APIs." },
242
245
  "./v4-adopter-delivery": { group: "reusable-build", summary: "Public v4 adopter delivery runtime, exact readback, N-1 lineage, and published archive verification APIs." },
246
+ "./v4-cross-platform-adopter-qualification": { group: "reusable-build", summary: "Exact-source v3 capability matrix reports, executed failure and recovery evidence, and cross-platform public adopter qualification APIs." },
243
247
  "./release-candidate-recovery": { group: "release-passport-trust", summary: "Fail-closed verification and immutable receipts for reusing a successful sealed candidate run without rebuilding product payloads." },
244
248
  "./release-train": { group: "governance-versioning", summary: "Frozen Release Cut identity, rooted Release Train transitions, Dev observations, supersession, replay, and legacy read compatibility APIs." },
245
249
  "./next-development-transition": { group: "governance-versioning", summary: "Provider-neutral completed-Alpha preservation and rooted next-development state, version-model, idempotency, and local materialization APIs." },
@@ -146,9 +146,7 @@ export async function runStableCandidateQualification(optionsInput = {}, clientI
146
146
  const options = normalizeStableCandidateQualificationOptions(optionsInput);
147
147
  const client = clientInput || createGitHubQualificationClient({ token: process.env.GITHUB_TOKEN });
148
148
  const candidate = await client.resolveExactAlpha(options.repository, options.candidateSha);
149
- if (!candidate) {
150
- return { schemaVersion: 1, contract: "kungfu-buildchain-stable-candidate-qualification", status: "skipped", reason: "exact-alpha-release-not-found", candidateSha: options.candidateSha };
151
- }
149
+ if (!candidate) return { schemaVersion: 1, contract: "kungfu-buildchain-stable-candidate-qualification", status: "skipped", reason: "exact-alpha-release-not-found", candidateSha: options.candidateSha };
152
150
 
153
151
  const build = await ensureWorkflowEvidence({
154
152
  client,
@@ -156,12 +154,12 @@ export async function runStableCandidateQualification(optionsInput = {}, clientI
156
154
  workflowFile: options.buildWorkflowFile,
157
155
  workflowName: options.buildWorkflowName,
158
156
  ref: candidate.tag,
159
- headSha: options.candidateSha,
157
+ headSha: candidate.sha,
160
158
  runName: "",
161
159
  options,
162
160
  });
163
161
 
164
- let status = await client.findCommitStatus(options.repository, options.candidateSha, options.canaryStatusContext);
162
+ let status = await client.findCommitStatus(options.repository, candidate.sha, options.canaryStatusContext);
165
163
  let canary = { state: status?.state === "success" ? "existing" : "pending", run: undefined };
166
164
  if (status?.state !== "success") {
167
165
  const canaryRef = options.canaryRef || await client.defaultBranch(options.canaryRepository);
@@ -177,15 +175,15 @@ export async function runStableCandidateQualification(optionsInput = {}, clientI
177
175
  workflowFile: options.canaryWorkflowFile,
178
176
  workflowName: options.canaryWorkflowName,
179
177
  ref: canaryRef,
180
- headSha: options.candidateSha,
181
- runName: `${options.canaryWorkflowName} / ${options.candidateSha}`,
178
+ headSha: candidate.sha,
179
+ runName: `${options.canaryWorkflowName} / ${candidate.sha}`,
182
180
  sourceSha: options.canarySha,
183
181
  options,
184
182
  });
185
183
  if (!options.dryRun) {
186
184
  status = await client.createCommitStatus({
187
185
  repository: options.repository,
188
- sha: options.candidateSha,
186
+ sha: candidate.sha,
189
187
  context: options.canaryStatusContext,
190
188
  targetUrl: canary.run.html_url,
191
189
  description: `No-apply ${options.canaryRepository} canary passed`,
@@ -256,11 +254,12 @@ export function createGitHubQualificationClient({
256
254
  }
257
255
  return {
258
256
  async resolveExactAlpha(repositoryName, candidateSha) {
259
- const releases = await api(`/repos/${repositoryName}/releases?per_page=100`);
257
+ const releases = (await api(`/repos/${repositoryName}/releases?per_page=100`)).sort((left, right) => right.tag_name.localeCompare(left.tag_name, "en", { numeric: true }));
260
258
  for (const release of releases) {
261
259
  if (!release.prerelease || !/^v\d+\.\d+\.\d+-alpha\.\d+$/.test(release.tag_name || "")) continue;
262
260
  const sha = await resolveTagSha(repositoryName, release.tag_name);
263
- if (sha === candidateSha) return { version: release.tag_name.slice(1), tag: release.tag_name, sha, releaseUrl: release.html_url };
261
+ if (sha !== candidateSha && !["ahead", "identical"].includes((await api(`/repos/${repositoryName}/compare/${sha}...${candidateSha}`)).status)) continue;
262
+ return { version: release.tag_name.slice(1), tag: release.tag_name, sha, releaseUrl: release.html_url };
264
263
  }
265
264
  return undefined;
266
265
  },
@@ -0,0 +1,439 @@
1
+ import crypto from "node:crypto";
2
+ import { execFileSync } from "node:child_process";
3
+
4
+ const FIXED_GENERATED_CONTRACTS = Object.freeze([
5
+ "dist/site/buildchain-contract.json",
6
+ "dist/site/capability-registry.json",
7
+ "dist/site/cli-registry.json",
8
+ "dist/site/controller-registry.json",
9
+ "dist/site/kfd-claims.json",
10
+ "dist/site/manual-registry.json",
11
+ "dist/site/node-api-registry.json",
12
+ "dist/site/page-registry.json",
13
+ "dist/site/public-surface-audit.json",
14
+ "dist/site/release-model.json",
15
+ "dist/site/release-passport-check-manifest.json",
16
+ "dist/site/site-manifest.json",
17
+ "dist/site/workflow-registry.json",
18
+ ]);
19
+
20
+ const PLATFORM_MARKERS = Object.freeze({
21
+ linux: /\b(?:ubuntu|linux)\b/iu,
22
+ macos: /\bmacos\b/iu,
23
+ "self-hosted": /\bself-hosted\b/iu,
24
+ windows: /\bwindows\b/iu,
25
+ });
26
+
27
+ export function sha256(value) {
28
+ return `sha256:${crypto.createHash("sha256").update(value).digest("hex")}`;
29
+ }
30
+
31
+ function stableJson(value) {
32
+ if (Array.isArray(value)) return value.map(stableJson);
33
+ if (!value || typeof value !== "object") return value;
34
+ return Object.fromEntries(
35
+ Object.entries(value)
36
+ .sort(([left], [right]) => left.localeCompare(right))
37
+ .map(([key, entry]) => [key, stableJson(entry)]),
38
+ );
39
+ }
40
+
41
+ export function git(root, args, { trim = true } = {}) {
42
+ const output = execFileSync("git", args, {
43
+ cwd: root,
44
+ encoding: "utf8",
45
+ maxBuffer: 64 * 1024 * 1024,
46
+ });
47
+ return trim ? output.trim() : output;
48
+ }
49
+
50
+ function gitJson(root, revision, relPath) {
51
+ return JSON.parse(
52
+ git(root, ["show", `${revision}:${relPath}`], { trim: false }),
53
+ );
54
+ }
55
+
56
+ function gitText(root, revision, relPath) {
57
+ return git(root, ["show", `${revision}:${relPath}`], { trim: false });
58
+ }
59
+
60
+ function gitPaths(root, revision) {
61
+ return git(root, ["ls-tree", "-r", "--name-only", revision])
62
+ .split("\n")
63
+ .filter(Boolean)
64
+ .sort();
65
+ }
66
+
67
+ function catalogEntry({ category, identity, path: relPath, selector, value }) {
68
+ return {
69
+ id: `${category}:${identity}`,
70
+ category,
71
+ identity,
72
+ evidence: {
73
+ path: relPath,
74
+ selector,
75
+ valueRoot: sha256(JSON.stringify(stableJson(value))),
76
+ },
77
+ };
78
+ }
79
+
80
+ function add(catalog, entry) {
81
+ if (catalog.has(entry.id))
82
+ throw new Error(`duplicate capability identity: ${entry.id}`);
83
+ catalog.set(entry.id, entry);
84
+ }
85
+
86
+ function values(entries) {
87
+ return (entries || [])
88
+ .map((entry) => (typeof entry === "string" ? entry : entry?.name))
89
+ .filter(Boolean);
90
+ }
91
+
92
+ function addNodeCatalog(catalog, packageJson, nodeRegistry) {
93
+ for (const [exportName, target] of Object.entries(
94
+ packageJson.exports || {},
95
+ )) {
96
+ add(
97
+ catalog,
98
+ catalogEntry({
99
+ category: "node-export",
100
+ identity: exportName,
101
+ path: "package.json",
102
+ selector: `exports[${JSON.stringify(exportName)}]`,
103
+ value: target,
104
+ }),
105
+ );
106
+ }
107
+ const symbolNames = new Map(
108
+ (nodeRegistry.symbols || []).map((symbol) => [symbol.id, symbol.name]),
109
+ );
110
+ for (const entry of nodeRegistry.exports || []) {
111
+ add(
112
+ catalog,
113
+ catalogEntry({
114
+ category: "documented-module",
115
+ identity: entry.export,
116
+ path: "dist/site/node-api-registry.json",
117
+ selector: `exports[export=${JSON.stringify(entry.export)}]`,
118
+ value: {
119
+ target: entry.target,
120
+ summary: entry.summary,
121
+ digest: entry.digest,
122
+ },
123
+ }),
124
+ );
125
+ for (const symbolId of entry.symbolIds || []) {
126
+ const name = symbolNames.get(symbolId);
127
+ if (!name)
128
+ throw new Error(
129
+ `node registry export ${entry.export} references unknown symbol ${symbolId}`,
130
+ );
131
+ add(
132
+ catalog,
133
+ catalogEntry({
134
+ category: "node-symbol",
135
+ identity: `${entry.export}#${name}`,
136
+ path: "dist/site/node-api-registry.json",
137
+ selector: `exports[export=${JSON.stringify(entry.export)}].symbolIds[name=${JSON.stringify(name)}]`,
138
+ value: name,
139
+ }),
140
+ );
141
+ }
142
+ }
143
+ }
144
+
145
+ function addCliCatalog(catalog, cliRegistry) {
146
+ for (const command of cliRegistry.commands || []) {
147
+ add(
148
+ catalog,
149
+ catalogEntry({
150
+ category: "cli-command",
151
+ identity: command.id,
152
+ path: "dist/site/cli-registry.json",
153
+ selector: `commands[id=${JSON.stringify(command.id)}]`,
154
+ value: { paths: command.paths, usage: command.usage },
155
+ }),
156
+ );
157
+ for (const option of command.options || []) {
158
+ add(
159
+ catalog,
160
+ catalogEntry({
161
+ category: "cli-option",
162
+ identity: `${command.id}#${option}`,
163
+ path: "dist/site/cli-registry.json",
164
+ selector: `commands[id=${JSON.stringify(command.id)}].options[${JSON.stringify(option)}]`,
165
+ value: option,
166
+ }),
167
+ );
168
+ }
169
+ }
170
+ }
171
+
172
+ function addWorkflowInterface(catalog, workflow, category, field) {
173
+ for (const name of values(workflow[field])) {
174
+ add(
175
+ catalog,
176
+ catalogEntry({
177
+ category,
178
+ identity: `${workflow.id}#${name}`,
179
+ path: "dist/site/workflow-registry.json",
180
+ selector: `workflows[id=${JSON.stringify(workflow.id)}].${field}[${JSON.stringify(name)}]`,
181
+ value: name,
182
+ }),
183
+ );
184
+ }
185
+ }
186
+
187
+ function addWorkflowCatalog(catalog, workflowRegistry) {
188
+ for (const workflow of workflowRegistry.workflows || []) {
189
+ add(
190
+ catalog,
191
+ catalogEntry({
192
+ category: "workflow",
193
+ identity: workflow.id,
194
+ path: "dist/site/workflow-registry.json",
195
+ selector: `workflows[id=${JSON.stringify(workflow.id)}]`,
196
+ value: { path: workflow.path, reusable: workflow.reusable },
197
+ }),
198
+ );
199
+ for (const [category, field] of [
200
+ ["workflow-input", "inputs"],
201
+ ["workflow-output", "outputs"],
202
+ ["workflow-secret", "secrets"],
203
+ ]) {
204
+ addWorkflowInterface(catalog, workflow, category, field);
205
+ }
206
+ }
207
+ for (const action of workflowRegistry.actions || []) {
208
+ add(
209
+ catalog,
210
+ catalogEntry({
211
+ category: "action",
212
+ identity: action.id,
213
+ path: "dist/site/workflow-registry.json",
214
+ selector: `actions[id=${JSON.stringify(action.id)}]`,
215
+ value: action.path,
216
+ }),
217
+ );
218
+ for (const name of values(action.inputs)) {
219
+ add(
220
+ catalog,
221
+ catalogEntry({
222
+ category: "action-input",
223
+ identity: `${action.id}#${name}`,
224
+ path: "dist/site/workflow-registry.json",
225
+ selector: `actions[id=${JSON.stringify(action.id)}].inputs[${JSON.stringify(name)}]`,
226
+ value: name,
227
+ }),
228
+ );
229
+ }
230
+ }
231
+ }
232
+
233
+ function addFileEntry(catalog, root, revision, category, relPath) {
234
+ add(
235
+ catalog,
236
+ catalogEntry({
237
+ category,
238
+ identity: relPath,
239
+ path: relPath,
240
+ selector: "$",
241
+ value: sha256(gitText(root, revision, relPath)),
242
+ }),
243
+ );
244
+ }
245
+
246
+ function addFileCatalog(catalog, root, revision, paths, pathSet) {
247
+ const schemas = paths.filter(
248
+ (entry) =>
249
+ entry.startsWith("contracts/") &&
250
+ entry.endsWith(".schema.json") &&
251
+ !entry.includes("/fixtures/"),
252
+ );
253
+ const configs = paths.filter((entry) =>
254
+ /^\.buildchain\/.*\.(?:json|toml)$/u.test(entry),
255
+ );
256
+ const generated = new Set([
257
+ ...FIXED_GENERATED_CONTRACTS.filter((entry) => pathSet.has(entry)),
258
+ ...paths.filter(
259
+ (entry) =>
260
+ entry.startsWith("dist/site/schemas/") &&
261
+ entry.endsWith(".schema.json"),
262
+ ),
263
+ ]);
264
+ const evidence = paths.filter(
265
+ (entry) =>
266
+ entry.startsWith("contracts/") &&
267
+ !entry.includes("/fixtures/") &&
268
+ /(?:evidence|passport|receipt|attestation|witness)/u.test(entry),
269
+ );
270
+ for (const relPath of schemas)
271
+ addFileEntry(catalog, root, revision, "source-schema", relPath);
272
+ for (const relPath of configs)
273
+ addFileEntry(catalog, root, revision, "config-contract", relPath);
274
+ for (const relPath of [...generated].sort())
275
+ addFileEntry(catalog, root, revision, "generated-contract", relPath);
276
+ for (const relPath of evidence)
277
+ addFileEntry(
278
+ catalog,
279
+ root,
280
+ revision,
281
+ "observable-evidence-contract",
282
+ relPath,
283
+ );
284
+ }
285
+
286
+ function addMechanismCatalog(
287
+ catalog,
288
+ mechanismInventory,
289
+ runtimeSemanticClosure,
290
+ isLiveV3,
291
+ ) {
292
+ const mechanisms = isLiveV3
293
+ ? mechanismInventory.mechanisms || []
294
+ : runtimeSemanticClosure.capabilities || [];
295
+ const relPath = isLiveV3
296
+ ? "architecture/v3-core-mechanism-inventory.json"
297
+ : "architecture/v4-runtime-semantic-closure.json";
298
+ for (const mechanism of mechanisms) {
299
+ add(
300
+ catalog,
301
+ catalogEntry({
302
+ category: "release-delivery-recovery",
303
+ identity: mechanism.id,
304
+ path: relPath,
305
+ selector: `capability[id=${JSON.stringify(mechanism.id)}]`,
306
+ value: mechanism,
307
+ }),
308
+ );
309
+ }
310
+ }
311
+
312
+ function addPlatformCatalog(catalog, root, revision, paths) {
313
+ const workflowText = paths
314
+ .filter(
315
+ (entry) =>
316
+ entry.startsWith(".github/workflows/") && /\.ya?ml$/u.test(entry),
317
+ )
318
+ .map((entry) => gitText(root, revision, entry))
319
+ .join("\n");
320
+ for (const [platform, pattern] of Object.entries(PLATFORM_MARKERS)) {
321
+ if (!pattern.test(workflowText)) continue;
322
+ add(
323
+ catalog,
324
+ catalogEntry({
325
+ category: "platform-branch",
326
+ identity: platform,
327
+ path: ".github/workflows",
328
+ selector: `runner-marker:${platform}`,
329
+ value: platform,
330
+ }),
331
+ );
332
+ }
333
+ }
334
+
335
+ export function collectRevisionCatalog({ root, revision, liveV3Revision }) {
336
+ const catalog = new Map();
337
+ const paths = gitPaths(root, revision);
338
+ const pathSet = new Set(paths);
339
+ const packageJson = gitJson(root, revision, "package.json");
340
+ const nodeRegistry = gitJson(
341
+ root,
342
+ revision,
343
+ "dist/site/node-api-registry.json",
344
+ );
345
+ const cliRegistry = gitJson(root, revision, "dist/site/cli-registry.json");
346
+ const workflowRegistry = gitJson(
347
+ root,
348
+ revision,
349
+ "dist/site/workflow-registry.json",
350
+ );
351
+ const mechanismInventory = gitJson(
352
+ root,
353
+ revision,
354
+ "architecture/v3-core-mechanism-inventory.json",
355
+ );
356
+ const runtimeSemanticClosure =
357
+ revision === liveV3Revision
358
+ ? null
359
+ : gitJson(
360
+ root,
361
+ revision,
362
+ "architecture/v4-runtime-semantic-closure.json",
363
+ );
364
+ addNodeCatalog(catalog, packageJson, nodeRegistry);
365
+ addCliCatalog(catalog, cliRegistry);
366
+ addWorkflowCatalog(catalog, workflowRegistry);
367
+ addFileCatalog(catalog, root, revision, paths, pathSet);
368
+ addMechanismCatalog(
369
+ catalog,
370
+ mechanismInventory,
371
+ runtimeSemanticClosure,
372
+ revision === liveV3Revision,
373
+ );
374
+ addPlatformCatalog(catalog, root, revision, paths);
375
+ return { catalog, paths: pathSet };
376
+ }
377
+
378
+ export function collectHistoryRows({
379
+ root,
380
+ priorRevision,
381
+ liveV3Revision,
382
+ v4Paths,
383
+ migrationPaths,
384
+ ownerAssignment,
385
+ }) {
386
+ const output = git(root, [
387
+ "diff",
388
+ "--name-status",
389
+ priorRevision,
390
+ liveV3Revision,
391
+ ]);
392
+ return output
393
+ .split("\n")
394
+ .filter(Boolean)
395
+ .map((line) => {
396
+ const fields = line.split("\t");
397
+ const sourcePath = fields.at(-1);
398
+ const migrationPath = migrationPaths.get(sourcePath);
399
+ if (!v4Paths.has(sourcePath) && !migrationPath)
400
+ throw new Error(
401
+ `unclassified post-family v3 history path: ${sourcePath}`,
402
+ );
403
+ if (migrationPath && !v4Paths.has(migrationPath))
404
+ throw new Error(
405
+ `history migration route is absent from the exact v4 cut: ${migrationPath}`,
406
+ );
407
+ return {
408
+ id: `history-change:${sourcePath}`,
409
+ category: "history-change",
410
+ disposition: migrationPath ? "executable-migration" : "v4-native",
411
+ ownerAssignment,
412
+ sourceEvidence: {
413
+ path: sourcePath,
414
+ selector: `git-diff-status:${fields[0]}`,
415
+ valueRoot: sha256(line),
416
+ },
417
+ v4Route: {
418
+ capabilityId: `path:${migrationPath || sourcePath}`,
419
+ evidence: { path: migrationPath || sourcePath, selector: "$" },
420
+ },
421
+ residual: null,
422
+ positiveProbe:
423
+ "the exact v3 history range includes the path and the exact v4 cut contains its route",
424
+ negativeProbe:
425
+ "an absent or unclassified changed path fails validation",
426
+ };
427
+ });
428
+ }
429
+
430
+ export function countsBy(rows, field) {
431
+ return Object.fromEntries(
432
+ [...new Set(rows.map((row) => row[field]))]
433
+ .sort()
434
+ .map((value) => [
435
+ value,
436
+ rows.filter((row) => row[field] === value).length,
437
+ ]),
438
+ );
439
+ }