Align the cap emulation on NextGraph's model, and confine it to a virtual user
Two batches, verified against nextgraph-rs throughout. P1a — the capability surface. Reading was an ACL (Map<doc, Set<principal>>), the exact inversion of key possession. It is now possession: `capFor(nuri)` is the only question, there is no principal parameter anywhere, and nothing turns a bare reference into a cap. Sharing is `shareCap(cap, toInbox)`, a Link deposit; receiving needs no operation. `Nuri` and `ReadCap` are template literal types, so passing a bare reference where a cap belongs is a compile error, with runtime guards behind it for JavaScript callers. The virtual user boundary. Every access function is now confined to the connected user, through two rules on one criterion (possession), implemented in two places so a lapse in either is caught by the other: authorization at the passage points, and "do not even attempt" at the callers. The polyfill's own machinery moved to physical.ts — unguarded, never exported — which replaced an exemption list: the machinery no longer gets waved through the guard, it calls something the guard never saw. Removed, as emulating capabilities the target does not have: - discovery.ts and its global index. There is no discovery in NextGraph; you follow links. It also pooled user data across wallets. - the cross-account fan-out (listEntityDocs, resolveReadGraphs, allAccounts, loadShim), which was cross-user enumeration by construction. - resolveInboxAnchor, a single inbox common to every user. Caps are now stored where NextGraph stores them, and read back rather than recomputed: AddRepo on the store's Store branch for documents a user creates, AddLink on its User branch for caps received. Inboxes belong to someone — the user's own, plus one per document — and connecting a user drains them all; that is the library's job, not the app's. Corrections worth recording: a ReadCap is `r:`, not `:k:` (reported by NextGraph's developer, verified in BlockRef::readcap_nuri); received caps DO have a register (AddLink), contrary to what this repo's notes claimed; and "wallet" upstream means keyring — what owns three stores is a user, so the vocabulary follows. The cap value is the constant OK: the only question the emulation answers is whether a cap is held. P1b replaces that one constant with a real key. After this the shape is right and the isolation is still fake. Nothing here may be described as anonymous or private.
This commit is contained in:
+22
-22
@@ -37,31 +37,29 @@ The governing constraints (all verified in `nextgraph-rs`, cited there):
|
||||
- No reactive union query, and the reactive ORM hangs if handed a per-entity
|
||||
/ unsynced graph fan-out (`RepoNotFound` aborts `orm_start_graph`).
|
||||
|
||||
## Two read regimes — enumerate vs follow
|
||||
## One read regime — follow, never enumerate
|
||||
|
||||
There is **no cross-wallet read** in current NextGraph, so nothing is globally
|
||||
enumerable "for free". The polyfill splits every list into one of two regimes:
|
||||
There is **no cross-wallet read** in current NextGraph, and there is no discovery
|
||||
either: **you cannot discover, you can only follow links**
|
||||
([`readcap-and-nuri-model.md`](./readcap-and-nuri-model.md) §4ter-bis). Nothing is
|
||||
globally enumerable, and nothing is meant to be.
|
||||
|
||||
### Events (all public) = the global index — the one enumeration hack
|
||||
> An earlier version of this document described a second regime — "all public
|
||||
> events, enumerated through a global index" — presented as the one justified
|
||||
> "hack". It was removed on 2026-07-30 along with `discovery.ts`: a global index
|
||||
> emulates a capability the target will never have, and it pools data across
|
||||
> wallets. A public document is reached because someone circulated its link, never
|
||||
> because it was listed.
|
||||
|
||||
Public events are the only thing enumerated across accounts, via the emulated
|
||||
discovery index (`discovery.readIndex`, see
|
||||
[`simulation.md`](./simulation.md) § *Emulated discovery index*). This is the one
|
||||
"hack", and it is justified precisely because P2P has no cross-wallet read: without
|
||||
a shared index a client could never learn that another account's public event-doc
|
||||
exists. `readIndex` yields the event-doc NURIs to open/sync; those repos
|
||||
then enter the local union and become union-queryable.
|
||||
|
||||
### Everything else = follow a graph, never enumerate across accounts
|
||||
### Everything = follow a graph, never enumerate across accounts
|
||||
|
||||
My participations / my profile, protected data an owner has granted me, my
|
||||
notifications — none of these is enumerated across accounts. Each is reached by
|
||||
what is already reachable to me:
|
||||
|
||||
- my own docs (always in `self.repos`);
|
||||
- docs an owner has granted me via a directed per-document read grant
|
||||
(`grantRead(doc, granteeId)` — see the per-document ReadCap in
|
||||
[`simulation.md`](./simulation.md));
|
||||
- my own docs (always in `self.repos`, and whose caps what I hold holds);
|
||||
- docs whose cap an owner has delivered to my inbox (`shareCap` — see the
|
||||
per-document ReadCap in [`simulation.md`](./simulation.md));
|
||||
- my inbox (deposits addressed to me).
|
||||
|
||||
The rule of thumb: access is not discovery. You only union-query over graphs you
|
||||
@@ -71,14 +69,16 @@ Accessing a document without read rights yields an empty result: a reactive / un
|
||||
read never decrypts a repo you hold no cap for, so it simply returns nothing (this
|
||||
matches NextGraph's union read). A targeted read of a repo you do not hold diverges
|
||||
in one way — it raises `RepoNotFound` rather than returning empty — and the read
|
||||
path tolerates that per-doc (a doc that throws is skipped). The cap-introspection
|
||||
used here (`canRead` / `governsRead`) is emulation-only; there is no NextGraph API
|
||||
behind it, so it has no migration target.
|
||||
path tolerates that per-doc (a doc that throws is skipped). The held-caps lookup used
|
||||
here (`capFor`) is emulation-only in its *implementation*; its shape is the target's
|
||||
(possession), so what disappears at migration is the lookup, not the model. Note
|
||||
there is deliberately no "may identity X read doc D?" call: the real model cannot
|
||||
answer that either.
|
||||
|
||||
## Listing = a bounded set of per-doc anchored reads (never a union-scan, never the ORM fan-out)
|
||||
|
||||
To produce a list, take the bounded, by-need set of doc NURIs (the index-yielded
|
||||
event NURIs, my own docs, the NURIs an owner has granted me) and read each one with its
|
||||
To produce a list, take the bounded, by-need set of doc NURIs (my own docs, and the
|
||||
NURIs whose cap someone delivered to me) and read each one with its
|
||||
own anchored `sparql_query` (`SELECT ?s ?p ?o WHERE { ?s ?p ?o }`, anchor = that
|
||||
doc NURI, in parallel and tolerant per-doc). The anchor restricts the query to that
|
||||
one repo's graph, so each read is O(1) in the doc's own size and independent of how
|
||||
|
||||
Reference in New Issue
Block a user