# Operations Guide

## Daily operation

The one-minute cron starts the scheduler. The scheduler performs work only when due and uses a MariaDB advisory lock to prevent overlapping runs.

Normal cadence:

- Due ICS sources synchronize according to their individual refresh intervals.
- Conflicts are recalculated at most every five minutes.
- Source health and event changes are evaluated after synchronization.
- Pending RSVP review runs at its configured interval, initially four hours.
- Incomplete-event checks run hourly.
- Morning, evening, and weekly digests run at their configured local times.
- Notification delivery retries failed messages up to three times.

## Calendar source lifecycle

Use the Calendar Sources screen to add, test, edit, disable, synchronize, and remove feeds.

Disable a source when the interruption may be temporary. Disabled sources remain stored but do not synchronize or participate in agendas and conflicts.

Remove a source only when it is permanently obsolete. Removal deletes its locally indexed events, event changes, and related conflicts through database relationships. It never changes the original provider calendar.

Changing a source URL clears conditional-fetch metadata and forces fresh content retrieval. Adding the same exact URL twice is blocked.

## Feed health

A healthy source shows a recent successful synchronization and zero consecutive failures. Review a source when:

- Consecutive failures reach two or more
- Last success exceeds the configured stale threshold
- The provider returns authentication or not-found errors
- Event counts suddenly fall to zero
- A schedule change is known to exist but has not appeared

The scheduler periodically forces a full content retrieval even when the provider supports ETag or Last-Modified responses. This is necessary so the rolling recurrence window advances over time.

## Conflict workflow

Conflicts can be:

- Open
- Suppressed until a chosen date
- Reopened
- Automatically resolved when later scans no longer detect them

Use `#overlap-ok` on one of the source events for a deliberate overlap. Use `#no-calendar-alert` to exclude an event from conflicts, change alerts, RSVP reviews, incomplete-event warnings, and digests.

A marker must exist in the title, description, or location of the original event and must be published by the source feed.

## Notification queue

The System page displays recent queued, sent, and failed notifications. Use the manual dispatch action after correcting mail or webhook configuration.

CLI dispatch:

```bash
php bin/console notifications:dispatch 100
```

Scheduled digests intentionally run at their configured times even when that time is at the boundary of quiet hours. Ordinary event, source, and incomplete-detail alerts respect quiet hours unless explicitly eligible for the urgent bypass.

## Backups

Create an application backup:

```bash
php bin/backup.php
```

The command creates a timestamped directory under `storage/backups` containing:

- A transactional MariaDB dump
- `.env`
- `VERSION`
- `composer.json`
- `composer.lock` when present
- A JSON manifest

The backup contains the encryption key and database credentials. Protect it as a secret, copy it to an encrypted off-server location, and restrict permissions.

A reasonable policy is:

- Daily database/application backup
- At least 14 daily restore points
- At least three monthly restore points
- Off-server encrypted copy
- Periodic restore test

## Restore procedure

1. Stop or comment out the application cron.
2. Restore the application release files.
3. Restore the original `.env` or another `.env` containing the same `APP_KEY`.
4. Create an empty MariaDB database and grant the application user access.
5. Import `database.sql`.
6. Run `composer install --no-dev --optimize-autoloader --no-interaction` with the selected EA-PHP version.
7. Run `php bin/console migrate` to apply any newer idempotent schema changes.
8. Verify `php bin/console status` and `/healthz`.
9. Restore the cron.
10. Run a full scheduler pass and inspect the dashboard.

Never restore the database without the matching `APP_KEY`.

## Updating the application

1. Create a backup.
2. Extract the new release into a separate directory.
3. Copy the existing `.env` into the new directory.
4. Run Composer with the selected EA-PHP binary.
5. Run smoke and PHPUnit tests when development dependencies are installed.
6. Run `php bin/console migrate`.
7. Point the subdomain document root or symlink to the new release's `public` directory.
8. Run `php bin/console scheduler:full`.
9. Verify health, dashboard, sources, conflicts, and notification delivery.
10. Keep the prior release until verification is complete.

## Useful commands

```bash
php bin/preflight.php
php bin/console status
php bin/console sources:list
php bin/console mirrors:list
php bin/console sync
php bin/console sync SOURCE_ID
php bin/console conflicts:scan 90
php bin/console scheduler:run
php bin/console scheduler:full
php bin/console notifications:dispatch 100
php tests/smoke.php
vendor/bin/phpunit
```

## Log locations

- `storage/logs/app.log` — application fallback log
- `storage/logs/cron-error.log` — recommended cron stderr target
- MariaDB `system_log` table — structured application errors
- MariaDB `audit_log` table — administrative changes
- MariaDB `sync_runs` table — source synchronization history
- MariaDB `notification_queue` and `notification_log` — delivery state

Review cPanel's Apache and PHP-FPM logs for failures occurring before the application can initialize.
