"""파이프라인 실행 중 만나는 예외. 예전 엔진은 모르는 step_type/rule_type 을 만나면 아무 일도 안 하고 텍스트를 그대로 통과시켰다(조용한 no-op). 오타 난 스텝 이름이 API 호출 한 번 없이 "성공"으로 지나가는 버그였다. 지금은 두 겹으로 막는다: 1. **생성 시점 검증** -- `create_pipeline` 이 steps 목록 전체를 STEP_REGISTRY 에서 해석한다. 모르는 이름은 run 이 시작되기도 전에 여기 예외로 죽는다. 2. 에러 메시지에 등록된 스텝 목록을 실어, 오타인지 미구현인지 바로 보이게 한다. (`.agents/01-plans/harness/2026-08-09-chosun-proofread-evalsample-migration.md` §5-C) """ from __future__ import annotations def _format_available(available: list[str] | None) -> str: if not available: return "" return f" (registered steps: {', '.join(available)})" class UnknownStepTypeError(ValueError): """step `type` 이 STEP_REGISTRY 에 등록돼 있지 않을 때.""" def __init__(self, step_type: str, available: list[str] | None = None) -> None: super().__init__(f"Unknown pipeline step type: {step_type!r}{_format_available(available)}") self.step_type = step_type class KnowledgeAssetError(FileNotFoundError): """스텝이 선언한 지식 자산(치환 사전·화이트리스트 등)을 읽을 수 없을 때. 2026-08-11 이전에는 이걸 삼켰다 -- `except Exception: warning; vocabulary = []`. 그 결과 `pipeline_prod_251231` 의 vocab 스텝 2개가 **빈 사전으로 통과**하며 run 은 "성공"으로 끝났고, 운영 기준선 점수가 오랫동안 치환 없이 측정됐다 (실측: prod 0개 로드 vs v24 146개). 모르는 스텝 이름을 즉시 죽이면서 지식 자산 유실은 통과시키는 건 앞뒤가 안 맞는다 -- 둘 다 "선언한 것이 실행되지 않았다"는 같은 사고다. 선언 자체가 없으면(키 부재) 이 예외는 나지 않는다 -- 그건 정상적인 no-op 이다. """ def __init__(self, asset: str, searched: list[str]) -> None: locations = "\n ".join(searched) super().__init__( f"선언된 지식 자산을 읽을 수 없습니다: {asset!r}\n {locations}\n" f"(config 자산은 03-evaluation/resources/ 아래, 데이터 자산은 데이터 루트 기준)" ) self.asset = asset self.searched = searched class UnknownRuleTypeError(UnknownStepTypeError): """legacy `type: rule` 스텝의 `rule_type` 이 등록돼 있지 않을 때. `UnknownStepTypeError` 의 하위 타입이다 -- rule 은 이제 별도 디스패치 계층이 아니라 레지스트리 항목일 뿐이고, 이 클래스는 기존 호출부/테스트와의 호환용 구분자로만 남는다. """ def __init__(self, rule_type: str, available: list[str] | None = None) -> None: ValueError.__init__( self, f"Unknown pipeline rule type: {rule_type!r}{_format_available(available)}" ) self.step_type = rule_type self.rule_type = rule_type