88914f50ae
Les 25 modules étaient à plat, nommés d'après ce qu'ils font mécaniquement (`store-registry`, `read-model`, `reach`, `caps`). Rien dans l'arborescence ne disait lesquels DEVIENDRONT le vrai SDK, lesquels tiennent lieu du travail que le verifier fera nativement, et lesquels n'existent que parce qu'un wallet est partagé — trois destins sans rapport. Quatre dossiers, les deux fichiers d'entrée restant à la racine pour que l'`exports` du paquet et le code du consommateur ne bougent pas : - `model/` — le modèle d'adressage de la cible, transcrit : vocabulaire pur, pas d'I/O. Survit comme connaissance. - `surface/` — ce que l'app touche, chaque symbole ayant un pendant cible documenté. Supprimé quand l'alias bascule ; le code de l'app est inchangé. - `emulated-verifier/` — les doublures de ce que le verifier fait nativement : possession, dépôt des caps, frontière, non-livraison, traitement des inbox, registres de branche, ouverture de repo. **C'est le dossier où diverger du modèle est possible.** Le préfixe `emulated-` porte le sens : tient lieu de, jamais est — cette bibliothèque ne réside dans aucune couche de la cible, elle les référence. - `shared-wallet/` — n'existe que parce qu'un wallet héberge toutes les identités. Aucun pendant, rien sur quoi s'aligner ; sa seule loi est de rester invisible depuis `surface/`. S'évapore, remplacé par rien. `store-registry-api.ts` devient `surface/placement.ts` : il faisait déjà à la main ce que la frontière de dossier fait structurellement — c'est la meilleure preuve interne du bien-fondé de ce rangement. Ce commit ne fait que déplacer et recâbler les imports (src, test, e2e). Les scissions des modules à cheval suivent. 157 tests unitaires, typecheck src/test/e2e vert.
104 lines
4.4 KiB
TypeScript
104 lines
4.4 KiB
TypeScript
/**
|
|
* physical — the polyfill's OWN machinery, operating on the PHYSICAL user.
|
|
*
|
|
* ── Two levels, two APIs, and only one of them is the app's ───────────────
|
|
* NextGraph sees exactly one user: the physical one, whose wallet everybody opens.
|
|
* On top of it the library fabricates **virtual users** — what the consumer calls
|
|
* an identity. Those are two different levels, and conflating them is how a
|
|
* boundary gets a hole in it:
|
|
*
|
|
* | | Level | Who calls it | Guarded |
|
|
* |---|---|---|---|
|
|
* | `docs.*`, `subscribeDoc` | the **virtual user** | the consumer app, and the library on the user's behalf | YES — confined to the connected user (`reach.ts`) |
|
|
* | this module | the **physical user** | the library's own machinery, and nothing else | no — it *is* the machinery the boundary is built on |
|
|
*
|
|
* **Nothing here is exported from the package.** `index.ts` must never re-export
|
|
* this module: an app holding these functions could read any document of any
|
|
* virtual user, which is precisely the boundary they exist below.
|
|
*
|
|
* ── Why a separate module rather than exemptions ──────────────────────────
|
|
* The store-root pointer and the doc-shim — the index of virtual users — cannot be
|
|
* subject to the boundary: resolving *which* documents a virtual user owns is what
|
|
* makes virtual users exist at all. An earlier version handled that with a list of
|
|
* exempt NURIs consulted by the guard. Separating the FUNCTIONS is stronger: the
|
|
* machinery does not call the guarded primitive and get waved through, it calls a
|
|
* different primitive that was never guarded. There is no exemption list to widen,
|
|
* to get wrong, or to infer.
|
|
*
|
|
* The rule for deciding which side a call belongs to:
|
|
*
|
|
* > Does this operate on the index of virtual users (the shim), or on the content
|
|
* > of one virtual user? The first is machinery; everything else is the user's,
|
|
* > and is confined.
|
|
*
|
|
* A virtual user's own stores, its inbox and its documents are the user's — they go
|
|
* through `docs.*` and are guarded, even though the library is what calls them.
|
|
*
|
|
* At migration this module disappears with the shim: there is no physical/virtual
|
|
* split once each user opens their own wallet.
|
|
*/
|
|
|
|
import { getConfig } from "../polyfill";
|
|
import { logAccess } from "./access-log";
|
|
import { isNuri } from "../model/nuri";
|
|
import type { Nuri } from "../model/types";
|
|
|
|
/**
|
|
* Create a document as the PHYSICAL user — the shim's own documents (the doc-shim,
|
|
* a virtual user's store documents at provisioning time, an inbox document).
|
|
*
|
|
* Creation is the one operation with no boundary to check: the document does not
|
|
* exist yet, so nobody can hold its cap. What matters is who is credited with it
|
|
* afterwards, which the caller decides by filing the cap among the caps that holder holds.
|
|
*/
|
|
export async function physicalCreate(
|
|
sessionId: string,
|
|
crdt = "Graph",
|
|
cls = "data:graph",
|
|
dest = "store",
|
|
store?: unknown,
|
|
): Promise<Nuri> {
|
|
const { ng } = getConfig();
|
|
const nuri = await ng.doc_create(sessionId, crdt, cls, dest, store);
|
|
if (typeof nuri !== "string" || !isNuri(nuri)) {
|
|
throw new Error(
|
|
`[ng-eventually] physicalCreate: the broker returned something that is not a NextGraph reference: ${JSON.stringify(nuri)}`,
|
|
);
|
|
}
|
|
logAccess("WRITE", nuri, "physicalCreate");
|
|
return nuri;
|
|
}
|
|
|
|
/**
|
|
* Read as the PHYSICAL user — for the shim only (the store-root pointer, the
|
|
* doc-shim's account records).
|
|
*
|
|
* Unguarded by design: this is how the library learns which documents a virtual
|
|
* user owns, so it cannot itself depend on knowing that. Do not reach for it to
|
|
* read a virtual user's content — that is `docs.sparqlQuery`, which is confined.
|
|
*/
|
|
export async function physicalQuery(
|
|
sessionId: string,
|
|
query: string,
|
|
base: string | undefined,
|
|
anchor: Nuri,
|
|
label = "physicalQuery",
|
|
): Promise<unknown> {
|
|
const { ng } = getConfig();
|
|
const result = await ng.sparql_query(sessionId, query, base, anchor);
|
|
logAccess("READ", anchor, label, " (physical)");
|
|
return result;
|
|
}
|
|
|
|
/** Write as the PHYSICAL user — the shim's own records. See {@link physicalQuery}. */
|
|
export async function physicalUpdate(
|
|
sessionId: string,
|
|
query: string,
|
|
anchor: Nuri,
|
|
label = "physicalUpdate",
|
|
): Promise<void> {
|
|
const { ng } = getConfig();
|
|
logAccess("WRITE", anchor, label, " (physical)");
|
|
return ng.sparql_update(sessionId, query, anchor);
|
|
}
|