The data layer is now reached through one pulled, version-pinned engagement
(`.project/concepts/data-layer/contract_polyfill-surface.md`, @1ecf511e9d).
That copy is the only reference: the provider's sources are never opened, and
what the contract does not answer is a gap raised with it, never worked around
here.
Surface
- `@ng-eventually/sdk` -> `@ng-eventually/polyfill`, one entry point.
- `configure` loses `getSession`, `normalizeId`, `currentUser`; the session
belongs to the package and its own `init` captures it.
- Placement is named by scope alone -- a session is one user, so the app no
longer passes an identity it had no way to obtain. This removes a constant
that made every user collide on one owner's document.
- `init(...)` then `await ensureIdentity()`, in that order, as one sequence:
React runs child effects first, so the two calls sat in the wrong order and
the contract now makes that throw.
- `sessionId` relayed as `string | number`, `materialize` -> `read`.
A rejection means "unknown", never "absent"
Four places treated a caught error as an empty result. The worst wrote a
duplicate participation: an unknown count read as zero defeated the idempotence
guard of `joinEvent`. Also fixed: a per-document count, a silently dropped
notification shown optimistically anyway, and a failed listing that left the
owned-event set empty and disabled the materializer for the whole session.
Shared identity is not a Festipod notion
A browser context is one user. The per-scenario identity plant is deleted at
its source and its five sites; what stays is the deployment's wallet file,
which the contract requires an application to serve.
Documentation
The doctrine no longer describes how the data layer works underneath: five
leaves whose subject was internals are gone, a dozen more are re-founded on the
contract's own words, and two frozen arbitrations about a deleted screen were
removed rather than left to mislead a future session.
Test harness
It can sign in at last: cucumber runs under node, which does not load `.env`,
so the harness never received the wallet material and every scenario silently
fell back to an empty local mode. A failed sign-in is now loud on both sides.
The suite also releases what it opens and exits on its own -- runs were still
resident hours after reporting, holding a browser and two servers.
Known red: `@data` cannot be measured. The served wallet accumulates and
nothing resets it; moving the browser profile aside does not, since the data
lives in the wallet file, not the profile.
Convention du projet pour la documentation projet. Traduction fidèle, sans
changement de fond : mêmes fiches, mêmes sections, mêmes liens. Le lint est
identique à la baseline (59 leaves, 0 nouveau lien cassé, wikilinks bit-à-bit
inchangés) et aucun `.feature` n'a été touché.
Le `summary:` du frontmatter est traduit lui aussi — c'est ce que le hook affiche
dans l'index, il porte autant que le corps.
RESTENT EN FRANÇAIS, délibérément :
- les fichiers .feature (convention explicite du projet : Etant donné/Quand/Alors)
et le bloc Gherkin cité dans brief_2026-07-06 ;
- les libellés d'interface cités en prose (« Entrer », « ✓ Je participe »,
« Voir tous les participants », « participant inconnu »…) : ce sont des chaînes
réelles de l'app, pas de la prose ;
- les noms de scénarios BDD ;
- les `triggers.keywords` des _overview : jetons de matching du hook, et la
conversation reste en français — les traduire aurait cassé la livraison.
EFFET SECONDAIRE UTILE : relire intégralement a fait remonter des contradictions
et des péremptions que personne ne voyait section par section. Notées, non
corrigées (hors périmètre de la traduction) :
- rule_document-per-entity décrit la lecture via readEntities/readUnion/
registerDoc/bumpRead, que rule_app-uses-sdk-surface-only déclare SUPPRIMÉS au
profit de watchShape/useShapeQuery. Une règle qui décrit des APIs retirées est
activement trompeuse — à traiter en priorité.
- brief_2026-07-06 §P3 réaffirme « prouvé par l'e2e D.2, sans reload » juste
après l'encadré qui déclare cette phrase fausse et sur-cadrée.
- knowledge_data-modes cite useShapeWithDefaults() là où useShapeQuery est
documenté ailleurs.
- knowledge_stack-and-commands : build:orm pointe ./src/shapes/* alors que les
shapes vivent sous src/shared/shapes/.
- knowledge_screens / knowledge_routing : ConnectScreen décrit comme absent du
registre mais présent en route.
- brief_2026-05-18 : « identifié si connecté » était ambigu en français (session
ouverte vs est une connexion) ; l'anglais a forcé à trancher — rendu par
« if a connection », à confirmer côté produit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
Dette accumulée depuis le 13/07 (27 marqueurs). Au-delà du vidage, trois
corrections de doctrine réellement fausse — c'est ce que le reconcile devait
attraper :
- app-security : `sharedWallet.ts` capture le mot de passe à l'ÉVALUATION du
module. Tant qu'un repli existait, un global posé trop tard ne faisait que
dégrader ; depuis que le wallet partagé est l'unique mode, il rend la barrière
INUTILISABLE (écran d'erreur, aucun champ). Conséquence non anticipée de la
décision shared-wallet-only → nouveau caveat.
- bdd-testing : la doctrine rendait des tests faux-verts. `ctx.newPage()` sur le
profil persistant relit l'IndexedDB local et ne prouve JAMAIS la durabilité
broker ; seul un contexte partagé neuf tranche. Un agent suivant la doctrine
écrivait un test qui passe sans rien vérifier → nouveau caveat.
- app-architecture : `knowledge_routing` décrivait encore une route `/login`
disparue, et `knowledge_screen-pattern` citait `LoginScreen` qui n'existe
plus. Nouveau caveat sur les deux espaces d'id vus depuis un écran.
Aussi : data-layer/knowledge_context-internals décrit la jointure
participation→profil et corrige un mécanisme de changement d'identité périmé ;
tech-stack raccroche la table des scripts au vrai point d'entrée cucumber ;
functional-domain note qu'« implémenté » ≠ « durable ».
Trois marqueurs soldés comme sans objet : ils visaient
`reconnexion-socket-mort.{feature,steps.ts}`, absents de l'arbre ET de tout
l'historique — expérience abandonnée avant tout commit. Ce qu'elle devait
établir est capturé ailleurs (caveat de durabilité, post-mortem polyfill, fiche
INBOX socket-death).
Liens morts vers une décision disparue avec le concept `nextgraph-platform`
réparés. Reste au lint : le brief 07-06 (superseded) porte des file:line et des
références aux internes NextGraph — laissé intact, il décrit l'Option-B encore
implémentée et se dissoudra à la graduation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
caveat_write-durability-across-disconnect + decision_2026-07-20 (wallet partagé = seul mode ; identifiant ≠ username profil) + amendement bun-first. Marqueurs _debt.md inclus (voyagent avec la branche, à réconcilier avant push).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
Deux corrections produit/tests demandées, empiriquement validées au broker réel.
1. Créateur ≠ hôte (décision produit). Il n'y a PAS de notion d'hôte : un
événement est public, simplement signalé par le créateur, qui n'est PAS
obligé de participer. `createEvent` n'écrit plus de participation-hôte et
`participantCount` démarre à 0 ; le matérialiseur du propriétaire dérive
`participantCount = |inscriptions actives|` (plus de base « +1 hôte »).
2. Isolation deux-identités : le trou réel était l'ABSENCE d'un test de
régression, pas un bug de code actif. Reproduction empirique (DIAG instrumenté,
retiré) : la fuite n'apparaît QUE si le reset `useEffect([username])` est
désactivé ET les caps vides (docs persistés d'une session antérieure sur wallet
gonflé) — le reset en place la neutralise. La sighting live venait d'un état
wallet pré-fix + identifiant réutilisé. Ajout du test permanent manquant :
- isolation-deux-identites.feature (@data) : A crée+rejoint E, une identité
fraîche B sur le même wallet ne voit E ni sur son accueil, ni via
isParticipating(E,B), et ne lit aucune participation portant le principal de A.
- us-13 : « Le créateur ne participe pas automatiquement » (count 0,
isParticipating false autoritatif, puis join→1, leave→0).
Harness: 4 helpers permanents (switchIdentity, currentIdentifier, homeEventTitles,
currentParticipations) pour piloter/observer l'identité en test.
Scénarios @multibrowser/us-7 réalignés (compteur 0→1 au lieu de 1→2).
Doctrine mise à jour (context-internals, actors-and-concepts).
Gates: build OK, tsc propre, @data verts (inscription, désinscription,
idempotence, compteur dérivé, auth ×4), lib @ng-eventually/client non touchée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
Enforce the boundary in code-comments and doctrine (adversarial-review cleanup):
- App comments in the data plane no longer narrate the SDK's internals: "emulated
curator"→"the inbox read", "fan-out"→"discovered", removed store-placement
reasoning and "polyfill/shim/mono-store" wording (FestipodDataContext,
registration, storeRegistry, ngSession, AccountContext, isolation, sharedWallet,
AccessGateScreen). Executable logic unchanged.
- Removed dangling references to the dissolved `nextgraph-platform` concept and
`brief_2026-06-15_shared-wallet-shim` from app code.
- knowledge_nextgraph-stack: dropped "mécanique d'émulation" from the boundary note.
- Settled and deleted all concept _debt.md (confirmatory; target leaves clean).
(Test-infra under workshop/ + generated features.ts still carry some simulation
vocabulary — parked as a separate below-SDK decision.)
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>
Enforce the project boundary: Festipod is written as if NextGraph were a mature,
finished SDK; @ng-eventually/client IS that SDK. NO current-NextGraph-state,
simulation, polyfill, shim, mono-store, store-id or broker-internal knowledge
remains in this repo — it now lives in the @ng-eventually/client repo.
- Dissolved the `nextgraph-platform` concept entirely (12 leaves — all
current-state/simulation, now in the lib's docs/). Rescued the genuine domain
parts into functional-domain/knowledge_data-scopes-and-discovery.md (which
entity → which scope; product-level discovery/notification intent), framed as
SDK usage with no mechanism.
- data-layer re-anchored to "how Festipod persists via the SDK": stripped
mono-store/private_store_id/RepoNotFound/DataCloneError/FESTIPOD_MULTISTORE.
Deleted the current-SDK compensation leaves (private-store-scope, multistore,
the 2026-03-17 ADRs, conditional-ng-init). Kept/reworded the domain + app
leaves; caveat_participation-deletion reduced to the domain contract.
- app-security reworded (isolation delegated to the SDK; app trusts it).
- AGENTS.md: dropped the nextgraph-platform row, reworded data-layer/
functional-domain/app-security, added the "Frontière SDK NextGraph" note.
- Fixed dangling [[links]]; concept lint clean (43 leaves).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- data-layer/caveat_multistore-is-multi-document (new): the recurring store vs
document confusion. Two axes — (A) which native store, (B) documents within a
store. FESTIPOD_MULTISTORE toggles axis B (multi-document), not multi-store.
Isolation (ReadCap) is per-document. As of T02.h the default path writes
shareable entities to the real protected store (axis A, step 1).
- rule_private-store-scope: rewritten — shareable entities now scope/@graph the
protected store; private anchors the shim/inbox + settings; "never did:ng:i"
kept. decision_2026-03-17 marked partially superseded.
- knowledge_stores-permissions: ⚠️ store↔document callout.
- knowledge_entities: MeetingPoint/Notification now persisted (not local-only).
- nextgraph-platform: decision_2026-06-17 records the emulated inbox; fork-inbox
brief marked short-circuited; discovery-model divergence (shipped fan-out vs
global-index target) flagged for confirmation.
- functional-domain/knowledge_roadmap, bdd-testing leaves updated. All doc-debt
settled; lint clean (60 leaves).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>