feat(data): discover public events via the global index (not fan-out)

On creating a public event, Festipod submits it to the discovery index (an SDK
call); the discovery screen reads the index instead of enumerating accounts. The
app knows nothing of the index's owner, inbox, or materialization — it treats the
lib as a finished SDK whose discovery is a global index. No store ids.

Unit-validated in the lib (79 tests). @data broker validation deferred: the
NextGraph broker (nextgraph.net/eu) was unreachable at run time — to be re-run
in T03.d once the broker recovers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-07-04 09:33:42 +02:00
parent 337a1e000d
commit a436c3bd79
7 changed files with 150 additions and 42 deletions
+23 -8
View File
@@ -361,29 +361,44 @@ function ConnectedHarness() {
return { docA, docB, listed };
},
// --- Public discovery cross-accounts (T02.e) -----------------------
// --- Public discovery via the GLOBAL INDEX (T03.c) -----------------
// Product-level scenario: a PUBLISHER account creates its own PUBLIC
// event document (createEntityDoc → makePublic via caps.open); a
// separate, NON-connected DISCOVERER account then materializes the
// cross-account public source (allAccounts → listEntityDocs('public'))
// and reads the event via a real useShape({graphs}). No friendship/
// connection is ever declared between them — discovery is by the public
// fan-out alone. Returns the publisher's doc + the discovered index.
// event document and SUBMITS its reference to the SDK global discovery
// index (submitEventToIndex). A separate, NON-connected DISCOVERER
// account then READS THE INDEX (readDiscoveredEvents) — deposit →
// materialize → read — and subscribes to the referenced document via a
// real useShape({graphs}). No friendship/connection is ever declared
// between them, and NO cross-account fan-out is used — discovery goes
// through the index alone. Returns the publisher's doc + the index refs.
// <FanoutProbe> is reused to mount the multi-graph subscription; the
// event is written into the publisher doc before the reader lists it.
async publishPublicEventAs(publisher: string, title: string) {
const reg = await import('../utils/storeRegistry');
const disc = await import('../data/discovery');
reg.resetRegistryCache();
await reg.ensureAccount(publisher);
const doc = await reg.createEntityDoc(publisher, 'public');
// Make it discoverable: submit the event reference to the global index.
await disc.submitEventToIndex({ doc, id: doc, title }, publisher);
return { doc };
},
async discoverPublicEventsAs(discoverer: string) {
const reg = await import('../utils/storeRegistry');
const disc = await import('../data/discovery');
// The discoverer account exists but is NOT connected to the publisher.
await reg.ensureAccount(discoverer);
reg.resetRegistryCache();
const listed = await reg.listEntityDocs('public'); // cross-account
// Read the GLOBAL INDEX (not a cross-account fan-out) to discover. The
// submit deposit needs a moment to land in the broker's queryable graph
// (same lag as any inbox deposit), so poll the index (bounded) until an
// entry appears before mounting the multi-graph subscription.
let listed: string[] = [];
for (let i = 0; i < 20 && listed.length === 0; i++) {
const refs = await disc.readDiscoveredEvents();
listed = [...new Set(refs.map(r => r.doc).filter(Boolean))];
if (listed.length) break;
await new Promise(r => setTimeout(r, 250));
}
setFanoutGraphs(listed);
return { listed };
},