Close the fixed blocker, and record what withdrawal converging faster does NOT mean

Six variations against the real application, one thing varied at a time: the
sign-up-breaks-the-next-connection defect is gone. New identity, immediate
reload or delayed; an identity already used for several cycles; and the very
identity poisoned by the pre-fix code, which now reconnects cleanly three times
out of three. So nothing needed healing -- the fix does not merely stop writing
the bad state, it makes what was already written harmless. The leaf is deleted
rather than graduated: its reproduction method, the stale-server trap it led to,
and "a provider gap is not worked around here" all live elsewhere already.

The count defect stays open, with its shape now known: it does converge across
connections, but takes one more than expected -- the first reconnect still shows
zero, the second shows the value.

And a correction worth keeping. The explanation offered for why withdrawal
converges in a single connection -- that it writes straight to the owner's
document instead of depositing -- is contradicted by the code: it deposits its
own marker into the same inbox, drained by the same owner routine, "symmetric"
by the code's own comment. The asymmetry is real and recorded as verified; its
cause is recorded as unknown rather than filled with a plausible story. Same
mechanism, same inbox, two connections against one, is a lead worth having
honestly.

The dev-server caveat is upgraded from inferred to verified, and it is worse
than it read: an edit to application source triggers a genuine rebuild, new
bundle hash and all, and that rebuild still carries the stale dependency. Only a
restart works, and "touch a file to force a rebuild" is now explicitly ruled out
as a substitute.
This commit is contained in:
Sylvain Duchesne
2026-08-16 22:40:08 +02:00
parent ac55dc96a4
commit ff26f26e60
7 changed files with 28 additions and 63 deletions
+2 -3
View File
@@ -27,10 +27,9 @@ 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
## Open defects — the sign-up flow does not deliver an honest count
- [[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
- [[bug_participant-count-stays-at-zero]] — the count stays at 0 through the session that signs up and through the first reconnect, only catching up on the second
## Pitfalls (read before touching deletions / event fields)
@@ -3,29 +3,36 @@ 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.
summary: After a sign-up the count stays at 0 for the rest of the session and through the first reconnect — it only reaches the true value on the SECOND reconnect, one connection later than expected. Withdrawal converges in a single connection, for a reason not yet established.
---
# The participant count does not converge in the same session
# The participant count takes one connection more than expected to converge
## 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.
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 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 does not move within the session — and, now measured, not on the reconnect that immediately follows it either.
**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
## The convergence, now measured, VERIFIED
**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.
The count does converge, but **one connection later than expected**: the first reconnect after the sign-up still reads 0; the count only reaches the true value on the **second** reconnect.
**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.
INFERRED: the first reconnect is the one that drains the deposit and writes the counter, and the write's own value shows up only on the load that follows it, not on the load that triggered it.
**Withdrawal, by contrast, converges in a single connection — VERIFIED.** Why it converges one connection sooner than sign-up does is NOT established: `leaveEvent` deposits its own marker (`depositLeave`) into the same inbox, processed by the same owner materializer as a join ([[knowledge_context-internals]] §participantCount), so the asymmetry is not explained by withdrawal skipping the deposit — that explanation does not survive a look at how `leaveEvent` is wired. Record the one-connection gap as open rather than reach for a mechanism.
## What is still open
Which side is at fault for the extra connection is still 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.
## 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.
3. Stay on the page and watch the count for a couple of minutes — it stays at 0.
4. Reconnect once — still 0. Reconnect a second time — now correct.
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.
Method: `bdd-testing` → [[cookbook_live-probe]]. Watching *after* the confirmation, over a real interval, and across two reconnects, is what makes this visible at all — every individual step reports success.
@@ -1,43 +0,0 @@
---
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]].
@@ -27,4 +27,4 @@ The app has **two modes**, both consumed through the `useFestipodData()` hook:
> 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".
> **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 does not show the user what happened: the count stays at 0 through the session and the first reconnect, only catching up on the second [[bug_participant-count-stays-at-zero]]. Do not read the paragraph above as "signing up works".