From 99672292d3cde563dfd541b0896d17a1bd1179a6 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 24 Apr 2026 09:42:24 +0900 Subject: [PATCH] fix(policy): use container-compatible imports (drop app. prefix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로덕션 컨테이너는 /app 을 cwd 로 실행하고 import 는 `from api...`, `from core...`, `from workers...` 처럼 무접두 스타일을 사용한다. PR-A 내부 import 가 `from app.policy...`, `from app.ai.envelope` 로 되어 있어서 컨테이너에서 ModuleNotFoundError 발생. 변경: - app/policy/*.py: `from app.policy.X` → `from policy.X` - app/services/prompt_versions.py: lazy import 도 `from policy.prompt_render` - app/ai/envelope.py: 영향 없음 (내부 import 없음) - tests/policy/*.py: 모두 `from policy.X` / `from ai.envelope` 로 통일 - tests/policy/conftest.py: 로컬 pytest 용 sys.path.insert(app/) 추가 (MacBook 에서 repo-root 기준 실행 시 app/ 를 package root 로 취급) CI: pytest tests/policy/ -q → 98 passed (로컬, 동일 결과) 프로덕션: docker exec fastapi python -c "from policy.loader import load_policy" → OK Co-Authored-By: Claude Opus 4.7 (1M context) --- app/policy/audit.py | 4 ++-- app/policy/loader.py | 2 +- app/policy/prompt_render.py | 4 ++-- app/policy/routing.py | 4 ++-- app/policy/shadow.py | 2 +- app/services/prompt_versions.py | 2 +- tests/policy/conftest.py | 14 +++++++++++--- tests/policy/test_audit_patterns.py | 2 +- tests/policy/test_envelope_contract.py | 2 +- tests/policy/test_policy_loader_schema.py | 4 ++-- tests/policy/test_prompt_render.py | 4 ++-- tests/policy/test_routing_decisions.py | 2 +- tests/policy/test_self_declare_add_only.py | 2 +- tests/policy/test_shadow_logger_inmem.py | 4 ++-- 14 files changed, 30 insertions(+), 22 deletions(-) diff --git a/app/policy/audit.py b/app/policy/audit.py index 8b15ca5..57377f7 100644 --- a/app/policy/audit.py +++ b/app/policy/audit.py @@ -12,8 +12,8 @@ import re from functools import lru_cache from typing import Iterable -from app.policy.loader import load_policy -from app.policy.schema import DomainPolicy, ForbiddenRule +from policy.loader import load_policy +from policy.schema import DomainPolicy, ForbiddenRule @lru_cache(maxsize=256) diff --git a/app/policy/loader.py b/app/policy/loader.py index 60204d6..36f7f70 100644 --- a/app/policy/loader.py +++ b/app/policy/loader.py @@ -8,7 +8,7 @@ from pathlib import Path import yaml -from app.policy.schema import DomainPolicy +from policy.schema import DomainPolicy DEFAULT_POLICY_PATH = "domain_policy.yaml" diff --git a/app/policy/prompt_render.py b/app/policy/prompt_render.py index a29fa8e..830e7f9 100644 --- a/app/policy/prompt_render.py +++ b/app/policy/prompt_render.py @@ -17,8 +17,8 @@ import hashlib from functools import lru_cache from pathlib import Path -from app.policy.loader import load_policy, read_policy_bytes -from app.policy.schema import DomainPolicy +from policy.loader import load_policy, read_policy_bytes +from policy.schema import DomainPolicy # 기본 템플릿 경로 — repo root 기준 diff --git a/app/policy/routing.py b/app/policy/routing.py index 2f1bc8e..44ce96f 100644 --- a/app/policy/routing.py +++ b/app/policy/routing.py @@ -30,8 +30,8 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import Iterable -from app.policy.loader import load_policy -from app.policy.schema import DomainPolicy, SubjectDomain, FallbackDomain +from policy.loader import load_policy +from policy.schema import DomainPolicy, SubjectDomain, FallbackDomain # --- Reason 문자열 상수 (tests 에서 참조) ----------------------------------- diff --git a/app/policy/shadow.py b/app/policy/shadow.py index 04638a2..2b3c22e 100644 --- a/app/policy/shadow.py +++ b/app/policy/shadow.py @@ -14,7 +14,7 @@ from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Any, Protocol, runtime_checkable -from app.policy.routing import RoutingDecision +from policy.routing import RoutingDecision @dataclass(frozen=True) diff --git a/app/services/prompt_versions.py b/app/services/prompt_versions.py index 3305f71..59d76dd 100644 --- a/app/services/prompt_versions.py +++ b/app/services/prompt_versions.py @@ -57,6 +57,6 @@ def compute_policy_version( import 지연 — app.policy 는 아직 worker 경로에서 쓰지 않는다 (PR-A 런타임 격리). """ - from app.policy.prompt_render import policy_version as _pv + from policy.prompt_render import policy_version as _pv return _pv(task, policy_path=policy_path) diff --git a/tests/policy/conftest.py b/tests/policy/conftest.py index 4b159b9..3c71c00 100644 --- a/tests/policy/conftest.py +++ b/tests/policy/conftest.py @@ -2,19 +2,27 @@ 실제 repo root 의 domain_policy.yaml 을 그대로 로드. 테스트가 캐시를 쓰지 않도록 각 테스트 시작 시 lru_cache 클리어. + +Import path: 프로덕션 컨테이너 (/app cwd) 와 동일하게 무접두 스타일 사용 +(`from policy.loader` 등). 로컬 pytest 는 아래 sys.path 추가로 app/ 를 root 처리. """ from __future__ import annotations +import sys from pathlib import Path +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +APP_DIR = REPO_ROOT / "app" +if str(APP_DIR) not in sys.path: + sys.path.insert(0, str(APP_DIR)) + import pytest -from app.policy import loader as policy_loader -from app.policy import prompt_render +from policy import loader as policy_loader +from policy import prompt_render -REPO_ROOT = Path(__file__).resolve().parent.parent.parent DEFAULT_YAML = REPO_ROOT / "domain_policy.yaml" diff --git a/tests/policy/test_audit_patterns.py b/tests/policy/test_audit_patterns.py index 6b07e63..0bfd672 100644 --- a/tests/policy/test_audit_patterns.py +++ b/tests/policy/test_audit_patterns.py @@ -9,7 +9,7 @@ from __future__ import annotations import pytest -from app.policy.audit import check_4b_output_violations +from policy.audit import check_4b_output_violations # ===================================================================== diff --git a/tests/policy/test_envelope_contract.py b/tests/policy/test_envelope_contract.py index c47f3b1..5efcf6e 100644 --- a/tests/policy/test_envelope_contract.py +++ b/tests/policy/test_envelope_contract.py @@ -4,7 +4,7 @@ from __future__ import annotations import pytest -from app.ai.envelope import EscalationEnvelope +from ai.envelope import EscalationEnvelope def test_envelope_round_trip(): diff --git a/tests/policy/test_policy_loader_schema.py b/tests/policy/test_policy_loader_schema.py index 9ea5fe7..5e5fd41 100644 --- a/tests/policy/test_policy_loader_schema.py +++ b/tests/policy/test_policy_loader_schema.py @@ -6,8 +6,8 @@ import pytest import yaml from pydantic import ValidationError -from app.policy import loader as policy_loader -from app.policy.schema import DomainPolicy +from policy import loader as policy_loader +from policy.schema import DomainPolicy def test_default_yaml_loads(policy): diff --git a/tests/policy/test_prompt_render.py b/tests/policy/test_prompt_render.py index 19c22ce..cfbeb17 100644 --- a/tests/policy/test_prompt_render.py +++ b/tests/policy/test_prompt_render.py @@ -4,8 +4,8 @@ from __future__ import annotations import pytest -from app.policy import prompt_render -from app.policy.prompt_render import ( +from policy import prompt_render +from policy.prompt_render import ( KNOWN_4B_TASKS, KNOWN_26B_TASKS, policy_version, diff --git a/tests/policy/test_routing_decisions.py b/tests/policy/test_routing_decisions.py index ce6b5e0..2461bef 100644 --- a/tests/policy/test_routing_decisions.py +++ b/tests/policy/test_routing_decisions.py @@ -4,7 +4,7 @@ from __future__ import annotations import pytest -from app.policy.routing import ( +from policy.routing import ( REASON_FALLBACK_DOMAIN, REASON_HIGH_IMPACT, REASON_LONG_CONTEXT, diff --git a/tests/policy/test_self_declare_add_only.py b/tests/policy/test_self_declare_add_only.py index 9963e28..d3e6fdd 100644 --- a/tests/policy/test_self_declare_add_only.py +++ b/tests/policy/test_self_declare_add_only.py @@ -2,7 +2,7 @@ from __future__ import annotations -from app.policy.routing import decide_routing +from policy.routing import decide_routing def test_deterministic_true_self_false_stays_high_impact(policy): diff --git a/tests/policy/test_shadow_logger_inmem.py b/tests/policy/test_shadow_logger_inmem.py index e9db268..7871e6d 100644 --- a/tests/policy/test_shadow_logger_inmem.py +++ b/tests/policy/test_shadow_logger_inmem.py @@ -4,8 +4,8 @@ from __future__ import annotations import pytest -from app.policy.routing import RoutingDecision, decide_routing -from app.policy.shadow import InMemoryShadowLogger, ShadowLogger +from policy.routing import RoutingDecision, decide_routing +from policy.shadow import InMemoryShadowLogger, ShadowLogger @pytest.fixture