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:
@@ -14,7 +14,8 @@ The spec below is unchanged — read it first. Everything from here to *Why this
|
||||
> - *"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.
|
||||
> - *"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. The word *keyring* is left standing everywhere below because this report is kept as written; read it as *"what the holder holds"*, which is what the code now calls it.
|
||||
> - `fileOwnCaps` — **renamed and split.** Writing a created document's cap is `holdOwnCap`, reading them back is `readStoreCaps`, and a user's own structure (three stores + inbox) is `fileOwnStructure`. Searching the code for `fileOwnCaps` finds nothing.
|
||||
|
||||
|
||||
## What landed
|
||||
@@ -171,7 +172,7 @@ Fixed in step 2 of [`2026-07-30-virtual-wallet-boundary.md`](2026-07-30-virtual-
|
||||
And it is inboxes, plural, at **two levels**:
|
||||
|
||||
- the **user/wallet** inbox — where ReadCaps arrive;
|
||||
- the inbox of **every document the user owns** — a document is addressable and has its own inbox upstream, so deposits land per document too.
|
||||
- the inbox of **every document the user owns**, so deposits land per document too. *(The justification originally given here — "a document has its own inbox upstream" — is **false**; see the correction in [`2026-07-30-virtual-wallet-boundary.md`](2026-07-30-virtual-wallet-boundary.md) step 7. The design decision stands on the consumer's need and on the record's per-`repo_id` shape, not on an upstream document inbox that does not exist.)*
|
||||
|
||||
What exists today and is reusable: `inbox.readSynced` (the cold, barrier-gated read meant exactly for "process the inbox at (re)connection"), the inline cap absorption in `inbox.read`, and `CapRegistry.onChange` to re-trigger the reads a late cap unblocks. What is missing is the **orchestration**: a connection hook that enumerates the identity's inboxes (wallet-level + one per owned document, via the scope indexes) and processes them, idempotently and without polling.
|
||||
|
||||
@@ -188,10 +189,11 @@ Not started. It changes the consumer contract in the right direction (one less o
|
||||
|
||||
## Verification status
|
||||
|
||||
- **Unit suite green — 138 tests**, typecheck clean on `src`, `test` and the e2e harness.
|
||||
- **Unit suite green — 146 tests**, typecheck clean on `src`, `test` and the e2e harness.
|
||||
- The typing was verified from a **consumer's** point of view, not just the library's: a synthetic app compiled against the entry points shows the two real mistakes (`shareCap(bareNuri, …)` and passing a raw `string` from storage) as compile errors, while every correct path — `capFor(doc)` → `shareCap(cap, inbox)`, and narrowing with the exported guards — needs no cast.
|
||||
- The acceptance test was **mutation-checked**: reverting both gardes (the discovery fold and the `readUnion` possession gate) makes `watch-shape.test.ts` (e) fail with the bare-referenced document reappearing. The test has teeth.
|
||||
- **The e2e was updated but NOT run** — it needs a real broker. `capsReadFilter` was rewritten around possession and a new `capsShareCap` step exercises the full share→inbox→absorb path against the real broker; both await a run.
|
||||
- **The e2e ran against the live broker (`nextgraph.eu`) on 2026-08-03 — 39 passed, 0 failed.** The first run was 22/8, and the eight refusals were not test noise: they exposed a **real hole in the surface**. `docs.docCreate` filed no cap for the creator, so a consumer could create a document through the public primitive and then be refused reading or writing it. Upstream that cannot happen — `doc_create` commits `AddRepo { read_cap }` to the store's Store branch, so the creator holds it from the first instant. Fixed at `packages/client/src/docs.ts:73`, and deliberately NOT replicated in `physical.ts`: the shim's own documents belong to no user, and `store-registry` files their caps where it knows whose they are. The remaining failures were the harness acting as a second identity without establishing it (`createEntityDoc(id, …)` with someone else connected) or reading an arbitrary document as an inbox; both are now `setCurrentUser` + `walletInbox`, which is what a consumer must do too.
|
||||
- **An e2e run against a persistent wallet must use a FRESH identity per run.** The second run was green and the third was not, on unchanged code: moving the inbox tests onto `walletInbox(id)` made the inbox *stable for its owner* — which is the point of an inbox — so a fixed id accumulates every past run's deposits and `deposits.length === 2` drifts to 4. Green-then-red on identical code is the tell. The disposable thing is the **user**, not the inbox: `run.ts` now stamps `@inbox-user-`/`@watcher-`/`@friend-` with `Date.now()`, as it already did for `@alice-`. Any future step that resolves a durable per-user document (inbox, stores, Links) inherits this constraint.
|
||||
- **The cap registry is process-wide and `bun test` shares modules across files**, so suites that read without declaring caps now reset explicitly (`read-model.test.ts`, `watch-shape.test.ts`). Worth knowing before adding a suite.
|
||||
|
||||
## Documentation state
|
||||
|
||||
@@ -115,7 +115,7 @@ Applies to every exported surface, including ones added later: **if it is expose
|
||||
1. ~~**Remove `discovery.***~~ — **DONE 2026-07-30.** `src/discovery.ts` and `test/discovery.test.ts` deleted; `INDEX_ACCOUNT`, `watchShape`'s public-scope fold and its discovery-index container subscription, `nurisFromRef`, the `submitToIndex` guard, and the e2e discovery block all removed. P1a's acceptance test did not need re-basing: `test/cross-user-access.test.ts` already proves the same property (a bare reference reads nothing, the link reads the document) on the model's own terms — following a link — so `watch-shape.test.ts` (e), which proved it on the discovery fold, was dropped. Docs realigned: the ADR is marked superseded, `read-model.md` now describes ONE regime (follow, never enumerate), and the root README's capability row records the removal.
|
||||
2. ~~**"My inbox" + the inbox read guard**~~ — **DONE 2026-07-30.** `storeRegistry.walletInbox(id)` gives every virtual user its own inbox document, created on first sight and recorded in the doc-shim under its own predicate (`shim:docInbox`), read by its OWN query so an account record written before this existed still resolves — the fixed account SELECT did not grow a fourth required field. `isOwnInbox(nuri)` is the predicate; `inbox.read` / `readSynced` (hence `watch`, which reads through it) refuse an inbox that is not the connected wallet's, and refuse outright when no identity is set. **Depositing stays open** — `post` / `shareCap` are untouched, because that is the one legitimate cross-wallet act. The shared `resolveInboxAnchor` (a reserved account's document, an inbox COMMON to every wallet) was removed: it was unused by the library and violated *nothing common*. Locked by `test/isolation-active.test.ts` *an inbox may be DEPOSITED into by anyone, and READ only by its owner*, which walks the exact breach — Alice deposits, cannot read back; Mallory knowing the NURI absorbs nothing; anonymous is refused; Bob reads his own and only then does the cap land.
|
||||
|
||||
*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~~ — **false, corrected 2026-08-03**: no document has an inbox upstream, and neither does the private store (see step 7's correction). 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 — the first still open, the second closed by steps 5–6
|
||||
|
||||
@@ -190,6 +190,8 @@ So the store-root pointer, the doc-shim and the account records go through the m
|
||||
|
||||
The asymmetry holds at both levels, and a test walks it: **anyone deposits** into a document's inbox (that is how a third party reaches its owner at all), **only the owner reads** it.
|
||||
|
||||
**Correction 2026-08-03 — this step ANTICIPATES, it does not mirror.** The paragraph above overstated the upstream fact, and an adversarial doc review caught it. Verified: `new_store_default` attaches an inbox only `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** two `AddInboxCap` commits in the engine are for the public and protected STORE repos (`engine/verifier/src/site.rs:128,149`). So today **no document has an inbox, and neither does the private store**. What is genuinely verified is the *shape*: `AddInboxCapV0` is keyed by `repo_id` (`engine/repo/src/types.rs:1973`), so the record accommodates an inbox on any repo. The step stands as delivered — a per-document inbox is what the consumer needs and what the record's shape supports — but it must be read as a bet on where upstream goes, not as an emulation of what it already does.
|
||||
|
||||
## Relation to P1b
|
||||
|
||||
P1b (per-document encryption, closing the read paths that bypass the guard) largely **becomes** this lot, better framed. Encrypting each document while leaving the wallet boundary open would be securing the windows with the door open — and conversely, once every access is confined to the connected wallet, "the emulated key is derivable" stops being the load-bearing weakness. This lot should therefore precede P1b, or absorb it.
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
>
|
||||
> **`discovery.ts` and its tests were removed on 2026-07-30**, along with `watchShape`'s public-scope fold and `INDEX_ACCOUNT`. See [`../briefs/2026-07-30-virtual-wallet-boundary.md`](../briefs/2026-07-30-virtual-wallet-boundary.md).
|
||||
>
|
||||
> One factual error below is worth naming so it is not carried into a future design: *"a native inbox (a primitive present on every document)"* is **false**. No document has an inbox upstream — only the public and protected STORE repos do (`engine/verifier/src/site.rs:128,149`; `doc_create` leaves `inbox: None`, `engine/repo/src/repo.rs:574`). See [`../nextgraph-current-state.md`](../nextgraph-current-state.md) § Inbox.
|
||||
>
|
||||
> What survives, and is worth keeping from the text below: the **3-stage frame** (`discovery → synchronization → query`) is still exactly right, with stage 1 re-read as *"a link reached you"* rather than *"you consulted an index"*. You still cannot query what you have not synchronized, and you still do not synchronize what nobody gave you. The **inbox** is what feeds stage 1 — which makes it the bootstrap of the whole reachability graph, not a side feature.
|
||||
>
|
||||
> Kept in full below as a record of what was built and why, and of the reasoning that has to be re-read through the correction above.
|
||||
|
||||
@@ -9,7 +9,8 @@ the shim opens repos. Original context: the consuming app.
|
||||
> **protected** store (`did:ng:${protected_store_id}`), verified to open without
|
||||
> `RepoNotFound`. **The central insight of this ADR still holds** and now applies
|
||||
> to **both** stores: you must open the repo via the store's NURI
|
||||
> (`orm_start_graph`) or you get `RepoNotFound`.
|
||||
> or you get `RepoNotFound`. *(How it is opened has since changed — see the note
|
||||
> under Decision.)*
|
||||
|
||||
## Context
|
||||
|
||||
@@ -39,10 +40,17 @@ entity to reuse) → falls back to `doc_create` and the same `RepoNotFound`.
|
||||
## Decision
|
||||
|
||||
**Option B**: use the store NURI as both the `useShape` scope AND the write
|
||||
`@graph`, exactly like `expense-tracker-rdf`. This is why this lib's shim opens a
|
||||
store repo via `orm_start_graph` before writing, and why **`did:ng:i` must never
|
||||
be used as a scope** (it breaks writes with `RepoNotFound`). See the
|
||||
`orm_start_graph` scope rule in [`../simulation.md`](../simulation.md).
|
||||
`@graph`, exactly like `expense-tracker-rdf`. This is why this lib's shim opens the
|
||||
store repo before writing, and why **`did:ng:i` must never be used as a scope** (it
|
||||
breaks writes with `RepoNotFound`). See the scope rule in
|
||||
[`../simulation.md`](../simulation.md).
|
||||
|
||||
*The decision stands; the mechanism named in it has been replaced.* Opening was
|
||||
`orm_start_graph` when this was written. It is now `ensureRepoOpen` — `doc_subscribe`
|
||||
plus a wait for the first `State` (`packages/client/src/open-repo.ts:167`) — after
|
||||
`orm_start_graph` was found to hang on a fan-out (`subscribe.ts:28,181`). What must be
|
||||
read here is the invariant *"open the repo, by its store NURI, before writing"*, not the
|
||||
call that used to implement it.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ An entity written just before a period of inactivity can be **silently lost**: i
|
||||
|
||||
Interpretation (**plausible mechanism, not settled**): the write was pushed into the local **outbox**, but the socket died before it was **durably flushed** into the broker topic; on reconnection, the outbox replay fails (`Err(TopicNotFound)`) because the topic was **never created on the broker side** → the event is abandoned. The account, for its part, had already been durably resolved (`resolveAccount → 1 record`): it is neither lost nor forked.
|
||||
|
||||
> **Epistemic caveat.** The evidence establishes the *symptom* (loss + `Err(TopicNotFound)` + `readScopeIndex → 0`). The exact *mechanism* is not settled between **(i) loss at write time** (the write never durably reaches the broker) and **(ii) cold-rehydration failure** (the write *is* on the broker but a fresh session does not reopen its own scope). The `Err(TopicNotFound)` on the outbox replay leans toward **(i) in this Firefox case**. See the @data repro below, which exhibits a neighboring symptom but **does not settle** (i) vs (ii).
|
||||
> **Epistemic caveat.** The evidence establishes the *symptom* (loss + `Err(TopicNotFound)` + the scope read returning 0 — logged above as `readScopeIndex`, since renamed `readUserStore`). The exact *mechanism* is not settled between **(i) loss at write time** (the write never durably reaches the broker) and **(ii) cold-rehydration failure** (the write *is* on the broker but a fresh session does not reopen its own scope). The `Err(TopicNotFound)` on the outbox replay leans toward **(i) in this Firefox case**. See the @data repro below, which exhibits a neighboring symptom but **does not settle** (i) vs (ii).
|
||||
|
||||
## Causal chain (TRACED — reading of the NextGraph core, to be re-verified)
|
||||
|
||||
- The `SerializationError` closes the socket. The core emits the disconnection: `broker.rs` → `LocalBrokerMessage::Disconnected` → `disconnections_sender.send(...)` (≈ `broker.rs:1051`, to be re-verified — volatile number, navigate by symbol).
|
||||
- The `SerializationError` closes the socket. The core emits the disconnection in **two** places, and they are not the same file: `engine/net/src/broker.rs:1074` sends `LocalBrokerMessage::Disconnected`, which the SDK turns into `disconnections_sender.send(...)` at `sdk/rust/src/local_broker.rs:648`. Navigate by symbol — the line numbers are volatile, and the earlier note in this file put the `send` in `broker.rs`, which it never was.
|
||||
- This disconnection is **pushed** to subscribers via `disconnections_subscribe(cb)` (PUSH stream).
|
||||
- **NextGraph reconnection is an unimplemented `// TODO`** (≈ `broker.rs:1051-1076`): nothing re-establishes the socket nor re-flushes the outbox.
|
||||
- `user_connect` returns a **snapshot** `{ server_id, server_ip, error, since }` at call time — not a stream, unusable for detecting a later drop.
|
||||
|
||||
@@ -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