# 0061 — Messages get a node-scoped short-ID that is also their served URL

Status: accepted (2026-09-06 WEBSERVE grill round 2, releases#272 / #147 / #148)

## Context

#147 wants a message to be referable — in a chat line, from the CLI, and as a link that opens
a rendered view of it. The choice was between a globally unique id (long, or coordinated) and
a short id that is unique per node and rides the node prefix for global meaning.

## Decision

**A message's short-ID is 8 characters of base32 over a content digest of the message, scoped
to the node that holds it.** The digest is minted here, for this purpose: no pre-W2 message
hash exists to re-use, so "its existing hash" (as this sentence first read) named something
that was never there. The preimage is length-prefixed over `(from_id, body, created_at, salt)`,
so no two different messages can serialize to one preimage by moving a delimiter into a body. Its served URL is `/<node>/m/<short-id>` (ADR-0056, Q7 grammar), so the
same token is both the thing you say in chat and the thing you click. The CLI reads and answers
it: `spt msg show <id>` renders one message with its attachment links; `spt send --reply-to
<id>` carries the parent id in the envelope so adapters may render a thread. The id appears in
the delivery envelope and in the `MSG_IN` / `MSG_OUT` IO events, so an adapter never has to
derive it.

## Supersession: this amends ADR-0020's `--reply-to` removal

ADR-0020 removed `spt send --reply-to`, and REQ-SEND-REPLYTO-REMOVE hard-removed it in v0.16.0.
**That removal stands; this decision re-uses the spelling for a different thing.** What ADR-0020
killed was a TARGET FALLBACK — a bare `spt send --reply-to <sender>` delivering with no positional
— together with a `REPLIED` label that had no wire effect. Both stay dead: `target` remains a
required positional, the flag still cannot stand in for it, and no label is stamped.

What returns is a flag with a WIRE EFFECT: it carries the parent's short-ID as an additive envelope
attribute, which is the thing ADR-0020 correctly observed did not exist at the time. The
requirement text is amended by replacement to the semantics it actually killed, and its unit cell is
repinned to those surviving properties rather than to the absence of the string.

## Alternatives rejected

- **Globally unique ids.** Either long enough to be unsayable or coordinated across nodes.
  Rejected: the node prefix already exists and already makes URLs global.
- **Sequential per-node counters.** Short, but they leak volume and collide across a store
  rebuild. Rejected in favour of a hash-derived token that is stable across rebuilds.

## Consequences

- #148's outbound ledger keys on the same id; nothing mints a second identifier later.
- Eight base32 characters over a hash is not collision-proof in principle; a collision on one
  node is detectable at mint time and resolved by lengthening that one id, which the grammar
  permits since the id is opaque to readers.
