한 줄 요약

완료를 단언하는 문장(done·완료·통과 등)이 실측 증거 토큰 없이 등장하면 차단하는 fail-closed 게이트다. 왜 배우나: 에이전트가 “다 했습니다”라고 말만 하고 실제로는 안 한 경우를 코드 한 줄 호출로 결정론적으로 걸러내기 위해서다.

그림

flowchart TD
    A["입력 텍스트 / Claim"] --> B{"검사 진입점"}
    B -->|"check_claim(Claim)"| C{"완료어 등장?<br/>COMPLETION_RE"}
    B -->|"scan_text(str)"| S0{"입력이 비었나?"}

    C -->|"없음"| P1["passed=True<br/>게이트 대상 아님"]
    C -->|"있음"| D{"evidence 비었나?"}
    D -->|"비어 있음"| F1["passed=False<br/>fail-closed: 증거 없음"]
    D -->|"있음"| E{"증거 토큰 중<br/>ACCEPTED_EVIDENCE_RE 매치?"}
    E -->|"하나도 없음"| F2["passed=False<br/>fail-closed: 증거 불일치"]
    E -->|"하나 이상"| P2["passed=True<br/>증거로 뒷받침됨"]

    S0 -->|"비었음"| F3["passed=False<br/>fail-closed: 검증 불가"]
    S0 -->|"내용 있음"| R{"범위-축소 패턴<br/>SCOPE_REDUCTION 매치?"}
    R -->|"매치"| F4["passed=False<br/>즉시 위반 (증거 무관)"]
    R -->|"없음"| Q{"약속 패턴 매치<br/>그리고 완료-증거 전무?"}
    Q -->|"예"| F5["passed=False<br/>약속-미실행"]
    Q -->|"아니오"| P3["passed=True<br/>early-stop 패턴 없음"]

쉽게 풀기

회사에 “끝냈습니다”라고 보고하는 직원이 있다고 하자. 정직한 관리자라면 보고를 곧이곧대로 믿지 않고 “증거를 보여줘”라고 한다. Evidence Gate가 바로 이 관리자다.

직원의 보고서를 받으면 관리자는 두 가지 방식으로 검사한다.

  1. 단계 하나: 보고서에 “끝”, “완료”, “통과”, “배포됨” 같은 완료 선언이 있는지 본다. 없으면 애초에 “끝났다”는 주장 자체가 없으니 통과시킨다. 트집 잡을 일이 없다.
  2. 단계 둘: 완료 선언이 있다면, 그 옆에 증거가 붙어 있는지 본다. 여기서 인정되는 증거는 정해져 있다. 종료 코드(exit code), 테스트 통과/실패 기록, grep 결과, “3개 발견” 같은 개수, 실제 파일 경로(예: src/app.py), 렌더링 결과, 스크린샷. 이 중 하나도 없으면 “말로만 했네” 하고 차단한다.

핵심 철학은 “의심스러우면 막는다(fail-closed)“이다. 보통의 시스템은 “문제 없어 보이면 통과”인데, 이 게이트는 반대다. 증거가 확실하지 않으면 무조건 실패 처리한다. 입력이 아예 비어 있어도 “검증할 수 없으니 실패”다.

두 번째 검사기(scan_text)는 또 다른 두 가지 나쁜 습관을 잡는다. 하나는 “범위 축소” — “시간 관계상 나머지는 생략하겠습니다” 같은 꼼수. 이건 증거가 있건 없건 보이는 즉시 위반이다. 다른 하나는 “약속만 하고 안 함” — “이제 구현하겠습니다”라고 말해놓고 정작 했다는 증거(“구현했”, “통과”, “exit 0” 등)가 한 줄도 없는 경우. 약속만 있고 실행 증거가 없으면 막는다. 단, “구현하겠습니다 … 구현했고 테스트 통과”처럼 약속 뒤에 실행 증거가 함께 있으면 정상 보고로 보고 통과시킨다(오탐 방지).

핵심 정리

항목함수/패턴판정 기준결과
완료 단언 검증check_claim완료어 있는데 증거 비었거나 불일치passed=False
완료어 없음check_claimCOMPLETION_RE 미매치passed=True (대상 아님)
범위 축소scan_textSCOPE_REDUCTION 매치passed=False (즉시)
약속-미실행scan_text약속 있고 완료-증거 전무passed=False
빈 입력scan_texts가 빈 문자열passed=False (fail-closed)

실제 예시

# fablelayer/evidence_gate.py — 핵심 자료구조와 진입점
 
@dataclass(frozen=True)
class Claim:
    text: str
    evidence: tuple[str, ...]
 
@dataclass(frozen=True)
class GateResult:
    passed: bool
    reasons: tuple[str, ...]
 
 
def check_claim(claim: Claim) -> GateResult:
    has_completion = _COMPLETION.search(claim.text) is not None
    if not has_completion:
        return GateResult(passed=True,
                          reasons=("no completion claim; gate not applicable",))
    if not claim.evidence:
        return GateResult(passed=False,
                          reasons=("completion claim present but evidence empty (fail-closed)",))
    matched = tuple(e for e in claim.evidence if _has_accepted_evidence(e))
    if not matched:
        return GateResult(passed=False,
                          reasons=("completion claim present but no evidence token matches "
                                   "ACCEPTED_EVIDENCE_RE (fail-closed)",))
    return GateResult(passed=True,
                      reasons=(f"completion claim grounded by {len(matched)} evidence token(s)",))
# 사용 예시 — 호출 측 관점
 
# 1) 증거 없는 완료 단언 → 차단
r = check_claim(Claim(text="구현 완료했습니다", evidence=()))
# r.passed == False
# r.reasons == ("completion claim present but evidence empty (fail-closed)",)
 
# 2) 증거가 있으나 인정 패턴에 안 맞음 → 차단
r = check_claim(Claim(text="통과", evidence=("아마 될 거예요",)))
# r.passed == False  (ACCEPTED_EVIDENCE_RE 불일치)
 
# 3) 인정 증거 동반 → 통과
r = check_claim(Claim(text="완료", evidence=("exit code 0", "tests 12 pass")))
# r.passed == True
 
# 4) 완료어 자체가 없음 → 게이트 대상 아님 → 통과
r = check_claim(Claim(text="작업을 검토 중입니다", evidence=()))
# r.passed == True
# scan_text — early-stop.sh 포팅 (약속-미실행 + 범위-축소)
 
# 범위 축소 꼼수 → 즉시 위반 (증거 무관)
scan_text("핵심만 구현했고 exit 0, 나머지는 생략하겠습니다").passed   # False
 
# 약속만 하고 실행 증거 전무 → 위반
scan_text("이제 테스트를 작성하겠습니다").passed                       # False
 
# 약속 + 실행 증거 동반 → 통과
scan_text("구현하겠습니다. 구현했고 tests passed, exit 0").passed     # True
 
# 빈 입력 → fail-closed
scan_text("").passed                                                  # False

요약 & 셀프체크

3줄 요약:

  • check_claim은 완료어가 있는데 인정 증거(ACCEPTED_EVIDENCE_RE)가 없으면 passed=False로 막는다.
  • scan_text는 범위-축소 패턴은 즉시 위반, 약속 패턴은 완료-증거가 전혀 없을 때만 위반으로 처리한다.
  • 전 구간이 fail-closed: 불확실하거나 입력이 비면 통과가 아니라 실패다.

질문:

  1. check_claim(Claim(text="완료", evidence=("나중에 확인"))처럼 증거가 비어있지 않지만 인정 패턴에 안 맞으면 결과는 무엇이고 그 이유 문자열은 어떤 것인가?
  2. scan_text에서 “구현하겠습니다”가 들어 있어도 통과될 수 있는 조건은 무엇인가? 어떤 패턴 묶음이 그 통과를 만드는가?
  3. 범위-축소 패턴과 약속-미실행 패턴이 위반을 판정하는 방식의 결정적 차이는 무엇인가(증거 고려 여부 관점)?

연결

FL_개요 · _분석축_루브릭