Optare v1.0 is now available. Get started →
Deployment
Schema Sync Validation

Gold Standard: Schema-Code Sync Validation

The 4-Layer Defense System

Layer 1: Compile-Time (TypeScript)

What: TypeScript types generated from Drizzle schema When: During development, at build time How: Drizzle automatically infers types from schema.ts

// This fails at compile time if schema doesn't match:
const user = await db.query.users.findFirst({
  where: eq(users.invalidColumn, "test") // ❌ Type error!
});

Check: pnpm typecheck


Layer 2: Pre-Deploy (Migration Validation)

What: CI validates migrations produce expected schema When: On every PR, before merge How: .github/workflows/schema-check.yml

Steps:

  1. Create ephemeral DB
  2. Apply all migrations
  3. Dump schema
  4. Compare to canonical-schema.sql
  5. Fail PR if mismatch

Check: Automatic on PR


Layer 3: Runtime (Health Checks)

What: Production endpoint validates critical schema elements When: Continuously (every request to /health/schema) How: app/routes/health.schema.ts

Validates:

  • Critical tables exist
  • Critical columns exist (e.g., pkceRequired)
  • Database is accessible

Check: curl https://your-app.com/health/schema

Integration: Hook this to your monitoring (Datadog, New Relic, etc.)


Layer 4: Continuous Monitoring (Nightly Drift Detection)

What: Compare production DB to canonical schema When: Nightly at 2 AM UTC How: .github/workflows/nightly-schema-check.yml

Actions:

  1. Introspect production database
  2. Compare to canonical-schema.sql
  3. If drift detected → Create GitHub Issue with label schema-drift
  4. Alert team via Slack/email (optional)

Check: Automatic daily run


How to Use This System

Daily Development

# Before committing:
pnpm typecheck  # Layer 1: Type safety
pnpm db:migration:check  # Layer 2 prep: Validate migration

Before Deploying

# CI will automatically run Layer 2
# Just ensure your PR passes schema-check.yml

After Deploying

# Check health endpoint (Layer 3):
curl https://id.optare.one/health/schema
 
# Expected response:
{
  "status": "healthy",
  "timestamp": "2025-11-29T13:45:00Z",
  "checks": {
    "table_user": true,
    "table_organization": true,
    "column_pkceRequired": true,
    "db_writable": true
  }
}

Weekly Maintenance

# Regenerate canonical schema (after migrations merge):
export DATABASE_URL=<staging-url>
pnpm db:migration:snapshot
git add libs/database/canonical-schema.sql
git commit -m "chore: update canonical schema"

Monitoring Setup

1. Hook Health Check to Monitoring

# datadog.yml or similar
checks:
  - name: Schema Health
    url: https://id.optare.one/health/schema
    interval: 300  # Every 5 minutes
    alert_on_failure: true

2. Subscribe to GitHub Issues

  • Watch repo for label: schema-drift
  • Set up Slack notifications for these issues

3. Add to Deployment Checklist

- [ ] Health check passes: /health/schema returns 200
- [ ] No open `schema-drift` issues
- [ ] TypeScript typecheck passes

What Each Layer Catches

IssueLayer 1
(TypeScript)
Layer 2
(CI)
Layer 3
(Health)
Layer 4
(Nightly)
Code uses wrong column name
Migration not generated
Migration not applied to prod
Manual schema change in prod⚠️
Concurrent migrations
Schema drift over time⚠️

Legend:

  • ✅ Always catches
  • ⚠️ Might catch (depends on what changed)
  • ❌ Doesn't catch
  • ➖ Not applicable

Real Example: The pkceRequired Issue

What Happened:

  • Developer added pkceRequired to schema.ts
  • Migration created: 0003_add_pkce_required.sql
  • Code deployed to production
  • Migration NOT applied to production DB
  • Result: column "pkceRequired" does not exist error

How Each Layer Would Have Caught It:

  1. Layer 1 (TypeScript): ❌ Didn't catch - types were generated correctly from schema.ts
  2. Layer 2 (CI): ✅ Would catch - CI applies migrations and compares
  3. Layer 3 (Health Check): ✅ Catches - /health/schema fails on missing column
  4. Layer 4 (Nightly): ✅ Catches - Creates issue: "Production missing column: pkceRequired"

With this system: Issue caught before or immediately after deployment.


Commands Reference

TaskCommand
Check types match schemapnpm typecheck
Validate migrationspnpm db:migration:check
Check production healthcurl https://app.com/health/schema
Update canonical schemapnpm db:migration:snapshot
Full sync checkpython scripts/check-schema-sync.py

This is the industry gold standard used by:

  • Stripe (migration validation + health checks)
  • GitHub (schema introspection + nightly checks)
  • Shopify (type generation + runtime validation)

Status: ✅ Production Ready