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
closedmode names what it does not close.DISCOVER— being found — is on by default, and no blanket posture reaches it, so a closed table rendersclosed (DISCOVER open (default)). Where someone has written an explicit open for it the line readsDISCOVER 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 closedfrom 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 namingWEB(per subject) or the node-wide surface mode does. The owning node checks the row under the fetching node’s handshake-proven identity and answers403with a body namingWEB. The two sides of aWEBrule: 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...
| spelling | means |
|---|---|
| a 64-hex pubkey | that node, exactly — tried first, so nothing can shadow it |
self | this node; a reserved word, so a machine labelled self cannot take it |
| a node name | the 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 sendandspt ring— so a sender-subject rule written forlingdoes not match traffic arriving by notify. Rules whose subject is a node (includingself) 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.