"""배포 확정 감사 로그 (스펙 §5-2) — Postgres 영속화. 컨테이너 파일시스템은 재배포 시 휘발하므로 감사 기록은 제공된 Postgres(DATABASE_URL)에 남긴다. 누가 / 언제 / 어떤 repo / 이전 버전 → 대상 버전 (롤백 추적). psycopg 는 지연 import 한다 — DB 미사용 환경/테스트에서 드라이버 없이도 모듈이 로드되도록. """ from __future__ import annotations from typing import Any from .config import get_settings _SCHEMA = """ CREATE TABLE IF NOT EXISTS deploy_audit ( id bigserial PRIMARY KEY, ts timestamptz NOT NULL DEFAULT now(), operator text NOT NULL, repo_uuid text NOT NULL, repo_name text NOT NULL, from_version integer, to_version integer, publication text NOT NULL ) """ _INSERT = """ INSERT INTO deploy_audit (operator, repo_uuid, repo_name, from_version, to_version, publication) VALUES (%(operator)s, %(repo_uuid)s, %(repo_name)s, %(from_version)s, %(to_version)s, %(publication)s) """ def _write(entry: dict[str, Any]) -> None: """감사 항목 1건을 DB 에 기록 (테이블 없으면 생성). 테스트에서 monkeypatch 지점.""" import psycopg # 지연 import dsn = get_settings().database_url if not dsn: raise RuntimeError("DATABASE_URL 미설정 — 감사 로그를 기록할 수 없습니다.") with psycopg.connect(dsn) as conn, conn.cursor() as cur: cur.execute(_SCHEMA) cur.execute(_INSERT, entry) def record_deploy( *, repo_uuid: str, repo_name: str, from_version: int | None, to_version: int | None, publication_href: str, operator: str = "operator", # TODO(auth): 로그인 도입 시 실제 사용자. 배포 시 비밀번호 재확인 예정. ) -> dict[str, Any]: entry = { "operator": operator, "repo_uuid": repo_uuid, "repo_name": repo_name, "from_version": from_version, "to_version": to_version, "publication": publication_href, } _write(entry) return entry