# WEBSERVE (#272) — intake JIT (wave map + REQ ids)

Authored by doyle 2026-09-06 ~10:30Z on the operator's greenlight ("shared understanding
confirmed. proceed with WEBSERVE"). Design record: ADR-0056..0061 + CONTEXT.md `## Web
serving` on branch `docs/webserve-272-grill` (@a87bd42a, off main; lands as the first thin
PR after v0.67.1). Grill rounds: `WEBSERVE-272-GRILL-ROUND-1.md`, `-ROUND-2.md`; board record
#272 comments 5558410252 + 5558560333. Milestone version: **v0.68.0**.

## Members (Q6 ruling)

Ride: #248 (registry / generalized fileserving), #249 (cross-node webserving), #246
(endpoint-to-endpoint transfer = attachments), #265 (adapter docs hosting), #268 (changelog in
docs), #147 (message short-ID + CLI + web URI), #266 (bootstrap hosting, per Q5).
Closing rider: #17 (FILE_ACCESS_HELPER now-signal; EVAL, blocked-by #246 — flips with W2).
Deferred: #148 (outbound ledger — additive, nothing blocks on it; DETACHED, stays BACKLOG).
Shelved: #271 (09:41Z).

## Standing rules for every lane

- Base off `origin/main` AFTER v0.67.1 lands (deployah's land call); thin lanes, ff-only,
  golden per ADR-0050 as a milestone batch at the end (deployah runs it; I assemble the head).
- Worktree under `.worktrees/ws272-<wave>`, own pool, claim from inside.
- `traceable-reqs.toml` FIRST (mint the wave's REQ ids below, no `"` in titles), then
  `traceable-reqs check` (exit 2 = did not parse).
- Gate (mine, per wave): treqs → workspace-bins prebuild → `xtask check` (docs drift +
  spacerun) → clippy → `nextest --no-fail-fast` filtered to the wave's crates + the e2e
  binaries it touches → same legs on kitsubito → exit FILES read → pick-audit at land.
  **IR-76 pre-flight**: three-arm golden check before any local nextest/build on hfenduleam.
- Docs are the contract adapters build from: every wave lands its docs-site page(s) in the
  same commit (harness-contract + `reference.md` via `--help`), and CONTEXT.md terms are
  already ratified — do not re-word them, cite them.
- Vocabulary is CONTEXT.md's: served resource, node-prefixed URL, serving registry, served
  root, served name, attachment, fetch, bootstrap surface, message short-ID.

## W0 — the serving substrate (todlando) — #248 + the local half of #249

- **REQ-WEB-URL-NODE-PREFIX** (ADR-0056; doc, impl, unit, int): the 5474 server answers
  `/<local-node>/…` for every facet; bare `/` → 302 to `/<local-node>/`; the docs server's
  bare paths stay as a compat alias (byte-true, REQ-DOCS-LOCAL-SERVER's surface contract
  untouched); sub-namespaces `f/`, `docs/`, `a/`, `m/`, `bin/`+`install` are reserved at the
  router even where the facet is not built yet (404 with the facet name, never a registry
  lookup); `/<local-node>/` renders the index page (the registry, path → URL, HTML + a
  `?json` twin for agents). A request for `/<other-node>/…` answers 502-with-node until W1.
- **REQ-WEB-SERVING-REGISTRY** (ADR-0057; doc, impl, unit, int): `$SPT_HOME/serve/registry`
  store (spt-store owns format + read path; daemon is the single writer); entry = id, kind
  (file | dir | attachment), absolute source path (file/dir) or snapshot path (attachment),
  served name, registered-at, ttl (attachments only), origin (endpoint id). Served-name rule
  exactly as ADR-0057: bare name to the first registrant, `<stem>~<n>.<ext>` after, the
  counter per `(stem, ext)` persisted, freed names never reused, same absolute path returns
  the same entry, `--as` honoured under the same rule. Verbs: `spt serve add <path> [--as
  <name>]`, `spt serve rm <name|id>`, `spt serve list [--json]` (path → URL). Manifest:
  `web_short_path` → a dir entry registered at adapter activation, removed at deactivation.
  Reference-served (file/dir) resolves at request time (edit visible, deleted → 404).
- **REQ-WEB-ACCESS-SURFACE** (ADR-0060; doc, impl, unit): mint the `WEB` row in the surface
  table (`default_on: true` within subnet — the DISCOVER shape; non-attributable until a
  sender stamp exists); the `--help` "Control surfaces" section grows it by existing
  (table-composed); the gate site is W1's stream family — in W0 the row exists and is
  rule-addressable, nothing dials it yet.
- Docs: new page `docs-site/src/serving/overview.md` (URL grammar, registry, served names,
  index page) + `reference.md` regen; api.md untouched in W0.
- Field acceptance (mine): browser opens `http://localhost:5474/hfenduleam/`, index lists a
  file I `serve add`; two files named `report.md` from two dirs list as `report.md` and
  `report~1.md`; removing the first and re-adding a third gives `report~2.md`.

## Register riders (hertz) — composed at the v0.67.1 close / #272 intake sweep, 2026-09-06

- **Floor trio, ONE workflow lane** (thin PR, off the golden path, lands BEFORE the WEBSERVE
  golden head so that head runs under it): IR-46 (weigh the disk-floor-instant remedies —
  the `golden.yml` touch at `04e32c8c` fired the trigger unweighed), IR-59 log-the-floor half
  (record free space beside every floor check), IR-73 (the 8 literal-first floor sites in
  `ci.yml`/`release.yml`). Entry text in `docs/INFRA-REGISTER.md` is the spec.
- **IR-66 residual**: the two remaining first-chunk-needle members — two one-line test edits.
- **IR-12 residual** rides W3's drift-gate riders (already hertz's).
- IR-59's log-the-floor line ALSO goes into IR-76's three-arm pre-flight on W0's driver
  template (todlando).

## W1 — cross-node (todlando) — #249

- **REQ-WEB-CROSS-NODE-PROXY** (doc, impl, unit, int): a request for `/<node>/…` where
  `<node>` is a subnet peer opens an Iroh stream (new `StreamFamily::Web`), the OWNER runs
  `access_check(…, surface::WEB, …)` under the handshake-proven origin (the `serve_xfer`
  shape), streams the response back; the local daemon proxies with NO cache; Range headers
  pass through; owner unreachable → 502 naming the node; owner refuses → 403 naming the
  surface. Unknown node name → 404 naming it.
- Int: twohost — kitsubito fetches a file registered on hfenduleam through kitsubito's own
  5474; a WEB deny rule on hfenduleam turns it into 403; a Range request returns 206.
- Docs: `serving/cross-node.md` + the access-surface docs page gains the WEB row text.

## W2 — attachments + message identity (todlando) — #246 + #147, then #17 flips

- **REQ-WEB-ATTACHMENT-PULL** (ADR-0058; doc, impl, unit, int): `spt send --attachment <path>`
  snapshots the bytes into `$SPT_HOME/serve/snapshots/`, registers an `attachment` entry
  (ttl default 30 d, `--ttl <dur>`), and the envelope carries the node-prefixed URL
  (`attachments: [{name, url, bytes}]`, additive key); the daemon pulse reaps expired
  snapshots and logs the count. Nothing is pushed to the receiver.
- **REQ-WEB-FETCH-VERB** (doc, impl, unit, int): `spt fetch <url|<node>/f/<name>> [dest]`
  writes the bytes to `dest` (default: basename in cwd, refusing to clobber without `--force`),
  prints the path; exit codes: 0 / 3 refused (403) / 1 failed.
- **REQ-MSG-SHORT-ID** (ADR-0061; doc, impl, unit, int): 8-char base32 over the message hash,
  node-scoped, minted at commit, collision → lengthen that id; carried in the delivery
  envelope, in `MSG_IN`/`MSG_OUT` io-event rows, and rendered by `/<node>/m/<id>` (HTML +
  `?json`); `spt msg show <id>` renders one message with its attachment URLs; `spt send
  --reply-to <id>` carries the parent id.
- **#17 rider** (WIDENED by operator directive 2026-09-06 ~10:56Z, ADR-0058 Amendment 1):
  `FILE_ACCESS_HELPER` now-signal category. Two triggers: (a) a delivered message carries
  attachments → the signal emits the exact `spt fetch <url>` line(s); (b) a USER's message
  quotes a filepath that exists on the user's node → core AUTO-REGISTERS that path as a
  served resource with a **24h TTL**, **audience = the one endpoint that received the
  message** (a new per-entry field; enforced where the fetch origin is proven — the W1
  cross-node stream; loopback is the trusted machine), and the signal hands that endpoint
  the `spt fetch <url>` line. Delta discipline: once per (message, path). Guards: existence
  check on the user's node, absolute/`~` paths only, cap per message (5), entries enumerable
  in `spt serve list` with origin = the message short-ID. Same-node user+agent: no
  registration, the signal says the path is local. REMOTE user (the #17 case) needs a
  cross-node "register on my behalf" request authorized by the user's attach session —
  rides W1's stream family, so this rider lands AFTER W1 (W2 already does). Operator RULED
  reference-served (live file/dir entry + ttl + audience, never a snapshot; ADR-0058 Amendment 1).
  Flip #17 EVAL → attached-and-WIP at W2 dispatch (operator ruled it a closing rider, Q6).
- Docs: `serving/attachments.md`, api.md now-signal category list, `messaging` page for
  short-ID + reply-to.

## W3 — facets (todlando; hertz takes the drift-gate test riders) — #265 + #268 + #266

- **REQ-WEB-ADAPTER-DOCS-FACET** (#265; doc, impl, unit, int): `/<node>/a/<adapter>/…`
  serves an installed adapter's docs. **MEASURE FIRST**: where an adapter's docs live after
  `adapter add` (archive `docs/`? `strings/`? nothing today?) — stop-and-refer with the
  measurement if no adapter ships docs; the facet may need a manifest key (`docs_dir`).
- **REQ-DOCS-CHANGELOG-PAGE** (#268; doc, impl): the docs site carries CHANGELOG.md as a page
  (`docs/changelog.md`, generated in the docs build so the drift gate holds it).
- **REQ-WEB-LAN-BOOTSTRAP-LISTENER** (ADR-0059; doc, impl, unit, int): `spt serve lan
  --bootstrap` starts a SECOND hyper listener on `0.0.0.0:5470` (config/env override) whose
  router knows ONLY `/bin/<platform>/spt[.exe]` + `/install` (a one-liner page); off by
  default, `spt serve lan --stop`; prints the LAN URL; now-signal carries `LAN-EXPOSED
  <url>` while up (a NowSpec category — ride the ratified render order, append at END);
  REQ-DOCS-LOCAL-SERVER's never-0.0.0.0 stays true for 5474 (unit asserts the 5474 bind is
  loopback while 5470 is up). **MEASURE FIRST**: where the current platform binaries are on
  disk (`$SPT_HOME/bin`? the update-set stage?) — the listener serves the INSTALLED binary
  set, never builds one.
- Int (kitsubito → hfenduleam over LAN): `curl http://hfenduleam:5470/bin/…` = bytes match the
  installed exe's sha256; `curl …:5470/hfenduleam/docs/` = 404; `curl …:5474/` from kitsubito
  = connection refused (loopback proof).

## Close riders

- **XFER retirement** (ADR-0060; rider on #246's close, W2 or W3 tail): delete
  `StreamFamily::Xfer`, `serve_xfer`, `fetch_file`/`push_file`, their tests, the `XFER`
  surface row; `ACCESS_SURFACE_RETIRED:<id>:<surface>` reported at store load for any rule
  naming it; docs + `--help` regen; CONTEXT.md's access-surface entry already states it.
  REQ id: **REQ-XFER-RETIRED** (impl, unit).
- **#148** stays detached BACKLOG (deferred by ruling; keys on REQ-MSG-SHORT-ID when built).
- **IR-76** driver pre-flight is a rider on W0's driver template (todlando authors the
  battery; the three-arm leg ships in it).

## Sequencing (todlando's queue)

1. v0.67.1 land call (deployah) → rebase #276 → thin PR → my gate → land.
2. #277 (iolog seq reset) thin lane → my gate → land. (#278, #279 are small; fold #279 into
   #277's lane if the tree is open, #278 stays its own thin lane, any order.)
3. W0 → W1 → W2 (+#17) → W3 (+ XFER retirement) → golden head to deployah → v0.68.0.
   Start W0's registry design + store work NOW in `.worktrees/ws272-w0` off origin/main and
   rebase at (1); nothing in W0 depends on #276/#277.
