# Multi-subnet membership model

## Status

accepted (2026-06-01)

## Context

ADR-0003 anchored identity to the node; ADR-0005 made a subnet = one TOTP seed = one user's mutually-trusted nodes, with cross-*user* subnets explicitly deferred. Both were written under an unstated assumption: **one subnet per node.** New feature work (node link UX, "join existing vs create new subnet", per-subnet endpoint hiding, subnet event notifications) requires a node to belong to **multiple subnets at once**, and the "external subnet pairing request" notion presupposes that multiplicity already exists. This forces the one-subnet assumption into the open and asks how identity, addressing, the registry, sync, pairing, and visibility behave when a node is a member of several subnets.

The constraint: satisfy multi-subnet membership now **without** un-deferring cross-*user* subnets (a much larger stranger-authentication redesign), while shaping the model so cross-user can drop in later.

## Decision

**A node may be a member of multiple subnets simultaneously — for v1, only multiple subnets of the *same user*** (e.g. a `home` fleet and a `work` fleet). A node holds *N* TOTP seeds / trust-contexts instead of one. The per-subnet trust boundary is unchanged (within each subnet, all nodes are the one user's own). Cross-*user* membership stays deferred but the model is built multiplicity-ready.

Resolved sub-decisions:

1. **Node-global identity, per-subnet visibility.** One `ling` exists on a node (one mind, one `tracked/` context); the node advertises it into each subnet it belongs to, subject to visibility (below). The subnet registry is **per-subnet**; the same endpoint can appear in several registries. Bare-id uniqueness holds *within* a subnet via a **join-time collision check**.

2. **Qualified addressing + rippled rename.** Two combinable qualifiers — subnet-qualified `home:ling` and node-qualified `ling@hfenduleam`. When a bare id is ambiguous across visible subnets (two distinct endpoints sharing a name), resolution **refuses and forces qualification** rather than guessing. A new **`spt rename <id> <new_id>`** changes an endpoint's logical ID and ripples to all instances (registry, every perch, the `a-<id>` context branch), collision-checked against every target subnet and reconciled by the 6.5 precedence marker.

3. **Unified subnet-name link discovery.** The TOTP code is also the relay rendezvous selector; discovery+link takes **TOTP code + subnet-name** in *all* cases. "Create-new" **names the subnet at creation** (no node-name mode). The relay rendezvous token is `H(subnet-name ‖ TOTP-epoch)` (under the hood; the user enters the raw name + code) so the plaintext label isn't exposed on the public relay. Payload is E2E-encrypted regardless (R-NET-2). See the ADR-0005 amendment.

4. **Per-subnet code fetch gated behind OS elevation.** Retrieving a subnet's current code *from a node* requires Windows UAC / Linux root-or-equivalent, or an elevated agent endpoint; otherwise the user falls back to their own authenticator app. Narrows the multi-subnet exposure (mere CLI/agent presence no longer yields any subnet's join-code).

5. **Subnet-exclusive sync.** An endpoint's mind replicates only within its home subnet by default. Endpoint config carries a **subnet-membership list** for sync; the mind replicates across every listed subnet's nodes. Distinct from visibility: being addressable in a subnet does not imply replicating the mind there.

6. **Visibility = "excluded", not "unlisted".** Hidden in subnet S means **not advertised AND not routable** from S (a real boundary, ready for the cross-user seam), not merely absent from `list`. Hidden iff `S.hide_new_endpoints` (per-subnet, captured at join time) OR `E.default_hide_from_new_subnets` (per-endpoint), unless an explicit per-`(E,S)` override wins. Both defaults ship OFF (visible). **Visibility gates sync:** `hidden ⟹ not synced`.

**Cross-user seam (forward, not built):** the seed key generalizes from per-subnet to **per-(subnet, user)**; the pairing/code prompt becomes 2-stage (user → subnet), collapsing to 1-stage under v1's single implicit user. This yields node↔user attribution and single-operation force-unlink of a user's whole node-group for free. v1 stores seeds in a shape that already accommodates the (subnet, user) key.

## Consequences

- The subnet registry, pairing, code-fetch, and context-sync all gain a subnet dimension; "one user's nodes" generalizes to "one user's nodes per subnet."
- New v1 requirements to register: multi-subnet membership, qualified addressing, `rename`, endpoint visibility, elevation-gated code fetch. These land in M4 (networking + registry + pairing).
- Endpoint visibility introduces a per-`(endpoint, subnet)` ACL surface the registry must carry and the resolution/routing path must enforce (excluded = unroutable).
- The cross-user (b) future is now a **seam, not a rewrite**: seed key, 2-stage prompt, `from`-attributed notifs, and excluded-visibility boundaries are all shaped for it.
- Amends ADR-0005 (pairing ceremony) — see that ADR's amendment; builds on ADR-0003 (node-anchored identity).
