"""canary DB 에 schema 를 올린다 — **별도 프로세스의 입구**. ★원본에 안 닿는다.

Codex ㉢ (2026-08-31) —

> fresh subprocess + allowlist env 로 실행하고, import 전에 같은 isolation
> gate 를 태우십시오. `env.py` 가 실제 `DATABASE_URL` 을 다시 덮지 않는지
> **끝점에서 잡고**, migration connection 안에서
> `current_database()==theroad_canary_<run_id>` 를 확인한 뒤에만 upgrade.
> 완료 후 `alembic_version==head` 확인.

## ★왜 「올린 뒤」로 잡나

`alembic/env.py:32` 는 `config.set_main_option("sqlalchemy.url",
settings.database_url)` 로 **제가 정한 URL 을 덮어쓴다.** 그래서 「이 연결
안에서 확인한다」를 `env.py` 를 고치지 않고는 할 수 없다. 대신 —

    ①`settings.database_url` 이 canary URL 인지 **올리기 전에** 본다
      (env.py 가 읽는 **바로 그 값**이다)
    ②올린 **뒤** canary DB 에 붙어 `alembic_version == head` 인지 본다
      ★env.py 가 딴 데로 갔으면 canary DB 는 **head 가 아니다**

②가 결정적이다 — 의도가 아니라 **결과**를 본다.

    python tools/grounding_audit/canary_alembic.py <run_id>
"""
from __future__ import annotations

import sys
from pathlib import Path
from typing import Any, Dict

#: ★`parents[2]` 는 **`backend/`** 다 — 저장소 뿌리가 아니다.
#:  앞 판은 여기에 다시 `"backend"` 를 붙여 `backend/backend/alembic.ini` 를
#:  가리켰다 (Codex 실측 2026-08-31). 시험이 「wrapper 를 가리키는지」만 보고
#:  **실제로 열어 보지 않아서** 놓쳤다.
BACKEND = Path(__file__).resolve().parents[2]
ALEMBIC_INI = BACKEND / "alembic.ini"
sys.path.insert(0, str(BACKEND))

from tools.grounding_audit import canary_isolation as ci  # noqa: E402


def upgrade(run_id: str, *, env: Dict[str, str]) -> Dict[str, Any]:
    """★문을 다 지난 뒤에만 표를 세운다. 결과를 **canary DB 에서** 확인한다.

    ★★★**빈 DB 에는 `alembic upgrade` 가 안 통한다** (실측 2026-08-31).
    저장소의 첫 migration 이 `CREATE INDEX ... ON scene_still` 이라 **표가
    이미 있다고 전제**한다 — alembic 역사가 기존 schema 위에서 시작했다.
    빈 DB 의 표를 세우는 production 길은 `init_db()` 다(시험 DB 도 그 길:
    `database.py:46` 「test PG DB 에서는 init_db 시점에 만들어진다」).

    그래서 —

        ①연결 **안에서** 어느 DB 인지 본다 (env.py 가 아니라 여기서)
        ②`init_db()` 로 표를 세운다 — production 과 **같은 함수**
        ③`alembic stamp head` 로 판 번호를 맞춘다
        ④`alembic_version == head` 를 **canary DB 에서** 확인한다
    """
    from alembic import command
    from alembic.config import Config
    from alembic.script import ScriptDirectory
    from sqlalchemy import text

    got = ci.assert_isolated(run_id, env=env)
    ci.assert_database_module_not_loaded()

    # ★①`env.py` 가 읽는 **바로 그 값**을 본다
    from app.core.config import settings

    want = ci.db_url(run_id)
    if str(settings.database_url) != want:
        raise ci.IsolationRefused(
            f"`settings.database_url` 이 {ci.masked(settings.database_url)!r} "
            f"다 — {ci.masked(want)!r} 여야 한다")

    import os

    os.environ["THEROAD_CANARY_RUN_ID"] = str(run_id)
    if not ALEMBIC_INI.is_file():
        raise ci.IsolationRefused(f"alembic.ini 를 못 찾았다: {ALEMBIC_INI}")
    cfg = Config(str(ALEMBIC_INI))
    head = ScriptDirectory.from_config(cfg).get_current_head()

    # ★②표를 세우기 **전에** 연결 안에서 DB 를 확인한다
    from app.core.database import engine, init_db

    with engine.connect() as con:
        now = con.execute(text("SELECT current_database()")).scalar()
        ci.assert_migration_connection(run_id, current_database=now)
    init_db()                               # ★production 과 같은 함수
    command.stamp(cfg, "head")              # ★③판 번호를 맞춘다

    # ★④**결과**를 canary DB 에서 본다
    with engine.connect() as con:
        now2 = con.execute(text("SELECT current_database()")).scalar()
        ci.assert_migration_connection(run_id, current_database=now2)
        ver = con.execute(
            text("SELECT version_num FROM alembic_version")).scalar()
        n = con.execute(text(
            "SELECT count(*) FROM information_schema.tables "
            "WHERE table_schema='public'")).scalar()
    ci.assert_at_head(alembic_version=ver, head=head)
    return {"db": now2, "alembic_version": ver, "head": head,
            "tables": int(n or 0), "how": "init_db + stamp", "ok": True}


def main() -> int:
    if len(sys.argv) < 2:
        print(__doc__)
        return 2
    import os

    got = upgrade(sys.argv[1], env=dict(os.environ))
    print(f"■ {got['db']} · 표 {got['tables']}개 · "
          f"alembic_version {got['alembic_version']} (head {got['head']})")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
