黄云龙 88a1311fe8
fix(DB): legacy backfill creates missing Index objects on existing tables (#4090)
* fix(DB): legacy backfill creates missing Index objects on existing tables

_run_baseline_create_all_sync calls create_all(tables=..., checkfirst=True).
SQLAlchemy's Table.create(checkfirst=True) skips the table AND all its Index
objects when the table already exists, so an index added to the ORM model after
the table was first provisioned (e.g. the partial unique index
uq_channel_connection_active_identity on channel_connections) is never created,
and stamping 0001_baseline skips alembic's own create_index call too.

Fix: after create_all, explicitly create every Index on every baseline table
with Index.create(checkfirst=True), which checks for the specific index
independently of the table.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(bootstrap): note future-index backfill collision risk for revisions

Add a short forward-looking block under the legacy index-level backfill (at
backend/packages/harness/deerflow/persistence/bootstrap.py:308-322) pointing
the next contributor at the same shape risk the module already documents for
_baseline_TABLE_NAMES: if a future post-baseline revision adds an index to a
baseline table via op.create_index(...) without checkfirst=True, the backfill
above will already have pre-created it and the upgrade will collide with
'index already exists'. Mirror safe_add_column and use checkfirst=True (or a
future safe_create_index helper) in such a revision.

* fix(#4090): scope legacy backfill index loop to _BASELINE_INDEX_NAMES

willem-bd identified that the index loop iterated table.indexes (current
ORM models full set), which includes post-baseline indexes like
uq_runs_thread_active from 0004. Creating these prematurely before their
owning revisions dedup step raises IntegrityError on legacy DBs with
duplicate active rows -- bricking bootstrap.

Changes:
- Add _BASELINE_INDEX_NAMES: frozenset of 23 indexes that 0001_baseline
  actually creates (mirrors _BASELINE_TABLE_NAMES pattern)
- Guard index-creation loop with _BASELINE_INDEX_NAMES filter
- Wrap each idx.create() in try/except for graceful duplicate-data handling
  (uq_channel_connection_active_identity has no owning revision dedup)
- Fix misleading checkfirst=True comment for alembic op.create_index
- Add guard test pinning _BASELINE_INDEX_NAMES against 0001 output
- Add regression test with duplicate active runs (the exact crash case)
- Add regression test with duplicate channel connections (graceful handling)

* fix(tests): update legacy backfill fixtures to current schema

- Seed runs and channel_connections using the actual 0001 baseline columns.
- Preserve the duplicate-active-row conditions exercised by the regression tests.
- Apply ruff formatting required by backend CI.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-14 21:41:35 +08:00
..