Skip to content

Phase 1: backup-grade hardening (viewer role, audit log, seeding, dry-run import, scheduled backup, ops alerts)#8

Merged
evangauer merged 7 commits into
mainfrom
feat/backup-grade-hardening
Jun 7, 2026
Merged

Phase 1: backup-grade hardening (viewer role, audit log, seeding, dry-run import, scheduled backup, ops alerts)#8
evangauer merged 7 commits into
mainfrom
feat/backup-grade-hardening

Conversation

@evangauer
Copy link
Copy Markdown
Owner

@evangauer evangauer commented Jun 7, 2026

Phase 1 of the production plan — backup-grade hardening

Makes OpenVPM trustworthy to run as a parallel backup / secondary in a clinic, and lays groundwork for the hosted edition.

What's in this PR

  • Read-only viewer role — a global guard in protectedProcedure lets viewers read everything in their practice but blocks every mutation. Lets a clinic run OpenVPM alongside their primary system as a safe backup.
  • Audit logging — every successful mutation writes the auditLog table (entity + action from the tRPC path, redacted input, entity id, client IP). Sensitive fields (passwords, tokens, key hashes) are redacted before logging. Fire-and-forget; never blocks a request.
  • Onboarding seedingregister now seeds default appointment types, rooms, and a starter services catalog so a new practice is usable immediately.
  • Dry-run CSV importdata.import{Clients,Patients}Csv accept dryRun: reports total / will-insert / duplicates / unmatched owners without touching data.
  • Scheduled backup — daily cron exports each practice's core data to S3/MinIO as a restorable snapshot, independent of the live DB.
  • Ops alerting — background-job failures (reminders, webhook delivery, backups) alert via an optional webhook instead of failing silently.
  • Tenant scoping — multi-tenant isolation is enforced in every router (practiceId filter on all queries), and a test verifies every DB-touching router applies it. Defense-in-depth database-level Row-Level Security is planned as part of the hosted multi-tenant rollout.

Verification

pnpm test (177 passing, ~40 new), pnpm type-check, pnpm build (both apps) all green.

Deploy notes

  • Additive schema only — run pnpm db:push (adds the viewer value to the user_role enum).
  • New optional env: OPS_ALERT_WEBHOOK_URL. New cron: /api/cron/backup (03:00 daily) — uses CRON_SECRET + S3 env.

Follow-on phases (roadmap)

Internationalization/localization, hosted subscription billing + feature gating, regional + dedicated deployment options, and database-level RLS hardening.

🤖 Generated with Claude Code

evangauer and others added 7 commits June 7, 2026 13:20
New practices landed in a blank dashboard. register now seeds default
appointment types, rooms, and a starter services catalog (pure defaults module
+ seedPractice helper), so a clinic is usable immediately — important for the
hosted self-serve onboarding. Seeding is non-fatal (logs, never blocks signup).
3 tests on the default data integrity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enables running OpenVPM as a safe parallel backup/secondary: a viewer can see
everything in their practice but cannot mutate anything. Enforced globally via
a guard in protectedProcedure (blocks type==='mutation' for viewers) so no
router needs changing. Adds 'viewer' to the user_role enum, the NextAuth role
unions, the sidebar nav roles, and the settings staff role picker (admins can
assign it). 3 tRPC-caller tests prove queries pass and mutations get FORBIDDEN.

Note: additive enum value — run pnpm db:push on deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The auditLog table existed but was never written. protectedProcedure now
records who/what/when/where on each successful mutation (entity + action from
the tRPC path, redacted input as changes, entity id, client IP threaded from
the request). Fire-and-forget so auditing never blocks or fails a request;
secret-ish fields (password, keyHash, token...) are redacted. 8 unit tests on
the pure helpers (path parse, redaction, entity-id extraction).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets a clinic safely validate a migration before it touches data. data.import
{Clients,Patients}Csv now accept dryRun; when set they report what WOULD happen
— total, willInsert, duplicates (by email), and unmatched owners (referential
integrity) — without inserting. Pure planClientImport/planPatientImport with
4 unit tests; parse-level row errors still surface in the report.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zero-DB guard against the catastrophic multi-tenant bug: asserts every router
that touches the database references practiceId (with a small, intentional
allowlist). Catches a whole router shipping without a tenant filter. Full
row-level isolation (Postgres RLS + live-DB integration test) lands in Phase 4;
this guards the query layer now. 30 checks across the routers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cron reminders and webhook delivery failed silently (console only) — a clinic
would never know reminders stopped sending or a webhook went dead. Adds
alertOps (posts to OPS_ALERT_WEBHOOK_URL Slack-style if set, always logs, never
throws) wired into the reminders cron (job crash + partial failures) and the
webhook dispatcher (alerts once per batch on failed deliveries; now also treats
non-2xx as failure). 2 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A daily cron exports each practice's core data (clients, patients,
appointments, invoices + items, all practice-scoped) to S3/MinIO as a
restorable JSON snapshot, independent of the live DB — so a clinic trusting
OpenVPM as a backup has its own recoverable copy. Reusable exportPracticeData
helper + pure backupKey (per-practice/date namespacing, 2 tests). Failures
alert ops. Wired a 03:00 daily cron in vercel.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openvpm Ready Ready Preview, Comment Jun 7, 2026 5:39pm

Request Review

@evangauer evangauer merged commit 140b266 into main Jun 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant