docs: deux actes, pas quatre besoins
La section listait quatre « besoins » — nommer, donner à lire, faire trouver, joindre. C'était confondre ce qu'un acte FAIT avec ce dont son résultat dépend. Un émetteur ne fait que deux choses, et tout cas d'usage est l'une des deux : - transmettre la RÉFÉRENCE (`access` vide) : le destinataire peut nommer le document — et le lire si celui-ci se trouve dans un store public. Même valeur transmise, résultat différent, décidé par l'endroit où est le document et non par ce qu'on a envoyé. - transmettre la référence ET la clé : le destinataire lit. Irréversible. « Faire trouver » n'était donc pas un troisième acte, mais le premier appliqué à un document en store public. Et « joindre » n'est pas un acte du tout : le locator dit si la référence est assez COMPLÈTE pour être résolue par un inconnu — cela relève du destinataire. D'où la conséquence pour tout ce que cette bibliothèque expose : un appel remet la clé ou ne la remet pas, et ce doit être l'intention explicite de l'appelant — jamais l'effet de bord d'une demande de référence. Où en est la bibliothèque, acte par acte : transmettre la référence est couvert sans appel dédié (toutes les références rendues sont nues) ; transmettre référence et clé l'est par `inbox.share`. Et une propriété du modèle n'est PAS émulée : un document en store public est lisible depuis une référence nue, là où `mayReach` exige toujours un cap détenu. L'écart va dans le sens sûr — nous sous-accordons — mais une app ne peut pas exprimer « je diffuse largement, la référence suffit », et son seul contournement serait de remettre la clé, ce qui casse la confidentialité composable. Section redondante supprimée, et le mot banni retiré des exemples.
This commit is contained in:
@@ -306,7 +306,7 @@ What remains true, and is a separate matter — the *delivery* path is unimpleme
|
||||
|
||||
*Consequence for this library*: **both durable registers are now emulated** (2026-07-30) — `AddRepo` as a `shim:readCap` record on a distinct subject of the store document (`storeBranch`), `AddLink` as `shim:link` on another (`userBranch`) — and the in-memory `CapRegistry` is what it always was, level 3: the cache. Caps are READ back from those records, never recomputed. What stays an invention is representing branches as RDF subjects at all: upstream both branches carry `BranchCrdt::None` and hold service commits, not triples. What is faithful is that the key sits beside the document, and that the listing (`contains`, the Main branch) is separate from the keys.
|
||||
|
||||
## 4sexies. What a NURI TRANSPORTS — and the four things one may want from a link
|
||||
## 4sexies. What a NURI TRANSPORTS — and the TWO acts that cover every use of it
|
||||
|
||||
**VERIFIED 2026-08-06** by reading `nextgraph-rs`, after a design discussion kept stalling on an assumed split between "a NURI" and "a link". There is no such split, and getting that wrong is what made this library ship a `linkTo` that was wrong three ways at once.
|
||||
|
||||
@@ -332,35 +332,35 @@ NgAccessV0 = ReadCap(ReadCap) | Token(Digest) | ExtRequest(Vec<u8>)
|
||||
|
||||
(`app_protocol.rs:54-62`.) An **empty** `access` is therefore a legitimate, meaningful state: the reference NAMES its target and grants nothing. That is not a degenerate link — see below, it is the common case.
|
||||
|
||||
### The four things one may want, and what each requires
|
||||
### There are TWO acts, and they cover everything
|
||||
|
||||
| What you want | What the reference must carry |
|
||||
|---|---|
|
||||
| **Name** a document — cite it, reference it from another document, let someone ask you for it | `target`; `access` EMPTY |
|
||||
| **Let someone read** a non-public document | `target` + `ReadCap`. Irreversible: whoever receives the message reads, and nothing checks anything later |
|
||||
| **Let someone find** a public document | `target` + `overlay`/`locator`; `access` EMPTY — the broker serves it, no key travels |
|
||||
| **Let someone JOIN from elsewhere** | `locator`. Without it a stranger with no broker in common opens nothing, key or no key |
|
||||
An earlier version of this section listed four "needs" (name, let read, let find, join). That was wrong: it confused what an act DOES with what its result depends on. A sender does exactly two things, and every use case is one of them:
|
||||
|
||||
| Act | What is transmitted | What the recipient can do |
|
||||
|---|---|---|
|
||||
| **Transmit the reference** | `target` (+ `overlay`/`locator`), `access` EMPTY | Name the document. **And read it, if it sits in a public store** — same value transmitted, different outcome, decided by where the document is and not by what was sent. |
|
||||
| **Transmit the reference AND the key** | the above + `ReadCap` in `access` | Read it. Irreversible: nothing is checked later, and there is no revoking a key already handed out (only rotation, which also cuts off legitimate recipients — `PermaShare`/PermaCap, `engine/repo/src/types.rs:1761`, is the reserved answer, unimplemented). |
|
||||
|
||||
"Let someone find a public document" is therefore not a third act: it is the first one, applied to a document that happens to be in a public store. And "join" is not an act at all — the `locator` is about the reference being COMPLETE enough to be resolved by a stranger (no locator → no broker to ask → nothing opens, key or no key). It belongs to the recipient's side.
|
||||
|
||||
The consequence for anything this library exposes: **a call either hands over the key or it does not**, and that must be the caller's explicit intent — never a side effect of asking for a reference.
|
||||
|
||||
### Three confusions worth naming, because each one was made here
|
||||
|
||||
**"No key" does not mean "public".** A key-less reference to a PROTECTED document withholds access deliberately — the recipient can name it, not read it. A `PublicRepoLinkV0` (`types.rs:5105-5127`) carries no `read_cap` for an unrelated reason: the content is served publicly, so there is nothing to hand over. Same shape, opposite situations.
|
||||
|
||||
**Naming is not reading, and it is the DEFAULT act.** This is what `test/cross-user-access.test.ts` proves: Alice publishes a public document that REFERENCES her protected one; Bob follows the public link, finds the reference, and can name the protected document while reading nothing of it. Publication is not recursive. Withholding the key is the ordinary, reversible gesture; including it is the exceptional, irreversible one.
|
||||
**Naming is not reading, and it is the DEFAULT act.** This is what `test/cross-user-access.test.ts` proves: Alice has a document in a public store that REFERENCES her protected one; Bob follows the public link, finds the reference, and can name the protected document while reading nothing of it. Publication is not recursive. Withholding the key is the ordinary, reversible gesture; including it is the exceptional, irreversible one.
|
||||
|
||||
**Nothing is checked at access time.** Reading IS possession — the engine verifies a permission on WRITE only (`verify_permission` → `PermissionDenied` in `Commit::verify`), never on read. So a reference cannot be "neutral about rights, resolved later": what you put in it IS the grant. There is no revoking a link you have circulated, only rotating the key (and `RepoLinkV0`'s own comment says shared caps go stale on refresh — `PermaShare` / PermaCap, `engine/repo/src/types.rs:1761`, is the reserved answer to that, unimplemented).
|
||||
|
||||
### Publishing here means distributing a key; upstream it does not
|
||||
### Where this library stands, act by act
|
||||
|
||||
The sharpest divergence this discussion surfaced, and it is not in `linkTo` — it is one level down.
|
||||
- **Transmit the reference** — covered, with no dedicated call: every reference the surface returns is bare (`createEntityDoc`, `docCreate`, `listMyEntityDocs`, `UnionSubject.subject`/`.graph`). An application cites what it already holds. Faithful.
|
||||
- **Transmit the reference and the key** — `inbox.share(doc, toUser)`. Names the document and the person; the key is looked up and sealed into a deposit, and the recipient applies it by connecting, with nothing to call. Faithful in shape. One recorded divergence: the deposit always goes to the recipient's PROTECTED inbox, where upstream the choice follows the profile the person was reached by (`engine/verifier/src/inbox_processor.rs:787`).
|
||||
|
||||
This library mints a cap on publication (`emulated-verifier/caps.ts` `publishRepoLink`) and its possession filter gates public documents like any other. So *publish* means, here, **distribute a key**. Upstream, a public document is readable because its STORE is public and brokers serve it accordingly (`expose_outer`); the link carries no key at all, and any holder fetches the current one from the outer overlay.
|
||||
|
||||
The emulation is therefore **over-strict, not inverted** — it under-grants. A cap-less reference to a public document resolves upstream and does not here. That matters in two ways: "circulate the link" stays the right gesture at migration (so the consumer act survives), but a consumer must not conclude that publishing is an act of key distribution, nor expect anything per-reader on a public document — upstream there is no grant, no revoke, no audience to build a UI on.
|
||||
|
||||
### Consequence for this library
|
||||
|
||||
`linkTo` (added 2026-08-05) fails all three: it always embeds the key — including where none is needed — never carries a locator, and offers no way to withhold. It must be rebuilt from the table above rather than patched. The recipient verb is missing entirely, which is why Bob's side of the scenario cannot be tested by the multi-actor rule.
|
||||
**And one property of the model this library does NOT emulate**: a document in a public store is readable from a bare reference. Here `mayReach` requires a held cap whatever the scope, so a bare reference never suffices. The gap is in the safe direction — we UNDER-grant, we do not over-grant — but it means an application cannot express "circulate widely, the reference is enough", and the only way it could work around that is by handing out the key, which is exactly what breaks composable confidentiality (§ 0). Left as a known limit rather than patched, because making public-scope documents readable without a cap would rest the property on a scope THIS library assigns, where upstream it comes from the store and from how brokers serve it (`expose_outer`).
|
||||
|
||||
Two things a consumer must not conclude from the emulation: that placing a document in a public store is an act of KEY DISTRIBUTION (here it mints one, upstream none travels), and that a public document has anything per-reader — upstream there is no grant, no revoke and no audience on it, so there is nothing to build a UI around.
|
||||
|
||||
## 5. What the polyfill emulates (caps.ts) — and where it still diverges
|
||||
|
||||
|
||||
Reference in New Issue
Block a user