274 lines
14 KiB
Markdown
274 lines
14 KiB
Markdown
# GovOPlaN Scheduling
|
||
|
||
<!-- govoplan-repository-type:start -->
|
||
**Repository type:** module (domain).
|
||
<!-- govoplan-repository-type:end -->
|
||
|
||
GovOPlaN Scheduling owns meeting scheduling and `Terminfindung`: finding
|
||
suitable times across people, resources, calendars, constraints, and scheduling
|
||
polls.
|
||
|
||
This repository is initialized as a discoverable module seed. It exposes the
|
||
module manifest, permission surface, role templates, and documentation metadata
|
||
before runtime APIs, database models, migrations, and WebUI routes are
|
||
introduced.
|
||
|
||
The core boundary decision register is in
|
||
`/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`.
|
||
|
||
## Scope
|
||
|
||
Scheduling owns:
|
||
|
||
- meeting scheduling polls and proposals built on `govoplan-poll`
|
||
- participant availability collection
|
||
- time-window and resource constraints
|
||
- candidate-slot ranking and conflict explanation
|
||
- scheduling state, reminders, and decision handoff
|
||
- optional links to external calendar/groupware systems through capabilities
|
||
- organizer permissions and poll administration semantics
|
||
|
||
Scheduling does not own:
|
||
|
||
- calendar primitives such as events, recurrence, availability storage, resources, and calendar adapters; those belong in `govoplan-calendar`
|
||
- fixed-slot public or internal appointment booking; that belongs in `govoplan-appointments`
|
||
- tasks, case work, approvals, and generic workflow state; those belong in `govoplan-tasks`, `govoplan-cases`, and `govoplan-workflow`
|
||
- mail delivery or notifications; those belong in `govoplan-mail` and `govoplan-notifications`
|
||
|
||
## Poll Audience Decision
|
||
|
||
Scheduling polls should support both internal and external/public
|
||
participation, but the first implementation may start internal-only.
|
||
|
||
- Internal polls use Access/IDM identities, groups, permissions, and calendar
|
||
free/busy capability lookups.
|
||
- External/public polls use signed participation links through Portal, minimal
|
||
participant identity, and explicit retention/privacy settings.
|
||
- Mixed polls are allowed only when the organizer has permission to invite
|
||
external participants and the poll explains which participant data is visible.
|
||
|
||
## Privacy, Retention, And Audit
|
||
|
||
Participant availability is sensitive operational data. Scheduling must record:
|
||
|
||
- poll creator, tenant, organizer, invited participants, and access scope
|
||
- what each participant can see about other participants
|
||
- retention deadline for availability responses and poll links
|
||
- audit events for poll creation, invite send, response update, decision,
|
||
cancellation, and handoff
|
||
- trace IDs for mail/notification/portal handoff
|
||
|
||
Availability responses should be removable or redacted after the poll decision
|
||
unless a configured process requires longer evidence retention.
|
||
|
||
## Candidate Capabilities
|
||
|
||
- `scheduling.polls`
|
||
- `scheduling.availabilityCollector`
|
||
- `scheduling.candidateSlots`
|
||
- `scheduling.decisionHandoff`
|
||
- `scheduling.portalParticipation`
|
||
|
||
## Manifest Dependency Decision
|
||
|
||
The Scheduling manifest declares `poll` as a required dependency because
|
||
availability collection and option/date poll primitives belong in
|
||
`govoplan-poll`.
|
||
|
||
Scheduling should model each meeting-finding flow as a poll-backed workflow:
|
||
Poll owns candidate options, signed participation links, responses, visibility,
|
||
and result aggregation. Scheduling stores the scheduling-specific context and
|
||
uses Poll context fields to point back to its request or proposal resource.
|
||
Typical workflow steps are collect availability, rank candidates, decide, notify
|
||
participants, and hand off to Calendar or Appointments.
|
||
|
||
The manifest declares `access`, `addresses`, and `evaluation` as optional
|
||
dependencies. Scheduling uses Core's principal-aware people-search boundary to
|
||
combine only the account and contact records visible to the current organizer;
|
||
it never calls the instance-wide Identity search. It may trigger post-event or
|
||
post-appointment feedback through Evaluation, and must not require any of these
|
||
optional modules just to find a meeting time.
|
||
|
||
## Expected Integrations
|
||
|
||
- `govoplan-poll`: required availability matrix and lightweight poll primitives
|
||
- `govoplan-evaluation`: optional post-event, post-appointment, or process feedback
|
||
- `govoplan-calendar`: availability, calendars, resources, rooms, recurrence, Open-Xchange/calendar adapters
|
||
- `govoplan-appointments`: conversion from a selected meeting time into a booked appointment where appropriate
|
||
- `govoplan-access` and `govoplan-idm`: optional participants, groups, directory lookup, permissions
|
||
- `govoplan-mail` and `govoplan-notifications`: invitations, reminders, confirmations
|
||
- `govoplan-portal`: external participant scheduling flows
|
||
- `govoplan-workflow` and `govoplan-tasks`: follow-up work after a time is selected
|
||
|
||
## Participant selection boundary
|
||
|
||
The editor uses Core's shared `PeoplePicker`. Its server-side search aggregates
|
||
the optional `access.people_search` and `addresses.people_search` capabilities,
|
||
and each provider applies the active principal and tenant visibility rules
|
||
before returning a candidate. Scheduling exposes only the fields needed to
|
||
select a person; provider provenance, address-book topology, group membership,
|
||
and other internals are not returned by its picker endpoint.
|
||
|
||
An account selection becomes an internal participant bound to that account.
|
||
A visible address-book contact becomes an external participant with a bounded
|
||
directory-selection reference and revision for later organizer editing. Manual
|
||
name-and-email entry is available only while the request allows external
|
||
participants. The picker stores neither provider-internal provenance nor a
|
||
global Identity record reference in participant metadata.
|
||
|
||
## First Package Scaffold Decision
|
||
|
||
The first backend implementation slice adds runtime APIs and storage around
|
||
poll-backed scheduling requests:
|
||
|
||
- scheduling request, candidate slot, and participant storage
|
||
- automatic `govoplan-poll` availability poll creation
|
||
- signed poll invitations for internal or external participants
|
||
- request lifecycle APIs: draft, collecting, closed, decided, handed off, cancelled
|
||
- result summaries sourced from Poll response aggregation
|
||
- optional Calendar free/busy checks, tentative holds, and final event creation
|
||
- notification outbox jobs for invitations, reminders, decisions,
|
||
cancellations, and participant access changes
|
||
- a first Scheduling WebUI package with request creation, slot matrix, Calendar
|
||
actions, decisions, and notification-job creation
|
||
|
||
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.
|
||
|
||
## 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.
|
||
|
||
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 1–90 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
|
||
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 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
|
||
field remains accepted for compatibility, defaults to `false`, and has no side
|
||
effect. Authenticated in-module responses can still lazily create a
|
||
gateway-bound invitation whose token is discarded.
|
||
|
||
Organizers and Scheduling administrators use the participant-specific
|
||
invitation action instead:
|
||
|
||
- `POST /scheduling/requests/{request_id}/participants/{participant_id}/invitation`
|
||
with `{"action":"copy","participant_revision":"..."}` rotates the previous
|
||
invitation and returns the new relative action URL once. The response is
|
||
marked `no-store`.
|
||
- The same endpoint with `{"action":"send"}` rotates the invitation and passes
|
||
its URL directly to the notification dispatch job; it also requires the
|
||
current `participant_revision`. Neither the API response nor Scheduling's
|
||
durable notification projection contains the token.
|
||
- `DELETE` on the same resource uses a JSON body containing the current
|
||
`participant_revision` and revokes the active link immediately. A revoke
|
||
against the refreshed no-link projection is an idempotent replay.
|
||
|
||
The semantic participant revision includes the current invitation identity.
|
||
It is checked after the participant row is locked, so stale copy, send, and
|
||
revoke commands return `409` before rotating a newer link or delivering to a
|
||
changed recipient.
|
||
|
||
The participant DataGrid presents copy, send, and revoke as a fixed icon-only
|
||
action group. Authorized but unavailable actions remain visible and explain
|
||
why they are disabled: for example, delivery is disabled without a dispatch
|
||
provider or recipient target, and revoke is disabled when no active link
|
||
exists. The delivery capability is exposed only in management projections.
|
||
Copy accepts only the same-origin Scheduling public path and does not persist
|
||
the bearer URL in component state, logs, or browser storage.
|
||
|
||
Links can be issued in any request state. Collection state and deadline checks
|
||
remain independent submission requirements, so a link to a draft, closed, or
|
||
decided request is read-only. Issue, copy, send-request, and revoke actions are
|
||
audited with request and participant identifiers but never a bearer token.
|
||
Only an organizer (under the ordinary Scheduling write policy) or a tenant-wide
|
||
Scheduling administrator can use these actions. If notification delivery is
|
||
not installed, `send` fails before rotating the current link and the organizer
|
||
can use `copy` for separate distribution.
|
||
|
||
Participant edits carry a semantic revision so a stale organizer form cannot
|
||
overwrite an invitation or response change. Corrections that retain a stable
|
||
account or directory identity update the existing participant. A display-name
|
||
correction keeps its invitation; changing a delivery email revokes the stale
|
||
link but keeps a response tied to the unchanged account identity.
|
||
|
||
Changing the canonical identity creates a new participant instead of assigning
|
||
the former participant's response to another person. In the same transaction,
|
||
Scheduling revokes the old invitation and Poll soft-deletes every matching
|
||
live response. Poll retains the answers and a bounded retirement record for
|
||
audit, while result summaries and capacity checks immediately exclude them.
|
||
Removing an invited or responding participant follows the same retirement
|
||
path. Scheduling records privacy-safe audit facts and queues a removal or
|
||
replacement notice to the former recipient without placing email addresses or
|
||
response contents in the audit event.
|
||
|
||
## 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.
|