Meeting Scheduling¶
Path: /meetings
Control Meetings are recurring meetings tied to a control's recurring evidence-collection objective — for example a quarterly PCI Scope Review or a Review of Reviews. Instead of tracking the meeting as a side task in someone's personal calendar, it becomes a first-class part of the control's cadence: the meeting recurs on the same frequency vocabulary as the control schedule, and when it is linked to a recurring control test its next occurrence advances in lock-step with that test.
Key Elements¶
- Control Meetings page — Lists the recurring meetings, the control each supports, the cadence, the provider, the next occurrence, and the attendees.
- Schedule now — Invokes the configured calendar provider to materialize the next occurrence and records the provider-assigned event id.
- Download .ics — Returns a valid RFC 5545 calendar file you can import into any calendar client, regardless of which provider the meeting uses.
Creating a Control Meeting¶
A meeting is created against a specific control:
The body sets the title, an optional description/agenda, the recurrence
frequency, the provider, the meeting duration_minutes, timezone, whether
it is_online, an optional location/join URL, and the attendees list.
- Open Control Meetings and choose the control the meeting supports
(e.g.
CC6.1). - Give the meeting a title (e.g. PCI Scope Review) and an agenda.
- Choose the cadence —
DAILY,WEEKLY,MONTHLY,QUARTERLY, orANNUAL. This reuses the same frequency enum as the control schedule. - Add attendees (see below).
- Save. The meeting appears in the list with its next occurrence computed from the cadence.
Auto-link to the recurring objective
If you do not supply a control_schedule_id, the meeting auto-links to
the control's existing ControlSchedule row when one exists. Once linked,
the meeting's next_occurrence advances together with the recurring control
test — scheduling the review is no longer a separate manual chore.
Attendee Assignment¶
Each meeting carries an attendee list. An attendee is either an internal user
(by user_id) or an external person identified by email + name. Every
attendee has a role that maps onto standard calendar attendee semantics:
| Role | Calendar meaning |
|---|---|
required |
Required participant |
optional |
Optional participant |
resource |
A room or resource mailbox |
On update, supplying an attendees list performs a full replacement of the
existing attendee set, so send the complete intended list each time.
How It Ties to the Control Evidence Cadence¶
The control's recurring evidence-collection objective and the meeting share one
cadence vocabulary (ScheduleFrequency). When a meeting is linked to a
control_schedule_id:
- It recurs on the same frequency as the control test.
- As the linked
ControlSchedulerecurs, the meeting'slast_occurrenceandnext_occurrenceare advanced one cadence step in step with the test.
This keeps the "we met to review this control" record aligned with the "we collected evidence for this control" record, so an auditor sees one coherent recurring objective rather than two drifting timelines.
Provider Matrix¶
The provider decides how a meeting is materialized on a real (or simulated) calendar. The provider is selected per-meeting and the factory falls back to the mock provider for any unknown value.
| Provider | Value | Behavior | Use when |
|---|---|---|---|
| Mock | mock |
Mints a synthetic external_event_id (mock-<uuid>); contacts no external calendar. Re-scheduling reuses the existing id so the synthetic event is stable across recurrences. |
The default on the demo and in any offline environment. |
| ICS | ics |
Generates a downloadable RFC 5545 .ics VEVENT (with RRULE, attendees, and proper line-folding) you import into any client. |
The offline-real option — a genuine, portable calendar artifact with no tenant required. |
| Microsoft Graph | graph |
Creates the recurring event in a Microsoft 365 tenant via Graph app-only. | You have a Microsoft 365 tenant and want the event to land in real Exchange calendars. |
Demo default is Mock
The demo has no Microsoft 365 tenant, so meetings default to the Mock
provider. POST /meetings/{id}/schedule returns a synthetic
external_event_id with an honest note that no external calendar was
contacted. The ICS download is available on every meeting regardless of
provider.
Scheduling and downloading¶
POST /api/v1/orgs/{org_id}/meetings/{meeting_id}/schedule # invoke the provider
GET /api/v1/orgs/{org_id}/meetings/{meeting_id}/ics # download an .ics
schedule invokes the meeting's provider and persists the returned
external_event_id plus a last_occurrence timestamp. The .ics endpoint
returns Content-Type: text/calendar as an attachment and works for any
provider. The quarterly cadence maps to RRULE:FREQ=MONTHLY;INTERVAL=3.
Microsoft Graph for Real Tenants¶
The Graph provider is implemented behind the same interface but documented as
configure-your-tenant — it is not active on the demo. When the tenant is
unconfigured, schedule raises ProviderNotConfigured, which the endpoint
surfaces as HTTP 503 rather than crashing the request.
App-only flow¶
The provider uses the client-credentials (app-only) flow:
- Acquire a token from the Entra tenant token endpoint
(
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, scopehttps://graph.microsoft.com/.default). - Check availability via
POST /users/{organizer}/calendar/getScheduleagainst the attendee mailboxes to read free/busy. This is a supported app-only call. The availability read is non-fatal — a failure to read free/busy does not block event creation. - Create the recurring event via
POST /users/{organizer}/eventswith a recurrence pattern derived from the cadence (quarterly →absoluteMonthlywithinterval: 3) and per-attendee entries carrying the event time zone.
findMeetingTimes is delegated-only
Microsoft's findMeetingTimes (suggest-a-slot) API is delegated-only —
it is not available to an app-only token. The app-only path therefore builds
candidate slots from getSchedule rather than asking Graph to suggest
times.
Tenant setup¶
- Register an application in Microsoft Entra ID (App registrations → New registration).
- Create a client secret for the app (Certificates & secrets).
- Grant application permissions and obtain admin consent:
Calendars.ReadWrite— to create the event on the organizer mailbox.Schedule.Read.All— to read attendee free/busy viagetSchedule.
- Choose an organizer mailbox — the mailbox the app creates events on behalf of.
Configuration keys¶
Set the following environment variables / settings, then select the graph
provider on a meeting:
| Key | Purpose |
|---|---|
MS_GRAPH_TENANT_ID |
Microsoft Entra tenant id for app-only access |
MS_GRAPH_CLIENT_ID |
The registered app (client) id |
MS_GRAPH_CLIENT_SECRET |
The app client secret |
MS_GRAPH_ORGANIZER_EMAIL |
Mailbox events are created on behalf of |
The provider considers itself configured only when all four are present. Until then it stays gated, and any attempt to schedule via Graph returns 503 with a message naming the missing keys and required permissions.
No SMTP on the demo
The demo has no SMTP relay, so invites are log-only. Use the .ics download as the offline-real way to deliver an invite to attendees.