@kungfu-tech/buildchain 4.1.6 → 4.1.7
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
|
@@ -57,7 +57,7 @@ export async function transferBuild(context, platform) {
|
|
|
57
57
|
sourceSha: plan.source.sha,
|
|
58
58
|
platformId: platform.id,
|
|
59
59
|
platformName: platform.name,
|
|
60
|
-
groups: [{ role: "payload", artifactName: names.
|
|
60
|
+
groups: [{ role: "payload", artifactName: names.transfer, paths }],
|
|
61
61
|
});
|
|
62
62
|
artifacts.push({
|
|
63
63
|
role: "relay",
|
|
@@ -71,7 +71,7 @@ export async function transferBuild(context, platform) {
|
|
|
71
71
|
} else
|
|
72
72
|
artifacts.push({
|
|
73
73
|
role: "payload",
|
|
74
|
-
ref: await upload(plan, names.
|
|
74
|
+
ref: await upload(plan, names.transfer, paths, sourceRoot),
|
|
75
75
|
});
|
|
76
76
|
const requestRoot = path.join(
|
|
77
77
|
sourceRoot,
|
|
@@ -23,6 +23,8 @@ export async function resolveBuildPlanAction(core, env) {
|
|
|
23
23
|
runtimeRoot,
|
|
24
24
|
runtime: JSON.parse(env.BUILDCHAIN_RUNTIME_SELECTION),
|
|
25
25
|
configPath: core.getInput("config-path"),
|
|
26
|
+
compatibilityInputs: core.getInput("compatibility-inputs"),
|
|
27
|
+
compatibilityEnvironment: Object.fromEntries(Object.entries(env).filter(([key]) => key.startsWith("BUILDCHAIN_HISTORICAL_"))),
|
|
26
28
|
workflow: {
|
|
27
29
|
ref: core.getInput("workflow-ref", { required: true }),
|
|
28
30
|
sha: workflowSha,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { runtimeContractLockDrift } from "../../contracts/runtime-contract-inspection.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import { containedBuildPath } from "../build-configuration.js";
|
|
3
5
|
import { scanConsumerPolicy } from "../../consumer/policy-scan.js";
|
|
4
6
|
import { validatePackageManagerContract } from "../package-manager.js";
|
|
5
7
|
|
|
@@ -16,11 +18,15 @@ export async function admitBuildSource({ plan, sourceRoot, runtimeRoot, workflow
|
|
|
16
18
|
expectedInvocationChannel: plan.identity.channel,
|
|
17
19
|
resolvedWorkflowSha: plan.entry.sha,
|
|
18
20
|
resolvedRuntimeSha: plan.identity.sha,
|
|
19
|
-
stableLockPath: ".buildchain/contract-lock.json",
|
|
20
|
-
alphaLockPath: ".buildchain/alpha-contract-lock.json",
|
|
21
|
+
stableLockPath: plan.contract.stable_lock_path || ".buildchain/contract-lock.json",
|
|
22
|
+
alphaLockPath: plan.contract.alpha_lock_path || ".buildchain/alpha-contract-lock.json",
|
|
21
23
|
});
|
|
22
24
|
if (!policy.ok) throw new Error("Build source rejected by floating consumer policy");
|
|
23
25
|
validatePackageManagerContract({ cwd: sourceRoot, expectedManager: "" });
|
|
24
26
|
const contract = JSON.parse(fs.readFileSync(path.join(runtimeRoot, "dist/site/buildchain-contract.json"), "utf8"));
|
|
25
|
-
|
|
27
|
+
const lock = JSON.parse(fs.readFileSync(containedBuildPath(sourceRoot, plan.contract.lock_path), "utf8"));
|
|
28
|
+
const lockDrift = runtimeContractLockDrift(lock, plan.identity.sha, contract.contractDigest);
|
|
29
|
+
return { policy: policy.receipt, policy_root: policy.receiptRoot, contract_digest: contract.contractDigest,
|
|
30
|
+
lock_status: plan.runtime?.origin || "contract-lock", lock_drift: lockDrift };
|
|
31
|
+
|
|
26
32
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { containedBuildPath } from "../build-configuration.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function historicalBuildArtifacts(plan, inputs, root) {
|
|
5
|
+
const remaining = { ...inputs };
|
|
6
|
+
const fields = {
|
|
7
|
+
"artifact-name": "name",
|
|
8
|
+
"artifact-name-template": "name_template",
|
|
9
|
+
"expected-artifacts-json": "expected_json",
|
|
10
|
+
};
|
|
11
|
+
for (const [input, field] of Object.entries(fields)) {
|
|
12
|
+
if (!Object.hasOwn(remaining, input)) continue;
|
|
13
|
+
plan.artifacts[field] = remaining[input];
|
|
14
|
+
delete remaining[input];
|
|
15
|
+
}
|
|
16
|
+
plan.build.artifacts.name = plan.artifacts.name;
|
|
17
|
+
if (Object.hasOwn(remaining, "artifact-paths")) {
|
|
18
|
+
const paths = remaining["artifact-paths"]
|
|
19
|
+
.split(/\r?\n/u)
|
|
20
|
+
.map((item) => item.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
plan.build.artifacts.paths = paths;
|
|
23
|
+
plan.artifacts.paths = paths
|
|
24
|
+
.map((item) => {
|
|
25
|
+
const relative = path.posix.join(plan.project.cwd, item);
|
|
26
|
+
containedBuildPath(root, relative);
|
|
27
|
+
return relative;
|
|
28
|
+
})
|
|
29
|
+
.join("\n");
|
|
30
|
+
delete remaining["artifact-paths"];
|
|
31
|
+
}
|
|
32
|
+
const expected = JSON.parse(plan.artifacts.expected_json);
|
|
33
|
+
if (!expected || typeof expected !== "object" || Array.isArray(expected))
|
|
34
|
+
throw new Error("expected-artifacts-json must describe artifact checks");
|
|
35
|
+
for (const field of ["minFiles", "maxFiles", "minTotalBytes"])
|
|
36
|
+
if (
|
|
37
|
+
expected[field] !== undefined &&
|
|
38
|
+
(!Number.isSafeInteger(expected[field]) || expected[field] < 0)
|
|
39
|
+
)
|
|
40
|
+
throw new Error(`Invalid artifact check: ${field}`);
|
|
41
|
+
if (expected.requiredPaths !== undefined) {
|
|
42
|
+
if (!Array.isArray(expected.requiredPaths))
|
|
43
|
+
throw new Error("requiredPaths must be an array");
|
|
44
|
+
for (const item of expected.requiredPaths) containedBuildPath(root, item);
|
|
45
|
+
}
|
|
46
|
+
return remaining;
|
|
47
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { resolvePublishGate } from "../../release/promotion/publish-gate.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { historicalEntrySelection } from "../../runtime/entry/compatibility.js";
|
|
5
|
+
import {
|
|
6
|
+
containedBuildPath,
|
|
7
|
+
normalizeBuildConfiguration,
|
|
8
|
+
} from "../build-configuration.js";
|
|
9
|
+
import { applyHistoricalBuildInputs } from "./compatibility.js";
|
|
10
|
+
import { historicalBuildArtifacts } from "./compatibility-artifacts.js";
|
|
11
|
+
import { rootOf } from "./values.js";
|
|
12
|
+
|
|
13
|
+
export function historicalBuildLocator(root, locator, inputs) {
|
|
14
|
+
const directory = inputs["working-directory"];
|
|
15
|
+
if (!directory || directory === ".") return locator;
|
|
16
|
+
containedBuildPath(root, directory);
|
|
17
|
+
if (locator)
|
|
18
|
+
throw new Error(
|
|
19
|
+
"config-path cannot be combined with a historical working-directory",
|
|
20
|
+
);
|
|
21
|
+
const candidates = [".buildchain/buildchain.toml", "buildchain.toml"].map(
|
|
22
|
+
(item) => path.posix.join(directory, item),
|
|
23
|
+
);
|
|
24
|
+
const present = candidates.filter((item) =>
|
|
25
|
+
fs.existsSync(containedBuildPath(root, item)),
|
|
26
|
+
);
|
|
27
|
+
if (present.length !== 1)
|
|
28
|
+
throw new Error(
|
|
29
|
+
"Historical working-directory must contain one buildchain.toml",
|
|
30
|
+
);
|
|
31
|
+
return present[0];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function applySelectors(plan, remaining) {
|
|
35
|
+
const selection = historicalEntrySelection(
|
|
36
|
+
remaining,
|
|
37
|
+
`${plan.identity.repository}/${plan.identity.visible_workflow}@${plan.identity.ref}`,
|
|
38
|
+
);
|
|
39
|
+
plan.contract.lock_path = selection.lockPath;
|
|
40
|
+
plan.contract.stable_lock_path = selection.stableLock;
|
|
41
|
+
plan.contract.alpha_lock_path = selection.alphaLock;
|
|
42
|
+
for (const [input, expected] of [
|
|
43
|
+
["buildchain-expected-channel", plan.identity.channel],
|
|
44
|
+
["buildchain-expected-major", plan.identity.major],
|
|
45
|
+
])
|
|
46
|
+
if (remaining[input] && remaining[input] !== expected)
|
|
47
|
+
throw new Error(`Historical ${input} does not match the called entry`);
|
|
48
|
+
for (const input of [
|
|
49
|
+
"buildchain-channel",
|
|
50
|
+
"buildchain-ref",
|
|
51
|
+
"buildchain-repository",
|
|
52
|
+
"buildchain-contract-lock-path",
|
|
53
|
+
"buildchain-stable-contract-lock-path",
|
|
54
|
+
"buildchain-alpha-contract-lock-path",
|
|
55
|
+
"buildchain-expected-channel",
|
|
56
|
+
"buildchain-expected-major",
|
|
57
|
+
"working-directory",
|
|
58
|
+
])
|
|
59
|
+
delete remaining[input];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function finishHistoricalBuildPlan(plan, inputs, root) {
|
|
63
|
+
if (!Object.keys(inputs).length) return;
|
|
64
|
+
const remaining = historicalBuildArtifacts(
|
|
65
|
+
plan,
|
|
66
|
+
applyHistoricalBuildInputs(plan, inputs),
|
|
67
|
+
root,
|
|
68
|
+
);
|
|
69
|
+
applySelectors(plan, remaining);
|
|
70
|
+
if (Object.hasOwn(remaining, "publish-channel")) {
|
|
71
|
+
const channel = remaining["publish-channel"];
|
|
72
|
+
plan.source.candidate_channel = channel === "major" ? "release"
|
|
73
|
+
: ["alpha", "release"].includes(channel) ? channel : "none";
|
|
74
|
+
delete remaining["publish-channel"];
|
|
75
|
+
}
|
|
76
|
+
delete remaining["publish-refs-json"];
|
|
77
|
+
if (remaining["artifact-relay-s3-role-arn"]) {
|
|
78
|
+
for (const kind of ["upload", "download"])
|
|
79
|
+
plan.environment.transfer[`${kind}_role_arn`] ||=
|
|
80
|
+
remaining["artifact-relay-s3-role-arn"];
|
|
81
|
+
delete remaining["artifact-relay-s3-role-arn"];
|
|
82
|
+
}
|
|
83
|
+
for (const [input, field] of [
|
|
84
|
+
["gate-profile-aggregate-json", "gate_profile_json"],
|
|
85
|
+
["release-candidate-family-evidence-json", "candidate_family_json"],
|
|
86
|
+
]) {
|
|
87
|
+
if (!Object.hasOwn(remaining, input)) continue;
|
|
88
|
+
JSON.parse(remaining[input]);
|
|
89
|
+
plan.evidence[field] = remaining[input];
|
|
90
|
+
delete remaining[input];
|
|
91
|
+
}
|
|
92
|
+
if (Object.keys(remaining).length)
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Unmapped historical build inputs: ${Object.keys(remaining).join(", ")}`,
|
|
95
|
+
);
|
|
96
|
+
plan.build = normalizeBuildConfiguration(plan.build);
|
|
97
|
+
plan.configuration_root = rootOf({ toml: plan.configuration_root, inputs });
|
|
98
|
+
plan.environment_root = rootOf(plan.environment);
|
|
99
|
+
plan.cache.toolchain_root = rootOf({
|
|
100
|
+
tools: plan.build.tools,
|
|
101
|
+
environment: plan.environment.tools,
|
|
102
|
+
});
|
|
103
|
+
plan.cache.policy_root = rootOf(plan.environment.cache);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function historicalPublishGate(plan, inputs, eventName) {
|
|
107
|
+
if (plan.identity.visible_workflow !== ".github/workflows/build.yml") return;
|
|
108
|
+
const resolved = resolvePublishGate({
|
|
109
|
+
trusted: true,
|
|
110
|
+
publishChannel: inputs["publish-channel"] || "none",
|
|
111
|
+
publishRefsJson: inputs["publish-refs-json"] || "",
|
|
112
|
+
eventName,
|
|
113
|
+
ref: plan.source.ref,
|
|
114
|
+
});
|
|
115
|
+
plan.historical_publish_gate = {
|
|
116
|
+
trustedEvent: resolved.trusted,
|
|
117
|
+
channel: resolved.publishChannel,
|
|
118
|
+
allowed: resolved.publishAllowed,
|
|
119
|
+
reason: resolved.publishReason,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { installationRoot } from "../../runtime/installation-root.js";
|
|
4
|
+
|
|
5
|
+
const contract = JSON.parse(
|
|
6
|
+
fs.readFileSync(
|
|
7
|
+
path.join(
|
|
8
|
+
installationRoot(import.meta.url),
|
|
9
|
+
"contracts/fixtures/consumer-upgrade/build-v4.0.0.json",
|
|
10
|
+
),
|
|
11
|
+
"utf8",
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
const bindings =
|
|
15
|
+
JSON.parse(
|
|
16
|
+
fs.readFileSync(
|
|
17
|
+
path.join(
|
|
18
|
+
installationRoot(import.meta.url),
|
|
19
|
+
"architecture/consumer-upgrade.json",
|
|
20
|
+
),
|
|
21
|
+
"utf8",
|
|
22
|
+
),
|
|
23
|
+
).entries.find((entry) => entry.adapter === "build-backbone")
|
|
24
|
+
?.inputVariables || {};
|
|
25
|
+
|
|
26
|
+
// The old workflow boundary is typed before values enter the current planner.
|
|
27
|
+
// Empty modern calls preserve the TOML plan; historical defaults do not replace
|
|
28
|
+
// explicit TOML choices when GitHub expands all optional workflow inputs.
|
|
29
|
+
export function historicalBuildInputs(value = {}, environment = {}) {
|
|
30
|
+
if (typeof value === "string") value = JSON.parse(value || "{}");
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
32
|
+
throw new Error("Historical build inputs must be an object");
|
|
33
|
+
const current = new Set([
|
|
34
|
+
"config-path",
|
|
35
|
+
"runtime-ref",
|
|
36
|
+
"contract-lock",
|
|
37
|
+
"resume-run-id",
|
|
38
|
+
"runtime-selection",
|
|
39
|
+
]);
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const [key, input] of Object.entries(value)) {
|
|
42
|
+
if (current.has(key)) continue;
|
|
43
|
+
const field = contract.inputs[key];
|
|
44
|
+
if (
|
|
45
|
+
!field ||
|
|
46
|
+
typeof input !== field.type ||
|
|
47
|
+
(typeof input === "number" && !Number.isFinite(input))
|
|
48
|
+
)
|
|
49
|
+
throw new Error(`Invalid historical build input: ${key}`);
|
|
50
|
+
if (input !== field.default) result[key] = input;
|
|
51
|
+
}
|
|
52
|
+
if (Object.keys(value).some((key) => Object.hasOwn(contract.inputs, key)))
|
|
53
|
+
for (const key of Object.keys(bindings)) {
|
|
54
|
+
const selected =
|
|
55
|
+
environment[
|
|
56
|
+
`BUILDCHAIN_HISTORICAL_${key.replaceAll("-", "_").toUpperCase()}`
|
|
57
|
+
];
|
|
58
|
+
if (selected && !value[key]) result[key] = String(selected);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const paths = {
|
|
64
|
+
"runner-preset": "environment.runners.preset",
|
|
65
|
+
"platforms-json": "environment.runners.platforms_json",
|
|
66
|
+
"control-runner-json": "environment.runners.control_json",
|
|
67
|
+
"aws-codebuild-project": "environment.runners.codebuild_project",
|
|
68
|
+
"aws-ec2-windows-runner-label": "environment.runners.windows_label",
|
|
69
|
+
"aws-ec2-macos-runner-label": "environment.runners.macos_label",
|
|
70
|
+
"self-hosted-offline-fallback": "environment.runners.offline_fallback",
|
|
71
|
+
"linux-container-preset": "environment.runners.container_preset",
|
|
72
|
+
"linux-container-image": "environment.runners.container_image",
|
|
73
|
+
"rustup-dist-server": "environment.tools.rustup_dist_server",
|
|
74
|
+
"rustup-update-root": "environment.tools.rustup_update_root",
|
|
75
|
+
"cargo-registry-index": "environment.tools.cargo_registry_index",
|
|
76
|
+
"checkout-cache-mode": "environment.checkout.mode",
|
|
77
|
+
"checkout-cache-mirror-url-template":
|
|
78
|
+
"environment.checkout.mirror_url_template",
|
|
79
|
+
"checkout-cache-reference-repository-template":
|
|
80
|
+
"environment.checkout.reference_repository_template",
|
|
81
|
+
"checkout-cache-fallback": "environment.checkout.fallback",
|
|
82
|
+
"checkout-cache-timeout-seconds": "environment.checkout.timeout_seconds",
|
|
83
|
+
"checkout-cache-github-timeout-seconds":
|
|
84
|
+
"environment.checkout.github_timeout_seconds",
|
|
85
|
+
"checkout-cache-fetch-attempts": "environment.checkout.fetch_attempts",
|
|
86
|
+
"checkout-history-mode": "environment.checkout.history_mode",
|
|
87
|
+
"artifact-transfer-mode": "environment.transfer.mode",
|
|
88
|
+
"artifact-relay-s3-bucket": "environment.transfer.bucket",
|
|
89
|
+
"artifact-relay-s3-region": "environment.transfer.region",
|
|
90
|
+
"artifact-relay-s3-prefix": "environment.transfer.prefix",
|
|
91
|
+
"artifact-relay-s3-upload-role-arn": "environment.transfer.upload_role_arn",
|
|
92
|
+
"artifact-relay-s3-download-role-arn":
|
|
93
|
+
"environment.transfer.download_role_arn",
|
|
94
|
+
"artifact-relay-s3-oidc-audience": "environment.transfer.oidc_audience",
|
|
95
|
+
"shifu-cache-profile-ref": "environment.cache.profile_ref",
|
|
96
|
+
"shifu-cache-profile-digest": "environment.cache.profile_digest",
|
|
97
|
+
"compiler-cache-provider": "environment.cache.provider",
|
|
98
|
+
"compiler-cache-platforms-json": "environment.cache.platforms_json",
|
|
99
|
+
"compiler-cache-required": "environment.cache.required",
|
|
100
|
+
"artifact-signing-request-upload-no-proxy":
|
|
101
|
+
"environment.signing.upload_no_proxy",
|
|
102
|
+
"credential-island-environment": "environment.signing.environment",
|
|
103
|
+
"fail-fast": "build.fail_fast",
|
|
104
|
+
"lifecycle-timeout-minutes": "build.timeout_minutes",
|
|
105
|
+
"artifact-retention-days": "build.artifacts.retention_days",
|
|
106
|
+
"artifact-compression-level": "build.artifacts.compression_level",
|
|
107
|
+
"release-candidate": "build.artifacts.release_candidate",
|
|
108
|
+
"artifact-finalization-command": "build.finalization.command",
|
|
109
|
+
"artifact-finalization-on-platform": "build.finalization.on_platform",
|
|
110
|
+
"verify-substage-evidence-path": "build.verification.substage_evidence_path",
|
|
111
|
+
"pre-upload-transport-smoke-scenario-path":
|
|
112
|
+
"build.transport_smoke.scenario_path",
|
|
113
|
+
"pre-upload-transport-smoke-artifact-root":
|
|
114
|
+
"build.transport_smoke.artifact_root",
|
|
115
|
+
"credential-island-macos-app-path": "build.macos_signing.app_path",
|
|
116
|
+
"credential-island-macos-platform-id": "build.macos_signing.platform",
|
|
117
|
+
"github-artifact-attestation-subject-path": "build.attestation.subject_path",
|
|
118
|
+
"github-artifact-attestation-platform-id": "build.attestation.platform",
|
|
119
|
+
"sample-process-tree": "build.diagnostics.sample_process_tree",
|
|
120
|
+
"process-sample-interval-ms": "build.diagnostics.sample_interval_ms",
|
|
121
|
+
"requested-parallelism": "build.diagnostics.requested_parallelism",
|
|
122
|
+
"buildchain-contract-compatibility-policy":
|
|
123
|
+
"build.contract.compatibility_policy",
|
|
124
|
+
"buildchain-contract-drift-issue-mode": "build.contract.drift_issue_mode",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export function applyHistoricalBuildInputs(plan, inputs) {
|
|
128
|
+
const remaining = { ...inputs };
|
|
129
|
+
for (const [input, field] of Object.entries(paths)) {
|
|
130
|
+
if (!Object.hasOwn(remaining, input)) continue;
|
|
131
|
+
const names = field.split(".");
|
|
132
|
+
const key = names.pop();
|
|
133
|
+
const target = names.reduce((value, name) => value[name], plan);
|
|
134
|
+
target[key] = remaining[input];
|
|
135
|
+
delete remaining[input];
|
|
136
|
+
}
|
|
137
|
+
for (const stage of ["install", "build", "verify"]) {
|
|
138
|
+
if (Object.hasOwn(remaining, `${stage}-command`)) {
|
|
139
|
+
plan.lifecycle[stage].command = remaining[`${stage}-command`];
|
|
140
|
+
plan.lifecycle[stage].configured = Boolean(remaining[`${stage}-command`]);
|
|
141
|
+
delete remaining[`${stage}-command`];
|
|
142
|
+
}
|
|
143
|
+
if (Object.hasOwn(remaining, `require-${stage}`)) {
|
|
144
|
+
plan.lifecycle[stage].required = remaining[`require-${stage}`];
|
|
145
|
+
delete remaining[`require-${stage}`];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const [input, tool] of [
|
|
149
|
+
["node-version", "node"],
|
|
150
|
+
["rust-toolchain", "rust"],
|
|
151
|
+
]) {
|
|
152
|
+
if (Object.hasOwn(remaining, input)) {
|
|
153
|
+
plan.tools[tool] = plan.build.tools[tool] = remaining[input];
|
|
154
|
+
delete remaining[input];
|
|
155
|
+
}
|
|
156
|
+
if (Object.hasOwn(remaining, `setup-${tool}`)) {
|
|
157
|
+
plan.tools[`setup_${tool}`] = remaining[`setup-${tool}`];
|
|
158
|
+
delete remaining[`setup-${tool}`];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return remaining;
|
|
162
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { resolveBuildIdentity } from "./identity.js";
|
|
1
2
|
import { installationRoot } from "../../runtime/installation-root.js";
|
|
2
3
|
import crypto from "node:crypto";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { loadBuildchainConfig } from "../../consumer/buildchain-config.js";
|
|
7
|
+
import { historicalBuildInputs } from "./compatibility.js";
|
|
8
|
+
import { historicalBuildLocator, finishHistoricalBuildPlan, historicalPublishGate } from "./compatibility-plan.js";
|
|
6
9
|
import {
|
|
7
10
|
containedBuildPath,
|
|
8
11
|
discoverBuildConfiguration,
|
|
@@ -25,6 +28,8 @@ export function resolveBuildConfiguration({
|
|
|
25
28
|
callerWorkflowRef = "",
|
|
26
29
|
eventName = "",
|
|
27
30
|
baseRef = "",
|
|
31
|
+
compatibilityInputs = {},
|
|
32
|
+
compatibilityEnvironment = {},
|
|
28
33
|
environmentRegistry = JSON.parse(
|
|
29
34
|
fs.readFileSync(
|
|
30
35
|
path.join(runtimeRoot, "architecture/build-environments.json"),
|
|
@@ -36,43 +41,9 @@ export function resolveBuildConfiguration({
|
|
|
36
41
|
if (!/^[a-f0-9]{40}$/u.test(value || ""))
|
|
37
42
|
throw new Error(`${label} must be an exact SHA`);
|
|
38
43
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
if (!match || match[1] !== repository)
|
|
43
|
-
throw new Error(
|
|
44
|
-
"Ordinary builds require the exact called workflow identity on a floating channel",
|
|
45
|
-
);
|
|
46
|
-
const identity = {
|
|
47
|
-
repository,
|
|
48
|
-
ref: match[3],
|
|
49
|
-
full_ref: `refs/tags/${match[3]}`,
|
|
50
|
-
sha: workflowSha,
|
|
51
|
-
channel: match[5] ? "alpha" : "stable",
|
|
52
|
-
major: match[4],
|
|
53
|
-
visible_workflow: match[2],
|
|
54
|
-
};
|
|
55
|
-
const callerPath = callerWorkflowRef
|
|
56
|
-
.split("@")[0]
|
|
57
|
-
.split("/.github/workflows/")[1];
|
|
58
|
-
if (callerPath) {
|
|
59
|
-
const caller = fs.readFileSync(
|
|
60
|
-
containedBuildPath(root, `.github/workflows/${callerPath}`),
|
|
61
|
-
"utf8",
|
|
62
|
-
);
|
|
63
|
-
const calls = [
|
|
64
|
-
...caller.matchAll(
|
|
65
|
-
/uses:\s+kungfu-systems\/buildchain\/(\.github\/workflows\/(?:build|\.build)\.yml)@(v\d+(?:-alpha)?)\s*$/gmu,
|
|
66
|
-
),
|
|
67
|
-
].filter((call) => call[2] === identity.ref);
|
|
68
|
-
const paths = [...new Set(calls.map((call) => call[1]))];
|
|
69
|
-
if (paths.length !== 1)
|
|
70
|
-
throw new Error(
|
|
71
|
-
"Unable to derive one visible build workflow from the exact caller",
|
|
72
|
-
);
|
|
73
|
-
identity.visible_workflow = paths[0];
|
|
74
|
-
}
|
|
75
|
-
const project = discoverBuildConfiguration(root, locator);
|
|
44
|
+
const identity = resolveBuildIdentity({ root, workflowRef, workflowSha, repository, callerWorkflowRef });
|
|
45
|
+
const historical = historicalBuildInputs(compatibilityInputs, compatibilityEnvironment);
|
|
46
|
+
const project = discoverBuildConfiguration(root, historicalBuildLocator(root, locator, historical));
|
|
76
47
|
const loaded = loadBuildchainConfig(path.resolve(root, project.cwd));
|
|
77
48
|
const build = normalizeBuildConfiguration(loaded.config.build);
|
|
78
49
|
if (
|
|
@@ -188,6 +159,8 @@ export function resolveBuildConfiguration({
|
|
|
188
159
|
: ".buildchain/contract-lock.json",
|
|
189
160
|
},
|
|
190
161
|
};
|
|
162
|
+
historicalPublishGate(plan, historical, eventName);
|
|
163
|
+
finishHistoricalBuildPlan(plan, historical, root);
|
|
191
164
|
for (const [stage, status] of Object.entries(plan.lifecycle)) {
|
|
192
165
|
if (status.required && !status.configured)
|
|
193
166
|
throw new Error(`buildchain.toml must declare lifecycle.${stage}`);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { containedBuildPath } from "../build-configuration.js";
|
|
1
3
|
import { rootOf } from "./values.js";
|
|
2
4
|
export function assertPlan(plan) {
|
|
3
5
|
const { root, ...body } = plan;
|
|
@@ -43,3 +45,43 @@ export function bindBuildPlan({
|
|
|
43
45
|
if (!workspace) throw new Error("Build workspace is required");
|
|
44
46
|
return { plan, platform, workspace };
|
|
45
47
|
}
|
|
48
|
+
|
|
49
|
+
export function resolveBuildIdentity({ root, workflowRef, workflowSha, repository, callerWorkflowRef }) {
|
|
50
|
+
const match = String(workflowRef).match(
|
|
51
|
+
/^([^/]+\/[^/]+)\/(\.github\/workflows\/[^@]+)@(?:refs\/(?:heads|tags)\/)?(v([1-9]\d*)(-alpha)?)$/u,
|
|
52
|
+
);
|
|
53
|
+
if (!match || match[1] !== repository)
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Ordinary builds require the exact called workflow identity on a floating channel",
|
|
56
|
+
);
|
|
57
|
+
const identity = {
|
|
58
|
+
repository,
|
|
59
|
+
ref: match[3],
|
|
60
|
+
full_ref: `refs/tags/${match[3]}`,
|
|
61
|
+
sha: workflowSha,
|
|
62
|
+
channel: match[5] ? "alpha" : "stable",
|
|
63
|
+
major: match[4],
|
|
64
|
+
visible_workflow: match[2],
|
|
65
|
+
};
|
|
66
|
+
const callerPath = callerWorkflowRef
|
|
67
|
+
.split("@")[0]
|
|
68
|
+
.split("/.github/workflows/")[1];
|
|
69
|
+
if (callerPath) {
|
|
70
|
+
const caller = fs.readFileSync(
|
|
71
|
+
containedBuildPath(root, `.github/workflows/${callerPath}`),
|
|
72
|
+
"utf8",
|
|
73
|
+
);
|
|
74
|
+
const calls = [
|
|
75
|
+
...caller.matchAll(
|
|
76
|
+
/uses:\s+kungfu-systems\/buildchain\/(\.github\/workflows\/(?:build|\.build)\.yml)@(v\d+(?:-alpha)?)\s*$/gmu,
|
|
77
|
+
),
|
|
78
|
+
].filter((call) => call[2] === identity.ref);
|
|
79
|
+
const paths = [...new Set(calls.map((call) => call[1]))];
|
|
80
|
+
if (paths.length !== 1)
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Unable to derive one visible build workflow from the exact caller",
|
|
83
|
+
);
|
|
84
|
+
identity.visible_workflow = paths[0];
|
|
85
|
+
}
|
|
86
|
+
return identity;
|
|
87
|
+
}
|
|
@@ -9,6 +9,7 @@ export function lifecycleOptions(plan, platform, stage, root) {
|
|
|
9
9
|
cwd: path.join(root, plan.project.cwd),
|
|
10
10
|
workspace: root,
|
|
11
11
|
stageName: stage,
|
|
12
|
+
command: plan.lifecycle[stage]?.command || "",
|
|
12
13
|
required: plan.lifecycle[stage]?.required || false,
|
|
13
14
|
timeoutMinutes: plan.build.timeout_minutes,
|
|
14
15
|
artifactName: names.payload,
|
|
@@ -34,6 +34,8 @@ export async function resolveBuildPlan(request, providers) {
|
|
|
34
34
|
callerWorkflowRef: workflow.callerRef,
|
|
35
35
|
eventName: event.name,
|
|
36
36
|
baseRef: source.baseRef,
|
|
37
|
+
compatibilityInputs: request.compatibilityInputs,
|
|
38
|
+
compatibilityEnvironment: request.compatibilityEnvironment,
|
|
37
39
|
});
|
|
38
40
|
const git = (...args) =>
|
|
39
41
|
execFileSync("git", ["-C", sourceRoot, ...args], {
|
|
@@ -58,7 +58,7 @@ export async function aggregate(context, plan, jobs, executions) {
|
|
|
58
58
|
runId: plan.run.id,
|
|
59
59
|
runAttempt: plan.run.attempt,
|
|
60
60
|
},
|
|
61
|
-
publishGate: {
|
|
61
|
+
publishGate: plan.historical_publish_gate || {
|
|
62
62
|
trustedEvent: true,
|
|
63
63
|
channel: "none",
|
|
64
64
|
allowed: false,
|
|
@@ -94,7 +94,9 @@ export async function aggregate(context, plan, jobs, executions) {
|
|
|
94
94
|
artifacts: await readProviderArtifacts(plan.run),
|
|
95
95
|
platforms: plan.platforms,
|
|
96
96
|
artifactName: plan.artifacts.name,
|
|
97
|
-
artifactNameTemplate: "
|
|
97
|
+
artifactNameTemplate: plan.identity.visible_workflow === ".github/workflows/build.yml"
|
|
98
|
+
? plan.artifacts.name_template || "{artifact}-{platform}-{sha}"
|
|
99
|
+
: "{artifact}-final-{platform}-{sha}",
|
|
98
100
|
sourceSha: plan.source.sha,
|
|
99
101
|
sourceRef: plan.source.ref,
|
|
100
102
|
repository: plan.run.repository,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { rootOf } from "../plan/values.js";
|
|
2
|
+
|
|
3
|
+
export function qualifyHistoricalBuild(conclusion, value) {
|
|
4
|
+
if (conclusion !== "success")
|
|
5
|
+
throw new Error(`Build did not qualify: ${conclusion || "missing"}`);
|
|
6
|
+
const result = typeof value === "string" ? JSON.parse(value) : value;
|
|
7
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
8
|
+
throw new Error("Missing rooted build result");
|
|
9
|
+
const { root, ...body } = result;
|
|
10
|
+
if (
|
|
11
|
+
result.schema !== "buildchain.build-result/v1" ||
|
|
12
|
+
result.status !== "success" ||
|
|
13
|
+
root !== rootOf(body)
|
|
14
|
+
)
|
|
15
|
+
throw new Error("Build result failed its contract or content root");
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function qualifyHistoricalBuildAction(core) {
|
|
20
|
+
qualifyHistoricalBuild(
|
|
21
|
+
core.getInput("conclusion", { required: true }),
|
|
22
|
+
core.getInput("result", { required: true }),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { historicalBuildOutputs } from "./historical-outputs.js";
|
|
1
2
|
import { aggregate } from "./aggregation.js";
|
|
2
3
|
import { controllerReceipt } from "./controller-receipt.js";
|
|
3
4
|
import path from "node:path";
|
|
@@ -97,6 +98,22 @@ async function finalizeBuild(context, jobs) {
|
|
|
97
98
|
["diagnostics-summary.json"],
|
|
98
99
|
file(".buildchain/artifacts"),
|
|
99
100
|
);
|
|
101
|
+
let historicalOutputs;
|
|
102
|
+
if (plan.identity.visible_workflow === ".github/workflows/build.yml") {
|
|
103
|
+
artifacts.controller_plan = await upload(
|
|
104
|
+
plan,
|
|
105
|
+
`${plan.artifacts.name}-controller-plan-${plan.source.sha}`,
|
|
106
|
+
["plan.json"],
|
|
107
|
+
file(".buildchain/controller"),
|
|
108
|
+
);
|
|
109
|
+
historicalOutputs = historicalBuildOutputs(
|
|
110
|
+
plan,
|
|
111
|
+
artifacts,
|
|
112
|
+
controller,
|
|
113
|
+
aggregated.summary,
|
|
114
|
+
file,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
100
117
|
const result = {
|
|
101
118
|
schema: "buildchain.build-result/v1",
|
|
102
119
|
status: "success",
|
|
@@ -104,6 +121,7 @@ async function finalizeBuild(context, jobs) {
|
|
|
104
121
|
source: plan.source,
|
|
105
122
|
runtime: plan.identity,
|
|
106
123
|
artifacts,
|
|
124
|
+
...(historicalOutputs ? { historical_outputs: historicalOutputs } : {}),
|
|
107
125
|
};
|
|
108
126
|
const rooted = { ...result, root: rootOf(result) };
|
|
109
127
|
await publishRecord(
|