Record what running the flow proved, and fix the leaf that caused one defect
The create-and-participate flow was driven in a real browser for the first time. It had been called correct by construction -- typecheck, build, reading -- and the probe found three defects none of those could see. Two open bugs, both major, both filed rather than worked around: signing up to your own event makes the NEXT connection fail outright (`ensureIdentity()` rejects inside the data layer's own inbox processing, 3/3, reproduced on a fresh origin and identity), and the participant count does not converge in the same session (2/2, 120 s and 75 s). Whether it converges at the next connection is recorded as UNKNOWN and unmeasurable, because the first bug stops the app from getting there. The doctrine defect is the one worth the trouble. `knowledge_build-pipeline` said production builds into `dist/`; `knowledge_deployment` said the container runs `bun run start` from `src/`. Both were written down, they contradicted each other, and the code followed the wrong one -- which is how a deployed app that could sign nobody in was shipped. The three paths now live in one table whose discriminating column is what is actually served: dev `src/`, production `src/`, and `dist/` served by nothing at all. A build artefact nobody serves is a trap for the next reader who assumes otherwise. The probe method itself is written down: the suite cannot run, a targeted probe can, and the difference is worth knowing before concluding that nothing is measurable. Stated once where a reader meets it: honest steps do not add up to an honest flow. Every gesture in the sign-up reports correctly, and the user is still told they participate while the count never moves and the next connection fails.
This commit is contained in:
@@ -27,6 +27,11 @@ How Festipod **persists its data** through NextGraph (P2P, local-first, end-to-e
|
||||
- [[rule_document-per-entity]] — every entity gets **its own document** (per scope), never one at store level; access is granted per document, so this is what makes isolation possible
|
||||
- [[rule_app-uses-sdk-surface-only]] — the pulled contract is the only reference; a gap in it is raised with the provider, never worked around here
|
||||
|
||||
## Open defects — the sign-up flow does not deliver
|
||||
|
||||
- [[bug_signup-breaks-the-next-connection]] — after a sign-up, the next connection fails; a **provider-side gap**, raised with the provider, nothing to work around here
|
||||
- [[bug_participant-count-stays-at-zero]] — the count never moves in the session that signs up; whether it converges later is **unknown**, and unmeasurable while the bug above holds
|
||||
|
||||
## Pitfalls (read before touching deletions / event fields)
|
||||
|
||||
- [[caveat_participation-deletion]] — withdrawal must be **authoritative** and must not come back
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
type: bug
|
||||
severity: major
|
||||
opened: 2026-08-16
|
||||
last_checked: 2026-08-16
|
||||
summary: After a sign-up the count stays at 0 for the rest of the session while the button reads « ✓ Je participe » — observed with the event's owner (the counter's only writer) present and connected. Whether it converges at the next connection is UNKNOWN, not known-good.
|
||||
---
|
||||
|
||||
# The participant count does not converge in the same session
|
||||
|
||||
## What happens, VERIFIED
|
||||
|
||||
In the create-and-participate flow — declare an event, sign up to it — the event's `participantCount` **stays at 0 for the rest of the session** while the button reads « ✓ Je participe ». **VERIFIED 2 runs out of 2**, the count still 0 **120 s** and **75 s** after the sign-up.
|
||||
|
||||
The count starting at 0 on creation is correct and is not the defect ([[knowledge_context-internals]] §participantCount: no host baseline). The defect is that it never moves afterwards.
|
||||
|
||||
**The "owner offline" explanation does not apply.** In this flow the signer **is** the event's owner, so the counter's only writer is present, connected, and watching the inbox it deposited into. Eventual delivery to an absent owner explains nothing here.
|
||||
|
||||
## What is NOT established
|
||||
|
||||
**Whether the count converges at the next connection is UNKNOWN** — it could not be measured, because [[bug_signup-breaks-the-next-connection]] makes the next connection fail. Do not write it down as converging, and do not treat "it will settle on reload" as a known behaviour: nobody has seen a reload.
|
||||
|
||||
**Which side is at fault is also open** — never written, or written and not re-read. The pair of measurement points laid down for exactly this question (the owner's materializer logging `participantCount` before → after its write, and the display read logging the value as exposed to the render — [[knowledge_context-internals]] §logging) is where a diagnosis starts; the probe read the value as displayed and did not settle the pair.
|
||||
|
||||
## Reproduce
|
||||
|
||||
1. Connect, declare an event (the count shows 0 — correct).
|
||||
2. Sign up to it; the button reaches « ✓ Je participe ».
|
||||
3. Stay on the page and watch the count for a couple of minutes. It stays at 0.
|
||||
|
||||
Method: `bdd-testing` → [[cookbook_live-probe]]. Watching *after* the confirmation, over a real interval, is what makes this visible at all — every individual step reports success.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
type: bug
|
||||
severity: major
|
||||
opened: 2026-08-16
|
||||
last_checked: 2026-08-16
|
||||
summary: After a sign-up, the NEXT connection fails — ensureIdentity() rejects inside the data layer's own inbox processing and AuthGate renders « Connexion impossible ». A provider-side gap, raised with the provider; the app names no document to any call and has nothing to fix here.
|
||||
---
|
||||
|
||||
# Signing up locks the account out of its next connection
|
||||
|
||||
## What happens, VERIFIED
|
||||
|
||||
Drive the create-and-participate flow (declare an event, sign up to it), then reconnect. `ensureIdentity()` **rejects**, and the app renders its named error panel, « Connexion impossible ». The rejection comes from **inside the data layer's own inbox processing** — a call the app never made.
|
||||
|
||||
The message, verbatim:
|
||||
|
||||
```
|
||||
docs.sparqlQuery: refused — the connected user does not hold this document's cap.
|
||||
Naming a document does not grant access to it
|
||||
```
|
||||
|
||||
**VERIFIED 3 runs out of 3**, including one on a **brand-new origin with a brand-new identity** — so this is not accumulated state from an old wallet ([[caveat_wallet-bloat-hang]] in `bdd-testing` is a different phenomenon and does not explain it).
|
||||
|
||||
**VERIFIED**: the symptom and the sequence — a sign-up, then a failing reconnection.
|
||||
**INFERRED**: that the deposit is what causes it. The sign-up is the only thing between a connection that works and the next one that does not, but nothing observed names the failing document.
|
||||
|
||||
## Why there is nothing to work around here
|
||||
|
||||
The app deposits through the **published** `inbox.postToDocument(doc, …)` ([[contract_polyfill-surface]]) and **names no document to any call** that could refuse one — it holds no inbox address at all ([[caveat_event-fields-not-persisted]] on why the vestigial `inbox` field must stay unused). The refusal is raised by a query the data layer issues for itself while draining what it was given.
|
||||
|
||||
So this is a **provider-side gap**, raised with the provider — not an app-side problem with a clever fix ([[rule_app-uses-sdk-surface-only]] §2: a workaround is a doctrine violation even when it works). There is no app-side recovery either: a rejected `ensureIdentity()` is the contract's own instruction *not to render past it*, because a session that failed looks exactly like an account that owns nothing.
|
||||
|
||||
## Reproduce
|
||||
|
||||
1. Connect, declare an event, sign up to it (the button reaches « ✓ Je participe »).
|
||||
2. Reconnect — a new page load through the broker, same identity.
|
||||
3. The barrier resolves, then the app shows « Connexion impossible » with the message above in the console.
|
||||
|
||||
A **fresh origin and a fresh identity** is what separates this defect from accumulated wallet state; run it that way before reporting anything new about it. Method: `bdd-testing` → [[cookbook_live-probe]].
|
||||
|
||||
## Blast radius
|
||||
|
||||
Every account is one sign-up away from being locked out, and the lock-out is permanent for that identity as far as anything observed goes. It also **blocks measurement of other defects**: whether the participant count converges across connections cannot be established while this holds — [[bug_participant-count-stays-at-zero]].
|
||||
@@ -70,8 +70,10 @@ The counter is **not** incremented by whoever joins: only a document's owner wri
|
||||
|
||||
- A participant writes their **own** participation document (protected), then **deposits** a marker into the event's inbox (`depositRegistration` / `depositLeave`, `src/shared/data/registration.ts`).
|
||||
- The event **owner's** session watches the inboxes of the events it owns (`inbox.watch`, no polling) and **recomputes** `participantCount` on its own event document. It is the counter's only writer, and it reads through `inbox.readSynced` — the synced view — not `inbox.read`.
|
||||
- **Derived, not incremented**: `materializeAttendance` computes the set of distinct active sign-ups (deposits deduped by `uid`, minus those cancelled). `participantCount = |active set|`. There is **no host baseline** — an event has no host, the declarer is not required to attend, so the counter starts at **0** on creation and moves only on real sign-ups. Because it is a pure function of the inbox, a replay converges: no double count, no phantom decrement. The write is guarded so it only fires on a genuine change.
|
||||
- **Derived, not incremented**: `materializeAttendance` computes the set of distinct active sign-ups (deposits deduped by `uid`, minus those cancelled). `participantCount = |active set|`. There is **no host baseline** — an event has no host, the declarer is not required to attend, so the counter starts at **0** on creation and moves only on real sign-ups. Being a pure function of the inbox, a replay is *designed* to converge: no double count, no phantom decrement. The write is guarded so it only fires on a genuine change.
|
||||
- **Owner offline = eventual.** While the owner is disconnected the count does not move for anyone else; nothing is lost. The materializer fires directly on connection, not only on a push, and it never locks in a premature 0.
|
||||
|
||||
> ⚠️ **This section describes the design, and the design is not what a live run does.** Driven end to end with the owner present and connected — the signer *being* the owner — the count stayed at **0** for the rest of the session after a sign-up. Read the convergence properties above as intent to be re-established, not as observed behaviour: [[bug_participant-count-stays-at-zero]].
|
||||
- The counter is an **aggregate**, not the list of named participants — `getEventParticipants` is governed by what the protected scope hands back.
|
||||
|
||||
Which event a deposit belongs to is matched on the **canonical id-form** — see [[knowledge_write-rights-are-ownership]] §Matching, which governs every event-id comparison in this file.
|
||||
|
||||
@@ -26,3 +26,5 @@ The app has **two modes**, both consumed through the `useFestipodData()` hook:
|
||||
- `error` → `LocalDataProvider` with the seed (graceful fallback)
|
||||
|
||||
> Mutations are **genuinely persisted** in connected mode: `joinEvent` writes a Participation into its own document and **deposits** into the event's inbox (the deposit is the delivery — no notification is written for the host), `leaveEvent` deletes authoritatively (see [[caveat_participation-deletion]]). Both **reject** rather than returning quietly when they cannot write, and the screen's confirmation follows the write. In local/demo mode they are **no-ops that still show a success toast** — see [[knowledge_context-internals]].
|
||||
>
|
||||
> **Per-call honesty is not flow-level honesty.** Every one of those calls tells the truth about itself, and the sign-up flow driven end to end still fails — [[bug_participant-count-stays-at-zero]] and [[bug_signup-breaks-the-next-connection]]. Do not read the paragraph above as "signing up works".
|
||||
|
||||
Reference in New Issue
Block a user