@kungfu-tech/buildchain 4.0.8 → 4.0.9-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 +12 -19
- package/CONTRIBUTING.md +6 -9
- package/README.md +11 -1
- package/architecture/agent-change-map.md +11 -4
- package/architecture/build-environments.json +71 -0
- package/architecture/build-orchestration.json +18 -0
- package/architecture/ci-lane-change-budget.json +34 -2
- package/architecture/internal-capabilities.json +17 -4
- package/architecture/maintainability-debt.json +34 -123
- package/architecture/maintainability-policy.json +19 -210
- package/architecture/release-topology.json +6 -5
- package/architecture/universal-workflow-bootstrap.json +5 -4
- package/architecture/universal-workflow-train-admission.json +2 -2
- package/bin/buildchain.mjs +1 -1
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/dist/site/buildchain-contract.json +41 -1131
- package/dist/site/buildchain-site.json +39 -144
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +1 -1
- package/dist/site/controller-registry.json +30 -868
- package/dist/site/kfd-claims.json +132 -394
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +34 -34
- package/dist/site/page-registry.json +30 -135
- package/dist/site/public-surface-audit.json +81 -401
- package/dist/site/publication-registry.json +2 -2
- package/dist/site/site-manifest.json +8 -8
- package/dist/site/workflow-registry.json +135 -391
- package/docs/MAP.md +5 -4
- package/docs/cli-reference.md +1 -1
- package/docs/getting-started.md +8 -7
- package/docs/node-api-reference.md +79 -79
- package/docs/reusable-build-surface.md +159 -1430
- package/docs/runtime-train-validation.md +45 -172
- package/package.json +1 -1
- package/packages/core/build-configuration.js +117 -0
- package/packages/core/buildchain-config.js +2 -0
- package/packages/core/buildchain-contract.js +14 -44
- package/packages/core/controller-evidence.js +2 -9
- package/scripts/buildchain-cli-help.mjs +1 -1
- package/scripts/check-floating-consumer-policy-contract.mjs +4 -7
- package/scripts/check-inventory.mjs +81 -54
- package/scripts/check-universal-workflow-bootstrap.mjs +12 -2
- package/scripts/generate-channel-build-workflow.mjs +50 -410
- package/scripts/generate-site-bundle.mjs +2 -1
- package/scripts/generate-universal-workflow-facades.mjs +1 -0
- package/scripts/init-repo.mjs +25 -26
- package/scripts/resolve-build-configuration.mjs +105 -0
- package/scripts/universal-workflow-self-dogfood.mjs +8 -1
- package/scripts/verify-golden-path.mjs +3 -3
|
@@ -6,183 +6,56 @@ doc_type: technical-reference
|
|
|
6
6
|
source_level: local-files
|
|
7
7
|
confidence: high
|
|
8
8
|
sensitivity: public
|
|
9
|
-
evidence_grade:
|
|
9
|
+
evidence_grade: B
|
|
10
10
|
review_state: unreviewed
|
|
11
|
-
last_reviewed: 2026-
|
|
11
|
+
last_reviewed: 2026-09-08
|
|
12
12
|
ai_provenance:
|
|
13
|
-
model_family: GPT-
|
|
13
|
+
model_family: GPT-6
|
|
14
14
|
product: Codex
|
|
15
|
-
generated_at: 2026-
|
|
15
|
+
generated_at: 2026-09-08
|
|
16
16
|
invisible_context: not asserted
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
# Runtime Train Validation
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
git push origin HEAD:refs/heads/train/v3/v3.0/<capability>
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Use a capability slug that names the behavior being validated, for example:
|
|
66
|
-
|
|
67
|
-
```text
|
|
68
|
-
train/v3/v3.0/runtime-loader
|
|
69
|
-
train/v3/v3.0/toolkit-diagnostics
|
|
70
|
-
train/v3/v3.0/site-source-of-truth
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
The pull request or validation request should include the train ref, the exact
|
|
74
|
-
commit SHA it points to, and the downstream evidence expected from consumers.
|
|
75
|
-
If the train is refreshed, state the new SHA in the validation thread.
|
|
76
|
-
|
|
77
|
-
After downstream validation succeeds, close out through the normal release
|
|
78
|
-
path. Merge the Buildchain pull request into the active `dev/*` mainline, run
|
|
79
|
-
the requested alpha or release promotion, and record the final mainline commit
|
|
80
|
-
plus release ref or tag in the delivery thread. Do not leave the train as the
|
|
81
|
-
item that still needs to be merged; it is only a temporary fast-use,
|
|
82
|
-
diagnostic, and rollback channel for initiating repositories. Retained trains
|
|
83
|
-
are cleaned up by a separate periodic Buildchain cleanup task.
|
|
84
|
-
|
|
85
|
-
## Formal artifact-signing authority ref
|
|
86
|
-
|
|
87
|
-
Artifact signing uses a durable, channel-neutral authority ref after its
|
|
88
|
-
runtime has passed downstream validation:
|
|
89
|
-
|
|
90
|
-
```text
|
|
91
|
-
authority/v3/v3.0/artifact-signing
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Unlike a train, this ref is a protected execution boundary. Alpha and stable
|
|
95
|
-
release intent use the same authority ref and the same
|
|
96
|
-
`buildchain-artifact-signing` environment; channel promotion never selects a
|
|
97
|
-
different certificate environment. Updates to the authority ref require a
|
|
98
|
-
reviewed pull request, the normal `check` and `verify` status contexts, and a
|
|
99
|
-
fast-forward-safe protected branch policy. Deletion and non-fast-forward
|
|
100
|
-
updates are forbidden.
|
|
101
|
-
|
|
102
|
-
The temporary `train/v3/v3.0/artifact-signing-authority` ref remains a bounded
|
|
103
|
-
rollback and diagnostic pointer during migration. It is not the production
|
|
104
|
-
identity and must not regain credential ownership.
|
|
105
|
-
|
|
106
|
-
## Consumer workflow requirement
|
|
107
|
-
|
|
108
|
-
Consumers keep their reusable workflow pinned to the stable shell:
|
|
109
|
-
|
|
110
|
-
```yaml
|
|
111
|
-
jobs:
|
|
112
|
-
build:
|
|
113
|
-
uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
To validate a train without committing temporary workflow refs, expose a
|
|
117
|
-
trusted manual pass-through once:
|
|
118
|
-
|
|
119
|
-
```yaml
|
|
120
|
-
on:
|
|
121
|
-
workflow_dispatch:
|
|
122
|
-
inputs:
|
|
123
|
-
buildchain-ref:
|
|
124
|
-
description: "Temporary Buildchain runtime ref for trusted manual validation"
|
|
125
|
-
required: false
|
|
126
|
-
default: ""
|
|
127
|
-
|
|
128
|
-
jobs:
|
|
129
|
-
build:
|
|
130
|
-
uses: kungfu-systems/buildchain/.github/workflows/.build.yml@v3
|
|
131
|
-
with:
|
|
132
|
-
buildchain-ref: ${{ inputs.buildchain-ref || '' }}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Buildchain initializes new package workflows with this pass-through. Existing
|
|
136
|
-
consumers that do not have it should add it once before validating a train.
|
|
137
|
-
|
|
138
|
-
## Validation request
|
|
139
|
-
|
|
140
|
-
Use this short request when a train is ready:
|
|
141
|
-
|
|
142
|
-
```text
|
|
143
|
-
Buildchain train ready: buildchain-ref=train/v3/v3.0/<capability>.
|
|
144
|
-
Keep uses: ...@v3; run workflow_dispatch with that buildchain-ref and report the runtime evidence summary.
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
The consumer should run a trusted `workflow_dispatch`, paste the train ref into
|
|
148
|
-
`buildchain-ref`, and report the workflow summary or aggregate Buildchain
|
|
149
|
-
summary. The evidence should include:
|
|
150
|
-
|
|
151
|
-
- workflow shell ref;
|
|
152
|
-
- requested runtime ref;
|
|
153
|
-
- resolved runtime ref;
|
|
154
|
-
- resolved runtime SHA;
|
|
155
|
-
- stability class;
|
|
156
|
-
- trust decision;
|
|
157
|
-
- rollback ref.
|
|
158
|
-
|
|
159
|
-
## Trust and limitation
|
|
160
|
-
|
|
161
|
-
Official floating channel refs such as `v3` and `v3-alpha` may be selected on
|
|
162
|
-
pull requests and pushes. Train refs and arbitrary exact-SHA overrides still
|
|
163
|
-
fail closed unless the event is `workflow_dispatch` and the actor has write,
|
|
164
|
-
maintain, or admin permission on the caller repository. Pull requests,
|
|
165
|
-
including fork-originated pull requests, cannot use train or exact-SHA
|
|
166
|
-
overrides.
|
|
167
|
-
|
|
168
|
-
That permission does not create a third channel. A train, authority ref, or
|
|
169
|
-
exact SHA must be bound to an alpha or stable shell lane, and the consumer lock
|
|
170
|
-
must prove that same lane and major. Trusted overrides can replace the runtime
|
|
171
|
-
coordinate for validation; they cannot combine a stable shell or lock with an
|
|
172
|
-
alpha runtime, or the reverse.
|
|
173
|
-
|
|
174
|
-
Runtime train validation covers Buildchain runtime scripts, CLI code, local
|
|
175
|
-
actions, configuration parsing, and lifecycle behavior. It cannot validate
|
|
176
|
-
changes that require the outer reusable workflow YAML itself to change, such as
|
|
177
|
-
new jobs, permissions, workflow outputs, or matrix topology. Those changes need
|
|
178
|
-
a canary workflow path or a temporary explicit workflow ref.
|
|
179
|
-
|
|
180
|
-
## v4 persisted-selector boundary
|
|
181
|
-
|
|
182
|
-
For v4 consumers, never commit a train or exact SHA in a `uses` node. Keep the
|
|
183
|
-
caller on `@v4` or `@v4-alpha`, retain both stable and alpha contract locks, and
|
|
184
|
-
pass a temporary train/SHA only through the trusted `workflow_dispatch`
|
|
185
|
-
`buildchain-ref` input. Consumer admission binds the selected lock to the exact
|
|
186
|
-
workflow-shell commit while recording the separately resolved runtime SHA.
|
|
187
|
-
Therefore a train can exercise candidate runtime code without weakening the
|
|
188
|
-
durable floating-selector policy or masquerading as a channel promotion.
|
|
21
|
+
Ordinary builds use the public `build.yml@v4` or `build.yml@v4-alpha`
|
|
22
|
+
workflow and select all project settings from `buildchain.toml`. The called
|
|
23
|
+
workflow SHA also selects the runtime; there is no second runtime selector.
|
|
24
|
+
See [Reusable Build Surface](reusable-build-surface.md) for configuration,
|
|
25
|
+
zero-input calls and the optional nested-project locator.
|
|
26
|
+
|
|
27
|
+
## Alpha qualification
|
|
28
|
+
|
|
29
|
+
Buildchain changes enter the protected development branch after review and
|
|
30
|
+
checks. Publish an alpha through the protected release workflow, then exercise
|
|
31
|
+
that public alpha on the exact consumer source. Record the called workflow SHA,
|
|
32
|
+
source SHA, channel-matching contract lock, configuration root and artifact
|
|
33
|
+
manifests. Promote stable only after the required alpha evidence succeeds.
|
|
34
|
+
|
|
35
|
+
For a breaking build-interface change, publish the producer before changing
|
|
36
|
+
its own public consumers. Retain the callers and locks for the currently
|
|
37
|
+
published interface while creating the first alpha from a successful PR-stage
|
|
38
|
+
candidate. Then migrate the callers and accept the published alpha contract,
|
|
39
|
+
qualify that public interface, and publish the completed alpha and stable.
|
|
40
|
+
This ordering adds no compatibility inputs to the new build workflow.
|
|
41
|
+
|
|
42
|
+
Source checks verify each consumer lock against the immutable source contract
|
|
43
|
+
at its accepted SHA. Hosted consumer admission separately verifies the actual
|
|
44
|
+
called floating workflow; unpublished producer code is not that dependency.
|
|
45
|
+
|
|
46
|
+
A train branch is a temporary diagnostic pointer, never a release channel or a
|
|
47
|
+
persisted consumer dependency. Ordinary builds do not accept train, SHA or
|
|
48
|
+
`buildchain-ref` inputs. Initialization does not create a runtime pass-through.
|
|
49
|
+
|
|
50
|
+
## Specialized release and recovery
|
|
51
|
+
|
|
52
|
+
Release and recovery entry points retain their own bounded runtime admission
|
|
53
|
+
contracts. Their runtime override capability does not extend to ordinary builds.
|
|
54
|
+
Follow [Release Flow](release-flow.md) and the particular entry point's contract;
|
|
55
|
+
a diagnostic train does not authorize publication, signing, or floating-ref
|
|
56
|
+
movement. Preserve existing source locks, exact candidate lineage and terminal
|
|
57
|
+
receipts when recovering a release.
|
|
58
|
+
|
|
59
|
+
The Buildchain self-build callers exercise the public floating channels using
|
|
60
|
+
root or fixture TOML. They do not commit a private runtime selector to qualify
|
|
61
|
+
that channel.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.9-alpha.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
// Project choices only. Provider endpoints, roles, runner labels and cache
|
|
5
|
+
// transport authority belong to the selected, runtime-owned environment.
|
|
6
|
+
const sections = {
|
|
7
|
+
tools: { node: "24", rust: "" },
|
|
8
|
+
artifacts: {
|
|
9
|
+
name: "buildchain-artifact", paths: ["dist", "build/stage"],
|
|
10
|
+
required_paths: [], min_files: 0, max_files: Number.MAX_SAFE_INTEGER,
|
|
11
|
+
min_total_bytes: 0, retention_days: 14, compression_level: 0,
|
|
12
|
+
release_candidate: false,
|
|
13
|
+
},
|
|
14
|
+
diagnostics: {
|
|
15
|
+
sample_process_tree: true, sample_interval_ms: 15000,
|
|
16
|
+
requested_parallelism: 0,
|
|
17
|
+
},
|
|
18
|
+
verification: { substage_evidence_path: "" },
|
|
19
|
+
finalization: { command: "", on_platform: false },
|
|
20
|
+
transport_smoke: { scenario_path: "", artifact_root: "." },
|
|
21
|
+
attestation: { subject_path: "", platform: "linux-x64" },
|
|
22
|
+
macos_signing: { app_path: "", platform: "macos-arm64" },
|
|
23
|
+
contract: {
|
|
24
|
+
compatibility_policy: "major-compatible",
|
|
25
|
+
drift_issue_mode: "compatible-and-breaking",
|
|
26
|
+
},
|
|
27
|
+
evidence: { gate_profile_path: "", candidate_family_path: "" },
|
|
28
|
+
};
|
|
29
|
+
const choices = {
|
|
30
|
+
"contract.compatibility_policy": ["major-compatible", "allow-additive", "exact"],
|
|
31
|
+
"contract.drift_issue_mode": ["off", "breaking-only", "compatible-and-breaking"],
|
|
32
|
+
};
|
|
33
|
+
const ranges = {
|
|
34
|
+
timeout_minutes: [1, 360],
|
|
35
|
+
"artifacts.retention_days": [1, 90],
|
|
36
|
+
"artifacts.compression_level": [0, 9],
|
|
37
|
+
"artifacts.min_files": [0, Number.MAX_SAFE_INTEGER],
|
|
38
|
+
"artifacts.max_files": [0, Number.MAX_SAFE_INTEGER],
|
|
39
|
+
"artifacts.min_total_bytes": [0, Number.MAX_SAFE_INTEGER],
|
|
40
|
+
"diagnostics.sample_interval_ms": [100, 3600000],
|
|
41
|
+
"diagnostics.requested_parallelism": [0, 1024],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function table(value, label) {
|
|
45
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
46
|
+
throw new Error(`${label} must be a table`);
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fields(value, defaults, prefix) {
|
|
52
|
+
table(value, `build.${prefix}`);
|
|
53
|
+
for (const key of Object.keys(value)) {
|
|
54
|
+
if (!Object.hasOwn(defaults, key)) throw new Error(`Unknown build.${prefix}${key}`);
|
|
55
|
+
}
|
|
56
|
+
return Object.fromEntries(Object.entries(defaults).map(([key, fallback]) => {
|
|
57
|
+
const label = `${prefix}${key}`;
|
|
58
|
+
const selected = value[key] === undefined ? structuredClone(fallback) : value[key];
|
|
59
|
+
const valid = Array.isArray(fallback)
|
|
60
|
+
? Array.isArray(selected) && selected.every((item) => typeof item === "string" && item.trim())
|
|
61
|
+
: typeof selected === typeof fallback;
|
|
62
|
+
if (!valid || (typeof selected === "string" && /\0/u.test(selected))) {
|
|
63
|
+
throw new Error(`Invalid build.${label} type or multiline value`);
|
|
64
|
+
}
|
|
65
|
+
if (ranges[label] && (!Number.isInteger(selected) || selected < ranges[label][0] || selected > ranges[label][1])) {
|
|
66
|
+
throw new Error(`build.${label} must be an integer in ${ranges[label].join("..")}`);
|
|
67
|
+
}
|
|
68
|
+
if (choices[label] && !choices[label].includes(selected)) throw new Error(`Invalid build.${label}: ${selected}`);
|
|
69
|
+
return [key, selected];
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function normalizeBuildConfiguration(value = {}) {
|
|
74
|
+
table(value, "build");
|
|
75
|
+
const { environment = "github-hosted", fail_fast = false, timeout_minutes = 120, ...groups } = value;
|
|
76
|
+
const result = fields({ environment, fail_fast, timeout_minutes }, { environment: "github-hosted", fail_fast: false, timeout_minutes: 120 }, "");
|
|
77
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(result.environment)) throw new Error("Invalid build.environment profile name");
|
|
78
|
+
for (const name of Object.keys(groups)) {
|
|
79
|
+
if (!Object.hasOwn(sections, name)) throw new Error(`Unknown build.${name}`);
|
|
80
|
+
}
|
|
81
|
+
for (const [name, defaults] of Object.entries(sections)) {
|
|
82
|
+
result[name] = fields(groups[name] === undefined ? {} : groups[name], defaults, `${name}.`);
|
|
83
|
+
}
|
|
84
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/u.test(result.artifacts.name)) throw new Error("Invalid build.artifacts.name");
|
|
85
|
+
if (result.artifacts.min_files > result.artifacts.max_files) throw new Error("build.artifacts.min_files exceeds max_files");
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function containedBuildPath(root, relative) {
|
|
90
|
+
if (typeof relative !== "string" || !relative || path.isAbsolute(relative) || relative.includes("\\") || /[\r\n\0]/u.test(relative) || relative.split("/").includes("..")) {
|
|
91
|
+
throw new Error("Build configuration paths must remain repository-relative");
|
|
92
|
+
}
|
|
93
|
+
const resolved = path.resolve(root, relative);
|
|
94
|
+
const realRoot = fs.realpathSync(root);
|
|
95
|
+
// Reject symlink escape through any existing parent, including a missing leaf.
|
|
96
|
+
let ancestor = resolved;
|
|
97
|
+
while (!fs.existsSync(ancestor)) ancestor = path.dirname(ancestor);
|
|
98
|
+
const realAncestor = fs.realpathSync(ancestor);
|
|
99
|
+
if (realAncestor !== realRoot && !realAncestor.startsWith(`${realRoot}${path.sep}`)) {
|
|
100
|
+
throw new Error(`Build configuration path escapes repository: ${relative}`);
|
|
101
|
+
}
|
|
102
|
+
return resolved;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function discoverBuildConfiguration(root, locator = "") {
|
|
106
|
+
const candidates = locator ? [locator] : [".buildchain/buildchain.toml", "buildchain.toml"];
|
|
107
|
+
const found = candidates.filter((candidate) => fs.existsSync(containedBuildPath(root, candidate)));
|
|
108
|
+
if (found.length !== 1) throw new Error(`Expected one buildchain.toml; found ${found.length}. Use config-path for a project outside the repository root.`);
|
|
109
|
+
const configPath = found[0];
|
|
110
|
+
if (path.basename(configPath) !== "buildchain.toml") throw new Error("config-path must locate a project's buildchain.toml");
|
|
111
|
+
const directory = path.posix.dirname(configPath);
|
|
112
|
+
const cwd = path.posix.basename(directory) === ".buildchain" ? path.posix.dirname(directory) : directory;
|
|
113
|
+
// Lifecycle and planning must discover the same file, never a second config.
|
|
114
|
+
const projectConfigs = [".buildchain/buildchain.toml", "buildchain.toml"].filter((candidate) => fs.existsSync(containedBuildPath(root, path.posix.join(cwd, candidate))));
|
|
115
|
+
if (projectConfigs.length !== 1) throw new Error("Ambiguous project configuration");
|
|
116
|
+
return { configPath, cwd };
|
|
117
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveBuildchainConfigPath,
|
|
9
9
|
} from "./buildchain-layout.js";
|
|
10
10
|
import { runShellCommandSync } from "./spawn-command.js";
|
|
11
|
+
import { normalizeBuildConfiguration } from "./build-configuration.js";
|
|
11
12
|
|
|
12
13
|
const CONFIG_FILE = BUILDCHAIN_CONFIG_PATH;
|
|
13
14
|
const RESERVED_LIFECYCLE_KEYS = new Set(["env", "shell"]);
|
|
@@ -206,6 +207,7 @@ export function loadBuildchainConfig(cwd = process.cwd()) {
|
|
|
206
207
|
export function normalizeBuildchainConfig(config) {
|
|
207
208
|
assertPlainObject(config, CONFIG_FILE);
|
|
208
209
|
const normalized = { ...config };
|
|
210
|
+
if (normalized.build !== undefined) normalized.build = normalizeBuildConfiguration(normalized.build);
|
|
209
211
|
if (normalized.project !== undefined) {
|
|
210
212
|
normalized.project = normalizeProjectSection(normalized.project);
|
|
211
213
|
}
|
|
@@ -236,24 +236,11 @@ export function createBuildchainContractWorld({
|
|
|
236
236
|
"release-candidate-artifact",
|
|
237
237
|
],
|
|
238
238
|
breakingDefaults: {
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
configurationAuthority: "buildchain.toml",
|
|
240
|
+
runtimeIdentity: "exact-called-workflow",
|
|
241
|
+
projectDiscovery: "unambiguous-root-or-config-path",
|
|
241
242
|
},
|
|
242
|
-
optionalInputs: [
|
|
243
|
-
"buildchain-ref",
|
|
244
|
-
"runner-preset",
|
|
245
|
-
"platforms-json",
|
|
246
|
-
"release-candidate",
|
|
247
|
-
"github-artifact-attestation-subject-path",
|
|
248
|
-
"github-artifact-attestation-platform-id",
|
|
249
|
-
"artifact-transfer-mode",
|
|
250
|
-
"checkout-cache-mode",
|
|
251
|
-
"checkout-cache-fallback",
|
|
252
|
-
"checkout-cache-timeout-seconds",
|
|
253
|
-
"checkout-cache-fetch-attempts",
|
|
254
|
-
"buildchain-contract-lock-path",
|
|
255
|
-
"buildchain-contract-drift-issue-mode",
|
|
256
|
-
],
|
|
243
|
+
optionalInputs: ["config-path"],
|
|
257
244
|
guarantees: [
|
|
258
245
|
"runtime floating refs are resolved to immutable SHAs before matrix jobs",
|
|
259
246
|
"publish source locks are verified before heavy build jobs",
|
|
@@ -270,26 +257,17 @@ export function createBuildchainContractWorld({
|
|
|
270
257
|
requiredInputs: [],
|
|
271
258
|
requiredOutputs: ["buildchain-channel", "buildchain-runtime-sha", "build-summary-artifact"],
|
|
272
259
|
breakingDefaults: {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
stableReleaseDefault: "stable",
|
|
277
|
-
ambiguousReleasePolicy: "fail-closed",
|
|
260
|
+
configurationAuthority: "buildchain.toml",
|
|
261
|
+
runtimeIdentity: "exact-called-workflow",
|
|
262
|
+
projectDiscovery: "unambiguous-root-or-config-path",
|
|
278
263
|
},
|
|
279
|
-
optionalInputs: [
|
|
280
|
-
"buildchain-channel",
|
|
281
|
-
"buildchain-ref",
|
|
282
|
-
"buildchain-alpha-contract-lock-path",
|
|
283
|
-
"buildchain-stable-contract-lock-path",
|
|
284
|
-
"buildchain-contract-lock-path",
|
|
285
|
-
],
|
|
264
|
+
optionalInputs: ["config-path"],
|
|
286
265
|
guarantees: [
|
|
287
|
-
"consumers
|
|
288
|
-
"
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"alpha and stable channel selections use separate consumer contract locks by default",
|
|
266
|
+
"single-project consumers require no workflow inputs",
|
|
267
|
+
"project settings have one TOML authority and environment profiles own infrastructure",
|
|
268
|
+
"the called floating ref determines channel and exact runtime SHA",
|
|
269
|
+
"stable and alpha select their matching repository contract locks",
|
|
270
|
+
"source, runtime and configuration roots are bound before the build matrix",
|
|
293
271
|
],
|
|
294
272
|
}),
|
|
295
273
|
surface(root, devDeliveryWorkflowContractSurface(pkg, majorLine)),
|
|
@@ -837,7 +815,7 @@ export function createBuildchainContractWorld({
|
|
|
837
815
|
requiredStages: descriptor.expected.stages.filter((stage) => stage.required).map((stage) => stage.id),
|
|
838
816
|
capabilities: descriptor.expected.capabilities,
|
|
839
817
|
evidenceRequirements: descriptor.expected.evidence,
|
|
840
|
-
inputClassificationPolicy: "buildchain-controller-input-policy/v1",
|
|
818
|
+
inputClassificationPolicy: descriptor.id === "build-lifecycle" ? "buildchain-configured-controller-input-policy/v1" : "buildchain-controller-input-policy/v1",
|
|
841
819
|
},
|
|
842
820
|
optionalInputs: Object.keys(descriptor.inputs),
|
|
843
821
|
controllerDescriptor: {
|
|
@@ -846,14 +824,6 @@ export function createBuildchainContractWorld({
|
|
|
846
824
|
registryDigest: resolvedControllerRegistry.digest,
|
|
847
825
|
inputClassifications: descriptor.inputs,
|
|
848
826
|
},
|
|
849
|
-
...(descriptor.id === "build-lifecycle"
|
|
850
|
-
? {
|
|
851
|
-
compatibleBreakingDigests: [
|
|
852
|
-
"sha256:e264a79f9f399038c2fcfd21e4168c68c2e1485ee5c651c02242a02b622ac2be",
|
|
853
|
-
"sha256:30745921541e9b0f70475bb2178c2559f6aef248f6680670ccd44d8c5a69a6b1",
|
|
854
|
-
],
|
|
855
|
-
}
|
|
856
|
-
: {}),
|
|
857
827
|
guarantees: [
|
|
858
828
|
"plans bind exact consumer source SHA, exact Buildchain runtime SHA, and the runtime contract digest",
|
|
859
829
|
"receipts bind the plan digest and preserve pass, fail, skip, and partial stage outcomes",
|
|
@@ -22,6 +22,7 @@ const CONTROLLER_SPECS = [
|
|
|
22
22
|
id: "build-lifecycle",
|
|
23
23
|
workflowId: ".build",
|
|
24
24
|
version: 1,
|
|
25
|
+
derivedInputs: { "configuration-root": { classification: "included", source: "resolved-build-plan" } },
|
|
25
26
|
capabilities: [
|
|
26
27
|
"source-lock",
|
|
27
28
|
"lifecycle-build",
|
|
@@ -47,15 +48,6 @@ const CONTROLLER_SPECS = [
|
|
|
47
48
|
"controller-receipt",
|
|
48
49
|
],
|
|
49
50
|
},
|
|
50
|
-
{
|
|
51
|
-
id: "build-channel-router",
|
|
52
|
-
workflowId: "build",
|
|
53
|
-
version: 1,
|
|
54
|
-
capabilities: ["channel-selection", "runtime-selection", "build-delegation"],
|
|
55
|
-
stages: ["resolve-channel", "override", "alpha", "stable", "aggregate"],
|
|
56
|
-
optionalStages: ["override", "alpha", "stable"],
|
|
57
|
-
evidence: ["nested-controller-receipt", "controller-receipt"],
|
|
58
|
-
},
|
|
59
51
|
{
|
|
60
52
|
id: "shifu-gate-profile-envelope",
|
|
61
53
|
workflowId: ".gate-profile",
|
|
@@ -208,6 +200,7 @@ export function createControllerRegistry({ workflows = [] } = {}) {
|
|
|
208
200
|
classification: classifyInput(name, secrets),
|
|
209
201
|
source: secrets.has(name) ? "workflow-call-secret" : "workflow-call-input",
|
|
210
202
|
}]));
|
|
203
|
+
Object.assign(inputs, spec.derivedInputs || {});
|
|
211
204
|
for (const [name, policy] of Object.entries(workflow.inputPolicies || {})) {
|
|
212
205
|
if (!inputs[name]) throw new Error(`controller ${spec.id} input policy references undeclared input ${name}`);
|
|
213
206
|
inputs[name] = { ...inputs[name], ...policy };
|
|
@@ -24,7 +24,7 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
24
24
|
buildchain candidate timeline --input <file-or-json> [--output <file>] [--json]
|
|
25
25
|
buildchain init [--cwd <dir>] [--type package|native|web-surface|infra-contract|publication-artifact|anchored-package] [--force]
|
|
26
26
|
[--package-manager pnpm|npm|yarn] [--runner-preset <preset>]
|
|
27
|
-
[--artifact-name <
|
|
27
|
+
[--artifact-name <name>]
|
|
28
28
|
buildchain validate [--cwd <dir>] [--require-version-state]
|
|
29
29
|
[--require-lifecycle-stages <comma-list>]
|
|
30
30
|
buildchain lifecycle run <stage> [--cwd <dir>] [--required]
|
|
@@ -123,15 +123,12 @@ export function checkFloatingConsumerPolicyContract() {
|
|
|
123
123
|
]);
|
|
124
124
|
const buildWorkflow = read(".github/workflows/.build.yml");
|
|
125
125
|
if (
|
|
126
|
-
|
|
126
|
+
buildWorkflow.includes("Normalize v3 expected identity aliases") ||
|
|
127
|
+
buildWorkflow.includes("buildchain-expected-channel:") ||
|
|
127
128
|
!buildWorkflow.includes(
|
|
128
|
-
"
|
|
129
|
+
"BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: ${{ fromJSON(needs.configure.outputs.plan-json).identity.channel }}",
|
|
129
130
|
) ||
|
|
130
|
-
!buildWorkflow.includes(
|
|
131
|
-
"BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: ${{ steps.expected-identity.outputs.expected-channel }}",
|
|
132
|
-
) ||
|
|
133
|
-
buildWorkflow.indexOf("Normalize v3 expected identity aliases") >
|
|
134
|
-
buildWorkflow.indexOf("Enforce v4 floating consumer policy")
|
|
131
|
+
!buildWorkflow.includes("BUILDCHAIN_WORKFLOW_SHA: ${{ job.workflow_sha }}")
|
|
135
132
|
)
|
|
136
133
|
fail("channel builds must disambiguate dual-channel caller invocations");
|
|
137
134
|
assertTrustGatedJobs(read(".github/workflows/.build.yml"), [
|