@kungfu-tech/buildchain 3.0.6 → 3.0.7-alpha.0
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 +4 -2
- package/actions/release-tail/README.md +17 -0
- package/bin/buildchain.mjs +11 -0
- package/bin/internal/command-registry.mjs +2 -0
- package/contracts/fixtures/release-tail-capabilities-v1/kungfu-alpha.json +273 -0
- package/contracts/publication-rehearsal-capsule-v1.schema.json +173 -0
- package/contracts/release-tail-capabilities-v1.schema.json +199 -0
- package/contracts/release-tail-provider-bindings-v1.schema.json +56 -0
- package/dist/site/agent-index.json +3 -0
- package/dist/site/artifact-schemas.json +6 -0
- package/dist/site/buildchain-contract.json +35 -20
- package/dist/site/buildchain-site.json +307 -17
- package/dist/site/capability-registry.json +12 -9
- package/dist/site/cli-registry.json +102 -0
- package/dist/site/controller-registry.json +14 -2
- package/dist/site/kfd-claims.json +247 -16
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +48 -3
- package/dist/site/node-api-registry.json +3108 -1409
- package/dist/site/page-registry.json +275 -9
- package/dist/site/public-surface-audit.json +304 -15
- package/dist/site/publication-authority-registry.json +26 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +5 -0
- package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +269 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +353 -0
- package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +94 -0
- package/dist/site/site-manifest.json +31 -7
- package/dist/site/workflow-registry.json +86 -9
- package/docs/MAP.md +5 -2
- package/docs/cli-reference.md +136 -0
- package/docs/node-api-reference.md +233 -84
- package/docs/publication-rehearsal.md +94 -0
- package/docs/release-tail-contract.md +160 -0
- package/docs/release-tail-provider-plane.md +120 -0
- package/package.json +8 -3
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-kfd-claims.js +1 -1
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/index.js +39 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper-repository.js +1 -0
- package/packages/core/paper-scaffold-content.js +21 -0
- package/packages/core/paper.js +28 -2
- package/packages/core/publication-rehearsal-projection.js +173 -0
- package/packages/core/publication-rehearsal-runtime.js +909 -0
- package/packages/core/release-tail-compatibility.js +60 -0
- package/packages/core/release-tail-provider-adapters.js +461 -0
- package/packages/core/release-tail-provider-plane.js +1228 -0
- package/scripts/assemble-publication-artifact-admission.mjs +1 -1
- package/scripts/assemble-self-publication-admission.mjs +1 -1
- package/scripts/buildchain-cli-help.mjs +13 -0
- package/scripts/check-core-mechanism-inventory.mjs +347 -0
- package/scripts/check-inventory.mjs +8 -8
- package/scripts/check-maintainability.mjs +9 -2
- package/scripts/check-release-tail-contract.mjs +435 -0
- package/scripts/generate-channel-promotion-workflow.mjs +10 -8
- package/scripts/generate-site-bundle.mjs +24 -0
- package/scripts/init-repo.mjs +26 -2
- package/scripts/materialize-self-release-candidate-version.mjs +131 -0
- package/scripts/release-tail.mjs +159 -0
- package/scripts/site-capability-metadata.mjs +11 -0
- package/scripts/v4-architecture.mjs +600 -0
- 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:
|
|
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:
|
|
162
|
+
policyDigest: gateBindings.policyDigest,
|
|
163
163
|
gateRegistryDigest: gateBindings.registryDigest,
|
|
164
164
|
controllerReceiptDigest: controllerReceipt.digest,
|
|
165
165
|
runnerProvenanceDigest: runnerProvenance.receiptDigest,
|
|
@@ -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 !==
|
|
734
|
-
throw new Error(`workflow-registry.json must expose the
|
|
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:
|
|
995
|
+
"uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@9a0cdf8d84aacf8c7daaac82efa43d1b34696a03\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')",
|
|
@@ -1114,7 +1114,7 @@ for (const retiredWorkflow of [
|
|
|
1114
1114
|
}
|
|
1115
1115
|
}
|
|
1116
1116
|
const promoteBuildchainRefAction = fs.readFileSync(path.join(root, "actions/promote-buildchain-ref/action.yml"), "utf8");
|
|
1117
|
-
const promoteBuildchainRefIndex =
|
|
1117
|
+
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
1118
|
for (const requiredSnippet of [
|
|
1119
1119
|
"github-release:",
|
|
1120
1120
|
"github-release-title:",
|
|
@@ -1140,8 +1140,8 @@ for (const requiredSnippet of [
|
|
|
1140
1140
|
}
|
|
1141
1141
|
}
|
|
1142
1142
|
for (const forbiddenSnippet of [
|
|
1143
|
-
"
|
|
1144
|
-
"uses:
|
|
1143
|
+
"uses: ./.github/workflows/.release-candidate-promote.yml",
|
|
1144
|
+
"uses: kungfu-systems/buildchain/.github/workflows/.release-candidate-promote.yml@",
|
|
1145
1145
|
]) {
|
|
1146
1146
|
if (buildchainRefPromotionWorkflow.includes(forbiddenSnippet)) {
|
|
1147
1147
|
throw new Error(`buildchain ref promotion workflow must use the declarative wrapper, found manual snippet: ${forbiddenSnippet}`);
|
|
@@ -1284,7 +1284,7 @@ if (!badgeEndpointRegistry.badges?.some((entry) => entry.id === "buildchain-rele
|
|
|
1284
1284
|
throw new Error("badge endpoint registry must include Buildchain Release Passport badge");
|
|
1285
1285
|
}
|
|
1286
1286
|
|
|
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"]) {
|
|
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", "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
1288
|
if (!fs.existsSync(path.join(root, "dist", "site", siteFile))) {
|
|
1289
1289
|
throw new Error(`site bundle missing ${siteFile}`);
|
|
1290
1290
|
}
|
|
@@ -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
|
-
|
|
285
|
+
const transition = policy.approvedNewFileTransitions?.[file];
|
|
286
|
+
if (transition && !String(transition.rationale || "").trim()) {
|
|
286
287
|
issues.push(
|
|
287
|
-
`${file}: new
|
|
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) {
|