{"record":{"id":"c9518afe4dfb5b7a","repo":"JuliusBrussee/caveman","slug":"postgres-begin-tenant-schema-inspection-w","errorCode":null,"errorMessage":"postgres: begin tenant schema inspection: %w","messagePattern":"postgres: begin tenant schema inspection: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":232,"sourceCode":"\n// organizationPolicyExpression matches the only accepted tenant predicate:\n// organization_id equals the transaction-local tenant GUC, optionally cast to\n// the column's scalar type by PostgreSQL. It is anchored so OR TRUE, a different\n// operator, a different GUC, or an extra predicate cannot pass by containing\n// the two expected strings.\nvar organizationPolicyExpression = regexp.MustCompile(`^organization_id=\\(*current_setting\\('app\\.current_organization_id'(?:::text)?(?:,true)?\\)\\)*(?:::[A-Za-z_][A-Za-z0-9_.$\"]*)?$`)\n\n// ValidateTenantSchema proves structural isolation invariants that are easy to\n// regress during schema evolution. Every tenant table must fail closed under\n// FORCE RLS, and every foreign-key edge between tenant tables must carry the\n// shared organization scope (plus project scope when both tables have it).\nfunc ValidateTenantSchema(ctx context.Context, pool *pgxpool.Pool) error {\n\ttx, err := pool.BeginTx(ctx, pgx.TxOptions{\n\t\tIsoLevel:   pgx.RepeatableRead,\n\t\tAccessMode: pgx.ReadOnly,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"postgres: begin tenant schema inspection: %w\", err)\n\t}\n\tdefer tx.Rollback(ctx)\n\n\ttables, err := inspectTenantTables(ctx, tx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"postgres: inspect tenant tables: %w\", err)\n\t}\n\tforeignKeys, err := inspectTenantForeignKeys(ctx, tx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"postgres: inspect tenant foreign keys: %w\", err)\n\t}\n\tif violations := tenantSchemaViolations(tables, foreignKeys); len(violations) > 0 {\n\t\treturn fmt.Errorf(\"postgres: tenant schema isolation incomplete: %s\", strings.Join(violations, \"; \"))\n\t}\n\tif err := validateResolverSchema(ctx, tx); err != nil {\n\t\treturn err\n\t}\n\tif err := tx.Commit(ctx); err != nil {","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L214-L250","documentation":"ValidateTenantSchema opens a REPEATABLE READ, READ ONLY transaction to inspect tenant table structure. If the transaction cannot be started, the pool's underlying connection error is wrapped with this message.","triggerScenarios":"NewRuntimePool calls ValidateTenantSchema; pool.BeginTx fails because the pool has no healthy connections — database unreachable, credentials rejected, max connections exhausted, or TLS negotiation failure.","commonSituations":"Postgres not yet ready at container startup; wrong host/port in the database URL; too many clients (max_connections); pgbouncer rejecting new connections; TLS verification failures with the hardened config.","solutions":["Inspect the wrapped %w error for the root cause (connection refused, auth failed, too many connections) and address it.","Confirm the database is reachable: `pg_isready -h <host> -p <port>` from the app environment.","Check pgxpool configuration limits (MaxConns) against the server's max_connections and pgbouncer pool_size.","Add readiness gating/retry with backoff before calling NewRuntimePool at startup."],"exampleFix":"// before\npool, err := postgresconfig.NewRuntimePool(ctx, os.Getenv(\"DATABASE_URL\"), \"tenant_role\")\n// immediately on container start\n// after\nwaitForDB(ctx, os.Getenv(\"DATABASE_URL\")) // poll with pg_isready/backoff\npool, err := postgresconfig.NewRuntimePool(ctx, os.Getenv(\"DATABASE_URL\"), \"tenant_role\")","handlingStrategy":"retry","validationCode":"if err := pool.Ping(ctx); err != nil {\n\treturn fmt.Errorf(\"database not reachable before schema validation: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := postgresconfig.ValidateTenantSchema(ctx, pool)\nif err != nil {\n\tvar pgErr *pgconn.PgError\n\tif !errors.As(err, &pgErr) { // likely connection-level; retry with backoff\n\t\treturn retryWithBackoff(ctx, func() error { return postgresconfig.ValidateTenantSchema(ctx, pool) })\n\t}\n\treturn err\n}","preventionTips":["Gate startup on database readiness (pg_isready or pool.Ping with backoff).","Size pool MaxConns against server max_connections and pgbouncer limits.","Verify TLS/credentials early so connection failures surface before schema validation."],"tags":["postgres","database","connection","transaction"],"backgroundTag":"database-query-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}