refactor(api): l'app nomme une personne ou un document, jamais une adresse d'inbox
L'app d'exemple a servi de juge, et elle a immédiatement montré ce que l'inventaire ne montrait pas : pour partager une note elle résolvait l'inbox du destinataire, pour lire ses messages elle résolvait l'adresse de la sienne. Deux gestes qu'aucune application n'aura à faire une fois la chose native — donc deux gestes qu'elle ne doit pas apprendre. - `shareCap(cap, toUser)` remplace `shareCap(cap, toInbox)`. Partager est un acte envers quelqu'un ; où est son inbox regarde la bibliothèque. - `inbox.readForDocument(doc)` : le propriétaire lit ses messages en nommant la note, comme le déposant la nomme pour en laisser un. - `storeRegistry.userInbox` et `documentInboxAddress` sortent de la surface publiée. Ils restent joignables en interne, où le shim en a besoin. Sortent aussi de `/polyfill`, chacun parce qu'une app qui code contre apprend ce qu'il faudra désapprendre : - `getCaps` / `CapRegistry` — la salle des machines. La question du consommateur est `capFor(doc)` : est-ce que je le détiens ? Le registre n'a ni successeur ni forme inerte ; ce qui s'appuie dessus sera à réécrire, pas à laisser en place. - `getCurrentUser` — une app sait qui elle a connecté ; le redemander à la bibliothèque est une commodité du wallet partagé. - `virtualUsers` / `IdentityStore` — se souvenir d'une identité entre deux sessions est aussi le travail de l'app en amont. L'écran d'accès persiste ce dont IL a besoin ; rien d'autre n'a à être exposé. Reste sur `/polyfill` ce qu'une app appelle vraiment : `configure` et `setCurrentUser`. Le reste y est du test ou de l'injection interne. 170 tests unitaires, e2e 42/42 contre le broker, typecheck vert sur la bibliothèque, l'exemple et le harnais.
This commit is contained in:
@@ -22,8 +22,6 @@ export {
|
||||
getStoreRegistryDeps,
|
||||
resetStoreRegistry,
|
||||
setCurrentUser,
|
||||
getCurrentUser,
|
||||
getCaps,
|
||||
capFor,
|
||||
resetCaps,
|
||||
} from "./shared-wallet/bootstrap";
|
||||
@@ -33,10 +31,26 @@ export {
|
||||
// lives in `inbox.ts` because sharing IS an inbox deposit (upstream: a sealed
|
||||
// message carrying the cap), but it is surfaced here so the cap vocabulary stays
|
||||
// on the polyfill side of the boundary rather than in the SDK-identical entry.
|
||||
export { CapRegistry } from "./emulated-verifier/caps";
|
||||
export { shareCap } from "./surface/inbox";
|
||||
export { connectedUser } from "./emulated-verifier/connect";
|
||||
|
||||
// --- what is deliberately NOT published --------------------------------------
|
||||
//
|
||||
// Removed 2026-08-05, each because an application coding against it learns something it
|
||||
// must unlearn — the one failure this library exists to prevent:
|
||||
//
|
||||
// - `getCaps` / `CapRegistry` — the emulation's engine room. The consumer question is
|
||||
// `capFor(doc)`: do I hold this? The registry object has neither a successor nor an
|
||||
// inert form, so anything built on it must be rewritten rather than left alone.
|
||||
// - `getCurrentUser` — an application knows who it signed in; asking the library back
|
||||
// is a convenience of the shared wallet, not a brick of the model.
|
||||
// - `virtualUsers` / `IdentityStore` — remembering an identity between sessions is the
|
||||
// application's job upstream too. The gate persists what IT needs
|
||||
// (`shared-wallet/access-gate.ts`); nothing else has to be exposed.
|
||||
//
|
||||
// What remains here is the whole polyfill-era surface: `configure`, `setCurrentUser`,
|
||||
// `capFor`, `shareCap` and the test resets. Two of them are what an application calls.
|
||||
|
||||
// --- identity persistence (polyfill-era, no SDK counterpart) ----------------
|
||||
//
|
||||
// Moved here from the SDK-identical entry on 2026-08-03. `accounts` persists WHICH
|
||||
@@ -44,7 +58,5 @@ export { connectedUser } from "./emulated-verifier/connect";
|
||||
// one shared wallet hosts several identities. The real SDK has no counterpart: there
|
||||
// each user opens their own wallet, and "who am I" is the session. Shipping it from
|
||||
// the SDK entry advertised as durable something that disappears at migration.
|
||||
export * as virtualUsers from "./shared-wallet/virtual-users";
|
||||
export type { VirtualUserStorage } from "./shared-wallet/virtual-users";
|
||||
// Config-shaped types the bootstrap needs; both describe the shim, not the SDK.
|
||||
export type { VirtualUserRecord, RegistrySession } from "./shared-wallet/account-registry";
|
||||
|
||||
@@ -31,6 +31,7 @@ import { subscribeDoc } from "./subscribe";
|
||||
import { ensureRepoOpen } from "../emulated-verifier/open-repo";
|
||||
import { getCaps, getCurrentUser, getStoreRegistryDeps } from "../shared-wallet/bootstrap";
|
||||
import { addLink, documentInboxAddress, isOwnInbox } from "../emulated-verifier/branch-registers";
|
||||
import { userInbox } from "../shared-wallet/account-registry";
|
||||
import { escapeLiteral } from "./sparql";
|
||||
import { hasReadCap } from "../model/nuri";
|
||||
import {
|
||||
@@ -279,14 +280,31 @@ function capOfPayload(payload: unknown): ReadCap | null {
|
||||
* The shape is right; the implementation is absent at both ends, so we emulate it
|
||||
* meanwhile.
|
||||
*/
|
||||
export async function shareCap(cap: ReadCap, toInbox: Nuri): Promise<void> {
|
||||
export async function shareCap(cap: ReadCap, toUser: string): Promise<void> {
|
||||
if (!hasReadCap(cap)) {
|
||||
throw new Error(
|
||||
"[ng-eventually] inbox.shareCap: expected a ReadCap (a NURI carrying `:r:`), " +
|
||||
`got a bare reference — naming is not reading: ${JSON.stringify(cap)}`,
|
||||
);
|
||||
}
|
||||
await post(toInbox, { payload: { kind: LINK_KIND, cap } });
|
||||
// Takes the RECIPIENT, not their inbox address. Sharing is an act toward someone;
|
||||
// which inbox carries it is the library's business, and an address is exactly what a
|
||||
// caller will not have to handle once this is native. It used to take `toInbox`, which
|
||||
// forced every consumer to resolve an address first — a step it would then have to
|
||||
// unlearn. Protected, because directed sharing is not a public announcement.
|
||||
await post(await userInbox(toUser, "protected"), { payload: { kind: LINK_KIND, cap } });
|
||||
}
|
||||
|
||||
/**
|
||||
* The messages left on a document YOU own — the read side of {@link postToDocument}.
|
||||
*
|
||||
* Named by the DOCUMENT, like the deposit side: an owner reading their own messages has
|
||||
* no more reason to handle an inbox address than a depositor does. Empty when the
|
||||
* document has no inbox, which is a state and not an error.
|
||||
*/
|
||||
export async function readForDocument(doc: Nuri): Promise<Deposit[]> {
|
||||
const address = await documentInboxAddress(doc);
|
||||
return address ? read(address) : [];
|
||||
}
|
||||
|
||||
// --- the read guard ------------------------------------------------------
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
*
|
||||
* At migration this file disappears: placement becomes the user's real per-scope
|
||||
* stores and the calls below become native SDK ones.
|
||||
*
|
||||
* **No inbox ADDRESS is published here**, deliberately (`userInbox`,
|
||||
* `documentInboxAddress`, removed 2026-08-05). An application deposits with
|
||||
* `inbox.postToDocument(doc, …)`, shares with `inbox.shareCap(cap, toUser)` and reads
|
||||
* its own with `inbox.readForDocument(doc)` — always naming a document or a person,
|
||||
* never an address. Upstream an address is resolved from a profile and never handled by
|
||||
* a caller, so exposing one taught a step that has to be unlearned. The example
|
||||
* application is the check: it must never name an inbox.
|
||||
*/
|
||||
|
||||
export {
|
||||
@@ -28,9 +36,7 @@ export {
|
||||
resolveScopeGraph,
|
||||
/** The NURI where GROUPED entities of `scope` are written (no per-entity document). */
|
||||
resolveWriteGraph,
|
||||
/** A user's own inbox — where caps and messages addressed to THEM arrive. */
|
||||
userInbox,
|
||||
/** Open an inbox on a document you OWN, so others can deposit into it. */
|
||||
/** WHERE to deposit for a document — readable by any holder of it. `undefined` if none. */
|
||||
} from "../shared-wallet/account-registry";
|
||||
export { documentInboxAddress, openDocumentInbox } from "../emulated-verifier/branch-registers";
|
||||
export { openDocumentInbox } from "../emulated-verifier/branch-registers";
|
||||
|
||||
Reference in New Issue
Block a user