Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration & docs — no undocumented knobs

A hard lesson from OpenLane/LibreLane: a global, flat namespace of hundreds of config variables, set through env/config.json, applied across steps — so you can’t tell which step reads which variable, what its impact is, or when it applies. Loom is designed so that failure mode is structurally impossible. This page is the convention every Loom step and engine follows.

The three failure modes we reject

OpenLane/LibreLaneLoom
Global, flat env-var namespacePer-step, typed config — scoped to the op that reads it
“Which var does this step use?” is unclearConfig is the step’s typed struct — the surface is finite and local
Impact / when-it-applies undocumentedEvery field documents impact + when, and docs are generated from the code

The five rules

  1. No global env vars for behavior. A step’s configuration is a typed struct (Rust + serde), passed as JSON or as typed Flow IR. Environment variables are for environment (paths, credentials) — never behavior knobs.
  2. Every field is documented in place — a doc-comment stating what it does, its impact, when it applies, and its default. No field ships without this.
  3. Every step self-describes. Each step/engine emits its config as a JSON Schema via --describe (the same pattern as vyges-events --schema and vyges metadata). The schema lives next to the struct, so it cannot drift from behavior. One owner, no drift.
  4. Docs are generated from --describe, not hand-written. The mdbook reference page for a step is generated from its schema — name, type, default, description, impact, example. Regenerated in CI; drift fails the build.
  5. There is a config catalog. One generated, searchable page lists every knob across every step — the index OpenLane never had — plus a worked example per step.

How config flows

   typed config struct  ──(--describe)──▶  JSON Schema  ──▶  generated mdbook reference
   (serde + doc-comments)                  (self-published)   + config catalog
          │                                                          ▲
          └──── consumed by the step (and by the flow layer) ────────┘

The typed struct is the single source of truth. It is what the step deserializes at run time, what --describe publishes, and what the flow layer validates against. Documentation is a projection of it — so it is always complete and always current.

Example — the insert-eco-buffers step

Config is a typed struct, LibreLane-compatible in shape:

#![allow(unused)]
fn main() {
/// One entry of `INSERT_ECO_BUFFERS`.
pub struct EcoBuffer {
    /// `"instance/pin"` — the pin to buffer. Impact: a buffer is spliced here.
    /// When: applied once, during the ECO surgery step (before legalization).
    pub target: String,
    /// Buffer master cell name. Impact: which library cell is inserted.
    pub buffer: String,
}
}

Invoked as a step with an explicit, worked example (never a bare env var):

vyges-opendb insert-eco-buffers --input in.odb --output out.odb --config eco.json
# eco.json: { "INSERT_ECO_BUFFERS": [ { "target": "inst42/A", "buffer": "sky130_fd_sc_hd__buf_2" } ] }

What this buys a user

  • Discoverability: --describe tells you exactly what a step accepts — no source-diving.
  • Locality: a knob’s impact is the op that owns it, not a global side effect.
  • Currency: docs (defaults, impacts, examples) are generated, so they never rot.
  • Composition: the flow layer carries per-op config validated against the schema, so “what to use when” is answered by the flow’s structure — not env-var archaeology.

Status

The generated half is live. Every engine’s CLI reference page is produced from both --help (the surface: subcommands and flags) and --describe (the contract: typed config, input schemas, maturity, provenance) — neither alone is enough, since an engine’s --describe usually covers one primary operation while its --help covers all of them. Multi-step tools are walked step by step, so each step’s config keys appear with their types and descriptions.

A step that publishes no contract is named on its own page rather than quietly omitted: a gap a reader can see is a gap someone can report. That is the standing convention in force — no step ships an undocumented knob.