# Cross-node serving

A node-prefixed URL means the same thing on every machine in the subnet. Paste
`http://localhost:5474/kitsubito/f/report.md` into a browser on `hfenduleam`
and the local daemon fetches the file from `kitsubito` and answers with it.
Nothing is copied: the owner serves the bytes, and the requesting node keeps
none of them.

<!-- [doc->REQ-WEB-CROSS-NODE-PROXY] -->
## How a peer's URL is answered

The local listener resolves the first path segment. When it names a **known
subnet member** (a node in this node's roster, by its advertised label), the
daemon opens one stream to that node and relays the owner's answer:

- The owner resolves the path through the same router its own listener uses,
  so a registered file is read at request time. An edit is visible on the next
  fetch; a removed entry or a deleted source answers the owner's own 404.
- `HEAD` returns the owner's headers with no body. `Content-Type` is the owner's.
- A `Range` header is forwarded as-is. The owner answers `206 Partial Content`
  with `Content-Range`; a range past the end answers `416` naming the length.
  The same grammar applies to a local file: `bytes=a-b`, `bytes=a-`, `bytes=-n`.
- The body streams back in bounded chunks as the owner sends it. A large file
  never needs its size in memory on either node, and nothing is written under
  the requesting node's `$SPT_HOME`.
- The peer's index page and its `?json` twin are fetched the same way:
  `/<peer>/` lists what that node serves.

Reserved facets stay router-first on the requesting node: `/<peer>/f/` with no
name, and the unbuilt `m/`, `bin/` and `install` facets, are answered locally
and open no stream.

## Who decides: the owner

The owning node runs its `WEB` access check before serving anything. The
origin it checks is the requesting node's identity as proven by the transport
handshake, never a value in the request. The subject is the endpoint that
registered the entry, so a rule written against that endpoint governs its
served files wherever they are fetched from. See
[Viewing access rules and posture](../networking/access-viewing.md) for the
`WEB` row.

## What each status means

| Status | Body starts with | Meaning |
|---|---|---|
| `200` / `206` | the owner's bytes | Served by the owner. |
| `403` | `ACCESS_DENIED: WEB:` | The owner's access rules refuse this node. The body names the surface, not a sender: `WEB` carries no sender identity yet. |
| `404` | `NOT_FOUND: served resource <name>` | The owner has no such served name (or its source is gone). |
| `404` | `NO_DOCS_LANDED:` or the docs page 404 | The first segment is not a known subnet member, so the request fell through to the local docs compatibility surface. |
| `502` | `NODE_UNAVAILABLE: <node>:` | The owner is a known member but could not be reached, dropped the stream, or never answered within the deadline. The body names the node and says why. |

A `502` arrives within a bounded time. The dial and each read are held to
the daemon's peer deadlines, so an owner that is offline, or that runs a
version without cross-node serving, produces an answer rather than a hang.

## Trying it

On the owning node:

```text
spt serve add ./report.md
spt serve list
```

On any other node in the subnet, using the owner's node name:

```text
curl -s http://localhost:5474/<owner>/f/report.md
curl -sI http://localhost:5474/<owner>/f/report.md
curl -s -r 0-3 http://localhost:5474/<owner>/f/report.md
```
