Commit Graph

15 Commits

Author SHA1 Message Date
Sylvain Duchesne 5e91771da6 fix(client): résolution de compte barrière-autoritative — fin du fork à la reconnexion
Bug: à la reconnexion, resolveAccount lisait le shim depuis le store-root
(did🆖${privateStoreId}), NON abonnable → pas de barrière first-State → un "0 rows"
à froid est ambigu → le retry (resolveAccountReliably/provisionRetry) échoue → nouveau
compte provisionné → FORK → données du compte invisibles.

Cause NextGraph (vérifiée nextgraph-rs): "trouvable-sans-lookup" (store-root) et
"abonnable" (did:ng:o:<RepoID aléatoire>) sont DISJOINTS — pas de doc à la fois
devinable et attendable → une résolution shim purement barrière est impossible.

Fix (indirection pointeur → doc-shim abonnable):
- Les AccountRecord migrent dans un doc-shim doc_create'd (did:ng:o:..., a une barrière).
- Un pointeur écrit-une-fois dans le store-root (<shim:root> <shim:shimDoc> <docShim>)
  le nomme. resolveShimDoc lit le pointeur → ensureRepoOpen(docShim) [barrière] → lecture
  de compte AUTORITATIVE (cold 0 = absent pour de vrai). Retry de compte SUPPRIMÉ.
- Micro-garde résiduel (pointerGuard, ex-provisionRetry) sur le SEUL triple pointeur
  écrit-une-fois; ne peut jamais forker un compte; fork de pointeur réconcilié au
  doc-shim canonique (lexicographiquement-min), sans perte.
- Migration: migrateLegacyRecords copie (pas déplace) les comptes de l'ancien store-root
  vers le doc-shim avant toute conclusion "absent"; idempotent; wallet neuf → no-op.

Tests: unit 128/128, e2e réel 42/42 (CONTRACT 2 = non-fork du compte à la reconnexion),
red-before/green-after prouvé. Docs: nextgraph-current-state (antagonisme + indirection),
simulation, migration-guide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 17:46:16 +02:00
Sylvain Duchesne f7dd9223e0 docs(read-model): distinguish the GRAPH ?g scan from a GRAPH <D> write
Append to the probe section: the anchorless GRAPH ?g SCAN spans every named graph
(O(wallet) union — the reason reads are per-doc anchored, preserved) is distinct
from a constant anchored GRAPH <D> WRITE, which round-trips to the same repo (no
phantom graph, verified by packages/client/e2e/). Re-verify via the harness if the
broker version changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 09:39:42 +02:00
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
Sylvain Duchesne 5717e08f6d docs: "what is emulated (and how it goes away)" recap table + read-path reconcile
README: new section with a recap table (11 rows) — for each emulated behavior:
what the consumer sees (SDK-shaped API), how it's emulated on one shared wallet,
the real NextGraph target, and the lib-only migration. Makes "emulated ≠ real,
migration is a lib-only swap" explicit.
simulation.md: opening banner that EVERYTHING in the file is emulation pending
real NextGraph; corrected the stale read-path paragraph (per-doc anchored, never
an anchorless union-scan). read-model.md: reactivity bullet aligned to per-doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 11:18:10 +02:00
Sylvain Duchesne b15229fbd5 docs: explicit virtual-wallet structure — the 3 emulated stores
Add a diagram + prose to simulation.md: a virtual wallet = one shim account keyed
by a virtual-wallet id; it has the 3 native stores (public/protected/private) but
EMULATED — each "store" is an index document (AccountRecord.docPublic/Protected/
Private) listing that scope's per-entity doc NURIs. Everything physical lives in
the ONE shared wallet's private store; the 3-store structure is the per-account
logical layer. At migration the index docs become real native stores.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:15:06 +02:00
Sylvain Duchesne ac2b026955 feat(client): read via per-doc ANCHORED queries; document virtual vs physical wallet
The anchorless union query (`GRAPH ?g`) scanned EVERY named graph in the local
store (the whole shared physical wallet) → O(wallet size), slow/timeouts on a
bloated wallet. Rewrite `readUnion` to run ONE ANCHORED `sparql_query` per by-need
doc (in parallel, per-doc tolerant): an anchored query is restricted to that
repo's graph, so it is O(1) per doc, INDEPENDENT of physical-wallet size. Keep the
ReadCap defense-in-depth gate.

docs/simulation.md: new "Physical wallet vs virtual wallet" section — the physical
shared wallet is a substrate that accumulates and must NEVER be enumerated/scanned;
each user's VIRTUAL wallet (the account's scope index in the shim) is the bounded
thing you enumerate ("list my documents"), then read those docs per-doc anchored.
read-model.md / nextgraph-current-state.md updated to the per-doc anchored rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 22:50:14 +02:00
Sylvain Duchesne 6a3501e700 feat(client): union-read ReadCap gate + listMyEntityDocs + doc corrections
- read-model.ts `readUnion` now applies the emulated ReadCap gate (drops a
  subject when its doc is governsRead && !canRead for the current identity), so
  per-scope isolation holds by construction AND by filter.
- store-registry: `listMyEntityDocs(username, scope)` (current account only) vs
  the all-accounts `listEntityDocs` fallback (documented as the enumeration to
  avoid on the read path).
- docs: nextgraph-current-state / read-model — corrected to the SOURCE-VERIFIED
  reality that the JS SDK exposes NO open/sync-by-cap primitive
  (load_repo_from_read_cap is pub(crate)); in the mono-wallet all repos are
  already local (same session), so the anchorless union spans them with no open
  step. simulation.md: listEntityDocs+useShape({graphs}) is a fallback, not the
  read path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:49:01 +02:00
Sylvain Duchesne e24f52749f feat(client): read-model surface — open/sync + anchorless union sparql_query
Proven against the real broker (probe): opening docs then a single anchorless
`sparql_query` with a `GRAPH ?g { ... }` body reads the LOCAL UNION of all synced
named graphs — the fast, hang-free replacement for the reactive-ORM per-document
fan-out (which aborted on any unsynced/fresh repo → 75s never-fires). New
`read-model.ts` (readUnion) exposes this; there is no reactive union query, so
listing is one-shot and consumers re-query on change. docs/read-model.md refined
with the probe finding (an explicit `GRAPH ?g` body iterates all named graphs
regardless of the anchor; the anchor only bounds the default graph). 93 tests
pass; tsc rc=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 18:20:09 +02:00
Sylvain Duchesne 5acc07a7e3 docs: query capability (local-union sparql_query) + the read model
Source-verified against nextgraph-rs:
- nextgraph-current-state.md: NextGraph keeps ONE local oxigraph store per
  session; each synced repo is a named graph. sparql_query with NO anchor
  (UserSite/None) queries the UNION of all synced graphs (set_default_graph_as
  _union); with an anchor it is restricted to one repo. Union is read-only
  (updates need a doc anchor). No reactive SPARQL (one-shot). Root cause of the
  ORM fan-out hang: orm_start_graph opens every graph in scope; a fresh/unsynced
  per-entity doc → RepoNotFound aborts the subscription → the 75s never-fires.
- read-model.md (new): the read model — events via the global index (the one
  enumeration hack); everything else by following a shared graph, opened/synced,
  then listed via a single anchorless union sparql_query (never the ORM per-doc
  fan-out); reactivity via re-query on a doc_subscribe/ORM change signal. Plus
  the minimal broker probe to confirm the union behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:32:22 +02:00
Sylvain Duchesne b1d06b68b9 fix(client): per-entity write round-trip + dedicated inbox anchor
Real-broker validation of the per-document model surfaced round-trip breaks the
fake-ng unit tests missed:
- The inbox anchor was the shim graph itself, making loadShim ~60s; give the
  inbox its own document so anchor resolution is fast and isolated.
- store-registry adjustments so per-entity documents created via the SDK are
  indexed and readable back through the scope fan-out.
docs/simulation.md updated. 89 tests pass; tsc rc=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 17:26:31 +02:00
Sylvain Duchesne bf753770b8 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>
2026-07-04 10:40:44 +02:00
Sylvain Duchesne 9951cd5223 feat(client): discovery via a global index (special @index account)
Add a generic discovery-index surface: submitToIndex(ref) deposits a reference
into the index document's inbox; readIndex() returns the materialized entries. A
reserved special account (@index) owns the index document; deposits flow through
the emulated inbox and are materialized by the emulated curator (the dedup/
moderation point). This replaces cross-account fan-out as the discovery path and
is more faithful to the target (a single owned index fed via its inbox). Generic
(the consumer supplies the reference to index). 79 tests pass; tsc rc=0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 09:33:42 +02:00
Sylvain Duchesne 7db5eef33f feat(client): activate ReadCap isolation via current identity + connections
Isolation was dormant (no current identity ever set). Now: setCurrentUser
records who is reading; declareConnections(neighborsOf) grants each protected
document's read cap to owner + connections. Reads discriminate through the
ReadCap filter: private→owner, protected→owner+connections, public→all. Generic
(the consumer injects identity + connections). Write-guard coverage limits
documented honestly in docs/simulation.md (real write paths bypass the JS proxy;
full enforcement awaits native caps). isolation-active.test.ts proves the
protected+connections path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:59:27 +02:00
Sylvain Duchesne e0d88b5076 feat(client): SDK-shaped scope resolvers (resolveScopeGraph/resolveInboxAnchor)
Expose a clean scope-based surface so consumers work by scope (public/protected/
private) and never see a physical store id — the library resolves placement and
performs the shared-wallet simulation internally. RegistrySession gains optional
protected/public store ids, supplied at the single injection point
(configureStoreRegistry). Zero domain knowledge. docs/simulation.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:41:34 +02:00
Sylvain Duchesne bea9f51d91 docs: own the current-NextGraph-state knowledge + boundary (lib side)
This library presents a mature-NextGraph SDK face to consumers while
compensating for the current SDK's gaps via a shared-wallet simulation. It
therefore OWNS all current-state + simulation knowledge — moved here out of the
Festipod app repo, which must treat this library as a finished SDK.

New docs/:
- nextgraph-current-state.md — what the current SDK/broker do and don't expose
  (5 store types, document=repo, per-document ReadCap, inbox not exposed, iframe
  RPC proxy, mono-user/no-global-data, wallet import constraint). Keeps the
  nextgraph-rs source pointers.
- simulation.md — how the lib emulates the mature behaviour on one shared wallet
  (shim, store!=document two axes, docCreate→private store, RepoNotFound scope
  rule, @ng-org double-proxy DataCloneError, emulated ReadCap/inbox/curator).
- decisions/ — the current-SDK ADRs (private-store-nuri-scope, sparql-delete,
  shared-wallet-login, discovery mechanism).
- fork-inbox-fallback.md — the Rust-patch/self-host route not taken.
- migration-guide.md — the checklist for when real NextGraph matures.

README: boundary framing from the lib's side + docs/ index; replaced the stale
"scaffold/stubbed" status with the actually-implemented mechanisms per source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:23:23 +02:00