Consumer-side of the @ng-eventually/client fidelity pass, plus the identifier UX:
- Identity: the user types an IDENTIFIER at the access barrier (AccessGateScreen),
in the same act that opens the shared wallet — the separate 'pick a username'
screen (ConnexionScreen) is removed. The identifier is a technical id (a pseudo
in practice, not a Festipod username), normalized (trim, @-stripped, lowercased)
and persisted before the broker redirect, then handed to the SDK as the identity.
AccountContext keeps its API but its stored value is now this normalized id.
- Relationship/connections are app-owned: new src/shared/utils/connections.ts holds
the bilateral registry and maps each link to the SDK's directed grantRead(doc,
grantee); the lib no longer carries a connection concept. Rewired FestipodData
and the @data harness to it.
- Login removed: accounts use the SDK's IdentityStore (set/clear/get); no faux
login/logout framing in the SDK boundary.
Doctrine reconciled: app-security (knowledge_authentication flow, knowledge_trust-model
directed grants, decision_2026-07-06_identifier-at-access-barrier), data-layer
(knowledge_context-internals: stable id principal + single-seed), app-architecture
(knowledge_screens auth inventory), bdd-testing (caveat_wallet-bloat-hang).
App gates: tsc no new errors, build OK. @data path unaffected (harness bypasses the
gate and sets identity directly; login() is not on that path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Multiple compounding defects kept the connected @data read at 0 entities:
- writeEntity/updateEntityField and registration helpers wrote into an explicit
GRAPH <plainNuri> named graph, invisible to the anchored default-graph read
(read-model.readDoc) after the read switched to per-doc anchored. Drop the
wrapper so writes land in the repo's default graph (matches the read).
- Seed entities are now owned by the CURRENT account, so protected seed docs
(user profiles) pass the per-document ReadCap gate and round-trip.
- Suppress the double seed (explicit loadTestData + 3s dev auto-seed) and add a
re-list signal so freshly-seeded protected docs enter the read set.
- @data step awaits the seed result and waits for events AND users > 0.
Documents the anchored-default-graph write pitfall in rule_document-per-entity.
Validated: connexion-nextgraph.feature @data = 4 scenarios / 13 steps green.
NB: the shared test wallet's private store bloats across runs and makes anchored
queries hang (>15s); a fresh .playwright-profile restores ~1.5s — durable wallet
hygiene is a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Read each by-need entity document with its own anchored query (bounded to the
current account's virtual wallet), never an anchorless scan of the physical shared
wallet. The 75s ORM hang stays gone; a non-empty PHYSICAL wallet now costs nothing
(never scanned). Removed the throwaway anchorless-union probe.
Known remaining (test-infra, not the product): the @data suite still times out
because THIS test account's VIRTUAL wallet is bloated (hundreds of docs
accumulated across this session's many runs) → per-doc reads are O(my docs), and
`clearWallet` still enumerates all accounts. Needs per-scenario test isolation
(fresh/small virtual wallet) + a virtual-wallet-scoped clear to validate green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the reactive-ORM per-entity fan-out read (which HUNG 75s: orm_start_graph
opened every scope graph and RepoNotFound on any fresh/unsynced doc aborted the
subscription) with the read model:
- readEntities.ts → lib readUnion: resolve the by-need doc set (my own scope docs
via listMyEntityDocs + public events via the discovery index — NOT all-accounts
fan-out), then ONE anchorless union sparql_query (GRAPH ?g, VALUES-pinned). Map
to app types. Re-query on a change signal (no reactive union query).
- countUserParticipations no longer fans out over all accounts (own docs only).
- await loadTestData in the seed step; deleted orphaned useShapeWithDefaults;
removed the old multistore-stopgap fan-out scenarios; added the read-model-probe.
- Doctrine: rule_document-per-entity read half + _overview rewritten to the union
model (write half unchanged).
Result: the 75s ORM hang is ELIMINATED (0 hangs; build/tsc/lib-93-tests green;
boundary clean). @data is NOT yet fully green: remaining failures are 90s step
timeouts in the test-harness broker data ops (clearWallet / runUnionProbe / seed)
this run — a harness/broker-op issue, not the read path. To finish separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@data oscillated 15-20/21 because the persistent test wallet accumulated data
across scenarios, growing the read fan-out. Add a cheap per-scenario reset
(resetDataState): a single SPARQL DELETE on the shim anchor graph clears the
account records, so allAccounts() collapses and the fan-out is bounded to what
the current scenario re-provisions (accounts recreated lazily). O(1) on one
graph — not a fan-out delete (which saturated the browser before). Called in the
@data Before hook, time-boxed so it can't starve the broker login budget.
Test-infra only — product model, boundary and app read path untouched.
Note: not yet re-measured to stable-green — the broker was degraded during the
bounded validation window (DNS/timeout flakiness). To re-measure when the broker
is stable. knowledge_data-layer-broker updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-document isolation refactor (one doc per entity) broke every @data
round-trip against the real broker (0 events readable) — fake-ng unit tests
missed it. Root causes + fixes:
- ngSet.add cannot write to an empty subscription scope ("Set is readonly
because scope is empty") → write each entity DIRECTLY into its own document via
SPARQL (new data/entityWrites.ts: writeEntity/updateEntityField), typing each
field with the correct RDF term per the SHEX shape (else the ORM drops the
entity on read). Reactive set stays read-only; the doc NURI is registered into
useShape({graphs}) for reactive reads.
- Current principal made STABLE and username-derived (urn:festipod:user:<name>),
available immediately at login and invariant — so a Participation's mandatory
fp:user is never empty and identity/cap-owner/connections all key on the same
value.
- Discovery deposits AS the current identity (harness sets current user first).
- Idempotence/deregistration checks made authoritative against the broker;
participantCount persisted via SPARQL. rule_document-per-entity enriched with
these write/read + stable-principal lessons.
Round-trip restored (seed readable, inscription+notif, persistent deregistration,
public discovery all pass in isolation). NOT yet stably green as a full suite:
@data oscillates 15–20/21 — residual failures are environmental (participation-
read fan-out lag on an accumulating persistent test wallet), same class as the
Chromium saturation; not a logic bug. Durable fix (follow-up): non-fan-out
materialized read + per-scenario test-wallet isolation. app build+tsc + lib 89
tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Festipod now follows the correct SDK logic: each entity (event, participation,
profile, notification) is created as its OWN document in its scope
(rule_document-per-entity), via the SDK create call — the store-root write path
and the FESTIPOD_MULTISTORE flag are gone. Reads subscribe the per-entity docs
with instant visibility on create; seed/bootstrap rewritten per-entity.
Removed all app-side access logic: utils/isolation.ts (applyIsolation) deleted.
The app only declares its identity (login) and its own bilateral connections
(sharing act), reads via the SDK, and trusts it — no access filtering in the app.
This makes the SDK's per-document ReadCap the sole, real isolation.
Unit-proven in the lib (89 tests). @data/@e2e validation deferred: the NextGraph
broker is unreachable — to be re-run in T03.d. Follow-up: unify app connection
principals (user IRI) onto the username key used by the SDK's cap owner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On creating a public event, Festipod submits it to the discovery index (an SDK
call); the discovery screen reads the index instead of enumerating accounts. The
app knows nothing of the index's owner, inbox, or materialization — it treats the
lib as a finished SDK whose discovery is a global index. No store ids.
Unit-validated in the lib (79 tests). @data broker validation deferred: the
NextGraph broker (nextgraph.net/eu) was unreachable at run time — to be re-run
in T03.d once the broker recovers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Festipod performs the domain acts that make isolation real: AccountContext
declares the current identity at login/change; FestipodDataContext declares its
connections (friendships) to the data SDK. Reads then discriminate by scope
through the SDK (private→owner, protected→owner+connections, public→all) — no
app-side filtering, no store ids, no awareness that isolation is emulated. New
@data scenario proves an unconnected account can't read another's protected
entity but can after connecting; public stays visible. @data 21/21.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consume the shim mechanics now living in the lib (docs/storeRegistry/
isolation/accounts) and remove the remaining direct @ng-org runtime imports.
- storeRegistry.ts keeps ONLY the Festipod EntityKind/entityScope mapping,
injects it via configureStoreRegistry({ getSession, normalizeUser }), and
re-exports the lib's storeRegistry.* (callers unchanged). Drops
`import { ng } from '@ng-org/web'`.
- harness-ng.tsx createSmokeDoc now uses docs.docCreate (real injected ng,
no DataCloneError) instead of ng.doc_create. Drops the @ng-org import.
- AccountContext.tsx thin React wrapper over accounts.AccountStore +
normalizeUsername; historical key `festipod.account.username` pinned →
zero behavior change. Context/Provider stay in the app.
- isolation.ts Festipod wrapper over the lib's pure isolation.applyIsolation.
Invariant reached: `grep "from '@ng-org'" src/ | grep -v 'import type'` lists
only ngSession (the configure injection point) + the two documented
test-harness exceptions (auth-setup.tsx, harness.tsx mock). No doc_create
goes through the lib's public proxy. App build + harness-ng bundle OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full-suite validation of the merge surfaced 4 failures, all multistore: routing
doc_create through the lib's `ng` proxy (685f6d3) breaks @ng-org/web's iframe
postMessage marshaling — DataCloneError "function could not be cloned" (a JS
Proxy over the iframe-RPC proxy = double proxy).
Fix: storeRegistry.ts and harness-ng.tsx (createSmokeDoc) call doc_create /
SPARQL on the real @ng-org/web `ng` directly again. useShape / init / login /
ReadCap still route through the lib. After the fix the 3 multistore scenarios
pass; full suite = 77 passed, 0 merge regressions.
Integration boundary documented in decision_2026-06-17: the in-app shim's
low-level NextGraph calls stay on the real SDK until storeRegistry moves INTO
the lib (where it would use the injected real ng, no double proxy). Lib TODO:
expose a doc_create/SPARQL primitive that uses the injected ng.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings 266e335 (staging shared wallet: file-assisted import + multi-browser
e2e) into the ng-eventually branch. Conflicts resolved so both lines of work
coexist and route through the lib where they overlap:
- harness-ng.tsx: combine ReadCap FilterProbe (ours) with main's SmokeProbe/
FanoutProbe; useShape + ng imported from @ng-eventually/client.
- ngSession.ts (auto): our single-injection-point configure() + main's hidden
logoutNg, which uses the lib's ng.
- useShapeWithDefaults.ts (auto): lib useShape + main's { graphs } multistore
scope.
- cucumber.json: single "tags": "not @wip" (both branches added it).
- brief_2026-06-15_shared-wallet-shim: keep main's implemented status; record
that the read filter now lives in the lib (decision_2026-06-17) while the
rest of the shim (storeRegistry/accounts/isolation) is still in-app, slated
to move into the lib.
Build OK; harness-ng bundles. TODO (next): verify all of main's NextGraph
surface routes through @ng-eventually/client (storeRegistry uses ng directly).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stopgap staging multi-user sur wallet partagé (cf. brief_2026-06-15_shared-wallet-shim).
Distribution / import du wallet :
- AccessGateScreen : barrière d'accès ON PAR DÉFAUT (désactivable via
globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ pour tests/dev). Fournit le FICHIER
.ngw + le mot de passe + un guide en 3 étapes (import assisté sur nextgraph.eu —
le broker hébergé n'autorise pas l'import inline pendant l'auth web-app).
- sharedWallet.ts + build.ts : fichier copié en /shared-wallet.ngw, mot de passe gravé.
- Ancien LoginScreen (/login) retiré ; atterrissage post-login -> /home.
- NextGraphContext : dé-piégeage de l'état "connecting" au retour (pageshow/bfcache).
Couche multistore stopgap : storeRegistry, isolation, AccountContext, FestipodDataContext.
Tests e2e multi-navigateur :
- browserPool + world.openBrowser : contextes frais isolés, 2 axes orthogonaux
(nb de navigateurs × modèle de wallet own/shared).
- @humain : parcours humain complet (télécharge -> importe le fichier sur
nextgraph.eu -> Entrer -> pseudo -> accueil).
- Bypass de la barrière pour @e2e via context.addInitScript.
- Convention @wip exclue via cucumber.json.
Docs (concepts) : nextgraph-platform (knowledge_broker-import-constraint,
decision_2026-06-17_assisted-wallet-import), bdd-testing (knowledge_multibrowser-harness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align Festipod's @data read-filter scenario and harness bridge with
ng-eventually's grant→ReadCap refactor: the access unit is the document
(an item's `@graph`), not the item.
- harness-ng.tsx: governDocument(reader, user)/setUser via getCaps()/resetCaps()
(replaces setupReadFilter/setGrantOf); FilterProbe exposes a lazy snapshot()
reflecting the current user without remount.
- read-filter.feature/steps: validate per-document ReadCap on the real
DeepSignalSet — govern the wallet document, grant the cap to another user
→ current user sees 0; current user gets the cap → sees all (all-or-nothing
in mono-store, the faithful behavior). 5/5 steps pass against the broker.
- doctrine: knowledge_stores-permissions records the verified store/document/
repo/ReadCap model (containment by reference, no read-cap inheritance);
decision_2026-06-17_eventually-library updates the access-rights + filter
status to the ReadCap model.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a @data scenario (workshop/read-filter) that enables the lib's read filter on the
real reactive ORM set (via a FilterProbe + setupReadFilter harness helper, granting each
participation to its own user) and asserts useShape returns only the target user's
participations. Validates the trickiest piece — filtering a live DeepSignalSet — against the
broker. @data 9/9. Doc: read filter marked implemented & validated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The app now takes its NextGraph runtime AND types from @ng-eventually/client; the
only place that imports the real @ng-org SDK is ngSession (the single injection point for
configure()). Lifecycle (init/initNg), data (useShape) and types (ShapeType, DeepSignalSet,
NG…) all go through the lib. Test infra (auth-setup, mock harness) and generated ORM
bindings keep a direct @ng-org import (documented). Validated: build, @ui 4/4, @data 8/8
against the real broker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reactive ORM data-plane now goes through the @ng-eventually/client wrapper
instead of @ng-org/orm directly; ngSession injects the real SDK into the polyfill via
configure(). Currently a transparent passthrough (lib mechanisms still stubbed) →
behavior unchanged. Validated: build, @ui 4/4, @data 8/8 against the real broker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Port modern clean theme (DM Sans, orange accent, app-* CSS classes)
and screen redesigns from festipod-mockups; replace sketchy Ubuntu
theme. New shared components: BottomNav, EventCover, EventMeetingPoints,
Toast, AvatarStack, Tag, RelevanceIcon.
- Restructure from prototyping shell to real mobile web app:
path-based routing (History API), Gallery/DemoMode/PhoneFrame removed,
Storybook setup for screen/component browsing.
- ConnectScreen ported from mockup (QR-based user connection); routed
at /profile/connect, wired from FriendsListScreen.
- Dev-only auto-seed of NG wallet when empty
(gated on NODE_ENV !== 'production'); bootstrapWallet already
self-checks for non-empty ngSet so safe even in race conditions.
- Render-based @ui test infrastructure: happy-dom + LocalDataProvider +
RouterProvider via src/shared/test-harness/renderHelper.tsx, exposed
on the world as renderedDoc. world.hasText/hasField/hasElement prefer
the rendered DOM and fall back to source for backward compatibility.
- Migrate 25 brittle @ui assertions from regex-on-source to DOM
queries; delete implementation-detail tests (showDuplicateWarning,
importableEvents, importedFrom — anti-patterns per the new contract).
Update feature files where the UI changed: "Mes amis" → "Mon réseau",
"Mes événements à venir" → "À venir" on home, Thématique removed
from create-event wizard, etc.
- Path-based @e2e steps (pushState + popstate dispatch) replacing the
legacy "#/demo/…" hash routing tied to the deleted Gallery.
- Add .project/knowledge/test-layer-contracts.md defining the role of
each test layer (@ui = display with seed data + DOM, @data = mutations
through NG broker, @e2e = critical user journeys) with anti-patterns
and migration consequences.
Test status: 75 passed / 71 skipped (explicit "non implémenté")
/ 2 failed (pre-existing @wip on ngSet.delete() NG ORM limitation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Writes (doc_create, orm_frontend_update) failed with RepoNotFound because
useShape with did:ng:i scope doesn't open individual repos in the verifier's
cache. Switched to did🆖${session.private_store_id} as both scope and
@graph, matching the expense-tracker-rdf pattern. This opens the private
store repo via orm_start_graph, making it available for subsequent writes.
Also adds wallet login step to ensureAuth so the verifier bootstraps repos
from the remote broker into localStorage on first run.
Key changes:
- useShapeWithDefaults accepts storeNuri param (private store NURI)
- FestipodDataContext.useNgData() passes private store scope
- ensureGraphNuri() simplified: reuse existing @graph or private_store_id
- ngBootstrap uses ensureGraphNuri + flushAndWait between ORM adds
- harness-ng.tsx uses private store scope for test bridge shapes
- hooks.ts: wallet creation logs in to bootstrap verifier repos
- E2e steps for data loading and persistence verification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add @e2e test layer: real app in broker iframe via Playwright
- Fix broker redirect: conditional auto-init only when inside iframe
- Fix seed data flash: empty data during 'connecting' phase
- Fix Gallery button in iframe: explicit navigate instead of history.back
- Add auth e2e feature scenarios and step definitions
- Update docs: bdd-testing, data-layer-testing, data-layer, AGENTS.md
- Add decision record for conditional NG init approach
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename steps/frontend/ to steps/ui/ across all modules and shared
- Add data-layer test harness (mock + real broker modes) with Playwright
- Add inscription data-layer steps (@data scenarios)
- Add test auth setup script and browser debug script
- Update docs (architecture, BDD testing, data-layer testing)
- Add ADR for headless wallet creation decision
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>