Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

.nornir/design/release-convergence-reasoning.md

facett-releaseview — a release process as THREE converging graphs

Status: DESIGN + SHIPPED (2026-07-17). Component: facett-releaseview (ReleaseView). Demo: the releaseview tab (facett-demo::releaseview_tab). Concept diagram: ../assets/release-convergence-planes.svg.

The idea

A release is a convergence. The same constellation dependency DAG exists in three states that a healthy release drives to agree:

  1. LOCAL — the working-tree / manifest versions (what you release from).
  2. SPARRING — the Gate-B holger local-rehearsal versions (the safe preview).
  3. CRATES_IO — the published, immutable versions (the target).

All three equal ⇒ converged (green). Divergence is not an error — it is a map of where to fix / release:

condition ConvergeStatus colour meaning
local == sparring == cratesio Converged green done
cratesio < local NeedsRelease red crates.io behind local → publish
sparring != cratesio LockDesync red resolved ≠ published → lock/registry desync
sparring != local BumpDrift amber rehearsal hoisted a bump local hasn't
(live event) Publishing accent-pulse a publish is in flight

Why it is NOT a new graph engine (Law #1 — REUSE)

facett-releaseview invents a component, not a renderer. It reuses:

Engine-agnostic (one-way, like the runa adapter)

ReleaseView::from_model(ReleaseConvergence) takes the neutral model in and renders. There is no dwarves / release-engine dependency — types flow one way, exactly the RunaPipelineAdapter pattern (.nornir/design/pipeline-adapter).

ReleaseConvergence {                     // the neutral, serde model
  title,
  nodes: Vec<CrateConvergence {          // one crate across the three graphs
    krate, local_ver, sparring_ver, cratesio_ver, status: ConvergeStatus }>,
  edges: Vec<DepEdge { from, to }>,       // dep DAG, by node index
}

derive_status(local, sparring, cratesio) -> ConvergeStatus is the pure kernel of the table above (total, no I/O, no engine types; unit-tested over all four version-derived states). Publishing is a live-event state, never derived.

The realtime seam

ReleaseConvergence::apply(ReleaseEvent) (surfaced through Msg::Event) flips one crate's state per call:

So a live release cascade animates the convergence node-by-node. The demo tab drives a scripted cascade (skade's bump, then publish dwarves → holger → nornir) and loops it.

Two renders (GPU-hero philosophy, CPU-honest)

Mirroring facett's "beautiful hero + always-works fallback":

Both renders are pure CPU egui-painter draws — wasm-clean, headless- snapshottable, no wgpu required to land green. That is deliberate: the render tests (tests/render.rs) run anywhere, and the demo tab is green on the web build.

DEFERRED (documented TODO — the wgpu CodeVault upgrade)

The 3-plane render is today a CPU pseudo-3D paint (isometric plane offsets, no depth buffer). The hero upgrade is to lower ReleaseConvergence onto facett-graph3d::CodeVault (the Wreck-It-Ralph 3D graph, wgpu, a real depth buffer): render the three states as three CodeVault depth planes, a crate node splits between planes when its versions diverge (+ the red/amber glitch glow the CodeVault already paints), and the planes snap together as crates converge. The seam is exactly the same neutral model — only the view changes, never the model or the (future) dwarves adapter. Not built here (kept the first landing green + CPU-portable); tracked as the release-3 follow-up.

dwarves-side follow-up (the one-way feed)

The release engine feeds ReleaseConvergence from:

That adapter lives on the dwarves side (or in a releaseview feature like the runa adapter), never here: facett-releaseview stays a pure viewer.

Tests (RED-when-broken)