/** * 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 { 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 { 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 { const { ng } = getConfig(); logAccess("WRITE", anchor, label, " (physical)"); return ng.sparql_update(sessionId, query, anchor); }