# How names resolve

The owner's direction (2026-10-21, through sysx), and how sysn builds toward it. The prior art is
the root's [docs/research/names.md](../../docs/research/names.md). The features named here are
sysn's unless they say otherwise.

## The five points

1. **Programs never see names.** The shell, as the person's agent, asks netd for a connection to
   `example.com!80`, and the job is lent the connection's data, never the name and never a resolver
   it can ask freely. DNS cannot carry a job's data out, and what a job may reach stays in the
   manifest, where `explain` and `why` show it. This builds on 021's `(talk dns name)` need and on
   `/net/cs`.
2. **A name is a hint; a key is the identity.** `/net/cs` reads a local table first, as Plan 9's
   `ndb` does: the system's own names (sysx.crops.no, the mini), each with its address and, later,
   its Noise key, so they are pinned with no trust in DNS. Other names go through DNS and are
   marked unauthenticated. There is no DNSSEC root (research/names.md, "What not to take"). The
   key half, and the Noise session, wait on the Noise key design, sysk 104 with sysc, after sysk
   089, 103, 092, 079, 080 and 076. The table of names and addresses waits on no one.
3. **Proven against hostile answers.** Done for what is read (sysn 010, 021): no read outside the
   message, names that cannot loop or overrun (RFC 9267), an answer taken only when it matches the
   query's id, port and question. Still to be stated as properties: the resolver always ends, is
   deterministic, and never follows a CNAME or delegation loop.
4. **A cache in netd**, each entry expiring by the time the caller hands in. The stack reads no
   clock (sysn's rule): netd gives the time, and the stack never keeps one of its own.
5. **Later, an encrypted path to the upstream resolver**: Noise to a resolver the owner runs, once
   sysc's Noise is done. Not DNS over TLS: there is no TLS stack, and none is wanted.

## Where each point stands

| Point | Today | Next |
| --- | --- | --- |
| 1 | Holds since 022. `tell example.com!80` is lent a tcp conversation's data; the name never reaches the job. `host` (021) is lent one dns conversation whose name the shell wrote, so it cannot ask another. | Since sysn 025 the translation is netd's `/net/cs` (below). |
| 2 | `/net/cs` in netd (025), the manifest's table first (026): `(from table)` or `(from dns)`. | Keys for the table's names, after sysk 104. |
| 3 | What is read is proven (010, 021), and since sysn 028 the rest is stated where a change cannot lose it: `dns-records`'s invariants say each record moves the scan on by 11 bytes at least, so it ends within the message whatever the count says, and that a CNAME's target is always behind the scan, so a chain that loops ends in none; every function is pure, so the same bytes give the same answer; a test keeps `net/dns.sysp` free of unbounded loops. That it ends is not proven: sysp proves no termination, so it rests on the bounds, which are proven, and on that test, which a later loop must pass. The resolver is a stub, so it never follows a delegation. | Done, but for termination, which is tested. |
| 4 | Since sysn 027: six names (eight until 029), each its address and when it expires by netd's clock, the TTL capped at a day; `(from cache)`. | A negative cache (RFC 2308), if lookups of missing names cost. |
| 5 | Not started. | Waits on sysc's Noise. |

## The connection server

Plan 9's `dial` writes `tcp!sysx.crops.no!22` to `/net/cs` and reads back each way to reach it, a
clone file and an address: `/net/tcp/clone 104.20.23.154!22`. `ndb/cs` looks the name up in `ndb`
first, then asks `/net/dns`. sysn 022 put that translation in the shell, the only dialer; sysn 025 moved it.
Points 2 and 4 put it in netd: the table and the cache are state that must live where the resolver
does and outlast one command, and netd keeps them in memory it already owns. So `/net/cs` becomes a
file of netd's, in Plan 9's shape. The shell writes `tcp!NAME!PORT` to it and reads back the clone
and address to dial, `(dial /net/tcp/clone 104.20.23.154!80 (from dns))`. The answer says where
it came from, `(from table)` or `(from dns)`, so an address from DNS is visibly
unauthenticated. cs asks with a dns record of its own, so a dial by name never takes `host`'s
conversation. No job is lent `/net/cs`: only the shell dials.

The local table's source is the manifest (sysn 026, agreed with sysk), each entry with named
fields so a key joins later as one more: `(names (sysx.crops.no (ip 104.20.23.154)) (mini (ip
10.0.2.2)))`, then `(mini (ip 10.0.2.2) (key ...))`. `tools/wire.rkt` wires it into netd, which
reads no files, so Plan 9's `ndb` file does not carry over; it refuses a name given twice, an
address that does not parse, and names in a manifest without netd. The table says what the
system's own names mean, not what may be reached: DNS still answers for other names, and reach is
the talk need's. sysk-b's printer of the manifest (sysk 108) is to say it in those words.
