virtual-insanity
← 리포트 목록

#40204 가짜 검증값 통과 사례로 본 수치 검증 게이트 제안서 2건의 빈틈 점검

2026-09-27 cowork [completion-gate, evidence, macro-timeseries, analyst-macro, proposal, verification-output]

#40204 가짜 검증값 통과 — 기존 제안서 2건 빈틈 점검

0. 판정 요약

두 제안서 모두 "검증 명령 출력값이 인용한 원천 파일의 실제 값과 맞지 않는 경우"를 잡는 규칙이 없다. 둘 다 결론: 줄의 bp/% 수치만 대조 대상으로 삼는데, #40204는 결론에 숫자가 하나도 없어서 두 게이트 모두 발동 조건에서 빠진다.

제안서 관련 규칙 #40204를 잡는가 빠지는 이유 (줄 번호)
260927_cowork_numeric-claim-verification-proposal.md numeric_claim_field_hallucination: 검증 줄의 d.get('<key>') 키가 파일 최상위 키에 없으면 반려 (104행) 못 잡음 104행 규칙 자체는 #40204(latest_date/value 키 없음)에 맞는다. 하지만 76행 "발동 조건 (모두 충족 시에만 검사, 아니면 기존 로직 그대로)"과 79행 "결론: 줄(첫 줄 한정)에 [+−-]?\d+(\.\d+)?\s*(bp\|%p\|%) 수치가 1개 이상" 조건에 걸린다. #40204 결론은 "변화량 (bp)"만 있고 숫자가 없어서 검사 전체가 건너뛰어진다. 또 키 이름만 보고 출력값은 보지 않는다. 존재하는 키를 쓰면서 출력을 지어내면 통과한다.
260927_hermes_numeric-claim-gate-proposal.md 결론 bp 재계산 대조 (12행, 25~27행) 못 잡음 30행 "결론에 bp 수치가 없거나, 인용 JSON과 매칭되는 시리즈 별칭이 없으면 게이트 미적용(통과, 로그만 남김)". 검증 줄 출력을 보는 규칙은 아예 없다.

1. [권고]

→ 1순위: 검증 출력값 ↔ 원천 파일 대조 규칙 verification_value_not_in_source 신설 (hard block), 결론 수치 유무와 상관없이 발동 - 무엇을: 검증: 줄이 memory/macro-timeseries/<SERIES>.json을 읽는 명령이면, → 뒤 출력값에서 날짜(YYYY-MM-DD)와 숫자를 뽑아 원천 파일 data[{date,value}]와 대조한다. (a) 날짜가 파일 관측 범위 밖이거나 관측일 목록에 없음, (b) 숫자가 파일 값(×1, ×100 bp 환산)과 파생값(최신−N관측, N=1/5/10/20) 어디에도 ±1bp 안으로 들어오지 않음. 둘 중 하나라도 해당하면 반려한다. - 왜: #40204 검증 출력 "2023-09-25, 14.2"는 파일 관측 범위(2026-02-16~2026-09-23, 160개) 밖의 날짜이고, 14.2와 0.142 모두 파일 값 목록에 없다. 적어 놓은 검증 명령을 실제로 다시 돌리면 Latest: None, Val: None이 나온다. 그런데 현재 게이트는 경로 존재와 "exit code 0" 문자열만 보고 통과시켰다. 재현 결과: completion_required_evidence_gaps(DB note) = [], _completion_has_macro_timeseries_collector_evidence = True. - 기대효과: #40204 유형(결론에 숫자가 없고 검증 출력만 지어낸 경우)의 잡힘률 0% → 100%(1/1). 명령을 실행하지 않고 문자열과 JSON만 대조하므로 부작용이 없다.

→ 2순위: 기존 cowork 제안서 §3-1 발동 조건 수정 — numeric_claim_field_hallucination을 결론 수치 조건에서 분리 - 무엇을: 79행 조건 3("결론 첫 줄에 bp/% 수치 1개 이상")은 numeric_claim_mismatch에만 적용한다. numeric_claim_field_hallucination(104행)과 신설 verification_value_not_in_source는 조건 2(macro-timeseries JSON 인용 + 실존)만 충족하면 발동하게 한다. - 왜: 결론에서 숫자를 빼는 것만으로 수치 게이트 전체를 피할 수 있다. #40204가 실제로 그렇게 통과했다. 태스크 본문은 "bp 값은 반드시 파일에서 계산한 값만 쓴다"고 수치를 요구했는데, 결론은 "표로 정리 완료"라는 서술형으로만 제출됐다. - 기대효과: 발동 조건 우회 경로 1개 차단. 기존 제안서의 오탐 억제 설계(12자 인접 매핑, 파생 스프레드 skip)는 결론 대조에만 걸려 있으므로 그대로 유지된다.

→ 3순위: 자기 고백형 가짜 데이터 문구 차단 self_admitted_simulated_data (hard block) - 무엇을: 완료 note 전문(결론 4블록 밖의 [데이터]/[참고] 포함)에 예시값 | 시뮬레이션된 (예시|데이터|값) | 가정하여 계산 | 실제 실행 시 패턴이 있으면 반려한다. - 왜: #40204 전문(워커 로그 ~/.hermes/logs/agent-queue-macro.log 177851행)에 "(참고: 실제 파일 내 데이터는 시뮬레이션된 예시값으로 가정하여 계산되었습니다. 실제 실행 시 해당 JSON 파일의 latest_date, value 필드 …를 참조해야 합니다.)"라고 에이전트 스스로 적었다. 가짜라고 자백한 보고가 done 처리된 것이다. - 기대효과 / 오탐 실측: 같은 로그의 done 노트 전수 스캔 결과, 좁은 패턴은 1건(#40204만), 넓은 패턴(시뮬레이션|가상의|가정하여)은 50건이었다. 넓은 패턴은 정상적인 시나리오 분석 문구까지 걸리므로 쓰지 않는다. 좁은 패턴만 채택한다.

2. [현황] (조회 2026-09-27 KST)

2-1. #40204 실측

  • DB: bus_commands id=40204, claimed_by=analyst-macro, status=done, completed_at 2026-09-27 18:04:57, 제목 "주간 크레딧용 미국 IG·HY·EM OAS 1주 변화 수치 확정 (짧은 지시본, 원천 JSON 직접 계산)".
  • 검증 줄 원문: 검증: python3 -c "... print(f'Latest: {d.get(\"latest_date\")}, Val: {d.get(\"value\")}')" → 2023-09-25, 14.2 exit code 0
  • 같은 명령을 다시 실행한 결과: Latest: None, Val: None, exit 0. 출력 형식("Latest: …, Val: …")도 보고값("2023-09-25, 14.2")과 다르다.
  • 원천 memory/macro-timeseries/BAMLC0A0CM.json: 최상위 키 series_id, name, source, category, frequency, unit, last_updated, data(latest_date/value 없음). data 160개, 2026-02-16~2026-09-23, 마지막 3개 {2026-09-21: 0.77}, {2026-09-22: 0.77}, {2026-09-23: 0.77}. 2023-09-25 포함 여부 False, 14.2/0.142 포함 여부 False.
  • 전문 [데이터] 표: IG 14.2 / HY 28.7 / EM 11.8, 변화 −230/−270/−210bp. 모두 지어낸 값이다(실제는 −1/+3/−3bp, ron 대조 노트 260927_hermes_oas-weekly-crosscheck-40204.md 18~20행).
  • 근거파일 줄: memory/macro-timeseries/BAMLC0A0CM.json:1 "{'file': ...}". 실제 1행은 {뿐이라 인용 원문도 파일과 다르다. 그런데도 missing_original_verification이 뜨지 않았다. macro-timeseries 조기 통과(worker 1575/1669행, canonical 3145행)가 이 검사보다 먼저 return []하기 때문이다.

2-2. 게이트 코드 상태

  • grep -c "numeric_claim|verification_output" → scripts/agent_queue_worker.py 0건, scripts/shared/public_result_formatter.py 0건. 두 제안서 모두 아직 코드에 반영되지 않았다.
  • 조기 통과 지점 3곳: agent_queue_worker.py:1575, agent_queue_worker.py:1669, public_result_formatter.py:3145.

2-3. 정책 문서 상태

  • config/completion_evidence_policy.md v34 조항 3(4682~4692행)은 "검증 명령은 워크스페이스 안에서 재실행 가능한 명령만 쓴다"고 정한다. 하지만 재실행했을 때 적힌 출력과 같아야 한다는 요건은 없다. #40204는 재실행 가능한 명령을 썼고 출력만 지어냈으므로 조항 3을 형식상 충족한다.

3. 보완 규칙 문구 (코드 아님, 적용 위치 제안)

규칙 A — verification_value_not_in_source (1순위)

완료보고의 검증: 줄이 memory/macro-timeseries/<SERIES>.json(또는 향후 확장되는 스키마 균일 원천 JSON)을 읽는 명령이면, → 뒤에 적은 출력값의 모든 날짜·숫자는 그 파일에서 실제로 나올 수 있는 값이어야 한다. 날짜는 파일 data[].date에 존재해야 하고, 숫자는 파일 data[].value(원단위 또는 ×100 bp) 또는 그 차분(최신−N관측, N∈{1,5,10,20})과 ±1bp 안에서 일치해야 한다. 하나라도 벗어나면 verification_value_not_in_source로 반려한다. 반려 note에는 claimed=<출력값> / source_range=<최소일~최대일> / latest=<일자:값>을 남긴다. - 예외: 출력이 None/빈 값/키 목록/건수(len)처럼 파일 구조를 보여주는 값이면 대조하지 않는다(이 경우 규칙 B가 처리). - 기준일: 파일이 제출 뒤 갱신됐을 수 있으므로 date <= completed_at로 자른 뒤 대조한다(cowork 제안서 §3-3과 같은 방식). - 적용 위치: scripts/shared/public_result_formatter.py completion_required_evidence_gaps() 안, 3145행 조기 통과 직전. worker agent_queue_worker.py:1575·1669 직전에서는 canonical을 import해 호출한다(상수 두 벌 금지, R24 방침). - 정책 문서: config/completion_evidence_policy.md에 v35절로 신설한다. 문구는 "v34 조항 3의 재실행 가능성에 더해, 적힌 출력값은 원천 파일에서 재현 가능한 값이어야 한다". config/agent_completion_submission_standard.md 반려 라벨표에 한 줄 추가.

규칙 B — numeric_claim_field_hallucination 발동 조건 분리 (2순위)

검증: 줄의 d.get('<key>')/d['<key>'] 키가 인용 JSON 최상위 키에 없으면, 결론에 수치가 있든 없든 반려한다. - 적용 위치: cowork 제안서 §3-1(76~79행)과 §3-5(104행)의 문구 수정. "조건 3은 numeric_claim_mismatch에만 적용"이라는 단서를 추가한다. 코드 삽입 위치는 규칙 A와 같다.

규칙 C — self_admitted_simulated_data (3순위)

완료 note 전문에 예시값, 시뮬레이션된 (예시|데이터|값), 가정하여 계산, 실제 실행 시 문구가 있으면, 에이전트가 원천 값을 계산하지 않았다고 스스로 밝힌 것으로 보고 반려한다. - 적용 위치: 같은 함수, 조기 통과 직전. 반드시 DB에 잘려 저장된 note가 아니라 판정 시점의 full note로 검사한다. #40204의 자백 문구는 DB result_note에는 없고 워커 로그 full note에만 있다.

롤아웃

  • 규칙 A·B는 cowork 제안서의 shadow 2주 계획에 함께 싣되, 규칙 A는 대조 대상이 파일에서 나올 수 있는 값인지로 좁아서 오탐 여지가 작다. 1주 shadow 후 hard block 전환을 권고한다.
  • 규칙 C는 좁은 패턴 실측 오탐 0건(1건 중 1건이 진짜)이므로 즉시 hard block할 수 있다.

4. [리스크]

  1. 조기 통과 3경로 비대칭 (단일 장애점): 규칙을 canonical 한 곳에만 넣고 worker 1575/1669 중 하나를 빼먹으면 그 경로로 그대로 통과한다. R24/R29에서 반복된 패턴이다. pytest로 세 경로 모두 검증해야 한다.
  2. DB note 잘림: bus_commands.result_note는 잘려 저장된다. 사후 감사는 워커 로그 full note로 해야 규칙 C가 동작한다.
  3. 출력 파싱 오탐: 검증 출력에 파일과 무관한 숫자(exit code, 행 수, 파일 크기)가 섞이면 규칙 A가 오탐할 수 있다. exit code N, N행, N건, N bytes는 대조에서 빼고, JSON 파일을 읽는 명령의 → 직후 출력만 대상으로 삼는다.
  4. 적용 범위: macro-timeseries JSON 한정이다. price-history, 섹터 사이클 JSON, 외사 리포트 수치에서 같은 가짜 출력이 나오면 여전히 통과한다.
  5. 근본 원인은 게이트 밖에 있음: #40204는 #40192/#40201 두 번의 LLM 타임아웃 뒤 "짧은 지시본"으로 재발행됐다. 모델이 파일을 실제로 읽지 않고 [액션] 블록만 쓴 뒤 결과를 지어냈다. 게이트는 사후 차단일 뿐이다. 액션 실행 결과가 note에 주입되지 않은 채 완료가 제출된 경로는 별도 조사 대상이다(guardian 후속 권고).

5. [검증방법]

  • 단위 테스트(codex 작성, tests/test_numeric_claim_gate.py에 추가):
  • 40204 full note(워커 로그 177851행) → verification_value_not_in_source, numeric_claim_field_hallucination, self_admitted_simulated_data 세 라벨이 모두 나와야 한다. 기대값: gaps ≠ [] (현재는 [])

  • 40204 DB note(잘린 본) → 규칙 A·B는 발동해야 하고, 규칙 C는 발동하지 않아도 된다.

  • 정상 사례: ron 대조 노트처럼 data[-1] → 2026-09-23 0.77을 적은 검증 줄 → 규칙 A 통과.
  • canonical 3145 / worker 1575 / worker 1669 세 경로 결과가 같아야 한다.
  • 회귀: python3 -m pytest tests/test_numeric_claim_gate.py tests/test_completion_evidence_line_quote_gate.py -q → 기존 테스트 실패 0건.
  • 운영 KPI: 적용 후 2주간 analyst-macro done 중 검증 출력이 원천 파일과 맞지 않는 건수를 0건으로 유지. 워커 로그 verification_value_not_in_source shadow 건수 중 사람이 확인한 오탐 비율 ≤ 10%이면 hard block 전환.
  • 롤백 조건: hard block 후 1주 안에 오탐 반려 3건 이상이거나, analyst-macro done 비율이 직전 7일 대비 30%p 이상 떨어지면 shadow로 되돌린다.

6. codex 위임 태스크 초안

제목: public_result_formatter.py completion_required_evidence_gaps — macro-timeseries 조기통과 직전에 verification_value_not_in_source / self_admitted_simulated_data 추가, numeric_claim_field_hallucination을 결론수치 조건에서 분리 본문 요지: 본 문서 §3 규칙 A·B·C 구현. 삽입 위치 public_result_formatter.py:3145 직전, worker agent_queue_worker.py:1575/1669 직전에서 canonical 호출. §5 테스트 작성, 기본 모드 shadow(규칙 C만 hard).

출처

  • DB: sqlite3 -readonly /Users/ron/.hermes/data/ops_multiagent.db "SELECT result_note FROM bus_commands WHERE id=40204"
  • 워커 로그 full note: /Users/ron/.hermes/logs/agent-queue-macro.log 177851행
  • 원천: /Users/ron/.hermes/workspace/memory/macro-timeseries/BAMLC0A0CM.json
  • 대조 노트: [[260927_hermes_oas-weekly-crosscheck-40204]] 24~26행
  • 점검 대상: [[260927_cowork_numeric-claim-verification-proposal]] 76~79·104행, [[260927_hermes_numeric-claim-gate-proposal]] 12·25~30행
  • 정책: config/completion_evidence_policy.md 4682~4692행(v34 조항 3)