본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 05. 04:20

조직 규칙(Org rules)과 프로젝트 규칙(Project rules)은 서로 다른 저장 공간이 필요합니다

요약

조직 차원의 규칙(Level 3)과 개별 프로젝트의 규칙(Level 2)을 분리하여 관리하는 Keystone의 구조를 설명합니다. 파일 트리 기반의 물리적 분리와 락파일을 통해 조직 정책이 프로젝트 규칙으로 변질되는 것을 방지하는 설계 원칙을 다룹니다.

핵심 포인트

  • 조직 정책(Level 3)과 프로젝트 규칙(Level 2)의 저장 공간 분리 필요성
  • Keystone은 파일 트리 구조를 통해 규칙 간의 경계를 명확히 유지함
  • 정책은 '무엇(what)'을 검사할지 선언하고, 프로젝트는 '어떻게(how)'를 결정함
  • 락파일과 해시를 사용하여 로컬 수정을 통한 정책 변질 방지

저는 모두 동일한 TODO 위생 규칙(hygiene rules)을 적용하고 싶어 하는 세 개의 리포지토리(repos)를 가지고 있습니다. 첫 번째 리포지토리는 소유자가 명시되지 않은 TODO: fix this를 제가 리뷰 과정에서 발견한 후 해당 규칙을 도입했습니다. 두 번째 리포지토리는 첫 번째 리포지토리의 CLAUDE.md를 복사하여 이를 채택했습니다. 세 번째 리포지토리에서는 제가 두 번째 프로젝트의 CLAUDE.md를 열어 세 번째로 복사하려는 상황을 인지하게 되었습니다.

이것이 바로 레벨 3(Level 3)가 방지하고자 하는 전형적인 실패 사례입니다.

분류 체계(taxonomy)를 작성하는 것은 충분히 쉽습니다. 레벨 2(Level 2)는 프로젝트 하네스(project harness)이고, 레벨 3(Level 3)은 조직 하네스(organization harness)입니다. 어려운 부분은 리포지토리 내부의 분리입니다. 만약 두 종류의 규칙이 모두 동일한 파일에 들어가게 되면, 누군가 무언가를 수정할 때마다 그 경계가 무너집니다. Keystone은 파일 트리(file tree) 자체에 선을 긋습니다.

Keystone이 긋는 경계

Keystone 하네스(harness)는 두 부분으로 나뉩니다.

프로젝트 부분은 harness/corpus/harness/guides/입니다. 이는 팀이 소유하며, 자유롭게 수정할 수 있습니다. 이는 _이 코드베이스(this codebase)_를 설명하는 추론(reasoning)과 규칙들입니다. 즉, 어떤 테스트 러너(test runner), 어떤 린트 설정(lint config), 어떤 관용구(idioms), 어떤 도메인 제약 조건(domain constraints)을 사용할지에 대한 내용입니다.

조직 부분은 harness/policies/입니다. 각 정책(policy)은 고유한 네임스페이스(namespace)를 가지며, 이를 게시한 사람이 소유합니다. 정책은 keystone init --policy <ref>를 통해 도입되고 keystone policy update <name>를 통해 업데이트됩니다. 정책 네임스페이스 내부에서 로컬 수정이 이루어지면 --force 옵션을 전달하지 않는 한 다음 업데이트가 차단됩니다.

harness/
├── corpus/              # 프로젝트 추론 (Level 2)
├── guides/              # 프로젝트 규칙 (Level 2)
...

두 가지 요소가 이 경계를 유지하게 만듭니다. 첫째, 모든 정책은 오직 자신의 서브트리(subtree) 내부에서만 작성하며, 설치 프로그램(installer)이 이를 강제합니다. 둘째, 락파일(lockfile)이 파일별 해시(hash)를 기록하므로, 업데이트 흐름에서 정책 파일이 로컬에서 수정되었는지 여부를 판단할 수 있습니다. 이러한 구조가 레벨 3(Level 3) 규칙이 조용히 레벨 2(Level 2) 규칙으로 변질되는 것을 막아줍니다.

센서(Sensors)는 의도적으로 정책(policy) 내부에 존재하지 않습니다. 센서는 프로젝트 툴링(lint, type-check, test)을 설명합니다. 정책은 무엇(what)을 검사해야 하는지를 선언할 수 있지만, 방법(how)은 프로젝트가 결정합니다. 이것이 올바른 구분입니다. 원칙(principles)은 분산되지만, 명령(commands)은 그렇지 않습니다.

예시: tacoda-policy

실제 사례를 보면 그 형태를 더 쉽게 파악할 수 있습니다. tacoda-policy는 제가 레이어를 엔드 투 엔드(end-to-end)로 테스트하기 위해 사용하는 예시 저장소(repo)입니다. 이 저장소는 문서화 및 TODO에 관한 저의 개인적인 선호도를 담고 있으며, tacoda라는 이름의 정책으로 설치됩니다.

이 저장소는 의도적으로 작게 구성되었습니다:

tacoda-policy/
├── keystone-policy.yaml
├── README.md
...

매니페스트(manifest)는 짧습니다. 이름, 버전, 설명으로 구성됩니다:

name: tacoda
version: 0.1.0
description: |
...

policy/ 디렉토리는 콘텐츠가 소비자(consumer)의 하네스(harness) 내부에 배치되는 방식을 반영합니다. 설치 프로그램은 정책 자체의 네임스페이스(policy/harness/policies/tacoda/) 이외의 모든 것을 거부하므로, 이 저장소가 소비자의 프로젝트 트리에 실수로 파일을 떨어뜨리는 일이 발생할 수 없습니다. 이러한 보증 덕분에

코퍼스 (corpus) 파일은 장문의 추론 (long-form reasoning) 내용을 담고 있습니다. 즉, Hemingway 테스트가 어디에서 유래했는지, 무엇이 삭제되고 무엇이 남는지, 왜 나쁜 문서가 문서가 없는 것보다 더 나쁜지에 대한 내용입니다. 에이전트 (agent)는 해당 토큰이 필요할 때만 그 비용을 지불합니다.

이러한 분리는 프로젝트 레이어 (project layer)에서 중요한 이유와 동일한 이유로 중요합니다. 가이드 (Guides)는 지출할 수 있는 가장 저렴한 토큰입니다. 반면 코퍼스는 에이전트가 판단을 내려야 할 때 제 역할을 다하는 온디맨드 컨텍스트 (on-demand context)입니다.

자신만의 정책 작성하기

정책 (policy)은 루트에 두 가지 요소를 가진 git 저장소 (repo)입니다. 매니페스트 (manifest)는 다음과 같습니다:

name: my-org
version: 0.1.0
description: |
...

그리고 소비자 레이아웃을 미러링하는 policy/ 디렉토리가 있습니다:

my-policy-repo/
├── keystone-policy.yaml
├── README.md
...

매니페스트의 name은 네임스페이스 (namespace)입니다. policy/ 하위의 모든 파일은 반드시 policy/harness/policies/my-org/ 내부에 존재해야 하며, 그 외의 것은 설치 시점에 거부됩니다. README와 매니페스트는 소비자가 아닌 사람을 위해 저장소 루트에 위치하며, 설치 프로그램 (installer)은 이를 무시합니다.

정책 내의 가이드 파일은 프로젝트 가이드와 동일해 보입니다. 짧고, 주변적이며, 규칙으로 가득 차 있습니다. 핵심 결론은 코퍼스를 가리킵니다:

# Vendors — rules

The rules from [`corpus/vendors.md`](../corpus/vendors.md).
...

코퍼스 파일은 추론 (reasoning)이 담기는 곳입니다. 왜 이 벤더 리스트를 사용하는지, 리뷰 프로세스가 어떻게 구성되는지, 이를 결정하게 만든 계약적 제약 조건은 무엇인지 등을 다룹니다. 프로젝트 코퍼스와 형태가 같으며, 로드 동작 (load behavior)도 동일합니다.

예기치 않은 상황 없는 업데이트

레벨 3 (Level 3)의 흥미로운 부분은 설치가 아닙니다. 두 번째로 일어나는 일입니다.

설치된 각 정책은 harness/.keystone.lock에 고정 (pinned)됩니다:

policies:
  tacoda:
    source_ref: "git+https://github.com/tacoda/tacoda-policy.git#v0.1.0"
...

새로운 참조 (ref)로 업데이트하려면:

keystone policy update tacoda '#v0.2.0'

또는 현재 참조를 다시 확인(re-resolve)하려면 (#main을 추적할 때 유용합니다):

keystone policy update tacoda

해시(hashes)는 안전망 역할을 합니다. 만약 설치 이후 정책 네임스페이스(policy namespace) 내부의 파일이 로컬에서 수정되었다면, 업데이트는 해당 파일을 덮어쓰는 것을 거부합니다. 로컬 수정을 되돌리거나, --force 플래그를 전달하여 데이터 손실을 감수해야 합니다. 이러한 거부 동작이야말로 "정책은 프로젝트에서 작성되지 않는다"라는 문장을 단순한 관례가 아닌 실제적인 제약 사항(constraint)으로 만듭니다.

만약 프로젝트가 진정으로 정책 규칙을 완화하거나 확장해야 한다면, 올바른 방법은 프로젝트 레이어(project layer)에서 처리하는 것입니다. harness/guides/ 아래에 정책 파일의 경로를 추적하고 편차(deviation)를 기록하는 프로젝트 가이드를 추가하십시오:

# Documentation — project deviation

This project relaxes
...

정책은 수정되지 않은 상태로 유지됩니다. 편차는 미래의 독자들이 찾아볼 위치에 존재하게 됩니다. 락파일(lockfile)은 깨끗하게 업데이트 상태를 유지합니다.

정책을 완화할 수 없는 경우

편차 패턴은 대부분의 규칙에 작동합니다. 하지만 프로젝트가 완화할 수 없도록 허용되지 않은 규칙에는 작동하지 않습니다. 법무팀에 의해 고정된 벤더 리스트(vendor list)는 권장 사항이 아닙니다. 릴리스를 통제하는 라이선스 규칙은 협상의 시작점이 아닙니다.

그것이 바로 strict가 존재하는 이유입니다. 이 플래그는 정책 매니페스트(policy manifest)에 존재하며 기본값은 false입니다:

yname: my-org
version: 0.2.0
strict: true
...

strict 정책은 가이드가 에이전트(agent)에 도달하는 방식에서 두 가지를 변경합니다.

첫째, 로드 순서(load order)입니다. harness/guides/ 아래의 프로젝트 가이드는 보통 정책 가이드보다 먼저 로드되므로, 나중에 로드되는 프로젝트 가이드가 정책 규칙 위에 놓이게 되어 에이전트가 프로젝트의 완화 사항을 마지막으로 읽게 됩니다. strict 정책은 이를 반대로 바꿉니다. 해당 정책의 가이드는 가장 마지막에 로드되며, 해당 정책이 다루는 모든 규칙에 대해 최종 결정권을 가집니다.

둘째, 권한(authority)입니다. 드리프트 센서(drift sensor)는 strict: true를 읽고 로드된 컨텍스트(context) 내에서 해당 정책의 규칙들을 재정의 불가능(non-overridable) 상태로 표시합니다. strict 정책 파일로 추적되는 프로젝트 레이어의 편차 가이드는 완화가 아닌 위반(violation)으로 취급됩니다. 에이전트는 이를 조용히 적용하지 않을 것입니다.

파일 수준의 동작은 변경되지 않습니다. 엄격한 정책 파일(strict policy file)에 대한 로컬 편집은 여전히 동일한 방식으로 업데이트를 차단하며, --force 역시 동일하게 작동합니다. '엄격함(Strict)'은 파일 소유권에 관한 것이 아니라, 에이전트 실행 시점(agent runtime)에서의 우선순위(precedence)에 관한 것입니다.

이를 절제하여 사용하십시오. 대부분의 규칙은 예외를 허용하는 통로(deviation door)가 필요합니다. 왜냐하면 대부분의 규칙에는 존중할 가치가 있는 예외 사례(edge cases)가 존재하기 때문입니다. '엄격함(Strict)'은 예외 사례에 대해 이미 논쟁이 끝났고 패배한 규칙들, 즉 준수 사항(compliance), 라이선스(licensing), 보안 태세(security posture)를 위한 것입니다. 이 플래그는 정책 작성자가 해당 규칙들을 단순히 주제가 다른 것이 아니라, 성격 자체가 다른 것으로 지정할 수 있도록 존재합니다.

엄격한 정책은 조직의 나머지 구성원들에 대한 약속입니다. 기본값을 false로 설정함으로써, 그 약속이 의미를 가질 수 있을 만큼 충분히 희소하게 유지됩니다.

분리가 번거로움을 감수할 가치가 있는 이유

레벨 2와 레벨 3를 분리하는 것에 반대하는 논거는 기억해야 할 개념이 하나 더 늘어난다는 것입니다. 반대로 분리를 찬성하는 논거는, 잘못된 레이어는 잘못된 소유자를 의미하며, 잘못된 소유자는 규칙이 부패하는 원인이 된다는 것입니다.

"Jest 대신 Vitest를 사용하라"는 규칙은 하나의 프로젝트에 속합니다. 반면 "모든 TODO에는 소유자를 명시하라"는 규칙은 제가 관여하는 모든 프로젝트에 걸쳐 저에게 속합니다. 만약 이 두 가지가 모두 동일한 CLAUDE.md에 담긴다면, 다음에 이 파일을 편집하는 사람은 어떤 규칙이 이동 가능하고 어떤 규칙이 이동 불가능한지 추측해야만 합니다. 그들은 잘못 추측할 것이고, 파일은 매 분기마다 점점 더 본래 목적에서 벗어나게(drift) 될 것입니다.

Keystone은 그 해답을 구조적으로 만듭니다. 프로젝트 규칙은 harness/guides/에 위치합니다. 조직 규칙은 harness/policies/<name>/guides/에 위치합니다. 락파일(lockfile)은 조직 규칙을 동기화 상태로 유지합니다. 네임스페이스 규칙(namespace rule)은 정책이 자신의 영역을 벗어나지 않도록 방지합니다. 드리프트 센서(drift sensor)는 에이전트 실행 시점에 두 레이어 모두를 동일한 방식으로 강제합니다. 에이전트는 규칙이 어디에서 왔는지 상관하지 않기 때문입니다. 파일을 편집하는 인간만이 그것을 신경 쓸 뿐입니다.

이것이 바로 명시할 가치가 있는 규칙입니다: harness는 단순히 규칙을 두는 장소가 아닙니다. harness는 적절한 사람이 편집할 수 있는 곳에 규칙을 두는 장소입니다. 레벨 3는 그 질문에 대한 답이 내려지는 레이어입니다.

예시 리포지토리(repo)는 tacoda-policy입니다. harness 설치 프로그램은 keystone입니다. 제가 계속해서 되풀이하는 말은 이것입니다: 제대로 된 분리의 비용은 디렉토리 하나를 더 만드는 것뿐입니다. 분리하지 않았을 때의 비용은 더 이상 신뢰할 수 없게 된 파일입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0