feat(scheduling): bound public cancellation notices

This commit is contained in:
2026-07-22 03:17:51 +02:00
parent ea2f721377
commit 58619484b6
7 changed files with 217 additions and 30 deletions

View File

@@ -131,9 +131,9 @@ poll-backed scheduling requests:
- a first Scheduling WebUI package with request creation, slot matrix, Calendar
actions, decisions, and notification-job creation
The next slices should add real notification delivery workers, richer public
participant pages, Calendar hold cleanup after decision, and advanced scoring
constraints such as required participants and quorum rules.
The next slices should add generic self-enrolment links after their abuse and
identity policy is agreed, Calendar hold cleanup after decision, and advanced
scoring constraints such as required participants and quorum rules.
The active backlog lives in Gitea issues.
@@ -168,11 +168,14 @@ 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.
Cancellation closes the backing Poll, so submission fails, while active links
remain usable as a reduced cancellation notice for a bounded period. The
deployment setting `SCHEDULING_CANCELLATION_NOTICE_DAYS` defaults to 30 and is
bounded to 190 days. Cancellation transactionally aligns governed invitation
expiry with that timestamp. The public projection contains only the request
title and cancellation timestamps; descriptions, locations, candidate slots,
comments, and previous answers are omitted. After the bound, access fails with
the same generic response as an invalid or expired link.
If the governed capability is absent, API responses advertise that policy
enforcement is unavailable and restricted links fail closed. Plaintext
@@ -187,15 +190,13 @@ 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.
The WebUI package registers `/scheduling/public/{request}/{token}` through
Core's explicit, backend-allowlisted public-route contract. The guest page uses
the shared UI components, prompts for email/password only when needed, prefills
an existing response, and enforces the snapshotted response rules. The token
stays in the path and is never copied into query parameters or browser storage.
Signed-in users also receive an in-app deep link without weakening the signed
guest-link boundary.
Creating, editing, and opening a request never issue public tokens or enqueue
invitation delivery. The deprecated `create_participant_invitations` request