Consolidation — the jera job ledger + the facett-holger / holger-embed pane
Class:
design. Companion guides:consolidation-jera-embed-guide.md(how to use it) andconsolidation-jera-embed-for-idiots.md(plain language). Extends the constellation designs inworkspace_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::sealis split intoseal(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 apromotejob beforeseal_innerand settles it after:- success →
finish(content_address, size)with the content address asresult_ref; - refusal / IO error →
fail_with_detail(reason).
- success →
-
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:
facett-<app>component crate — the pane's facett-Elm body, separable from the app shell.-
<guest>-embedadapter 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. - Promotion storage transition the ledger wraps:
promotion-design.md.