Keep sign-ups unlinkable on ActivityPods, and land the pending coherence-pass work
Participation privacy (decision_2026-10-29_participation-privacy-mechanism): a sign-up is a private Join naming the meeting point, addressed to nobody, handled by the backend on emission. Attendee lists hold member URNs hashed from (WebID, event, a secret kept in the participant's profile), so counts stay public and only contacts recognise anyone; the creator has no read right and is notified anonymously. Each person's participations collection, created by the backend at the first sign-up, is readable by their /contacts WAC group only, never world-readable. The member URN is sticky per (meeting point, participant) and every collection write is verified by reading back, so a refused write leaves the act unrecorded and retryable. The EventRef mechanism and its browser-side provisioning are gone. The backend now runs with WebACL on, as the reference apps do, on its own secure datasets festipod / settings-festipod instead of the pod provider's settings dataset; pod-notifications needs it. The compose header and the deployment leaf say how to reset a stack whose datasets predate this. The rights matrix covers the new mechanism live (148 hard lines), including a forged contact Accept between throwaway accounts: the stranger enters the contacts list (platform defect, reported upstream) but is refused the profile and the participations. Tests may attack as long as they never break the dev environment (rule_tests-never-break-the-dev-environment). Also lands the coherence-pass work that was still uncommitted: contacts as the Pod's social graph and invite links, image upload to the Pod, the meeting-point form, profile completion gate, and the doctrine for them. Superseded briefs and decisions are deleted rather than annotated.
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Signed in on a Pod, the app is held behind ProfileCompletionGate — a profile with an empty name shows the profile form in onboarding mode, a Pod with no readable profile shows an explanatory message; the only completion flag is the presence of a name
|
||||
---
|
||||
|
||||
# Profile completion gate
|
||||
|
||||
`FestipodDataContextValue.profileStatus` (`'loading' | 'ready' | 'missing'`, absent on providers where it does not apply) is computed by the refine provider from the actor document and the profile read. `ProfileCompletionGate` (in the app shell, inside the data provider) uses it:
|
||||
|
||||
- `loading` → nothing is rendered yet;
|
||||
- `missing` → a blocking, honest message: the Pod links no readable profile and Festipod cannot create or link one (outside the pinned contract);
|
||||
- `ready` with an empty `currentUser.name` → `UpdateProfileScreen` in `onboarding` mode (explanation, no way out until saved, no pre-filled fiction);
|
||||
- otherwise the routed screen.
|
||||
|
||||
The flag is the name itself — nothing is stored locally, so the form disappears for good once a name is saved and the profile re-read.
|
||||
@@ -39,6 +39,7 @@ export function MyScreen() { // named function, NEVER any props
|
||||
- **Zero props, no exception left**: every registered screen receives nothing; everything comes from context/hooks (`useFestipodData`, `useNavigate`, `useParams`). `WelcomeScreen` does not use `useFestipodData` (intro) — but still takes no props. (`LoginScreen`/`ConnexionScreen`/`AccessGateScreen` no longer exist — Festipod renders no access screen of its own; see [[knowledge_screens]] and [[knowledge_app-shell]].)
|
||||
- **Identity: the current user may not be there yet.** `currentUserId` is the NURI of the profile document this session **owns**, so it is **empty until that document resolves** — see [[caveat_identity-ids-in-screens]] before keying anything on it.
|
||||
- **The confirmation FOLLOWS the write.** Mutations reject rather than returning quietly, so a screen must `await` (or `.then`) before announcing anything: success toast and navigation on resolve, an error toast on reject, and the user kept on their edits. A toast fired beside the call announces a write that may never have happened — the pattern that had to be corrected on the event and profile edit screens.
|
||||
- **Reading what the session has not read yet is asked for from an effect.** A screen about one event calls `openEvent(eventId)`, a screen about someone else's events calls `openUserEvents(userId)` (`UserProfileScreen`); both are state changes, so they run in a `useEffect` keyed on the id, never during render, and the getters (`getEvent`, `getUserEvents`) answer with what has landed. They are idempotent and are no-ops on providers whose read set is already complete (local fixtures); `openUserEvents` is also a no-op for oneself and for anyone who is not a contact. Verified in `festipodDataBase.ts` (the context contract), `refineData.tsx` (the ActivityPods implementation).
|
||||
- **A write affordance is decided by ownership, in three states.** Both the control that *offers* the write and the screen that *performs* it consult the same answer (`data-layer` → [[knowledge_write-rights-are-ownership]]). `unknown` is never folded into either side: showing the form lets a non-owner edit on a guess, and bouncing them out tells a genuine owner their thing is not theirs. Render it as its own pending state.
|
||||
- **Layout**: full-height flex column; `Header` at the top, content at `flex:1; overflow:auto`, `BottomNav` at the bottom **only for hub screens** (Home, Events, Profile, Friends). Flow screens (creation, editing, detail) have no `BottomNav`.
|
||||
- **Feedback**: `showToast(message, 'success'|'info'|'error')` (`ToastContainer` mechanism exported by `sketchy/`).
|
||||
|
||||
Reference in New Issue
Block a user