# Subnet presence + endpoint-state display — design

> Source: `/grill-with-docs` 2026-06-27/28 (operator + doyle), off the SCELTOUIN incident — a remote
> node painted another node's DEAD endpoints as ONLINE. The grill unified the picker's status-square
> semantics across local and remote, and corrected the gossiped-status model against `resting.rs` +
> CONTEXT.md ("instance state" glossary). No ADR (a correctness + display fix; reversible). Lands as
> two `REQ-*`: `REQ-PRESENCE-LIVENESS-TRUTH` (gossip-status correctness) + `REQ-SUBNET-DISPLAY-PARITY`
> (unified palette + the gossip/render parity it needs).

## The instance-state model (canonical: CONTEXT.md "instance state")

active = the bare-`id` **routing target** (actively driven) · dormant = **warm** non-target sibling
(lost attention / driver detached; multi-instance routing differentiator) · suspended = **cold**,
resumable-on-wake, **node up** · offline = **node down**, never self-gossiped (remote-inferred via
epoch-lease eviction). `resting.rs` implements active/dormant/suspended; offline is registry-only.

## A. Gossip-status truth → `REQ-PRESENCE-LIVENESS-TRUTH`

`registryhost.rs:397-405` derives the per-endpoint gossiped `Status`. The `else` (not-bound-alive)
branch currently advertises `Dormant` — wrong: Dormant requires warm/running. Fix:
- **live-but-unbound** (a live broker session; `is_perch_alive` is bound-gated, so the `else` catches
  these — reuse the picker's `is_perch_unbound` signal) → `Active`/`Dormant` (still warm).
- **else** (cold, no live session — but the node is UP, since *this very daemon* is gossiping) →
  **`Suspended`**. **Never `Dormant`** (not warm) and **never `Offline`** (the node is up; a live node
  never self-gossips Offline — `Offline` is what a remote viewer infers when a node stops gossiping).

This alone removes the false-ONLINE. `dormant` keeps gossiping (routing/MRA needs it) but **renders
as its online flavor** (no distinct glyph — the dormant→suspended auto-suspend timer disambiguates
recency by the time it would matter).

## B. Unified display palette + gossip parity → `REQ-SUBNET-DISPLAY-PARITY`

**Local and remote rows render identically** (the subnet view shows everything the local view does).
Today `data.rs:293-294` reduces remote rows to plain green/gray because bound/unbound, controlled, and
harness-only aren't propagated. Fix: **gossip those per-`Instance` fields** and derive the full
`EpDisplay` for subnet rows the same as local.

**Fill = actionable:** *filled* = you can act now (rc-control if online; **wake** if suspended on a live
node) · *hollow* = you cannot (harness-only has no control seat; offline node is gone).

| Color | Fill | Glyph | State | Change from today |
|---|---|---|---|---|
| green | filled | ■ | online · bound · free (broker-controllable) | unchanged |
| blue | filled | ■ | online · **controlled** (`driven_by`; desc pane shows `controlled by <node>`) | + driver node |
| red | filled | ■ | online · **unbound** (controlled-or-not — rc-attachable, not msg-bound; controlled-ness shown via available options, not glyph) | **was** green-hollow; **absorbs** UnboundControlled |
| amber | hollow | ▢ | online · **harness-only** (no broker seat → can't rc) | **was** amber-*filled* |
| gray | filled | ■ | **suspended** (cold, node up — wakeable) | **NEW combo** |
| gray | hollow | ▢ | **offline** (node down) | now **remote-only** (a local node is always up) |

`EpDisplay` rework: **drop** `UnboundControlled`; `Unbound` → red-filled; `HarnessOnly` →
amber-hollow; **add** `Suspended` (gray-filled). Picker mapping: `Active|Dormant` → online flavor
(green/blue/red/amber by bound/controlled/harness), `Suspended` → gray-filled, `Offline` → gray-hollow.

**Gossip additions (per `Instance`, additive/forward-compat):** `bound`/unbound, `controlled` +
**driver node**, `harness_only`. (`Offline` is never gossiped — node-down is remote-inferred.)

**Residual tension (resolved):** under "hollow = cannot control," a *suspended* endpoint also can't be
controlled, yet it is gray-**filled** — reconciled by reading filled as "actionable = control **or
wake**" (suspended is wakeable on a live node; offline is not). `red` doubles as an "incomplete /
needs-attention" signal for the live-but-unbound state — intended.

## Cross-cutting

- CONTEXT.md "instance state" glossary already records the four-state model (done this session). The
  display palette is impl detail (lives here, not the glossary).
- Public docs (ride the build): gh-pages `terminal`/picker status-square legend + the new palette.
- Folds into the v0.17.0 milestone JIT (`V0.17.0-...`) as the presence/display waves.
