docs: sept affirmations sur NextGraph requalifiées à la source
Lot F de la revue adverse. Chaque affirmation relue dans `nextgraph-rs` par symbole avant
d'être réécrite ; aucune ne s'est révélée exacte.
- `AddLink` / `RemoveLink` / `RepoLinkV0` étaient présentés comme **implémentés**. Leurs
arms de vérificateur sont des `Ok(())`, là où celui d'`AddRepo` fait un vrai travail, et
rien ne les construit. Le tableau dit désormais « déclaré, stubbé », et la conclusion qui
en déduisait « le registre existe, seule la livraison manque » est corrigée : **les deux
bouts** sont déclarés-et-stubbés.
- La table `inboxes` était dite « reconstruite vide à chaque session » — elle est
repeuplée au chargement, et la clé privée d'inbox est persistée par repo. L'argument de
sécurité qui s'appuyait dessus repose maintenant sur le bon motif : la table est **par
vérificateur**, pas éphémère.
- La citation de « `doc_create` laisse `inbox: None` » pointait un constructeur réservé aux
tests ; re-ciblée sur le chemin de production.
- `ExtObjectGet` était dit « le seul » primitif accessible à un non-membre et exigeant les
clés : il y en a trois, et sa structure n'a aucun champ de clé.
- L'en-tête de `public-store.ts` était marqué **VERIFIED** alors qu'il repose sur un
commentaire de doc, et la condition qu'il citait (« si les brokers pairs l'autorisent »)
disparaissait de la conclusion. Requalifié en **pari**, condition rétablie.
- Deux sur-restrictions corrigées (ce qu'écrit le traitement d'un `ContactDetails`, et le
prétendu « miroir 1:1 » de `NuriV0`, qui a dix champs).
**Sur la grammaire du ReadCap, une correction de MA correction.** J'avais écrit que la
forme `{target}:r:{cap}` était notre invention. Faux : le segment `r:` et son encodage sont
ceux d'amont, et l'auteur de NextGraph l'a énoncé. Ce qui est établi est plus étroit —
aucun parseur amont n'accepte aujourd'hui un NURI de repo qui le porte, et le segment est
produit comme valeur de champ. J'avais conclu d'une implémentation absente à ce que la
cible ferait, ce que la doctrine du projet interdit nommément. Seule « P1b remplace la
valeur, pas la forme » est corrigée, requalifiée en **pari**.
**Et la surface ne publie plus de type que personne n'utilise.** `export * from
"./model/types"` publiait huit types en bloc ; c'est une liste nommée de six. `ReadCap`
sort — aucune signature publiée ne le prend ni ne le rend, seules deux fonctions privées
de `inbox.ts` s'en servent — et `InboxScope` aussi. Un type n'est publié que si une
signature publiée l'utilise.
197 tests, 0 échec ; les trois typechecks propres ; `lint` sans erreur.
This commit is contained in:
@@ -432,12 +432,22 @@ export async function readLinks(): Promise<ReadCap[]> {
|
||||
* construction, and at any time (see the User-branch note above).
|
||||
*
|
||||
* What is true is narrower: no code path CREATES one for a document — `new_store_default`
|
||||
* attaches one only `if !private` (`verifier.rs:2994`), `doc_create` leaves `inbox: None`
|
||||
* (`repo.rs:574`), and the only two `AddInboxCap` commits in the engine are for the
|
||||
* attaches one only `if !private` (`verifier.rs:2994`), `doc_create` leaves `inbox: None`,
|
||||
* and the only two `AddInboxCap` commits in the engine are for the
|
||||
* public and protected STORE repos (`engine/verifier/src/site.rs:128,149`). So the
|
||||
* capability exists and is simply unexposed above level 1: this function is aligned on
|
||||
* the engine's model, it does not bet past it.
|
||||
*
|
||||
* *(The `inbox: None` claim is true; its citation was wrong until 2026-08-10. It pointed
|
||||
* at `repo.rs:574`, inside `Repo::new_with_member` (`engine/repo/src/repo.rs:543`) —
|
||||
* a constructor reached only from `Repo::new_with_perms`, itself gated
|
||||
* `#[cfg(any(test, feature = "testing"))]` (`repo.rs:186-192`), and from `#[cfg(test)]`
|
||||
* blocks (`branch.rs:387,490`; `commit.rs:1659,1849,1919`). The PRODUCTION path is
|
||||
* `doc_create` → `Verifier::new_repo_default` (`engine/verifier/src/verifier.rs:3004`,
|
||||
* called at `request_processor.rs:689`) → `Store::create_repo_default`
|
||||
* (`engine/repo/src/store.rs:264`) → `create_repo_with_keys` (`store.rs:284`), which
|
||||
* builds the `Repo` with `inbox: None` at `store.rs:691`.)*
|
||||
*
|
||||
* Lazy on purpose, for the same reason: creating an inbox document for every entity up
|
||||
* front would double every `createEntityDoc` for inboxes most documents never receive
|
||||
* anything in. Upstream the keypair is cheap; here an inbox is a document, so it is
|
||||
@@ -472,9 +482,18 @@ export async function openDocumentInbox(docLike: NuriLike): Promise<Nuri> {
|
||||
// false: that commit lands on the committer's OWN User branch, so anyone may write
|
||||
// one naming anyone's repo. What protects upstream is that an inbox address is never
|
||||
// PUBLISHED — it is TRANSMITTED (in a `ContactDetails` message, or a profile QR
|
||||
// code), and `inboxes: PubKey → RepoId` is a per-verifier local table
|
||||
// (`engine/verifier/src/verifier.rs:105`, rebuilt empty each session). A forged pair
|
||||
// reaches nobody, because nobody was told about it.
|
||||
// code), and `inboxes: PubKey → RepoId` is a table of the VERIFIER
|
||||
// (`engine/verifier/src/verifier.rs:105`) — one per user. A forged pair reaches
|
||||
// nobody because it only ever lands in the forger's OWN table; nobody else was told.
|
||||
//
|
||||
// The motive matters, and it was wrong here until 2026-08-10: this comment said the
|
||||
// table is "rebuilt empty each session", which is not what the source does. It is
|
||||
// initialized empty at construction (`:520`, `:2820`) and then REPOPULATED at every
|
||||
// load — `Verifier::load` (`:534-566`) → `add_repo_without_saving` (`:2871`) →
|
||||
// `add_repo_` (`:2887`), which re-inserts `repo.inbox.to_pub() → repo.id` for each
|
||||
// repo it reloads — and the inbox private key itself is persisted per repo
|
||||
// (`INBOX_CAP`, `engine/verifier/src/user_storage/repo.rs:61,171,207,362`). So the
|
||||
// knowledge is durable; what it is not is SHARED. Per-verifier, not ephemeral.
|
||||
//
|
||||
// We publish instead of transmitting — the only way a third party can find the
|
||||
// address at all here — which creates a vector upstream does not have: whoever can
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
/**
|
||||
* public-store — a document in a PUBLIC store gives up its ReadCap to whoever asks.
|
||||
*
|
||||
* ── The upstream mechanism this emulates (VERIFIED) ───────────────────────
|
||||
* `PublicRepoLinkV0` (`engine/net/src/types.rs:5098-5124`) carries `repo`,
|
||||
* `public_store` and `peers` — and **no `read_cap`**. Its own doc comment says why:
|
||||
* ── The upstream mechanism this emulates — a DECLARED model, so a BET ──────
|
||||
* **Labelled VERIFIED until 2026-08-10, wrongly.** What supports it is a doc COMMENT
|
||||
* on a type nothing constructs — a statement of intent, not of behaviour — and this
|
||||
* repo's own rules say both halves of that: a comment describing the current state is
|
||||
* not the intent, and an absent implementation is not evidence either. So this is a
|
||||
* bet, and `docs/document-links.md` § 5 and `docs/readcap-and-nuri-model.md` § 4sexies
|
||||
* already called it one. This header now says the same word.
|
||||
*
|
||||
* What IS read in source: `PublicRepoLinkV0` (`engine/net/src/types.rs:5098-5124`)
|
||||
* carries `repo`, `public_store` and `peers` — and **no `read_cap`**. Its own doc
|
||||
* comment says why:
|
||||
*
|
||||
* > *"The latest ReadCap of the branch (or main branch) will be **downloaded from
|
||||
* > the outerOverlay**, if the peer brokers listed below allow it. […] This link is
|
||||
* > durable, because the public site are **served differently by brokers**."*
|
||||
* > the outerOverlay**, **if the peer brokers listed below allow it**. […] This link
|
||||
* > is durable, because the public site are **served differently by brokers**."*
|
||||
*
|
||||
* So for a repo in a public store, the key is not something a sender hands over: it is
|
||||
* something the **network gives to anyone who asks**. The broker decides, by pinning
|
||||
* the outer overlay (`expose_outer`, `engine/broker/src/server_storage/core/overlay.rs:103-133`).
|
||||
* That is the whole of the property — nothing about the reader, everything about where
|
||||
* the document sits and how brokers serve it.
|
||||
* something the **network gives to whoever asks — and whom the peer brokers allow**.
|
||||
* That condition is part of the mechanism, not decoration: the broker decides, by
|
||||
* pinning the outer overlay (`expose_outer`, `engine/broker/src/server_storage/core/overlay.rs:103-133`).
|
||||
* Nothing about the reader; everything about where the document sits and how brokers
|
||||
* serve it.
|
||||
*
|
||||
* And what is NOT wired, which is precisely why this is a bet: both `PinRepo`
|
||||
* constructors hard-code `expose_outer: false`
|
||||
* (`engine/net/src/actors/client/pin_repo.rs:51,79`), so no client ever asks for the
|
||||
* exposure; and `ExtTopicSyncReq` — the anonymous branch-sync such a link needs — is
|
||||
* declared and falls into `unimplemented!()` (`engine/net/src/types.rs:4523,4533`).
|
||||
* The emulation follows the model the engine DECLARES, in a place the engine does not
|
||||
* yet serve. That is this library's intended posture, named here as the bet it is.
|
||||
*
|
||||
* ── What that means for the model, and why nothing is special-cased ───────
|
||||
* Possession stays the ONE criterion. A public document is readable not because the
|
||||
* guard makes an exception for it, but because its cap is **obtainable**: you ask, you
|
||||
* receive, you hold it, and from there the ordinary path applies. `reach.ts` is
|
||||
* untouched, and "whoever has the reference AND the key reads" still describes
|
||||
* everything — a public store simply hands the key to whoever has the reference.
|
||||
* everything — a public store hands the key to whoever has the reference, where the
|
||||
* brokers serving that store allow it (see the condition above).
|
||||
*
|
||||
* The consequence an application must be able to rely on: **a bare reference to a
|
||||
* document in a public store is enough**, and that is why nothing in this library
|
||||
|
||||
@@ -46,7 +46,23 @@
|
||||
// At migration the build alias is removed and these resolve to the real SDK. The
|
||||
// per-symbol ruling, with its epistemic label, is in `docs/api-contract.md`.
|
||||
|
||||
export * from "./model/types";
|
||||
// A type is published only when a PUBLISHED SIGNATURE uses it. `export *` published
|
||||
// eight in one gesture (2026-08-10: it was a blanket re-export), of which two named
|
||||
// nothing a consumer can reach — `ReadCap` (used only by two private helpers of
|
||||
// `surface/inbox.ts`) and `InboxScope` (used only by the unpublished
|
||||
// `account-registry.userInbox`). A published type with no published signature is a
|
||||
// promise about the target that nothing here keeps: it invites a consumer to hold a
|
||||
// value it has no call to obtain — and for `ReadCap`, the one value the model says a
|
||||
// caller must never be handed on request. They stay DEFINED in `model/types.ts`, where
|
||||
// the library uses them; they stop being surface. `docs/api-contract.md` § 10, § 14.
|
||||
// Each one, and the signature that earns it its place:
|
||||
// Nuri every reference the surface RETURNS
|
||||
// NuriLike every reference the surface ACCEPTS
|
||||
// Scope `storeRegistry.*`, `watchShape`
|
||||
// PrincipalId `ensureIdentity`, `inbox.Deposit`/`PostOptions`, `EventuallyConfig`
|
||||
// NgLike `EventuallyConfig.ng`
|
||||
// UseShapeLike `EventuallyConfig.useShape`
|
||||
export type { Nuri, NuriLike, Scope, PrincipalId, NgLike, UseShapeLike } from "./model/types";
|
||||
export { useShape } from "./surface/use-shape";
|
||||
export { watchShape } from "./surface/watch-shape";
|
||||
export type { ShapeQuery, ShapeObservable } from "./surface/watch-shape";
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
/**
|
||||
* NURI primitives — the cap-less / cap-bearing distinction, kept as ONE object.
|
||||
*
|
||||
* Upstream a NURI is a single type, `NuriV0 { target, access }`: a cap-less NURI
|
||||
* simply has an empty `access`. `did:ng:` is the URI SCHEME prefix (inboxes,
|
||||
* branches and overlays all carry it) — it does NOT mean "without cap". The
|
||||
* discriminant is the `:r:` segment:
|
||||
* Upstream a NURI is a single type, `NuriV0` — TEN fields: `identity, target,
|
||||
* entire_store, objects, signature, branch, overlay, access, topic, locator`
|
||||
* (`engine/net/src/app_protocol.rs:181-194`) — and a cap-less NURI is simply one
|
||||
* whose `access` is empty. This module transcribes **two** of those ten (`target`,
|
||||
* and the cap half of `access`); the other eight have no counterpart here.
|
||||
* `did:ng:` is the URI SCHEME prefix (inboxes, branches and overlays all carry it) —
|
||||
* it does NOT mean "without cap". The discriminant is the `:r:` segment:
|
||||
*
|
||||
* did:ng:o:{doc}:v:{overlay} — names, does NOT read (a {@link Nuri})
|
||||
* did:ng:o:{doc}:v:{overlay}:r:{cap} — names AND reads (a {@link ReadCap})
|
||||
@@ -19,9 +22,11 @@
|
||||
* 2026-07-30; it was the wrong letter *and* the wrong structure.
|
||||
*
|
||||
* These helpers are INTERNAL to the library. The parsed form {@link parseNuri}
|
||||
* mirrors `NuriV0 { target, access }` 1:1 but never surfaces in the SDK-identical
|
||||
* entry's signatures — the real SDK takes plain `String`s and enforces at runtime,
|
||||
* through cryptography, so no branded type and no parsed struct leaks outward.
|
||||
* mirrors that PAIR — `target` and the cap — and not the type: it was described as a
|
||||
* "1:1 mirror of `NuriV0`" until 2026-08-10, which claimed eight fields it has never
|
||||
* carried. It never surfaces in the SDK-identical entry's signatures either — the
|
||||
* real SDK takes plain `String`s and enforces at runtime, through cryptography, so no
|
||||
* branded type and no parsed struct leaks outward.
|
||||
*
|
||||
* ── The stand-in key (deliberately NOT a secret) ───────────────────────────
|
||||
* This library is deliberately insecure (see docs/vision.md). The only question it
|
||||
@@ -80,8 +85,9 @@ export function targetOf(nuri: Nuri): Nuri {
|
||||
}
|
||||
|
||||
/**
|
||||
* The parsed form — a 1:1 mirror of upstream `NuriV0 { target, access }`, where a
|
||||
* cap-less NURI has no `readCap`. Library-internal (see the module header).
|
||||
* The parsed form — upstream `NuriV0`'s `target` plus the cap half of its `access`,
|
||||
* and none of the type's eight other fields; a cap-less NURI has no `readCap`.
|
||||
* Library-internal (see the module header).
|
||||
*/
|
||||
export function parseNuri(nuri: Nuri): { target: Nuri; readCap?: ReadCap } {
|
||||
return hasReadCap(nuri) ? { target: targetOf(nuri), readCap: nuri } : { target: nuri };
|
||||
|
||||
@@ -292,9 +292,13 @@ function capOfPayload(payload: unknown): ReadCap | null {
|
||||
* - the field exists, `ContactDetails.read_cap: Option<ReadCap>`
|
||||
* (`engine/net/src/types.rs:4233`), but building a message that carries one is
|
||||
* `read_cap: if with_readcap { unimplemented!() }` (`types.rs:3786`);
|
||||
* - and the receiver ignores it: `InboxMsgContent::ContactDetails` writes only
|
||||
* `ng:site`/`ng:protected` + `ng:*_inbox` into a fresh contact document
|
||||
* (`engine/verifier/src/inbox_processor.rs:778-830`), never `details.read_cap`.
|
||||
* - and the receiver ignores it: `InboxMsgContent::ContactDetails` creates a fresh
|
||||
* contact document and writes `ng:site`/`ng:protected` + `ng:*_inbox`, a
|
||||
* `vcard:Individual` type, a `vcard:fn` name and an optional `vcard:hasEmail`,
|
||||
* then sets the header title (`engine/verifier/src/inbox_processor.rs:778-845`) —
|
||||
* but never `details.read_cap`. *(The list was "only the two `ng:` predicates"
|
||||
* until 2026-08-10, which understated what the arm writes; the load-bearing part
|
||||
* is the omission, not the length of the list.)*
|
||||
*
|
||||
* Do NOT read `InboxMsgContent::Link` as the intended channel either: it is a **unit
|
||||
* variant carrying nothing** (`engine/net/src/types.rs:4251`).
|
||||
|
||||
Reference in New Issue
Block a user