Skip to content

docs: an edge site is a Thing, not a leaf_nodes record - #6

Merged
skeeeon merged 2 commits into
mainfrom
fix/docs-truth-pass
Sep 16, 2026
Merged

skeeeon merged 2 commits into
mainfrom
fix/docs-truth-pass

Conversation

@skeeeon

@skeeeon skeeeon commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Follows stone-age-io/platform, which dropped the leaf_nodes collection and the leaf-sync binary, and stone-age-io/agent, which absorbed the latter.

leaf-nodes.md is rewritten, not edited

It used to describe a collection, a per-site binary and a config mirror. All three are gone. The filename stays so inbound links survive.

What replaces it is one route — GET /api/me/leaf-config — plus the reasons the alternatives were rejected, which is the part worth having in docs:

  • no gateway flag, because thing_types already says what a device is and a second marker is a second thing to get wrong;
  • no stored domain, because it was a second copy of the code that could disagree with it, and when it did the site simply stopped appearing;
  • no config mirror, because nothing consumed the mirrored rows.

Two things were rescued from the old cmd/leaf-sync README before it was deleted, since they existed nowhere else:

  • The two operator-mode directives a generated nats-leaf.conf must carry — a leaf remote's account key, and the $SYS account JWT in resolver_preload, without which the server dies before JetStream starts — and the distinction between preloading the $SYS account JWT, which grants nothing, and a $SYS user credential, which is never served.
  • One writer per bucket for the digital twin, with the number that made the case: ~170k writes to a single key in 300ms when both ends wrote one bucket. It does not pick a loser on a conflict, it oscillates.

New in §7: site connectivity comes from $SYS.REQ.ACCOUNT.PING.CONNZ, asked by the console over its own in-account connection, with the publish-DENY-beats-ALLOW trap written down beside it. That one surfaces as a bare request timeout with the real reason arriving only on the async error handler, so it is the failure someone re-tightening a NATS Role would otherwise spend a day on.

The rest of the pass

authorization.md §6 rewritten as "Gateways and the Edge", keeping the point that mattered: the route gates on nothing, because everything it serves is public trust material or the caller's own credential — so the blast radius of a leaked edge credential is a fixed list of named fields rather than a consequence of rules that change for unrelated reasons. The leaf_nodes rows come out of the role table.

agent.md loses the "not to be confused with leaf-sync" note and gains the four edge capabilities. It also corrects two claims that were already stale before this change: it said the Agent "neither fetches nor manages Nebula" and "does not manage the Nebula interface". Both have been untrue since the overlay feature landed.

health-metrics.md §5 rewritten for the agent's agent_edge_* series and its two checks (not four), with the alert expressions updated to match.

configuration.md, connectivity.md, index.md, getting-started.md, operations.md, platform-ui-entities.md, overview.md, platform-layers.md updated for the platform: config block, auth.type: "platform", and platform.sync_interval. operations.md's failure-mode and upgrade-order tables now say that restarting the agent on a site bounces the leaf server only if nats.server_config is set.

ADR 0002 is annotated, not edited

It cites leaf_nodes as the precedent for freezing code. That is what was argued at the time, and an ADR is a record of that argument — rewriting it would erase the decision it exists to document. A status note points at things.code as today's precedent instead.

Verification

All internal links resolve across 21 files — target file exists, and every #anchor matches a heading slugified the way mkdocs (python-markdown's toc extension) does. That check caught two broken anchors, one of them written during this pass.


Merge with platform: stone-age-io/platform#11 and agent: stone-age-io/agent#4 — the three land together.

🤖 Generated with Claude Code

Follows the platform change that dropped the `leaf_nodes` collection and the
`leaf-sync` binary, and the agent change that absorbed the latter.

leaf-nodes.md is rewritten rather than edited. It used to describe a
collection, a per-site agent and a config mirror; all three are gone, and
what replaces them is one route (GET /api/me/leaf-config) plus the reasons
the alternatives were rejected -- no gateway flag because thing_types
already says what a device is, no stored `domain` because it was a second
copy of the code that could disagree with it, no config mirror because
nothing consumed the mirrored rows.

Two things worth keeping that were only in the old cmd/leaf-sync README:

- The two operator-mode directives a generated nats-leaf.conf must carry (a
  leaf remote's `account` key, and the $SYS account JWT in resolver_preload,
  without which the server dies before JetStream starts) -- and the
  distinction between preloading the $SYS *account* JWT, which grants
  nothing, and a $SYS *user* credential, which is never served.
- One writer per bucket for the digital twin, with the number that made the
  case: ~170k writes to a single key in 300ms when both ends wrote one
  bucket.

New in §7 of leaf-nodes.md: site connectivity comes from
$SYS.REQ.ACCOUNT.PING.CONNZ, asked by the console over its own in-account
connection, with the publish-DENY-beats-ALLOW trap written down next to it --
that one surfaces as a bare request timeout, so it is the failure someone
re-tightening a NATS Role would otherwise spend a day on.

authorization.md §6 is rewritten for the same reason and keeps the point
that mattered: the route gates on nothing, because everything it serves is
public trust material or the caller's own credential, and the blast radius
of a leaked edge credential is a fixed list of named fields rather than a
consequence of rules that change for unrelated reasons.

agent.md drops the "not to be confused with leaf-sync" note and gains the
four edge capabilities; it also corrects two claims that had gone stale
before this change -- the Agent does manage Nebula now, in-process.

ADR 0002 is annotated rather than edited: it cites leaf_nodes as the
precedent for freezing `code`, and an ADR records what was argued at the
time. The note points at things.code as today's precedent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two errors, both of which would have been read as fact by someone configuring
a deployment.

**"No listening ports."** agent.md said the Agent initiates all connections
outbound and listens on nothing. That was true once. It is not true now: the
platform block talks outbound HTTPS, a site gateway hosts a nats-server that
local devices connect in to, and /ready + /metrics bind 127.0.0.1:9100 on
every agent by default.

Replaced with the property that is actually true and is the one a device on a
customer network depends on -- nothing can instruct an Agent except over the
NATS connection it dialed itself, so no port forward is ever needed to manage
one. New §6 carries a direction-and-port table for whoever writes the firewall
rules, including the detail that an ordinary Nebula host binds an ephemeral
port (pb-nebula writes listen.port: 0 for anything that is not a lighthouse or
a relay), so there is nothing to open for the overlay either.

**observability.addr defaults to 127.0.0.1:9100, not "".** configuration.md
and health-metrics.md both documented the default as empty and the feature as
off by default; the agent's defaults.go sets the address, so the listener is up
unless you set the key empty. Both corrected, and both now note that 9100 is
node_exporter's own port on Linux and FreeBSD -- these same docs offer that
exporter as an alternative metrics source, and a bind failure there is logged
rather than fatal, so the symptom is a scrape target that quietly never came
up. health-metrics.md's scrape example already used 9101 while the prose said
9100; the example was right and now says why.

Also: agent.md listed local health as one of four gateway capabilities. There
are three. observability is on for every Agent -- the reason to answer locally
has nothing to do with running a leaf node -- and nebula.enabled is per-device
in the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@skeeeon
skeeeon merged commit 4e9339e into main Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant