@kungfu-tech/buildchain 4.0.1-alpha.8 → 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.
- package/README.md +9 -6
- package/actions/v4-release-candidate-promote/README.md +3 -0
- package/architecture/agent-change-map.md +1015 -0
- package/architecture/ci-lane-change-budget.json +70 -1
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/internal-capabilities.json +156 -11
- package/architecture/maintainability-debt.json +3229 -0
- package/architecture/maintainability-policy.json +480 -129
- package/architecture/v3-core-mechanism-inventory.json +4 -3
- package/architecture/v3-v4-capability-closure.md +59 -0
- package/architecture/v3-v4-live-capability-inventory.json +102404 -0
- package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
- package/architecture/v4-runtime-semantic-closure.json +710 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +123 -32
- package/dist/site/buildchain-site.json +96 -28
- package/dist/site/capability-registry.json +6 -6
- package/dist/site/cli-registry.json +5 -2
- package/dist/site/controller-registry.json +41 -5
- package/dist/site/kfd-claims.json +170 -21
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +1474 -215
- package/dist/site/page-registry.json +85 -17
- package/dist/site/public-surface-audit.json +85 -20
- package/dist/site/publication-authority-registry.json +28 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +5 -0
- package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +94 -18
- package/docs/MAP.md +1 -1
- package/docs/cli-reference.md +4 -4
- package/docs/dev-delivery-warrant.md +12 -9
- package/docs/node-api-reference.md +291 -206
- package/docs/publish-transaction.md +11 -4
- package/docs/release-candidate.md +7 -0
- package/docs/v4-adopter-delivery.md +23 -4
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/package.json +8 -3
- package/packages/core/adopter-delivery-passport.js +36 -0
- package/packages/core/buildchain-contract.js +31 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/dev-delivery-warrant-settlement.js +239 -3
- package/packages/core/dev-delivery-warrant-state.js +15 -2
- package/packages/core/dev-delivery-warrant.js +21 -4
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/index.js +1 -0
- package/packages/core/paper-scaffold-content.js +401 -0
- package/packages/core/paper.js +12 -378
- package/packages/core/publication-control-plane-audit.js +135 -111
- package/packages/core/publication-rehearsal-projection.js +104 -0
- package/packages/core/publication-rehearsal-runtime.js +190 -0
- package/packages/core/release-candidate-recovery.js +6 -5
- package/packages/core/release-passport.js +82 -184
- package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
- package/packages/core/v4-floating-consumer-policy.js +3 -2
- package/packages/core/v4-floating-consumer-release-passport.js +134 -15
- package/packages/core/v4-publication-qualification.js +222 -0
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +17 -17
- package/scripts/buildchain-cli-help.mjs +3 -0
- package/scripts/check-internal-architecture.mjs +23 -3
- package/scripts/check-inventory.mjs +23 -5
- package/scripts/check-maintainability.mjs +145 -4
- package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
- package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -3
- package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
- package/scripts/dev-delivery-warrant-options.mjs +27 -11
- package/scripts/dev-delivery-warrant.mjs +45 -5
- package/scripts/dev-pr-auto-merge.mjs +25 -11
- package/scripts/dev-pr-delivery-warrant.mjs +44 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-agent-change-map.mjs +118 -0
- package/scripts/generate-release-candidate-passport.mjs +26 -1
- package/scripts/generate-site-bundle.mjs +6 -0
- package/scripts/maintainability-domain-metrics.mjs +237 -0
- package/scripts/maintainability-governance.mjs +473 -0
- package/scripts/maintainability-metrics.mjs +28 -6
- package/scripts/project-cut-merge-queue-admission.mjs +394 -0
- package/scripts/promotion-routing-evidence.mjs +35 -6
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/release-line-policy.mjs +25 -15
- package/scripts/release-tail.mjs +19 -2
- package/scripts/resume-from-candidate-run.mjs +106 -103
- package/scripts/run-lifecycle-core.mjs +341 -376
- package/scripts/site-capability-metadata.mjs +4 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v3-v4-capability-catalog.mjs +439 -0
- package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
- 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:
|
|
157
|
+
headSha: candidate.sha,
|
|
160
158
|
runName: "",
|
|
161
159
|
options,
|
|
162
160
|
});
|
|
163
161
|
|
|
164
|
-
let status = await client.findCommitStatus(options.repository,
|
|
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:
|
|
181
|
-
runName: `${options.canaryWorkflowName} / ${
|
|
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:
|
|
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
|
|
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
|
+
}
|