From b25bafbb900eadf10404632b765e9b5694056467 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Thu, 1 Oct 2026 16:38:16 +0200 Subject: [PATCH] Correct what the contract says about the participant count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit wrote a requirement into types.ts that is false: that the count needs an inbox, and that a CRUD-only backend therefore cannot carry the model. That came from misreading the brief's "no filtering" as "the readable participations are not all of them". Every Participation is readable by everyone. What is protected is the link to a person — only a connection can read the profile one points at and recognise who it is. So the count is obtained by counting an event's participations, which is ordinary CRUD and asks nothing of a backend. Left standing, because the measurements did establish it: the count must not be an incremented field. Reading a stored counter out of a list, adding one and writing it back derives each write from a read of the previous one, which races itself — and that mutated counter is the one the project's brief supersedes. The ng-org backend stays withdrawn, on the reason that holds on its own: it is not the target, the NextGraph+polyfill provider is. --- src/shared/refine/providers/index.ts | 31 +++++++++++++---------- src/shared/refine/providers/types.ts | 38 ++++++++++++++-------------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/shared/refine/providers/index.ts b/src/shared/refine/providers/index.ts index 1ceb7ae..849b7dd 100644 --- a/src/shared/refine/providers/index.ts +++ b/src/shared/refine/providers/index.ts @@ -7,21 +7,24 @@ * selection below, `RefineProvider`'s runtime switch. What is missing is backends. * * WHY THE FIRST ONE WAS WITHDRAWN. `@ng-org/refine-providers` was wired and - * measured, and it works as plain CRUD over SHEX shapes. But Festipod's sign-up - * model needs more than CRUD: `participantCount` counts participants this session - * cannot see, which the project derives from inbox deposits recomputed by the - * event's owner — see the requirement in `types.ts`. A counter incremented from a - * value read back out of a list is a different mechanism, it races itself, and the - * project's own brief declares it superseded. Rather than harden a counter on a - * backend that structurally cannot carry the model, the wiring was removed; the - * worked implementation is in this branch's history (`providers/nextgraph.ts`, - * up to the commit that withdrew it) and is the reference for the shape of the - * next one. + * measured, and it works as plain CRUD over SHEX shapes. It was withdrawn because + * it is not the target: what is coming is a Refine provider over NextGraph *plus + * the polyfill*, and hardening this one meanwhile buys nothing. * - * WHAT COMES NEXT, and why the seam stays: a Refine provider over NextGraph *plus - * the polyfill*, which does publish inboxes, and `@activitypods/refine-providers`. - * Festipod has to support both, so the structure is kept and only the entry is - * gone. + * NOT because it was incapable — an earlier version of this comment said so, and + * was wrong. It blamed the absence of an inbox for the participant count, having + * misread the model: every Participation is readable by everyone, only the link + * to a person is protected, so counting them is ordinary CRUD. See `types.ts`. + * What the measurements did establish is narrower and still worth knowing: the + * count must not be an incremented field, because each write would derive from a + * read of the previous one and race itself. + * + * The worked implementation is in this branch's history (`providers/nextgraph.ts`, + * up to the commit that withdrew it) and is the reference for the next one's shape. + * + * WHAT COMES NEXT, and why the seam stays: that NextGraph+polyfill provider, and + * `@activitypods/refine-providers`. Festipod has to support both, so the structure + * is kept and only the entry is gone. * * ADDING ONE is a file implementing `RefineBackend` plus a line here. What that * file must answer is in `types.ts` and cannot be guessed: how its providers are diff --git a/src/shared/refine/providers/types.ts b/src/shared/refine/providers/types.ts index 7af642b..eae4ab9 100644 --- a/src/shared/refine/providers/types.ts +++ b/src/shared/refine/providers/types.ts @@ -112,28 +112,28 @@ export interface RefineBackend { } /** - * WHAT A BACKEND MUST BE ABLE TO DO, beyond CRUD — recorded here because the - * first backend tried could NOT, and that is why it was withdrawn. + * HOW THE PARTICIPANT COUNT IS OBTAINED — a domain rule, not a backend capability. * - * `participantCount` is **the number of participants, including those this - * session cannot see**. So it is not a count of the participations we can read: - * filtering by what we are allowed to see would under-report it. The project's - * model (concept `data-layer`, brief_2026-07-20_attendance-set-model, and - * knowledge_context-internals) derives it instead: + * `participantCount` is the number of participants, and every Participation is + * READABLE BY EVERYONE: what is protected is the link to a person, not the + * record. Only a connection can read the profile a participation points at and + * recognise who it is. So the count is obtained by COUNTING the participations of + * an event — no filtering by visibility, because none is needed (concept + * `data-layer`, brief_2026-07-20_attendance-set-model). * - * - whoever signs up writes their OWN participation, then DEPOSITS a marker - * into the event's inbox; - * - the event OWNER's session watches the inboxes of its own events and - * RECOMPUTES the count from the deposits, deduped, minus the cancelled ones. - * It is the field's only writer, and the count is `|active set|`. + * This means NO backend capability is required for it: counting records is CRUD. + * An earlier version of this comment claimed the opposite — that an inbox was + * needed, and that a CRUD-only backend therefore could not carry the model. That + * was a misreading of the brief's "no filtering", and it is wrong. The inbox on + * the sibling branch is how a sign-up is DELIVERED to the event's owner so the + * owner can maintain the aggregate; it is one implementation of the count, not a + * precondition for having one. * - * Two consequences for this contract. A backend offering only CRUD over records - * — no inbox, no per-document delivery — cannot support it: incrementing a - * counter read back from a list is a different mechanism, it races itself, and - * the project's brief declares it superseded. And a backend that CAN must say so, - * because the app's sign-up path differs accordingly. - * - * Until a backend provides it, `participantCount` is whatever the record carries. + * What IS a rule, and what the race measured here proved: the count must not be + * an INCREMENTED field. Reading a stored counter out of a list, adding one and + * writing it back derives each write from a read of the previous one, which + * races itself through any data provider — and the project's brief declares that + * mutated counter superseded. */ /** Every resource name a backend serves, derived from its bindings. */