docs: état courant NextGraph enrichi + modèle cible aligné + retrait du lot PW

MODÈLE CIBLE (readcap-and-nuri-model) — trois ajouts, deux corrections :
- Store public : lisible par l'URL, et NON récursif — un contenu public peut
  référencer du contenu privé sans y donner accès. C'est la non-récursivité qui
  porte la valeur (objet public pointant vers de l'identité privée).
- Le trousseau : la branche de store, où chaque création commite AddRepo{read_cap}
  — avec l'avertissement explicite que ce n'est PAS le mécanisme de partage.
  Confondre l'index privé et le geste de partage mène à « on partage le store »,
  ce qui livrerait tout son contenu présent et futur.
- Rotation de clé : re-livraison par inbox, traitée automatiquement à la
  connexion. Écrit comme DIRECTION, en signalant que le commentaire amont dont ça
  partait décrit l'état courant.
- Levée de la confusion did/NURI en tête de la section grammaire : `did🆖` est
  un préfixe de schéma présent partout, pas un marqueur de « sans cap ». C'est un
  seul objet, avec ou sans la clé dedans.
- Livraison de cap par inbox signalée comme MANQUE (forme bonne, chemin absent).

ÉTAT COURANT (nextgraph-current-state) — 218 lignes ajoutées, structure intacte :
livraison de cap par inbox non implémentée ; vérification de signature d'auteur
jamais appelée au runtime (members map vide, //TODO) ; aucune sonde d'existence
au niveau SDK ; expose_outer codé en dur à false, absent du SDK ; protocole Ext
sans aucun contrôle. Plus trois constats d'exploitation : heal cold-start,
fork de compte sur provision concurrente, et l'abort du flush outbox sur
TopicNotFound. La mort du socket est seulement référencée (déjà couverte).

CORRECTION D'UN FAIT QUE J'AVAIS ÉNONCÉ FAUX : le digest d'auteur n'est PAS clé
sous le secret de lecture — il est clé par l'overlay outer, public. C'est le
CONTENU du commit qui est chiffré. La conclusion « vérifier suppose de pouvoir
lire » tient, le mécanisme diffère.

Lot PW (WriteCap = membership) RETIRÉ de la liste des phases : il restait planifié
alors que le brief déclare plus haut qu'il n'y a pas de membership. Il était en
outre justifié par un besoin de dédup par signature que le consommateur n'a pas —
sa dédup s'appuie sur l'overlay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
This commit is contained in:
Sylvain Duchesne
2026-07-27 17:42:14 +02:00
parent 8764daff4f
commit b2cb774124
3 changed files with 298 additions and 5 deletions
+69
View File
@@ -45,6 +45,12 @@ sa clé privée.
Le « ciblage wallet » vit donc dans **l'enveloppe de scellage**, pas dans le cap :
le cap reste `{id, clé}`, possession-based.
> **État courant (2026-07-27) — le chemin est un MANQUE, pas un désaccord.** Le
> champ `ContactDetails.read_cap` existe, mais la construction du message est
> `unimplemented!()` (son unique appelant passe « sans read_cap ») et le récepteur
> **jette** le cap qu'il recevrait. La *forme* est donc la bonne ; l'implémentation
> n'est pas là. Le polyfill l'émule en attendant — fiche dans le bug-inbox.
## 3. Révocation = re-key (grossier, non-rétroactif)
On ne « reprend » pas une clé livrée. Révoquer = **re-chiffrer** avec une nouvelle
@@ -60,8 +66,32 @@ clé et ne la re-sceller qu'aux autorisés restants.
**antérieures** au refresh).
- Livraison **durable** d'un cap = `PermaCap` — encore **TODO** (`repo/types.rs:578`).
### DIRECTION — la rotation ne fait PAS perdre l'accès (confirmé PO, 2026-07-27)
**Ne pas lire le commentaire ci-dessus comme l'intention.** « *if they don't
subscribe, they lose access after the refresh* » décrit **l'état courant**, pas la
cible. Ce que NextGraph vise :
> Quand une clé tourne, la nouvelle est **envoyée dans l'inbox** des utilisateurs
> qui conservent le droit d'accès. Cette inbox est **traitée automatiquement** dès
> qu'un client de l'utilisateur se connecte.
Donc l'accès n'est **pas perdu**, il est **différé** jusqu'à la prochaine connexion
— cohérent avec le local-first. Conséquences de forme : **aucune obligation
d'abonnement** à exposer au consommateur ; une re-livraison emprunte **le même
canal** que la livraison initiale, donc le mécanisme de partage couvre les deux
sans cas particulier. La **révocation** reste « cesser de re-livrer », non
rétroactive.
## 4. Grammaire NURI : cap-less vs cap-porteur (le segment `:k:`)
**Lever la confusion d'abord** : `did:ng:` n'est **pas** un marqueur de « sans
cap », c'est le **préfixe de schéma d'URI** — présent partout (inbox `did:ng:d:…`,
branche `did:ng:b:…`, overlay `did:ng:v:…`, document `did:ng:o:…`). Un NURI **est**
un `did:ng:…`. Il n'y a donc pas « le did » d'un côté et « le NURI » de l'autre :
c'est **un seul objet**, avec ou sans la clé dedans — un seul type en amont,
`NuriV0 { target, access }`, où un NURI cap-less a simplement `access` vide.
Le discriminant est le segment **`:k:{clé}`** : présent = cap-porteur ; **absent =
cap-less** (nomme/localise **sans** donner le droit de lire). C'est de **première
classe** dans le type : `NuriV0.target` (des ids) et `access`/`objects` (le cap)
@@ -156,6 +186,45 @@ Leçon transposable : vérifier qu'une garde d'accès **laisse passer** ne prouv
qu'une opération est atteignable — encore faut-il pouvoir **nommer** ce qu'on
demande.
## 4ter. Le store public : lisible par l'URL, et NON récursif
Principe cible (confirmé PO, 2026-07-27) :
> **Un élément du store public est public : qui a l'URL lit le contenu.**
> Mais **pas récursivement** — un contenu public peut *référencer* du contenu
> privé, et la référence ne donne **pas** accès au référencé.
C'est un **second mécanisme**, à côté de la possession de clé (§1) — pas une
entorse. Et c'est la **non-récursivité** qui porte la valeur : elle autorise un
objet public qui **pointe** vers de l'identité privée, sans la divulguer. C'est
exactement le motif dont un modèle de présence anonyme a besoin.
*Détail d'implémentation, à ne PAS faire porter par la forme* : NextGraph s'oriente
vers un **non-chiffrement** du contenu du store public (les données restant
**signées**). Une surface ne doit pas en dépendre. Et si le store public ne se
comporte pas comme ce principe le décrit, c'est **le polyfill** qui s'adapte, pas
le consommateur.
## 4quater. Le trousseau : d'où le propriétaire tire les caps de SES documents
À chaque création de document, un `AddRepo { read_cap }` est commité sur une
**branche du store** — le store étant lui-même un repo, doté de branches **typées**
(le mot « branche » n'a rien de git : c'est un compartiment à rôle défini). Cette
branche liste **les documents du store, chacun avec sa clé de lecture**.
Elle **est** donc le **trousseau du propriétaire** : le mécanisme par lequel il
retrouve les caps de ses propres documents. En amont, le trousseau, c'est le
**wallet**.
**Ce n'est PAS le mécanisme de partage.** Confusion facile et coûteuse : en déduire
« on partage au niveau du store » est faux — livrer un cap de store donnerait accès
à **tout** son contenu, présent et futur. **L'unité de partage est le document**
(§2). Le trousseau est un index privé, pas un geste de partage.
*(VÉRIFIÉ pour le mécanisme `AddRepo { read_cap }` ; le **nom exact** des branches
et l'énumération de leurs types n'ont pas été re-tracés — à confirmer si ce point
devient porteur.)*
## 5. Ce que le polyfill émule (caps.ts) — et où ça diverge
`packages/client/src/caps.ts` modélise `readers: Map<Nuri, Set<PrincipalId>>` +