@kungfu-tech/buildchain 0.0.0-bootstrap.0 → 2.0.13-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -0
- package/bin/buildchain.mjs +222 -0
- package/docs/cli.md +124 -0
- package/docs/lifecycle-protocol.md +422 -0
- package/docs/publish-transaction.md +285 -0
- package/docs/reusable-build-surface.md +350 -0
- package/docs/web-surface-deployments.md +211 -0
- package/package.json +52 -1
- package/packages/core/README.md +15 -0
- package/packages/core/buildchain-config.js +721 -0
- package/packages/core/index.js +40 -0
- package/packages/core/package-manager.js +291 -0
- package/packages/core/publish-transaction.js +418 -0
- package/packages/core/release-line-dry-run.js +296 -0
- package/scripts/aggregate-build-summary.mjs +88 -0
- package/scripts/build-contract-core.mjs +731 -0
- package/scripts/check-inventory.mjs +325 -0
- package/scripts/init-repo.mjs +316 -0
- package/scripts/npm-publish-dry-run.mjs +176 -0
- package/scripts/npm-publish-transaction.mjs +268 -0
- package/scripts/publish-source-ref-resolver.mjs +113 -0
- package/scripts/release-line-dry-run.mjs +53 -0
- package/scripts/release-line-policy.mjs +141 -0
- package/scripts/release-transaction.mjs +212 -0
- package/scripts/resolve-build-contract.mjs +63 -0
- package/scripts/resolve-publish-gate.mjs +33 -0
- package/scripts/resolve-publish-source.mjs +99 -0
- package/scripts/run-lifecycle-core.mjs +162 -0
- package/scripts/run-lifecycle.mjs +40 -0
- package/scripts/strip-trailing-whitespace.mjs +14 -0
- package/scripts/tsup-action.config.mjs +19 -0
- package/scripts/verify-publish-source-lock.mjs +37 -0
- package/scripts/verify-release-pr.mjs +34 -0
- package/scripts/web-surface-core.mjs +382 -0
- package/scripts/web-surface.mjs +112 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# Lifecycle Protocol
|
|
2
|
+
|
|
3
|
+
Buildchain uses `buildchain.toml` as the v2 repository configuration format.
|
|
4
|
+
The file is optional for simple JavaScript repositories, but it is the preferred
|
|
5
|
+
way to describe release version state and lifecycle commands when a project is
|
|
6
|
+
not a plain pnpm, npm, or yarn workspace.
|
|
7
|
+
|
|
8
|
+
Only TOML is supported in v2. YAML, JSON, and JavaScript config files are not
|
|
9
|
+
loaded.
|
|
10
|
+
|
|
11
|
+
## Minimal File
|
|
12
|
+
|
|
13
|
+
```toml
|
|
14
|
+
schema = 1
|
|
15
|
+
|
|
16
|
+
[version]
|
|
17
|
+
required = true
|
|
18
|
+
|
|
19
|
+
[[version.files]]
|
|
20
|
+
type = "json"
|
|
21
|
+
path = "package.json"
|
|
22
|
+
key = "version"
|
|
23
|
+
|
|
24
|
+
[lifecycle.verify]
|
|
25
|
+
commands = [
|
|
26
|
+
"pnpm run check",
|
|
27
|
+
]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`schema = 1` is required. Buildchain fails closed when the schema is missing or
|
|
31
|
+
unknown.
|
|
32
|
+
|
|
33
|
+
## Version State
|
|
34
|
+
|
|
35
|
+
Version state is the source file evidence that matches a release tag. During
|
|
36
|
+
promotion, Buildchain writes the selected release or prerelease version into the
|
|
37
|
+
configured files, verifies the resulting tree, creates a source version commit,
|
|
38
|
+
then moves exact and floating refs.
|
|
39
|
+
|
|
40
|
+
Supported version file types:
|
|
41
|
+
|
|
42
|
+
| Type | Use case | Required fields |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `json` | `package.json`, JSON manifests | `path`, `key` |
|
|
45
|
+
| `toml` | `pyproject.toml`, other TOML manifests | `path`, `key` |
|
|
46
|
+
| `regex` | `CMakeLists.txt`, `conanfile.py`, plain version files | `path`, `pattern`, `replacement` |
|
|
47
|
+
|
|
48
|
+
`key` is a dotted key path:
|
|
49
|
+
|
|
50
|
+
```toml
|
|
51
|
+
[[version.files]]
|
|
52
|
+
type = "toml"
|
|
53
|
+
path = "pyproject.toml"
|
|
54
|
+
key = "project.version"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Regex files must expose the current version through a named capture group called
|
|
58
|
+
`version`:
|
|
59
|
+
|
|
60
|
+
```toml
|
|
61
|
+
[[version.files]]
|
|
62
|
+
type = "regex"
|
|
63
|
+
path = "CMakeLists.txt"
|
|
64
|
+
pattern = 'project\([^)]* VERSION (?<version>[^ )]+)'
|
|
65
|
+
replacement = '${version}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If `version.required = true`, promotion fails when no configured version files
|
|
69
|
+
are available.
|
|
70
|
+
|
|
71
|
+
### Anchored Manual Versions
|
|
72
|
+
|
|
73
|
+
Some repositories do not derive their package version from the Buildchain
|
|
74
|
+
release tag. `libnode` is the canonical example: the package version is anchored
|
|
75
|
+
to an explicitly selected upstream Node.js release such as `22.22.3-kf.0`, while
|
|
76
|
+
the channel line may be `release/v22/v22.22`.
|
|
77
|
+
|
|
78
|
+
Those repositories can opt into anchored manual semantics:
|
|
79
|
+
|
|
80
|
+
```toml
|
|
81
|
+
[version]
|
|
82
|
+
required = true
|
|
83
|
+
strategy = "anchored"
|
|
84
|
+
next = "manual"
|
|
85
|
+
manifest = "libnode.release.json"
|
|
86
|
+
|
|
87
|
+
[[version.files]]
|
|
88
|
+
type = "json"
|
|
89
|
+
path = "package.json"
|
|
90
|
+
key = "version"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With `strategy = "anchored"` and `next = "manual"`:
|
|
94
|
+
|
|
95
|
+
- Buildchain validates the configured version files and anchor manifest, but it
|
|
96
|
+
does not rewrite those files to the Buildchain release tag.
|
|
97
|
+
- `lifecycle.verify` is the project-owned truth gate. It should compare the
|
|
98
|
+
package version, anchor manifest, and upstream source/submodule state.
|
|
99
|
+
- release promotion still creates the exact/floating production refs for the
|
|
100
|
+
current line;
|
|
101
|
+
- release promotion does not auto-create the next alpha branch or tag;
|
|
102
|
+
- the action output `next-anchor-required` is `true`, signaling that the next
|
|
103
|
+
upstream anchor line must be created explicitly by the repository.
|
|
104
|
+
|
|
105
|
+
The configured anchor manifest must be JSON or TOML. Buildchain does not
|
|
106
|
+
interpret project-specific field names; it only loads the manifest and exposes
|
|
107
|
+
its top-level fields to validation summaries and lifecycle environment:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
BUILDCHAIN_VERSION_STRATEGY=anchored
|
|
111
|
+
BUILDCHAIN_VERSION_NEXT=manual
|
|
112
|
+
BUILDCHAIN_ANCHOR_MANIFEST=libnode.release.json
|
|
113
|
+
BUILDCHAIN_ANCHOR_MANIFEST_JSON={"nodeTag":"v22.22.3",...}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The upstream anchor decision remains outside Buildchain. A future line such as
|
|
117
|
+
`dev/v24/v24.xx` should be created by an explicit repository workflow or human
|
|
118
|
+
decision after the upstream version has been selected and checked in.
|
|
119
|
+
|
|
120
|
+
## Lifecycle Stages
|
|
121
|
+
|
|
122
|
+
Lifecycle stages are declarative shell commands. A stage can use exactly one of:
|
|
123
|
+
|
|
124
|
+
- `command`: one shell command;
|
|
125
|
+
- `commands`: multiple shell commands run in order;
|
|
126
|
+
- `script`: a multiline shell script.
|
|
127
|
+
|
|
128
|
+
Any command failure fails the stage. `timeout_minutes`, `retries`, `shell`, and
|
|
129
|
+
`env` can be attached to a stage.
|
|
130
|
+
|
|
131
|
+
During version-state verification, Buildchain also sets `BUILDCHAIN_VERSION` to
|
|
132
|
+
the release or prerelease version being verified.
|
|
133
|
+
|
|
134
|
+
```toml
|
|
135
|
+
[lifecycle.install]
|
|
136
|
+
timeout_minutes = 10
|
|
137
|
+
retries = 3
|
|
138
|
+
commands = [
|
|
139
|
+
"pnpm install --frozen-lockfile",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[lifecycle.build]
|
|
143
|
+
commands = [
|
|
144
|
+
"pnpm run build",
|
|
145
|
+
"pnpm run package",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
[lifecycle.verify]
|
|
149
|
+
shell = "bash"
|
|
150
|
+
script = """
|
|
151
|
+
set -euo pipefail
|
|
152
|
+
pnpm run check
|
|
153
|
+
git diff --check
|
|
154
|
+
"""
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Shared environment variables can be declared once:
|
|
158
|
+
|
|
159
|
+
```toml
|
|
160
|
+
[lifecycle.env]
|
|
161
|
+
PYTHONUNBUFFERED = "1"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Stage-specific environment variables override shared lifecycle environment:
|
|
165
|
+
|
|
166
|
+
```toml
|
|
167
|
+
[lifecycle.test]
|
|
168
|
+
command = "pytest"
|
|
169
|
+
|
|
170
|
+
[lifecycle.test.env]
|
|
171
|
+
PYTHONPATH = "src"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Publish Stage
|
|
175
|
+
|
|
176
|
+
`lifecycle.publish` is the project-owned side-effect stage. It may call npm,
|
|
177
|
+
PyPI, Conan, CMake packaging scripts, Docker/OCI registries, S3 uploaders, or
|
|
178
|
+
any other publisher. Buildchain does not assume the tool; it assumes the
|
|
179
|
+
evidence contract.
|
|
180
|
+
|
|
181
|
+
```toml
|
|
182
|
+
[lifecycle.publish]
|
|
183
|
+
script = """
|
|
184
|
+
set -euo pipefail
|
|
185
|
+
python scripts/publish_wheels.py
|
|
186
|
+
node scripts/publish-images.mjs
|
|
187
|
+
node scripts/write-publish-evidence.mjs
|
|
188
|
+
"""
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
When `actions/promote-buildchain-ref` runs with `publish-transaction: "true"`,
|
|
192
|
+
the publish stage receives:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
BUILDCHAIN_VERSION
|
|
196
|
+
BUILDCHAIN_CHANNEL
|
|
197
|
+
BUILDCHAIN_SOURCE_SHA
|
|
198
|
+
BUILDCHAIN_TARGET_REF
|
|
199
|
+
BUILDCHAIN_RELEASE_STATE
|
|
200
|
+
BUILDCHAIN_EVIDENCE_DIR
|
|
201
|
+
BUILDCHAIN_RELEASE_SHA
|
|
202
|
+
BUILDCHAIN_RELEASE_MATERIAL_SHA
|
|
203
|
+
BUILDCHAIN_PUBLISH_TOOLING_SHA
|
|
204
|
+
BUILDCHAIN_PUBLISH_EVIDENCE
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The stage must write publish evidence JSON. Buildchain validates that evidence
|
|
208
|
+
before exact tags and floating refs move. In GitHub Actions, the promotion
|
|
209
|
+
action also persists `state.json` and `evidence.json` to
|
|
210
|
+
`refs/heads/buildchain/release-state/<version>` so fresh runners can recover
|
|
211
|
+
without local workspace residue. See
|
|
212
|
+
[`docs/publish-transaction.md`](publish-transaction.md) for the state machine,
|
|
213
|
+
evidence schema, and recovery commands.
|
|
214
|
+
|
|
215
|
+
## Promotion Semantics
|
|
216
|
+
|
|
217
|
+
`actions/promote-buildchain-ref` consumes `version.files`, `lifecycle.verify`,
|
|
218
|
+
and optionally `lifecycle.publish`.
|
|
219
|
+
|
|
220
|
+
The verify stage runs after Buildchain has applied the generated version-state
|
|
221
|
+
changes to the local checkout, and before it creates release commits or moves
|
|
222
|
+
refs. After the command finishes, Buildchain checks that only declared
|
|
223
|
+
version-state files changed. This prevents verification from quietly adding
|
|
224
|
+
extra source changes to the release commit.
|
|
225
|
+
|
|
226
|
+
On protected alpha and release branches, the generated version-state commit is
|
|
227
|
+
merged through a normal pull request. This keeps review requirements,
|
|
228
|
+
conversation resolution, strict status checks, and admin enforcement intact.
|
|
229
|
+
After that PR lands, Buildchain verifies that the version-state PR changed only
|
|
230
|
+
declared version files from the legal channel-promotion parent before it moves
|
|
231
|
+
tags.
|
|
232
|
+
|
|
233
|
+
The action input `verification-command` remains supported. When it is provided,
|
|
234
|
+
it overrides `lifecycle.verify` for that invocation.
|
|
235
|
+
|
|
236
|
+
## Migration Preflight
|
|
237
|
+
|
|
238
|
+
Heavy repositories can validate their Buildchain declaration before they are
|
|
239
|
+
ready to run the real build. `actions/validate-config` checks that
|
|
240
|
+
`buildchain.toml` parses, configured version-state files exist, configured
|
|
241
|
+
version keys are strings, and required lifecycle stage names are declared.
|
|
242
|
+
For web-surface repositories it also validates `project`, `channels`, `deploy`,
|
|
243
|
+
`retention`, and `security` declarations.
|
|
244
|
+
|
|
245
|
+
It does not run lifecycle commands. This is useful for repositories such as
|
|
246
|
+
`libnode`, where `lifecycle.build` represents an expensive multi-platform native
|
|
247
|
+
build and the first migration milestone is to prove the release metadata and
|
|
248
|
+
lifecycle protocol without consuming build runners.
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
- uses: kungfu-systems/buildchain/actions/validate-config@v2
|
|
252
|
+
with:
|
|
253
|
+
require-version-state: "true"
|
|
254
|
+
require-lifecycle-stages: "install,build,verify"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Web-surface repositories can use the same action without requiring version
|
|
258
|
+
state:
|
|
259
|
+
|
|
260
|
+
```yaml
|
|
261
|
+
- uses: kungfu-systems/buildchain/actions/validate-config@v2
|
|
262
|
+
with:
|
|
263
|
+
require-lifecycle-stages: "build,verify"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The action exposes project and deploy metadata through outputs such as
|
|
267
|
+
`project-type`, `project-site`, `channels`, and `deploy-adapters-json`.
|
|
268
|
+
|
|
269
|
+
## Web-Surface Projects
|
|
270
|
+
|
|
271
|
+
`project.type = "web-surface"` is for sites, docs, browser apps, and operator
|
|
272
|
+
consoles whose release object is a deployed surface, not a package version.
|
|
273
|
+
|
|
274
|
+
```toml
|
|
275
|
+
schema = 1
|
|
276
|
+
|
|
277
|
+
[project]
|
|
278
|
+
type = "web-surface"
|
|
279
|
+
name = "site-kungfu-tech"
|
|
280
|
+
site = "kungfu-tech"
|
|
281
|
+
|
|
282
|
+
[channels.preview]
|
|
283
|
+
url_pattern = "https://{alias}.preview.kungfu.tech"
|
|
284
|
+
visibility = "ephemeral"
|
|
285
|
+
noindex = true
|
|
286
|
+
|
|
287
|
+
[channels.staging]
|
|
288
|
+
url = "https://staging.kungfu.tech"
|
|
289
|
+
visibility = "protected"
|
|
290
|
+
requires_auth = true
|
|
291
|
+
noindex = true
|
|
292
|
+
promotable = true
|
|
293
|
+
|
|
294
|
+
[channels.production]
|
|
295
|
+
url = "https://kungfu.tech"
|
|
296
|
+
visibility = "public"
|
|
297
|
+
canonical = true
|
|
298
|
+
noindex = false
|
|
299
|
+
|
|
300
|
+
[deploy.production]
|
|
301
|
+
adapter = "aws-s3-cloudfront"
|
|
302
|
+
bucket = "kungfu-tech-production"
|
|
303
|
+
artifact_path = "dist"
|
|
304
|
+
secret_refs = ["AWS_ROLE_ARN"]
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
See [Web-surface deployments](web-surface-deployments.md) for the manifest,
|
|
308
|
+
preview alias, retention, cleanup, and dry-run deploy contract.
|
|
309
|
+
|
|
310
|
+
## Examples
|
|
311
|
+
|
|
312
|
+
### Node Workspace
|
|
313
|
+
|
|
314
|
+
```toml
|
|
315
|
+
schema = 1
|
|
316
|
+
|
|
317
|
+
[version]
|
|
318
|
+
required = true
|
|
319
|
+
|
|
320
|
+
[[version.files]]
|
|
321
|
+
type = "json"
|
|
322
|
+
path = "package.json"
|
|
323
|
+
key = "version"
|
|
324
|
+
|
|
325
|
+
[lifecycle.verify]
|
|
326
|
+
commands = [
|
|
327
|
+
"pnpm run check",
|
|
328
|
+
]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Python Package
|
|
332
|
+
|
|
333
|
+
```toml
|
|
334
|
+
schema = 1
|
|
335
|
+
|
|
336
|
+
[version]
|
|
337
|
+
required = true
|
|
338
|
+
|
|
339
|
+
[[version.files]]
|
|
340
|
+
type = "toml"
|
|
341
|
+
path = "pyproject.toml"
|
|
342
|
+
key = "project.version"
|
|
343
|
+
|
|
344
|
+
[lifecycle.install]
|
|
345
|
+
command = "python -m pip install -e .[test]"
|
|
346
|
+
|
|
347
|
+
[lifecycle.build]
|
|
348
|
+
command = "python -m build"
|
|
349
|
+
|
|
350
|
+
[lifecycle.verify]
|
|
351
|
+
commands = [
|
|
352
|
+
"python -m build",
|
|
353
|
+
"pytest",
|
|
354
|
+
]
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### CMake and Conan
|
|
358
|
+
|
|
359
|
+
```toml
|
|
360
|
+
schema = 1
|
|
361
|
+
|
|
362
|
+
[[version.files]]
|
|
363
|
+
type = "regex"
|
|
364
|
+
path = "CMakeLists.txt"
|
|
365
|
+
pattern = 'project\([^)]* VERSION (?<version>[^ )]+)'
|
|
366
|
+
replacement = '${version}'
|
|
367
|
+
|
|
368
|
+
[lifecycle.configure]
|
|
369
|
+
commands = [
|
|
370
|
+
"conan install . --build=missing",
|
|
371
|
+
"cmake -S . -B build -DCMAKE_BUILD_TYPE=Release",
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
[lifecycle.build]
|
|
375
|
+
command = "cmake --build build --config Release"
|
|
376
|
+
|
|
377
|
+
[lifecycle.verify]
|
|
378
|
+
commands = [
|
|
379
|
+
"cmake --build build --config Release",
|
|
380
|
+
"ctest --test-dir build --output-on-failure",
|
|
381
|
+
]
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Docker Image
|
|
385
|
+
|
|
386
|
+
```toml
|
|
387
|
+
schema = 1
|
|
388
|
+
|
|
389
|
+
[[version.files]]
|
|
390
|
+
type = "json"
|
|
391
|
+
path = "package.json"
|
|
392
|
+
key = "version"
|
|
393
|
+
|
|
394
|
+
[lifecycle.build]
|
|
395
|
+
command = "docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} ."
|
|
396
|
+
|
|
397
|
+
[lifecycle.verify]
|
|
398
|
+
command = "docker build -f Dockerfile -t kungfutrader/example:verify ."
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Docker publishing is an external side effect and should be gated by a release
|
|
402
|
+
workflow after version-state promotion has been verified.
|
|
403
|
+
|
|
404
|
+
## Design Boundaries
|
|
405
|
+
|
|
406
|
+
The lifecycle protocol is also the command source for the reusable build
|
|
407
|
+
surface. `.github/workflows/.build.yml` runs `lifecycle.install`,
|
|
408
|
+
`lifecycle.build`, and `lifecycle.verify` by default, while allowing callers to
|
|
409
|
+
override each stage with explicit workflow inputs. The underlying
|
|
410
|
+
`actions/run-lifecycle` action can be used directly by repositories that need a
|
|
411
|
+
custom workflow but still want Buildchain's lifecycle and deterministic manifest
|
|
412
|
+
contract.
|
|
413
|
+
|
|
414
|
+
Buildchain lifecycle commands are data, not executable configuration files.
|
|
415
|
+
They make release behavior reviewable in pull requests and keep the release
|
|
416
|
+
fact chain simple:
|
|
417
|
+
|
|
418
|
+
1. choose the channel branch and release line;
|
|
419
|
+
2. generate a source version commit from declared version files;
|
|
420
|
+
3. verify that exact tree;
|
|
421
|
+
4. move exact tags and floating refs only after verification succeeds;
|
|
422
|
+
5. run publish or deployment side effects in separately gated workflows.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Publish Transaction
|
|
2
|
+
|
|
3
|
+
Buildchain release promotion is not just tag movement. A release can also publish
|
|
4
|
+
external artifacts: npm packages, Python wheels, OCI images, binary archives,
|
|
5
|
+
metadata manifests, or site deployment records. Those side effects are harder
|
|
6
|
+
than Git refs because most registries are append-only: a failed rerun must know
|
|
7
|
+
which artifacts already exist, which are still missing, and whether any existing
|
|
8
|
+
artifact conflicts with the release material.
|
|
9
|
+
|
|
10
|
+
Buildchain v2 models that work as a release transaction.
|
|
11
|
+
|
|
12
|
+
## Why This Exists
|
|
13
|
+
|
|
14
|
+
The old ABV workflow made Git refs the visible release authority. That was
|
|
15
|
+
enough when "release" meant "create a version commit, move tags, and let
|
|
16
|
+
downstream jobs react." It is not enough when a single publish run must also
|
|
17
|
+
upload packages and images.
|
|
18
|
+
|
|
19
|
+
The failure mode to avoid is:
|
|
20
|
+
|
|
21
|
+
1. publish an external artifact;
|
|
22
|
+
2. fail before moving the exact release tag or floating channel refs;
|
|
23
|
+
3. rerun from a new job id with no memory of the artifact;
|
|
24
|
+
4. either republish something different or move refs without proving the
|
|
25
|
+
already-published artifact matches the release.
|
|
26
|
+
|
|
27
|
+
The transaction gives reruns a stable identity and a machine-readable state so
|
|
28
|
+
Buildchain can resume safely. The identity is:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
repository + version + source_sha + target_ref
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
It is not the GitHub Actions run id.
|
|
35
|
+
|
|
36
|
+
## Durable State
|
|
37
|
+
|
|
38
|
+
`actions/promote-buildchain-ref` stores release transaction state in a
|
|
39
|
+
machine-managed Git branch:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
refs/heads/buildchain/release-state/<version>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The branch contains:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
state.json
|
|
49
|
+
evidence.json # present after publish evidence exists
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The local `.buildchain/release-state/...` and
|
|
53
|
+
`.buildchain/release-evidence/...` files are working copies. They are useful for
|
|
54
|
+
local inspection and lifecycle commands, but they are not the durable truth for
|
|
55
|
+
GitHub-hosted reruns. On action startup, Buildchain reads the durable state ref
|
|
56
|
+
first, restores the local working copies, and only then decides whether to
|
|
57
|
+
publish, repair, or finalize.
|
|
58
|
+
|
|
59
|
+
Every meaningful state transition is written to the durable ref before public
|
|
60
|
+
release refs move. If the durable write fails, the action fails closed.
|
|
61
|
+
|
|
62
|
+
Durable release-state refs reserve their exact version even when the public exact
|
|
63
|
+
tag was never created. If a later machine run sees a failed or repair-required
|
|
64
|
+
state for `vX.Y.Z-alpha.N` and cannot resume it with the same transaction
|
|
65
|
+
identity, alpha version selection must advance to the next prerelease instead
|
|
66
|
+
of reusing or overwriting that failed transaction slot.
|
|
67
|
+
|
|
68
|
+
## Lifecycle
|
|
69
|
+
|
|
70
|
+
Repositories declare publish work in `buildchain.toml`:
|
|
71
|
+
|
|
72
|
+
```toml
|
|
73
|
+
[lifecycle.publish]
|
|
74
|
+
commands = [
|
|
75
|
+
"python scripts/publish_wheels.py",
|
|
76
|
+
"node scripts/publish-images.mjs",
|
|
77
|
+
"node scripts/write-publish-evidence.mjs",
|
|
78
|
+
]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`actions/promote-buildchain-ref` runs `lifecycle.publish` only when
|
|
82
|
+
`publish-transaction: "true"` is set or when a `publish-command` input is
|
|
83
|
+
provided. The action sets:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
BUILDCHAIN_VERSION
|
|
87
|
+
BUILDCHAIN_CHANNEL
|
|
88
|
+
BUILDCHAIN_SOURCE_SHA
|
|
89
|
+
BUILDCHAIN_TARGET_REF
|
|
90
|
+
BUILDCHAIN_RELEASE_STATE
|
|
91
|
+
BUILDCHAIN_EVIDENCE_DIR
|
|
92
|
+
BUILDCHAIN_RELEASE_SHA
|
|
93
|
+
BUILDCHAIN_RELEASE_MATERIAL_SHA
|
|
94
|
+
BUILDCHAIN_PUBLISH_TOOLING_SHA
|
|
95
|
+
BUILDCHAIN_PUBLISH_EVIDENCE
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Buildchain itself uses this contract for npm publishing:
|
|
99
|
+
|
|
100
|
+
```toml
|
|
101
|
+
[lifecycle.publish]
|
|
102
|
+
command = "node scripts/npm-publish-transaction.mjs"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
That script validates that `package.json` matches `BUILDCHAIN_VERSION`, runs
|
|
106
|
+
`npm publish --access public --tag <alpha|latest>` through npm Trusted
|
|
107
|
+
Publishing, and writes npm artifact evidence before the promotion action moves
|
|
108
|
+
public refs.
|
|
109
|
+
|
|
110
|
+
`BUILDCHAIN_RELEASE_MATERIAL_SHA` is the source material whose artifacts must
|
|
111
|
+
match. `BUILDCHAIN_PUBLISH_TOOLING_SHA` identifies the publishing code. A repair
|
|
112
|
+
run may change tooling, but material drift fails closed.
|
|
113
|
+
|
|
114
|
+
## Evidence
|
|
115
|
+
|
|
116
|
+
The publish lifecycle must write JSON evidence. Buildchain validates common
|
|
117
|
+
fields and required artifact identities before final refs move.
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"schema": 1,
|
|
122
|
+
"version": "2.0.11",
|
|
123
|
+
"channel": "release",
|
|
124
|
+
"source_sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
125
|
+
"release_sha": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
126
|
+
"target_ref": "release/v2/v2.0",
|
|
127
|
+
"release_material_sha": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
128
|
+
"publish_tooling_sha": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
129
|
+
"artifacts": [
|
|
130
|
+
{
|
|
131
|
+
"group": "node",
|
|
132
|
+
"kind": "npm",
|
|
133
|
+
"name": "@kungfu-systems/example",
|
|
134
|
+
"ref": "2.0.11",
|
|
135
|
+
"digest": "sha256:..."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"group": "image",
|
|
139
|
+
"kind": "oci",
|
|
140
|
+
"name": "ghcr.io/kungfu-systems/example",
|
|
141
|
+
"ref": "2.0.11",
|
|
142
|
+
"digest": "sha256:..."
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The generic contract is intentionally small:
|
|
149
|
+
|
|
150
|
+
- `version`, `channel`, `source_sha`, `release_sha`, and `target_ref` must match
|
|
151
|
+
the promotion run;
|
|
152
|
+
- required artifacts must appear in evidence;
|
|
153
|
+
- evidence used by a GitHub-hosted rerun must either be stored in the durable
|
|
154
|
+
state ref or be reconstructed by a machine-verifiable consumer command;
|
|
155
|
+
- existing artifacts with the same identity and digest are accepted on rerun;
|
|
156
|
+
- missing artifacts can be published by the next run;
|
|
157
|
+
- an existing artifact with a different digest puts the transaction into
|
|
158
|
+
`repair_required`.
|
|
159
|
+
|
|
160
|
+
Artifact identity is `group + kind + name + ref`. A required artifact that omits
|
|
161
|
+
`group` matches any group with the same `kind + name + ref`.
|
|
162
|
+
|
|
163
|
+
## Registry Truth Contract
|
|
164
|
+
|
|
165
|
+
Buildchain owns transaction orchestration, finalization ordering, durable state,
|
|
166
|
+
and generic evidence validation. It does not embed registry clients for npm,
|
|
167
|
+
PyPI, GHCR/OCI, GitHub Releases, S3, Conan, CMake packaging, or project-specific
|
|
168
|
+
download pages.
|
|
169
|
+
|
|
170
|
+
Consumer `lifecycle.publish` commands own registry truth. A valid consumer stage
|
|
171
|
+
must be idempotent and machine-verifiable:
|
|
172
|
+
|
|
173
|
+
- inspect the target registry before publishing;
|
|
174
|
+
- accept an existing exact artifact only when version, identity, digest, and
|
|
175
|
+
release-source binding match;
|
|
176
|
+
- publish missing required artifacts;
|
|
177
|
+
- reject conflicting existing artifacts and write evidence that lets Buildchain
|
|
178
|
+
move the transaction to `repair_required`;
|
|
179
|
+
- write `BUILDCHAIN_PUBLISH_EVIDENCE` after every successful inspect/publish
|
|
180
|
+
cycle;
|
|
181
|
+
- leave floating aliases such as npm dist-tags, PyPI stable markers, OCI
|
|
182
|
+
floating tags, GitHub Release "published" status, or download-page stable
|
|
183
|
+
links to a finalization step after Buildchain evidence validation.
|
|
184
|
+
|
|
185
|
+
The first-class adapter surface is command-based. Projects may wrap npm, PyPI,
|
|
186
|
+
GHCR/OCI, GitHub Release assets, archives, SBOMs, provenance, or checksums
|
|
187
|
+
however they need, as long as they emit the common evidence contract.
|
|
188
|
+
|
|
189
|
+
## States
|
|
190
|
+
|
|
191
|
+
The state machine is:
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
prepared -> publishing -> published -> finalizing -> complete
|
|
195
|
+
| | |
|
|
196
|
+
v v v
|
|
197
|
+
publish_failed repair_required failed_permanently
|
|
198
|
+
|
|
|
199
|
+
v
|
|
200
|
+
abandoned
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Supported states:
|
|
204
|
+
|
|
205
|
+
| State | Meaning |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| `prepared` | Transaction identity was created, but publish has not started. |
|
|
208
|
+
| `publishing` | Publish lifecycle is running or may have been interrupted. |
|
|
209
|
+
| `publish_failed` | Publish command failed before valid evidence was produced. |
|
|
210
|
+
| `published` | Evidence is valid; refs have not necessarily finalized. |
|
|
211
|
+
| `finalizing` | Buildchain is moving exact/floating refs or needs a later run to do it. |
|
|
212
|
+
| `complete` | Required evidence is valid and refs have finalized. |
|
|
213
|
+
| `repair_required` | Existing evidence or artifact state conflicts with expected release material. |
|
|
214
|
+
| `abandoned` | A human or controlled process abandoned this transaction, usually because a newer version supersedes it. |
|
|
215
|
+
| `failed_permanently` | Recovery should not continue without explicit override. |
|
|
216
|
+
|
|
217
|
+
`repair_required`, `abandoned`, and `failed_permanently` fail closed unless the
|
|
218
|
+
operator passes an explicit override. That override is for controlled repair
|
|
219
|
+
runs, not normal retry behavior.
|
|
220
|
+
|
|
221
|
+
## Ref Ordering
|
|
222
|
+
|
|
223
|
+
When publish transactions are enabled, promotion order is:
|
|
224
|
+
|
|
225
|
+
1. verify target source and governance;
|
|
226
|
+
2. create or reuse the version-state release commit;
|
|
227
|
+
3. acquire or resume the release transaction;
|
|
228
|
+
4. run `lifecycle.publish` or accept already-valid evidence;
|
|
229
|
+
5. validate evidence and required artifacts;
|
|
230
|
+
6. move exact release/prerelease tag;
|
|
231
|
+
7. move floating tags and channel refs;
|
|
232
|
+
8. mark the transaction `complete`.
|
|
233
|
+
|
|
234
|
+
If protected branches require a generated version-state PR, the transaction can
|
|
235
|
+
stop in `finalizing` and output `finalization-needed=true`. A later run can
|
|
236
|
+
resume from the same transaction state and complete ref movement without
|
|
237
|
+
republishing matching artifacts.
|
|
238
|
+
|
|
239
|
+
If finalization fails after an exact Git tag is created, the next run reads the
|
|
240
|
+
durable `finalizing` state, verifies the exact tag points at the recorded
|
|
241
|
+
release SHA, and retries the remaining floating refs. An exact tag at a
|
|
242
|
+
different SHA is a material conflict and blocks recovery.
|
|
243
|
+
|
|
244
|
+
## CLI Recovery
|
|
245
|
+
|
|
246
|
+
Local recovery commands operate on the same state/evidence files:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
node scripts/release-transaction.mjs inspect --version v2.0.11
|
|
250
|
+
node scripts/release-transaction.mjs recover --version v2.0.11
|
|
251
|
+
node scripts/release-transaction.mjs finalize --version v2.0.11
|
|
252
|
+
node scripts/release-transaction.mjs abort --version v2.0.11 --superseded-by v2.0.12
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The CLI is a diagnostic and local repair surface. It reports the durable
|
|
256
|
+
`state_ref`, but remote durable-ref writes and public Git ref finalization are
|
|
257
|
+
owned by `actions/promote-buildchain-ref`, because that action runs inside the
|
|
258
|
+
same governed GitHub permissions and branch-protection checks as release
|
|
259
|
+
promotion. In other words, CLI `finalize` can mark the local transaction state
|
|
260
|
+
complete after valid evidence; the machine-operated public finalization path is
|
|
261
|
+
to rerun the promotion action.
|
|
262
|
+
|
|
263
|
+
When no state file exists, creation commands also require:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
--repository kungfu-systems/buildchain \
|
|
267
|
+
--source-sha <sha> \
|
|
268
|
+
--release-sha <sha> \
|
|
269
|
+
--target-ref release/v2/v2.0 \
|
|
270
|
+
--channel release
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Build-Images Follow-Up
|
|
274
|
+
|
|
275
|
+
`build-images` should consume this contract rather than inventing a separate
|
|
276
|
+
workflow rule. The expected integration shape is:
|
|
277
|
+
|
|
278
|
+
- image build writes OCI digests into publish evidence;
|
|
279
|
+
- required image families are passed through `publish-required-artifacts-json`;
|
|
280
|
+
- reruns check GHCR or the target registry and accept existing images only when
|
|
281
|
+
tag and digest match;
|
|
282
|
+
- preview or alpha image tags remain non-stable until the transaction evidence
|
|
283
|
+
validates;
|
|
284
|
+
- production image aliases move only after all required image artifacts are
|
|
285
|
+
present and the Buildchain exact release tag has finalized.
|