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

nordisk/Skidbladnir

src/service.rs

//! **INSTALL-TREE**, the root's view (lane B4): every item of
//! [`skidbladnir_systemd::service`] at its old path, plus what is not a systemd
//! host's concern: the Kubernetes renderer ([`crate::k8s::KubernetesRenderer`]),
//! the Windows SCM renderer ([`WindowsServiceRenderer`], over [`crate::msi`]), the
//! signed-bundle refresh ([`crate::service_backend::ServiceRefresh`]) and the
//! git watch ([`LifecycleWatch`]).

pub use skidbladnir_systemd::service::*;

pub use crate::service_backend::ServiceRefresh;
pub use crate::service_windows::WindowsServiceRenderer;

/// Resolve a [`Backend`] to its renderer: every backend this crate renders,
/// Kubernetes and the Windows SCM included. `None` only for a declared but
/// unrendered backend, never a fake success.
pub fn renderer_for(backend: Backend) -> Option<Box<dyn ServiceRenderer>> {
    match backend {
        Backend::Kubernetes => Some(Box::new(crate::k8s::KubernetesRenderer)),
        Backend::WindowsService => Some(Box::new(WindowsServiceRenderer)),
        other => skidbladnir_systemd::service::renderer_for(other),
    }
}

/// **WATCH-REFRESH — poll git `HEAD`; refresh on change, no-op when unchanged.**
/// The one tick behind `korp-server --watch-refresh` / `dwarves-full
/// --watch-refresh`: read the current commit (gix, zero-shell), diff it against the
/// persisted [`crate::watch::WatchStamp`], and only on a MOVE drive the refresh
/// engine through the injected [`crate::watch::RefreshTrigger`], advancing the stamp
/// on success. Delegates to [`crate::watch::watch_refresh_once`].
pub trait LifecycleWatch {
    fn watch_refresh(&self, req: &crate::watch::WatchRequest<'_>) -> anyhow::Result<crate::watch::WatchOutcome>;
}

impl LifecycleWatch for Lifecycle {
    fn watch_refresh(&self, req: &crate::watch::WatchRequest<'_>) -> anyhow::Result<crate::watch::WatchOutcome> {
        crate::watch::watch_refresh_once(req)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use skidbladnir_systemd::systemd::RestartPolicy;

    fn holger() -> ServiceSpec {
        ServiceSpec::new("holger", "Holger artifact repository", "/opt/holger/bin/holger")
            .arg("serve")
            .arg("--config")
            .arg("/etc/holger/holger.toml")
            .user("_holger")
            .working_dir("/var/lib/holger")
            .env("HOLGER_DATA", "/var/lib/holger")
            .after("nornir-server")
            .restart(RestartPolicy::OnFailure)
            .socket(Socket::tcp(7070))
    }

    /// **Kubernetes has the real thing on both sides — render both.**
    ///
    /// `resources.limits.memory` IS the hard ceiling (the kubelet writes the container
    /// cgroup's `memory.max`; exceed it and the container is OOM-killed and restarted —
    /// the `MemoryMax=` + `Restart=` pair). `resources.requests.memory` carries the
    /// soft ceiling and is a near-miss, not a twin: it is a SCHEDULING guarantee, not a
    /// throttle. It is the only soft-side number the pod API takes, and naming the
    /// difference beats dropping the declaration.
    ///
    /// RED direction: delete the `resources:` block from `KubernetesRenderer` and a pod
    /// that declared a ceiling gets scheduled with none — the cluster-shaped version of
    /// 2026-08-20.
    #[test]
    fn the_kubernetes_renderer_renders_limits_and_requests() {
        let spec = ServiceSpec::new("dwarves-server", "d", "/usr/local/bin/dwarves-server")
            .image("dwarves:1.0")
            .memory(MemoryBound::gib(16, 24).unwrap());
        let manifests: String = crate::k8s::KubernetesRenderer
            .render(&spec).unwrap()
            .files
            .iter()
            .map(|f| f.contents.clone())
            .collect();

        assert!(
            manifests.contains("            limits:\n              memory: \"24Gi\"\n"),
            "the HARD ceiling is resources.limits.memory:\n{manifests}"
        );
        assert!(
            manifests.contains("            requests:\n              memory: \"16Gi\"\n"),
            "the SOFT ceiling goes to requests — a near-miss, named as one:\n{manifests}"
        );
        // Nothing declared ⇒ no resources block, so every existing manifest is
        // byte-identical (these are exact-bytes contract manifests).
        let plain: String = crate::k8s::KubernetesRenderer
            .render(&ServiceSpec::new("s", "d", "/bin/s").image("s:1")).unwrap()
            .files
            .iter()
            .map(|f| f.contents.clone())
            .collect();
        assert!(
            !plain.contains("resources:\n            limits"),
            "no declaration ⇒ no container resources block:\n{plain}"
        );
    }

    /// Windows honest edges: `RestartPolicy::No` ⇒ NO crash-recovery block; a no-env
    /// spec carries NO env `ServiceConfig`; no deps ⇒ NO `depend=` in the create verb;
    /// and an unset user defaults the account to the built-in `LocalSystem`.
    #[test]
    fn windows_service_no_restart_no_env_and_default_account() {
        let spec = ServiceSpec::new("svc", "d", "/bin/svc").restart(RestartPolicy::No);
        let wv = WindowsServiceRenderer.render(&spec).unwrap();
        let wxs = wv.file_ending("svc.wxs").expect("a .wxs fragment");
        // No restart ⇒ no recovery block.
        assert!(
            !wxs.contains("FirstFailureActionType"),
            "No ⇒ no crash-recovery:\n{wxs}"
        );
        // No env ⇒ no env ServiceConfig.
        assert!(!wxs.contains("<!-- env:"), "no env ⇒ no env block:\n{wxs}");
        // Enable-at-boot is still auto-start.
        assert!(wxs.contains("Start=\"auto\""));
        // Unset user → the built-in LocalSystem account, in BOTH the XML and the verb.
        assert!(
            wxs.contains("Account=\"LocalSystem\""),
            "default account:\n{wxs}"
        );
        assert!(wv.enable.contains(&"LocalSystem".to_string()));
        assert_eq!(
            wv.enable
                .iter()
                .position(|s| s == "obj=")
                .map(|i| &wv.enable[i + 1]),
            Some(&"LocalSystem".to_string())
        );
        // No deps ⇒ no depend= flag.
        assert!(
            !wv.enable.contains(&"depend=".to_string()),
            "no deps ⇒ no depend=: {:?}",
            wv.enable
        );
    }

    // The cross-backend registry tests (lane B4): they span the Kubernetes and the
    // Windows renderers, so they run against the root's `renderer_for`.

    /// `AgentMode` is pure classification metadata: a Fat spec and an otherwise
    /// identical Thin spec render BYTE-IDENTICAL units on every backend (the mode
    /// classifies the node; it does not change the unit), and it round-trips.
    #[test]
    fn agent_mode_is_metadata_only_same_rendered_output() {
        let base = || {
            ServiceSpec::new("nornir", "nornir server", "/usr/local/bin/nornir-server")
                .user("nornir")
                .socket(Socket::tcp(7878))
        };
        let fat = base().agent(AgentMode::Fat);
        let thin = base().agent(AgentMode::Thin);
        assert_ne!(fat.agent, thin.agent, "the specs really differ in mode");

        for backend in [
            Backend::SystemdSystem,
            Backend::OpenRc,
            Backend::SysV,
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::Kubernetes,
            Backend::Oci,
            Backend::Quadlet,
            Backend::WindowsService,
        ] {
            let rf = renderer_for(backend).unwrap().render(&fat).unwrap();
            let rt = renderer_for(backend).unwrap().render(&thin).unwrap();
            assert_eq!(
                rf.files, rt.files,
                "{backend:?}: agent mode must not change files"
            );
            assert_eq!(
                rf.enable, rt.enable,
                "{backend:?}: agent mode must not change verbs"
            );
        }
        // And the mode survives a JSON round-trip.
        let back: ServiceSpec =
            serde_json::from_str(&serde_json::to_string(&thin).unwrap()).unwrap();
        assert_eq!(back.agent, AgentMode::Thin);
    }

    /// Full backend-coverage matrix: EVERY backend now resolves to a renderer whose
    /// `backend()` matches the key it was looked up under (11/11 — no honest `None`
    /// remains). Guards against a renderer being wired to the wrong `Backend` (a
    /// fake-success class of bug) and against the count silently regressing.
    #[test]
    fn backend_coverage_matrix_is_honest_and_self_consistent() {
        let implemented = [
            Backend::SystemdSystem,
            Backend::SystemdUser,
            Backend::OpenRc,
            Backend::SysV,
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::Kubernetes,
            Backend::Oci,
            Backend::Quadlet,
            Backend::WindowsService,
        ];
        let mut resolved = 0;
        for b in Backend::all() {
            match renderer_for(*b) {
                Some(r) => {
                    assert!(
                        implemented.contains(b),
                        "{b:?} resolved but is not in the implemented set"
                    );
                    assert_eq!(
                        r.backend(),
                        *b,
                        "{b:?}: renderer reports a DIFFERENT backend"
                    );
                    resolved += 1;
                }
                None => assert!(
                    !implemented.contains(b),
                    "{b:?} is implemented but resolved to None"
                ),
            }
        }
        assert_eq!(
            resolved,
            implemented.len(),
            "exactly the implemented backends resolve"
        );
    }

    /// Stinky #20: config files ship WITH the spec. Every host backend renders them at
    /// their own path and mode; the container backends also bind-mount them read-only
    /// at the same path; Windows refuses rather than drop them.
    #[test]
    fn config_files_render_on_every_host_backend() {
        let spec = ServiceSpec::new("bifrost", "Bifrost", "/usr/local/bin/bifrost")
            .config_file("/etc/bifrost/m.yml", "graph: lake\n");
        let mut spec600 = spec.clone();
        spec600.config_files[0].mode = 0o640;
        for b in [
            Backend::SystemdSystem,
            Backend::OpenRc,
            Backend::SysV,
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::Oci,
            Backend::Quadlet,
        ] {
            let r = renderer_for(b).unwrap().render(&spec600).unwrap();
            let f = r
                .files
                .iter()
                .find(|f| f.path == "/etc/bifrost/m.yml")
                .unwrap_or_else(|| panic!("{b:?} dropped the config file: {:?}", r.files));
            assert_eq!(f.contents, "graph: lake\n", "{b:?}");
            assert_eq!(f.mode, 0o640, "{b:?}");
        }
        let oci = renderer_for(Backend::Oci).unwrap().render(&spec).unwrap();
        assert!(
            oci.start.windows(2).any(|w| w[0] == "--volume" && w[1] == "/etc/bifrost/m.yml:/etc/bifrost/m.yml:ro"),
            "{:?}",
            oci.start
        );
        let q = renderer_for(Backend::Quadlet).unwrap().render(&spec).unwrap();
        assert!(q.files[0].contents.contains("\nVolume=/etc/bifrost/m.yml:/etc/bifrost/m.yml:ro\n"));
        let colon = ServiceSpec::new("bifrost", "B", "/b").config_file("/etc/a:b", "x");
        assert!(renderer_for(Backend::Oci).unwrap().render(&colon).is_err());
        assert!(renderer_for(Backend::SystemdSystem).unwrap().render(&colon).is_ok());
        let w = renderer_for(Backend::WindowsService).unwrap().render(&spec);
        assert!(w.unwrap_err().to_string().contains("config file"));
        assert_eq!(to_systemd_spec(&spec).config_files, spec.config_files);
    }

    #[test]
    fn every_backend_carries_secrets_privately_or_refuses() {
        const TOKEN: &str = "tok-do-not-leak-123";
        let spec = holger()
            .image("registry.example/holger:1")
            .secret_env("HOLGER_TOKEN", TOKEN);
        for &b in Backend::all() {
            let r = renderer_for(b).expect("every backend renders").render(&spec);
            let Ok(r) = r else {
                assert_eq!(b, Backend::WindowsService, "only Windows may refuse: {r:?}");
                continue;
            };
            let holders: Vec<_> = r.files.iter().filter(|f| f.contents.contains(TOKEN)).collect();
            assert!(!holders.is_empty(), "{b:?} dropped the secret: {:#?}", r.files);
            for f in holders {
                assert_eq!(f.mode, 0o600, "{b:?}: secret in non-private {}", f.path);
            }
        }
    }

    /// Stinky #2: EVERY backend either carries a secret in a private (0600) file it
    /// renders, or REFUSES. Before, only k8s and `to_systemd_spec` read `secret_env`;
    /// every other renderer dropped it and the service started without its token.
    /// Stinky #7/#17: the neutral spec reached every renderer unchecked — a service
    /// name of `../x` became `/etc/init.d/../x`, a newline in a description or user
    /// injected lines into a unit/script, and an env key with a space or `=` produced
    /// an assignment no backend can express. Every backend now refuses such a spec.
    #[test]
    fn every_backend_refuses_an_unsafe_spec() {
        let base = || holger().image("registry.example/holger:1");
        let bad: Vec<(&str, ServiceSpec)> = vec![
            ("name traversal", { let mut s = base(); s.name = "../x".into(); s }),
            ("name slash", { let mut s = base(); s.name = "a/b".into(); s }),
            ("description newline", { let mut s = base(); s.description = "a\nb".into(); s }),
            ("user newline", base().user("svc\nroot")),
            ("working dir newline", base().working_dir("/srv\n/x")),
            ("env key space", base().env("A B", "v")),
            ("secret key =", base().secret_env("T=U", "v")),
            ("operator key digit", base().operator_env("1X", "v")),
        ];
        for (what, spec) in &bad {
            for &b in Backend::all() {
                let r = renderer_for(b).expect("registered").render(spec);
                assert!(r.is_err(), "{b:?} rendered a spec with {what}");
            }
        }
        // A multi-line VALUE is valid: every backend whose env format can carry it
        // renders it. The literal NAME=VALUE files (dinit, podman) cannot and refuse
        // it by name (stinky #10), and Windows refuses env it would store in the clear.
        for &b in Backend::all() {
            let r = renderer_for(b)
                .unwrap()
                .render(&base().env("K", "multi\nline value"));
            match b {
                Backend::WindowsService => {}
                Backend::Dinit | Backend::Oci => {
                    assert!(r.is_err(), "{b:?} wrote a newline into a literal env file")
                }
                _ => {
                    r.unwrap_or_else(|e| panic!("{b:?} refused a valid spec: {e}"));
                }
            }
        }
    }

    /// **The five backends with NO cgroup route must SAY so, naming the numbers.**
    ///
    /// runit's `chpst -m`, s6's `s6-softlimit -m`, dinit's `rlimit-data` and SysV's
    /// `ulimit -v` all *look* like the answer and are a different mechanism: an rlimit
    /// caps one process's address space, is accounted per-process, never reclaims, has
    /// no soft ceiling, and when it bites the allocation fails inside the program
    /// instead of the cgroup being killed and restarted. Rendering one would claim a
    /// bound that is not one and leave the box exactly as exposed as it was on
    /// 2026-08-20. Windows' job objects are real but unreachable from `sc.exe` or a WiX
    /// `<ServiceInstall>`.
    ///
    /// So: an explicit `NOT HONOURED` note carrying BOTH numbers, in the artifact the
    /// operator reads — the same discipline as OpenRC's supplementary-groups note.
    ///
    /// RED direction: drop any one of the five `memory_not_honoured_note` calls and
    /// that backend's assertion fails while its script still renders and installs —
    /// precisely the silent drop this asserts against.
    #[test]
    fn every_backend_without_a_cgroup_route_names_the_ceiling_it_cannot_honour() {
        let bound = MemoryBound::gib(16, 24).unwrap();
        let spec = ServiceSpec::new(
            "dwarves-server",
            "the forge",
            "/usr/local/bin/dwarves-server",
        )
        .user("dwarves")
        .memory(bound);
        let plain = ServiceSpec::new(
            "dwarves-server",
            "the forge",
            "/usr/local/bin/dwarves-server",
        )
        .user("dwarves");

        let all_files = |s: &ServiceSpec, b: Backend| -> String {
            renderer_for(b)
                .unwrap()
                .render(s).unwrap()
                .files
                .iter()
                .map(|f| f.contents.clone())
                .collect()
        };

        for backend in [
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::SysV,
            Backend::WindowsService,
        ] {
            let text = all_files(&spec, backend);
            assert!(
                text.contains("NOT HONOURED"),
                "{backend:?}: an unrenderable ceiling must be VISIBLE, not silent:\n{text}"
            );
            assert!(
                text.contains("17179869184") && text.contains("25769803776"),
                "{backend:?}: the note must name BOTH numbers or nobody can act on it:\n{text}"
            );
            // THE COUNTERWEIGHT: nothing declared ⇒ no note, so every existing script,
            // service dir and .wxs is unchanged.
            let untouched = all_files(&plain, backend);
            assert!(
                !untouched.contains("NOT HONOURED") && !untouched.contains("skidbladnir:"),
                "{backend:?}: nothing declared, nothing to warn about:\n{untouched}"
            );
        }

        // …and the backends that DO have a route must never emit the note: claiming a
        // gap you do not have is the same dishonesty from the other side.
        for backend in [
            Backend::SystemdSystem,
            Backend::SystemdUser,
            Backend::OpenRc,
            Backend::Oci,
            Backend::Quadlet,
            Backend::Kubernetes,
        ] {
            let text = all_files(&spec, backend);
            assert!(
                !text.contains("NOT HONOURED"),
                "{backend:?} has a real route and must render it, not excuse itself:\n{text}"
            );
        }
    }

    /// **Every backend that cannot react to an OOM kill must SAY so — and Quadlet,
    /// which can, must render it.**
    ///
    /// The ceiling's fan-out one axis over, with one deliberate difference in the
    /// seam: `Memory=` goes on Quadlet's `[Container]` side (podman must bound the
    /// container's cgroup), while `OOMPolicy=` goes on the `[Service]` side, because
    /// the reaction is a decision the service MANAGER makes about a unit and podman
    /// has no equivalent flag — the same reason `Restart=` is already a `[Service]`
    /// key here instead of `--restart`.
    ///
    /// OpenRC is asserted in the NOT-HONOURED set on purpose, and that is a judgement
    /// rather than a limitation: `rc_cgroup_settings` really can write
    /// `memory.oom.group 1`, which is exactly what `OOMPolicy=kill` is defined as, so
    /// a third of this axis is reachable. Rendering only that third would make
    /// `continue` and `stop` silently mean something else, so the note names the
    /// by-hand route and claims nothing.
    ///
    /// RED direction: drop any one `oom_not_honoured_note` call and that backend
    /// renders a service whose declared reaction has vanished without a word; drop the
    /// Quadlet block and a container service inherits the box's `DefaultOOMPolicy=`
    /// while the spec says otherwise.
    #[test]
    fn every_backend_without_an_oom_route_names_the_reaction_it_cannot_honour() {
        let spec = ServiceSpec::new(
            "dwarves-server",
            "the forge",
            "/usr/local/bin/dwarves-server",
        )
        .user("dwarves")
        .oom_policy(OomPolicy::Continue);
        let plain = ServiceSpec::new(
            "dwarves-server",
            "the forge",
            "/usr/local/bin/dwarves-server",
        )
        .user("dwarves");

        let all_files = |s: &ServiceSpec, b: Backend| -> String {
            renderer_for(b)
                .unwrap()
                .render(s).unwrap()
                .files
                .iter()
                .map(|f| f.contents.clone())
                .collect()
        };

        for backend in [
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::SysV,
            Backend::WindowsService,
            Backend::OpenRc,
        ] {
            let text = all_files(&spec, backend);
            assert!(
                text.contains("NOT HONOURED") && text.contains("OOMPolicy=continue"),
                "{backend:?}: an unrenderable reaction must be VISIBLE, and must quote \
                 what was declared:\n{text}"
            );
            assert!(
                text.contains("KEEPS RUNNING"),
                "{backend:?}: the note has to say what the reaction MEANS, or an operator \
                 cannot reproduce it by hand:\n{text}"
            );
            // THE COUNTERWEIGHT: nothing declared ⇒ not one word added.
            let untouched = all_files(&plain, backend);
            assert!(
                !untouched.contains("NOT HONOURED") && !untouched.contains("skidbladnir:"),
                "{backend:?}: nothing declared, nothing to warn about:\n{untouched}"
            );
        }

        // Quadlet HAS the directive — through the `[Service]` pass-through, not on the
        // container — so it must render it and must not excuse itself.
        let quadlet = all_files(&spec, Backend::Quadlet);
        assert!(
            quadlet.contains("OOMPolicy=continue\n") && !quadlet.contains("NOT HONOURED"),
            "quadlet passes [Service] keys straight through to the generated unit:\n{quadlet}"
        );
        assert!(
            !all_files(&plain, Backend::Quadlet).contains("OOMPolicy"),
            "…and nothing declared still renders a byte-identical .container file"
        );
        // Both systemd scopes render the real directive.
        for backend in [Backend::SystemdSystem, Backend::SystemdUser] {
            let text = all_files(&spec, backend);
            assert!(
                text.contains("OOMPolicy=continue\n"),
                "{backend:?} has the directive natively:\n{text}"
            );
        }
    }

    /// **Every backend that cannot rate-limit restarts must SAY so — and Quadlet,
    /// which can, must render it.**
    ///
    /// The note carries the DERIVED OUTCOME ("give up after N attempts and STAY
    /// DOWN"), not just the two numbers, because an operator granting this on another
    /// init system needs the behaviour — reproducing `12` and `5` onto a supervisor
    /// with different arithmetic gets the opposite result.
    ///
    /// dinit and OpenRC are in the NOT-HONOURED set as a JUDGEMENT, not a limitation:
    /// both are believed to have a real structural twin, and neither is rendered
    /// because no openrc/dinit/runit/s6 binary or man page exists on the box this was
    /// written on, and a mis-spelled variable in either is SILENTLY IGNORED — a limit
    /// that looks granted and does nothing, which is worse than a note. Same ruling as
    /// the OpenRC OOM partial.
    ///
    /// RED direction: drop any one `start_limit_not_honoured_note` and that backend
    /// renders a service whose declared give-up point has vanished.
    #[test]
    fn every_backend_without_a_rate_limiter_names_the_budget_it_cannot_honour() {
        let spec = ServiceSpec::new("dwarves-server", "the forge", "/usr/local/bin/d")
            .user("dwarves")
            .restart_sec(2)
            .start_limit(StartLimit::give_up_after(5, 2).unwrap());
        let plain =
            ServiceSpec::new("dwarves-server", "the forge", "/usr/local/bin/d").user("dwarves");

        let all_files = |s: &ServiceSpec, b: Backend| -> String {
            renderer_for(b)
                .unwrap()
                .render(s).unwrap()
                .files
                .iter()
                .map(|f| f.contents.clone())
                .collect()
        };

        for backend in [
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::SysV,
            Backend::WindowsService,
            Backend::OpenRc,
        ] {
            let text = all_files(&spec, backend);
            assert!(
                text.contains("NOT HONOURED"),
                "{backend:?}: an unrenderable budget must be VISIBLE:\n{text}"
            );
            assert!(
                text.contains("StartLimitIntervalSec=12") && text.contains("StartLimitBurst=5"),
                "{backend:?}: the note must quote what was declared:\n{text}"
            );
            assert!(
                text.contains("give up after 5 attempts") && text.contains("STAY DOWN"),
                "{backend:?}: …and the OUTCOME, not just the arithmetic:\n{text}"
            );
            // THE COUNTERWEIGHT.
            let untouched = all_files(&plain, backend);
            assert!(
                !untouched.contains("NOT HONOURED") && !untouched.contains("skidbladnir:"),
                "{backend:?}: nothing declared, nothing to warn about:\n{untouched}"
            );
        }

        // Quadlet renders the real directive, on the [Unit] side this time.
        let quadlet = all_files(&spec, Backend::Quadlet);
        assert!(
            quadlet.contains("StartLimitIntervalSec=12\n")
                && quadlet.contains("StartLimitBurst=5\n")
                && !quadlet.contains("NOT HONOURED"),
            "quadlet passes [Unit] keys through to the generated unit:\n{quadlet}"
        );
        assert!(
            !all_files(&plain, Backend::Quadlet).contains("StartLimit"),
            "…and nothing declared still renders a byte-identical .container file"
        );
        // A `never_give_up` note must read as the OPPOSITE intent, not as a budget.
        let forever = all_files(
            &ServiceSpec::new("d", "d", "/bin/d").start_limit(StartLimit::never_give_up()),
            Backend::Runit,
        );
        assert!(
            forever.contains("RETRY FOREVER") && !forever.contains("give up after"),
            "an explicit no-limit must not be described as a budget:\n{forever}"
        );
    }

    /// The backend registry is honest: implemented backends resolve to a renderer,
    /// declared-but-unrendered ones resolve to `None` (never a fake), and the slug
    /// axis covers the whole tree for the coverage matrix.
    #[test]
    fn registry_resolves_only_implemented_backends() {
        assert!(renderer_for(Backend::SystemdSystem).is_some());
        assert!(renderer_for(Backend::SystemdUser).is_some());
        assert!(renderer_for(Backend::OpenRc).is_some());
        assert!(renderer_for(Backend::SysV).is_some());
        assert!(renderer_for(Backend::Runit).is_some());
        assert!(renderer_for(Backend::S6Rc).is_some());
        assert!(renderer_for(Backend::Dinit).is_some());
        assert!(renderer_for(Backend::Kubernetes).is_some());
        assert!(renderer_for(Backend::Oci).is_some());
        assert!(renderer_for(Backend::Quadlet).is_some());
        assert!(renderer_for(Backend::WindowsService).is_some());
        // Full coverage: EVERY declared backend now resolves to a renderer.
        assert!(
            Backend::all().iter().all(|b| renderer_for(*b).is_some()),
            "11/11 render"
        );
        // Every backend has a stable, unique slug.
        let slugs: Vec<_> = Backend::all().iter().map(|b| b.slug()).collect();
        let uniq: std::collections::BTreeSet<_> = slugs.iter().collect();
        assert_eq!(slugs.len(), uniq.len(), "slugs are unique");
        assert_eq!(Backend::all().len(), 11);
    }

    /// **WHICH service the kernel picks — the axis that would have saved gunnar.**
    ///
    /// The completion of the OOM story, and the only one of the four that is about the
    /// NEIGHBOURS. The ceiling bounds one service and the reaction decides its fate
    /// once reaped; neither says who gets reaped when the machine as a whole runs out.
    /// On 2026-08-20 that was `gunnar-service`, taken alongside the forge that caused
    /// the shortage, because every unit on the box sat at the same score.
    ///
    /// podman HAS the exact flag (`--oom-score-adj int  Tune the host's OOM
    /// preferences (-1000 to 1000)`, read off `podman run --help` on this box), so the
    /// OCI renderer honours it — the one OOM axis that argv backend can express.
    ///
    /// RED direction: drop the `spec.oom_score_adjust` block from `systemd_unit_scoped`
    /// and a declared bias vanishes while the unit still installs.
    #[test]
    fn the_kernels_choice_of_victim_is_declared_and_fans_out() {
        let spec = ServiceSpec::new("dwarves-server", "the forge", "/usr/local/bin/d")
            .user("dwarves")
            .oom_score_adjust(OomScoreAdjust::sacrifice_first());
        let plain =
            ServiceSpec::new("dwarves-server", "the forge", "/usr/local/bin/d").user("dwarves");

        // systemd renders the real directive, in [Service] beside the reaction.
        assert!(spec.render_systemd_unit().contains("OOMScoreAdjust=500\n"));
        assert!(
            !plain.render_systemd_unit().contains("OOMScoreAdjust"),
            "undeclared must stay byte-identical"
        );

        let all_files = |s: &ServiceSpec, b: Backend| -> String {
            renderer_for(b)
                .unwrap()
                .render(s).unwrap()
                .files
                .iter()
                .map(|f| f.contents.clone())
                .collect()
        };

        // podman: the verified flag, in argv — not a note.
        let oci = renderer_for(Backend::Oci).unwrap().render(&spec).unwrap();
        assert!(
            oci.start.contains(&"--oom-score-adj=500".to_string()),
            "podman has the exact flag: {:?}",
            oci.start
        );
        // Quadlet: the same flag through PodmanArgs, the container side of the seam.
        assert!(all_files(&spec, Backend::Quadlet).contains("PodmanArgs=--oom-score-adj=500\n"));

        // The five shell backends + Windows name it, and hand over the VERIFIED route.
        for backend in [
            Backend::Runit,
            Backend::S6Rc,
            Backend::Dinit,
            Backend::SysV,
            Backend::OpenRc,
            Backend::WindowsService,
        ] {
            let text = all_files(&spec, backend);
            assert!(
                text.contains("NOT HONOURED") && text.contains("OOMScoreAdjust=500"),
                "{backend:?} must name the declared bias:\n{text}"
            );
            assert!(
                text.contains("echo 500 > /proc/self/oom_score_adj"),
                "{backend:?}: hand over the by-hand COMMAND, not a description:\n{text}"
            );
            assert!(
                !all_files(&plain, backend).contains("skidbladnir:"),
                "{backend:?}: nothing declared, nothing to warn about"
            );
        }

        // The PROTECTIVE direction carries the measured privilege caution; the
        // sacrificial one must NOT, or the caution stops being read.
        let protected = ServiceSpec::new("gunnar-service", "g", "/bin/g")
            .oom_score_adjust(OomScoreAdjust::new(-500).unwrap());
        let note = all_files(&protected, Backend::Runit);
        assert!(
            note.contains("CAP_SYS_RESOURCE") && note.contains("-500 FAILED"),
            "a negative bias needs the capability, and the note must say so:\n{note}"
        );
        assert!(
            !all_files(&spec, Backend::Runit).contains("CAP_SYS_RESOURCE"),
            "raising is unprivileged — do not cry wolf on the common direction"
        );

        // Out of range is refused by name, quoting the value and the bounds.
        let refused = OomScoreAdjust::new(1001).expect_err("systemd rejects the whole unit");
        assert!(refused.to_string().contains("1001") && refused.to_string().contains("-1000"));
        assert!(OomScoreAdjust::new(-1000).is_ok() && OomScoreAdjust::new(1000).is_ok());
        assert!(OomScoreAdjust::new(-1001).is_err());
        assert!(OomScoreAdjust::sacrifice_first().value() > 0);
        assert!(!OomScoreAdjust::sacrifice_first().needs_privilege());
        assert!(OomScoreAdjust::new(-1).unwrap().needs_privilege());
    }

    /// Windows service renderer: ONE spec renders a real WiX `.wxs` service fragment
    /// (through the SHARED `msi::render_service_install` seam) — `<ServiceInstall>`
    /// with the account, delayed-auto start and a `restart` crash-recovery block, the
    /// env carried in the `ServiceConfig` — plus the `sc.exe` create/start/query/stop
    /// verbs (create carrying `binPath=`/`obj=`/`depend=`/`DisplayName=`). Every
    /// assertion is RED-when-broken (a wrong attr/flag/order fails the build).
    #[test]
    fn windows_service_renders_a_wix_fragment_and_sc_verbs() {
        let spec = holger();
        let wv = renderer_for(Backend::WindowsService).unwrap().render(&spec).unwrap();
        assert_eq!(wv.backend, Backend::WindowsService);

        // File: a standalone WiX `.wxs` fragment via the reused MSI seam.
        let wxs = wv
            .file_ending("holger.wxs")
            .expect("a .wxs service fragment");
        assert!(wxs.starts_with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
        assert!(wxs.contains("<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">"));
        assert!(wxs.contains("<Fragment Id=\"holgerService\">"));
        assert!(wxs.contains("<ServiceInstall"));
        assert!(wxs.contains("Name=\"holger\""));
        assert!(wxs.contains("DisplayName=\"Holger artifact repository\""));
        assert!(
            wxs.contains("Account=\"_holger\""),
            "user → the SCM account:\n{wxs}"
        );
        assert!(wxs.contains("Start=\"auto\""));
        assert!(wxs.contains("DelayedAutoStart=\"yes\""));
        // RestartPolicy::OnFailure (restart_sec default 2) ⇒ a crash-recovery block —
        // restart_sec IS expressible on Windows (unlike the OCI backend).
        assert!(
            wxs.contains("FirstFailureActionType=\"restart\""),
            "restart recovery:\n{wxs}"
        );
        assert!(
            wxs.contains("RestartServiceDelayInSeconds=\"2\""),
            "restart_sec → recovery delay:\n{wxs}"
        );
        // env is carried in the fragment (sc.exe cannot set it).
        assert!(
            wxs.contains("<!-- env: HOLGER_DATA=/var/lib/holger -->"),
            "env → ServiceConfig block:\n{wxs}"
        );
        assert!(wxs.contains("<ServiceControl"));
        assert_eq!(wv.files[0].path, "C:\\ProgramData\\holger\\holger.wxs");
        assert_eq!(wv.files[0].mode, 0o644);

        // Verbs: sc.exe create IS the enable (binPath=/obj=/depend=/DisplayName=).
        assert_eq!(
            wv.enable,
            vec![
                "sc.exe",
                "create",
                "holger",
                "binPath=",
                "/opt/holger/bin/holger serve --config /etc/holger/holger.toml",
                "start=",
                "auto",
                "obj=",
                "_holger",
                "depend=",
                "nornir-server",
                "DisplayName=",
                "Holger artifact repository",
            ]
        );
        assert_eq!(wv.start, vec!["sc.exe", "start", "holger"]);
        assert_eq!(wv.status, vec!["sc.exe", "query", "holger"]);
        assert_eq!(wv.stop, vec!["sc.exe", "stop", "holger"]);
    }
}