같은 코드 조각이나 글을 두 번 놓아봐. 한쪽엔 유명한 모델 이름을 붙이고 다른 쪽엔 이름을 가려. 내용은 한 줄도 안 바뀌었는데 판결이 달라지면, 그 시스템은 결과물이 아니라 평판을 재고 있어. 그래서 Anvil은 심사의 단위를 만든 사람이 아니라 만들어진 물건으로 잡아.
결과물을 앞세운다는 게 맥락을 없앤다는 뜻은 아니야. 봉인된 레일의 심사위원은 과제서, 결과물, 심사 지침을 받아야 해. Open Run은 선택 경로 보고서, 진행 방식 공개, 증거 묶음까지 선언된 심사 대상에 더해. 대신 제공자 이름, 모델 등급, 작업자의 대화 기록, 걸린 시간, 고생담처럼 저자를 그럴듯해 보이게 할 뿐인 재료는 빼. 그런 정보는 운영 기록 줄기에 남을 수 있어도 심사의 근거는 아니야.
이 경계는 개선도 깨끗하게 만들어. 다음 작업자는 앞선 결과물을 바꿀 수 있지만 앞선 작업자의 권위까지 물려받지는 않아. Relay는 사람의 계보가 아니라 물건이 지나온 상태의 연쇄야. 나중에 출처를 봉인 해제해도 그건 역사를 설명할 뿐, 심사 때 썼어야 할 증거로 소급되지 않아.
확인법은 단순해. 작업자 이름표를 A, B, C로 바꿔도 심사 지침이 한 글자도 안 바뀌어야 해. 바뀐다면 아직 손을 심사하고 있는 거야.
주소를 갖는 건 물건이야. 저장하고 비교하고 변형하고 판단하는 단위는 결과물로 두고, 작업자의 정체성은 따로 봉인해서 보관해.
Code
심사 입력과 운영 출처를 갈라놓기·python
JUDGE_FIELDS = {"brief", "artifacts", "judge_instructions"}
TRAIL_ONLY = {"worker", "provider", "elapsed_s", "events"}
def judge_packet(seat_rows, brief, instructions):
"""심사위원이 받는 전부. 여기 없는 건 심사 근거가 아니다."""
return {
"brief": brief,
"artifacts": [row["bytes"] for row in seat_rows],
"judge_instructions": instructions,
}
def operator_trail(seat_rows):
"""같은 회차를 기록은 하되, 심사 패킷과 절대 섞지 않는다."""
return [{k: row[k] for k in TRAIL_ONLY} for row in seat_rows]
rows = [
{"bytes": b"def solve(): ...", "worker": "w1",
"provider": "vendor-a", "elapsed_s": 41, "events": []},
{"bytes": b"def solve(): pass", "worker": "w2",
"provider": "vendor-b", "elapsed_s": 12, "events": []},
]
packet = judge_packet(rows, "버그를 고쳐라", "과제서를 가장 잘 만족하는 쪽")
assert set(packet) == JUDGE_FIELDS
assert not (set(packet) & TRAIL_ONLY) # 겹치면 경계가 무너진 것
print(len(packet["artifacts"]), "개 결과물, 저자 정보 0건")