> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Calendar & Scheduling — Admin Guide

> Calendar integration allows staff to connect Google or Microsoft calendars for two-way event sync and meeting scheduling from within CE.

## Overview

Calendar integration allows staff to connect Google or Microsoft calendars for two-way event sync and meeting scheduling from within CE.

## Configuration

### Module Settings

Three organization-level settings control calendar behavior (CE Settings):

| Setting                  | Default | Range      | Description                                |
| ------------------------ | ------- | ---------- | ------------------------------------------ |
| Default meeting duration | 30 min  | 15–480 min | Pre-filled duration for new meetings       |
| Sync poll interval       | 15 min  | 5–60 min   | How often the system checks for new events |
| Pre-meeting notification | 15 min  | 5–1440 min | Minutes before meeting to send reminder    |

### Meeting Follow-up Tasks

When a meeting is scheduled from CE, Encore can automatically create a follow-up task in the unified task list and notify the assignee. The **Meeting Follow-up Tasks** card on **CE → Settings** controls the per-organization defaults:

| Setting                     | Default | Range    | Description                                                                                                                      |
| --------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Auto-create follow-up tasks | On      | On / Off | When on, each scheduled meeting fail-openly creates a follow-up task. The calendar push is never blocked if task creation fails. |
| Default follow-up days      | 3       | 1–14     | Days after the meeting end time that the follow-up task is due.                                                                  |

The follow-up task is created with `source_type = calendar_event`, priority `medium`, and status `open`. It is assigned to the person who booked the meeting unless they pick a different assignee in the **Schedule Meeting** dialog. Assignees outside the booking user's organization are rejected — the task falls back to the booking user.

Admins need the `ce.admin` permission to see and edit this card. Users without admin access still see the in-dialog follow-up controls; they just cannot change the org defaults.

### Permissions

Assign these permissions via the Roles & Permissions settings:

| Permission             | Purpose                                              |
| ---------------------- | ---------------------------------------------------- |
| `ce.calendar.connect`  | Allow users to connect/disconnect calendar accounts  |
| `ce.calendar.schedule` | Allow users to schedule meetings from CE             |
| `ce.calendar.view`     | Allow users to view their own synced events          |
| `ce.calendar.view-all` | Allow users to view all organization calendar events |

### OAuth Provider Setup

Calendar OAuth requires provider credentials configured as Supabase secrets:

* **Google:** `GOOGLE_CALENDAR_CLIENT_ID`, `GOOGLE_CALENDAR_CLIENT_SECRET`
* **Microsoft:** `MICROSOFT_CALENDAR_CLIENT_ID`, `MICROSOFT_CALENDAR_CLIENT_SECRET`

### Sync Architecture

* Events sync bi-directionally between external calendars and `ce_calendar_events`
* Outbound meetings (scheduled from CE) create activities linked via `activity_id`
* Inbound events are stored with `sync_direction = 'inbound'`
* Token encryption uses Supabase Vault for `access_token_encrypted` and `refresh_token_encrypted`

## Security

* All calendar data is tenant-isolated via `organization_id` RLS
* Tokens are encrypted at rest
* Connection ownership enforced by `ce_user_owns_calendar_connection()` SECURITY DEFINER function
* Cross-user visibility requires `ce.calendar.view-all` permission

***

## BYO Google OAuth (PF-104)

By default, calendar OAuth uses the Encore-managed Google client
(`GOOGLE_CALENDAR_CLIENT_ID` / `GOOGLE_CALENDAR_CLIENT_SECRET` configured at
the platform level). Tenants who require their own Google client (for
branding on the consent screen, internal-only OAuth scopes, or workspace
admin policies) can register a per-organization Google Workspace
connection.

### Setup steps

1. In Google Cloud Console, create an OAuth 2.0 Client ID of type
   **Web application**.
2. Add the Encore callback URL to **Authorized redirect URIs**:
   `https://<your-encore-domain>/functions/v1/calendar-oauth-callback`.
3. In Encore, navigate to **CE → Settings → Calendar Connections**.
4. In the **Google credentials** banner, click **Configure organization
   credentials** to open the Google Workspace integration settings.
5. Paste the `oauth_client_id` and `oauth_client_secret` from Google Cloud
   Console. They are stored in the **PF-76 Credential Vault** (encrypted at
   rest, never returned to the browser after save).
6. New calendar connections from this organization will automatically use
   the BYO client and be linked via `gws_connection_id`. Existing
   connections continue to use whichever client was active at connect time.

### Status indicator

The Calendar Connections page shows which client is active per connection:

* **Your organization** — connection used the BYO Google Workspace client.
* **Encore platform default** — connection used the platform-managed
  client.

## Organization Calendar via Google Workspace (PF-101)

In addition to per-user OAuth, staff can schedule meetings through your
organization's Google Workspace connection using domain-wide delegation. The
**Schedule Meeting** dialog exposes an **Organization Calendar** option whenever
the safeguard below passes. The event is then created via the platform Google
Workspace surface instead of the user's personal calendar grant.

### Prerequisites

The Organization Calendar option only appears for a user when all of the
following are true (fails closed):

* A `pf_google_workspace_connections` row exists for the organization with
  `status = 'connected'`.
* The connection has `capability_calendar_enabled = true` and a non-null
  `baa_attested_at`.
* The signed-in user's email domain exactly matches the connection's
  `primary_domain`.

If any check fails, the option is hidden and users fall back to their personal
calendars. See the [Google Workspace integration overview](/integrations/GOOGLE_WORKSPACE_OVERVIEW)
for connection setup, BAA attestation, and capability flags.

### Behavior notes

* Events scheduled through this path are written via
  `createGoogleCalendarEvent` and logged as CE activities just like
  personal-OAuth meetings; the calendar event cache and activity timeline are
  invalidated on success.
* Free/busy overlay in the dialog is currently sourced from the personal-OAuth
  calendar; users see availability only when they pick a personal connection.
  A domain-wide-delegation free/busy overlay is planned.
* Errors are sanitized before being shown in the toast — no raw Google API
  payloads are surfaced to end users.

## Vault token migration (PF-101 WS4)

Calendar connections created before PF-101 stored access and refresh
tokens as plaintext in `ce_calendar_connections.access_token_encrypted`
and `refresh_token_encrypted`. Those rows are flagged with
`migration_required = true`.

### Running the backfill

Org admins (`org_admin` or `platform_admin`) will see a **Vault migration
available** banner on the Calendar Connections page when at least one
legacy row exists. Click **Run vault migration** to execute the backfill:

* The UI calls the `calendar-token-backfill` edge function in batches of
  25 connections until none remain.
* For each row, the function moves any present plaintext token into the
  PF-76 Credential Vault, links it via `access_token_credential_id` /
  `refresh_token_credential_id`, clears the legacy column, and sets
  `migration_required = false` + `migrated_at = now()`.
* The migration is idempotent and safe to re-run.

### Verification

After migration:

```sql theme={null}
SELECT count(*) FILTER (WHERE migration_required) AS pending,
       count(*) FILTER (WHERE NOT migration_required) AS migrated
FROM   ce_calendar_connections
WHERE  organization_id = '<org-id>';
```

`pending` should be `0`. Edge functions (`calendar-freebusy`,
`calendar-schedule`, `calendar-sync`) automatically resolve tokens
vault-first via `getFreshCalendarAccessToken()` and fall back to legacy
columns only for rows the backfill has not yet processed, so service
continuity is preserved during the transition.

### Logs & observability

* Edge Function logs include a `correlationId` per backfill batch and
  per token refresh; no token material or attendee email is ever logged.
* Token refresh failures are logged with `error_code` (e.g.
  `invalid_grant`) so admins can prompt the user to reconnect.
