Kubernetes · AI Agents
동점인 점수,
서로 다른 두 종류의 잘못
두 모델이 같은 Kubernetes 매니페스트 작성 과제를 각자 독립적으로 수행했다. 둘 다 하네스에서 9/9를 받았다 — 정확히 동점, 독립적으로 재확인됨. 각자 실제로 무엇을 썼는지 읽어보니 하네스가 볼 수 없는 진짜 결함이 하나씩 있었고, 둘은 서로 상쇄되지 않았다. 각 모델은 서로 다른 방식으로 틀려 있었다.
과제: 야간 배치 트래픽이 몰리는, 외부에 노출되지 않는 내부 전용 소포 추적 서비스를 추가하되, 데이터베이스 자격증명과 각 단계마다 검증 게이트가 있는 dev→staging→prod 프로모션 파이프라인이 필요하다. 두 모델, 동일한 프롬프트, 서로의 작업을 볼 수 없도록 별도 worktree에서 실행. 둘 다 채점 하네스를 직접 돌려보고 적용 가능한 모든 검사가 통과할 때까지 반복하라는 지시를 받았다.
점수는 아무것도 구분해내지 못했다
둘 다 9/9 applicable checks passed (1 N/A excluded)를 자체 보고했다. 자체 보고를 믿지 않고 정본 체크아웃에서 각 모델이 실제로 커밋한 파일을 상대로 하네스를 독립적으로 재실행하자 두 숫자 모두 정확히 재현됐다. 하네스가 측정하는 구조적 축 — 리소스 제한, 프로브, TLS, RBAC 범위, 오토스케일링 합리성, 프로모션 게이트 존재 여부 — 에서는 둘 사이에 아무 차이도 없었다. 근소한 차이가 아니다. 두 번 검증된 완전히 동일한 결과였다.
진짜 동점을 만들어내는 하네스는 제 몫을 하고 있는 것이다 — 자신이 검사하는 축에 존재하지 않는 차이를 억지로 찾아낼 의무는 없다. 흥미로운 부분은 정확히 동점인 점수가 보통이라면 비교를 끝냈을 지점에서 시작된다.
스스로를 무력화하는 NetworkPolicy
한 모델의 NetworkPolicy를 직접 읽어보니 검사는 통과하지만 그래서는 안 되는 규칙이 하나 있었다. 예상되는 webhook-gateway 네임스페이스를 정확히 지정한 규칙 옆에, namespaceSelector: {}를 가진 두 번째 규칙이 추가돼 있었다 — 빈 셀렉터는 Kubernetes에서 클러스터의 모든 네임스페이스와 매칭된다. 그 규칙 자신의 주석이 주장하는 "다른 내부 서비스들"이 아니라. 그러면 첫 번째의 정교하게 범위를 좁힌 규칙은 무의미해진다: 정책 전체가 결국 어떤 네임스페이스의 어떤 파드에서 온 트래픽이든 그 서비스 포트로 받아들이게 된다. 진짜 최소 권한 원칙 위반이지만 검사에는 보이지 않는다. check_networking.py의 netpol 규칙은 그 네임스페이스에 어떤 NetworkPolicy가 존재하는지만 확인할 뿐, 그게 실제로 허용하는 것이 원래 허용해야 할 것과 일치하는지는 확인하지 않기 때문이다. 다른 모델의 ingress 규칙들은 정확히 실제 네임스페이스 두 개만 지정했고, 어디에도 캐치올이 없었다.
아무것도 가리키지 않는 프로모션 파이프라인
간극은 다른 파일에서 반대 방향으로 나타났다. 이 저장소 자신의 프로모션 검사용 최소 픽스처는 Stage 리소스만 담고 있다 — 채점되는 것만이 유일한 임무이기 때문에 의도적으로 최소한이다. 실제 Kargo 파이프라인은 Warehouse도 필요하다. Stage의 requestedFreight[].origin이 실제로 가리키는, freight의 출처가 되는 객체다. 그리고 보통 둘 다 담을 Project도 필요하다. 한 모델의 파이프라인은 검사를 통과할 만큼 충분히 최소 픽스처를 그대로 따라갔고 — 자기 제출물 어디에도 정의되지 않은 Warehouse를 참조했다. 검사는 통과했다. 실제 클러스터에서는 절대 freight를 발견하지 못할 것이다. 참조가 아무것도 가리키지 않기 때문이다. 다른 모델의 파이프라인은 짝이 맞는 Project와 Warehouse를 포함하고 있었다. 즉 채점기를 만족시키는 데 필요한 최소한보다 실제 문서를 더 깊이 읽었고, 실제로 적용하면 동작할 무언가를 만들어냈다는 뜻이다.
스스로를 무력화하는 NetworkPolicy를 가진 모델이 더 완전하고 실제로 배포 가능한 프로모션 파이프라인을 갖고 있었다. 올바르게 범위를 좁힌 NetworkPolicy를 가진 모델은 존재하지 않는 리소스를 참조하는 프로모션 파이프라인을 갖고 있었다. 동점인 구조적 점수 위에, 서로 무관하고 독립적인 품질 결함 두 개가 각 모델마다 하나씩, 서로 다른 파일에 얹혀 있었다.
점수가 애초에 묻지 않았던 차이 하나 더
runAsNonRoot 관련 차이는 둘 다 채점 대상이 아니었지만, 한 모델의 컨테이너 securityContext는 다른 모델보다 더 나아갔다: 검사가 실제로 확인하는 기본값 runAsNonRoot 위에 readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, capabilities.drop: [ALL]까지 쌓았다. 검사 자신의 최소 권한 항목은 자신이 확인하도록 작성된 딱 그 필드 하나만 검증한다 — 같은 원칙에 대한 더 완전한 답이라고 해서 더 높은 점수로 이어지지는 않는다.
동점이 실제로 뜻하는 것
동점인 구조적 점수는 동등한 산출물 품질을 뜻하지 않는다 — 어느 모델 쪽으로도 아니고, 사소한 차이도 아니다. 스스로를 무력화하는 캐치올 방화벽 규칙과 조용히 freight를 발견하지 못하는 파이프라인은 둘 다 프로덕션에서 중요한 종류의 결함이다. 이게 실제로 뜻하는 건 더 좁고 더 쓸모 있다: 하네스는 자신이 검사하도록 만들어진 것을 정확히 측정한다. 그리고 그 바깥에 있는 것 — 규칙의 로직이 자기 주석의 주장대로 실제로 동작하는지, 참조된 리소스가 같은 제출물 다른 곳에 실제로 존재하는지 — 는 어떤 모델이 만들었든, 두 점수가 어떻게 비교되든 상관없이 매번 직접 산출물을 읽어서 확인해야 한다.
docs/benchmark.md — 전체 실행 내역과 두 제출물의 완전한 품질 결함 분석