Align the polyfill's surface and docs with the verified NextGraph reality and
remove application-level concepts:
- Identity is an ID, not a username: AccountRecord.id, shim predicate shim:id,
normalizeId; accounts core becomes IdentityStore (set/clear/get) — the faux
login/logout framing is gone (identity is set at wallet-import time).
- Relationship/connection is an application concept, not a platform primitive
(NextGraph has no bilateral-connection primitive: grantee is unpersisted
scaffolding, cap-send is unimplemented). Remove connections.ts; caps exposes
only a directed grantRead(doc, granteeId) + a read-only protectedDocsOf(owner).
Delete the now-dead isolation.ts social-visibility axis.
- Inbox docs: NextGraph has no separate curator — the recipient's own verifier
unseals and applies each queued sealed message inline (process_inbox);
inbox_post_link is a proposed/future API. Stop attributing the emulated
curator to the platform.
- Read isolation reframed around the outcome: no cap -> empty union read;
targeted read of an unheld repo -> RepoNotFound; cap introspection
(canRead/governsRead) is emulation-only with no NextGraph API behind it.
- read-model.md corrected: the listing path is per-doc ANCHORED default-graph
queries, never the anchorless GRAPH ?g union (that is O(wallet)); the probe
section no longer claims the opposite.
- README recap table restructured (target | current NextGraph status | current
emulation); INDEX_ACCOUNT documented as reservedAccount("index") in the
sentinel namespace; de-domained generic-layer comments; softened tone.
Consumer application (Festipod) rewired separately to own the relationship
concept and feed the lib an id. Lib gates: bun test 83 pass / 0 fail, tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.0 KiB
ADR — Discovery mechanism (inbox-fed index, fan-out)
Date: 2026-06-16 · Status: mechanism accepted; target owner undecided.
Ported here for the discovery mechanism it defines — the piece this lib
realizes (inbox.ts post/materialize/watch; store-registry.ts fan-out). The
product intent (what a consumer application should surface) is the consumer
application's concern, not this lib's; only the mechanism is recorded here.
Access is not discovery
- Access: may I read this document if I hold it? A public entity is world-readable with its NURI.
- Discovery: how do I learn it exists, in order to read it? This is the ADR's topic.
The mechanism
- A single global index, fed via its inbox. The creator does not edit the index directly: it deposits a reference into the index's inbox. The index is an owned document (public read), built up from its inbox (a watcher ingests deposits and adds entries).
- Primary discovery is that global index.
- Relational is a secondary axis, overlaid: a peer's participations, markers on the global list. It rests on existing per-item data (protected scope), with no new primitive.
The 3-stage frame
discovery → synchronization → query
- Discovery: the index gives the NURIs of the entity documents.
- Synchronization: subscribe to those documents so they replicate locally
(verifier:
self.repos+ oxigraph dataset). - Query: query what is now local (sort, limit, reactivity). SPARQL/ORM
run on the local set only (
resolve_target_for_sparqlsearchesself.repos) — you cannot query what is not loaded.
Corollary: a reactive query does not replace the index — it runs at stage 3 on the local union that stages 1-2 built. You don't sync what you didn't discover.
Why one reused mechanism
- No Group store. The index is not open-write: it is an owned document (public read) plus a native inbox (a primitive present on every document). Nobody writes the index but its owner (by ingesting inbox deposits). So the model stays "3 stores + Dialog + inboxes, no Group store."
- One mechanism, reused. The inbox + ingest watcher serve both
submitting an entity to the index and a registration/deposit in one consumer
app — same
inbox.postAPI, same handling. This is exactlyinbox.tsin this lib (post/read/materialize/watch). - Natural dedup / moderation point: the inbox → index ingest is where duplicates are detected / moderated before insertion.
Index owner — target model undecided
NextGraph apps and services are mono-user with no global data
(see ../nextgraph-current-state.md § Apps &
services), so a dedicated service with its own wallet sharing a freely-readable
index is not a NextGraph shape. The only path glimpsed for a global document is a
singleton app bound to the developer-user — not implemented, uncertain, to explore
later. This is why a global-index package is a deferred separate package in this lib
(see the top-level README).
Polyfill reality — the fan-out drift is now RESOLVED (special-account index)
The shared-wallet polyfill originally shipped a cross-account fan-out over
every account's public documents (store-registry.ts listEntityDocs('public')
/ resolveReadGraphs) — one account saw another's public entity without any
relationship to its creator. This ADR classified that per-account fan-out as a drift
to be replaced by the single global index.
That drift is now resolved in the polyfill. The inbox-fed global index of
this ADR is implemented on top of a reserved special account in the shim
(discovery.ts, INDEX_ACCOUNT = reservedAccount("index") — a sentinel-prefixed
key in the shim's reserved namespace that normalizeId can never produce, so it is
disjoint from any normalized user id, not the literal "@index") that owns the
index document while
the target owner stays undecided: submitToIndex(ref) deposits into the index
document's inbox; readIndex() ingests (dedups) the entries. The app-facing
discovery path is now "read the index", exactly as this ADR prescribes — not
the fan-out. The cross-account fan-out survives only as an internal lib
fallback (it still powers per-scope listing like resolveReadGraphs), never
the discovery route. The special account is the provisional owner; at migration
it disappears and ownership moves to the decided global-index owner (see
../migration-guide.md) with the consumer application's
surface (submitToIndex / readIndex) unchanged.
Alternatives rejected (mechanism)
- Open-write index (creator writes the index directly): required a collaborative document (Group store, SDK-blocked) and exposed the index to corruption. Replaced by inbox deposit + owner-side ingest.
- Purely relational discovery (
social_query): rejected as primary (a global list is wanted); kept as a secondary axis. - No index, direct reactive query: impossible — SPARQL is local-only (stage 3).