From 2f6d46c06e007c0c2f55e86610264e70d6f57b06 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 21 Jul 2026 21:17:26 +0200 Subject: [PATCH] docs(scheduling): document response and privacy lifecycle --- README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/README.md b/README.md index 8d7233c..4b845e1 100644 --- a/README.md +++ b/README.md @@ -118,3 +118,88 @@ participant pages, Calendar hold cleanup after decision, and advanced scoring constraints such as required participants and quorum rules. The active backlog lives in Gitea issues. + +## Signed-participation policy gate + +Scheduling persists the response policy and snapshots it onto each governed +Poll invitation. Public access uses +`/scheduling/public/{request_id}/{token}`; Poll's ordinary public routes reject +these gateway-bound tokens, so callers cannot bypass Scheduling's password and +request checks. Passwords are write-only, stored only as salted PBKDF2 hashes, +and failed checks are throttled through Redis when configured with a bounded +in-process fallback. + +Poll is the transactional authority for response rules. Its governed submission +holds the Poll-row lock while enforcing single choice, `Maybe`, participant +email, comments, idempotency and per-option capacity. Scheduling then updates +its participant projection in the same database transaction. Candidate-slot +add/remove and participant-link revocation also go through the governed Poll +capability; exact retries are idempotent, and option mutations invalidate only +the affected answers. + +Public submissions lock the Scheduling request and participant rows before +entering Poll, matching organizer edit lock order and making first-use email +binding atomic. Both authenticated and public submission paths independently +require the Scheduling request to be collecting and its deadline not to have +passed, so a stale or incorrectly reopened Poll projection cannot accept a +response. Changing a request deadline transactionally updates each governed +invitation's expiry in Poll under owner- and gateway-bound row locks. The same +link and invitation identity survive future, past, extended, and cleared +deadlines: a past deadline makes the link unusable, a later extension makes the +same link usable again, and clearing the deadline removes its expiry. No raw +replacement token crosses the PATCH response, and existing responses plus +participant status remain attached to the same durable respondent identity. + +Open lifecycle decision: cancellation closes the backing Poll, so submission +fails, but an otherwise valid invitation can still resolve the reduced public +view and show that the request was cancelled. Decide whether cancellation +should revoke links immediately or retain that acknowledgement view for a +bounded period; links without a deadline would otherwise remain readable. + +If the governed capability is absent, API responses advertise that policy +enforcement is unavailable and restricted links fail closed. Plaintext +passwords, token hashes, response-gateway internals and the participant roster +are not exposed by the public response model. + +Authenticated participant list/detail projections likewise omit tenant, +organizer, Poll and Calendar identifiers, connector metadata, invitation and +identity bindings. Free/busy conflicts are reduced to useful time/status +fields. Organizers and scheduling administrators retain the full management +projection; participants retain candidate-slot revisions, their own marker and +email, response settings, aggregates, and any roster names/statuses permitted +by the configured privacy policy. + +The WebUI package exposes typed clients for the public access and submission +endpoints. A signed-out browser page cannot yet be registered by a module: +Core's `App` renders `PublicLandingPage` directly whenever `auth` is absent and +only mounts module route contributions inside the authenticated branch. Until +Core gains an explicit, allowlisted `publicRoutes` contract, notification action +URLs under `/scheduling/public/{request}/{token}` must be treated as a blocked +frontend handoff rather than a working guest page. The token is never moved into +query parameters, browser storage, or an authenticated API contract while that +shell boundary is unresolved. + +Draft saves never issue public tokens or enqueue invitation delivery, even when +`create_participant_invitations` is left at its compatibility default. A +collecting request may explicitly issue invitations; authenticated in-module +responses lazily create a gateway-bound invitation and discard its token. The +draft-to-open transition therefore supports authenticated lazy responses, but +does not make a guest link available from the current UI. The product decision +still open is the explicit organizer workflow for issuing or reissuing public +links after a draft is opened and, when Mail is installed, whether that action +should also enqueue delivery or return links for separate distribution. Until +that workflow is agreed, opening a draft does not silently send anything. + +## FieldLabel omission register + +Scheduling uses the shared `FieldLabel` for form controls that need explanatory +inline help. The only intentional omissions are: + +- Candidate-slot and participant DataGrid cells: column headers supply the + visible label and each interactive cell has an accessible name. +- Per-slot availability selects: the enclosing visible slot/date text is the + native label for that individual choice. + +There are no other authorized Scheduling omissions. Inline help remains +controlled by the user's shared interface setting; hiding it does not remove +accessible labels.