diff --git a/docs/data-model.md b/docs/data-model.md index 86177bd..18b04c1 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -372,7 +372,7 @@ - Alembic revisions in migrations/versions/ are the ordered schema history for persistent environments. - app/repositories/postgres.py includes ensure_schema() for bootstrap safety, but long-term schema evolution should run through Alembic. -- Current Alembic head is `20260814_0029`; its strategy columns, default seed, and constraints are guarded for ensure_schema-first and repeatable execution. +- Current Alembic head is `20260820_0030`; the preceding `0029` strategy columns, default seed, and constraints and the `0030` unified-ledger keyset index are guarded for ensure_schema-first and repeatable execution. - Comments, tags, auth tokens, and ledger rows are normalized into dedicated tables keyed to users or portfolios. ## Current Gaps / Planned Changes diff --git a/docs/project-structure.md b/docs/project-structure.md index 16d13ff..3acf709 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -138,7 +138,7 @@ |-----------|-----------|------| | ensure_schema() 전용 | `idx_auth_tokens_service_name_unique` (partial index, BAT-006/REQ-017: `AND revoked_at IS NULL` 조건 추가) | Alembic revision 20260520_0022에도 CONCURRENTLY 버전 존재 | | Alembic 전용 | `portfolios.market`, `auth_tokens.runtime_id`, `idx_auth_tokens_runtime_id_unique`, `broker_credentials`, `ledger_trades` | ensure_schema()에 없음 — v1 API 전제 조건 | -| 양쪽 공유 (IF NOT EXISTS 보호) | users, auth_tokens, strategy_sets (`status`, `revision`, `updated_at` 포함), portfolios, portfolio_ledger, portfolio_comments, portfolio_tags + 대부분의 인덱스 | ensure_schema() 우선 실행 후 Alembic `20260814_0029` 실행 시 충돌 없음; 기본 전략 seed/constraints도 존재 여부 guard 적용 | +| 양쪽 공유 (IF NOT EXISTS 보호) | users, auth_tokens, strategy_sets (`status`, `revision`, `updated_at` 포함), portfolios, portfolio_ledger, portfolio_comments, portfolio_tags + 대부분의 인덱스 | ensure_schema() 우선 실행 후 Alembic `20260820_0030`까지 실행 시 충돌 없음; 기본 전략 seed/constraints와 unified-ledger keyset index도 존재 여부 guard 적용 | ### 허용 실행 순서 diff --git a/scripts/image_release.py b/scripts/image_release.py index 22f16ba..497a82d 100755 --- a/scripts/image_release.py +++ b/scripts/image_release.py @@ -25,8 +25,8 @@ COMMIT_RE = re.compile(r"^[0-9a-f]{40}$") VERSION_REF_RE = re.compile(r"^refs/tags/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$") IMAGE_ID_RE = re.compile(r"^sha256:[0-9a-f]{64}$") DIGEST_RE = re.compile(r"(?:^|\s)digest:\s*(sha256:[0-9a-f]{64})(?:\s|$)") -EXPECTED_MIGRATION_COUNT = 29 -EXPECTED_ALEMBIC_HEAD = "20260814_0029" +EXPECTED_MIGRATION_COUNT = 30 +EXPECTED_ALEMBIC_HEAD = "20260820_0030" @dataclass(frozen=True) class Result: diff --git a/tests/release/test_image_release.py b/tests/release/test_image_release.py index c6952ad..aa8625a 100644 --- a/tests/release/test_image_release.py +++ b/tests/release/test_image_release.py @@ -33,8 +33,8 @@ class RecordingRunner: if command[:3] == ["docker", "run", "--rm"] and "urllib.request" in command[-1]: enabled = "TARGET:8000" not in command[-1] and "plain" not in command[-1] return image_release.Result('{"status":"ok","db_enabled":' + str(enabled).lower() + '}') - if command[:3] == ["docker", "run", "--rm"] and "alembic_version" in command[-1]: return image_release.Result("20260814_0029\n") - if command[:3] == ["docker", "run", "--rm"] and "health-check" in command: return image_release.Result('{"database_revision":"20260814_0029"}\n') + if command[:3] == ["docker", "run", "--rm"] and "alembic_version" in command[-1]: return image_release.Result("20260820_0030\n") + if command[:3] == ["docker", "run", "--rm"] and "health-check" in command: return image_release.Result('{"database_revision":"20260820_0030"}\n') return image_release.Result() def artifacts(tmp_path: Path): @@ -73,13 +73,13 @@ def test_project_version_must_match_release_tag(tmp_path): def test_installed_smoke_has_import_cli_exact_inventory_and_cleanup(): inventory = tuple( - [f"202601{i:02d}_{i:04d}_migration.py" for i in range(1, 29)] - + ["20260814_0029_strategy_catalog_v1.py"] + [f"202601{i:02d}_{i:04d}_migration.py" for i in range(1, 30)] + + ["20260820_0030_portfolio_ledger_keyset_index.py"] ) runner = RecordingRunner(); image_release.smoke_installed_image("candidate", inventory, runner, 17) runs = [x[0] for x in runner.commands if x[0][:2] == ["docker","run"]] assert len(runs) == 3 and all("--read-only" in x and "none" in x for x in runs) - assert "migration_inventory" in runs[-1][-1] and "20260814_0029" in runs[-1][-1] + assert "migration_inventory" in runs[-1][-1] and "20260820_0030" in runs[-1][-1] assert len([x for x in runner.commands if x[0][:3] == ["docker","rm","--force"]]) == 3 def _assert_exact_startup_cleanup(commands: list[list[str]]) -> None: