docs+refactor(client): fidelity pass — id identity, drop connections, no faux-login, accurate NextGraph framing
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>
This commit is contained in:
@@ -5,38 +5,36 @@
|
||||
* an opaque reference and interprets the entries it reads back.
|
||||
*
|
||||
* ── The mechanism (see docs/decisions/discovery-model.md) ─────────────────
|
||||
* Access ≠ discovery. A public entity is world-readable *with its NURI*; the
|
||||
* discovery index is how a client learns that NURI EXISTS without holding a
|
||||
* connection to its creator. There is ONE global index — an OWNED document
|
||||
* (public read), fed via ITS OWN inbox, and MATERIALIZED by a curator. Nobody
|
||||
* writes the index directly: a creator DEPOSITS a reference into the index's
|
||||
* inbox; the (emulated) curator ingests deposits into entries. Materialization
|
||||
* is the natural dedup / moderation point.
|
||||
* Access and discovery are separate concerns. A public entity is world-readable
|
||||
* with its NURI; the discovery index is how a client learns that NURI exists
|
||||
* without holding a grant to read its creator's other documents. There is one
|
||||
* global index — an owned document (public read), fed via its own inbox. A
|
||||
* creator deposits a reference into the index's inbox; reading the index folds
|
||||
* those deposits into entries, deduplicating identical references along the way.
|
||||
*
|
||||
* ── The special account (polyfill owner) ──────────────────────────────────
|
||||
* "Who owns the global index" is undecided in the target (NextGraph is
|
||||
* mono-user with no global data — see docs/nextgraph-current-state.md § Apps &
|
||||
* services; a singleton app is the only glimpsed path). So the polyfill parks
|
||||
* ownership on a RESERVED SPECIAL ACCOUNT in the shim ({@link INDEX_ACCOUNT}).
|
||||
* Its `public` scope document is the index document; deposits land in that
|
||||
* document's inbox (a stable NURI: every client opening the same shared wallet
|
||||
* resolves the same account → same document). This replaces the cross-account
|
||||
* fan-out (`store-registry.ts` `listEntityDocs`) as the app-facing discovery
|
||||
* path — the fan-out survives only as an internal fallback (see {@link readIndex}).
|
||||
* Ownership of a truly global index is undecided in the real platform, where an
|
||||
* identity's apps and services see only what that identity shares. The polyfill
|
||||
* therefore parks ownership on a reserved special account in the shim
|
||||
* ({@link INDEX_ACCOUNT}). Its `public` scope document is the index document;
|
||||
* deposits land in that document's inbox (a stable NURI: every client opening the
|
||||
* same shared wallet resolves the same account, so the same document). This is
|
||||
* the app-facing discovery path, in place of a cross-account fan-out
|
||||
* (`store-registry.ts` `listEntityDocs`), which survives only as an internal
|
||||
* fallback (see {@link readIndex}).
|
||||
*
|
||||
* ── TARGET vs POLYFILL ────────────────────────────────────────────────────
|
||||
* Target: `submitToIndex` seals a reference into the index's native inbox
|
||||
* (`inbox_post_link`) and a SEPARATE curator package materializes deposits into
|
||||
* the owned index document; `readIndex` is a query on the materialized index.
|
||||
* Here, everything is emulated in-lib on the shared wallet (deposit via
|
||||
* `inbox.post`, materialize via `inbox.read`). At migration the special account
|
||||
* disappears; ownership moves to the decided global-index owner and this module
|
||||
* points `readIndex` at the real materialized document. The consumer surface
|
||||
* (`submitToIndex` / `readIndex`) is designed to survive that swap unchanged.
|
||||
* ── Real target vs this emulation ─────────────────────────────────────────
|
||||
* The intended real shape is: `submitToIndex` seals a reference into the index
|
||||
* document's own inbox (a future `inbox_post_link`), and reading the index is a
|
||||
* query on the materialized index document. Here, everything runs in-lib on the
|
||||
* shared wallet (deposit via `inbox.post`, fold via `inbox.read`). Against real
|
||||
* NextGraph the special account gives way to the decided global-index owner and
|
||||
* `readIndex` points at that document; the consumer surface (`submitToIndex` /
|
||||
* `readIndex`) is designed to survive that change unchanged.
|
||||
*
|
||||
* All NextGraph I/O routes through `inbox.ts` (which routes through the T01.a
|
||||
* `docs` primitives, the REAL injected `ng`), so this module imports NO
|
||||
* `@ng-org` package.
|
||||
* All NextGraph I/O routes through `inbox.ts` (which routes through the `docs`
|
||||
* primitives, the real injected `ng`), so this module imports no `@ng-org`
|
||||
* package.
|
||||
*/
|
||||
|
||||
import * as inbox from "./inbox";
|
||||
@@ -45,12 +43,12 @@ import { getCaps } from "./polyfill";
|
||||
import type { Nuri, PrincipalId } from "./types";
|
||||
|
||||
/**
|
||||
* The reserved SPECIAL ACCOUNT that owns the global discovery index in the
|
||||
* polyfill. It hosts the index document but is never a real user. It lives in
|
||||
* the registry's RESERVED namespace ({@link reservedAccount}), whose key
|
||||
* `normalizeUser` can never produce — so a user named "index"/"@index" can NOT
|
||||
* hijack it (they would normalize to "index", a disjoint key). Disappears at
|
||||
* migration (see file header).
|
||||
* The reserved special account that owns the global discovery index in the
|
||||
* polyfill. It hosts the index document but is never a real identity. It lives in
|
||||
* the registry's reserved namespace ({@link reservedAccount}), whose key
|
||||
* `normalizeId` can never produce, so an id of "index"/"@index" cannot hijack it
|
||||
* (it normalizes to "index", a disjoint key). Removed against real NextGraph
|
||||
* (see file header).
|
||||
*/
|
||||
export const INDEX_ACCOUNT = reservedAccount("index");
|
||||
|
||||
@@ -68,16 +66,16 @@ export interface IndexEntry {
|
||||
export interface SubmitOptions {
|
||||
/**
|
||||
* Who is submitting. Omit for the current identity, or pass `null` for an
|
||||
* ANONYMOUS submission. `from` is BOUND to the current identity by the inbox
|
||||
* (naming another principal is rejected as a spoof — see {@link inbox.post}).
|
||||
* anonymous submission. `from` is bound to the current identity by the inbox
|
||||
* (naming another identity is rejected as a spoof — see {@link inbox.post}).
|
||||
*/
|
||||
from?: PrincipalId | null;
|
||||
/**
|
||||
* The NURI of the document being made discoverable. When given, the index
|
||||
* enforces PUBLIC-ONLY: a document under a non-public (protected/private) read
|
||||
* policy is REFUSED — the public index must never leak a governed document's
|
||||
* NURI. Omit it only for a ref with no addressable document (rare); a governed
|
||||
* doc always passes it so the guard can fire.
|
||||
* admits only a public document: one under a non-public (protected/private)
|
||||
* read policy is refused, so the world-readable index never exposes a governed
|
||||
* document's NURI. Omit it only for a ref with no addressable document (rare);
|
||||
* a governed document passes it so the guard can fire.
|
||||
*/
|
||||
doc?: Nuri;
|
||||
/** Optional deposit timestamp (ms epoch). Omitted → `Date.now()`. Passing it
|
||||
@@ -105,14 +103,15 @@ async function indexInboxNuri(): Promise<Nuri> {
|
||||
/**
|
||||
* Submit a reference to the global discovery index — the SDK act "make this
|
||||
* discoverable". Deposits `ref` into the index document's inbox via
|
||||
* {@link inbox.post}; the curator ({@link readIndex}) materializes it into an
|
||||
* entry. GENERIC: `ref` is opaque here (the consumer serializes whatever a
|
||||
* client needs to later locate the entity — e.g. an entity document NURI plus
|
||||
* discovery metadata). `from` follows the inbox convention (anonymous if `null`).
|
||||
* {@link inbox.post}; reading the index ({@link readIndex}) folds it into an
|
||||
* entry. `ref` is opaque here (the consumer serializes whatever a client needs to
|
||||
* later locate the entity — e.g. an entity document NURI plus discovery metadata).
|
||||
* `from` follows the inbox convention (anonymous when `null`).
|
||||
*
|
||||
* PUBLIC-ONLY: when `opts.doc` names the document being surfaced, a document under
|
||||
* a non-public read policy (protected/private) is REFUSED — the global index is
|
||||
* world-readable, so admitting a governed doc's NURI would leak it past its scope.
|
||||
* When `opts.doc` names the document being surfaced, a document under a
|
||||
* non-public read policy (protected/private) is refused: the global index is
|
||||
* world-readable, so admitting a governed document's NURI would expose it past
|
||||
* its scope.
|
||||
*/
|
||||
export async function submitToIndex(ref: unknown, opts?: SubmitOptions): Promise<void> {
|
||||
const doc = opts?.doc;
|
||||
@@ -135,11 +134,11 @@ export async function submitToIndex(ref: unknown, opts?: SubmitOptions): Promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Read (materialize) the global discovery index — the EMULATED CURATOR. Reads
|
||||
* every submission from the index inbox, DEDUPLICATES by serialized `ref` (the
|
||||
* materialization dedup / moderation point of the discovery model: a duplicate
|
||||
* submission surfaces once), and returns the entries sorted by `ts` ascending.
|
||||
* At migration this becomes a query on the real materialized index document.
|
||||
* Read the global discovery index. Reads every submission from the index inbox,
|
||||
* deduplicates by serialized `ref` (a duplicate submission surfaces once — the
|
||||
* discovery model's moderation point), and returns the entries sorted by `ts`
|
||||
* ascending. Against real NextGraph this becomes a query on the materialized
|
||||
* index document.
|
||||
*/
|
||||
export async function readIndex(): Promise<IndexEntry[]> {
|
||||
const target = await indexInboxNuri();
|
||||
@@ -157,9 +156,9 @@ export async function readIndex(): Promise<IndexEntry[]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the discovery index — the emulated curator's watcher. Polls
|
||||
* {@link readIndex} and fires `onEntries` whenever the index changes. Returns an
|
||||
* unsubscribe. Fires once immediately. (Deduplication is applied on each read.)
|
||||
* Watch the discovery index. Polls {@link readIndex} and fires `onEntries`
|
||||
* whenever the index changes. Returns an unsubscribe. Fires once immediately.
|
||||
* (Deduplication is applied on each read.)
|
||||
*/
|
||||
export function watchIndex(
|
||||
onEntries: (entries: IndexEntry[]) => void,
|
||||
|
||||
Reference in New Issue
Block a user