@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.
- package/README.md +4 -2
- package/actions/promote-buildchain-ref/README.md +10 -0
- package/actions/release-tail/README.md +17 -0
- package/bin/buildchain.mjs +11 -0
- package/bin/internal/command-registry.mjs +2 -0
- package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
- package/contracts/engineering-housekeeper-v1.schema.json +143 -0
- package/contracts/fixtures/engineering-housekeeper-v1/cases.json +68 -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 +42 -27
- package/dist/site/buildchain-site.json +384 -33
- package/dist/site/capability-registry.json +15 -12
- package/dist/site/cli-registry.json +102 -0
- package/dist/site/controller-registry.json +19 -3
- package/dist/site/kfd-claims.json +359 -17
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +53 -7
- package/dist/site/node-api-registry.json +5882 -3605
- package/dist/site/page-registry.json +348 -21
- package/dist/site/public-surface-audit.json +396 -16
- package/dist/site/publication-authority-registry.json +106 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +7 -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 +35 -11
- package/dist/site/workflow-registry.json +204 -10
- package/docs/MAP.md +6 -2
- package/docs/auditable-demo.md +2 -2
- package/docs/cli-reference.md +136 -0
- package/docs/dev-delivery-warrant.md +49 -4
- package/docs/engineering-housekeeper.md +138 -0
- package/docs/lifecycle-protocol.md +4 -2
- package/docs/node-api-reference.md +495 -281
- package/docs/publication-rehearsal.md +94 -0
- package/docs/release-governance.md +17 -2
- package/docs/release-tail-contract.md +160 -0
- package/docs/release-tail-provider-plane.md +120 -0
- package/docs/reusable-build-surface.md +11 -0
- package/package.json +11 -3
- package/packages/core/artifact-signing.js +61 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-config.js +66 -6
- package/packages/core/buildchain-kfd-claims.js +1 -1
- package/packages/core/buildchain-publication-authority.js +5 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-shadow.js +502 -0
- package/packages/core/dev-delivery-warrant.js +15 -6
- package/packages/core/diagnostics.js +8 -3
- package/packages/core/engineering-housekeeper-github-client.js +222 -0
- package/packages/core/engineering-housekeeper-github.js +501 -0
- package/packages/core/engineering-housekeeper.js +259 -0
- package/packages/core/index.js +42 -0
- package/packages/core/kfd-gate.js +45 -15
- 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 +921 -0
- package/packages/core/release-passport.js +130 -20
- 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 +2 -2
- package/scripts/audit-publication-control-plane.mjs +1 -1
- package/scripts/auditable-demo-bundle-verification.mjs +2 -3
- package/scripts/auditable-demo-platform.mjs +2 -2
- package/scripts/auditable-demo-renditions.mjs +1 -1
- package/scripts/auditable-demo.mjs +2 -2
- package/scripts/build-contract-core.mjs +8 -3
- package/scripts/build-standalone-binary.mjs +14 -3
- package/scripts/buildchain-cli-help.mjs +13 -0
- package/scripts/check-core-mechanism-inventory.mjs +347 -0
- package/scripts/check-inventory.mjs +10 -8
- package/scripts/check-maintainability.mjs +9 -2
- package/scripts/check-release-tail-contract.mjs +435 -0
- package/scripts/dev-delivery-warrant.mjs +31 -4
- package/scripts/dev-pr-auto-merge.mjs +30 -4
- package/scripts/dev-pr-delivery-warrant.mjs +50 -0
- package/scripts/engineering-housekeeper-workflow.mjs +394 -0
- package/scripts/generate-channel-promotion-workflow.mjs +10 -8
- package/scripts/generate-site-bundle.mjs +47 -4
- package/scripts/init-repo.mjs +26 -2
- package/scripts/inspect-artifact-signing-requests.mjs +6 -0
- package/scripts/materialize-self-release-candidate-version.mjs +137 -0
- package/scripts/publication-commit-evidence.mjs +69 -23
- package/scripts/release-candidate-resolver.mjs +16 -10
- package/scripts/release-tail.mjs +159 -0
- package/scripts/resume-from-candidate-run.mjs +123 -9
- package/scripts/seal-artifact-signing-requests.mjs +6 -0
- package/scripts/site-capability-metadata.mjs +13 -0
- package/scripts/v4-architecture.mjs +600 -0
- package/scripts/web-surface-core.mjs +8 -2
- 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,
|
|
@@ -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() +
|
|
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() +
|
|
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
|
-
?
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
-
|
|
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 !==
|
|
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@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 =
|
|
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
|
-
"
|
|
1144
|
-
"uses:
|
|
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
|
-
|
|
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) {
|