# 0056 — Every served URL is node-prefixed, local resources included

Status: accepted (2026-09-06 WEBSERVE grill round 1, releases#272 / #249)

## Context

WEBSERVE turns the loopback docs server into the node's general serving surface: registry
files, adapter docs, message views, and — across the subnet — the same resources of every
other node, proxied over Iroh by the local daemon. Two grammars were on the table: local
resources un-prefixed (`/cspt/file.md`) with a `/n/<node>/…` form only for remote ones, or
one grammar where every resource, local or not, lives under its owning node's name.

## Decision

**One URL grammar: `http://localhost:5474/<node-name>/…` for everything.** A resource on
HFENDULEAM is `/hfenduleam/…` on HFENDULEAM itself and on every other subnet machine; the
local daemon resolves the prefix and proxies when the owner is elsewhere. Bare
`http://localhost:5474/` redirects to `/<local-node>/`. The docs server's existing bare paths
stay reachable as a compatibility alias under the local node prefix.

**Grammar under the prefix (round 2, Q7):** one sub-namespace per facet, so a path says what
it is — `/<node>/f/<name>` registry files, `/<node>/docs/…` the docs site, `/<node>/a/<adapter>/…`
adapter docs, `/<node>/m/<short-id>` message views, `/<node>/bin/…` and `/<node>/install` the
bootstrap surface (LAN listener only, ADR-0059). `/<node>/` redirects to the docs facet
as ruled in Amendment 2 below; `spt serve list` is the exposure audit. A facet added
later needs a prefix ruling; in exchange no two facets can ever collide on a name.

**Scheme (round 2, Q11):** plain `http://` only. Every consumer — a browser, `curl`, an agent's
fetch — already speaks it; `spt fetch` accepts the http URL and a local `<node>/f/<name>`
shorthand it expands itself.

## Alternatives rejected

- **An `spt://` scheme.** Would need a handler registered on every OS and a client on every
  consumer, to buy nothing the http URL does not already carry. Rejected.
- **Un-prefixed local, prefixed remote.** Shorter local URLs, but a URL's meaning then depends
  on which machine it is pasted into: `/cspt/file.md` is one file here and a different one
  there. Rejected: a URL that cannot be pasted between subnet machines is not a link, it is a
  path.

## Consequences

- Every SPT URL is portable by construction; agents and humans exchange them without a
  "which node" rider, which is what #147's message links and #17's FILE_ACCESS_HELPER need.
- Local URLs are longer, and a node rename moves every URL under it — renames are already
  rare and identity-bearing; this adds one more reason to treat them so.
- The node prefix is the only cross-node disambiguator; per-node name collisions are handled
  inside the registry (ADR-0057), never by the grammar.

## Amendment 1 — router precedence when a node name collides with a docs compat path (doyle ruling, 2026-09-06, W0 stop-and-refer)

The docs site's bare paths (`/cli/…`, `/reference/…`, `/llms-full.txt`, …) stay reachable as a
compatibility alias. A subnet peer may be named like a docs section (`cli`), making
`/cli/…` both a node prefix and a bare docs path. Precedence at the router, first match wins:

1. `/` → 302 to `/<local-node>/`.
2. First segment is the **local node** → local facets (`f/`, `docs/`, `a/`, `m/`, `bin/`,
   `install`; unknown facet → 404 naming the facet).
3. First segment is a **known subnet peer** → that node's URL (502-with-node until the
   cross-node proxy lands; then proxied).
4. Otherwise → the **docs compat alias**, byte-true to the docs server's published surface
   (REQ-DOCS-LOCAL-SERVER); no page → the docs 404.

**A node name shadows the alias, never the reverse.** The node-prefixed URL is the one grammar
and must resolve on every machine; the alias exists for old links and every alias path has a
canonical twin `/<local-node>/docs/<path>` that never flips. Resolving the alias first would
make a peer's canonical URL unreachable from this node — exactly the "URL that only works on
one machine" this ADR forbids. The shadow is per section and only while such a peer is in the
subnet; the serving page documents it as the alias's one caveat and points at the canonical
form.

**The docs root files are never shadowed — by shape, not by admission policy.** A node name is
the OS hostname (`hostlabel::os_hostname` trims and rejects empty, nothing more; dotted
hostnames exist and there is no node-name mint or rename verb to refuse at). So the llms
contract's verbatim root surface (`/llms.txt`, `/llms-full.txt`, `/manifest.schema.json`, the
root `*.md` twins) is protected structurally: a node-prefixed URL always has a segment after the
prefix (`/<node>/…`; a bare `/<node>` 302s to `/<node>/`), while a root file is a single leaf
segment. Rule 2.5, between local and peer: a **single-segment path that exactly names a docs
root file** serves that file. A hostname equal to a root filename loses only that one leaf URL
on other machines; its prefixed grammar `/<node>/…` is untouched.

**Reserved facet names are not node names, and W0 adds no admission policy.** There is no
mint to refuse at, and a daemon must not refuse to boot on the shape of its hostname. A node
whose hostname is `docs`, `f`, `a`, `m`, `bin` or `install` is reported loud at daemon startup
(and by the peer that learns of it at join), and the router never treats a top-level facet
token as a node — such a node is unreachable by prefix from other machines until renamed at
the OS. Dotted hostnames are grandfathered as ordinary node names. Node-name admission
(shape rules, a rename verb) is an intake candidate, not a WEBSERVE rider.

## Amendment 2 — node roots open docs (2026-09-11, releases#301 / #304)

<!-- [doc->REQ-WEB-URL-NODE-PREFIX] -->
The operator supersedes the original browser registry index: `/<node>/` answers
HTTP 302 to `/<node>/docs/`, preserving the query string. This applies to the
local node and remote nodes through the same owner-side router. The redirect
uses a path-only Location, keeping the browser on its actual local listener.
Bare `/` and bare `/<node>` retain their existing redirects to `/<node>/`.

The registry index and its `?json` twin are retired, not moved to another
browser path. `spt serve list` and `spt serve list --json` remain the exposure
audit. Docs compatibility aliases, facet precedence, root-file exceptions,
directory redirects, and cross-node authorization are unchanged.
