@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.
Files changed (35) hide show
  1. package/README.md +262 -0
  2. package/bin/buildchain.mjs +222 -0
  3. package/docs/cli.md +124 -0
  4. package/docs/lifecycle-protocol.md +422 -0
  5. package/docs/publish-transaction.md +285 -0
  6. package/docs/reusable-build-surface.md +350 -0
  7. package/docs/web-surface-deployments.md +211 -0
  8. package/package.json +52 -1
  9. package/packages/core/README.md +15 -0
  10. package/packages/core/buildchain-config.js +721 -0
  11. package/packages/core/index.js +40 -0
  12. package/packages/core/package-manager.js +291 -0
  13. package/packages/core/publish-transaction.js +418 -0
  14. package/packages/core/release-line-dry-run.js +296 -0
  15. package/scripts/aggregate-build-summary.mjs +88 -0
  16. package/scripts/build-contract-core.mjs +731 -0
  17. package/scripts/check-inventory.mjs +325 -0
  18. package/scripts/init-repo.mjs +316 -0
  19. package/scripts/npm-publish-dry-run.mjs +176 -0
  20. package/scripts/npm-publish-transaction.mjs +268 -0
  21. package/scripts/publish-source-ref-resolver.mjs +113 -0
  22. package/scripts/release-line-dry-run.mjs +53 -0
  23. package/scripts/release-line-policy.mjs +141 -0
  24. package/scripts/release-transaction.mjs +212 -0
  25. package/scripts/resolve-build-contract.mjs +63 -0
  26. package/scripts/resolve-publish-gate.mjs +33 -0
  27. package/scripts/resolve-publish-source.mjs +99 -0
  28. package/scripts/run-lifecycle-core.mjs +162 -0
  29. package/scripts/run-lifecycle.mjs +40 -0
  30. package/scripts/strip-trailing-whitespace.mjs +14 -0
  31. package/scripts/tsup-action.config.mjs +19 -0
  32. package/scripts/verify-publish-source-lock.mjs +37 -0
  33. package/scripts/verify-release-pr.mjs +34 -0
  34. package/scripts/web-surface-core.mjs +382 -0
  35. 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.