炼 · Alchemical Refinement · v0.1.0 Beta
“Fire without the ash.”

Ephemeral crucibles that refine code into reproducible artifacts.

Ephemeral Build Substrate for
Provider-Pluggable BuildKit Sessions

Callers (provisioning, vapora, workspace CI) supply BuildDirectives in NCL. lian-build controls compute provisioning, OCI cache flow, and multi-actor session namespacing. Compute (hcloud / proxmox / docker-local) and registry (zot / harbor / ghcr) are plug-in slots, not hard dependencies.

4
Axioms
3
Spiral Tensions
3
Sizing Tiers
2
Hard Constraints
Foundations · .ontology/core.ncl

Four Axioms

The invariant set the rest of the system is built on. Each is declared in .ontology/core.ncl with invariant = true; violating one is an architectural break, not a routine refactor.

A1 · ephemeral-builds

Ephemeral Builds

Build environments are spawned on demand, torn down after completion. No persistent build state exists outside the content-addressed cache and the registry. This prevents environment drift and makes builds reproducible by construction.

A2 · provider-pluggability

Provider Pluggability

Compute and registry are plug-in slots, not hard dependencies.

A3 · cache-content-addressed

Content-Addressed Cache

All build cache is stored as OCI artifacts, keyed by content hash.

A4 · caller-supplies-directives

Caller Supplies Directives

lian-build does not decide what to build or how — callers do.

Build Flow · src/main.rs::main

Architecture

A single binary orchestrates an external HTTP service, drives a spawned VM over SSH, and emits lifecycle events on NATS.

Caller provisioning · vapora · workspace CI supplies BuildDirectives.ncl → CLI flags --workspace --context --image lian-build main.rs · run_build() orchestrator_client sizing buildctl_runner retry · nats_events Orchestrator localhost:9011 spawn · destroy · p95 · metrics HTTP NATS <prefix>.<ws>.build.* started · completed · failed events Spawned Runner VM cx22 → cx32 → cx42 → cx52 rsync context · buildctl build OOM exit 137 → tier walk · max 1 retry SSH OCI Registry zot · harbor · ghcr image push · cache import / export ci/<ws>/* · dev/<actor>-<ws>/* cache · push spawn → rsync → buildctl → record_metrics → destroy (always, even on failure)
Module Responsibility
main.rs CLI parsing, top-level orchestration, NATS event dispatch, OOM-retry control flow.
orchestrator_client.rs HTTP client: spawn/destroy/p95/record_metrics, wrapping responses in ApiResponse<T>.
buildctl_runner.rs Drives the spawned VM over SSH. rsync context, run buildctl remotely, detect OOM via exit 137.
sizing.rs Three-tier sizing resolution: explicit spec → P95 × 1.2 → language defaults.
retry.rs OOM-retry policy: MAX_OOM_RETRIES = 1 (ADR-039). Tier walk cx22→cx32→cx42→cx52.
nats_events.rs BuildEventPublisher over platform-nats::EventStream. Publishes started/completed/failed.
Cache Model · schemas/cache_policy.ncl

Cache Namespace Split

Sessions read both ci/* and their own dev/*; CI never imports from dev/*.

ci/<workspace>/*

Canonical · Read-only for sessions

Written by CI. Sessions import but never write back.

dev/<actor-id>-<workspace>/*

Ephemeral · Per session actor

One namespace per SessionActor. Disposition: export / discard / rollback.

Sizing · src/sizing.rs::resolve

Three-Tier Resolution

First match wins.

1
Explicit BuildSpec
.build-spec.ncl in the build context, validated against schemas/build_spec.ncl. Parsed via nickel export --format json.
2
P95 Historical × 1.2
OrchestratorClient::get_p95(workspace) returns measured P95 from prior runs.
3
Language Defaults
RunnerSize::language_default(lang).
Lift-out Boundary · ADR-001

Two Hard Constraints

These two are grep-checked and define the lift-out boundary.

C1 · no-provisioning-lib-import · Hard
lian-build source code must not import any crate from the provisioning workspace as a library dependency.
grep -rE 'platform-config|provisioning|stratum-' Cargo.toml src/ → must be empty
Note: platform-nats from stratumiops is allowed; constraint targets provisioning + stratum- crates.
C2 · build-directives-ncl-vocabulary · Hard
Callers must supply intent via lian-build's NCL BuildDirectives schema.
grep -rE 'provisioning_workspace|vapora_|woodpecker_' src/ → must be empty
Caller-specific logic stays in caller-supplied directives, not in core.
Invocation · single binary

CLI Reference

The current entry is flat CLI args.

lian-build \
  --workspace <name>              # required, env BUILDKIT_WORKSPACE
  --context <dir>                 # required, build context (rsynced to runner)
  --image <ref>                   # required, fully-qualified target image
  --ssh-key <path>                # required, env BUILDKIT_SSH_KEY
  [--dockerfile Dockerfile]       # relative to context
  [--cache-from <ref>] [--cache-to <ref>]
  [--language rust|go|java|...]   # sizing hint (tier 3)
  [--runner-image <id>]           # env BUILDKIT_RUNNER_IMAGE
  [--orchestrator-url <url>]      # default http://localhost:9011
  [--nats-url <url>]              # omit to disable event publishing
  [--nats-nkey-seed <seed>]
  [--nats-subject-prefix <p>]     # default lian-build