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
+28 -37
View File
@@ -1,8 +1,7 @@
import { test, expect } from "bun:test";
import {
AccountStore,
browserAccountStore,
normalizeUsername,
IdentityStore,
browserIdentityStore,
ACCOUNT_STORAGE_KEY,
type AccountStorage,
} from "../src/accounts";
@@ -18,47 +17,39 @@ function fakeStorage(): AccountStorage & { map: Map<string, string> } {
};
}
test("normalizeUsername: strips leading @, trims, lowercases", () => {
expect(normalizeUsername("marie")).toBe("marie");
expect(normalizeUsername("@Marie")).toBe("marie");
expect(normalizeUsername(" @@MARIE ")).toBe("marie");
expect(normalizeUsername(null)).toBe("");
expect(normalizeUsername(undefined)).toBe("");
});
test("AccountStore: login persists a trimmed username, get reads it back", () => {
test("IdentityStore: set persists a trimmed id, get reads it back", () => {
const s = fakeStorage();
const store = new AccountStore(s);
const store = new IdentityStore(s);
expect(store.get()).toBeNull();
expect(store.login(" Marie ")).toBe("Marie"); // trimmed, NOT normalized (display form)
expect(store.get()).toBe("Marie");
expect(s.map.get(ACCOUNT_STORAGE_KEY)).toBe("Marie");
expect(store.set(" marie ")).toBe("marie"); // trimmed
expect(store.get()).toBe("marie");
expect(s.map.get(ACCOUNT_STORAGE_KEY)).toBe("marie");
});
test("AccountStore: blank login is ignored, keeps the previous value", () => {
const store = new AccountStore(fakeStorage());
store.login("bob");
expect(store.login(" ")).toBe("bob");
test("IdentityStore: a blank id is ignored, keeps the previous value", () => {
const store = new IdentityStore(fakeStorage());
store.set("bob");
expect(store.set(" ")).toBe("bob");
expect(store.get()).toBe("bob");
});
test("AccountStore: logout clears the username (no throw)", () => {
const store = new AccountStore(fakeStorage());
store.login("bob");
store.logout();
test("IdentityStore: clear removes the id (no throw)", () => {
const store = new IdentityStore(fakeStorage());
store.set("bob");
store.clear();
expect(store.get()).toBeNull();
});
test("AccountStore: null storage degrades to non-persisting (SSR-safe)", () => {
const store = new AccountStore(null);
test("IdentityStore: null storage degrades to non-persisting (SSR-safe)", () => {
const store = new IdentityStore(null);
expect(store.get()).toBeNull();
expect(store.login("bob")).toBe("bob"); // returns the value, just doesn't persist
expect(store.set("bob")).toBe("bob"); // returns the value, just doesn't persist
expect(store.get()).toBeNull();
store.logout(); // no throw
store.clear(); // no throw
});
test("AccountStore: swallows storage errors on read and write", () => {
test("IdentityStore: swallows storage errors on read and write", () => {
const throwing: AccountStorage = {
getItem: () => {
throw new Error("boom");
@@ -70,15 +61,15 @@ test("AccountStore: swallows storage errors on read and write", () => {
throw new Error("boom");
},
};
const store = new AccountStore(throwing);
const store = new IdentityStore(throwing);
expect(store.get()).toBeNull(); // read error swallowed → null
expect(() => store.login("bob")).not.toThrow();
expect(() => store.logout()).not.toThrow();
expect(() => store.set("bob")).not.toThrow();
expect(() => store.clear()).not.toThrow();
});
test("browserAccountStore returns a working store (uses global localStorage if present)", () => {
const store = browserAccountStore("ng-eventually.test.account");
expect(store).toBeInstanceOf(AccountStore);
// Behaves regardless of environment: login returns the value.
expect(store.login("zoe")).toBe("zoe");
test("browserIdentityStore returns a working store (uses global localStorage if present)", () => {
const store = browserIdentityStore("ng-eventually.test.account");
expect(store).toBeInstanceOf(IdentityStore);
// Behaves regardless of environment: set returns the value.
expect(store.set("zoe")).toBe("zoe");
});