# Google Busy-Mirror Bridge

The Google bridge writes private category-only Busy events into a destination Google calendar. It runs inside the destination Google account and polls Calendar Control Center every five minutes.

The VPS does not receive permission to edit the destination Google account. The bridge uses a target-specific bearer token and can retrieve only the Busy feed configured for that target.

## Before creating a bridge

Make sure the destination account's own source calendar is already present in Calendar Control Center. When creating the mirror target, exclude that source. This prevents a Busy event from being read back and mirrored in a loop.

The ICS parser also ignores events marked as Calendar Control Center copies, but source exclusion remains the primary safety control.

## Create the mirror target

1. Open Busy-Mirror Targets.
2. Select Add Mirror Target.
3. Give the target a clear name, such as `Personal Google Account`.
4. Use `primary` for the destination calendar ID unless a separate Google calendar should hold the Busy copies.
5. Keep the title template as `Busy — {category}`.
6. Exclude every source owned by the destination account.
7. Optionally limit the target to selected categories.
8. Save the target and copy its one-time token.

Treat the token like a password. Rotate it immediately if it is exposed.

## Install the Apps Script

While signed into the destination Google account:

1. Create a standalone Google Apps Script project.
2. Copy `resources/google-apps-script/CalendarBridge.gs` into the project as `Code.gs`.
3. In the Apps Script editor, open Services and add the Advanced Google Calendar API service.
4. Edit these constants at the top of the script:

```javascript
const WCCC_ENDPOINT = 'https://calendar.thewilhelms.com/api/mirror';
const WCCC_TOKEN = 'PASTE_THE_TARGET_TOKEN_HERE';
const TARGET_CALENDAR_ID = 'primary';
const SIMULATION_MODE = true;
const BRIDGE_ALERT_EMAIL = 'scott@thewilhelms.com';
```

5. Save the project.
6. Run `testConnection` and approve the requested Google authorization.
7. Review the execution log.
8. Run `previewChanges` and confirm that only expected category-based Busy events would be created.

## Enable live synchronization

After the preview is correct:

1. Set `SIMULATION_MODE` to `false` in the Apps Script.
2. Save the project.
3. Run `syncBusyBlocks` manually once.
4. Inspect the destination Google Calendar.
5. Run `installTriggers` to create one five-minute trigger.

The application-level simulation setting is a rollout reminder and dashboard control. The script's `SIMULATION_MODE` constant is the control that prevents Google writes. Keep both in simulation during initial validation.

## What the bridge writes

Each managed event contains:

- A title such as `Busy — Work` or `Busy — Officiating`
- The original start and end time
- The all-day state when allowed
- Private visibility
- Opaque/busy transparency
- No original title, description, location, attendees, conference link, or reminders
- Private extended properties identifying it as application-managed

The bridge sends no attendee updates.

## Safe update and deletion behavior

The script searches only for events carrying the private `wcccManaged` property. It does not edit or delete ordinary calendar events.

During each run it:

- Creates missing managed Busy events
- Updates managed events whose title, time, all-day state, or fingerprint changed
- Deletes stale managed events no longer present in the feed
- Removes duplicate managed events with the same application key
- Leaves every unmanaged Google event untouched

`cleanupAllBusyCopies` removes every event managed by that bridge. It honors `SIMULATION_MODE`.

## Token rotation

1. Open the mirror target in Calendar Control Center.
2. Select Rotate Token.
3. Paste the new token into the Apps Script.
4. Save and run `testConnection`.
5. The old token stops working immediately.

## Removing a bridge

1. Set `SIMULATION_MODE` to `false` only when you intend to perform cleanup.
2. Run `cleanupAllBusyCopies`.
3. Run `uninstallTriggers`.
4. Delete or disable the Apps Script project.
5. Remove the target in Calendar Control Center.

Removing the target first invalidates its token but does not remove Busy events already written to Google.

## Clarkson limitation

The Clarkson calendar can always be monitored through its ICS source once the feed is configured. Busy writes into Clarkson depend on Clarkson permitting Apps Script and the Advanced Calendar API service for the account.

When those are blocked:

- Do not install a Clarkson destination bridge.
- Clarkson commitments still participate in conflict detection.
- Clarkson commitments can still be mirrored into personal and Excelsior accounts.
- Personal, family, Excelsior, and officiating commitments cannot automatically create Busy blocks inside the Clarkson calendar.

## Troubleshooting

Use these script functions:

- `testConnection` — verifies endpoint, token, target calendar, and Calendar service
- `previewChanges` — shows intended writes without changing Google
- `syncBusyBlocks` — performs a simulation or live synchronization
- `getLastBridgeReport` — displays the most recent stored report
- `installTriggers` — replaces older bridge triggers with one five-minute trigger
- `uninstallTriggers` — removes bridge triggers
- `cleanupAllBusyCopies` — removes only application-managed events

Apps Script sends its own failure emails for installable trigger failures. `BRIDGE_ALERT_EMAIL` adds a direct bridge failure message when configured.
