commit 40be73379734a811d504c488be08fcd02dbd7391 Author: Jesús Pérez Date: Mon May 4 18:23:52 2026 +0100 chore: init repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ba45c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +CLAUDE.md +.claude +logs +logs-archive +utils/save*sh +.fastembed_cache +presentaciones +COMMIT_MESSAGE.md +.wrks +nushell +nushell-* +*.tar.gz +#*-nushell-plugins.tar.gz +github-com +.coder +target +artifacts/ +distribution +.qodo +# enviroment to load on bin/build +.env +# OSX trash +.DS_Store + +# Vscode files +.vscode + +# Emacs save files +*~ +\#*\# +.\#* + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +# cscope-related files +cscope.* + +# User cluster configs +.kubeconfig + +.tags* + +# direnv .envrc files +.envrc + +# make-related metadata +/.make/ + +# Just in time generated data in the source, should never be committed +/test/e2e/generated/bindata.go + +# This file used by some vendor repos (e.g. github.com/go-openapi/...) to store secret variables and should not be ignored +!\.drone\.sec + +# Godeps workspace +/Godeps/_workspace + +/bazel-* +*.pyc + +# generated by verify-vendor.sh +vendordiff.patch +.claude/settings.local.json + +# Generated SBOM files +SBOM.*.json +*.sbom.json + +# UnoCSS build +assets/css/node_modules/ +assets/css/pnpm-lock.yaml +crates/ontoref-daemon/public/css/ontoref.css diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..cbedbf0 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,115 @@ +# Changes + +Record of accepted architectural decisions and visible surface changes for +`lian-build`. Format follows [Keep a Changelog]; project is pre-1.0 so the +SemVer guarantee is **not** yet in force — schema and CLI shape may break +between minor versions until `0.1.0` is tagged. + +Dates reflect the ADR acceptance date or the date the on-disk artefact was +recorded; this project does not yet publish git tags or release builds. + +[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ + +--- + +## [Unreleased] — 0.1.0 + +### Architecture + +- **adr-003 — Nickel coupled via subprocess, not library linkage** *(2026-05-03)* + All NCL → Rust conversions flow through `directives::nickel_export(path, + &[import_paths])`, which spawns `nickel export --format json` and feeds + serde. No `nickel-lang*` crate is added to `Cargo.toml`. `nickel` is a + runtime requirement on `$PATH`. + +- **adr-002 — CLI surface is subcommand-only with stdout reserved for + structured envelopes** *(2026-05-03)* + `lian-build` now exposes `{build, integrate}` as clap subcommands. There is + no default subcommand and no flat-arg compatibility shim. `tracing_subscriber` + is pinned to `with_writer(std::io::stderr)` so stdout carries at most one + envelope per invocation. **Breaking** for any caller invoking the legacy + `buildkit-launcher` flat-arg form: migrate to `lian-build build `. + +- **adr-001 — lian-build is a standalone build substrate, not part of + provisioning** *(2026-05-01)* + `buildkit-launcher` and `buildkit_runner` extracted from + `provisioning/platform/crates/buildkit-launcher` and + `provisioning/extensions/components/buildkit_runner` into this standalone + project. Binary renamed from `buildkit-launcher` to `lian-build`. + +### Added + +- `lian-build build` subcommand. Accepts flat args **or** `--directives + `; directives take precedence over flat-arg fields when both are + present (`src/main.rs::resolve_build_plan`). +- `lian-build integrate` subcommand. Federated probe that reads a + `SecretDeliveryContext` JSON envelope from stdin and emits a single + `ResultEnvelope` JSON line on stdout (`src/integration/`). +- `BuildDirectives` NCL vocabulary (`schemas/build_directives.ncl`) and + matching Rust types in `src/directives.rs` with serde round-trip tests. +- `BuildSpec` schema (`schemas/build_spec.ncl`) with `bounded_cpu_` ≤ 256 and + `bounded_time_budget_` ≤ 1440 min contract bounds; honoured by + `sizing::resolve` at the explicit-spec tier. +- `CachePolicy` schema (`schemas/cache_policy.ncl`) with `BuildMode` = + `'ci | 'session`, `SessionActor` (`'human | 'agent | 'ci_aux`), and + `SessionCacheDisposition` (`'export | 'discard | 'rollback`). +- `src/cache.rs` — `build_cache_flags(workspace, mode, registry)` returning + import/export `Vec`; enforces `ci//*` (canonical, + read-only to sessions) and `dev/-/*` (per-session). + Invariants guarded by unit tests: `ci_never_imports_dev`, + `session_never_exports_ci`, `session_imports_both_layers`, + `mode_switch_purity`. +- `src/sizing.rs` — three-tier resolution: explicit `.build-spec.ncl` → + P95 historical (×1.2, floored) → language defaults (rust=4/8/60, + go=2/4/30, java|kotlin|scala=4/8/45, default=2/4/30). +- `src/retry.rs` — `MAX_OOM_RETRIES = 1` (hard bound), `SIZE_TIERS` walk + `cx22 → cx32 → cx42 → cx52`. OOM is detected via exit-code `137` or stderr + matching `OOM` / `Killed`. +- `src/orchestrator_client.rs` — HTTP client wrapping responses in + `ApiResponse { success, data, error }`. Methods: `spawn_runner`, + `destroy_runner`, `get_p95`, `record_metrics`. +- `src/buildctl_runner.rs` — `rsync_context` and `run_buildctl` over SSH. +- `src/nats_events.rs` — `BuildEventPublisher` over + `platform_nats::EventStream`. Publishes `started` / `completed` / `failed` + to `..build.`. Connect failures warn and disable + publishing; they never fail a build. +- `defaults/build_directives.ncl` — `make_*` constructors and + `ci_cache_policy` / `session_cache_policy` helpers. +- `examples/sample.ncl` — example `BuildDirectives` instance. +- Ontoref scaffolding: `.ontology/{core,state,gate,manifest}.ncl`, + `.ontoref/project.ncl`, `card.ncl`, `reflection/{backlog,qa}.ncl`, + `reflection/modes/`, `catalog/{domains,modes}/`. +- Repository assets: `assets/` (logo and branding), `justfile` with imports + from `justfiles/{build,test,ci}.just`, `.pre-commit-config.yaml`. + +### Changed + +- Binary target renamed `buildkit-launcher` → `lian-build`. The + `tracing` env-filter directive remains the legacy crate name + (`buildkit_launcher=info`) — explicitly noted in `CLAUDE.md`. +- `--platforms` is now a comma-separated list (multi-platform builds); + legacy single-value invocations remain valid. + +### Constraints (grep-checked, ADR-001) + +- `no-provisioning-lib-import` — `Cargo.toml` and `src/` must not match + `platform-config|provisioning|stratum-`. The `platform-nats` path-dep from + `stratumiops` is explicitly allowed (it is shared infrastructure, not the + provisioning workspace). +- `build-directives-ncl-vocabulary` — `src/` must not match + `provisioning_workspace|vapora_|woodpecker_`. + +### Status (`.ontology/state.ncl`) + +| Dimension | Current | Desired | +|------------------------------------|--------------------------|---------------------------| +| provider-pluggability-maturity | hcloud-zot-only | provider-trait-stable | +| session-multi-actor-maturity | ci-single-actor | multi-actor-stable | +| active-active-registry-maturity | single-zot-libre-wuji | active-active-verified | +| caller-integration-maturity | provisioning-only | multi-caller-stable | +| peer-publishing-status | packaged | production | + +`ComputeProvider` / `RegistryProvider` Rust traits are **not yet declared**; +the orchestrator client is currently hcloud-shaped. Compute is selected via +NCL discriminant but dispatch in core is monolithic — see ADR-001 and the +`provider-pluggability-maturity` dimension. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d31d634 --- /dev/null +++ b/README.md @@ -0,0 +1,244 @@ +

+ ontoref +

+ +
+ +# lian-build + +> 炼 — *alchemical refinement.* Standalone build substrate for ephemeral BuildKit sessions. + +`lian-build` is a single Rust binary that orchestrates ephemeral remote BuildKit +runs against a pluggable orchestrator. + +Callers (provisioning, vapora, workspace CI) supply intent as `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. + +| | | +|---|---| +| 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 discipline · adr-003 Nickel via subprocess | + +--- + +## What it is + +- An **orchestrator client**, not a buildkitd. It spawns runners through an + external HTTP orchestrator (default `http://localhost:9011`), `rsync`s the + build context, then drives `buildctl` over SSH. +- A **directives consumer**. Schemas live in `schemas/*.ncl`; the Rust types + in `src/directives.rs` mirror them and round-trip via `serde_json`. +- An **event publisher**. `started` / `completed` / `failed` lifecycle events + go to NATS at `..build.` (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 coupled to provisioning. ADR-001 forbids importing `provisioning`, + `platform-config`, or any `stratum-`-prefixed crate (grep-checked). +- Not a Nickel runtime. NCL is parsed by shelling out to the `nickel` CLI + (ADR-003). `nickel` must be on `$PATH`. + +--- + +## CLI + +Two subcommands, no default, no flat-arg fallback (ADR-002). All logs go to +**stderr**; **stdout** is reserved for one structured envelope per invocation. + +### `lian-build build` + +Run a build on an ephemeral runner. Flat args **or** a directives file: + +```bash +lian-build build \ + --workspace \ + --context \ + --image \ + --ssh-key \ + [--directives ] \ + [--dockerfile Dockerfile] \ + [--cache-from ] [--cache-to ] \ + [--language rust|go|java|...] \ + [--platforms linux/amd64,linux/arm64] \ + [--runner-image ] \ + [--orchestrator-url ] \ + [--nats-url ] [--nats-nkey-seed ] [--nats-subject-prefix

] +``` + +When `--directives ` is supplied it takes precedence over flat-arg +fields except `--ssh-key`, which is still required separately (directives +don't yet carry an inline SSH reference). + +Environment fallbacks: `BUILDKIT_WORKSPACE`, `BUILDKIT_SSH_KEY`, +`BUILDKIT_RUNNER_IMAGE`, `ORCHESTRATOR_URL`, `NATS_URL`, `NATS_NKEY_SEED`, +`NATS_SUBJECT_PREFIX`. + +### `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. + +```bash +echo '' | lian-build integrate \ + [--nats-url ] [--nats-nkey-seed ] +``` + +Omit `--nats-url` to skip event emission with a warning (the envelope still +goes to stdout). + +--- + +## Three-tier runner sizing + +`sizing::resolve` walks three sources, first match wins: + +1. **Explicit** — `.build-spec.ncl` in the build context, validated against + `schemas/build_spec.ncl` (`bounded_cpu_` ≤ 256, `bounded_time_budget_` ≤ + 1440 min). +2. **P95 historical** — `OrchestratorClient::get_p95(workspace)` returns + measured CPU / memory P95 from prior runs; multiplied by 1.2, floored at + `min(2 cpu, 4 GB)`. +3. **Language defaults** — `RunnerSize::language_default(lang)`: + + | Language | CPU | Memory | Time budget | + |------------------------|-----|--------|-------------| + | `rust` | 4 | 8 GB | 60 min | + | `go` | 2 | 4 GB | 30 min | + | `java` / `kotlin` / `scala` | 4 | 8 GB | 45 min | + | *(default)* | 2 | 4 GB | 30 min | + +On exit-code `137` or stderr containing `OOM` / `Killed`, the build retries +**once** at the next tier (`cx22` → `cx32` → `cx42` → `cx52`). The retry cap +is hard-bound (`retry::MAX_OOM_RETRIES = 1`). + +--- + +## Cache namespacing + +Two layers, defined in `schemas/cache_policy.ncl` and enforced in `src/cache.rs`: + +- `ci//*` — canonical, written by CI, **read-only** to sessions. +- `dev/-/*` — 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`, `ComputeProviderRef`, `RegistryProviderRef`, `RunnerOverride`, `NatsEventConfig` | +| `schemas/build_spec.ncl` | `BuildSpec` (per-repo `.build-spec.ncl`) | +| `schemas/cache_policy.ncl` | `CachePolicy`, `BuildMode`, `SessionActor`, `SessionCacheDisposition` | +| `schemas/build_result.ncl` | `BuildResult` envelope shape | +| `schemas/vault_refs.ncl` | `VaultCredRef`, `VaultKeyRef` | +| `defaults/build_directives.ncl` | `make_*` constructors, `ci_cache_policy` / `session_cache_policy` helpers | + +Validate with the `nickel` CLI: + +```bash +nickel export \ + --import-path /Users/Akasha/Development/lian-build \ + --import-path /Users/Akasha/Development/lian-build/schemas \ + --import-path /Users/Akasha/Development/ontoref \ + --import-path /Users/Akasha/Development/ontoref/ontology \ + schemas/build_directives.ncl +``` + +--- + +## Hard constraints (ADR-001) + +These two rules are grep-checked and define the lift-out boundary: + +1. **`no-provisioning-lib-import`** — `Cargo.toml` and `src/` must not match + `platform-config|provisioning|stratum-`. The `platform-nats` path-dep from + `stratumiops` is explicitly allowed; the constraint targets the + `provisioning` workspace and `stratum-`-prefixed crates. +2. **`build-directives-ncl-vocabulary`** — `src/` must not match + `provisioning_workspace|vapora_|woodpecker_`. Caller-specific logic stays + in caller-supplied directives, not in core. + +--- + +## Build / test / run + +```bash +cargo build # debug +cargo build --release # release at target/release/lian-build +cargo clippy -- -D warnings # mandatory before commit +cargo fmt +cargo test # full suite +cargo test # single test by substring +cargo test -- --nocapture # show tracing in tests +``` + +`just` recipes live in `justfiles/{build,test,ci}.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 parsing, top-level orchestration, OOM-retry control flow + buildctl_runner.rs # rsync_context + run_buildctl over SSH; OOM_EXIT_CODE = 137 + cache.rs # BuildMode, build_cache_flags, ci/* vs dev//* invariants + directives.rs # BuildDirectives ↔ JSON via `nickel export` subprocess + integration/ # federated probe handler (stdin → ResultEnvelope on stdout) + context.rs · event.rs · handler.rs · result.rs · mod.rs + nats_events.rs # BuildEventPublisher over platform-nats::EventStream + orchestrator_client.rs # HTTP client: spawn_runner, destroy_runner, get_p95, record_metrics + retry.rs # MAX_OOM_RETRIES = 1; SIZE_TIERS walk (cx22→cx32→cx42→cx52) + sizing.rs # three-tier resolution +``` + +Operational surface: + +``` +adrs/ # accepted ADRs (NCL) +.ontology/ # core, state, gate, manifest — design intent +reflection/ # modes, backlog, qa +schemas/ # caller-facing NCL contracts +defaults/ # constructor / helper NCL +catalog/{domains,modes}/ # federated peer publishing layout +examples/sample.ncl # example BuildDirectives instance +tests/fixtures/ # integration test fixtures +.coder/ # session interaction files (not product docs) +.claude/ # operational config (symlinks into shared dev-system) +``` + +--- + +## Further reading + +- `adrs/adr-001-lian-build-as-standalone.ncl` — why this project exists, + alternatives rejected, the two grep-checked invariants. +- `adrs/adr-002-cli-subcommand-discipline.ncl` — subcommand-only surface, + stderr/stdout discipline. +- `adrs/adr-003-nickel-via-subprocess.ncl` — why `nickel` is on `$PATH`, + not in `Cargo.toml`. +- `.ontology/core.ncl` — axioms (ephemeral-builds, provider-pluggability, + cache-content-addressed, caller-supplies-directives), tensions, practices. +- `.ontology/state.ncl` — five maturity dimensions and their current + transitions (provider-pluggability, session-multi-actor, + active-active-registry, caller-integration, peer-publishing). +- `CHANGES.md` — record of accepted decisions and visible surface changes. diff --git a/assets/branding/index.html b/assets/branding/index.html new file mode 100644 index 0000000..a777c22 --- /dev/null +++ b/assets/branding/index.html @@ -0,0 +1,1062 @@ + + + + + + Lian Build — Branding Assets + + + + +

+ +
+
+
+

Lian Build

+

Fire without the ash.

+

Ephemeral crucibles that refine code into reproducible artifacts

+
+
+

Branding Assets

+
+
+ + +
+

Logo Variants

+ +
+ + +
+

Icon Variants

+ +
+ + +
+

Monochrome Variants

+ +
+ + +
+

Scalability Test

+
+

Icon clarity at different sizes — from favicon to app icon

+
+
+
+
+ 16×16 +
+
16×16
+
Favicon
+
+
+
+ 32×32 +
+
32×32
+
Browser tab
+
+
+
+ 64×64 +
+
64×64
+
App icon
+
+
+
+ 128×128 +
+
128×128
+
Apple touch
+
+
+
+ 200×200 +
+
200×200
+
PWA icon
+
+
+
+ + +
+

Color Palette

+
+
+
+
Flame
+
#e86c2f📋
+
+
+
+
Amber
+
#f5a623📋
+
+
+
+
Core
+
#fcd99e📋
+
+
+
+
Gold
+
#bd9156📋
+
+
+
+
Forge
+
#f29a3d📋
+
+
+
+
Char
+
#c25a1a📋
+
+
+
+
Ash
+
#a8a8a0📋
+
+
+
+
Dark
+
#0d1117📋
+
+
+
+ + +
+

Typography

+
+ +
+
Wordmark
+
lian
+
+
+ Family + Jost +
+
+ Weight + 500 +
+
+ Tracking + .22em +
+
+ Use + Logo, H1 +
+
+
+ +
+
Subtitle
+
build
+
+
+ Family + Jost +
+
+ Weight + 500 +
+
+ Size + 14px SVG +
+
+ Use + Subtitle +
+
+
+ +
+
UI / Body
+
Fire without the ash.
+
+
+ Family + System stack +
+
+ Weight + 400 +
+
+ Size + 16px +
+
+ Use + Body text +
+
+
+ +
+
Code / Files
+
lian-h.svg
+
+
+ Family + Monaco +
+
+ Weight + 400 +
+
+ Size + 14px +
+
+ Use + Code, files +
+
+
+ +
+ +
+

Font Usage

+

Jost (weight 500, tracking .22em) for the wordmark and subtitle. System font stack for all UI elements. The code symbols (</>) inside the flame glyph are drawn as SVG paths — no font dependency.

+
Google Fonts import:
+
+ @import url('https://fonts.googleapis.com/css2?family=Jost:wght@400;500&display=swap'); + +
+
CSS font-family (wordmark fallback):
+
+ font-family: "Jost", "Avenir Next", Futura, "Century Gothic", "Helvetica Neue", system-ui, sans-serif; + +
+
+
+ + +
+

Usage Guidelines

+ +
+

Logo Usage

+

Use the horizontal logo for primary brand identification on websites, headers, and marketing materials. The vertical variant works well for mobile, splash screens, and posters. For print and PDFs, always use the static variants (no animations). Maintain a minimum of 15px clear space around all logos. Never distort, rotate, or modify the aspect ratio.

+
+ +
+

Color Palette

+

Flame (#e86c2f) and Amber (#f5a623) are the primary brand colors — they represent the forge: heat, transformation, and precision engineering. Gold (#bd9156) is used for the wordmark "lian", conveying maturity and craft. Forge (#f29a3d) is used for the subtitle "build". Use Dark (#0d1117) as the background for dark-mode contexts. Monochrome variants are for print and accessibility requirements.

+
+ +
+

Typography

+

Use Jost (weight 500, letter-spacing .22em) for the wordmark "lian" and subtitle "build". The font conveys geometric clarity while remaining approachable. For all UI elements and body text, use the system font stack. For filenames, code snippets, and hex values, use Monaco / Courier New monospace. The </> code symbol inside the flame is drawn as pure SVG paths — no external font required.

+
+ +
+

Icon / Glyph Usage

+

Use lian-icon.svg for digital applications with animation support. Use lian-icon-static.svg for favicons, print, and contexts where animation is inappropriate. The glyph is a flame shape representing the forge metaphor — transformation through fire. The inner core pulses as a living ember. The crucible line (horizontal bar at the base) grounds the flame. The </> code marks inside the flame link build systems to software craft.

+
+ +
+

Dark Mode

+

Dark backgrounds of #0d1117 or darker provide optimal contrast — the flame colors are inherently bright and stand out on dark surfaces without adjustment. Use lian-dark-h.svg and lian-dark-v.svg for dark UI applications; these variants use brighter Gold (#d4a870) and Forge (#fbb555) for the text to maintain contrast. Avoid placing the standard logo on dark backgrounds without using the dark-specific variants.

+
+
+ +
+

Lian Build Branding System v1.0 · Fire without the ash. · All assets SVG format

+
+
+ + + + diff --git a/assets/branding/lian-dark-h.svg b/assets/branding/lian-dark-h.svg new file mode 100644 index 0000000..a786bd7 --- /dev/null +++ b/assets/branding/lian-dark-h.svg @@ -0,0 +1,71 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-dark-v.svg b/assets/branding/lian-dark-v.svg new file mode 100644 index 0000000..8101c78 --- /dev/null +++ b/assets/branding/lian-dark-v.svg @@ -0,0 +1,71 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-h-static.svg b/assets/branding/lian-h-static.svg new file mode 100644 index 0000000..2ea0718 --- /dev/null +++ b/assets/branding/lian-h-static.svg @@ -0,0 +1,29 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-h.svg b/assets/branding/lian-h.svg new file mode 100644 index 0000000..242d3e0 --- /dev/null +++ b/assets/branding/lian-h.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-icon-static.svg b/assets/branding/lian-icon-static.svg new file mode 100644 index 0000000..8b4452e --- /dev/null +++ b/assets/branding/lian-icon-static.svg @@ -0,0 +1,23 @@ + + + Lian Build icon + + + + + + + + + + + + + + + + + + + + diff --git a/assets/branding/lian-icon.svg b/assets/branding/lian-icon.svg new file mode 100644 index 0000000..a11823e --- /dev/null +++ b/assets/branding/lian-icon.svg @@ -0,0 +1,59 @@ + + + Lian Build icon + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/branding/lian-mono-black-h.svg b/assets/branding/lian-mono-black-h.svg new file mode 100644 index 0000000..039675c --- /dev/null +++ b/assets/branding/lian-mono-black-h.svg @@ -0,0 +1,21 @@ + + + Lian Build + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-mono-black-v.svg b/assets/branding/lian-mono-black-v.svg new file mode 100644 index 0000000..6cd0875 --- /dev/null +++ b/assets/branding/lian-mono-black-v.svg @@ -0,0 +1,21 @@ + + + Lian Build + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-mono-white-h.svg b/assets/branding/lian-mono-white-h.svg new file mode 100644 index 0000000..cf2fc34 --- /dev/null +++ b/assets/branding/lian-mono-white-h.svg @@ -0,0 +1,22 @@ + + + Lian Build + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-mono-white-v.svg b/assets/branding/lian-mono-white-v.svg new file mode 100644 index 0000000..aa7ab7c --- /dev/null +++ b/assets/branding/lian-mono-white-v.svg @@ -0,0 +1,22 @@ + + + Lian Build + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-text.svg b/assets/branding/lian-text.svg new file mode 100644 index 0000000..eee3e33 --- /dev/null +++ b/assets/branding/lian-text.svg @@ -0,0 +1,18 @@ + + + Lian Build wordmark + + + + lian + build + diff --git a/assets/branding/lian-v-static.svg b/assets/branding/lian-v-static.svg new file mode 100644 index 0000000..9c98d1f --- /dev/null +++ b/assets/branding/lian-v-static.svg @@ -0,0 +1,29 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/branding/lian-v.svg b/assets/branding/lian-v.svg new file mode 100644 index 0000000..35f026c --- /dev/null +++ b/assets/branding/lian-v.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/lian-h-static.svg b/assets/lian-h-static.svg new file mode 100644 index 0000000..2ea0718 --- /dev/null +++ b/assets/lian-h-static.svg @@ -0,0 +1,29 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/lian-h.svg b/assets/lian-h.svg new file mode 100644 index 0000000..242d3e0 --- /dev/null +++ b/assets/lian-h.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/lian-v-static.svg b/assets/lian-v-static.svg new file mode 100644 index 0000000..9c98d1f --- /dev/null +++ b/assets/lian-v-static.svg @@ -0,0 +1,29 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/lian-v.svg b/assets/lian-v.svg new file mode 100644 index 0000000..35f026c --- /dev/null +++ b/assets/lian-v.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/web/index.html b/assets/web/index.html new file mode 100644 index 0000000..1dfc3e1 --- /dev/null +++ b/assets/web/index.html @@ -0,0 +1,13 @@ + lian-build — Ephemeral Build Substrate
炼 · 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
\ No newline at end of file diff --git a/assets/web/lian-h-static.svg b/assets/web/lian-h-static.svg new file mode 100644 index 0000000..2ea0718 --- /dev/null +++ b/assets/web/lian-h-static.svg @@ -0,0 +1,29 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/web/lian-h.svg b/assets/web/lian-h.svg new file mode 100644 index 0000000..242d3e0 --- /dev/null +++ b/assets/web/lian-h.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/web/lian-icon.svg b/assets/web/lian-icon.svg new file mode 100644 index 0000000..a11823e --- /dev/null +++ b/assets/web/lian-icon.svg @@ -0,0 +1,59 @@ + + + Lian Build icon + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/web/lian-v.svg b/assets/web/lian-v.svg new file mode 100644 index 0000000..35f026c --- /dev/null +++ b/assets/web/lian-v.svg @@ -0,0 +1,70 @@ + + + Lian Build + + + + + + + + + + + + + + + + + + + + + lian + build + diff --git a/assets/web/minify.sh b/assets/web/minify.sh new file mode 100755 index 0000000..6beb588 --- /dev/null +++ b/assets/web/minify.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# Minify HTML files from src/ to production versions in this directory. +# Adapted from /Users/Akasha/Development/ontoref/assets/web/minify.sh. +# Usage: ./minify.sh + +set -euo pipefail + +BASE_DIR="$(cd "$(dirname "$0")" && pwd)" +FILES=("index.html") + +minify_file() { + local filename="$1" + local SRC_FILE="${BASE_DIR}/src/${filename}" + local OUT_FILE="${BASE_DIR}/${filename}" + local TEMP_FILE="${OUT_FILE}.tmp" + + if [ ! -f "$SRC_FILE" ]; then + echo " Source file not found: $SRC_FILE (skipping)" + return 0 + fi + + echo "" + echo "Minifying ${filename}..." + echo " Input: $SRC_FILE" + echo " Output: $OUT_FILE" + + perl -e " +use strict; +use warnings; + +open(my \$fh, '<', '$SRC_FILE') or die \$!; +my \$content = do { local \$/; <\$fh> }; +close(\$fh); + +# Preserve
, , and