feat(client): real per-document isolation + bilateral connections + deposit guards
The ReadCap filter now enforces on per-entity documents (consumers create one doc per entity, so each has a declared policy — private→owner, protected→owner+ connections, public→all). Isolation is genuinely active, not dormant. - connections.ts (new): a BILATERAL connection registry — a link grants protected read only when BOTH sides have asserted it (each assertion bound to its author). A unilateral/self-declared connection grants nothing (closes the confused-deputy hole). declareConnections is authenticated to the current identity. - inbox.post: `from` is bound to the current identity — a spoofed `from` throws. - discovery.submitToIndex: PUBLIC-ONLY — a governed non-public doc is refused (no protected/private leak into the world-readable index). - docs/simulation.md: documents this as application-level emulated isolation on a shared wallet (not crypto); at NextGraph maturity → real caps, consumer unchanged. 89 tests pass (+10 covering: active protected isolation via bilateral connect, unilateral grants nothing, from-spoof rejected, non-public submit refused). tsc rc=0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
|
||||
import * as inbox from "./inbox";
|
||||
import { ensureAccount, reservedAccount } from "./store-registry";
|
||||
import { getCaps } from "./polyfill";
|
||||
import type { Nuri, PrincipalId } from "./types";
|
||||
|
||||
/**
|
||||
@@ -66,11 +67,19 @@ export interface IndexEntry {
|
||||
/** Options for {@link submitToIndex}. */
|
||||
export interface SubmitOptions {
|
||||
/**
|
||||
* Who is submitting. Omit (or pass `null`) for an ANONYMOUS submission; pass a
|
||||
* principal id to identify the submitter. Defaults to the current polyfill user
|
||||
* when the property is entirely absent (mirrors {@link inbox.post}).
|
||||
* 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}).
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
doc?: Nuri;
|
||||
/** Optional deposit timestamp (ms epoch). Omitted → `Date.now()`. Passing it
|
||||
* keeps tests deterministic. */
|
||||
ts?: number;
|
||||
@@ -100,8 +109,23 @@ async function indexInboxNuri(): Promise<Nuri> {
|
||||
* 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`).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export async function submitToIndex(ref: unknown, opts?: SubmitOptions): Promise<void> {
|
||||
const doc = opts?.doc;
|
||||
if (doc !== undefined) {
|
||||
const caps = getCaps();
|
||||
// A governed doc is submittable ONLY if it is public (anonymous may read it).
|
||||
if (caps.governsRead(doc) && !caps.canRead(doc, null)) {
|
||||
throw new Error(
|
||||
"[ng-eventually] submitToIndex: only PUBLIC documents may be submitted to " +
|
||||
"the discovery index — a protected/private document must not be surfaced.",
|
||||
);
|
||||
}
|
||||
}
|
||||
const target = await indexInboxNuri();
|
||||
await inbox.post(target, {
|
||||
payload: ref,
|
||||
|
||||
Reference in New Issue
Block a user