Record the public-data and rights-matrix work in the handoff, the usage declaration and the doctrine
The handoff gets the closed state of the T05 work with its commits, the new gate commands, the user decisions (attendee lists stay world-readable for now, a revoked app logs the user out, the local network is trusted), the checks kept manual, the platform defects to report upstream, the documented limits of the backend hardening and the open follow-ups. The usage declaration records that Festipod drives the app-status probe, relies on the installed field and on sending to through create, and the subscribe Forbidden friction; the discovery leaf and the local-platform cookbook match the current design and bring-up.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: The product model of confidentiality and discovery — every entity lives in the SCOPE matching who must read it; other users' events are found through a shared index, not by reading the public scope
|
||||
summary: The product model of confidentiality and discovery — every entity lives in the SCOPE matching who must read it; other users' events are found through a shared index (the Festipod backend's catalog on ActivityPods), not by reading the public scope
|
||||
---
|
||||
|
||||
# Data scopes and discovery
|
||||
|
||||
The **product** model of who sees what, and of how events are found. This is **domain**: the technical *how* is the `@ng-eventually/polyfill` data SDK's business — the app only states **the business intent**.
|
||||
The **product** model of who sees what, and of how events are found. This is **domain**: the technical *how* belongs to the data layer behind the active backend (concept `data-layer`) — the app only states **the business intent**.
|
||||
|
||||
## Three scopes per piece of data
|
||||
|
||||
@@ -21,21 +21,24 @@ Every entity is stored in the **scope** matching who must be able to read it:
|
||||
| Private profile (settings, email, preferences) | **private** | the holder alone |
|
||||
| Connection A↔B (bilateral link, + future messaging) | **dialog** | both users |
|
||||
|
||||
**Deviation on the ActivityPods backend (user decision 2026-10-12, open):** a public meeting point's attendee list — its `apods:attendees` collection, whose members are WebIDs, which embed usernames — is **world-readable**, the pod's default, instead of protected. Moving it to the protected scope is a later task; until then the end-to-end rights matrix tracks it as a KNOWN-DEVIATION line, never as a positive assertion. When the meeting point is switched private, its attendee list stops being readable anonymously with it.
|
||||
|
||||
Guiding principle: **the "public" side (meeting point, event) and the "personal" side (profile, participations, connections) coexist within a single user.** Personal information is reserved to the **network** (bilateral connections), never visible to an ordinary user.
|
||||
|
||||
- **Meeting points / events are universally public.** Any user can read them and subscribe; creating a meeting point makes you its host, declaring an event makes you its declarer (no prerequisite).
|
||||
- **The host is the sole holder of write rights** on their meeting point; the declarer has no particular right over the meeting points grafted onto their event.
|
||||
- **Bilateral connection**: `DemandeDeConnexion` (unilateral, transient) → `Connexion` (bilateral, persistent) — the latter opens access to the other person's *protected* data.
|
||||
|
||||
Festipod **places each entity in its scope**; isolation between scopes is **handled by the data SDK**, not by application code (see concept `app-security`).
|
||||
Festipod **places each entity in its scope**; isolation between scopes is **handled by the data layer** (the SDK on NextGraph; the pod's access control and the Festipod backend's public-read grants on ActivityPods), not by screen code (see concept `app-security`).
|
||||
|
||||
## Event discovery
|
||||
|
||||
Reading the `public` scope only ever returns **this session's own** public documents — there is no call that unions every user's public store (concept `data-layer`, [[contract_polyfill-surface]]). A declared event was therefore reachable by its declarer alone, which made the whole cross-user sign-up flow — the product's premise — unreachable.
|
||||
A user's own reads only ever return **their own** records — on either backend, no call unions every user's public data (concept `data-layer`). Discovering events one did not create — the product's premise — therefore goes through a **shared index**: the **primary** discovery axis. A **secondary**, relational axis stays layered on top: the connections' *protected* participations ("my friends are attending…").
|
||||
|
||||
**A user discovers events they did not create through a shared index**: an ordinary public document, indistinguishable from any other, that the app reaches by a reference it hardcodes. Anyone may deposit a reference to their event into it; only the index's owner curates those deposits into visible entries, ordered by the event's start date. This is the **primary** discovery axis, settled as [[decision_2026-08-17_discovery-through-a-shared-index]] (concept `data-layer`) — **no code consumes the index yet.** A **secondary**, relational axis stays layered on top: the connections' *protected* participations ("my friends are attending…").
|
||||
The intent is the same on both backends — every public event is findable by everyone — but the index is not:
|
||||
|
||||
Four costs come with it, accepted rather than solved: an event becomes findable only once **someone curates** the index, on no fixed schedule — curation is an operator role, not a feature that runs itself; an event declared before its document could carry the field the index reads never becomes findable through it, permanently; a withdrawn or later-corrected event **stays listed** — nothing here removes an entry, so a reader of the index must tolerate a reference that no longer resolves, or resolves to something changed; and an event's position in the list is **frozen at the moment it was curated** — correcting its date afterwards does not move it. Full mechanics and the rejected alternative: [[decision_2026-08-17_discovery-through-a-shared-index]].
|
||||
- **ActivityPods (the default backend): the Festipod backend's catalog.** An event or meeting point is public when Festipod creates it addressed to the ActivityStreams Public collection (`to: as:Public`). Such a record is readable by anyone at its URI, and the Festipod backend lists it in its catalog at once — no curation. The listing follows the record: the owner's edits update it; a deletion, or a switch to private, removes it and makes the record and its attendee list unreadable anonymously again. Only a record's owner can change its listing. Découvrir shows the user's own events plus the catalog's; opening another user's event reads that event, its meeting points and their attendee counts anonymously, by URI. Costs, accepted: a record created while the backend was not receiving the pod's notifications is never listed (no backfill), and the catalog lists meeting points too, so its own total is not an event count. Verified end to end by `bun run test:e2e-rights` (groups catalog, discovery, visibility).
|
||||
- **NextGraph (backend suspended): a curated shared-index document** — [[decision_2026-08-17_discovery-through-a-shared-index]] (concept `data-layer`), no code consumes it yet. An ordinary public document the app reaches by a reference it hardcodes; anyone may deposit a reference to their event, only the index's owner curates deposits into visible entries, ordered by start date. Its accepted costs: an event is findable only once **someone curates** the index (an operator role, no fixed schedule); an event declared before its document could carry the indexed field never becomes findable; a withdrawn or corrected event **stays listed**, so a reader must tolerate a reference that no longer resolves or resolves to something changed; an entry's position is **frozen at curation**.
|
||||
|
||||
> **Sign-up notification (product intent).** Signing up to a meeting point notifies its host: identified if the participant is one of the host's connections, **unnamed otherwise**. This "identified if known, unnamed otherwise" falls out of scope placement — the host can read the sign-up, but not the *protected* profile it points at unless they are connected. The app states the intent; it implements no filter of its own.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user