← Ascendy EN

meta

내 리뷰 도구는 아무것도 통과시킬 수 없었다 — 고치자마자 나를 잡았다

· Ascendy Engineering


TL;DR

이 글에 대하여. 여기 적힌 이슈·PR·리뷰는 전부 공개 repo(Apache-2.0)의 사건이다. 같은 적대적 검증 결의 루프 엔지니어링의 심장은 검증이다, 내가 가장 신뢰한 모델이 나를 막다른 길로 안내했다와 이어진다.

통과시킬 수 없는 리뷰어

쌓여 있던 엔진 PR들을 정리하려 했다. 기준은 단순했다 — “교차모델 리뷰를 통과하면 머지.” 그래서 일회성 리뷰 명령을 돌렸다. 그런데 결과가 이상했다. 코드 평가는 깨끗한데(회귀 없음), 최종 결정은 매번 CHANGES_REQUESTED였다.

사유를 보니 코드 얘기가 아니었다. “verification.log를 못 찾음. state.json을 못 찾음.” 무슨 파일을 못 찾아서 거부한다는 것이다. 그제야 깨달았다 — 이 명령은 어떤 브랜치에서도 APPROVED를 낼 수 없는 상태였다. 문서엔 분명히 “exit 0이면 APPROVED, CI 게이트로 써도 된다”고 적혀 있는데, 그게 구조적으로 불가능했다.

fail-closed가 성립 불가능한 전제를 상속하면

원인은 단순했다. redteam에는 두 가지 리뷰 표면이 있다. 하나는 파이프라인 — 태스크별 상태기계가 돌며 verification.log·state.json 같은 산출물을 남기고, 리뷰어가 그것들을 “필수 검사”로 확인한다. 다른 하나는 일회성 리뷰 — 상태기계 바깥에서 “지금 이 브랜치 diff를 그냥 교차모델로 봐줘”를 하는 표면이다.

문제는 일회성 리뷰가 파이프라인 전용 필수 검사를 그대로 물려받았다는 것이다. 일회성 모드엔 그 산출물 파일이 설계상 존재할 수 없다. 그러니 리뷰어는 매번 “필수 파일이 없다”며 fail-closed로 거부했다. 코드를 보기도 전에.

여기서 일반화할 교훈이 하나 나온다 — fail-closed 게이트가 자기가 성립할 수 없는 전제를 상속하면, 그건 안전장치가 아니라 영구 거부기다. 안전을 위해 “의심스러우면 막는다”는 옳지만, 애초에 충족 불가능한 조건을 막음의 근거로 삼으면 게이트 전체가 죽는다. 어느 하네스에도, 어느 CI에도 적용되는 함정이다.

고치고 — 그 도구로 그 수정을 검증했다

수정은 범위를 좁게 잡았다. 일회성 리뷰에게 “이건 태스크 산출물이 없는 standalone 리뷰다. 그 필수 검사를 적용하지 말고, 파일이 없다는 이유로 거부하지 마라. 오직 diff 자체로만 판정하라 — 보안 체크리스트, 하드룰, 회귀, 그리고 ‘새로 추가된 테스트는 변경 전엔 실패해야 한다’는 기준으로” 라고 명시했다. 파이프라인의 검증 게이트와 self-review 가드는 건드리지 않았다. 일회성에서 성립할 수 없는 전제 하나만 풀었다.

그리고 — 고친 그 도구로, 그 수정 자신의 diff를 리뷰시켰다. 결과는 APPROVED(회귀 없음, 새 테스트가 변경 전엔 실패함을 확인). 고친 도구로 고친 것을 검증한 셈이다. 검증 도구는 dogfood하지 않으면 죽은 게이트가 된다는 걸, 죽었다 살아난 도구로 다시 확인했다.

도구가 회복되자마자, 나를 잡았다

여기서부터가 진짜다. 이어서 같은 도구로 다른 변경을 리뷰했다 — 역할별로 모델을 지정하는 설정 기능이었다.

나는 그 구현에서, 작성자=리뷰어 붕괴를 막는 self-review 가드를 reviewer 역할뿐 아니라 rescue 역할에도 걸었다. 그리고 내 자체 리뷰 노트에 이렇게 옹호했다 — “런타임 가드는 reviewer만 보는데 내 CLI는 rescue까지 본다. parity 개선이다.” 확신이 있었다.

고친 도구로 Codex가 그 diff를 보자마자 반증했다. 런타임에서 rescue는 헤드리스 리뷰어가 아니다. rescue 단계는 사람이 쓴 보고서를 검증할 뿐, 리뷰어 어댑터를 호출하지도 않는다 — 리뷰어 단계 목록에서 명시적으로 제외돼 있다. 따라서 rescue에 “교차프로바이더여야 한다”는 불변식을 강제하면, 지원되는 정상 구성을 거부하는 회귀가 된다. 기본값에서 작성자와 리뷰어를 뒤바꾼 페어를 쓰려는 사용자가, 멀쩡히 돌아갈 구성인데도 막히는 것이다.

내가 “개선”이라 부른 것이 사실은 회귀였다. 그리고 그걸 본 건 — 코드를 쓴 나도, 내 자체 리뷰 노트도 아니었다. 작성자와 다른 프로바이더의 리뷰어만이 그걸 봤다. (그 설정 기능은 결국 같은 이슈를 더 깔끔하게 구현한 다른 PR로 닫혔고, 그쪽은 처음부터 이 함정이 구조적으로 없었다.)

왜 이게 redteam의 핵심인가

redteam의 전제는 한 문장이다 — “코드를 짠 모델이, 그 코드의 안전성까지 스스로 판단해서는 안 된다.” 이번 사건은 그걸 두 겹으로 보여줬다.

첫째, 검증 도구 자체가 망가져 있었는데, 그건 쓰려고 했을 때 비로소 드러났다. 도구는 만들어두는 게 아니라 써봐야 산다.

둘째, 도구가 회복되자 — 내가 확신을 갖고 옹호한 판단이 다른 프로바이더 앞에서 무너졌다. 이게 핵심이다. 교차모델 리뷰의 값어치는 작성자가 헷갈릴 때가 아니라 확신할 때 가장 크다. 자기가 틀렸다고 의심하는 코드는 어차피 다시 본다. 위험한 건 옳다고 믿는 코드다. 그 자기정당화를 멈춰 세우는 건, 같은 머리에서 나온 두 번째 시선이 아니라 — 다른 계열의, 동의할 이유가 없는 시선이다.

가져갈 것


저작·인용: 이 글은 Ascendy Engineering이 작성했으며 출처 표기 시 재인용 가능합니다. 잘못된 정보를 발견하면 GitHub 이슈로 알려주세요.


Tags: adversarial-review, dogfooding, redteam, cross-provider, agent-pair