# BAT-021 Implementation ## 변경 파일 ### `app/repositories/postgres.py` **T-1: ensure_schema() broker_credentials CREATE TABLE 갱신** 구 정의 (Step1 이전): ```sql PRIMARY KEY (user_id, broker) -- daemon_id 없음 ``` 신 정의 (Step2 이후): ```sql daemon_id TEXT NOT NULL, broker TEXT NOT NULL, user_id TEXT NOT NULL, account_ref TEXT NOT NULL, encrypted_payload TEXT NOT NULL, credential_version INTEGER NOT NULL DEFAULT 1, last_rotated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (daemon_id, broker) ``` **T-2: `_row_to_broker_credential` Step2 컬럼 순서 적용** 구 매핑: `row[0]=user_id, row[1]=broker, ..., row[6]=daemon_id` 신 매핑: `row[0]=daemon_id, row[1]=user_id, row[2]=broker, row[3]=account_ref, row[4]=encrypted_payload, row[5]=credential_version, row[6]=last_rotated_at` **T-3: `upsert_broker_credential` ON CONFLICT 수정 + fallback 제거** - `ON CONFLICT (user_id, broker)` → `ON CONFLICT (daemon_id, broker)` - `RETURNING` 컬럼 순서: `daemon_id, user_id, broker, account_ref, encrypted_payload, credential_version, last_rotated_at` - `try/except` fallback 완전 제거 (트랜잭션 abort 이슈 동시 해소) **T-4: `list_broker_credentials` fallback 제거 + SELECT 컬럼 순서 수정** - `try/except` fallback 완전 제거 - `SELECT daemon_id, user_id, broker, ...` 순서로 변경 ### `tests/test_bat020_broker_credential_domain.py` - `test_row_to_broker_credential_with_daemon_id`: 행 순서를 Step2 컬럼 순서로 수정 (AC-3 갱신) - `test_row_to_broker_credential_without_daemon_id`: Step2 5컬럼 기본값 테스트로 갱신 (AC-4 갱신) - `_make_rows_for_user`: Step2 컬럼 순서로 갱신 ### 신규 파일 - `tests/test_bat021_step2_ensure_schema_and_upsert.py`: 12개 신규 테스트 - `docs/runbooks/bat-021-req047-step2-migration-runbook.md` - `docs/batches/bat-021_20260628_req047-step2-integration-and-runbook/` (index, planning, design, implementation, verification) ## 테스트 count (구현 후) - 기존: 509 passed - 신규: 12 추가 (test_bat021_step2_ensure_schema_and_upsert.py) - 합계: 521 passed