JuliusBrussee/caveman · critical

postgres: tenant schema isolation incomplete

Error message

postgres: tenant schema isolation incomplete: %s

What it means

ValidateTenantSchema compares the inspected tenant tables and foreign keys against the required tenant-isolation contract via tenantSchemaViolations. If any violations are found, it fails startup with a semicolon-joined list of what is missing or wrong in the tenant schema. This is a deliberate migration/schema-drift guard: the database schema does not match what the library requires.

Solutions

  1. Read the joined violation list in the error — it names each missing table, column, or offending foreign key
  2. Run the project's pending migrations (e.g. migrate up) so the tenant schema matches the expected version
  3. Fix any cross-tenant foreign keys reported by dropping/recreating them with the tenant column in the key
  4. Verify you are pointing at the intended database/DSN (not an old environment)
  5. Re-run NewRuntimePool and confirm ValidateTenantSchema passes
Defensive patterns

Strategy: validation

Validate before calling

// before NewRuntimePool, check migrations are current
var dirty bool
if err := db.QueryRow("SELECT bool_or(version IS NULL) FROM schema_migrations").Scan(&dirty); err != nil || dirty { return errors.New("run migrations first") }

Try / catch

if err := NewRuntimePool(ctx, cfg); err != nil {
    if strings.Contains(err.Error(), "tenant schema isolation incomplete") {
        log.Fatalf("apply pending migrations before starting: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling NewRuntimePool against a database where the tenant schema is incomplete: missing required tenant tables, tables missing RLS/tenant columns, or foreign keys that cross tenant boundaries — tenantSchemaViolations returns a non-empty list.

Common situations: Migrations not applied (or partially applied) before starting the service; a migration added a table without the tenant-id column or without proper FK scoping; pointing a staging/production pool at a database created for an older schema version; someone altered the schema manually.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/59be185af82b6ce9. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/postgresconfig/postgresconfig.go:245

	tx, err := pool.BeginTx(ctx, pgx.TxOptions{
		IsoLevel:   pgx.RepeatableRead,
		AccessMode: pgx.ReadOnly,
	})
	if err != nil {
		return fmt.Errorf("postgres: begin tenant schema inspection: %w", err)
	}
	defer tx.Rollback(ctx)

	tables, err := inspectTenantTables(ctx, tx)
	if err != nil {
		return fmt.Errorf("postgres: inspect tenant tables: %w", err)
	}
	foreignKeys, err := inspectTenantForeignKeys(ctx, tx)
	if err != nil {
		return fmt.Errorf("postgres: inspect tenant foreign keys: %w", err)
	}
	if violations := tenantSchemaViolations(tables, foreignKeys); len(violations) > 0 {
		return fmt.Errorf("postgres: tenant schema isolation incomplete: %s", strings.Join(violations, "; "))
	}
	if err := validateResolverSchema(ctx, tx); err != nil {
		return err
	}
	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("postgres: finish tenant schema inspection: %w", err)
	}
	return nil
}

func inspectTenantTables(ctx context.Context, queryer catalogQuerier) ([]tenantTableSchema, error) {
	rows, err := queryer.Query(ctx, `
		SELECT c.relname,
		       NOT organization.attnotnull,
		       c.relrowsecurity,
		       c.relforcerowsecurity,
		       pol.oid IS NOT NULL,
		       coalesce(pol.polpermissive, false),

View on GitHub (pinned to 3ee70a1026)