diff --git a/docs/DESIGN-signed-records-manifest.md b/docs/DESIGN-signed-records-manifest.md index a6c6fc6..7adf47a 100644 --- a/docs/DESIGN-signed-records-manifest.md +++ b/docs/DESIGN-signed-records-manifest.md @@ -1,6 +1,6 @@ # Signed records manifest (BNS records v1) -**Status:** shipped v1 — gateway `GET/POST /api/records/` and +**Status:** shipped v2 — gateway `GET/POST /api/records/` and verified `GET /api/dns/` live on `silentmode.st/navigate.st`; owner-tracking in the indexer live; portal DNS editor + wallet-side `signMessage` + `signRecordsManifest` shipped. Native DNS server @@ -62,11 +62,70 @@ under the fixed filename `_records.json`. Shape: - `updated_at`: ISO-8601 UTC. Human-readable only — verification is on `seq`. - `dns.*`: DNS record slots. `null` or `[]` means "not set". `A`/`AAAA`/`TXT` are string arrays. `MX` is objects `{pref, host}`. `NS` is a string array - of host names. Additional slots (`CAA`, `SRV`) reserved for v2. + of host names. `CAA` and `SRV` are accepted too. +- `hosts`: optional, v2 — per-subdomain routing and records. See below. - `meta`: unstructured; ignored by DNS resolvers, useful for portal UI. - `sig`: BCH-style message signature (same format `libauth.signMessage` produces) over the canonical bytes of the envelope (see below). +### v2 — per-host entries (`hosts`) + +v1 described the apex and nothing else. A name's subdomains served the folder +their labels spelled out, with no way to change it and no way to give one its +own records. v2 adds one optional block: + +```json +"hosts": { + "www": { "path": "" }, + "shop": { "path": "store/v2" }, + "blog": { "u": "https://blog.example.com" }, + "old": { "block": true }, + "mail": { "dns": { "A": ["5.6.7.8"] } }, + "*": { "u": "https://example.x/" } +} +``` + +**Keys** are the labels ABOVE the registered name, in DNS order: `www`, +`a.b`, or `*` for everything not named explicitly. Exact match wins over the +wildcard, so a named host opts out of a blanket rule. The apex is never a key +— its content comes from the chain, and letting an off-chain blob redirect +the main site would move the name's trust boundary without the owner spending +a transaction. At most 200 hosts, at most 8 labels deep. + +**Fields**, any combination: +- `path` — the folder inside the bucket this host serves, replacing the + derived one. `""` means the bucket root, which is how `www` is made to + mirror the apex. Relative, no leading `/`, no `..`. +- `u` — redirect the host to this URL (302). http(s) only, ≤2048 chars. +- `block` — this host returns 404 even if a folder of that name exists. +- `dns` — the same slots as the apex `dns`, for this host. + +**Precedence within an entry:** `block` > `u` > `path`. The most restrictive +intent wins, so a host that is somehow both blocked and redirected stays +blocked. + +**The default when no entry matches** is unchanged and needs no manifest at +all: the labels above the name, REVERSED, become the folder path — DNS reads +most specific first, a path reads least specific first, so +`chipnet.explorer.aegis.x` and `aegis.x/explorer/chipnet/` are the same page. + +**DNS inheritance.** A host with its own `dns` block owns its record set +outright — `www` does not inherit the apex's `MX`, the way classic DNS works. +A host without one inherits the apex, the way everything else on BNS does. +`GET /api/dns/` applies this and reports which host it answered for. + +**Compatibility.** A v1 manifest stays valid forever and needs no +republishing; it simply has no `hosts`. A manifest that fails to verify is +treated exactly like no manifest — the name keeps serving from its chain +records rather than failing shut. + +**Where it is enforced.** `lib/manifest.js` (`verifyManifest`, +`validateHosts`) is the only definition of validity, shared by the read path, +the write path and `serve()`. `lib/record-picker.js` (`hostOverride`, +`hostAction`, `subFolderFor`) decides which entry governs a request and which +folder it resolves to; it carries the parity invariant with the inline copy in +`TheseusNavigator/main.js`. Tests: `test/manifest-hosts.test.mjs`. + ### Canonicalisation and signing The signable bytes are the JSON serialisation of the manifest **without the @@ -145,10 +204,21 @@ existing content model already works. - Resolver support in Theseus + Ariadne staged behind a flag until the portal-side has been in production for a week. -### What ships in v2 +### Shipped in v2 + +- `hosts`: per-subdomain routing (`path`, `u`, `block`) and per-host `dns`. +- CAA/SRV records. +- Portal editor for both halves, on one signed manifest and one publish. + +### Still open - Direct-to-Sia write flow for users who hold their own S3 credentials. -- Owner-controlled manifest that overrides `h`/`s3`/etc. entirely. -- CAA/SRV/HTTPS records. +- Owner-controlled manifest that overrides `h`/`s3`/etc. for the apex. +- HTTPS/SVCB records. +- The public authoritative DNS bridge. Until it exists the `dns` half of the + manifest is write-only: the daemon's own server (`lib/dns-server.js`) + answers `A` with the local resolver address and NODATA for everything else, + and the only consumer of a manifest `A` record is Theseus, as a last resort + when a name carries no content record. - Federated resolver caches for `seq` (mitigates the case where two writes race and each thinks its `seq` is winning).