Kungfu UNGFU™Developer Platform

Back to Buildchain homemanual / manual:lifecycle-protocol

Lifecycle Protocol

Buildchain manual page.

Lifecycle Protocol

Buildchain uses .buildchain/buildchain.toml as the v2 repository configuration format. The file is optional for simple JavaScript repositories, but it is the preferred way to describe release version state and lifecycle commands when a project is not a plain pnpm, npm, or yarn workspace.

For compatibility, Buildchain still reads a legacy root buildchain.toml when .buildchain/buildchain.toml is absent. New repositories should use the .buildchain/ layout so all Buildchain-owned local state lives under one directory.

Only TOML is supported in v2. YAML, JSON, and JavaScript config files are not loaded.

Minimal File

schema = 1

[version]
required = true

[[version.files]]
type = "json"
path = "package.json"
key = "version"

[lifecycle.verify]
commands = [
  "pnpm run check",
]

schema = 1 is required. Buildchain fails closed when the schema is missing or unknown.

Version State

Version state is the source file evidence that matches a release tag. During promotion, Buildchain writes the selected release or prerelease version into the configured files, verifies the resulting tree, creates a source version commit, then moves exact and floating refs.

Supported version file types:

Type Use case Required fields
json package.json, JSON manifests path, key
toml pyproject.toml, other TOML manifests path, key
regex CMakeLists.txt, conanfile.py, plain version files path, pattern, replacement

key is a dotted key path:

[[version.files]]
type = "toml"
path = "pyproject.toml"
key = "project.version"

Configured JSON and TOML version files are semantic no-ops when the declared key already equals the requested version. Buildchain preserves the repository’s original TOML bytes in that case instead of serializing the whole document and creating formatter-only release state. When a TOML version really changes, Buildchain applies a parser-verified lossless edit to that key and fails closed if it cannot prove a unique edit; unrelated arrays, comments, and formatting remain repository-owned.

Regex files must expose the current version through a named capture group called version:

[[version.files]]
type = "regex"
path = "CMakeLists.txt"
pattern = 'project\([^)]* VERSION (?<version>[^ )]+)'
replacement = '${version}'

If version.required = true, promotion fails when no configured version files are available.

Anchored Manual Versions

Some repositories do not derive their package version from the Buildchain release tag. libnode is the canonical example: the package version is anchored to an explicitly selected upstream Node.js release such as 22.22.3-kf.0, while the channel line may be release/v22/v22.22.

Those repositories can opt into anchored manual semantics:

[version]
required = true
strategy = "anchored"
next = "manual"
manifest = "libnode.release.json"
derived_files = [
  "dist/version-witness.json",
]

[[version.files]]
type = "json"
path = "package.json"
key = "version"

With strategy = "anchored" and next = "manual":

  • Buildchain validates the configured version files and anchor manifest, but it does not rewrite those files to the Buildchain release tag.
  • lifecycle.verify is the project-owned truth gate. It should compare the package version, anchor manifest, and upstream source/submodule state.
  • version.derived_files can declare committed witnesses or generated metadata whose bytes are derived by lifecycle.version-state. The field is valid only for anchored/manual projects that declare both lifecycle.version-state and lifecycle.verify.
  • release-candidate builds rerun derivation and verification before heavy platform builds, require the committed tree to remain unchanged, and bind the alpha/release tree identities plus declared file digests into controller and release-passport evidence.
  • release promotion permits differences from the tested alpha tree only in version.files, the anchor manifest, and declared version.derived_files; any other changed path fails closed before publication.
  • release promotion still creates the exact/floating production refs for the current line;
  • release promotion does not auto-create the next alpha branch or tag;
  • the action output next-anchor-required is true, signaling that the next upstream anchor line must be created explicitly by the repository.

The configured anchor manifest must be JSON or TOML. Buildchain does not interpret project-specific field names; it only loads the manifest and exposes its top-level fields to validation summaries and lifecycle environment:

BUILDCHAIN_VERSION_STRATEGY=anchored
BUILDCHAIN_VERSION_NEXT=manual
BUILDCHAIN_ANCHOR_MANIFEST=libnode.release.json
BUILDCHAIN_ANCHOR_MANIFEST_JSON={"nodeTag":"v22.22.3",...}

The upstream anchor decision remains outside Buildchain. A future line such as dev/v24/v24.xx should be created by an explicit repository workflow or human decision after the upstream version has been selected and checked in.

Lifecycle Stages

Lifecycle stages are declarative shell commands. A stage can use exactly one of:

  • command: one shell command;
  • commands: multiple shell commands run in order;
  • script: a multiline shell script.

Any command failure fails the stage. timeout_minutes, retries, shell, and env can be attached to a stage.

During version-state verification, Buildchain also sets BUILDCHAIN_VERSION to the release or prerelease version being verified.

[lifecycle.install]
timeout_minutes = 10
retries = 3
commands = [
  "pnpm install --frozen-lockfile",
]

[lifecycle.build]
commands = [
  "pnpm run build",
  "pnpm run package",
]

[lifecycle.check]
command = "pnpm run check:source"

[lifecycle.verify]
shell = "bash"
script = """
set -euo pipefail
pnpm run check
git diff --check
"""

lifecycle.check is the repository-owned source-acceptance gate. It should validate the checked-out source revision without entering the build, artifact, or release lifecycle. Consumers can run it on GitHub-hosted Linux through .github/workflows/check.yml@v3 with mode: source; the reusable workflow runs only lifecycle.install and lifecycle.check. The default mode: verify continues to run lifecycle.install and lifecycle.verify for existing callers. Both executed stages receive BUILDCHAIN_CHECK_MODE=source or BUILDCHAIN_CHECK_MODE=verify, so a repository whose normal install path can compile native tooling can select a provisioning-only install path for source acceptance without weakening promotion installs. The reusable job name remains check, and upload-artifacts: false disables evidence upload without changing the job conclusion used by branch protection.

Shared environment variables can be declared once:

[lifecycle.env]
PYTHONUNBUFFERED = "1"

Stage-specific environment variables override shared lifecycle environment:

[lifecycle.test]
command = "pytest"

[lifecycle.test.env]
PYTHONPATH = "src"

Publish Stage

lifecycle.publish is the project-owned side-effect stage. It may call npm, PyPI, Conan, CMake packaging scripts, Docker/OCI registries, S3 uploaders, or any other publisher. Buildchain does not assume the tool; it assumes the evidence contract.

[lifecycle.publish]
script = """
set -euo pipefail
python scripts/publish_wheels.py
node scripts/publish-images.mjs
node scripts/write-publish-evidence.mjs
"""

When actions/promote-buildchain-ref runs with publish-transaction: "true", the publish stage receives the transaction identity plus the resolved publish contract:

BUILDCHAIN_VERSION
BUILDCHAIN_CHANNEL
BUILDCHAIN_SOURCE_SHA
BUILDCHAIN_TARGET_REF
BUILDCHAIN_RELEASE_STATE
BUILDCHAIN_EVIDENCE_DIR
BUILDCHAIN_RELEASE_SHA
BUILDCHAIN_RELEASE_MATERIAL_SHA
BUILDCHAIN_PUBLISH_TOOLING_SHA
BUILDCHAIN_PUBLISH_EVIDENCE
BUILDCHAIN_PUBLISH_MODE
BUILDCHAIN_PUBLISH_AUTH
BUILDCHAIN_NPM_DIST_TAG
BUILDCHAIN_PACKAGE_SET_ORDER
BUILDCHAIN_PACKAGE_SET_MAIN_PACKAGE

The stage must write publish evidence JSON. Buildchain validates that evidence before exact tags and floating refs move. In GitHub Actions, the promotion action also persists state.json and evidence.json to refs/heads/buildchain/release-state/<version> so fresh runners can recover without local workspace residue. See docs/publish-transaction.md for the state machine, evidence schema, and recovery commands.

For npm packages, prefer:

[publish]
mode = "publish-final-version"
auth = "trusted-publishing"
dist_tag = "latest"

Use mode = "promote-existing-version" only for explicit same-version dist-tag recovery, and pair it with auth = "npm-token". Trusted Publishing does not authorize npm dist-tag add; Buildchain fails that combination before any publish transaction side effect.

Promotion Semantics

actions/promote-buildchain-ref consumes version.files, lifecycle.verify, and optionally lifecycle.publish.

The verify stage runs after Buildchain has applied the generated version-state changes to the local checkout, and before it creates release commits or moves refs. After the command finishes, Buildchain checks that only declared version-state files changed. This prevents verification from quietly adding extra source changes to the release commit.

Buildchain-owned untracked runtime evidence is excluded only through an exact internal allowlist. This includes contract-drift issue material under .buildchain/contract-drift/ and the paper workflow’s .buildchain/publication-result.json, alongside release-candidate, passport, release-state, KFD, and runtime evidence directories. Tracked changes, ordinary source files, and undeclared .buildchain/* paths still fail version verification.

On protected alpha and release branches, the generated version-state commit is applied by the promotion automation after the reviewed channel PR has merged. Buildchain keeps review requirements, conversation resolution, strict status checks, and admin enforcement for human channel changes, but admits only the exact GitHub Actions App to the target-bound bypass allowlist for generated release bookkeeping. Buildchain also creates the configured required check on the exact generated version-state commit before patching the protected ref, then applies the protected ref update with the declared generated ref update token. The reusable wrapper uses the run-scoped github.token for that protected bookkeeping update. If finalization bookkeeping is still rejected, Buildchain creates or reuses a same-repository buildchain/version-state/* PR and reports finalization-needed=true so a later idempotent promotion run can resume. This fallback also applies to strict alpha target and dev reconciliation. The generated PR must pass the repository’s normal protected checks, review, and merge-queue policy; Buildchain never weakens that policy or treats PR creation as completed finalization.

For version.strategy = "anchored" with version.next = "manual", release promotion does not generate a Buildchain-owned version-state commit. In that mode, a protected alpha -> release PR may carry the declared version.files from the tested alpha package version to the final package version, and may carry the configured version.manifest with it. Buildchain only accepts that release tree difference when the PR is the valid channel-promotion PR, the changed paths are limited to those declared version files plus the anchor manifest, and lifecycle.verify or verification-command has validated the checked-out release material. Any code or undeclared file change still fails the release tree gate.

The action input verification-command remains supported. When it is provided, it overrides lifecycle.verify for that invocation. The override inherits [lifecycle.env] and lifecycle.verify.env; when the configured stage declares shell, it also inherits that shell. If the stage has no explicit shell, Buildchain preserves the platform-default shell for compatibility.

Migration Preflight

Heavy repositories can validate their Buildchain declaration before they are ready to run the real build. actions/validate-config checks that .buildchain/buildchain.toml parses, configured version-state files exist, configured version keys are strings, and required lifecycle stage names are declared. For web-surface repositories it also validates project, channels, deploy, retention, and security declarations.

It does not run lifecycle commands. This is useful for repositories such as libnode, where lifecycle.build represents an expensive multi-platform native build and the first migration milestone is to prove the release metadata and lifecycle protocol without consuming build runners.

- uses: kungfu-systems/buildchain/actions/validate-config@v3
  with:
    require-version-state: "true"
    require-lifecycle-stages: "install,build,verify"

Web-surface repositories can use the same action without requiring version state:

- uses: kungfu-systems/buildchain/actions/validate-config@v3
  with:
    require-lifecycle-stages: "build,verify"

The action exposes project and deploy metadata through outputs such as project-type, project-site, channels, and deploy-adapters-json.

Web-Surface Projects

project.type = "web-surface" is for sites, docs, browser apps, and operator consoles whose release object is a deployed surface, not a package version.

schema = 1

[project]
type = "web-surface"
name = "site-libkungfu-dev"
site = "libkungfu-dev"

[channels.preview]
url_pattern = "https://{alias}.preview.libkungfu.dev"
visibility = "ephemeral"
noindex = true

[channels.staging]
url = "https://staging.libkungfu.dev"
visibility = "protected"
access_control = "managed-network"
edge_auth = "none"
noindex = true
promotable = true

[channels.production]
url = "https://libkungfu.dev"
visibility = "public"
canonical = true
noindex = false

[surfaces.hub]
path = "/"
production_url = "https://libkungfu.dev"
staging_url = "https://staging.libkungfu.dev"
preview_url_pattern = "https://{alias}.preview.libkungfu.dev"

[surfaces.core]
path = "/core/"
production_url = "https://core.libkungfu.dev"
staging_url = "https://core.staging.libkungfu.dev"
preview_url_pattern = "https://core-{alias}.preview.libkungfu.dev"

[deploy.production]
adapter = "aws-s3-cloudfront"
bucket = "libkungfu-dev-production"
artifact_path = "dist"
secret_refs = ["AWS_ROLE_ARN"]

See Web-surface deployments for named surface host mappings, the manifest, preview alias, retention, cleanup, and dry-run deploy contract.

Infra-Contract Projects

project.type = "infra-contract" is for infrastructure contract repositories that need provider-neutral desired, plan, approval, apply, observe, contract, and propagation evidence. buildchain init --type infra-contract wires lifecycle.verify to buildchain infra-contract --mode ci, which writes mutation-free plan, contract, propagation dry-run, evidence bundle, and verification artifacts under .buildchain/. The surface supports manual-observed, observe-only, and mocked adapter fixtures without reading cloud state files or executing live infrastructure mutation. See Infra Contract.

Examples

Node Workspace

schema = 1

[version]
required = true

[[version.files]]
type = "json"
path = "package.json"
key = "version"

[lifecycle.verify]
commands = [
  "pnpm run check",
]

Python Package

schema = 1

[version]
required = true

[[version.files]]
type = "toml"
path = "pyproject.toml"
key = "project.version"

[lifecycle.install]
command = "python -m pip install -e .[test]"

[lifecycle.build]
command = "python -m build"

[lifecycle.verify]
commands = [
  "python -m build",
  "pytest",
]

CMake and Conan

schema = 1

[[version.files]]
type = "regex"
path = "CMakeLists.txt"
pattern = 'project\([^)]* VERSION (?<version>[^ )]+)'
replacement = '${version}'

[lifecycle.configure]
commands = [
  "conan install . --build=missing",
  "cmake -S . -B build -DCMAKE_BUILD_TYPE=Release",
]

[lifecycle.build]
command = "cmake --build build --config Release"

[lifecycle.verify]
commands = [
  "cmake --build build --config Release",
  "ctest --test-dir build --output-on-failure",
]

Docker Image

schema = 1

[[version.files]]
type = "json"
path = "package.json"
key = "version"

[lifecycle.build]
command = "docker build -f Dockerfile -t kungfutrader/example:${BUILDCHAIN_VERSION} ."

[lifecycle.verify]
command = "docker build -f Dockerfile -t kungfutrader/example:verify ."

Docker publishing is an external side effect and should be gated by a release workflow after version-state promotion has been verified.

Design Boundaries

The lifecycle protocol is also the command source for the reusable build surface. .github/workflows/.build.yml runs lifecycle.install, lifecycle.build, and lifecycle.verify by default, while allowing callers to override each stage with explicit workflow inputs. The underlying actions/run-lifecycle action can be used directly by repositories that need a custom workflow but still want Buildchain’s lifecycle and deterministic manifest contract.

Buildchain lifecycle commands are data, not executable configuration files. They make release behavior reviewable in pull requests and keep the release fact chain simple:

  1. choose the channel branch and release line;
  2. generate a source version commit from declared version files;
  3. verify that exact tree;
  4. move exact tags and floating refs only after verification succeeds;
  5. run publish or deployment side effects in separately gated workflows.

Page metadata

Route
/docs/lifecycle-protocol/
Category
manual
Source path
docs/lifecycle-protocol.md
Package
@kungfu-tech/buildchain@3.0.2-alpha.2
Digest
sha256:55425241363da6105a6b36a46bf8c9d7edd5b0e0f9dbcebe1452327642eb2af9