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

nordisk/heimdall

docs/KERNEL-TOOLING-MAP.md

Kernel tooling read-through (2026-11-03)

Read-only review of the source as it is on the git server today. No code was changed. Purpose: say what exists, what is connected, what is not, and how it could fit together.

Commits read: edda main 3d8fe66, tunnr main 3953015, heimdall main d747087 (the no-pivot shim lands in 9ac44df..d747087; the shared clone on the build box still sits at f4b5002), holger main a76203a plus branch kern/appliance-lsm 390cb8c, dwarves main c5150d2, nornir main eed4eaf8. Line numbers refer to those commits. "Not verified" means I did not run it.

Status words used for every arrow:


0. Glossary (for an outside reader)

Name Meaning
Build Thing Our build system. The engine is the Rust crate nornir-build-thing (repo edda). Each product repo has a thin <repo>-build-thing crate that declares its rows in Rust and emits them as .nornir/build-variants.toml.
row One declared artifact: [[build.kernel]] (a kernel), [[build.installer]] (an appliance medium), [[build.variant]] (a binary or container).
fragment A Kconfig fragment (CONFIG_X=y lines). Rows are composed as preset + profile + closures + requirements + user fragment, then olddefconfig.
pin A content hash that fixes an input: the kernel tarball sha, the toolchain image id, a ledger key.
ledger A built kernel looked up by its pin before a rebuild (nornir.kernel-ledger-key/1).
requires A [[build.installer.requires]] row: a consumer says a kernel symbol must be y/n, with a reason.
veto A row-level CONFIG_X=n decision, allowed only after a coverage run proved the symbol never executed. Written with a receipt.
receipt A JSON evidence file (nornir.kernel-veto/1, nornir.kernel-coverage/1) that says what was measured, when, with which tools.
door A CLI verb of a <repo>-build-thing binary (tunnr-build-thing kernel …).
suite A shell script that boots the row's medium under KVM and prints STAGE n PASS/FAIL lines; the coverage/veto verbs drive it.
korp The appliance OS layer in tunnr: korp-installer (medium PID 1), korp-start (root of trust that loads a seccomp profile and execs the payload).
Heimdall Our sandbox: seccomp profiles (probe → generate → check), Landlock, a youki container shim, a broker for file/socket calls, a node signer for Kubernetes (heimdall-k8s). AGPL; always run as a separate binary by MIT code.
shim / broker heimdall-container-shim creates the job container (youki); the broker answers the job's openat/connect/execve through seccomp USER_NOTIF.
profile A measured seccomp allow-list per program × arch × libc: .probe.json (evidence), .json (reviewable), .oci.json (what the kernel loads).
Rusternetes Our Kubernetes node (kubelet + control plane, youki backend) shipped as a tunnr payload.
dwarves Our forge/operator tool (MIT): dwarves buildthing builds rows; dwarves tune is the Heimdall tuner (probe/gen/check/approve/show/app).
Mímir Our code graph (repo nornir): source call graph from rustc, binary symbols, a system-call boundary. The owner's engine for "possible syscalls".

1. System map

                 BUILD BOX (podman, no KVM)                          KVM HOST                       NODE / APPLIANCE
 ┌──────────────────────────────────────────────────────┐   ┌─────────────────────────┐   ┌─────────────────────────────┐
 │ edda: nornir-build-thing (engine)                    │   │ scripts/kernel-suite.sh │   │ korp-start (PID-1 root of   │
 │  kernel.rs  rows→fragment→container build→pin        │   │  rusternetes-kvm.sh     │   │  trust) loads seccomp/*.oci │
 │  kernel_trim  what-if / candidates / coverage / veto │──►│  rusternetes-check.py   │   │ node PID-1 (supervise,      │
 │  syscall_join measured calls → CONFIG options        │ C │  memsave snapshots      │   │  landlock, filter)          │
 │  measure.rs   heimdall binary → .nornir/profiles     │   │ holger tools/           │   │ Rusternetes kubelet (youki) │
 └───────────▲────────────────────────▲─────────────────┘   │  kernel-suite.sh        │   │  ──► heimdall-k8s signer    │
             │ path dep (C)           │ path dep (C)        │  holuse-init.c          │   │ Heimdall shim+broker (jobs) │
 ┌───────────┴────────────┐  ┌────────┴─────────────────┐   └────────────▲────────────┘   └─────────────▲───────────────┘
 │ tunnr-build-thing      │  │ dwarves                  │                │ ScriptSuite (C)              │ medium (C)
 │  5 kernel rows, pins   │  │  buildthing (compose)    │                │                              │
 │  kernel-vetoes/*.list  │  │  kernel_ledger           │   ┌────────────┴────────────────┐            │
 │  door: kernel-probe,   │  │  tune probe/gen/check/   │   │ .nornir/kernel-coverage/    │            │
 │   kernel what-if…veto, │  │   approve/show/app       │   │   <row>/<sha12>.json   (C)  │            │
 │   measure syscalls/gate│  │  dwarves-tune-graph      │   │ .nornir/kernel-vetoes/<row>/│            │
 │  korp-installer (ISO)  │  │   (reads Graph JSON)     │   │   protect.list, receipts/   │            │
 │  payloads/rusternetes- │  └──────────┬───────────────┘   │ .nornir/profiles/<row>/     │            │
 │   node, xtask stage    │             │ runs the binary   │   measure.toml/json, *.json │            │
 └───────────┬────────────┘             ▼                   │   kernel-needs.json (MANUAL)│            │
             │ emit (C)      ┌──────────────────────────┐   └─────────────────────────────┘            │
             ▼               │ heimdall (AGPL binary)   │                                               │
 .nornir/build-variants.toml │  profile probe/gen/check │   ┌─────────────────────────────┐            │
 (installer.requires =n      │  probe-exec/-container   │   │ holger .nornir/kernel-usage/│            │
  from the veto list)   (C)  │  KernelReport (runtime)  │   │  needs.json, coverage.json, │            │
                             │  k8s signer, shim, broker│   │  heimdall-needs.fragment    │──MISSING──►│ Holger kernel rows
                             └──────────────────────────┘   │  (a REPORT; nothing reads   │  (rows exist only on branch
                                                            │   it back)                  │   kern/appliance-lsm)
 ┌──────────────────────────────────────────────────────┐   └─────────────────────────────┘
 │ Mímir (nornir): driver CALLS edges, binary symbols,  │
 │  boundary @syscall nodes (x86_64), libc map, BTF     │──MISSING (step 1: walk + Graph JSON)──► dwarves-tune-graph --graph
 └──────────────────────────────────────────────────────┘                                        ──MISSING──► syscall_join / veto

1.1 Pieces and where they run

Piece Repo / path Runs on Role
nornir-build-thing engine edda crates/nornir-build-thing/src/{kernel.rs,kernel_trim.rs,kernel_trim/,syscall_join.rs,kernel_requires.rs,kernel_features.rs,measure.rs} build box (kernel compile in a podman container via draupnir) rows, fragments, pins, ledger key, what-if, coverage model, veto rule, syscall join, measure step
tunnr-build-thing tunnr crates/tunnr-build-thing/src/{kernel.rs,vetoes.rs,main.rs,plan.rs,rusternetes.rs,recipe.rs} build box the 5 kernel rows, the toolchain pin, the veto lists, every kernel … door
kernel suite tunnr scripts/kernel-suite.sh, scripts/rusternetes-kvm.sh, scripts/rusternetes-check.py compose on build box, boot on KVM host the one suite (bridge, rusternetes-node only)
Holger usage report holger .nornir/kernel-usage/holger-scan-worker/ (+ tools/kernel-suite.sh, holuse-init.c) KVM host a measured report: needs.json, coverage, 8 veto candidates, heimdall-needs.fragment
dwarves tuner dwarves crates/dwarves/src/{tune.rs,tune/review.rs,tune_app.rs,kernel_ledger.rs}, crates/dwarves-tune-graph/ build box probe/gen/check/approve/show/app; graph cross-check reader; kernel ledger
Heimdall heimdall crates/* build box (probe, tests), node/appliance (shim, broker, k8s signer) profiles, runtime kernel-feature report, container jobs
Node OS tunnr crates/korp-installer, payloads/rusternetes-node/ node ISO, PID-1, korp-start seccomp floor, Landlock, youki, RuntimeClass heimdall, CNI
Mímir nornir crates/{mimir-driver,mimir-binary,mimir-ingest/src/boundary,mimir-room} build box code graph; the future "possible syscalls" producer

1.2 Every arrow, with its status

# From → To Status Evidence
1 tunnr-build-thing → edda engine CONNECTED path dep crates/tunnr-build-thing/Cargo.toml:41; main.rs:354-447 calls kernel_trim::run_kernel_verb_in, measure::run_measure_verb
2 kernel rows → merged fragment → container build → .kernel-out/<name>/{bzImage,kernel.config,<name>.kernel-pin.json,kernel.why.toml} CONNECTED edda kernel.rs:1652 (merged_fragment), :4318 (build_kernel_with), :4758-4790 (outputs)
3 kernel pin → consumer (installer) CONNECTED edda kernel.rs:4151 (resolve_kernel_for), korp forge.rs:192-205
4 Rust rows → .nornir/build-variants.toml → parity test CONNECTED edda emit.rs:65-107; tunnr tests/emitted_toml.rs:24
5 veto list → installer kernel edge (=n requires) → emitted toml CONNECTED tunnr vetoes.rs:16-18 (include_str!), recipe.rs:533,564, .nornir/build-variants.toml:1226-1231
6 kernel coverage → suite script → KVM → coverage JSON CONNECTED to the script, MANUAL inside it edda veto.rs:242 (ScriptSuite runs bash <script>), coverage.rs:734 (store); the build-box/KVM split is KS_COMPOSE_ONLY/KS_MEDIA env plus a hand sync of the kernel tree (kernel-suite.sh:49-73)
7 coverage JSON + protect.list → kernel candidates / kernel veto CONNECTED edda coverage.rs:744 (load_for), :763-801 (protect), veto.rs:507-643 (run_veto)
8 veto → receipt JSON → anyone CONNECTED to write, barely read written veto.rs:636; read by list_receipts veto.rs:646 (the vetoes verb) and one unit test tunnr vetoes.rs:55-69. The build trusts the .list line, never the receipt.
9 heimdall probe → .nornir/profiles/<row>/… (measure.json, *.probe.json, *.json, *.oci.json) CONNECTED edda measure.rs (module doc + run_measure_verb :1096) runs the heimdall binary; dwarves tune.rs:18-21 calls edda measure
10 profiles → kernel needs (syscall → CONFIG join) CONNECTED edda syscall_join.rs:602 (MeasuredRow::load), :721 (needs)
11 profiles → veto refusal (a measured call protects its option) CONNECTED edda kernel_trim.rs:160 (join_syscalls), syscall_join.rs:850 (vetoes_for), veto.rs what-if clean check
12 kernel needs --json → .nornir/profiles/<row>/kernel-needs.json → dwarves tune show MANUAL reader exists: dwarves tune/review.rs:224; writer is a shell redirect in dwarves/README.md:162. needs() writes nothing (syscall_join.rs:721). No row in any repo has the file (only a dwarves test fixture).
13 heimdall-needs.fragment (Holger report) → Holger kernel rows MISSING on main fragment holger/.nornir/kernel-usage/holger-scan-worker/heimdall-needs.fragment; main a76203a has no CONFIG_SECURITY/LANDLOCK/LSM requires; branch kern/appliance-lsm 390cb8c has them hand-typed as [[build.installer.requires]] (CONFIG_SECURITY, CONFIG_SECURITY_LANDLOCK, CONFIG_LSM="landlock,lockdown,yama", CONFIG_CROSS_MEMORY_ATTACH).
14 Heimdall runtime needs → any machine-readable kernel need MISSING heimdall container/src/kernel.rs:118 (missing_for_strict) returns prose; the only CONFIG string is a message (:127). No needs output, no fragment.
15 Holger tools/kernel-suite.sh → tunnr-build-thing kernel-probe MANUAL (script) holger/.nornir/kernel-usage/holger-scan-worker/tools/kernel-suite.sh:37; nothing in tunnr calls kernel-probe (tunnr's own suite composes via dwarves buildthing, kernel-suite.sh:60)
16 measured node profile → packed seccomp profile on the node MISSING the packed approved-x86_64.oci.json (361 calls) is hand-written (tunnr rusternetes.rs:984-1011); the measured node-pid1 profile (17 calls) is not packed; korp-start, rusternetes, rusternetes-youki-shim are needs_vm, never measured (.nornir/profiles/rusternetes-node/measure.toml)
17 korp-start → seccomp/<entry>.oci.json → payload CONNECTED tunnr crates/korp-installer/src/seccomp.rs:1-95, src/bin/korp-start.rs:674-685
18 Rusternetes kubelet → heimdall-k8s signer (RuntimeClass heimdall) CONNECTED tunnr payloads/rusternetes-node/src/heimdall.rs:1-50, handlers/heimdall.json; heimdall k8s/src/lib.rs:100,336
19 Heimdall runtime kernel report → signed result CONNECTED heimdall container/src/kernel.rs:94, core/src/types.rs:573
20 dwarves kernel ledger → edda resolve_built_kernel_with CONNECTED when configured dwarves kernel_ledger.rs:263; needs DWARVES_HOLGER_DIR; edda's own public resolve_built_kernel (kernel.rs:3630) always uses NoLedger
21 Build Thing kernel data → AI / MCP / llms / nornir graph MISSING no kernel module mentions MCP, llms or nornir-introspect; the only machine faces are the --json schemas (nornir.kernel-what-if/1, -candidates/1, -needs/1, -coverage/1, -veto/1)
22 Mímir → Graph JSON → dwarves-tune-graph --graph MISSING reader exists: dwarves dwarves-tune-graph/src/lib.rs:218 (read_graph), xcheck.rs:39; producer: none in nornir (no code writes unresolved_sites/syscalls Graph; mimir-room/src/query.rs:219 refuses SYSCALLS edges until the universe is folded). walk.rs:6 and srcgraph/mod.rs:36-41 are owner-reserved stubs; tests #[ignore].
23 Graph JSON → syscall_join / veto MISSING MeasuredRow (syscall_join.rs:602) reads only measured profiles; no "possible" field
24 drift gate (measure gate) → forge/CI CONNECTED as a verb, pipeline not verified tunnr main.rs:227; edda measure.rs (parse_gate_verb, run_gate); tunnr xtask has no drift verb (xtask/src/*.rs)

2. Inventory

2.1 Build Thing kernel side (edda crates/nornir-build-thing)

Item path:line Does In → Out Called by
KernelBuild row kernel.rs:641-847 one [[build.kernel]]: version, tarball_sha256, preset, profile, toolchain_image(_sha256), lto, solver, cpus, mem Rust decl → .nornir/build-variants.toml <repo>-build-thing
fragments kernel.rs:116-154, src/kconfig/{base,hardened,k8s-hardened,profile,minimal,deps-tinyconfig,nuked}.config preset/profile layers (include_str!) — merge
merge kernel.rs:1601-1708 preset → profile → minimal → LTO → consumer requires → user fragment → probe fragment; later line wins; a contradicted requirement is refused fragments → one .config input build, ledger_key_of, kernel_trim
toolchain image kernel.rs:161-255,958-982,1060,2213,4913 nordisk-kernel-toolchain@sha256:<pin>; reproducible mint; re-pin hint Containerfile src/containerfiles/kernel-toolchain.Containerfile build
build kernel.rs:4318-4790 preflight disk, fetch tarball (sha), merge_config+olddefconfig+canary in a bounded airgapped container (draupnir/bollard), collect → .kernel-out/<name>/{bzImage,kernel.config,kernel.base.config,<name>.kernel-pin.json,kernel.why.toml}, pins/<ledger_key>/ dwarves buildthing <donor> --variant <kernel>
pin receipt kernel.rs:3430-3523 nornir.kernel-pin/1: shas of fragment, bzImage, config, toolchain id file slot_holds :3577, resolve_kernel_for :4151
ledger key kernel.rs:3668-3683 nornir.kernel-ledger-key/1 over version · tarball · toolchain ref · profile · fragment sha → StepPin.ledger_key (build_dag.rs:492) consumers, dwarves ledger
probe boot (TCG) kernel.rs:2867-3231 QEMU TCG boot in the toolchain container, serial markers bzImage → KernelProbe result tests/kernel_probe_live.rs
what-if / candidates kernel_trim.rs:228-660 TrimModel::what_if (olddefconfig-style settle), candidates ranked by bytes leaving, InverseCheck .config, Kconfig graph, TreeSizes → nornir.kernel-what-if/1, -candidates/1 run_kernel_verb_in :1332
gcov coverage kernel_trim/coverage.rs:45-801 DEV-only gcov probe kernel; read counters from vmlinux + QEMU memsave dumps; per-symbol Executed/NeverExecuted/Unmeasured; protect.list suite output → .nornir/kernel-coverage/<row>/<sha12>.json (nornir.kernel-coverage/1) kernel coverage
veto kernel_trim/veto.rs:92-646 ScriptSuite protocol; run_veto: one symbol, refused unless never-executed, unprotected, what-if clean, inverse holds; rerun suite without probe; append .list; receipt → <store>/<row>.list, .nornir/kernel-vetoes/<row>/receipts/<date>-CONFIG_X.json (nornir.kernel-veto/1) kernel veto
syscall join syscall_join.rs:138-868 SyscallMap read off the kernel tree (syscall_64.tbl, SYSCALL_DEFINE, Makefile gates, sys_ni.c), cached nornir.syscall-map/1; MeasuredRow from measure.json + *.probe.json; needs, vetoes_for, join_candidates profiles + kernel tree → nornir.kernel-needs/1 (stdout only) kernel needs, what-if
requires closure kernel_requires.rs:216-1417 feature → KconfigReq; union; contradictions; WhyIndex → kernel.why.toml rows → fragment lines build, plan
feature model kernel_features.rs:190-730 decision layer: `--opt kernel.preset profile =on
measure step measure.rs `measure syscalls [--check --gen --workload], measure gate; runs the external pinned heimdall` binary
live tests tests/{kernel_build_live,kernel_probe_live,kernel_offline_live,pinned_recipe_reproduces,toolchain_images_live}.rs all #[ignore], need podman socket, network or unshare --net; none need KVM by hand

2.2 tunnr (node OS, media, kernel rows, doors, suite)

Item path:line Does In → Out Runs on
kernel rows crates/tunnr-build-thing/src/kernel.rs:330-467, recipe.rs:839-845 tunnr-kernel (nuked/hardened), -scsi, -nvme, -hyperv (slim/hardened) = the lean appliance kernels; tunnr-kernel-k8s (slim/k8s-hardened, Landlock+BPF) 6.12.104, tarball sha 949d166a…, closures variants/kernel/tunnr-hardening.config, tunnr-k8s-hardening.config build box
toolchain pin kernel.rs:302-308 TOOLCHAIN_IMAGE_SHA256 = 3f632569… = the image id (changed 2026-11-03 from a manifest digest) checked by unit tests :818-823,:880-960; fail-closed check in edda toolchain_archive.rs:104-122
lean-pin gate tests/rusternetes_node_kernel.rs:188-256 pins the standalone fragment sha and the lean consumer ledger key per row; k8s has Landlock, lean rows keep # CONFIG_SECURITY is not set
parity gates tests/emitted_toml.rs:24, tests/kernel_toolchain_edge.rs:45-51, kernel.rs:1228,1078 toml = code; Containerfile = edda's; k8s closure = shared closure except the LSM block; derived rows differ only by preset
veto lists crates/tunnr-build-thing/kernel-vetoes/{rusternetes-node,rusternetes-node-aya}.list, src/vetoes.rs one applied veto: CONFIG_MOUSE_PS2_BYD (receipt 2026-11-03T144637Z, suite bridge 8/8 PASS, 10 NOTRUN, bzImage −65536 B); aya list empty include_str! → requires =n (recipe.rs:533,564)
protect / coverage / receipts .nornir/kernel-vetoes/rusternetes-node/{protect.list,receipts/}, .nornir/kernel-coverage/rusternetes-node/{380dcfbd46e9,58a245df9d77}.json 52 protect globs; two coverage reports (one stale fragment); no report for the post-veto fragment 333150ec…
doors src/main.rs:102-463 --list, toml, emit, facts, plan, preflight, `measure syscalls gate, kernel-probe [--repo] [--probe gcov off] [--fragment-extra f](:301-377),kernel what-if
suite scripts/kernel-suite.sh ` <probe off> ; compose cp+worker via dwarves buildthing … --build kernel(:58-64);KS_COMPOSE_ONLY=1stops after compose;KS_MEDIA=boots given ISOs; KVM slot flockKS_KVM_LOCK(:101-103);rusternetes-kvm.sh+rusternetes-check.pystages;memsave` after each stage (:82-90); 10 fixed NOTRUN lines (:129-133) only suite bridge, only rusternetes-node (:43-46)
xtask xtask/src/{main.rs,rusternetes.rs,rusternetes_upgrade.rs,rusternetes_secrets.rs,rusternetes_kek.rs} rusternetes stage (Rusternetes commit 2ed5e700, heimdall-k8s at f4b5002, tunnr-etcd, CNI 1.9.1, all sha-checked) → .rusternetes-out/ + manifest.sha256; stage-aya; `pki token revoke
korp-installer crates/korp-installer/src/{isocompose.rs,forge.rs,kernel.rs,seccomp.rs,bin/korp-start.rs} ISO = korp-boot + static-musl PID-1s + initramfs + kernel; kernel from the row's kernel_donor (proved against the pin, forge.rs:192-205), or $KORP_KERNEL, or host /boot/vmlinuz; korp-kernel.blake3 is still all zeros build box
node PID-1 payloads/rusternetes-node/src/{main.rs:120-204,supervise.rs,landlock.rs,filter.rs,heimdall.rs,lay.rs} seed → stage → filter → tmpfs root → lay → seccomp self-test → Landlock (CSI only; PID-1 and Rusternetes none, landlock.rs:35 "waiting on the shim door") → network → join → upgrade door → VIP → supervise (KUBELET_CONTAINER_BACKEND=youki, supervise.rs:453-460) node
seccomp floor crates/korp-installer/src/seccomp.rs, payloads/rusternetes-node/filter.rs:314-319 seccomp/<entry>.oci.json from the signed archive; default deny, KILL_PROCESS; korp.seccomp=strict refuses unprofiled payloads; the 361-call hand-written approved-x86_64.oci.json packed for rusternetes-node and rusternetes/bin/rusternetes node
measured profiles .nornir/profiles/rusternetes-node/{measure.toml,measure.json,node-pid1/*}, .nornir/profiles/rusternetes-node-aya/{node-pid1,tunnr-cni}/* tool pin nordisk-heimdall-cli 0.2.0 + sha; node-pid1 17 calls; korp-start, rusternetes, youki shim = needs_vm build box
RuntimeClass heimdall payloads/rusternetes-node/src/heimdall.rs, handlers/heimdall.json, .nornir/heimdall-runtimeclass.md spawns heimdall-k8s, publishes its key as an annotation, handler = RO root, no caps, no net, digest-only images doc says step 2 PARKED; code ships it node
CNI payloads/rusternetes-node/Cargo.toml:28-38, src/lay.rs, src/datapath/*, payloads/tunnr-net-ebpf bridge/host-local/loopback; cni-cilium; cni-aya (eBPF) with Kconfig requirement sets rusternetes.rs:674,730,786,809 node
proof scripts scripts/rusternetes-aya.py, scripts/rusternetes-ha.py, scripts/rusternetes-ha/*.sh aya: WG keys, DNS, ClusterIP, NodePort, pod-to-pod; HA: 6-VM Raft, leader, writer/verify, VIP, join-once, rollout; stdout only not reachable from the kernel suite (they are its NOTRUN lines) KVM host

2.3 Heimdall

Crate Does Kernel-relevant outputs
core Job/Policy/SignedResult types; KernelFeatures (types.rs:573); SeccompProfileRef (:320) every signed result names the kernel features seen
seccomp profiles, BPF builder, OCI writer/reader (oci.rs:33,93), tables x86_64 + aarch64 (tables/, scripts/gen-syscall-tables.sh) .oci.json sha = the pin
probe learn (learn.rs:97, live.rs:108), merge (observe.rs:81), minimise (minimise.rs:99), codegen, check (check.rs:41) .probe.json, .json, .oci.json, .rs
container HeimdallRunner: youki, Landlock, seccomp, cgroups, broker, learn; KernelReport (kernel.rs:23), missing_for_strict (:118); shim with no_pivot when / is the initial rootfs (shim.rs at d747087; heimdall-plain/src/rootfs.rs:23,41) runtime report only; no fragment
broker USER_NOTIF+ADDFD supervisor; its own kernel probe Support (support.rs:18); creates files as the caller (setfsuid guard, 9ac44df)
k8s node signer for RuntimeClass heimdall (lib.rs:100,167,221,336); Landlock-confined (:484-490); reports what the kubelet says it applied
cli `heimdall run verify-result
profiles/ one checked-in workload heimdall-wasm-host (x86_64 glibc+musl)
scan, wasm, run, trace, plain static-scan contract for Holger, wasm host, run, dynamic trace, plain youki runner trace gives per-program syscall facts (trace/doc.rs:125)

No approve verb, no review UI, no "heimdall-tune" exists in Heimdall. Approval lives in dwarves (tune approve → approved.toml, tune app approve, RowApprover in dwarves-server) and edda measure (approval_status, approve).

2.4 Holger kernel usage report (.nornir/kernel-usage/holger-scan-worker/)

File What
README.md the report: two halves, findings, reproduce
measure.json, syscalls-{scan,job}.probe.json the worker's measured calls in scan mode (23) and job mode (30)
needs.json nornir.kernel-needs/1 from tunnr-build-thing kernel needs holger-installer-metered --repo <holger> --json: only CONFIG_FUTEX and CONFIG_MMU gate a worker call; 17 enabled gating options have no worker caller (a trim hint, not a veto)
coverage.json, coverage.txt nornir.kernel-coverage/1, GREEN, suite scan-worker: 859 live symbols, 220 executed, 61 never, 578 unmeasured; 3 not run (server, clamd, boot path)
candidates.txt 219 candidates, 8 veto-ready with coverage
heimdall-needs.fragment CONFIG_SECURITY=y, CONFIG_SECURITY_LANDLOCK=y, CONFIG_LSM="landlock", CONFIG_CROSS_MEMORY_ATTACH=y — hand-written from the measured refusal; merged via --fragment-extra for the run
tools/kernel-suite.sh, holuse-init.c, prep-jobs.py, probe-jobs.sh, cpio.py the Holger-specific suite (9 required env vars, kernel-probe compose, direct kernel boot, tmpfs root)

Finding 1 of that report (the shipped Holger kernel cannot run Heimdall strict) is addressed on holger branch kern/appliance-lsm by hand-typed requires rows, and in heimdall d747087 by the no-pivot shim. Nothing reads needs.json, coverage.json or the fragment back by code.

2.5 dwarves

Item path:line Does
`dwarves tune probe gen check
`dwarves tune app run approve` tune_app.rs:381
dwarves-tune-graph crates/dwarves-tune-graph/src/{lib.rs,xcheck.rs,profile.rs,main.rs} `--graph <graph.json> --profile <probe
kernel ledger crates/dwarves/src/kernel_ledger.rs index = action cache, bytes = artifact store; record, resolve_built_kernel(_for); honest no-op when DWARVES_HOLGER_DIR unset
--opt kernel.* buildthing.rs:536-574 the operator decision door, resolved against the donor's declaration

2.6 Mímir (nornir) — the possible-syscalls engine

Exists path Does
source call graph crates/mimir-driver CALLS edges from rustc HIR/MIR, #[cfg] gating
binary layer crates/mimir-binary/src/lib.rs linked symbols joined onto the same node ids (v0 demangle, mono instances, FFI border)
boundary crates/mimir-ingest/src/boundary/{mod.rs,x86_64.rs,libc_map.rs,class.rs,btf.rs} @syscall nodes per x86_64 table entry + <indirect> (mod.rs:51); libc map (open64 → openat); raw doors → <indirect> UNCERTAIN; kernel types from BTF; SYSCALLS edge kind (mimir-schema/src/lib.rs:290)
room crates/mimir-room/src/{path.rs,export.rs,query.rs} shortest path A→B over chosen edge kinds (path.rs:45), edge export (export.rs:187); SYSCALLS edges only when the dependency universe is folded in (query.rs:219)
plan .nornir/mimir-syscalls-plan.md steps 1–6; 1–4 unassigned; 6 points at Holger 3.2

Missing (plan steps): 1 walk from entry points to SYSCALLS and export the Graph JSON dwarves-tune-graph reads; 2 unresolved edges for dyn Trait / fn pointers / Box<dyn Fn>; 3 dependency sources from Holger's crates.io mirror; 4 aarch64 table; 5 bench on rusternetes-node + tunnr-cni against their approved profiles.

Consumers of Mímir's output (who reads what, once step 1 exists):

Output Consumer Repo of the consumer change Effect
graph.json per program (Graph type: syscalls{name→Reach}, unresolved_sites, dlopen, …) dwarves-tune-graph cross_check (exists, xcheck.rs:39) via dwarves tune app and tune show dwarves: none for the reader; tune show should look for .nornir/profiles/<row>/<program>/graph.json possible-but-unseen = warn before approve; seen-but-not-possible = red
the same graph.json edda syscall_join::MeasuredRow gains an optional possible set per program edda syscall_join.rs:602,721,850 kernel needs lists options needed by possible calls too (class "possible, unexercised"); vetoes_for refuses a veto whose option gates a possible call (never widens a profile; only protects the kernel)
the same Build Thing veto what-if edda kernel_trim.rs:160 a possible call becomes a protect reason with a path, so a trim cannot drop what the code can reach
the same Heimdall profiles none Heimdall never widens a filter from a graph (dwarves rule, lib.rs:16); only the review warns

Repo fit per plan step: 1, 2, 4 → nornir (mimir-room export verb writing the Graph JSON from @syscall nodes; the aarch64 table beside boundary/x86_64.rs); 3 → nornir mimir-ingest reading crate sources from Holger (a Holger door if one is missing); 5 → tunnr (.nornir/profiles/<row>/<program>/graph.json + the cross-check report committed beside the row), run through dwarves tune. The walk itself stays the owner's; lanes only add readers of the JSON.


3. Data flow today

 [[build.kernel]] row (tunnr kernel.rs)                 measure.toml (hand-written, per row)
        │ preset + profile + closure                             │
        ▼                                                        ▼
 requires (consumers, incl. veto list =n) ──► merged fragment    heimdall profile probe (external binary, privileged podman)
        │                                      (edda kernel.rs:1652)      │
        ▼                                                                 ▼
 kernel build in container ──► .kernel-out/<name>/ + kernel-pin.json   .nornir/profiles/<row>/{measure.json,<prog>/*.probe.json,*.json,*.oci.json}
        │                            │ ledger key                          │                          │
        ▼                            ▼                                     ▼                          ▼ (MISSING: not packed)
 korp-installer ISO (kernel_donor) dwarves kernel ledger            kernel needs (syscall_join)   korp-start seccomp/<entry>.oci.json
        │                                                                   │ stdout only          (hand-written 361-call file today)
        ▼                                                                   ▼ MANUAL redirect
 kernel-suite.sh (KVM): boot, stages, memsave  ◄── kernel coverage        kernel-needs.json ──► dwarves tune show
        │                                                                   
        ▼                                                                   
 .nornir/kernel-coverage/<row>/<sha12>.json ──► kernel candidates ──► kernel veto ──► <row>.list + receipt JSON
        ▲ protect.list                                   ▲ what-if (+ measured-call refusal)        │
        │                                                │                                           ▼
        └── PROBE_ALTERED always protected               └── syscall_join::vetoes_for               include_str! → recipe → emit → build-variants.toml

 Holger: tools/kernel-suite.sh ──► tunnr-build-thing kernel-probe --repo <holger> --fragment-extra heimdall-needs.fragment
         ──► needs.json, coverage.json, candidates.txt, heimdall-needs.fragment  (a REPORT; nothing consumes it)
         ──MISSING──► Holger [[build.installer.requires]] (hand-typed on branch kern/appliance-lsm)

 Mímir: driver CALLS + binary symbols + @syscall boundary ──MISSING step 1──► graph.json ──► dwarves-tune-graph (reader exists)
                                                                                       └──MISSING──► syscall_join possible set

4. Overlaps, duplicates, gaps and sharp edges (seen in code)

# Where What
S1 edda kernel.rs:962-977,1087-1106; kernel-guide.md:295-311; kernel-for-idiots.md; tunnr kernel.rs:302-308,916 Pin semantics clash. tunnr now pins the image id; edda builds name@sha256:<pin> (a manifest ref) and its comment calls the image-id reading "WRONG"; tunnr's pin_defects still demands @sha256:; the two guides teach the opposite rule. Works today only if the engine resolves the id (not verified at run time).
S2 edda kernel.rs:3668 vs :1538 Ledger key misses axes. ledger_key omits toolchain (gcc/clang), solver, base; canonical_bytes has them. A gcc and a clang build share one pins/<key>/ slot and overwrite each other.
S3 edda kernel.rs:3630, dwarves kernel_ledger.rs Ledger has one implementation, in dwarves, behind DWARVES_HOLGER_DIR. edda's public resolver always uses NoLedger.
S4 edda kernel.rs:4782, kernel_requires.rs:812, kernel_trim.rs:1420 kernel.why.toml is written but never read in production. WhyIndex::from_toml is test-only; kernel_trim recomputes the index.
S5 edda veto.rs:636,646; tunnr vetoes.rs:55-69; tunnr recipe.rs:533 Receipts are not consumed. The build trusts the .list line; the receipt JSON is read only by the vetoes listing verb and one unit test. No check that a list line has a receipt, that it says applied, or that it belongs to the current fragment.
S6 tunnr main.rs:432-434, vetoes.rs:24-30 --repo vetoes land nowhere. A veto for another repo's row is written to <other>/crates/tunnr-build-thing/kernel-vetoes (Holger has no such crate); list_of compiles in only tunnr's two rows.
S7 tunnr vetoes.rs:16-18, main.rs:50 A veto needs a rebuild of the door and a re-emit (include_str!), a manual step after every veto.
S8 edda syscall_join.rs:721; dwarves tune/review.rs:224, README.md:162 kernel-needs.json has a reader and no writer (shell redirect only). No row in any repo has the file.
S9 heimdall container/src/kernel.rs:118-127 Heimdall's kernel needs are prose. No machine-readable list, no fragment. The Holger fragment and the branch rows were typed by hand from a failure message.
S10 heimdall container/src/kernel.rs:56 vs broker/src/support.rs:18 Two kernel probes that can disagree. KernelReport infers ADDFD from the release string; the broker probes it for real.
S11 heimdall probe/observe.rs, minimise.rs, seccomp/oci.rs, trace/doc.rs:125; dwarves Seen; edda MeasuredRow Five syscall-set shapes (probe Observations, Generated profile, OCI JSON, trace facts, dwarves Seen) and three readers of .probe.json (heimdall, edda syscall_join, dwarves-tune-graph), each parsing by field name.
S12 edda syscall_join.rs:259 (MakeGates) vs coverage.rs:388 (linked_gates) Object → CONFIG gate resolution exists twice, with different rules, both over makefile_gates.
S13 tunnr rusternetes.rs:984-1011; .nornir/profiles/rusternetes-node/measure.toml The packed node seccomp profile is hand-written (361 calls) and reused for two entries; the measured node-pid1 profile (17 calls) is not packed; korp-start, rusternetes and the youki shim are never measured (needs_vm).
S14 tunnr payloads/rusternetes-node/src/landlock.rs:35 Landlock is off for Rusternetes and every pod ("waiting on the shim door"). Only CSI has rules.
S15 tunnr scripts/kernel-suite.sh:43-56,96; .nornir/kernel-coverage/rusternetes-node/ One suite, one row. bridge for rusternetes-node only; the aya row has no suite, no coverage, an empty veto list. The applied veto ran with 10 checks NOTRUN. No coverage for the post-veto fragment 333150ec…; 380dcfbd46e9.json is stale. Line 96 has a garbled comment. The build-box/KVM split needs a hand sync of the kernel tree.
S16 tunnr main.rs:465, xtask/src/main.rs:69 Usage strings are stale: kernel-probe, preflight, measure gate missing from the door's usage; xtask lists only `stage
S17 tunnr .nornir/heimdall-runtimeclass.md; heimdall docs/SPEC.md:146 Stale docs. RuntimeClass doc says step 2 PARKED while the code ships it; SPEC says the RuntimeClass "runs pods through heimdall-run" while heimdall-k8s only signs what the kubelet claims (k8s/src/lib.rs:88,167).
S18 tunnr crates/korp-installer/kernel/korp-kernel.blake3:17 A dead pin: all zeros; only the $KORP_KERNEL path reads it.
S19 edda coverage.rs:730 Coverage reports keyed by a 12-char sha prefix; a collision fails load_for's full-sha check instead of finding the right file.
S20 edda coverage.rs:13 vs kernel.rs:3076 "Boots under KVM" is in the row script, not the crate; the in-crate probe boot is TCG. Two structs named KernelProbe (kernel.rs:3231 result; coverage.rs:73 gcov enum).
S21 tunnr xtask/src/rusternetes.rs:159 Cargo toolchain image pinned by tag only (localhost/…:live-tools-ssl-proto), the very prefix kernel.rs:230 warns about.
S22 edda kernel modules No AI/MCP/llms face for kernel data. Only --json schemas. The owner's "visible in AI" is not wired.
S23 heimdall xtask/src/main.rs:13-29; scripts/*.sh xtask accepts --quick/--nightly and ignores them; every real gate is a shell script with privileged podman. Two near-duplicate youki shims (plain/src/shim.rs, container/src/shim.rs).
S24 heimdall profiles Profile digests live in CHANGELOG/docs and callers' SeccompProfileRef, not beside the profile; no corpus hash in a profile; aarch64 has tables but no profile or kernel test.

5. Dead or orphaned

Item path:line State
kernel.why.toml edda kernel.rs:4782,4306; reader kernel_requires.rs:812 written; read only by tests
veto_requirements (edda) kernel_trim/veto.rs:92 called only by veto/tests.rs:131 inside edda (tunnr calls it through vetoes.rs)
veto receipt JSON tunnr .nornir/kernel-vetoes/rusternetes-node/receipts/*.json read by one unit test and the vetoes list verb; never checked by the build
kernel-needs.json dwarves tune/review.rs:19,224 reader with no writer; only a test fixture exists
kernel-probe door tunnr main.rs:301-377 called only by Holger's hand-run suite script
run_kernel_verb in edda kernel_trim.rs:~1224/1332 no CLI in edda; only syscall_join/tests.rs:331 and the tunnr door
KernelLedger trait in edda kernel.rs:3642 NoLedger + test MapLedger only; the real one is in dwarves
MINIMAL_FRAGMENT edda kernel.rs minimal is refused on x86_64, the only wired arch
walk::analyze, srcgraph::* dwarves dwarves-tune-graph/src/walk.rs:6, srcgraph/mod.rs:36-46 owner-reserved stubs, tests #[ignore] (correct by decision; listed so nobody "fixes" them)
korp-kernel.blake3 tunnr crates/korp-installer/kernel/korp-kernel.blake3 all-zeros placeholder
380dcfbd46e9.json tunnr .nornir/kernel-coverage/rusternetes-node/ coverage of a fragment no row has
heimdall-needs.fragment, needs.json, coverage.json (Holger) holger/.nornir/kernel-usage/holger-scan-worker/ report files; no code reads them
--quick, --nightly heimdall xtask/src/main.rs:13-29 parsed, ignored
offsite::restore_latest holger server/lib/src/offsite.rs:1143 called only by server/lib/tests/the_offsite_copy_restores_a_fresh_box.rs:114,153
two kernel probes heimdall container/src/kernel.rs:56, broker/src/support.rs:18 duplicated logic
MakeGates vs linked_gates edda syscall_join.rs:259, coverage.rs:388 duplicated logic

6. How it could work together

The boundary that already holds in code and licence: Build Thing (MIT) never links Heimdall (AGPL); it runs the binary and reads JSON by field name. The owner's engine (Mímir) writes a Graph JSON; lanes only read it. Every option below keeps both.

Homes

Concern Home Why
rows, fragments, pins, ledger, variants, emit, parity Build Thing (edda engine + <repo>-build-thing) it is the build
what-if, candidates, coverage model, veto rule, receipts Build Thing they change the kernel artifact
syscall → CONFIG join Build Thing (edda syscall_join) it needs the composed kernel tree; it is a pure reader of profile files
measured syscalls, Landlock/LSM needs, profiles, approval, drift Heimdall tooling (heimdall binary + dwarves tune + edda measure) runtime need, runtime evidence
Heimdall's own kernel needs Heimdall (emit) → Build Thing (consume as requires) the runtime knows what it needs; the build must state it as rows
possible syscalls (graph) Mímir (owner) → a JSON file beside the row the plan's boundary
the suite (boot the medium, print stages) the product repo (scripts/kernel-suite.sh, Holger tools/) only the product knows how to boot and exercise itself

Interface = files in the row directory, with schemas (no code coupling)

.nornir/profiles/<row>/
  measure.toml, measure.json, approved.toml            Heimdall tooling (exists)
  <program>/<arch>-<libc>.{probe.json,json,oci.json}   Heimdall tooling (exists)
  <program>/graph.json                                 Mímir  (MISSING: plan step 1)
  kernel-needs.json      nornir.kernel-needs/1         Build Thing join  (MISSING writer)
  kernel-requires.toml   generated requires rows       Build Thing from heimdall needs (MISSING)
.nornir/kernel-coverage/<row>/<sha>.json               Build Thing (exists)
.nornir/kernel-vetoes/<row>/{protect.list,receipts/}   Build Thing (exists); the .list should live here too

Option A — keep the three homes, close the missing writers (smallest)

Option B — a shared MIT crate nordisk-kernel-evidence

Option C — Heimdall owns the "needs" end to end


7. Recommendation

Option A now, B later. The pieces exist and each file already has a schema; what is missing is four writers and one reader. Keep Mímir's walk the owner's; lanes add only the graph.json reader in syscall_join and the tune show lookup.

Smallest first step: kernel needs <row> --json --save writes .nornir/profiles/<row>/kernel-needs.json (edda kernel_trim.rs verb + tunnr main.rs usage), and the Holger report's needs.json is copied there as the first real file. One PR, one test, and dwarves tune show starts showing the kernel join for a real row.


8. Fix list (smallest first)

# Fix Repo Files
1 kernel needs --save writes kernel-needs.json beside the row; tune show then reads a real file edda, tunnr edda/crates/nornir-build-thing/src/kernel_trim.rs (verb), src/syscall_join.rs:721; tunnr/crates/tunnr-build-thing/src/main.rs:378-463,465
2 Usage strings: add kernel-probe, preflight, measure gate; xtask verbs tunnr crates/tunnr-build-thing/src/main.rs:465, xtask/src/main.rs:69
3 Suite script hygiene: fix the garbled comment, document KS_MEDIA tree sync, remove the stale coverage file tunnr scripts/kernel-suite.sh:49-56,96, .nornir/kernel-coverage/rusternetes-node/380dcfbd46e9.json
4 One pin rule: decide image id vs manifest digest, make edda's ref builder, tunnr's pin_defects and both guides agree edda, tunnr edda/.../src/kernel.rs:958-982,1087-1106, edda/.nornir/kernel-guide.md:295-311, edda/.nornir/kernel-for-idiots.md; tunnr/crates/tunnr-build-thing/src/kernel.rs:302-308,916
5 Heimdall: one kernel probe (KernelReport uses the broker's real probe for ADDFD); fix the stale comment heimdall crates/heimdall-container/src/kernel.rs:7,56, crates/heimdall-broker/src/support.rs:18
6 Ledger key carries toolchain, solver, base edda crates/nornir-build-thing/src/kernel.rs:3668,4787
7 heimdall kernel needs --json: kconfig lines strict mode requires, with reasons heimdall crates/heimdall-container/src/kernel.rs:118-127, crates/heimdall-cli/src/main.rs
8 Build Thing consumes 7: kernel requires-from-needs generates [[build.installer.requires]] rows; Holger's hand-typed LSM rows become generated; merge the branch edda, holger edda/.../src/kernel_requires.rs; holger branch kern/appliance-lsm (.nornir/build-variants.toml rows), holger-build-thing recipe
9 Veto list beside its receipts; the build refuses a list line with no applied receipt on the current fragment; --repo vetoes land in the other repo's .nornir/kernel-vetoes/<row>/ edda, tunnr edda/.../kernel_trim/veto.rs:108,636; tunnr/crates/tunnr-build-thing/src/{vetoes.rs:16-30,main.rs:432-434,recipe.rs:533,564}
10 Coverage for the post-veto fragment; a suite for the aya row tunnr scripts/kernel-suite.sh:43-46, .nornir/kernel-coverage/rusternetes-node-aya/
11 Pack the measured profiles instead of the 361-call hand-written one; a VM leg for korp-start, rusternetes, the youki shim (needs_vm) tunnr crates/tunnr-build-thing/src/rusternetes.rs:984-1011, .nornir/profiles/rusternetes-node/measure.toml, scripts/kernel-suite.sh
12 Landlock for Rusternetes and pods (the "shim door") tunnr, heimdall payloads/rusternetes-node/src/landlock.rs:35, src/heimdall.rs
13 Either read kernel.why.toml (WhyIndex::from_toml) in kernel_trim or stop writing it edda src/kernel.rs:4782,4306, src/kernel_requires.rs:812, src/kernel_trim.rs:1420
14 One object→gate resolver (MakeGates and linked_gates) edda src/syscall_join.rs:259, src/kernel_trim/coverage.rs:388
15 Stale docs: RuntimeClass step 2, SPEC §9, kernel minimums tunnr, heimdall tunnr/.nornir/heimdall-runtimeclass.md; heimdall/docs/SPEC.md:55,146, docs/SECCOMP.md
16 Kernel data visible to AI: kernel rows, needs, coverage totals and vetoes in the nornir introspection / llms output edda, nornir edda/crates/nornir-build-thing/src/{surface.rs,suite_cli.rs}, nornir/crates/nornir-introspect
17 Mímir plan steps 1–5 (owner assigns): 1 walk + graph.json export, 2 unresolved edges, 4 aarch64 table → nornir crates/mimir-room (export), crates/mimir-ingest/src/boundary/; 3 sources from Holger → nornir mimir-ingest/src/checkout.rs (+ a Holger door if needed); 5 bench on rusternetes-node, tunnr-cni → tunnr .nornir/profiles/<row>/<program>/graph.json + report nornir, holger, tunnr as listed
18 syscall_join reads graph.json as a possible set: needs class "possible, unexercised"; a veto cannot drop an option a possible call needs edda src/syscall_join.rs:602,721,850, src/kernel_trim.rs:160
19 Remove the dead korp-kernel.blake3 placeholder path or fill it tunnr crates/korp-installer/kernel/korp-kernel.blake3, src/kernel.rs:33-42
20 Option B: shared evidence crate (after 1–18) edda, dwarves new crate; dwarves-tune-graph/src/lib.rs types; syscall_join.rs