Skip to content

fix: validate_schema() honors ck naming_convention on lease CHECK#102

Merged
lesnik512 merged 1 commit into
mainfrom
fix/validate-schema-naming-convention
Jun 16, 2026
Merged

fix: validate_schema() honors ck naming_convention on lease CHECK#102
lesnik512 merged 1 commit into
mainfrom
fix/validate-schema-naming-convention

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Problem

validate_schema() raised a spurious RuntimeError for any deployment whose MetaData carries a SQLAlchemy ck naming_convention:

Outbox schema mismatch: missing CHECK constraint 'outbox_faststream_lease_ck' (expected 'acquired_token is null = acquired_at is null')

…even with a perfectly valid schema.

Root cause. SQLAlchemy re-templates an explicitly-named CheckConstraint through a ck convention — the given name fills the %(constraint_name)s token — so the live constraint is named e.g. ck_outbox_faststream_outbox_faststream_lease_ck, not outbox_faststream_lease_ck. The CHECK probe hard-coded the literal f"{table.name}_lease_ck", never matched the re-templated name, and reported "missing". Confirmed empirically: under a ck convention the constraint resolves to the ck_… name, while the indexes keep their literal names (the ix/uq keys only re-template auto-named indexes).

Fix

_validate_check_constraints_sync now derives the expected name from the Table object via a new _resolve_check_constraint_name — it identifies the lease constraint by its normalized predicate and returns its convention-resolved .name, falling back to the literal name only when the table carries no matching constraint (reflected/hand-built Table). The expectation now always matches what SQLAlchemy / Alembic emit from your metadata, convention or not.

Tests

  • Unit (no Postgres): convention-resolved name honored (the failing case), missing-under-convention reports the resolved name, literal-name fallback for a constraint-free table.
  • Integration (Postgres): builds the outbox table on a ck-convention MetaData, create_alls it, asserts validate_schema() does not raise.

Full suite: 525 passed, 100% coverage; lint (ruff + ty) clean; docs build clean.

Docs / housekeeping (same PR)

  • docs/operations/alembic.md — replaced the now-misleading op.f('outbox_lease_ck') caveat (it forced a literal name the probe no longer expects under a convention) with an introspect-the-rendered-name recipe.
  • architecture/dlq.md — recorded the convention-awareness invariant in the validate_schema() mechanics section.
  • planning/releases/0.10.2.md — release notes (patch: bugfix only, opt-in path, no API change).
  • planning/changes/active/ — removed a stale draft duplicate of the already-shipped+archived fix: actionable error for Alembic-blind schema drift #99 bundle (canonical copy remains in archive/; the active index already read _None._).

Compatibility

validate_schema() is opt-in. With no naming_convention, behavior is identical to 0.10.1. With a ck convention, validation that previously raised spuriously now passes when the DB constraint carries the convention-rendered name (created via create_all or convention-aware Alembic). No other behavior change.

🤖 Generated with Claude Code

A MetaData carrying a SQLAlchemy `ck` naming_convention re-templates the
package's explicitly-named lease CheckConstraint (the given name fills the
%(constraint_name)s token), so the live constraint is named e.g.
`ck_<table>_<table>_lease_ck`, not `<table>_lease_ck`. The CHECK probe
hard-coded the literal name, never found the re-templated one, and raised a
spurious "missing CHECK constraint" on a valid schema for every deployment
using the SQLAlchemy/Alembic-recommended convention.

_validate_check_constraints_sync now derives the expected name from the Table
object via _resolve_check_constraint_name (identify the lease constraint by its
normalized predicate, use its convention-resolved .name), falling back to the
literal name only when the table carries no matching constraint. Explicitly
named indexes are unaffected (ix/uq keys only re-template auto-named indexes).

- tests: convention-resolved name honored, missing-under-convention, literal
  fallback (unit), and an end-to-end Postgres pass under a ck convention.
- docs/operations/alembic.md: replace the misleading op.f(literal) caveat with
  an introspect-the-rendered-name recipe.
- architecture/dlq.md: record the convention-awareness invariant.
- planning/releases/0.10.2.md: release notes.
- planning: remove stale draft duplicate of the shipped #99 bundle from active/
  (canonical copy already in archive/).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@lesnik512 lesnik512 merged commit b95225a into main Jun 16, 2026
3 checks passed
@lesnik512 lesnik512 deleted the fix/validate-schema-naming-convention branch June 16, 2026 19:33
lesnik512 added a commit that referenced this pull request Jun 17, 2026
#102 made the CHECK probe predict the constraint name off the Table object,
which under a `ck` naming_convention resolves to the doubled
`ck_<table>_<table>_lease_ck`. But a hand-written migration
(`op.create_check_constraint('<table>_lease_ck', ...)`) creates the literal
name verbatim — Alembic op functions don't apply target_metadata's
convention — so the probe demanded a name the migration never produces and
false-failed a valid schema. The live name is unpredictable from the package
side; only the predicate is stable.

_validate_check_constraints_sync now matches the lease CHECK by predicate, not
name: it passes if any live CHECK enforces
`(acquired_token IS NULL) = (acquired_at IS NULL)` under any name, and
otherwise reports `missing CHECK constraint enforcing '<predicate>'`. Removes
_resolve_check_constraint_name and the now-unused CheckConstraint import. A
drifted predicate now reads as "missing" (the correct one is absent); the
Alembic-blind remediation pointer still fires.

- tests: predicate matched under the convention-doubled name, under the literal
  name, missing-describes-predicate, drifted-reads-as-missing (unit), plus an
  end-to-end Postgres pass for convention metadata + a literally-named CHECK.
- docs/operations/alembic.md: the CHECK name doesn't matter; match the predicate.
- architecture/dlq.md: record the match-by-predicate invariant.
- planning/releases/0.10.3.md: release notes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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