- Rust 77.5%
- HTML 11.8%
- Nickel 8%
- Just 2.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Fresh history. The previous repository published runtime state (daemon task
records with server addresses and root ssh invocations) and carried one
operator's infrastructure inside what is meant to be a caller-agnostic
substrate. Rewriting history would have left those blobs reachable by SHA on
the forge, so the repository was recreated instead of scrubbed.
What this repo is, and what it deliberately is not:
- Vocabulary: `schemas/` (BuildDirectives, BuildSpec, CachePolicy, vault refs)
and `defaults/` constructors. This is the caller-facing contract.
- Implementation: the `lian-build` binary — directive parsing, three adapter
kinds ('local pre-flight, 'remote endpoint, 'fleet NATS claim with per-claim
ephemeral X25519 age TLS), cache namespacing, manifest-index merge, daemon
and web UI.
- No infrastructure. Per adr-009 and the axiom `caller-supplies-directives`,
no registry host, fleet name, subject prefix, network or credential path is
compiled in or committed. `examples/` demonstrates each adapter kind with
placeholder values only; the concrete bindings live in the caller's own repo.
Two infrastructure defaults that used to be hardcoded are now required from
the caller: `adapter.subject_prefix` for 'fleet builds, and AUX_PULL_REGISTRY
(formerly a built-in lamina registry host) for the base-image auth entry.
Design intent — ontology, ADRs, reflection, catalog — lives in the
constellation spine at `../.ontoref/`, not here.
|
||
| .cargo | ||
| .config | ||
| assets | ||
| defaults | ||
| examples | ||
| justfiles | ||
| schemas | ||
| src | ||
| tests/fixtures | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| card.ncl | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| images-time.md | ||
| justfile | ||
| manifest.ncl | ||
| README.md | ||
lian-build
炼 — alchemical refinement. Standalone build substrate for ephemeral BuildKit sessions.
lian-build is a single Rust binary that drives ephemeral BuildKit builds. It
resolves a buildkitd endpoint from caller-supplied BuildDirectives, drives
buildctl against it, pushes the image, and emits NATS lifecycle events.
Per adr-009, lian-build has no provider knowledge: it does not spawn VMs, hold cloud tokens, or manage SSH keys, networks, or firewalls. It only knows a buildkitd endpoint and the TLS to reach it. Resource acquisition belongs to whoever owns the infrastructure — fleet-daemon, the operator's local daemon, or a remote endpoint procured out-of-band.
Callers (provisioning, lamina, vapora, workspace CI) supply intent as
BuildDirectives in NCL; lian-build controls execution and OCI cache flow.
| Crate | lian-build (binary), 0.1.0 |
| Status | Beta · pre-1.0, schema and CLI surface still mobile |
| Edition | 2021 |
| ADRs | adr-001 lift-out · adr-002 CLI subcommand · adr-003 Nickel via subprocess · adr-004 local tier · adr-005 provider catalog (superseded by adr-009) · adr-006 catalog structure · adr-007 extra contexts · adr-008 ontoref-foundation migration · adr-009 no provider knowledge |
What it is
- An endpoint resolver + buildctl driver.
AdapterRef.kindselects the buildkitd:'local(on-host daemon),'remote(external endpoint + TLS), or'fleet(NATS claim against a declared fleet, per-claim-encrypted TLS). A single dispatch path (run_buildctl_lite) streams the context via gRPC. - A pre-flight gate. For
'local, lian-build queries host memory/CPU (sysinfo) and fails fast if the directivesspecblock exceeds capacity. - A directives consumer. Schemas live in
schemas/*.ncl; the Rust types insrc/directives.rsmirror them and round-trip viaserde_json. - An event publisher.
started/completed/failedlifecycle events go to NATS at<prefix>.<workspace>.build.<event>(best-effort — NATS failures never fail a build).
What it is not
- Not a library. There is no
lib.rs. Public surface is the CLI and the NCL schemas. - Not a VM orchestrator. Per adr-009 it spawns no compute on any path — no orchestrator HTTP client, no SSH runner, no provider catalog.
- Not coupled to provisioning. ADR-001 forbids importing
provisioning,platform-config, or anystratum--prefixed crate (grep-checked). - Not a Nickel runtime. NCL is parsed by shelling out to the
nickelCLI (ADR-003).nickelmust be on$PATH.
The tetra-build ecosystem
lian-build is one of four standalone projects that together form
tetra-build — the ecosystem's container build pipeline. Each project is
versioned and released on its own; they connect through declared contracts
(NCL directives, NATS subjects, OCI artifacts), never through library
linkage.
| Project | Role | Relation to lian-build |
|---|---|---|
| forge-fleet | Fleet control plane — claim-arbitration daemon + node agent | Supplies the buildkitd endpoint behind the 'fleet adapter kind |
| lian-build | Build driver — resolves an endpoint, drives buildctl, emits events |
— (this project) |
| lamina | Build-layer catalog — toolchain + cargo-chef pre-cooked dep layers | A caller — supplies BuildDirectives; its layers feed builds as extra_contexts |
| solera | Runtime-image catalog — deployable service images | A caller — builds its images via lian-build build --directives |
The shared goal (the common CTA): fast, reproducible, ephemeral OCI builds. The specialization: forge-fleet owns where builds run, lian-build owns how they run, lamina owns what goes in, solera owns what comes out. adr-009 draws the line precisely — lian-build holds no provider knowledge, so forge-fleet can appear, change, or be replaced with zero lian-build changes.
Adapter kinds (adr-009)
BuildDirectives.adapter.kind is exactly [| 'local, 'remote, 'fleet |]:
'local— buildkitd on the same host. lian-build runs a host-capacity pre-flight againstBuildSpec.memory_gb_min/cpu_minbefore dispatch.buildkit_addroverrides the default socket.'remote— an externally-supplied buildkitdendpointplus atls_bundle_ref(SOPS-encrypted{ca, cert, key}PEM bundle). lian-build connects and builds; it does not know what runs the endpoint.'fleet— lian-build emits a NATS claim against a declaredfleet_ref, generates a fresh ephemeral X25519 age keypair, receives a per-claim-encrypted TLS grant, builds, and releases the claim. Resolves to'remoteinternally.
CLI
All subcommands, no default, no flat-arg fallback (ADR-002). All logs go to stderr; stdout carries structured output (envelopes, JSON, tables).
Daemon-facing subcommands (project, runner, trigger, status, builds,
adapter) default to http://localhost:19012 and can be overridden via
LIAN_BUILD_DAEMON_URL.
lian-build build
Run a build. A BuildDirectives NCL file is the primary input; flat args are
accepted for the directives-less smoke-test path ('local only, no spec).
lian-build build \
--directives <file.ncl> \
[--context <dir>] \
[--platforms linux/amd64,linux/arm64] \
[--cache-from <ref>] [--cache-to <ref>] \
[--nats-url <url>] [--nats-nkey-seed <seed>] [--nats-subject-prefix <p>] \
[--secrets-base <dir>]
The adapter block in the directives selects 'local / 'remote / 'fleet.
--secrets-base points at the SOPS secrets root used to resolve registry push
credentials, remote TLS bundles, and fleet NKey seeds.
Env: BUILDKIT_WORKSPACE, NATS_URL, NATS_NKEY_SEED, NATS_SUBJECT_PREFIX,
LIAN_BUILD_SECRETS_BASE.
lian-build serve
Start the lifecycle daemon — HTTP API, project registry, TTL sweeper, optional web UI.
lian-build serve \
[--config <file.ncl|.toml|.json>] \
[--port 19012] \
[--state-file <path>] \
[--config-dir <dir>] \
[--data-dir <dir>] \
[--templates-dir <dir>] \
[--admin-key <key>] \
[--nats-url <url>]
Env: LIAN_BUILD_DAEMON_CONFIG, LIAN_BUILD_DAEMON_PORT,
LIAN_BUILD_STATE_FILE, LIAN_BUILD_CONFIG_DIR, LIAN_BUILD_DATA_DIR,
LIAN_BUILD_TEMPLATES_DIR, LIAN_BUILD_ADMIN_KEY.
lian-build trigger
Trigger a build for a registered project via the daemon API. Blocks until the
build reaches a terminal state (override with --no-wait).
lian-build trigger <slug> \
--adapter <adapter-name> \
[--platform linux/arm64] \
[--stage <stage>] \
[--cache-from <ref>] [--cache-to <ref>] \
[--keep] [--no-wait] [--poll-interval 2] \
[--format json|id]
--format id prints only the build UUID — useful for scripting:
BUILD_ID=$(lian-build trigger myproject --adapter provisioning --format id).
lian-build integrate
Federated probe. Reads a SecretDeliveryContext JSON envelope from stdin,
emits a ResultEnvelope JSON line on stdout, optionally publishes a
completion event to NATS.
echo '<context-json>' | lian-build integrate \
[--nats-url <url>] [--nats-nkey-seed <seed>]
lian-build project
Manage registered projects (daemon must be running).
lian-build project register <slug> <directives-path> [--root <dir>] [--workspace <name>]
lian-build project list
lian-build project status <slug>
lian-build project remove <slug>
lian-build runner
Inspect and manage active runners.
lian-build runner list
lian-build runner pin <id>
lian-build runner unpin <id>
lian-build runner destroy <id>
lian-build adapter
List configured daemon runner adapters and run live health probes.
lian-build adapter [--format table|json]
lian-build builds
List and clear build records stored by the daemon.
lian-build builds list [--project <slug>] [--status queued|running|completed|failed] \
[--adapter <name>] [--older-than <secs>] [--limit <n>]
lian-build builds clear [--project <slug>] [--status <s>] [--adapter <name>] [--older-than <secs>]
lian-build events
Stream live NATS build/runner events to stdout.
lian-build events --nats-url <url> \
[--nats-nkey-seed <seed>] \
[--subject-prefix lian-build] \
[--subject <pattern>] \
[--format json|human]
lian-build status
Print a daemon status summary (runners, projects, adapters) to stdout.
lian-build status [--daemon-url <url>]
Pre-flight host capacity (adr-009)
When adapter.kind = 'local, lian-build runs a pre-flight before any buildkitd
traffic. src/preflight.rs::check_local_capacity reads host memory and logical
CPU count via the sysinfo crate and compares them against the directives
spec block:
spec = { memory_gb_min = 16, cpu_min = 8 },
If the host is under-spec the build fails fast with a clear PreflightError
that suggests switching the adapter to 'fleet. A directive with no spec
block is a no-op. 'remote defers sizing to the caller; 'fleet defers it to
fleet-daemon's claim arbitration — the gate is scoped to 'local.
Cache namespacing
Two layers, defined in schemas/cache_policy.ncl and enforced in src/cache.rs:
ci/<workspace>/*— canonical, written by CI, read-only to sessions.dev/<actor-id>-<workspace>/*— ephemeral per-session actor.
Sessions read from both layers; CI never imports from dev/*. These
invariants are guarded by tests under src/cache.rs.
NCL contract surface
| Schema | Defines |
|---|---|
schemas/build_directives.ncl |
BuildDirectives, BuildArtifact (incl. extra_contexts), AdapterRef ('local/'remote/'fleet), RegistryProviderRef, SigningConfig, NatsEventConfig, SccacheConfig |
schemas/build_spec.ncl |
BuildSpec — host resource floor (memory_gb_min, cpu_min) for the 'local pre-flight |
schemas/build_result.ncl |
BuildResult envelope shape |
schemas/cache_policy.ncl |
CachePolicy, BuildMode, SessionActor, SessionCacheDisposition |
schemas/vault_refs.ncl |
VaultCredRef, VaultKeyRef |
defaults/build_directives.ncl |
make_* constructors, ci_cache_policy / session_cache_policy helpers |
Validate with the nickel CLI:
nickel typecheck --import-path . schemas/build_directives.ncl
nickel export --import-path . schemas/build_spec.ncl
Hard constraints (ADR-001)
These two rules are grep-checked and define the lift-out boundary:
no-provisioning-lib-import—Cargo.tomlandsrc/must not matchplatform-config|provisioning|stratum-. Theplatform-natspath-dep fromstratumiopsis explicitly allowed; the constraint targets theprovisioningworkspace andstratum--prefixed crates.build-directives-ncl-vocabulary—src/must not matchprovisioning_workspace|vapora_|woodpecker_. Caller-specific logic stays in caller-supplied directives, not in core.
ADR Hard constraints across all ADRs are checked by ontoref adr validate.
Build / test / run
cargo build # debug
cargo build --release # release at target/release/lian-build
cargo clippy --all-targets -- -D warnings # mandatory before commit
cargo fmt
cargo test # full suite
cargo test <name> # single test by substring
just recipes live in justfiles/{build,test,ci,catalog,services}.just. Run
just (or just help) to list them.
Stratumiops peer dependency
platform-nats is consumed as a local path dependency from
/Users/Akasha/Development/stratumiops/crates/platform-nats (declared
directly in Cargo.toml). That path must exist for cargo build to
succeed — there is no feature flag to disable it.
Module layout
src/
main.rs # CLI entry — Cli/Commands, mod declarations, dispatch
cli/ # one handler module per subcommand
args.rs # all clap arg structs
build.rs # run_build_command — adapter-kind dispatch, pre-flight, fleet claim
serve.rs · trigger.rs · project.rs · runner.rs · status.rs
events.rs · builds.rs · adapter.rs
preflight.rs # check_local_capacity — sysinfo host-capacity gate for 'local
buildctl_runner.rs # run_buildctl_lite — drives buildctl against (addr, optional TLS)
fleet_client.rs # FleetClient — NATS claim flow, per-claim X25519 age TLS delivery
directives.rs # BuildDirectives ↔ JSON via `nickel export` subprocess
cache.rs # BuildMode, build_cache_flags, ci/* vs dev/<actor>/* invariants
project.rs # project registry and build records; JSON persistence
api_catalog.rs # HTTP handlers for catalog domain/mode queries
nats_events.rs # BuildEventPublisher over platform-nats
nats_subscriber.rs # NATS event subscriber for the `events` subcommand
daemon/
mod.rs # lifecycle daemon — runner state, TTL enforcement, NATS events
adapter.rs # Adapter, AdapterStatus, concurrent health_check_all
config.rs # DaemonConfig loading (.ncl / .toml / .json)
server.rs # Axum HTTP router; all REST handlers
state.rs # AppState (runners, projects, builds, adapters, NATS, Tera)
sweeper.rs # TTL enforcement background task
integration/ # federated probe handler (stdin → ResultEnvelope on stdout)
ui/ # Tera + HTMX web UI served by the daemon
Operational surface:
schemas/ # caller-facing NCL contracts (build_directives, build_spec, …)
defaults/ # constructor / helper NCL
examples/ # local / remote / fleet BuildDirectives, all placeholder values
tests/fixtures/ # integration test fixtures
../.ontoref/ # constellation spine — design intent, not shipped code
ontology/ # core, state, gate, manifest
adrs/ # accepted ADRs (NCL), adr-001 through adr-009
reflection/ # modes, backlog, qa
catalog/ # adapter runner scripts, domain contracts
Further reading
../.ontoref/adrs/adr-001-lian-build-as-standalone.ncl— why this project exists, alternatives rejected, the two grep-checked invariants.../.ontoref/adrs/adr-002-cli-subcommand-discipline.ncl— subcommand-only surface, stderr/stdout discipline.../.ontoref/adrs/adr-003-nickel-via-subprocess.ncl— whynickelis on$PATH, not inCargo.toml.../.ontoref/adrs/adr-004-local-build-tier.ncl— local build tier; three-tier isolation model.../.ontoref/adrs/adr-005-compute-provider-catalog.ncl— superseded by adr-009. The in-daemon provider catalog with health probes.../.ontoref/adrs/adr-006-catalog-structure.ncl— two-layer catalog; provisioning imports via--import-pathbridge.../.ontoref/adrs/adr-007-extra-contexts-named-buildkit-contexts.ncl—BuildArtifact.extra_contexts;extra-ctx-<name>gRPC local-id;is_local_contextdetection rule.../.ontoref/adrs/adr-008-ontoref-foundation-migration.ncl— auth/ui migration to the ontoref-foundation sibling crates (the compute integration was retired by adr-009).../.ontoref/adrs/adr-009-no-provider-knowledge.ncl—AdapterRef.kindreduced to'local/'remote/'fleet; provider catalog and orchestrator client removed;'localpre-flight; per-claim ephemeral X25519 age TLS for'fleet.../.ontoref/ontology/core.ncl— axioms, tensions, practices, and named caller nodes.../.ontoref/ontology/state.ncl— five maturity dimensions and their transitions.CHANGELOG.md— record of accepted decisions and visible surface changes.