From 4c6b1ff701b921bc510561b2eb3dc0e8500d0ba8 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 15 Aug 2026 11:35:18 +0200 Subject: [PATCH] feat(swarm-nats): JetStream, with the per-account grant the server needs Retention so a reader can ask what a hive last said without anyone keeping a second copy. The container is ephemeral = false, so the store survives a restart with no bind mount, and losing it degrades to the true answer (nothing known) rather than a stale one. The per-account grant is the part that is easy to miss: the server option gives the SERVER JetStream, and an account gets it only from its own. Measured against a running 2.14.1 with this exact two-account shape - global-only makes kv operations fail 10039 while the server starts cleanly and logs Starting JetStream, so no rendering check can see it. Deliberately not granted to the callout account: the responder mints credentials and has no business holding stream state. --- nix/host-modules/swarm-nats.nix | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/nix/host-modules/swarm-nats.nix b/nix/host-modules/swarm-nats.nix index 519b1bbb..bdadea39 100644 --- a/nix/host-modules/swarm-nats.nix +++ b/nix/host-modules/swarm-nats.nix @@ -276,6 +276,18 @@ in services.nats = { enable = true; + # Retention, so a reader can ask "what did this hive last + # say?" without anyone keeping a second copy. The upstream + # option also wires `settings.jetstream.store_dir = dataDir`; + # the container is `ephemeral = false`, so that survives a + # restart with no bind mount. + # + # ⚠️ Losing the store is not a correctness problem here: a + # reader then sees nothing for every hive, which is the true + # answer until each one publishes again. It degrades to + # honesty rather than to a stale "healthy". + jetstream = true; + serverName = "swarm-nats"; port = cfg.port; settings = { @@ -307,7 +319,25 @@ in # with a hole. So the only way to get a live server here is # a real key whose seed nobody but the responder holds. ${calloutAccount}.users = [ { nkey = cfg.calloutUserPublicKey; } ]; - ${clientAccount} = { }; + # ⚠️ `services.nats.jetstream = true` gives the SERVER + # JetStream; an account gets it only from its own grant. + # Measured against a running 2.14.1 with this exact + # two-account shape, because the failure is invisible to + # any config-rendering check: + # + # global jetstream only → `nats kv add` from this + # account fails `code=503 err_code=10039 jetstream + # not enabled for account`, while the server starts + # cleanly and logs "Starting JetStream" + # + this line → the same command succeeds + # + # The grant is per-account by design, and that is worth + # keeping: the callout account above deliberately does + # NOT get it. The responder mints credentials; it has no + # business holding stream state. + ${clientAccount} = { + jetstream = "enabled"; + }; }; authorization = {