# 해자를 정책 문서로 지키지 마라 (3/5)

Source: https://lodos.md/ko/blog/make-your-moat-a-ratchet-ko
Published: 2026-08-11 · Author: Samet Samyeli · Language: ko · Reading time: 7 min

정책 문서로 지키는 해자는 주니어 엔지니어 한 명, 새벽의 리팩터링 한 번이면 썩는다. 나는 커밋마다 검사 두 개를 강제로 돌린다. 거부 규칙이 슬그머니 풀리면 빌드가 실패하고, 어렵게 얻은 교훈이 검색되지 않으면 위키가 자기 검사에 걸린다. 둘 다 같은 HMAC 체인에 봉인된다.

---

> 대부분의 AI 제품은 해자를 정책 문서로 지킨다. 그 문서는 주니어 엔지니어 한 명이 들어오거나 새벽에 리팩터링이 한 번 지나가면 소리 없이 썩는다. 나는 다르게 지킨다. 커밋마다 검사 두 개가 강제로 돌아간다. 하나는 절대 만들지 않겠다고 못 박은 것을 내가 출시하려 드는 순간 빌드를 세운다. 다른 하나는 어렵게 얻은 교훈이 검색되지 않거나 앞뒤가 어긋나는 순간 위키를 자기 검사에 걸리게 한다. 둘 다 HMAC 체인에 봉인되어 있다. 그래서 이 해자는 한 방향으로만 조여진다. 거부 규칙 하나, 교훈 하나가 쌓일 때마다 시스템은 뒤로 돌아가기가 더 어려워지고 쓸모는 더 커진다.

나는 커밋마다 스크립트 두 개를 돌린다.

첫 번째 스크립트는 절대 출시하지 않겠다고 못 박은 것들을 코드베이스 전체에서 grep으로 훑는다. 찾는 것은 넷이다. 문자열을 실행 코드로 바꾸는 eval류 장치, 비밀값을 그대로 돌려주는 도구, 모델 제공자 API를 직접 호출하는 모듈, 그리고 에이전트가 데이터를 빼돌리는 통로가 되는 치명적 3요소다. 하나라도 걸리면 빌드는 그 자리에서 멈춘다.

![공유된 HMAC 체인 위에 두 개의 열이 좌우로 마주 본다. 왼쪽 거부 쪽은 코드를 grep으로 훑어 있어서는 안 될 것을 찾고, 하나라도 나오면 빌드를 세운다. 오른쪽 지식 쪽은 위키를 lint로 훑어 빠진 것을 찾고, 하나라도 나오면 무결성 검사를 실패로 돌린다. 두 쪽 모두 자기 기록을 같은 위변조 감지 로그에 남긴다.](/blog/make-your-moat-a-ratchet/2a.webp)

두 번째 스크립트는 위키를 훑으면서 정반대를 묻는다. 여기 있어야 하는데 없는 것은 무엇인가? 출처가 붙지 않은 주장, 어디에서도 연결되지 않은 개념 페이지, 몇 달 간격으로 쓰여 서로 어긋나 버린 두 페이지가 여기서 걸린다. 첫 번째 스크립트는 시스템이 CVE가 되는 것을 막고, 두 번째 스크립트는 시스템이 이미 배운 것을 잊는 것을 막는다.

나는 lodos를 규칙 하나 위에 세웠다. 중요한 거부는 빠짐없이 빌드 안의 grep이 되고, 중요한 지식은 빠짐없이 버전이 남고 검색되는 페이지가 된다.

둘 다 HMAC 체인 안에 들어 있어서 누가 손대는 순간 요란하게 깨진다. 추가하지 않겠다고 정한 것을 적어라. 잊지 않겠다고 정한 것을 적어라. 그리고 그 둘이 지켜지지 않으면 빌드가 출시를 거부하게 만들어라. 대부분의 AI 제품은 되고 싶은 모습을 적어 두고 정책 문서로 지킨다. 그보다 적은 수의 팀은 되지 않겠다고 정한 모습을 적어 두고 코드로 지킨다. 가장 드문 세 번째 부류는 잊지 않겠다고 정한 것까지 적어 두고, 페이지 하나만 어긋나도 스스로 무결성 검사에 걸리는 위키로 그것을 지킨다. 복리로 불어나는 해자는 이 세 번째 부류에서만 나온다.

![정책 문서로 만든 해자와 한 방향으로만 조여지는 해자를 나란히 놓고 비교한 그림. 왼쪽에서는 문서에 적힌 약속이 주니어 엔지니어와 리팩터링과 아무도 눈치채지 못한 표류를 차례로 지나면서 신뢰 곡선이 0으로 내려앉는다. 오른쪽에서는 grep과 위키 lint가 커밋마다 요란하게 실패하면서, 같은 열두 달 동안 신뢰 곡선이 계단처럼 올라간다.](/blog/make-your-moat-a-ratchet/3a.webp)

## 썩는 것과 썩지 않는 것

정책 문서로 만든 해자는 소리 없이 죽는다. "비밀값은 절대 로그에 남기지 않습니다." "모든 도구 출력은 샌드박스를 거칩니다." "LLM에 넘기기 전에 검증합니다." 이런 문장은 SOC 2 문서에 적혀 있고, 적힌 그날에는 사실이다. 그러다 주니어 엔지니어가 디버그 로그를 하나 심는다. 리팩터링 한 번에 마스킹 로직이 기능 플래그 뒤로 숨는다. 문장은 문서에 그대로 남아 있지만 시스템은 더 이상 그 문장대로 움직이지 않는다. 깨지는 것은 아무것도 없다. 아무도 눈치채지 못한다.

지식으로 쌓은 해자는 그보다 더 빨리 죽는다. 창업자가 새벽 두 시에 고객이 왜 떠났는지 알아낸다. 그 깨달음을 Slack에 던져 놓고 일주일이면 잊는다. 교훈은 검색할 수 있는 곳 어디에도 남지 않는다. 석 달 뒤 같은 상황이 돌아오고, 창업자는 같은 수업료를 다시 낸다. 이쪽이 가장 비싼 이유는 창업자 본인조차 그 일이 벌어지는 줄 모르기 때문이다.

두 죽음의 처방은 같다. 지키려는 것을 그것이 무너질 때 함께 무너지는 구조에 묶어라. 거부 규칙이 슬그머니 풀리면 빌드가 실패하게 하라. 페이지가 어디에서도 연결되지 않거나, 주제에서 벗어나거나, 앞뒤가 어긋나면 위키가 자기 lint에 걸리게 하라. 처방은 프로세스가 아니다. 시스템이 매번 빠짐없이 자기 자신에게 돌리는 검사다.

![세 가지 작업으로 이루어진 순환을 그린 그림. 수집은 원본 자료를 페이지 초안으로 바꾸고, 그 초안은 정본이 되기 전에 변경 내용을 나란히 보여주는 승인 창을 거친다. 질의는 인용을 붙여 답을 만들고, 그 답은 다시 페이지로 저장할 수 있다. 점검은 연결되지 않은 페이지와 출처 없는 주장과 서로 어긋난 서술을 찾아낸다. 그 아래에는 lodos가 원본 패턴에서 일부러 벗어난 세 가지가 적혀 있다. 암호화된 SQLite 저장, 제안만 허용하는 자율성, HMAC으로 이어 붙인 감사 기록.](/blog/make-your-moat-a-ratchet/4a.webp)

## Karpathy 패턴을 상업용으로 바꾸기

지식 쪽 절반은 내 아이디어가 아니다. Andrej Karpathy가 LLM 위키를 세 가지 작업과 주장 하나로 정리해 두었다. 위키가 실행 파일이고, 원본 자료가 소스 코드이고, LLM이 컴파일러이고, lint가 테스트이고, 질문이 런타임이라는 것이다.

나는 그 패턴을 그대로 가져오되 세 가지를 일부러 바꿨다.

- **저장 방식.** Karpathy는 마크다운 파일 폴더와 git을 쓴다. 나는 SQLite와 구역별로 암호화한 금고를 쓴다. 상호운용성을 내주고 기밀성을 얻은 셈이다. 마크다운으로 깔끔하게 내보낼 수 있으니, 원하면 언제든 Obsidian으로 열면 된다.
- **자율성.** Karpathy는 LLM이 곧바로 쓰게 둔다. 나는 모든 수정을 제안으로만 받고, 변경 내용을 나란히 보여주는 승인 창을 반드시 거치게 한다. 에이전트는 정본 페이지에 직접 손대지 못한다. 승인 한 번에 5초에서 10초를 쓰지만, 그 대가로 에이전트가 내 행동 규칙을 몰래 고쳐 쓰는 일은 없다.
- **감사 기록.** Karpathy는 평범한 마크다운 기록을 남긴다. 나는 마스터 비밀번호에서 파생한 하위 키로 서명한 HMAC 체인을 남긴다. 기록에 손을 대면 다음 실행 때 체인이 끊긴 것으로 드러난다.

철학의 차이가 아니다. 개인용으로 만들어진 지식 관리 패턴을 파는 제품에 맞게 손본 것이다. 원래 정신은 그대로 두고, 컴플라이언스를 사는 고객에게 값을 받을 수 있는 물건으로 만들었다.

## 양쪽 다 한 방향으로만 조여진다

거부 쪽과 지식 쪽은 생김새가 같다. 한쪽은 절대 있어서는 안 되는 것을 찾고, 다른 쪽은 절대 빠져서는 안 되는 것을 찾는다. 둘 다 무언가가 없는지 확인하는 검사다. 둘 다 요란하게 실패한다. 둘 다 같은 HMAC 체인에 봉인된다.

위키에 질문을 던져 좋은 답이 나오면, 화면에 "이 답을 페이지로 저장" 버튼이 한 번 뜬다. 누르는 순간 그 답은 그 자리에 남는다. 다음 작업에서 다시 찾을 수 있고, 같은 질문을 두 번 할 일이 없어진다. 내가 보는 지표는 저장률이고, 이 숫자는 시간이 갈수록 올라간다. 이 시스템에서 쓸수록 확실하게 강해지는 부분은 위키뿐이다.

거부 쪽도 방식이 같다. 불변식을 위협하는 기능이 새로 들어올 때마다 검증 스크립트에 불변식이 하나씩 더 적힌다. 여덟 달 만에 검사 항목이 일곱 개에서 서른두 개로 늘었다. 새 기능은 자기가 열어젖힌 악용 경로를 닫는 grep과 함께 출시된다. 기능이 하나 나갈 때마다 코드베이스가 거부하는 목록은 늘어나기만 한다.

대부분의 팀은 이 둘을 별개의 문제로 다룬다. 사실은 같은 규율을 서로 다른 두 가지 "없어야 할 것"에 적용한 것뿐이다. 절대 있어서는 안 되는 것, 그리고 절대 잊어서는 안 되는 것.

## 내가 치르는 비용, 그리고 그 비용이 핵심인 이유

속도는 정확히 두 군데에서 잃는다. 거부 규칙과 부딪히는 기능에서 한 번, 위키를 무시했다면 그냥 갈아엎었을 코드에서 한 번. 어떤 파워 유저는 워크플로 안에 식을 직접 써서 굴리는 기능을 원한다. 거절했다. 어떤 고객은 비밀값을 그대로 돌려주는 도구를 원한다. 거절했다. 언젠가 나 자신도 승인 절차가 답답하다는 이유로 그걸 뜯어내고 싶어질 것이다. 빌드가 놔두지 않는다.

그래서 내 릴리스 노트는 경쟁사 것과 다르게 생겼다. 새 기능만큼이나 거부한 것들이 자주 등장한다. 일부러 만들지 않기로 한 것들을 지우고 나서야 매끈해지는 성장 서사 같은 건 없다.

내가 얻는 것은 랜딩 페이지에 들어가지 않는다. 값은 규제 산업 고객과 마주 앉은 자리에서 한다. 그 고객에게 "우리는 그렇게 할 수 없습니다"와 "그러지 않겠다고 약속드립니다"는 전혀 다른 말이다. 컴플라이언스 담당자에게 위변조가 드러나는 HMAC 체인과 평범한 마크다운 기록은 전혀 다른 물건이다. 오늘 위키를 채워 두는 창업자는 여섯 달 뒤 그 내용이 필요해질 창업자에게 읽힌다. 이 사람들 중 누구에게도 마케팅 문구로는 닿을 수 없다.

![다섯 조각을 카드로 늘어놓고 선으로 이어 놓은 그림. 코드 저장소는 거부 쪽의 불변식 스크립트로 이어지고, 위키 lint는 지식 쪽을 맡는다. 두 갈래 모두 공유된 HMAC 체인에 기록을 남긴다. 제안만 허용하는 순환에서는 모든 변경이 정본이 되기 전에 승인 창을 지난다.](/blog/make-your-moat-a-ratchet/5a.webp)

## 가져가도 좋은 다섯 조각

다섯 개는 서로를 필요로 하지 않는다. 다만 다 모이면 하나의 패턴이 된다.

1. 어떤 형태의 식 평가기도 들어 있지 않은 선언형 워크플로 엔진.
2. AI가 평문 비밀값을 돌려주는 일이 구조적으로 불가능한 이중 저장 금고.
3. 일부러 어겨 보고 정말 걸리는지 확인하는 규율로 짠 빌드 시점 불변식 스크립트.
4. Karpathy의 세 가지 작업을 구현하고 기록을 HMAC 체인으로 감싼 위키.
5. 에이전트가 자기 행동 규칙을 직접 쓰지 못하고 제안만 하는 학습 순환.

순서는 상관없다. 다섯 조각이 공유하는 것은 규율 하나뿐이다. 출시하지 않겠다고 정한 것과 잊지 않겠다고 정한 것을 적고, 둘 다 빌드에서 강제로 검사하고, 일부러 어겨 봐서 그 검사가 정말 걸리는지 확인하는 것. 나머지는 전부 엔지니어링이다.

![다음 순환을 여섯 단계 사슬로 그린 그림. 에이전트가 대화를 지켜보다 부족한 기능을 발견하고 수정안을 쓴다. 그 수정안은 반드시 거쳐야 하는 관문으로 표시된 승인 창 앞에서 멈추고, 사람이 승인하거나 되돌린 뒤에야 정본이 되어 출시된다. 모든 제안과 결정은 HMAC 체인에 남는다.](/blog/make-your-moat-a-ratchet/6a.webp)

## 다음 이야기

*마지막 순환이 제대로 굴러가기까지 제일 오래 걸렸다. 에이전트는 대화를 지켜보다가 자기 스킬 목록에서 부족한 곳을 발견하고 수정안을 쓴다. 나는 승인하거나 되돌린다. 그 수정안도 다른 위키 제안과 똑같은 승인 창을 지난다. 에이전트는 끝까지 자기 행동 규칙을 직접 쓰지 못한다. 제안과 승인 사이의 그 관문이 진짜 해자다. 다음 글은 그 순환에 대한 이야기이고, 이 모든 엔지니어링을 정당화해 준 행동 지침 프롬프트 속 짧은 한 구절에 대한 이야기다.*

[대기자 명단에 등록하기](https://lodos.md/)
