feat!: curer n'est plus un appel, c'est ce que fait le traitement de l'inbox
This commit is contained in:
+10
-3
@@ -14,7 +14,7 @@
|
||||
* ends up typed as a reference.
|
||||
*/
|
||||
|
||||
import type { CurationReport, IndexEntry, UnionSubject } from "../src/index";
|
||||
import type { IndexEntry, UnionSubject } from "../src/index";
|
||||
|
||||
/** What the leak probe observed — see `run.ts`'s last journey. */
|
||||
export interface BrokenInboxOutcome {
|
||||
@@ -60,8 +60,15 @@ export interface IndexingBridge {
|
||||
referConfigured(object: string): Promise<void>;
|
||||
/** Hand a NAMED index a reference — used where no identity boundary is crossed. */
|
||||
referTo(index: string, object: string): Promise<void>;
|
||||
/** Resolve the references this index received and add what can be added. Owner only. */
|
||||
curate(index: string): Promise<CurationReport>;
|
||||
/**
|
||||
* Connect again — obtain a fresh `Indexing` handle, which is what a page load does.
|
||||
*
|
||||
* There is no curating act to drive: an index is curated at its creator's next
|
||||
* connection and on each deposit while the creator is connected. This is the first
|
||||
* of the two, driven deliberately so a journey has a point at which the catching
|
||||
* up is over; the second needs nothing from anyone.
|
||||
*/
|
||||
reconnect(): Promise<void>;
|
||||
/** The index's entries, ordered by value. */
|
||||
read(index: string): Promise<IndexEntry[]>;
|
||||
|
||||
|
||||
+9
-11
@@ -3,7 +3,7 @@
|
||||
* `@ng-helpers/indexing` writes one, and nothing more.
|
||||
*
|
||||
* ── Why an application and not a bag of library calls ──────────────────────
|
||||
* The 69 unit tests in `test/` run against a fake this repository wrote. They prove the
|
||||
* The 80 unit tests in `test/` run against a fake this repository wrote. They prove the
|
||||
* indexing RULES are consistent; they cannot prove that NextGraph does what the fake
|
||||
* pretends, because the fake is the thing being asked. This page closes that gap by
|
||||
* putting the real broker underneath: it imports `@ng-eventually/polyfill` for real,
|
||||
@@ -35,12 +35,7 @@ import {
|
||||
import { ng as realNg, init as realInit } from "@ng-org/web";
|
||||
|
||||
import { indexing, polyfillPort } from "../src/index";
|
||||
import type {
|
||||
CurationReport,
|
||||
IndexEntry,
|
||||
Indexing,
|
||||
NextGraphPort,
|
||||
} from "../src/index";
|
||||
import type { IndexEntry, Indexing, NextGraphPort } from "../src/index";
|
||||
import type { BrokenInboxOutcome, IndexingBridge } from "./bridge";
|
||||
|
||||
// ── bootstrap: the one polyfill-era call, then the SDK-shaped ones ──────────
|
||||
@@ -91,7 +86,10 @@ async function boot(): Promise<void> {
|
||||
state.who = await ensureIdentity();
|
||||
const session = await sessionReady;
|
||||
port = polyfillPort({ sessionId: session.session_id });
|
||||
api = indexing(port);
|
||||
// One await, and curation is part of it: obtaining the handle processes the inboxes
|
||||
// of the indexes this identity owns and leaves them watched. This application never
|
||||
// curates anything, and has nothing to call if it wanted to.
|
||||
api = await indexing(port);
|
||||
state.status = "ready";
|
||||
}
|
||||
|
||||
@@ -175,8 +173,8 @@ const bridge: IndexingBridge = {
|
||||
await ready().refer(index, object);
|
||||
},
|
||||
|
||||
async curate(index: string): Promise<CurationReport> {
|
||||
return ready().curate(index);
|
||||
async reconnect(): Promise<void> {
|
||||
api = await indexing(readyPort());
|
||||
},
|
||||
|
||||
async read(index: string): Promise<IndexEntry[]> {
|
||||
@@ -199,7 +197,7 @@ const bridge: IndexingBridge = {
|
||||
// Everything real except the inbox step. The failure is injected at the exact moment
|
||||
// the question is about: after the document exists and carries its descriptor, before
|
||||
// anyone can deposit into it.
|
||||
const broken = indexing({
|
||||
const broken = await indexing({
|
||||
...p,
|
||||
openInbox: async (): Promise<void> => {
|
||||
throw new Error("[e2e] injected: the inbox could not be opened");
|
||||
|
||||
+38
-49
@@ -126,12 +126,11 @@ const { check, journey, finish } = declareSuite({
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Bob hands the index a reference, and Alice curates it",
|
||||
name: "Bob hands the index a reference, and Alice's next connection curates it",
|
||||
checks: [
|
||||
"a stranger's deposit into the index's inbox is accepted",
|
||||
"curation reports Bob's object as indexed",
|
||||
"the indexed value was read off Bob's object, and never travelled in his deposit",
|
||||
"a stranger's deposit into the index's inbox reached its owner and became an entry",
|
||||
"the entry is stored under Bob's object's own reference as its subject",
|
||||
"the indexed value was read off Bob's object, and never travelled in his deposit",
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -145,8 +144,7 @@ const { check, journey, finish } = declareSuite({
|
||||
{
|
||||
name: "An object carrying nothing for the field is not indexed",
|
||||
checks: [
|
||||
"curation reports it skipped for want of the field, rather than indexed",
|
||||
"the index still holds exactly one entry",
|
||||
"the unrelated object is not indexed, and the index still holds exactly one entry",
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -365,7 +363,7 @@ async function main(): Promise<void> {
|
||||
});
|
||||
|
||||
await journey({
|
||||
name: "Bob hands the index a reference, and Alice curates it",
|
||||
name: "Bob hands the index a reference, and Alice's next connection curates it",
|
||||
needs: [
|
||||
aliceIsUp,
|
||||
bobIsUp,
|
||||
@@ -379,30 +377,11 @@ async function main(): Promise<void> {
|
||||
bob!.frame.evaluate((o) => window.__indexing.referConfigured(o), bobsObject!),
|
||||
);
|
||||
|
||||
const report = await step("Alice curating", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate((i) => window.__indexing.curate(i), index!),
|
||||
);
|
||||
// The deposit is proven ARRIVED, by the only person who can see it. That the post
|
||||
// did not throw is a weaker claim entirely — it says the call returned, not that
|
||||
// anything crossed the identity boundary — and asserting it would be asserting a
|
||||
// constant. Alice reads her own inbox; one outcome means one deposit reached it.
|
||||
check(
|
||||
"a stranger's deposit into the index's inbox is accepted",
|
||||
report.outcomes.length === 1,
|
||||
`from=${BOB} outcomes=${report.outcomes.length}`,
|
||||
);
|
||||
const forBob = report.outcomes.find(
|
||||
(o) => "object" in o && o.object === bobsObject,
|
||||
);
|
||||
check(
|
||||
"curation reports Bob's object as indexed",
|
||||
forBob?.result === "indexed",
|
||||
`outcomes=${JSON.stringify(report.outcomes)}`,
|
||||
);
|
||||
check(
|
||||
"the indexed value was read off Bob's object, and never travelled in his deposit",
|
||||
forBob?.result === "indexed" && forBob.value === "2026-08-17T09:00:00Z",
|
||||
`value=${forBob !== undefined && "value" in forBob ? forBob.value : "(none)"}`,
|
||||
// NOBODY CURATES: there is nothing on the surface to call. Alice's page connects
|
||||
// again — what a page load does — and her session processes the inboxes of the
|
||||
// indexes she owns, this one among them.
|
||||
await step("Alice connecting again", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate(() => window.__indexing.reconnect()),
|
||||
);
|
||||
|
||||
// THE WRITE FORM, answered. An entry is a triple whose subject is another
|
||||
@@ -412,11 +391,27 @@ async function main(): Promise<void> {
|
||||
alice!.frame.evaluate((d) => window.__indexing.readRaw(d), index!),
|
||||
);
|
||||
const entry = raw.find((s) => s.subject === bobsObject);
|
||||
// The deposit is proven ARRIVED by its only possible effect: nobody but Alice
|
||||
// reads that inbox, so an entry for Bob's object means his deposit crossed the
|
||||
// identity boundary and her session found it. That the post did not throw is a
|
||||
// weaker claim entirely — it says the call returned, and nothing more.
|
||||
check(
|
||||
"a stranger's deposit into the index's inbox reached its owner and became an entry",
|
||||
entry !== undefined,
|
||||
`from=${BOB} subjects=${JSON.stringify(raw.map((s) => s.subject))}`,
|
||||
);
|
||||
check(
|
||||
"the entry is stored under Bob's object's own reference as its subject",
|
||||
(entry?.props[ENTRY_VALUE] ?? []).includes("2026-08-17T09:00:00Z"),
|
||||
entry !== undefined && Object.hasOwn(entry.props, ENTRY_VALUE),
|
||||
`subjects=${JSON.stringify(raw.map((s) => s.subject))}`,
|
||||
);
|
||||
// The value never travelled: a deposit is the reference and nothing else, so its
|
||||
// presence here means the curation read it off Bob's object itself.
|
||||
check(
|
||||
"the indexed value was read off Bob's object, and never travelled in his deposit",
|
||||
(entry?.props[ENTRY_VALUE] ?? []).includes("2026-08-17T09:00:00Z"),
|
||||
`entry=${JSON.stringify(entry?.props ?? {})}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -446,16 +441,16 @@ async function main(): Promise<void> {
|
||||
|
||||
// Deposits are never retired, so every run sees every deposit again. Convergence
|
||||
// is what makes that affordable.
|
||||
const again = await step("Alice curating a second time", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate((i) => window.__indexing.curate(i), index!),
|
||||
await step("Alice connecting a second time", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate(() => window.__indexing.reconnect()),
|
||||
);
|
||||
const still = await step("Alice reading the index again", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate((i) => window.__indexing.read(i), index!),
|
||||
);
|
||||
check(
|
||||
"curating a second time changes nothing, and the index still holds one entry",
|
||||
again.outcomes.every((o) => o.result === "unchanged") && still.length === 1,
|
||||
`outcomes=${JSON.stringify(again.outcomes)} entries=${still.length}`,
|
||||
"connecting a second time changes nothing, and the index still holds one entry",
|
||||
still.length === 1 && still[0]?.object === bobsObject,
|
||||
`entries=${JSON.stringify(still)}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -478,22 +473,16 @@ async function main(): Promise<void> {
|
||||
bob!.frame.evaluate((o) => window.__indexing.referConfigured(o), other),
|
||||
);
|
||||
|
||||
const report = await step("Alice curating the unrelated reference", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate((i) => window.__indexing.curate(i), index!),
|
||||
);
|
||||
const forOther = report.outcomes.find((o) => "object" in o && o.object === other);
|
||||
check(
|
||||
"curation reports it skipped for want of the field, rather than indexed",
|
||||
forOther?.result === "skipped" && forOther.reason === "no-field",
|
||||
`outcome=${JSON.stringify(forOther)}`,
|
||||
await step("Alice connecting after the unrelated reference", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate(() => window.__indexing.reconnect()),
|
||||
);
|
||||
|
||||
const entries = await step("Alice reading the index once more", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate((i) => window.__indexing.read(i), index!),
|
||||
);
|
||||
check(
|
||||
"the index still holds exactly one entry",
|
||||
entries.length === 1,
|
||||
"the unrelated object is not indexed, and the index still holds exactly one entry",
|
||||
entries.length === 1 && !entries.some((e) => e.object === other),
|
||||
`entries=${JSON.stringify(entries)}`,
|
||||
);
|
||||
},
|
||||
@@ -593,8 +582,8 @@ async function main(): Promise<void> {
|
||||
await step("Bob depositing the hostile reference", BRIDGE_MS, () =>
|
||||
bob!.frame.evaluate((o) => window.__indexing.referConfigured(o), object),
|
||||
);
|
||||
await step("Alice curating the hostile reference", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate((i) => window.__indexing.curate(i), index!),
|
||||
await step("Alice connecting after the hostile reference", BRIDGE_MS, () =>
|
||||
alice!.frame.evaluate(() => window.__indexing.reconnect()),
|
||||
);
|
||||
|
||||
// Read the index document RAW: it must still declare its own field. An injected
|
||||
|
||||
Reference in New Issue
Block a user