Skip to content

Operations

Do not forget the three secrets from Configuration. A database and bucket restored without QNOP_AUTH_ENCRYPTION_KEY and QNOP_AUTH_ENCRYPTION_SALT will come up, but everything encrypted at rest — stored OIDC client secrets among them — is unreadable.

  1. Read the release notes for the version you are moving to.
  2. Take a backup of both stores.
  3. Pin the new image version and restart.

Liquibase runs the schema migration on startup. The first boot on a new version therefore takes longer than usual; the health check’s start period accounts for this.

Migrations are forward-only. There is no downgrade path — going back to an older image after a migration means restoring from the backup you took in step two. This is the reason step two is not optional.

Review e-mail changes cadence. The per-person switch for review mail used to be on or off; it now offers as it happens, daily digest and off, and the migration maps it like this:

  • off stays off — nobody who opted out starts receiving mail;
  • on, and every account that never touched the setting, becomes the daily digest.

So after the upgrade most people get one summary each morning instead of a mail per event. Mentions still arrive immediately. Anyone who wants the old behaviour picks as it happens in their profile. Tell your users before you upgrade.

GET /actuator/health is anonymous; point your load balancer or orchestrator at it, or at the liveness and readiness groups underneath it.

Everything else on /actuator/** — health details, metrics, the Prometheus endpoint — requires an authenticated ADMIN. Scraping metrics means giving the scraper admin credentials, which is a deliberate default: the operational surface is not public.

Beyond the actuator, Administration → Scheduler shows the maintenance jobs and their last runs, and Administration → Storage consistency reports mismatches between the database and the bucket.

qnop schedules several maintenance jobs. In a multi-instance deployment they are coordinated so exactly one instance runs each sweep.

JobDefault scheduleWhat it does
Staged-object reaper0 30 3 * * *removes uploads that were staged but never referenced, after a grace period
Orphan reaper0 15 4 * * *removes bucket objects nothing points at any more
Review archive0 50 3 * * *archives reviews older than the configured window
Review purge0 55 3 * * *purges archived reviews after their retention window
Notification sweep0 15 4 * * *deletes in-app notifications older than their retention window, read or not
Review digest0 5 * * * *sends each recipient on daily mail one summary of their unread notifications, from 08:00 in their own timezone

The archive, purge and notification retention windows are set in Administration → Settings, not in the environment. The digest runs hourly on purpose: it goes out on the first run at or after 08:00 in each recipient’s own timezone, at most once a day, and a quiet day sends nothing. Both archiving and purging are recorded in the audit trail.

Long-running work triggered by users — text extraction, re-anchoring annotations onto a new version — runs through a durable job queue held in PostgreSQL rather than in memory. A crash or restart mid-extraction resumes rather than losing the work.

Every relevant action is appended to the audit trail: annotation lifecycle (created, classified, resolved, reopened, dismissed, auto-closed), placement changes, workflow transitions, review archive/unarchive/purge, due-date changes, extraction outcomes, orphan deletions and scheduler runs.

It is exposed at GET /api/v1/audit/events, filterable, and readable by the AUDITOR role or an ADMIN — that is the only surface the auditor role unlocks. Actor identity is deliberately resolved there even where the UI pseudonymises it elsewhere, because the point of the trail is accountability.