fix(caps): créer un document en donne le cap, + corriger 9 faits NextGraph
Le trou trouvé par l'e2e contre le broker en ligne : `docs.docCreate` ne
déposait aucun cap pour le créateur, donc un consommateur pouvait créer un
document par la primitive publique puis se voir refuser sa lecture et son
écriture. En amont c'est impossible — `doc_create` commite
`AddRepo { read_cap }` sur la branche Store du store, et le créateur le détient
dès le premier instant. Délibérément non répliqué dans `physical.ts` : les
documents du shim n'appartiennent à aucun utilisateur virtuel, et
`store-registry` classe leurs caps là où il sait à qui ils sont.
e2e : 22 passés / 8 échoués → 39 / 0. Les autres échecs venaient du harnais,
qui agissait comme une seconde identité sans l'établir, ou lisait un document
quelconque comme une inbox. Un run e2e contre un wallet persistant exige une
identité FRAÎCHE par run : `walletInbox(id)` rend l'inbox stable pour son
propriétaire — c'est son intérêt — donc un id fixe accumule les dépôts des runs
précédents (vert au 2e run, rouge au 3e, à code inchangé).
Revue adverse de la documentation, 9 défauts, tous vérifiés à la source avant
correction :
- « chaque document a une inbox native » est FAUX. Seuls les repos de store
public et protected en ont une (`site.rs:128,149`) ; `new_store_default` n'en
pose que `if !private` et `doc_create` laisse `inbox: None`. Le store privé
n'en a pas non plus. Ce que le code fait est donc une ANTICIPATION — assumée
et notée comme telle dans `documentInbox`, le brief et l'ADR discovery. Ce qui
est vérifié, c'est la FORME : `AddInboxCapV0` est clé par `repo_id`.
- `InboxMsgContent::Link` est une variante unit sans charge utile : l'inbox ne
transporte aucun ReadCap. `shareCap` était juste et le reste ; ses citations
sont complétées aux deux bouts (émetteur `unimplemented!()`, récepteur qui
ignore `details.read_cap`).
- les 3 stores appartiennent au user (`SiteV0`), pas au wallet ;
- le TODO `OpenRepo` ne concerne pas la lecture cross-wallet — il est dans
`open_branch_`, après `RepoNotFound` ; charger par cap, c'est
`load_repo_from_read_cap` ;
- la liste des méthodes JS était un sous-ensemble présenté comme la surface
(77 exportées) ;
- `outbox-log.ts` n'enregistre rien : il inspecte l'outbox du SDK ;
- l'ADR private-store-nuri-scope citait `orm_start_graph` au présent, remplacé
par `ensureRepoOpen` ;
- l'incident write-loss plaçait `disconnections_sender.send` dans `broker.rs` ;
- la section « Apps & services » n'a aucune citation et rien ne lui correspond
dans le moteur : marquée à re-confirmer, pas à citer comme vérifiée.
Aussi : `fileOwnCaps` n'existe plus (`holdOwnCap` / `readStoreCaps` /
`fileOwnStructure`) — pointeur mort corrigé dans `caps.ts`.
This commit is contained in:
@@ -22,14 +22,24 @@ Where the ground truth lives, so future re-verification is cheap:
|
||||
dispatch (the truth on what is actually *processed*).
|
||||
- `engine/net/src/types.rs` — inbox types (`InboxPost`, `InboxMsg`, `InboxMsgContent`).
|
||||
- `engine/verifier/src/inbox_processor.rs` — inbox message handling.
|
||||
- `engine/verifier/src/verifier.rs:1423` — the `OpenRepo` TODO (cross-wallet read).
|
||||
- `engine/verifier/src/verifier.rs:2237` — `load_repo_from_read_cap`, the one path that
|
||||
brings a repo in FROM a cap (`pub(crate)`, see § *Capability / ReadCap granularity*).
|
||||
- `engine/verifier/src/verifier.rs:1423` — the `OpenRepo` TODO. It is **not** about
|
||||
loading an unheld repo: it sits inside `open_branch_`, past
|
||||
`self.repos.get_mut(repo_id).ok_or(RepoNotFound)?` (`:1331`), so the repo is already
|
||||
held by the time that line runs. What is missing is the broker-side `OpenRepo`
|
||||
request, worked around with a pin.
|
||||
- `engine/repo/src/types.rs` — `RootBranchV0.store: StoreOverlay` (repo → its store).
|
||||
|
||||
## The 5 store types
|
||||
|
||||
Every wallet has the **3 default stores** out of the box (session fields
|
||||
`private_store_id`, `protected_store_id`, `public_store_id`). Group and Dialog
|
||||
are created on demand.
|
||||
The **3 default stores** belong to a **user**, not to the wallet. A wallet holds
|
||||
`sites: HashMap<String, SiteV0>` (`engine/wallet/src/types.rs:456`), and it is `SiteV0`
|
||||
that carries `public` / `protected` / `private` (`engine/verifier/src/site.rs:31-37`) —
|
||||
one wallet can hold several, which is exactly why "wallet" is the wrong unit to reason
|
||||
in (see `docs/readcap-and-nuri-model.md` §4quinquies, *Nomenclature first*). A session exposes the three as
|
||||
`private_store_id`, `protected_store_id`, `public_store_id` — those are the connected
|
||||
USER's. Group and Dialog are created on demand.
|
||||
|
||||
| Store | Read | Write | Creation |
|
||||
|---|---|---|---|
|
||||
@@ -104,11 +114,27 @@ offline"*; *"removing permissions … requires a SyncSignature"* (synchronous).
|
||||
|
||||
## Inbox
|
||||
|
||||
Every document has a native inbox. A non-editor can deposit a link (DID
|
||||
cap) into it without being invited as an editor; the owner moderates. NURI:
|
||||
`did:ng:d:<inbox_id>`. Content: the `InboxMsgContent` enum (`ContactDetails`,
|
||||
**Only two repos have an inbox today: a user's public and protected STORES.** Not
|
||||
documents, and not the private store. `new_store_default` attaches one solely `if
|
||||
!private` (`engine/verifier/src/verifier.rs:2994`), and `doc_create` goes through
|
||||
`new_repo_default`, which leaves `inbox: None` (`engine/repo/src/repo.rs:574`). The only
|
||||
`AddInboxCap` commits in the whole engine are the two in `engine/verifier/src/site.rs:128,149`
|
||||
— one for the public store repo, one for the protected one.
|
||||
|
||||
The *register* is nonetheless per-repo: `AddInboxCapV0 { repo_id, overlay, priv_key }`
|
||||
(`engine/repo/src/types.rs:1973`) records **which repo** an inbox is opened for, so the
|
||||
shape accommodates an inbox on any repo. Nothing creates one, which is a different
|
||||
statement from the shape forbidding it — and it is why this lib's per-document inbox is
|
||||
an ANTICIPATION of that shape, not an emulation of something upstream already does.
|
||||
|
||||
A non-editor can deposit into an inbox without being invited as an editor; the owner
|
||||
moderates. NURI: `did:ng:d:<inbox_id>`. Content: the `InboxMsgContent` enum (`ContactDetails`,
|
||||
`DialogRequest`, `Link`, `Patch`, `ServiceRequest`, `ExtRequest`,
|
||||
`RemoteQuery`, `SocialQuery`…). Messages are sealed (`crypto_box::seal`) to
|
||||
`RemoteQuery`, `SocialQuery`…, `engine/net/src/types.rs:4249-4261`). Note what `Link`
|
||||
is: a **unit variant, carrying nothing** — not a link, not a cap, just a discriminant.
|
||||
Reading it as "the inbox can deliver a read capability" is the trap this file exists to
|
||||
prevent; see the *Consequence for this lib* below, which says the same thing from the
|
||||
other end. Messages are sealed (`crypto_box::seal`) to
|
||||
the inbox pubkey, so only the owner decrypts. The `from` field is optional, so an
|
||||
anonymous sender is possible. This is the "identified if known, anonymous
|
||||
otherwise" behaviour native to the protocol.
|
||||
@@ -241,7 +267,8 @@ with the whole wallet, which is why the read path is per-doc anchored: the ancho
|
||||
read makes a non-empty wallet irrelevant. At the real multi-store
|
||||
migration this is unchanged (the anchored read is native); only bringing a repo into
|
||||
the session changes: opening a real per-user store repo by cap becomes a native
|
||||
broker sync (the `OpenRepo` TODO at `verifier.rs:1423`). Opening still requires the
|
||||
broker sync, through `load_repo_from_read_cap` (`verifier.rs:2237`) — not through the
|
||||
`OpenRepo` TODO at `:1423`, which concerns a repo already held. Opening still requires the
|
||||
repo's NURI + ReadCap — there is no store-level read inheritance (see
|
||||
§ Capability / ReadCap granularity).
|
||||
|
||||
@@ -355,10 +382,16 @@ Listing must go through a one-shot union `sparql_query` instead — see
|
||||
installed version) **does NOT expose**: Group/Dialog store creation; capability
|
||||
sharing (a NURI with rights); permission manipulation; inbox deposit/read.
|
||||
|
||||
Available JS methods: `doc_create`, `doc_subscribe`, `sparql_query`,
|
||||
The JS methods this lib USES: `doc_create`, `doc_subscribe`, `sparql_query`,
|
||||
`sparql_update`, `orm_start_graph`, `orm_start_discrete`, `graph_orm_update`,
|
||||
`discrete_orm_update`, `file_get`, `app_request_stream`. The docs announce *"An
|
||||
API will be provided for permission manipulation"* (no date).
|
||||
`discrete_orm_update`, `file_get`, `app_request_stream`. That is a working subset,
|
||||
**not** the surface: `NGModule` exports **77** (`@ng-org/web@0.1.2-alpha.13`,
|
||||
`dist/index.d.ts:140-268`), including `app_request`, `session_stop`,
|
||||
`disconnections_subscribe`, `social_query_start`, `upload_start`/`upload_chunk`/
|
||||
`upload_done`, and the whole `wallet_*` family. Read "not in the list above" as "we do
|
||||
not call it", never as "it does not exist" — several sections of this very file discuss
|
||||
methods absent from that subset. The docs announce *"An API will be provided for
|
||||
permission manipulation"* (no date).
|
||||
|
||||
## Integration & deployment model
|
||||
|
||||
@@ -439,6 +472,15 @@ methods: `doc_subscribe`, `orm_start_graph`, `orm_start_discrete`, `file_get`,
|
||||
NextGraph's app/service execution model — important because it **invalidates**
|
||||
the idea of "a service with its own wallet sharing global data".
|
||||
|
||||
> **Provenance: NOT verified against `nextgraph-rs`.** Every other section of this file
|
||||
> cites the engine; this one cites nothing, and nothing in the local clone corresponds to
|
||||
> it — no app/service runtime, no settings-document type, no singleton notion. It arrived
|
||||
> with `bea9f51`, moved wholesale from the consumer app's repo, and its own source (an
|
||||
> exchange with the PO, the official docs, or an inference) was not recorded. The
|
||||
> conclusion below carries real weight — it is what deferred a global index in this lib —
|
||||
> so treat it as **a claim to re-confirm with the PO**, not as an engine fact. Do not
|
||||
> extend it, and do not cite it as verified.
|
||||
|
||||
- **Apps AND services are mono-user.** They see only **what the user makes
|
||||
available** to them. There is **no global data** natively, and no central
|
||||
service holding shared data.
|
||||
@@ -734,6 +776,8 @@ semantics of the replay path itself (VERIFIED by reading `send_outbox`).
|
||||
|
||||
**Consequence for this lib:** a queued write can be dropped without any observable error,
|
||||
and one unknown topic can take the rest of the queue with it. The polyfill's own
|
||||
`outbox-log.ts` records write intents but cannot replay them into the core, and no
|
||||
`outbox-log.ts` does not record anything: it exports a single `inspectOutbox()` that
|
||||
READS the SDK's own `sessionStorage` outbox and logs how many peers still have queued
|
||||
writes. It observes the symptom; it holds nothing it could replay, and no
|
||||
write-durability confirmation exists to await — so "the write returned" is not "the write
|
||||
is durable".
|
||||
|
||||
Reference in New Issue
Block a user