---
id: PRD-0006
title: "M5 — Hardening: Invites, Visibility, Per-Channel Perms, Multi-Home"
status: done
owner: "Aaron Roney"
created: 2026-07-01
updated: 2026-07-02

depends_on:
- PRD-0005

principles:
- "Multi-home is the killer use case: one session, act in a private ops channel while notify on public ones (DESIGN §8/§9)."
- "Levels resolve per (server, channel); overrides are live-changeable (DESIGN §9)."
- "Simple stays simple: no store-and-forward, no HA — hardening means correctness under churn, not new subsystems."

references:
- name: "Conclave design (multi-home §8, permission resolution §9, presence/reconnect §10/§16, visibility/invites §6)"
  url: "docs/DESIGN.md"

acceptance_tests:
- id: uat-001
  name: "Invite tokens honor single-use and expiry; a redeemed/expired token is refused"
  command: cargo nextest run -E 'test(/invite/)'
  uat_status: verified
- id: uat-002
  name: "Visibility gating: unlisted joinable only by exact name; private names never appear in discovery for non-members"
  command: cargo nextest run -E 'test(/visibility/)'
  uat_status: verified
- id: uat-003
  name: "Per-(server,channel) permission override applied live (conclave perm set) without reconnect"
  command: cargo nextest run -E 'test(/perm_live/)'
  uat_status: verified
- id: uat-004
  name: "Multi-home: one session joined to two servers routes channel messages and whispers to the correct (server, target)"
  command: cargo nextest run -E 'test(/e2e_multihome/)'
  uat_status: verified
- id: uat-005
  name: "Full gate is green (cargo make uat)"
  command: cargo make uat
  uat_status: verified

tasks:
- id: T-001
  title: "Invite tokens: single-use + expiry lifecycle"
  priority: 1
  status: done
  notes: "uses_remaining / expires_at enforcement on redeem; revoke. Redeem adds username to ACL (idempotent for already-members)."
- id: T-002
  title: "Visibility tier enforcement end-to-end"
  priority: 1
  status: done
  notes: "public listed+open; unlisted not listed but joinable by exact name; private ACL/token-gated and never leaked in list_channels/who to non-members."
- id: T-003
  title: "Per-(server,channel) permission overrides + live perm set"
  priority: 1
  status: done
  notes: "Resolution order per-(server,channel)/whisper override -> machine default. conclave perm set applies live (hot reload) and takes effect on the next inbound message; conclave perm show prints the resolved table."
- id: T-004
  title: "Multi-home targeting UX"
  priority: 1
  status: done
  notes: "Outbound requires (server, channel) / (server, target-path) when multi-homed; sensible default to the sole/last connection otherwise. Per-server username/machine label resolution (DESIGN §5). Clear errors on ambiguous targets."
- id: T-005
  title: "Reconnect/presence hardening + edge cases"
  priority: 2
  status: done
  notes: "Backoff tuning, half-open reap edge cases, bridge process-death vs connection-drop (document re-/join requirement), whisper-to-offline error clarity."
- id: T-006
  title: "Docs polish: README Mermaid protocol diagrams + cargo make uat gate"
  priority: 2
  status: done
  notes: "Fill the README Protocol section with Mermaid sequence diagrams (auth handshake, channel fan-out, whisper, permission-relay). Wire cargo make uat as the one-true gate across the suite."
---

# Summary

Harden the fabric for real use: full invite-token and visibility semantics, live per-`(server,
channel)` permission overrides, the multi-home targeting UX that enables the flagship "act in a
private channel, notify on public ones" workflow, and reconnect/presence edge-case hardening.

# Problem

M2–M4 deliver the happy path; this milestone makes it robust and delivers the multi-home experience
that motivated choosing full multi-user from the start. It also fills in the documented protocol
diagrams and the aggregate `uat` gate.

# Goals

1. Complete invite (single-use/expiry) and visibility (public/unlisted/private) semantics.
2. Live per-`(server, channel)` permission overrides via `conclave perm set`.
3. Multi-home targeting UX with clear disambiguation.
4. Reconnect/presence hardening + the README protocol diagrams + a green `cargo make uat`.

# Technical Approach

Layer the remaining membership/visibility rules onto the M2 server, wire the local permission
config for live per-channel overrides in the M3 bridge, and finish the outbound targeting so one
session can address multiple servers unambiguously. Harden the reconnect/heartbeat paths against
churn. Document the handshake and routing as Mermaid sequence diagrams and promote `cargo make uat`
to the canonical full gate.

# Assumptions

- M2–M4 are complete and green; this is additive hardening, not new subsystems.

# Constraints

- No store-and-forward, no HA (DESIGN §19). Hardening = correctness under churn.

# References to Code

- `docs/DESIGN.md` §6 (channels/invites/visibility), §8 (multi-home), §9 (levels), §10/§16 (presence/errors).

# Non-Goals (MVP)

- Everything in DESIGN §19 (E2E, recovery, hide, flood control, HA, alternative sinks, store-and-forward).

# History
(Entries appended during implementation go below this line.)

## 2026-07-02 — M5 executed (T-001…T-006) — v1 complete

The final hardening milestone landed with TDD; `cargo make uat` is green (121 tests). All five UATs
pass: `test(/invite/)` (9), `test(/visibility/)` (4), `test(/perm_live/)` (2), `test(/e2e_multihome/)`
(1), and `cargo make uat` (the full gate).

Most of the substance was already implemented in M2–M4; M5 proved it under churn, added the one
missing capability (live `set_perm`), and finished the docs — as the "hardening, not new subsystems"
principle intended.

- **Invites (T-001):** the single-use / multi-use / expiry / revoke lifecycle (implemented in M2's
  `hub.redeem_invite`) is now covered by hub-level unit tests — a spent, expired, revoked, or
  wrong-channel token is refused; a valid redeem subscribes and adds the user to the ACL.
- **Visibility (T-002):** end-to-end tests confirm unlisted is joinable by exact name but never
  listed in discovery, and private is neither joinable without an ACL entry/token nor leaked to
  non-members.
- **Live perms (T-003):** a new **`set_perm` MCP tool** changes the local autonomy level for a
  channel / whisper scope / machine default **live** — it takes effect on the very next inbound
  message with no reconnect (mute drops, converse permits emit). `conclave perm set` remains the
  on-disk path for the next bridge start.
- **Multi-home (T-004):** the flagship e2e — one bridge, one key, two servers — routes channel
  messages and whispers to the correct `(server, target)`; the per-server registration resolves the
  local path. `Bridge::spawn_all` connects to every configured server.
- **Hardening + docs (T-005/T-006):** reconnect/reap semantics documented (DEVELOPMENT.md); README
  protocol Mermaid diagrams updated to the shipped frames (Register/Auth + ServerInfo, sender-
  exclusion, the MCP inject + permission relay); `cargo make uat` is the canonical full gate.

No protocol changes this milestone (version stays 1). This closes the v1 build (M0–M5); §19 (E2E,
recovery, hide, flood control, HA, store-and-forward) remains explicitly out of scope.
