@kungfu-tech/buildchain 4.1.6 → 4.1.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/AGENTS.md +7 -5
- package/CONTRIBUTING.md +12 -6
- package/README.md +9 -5
- package/architecture/action-taxonomy.json +2 -0
- package/architecture/agent-change-map.md +6 -1
- package/architecture/build-orchestration.json +7 -1
- package/architecture/ci-lane-change-budget.json +1836 -0
- package/architecture/consumer-upgrade.json +6482 -0
- package/architecture/internal-capabilities.json +5 -2
- package/architecture/maintainability-debt.json +18 -10
- package/architecture/maintainability-policy.json +9 -9
- package/architecture/release-topology.json +10 -1
- package/architecture/universal-workflow-bootstrap.json +88 -18
- package/architecture/universal-workflow-capability-policy.json +2 -2
- package/architecture/workflow-taxonomy.json +2 -1
- package/contracts/dev-delivery-authority-v2.schema.json +29 -3
- package/contracts/fixtures/consumer-upgrade/build-v4.0.0.json +764 -0
- package/contracts/fixtures/consumer-upgrade/consumer-calls.json +447 -0
- package/contracts/fixtures/consumer-upgrade/dev-delivery-identity-v4.0.0.json +27 -0
- package/contracts/fixtures/consumer-upgrade/dev-pr-auto-merge-v4.0.0.json +256 -0
- package/contracts/fixtures/consumer-upgrade/dev-pr-auto-merge-v4.0.9.json +358 -0
- package/contracts/fixtures/consumer-upgrade/release-v4.0.0.json +815 -0
- package/contracts/promotion-invocation-v1.schema.json +5 -0
- package/dist/site/buildchain-contract.json +40 -23
- package/dist/site/buildchain-site.json +20 -20
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/controller-registry.json +17 -5
- package/dist/site/kfd-claims.json +1376 -143
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +4 -4
- package/dist/site/node-api-registry.json +22 -22
- package/dist/site/page-registry.json +14 -14
- package/dist/site/public-surface-audit.json +1733 -101
- package/dist/site/publication-authority-registry.json +651 -32
- package/dist/site/publication-registry.json +1 -1
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +29 -3
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +2501 -149
- package/docs/MAP.md +5 -4
- package/docs/code-organization.md +11 -8
- package/docs/getting-started.md +31 -2
- package/docs/migration-inventory.md +8 -6
- package/docs/node-api-reference.md +36 -36
- package/docs/workflow-path-migration.md +8 -7
- package/package.json +1 -1
- package/packages/core/build/artifact/contracts.js +10 -2
- package/packages/core/build/artifact/transport.js +1 -1
- package/packages/core/build/artifact/upload.js +2 -2
- package/packages/core/build/plan/action.js +2 -0
- package/packages/core/build/plan/admission.js +9 -3
- package/packages/core/build/plan/compatibility-artifacts.js +47 -0
- package/packages/core/build/plan/compatibility-plan.js +121 -0
- package/packages/core/build/plan/compatibility.js +162 -0
- package/packages/core/build/plan/configuration.js +10 -37
- package/packages/core/build/plan/identity.js +42 -0
- package/packages/core/build/plan/lifecycle.js +1 -0
- package/packages/core/build/plan/resolve.js +2 -0
- package/packages/core/build/summary/aggregation.js +4 -2
- package/packages/core/build/summary/compatibility.js +24 -0
- package/packages/core/build/summary/finalization.js +18 -0
- package/packages/core/build/summary/historical-outputs.js +88 -0
- package/packages/core/contracts/runtime-contract-inspection.js +8 -0
- package/packages/core/dev-delivery/candidate/submission.js +2 -1
- package/packages/core/dev-delivery/commands/dev-delivery-authority-command-adapters.mjs +2 -1
- package/packages/core/dev-delivery/commands/dev-delivery-authority.mjs +6 -5
- package/packages/core/dev-delivery/commands/dev-delivery-warrant-options.mjs +22 -3
- package/packages/core/dev-delivery/dev-delivery-candidate-identity.js +13 -4
- package/packages/core/dev-delivery/dev-delivery-warrant.js +4 -1
- package/packages/core/dev-delivery/warrant/handoff.js +2 -1
- package/packages/core/dev-delivery/warrant/transitions.js +2 -2
- package/packages/core/governance/buildchain-publication-authority.js +5 -9
- package/packages/core/release/github-release-metadata.js +58 -0
- package/packages/core/release/promote-candidate/action.js +4 -2
- package/packages/core/release/promote-candidate/preparation.js +6 -0
- package/packages/core/release/promote-candidate/publication.js +36 -0
- package/packages/core/release/promote-candidate/release-documents.js +1 -1
- package/packages/core/release/promotion/candidate.js +4 -2
- package/packages/core/release/promotion/compatibility-context.js +93 -0
- package/packages/core/release/promotion/compatibility-qualification.js +48 -0
- package/packages/core/release/promotion/compatibility.js +141 -0
- package/packages/core/release/promotion/historical-evidence-action.js +17 -0
- package/packages/core/release/promotion/historical-evidence-files.js +112 -0
- package/packages/core/release/promotion/historical-evidence.js +231 -0
- package/packages/core/release/promotion/qualification.js +12 -1
- package/packages/core/release/promotion/routing.js +3 -2
- package/packages/core/release/promotion-request.js +31 -2
- package/packages/core/release/release-tail-provider-adapters.js +18 -0
- package/packages/core/runtime/entry/actions.js +4 -1
- package/packages/core/runtime/entry/compatibility.js +53 -0
- package/packages/core/workflow/engine/conformance.js +7 -3
- package/packages/core/workflow/pipeline/controller.js +26 -26
- package/scripts/check-dev-delivery-actions.mjs +12 -0
- package/scripts/check-inventory.mjs +1 -1
- package/scripts/check-public-dogfood-contract.mjs +2 -0
- package/scripts/check-release-topology.mjs +5 -1
- package/scripts/check-self-consumer-contract.mjs +5 -1
- package/scripts/generate-bootstrap-admission.mjs +20 -0
- package/scripts/generate-site-bundle.mjs +5 -5
- package/scripts/generate-workflow-taxonomy.mjs +17 -0
- package/scripts/maintainability-metrics.mjs +30 -2
- package/scripts/maintainability-public-surface.mjs +5 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Retain old selector names at the historical workflow boundary. The selected
|
|
2
|
+
// runtime still goes through the same lock validation and trusted recovery gate.
|
|
3
|
+
export function historicalEntrySelection(value, workflowRef) {
|
|
4
|
+
if (!value) return {};
|
|
5
|
+
const inputs = typeof value === "string" ? JSON.parse(value) : value;
|
|
6
|
+
if (!inputs || typeof inputs !== "object" || Array.isArray(inputs))
|
|
7
|
+
throw new Error("Historical entry inputs must be an object");
|
|
8
|
+
const read = (name, fallback = "") => {
|
|
9
|
+
const result = inputs[name] ?? fallback;
|
|
10
|
+
if (typeof result !== "string")
|
|
11
|
+
throw new Error(`Invalid historical selector: ${name}`);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
14
|
+
if (
|
|
15
|
+
read("buildchain-repository", "kungfu-systems/buildchain") !==
|
|
16
|
+
"kungfu-systems/buildchain"
|
|
17
|
+
)
|
|
18
|
+
throw new Error(
|
|
19
|
+
"Historical entry requires the official Buildchain repository",
|
|
20
|
+
);
|
|
21
|
+
const calledChannel = /@(?:refs\/tags\/)?v4-alpha$/u.test(workflowRef)
|
|
22
|
+
? "alpha"
|
|
23
|
+
: "stable";
|
|
24
|
+
const requested = read("buildchain-channel", "auto");
|
|
25
|
+
if (!["auto", "alpha", "stable"].includes(requested))
|
|
26
|
+
throw new Error("Invalid historical Buildchain channel");
|
|
27
|
+
const channel = requested === "auto" ? calledChannel : requested;
|
|
28
|
+
let ref = read("buildchain-ref").replace(/^v[23](-alpha)?$/u, "v4$1");
|
|
29
|
+
if (workflowRef?.split("@")[0] === "kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml") {
|
|
30
|
+
const recovery = read("resume-buildchain-runtime-sha");
|
|
31
|
+
if (recovery && !/^[a-f0-9]{40}$/u.test(recovery))
|
|
32
|
+
throw new Error("Historical recovery selector requires an exact runtime SHA");
|
|
33
|
+
if (recovery) ref = recovery;
|
|
34
|
+
}
|
|
35
|
+
const stableLock = read(
|
|
36
|
+
"buildchain-stable-contract-lock-path",
|
|
37
|
+
".buildchain/contract-lock.json",
|
|
38
|
+
);
|
|
39
|
+
const alphaLock = read(
|
|
40
|
+
"buildchain-alpha-contract-lock-path",
|
|
41
|
+
".buildchain/alpha-contract-lock.json",
|
|
42
|
+
);
|
|
43
|
+
return {
|
|
44
|
+
runtimeRef:
|
|
45
|
+
ref && ref !== (calledChannel === "alpha" ? "v4-alpha" : "v4") ? ref : "",
|
|
46
|
+
lockPath:
|
|
47
|
+
read("buildchain-contract-lock-path") ||
|
|
48
|
+
(channel === "alpha" ? alphaLock : stableLock),
|
|
49
|
+
channel,
|
|
50
|
+
stableLock,
|
|
51
|
+
alphaLock,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { compatibilityWorkflowTarget } from "../../consumer/compatibility-workflows.js";
|
|
4
5
|
import { exactObject, exactRuntime, fail, contentRoot } from "./identity.js";
|
|
5
6
|
function rawFileRoot(runtimeRoot, relative) {
|
|
6
7
|
const hash = crypto.createHash("sha256");
|
|
@@ -41,14 +42,17 @@ export function executeBootstrapConformance(
|
|
|
41
42
|
),
|
|
42
43
|
),
|
|
43
44
|
);
|
|
45
|
+
const governed = [...new Set(architecture.bootstrapGovernedWorkflows.map(relative =>
|
|
46
|
+
compatibilityWorkflowTarget(runtimeRoot, relative, fs.readFileSync(path.join(runtimeRoot, relative), "utf8"))
|
|
47
|
+
))];
|
|
44
48
|
if (
|
|
45
|
-
|
|
49
|
+
governed.length !==
|
|
46
50
|
request.payload.expectedGovernedWorkflowCount ||
|
|
47
51
|
architecture.status !== "active" ||
|
|
48
52
|
policy.allowedCapabilities.includes("workflow-contract")
|
|
49
53
|
)
|
|
50
54
|
fail("candidate Bootstrap conformance does not close execution semantics");
|
|
51
|
-
for (const relative of
|
|
55
|
+
for (const relative of governed) {
|
|
52
56
|
const source = fs.readFileSync(path.join(runtimeRoot, relative), "utf8");
|
|
53
57
|
if (
|
|
54
58
|
relative !== architecture.bootstrap.publicWorkflow &&
|
|
@@ -60,7 +64,7 @@ export function executeBootstrapConformance(
|
|
|
60
64
|
schema: "kungfu-buildchain-v4-universal-bootstrap-conformance-result/v1",
|
|
61
65
|
status: "candidate-engine-executed",
|
|
62
66
|
runtime: exactRuntime(admission),
|
|
63
|
-
governedWorkflowCount:
|
|
67
|
+
governedWorkflowCount: governed.length,
|
|
64
68
|
architectureRoot: rawFileRoot(
|
|
65
69
|
runtimeRoot,
|
|
66
70
|
"architecture/universal-workflow-bootstrap.json",
|
|
@@ -37,6 +37,11 @@ async function groupBuild(event, inputs, host) {
|
|
|
37
37
|
return { operation: "build", context };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
async function wakePipeline(host, attempt, reason) {
|
|
41
|
+
await host.wake(attempt);
|
|
42
|
+
return { operation: "wait", reason, attempt };
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
export async function controlPipeline(name, payload, inputs, host) {
|
|
41
46
|
const event = pipelineEvent(name, payload, host.repository);
|
|
42
47
|
host.terminalOnly = event.terminalOnly;
|
|
@@ -66,14 +71,12 @@ export async function controlPipeline(name, payload, inputs, host) {
|
|
|
66
71
|
if (
|
|
67
72
|
current.identity.requestKey.startsWith("recover:") &&
|
|
68
73
|
recordDigest(current.events[0].runtime) !== recordDigest(host.runtime)
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
76
|
-
}
|
|
74
|
+
)
|
|
75
|
+
return wakePipeline(
|
|
76
|
+
host,
|
|
77
|
+
current.identity.id,
|
|
78
|
+
"admitted-recovery-runtime-continuation-required",
|
|
79
|
+
);
|
|
77
80
|
if (
|
|
78
81
|
["failure", "cancelled", "superseded", "complete"].includes(
|
|
79
82
|
session.observed.status,
|
|
@@ -90,14 +93,12 @@ export async function controlPipeline(name, payload, inputs, host) {
|
|
|
90
93
|
if (
|
|
91
94
|
admission.live.source &&
|
|
92
95
|
host.selection.source.sha !== admission.live.source.commit
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
};
|
|
100
|
-
}
|
|
96
|
+
)
|
|
97
|
+
return wakePipeline(
|
|
98
|
+
host,
|
|
99
|
+
session.observed.attempt,
|
|
100
|
+
"source-generation-runtime-selection-required",
|
|
101
|
+
);
|
|
101
102
|
if (session.intent.expectedNodes.includes("warrant"))
|
|
102
103
|
host.queueExit ||= githubPipelineQueueExit(
|
|
103
104
|
host.github.graphql,
|
|
@@ -118,16 +119,15 @@ export async function controlPipeline(name, payload, inputs, host) {
|
|
|
118
119
|
return { operation: "build", context };
|
|
119
120
|
}
|
|
120
121
|
await host.project(session);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
122
|
+
// PR run.head_sha and signing certificates can name different commits.
|
|
123
|
+
const dispatchPublication =
|
|
124
|
+
operation.operation === "publish" && event.kind !== "attempt";
|
|
125
|
+
if (operation.operation === "successor" || dispatchPublication)
|
|
126
|
+
return wakePipeline(
|
|
127
|
+
host,
|
|
128
|
+
session.observed.attempt,
|
|
129
|
+
operation.reason || "publication-requires-a-normal-dispatch",
|
|
130
|
+
);
|
|
131
131
|
if (operation.operation === "settle") {
|
|
132
132
|
const settled = await host.settle(
|
|
133
133
|
session,
|
|
@@ -6,6 +6,7 @@ import assert from "node:assert/strict";
|
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import YAML from "yaml";
|
|
8
8
|
import { lowerSelfReferencesForLint } from "./workflow-self-reference.mjs";
|
|
9
|
+
import { compatibilityWorkflowTarget } from "../packages/core/consumer/compatibility-workflows.js";
|
|
9
10
|
const root = process.cwd();
|
|
10
11
|
const contract = JSON.parse(fs.readFileSync(path.join(root, "architecture/dev-delivery-orchestration.json"), "utf8"));
|
|
11
12
|
const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-delivery-actionlint-")));
|
|
@@ -16,6 +17,17 @@ try {
|
|
|
16
17
|
fs.mkdirSync(path.join(temp, ".buildchain"));
|
|
17
18
|
fs.symlinkSync(path.join(root, "actions"), path.join(temp, "actions"), "junction");
|
|
18
19
|
const files = fs.readdirSync(workflows).filter(file => /\.ya?ml$/u.test(file)).map(file => path.join(workflows, file));
|
|
20
|
+
const configPath = path.join(temp, ".github/actionlint.yaml");
|
|
21
|
+
const config = YAML.parse(fs.readFileSync(configPath, "utf8"));
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const relative = `.github/workflows/${path.basename(file)}`;
|
|
24
|
+
const canonical = compatibilityWorkflowTarget(root, relative, fs.readFileSync(file, "utf8"));
|
|
25
|
+
// Verified generated copies inherit only their canonical source's scoped
|
|
26
|
+
// rules for GitHub contexts newer than the pinned actionlint release.
|
|
27
|
+
if (canonical !== relative && config.paths?.[canonical])
|
|
28
|
+
config.paths[relative] = config.paths[canonical];
|
|
29
|
+
}
|
|
30
|
+
fs.writeFileSync(configPath, YAML.stringify(config));
|
|
19
31
|
for (const node of contract.nodes) for (const implementation of node.implementations) {
|
|
20
32
|
const action = YAML.parse(fs.readFileSync(path.join(root, implementation.action), "utf8"));
|
|
21
33
|
assert.deepEqual(Object.keys(action.inputs || {}), implementation.inputs, `${implementation.action}: input contract drift`);
|
|
@@ -296,7 +296,7 @@ assertPromotionInventory(root);
|
|
|
296
296
|
for (const workflow of [channelBuildWorkflow, reusableBuildWorkflow]) {
|
|
297
297
|
const block = workflow.split(" inputs:\n")[1].split(" secrets:\n")[0];
|
|
298
298
|
const names = [...block.matchAll(/^ ([a-z0-9-]+):$/gm)].map((match) => match[1]);
|
|
299
|
-
if (JSON.stringify(names.sort()) !== JSON.stringify(['config-path','contract-lock','resume-run-id','runtime-ref','runtime-selection'])) throw new Error('build
|
|
299
|
+
if (JSON.stringify(names.sort()) !== JSON.stringify(['compatibility-inputs','config-path','contract-lock','resume-run-id','runtime-ref','runtime-selection'])) throw new Error('build component must expose only its locator, runtime transport and historical argument transport');
|
|
300
300
|
}
|
|
301
301
|
if ((channelBuildWorkflow.match(/uses: \.\/\.github\/workflows\/\.build\.yml/g) || []).length !== 1) {
|
|
302
302
|
throw new Error("public build facade must invoke its exact backbone once");
|
|
@@ -5,6 +5,7 @@ import YAML from "yaml";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { checkSelfConsumerContract } from "./check-self-consumer-contract.mjs";
|
|
8
|
+
import { compatibilityWorkflowTarget } from "../packages/core/consumer/compatibility-workflows.js";
|
|
8
9
|
|
|
9
10
|
const DEFAULT_ROOT = path.resolve(
|
|
10
11
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
@@ -188,6 +189,7 @@ function assertWorkflowInventory(root) {
|
|
|
188
189
|
if (!entry.isFile() || !/\.ya?ml$/u.test(entry.name)) continue;
|
|
189
190
|
let relative = `.github/workflows/${entry.name}`;
|
|
190
191
|
const text = read(root, relative);
|
|
192
|
+
relative = compatibilityWorkflowTarget(root, relative, text);
|
|
191
193
|
if (
|
|
192
194
|
relative !== REUSABLE_PATH &&
|
|
193
195
|
(text.includes(
|
|
@@ -5,6 +5,7 @@ import { assertPipelinePublicationTopology } from "./check-pipeline-publication-
|
|
|
5
5
|
import { inspectWorkflowJob, readWorkflow } from "./workflow-action-graph.mjs";
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
7
|
import fs from "node:fs";
|
|
8
|
+
import { readConsumerUpgrade, compatibilityWorkflowTarget } from "../packages/core/consumer/compatibility-workflows.js";
|
|
8
9
|
import ts from "typescript";
|
|
9
10
|
|
|
10
11
|
import path from "node:path";
|
|
@@ -406,7 +407,10 @@ export function findUnknownReleaseTopology(
|
|
|
406
407
|
}
|
|
407
408
|
|
|
408
409
|
function assertClosedWorld(workflowPaths) {
|
|
409
|
-
const
|
|
410
|
+
const retained = (readConsumerUpgrade(root)?.entries || []).filter(entry =>
|
|
411
|
+
workflowPaths.includes(compatibilityWorkflowTarget(root, entry.path, read(entry.path)))
|
|
412
|
+
).map(entry => entry.path);
|
|
413
|
+
const unknown = findUnknownReleaseTopology([...workflowPaths, ...retained]);
|
|
410
414
|
assert.deepEqual(
|
|
411
415
|
unknown,
|
|
412
416
|
[],
|
|
@@ -4,7 +4,8 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { parse as yaml } from "yaml";
|
|
5
5
|
import * as consumerPlan from "../packages/core/consumer/contract/plan.js";
|
|
6
6
|
import { consumerWorkflows } from "../packages/core/consumer/contract/entries.js";
|
|
7
|
-
import { workflowPath } from "../packages/core/workflow/workflow-taxonomy.mjs";
|
|
7
|
+
import { checkWorkflowTaxonomy, workflowPath } from "../packages/core/workflow/workflow-taxonomy.mjs";
|
|
8
|
+
import { readConsumerUpgrade } from "../packages/core/consumer/compatibility-workflows.js";
|
|
8
9
|
|
|
9
10
|
export const SELF_ENTRY_CHANNEL = "v4";
|
|
10
11
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -33,6 +34,9 @@ export function checkSelfConsumerContract(root = ROOT) {
|
|
|
33
34
|
if (read(file) !== bytes) fail(`${file}: generated caller drift`);
|
|
34
35
|
const taxonomy = JSON.parse(read("architecture/workflow-taxonomy.json"));
|
|
35
36
|
const registered = new Set(taxonomy.entries.map(workflowPath));
|
|
37
|
+
const inventory = checkWorkflowTaxonomy(root, { integration: false, documentation: false });
|
|
38
|
+
if (!inventory.ok) fail(inventory.errors.join("; "));
|
|
39
|
+
for (const entry of readConsumerUpgrade(root)?.entries || []) registered.add(entry.path);
|
|
36
40
|
const observed = fs
|
|
37
41
|
.readdirSync(path.join(root, ".github/workflows"))
|
|
38
42
|
.filter((file) => /\.ya?ml$/u.test(file))
|
|
@@ -3,6 +3,7 @@ import { format } from "prettier";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { readConsumerUpgrade } from "../packages/core/consumer/compatibility-workflows.js";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const contract = JSON.parse(
|
|
@@ -16,6 +17,25 @@ const target = path.join(
|
|
|
16
17
|
"architecture/universal-workflow-capability-policy.json",
|
|
17
18
|
);
|
|
18
19
|
const policy = JSON.parse(fs.readFileSync(target, "utf8"));
|
|
20
|
+
for (const entry of readConsumerUpgrade(root)?.entries || []) {
|
|
21
|
+
if (!entry.interface.workflow_call) continue;
|
|
22
|
+
for (const field of [
|
|
23
|
+
"inventoryWorkflows",
|
|
24
|
+
"directCapabilityWorkflows",
|
|
25
|
+
"bootstrapGovernedWorkflows",
|
|
26
|
+
]) {
|
|
27
|
+
if (
|
|
28
|
+
contract[field].includes(entry.target) &&
|
|
29
|
+
!contract[field].includes(entry.path)
|
|
30
|
+
)
|
|
31
|
+
contract[field].push(entry.path);
|
|
32
|
+
contract[field].sort();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(root, "architecture/universal-workflow-bootstrap.json"),
|
|
37
|
+
await format(JSON.stringify(contract), { parser: "json" }),
|
|
38
|
+
);
|
|
19
39
|
policy.contractRoots = contract.bootstrap.admissionPolicySources
|
|
20
40
|
.map((file) => {
|
|
21
41
|
if (path.isAbsolute(file) || file.split("/").includes(".."))
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { actionSurfaceAudit } from "./site-action-audit.mjs";
|
|
3
3
|
import { actionInventory } from "../packages/core/contracts/action-inventory.js";
|
|
4
4
|
import { readWorkflowTaxonomy, workflowPath } from "../packages/core/workflow/workflow-taxonomy.mjs";
|
|
5
|
+
import { readConsumerUpgrade } from "../packages/core/consumer/compatibility-workflows.js";
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import crypto from "node:crypto";
|
|
@@ -830,12 +831,14 @@ function buildSiteBundle() {
|
|
|
830
831
|
};
|
|
831
832
|
const nodeApiRegistry = createSiteNodeApiRegistry({ root, packageJson, nodeApiMeta, sha256File, publicSurfaceLifecycle });
|
|
832
833
|
const taxonomy = readWorkflowTaxonomy(root);
|
|
834
|
+
const retainedEntries = readConsumerUpgrade(root)?.entries || [];
|
|
833
835
|
const workflowRegistry = {
|
|
834
836
|
schemaVersion: 1,
|
|
835
837
|
contract: "kungfu-buildchain-workflow-registry",
|
|
836
838
|
workflowSource: ".github/workflows reverse input enumeration",
|
|
837
839
|
workflows: enumerateWorkflowInputs({ root }).map((entry) => {
|
|
838
|
-
const
|
|
840
|
+
const retained = retainedEntries.find(item => item.path === entry.path);
|
|
841
|
+
const classification = taxonomy?.entries.find((item) => workflowPath(item) === (retained?.target || entry.path));
|
|
839
842
|
const semanticId = classification?.id || entry.id;
|
|
840
843
|
const surfaceById = new Map([
|
|
841
844
|
["build", "channel-build-router"],
|
|
@@ -877,14 +880,10 @@ function buildSiteBundle() {
|
|
|
877
880
|
[".auditable-demo", "preview"],
|
|
878
881
|
[".declarative-auditable-demo", "preview"],
|
|
879
882
|
["release-propagation", "preview"],
|
|
880
|
-
["candidate-lab", "repository-internal"],
|
|
881
|
-
["build-surface-fixture", "repository-internal"],
|
|
882
|
-
["buildchain-stable-candidate-qualification", "repository-internal"],
|
|
883
883
|
["engineering-housekeeper", "preview"],
|
|
884
884
|
["engineering-housekeeper-daily", "preview"],
|
|
885
885
|
["engineering-housekeeper-weekly", "preview"],
|
|
886
886
|
["engineering-housekeeper-monthly", "preview"],
|
|
887
|
-
["self-hosted-runner-smoke", "compatibility-fixture"],
|
|
888
887
|
]);
|
|
889
888
|
const engineeringHousekeeper = semanticId.startsWith("engineering-housekeeper");
|
|
890
889
|
if (classification?.role === "component") statusById.set(semanticId, "repository-internal");
|
|
@@ -905,6 +904,7 @@ function buildSiteBundle() {
|
|
|
905
904
|
? "One reusable policy and evidence boundary owns Engineering Housekeeper execution; scheduled callers contain cadence values only."
|
|
906
905
|
: "Canonical workflow owns one declared API or orchestration responsibility.",
|
|
907
906
|
}),
|
|
907
|
+
...(retained ? { compatibilityPromise: "retained-consumer-contract", status: "compatibility", apiRole: "compatibility", nonDuplicationRationale: "Generated from the registered canonical implementation; retained published inputs, outputs, defaults, permissions and job contexts are verified before release." } : {}),
|
|
908
908
|
};
|
|
909
909
|
}),
|
|
910
910
|
actionSource: "actions/*/*/*/action.yml reverse input enumeration",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
compatibilityWorkflowEntries,
|
|
6
|
+
renderCompatibilityWorkflow,
|
|
7
|
+
} from "../packages/core/consumer/compatibility-workflows.js";
|
|
4
8
|
import {
|
|
5
9
|
readWorkflowTaxonomy,
|
|
6
10
|
renderWorkflowCatalog,
|
|
@@ -19,6 +23,19 @@ for (const entry of policy.entries) {
|
|
|
19
23
|
fs.readFileSync(path.join(root, workflowPath(entry)), "utf8"),
|
|
20
24
|
);
|
|
21
25
|
}
|
|
26
|
+
for (const entry of compatibilityWorkflowEntries(
|
|
27
|
+
root,
|
|
28
|
+
undefined,
|
|
29
|
+
policy.entries.map((entry) => ({ ...entry, path: workflowPath(entry) })),
|
|
30
|
+
)) {
|
|
31
|
+
fs.writeFileSync(
|
|
32
|
+
path.join(root, entry.path),
|
|
33
|
+
renderCompatibilityWorkflow(
|
|
34
|
+
entry,
|
|
35
|
+
fs.readFileSync(path.join(root, entry.target), "utf8"),
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
22
39
|
fs.writeFileSync(path.join(root, TAXONOMY_DOC), renderWorkflowCatalog(policy));
|
|
23
40
|
process.stdout.write(
|
|
24
41
|
`Generated ${policy.entries.length} canonical workflow declarations.\n`,
|
|
@@ -5,6 +5,10 @@ import path from "node:path";
|
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import ts from "typescript";
|
|
8
|
+
import {
|
|
9
|
+
CONSUMER_UPGRADE_PATH,
|
|
10
|
+
renderCompatibilityWorkflow,
|
|
11
|
+
} from "../packages/core/consumer/compatibility-workflows.js";
|
|
8
12
|
import {
|
|
9
13
|
dependencyCycles,
|
|
10
14
|
relativeImports,
|
|
@@ -292,7 +296,29 @@ function collectMaintainabilityMetrics({
|
|
|
292
296
|
path: file,
|
|
293
297
|
text: readTrackedFile(root, file, revision),
|
|
294
298
|
}));
|
|
295
|
-
const
|
|
299
|
+
const retained = files.includes(CONSUMER_UPGRADE_PATH)
|
|
300
|
+
? JSON.parse(readTrackedFile(root, CONSUMER_UPGRADE_PATH, revision)).entries
|
|
301
|
+
: [];
|
|
302
|
+
const generatedWorkflows = new Set(
|
|
303
|
+
retained
|
|
304
|
+
.filter((entry) => {
|
|
305
|
+
const actual = physicalWorkflows.find(
|
|
306
|
+
(file) => file.path === entry.path,
|
|
307
|
+
);
|
|
308
|
+
const canonical = physicalWorkflows.find(
|
|
309
|
+
(file) => file.path === entry.target,
|
|
310
|
+
);
|
|
311
|
+
return (
|
|
312
|
+
actual &&
|
|
313
|
+
canonical &&
|
|
314
|
+
actual.text === renderCompatibilityWorkflow(entry, canonical.text)
|
|
315
|
+
);
|
|
316
|
+
})
|
|
317
|
+
.map((entry) => entry.path),
|
|
318
|
+
);
|
|
319
|
+
const logicalWorkflows = physicalWorkflows.filter(
|
|
320
|
+
(entry) => !generatedWorkflows.has(entry.path),
|
|
321
|
+
);
|
|
296
322
|
const workflowMetrics = Object.fromEntries(
|
|
297
323
|
logicalWorkflows.map((entry) => [
|
|
298
324
|
entry.path,
|
|
@@ -319,7 +345,7 @@ function collectMaintainabilityMetrics({
|
|
|
319
345
|
(total, entry) => total + entry.lines,
|
|
320
346
|
0,
|
|
321
347
|
);
|
|
322
|
-
const workflowLines = sumLines(
|
|
348
|
+
const workflowLines = sumLines(logicalWorkflows.map((entry) => entry.path));
|
|
323
349
|
const actionDefinitionLines = sumLines(actionDefinitions);
|
|
324
350
|
const head = revision || gitText(root, ["rev-parse", "HEAD"]).trim();
|
|
325
351
|
return {
|
|
@@ -334,6 +360,8 @@ function collectMaintainabilityMetrics({
|
|
|
334
360
|
testLines: sumLines(testFiles),
|
|
335
361
|
workflowFiles: logicalWorkflows.length,
|
|
336
362
|
workflowLines,
|
|
363
|
+
generatedCompatibilityWorkflowFiles: generatedWorkflows.size,
|
|
364
|
+
generatedCompatibilityWorkflowLines: sumLines([...generatedWorkflows]),
|
|
337
365
|
actionDefinitions: actionDefinitions.length,
|
|
338
366
|
actionDefinitionLines,
|
|
339
367
|
automationImplementationLines:
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { readConsumerUpgrade } from "../packages/core/consumer/compatibility-workflows.js";
|
|
3
4
|
|
|
4
5
|
function readJson(root, file) {
|
|
5
6
|
return JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
//
|
|
9
|
-
// contracts do not require aliases or forwarding implementations in 4.1.
|
|
9
|
+
// Current APIs and explicitly retained published contracts have distinct owners.
|
|
10
10
|
export function evaluatePublicSurface({ root, policy }) {
|
|
11
11
|
const issues = [];
|
|
12
|
+
const retained = new Set((readConsumerUpgrade(root)?.entries || []).map(entry => entry.path));
|
|
12
13
|
const groups = new Set(readJson(root, "dist/site/capability-registry.json").groups.map(entry => entry.id));
|
|
13
14
|
for (const [file, collection, kind, key] of [
|
|
14
15
|
["dist/site/cli-registry.json", "commands", "cli", "id"],
|
|
@@ -26,7 +27,8 @@ export function evaluatePublicSurface({ root, policy }) {
|
|
|
26
27
|
}
|
|
27
28
|
if (!groups.has(entry.capabilityGroup)) issues.push(`${label}: capability group ${entry.capabilityGroup || "<empty>"} is not registered`);
|
|
28
29
|
if (!entry.nonDuplicationRationale?.trim()) issues.push(`${label}: public surface requires a non-duplication rationale`);
|
|
29
|
-
|
|
30
|
+
const promise = kind === "workflow" && retained.has(entry.path) ? "retained-consumer-contract" : "current-contract-only";
|
|
31
|
+
if (entry.compatibilityPromise !== promise) issues.push(`${label}: compatibility promise does not match the registered contract`);
|
|
30
32
|
if (kind === "action" && !["public", "implementation"].includes(entry.apiRole)) issues.push(`${label}: action API role is missing`);
|
|
31
33
|
const target = kind === "node" ? entry.target : ["workflow", "action"].includes(kind) ? entry.path : null;
|
|
32
34
|
if (target) {
|