diff --git a/docs/briefs/2026-07-20-caps-emulation-alignment.md b/docs/briefs/2026-07-20-caps-emulation-alignment.md
index 4b945fa..4dbed75 100644
--- a/docs/briefs/2026-07-20-caps-emulation-alignment.md
+++ b/docs/briefs/2026-07-20-caps-emulation-alignment.md
@@ -62,6 +62,79 @@ que la vision interdit. **Fermer ces voies = le cœur de P1.**
5. **Révocation = re-key** émulé : invalider l'ancien token, re-livrer un nouveau
aux autorisés restants ; **non-rétroactif**.
+## ~~Périmètre élargi : le WriteCap (= membership)~~ — RETIRÉ (2026-07-21)
+
+**Cette section était fausse et est conservée barrée comme garde-fou.** Elle
+importait une notion de *membership* lue dans l'**état courant** de `nextgraph-rs`
+(`AddMember`, `PermissionV0`, `member_pubkey`) et la promouvait en **forme cible**.
+Or (a) ces types sont un **échafaudage inerte** au runtime — `verify_sig` /
+`verify_perm` ne sont appelés que dans des tests unitaires, les `Repo` sont
+construits avec `members: HashMap::new()` ; et (b) le modèle cible **n'a pas de
+notion d'appartenance du tout** : uniquement des **clés et des URLs**, symétrique
+et asymétrique. Une forme en `member`/`role`/`permission` est donc exactement la
+**mauvaise forme** que ce brief existe pour empêcher.
+
+**La leçon de méthode, qui vaut plus que la section retirée** : lire l'état
+courant de NextGraph pour en **déduire** la forme cible est une erreur — l'état
+courant contient de l'inachevé qu'il ne faut pas figer dans le polyfill. Le source
+sert à vérifier un **mécanisme** existant, jamais à inférer une **intention**.
+
+Contenu erroné conservé ci-dessous à titre de trace :
+
+
+Section retirée
+
+**Pourquoi c'est ici et pas ailleurs.** Le brief était d'abord centré ReadCap ; un
+constat adverse a montré qu'il **ne compose pas** avec son consommateur : le brief
+Festipod « inscriptions par Set » a besoin de **dédupliquer** les participations
+(un user = une participation par événement), et la seule base non-applicative
+disponible est la **signature d'auteur du commit** — donc un primitif d'**écriture**,
+pas de lecture. Un polyfill qui n'expose que la forme du ReadCap laisse le
+consommateur inventer sa propre dédup applicative → exactement la mauvaise forme.
+
+**La forme réelle (VÉRIFIÉE, cf. `readcap-and-nuri-model.md` §1)** — et elle est
+**asymétrique** de la lecture, ce qui est le point le plus facile à rater :
+
+- **Lecture = possession d'une clé.** Pas d'ACL. Qui détient, lit.
+- **Écriture = membership + permissions** (`AddMember`, `AddPermission` sur le
+ `RootBranch`). C'est **bel et bien une liste d'autorisation** — pas de la
+ possession. Émuler l'écriture « par possession de token » serait aussi faux que
+ l'ACL de lecture actuelle, en miroir.
+- **Les commits SONT signés** par un `UserId` (clé **technique**, distincte du
+ profil) — donc un identifiant de dédup **existe** nativement, sans pseudonyme
+ applicatif.
+- **Mais vérifier une signature exige d'être membre du repo** (accès au
+ `member_pubkey`). Un tiers non-membre voit un commit signé sans pouvoir
+ l'attribuer.
+- **Le dépôt d'inbox n'est PAS authentifié** (sealed box anonyme) : un `from`
+ déclaré est du contenu, pas une preuve.
+
+**Ce que ça impose au polyfill.** Exposer `membership` comme primitif **distinct**
+de la possession de cap, avec au minimum : ajouter/retirer un membre d'un repo,
+lire la members map **quand on est membre**, et **vérifier l'auteur d'un commit**
+(→ un digest d'auteur, **par-overlay donc par-store**). C'est ce dernier point qui
+débloque la dédup côté Festipod.
+
+**La conséquence de forme, à documenter explicitement** (sinon le consommateur se
+trompe de modèle) : le digest d'auteur étant **par-store**, le choix du découpage
+en stores **est** le choix du niveau de corrélation. Un store **stable par-user**
+donne un identifiant traçable **cross-événement** ; un store **par-événement**
+donne un pseudonyme **local à l'événement** — dédup possible, corrélation
+impossible. Festipod a besoin du second. Le polyfill doit donc rendre ce découpage
+**exprimable**, pas le figer.
+
+**Reste ouvert** : « le créateur d'un événement peut-il être membre du store qui
+contient les participations, sans pour autant en détenir la clé de lecture ? » —
+c'est-à-dire membership (écriture/vérification) et possession (lecture)
+réellement **orthogonales**. Si NextGraph les couple, la dédup vérifiée et
+l'anonymat s'excluent, et c'est le brief Festipod qui doit trancher ce qu'il
+sacrifie. **À vérifier avant de façonner l'API.**
+
+
+
+*(Question devenue sans objet : il n'y a pas de membership. La dédup ne passe pas
+par une vérification de signature — voir le brief Festipod « inscriptions ».)*
+
## Questions ouvertes
- **TRANCHÉ (directive PO, 2026-07-21)** : on **simule la crypto** (donnée chiffrée
@@ -91,14 +164,39 @@ que la vision interdit. **Fermer ces voies = le cœur de P1.**
**Livrable** : YES/NO/PARTIAL par Q1/Q2/Q3 + le primitif exact (file:line) + la forme d'API à exposer (si OUI), ou le constat que le compteur change (si NON).
**Décision gatée** : OUI → P1 ; NON → le brief inscriptions revoit le compteur (pas anonyme, ou autre primitif).
+### Verdict du spike (2026-07-21) — VÉRIFIÉ dans `nextgraph-rs`
+
+| | Réponse | Preuve |
+|---|---|---|
+| **Q1 — existence/fetch sans cap** | **OUI, partiel** | Les lectures ne sont pas cap-gatées : `blocks_get.rs`, `blocks_exist.rs`, `topic_sync_req.rs` servent les blocs sans exiger ReadCap ni membership. **Seul garde : l'overlay**. Nuance non levée : overlay *inner* vs *outer* (`expose_outer`, défaut `false`) — **sonde en cours**. |
+| **Q2 — détecter une suppression sans clé** | **NON** | Broker append-only ; une suppression est un **commit tombstone chiffré** (`RemoveRepo`), no-op côté verifier. Sans clé on observe « une activité », jamais « une suppression ». |
+| **Q3 — confidentialité** | **OUI** | Blocs stockés en ciphertext ; la clé est `#[serde(skip)]` (`types.rs`), dérivée du `ReadCapSecret`. Le keyless ne donne **jamais** le contenu. |
+
+**Ce que ça décide.**
+- **P1 est débloqué** : `resolveCapLess(nuri) → { exists }` est la bonne forme — mais **`{ exists | deleted }` ne l'est PAS**. Ne pas exposer d'état `deleted`, ce serait inventer une capacité que la cible n'aura jamais (précisément le mode d'échec que ce brief combat).
+- **Le retrait doit être un message, pas une observation.** Côté consommateur : un *nudge* explicite. Le polyfill n'a **rien** à émuler pour ça — juste à ne pas prétendre le contraire.
+- **Reste gaté** : la nuance d'overlay (Q1). Si un non-membre ne peut pas rejoindre l'overlay d'un store *protected* tiers, le fetch keyless est inatteignable **en pratique** malgré un chemin d'autorisation ouvert — et le compteur anonyme retombe sur du déclaratif.
+
## Esquisse de phases
- **P1** — distinction cap-less / cap-porteur dans les NURI + le read-model
- (résoudre un cap-less = nommer/compter, pas lire).
+ (résoudre un cap-less = nommer/compter/prouver l'existence, **pas** lire, **pas**
+ de `deleted`). Inclut la **fermeture des bypass** (`inbox.read`, `sparqlQuery`)
+ sans laquelle la distinction n'est pas tenue.
- **P2** — remplacer l'ACL par un modèle de **possession de token** (grant = livrer
- à un destinataire ; enforcement = possession).
+ à un destinataire ; enforcement = possession). *Requalifié par la revue adverse :
+ le vrai contenu de P2 est **durabilité + cap-less + re-partage par le détenteur**,
+ pas « inverser l'ACL » — sans crypto, inverser ne produit aucun delta observable.*
- **P3** — révocation par re-key (invalidation + re-livraison, non-rétroactive).
-- **P4** — adapter l'API consommateur + `migration-guide.md`.
+- **PW** — **WriteCap = membership** : primitif distinct (add/remove member,
+ members map lisible par un membre, **vérification d'auteur de commit** → digest
+ par-store), et découpage en stores **exprimable** par le consommateur.
+ *Indépendant de P1–P3 ; **bloquant pour la dédup Festipod**, donc à ordonnancer
+ tôt si c'est ce besoin-là qui presse.*
+- **P4** — adapter l'API consommateur + `migration-guide.md`. *La revue adverse
+ requalifie ce lot : ce n'est pas un swap d'API mais une **re-architecture
+ consommateur** (le grant se déplace vers l'acceptation de connexion et devient
+ persistant ; `declareConnections` disparaît).*
## Revue adverse (2026-07-20) — à intégrer
diff --git a/docs/readcap-and-nuri-model.md b/docs/readcap-and-nuri-model.md
index b6316ca..c356c9f 100644
--- a/docs/readcap-and-nuri-model.md
+++ b/docs/readcap-and-nuri-model.md
@@ -82,8 +82,56 @@ regexes `net/types.rs` :
- `did:ng:o:{repo}:c:{commit}:k:{clé}` (`RE_COMMIT` types.rs:73)
- liste `RE_OBJECTS` `…:[cj]:{id}:k:{clé}…:l:{locator}` (types.rs:64)
-L'`overlay` est **dérivé publiquement** de l'id du repo (`OverlayId::outer(store_id)`,
-`:259`) → id+overlay ne fuitent **aucun secret**.
+Le segment `:v:` est l'**overlay**, qui a sa propre section ci-dessous — c'est le
+point le plus lourd de conséquences pour les modèles de présence anonyme.
+
+## 4bis. L'overlay est l'espace réseau d'un STORE — jamais d'un document
+
+**L'overlay est l'unité d'adressage réseau d'un store.** Chez le broker, les blocs
+sont rangés sous une clé `(overlay, block_id)`, et les pairs se synchronisent
+*dans* un overlay. Deux formes par store :
+
+| | Dérivation | Qui peut le calculer |
+|---|---|---|
+| **outer** | `OverlayId::outer(store_id)` = BLAKE3 **public** | tout le monde (le store_id suffit) |
+| **inner** | `OverlayId::inner(store_id, readcap_secret)` = BLAKE3 **keyed** | seulement qui détient la clé de lecture du store |
+
+Cohérent avec le reste du modèle : pas de rôle ni de liste, seulement « détiens-tu
+la clé qui permet de dériver cet identifiant ». `outer` = le nom public du store,
+`inner` = son nom privé.
+
+**Le `:v:` d'un NURI de DOCUMENT porte l'overlay de son STORE** (VÉRIFIÉ, chaîne
+lue de bout en bout) : `NuriV0::repo_graph_name(repo_id, overlay_id)` formate
+`o:{repo_id}:v:{overlay_id}` ; dans `doc_create` la valeur injectée est
+`store.outer_overlay()` — le store **contenant**, jamais le `repo_id`. Un `Repo` ne
+porte **aucun** champ overlay (seulement `store: Arc`) ; c'est `Store` qui
+porte `overlay_id`. **Contre-preuve mécanique** : dans `Store`, `get`/`put`/`del`/`has`
+passent tous `&self.overlay_id` au block storage — tous les documents d'un store
+partagent le namespace de blocs, donc un overlay par-document est structurellement
+impossible.
+
+### La conséquence à connaître : le `:v:` est un pseudonyme stable
+
+**Tous les documents d'une même personne dans son store protected portent le MÊME
+`:v:`** = `outer(protected_store_id)`. Donc une référence cap-less — précisément
+celle qu'on utilise pour « nommer sans donner à lire » — **expose l'appartenance
+au store**, c'est-à-dire un **identifiant pseudonyme stable et permanent de la
+personne**. Le store_id lui-même ne fuit pas (BLAKE3 non inversible), donc ça ne
+dit pas *qui* ; mais c'est un **handle constant**, le même partout et pour
+toujours, corrélable par quiconque collecte des références cap-less.
+
+**Le couplage qui en résulte, et qui contraint tout modèle de présence anonyme** :
+ce même `:v:` est *simultanément* (a) ce qui permet de **dédupliquer** des
+références sans les lire — deux références de même `:v:` viennent de la même
+personne — et (b) ce qui permet de **tracer** cette personne d'un contexte à
+l'autre. **C'est le même bit d'information.** On ne peut pas obtenir la dédup sans
+concéder le traçage, ni supprimer le traçage sans perdre la dédup — sauf à changer
+le découpage en stores, ce qui déplace le curseur mais ne supprime pas l'arbitrage.
+
+*Nuances.* Le `:v:` du NURI est l'overlay **outer**, alors que le trafic
+client↔broker et le stockage local utilisent l'**inner** — valeur différente, mais
+tirée du store elle aussi, donc la propriété tient dans les deux cas. Un store
+`Dialog` renvoie un `Inner`, toujours store-scopé.
INFÉRÉ : un détenteur **sans clé** peut vraisemblablement **récupérer les blocs
chiffrés** (avec l'overlay, toujours cap-less) → vérifier l'**existence** d'un doc
diff --git a/docs/vision.md b/docs/vision.md
index 29570ad..0011370 100644
--- a/docs/vision.md
+++ b/docs/vision.md
@@ -40,8 +40,16 @@ lecture = **détenir la clé**, exactement comme en cible.
## Conséquences de forme (à respecter partout)
-- **Lecture = possession d'une clé** (ReadCap = `{id, clé}`). Un id nu ne lit pas.
-- **Écriture = membership/permissions** (primitif **distinct** — PAS de la possession).
+- **Tout est clés et URLs.** Il n'y a **pas** de notion d'appartenance, de rôle ni
+ de liste d'autorisation dans le modèle : uniquement de la cryptographie
+ symétrique et asymétrique, des URIs, et qui détient quelle clé. Toute forme
+ exposée qui ressemble à une ACL, un `member`, un `role` ou une `permission` est
+ une **mauvaise forme**, quel que soit l'échafaudage qu'on peut lire par ailleurs
+ dans l'état courant de NextGraph.
+- **Lecture = possession de la clé de lecture** (ReadCap = `{id, clé}`). Un id nu
+ (un `did` sans ReadCap) ne lit pas.
+- **Écriture = possession de la clé d'écriture** — une clé **distincte** de celle
+ de lecture, donc un axe distinct, mais **de la possession elle aussi**.
- **Partage d'un cap = le sceller à un destinataire** (livraison **durable**, au
moment du partage — PAS une ACL re-déclarée à chaque session).
- **Révocation = re-key** (nouvelle clé ; les anciens détenteurs gardent l'ancien