Skip to content

Keep fresh database migrations idempotent - #782

Merged
Y1fe1Zh0u merged 2 commits into
mainfrom
fix/fresh-install-migration-idempotency
Jul 24, 2026
Merged

Keep fresh database migrations idempotent#782
Y1fe1Zh0u merged 2 commits into
mainfrom
fix/fresh-install-migration-idempotency

Conversation

@Y1fe1Zh0u

@Y1fe1Zh0u Y1fe1Zh0u commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Root cause

The initial_schema revision creates tables from current ORM metadata on a fresh database. The logical-deletion models already contain the deleted_at columns and active partial indexes, but add_agent_model_deleted_at attempted to create the same objects unconditionally. Fresh deployments therefore failed with PostgreSQL DuplicateColumnError before application startup.

What changed

  • Inspect each logical-deletion column and index before creating it.
  • Handle partial schemas object by object instead of using one migration-wide early return.
  • Guard downgrade operations against missing objects.
  • Add focused migration tests for missing, complete, and partial schemas.
  • Add a standalone Drone stage that starts an empty PostgreSQL database and runs alembic upgrade head without starting the backend application, Redis, or frontend.
  • Verify both deleted_at columns, both active partial indexes, and the Alembic head after migration.
  • Preserve upgrade coverage when v1.11.2 is the source release by building its database through the parent revision and applying the corrected head before starting the old application.

Impact

Fresh installations can reach the current Alembic head even though the initial revision has already materialized the current logical-deletion schema. Existing databases still receive any missing columns or indexes during normal upgrades.

Validation

  • 12 scoped backend tests passed.
  • Ruff passed for changed Python files.
  • Shell syntax validation passed.
  • Docker Compose configuration validation passed.
  • Drone and Compose YAML parsing passed.
  • git diff --check passed.
  • Drone build 377 passed:
    • fresh-database-migration-test
    • local-deploy-test
    • local-upgrade-test from v1.11.2
    • cleanup-ci-images

The successful upgrade reached add_agent_model_deleted_at head and LangGraph checkpoint version 9 while preserving the database and workspace sentinels.

Follow-up scope

This hotfix does not rewrite initial_schema or change dependency resolution:

  1. Replace the dynamic ORM-backed initial revision with an audited static baseline in a separate migration-governance PR.
  2. Pin Alembic, SQLAlchemy, and asyncpg exact versions in a separate reproducible-build PR.

Those stability improvements require broader audits and are intentionally separated from the deployment-blocking fix.

The initial revision builds the current ORM schema on empty databases, so the logical-deletion revision must tolerate its columns and indexes already existing. Inspect each object independently and add a migration-only empty-database CI stage so this path fails before full application startup.

Constraint: The dynamic initial_schema baseline remains unchanged in this hotfix.

Rejected: Rewrite the historical baseline here | requires a full migration-chain schema audit and would broaden an urgent deployment fix.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Keep new schema revisions compatible with fresh metadata creation until initial_schema is replaced by a static baseline.

Tested: 12 scoped pytest tests; Ruff on changed Python; shell syntax; Compose config; YAML parsing; git diff check.

Not-tested: Local PostgreSQL container run because the Docker daemon is unavailable; the new Drone stage provides this verification.
The upgrade job selects v1.11.2 as its source, but that released image cannot bootstrap an empty database because it contains the duplicate-column migration. Build its schema through the parent revision, apply the corrected head with the target image, then continue the existing old-app and target-app upgrade assertions.

Constraint: v1.11.2 is the latest release tag and is itself unable to initialize the CI source database.

Rejected: Skip the upgrade job or ignore its failure | would leave the PR red and remove existing-database coverage.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Remove the v1.11.2 bootstrap compatibility path after it is no longer a supported upgrade source.

Tested: POSIX shell syntax; 12 scoped pytest tests; git diff check.

Not-tested: Local Docker execution because the Docker daemon is unavailable; Drone exercises this exact path.
@Y1fe1Zh0u
Y1fe1Zh0u marked this pull request as ready for review July 24, 2026 04:05
@Y1fe1Zh0u
Y1fe1Zh0u merged commit 6fa42ed into main Jul 24, 2026
1 check 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