fix: ng-helpers rejoint la surface dev.6, et son faux suit le contrat
This commit is contained in:
+125
-6
@@ -27,9 +27,17 @@ import type { Nuri } from "../src/port";
|
||||
* (`"DEL" + "ETE …"`) is one string by the time it arrives, so the split buys
|
||||
* nothing.
|
||||
*
|
||||
* ## Every rule below is one the polyfill actually enforces
|
||||
* ## Every rule below is one `contract_polyfill-surface` states
|
||||
*
|
||||
* - only a document's owner writes to it (`docs.sparqlUpdate`'s `assertMayWrite`);
|
||||
* - a store is named by its REFERENCE and by nothing else: `privateStore()`,
|
||||
* `protectedStore()` and `publicStore()` hand one out, `createEntityDoc(store?)`
|
||||
* takes one, and every entry "accepts `NuriLike` and validates at the door" -- so
|
||||
* a word where a reference belongs is refused rather than understood;
|
||||
* - omitting the store places the document in the PRIVATE store;
|
||||
* - "you read a document whose key you hold: you created it, it was shared with you,
|
||||
* or it sits in a public store, which serves its read key to whoever asks" -- so a
|
||||
* stranger reaches a document in its owner's public store, and no other;
|
||||
* - only a document's owner writes to it, and holding a read key never grants a write;
|
||||
* - anyone may DEPOSIT into a document's inbox (`inbox.postToDocument`);
|
||||
* - depositing into a document whose owner never opened an inbox THROWS, rather
|
||||
* than going nowhere;
|
||||
@@ -37,6 +45,14 @@ import type { Nuri } from "../src/port";
|
||||
* (`openDocumentInbox`: doing so publishes the document's address);
|
||||
* - a document with no inbox has no address to read — a state, not an error.
|
||||
*
|
||||
* ## The three withdrawn placement entries are modelled by their ABSENCE
|
||||
*
|
||||
* `listMyEntityDocs`, `resolveScopeGraph` and `resolveWriteGraph` left the engagement.
|
||||
* Nothing here answers for them, so reaching for one throws by name (see
|
||||
* {@link namespace}) -- which is the only thing a fake can honestly say about a call
|
||||
* that no longer exists. The same holds for `useShapeQuery`: this package reads
|
||||
* nothing, and the end-to-end application is where that read is exercised.
|
||||
*
|
||||
* ## What is NOT modelled, and what happens then
|
||||
*
|
||||
* Anything the adapter reaches for on `docs`, `inbox` or `storeRegistry` that is
|
||||
@@ -241,6 +257,8 @@ export interface RecordedCall {
|
||||
interface StoredDocument {
|
||||
readonly nuri: Nuri;
|
||||
readonly owner: string;
|
||||
/** The store it was recorded in -- a REFERENCE, which is the only name a store has. */
|
||||
readonly store: Nuri;
|
||||
readonly subjects: Map<string, Map<string, string[]>>;
|
||||
/** `undefined` until the owner opens one — the state `postToDocument` refuses. */
|
||||
deposits: Deposit[] | undefined;
|
||||
@@ -255,6 +273,15 @@ export interface FakePolyfill {
|
||||
sessionId(): string;
|
||||
/** Every triple in a document, read from outside the adapter. */
|
||||
contentsOf(doc: string): { subject: string; predicate: string; values: string[] }[];
|
||||
/**
|
||||
* The store a document was recorded in, as the reference the accessors hand out.
|
||||
* An INSPECTION: placement is not something this package can read back, and the
|
||||
* point of holding it is that a document placed in the wrong store is visible here
|
||||
* rather than only in a broker's refusal.
|
||||
*/
|
||||
storeOf(doc: string): Nuri;
|
||||
/** The three stores of an identity, as `privateStore()` and its two siblings answer. */
|
||||
storesOf(user: string): { private: Nuri; protected: Nuri; public: Nuri };
|
||||
/**
|
||||
* What is waiting in a document's inbox, oldest first — or `null` when its owner
|
||||
* never opened one. An INSPECTION, not an entry of the polyfill: this package
|
||||
@@ -270,6 +297,7 @@ export interface FakePolyfill {
|
||||
*/
|
||||
export function installFakePolyfill(): FakePolyfill {
|
||||
const documents = new Map<string, StoredDocument>();
|
||||
const stores = new Map<string, { private: Nuri; protected: Nuri; public: Nuri }>();
|
||||
const calls: RecordedCall[] = [];
|
||||
let currentUser = "nobody";
|
||||
let documentCount = 0;
|
||||
@@ -339,6 +367,16 @@ export function installFakePolyfill(): FakePolyfill {
|
||||
const inboxImpl = {
|
||||
async postToDocument(doc: unknown, options: unknown): Promise<void> {
|
||||
const stored = require(String(doc));
|
||||
if (!mayRead(currentUser, stored)) {
|
||||
// Reaching a document's owner goes THROUGH the document, so a depositor that
|
||||
// cannot open it has no way in. This is what makes the public store a
|
||||
// load-bearing choice of `create` rather than a decoration: place an index
|
||||
// anywhere else and no stranger can hand it anything.
|
||||
throw new Error(
|
||||
`[ng-eventually] inbox.postToDocument: you cannot read the document ` +
|
||||
`${JSON.stringify(doc)}, so you cannot reach its inbox`,
|
||||
);
|
||||
}
|
||||
if (stored.deposits === undefined) {
|
||||
throw new Error(
|
||||
`[ng-eventually] inbox.postToDocument: this document has no inbox — either its ` +
|
||||
@@ -354,14 +392,89 @@ export function installFakePolyfill(): FakePolyfill {
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The three stores an identity has, minted on first ask and stable afterwards.
|
||||
*
|
||||
* There is no identity parameter on any of the accessors -- a session is one user's
|
||||
* -- so these are keyed by whoever is signed in, exactly as the engagement has it.
|
||||
* A store is a REFERENCE: it carries no key, and it is told from every other
|
||||
* reference only by being one of the three.
|
||||
*/
|
||||
function storesOf(user: string): { private: Nuri; protected: Nuri; public: Nuri } {
|
||||
const known = stores.get(user);
|
||||
if (known !== undefined) return known;
|
||||
const minted = {
|
||||
private: `did:ng:o:store-private-${user}` as Nuri,
|
||||
protected: `did:ng:o:store-protected-${user}` as Nuri,
|
||||
public: `did:ng:o:store-public-${user}` as Nuri,
|
||||
};
|
||||
stores.set(user, minted);
|
||||
return minted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this identity holds a key that opens the document.
|
||||
*
|
||||
* Its creator does; so does anyone at all when it sits in a PUBLIC store, which
|
||||
* serves its read key to whoever asks. Nothing else here shares a key, because
|
||||
* nothing here calls `inbox.share`.
|
||||
*/
|
||||
function mayRead(user: string, stored: StoredDocument): boolean {
|
||||
return stored.owner === user || stored.store === storesOf(stored.owner).public;
|
||||
}
|
||||
|
||||
const storeRegistryImpl = {
|
||||
async createEntityDoc(scope: unknown): Promise<Nuri> {
|
||||
if (scope !== "public" && scope !== "mine") {
|
||||
throw new Error(`[fake-polyfill] unknown scope ${JSON.stringify(scope)}`);
|
||||
async privateStore(): Promise<Nuri> {
|
||||
return storesOf(currentUser).private;
|
||||
},
|
||||
|
||||
async protectedStore(): Promise<Nuri> {
|
||||
return storesOf(currentUser).protected;
|
||||
},
|
||||
|
||||
async publicStore(): Promise<Nuri> {
|
||||
return storesOf(currentUser).public;
|
||||
},
|
||||
|
||||
async createEntityDoc(store?: unknown): Promise<Nuri> {
|
||||
const mine = storesOf(currentUser);
|
||||
// Omitted means the PRIVATE store, as `doc_create` does when no store is given.
|
||||
// It is a real placement and not a default to shrug at: a document that lands
|
||||
// there is one no stranger can open.
|
||||
let placement: Nuri;
|
||||
if (store === undefined) {
|
||||
placement = mine.private;
|
||||
} else {
|
||||
// Validated AT THE DOOR, which is what makes a word where a reference belongs
|
||||
// a refusal rather than a silent placement. This is the exact shape that was
|
||||
// green here for a whole version while the broker rejected it.
|
||||
if (typeof store !== "string" || !store.startsWith("did:ng:")) {
|
||||
throw new Error(
|
||||
"[fake-polyfill] storeRegistry.createEntityDoc: not a NextGraph reference " +
|
||||
"-- a store is named by the reference privateStore(), protectedStore() " +
|
||||
`or publicStore() hands back, got ${JSON.stringify(store)}`,
|
||||
);
|
||||
}
|
||||
const named = [mine.private, mine.protected, mine.public].find((s) => s === store);
|
||||
if (named === undefined) {
|
||||
// "A rejection means unknown, never absent": a store this identity does not
|
||||
// have is a document that could not be recorded, so the call throws.
|
||||
throw new Error(
|
||||
`[fake-polyfill] storeRegistry.createEntityDoc: ${store} is not one of this ` +
|
||||
`identity's stores, so the document could not be recorded in it`,
|
||||
);
|
||||
}
|
||||
placement = named;
|
||||
}
|
||||
documentCount += 1;
|
||||
const nuri = `did:ng:o:doc-${documentCount}` as Nuri;
|
||||
documents.set(nuri, { nuri, owner: currentUser, subjects: new Map(), deposits: undefined });
|
||||
documents.set(nuri, {
|
||||
nuri,
|
||||
owner: currentUser,
|
||||
store: placement,
|
||||
subjects: new Map(),
|
||||
deposits: undefined,
|
||||
});
|
||||
return nuri;
|
||||
},
|
||||
|
||||
@@ -440,6 +553,12 @@ export function installFakePolyfill(): FakePolyfill {
|
||||
if (stored.deposits === undefined) return null;
|
||||
return [...stored.deposits].sort((a, b) => a.ts - b.ts);
|
||||
},
|
||||
storeOf(doc: string) {
|
||||
return require(doc).store;
|
||||
},
|
||||
storesOf(user: string) {
|
||||
return storesOf(user);
|
||||
},
|
||||
contentsOf(doc: string) {
|
||||
const stored = require(doc);
|
||||
const out: { subject: string; predicate: string; values: string[] }[] = [];
|
||||
|
||||
Reference in New Issue
Block a user