13 lines
No EOL
42 KiB
HTML
13 lines
No EOL
42 KiB
HTML
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title data-en="lian-build — Ephemeral Build Substrate" data-es="lian-build — Sustrato de Build Efímero" data-key="page-title" > lian-build — Ephemeral Build Substrate </title><link rel="icon" type="image/svg+xml" href="lian-icon.svg" /><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" /><style> *{margin:0;padding:0;box-sizing:border-box;}html{scroll-behavior:smooth;}:root{--lb-flame:#e86c2f;--lb-amber:#f5a623;--lb-gold:#bd9156;--lb-forge:#f29a3d;--lb-char:#c25a1a;--lb-core:#fcd99e;--bg-primary:#ffffff;--bg-secondary:#f8f7f5;--bg-elev:#ffffff;--text-primary:#1a1a14;--text-secondary:#6b6b5e;--text-muted:#94948a;--border-color:#e5e3de;--code-bg:#f3efe8;--code-text:#1a1a14;--grad-fg:linear-gradient( 135deg,#e86c2f 0%,#f5a623 60%,#fcd99e 100% );--grad-bg:radial-gradient( circle at 18% 18%,rgba(232,108,47,0.1) 0%,transparent 55% ),radial-gradient( circle at 82% 12%,rgba(245,166,35,0.1) 0%,transparent 55% ),radial-gradient( circle at 50% 95%,rgba(252,217,158,0.16) 0%,transparent 60% );}html.dark{--bg-primary:#0d1117;--bg-secondary:#141920;--bg-elev:#161c24;--text-primary:#e8e6e0;--text-secondary:#a8a49a;--text-muted:#6b6b5e;--border-color:#252d36;--code-bg:#0a0e13;--code-text:#fcd99e;--grad-bg:radial-gradient( circle at 18% 18%,rgba(232,108,47,0.14) 0%,transparent 55% ),radial-gradient( circle at 82% 12%,rgba(245,166,35,0.1) 0%,transparent 55% ),radial-gradient( circle at 50% 95%,rgba(189,145,86,0.1) 0%,transparent 60% );}body{font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:var(--bg-primary);color:var(--text-primary);overflow-x:hidden;transition:background 0.3s,color 0.3s;}.gradient-bg{position:fixed;inset:0;z-index:-1;background:var(--grad-bg);pointer-events:none;}.controls{position:fixed;top:1.25rem;right:1.25rem;z-index:100;display:flex;gap:0.5rem;}.lang-toggle,.theme-toggle{background:var(--bg-elev);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border-color);border-radius:999px;padding:0.3rem;display:flex;align-items:center;gap:0.2rem;}.lang-btn,.theme-btn{background:transparent;border:none;color:var(--text-secondary);padding:0.4rem 0.75rem;border-radius:999px;cursor:pointer;font:700 0.8rem "Inter",sans-serif;text-transform:uppercase;transition:all 0.25s ease;}.lang-btn:hover,.theme-btn:hover{color:var(--lb-flame);}.lang-btn.active{background:var(--grad-fg);color:#fff;}.theme-btn{padding:0.4rem 0.6rem;font-size:1rem;}.container{max-width:1200px;margin:0 auto;padding:2rem 1.5rem;position:relative;}header.hero{padding:3rem 0 0rem;text-align:center;animation:fadeInUp 0.8s ease-out;}@keyframes fadeInUp{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:translateY(0);}}.hero-logo{display:inline-block;margin-bottom:1.25rem;}.hero-logo img{height:200px;width:auto;filter:drop-shadow(0 6px 24px rgba(232,108,47,0.25));}.hero-badge{border:1px solid var(--border-color);color:var(--lb-flame);font:600 0.78rem "JetBrains Mono",monospace;padding:0.4rem 0.9rem;border-radius:999px;margin-bottom:1.2rem;letter-spacing:0.02em;}.hero-slogan{font-family:"Inter",sans-serif;font-style:italic;font-weight:700;font-size:clamp(1.6rem,3.4vw,2.4rem);letter-spacing:-0.01em;background:var(--grad-fg);-webkit-background-clip:text;background-clip:text;color:transparent;margin-bottom:1rem;line-height:1.2;}.hero-subtag{font-size:clamp(0.95rem,1.5vw,1.05rem);color:var(--text-secondary);font-style:italic;max-width:640px;margin:-0.4rem auto 1.6rem;line-height:1.5;}h1.hero-title{font-size:clamp(2.2rem,5vw,3.6rem);font-weight:800;line-height:1.1;letter-spacing:-0.02em;margin-bottom:1rem;}h1.hero-title .accent{background:var(--grad-fg);-webkit-background-clip:text;background-clip:text;color:transparent;}.hero-tagline{font-size:clamp(1rem,2vw,1.25rem);color:var(--text-secondary);max-width:760px;margin:0 auto 2rem;line-height:1.55;}.hero-cta{display:inline-flex;gap:0.75rem;flex-wrap:wrap;justify-content:center;}.btn{display:inline-flex;align-items:center;gap:0.5rem;padding:0.8rem 1.4rem;border-radius:8px;font:600 0.95rem "Inter",sans-serif;text-decoration:none;transition:transform 0.2s ease,box-shadow 0.2s ease;border:1px solid transparent;}.btn:hover{transform:translateY(-1px);}.btn-primary{background:var(--grad-fg);color:#fff;box-shadow:0 6px 18px rgba(232,108,47,0.28);}.btn-secondary{background:var(--bg-elev);color:var(--text-primary);border-color:var(--border-color);}.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem;margin:2.5rem 0 4rem;}.stat{background:var(--bg-elev);border:1px solid var(--border-color);border-radius:12px;padding:1.25rem 1rem;text-align:center;}.stat-value{font:800 2rem "JetBrains Mono",monospace;background:var(--grad-fg);-webkit-background-clip:text;background-clip:text;color:transparent;}.stat-label{font-size:0.85rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:0.05em;}.section{padding:4rem 0;}.section+.section{border-top:1px solid var(--border-color);}.section-eyebrow{font:600 0.78rem "JetBrains Mono",monospace;color:var(--lb-flame);letter-spacing:0.12em;text-transform:uppercase;margin-bottom:0.6rem;}.section-title{font-size:clamp(1.6rem,3vw,2.2rem);font-weight:800;margin-bottom:0.8rem;letter-spacing:-0.01em;}.section-lede{color:var(--text-secondary);max-width:720px;line-height:1.6;margin-bottom:2.5rem;}#axioms{padding-top:0;}.axiom-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1rem;}.axiom-card{background:var(--bg-elev);border:1px solid var(--border-color);border-radius:12px;padding:1.5rem;position:relative;transition:transform 0.2s ease,border-color 0.2s ease;}.axiom-card:hover{transform:translateY(-2px);border-color:var(--lb-amber);}.axiom-card .axiom-id{font:600 0.75rem "JetBrains Mono",monospace;color:var(--lb-flame);margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.06em;}.axiom-card h3{font-size:1.15rem;margin-bottom:0.6rem;}.axiom-card p{color:var(--text-secondary);font-size:0.92rem;line-height:1.55;}.arch-wrap{background:var(--bg-elev);border:1px solid var(--border-color);border-radius:12px;padding:1.5rem;margin-bottom:2rem;overflow-x:auto;}.arch-wrap svg{width:100%;height:auto;max-width:1100px;display:block;margin:0 auto;}.arch-svg .label-fg{fill:var(--text-primary);}.arch-svg .label-mut{fill:var(--text-secondary);}.arch-svg .label-mono{font-family:"JetBrains Mono",monospace;}.arch-svg .stroke-soft{stroke:var(--border-color);}.module-table{width:100%;border-collapse:collapse;background:var(--bg-elev);border:1px solid var(--border-color);border-radius:12px;overflow:hidden;}.module-table th,.module-table td{padding:0.85rem 1rem;text-align:left;border-bottom:1px solid var(--border-color);font-size:0.92rem;}.module-table th{background:var(--bg-secondary);color:var(--text-secondary);text-transform:uppercase;letter-spacing:0.05em;font-size:0.78rem;}.module-table tr:last-child td{border-bottom:none;}.module-table code{font:500 0.85rem "JetBrains Mono",monospace;color:var(--lb-flame);background:var(--code-bg);padding:0.1rem 0.4rem;border-radius:4px;}.cache-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1rem;}.cache-card{background:var(--bg-elev);border:1px solid var(--border-color);border-radius:12px;padding:1.5rem;}.cache-card.ci{border-top:3px solid var(--lb-amber);}.cache-card.dev{border-top:3px solid var(--lb-flame);}.cache-card h3{font:600 1.05rem "JetBrains Mono",monospace;margin-bottom:0.5rem;}.cache-card .pill{display:inline-block;font:600 0.7rem "Inter",sans-serif;padding:0.2rem 0.6rem;border-radius:999px;text-transform:uppercase;letter-spacing:0.06em;margin-bottom:0.8rem;}.cache-card.ci .pill{background:rgba(245,166,35,0.15);color:var(--lb-char);}.cache-card.dev .pill{background:rgba(232,108,47,0.15);color:var(--lb-flame);}html.dark .cache-card.ci .pill{color:var(--lb-amber);}.cache-card p{color:var(--text-secondary);line-height:1.55;font-size:0.92rem;}.tier-ladder{display:grid;gap:0.8rem;}.tier{display:grid;grid-template-columns:56px 1fr;background:var(--bg-elev);border:1px solid var(--border-color);border-radius:12px;overflow:hidden;}.tier-num{background:var(--grad-fg);color:#fff;font:800 1.4rem "JetBrains Mono",monospace;display:grid;place-items:center;}.tier-body{padding:1rem 1.25rem;}.tier-name{font:700 1rem "Inter",sans-serif;margin-bottom:0.25rem;}.tier-desc{color:var(--text-secondary);font-size:0.9rem;line-height:1.5;}.tier-desc code{font:500 0.85em "JetBrains Mono",monospace;background:var(--code-bg);color:var(--lb-flame);padding:0.05em 0.4em;border-radius:4px;}.constraint{background:var(--bg-elev);border:1px solid var(--border-color);border-left:4px solid var(--lb-flame);border-radius:10px;padding:1.25rem 1.4rem;margin-bottom:1rem;}.constraint-id{font:700 0.8rem "JetBrains Mono",monospace;color:var(--lb-flame);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:0.4rem;}.constraint-claim{font-size:1rem;font-weight:600;margin-bottom:0.5rem;}.constraint-grep{font:500 0.8rem "JetBrains Mono",monospace;background:var(--code-bg);color:var(--code-text);padding:0.5rem 0.7rem;border-radius:6px;display:inline-block;word-break:break-all;}.constraint-rationale{color:var(--text-secondary);font-size:0.9rem;line-height:1.55;margin-top:0.6rem;}.cli{background:#0a0e13;color:#e8e6e0;padding:1.5rem 1.75rem;border-radius:12px;font:500 0.88rem "JetBrains Mono",monospace;line-height:1.6;overflow-x:auto;border:1px solid #252d36;}.cli .opt{color:var(--lb-amber);}.cli .val{color:var(--lb-core);}.cli .cmt{color:#6b6b5e;}.cli .req{color:var(--lb-flame);font-weight:700;}footer{border-top:1px solid var(--border-color);padding:3rem 0 4rem;text-align:center;color:var(--text-secondary);}footer .footer-logo{height:28px;margin-bottom:1rem;opacity:0.85;}footer .footer-meta{font:500 0.82rem "JetBrains Mono",monospace;margin-top:0.6rem;color:var(--text-muted);}footer a{color:var(--lb-flame);text-decoration:none;}footer a:hover{text-decoration:underline;}@media (max-width:640px){.controls{top:0.75rem;right:0.75rem;flex-direction:column;align-items:flex-end;}header.hero{padding:5rem 0 0rem;}}</style></head><body><div class="gradient-bg"></div><div class="controls"><div class="lang-toggle" role="group" aria-label="Language"><button class="lang-btn active" data-lang="en" onclick="switchLanguage('en')" > EN </button><button class="lang-btn" data-lang="es" onclick="switchLanguage('es')"> ES </button></div><div class="theme-toggle" role="group" aria-label="Theme"><button id="theme-btn" class="theme-btn" onclick="toggleTheme()" aria-label="Toggle theme" > ☀️ </button></div></div><div class="container"><header class="hero"><a class="hero-logo" href="#"><img id="hero-logo-img" src="lian-h.svg" alt="lian-build" /></a><div class="hero-badge" data-en="炼 · Alchemical Refinement · v0.1.0 Beta" data-es="炼 · Refinamiento Alquímico · v0.1.0 Beta" data-key="hero-badge" > 炼 · Alchemical Refinement · v0.1.0 Beta </div><div class="hero-slogan" data-en="“Fire without the ash.”" data-es="“Fuego sin ceniza.”" data-key="hero-slogan" > “Fire without the ash.” </div><p class="hero-subtag" data-en="Ephemeral crucibles that refine code into reproducible artifacts." data-es="Crisoles efímeros que refinan código en artefactos reproducibles." data-key="hero-subtag" > Ephemeral crucibles that refine code into reproducible artifacts. </p><h1 class="hero-title"><span data-en="Ephemeral Build Substrate for" data-es="Sustrato de Build Efímero para" data-key="hero-title-1" >Ephemeral Build Substrate for</span ><br /><span class="accent" data-en="Provider-Pluggable BuildKit Sessions" data-es="Sesiones BuildKit con Proveedor Conectable" data-key="hero-title-2" >Provider-Pluggable BuildKit Sessions</span ></h1><p class="hero-tagline" data-en="Callers (provisioning, vapora, workspace CI) supply <code>BuildDirectives</code> 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." data-es="Los llamadores (provisioning, vapora, CI de workspace) suministran <code>BuildDirectives</code> en NCL. lian-build controla el aprovisionamiento de cómputo, el flujo de caché OCI y el espaciado de nombres por actor. Cómputo (hcloud / proxmox / docker-local) y registro (zot / harbor / ghcr) son slots conectables, no dependencias rígidas." data-key="hero-tagline" > Callers (provisioning, vapora, workspace CI) supply <code>BuildDirectives</code> 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. </p><div class="hero-cta"><a class="btn btn-primary" href="https://rlung.librecloud.online/LibreCloud/lian-build" data-en="View Repository →" data-es="Ver Repositorio →" data-key="cta-repo" >View Repository →</a ><a class="btn btn-secondary" href="#architecture" data-en="Architecture" data-es="Arquitectura" data-key="cta-arch" >Architecture</a ><a class="btn btn-secondary" href="#cli" data-en="CLI Reference" data-es="Referencia CLI" data-key="cta-cli" >CLI Reference</a ></div><div class="stats"><div class="stat"><div class="stat-value">4</div><div class="stat-label" data-en="Axioms" data-es="Axiomas" data-key="stat-axioms" > Axioms </div></div><div class="stat"><div class="stat-value">3</div><div class="stat-label" data-en="Spiral Tensions" data-es="Tensiones Espiral" data-key="stat-tensions" > Spiral Tensions </div></div><div class="stat"><div class="stat-value">3</div><div class="stat-label" data-en="Sizing Tiers" data-es="Niveles de Sizing" data-key="stat-sizing" > Sizing Tiers </div></div><div class="stat"><div class="stat-value">2</div><div class="stat-label" data-en="Hard Constraints" data-es="Constraints Duros" data-key="stat-constraints" > Hard Constraints </div></div></div></header><section class="section" id="axioms"><div class="section-eyebrow" data-en="Foundations · .ontology/core.ncl" data-es="Fundamentos · .ontology/core.ncl" data-key="ax-eyebrow" > Foundations · .ontology/core.ncl </div><h2 class="section-title" data-en="Four Axioms" data-es="Cuatro Axiomas" data-key="ax-title" > Four Axioms </h2><p class="section-lede" data-en="The invariant set the rest of the system is built on. Each is declared in <code>.ontology/core.ncl</code> with <code>invariant = true</code>; violating one is an architectural break, not a routine refactor." data-es="El conjunto invariante sobre el que se construye el resto del sistema. Cada uno se declara en <code>.ontology/core.ncl</code> con <code>invariant = true</code>; violar uno es una ruptura arquitectónica, no un refactor de rutina." data-key="ax-lede" > The invariant set the rest of the system is built on. Each is declared in <code>.ontology/core.ncl</code> with <code>invariant = true</code>; violating one is an architectural break, not a routine refactor. </p><div class="axiom-grid"><article class="axiom-card"><div class="axiom-id">A1 · ephemeral-builds</div><h3 data-en="Ephemeral Builds" data-es="Builds Efímeros" data-key="ax1-h" > Ephemeral Builds </h3><p data-en="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." data-es="Los entornos de build se levantan bajo demanda y se destruyen al terminar. No existe estado persistente de build fuera de la caché direccionada por contenido y el registro. Esto previene el drift de entorno y hace que los builds sean reproducibles por construcción." data-key="ax1-p" > 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. </p></article><article class="axiom-card"><div class="axiom-id">A2 · provider-pluggability</div><h3 data-en="Provider Pluggability" data-es="Proveedor Conectable" data-key="ax2-h" > Provider Pluggability </h3><p data-en="Compute (where buildkitd runs) and registry (where images and cache land) are plug-in slots. lian-build defines <code>ComputeProvider</code> and <code>RegistryProvider</code> traits; concrete implementations (hcloud, proxmox, docker-local; zot, harbor, ghcr) are selected at runtime via config." data-es="Cómputo (dónde corre buildkitd) y registro (dónde aterrizan imágenes y caché) son slots conectables. lian-build define los traits <code>ComputeProvider</code> y <code>RegistryProvider</code>; las implementaciones concretas (hcloud, proxmox, docker-local; zot, harbor, ghcr) se seleccionan en runtime vía config." data-key="ax2-p" > Compute and registry are plug-in slots, not hard dependencies. </p></article><article class="axiom-card"><div class="axiom-id">A3 · cache-content-addressed</div><h3 data-en="Content-Addressed Cache" data-es="Caché Direccionada por Contenido" data-key="ax3-h" > Content-Addressed Cache </h3><p data-en="All build cache is stored as OCI artifacts in the registry, keyed by content hash. No NFS, no shared volumes, no sticky state. Namespaces split into <code>ci/*</code> (CI-written, read-only for sessions) and <code>dev/<actor-id>-*</code> (per-actor ephemeral)." data-es="Toda la caché de build se almacena como artefactos OCI en el registro, indexada por hash de contenido. Sin NFS, sin volúmenes compartidos, sin estado pegajoso. Los namespaces se dividen en <code>ci/*</code> (escrito por CI, solo-lectura para sesiones) y <code>dev/<actor-id>-*</code> (efímero por actor)." data-key="ax3-p" > All build cache is stored as OCI artifacts, keyed by content hash. </p></article><article class="axiom-card"><div class="axiom-id">A4 · caller-supplies-directives</div><h3 data-en="Caller Supplies Directives" data-es="El Llamador Suministra Directivas" data-key="ax4-h" > Caller Supplies Directives </h3><p data-en="lian-build does not decide what to build or how. Callers (workspace infras, CI pipelines, vapora agents) supply <code>BuildDirectives</code> in lian-build's NCL vocabulary. lian-build controls execution; callers control intent. This separation makes lian-build reusable across arbitrarily different build strategies." data-es="lian-build no decide qué ni cómo construir. Los llamadores (infras de workspace, pipelines CI, agentes vapora) suministran <code>BuildDirectives</code> en el vocabulario NCL de lian-build. lian-build controla ejecución; los llamadores controlan intención." data-key="ax4-p" > lian-build does not decide what to build or how — callers do. </p></article></div></section><section class="section" id="architecture"><div class="section-eyebrow" data-en="Build Flow · src/main.rs::main" data-es="Flujo del Build · src/main.rs::main" data-key="arch-eyebrow" > Build Flow · src/main.rs::main </div><h2 class="section-title" data-en="Architecture" data-es="Arquitectura" data-key="arch-title" > Architecture </h2><p class="section-lede" data-en="A single binary orchestrates an external HTTP service, drives a spawned VM over SSH, and emits lifecycle events on NATS. Everything below is what happens between <code>lian-build --workspace ws --context dir --image ref</code> being invoked and the runner being destroyed." data-es="Un único binario orquesta un servicio HTTP externo, controla la VM levantada por SSH y emite eventos de ciclo de vida por NATS. Todo lo de abajo es lo que pasa entre invocar <code>lian-build --workspace ws --context dir --image ref</code> y la destrucción del runner." data-key="arch-lede" > A single binary orchestrates an external HTTP service, drives a spawned VM over SSH, and emits lifecycle events on NATS. </p><div class="arch-wrap"><svg class="arch-svg" viewBox="0 0 1100 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="lian-build architecture diagram" ><defs><linearGradient id="grad-flame" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#e86c2f" /><stop offset="1" stop-color="#f5a623" /></linearGradient><linearGradient id="grad-soft" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fcd99e" stop-opacity="0.32" /><stop offset="1" stop-color="#bd9156" stop-opacity="0.10" /></linearGradient><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse" ><path d="M0,0 L10,5 L0,10 z" fill="#e86c2f" /></marker><marker id="arrow-mut" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse" ><path d="M0,0 L10,5 L0,10 z" fill="#bd9156" /></marker></defs><g><rect x="380" y="20" width="340" height="78" rx="10" fill="url(#grad-soft)" stroke="#e86c2f" stroke-width="1.5" /><text x="550" y="48" text-anchor="middle" class="label-fg" font-family="Inter, sans-serif" font-weight="700" font-size="15" > Caller </text><text x="550" y="68" text-anchor="middle" class="label-mut label-mono" font-size="12" > provisioning · vapora · workspace CI </text><text x="550" y="86" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="11" > supplies BuildDirectives.ncl → CLI flags </text></g><line x1="550" y1="98" x2="550" y2="148" stroke="#e86c2f" stroke-width="2" marker-end="url(#arrow)" /><text x="565" y="125" class="label-mut label-mono" font-size="11"> --workspace --context --image </text><g><rect x="280" y="150" width="540" height="120" rx="12" fill="url(#grad-flame)" /><text x="550" y="182" text-anchor="middle" fill="#fff" font-family="Inter, sans-serif" font-weight="800" font-size="18" > lian-build </text><text x="550" y="204" text-anchor="middle" fill="#fff" opacity="0.95" font-family="JetBrains Mono, monospace" font-size="12" > main.rs · run_build() </text><g font-family="JetBrains Mono, monospace" font-size="11" fill="#fff" ><rect x="298" y="220" width="124" height="34" rx="6" fill="rgba(255,255,255,0.18)" /><text x="360" y="241" text-anchor="middle"> orchestrator_client </text><rect x="430" y="220" width="100" height="34" rx="6" fill="rgba(255,255,255,0.18)" /><text x="480" y="241" text-anchor="middle">sizing</text><rect x="540" y="220" width="124" height="34" rx="6" fill="rgba(255,255,255,0.18)" /><text x="602" y="241" text-anchor="middle"> buildctl_runner </text><rect x="672" y="220" width="130" height="34" rx="6" fill="rgba(255,255,255,0.18)" /><text x="737" y="241" text-anchor="middle"> retry · nats_events </text></g></g><g><rect x="880" y="170" width="200" height="80" rx="10" fill="var(--bg-elev,#fff)" class="stroke-soft" stroke-width="1.5" /><text x="980" y="198" text-anchor="middle" class="label-fg" font-family="Inter, sans-serif" font-weight="700" font-size="14" > Orchestrator </text><text x="980" y="218" text-anchor="middle" class="label-mut label-mono" font-size="11" > localhost:9011 </text><text x="980" y="236" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="10.5" > spawn · destroy · p95 · metrics </text></g><line x1="820" y1="210" x2="876" y2="210" stroke="#e86c2f" stroke-width="2" marker-end="url(#arrow)" /><text x="848" y="202" text-anchor="middle" class="label-mut label-mono" font-size="10" > HTTP </text><g><rect x="20" y="170" width="200" height="80" rx="10" fill="var(--bg-elev,#fff)" class="stroke-soft" stroke-width="1.5" /><text x="120" y="198" text-anchor="middle" class="label-fg" font-family="Inter, sans-serif" font-weight="700" font-size="14" > NATS </text><text x="120" y="218" text-anchor="middle" class="label-mut label-mono" font-size="11" > <prefix>.<ws>.build.* </text><text x="120" y="236" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="10.5" > started · completed · failed </text></g><line x1="280" y1="210" x2="224" y2="210" stroke="#bd9156" stroke-width="1.6" stroke-dasharray="4 3" marker-end="url(#arrow-mut)" /><text x="252" y="202" text-anchor="middle" class="label-mut label-mono" font-size="10" > events </text><g><rect x="600" y="340" width="280" height="120" rx="12" fill="var(--bg-elev,#fff)" class="stroke-soft" stroke-width="1.5" /><text x="740" y="368" text-anchor="middle" class="label-fg" font-family="Inter, sans-serif" font-weight="700" font-size="15" > Spawned Runner VM </text><text x="740" y="388" text-anchor="middle" class="label-mut label-mono" font-size="12" > cx22 → cx32 → cx42 → cx52 </text><text x="740" y="408" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="11.5" > rsync context · buildctl build </text><text x="740" y="428" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="11.5" > OOM exit 137 → tier walk · max 1 retry </text></g><line x1="620" y1="270" x2="700" y2="338" stroke="#e86c2f" stroke-width="2" marker-end="url(#arrow)" /><text x="690" y="305" class="label-mut label-mono" font-size="10.5"> SSH </text><g><rect x="220" y="340" width="280" height="120" rx="12" fill="var(--bg-elev,#fff)" class="stroke-soft" stroke-width="1.5" /><text x="360" y="368" text-anchor="middle" class="label-fg" font-family="Inter, sans-serif" font-weight="700" font-size="15" > OCI Registry </text><text x="360" y="388" text-anchor="middle" class="label-mut label-mono" font-size="12" > zot · harbor · ghcr </text><text x="360" y="408" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="11.5" > image push · cache import / export </text><text x="360" y="428" text-anchor="middle" class="label-mut label-mono" font-size="11" > ci/<ws>/* · dev/<actor>-<ws>/* </text></g><line x1="480" y1="270" x2="400" y2="338" stroke="#e86c2f" stroke-width="2" marker-end="url(#arrow)" /><text x="425" y="305" class="label-mut label-mono" font-size="10.5"> cache · push </text><text x="550" y="500" text-anchor="middle" class="label-mut" font-family="Inter, sans-serif" font-size="11" > spawn → rsync → buildctl → record_metrics → destroy (always, even on failure) </text></svg></div><table class="module-table"><thead><tr><th data-en="Module" data-es="Módulo" data-key="mod-h-1"> Module </th><th data-en="Responsibility" data-es="Responsabilidad" data-key="mod-h-2" > Responsibility </th></tr></thead><tbody><tr><td><code>main.rs</code></td><td data-en="CLI parsing, top-level orchestration, NATS event dispatch, OOM-retry control flow." data-es="Parsing CLI, orquestación top-level, despacho de eventos NATS, flujo de retry por OOM." data-key="mod-main" > CLI parsing, top-level orchestration, NATS event dispatch, OOM-retry control flow. </td></tr><tr><td><code>orchestrator_client.rs</code></td><td data-en="HTTP client: <code>spawn_runner</code>, <code>destroy_runner</code>, <code>get_p95</code>, <code>record_metrics</code>. Wraps responses in <code>ApiResponse<T></code>." data-es="Cliente HTTP: <code>spawn_runner</code>, <code>destroy_runner</code>, <code>get_p95</code>, <code>record_metrics</code>. Envuelve respuestas en <code>ApiResponse<T></code>." data-key="mod-orch" > HTTP client: spawn/destroy/p95/record_metrics, wrapping responses in ApiResponse<T>. </td></tr><tr><td><code>buildctl_runner.rs</code></td><td data-en="Drives the spawned VM over SSH. Pushes context with <code>rsync</code>, runs <code>buildctl</code> remotely, detects OOM via exit code 137." data-es="Controla la VM levantada por SSH. Empuja el contexto con <code>rsync</code>, corre <code>buildctl</code> remotamente, detecta OOM por exit code 137." data-key="mod-bctl" > Drives the spawned VM over SSH. rsync context, run buildctl remotely, detect OOM via exit 137. </td></tr><tr><td><code>sizing.rs</code></td><td data-en="Three-tier sizing resolution: explicit <code>.build-spec.ncl</code> → P95 historical × 1.2 → language defaults." data-es="Resolución de sizing en tres niveles: <code>.build-spec.ncl</code> explícito → P95 histórico × 1.2 → defaults por lenguaje." data-key="mod-sz" > Three-tier sizing resolution: explicit spec → P95 × 1.2 → language defaults. </td></tr><tr><td><code>retry.rs</code></td><td data-en="OOM-retry policy: <code>MAX_OOM_RETRIES = 1</code> (hard bound from ADR-039). Tier walk: cx22 → cx32 → cx42 → cx52." data-es="Política de retry por OOM: <code>MAX_OOM_RETRIES = 1</code> (cota dura por ADR-039). Recorrido de tiers: cx22 → cx32 → cx42 → cx52." data-key="mod-rt" > OOM-retry policy: MAX_OOM_RETRIES = 1 (ADR-039). Tier walk cx22→cx32→cx42→cx52. </td></tr><tr><td><code>nats_events.rs</code></td><td data-en="<code>BuildEventPublisher</code> over <code>platform-nats::EventStream</code>. Publishes started/completed/failed." data-es="<code>BuildEventPublisher</code> sobre <code>platform-nats::EventStream</code>. Publica started/completed/failed." data-key="mod-ne" > BuildEventPublisher over platform-nats::EventStream. Publishes started/completed/failed. </td></tr></tbody></table></section><section class="section" id="cache"><div class="section-eyebrow" data-en="Cache Model · schemas/cache_policy.ncl" data-es="Modelo de Caché · schemas/cache_policy.ncl" data-key="ca-eyebrow" > Cache Model · schemas/cache_policy.ncl </div><h2 class="section-title" data-en="Cache Namespace Split" data-es="División de Namespaces de Caché" data-key="ca-title" > Cache Namespace Split </h2><p class="section-lede" data-en="Sessions read both <code>ci/*</code> and their own <code>dev/*</code>; CI never imports from <code>dev/*</code>. This resolves the <em>build-speed-vs-isolation</em> Spiral tension: shared reads, isolated writes." data-es="Las sesiones leen tanto <code>ci/*</code> como su propio <code>dev/*</code>; CI nunca importa desde <code>dev/*</code>. Esto resuelve la tensión Espiral <em>build-speed-vs-isolation</em>: lecturas compartidas, escrituras aisladas." data-key="ca-lede" > Sessions read both ci/* and their own dev/*; CI never imports from dev/*. </p><div class="cache-grid"><div class="cache-card ci"><h3>ci/<workspace>/*</h3><span class="pill" data-en="Canonical · Read-only for sessions" data-es="Canónico · Solo-lectura para sesiones" data-key="ca-pill-ci" >Canonical · Read-only for sessions</span ><p data-en="Written by CI pipelines. Sessions can import from this namespace but never write back. This is the warm cache that keeps the golden path fast." data-es="Escrito por pipelines de CI. Las sesiones pueden importar desde este namespace pero nunca escribir en él. Es la caché caliente que mantiene la golden path rápida." data-key="ca-p-ci" > Written by CI. Sessions import but never write back. </p></div><div class="cache-card dev"><h3>dev/<actor-id>-<workspace>/*</h3><span class="pill" data-en="Ephemeral · Per session actor" data-es="Efímero · Por actor de sesión" data-key="ca-pill-dev" >Ephemeral · Per session actor</span ><p data-en="One namespace per <code>SessionActor</code> (Human / Agent / CiAux). On session end the disposition decides — <code>'export</code> persists, <code>'discard</code> wipes, <code>'rollback</code> reverts to entry." data-es="Un namespace por <code>SessionActor</code> (Human / Agent / CiAux). Al cierre de sesión la disposición decide — <code>'export</code> persiste, <code>'discard</code> borra, <code>'rollback</code> revierte al estado inicial." data-key="ca-p-dev" > One namespace per SessionActor. Disposition: export / discard / rollback. </p></div></div></section><section class="section" id="sizing"><div class="section-eyebrow" data-en="Sizing · src/sizing.rs::resolve" data-es="Sizing · src/sizing.rs::resolve" data-key="sz-eyebrow" > Sizing · src/sizing.rs::resolve </div><h2 class="section-title" data-en="Three-Tier Resolution" data-es="Resolución en Tres Niveles" data-key="sz-title" > Three-Tier Resolution </h2><p class="section-lede" data-en="First match wins. Explicit spec is authoritative; P95 is the empirical fallback; language default is the floor when nothing else is known." data-es="Gana la primera coincidencia. La spec explícita es autoritativa; el P95 es el fallback empírico; el default por lenguaje es el suelo cuando no hay más datos." data-key="sz-lede" > First match wins. </p><div class="tier-ladder"><div class="tier"><div class="tier-num">1</div><div class="tier-body"><div class="tier-name" data-en="Explicit BuildSpec" data-es="BuildSpec Explícito" data-key="sz1-n" > Explicit BuildSpec </div><div class="tier-desc" data-en="<code>.build-spec.ncl</code> in the build context, validated against <code>schemas/build_spec.ncl</code> (<code>bounded_cpu_</code> ≤256, <code>bounded_time_budget_</code> ≤1440 min). Parsed via subprocess <code>nickel export --format json</code>." data-es="<code>.build-spec.ncl</code> en el contexto del build, validado contra <code>schemas/build_spec.ncl</code> (<code>bounded_cpu_</code> ≤256, <code>bounded_time_budget_</code> ≤1440 min). Parseado vía subproceso <code>nickel export --format json</code>." data-key="sz1-d" > .build-spec.ncl in the build context, validated against schemas/build_spec.ncl. Parsed via nickel export --format json. </div></div></div><div class="tier"><div class="tier-num">2</div><div class="tier-body"><div class="tier-name" data-en="P95 Historical × 1.2" data-es="P95 Histórico × 1.2" data-key="sz2-n" > P95 Historical × 1.2 </div><div class="tier-desc" data-en="<code>OrchestratorClient::get_p95(workspace)</code> returns measured CPU/memory P95 from prior runs. Multiplied by 1.2 for headroom; floored at min(2 cpu, 4 GB)." data-es="<code>OrchestratorClient::get_p95(workspace)</code> devuelve el P95 medido de CPU/memoria de runs previos. Se multiplica por 1.2 para holgura; suelo en min(2 cpu, 4 GB)." data-key="sz2-d" > OrchestratorClient::get_p95(workspace) returns measured P95 from prior runs. </div></div></div><div class="tier"><div class="tier-num">3</div><div class="tier-body"><div class="tier-name" data-en="Language Defaults" data-es="Defaults por Lenguaje" data-key="sz3-n" > Language Defaults </div><div class="tier-desc" data-en="<code>RunnerSize::language_default(lang)</code>: rust = 4 cpu / 8 GB / 60 min · go = 2 / 4 / 30 · java | kotlin | scala = 4 / 8 / 45 · default = 2 / 4 / 30." data-es="<code>RunnerSize::language_default(lang)</code>: rust = 4 cpu / 8 GB / 60 min · go = 2 / 4 / 30 · java | kotlin | scala = 4 / 8 / 45 · default = 2 / 4 / 30." data-key="sz3-d" > RunnerSize::language_default(lang). </div></div></div></div></section><section class="section" id="constraints"><div class="section-eyebrow" data-en="Lift-out Boundary · ADR-001" data-es="Frontera de Lift-out · ADR-001" data-key="cn-eyebrow" > Lift-out Boundary · ADR-001 </div><h2 class="section-title" data-en="Two Hard Constraints" data-es="Dos Constraints Duros" data-key="cn-title" > Two Hard Constraints </h2><p class="section-lede" data-en="These two are <strong>grep-checked</strong> and define the lift-out boundary from provisioning. Violating either re-couples lian-build to its host project." data-es="Estos dos se <strong>verifican por grep</strong> y definen la frontera de lift-out frente a provisioning. Violar cualquiera vuelve a acoplar lian-build a su proyecto anfitrión." data-key="cn-lede" > These two are grep-checked and define the lift-out boundary. </p><div class="constraint"><div class="constraint-id"> C1 · no-provisioning-lib-import · Hard </div><div class="constraint-claim" data-en="lian-build source code must not import any crate from the provisioning workspace as a library dependency." data-es="El código fuente de lian-build no debe importar ningún crate del workspace de provisioning como dependencia de librería." data-key="cn1-claim" > lian-build source code must not import any crate from the provisioning workspace as a library dependency. </div><div class="constraint-grep"> grep -rE 'platform-config|provisioning|stratum-' Cargo.toml src/ → must be empty </div><div class="constraint-rationale" data-en="Note: <code>platform-nats</code> from the broader stratumiops shared infrastructure is allowed — the constraint targets the <code>provisioning</code> workspace specifically and <code>stratum-</code>-prefixed crates." data-es="Nota: <code>platform-nats</code> desde la infraestructura compartida stratumiops está permitido — el constraint apunta específicamente al workspace <code>provisioning</code> y crates con prefijo <code>stratum-</code>." data-key="cn1-r" > Note: platform-nats from stratumiops is allowed; constraint targets provisioning + stratum- crates. </div></div><div class="constraint"><div class="constraint-id"> C2 · build-directives-ncl-vocabulary · Hard </div><div class="constraint-claim" data-en="Callers must supply intent via lian-build's NCL <code>BuildDirectives</code> schema; no caller-specific logic may exist in lian-build core." data-es="Los llamadores deben suministrar la intención vía el esquema NCL <code>BuildDirectives</code>; no puede existir lógica específica del llamador en el núcleo de lian-build." data-key="cn2-claim" > Callers must supply intent via lian-build's NCL BuildDirectives schema. </div><div class="constraint-grep"> grep -rE 'provisioning_workspace|vapora_|woodpecker_' src/ → must be empty </div><div class="constraint-rationale" data-en="Caller-specific logic stays in caller-supplied directives, not in core. The schema in <code>schemas/build_directives.ncl</code> is the source of truth — when work moves toward <code>--directives <ncl-path></code>, do not branch on caller identity in core code." data-es="La lógica específica del llamador vive en las directivas suministradas, no en el núcleo. El esquema en <code>schemas/build_directives.ncl</code> es la fuente de verdad — cuando el trabajo evolucione hacia <code>--directives <ncl-path></code>, no ramificar por identidad de llamador en el código del núcleo." data-key="cn2-r" > Caller-specific logic stays in caller-supplied directives, not in core. </div></div></section><section class="section" id="cli"><div class="section-eyebrow" data-en="Invocation · single binary" data-es="Invocación · binario único" data-key="cli-eyebrow" > Invocation · single binary </div><h2 class="section-title" data-en="CLI Reference" data-es="Referencia CLI" data-key="cli-title" > CLI Reference </h2><p class="section-lede" data-en="The current entry is flat CLI args; when work moves toward <code>--directives <ncl-path></code>, the schema in <code>schemas/build_directives.ncl</code> becomes the source of truth." data-es="La entrada actual son flags CLI planos; cuando el trabajo evolucione hacia <code>--directives <ncl-path></code>, el esquema en <code>schemas/build_directives.ncl</code> será la fuente de verdad." data-key="cli-lede" > The current entry is flat CLI args. </p> <pre class="cli">lian-build \
|
||
<span class="opt">--workspace</span> <span class="val"><name></span> <span class="cmt"># <span class="req">required</span>, env BUILDKIT_WORKSPACE</span>
|
||
<span class="opt">--context</span> <span class="val"><dir></span> <span class="cmt"># <span class="req">required</span>, build context (rsynced to runner)</span>
|
||
<span class="opt">--image</span> <span class="val"><ref></span> <span class="cmt"># <span class="req">required</span>, fully-qualified target image</span>
|
||
<span class="opt">--ssh-key</span> <span class="val"><path></span> <span class="cmt"># <span class="req">required</span>, env BUILDKIT_SSH_KEY</span>
|
||
[<span class="opt">--dockerfile</span> <span class="val">Dockerfile</span>] <span class="cmt"># relative to context</span>
|
||
[<span class="opt">--cache-from</span> <span class="val"><ref></span>] [<span class="opt">--cache-to</span> <span class="val"><ref></span>]
|
||
[<span class="opt">--language</span> <span class="val">rust|go|java|...</span>] <span class="cmt"># sizing hint (tier 3)</span>
|
||
[<span class="opt">--runner-image</span> <span class="val"><id></span>] <span class="cmt"># env BUILDKIT_RUNNER_IMAGE</span>
|
||
[<span class="opt">--orchestrator-url</span> <span class="val"><url></span>] <span class="cmt"># default http://localhost:9011</span>
|
||
[<span class="opt">--nats-url</span> <span class="val"><url></span>] <span class="cmt"># omit to disable event publishing</span>
|
||
[<span class="opt">--nats-nkey-seed</span> <span class="val"><seed></span>]
|
||
[<span class="opt">--nats-subject-prefix</span> <span class="val"><p></span>] <span class="cmt"># default lian-build</span></pre> </section><footer><img class="footer-logo" id="footer-logo-img" src="lian-mono-black-h.svg" alt="lian-build" /><p data-en="lian-build · Ephemeral build substrate. Provider-pluggable. Cache content-addressed. Caller-supplied directives." data-es="lian-build · Sustrato de build efímero. Proveedor conectable. Caché direccionada por contenido. Directivas suministradas por el llamador." data-key="ft-tag" > lian-build · Ephemeral build substrate. Provider-pluggable. Cache content-addressed. Caller-supplied directives. </p><p class="footer-meta"> v0.1.0 · Beta · <a href="https://rlung.librecloud.online/LibreCloud/lian-build" >rlung.librecloud.online/LibreCloud/lian-build</a ></p></footer></div><script> const THEME_KEY = "lian-build-web-theme";const LANG_KEY = "lian-build-web-lang";function applyTheme(t){const root = document.documentElement;const btn = document.getElementById("theme-btn");const hero = document.getElementById("hero-logo-img");const foot = document.getElementById("footer-logo-img");if(t === "dark"){root.classList.add("dark");if(btn)btn.innerHTML = "🌙";if(hero)hero.src = "lian-h.svg";if(foot)foot.src = "lian-h-static.svg";}else{root.classList.remove("dark");if(btn)btn.innerHTML = "☀️";if(hero)hero.src = "lian-h.svg";if(foot)foot.src = "lian-h-static.svg";}}function getTheme(){const stored = localStorage.getItem(THEME_KEY);if(stored)return stored;return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";}function toggleTheme(){const next = document.documentElement.classList.contains("dark")? "light" : "dark";localStorage.setItem(THEME_KEY,next);applyTheme(next);}applyTheme(getTheme());function applyLanguage(lang){document.querySelectorAll("[data-en]").forEach(function(el){const text = el.getAttribute("data-" + lang);if(text != null)el.innerHTML = text;});document.querySelectorAll(".lang-btn").forEach(function(btn){btn.classList.toggle("active",btn.getAttribute("data-lang")=== lang,);});document.documentElement.lang = lang;}function switchLanguage(lang){localStorage.setItem(LANG_KEY,lang);applyLanguage(lang);}applyLanguage(localStorage.getItem(LANG_KEY)|| "en");</script></body></html> |