<!-- nornir:class: design -->
# Consolidation — the jera job ledger + the facett-holger / holger-embed pane
> **Class:** `design`. Companion guides: [`consolidation-jera-embed-guide.md`](consolidation-jera-embed-guide.md)
> (how to use it) and [`consolidation-jera-embed-for-idiots.md`](consolidation-jera-embed-for-idiots.md)
> (plain language). Extends the constellation designs in `workspace_nordisk/.nornir/`
> (`consolidation-panels-maps-design.md` §1–§2, `wasm-ui-panels-design.md` §3).

This lands three **additive** consolidation seams in holger — no working path removed,
no behaviour changed for an unconfigured caller. holger already adopts **skidbladnir**
(service/systemd); these close the remaining gaps the 2026-07-12 survey flagged.
---
## 1. The durable job ledger (jera) — every promotion is a tracked job
**Gap closed:** holger ran its real background work (promotions) with no durable ledger —
a `seal()` happened and left no listable record of the operation. The constellation law
(`consolidation-panels-maps-design.md` §1) is: **route tracked work through `jera::jobs`,
not ad-hoc spawning.** jera is the one shared job ledger (a redb list of long-running ops,
`running → done|failed` + timing), already used by nornir and korp. It is a **bounded**
operational view — jera keeps `jobs.redb` to the most recent `jera::jobs::CAP` (= 2000)
terminal jobs so a long-running server stays lean; holger's **permanent** promotion record
remains its append-only **audit log** (`holger-audit`). The ledger answers "what did the
server run recently?", the audit log answers "what was ever promoted?".
**Design (chosen):** the `PromotionEngine` gains an **optional** `jera::jobs::JobStore`.
- `PromotionEngine::seal` is split into `seal` (the wrapper) + `seal_inner` (the
byte-for-byte-unchanged storage transition: blake3 content-address → blob write →
exclusive ref). The wrapper, **only when a ledger is attached**, opens a `promote`
job before `seal_inner` and settles it after:
- success → `finish(content_address, size)` with the content address as `result_ref`;
- refusal / IO error → `fail_with_detail(reason)`.
- **No ledger attached ⇒ the original fast path, no ledger write** — the default for every
existing caller and test. This is the additive keystone: `with_job_ledger(store)` is opt-in.
- The server (`Holger::start`) opens the ledger **best-effort** at `<data>/holger-jobs/jobs.redb`
(beside the prod store). If it can't open (read-only dir), the server logs and serves exactly
as before — promotions just aren't recorded. Never a hard failure.
**Where `<data>` resolves.** All five operational stores — audit log, job ledger, PROD
store, SBOM store, property store — derive from one anchor: `Holger::data_root()`, the
parent of the resolved `audit.dir`. Resolution is `Holger::resolved_audit_dir()`:
| `audit.dir` | `$HOLGER_DATA` | resolves to |
|---|---|---|
| absolute (`/srv/audit`) | anything | `/srv/audit` — explicit config always wins |
| relative (default `holger-audit`) | set | `$HOLGER_DATA/holger-audit` |
| relative | unset | CWD-relative (last resort) |
`$HOLGER_DATA` is what the installer already establishes (`~/.holger` user scope,
`/home/holger/.holger` system scope; `holger-server install` also sets `WorkingDirectory=`),
so an **installed** server resolves identically with or without the env var. The env branch
exists for the **embedded** case: holger running in-process inside dwarves or nornir inherits
*their* CWD, and a purely CWD-relative default scattered a fresh audit log + job ledger into
whatever directory the host started in. Policy is unit-tested (`audit_dir_anchor_tests`).
`jera` is a `default-features = false` dependency of `holger-server-lib` — the ledger only,
none of jera's bollard-container / KVM-boot runners, so the airgap core stays lean.
**Why not a new holger-local ledger?** That would duplicate jera. The law is one crate per
concern; holger depends on it.
**Bench:** `holger.promote_ledger` (`bench-scenarios`, feature `promote-ledger`) seals a burst
of distinct artifacts through the real engine **with** the ledger attached and reports
seals/sec + MB/s of the full hot path (blake3 + blob + ref + `running → done` row).
**Test:** `promote::tests::{seal_records_a_done_job_in_the_ledger, refused_reseal_records_a_failed_job,
seal_without_ledger_writes_no_job}` — the ledger records done/failed, and the no-ledger path
is untouched.
---
## 2. The shared pane — `facett-holger` component + `holger-embed` adapter (native, no wasm)
**Gap closed:** holger's registry-browser pane was welded into the app (and a duplicate lived
in `holger-demo/panel.rs`), with **no** `facett-holger` component crate; and the one cross-app
embed (nornir's in-process Holger tab, `nornir/src/holger_embed.rs`) was a **bespoke** direct
linkage, not the reusable `<guest>-embed` shape. The constellation standard
(`consolidation-panels-maps-design.md` §2, `wasm-ui-panels-design.md` §3) is **two pieces**:
1. **`facett-<app>` component crate** — the pane's facett-Elm body, separable from the app shell.
2. **`<guest>-embed` adapter crate** — `fn panel(source) -> Box<dyn Panel>`: links the component
+ a data source; a host drops the returned panel into a tab. **Backend (2): native, no wasm.**
### 2.1 `facett-holger` (the component — lives in `facett/`)
The holger registry & artifact browser as one `facett_core::Facet`: a repository roster over
the selected repo's artifact listing, both via `facett_table::Table`. **Backend-agnostic** — fed
a serde `RegistryModel` (`repos` / `artifacts` / `selected_repo`); no holger, network, or FS dep.
`RegistryBrowser::from_fixture()` (sample data, browser-safe) and `from_model(model)` (live data).
It is the reusable unit: holger-demo mounts it, and the embed adapter hosts it in a peer app.
The interim `holger-demo/panel.rs` duplicate is **deleted** — holger-demo now consumes the crate.
### 2.2 `holger-embed` (the adapter — lives in `holger/`)
The reusable `<guest>-embed` generalization of nornir's bespoke `read_registry`:
- `read_model(data_dir, grpc, http, limit) -> RegistryModel` — reads holger's dev-pair registry
through its **functional (in-process) API** (`dev_pair_ron → instantiate_backends → list`),
no socket, into the component's model. Opens the panel on the first non-empty repo.
- `panel(source) -> Box<dyn Facet>` — the entry point. `Source::{InProcess, Model, Fixture}`:
in-process read (fat mode), a caller-supplied model (**the thin-gRPC insertion point** — a host
with only a socket assembles the model and hands it in), or the fixture.
`facett_core::Facet` (`title`/`ui`/`state_json`) **is** the constellation's current `Panel`
contract; when the design's dedicated `Panel` trait lands, `panel`'s return type upgrades with
no call-site change. The guest's `ui` runs directly in the host's egui frame — full native perf,
no serialize boundary, **no wasm**.
**nornir stays working (additive):** nornir's existing in-process Holger tab
(`nornir/src/holger_embed.rs`) is left untouched. `holger-embed` is the clean template it (and
dwarves' `facett-brokkrview`, korp's panes) migrate onto next — a follow-up in nornir's repo, not
this change.
**Test:** `holger-embed`'s `read_model_enumerates_dev_pair_and_feeds_the_panel` (the functional-API
walk → a live panel opened on the published crate) + `fixture_panel_reports_holger_state`. The
component ships one `nornir-robotui` scenario + testmatrix row (`facett-holger/tests/robot.rs`).
---
## 3. Relationship
- Seam law + adoption matrix: `workspace_nordisk/.nornir/consolidation-panels-maps-design.md`.
- Native embed backend (2): `workspace_nordisk/.nornir/wasm-ui-panels-design.md` §3.
- The in-process functional API this builds on: [`embed-design.md`](embed-design.md).
- Promotion storage transition the ledger wraps: [`promotion-design.md`](promotion-design.md).