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:
Sylvain Duchesne
2026-07-06 14:02:16 +02:00
parent d39b12885a
commit 63ecfeeff8
31 changed files with 1059 additions and 1396 deletions
+14 -48
View File
@@ -11,19 +11,18 @@
import type { NgLike, UseShapeLike, PrincipalId } from "./types";
import type { RegistrySession } from "./store-registry";
import { CapRegistry } from "./caps";
import { ConnectionRegistry, bilateralConnections } from "./connections";
/**
* Consumer-injected dependencies of the storeRegistry (polyfill-era). The
* registry itself is generic (it knows only native scopes); the consumer wires
* up how to reach the shared-wallet session and how to normalize a username
* up how to reach the shared-wallet session and how to normalize an identity id
* used as the shim key. Removed at migration along with the whole shim.
*/
export interface StoreRegistryDeps {
/** Resolve the current shared-wallet session (id + private-store anchor). */
getSession: () => Promise<RegistrySession>;
/** Normalize a username for shim keying. Default: trim (identity-ish). */
normalizeUser?: (username: string) => string;
/** Normalize an identity id for shim keying. Default: trim (identity-ish). */
normalizeId?: (id: string) => string;
}
export interface EventuallyConfig {
@@ -47,9 +46,6 @@ let registryDeps: Required<StoreRegistryDeps> | null = null;
/** The emulated ReadCap/WriteCap registry. Empty until the app declares caps;
* while it has no read policy the read filter passes through (no regression). */
let caps = new CapRegistry();
/** The emulated BILATERAL connection registry. Accumulates directed assertions
* (each authored by the asserting identity); only two-sided links materialize. */
let connectionRegistry = new ConnectionRegistry();
export function configure(c: EventuallyConfig): void {
cfg = c;
@@ -70,7 +66,7 @@ export function resetConfig(): void {
}
/**
* Wire the storeRegistry's consumer-injected dependencies (session + username
* Wire the storeRegistry's consumer-injected dependencies (session + identity-id
* normalization). Must be called before any storeRegistry.* use. Separate from
* {@link configure} because it's storeRegistry-specific and, like the shim,
* disappears at migration.
@@ -78,7 +74,7 @@ export function resetConfig(): void {
export function configureStoreRegistry(deps: StoreRegistryDeps): void {
registryDeps = {
getSession: deps.getSession,
normalizeUser: deps.normalizeUser ?? ((u: string) => u.trim()),
normalizeId: deps.normalizeId ?? ((id: string) => id.trim()),
};
}
@@ -95,7 +91,12 @@ export function resetStoreRegistry(): void {
registryDeps = null;
}
/** Set the current app-level user (the polyfill's notion of "who am I"). */
/**
* Set the current identity id — who the SDK is reading/writing as. In the target
* this is the wallet user established at wallet-import time; here the consumer
* relays that id through this call so the read filter and the inbox `from` know
* who is acting. Passing `null` clears it (no identity yet, e.g. during startup).
*/
export function setCurrentUser(id: PrincipalId | null): void {
currentUser = id;
}
@@ -104,51 +105,16 @@ export function getCurrentUser(): PrincipalId | null {
return currentUser;
}
/** The emulated cap registry — the app grants/opens caps on it (as it will via
* real cap operations in the target). The read filter consults it. */
/** The emulated cap registry — the app opens a document's read policy and issues
* directed read grants on it (as it will via real cap operations in the target).
* The read filter consults it. */
export function getCaps(): CapRegistry {
return caps;
}
/**
* Declare the CURRENT identity's own connections to the SDK — the domain sharing
* act "a protected document is readable by its owner AND that owner's connections".
*
* AUTHENTICATED / BILATERAL. Each entry in `peers` is a principal the CURRENT user
* (`getCurrentUser()`, or an explicit `as`) asserts a connection to. The lib
* records that assertion as authored BY the current identity — a session can only
* ever assert its OWN side. A protected read is granted between two principals only
* when BOTH have asserted the other (a materialized two-sided link). So a reader
* who unilaterally self-declares a connection to an owner gets NOTHING: the owner
* never asserted them back. Public stays world-readable; private stays owner-only.
* Re-callable whenever the connection graph changes (additive + idempotent).
*
* SDK-shaped: the consumer passes principals only — never a document NURI, a store
* id, or the cap registry internals. `as` names the asserting identity explicitly
* (defaults to the current user); the consumer normally omits it.
*/
export function declareConnections(peers: Iterable<PrincipalId>, as?: PrincipalId): void {
const self = as ?? currentUser;
if (self) for (const peer of peers) connectionRegistry.assert(self, peer);
// Re-derive protected grants from the CURRENT bilateral view (only two-sided
// links surface as neighbours). Idempotent: grants only ever accumulate.
caps.grantReadToConnections((owner) => connectionRegistry.neighbors(owner));
}
/** @internal — the bilateral connection registry (mainly for tests / adapters). */
export function getConnectionRegistry(): ConnectionRegistry {
return connectionRegistry;
}
/** The current bilateral connection view (only two-sided links surface). */
export function getConnections() {
return bilateralConnections(connectionRegistry);
}
/** Reset all emulated caps (mainly for tests / fresh sessions). */
export function resetCaps(): void {
caps = new CapRegistry();
connectionRegistry = new ConnectionRegistry();
}
// Cap surface — polyfill-era (caps are emulated now; native at migration).