Files
ng-eventually/packages/client/test/caps.test.ts
T
Sylvain Duchesne 63ecfeeff8 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>
2026-07-06 14:02:16 +02:00

84 lines
3.8 KiB
TypeScript

import { test, expect } from "bun:test";
import { CapRegistry } from "../src/caps";
test("public documents are readable by anyone, even anonymous", () => {
const caps = new CapRegistry();
caps.open("did:ng:o:pub", "public", "alice");
expect(caps.canRead("did:ng:o:pub", null)).toBe(true);
expect(caps.canRead("did:ng:o:pub", "bob")).toBe(true);
});
test("protected documents: owner + explicitly granted principals only", () => {
const caps = new CapRegistry();
caps.open("did:ng:o:prot", "protected", "alice");
expect(caps.canRead("did:ng:o:prot", "alice")).toBe(true);
expect(caps.canRead("did:ng:o:prot", "bob")).toBe(false);
caps.grantRead("did:ng:o:prot", "bob"); // a directed grant issues bob the read cap
expect(caps.canRead("did:ng:o:prot", "bob")).toBe(true);
});
test("private documents: owner only", () => {
const caps = new CapRegistry();
caps.open("did:ng:o:priv", "private", "alice");
expect(caps.canRead("did:ng:o:priv", "alice")).toBe(true);
expect(caps.canRead("did:ng:o:priv", "bob")).toBe(false);
expect(caps.canRead("did:ng:o:priv", null)).toBe(false);
});
test("protectedDocsOf surfaces an owner's protected documents for directed grants", () => {
const caps = new CapRegistry();
caps.open("did:ng:o:prot1", "protected", "alice");
caps.open("did:ng:o:prot2", "protected", "alice");
caps.open("did:ng:o:pub", "public", "alice"); // not protected → excluded
caps.open("did:ng:o:priv", "private", "alice"); // not protected → excluded
caps.open("did:ng:o:bob", "protected", "bob"); // other owner → excluded
expect(caps.protectedDocsOf("alice").sort()).toEqual([
"did:ng:o:prot1",
"did:ng:o:prot2",
]);
expect(caps.protectedDocsOf("bob")).toEqual(["did:ng:o:bob"]);
expect(caps.protectedDocsOf("carol")).toEqual([]);
// A directed grant on one of them makes the reader read that doc only.
caps.grantRead("did:ng:o:prot1", "carol");
expect(caps.canRead("did:ng:o:prot1", "carol")).toBe(true);
expect(caps.canRead("did:ng:o:prot2", "carol")).toBe(false);
});
test("write is restricted to write-cap holders; the creator always holds it", () => {
const caps = new CapRegistry();
caps.open("did:ng:o:pub", "public", "alice");
expect(caps.canWrite("did:ng:o:pub", "alice")).toBe(true);
expect(caps.canWrite("did:ng:o:pub", "bob")).toBe(false);
expect(caps.canWrite("did:ng:o:pub", null)).toBe(false);
});
test("holding a document's cap does NOT grant another document (no inheritance)", () => {
const caps = new CapRegistry();
caps.grantRead("did:ng:o:doc1", "alice");
expect(caps.canRead("did:ng:o:doc1", "alice")).toBe(true);
expect(caps.canRead("did:ng:o:doc2", "alice")).toBe(false); // separate repo, separate cap
});
test("governsRead / hasReadPolicy distinguish governed from ungoverned documents", () => {
const caps = new CapRegistry();
expect(caps.hasReadPolicy()).toBe(false);
caps.grantRead("did:ng:o:doc1", "alice");
expect(caps.hasReadPolicy()).toBe(true);
expect(caps.governsRead("did:ng:o:doc1")).toBe(true);
expect(caps.governsRead("did:ng:o:unknown")).toBe(false); // not declared → not enforced
});
test("governsWrite / hasWritePolicy distinguish governed from ungoverned documents", () => {
const caps = new CapRegistry();
expect(caps.hasWritePolicy()).toBe(false);
caps.open("did:ng:o:doc1", "private", "alice"); // owner gets the write cap
expect(caps.hasWritePolicy()).toBe(true);
expect(caps.governsWrite("did:ng:o:doc1")).toBe(true);
expect(caps.governsWrite("did:ng:o:unknown")).toBe(false); // not declared → not enforced
// A public doc grants read to all but its write cap is still owner-only.
const pub = new CapRegistry();
pub.open("did:ng:o:pub", "public", "alice");
expect(pub.hasWritePolicy()).toBe(true);
expect(pub.governsWrite("did:ng:o:pub")).toBe(true);
});