Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Viewing access rules and posture

Access views are roster-first: instead of one flat dump of every rule, each view lists the access entities — the subnets, machines, and endpoints that rules or modes actually name — grouped by type, each with its rule count and (for a subnet or this machine) its mode. The granular rule list is readable only per named entity, because reading a posture is a comparison per entity, not a scroll.

Per-endpoint: spt endpoint access

spt endpoint access             # every ruled endpoint's roster
spt endpoint access wanda       # one endpoint's roster

A roster line per ruled entity, grouped subnets → machines → endpoints:

access entities ruled for 'wanda':
  bignet - mode: closed (DISCOVER open (default)) - 2 access rules
  quietnet - mode: open
  HFENDULEAM - mode: open
  flynn@kitsubito (bignet, quietnet) - 1 access rule
  • A subnet appears when rules name it or when its captured mode decides the endpoint’s fallback posture — a mode source is part of the picture even with zero explicit rules.
  • A closed mode names what it does not close. DISCOVER — being found — is on by default, and no blanket posture reaches it, so a closed table renders closed (DISCOVER open (default)). Where someone has written an explicit open for it the line reads DISCOVER open (pinned) instead: a pin outlives a change of the default and is a different fact from the default itself. The only things that close it are a deny row naming the surface (per subject) and, node-wide, spt api access-node-surface-mode DISCOVER closed from this node’s engine room.
  • WEB — a served file fetched by another subnet member — is also on by default within the subnet: registering a file is already a deliberate act of exposure, so a blanket closed posture does not close it. A deny row naming WEB (per subject) or the node-wide surface mode does. The owning node checks the row under the fetching node’s handshake-proven identity and answers 403 with a body naming WEB. The two sides of a WEB rule: its origin matches the fetching node or that node’s subnet (no sender endpoint exists to name yet); its subject is the endpoint that registered the file, or the node itself for the index and the docs.
  • This machine (colored like it is in spt endpoint list) appears with its own mode when one is set.
  • A ruled endpoint resolves to id@machine (shared subnets) where this node can see it, and renders bare where it cannot — being unresolvable does not hide the rule.
  • Entities with no rules for the target are omitted entirely.

Drill down to the granular rules of one named entity:

spt endpoint access wanda --subnet-rules bignet
spt endpoint access --endpoint-rules flynn
spt endpoint access --node-rules <pubkey-hex>

Each prints the matching rules as a markdown table (subject, surfaces, deciding tier, verdict) — the same renderer the engine room’s session briefing uses, so the grid you are briefed with and the grid you can ask for again never drift. Markdown because a briefing is read wherever an agent relays it: a space-aligned grid is reflowed into a run-on by a chat surface, and a table that stops being a table in transit is not one. Cells stay padded, so the same render is still column-aligned in a terminal.

A node subject reads as the node’s name, not its pubkey:

| SCOPE    | TIER          | SUBJECT        | SURFACE | ORIGIN | DECISION | FROM   |
| -------- | ------------- | -------------- | ------- | ------ | -------- | ------ |
| node     | node-rule     | subnet:bignet  | DISCOVER| -      | allow    | manual |
| sparrow  | endpoint-rule | node:ENLYZEAM  | MSG     | -      | deny     | manual |

A node this machine cannot name — no roster entry, no advertised label — keeps its full pubkey hex instead. Never blank, never an error, and never shortened: a truncated hash is the thing that was unreadable in the first place, and the full one is at least pasteable into a rule. The name is a rendering only; --json keeps the full hex as the row’s subject and carries the resolved name beside it, so anything keying on the pubkey is unaffected.

One naming caveat: an endpoint literally named allow, revoke, or open parses as the editing subcommand, so it has no positional path — reach its rules through the drill-down flags instead.

Node tier: spt node access

spt node access

The node-tier roster: the entities the node-wide rules name, this machine’s own mode (with per-surface exceptions, e.g. closed (MSG open, DISCOVER open (default))), and the captured subnet modes — the tier every hosted endpoint falls through to when nothing more specific matches. This is also the tier the engine room’s per-surface verb writes, so a node that has closed being found reads closed (DISCOVER closed) here.

Governing this node’s own traffic

A rule whose subject is this node’s own id governs traffic authored here — one agent on this machine reaching another. It is an ordinary rule at the tiers you already have: no separate tier, no separate command, no schema of its own.

You never type your own 64-character pubkey for it. --node takes three spellings and they are tried in this order:

spt node access deny --surfaces MSG --node self
spt endpoint access allow ling --surfaces MSG --node KITSUBITO
spt endpoint access allow ling --surfaces MSG --node 6e4abfa9aa08...
spellingmeans
a 64-hex pubkeythat node, exactly — tried first, so nothing can shadow it
selfthis node; a reserved word, so a machine labelled self cannot take it
a node namethe node carrying that name, as spt subnet status --nodes shows it

The rule stores the hex, whichever you typed. A name is presentation and a lease that can move to another machine; the identity is the key. If a name is carried by two machines the command refuses and names both, rather than picking one and writing a rule about a node you did not mean. In every view the stored subject renders like any other node subject — node:<name> when the name resolves, the full hex otherwise — never as self: self is something you type, not something a rule is.

Written at node scope it governs intra-node traffic for every endpoint hosted here. Written on one endpoint it governs what other same-node endpoints may do to that endpoint, and endpoint rules are consulted before node-wide ones, so a per-endpoint allow re-opens one target through a node-wide deny.

Two things it deliberately does not do. Modes still abstain for local traffic — a closed posture is about who may reach this machine from outside it, and closing the node has never meant stopping the agents on it talking to each other; only a rule that names the local origin speaks about local traffic. And a correlated reply is still admitted with no rule of its own, own-node deny included, so an agent you messaged can always answer you.

On a node with no identity yet, --node self refuses rather than minting one. Writing an access rule must not create the identity that rule is about. Give the node an identity with spt subnet create <name> or spt subnet join first.

Coverage, as it stands today. A rule naming a specific sender endpoint is matched from the sender the delivery path supplies, and the notify path supplies it in a different shape from spt send and spt ring — so a sender-subject rule written for ling does not match traffic arriving by notify. Rules whose subject is a node (including self) are unaffected. Tracked as releases#215.

Subnet modes: spt subnet status

A subnet is a subject of rules and a source of modes — never a rule-holding target, so there is no spt subnet access. Its mode facts live on its own view:

spt subnet status bignet

states three things, in words even when absent:

  • declared by subnet — the posture the subnet declares, as this node knows it;
  • captured here — what this node actually enforces as its fallback (captured at join or at mint, changed only through the engine room’s refresh);
  • PENDING — a declared change this node has seen but not adopted, with when it was seen. Your posture does not change until the engine room adopts it.

The bare spt subnet status — no subnet named — carries the first two of those facts for every subnet it lists, as a MODE and a CAPTURED HERE column. Both views state an absence in words rather than leaving a cell blank: a subnet that declares closed while this node captured nothing is enforcing open, and that is exactly the state a membership-only table would have shown as an ordinary healthy row.

The node that mints a subnet captures its own declaration

Creating a subnet is the consenting act, so spt subnet create records the mode it just declared into this node’s access store — the same capture a joining node performs at join. Without it a --closed subnet is asymmetric by construction: every joiner enforces the posture while the minting node, having nothing captured, falls through to implicit open on every surface.

A subnet minted by an older build captured nothing, and nothing back-fills it silently: a subnet record carries no “minted here” marker, so declared present and captured absent cannot be told apart from joined before subnet modes existed — and retro-capturing the second would close a running mesh without anyone asking for it. Heal such a node explicitly, from its engine room:

spt api access-refresh bignet

The create ceremony prints that remedy itself, so the state is discoverable from the command that produces it.

No remote rule-read

Rules are node-sovereign. There is no verb, on any surface, that reads another node’s access rules — viewing a node’s rules means running the CLI on that node. What a subnet declares travels as an advisory; what a node enforces is its own, and only its own console will tell you.