# 0057 — The serving registry is the primitive; served names are stable and disambiguated

Status: accepted (2026-09-06 WEBSERVE grill round 1, releases#272 / #248)

## Context

#248 offered two ways for a node to expose files: a dedicated served FOLDER per adapter or
entity (drop a file in, it is served — zero steps, serves everything in it), or a core
REGISTRY of individual entries added deliberately (`spt serve add <path>`, attachments
auto-registering on send). Files from different directories can share a filename, so whichever
primitive wins needs a rule for two `report.md`s.

## Decision

**The registry is the primitive.** A served folder is not a second mechanism: it is a registry
entry of type *dir*, declared by an adapter in its manifest, so one enumerable list always
answers "what is this node exposing" — an audit is one command (or one index page).

**Served names are stable and disambiguated by suffix.** When an entry's bare name
`<stem>.<ext>` is already taken on this node, the new entry is served as `<stem>~<n>.<ext>`
(the suffix sits before the extension so the file still opens as its type). Rules:

- the first registrant keeps the bare name; `n` increments per `(stem, ext)`;
- the assignment is persisted on the entry, so an entry's URL never changes for its life;
- a freed name is never reused for a different absolute path or kind; its former path
  may reclaim it (Amendment 2 below), so a stale link never opens a different resource;
- re-registering the same absolute path returns the existing entry, not a new suffix;
- a registrant may name the entry explicitly (`--as <name>`), subject to the same rule;
- disambiguation is per node — across nodes the URL's node prefix (ADR-0056) already separates
  names.

## Alternatives rejected

- **Folders as the primitive.** Zero-step, but exposure becomes a property of where a file
  happens to sit, and "what am I serving" has no single answer. Rejected: exposure must be a
  deliberate act with an enumerable record.
- **Content-hash paths as the canonical name.** Collision-proof, but unreadable in a chat line
  and unstable across an edit of a reference-served file. Kept out; a hash can be a secondary
  alias later without changing this decision.
- **Reusing freed names for another path or kind.** Shorter names over time, at the cost
  of a link that can change meaning. Rejected: a link must not lie.

## Consequences

- `spt serve list` (and the node index page) is the audit surface; agents read the URL from it
  rather than guessing a name.
- Attachments (ADR-0058) are registry entries, so message links inherit the same stability.
- The registry persists a counter per `(stem, ext)` and each assigned name's prior
  absolute path and kind; both are required to preserve a link's meaning.

## Amendment 1 — where an adapter's served root lives (doyle ruling, 2026-09-06, W0 stop-and-refer)

The grill named the served root's URL (`web_short_path`) but never its filesystem source, and
the adapter's install tree is not it — `manifest.toml`, `record.toml`, `strings/` are not
for serving, and an adapter must never expose its install root by inference.

- **The served root is core-owned:** `$SPT_HOME/adapters/<adapter>/web/`, a sibling of
  `strings/` in the per-adapter aux dir. Core creates it (empty) at activation, registers the
  *dir* entry, and removes the entry at deactivation (the directory and its files stay — they
  are the adapter's output, and an update or re-activation must not eat them). The adapter
  writes what it wants served into that directory at runtime; nothing else is ever served on
  its behalf.
- **It answers at the adapter facet.** `/<node>/a/<adapter>/…` (ADR-0056's reserved `a/`
  namespace) *is* this entry — the facet is the adapter's served root, and "adapter docs" are
  whatever the adapter puts there. `web_short_path = "cspt"` adds an **alias**
  `/<node>/cspt/…` to the same entry; it never creates a second entry or a second directory.
  Without `web_short_path` the facet still serves; with it, both names do.
- **The alias obeys the served-name rule above** (it is an explicit `--as`): a reserved facet
  name (`f`, `docs`, `a`, `m`, `bin`, `install`) is refused at activation with a diagnostic
  that names the field; a name another entry already holds is disambiguated `<name>~<n>`
  exactly as any other registrant, so an alias never silently takes over another's URL.
- Path discipline is the `strings/` rule: the served subtree is `web/` itself, no `..`, no
  absolute paths, symlinks out of the subtree are not followed.

This keeps exposure a deliberate act (the adapter writes a file into the one directory that
serves) with one enumerable answer (`spt serve list` shows the dir entry and its alias).

## Amendment 2 — a freed name may be re-taken by the path it last named

<!-- [doc->REQ-WEB-SERVING-REGISTRY] -->

Ruled by doyle, 2026-09-06, from the W0 reactivation smoke finding.

The never-reuse rule protects a link from changing **meaning**. A name whose last
registrant was absolute path P may be assigned again to a registration of P
(**same kind**), and only to P; any other path takes the next suffix as before.
The history therefore records `(name → last path)` rather than bare names,
with the prior kind retained to enforce the same-kind condition.

Consequently, an adapter's alias and served root survive deactivate/activate
and `adapter update` unchanged. A stale link answers *not found* or the same
file it always named, never a different one.

Acceptance: remove `report.md` and re-add the same file to reclaim `report.md`.
A different third file still receives `report~2.md`. Adapter deactivation and
reactivation preserve `reports`, rather than allocating `reports~1`.
