Fix documentation defects found by an adversarial review

Fifteen findings, all verified before acting. The ones that mattered:

- Corrections added without updating what they corrected. §5's table still
  said a cap-less NURI is one "without :k:", two hundred lines after §4
  established the discriminant is `r:`. Same shape of defect in the P1a
  report, which kept the sentence "it is the owner's keyring, upstream the
  keyring is the wallet" — the exact sentence §4quater declares wrong, and the
  one that produced a global in-memory keyring.

- A wrong source citation: RootCapRefresh/BranchCapRefresh live in
  verifier/src/commits/mod.rs, not repo/src/commit.rs, and are no-op stubs.

- Documentation describing deleted code: isolation.ts, discovery.readIndex,
  the global index, and an acceptance test that was dropped with discovery.

- The P1a implementation report had aged into being wrong in four places
  (caps not persisted, inbox processing not started, plain string types, the
  :k: segment). It is dated, so it now carries a header saying what later lots
  overtook, rather than being rewritten.

- vision.md stated "a document's data is stored encrypted" in the present
  tense. That is the target; here the cap value is the constant OK and nothing
  is encrypted. Said plainly now.

- Prose left mangled by an earlier mechanical find-and-replace, in four places
  I had claimed were repaired.

Also: reach.ts and connect.ts had no home in the permanent docs — the boundary
and the connection sequence are now described in simulation.md, not only in a
brief.
This commit is contained in:
Sylvain Duchesne
2026-08-03 11:34:24 +02:00
parent ae9c32e271
commit 9d3e2d2bfe
13 changed files with 39 additions and 28 deletions
+2 -2
View File
@@ -88,7 +88,7 @@ away; the app code (SDK-shaped) is unchanged.
testable). testable).
- Authorization is emulated capabilities: documents carry grants; the client - Authorization is emulated capabilities: documents carry grants; the client
enforces them generically (read filter + write guard). The app declares a enforces them generically (read filter + write guard). The app declares a
document's read policy and issues directed grants — the same acts it will document, shares one document's cap to an inbox — the same acts it will
perform in the target. No policy is injected. perform in the target. No policy is injected.
- Inbox: the client `inbox` namespace deposits (`post`) and, in the shared-wallet - Inbox: the client `inbox` namespace deposits (`post`) and, in the shared-wallet
emulation, reads the deposits back (`read` / `materialize` / `watch`) in place emulation, reads the deposits back (`read` / `materialize` / `watch`) in place
@@ -107,7 +107,7 @@ Implemented. The polyfill mechanisms are wired against a real broker, not stubbe
(avoids the `@ng-org` double-proxy `DataCloneError`). (avoids the `@ng-org` double-proxy `DataCloneError`).
- Emulated ReadCaps — `caps.ts` (`CapRegistry`, per-document, directed grants) + - Emulated ReadCaps — `caps.ts` (`CapRegistry`, per-document, directed grants) +
read filter `read-filter.ts` (reactive-set `Proxy` view), applied by read filter `read-filter.ts` (reactive-set `Proxy` view), applied by
`use-shape.ts` only when a policy is declared. `use-shape.ts` only once a cap exists (`caps.isEnforcing()`).
- Write guard — `ng-proxy.ts` (`sparql_update` override, emulated write cap). - Write guard — `ng-proxy.ts` (`sparql_update` override, emulated write cap).
- Inbox — `inbox.ts` (`post` / `read` / `materialize` / `watch`). - Inbox — `inbox.ts` (`post` / `read` / `materialize` / `watch`).
- Identity — `accounts.ts` (`IdentityStore`, injected storage). - Identity — `accounts.ts` (`IdentityStore`, injected storage).
@@ -8,6 +8,15 @@ The spec below is unchanged — read it first. Everything from here to *Why this
# Implementation report (2026-07-28) # Implementation report (2026-07-28)
> **Superseded in places by later lots — read with [`2026-07-30-virtual-wallet-boundary.md`](2026-07-30-virtual-wallet-boundary.md).** This report was accurate on 2026-07-28 and is kept as written; four of its statements have since been overtaken, and a fifth was wrong when written:
>
> - *"ReadCaps are NOT persisted as caps anywhere. There is no key store"* — **no longer true.** Both durable registers are now emulated: `shim:readCap` on the store's Store branch (`AddRepo`) and `shim:link` on its User branch (`AddLink`). Caps are read back, not recomputed.
> - *"Processing inboxes … Not started"* — **done** (`src/connect.ts`), at both levels, including per-document inboxes.
> - *"`Nuri` and `ReadCap` are plain strings"* — **superseded the same week**: they are template literal types, so the confusion the runtime guard catches is now also a compile error. The *Typing* section below records the change; the earlier sentences were not rewritten.
> - The `:k:` segment throughout — **a ReadCap is `r:`** (`BlockRef::readcap_nuri`, `engine/repo/src/types.rs:518-521`), reported by NextGraph's developer and verified. `:k:` belongs to objects, files and commits.
> - *"That branch lists the store's documents… It is the owner's keyring. Upstream, the keyring is the wallet"* — **wrong when written**, and it is the sentence that produced a global in-memory "keyring". There is no keyring object; the wallet holds one root key per user. See [`../readcap-and-nuri-model.md`](../readcap-and-nuri-model.md) §4quater.
## What landed ## What landed
| Spec | Where | | Spec | Where |
@@ -1,6 +1,6 @@
# Brief — the virtual user boundary # Brief — the virtual user boundary
**Status: specified 2026-07-30; all four steps done.** Sits alongside `2026-07-20-caps-emulation-alignment.md` (the wider caps chantier) and `2026-07-27-p1a-cap-surface.md` (the surface, implemented). This lot is about something more fundamental than either: **what a virtual user is allowed to reach.** **Status: specified 2026-07-30; all seven steps done** (four planned, three added as the work uncovered them). Sits alongside `2026-07-20-caps-emulation-alignment.md` (the wider caps chantier) and `2026-07-27-p1a-cap-surface.md` (the surface, implemented). This lot is about something more fundamental than either: **what a virtual user is allowed to reach.**
## Why this lot exists ## Why this lot exists
@@ -117,7 +117,7 @@ Applies to every exported surface, including ones added later: **if it is expose
*Not done, and deliberately*: per-DOCUMENT inboxes. Upstream every document has one; here only the wallet does. **The PO has ruled they must come** (2026-07-30) — *"it can come in a second step, but it must come"* — so this is a commitment, not an option. The guard predicate (`isOwnInbox`) is where they plug in: it answers "is this inbox mine?", which extends to "…one of my documents' inboxes" without changing a single caller. *Not done, and deliberately*: per-DOCUMENT inboxes. Upstream every document has one; here only the wallet does. **The PO has ruled they must come** (2026-07-30) — *"it can come in a second step, but it must come"* — so this is a commitment, not an option. The guard predicate (`isOwnInbox`) is where they plug in: it answers "is this inbox mine?", which extends to "…one of my documents' inboxes" without changing a single caller.
### Two defects this step surfaced, both open ### Two defects this step surfaced — the first still open, the second closed by steps 56
**`walletInbox(id)` is a directory, and directories do not exist.** It resolves ANY wallet's inbox from its identity id, and it is exported (`storeRegistry.*` is re-exported from the SDK entry). But you cannot look someone up in NextGraph — you cannot discover, you can only follow links. Their inbox NURI reaches you because *they gave it to you*, not because you resolved it from a name. Resolving **my own** inbox is legitimate plumbing; resolving **anyone's** is the same shape as the discovery index just removed. Fix: the public surface becomes "my inbox" (no argument), and reaching someone else's requires a NURI you were given. Resolution-by-id stays internal, for the shim and the tests. **`walletInbox(id)` is a directory, and directories do not exist.** It resolves ANY wallet's inbox from its identity id, and it is exported (`storeRegistry.*` is re-exported from the SDK entry). But you cannot look someone up in NextGraph — you cannot discover, you can only follow links. Their inbox NURI reaches you because *they gave it to you*, not because you resolved it from a name. Resolving **my own** inbox is legitimate plumbing; resolving **anyone's** is the same shape as the discovery index just removed. Fix: the public surface becomes "my inbox" (no argument), and reaching someone else's requires a NURI you were given. Resolution-by-id stays internal, for the shim and the tests.
+5 -8
View File
@@ -76,9 +76,7 @@ Replace the emulated `inbox.ts` deposit (`docs.sparqlUpdate` into a shared-walle
document) with the native `inbox_post_link` (proposed/future). On the read side the document) with the native `inbox_post_link` (proposed/future). On the read side the
recipient's own verifier unseals each queued sealed message and applies it inline recipient's own verifier unseals each queued sealed message and applies it inline
when it processes its inbox — there is no separate curator to build; the in-lib read when it processes its inbox — there is no separate curator to build; the in-lib read
emulation simply goes away (see the deferred global-index note in the top-level emulation simply goes away. *(There is no global index to replace the cross-account fan-out: both were removed on 2026-07-30 — you cannot discover in NextGraph, you follow links.)*
README and [`decisions/discovery-model.md`](./decisions/discovery-model.md)). The
single global index replaces the cross-account fan-out.
### 5. Retire the identity store → real per-user login ### 5. Retire the identity store → real per-user login
Remove `accounts.ts` (the `IdentityStore` that persists the identity id in Remove `accounts.ts` (the `IdentityStore` that persists the identity id in
@@ -87,11 +85,10 @@ becomes the real per-user login
(see [`decisions/shared-wallet-login-flow.md`](./decisions/shared-wallet-login-flow.md)). (see [`decisions/shared-wallet-login-flow.md`](./decisions/shared-wallet-login-flow.md)).
The flow shape ("broker redirect → app") does not change. The flow shape ("broker redirect → app") does not change.
### 6. Drop the isolation scaffold ### 6. ~~Drop the isolation scaffold~~ — already gone
`isolation.ts` (application-visibility scaffold) disappears against a `isolation.ts` (the old application-visibility filter) was deleted from the library;
different piece of infra than the caps: real per-account wallets, and the nothing remains to remove at migration. Kept as a numbered step so the following
relationship concept the consumer application owns. Distinct axis from ReadCaps — numbers stay stable across references.
remove independently.
### 7. Remove the build alias — the client becomes the real SDK ### 7. Remove the build alias — the client becomes the real SDK
The consumer application imports `@ng-org/web` / `@ng-org/orm` resolved to this lib The consumer application imports `@ng-org/web` / `@ng-org/orm` resolved to this lib
+1 -1
View File
@@ -663,7 +663,7 @@ On a FRESH session over the SAME persistent wallet (reconnect, new page, re-logi
anchored `sparql_query` against a document written in an earlier session comes back with anchored `sparql_query` against a document written in an earlier session comes back with
**0 rows and no error** — persisted documents read as empty. Observed on every anchored **0 rows and no error** — persisted documents read as empty. Observed on every anchored
reader of the polyfill and healed identically in each (`ensureRepoOpen` before the read, reader of the polyfill and healed identically in each (`ensureRepoOpen` before the read,
`packages/client/src/open-repo.ts`): the discovery index (`discovery.ts` `readIndex`), `packages/client/src/open-repo.ts`): the user's own documents,
the user's store (`store-registry.ts` `readUserStore`), the by-need doc batch the user's store (`store-registry.ts` `readUserStore`), the by-need doc batch
(`read-model.ts` `readUnion`), and the store-root pointer read (`store-registry.ts` (`read-model.ts` `readUnion`), and the store-root pointer read (`store-registry.ts`
`resolvePointer`). The heal is `doc_subscribe(nuri)` → await the first `State` (the sync `resolvePointer`). The heal is `doc_subscribe(nuri)` → await the first `State` (the sync
+1 -1
View File
@@ -57,7 +57,7 @@ My participations / my profile, protected data an owner has granted me, my
notifications — none of these is enumerated across accounts. Each is reached by notifications — none of these is enumerated across accounts. Each is reached by
what is already reachable to me: what is already reachable to me:
- my own docs (always in `self.repos`, and whose caps what I hold holds); - my own docs (always in `self.repos`, and whose caps I hold);
- docs whose cap an owner has delivered to my inbox (`shareCap` — see the - docs whose cap an owner has delivered to my inbox (`shareCap` — see the
per-document ReadCap in [`simulation.md`](./simulation.md)); per-document ReadCap in [`simulation.md`](./simulation.md));
- my inbox (deposits addressed to me). - my inbox (deposits addressed to me).
+5 -5
View File
@@ -2,7 +2,7 @@
**Established 2026-07-20**, VERIFIED by direct reading of the `nextgraph-rs` Rust core (except for points marked INFERRED). The `file:line` references are dated — line numbers are volatile, navigate by symbol/regex. **Established 2026-07-20**, VERIFIED by direct reading of the `nextgraph-rs` Rust core (except for points marked INFERRED). The `file:line` references are dated — line numbers are volatile, navigate by symbol/regex.
Purpose: to give the ground truth of NextGraph's access-rights model, in order to align the polyfill's `caps.ts` emulation (today an ACL — the inverse of the real model). This is the basis for the item "align ReadCap/WriteCap with NextGraph". Purpose: to give the ground truth of NextGraph's access-rights model, in order to align the polyfill's `caps.ts` emulation (an ACL until P1a — the inverse of the real model; realigned 2026-07-28, see §5). This is the basis for the item "align ReadCap/WriteCap with NextGraph".
> ## How to use this document — verify, never infer > ## How to use this document — verify, never infer
> >
@@ -48,7 +48,7 @@ So "wallet targeting" lives in the **sealing envelope**, not in the cap: the cap
A delivered key is not "taken back". To revoke = **re-encrypt** with a new key and re-seal it only to the remaining authorized holders. A delivered key is not "taken back". To revoke = **re-encrypt** with a new key and re-seal it only to the remaining authorized holders.
- "Capabilities are not durable: they can be refreshed by members and previously shared Caps become obsolete/revoked… if [a member] doesn't subscribe, they lose access after the refresh" (`net/types.rs:5055-5058`). - "Capabilities are not durable: they can be refreshed by members and previously shared Caps become obsolete/revoked… if [a member] doesn't subscribe, they lose access after the refresh" (`net/types.rs:5055-5058`).
- Mechanism: `RootCapRefresh` / `BranchCapRefresh` (`repo/src/commit.rs:616,630`; perms `types.rs:1748-1749`). - Mechanism: `RootCapRefresh` / `BranchCapRefresh` (`engine/verifier/src/commits/mod.rs:616,630` — both no-op `Ok(())` stubs today; perms `types.rs:1748-1749`).
- Consequences: **coarse** (repo/branch scale), **non-retroactive** (what was read before remains known to the former holder; they only decrypt the versions **prior to** the refresh). - Consequences: **coarse** (repo/branch scale), **non-retroactive** (what was read before remains known to the former holder; they only decrypt the versions **prior to** the refresh).
- **Durable** delivery of a cap = `PermaCap` — still **TODO** (`repo/types.rs:578`). - **Durable** delivery of a cap = `PermaCap` — still **TODO** (`repo/types.rs:578`).
@@ -178,7 +178,7 @@ The distinction is the operative one, and it is sharp:
The test to apply to anything shared: *does removing it stop the virtual users from functioning, or does it merely stop users from seeing each other's content?* Only the first justifies existing outside a wallet. The test to apply to anything shared: *does removing it stop the virtual users from functioning, or does it merely stop users from seeing each other's content?* Only the first justifies existing outside a wallet.
*Impact on this library, recorded 2026-07-30 and not yet resolved*: `discovery.ts` (a global index owned by a reserved `@index` account, `submitToIndex` / `readIndex` / `watchIndex`) emulates exactly the capability described above as non-existent, **and** holds pooled user data, and `watchShape('public')` folds it into its read set. The ADR that specified it ([`decisions/discovery-model.md`](decisions/discovery-model.md)) already recorded that a freely-readable global index "is not a NextGraph shape" and rested on a singleton-app path that is "not implemented, uncertain". That reservation is now a verdict on both counts. See [`briefs/2026-07-30-virtual-wallet-boundary.md`](briefs/2026-07-30-virtual-wallet-boundary.md). *Impact on this library, RESOLVED 2026-07-30 — the module was removed*: `discovery.ts` (a global index owned by a reserved `@index` account, `submitToIndex` / `readIndex` / `watchIndex`) emulates exactly the capability described above as non-existent, **and** holds pooled user data, and `watchShape('public')` folds it into its read set. The ADR that specified it ([`decisions/discovery-model.md`](decisions/discovery-model.md)) already recorded that a freely-readable global index "is not a NextGraph shape" and rested on a singleton-app path that is "not implemented, uncertain". That reservation is now a verdict on both counts. See [`briefs/2026-07-30-virtual-wallet-boundary.md`](briefs/2026-07-30-virtual-wallet-boundary.md).
## 4quater. Where an owner gets the caps for THEIR OWN documents — the Store branch ## 4quater. Where an owner gets the caps for THEIR OWN documents — the Store branch
@@ -280,9 +280,9 @@ What remains true, and is a separate matter — the *delivery* path is unimpleme
| Durability | **durable** (key delivered once) | durable **in shape**: creation and re-listing refile own caps from the scope index (the emulated `AddRepo` branch); a delivered cap persists in the recipient's inbox document | | Durability | **durable** (key delivered once) | durable **in shape**: creation and re-listing refile own caps from the scope index (the emulated `AddRepo` branch); a delivered cap persists in the recipient's inbox document |
| Revocation | coarse **re-key**, non-retroactive | **not emulated** (P3). Nothing pretends to revoke | | Revocation | coarse **re-key**, non-retroactive | **not emulated** (P3). Nothing pretends to revoke |
| Granularity | repo / branch / commit / object | **one cap per doc-NURI** | | Granularity | repo / branch / commit / object | **one cap per doc-NURI** |
| Ref. without rights | **cap-less NURI** (no `:k:`) | same — `Nuri` names, `ReadCap` names and reads | | Ref. without rights | **cap-less NURI** (no `r:` segment) | same — `Nuri` names, `ReadCap` names and reads |
**The divergence that REMAINS, and it is the load-bearing one**: the stand-in key is a constant (`OK`) rather than a secret, and several read paths consult no cap at all (`docs.sparqlQuery`/`sparqlUpdate`, the whole inbox, `store-registry`, `discovery.readIndex`, `subscribe`, `open-repo`). So P1a bought the **shape**, not the isolation — per-document encryption and closing that inventory are **P1b**. Nothing may be claimed "anonymous" or "private" before it. **The divergence that REMAINS**: the stand-in cap value is the constant `OK` rather than a secret. The read paths that once consulted no cap at all are now confined to the connected virtual user (`reach.ts`, 2026-07-30) — `docs.sparqlQuery`/`sparqlUpdate` and `subscribeDoc` are guarded, the inbox is read only by its owner, and the shim's own machinery moved to unguarded primitives that are never exported. So what is left for **P1b** is per-document encryption: replacing one constant with a real key. Until then, nothing may be claimed "anonymous" or "private".
**App-facing**: `declareConnections` (on the consumer side), which re-declared "my connections read my protected entities" **every session**, was an artifact of the ephemeral ACL — **it disappears**. The grant moves to the moment a connection is accepted (`shareCap` once, per document), which is a consumer **re-architecture**, not an API swap. **App-facing**: `declareConnections` (on the consumer side), which re-declared "my connections read my protected entities" **every session**, was an artifact of the ephemeral ACL — **it disappears**. The grant moves to the moment a connection is accepted (`shareCap` once, per document), which is a consumer **re-architecture**, not an API swap.
+3 -3
View File
@@ -374,7 +374,7 @@ documents whose caps you hold, and nothing else. Proven in
`test/isolation-active.test.ts` (a document nobody shared is unreadable; a share to `test/isolation-active.test.ts` (a document nobody shared is unreadable; a share to
one inbox reveals it there and only there; a bare reference reads nothing while the one inbox reveals it there and only there; a bare reference reads nothing while the
repo link opens the published document; a returning identity keeps its caps) and in repo link opens the published document; a returning identity keeps its caps) and in
`test/watch-shape.test.ts` (e), the acceptance test below. `test/cross-user-access.test.ts`, the acceptance test below.
This discrimination is only observable because each entity is its own document (the This discrimination is only observable because each entity is its own document (the
consumer application creates per-entity docs via `createEntityDoc`) — in a mono-store consumer application creates per-entity docs via `createEntityDoc`) — in a mono-store
@@ -387,7 +387,7 @@ Alice owns a protected document holding a secret and a public one that carries a
reference, and can NAME the protected document while reading nothing of it — reference, and can NAME the protected document while reading nothing of it —
publication is **not recursive**. Charlie, holding the same link plus the protected publication is **not recursive**. Charlie, holding the same link plus the protected
document's cap (delivered to his inbox), reads through the very same reference. The document's cap (delivered to his inbox), reads through the very same reference. The
only difference between them is what what they hold holds; nobody was named to any only difference between them is each of them holds; nobody was named to any
registry. And dynamically: the cap lands in Bob's inbox, his client processes it, and registry. And dynamically: the cap lands in Bob's inbox, his client processes it, and
the read that was empty yields the content — the held-caps signal re-running it. the read that was empty yields the content — the held-caps signal re-running it.
@@ -423,7 +423,7 @@ possession-shaped (`createEntityDoc` files a cap, `shareCap` delivers one,
one document per entity, the per-document cap discriminates at entity granularity — one document per entity, the per-document cap discriminates at entity granularity —
the target's behaviour. the target's behaviour.
The old item-level application-visibility filter (`isolation.ts` The old item-level application-visibility filter (the since-deleted `isolation.ts`
`applyIsolation`, a `Set`-of-records filter keyed on owner+scope) is retired `applyIsolation`, a `Set`-of-records filter keyed on owner+scope) is retired
from the consumer path: the application carries no access logic — it declares its from the consumer path: the application carries no access logic — it declares its
identity and shares caps, and trusts the SDK. Its matrix functions are identity and shares caps, and trusts the SDK. Its matrix functions are
+3 -1
View File
@@ -18,7 +18,9 @@ Without a minimum of crypto simulation, damaging shortcuts get taken (reading th
> **A `did` (bare id, WITHOUT a ReadCap) and a NURI (WITH a ReadCap) are treated GENUINELY differently: the former does NOT allow reading the data; the latter is SUFFICIENT and REQUIRED.** > **A `did` (bare id, WITHOUT a ReadCap) and a NURI (WITH a ReadCap) are treated GENUINELY differently: the former does NOT allow reading the data; the latter is SUFFICIENT and REQUIRED.**
Concretely: a document's data is **stored encrypted** (per-doc symmetric encryption, however lightweight); the **ReadCap = the key**; without it, **decrypting/reading is impossible**. No ACL, no plaintext accessible "on the side". Obtaining read access = **holding the key**, exactly as in the target model. Concretely, **in the target**: a document's data is **stored encrypted** (per-doc symmetric encryption, however lightweight); the **ReadCap = the key**; without it, **decrypting/reading is impossible**. No ACL, no plaintext accessible "on the side". Obtaining read access = **holding the key**, exactly as in the target model.
> **Not yet true here, and saying so matters.** The shape is in place — possession decides, every access is confined to the connected virtual user, caps are stored and read back — but the cap value is the constant `OK` and nothing is encrypted. Per-document encryption is **P1b**, and it is one function (`nuri.ts` `mintCap`). Until it lands, nothing this library does may be described as anonymous or private.
## Shape consequences (to respect everywhere) ## Shape consequences (to respect everywhere)
+3
View File
@@ -92,6 +92,9 @@ useShape<T extends BaseType>(
- `shape` — the ORM shape type (generated from a SHEX shape). Names the entity type - `shape` — the ORM shape type (generated from a SHEX shape). Names the entity type
and the properties to materialize. and the properties to materialize.
- `scope` — where to read: a `{ graphs, subjects }` scope object or a NURI string. - `scope` — where to read: a `{ graphs, subjects }` scope object or a NURI string.
**Not to be confused with this library's `Scope`** (`types.ts`), which is the
literal union `public | protected | private` naming a store. Same word, two
meanings: the ORM's is a read target, ours is a placement.
`undefined` yields an empty read. `undefined` yields an empty read.
- **Returns** a `DeepSignalSet<T>` — a **live reactive set**. Iterate it like a set; - **Returns** a `DeepSignalSet<T>` — a **live reactive set**. Iterate it like a set;
the component re-renders whenever the set changes. the component re-renders whenever the set changes.
+1 -1
View File
@@ -63,7 +63,7 @@ export class CapRegistry {
* whoever HOLDS the link, exactly like §5 of the brief says ("whoever has the * whoever HOLDS the link, exactly like §5 of the brief says ("whoever has the
* URL reads the content"), and holding it means having received it. The set * URL reads the content"), and holding it means having received it. The set
* exists so the library can refuse to surface a document its holder never * exists so the library can refuse to surface a document its holder never
* published (see `discovery.submitToIndex`). * published. *(This fed `discovery.submitToIndex`, removed 2026-07-30; the flag is kept because publishing is still what turns a document into a shareable link.)*
*/ */
private published = new Set<Nuri>(); private published = new Set<Nuri>();
/** doc NURI → principals holding its WRITE cap. Decorative until P1b. */ /** doc NURI → principals holding its WRITE cap. Decorative until P1b. */
+2 -2
View File
@@ -295,7 +295,7 @@ export async function read(targetInbox: Nuri): Promise<Deposit[]> {
await assertOwnInbox(targetInbox, "read"); await assertOwnInbox(targetInbox, "read");
const sid = await sessionId(); const sid = await sessionId();
// NOTE: cold-start repo opening is done by the COLD DIRECT readers that need it // NOTE: cold-start repo opening is done by the COLD DIRECT readers that need it
// (e.g. `discovery.readIndex` → `ensureInboxRepoOpen`), NOT here — `inbox.watch` // (a cold reader that opens the repo before reading), NOT here — `inbox.watch`
// already holds the repo open via its own `subscribeDoc`, so opening a second // already holds the repo open via its own `subscribeDoc`, so opening a second
// bootstrap subscription from inside a watch's re-read would be redundant and can // bootstrap subscription from inside a watch's re-read would be redundant and can
// race the watch's own initial-`State` delivery. Keeping `read` a pure anchored // race the watch's own initial-`State` delivery. Keeping `read` a pure anchored
@@ -384,7 +384,7 @@ export const materialize = read;
* `self.repos`, so a plain anchored `read` resolves an unopened repo and silently * `self.repos`, so a plain anchored `read` resolves an unopened repo and silently
* returns 0 deposits even for a deposit a remote session already synced to the * returns 0 deposits even for a deposit a remote session already synced to the
* broker. Gating on the sync barrier makes the read see the synced deposits. This * broker. Gating on the sync barrier makes the read see the synced deposits. This
* is the SAME cold-read heal `discovery.readIndex` applies to the index inbox. * is the same cold-read heal any cold direct reader needs.
* *
* NOT for the `watch` path: {@link watch} already holds the repo open via its own * NOT for the `watch` path: {@link watch} already holds the repo open via its own
* `subscribeDoc`, so opening a second bootstrap subscription from inside a watch * `subscribeDoc`, so opening a second bootstrap subscription from inside a watch
@@ -14,7 +14,7 @@
* inbox files it, which fires the held-caps signal, which re-runs the read the * inbox files it, which fires the held-caps signal, which re-runs the read the
* protected document appears with nothing else happening. * protected document appears with nothing else happening.
* *
* The difference between Bob and Charlie is ONLY what what they hold holds. There is * The difference between Bob and Charlie is ONLY each of them holds. There is
* no authorization list anywhere, and nobody was named to the registry. * no authorization list anywhere, and nobody was named to the registry.
*/ */
import { test, expect, mock, afterAll } from "bun:test"; import { test, expect, mock, afterAll } from "bun:test";
@@ -257,7 +257,7 @@ test("Charlie: same public document, same reference — and he reads through it"
expect(await readValues([ref], SECRET)).toEqual(["the-protected-content"]); expect(await readValues([ref], SECRET)).toEqual(["the-protected-content"]);
}); });
test("the ONLY difference between Bob and Charlie is what what they hold holds", async () => { test("the ONLY difference between Bob and Charlie is each of them holds", async () => {
inject(); inject();
const { protDoc, pubLink, protCap } = await aliceSetsUpHerDocuments(); const { protDoc, pubLink, protCap } = await aliceSetsUpHerDocuments();
const CHARLIE_INBOX = await walletInbox("charlie"); const CHARLIE_INBOX = await walletInbox("charlie");