FIELD MANUAL · v1

위험한 변경을
책임 있게 운영하는 법

설치부터 잠금 규칙, 승인과 감사 기록까지. DevHive를 실제 팀 운영에 적용하는 데 필요한 절차와 판단 기준을 설명합니다.

01

DevHive란 무엇인가

DevHive는 AI·사람이 올린 PR 중 회사가 정한 규칙에 걸린 위험한 변경만 사람 앞에 세우고, 그 결정을 위조되지 않는 기록으로 봉인하는 PR 거버넌스 게이트입니다. 코드리뷰·SAST·CODEOWNERS를 대체하지 않고 그 위에서 하나의 필수 체크로 동작합니다.

무엇을 푸는가

AI가 코드를 쏟아내는 속도를 사람의 검토가 따라가지 못합니다. 모든 PR을 한 줄로 세워 사람이 다 읽는 방식은 병목이 되고, 반대로 그냥 통과시키면 결제 요율·인증 설정 같은 값비싼 실수가 조용히 머지됩니다. DevHive는 이 둘 사이를 가릅니다 — 회사가 지정한 잠금 규칙(Lock)에 걸린 변경만 머지를 세워 사람 앞에 두고, 나머지는 통과 이유를 기록한 채 그대로 흘려보냅니다. 무엇을 세울지는 AI가 아니라 회사가 소유한 규칙이 정합니다.

핵심 4단계 흐름

  1. 규칙에 걸림 — PR의 변경 파일이 잠금 규칙의 경로(match)에 매칭되면 게이트가 발동합니다. detect가 붙은 규칙은 '어느 파일'을 넘어 '파일 안의 무엇'(상수 값 변경·가드 제거·함수 시그니처 변경)까지 봐야 히트합니다.
  2. 머지 차단 — 걸린 규칙의 판정(verdict)이 HOLD/BLOCK이면 GitHub 필수 체크 devhive/governance가 실패로 찍혀 머지가 막히고, 담당 역할(notifyRole)에 알림이 나갑니다.
  3. 사람이 승인/반려 — HOLD는 지정 역할의 사람이 승인해야 풀립니다. 승인은 결정 시점의 head SHA(코드 지문)에 바인딩되어, 승인 뒤 한 줄만 바뀌어도 자동 무효가 되고 바뀐 부분만 다시 확인해 재승인합니다. 반려는 새 커밋이 오기 전까지 게이트를 sticky하게 닫습니다.
  4. 위조 안 되는 기록으로 봉인 — 판정·승인·반려·알림 실패까지 모든 결정이 이전 기록의 해시를 물고 이어지는 Evidence 체인에 남습니다. 한 건이라도 고치면 체인 검증(verifyChain)이 깨져 위조가 드러납니다.

판정 4단계와 게이트 결과

잠금 action판정(verdict)게이트 결과
notifyNOTIFY통과 — 담당 역할에 알림만 발송, 머지 안 막음
holdHOLD지정 역할의 사람 승인 필요 — 없으면 실패, 반려면 새 커밋까지 차단
blockBLOCK승인으로 해제 불가 — 잠금 규칙 자체를 바꿔야 풀림
(판정 불가)HOLD · fail-closed파일 목록 누락·잘못된 규칙 등은 안전하게 닫음(실패)

기존 도구 위의 PR Governance Layer

DevHive는 코드리뷰·SAST·CODEOWNERS를 대체하지 않습니다. 브랜치 보호의 required status check로 devhive/governance를 얹어, 기존 체크들과 나란히 서는 하나의 거버넌스 계층으로 동작합니다. 다른 도구들이 '코드가 맞는가'를 본다면 DevHive는 '이 위험한 변경을 누가 책임지고 승인했는가, 그 결정이 위조 불가하게 남았는가'를 강제합니다. DevHive는 코드를 만들거나 고치지 않습니다 — 무엇이 왜 걸렸는지 전달하고, 고쳐서 돌아온 변경을 다시 판정할 뿐입니다.

판정이 믿을 만한 이유

  • 판정은 순수함수입니다 — verdict = f(변경, 규칙셋@해시, 엔진@버전). LLM·네트워크·시계 의존이 0이라 같은 입력이면 언제나 같은 결론이 나오고, 콘솔의 [재실행]으로 감사 앞에서 그대로 재현됩니다.
  • 규칙은 단계적으로 켭니다 — 새 잠금은 기본 shadow(지켜보기)로 저장되어 게이트·알림에 영향이 없고, 관찰 후 notify(알림만) → enforce(본판정)로 승급합니다. 규칙 하나가 조직 전체를 세우는 사고를 막습니다.
  • 결정 브리핑(선택적 LLM 요약)은 표시 전용입니다 — 승인/거절 지시 필드가 없고 판정 경로 밖에 있어, 브리핑이 죽어도 게이트·승인은 무영향입니다.
02

시작하기

DevHive를 처음 켠 팀이 로그인부터 게이트 발효까지 밟는 순서입니다. 대시보드 상단 "시작하기" 체크리스트를 그대로 따라가면 되고, 마지막에 GitHub 쪽에서 손수 해야 하는 결정적 한 단계가 있습니다.

먼저 구글 또는 GitHub 계정으로 로그인하고 조직을 하나 만듭니다(조직 만들기 화면). 조직이 생기면 대시보드 상단에 진행률 배지가 달린 "시작하기" 체크리스트가 나타납니다. 아래 4단계를 순서대로 밟으세요. 필수 3개(①②③)가 끝나면 체크리스트는 자동으로 사라집니다 — ④ 팀 초대는 선택이라 안 해도 완료로 칩니다.

온보딩 4단계

  1. GitHub App 설치·저장소 연결 — 리포지토리 섹션(/repos)에서 [GitHub에서 설치]를 눌러 GitHub App을 설치합니다. 설치를 완료한 바로 그 GitHub 계정으로 소유권을 검증한 뒤에만 활성 조직에 연결됩니다(설치 콜백 code로 GET /user/installations 대조 — installation id 치환·열거 공격 차단). 연결·해제는 Evidence(installation_link/installation_unlink)로 남습니다. 연결된 설치의 PR만 게이트 대상이 되고, 미연결 저장소는 DevHive가 지키지 않습니다.
  2. 첫 잠금 규칙 만들기 (shadow로 시작) — 설정 섹션(/settings)의 룰 폼에서 잠금 규칙을 만듭니다. 새 규칙은 기본 shadow로 저장돼 매칭돼도 게이트·알림에 영향이 없고 shadowHits로만 관찰됩니다. 켜기 전에 [백테스트]를 누르면 이 규칙이 켜져 있었다면 지난 90일 PR 이력에서 무엇이 걸렸을지를 읽기 전용으로 리플레이해 보여줍니다(아무것도 저장·차단하지 않음). 충분히 관찰한 뒤 shadow → notify → enforce로 단계를 승급하면(사유 필수), 그때부터 enforce 히트에 사람 승인이 강제됩니다.
  3. 승인자 등록 — 승인 콘솔(/console) 첫 진입에서 자신을 조직 승인자로 등록합니다. login은 폼에 직접 입력하는 값이 아니라 OAuth로 검증된 본인 GitHub login으로 자동 결박됩니다(화면에 고정·수정 불가). 그래서 구글이 아니라 GitHub 계정으로 로그인해야 등록 폼이 열립니다 — 구글로 로그인한 상태면 "GitHub 계정으로 로그인해야 등록할 수 있습니다" 안내가 뜹니다. 역할(예: compliance_officer, security_lead)만 콤마로 구분해 1개 이상 입력하면 됩니다. 등록·승인은 모두 감사 기록(Evidence)에 남습니다.
  4. 팀 초대 (선택) — 설정 섹션의 멤버에서 초대 링크를 만들어 동료를 합류시킵니다. 링크는 소지자 합류(bearer)·만료 7일·조직당 활성 1개(재생성하면 기존 링크 무효)입니다. 이 단계는 선택이므로 건너뛰어도 체크리스트는 완료로 처리됩니다.

체크리스트 항목이 언제 "완료"로 바뀌나

단계화면완료로 인정되는 조건
① GitHub 연결리포지토리 /repos설치가 1개 이상 연결됨
② 첫 잠금 규칙설정 /settings잠금 규칙이 1개 이상 생성됨
③ 승인자 등록승인 콘솔 /console본인이 이 조직의 승인자로 등록됨
④ 팀 초대 (선택)설정 /settings조직 멤버가 2명 이상

알아두면 좋은 것

  • 체크리스트는 어딘가에 저장되는 게 아니라 실제 데이터에서 그때그때 파생됩니다 — 연결·규칙·승인자를 지우면 해당 항목이 다시 미완료로 돌아오고 체크리스트도 다시 뜹니다.
  • 백테스트는 판정 경로와 같은 GitHub 토큰을 쓰기 때문에 조직당 1분에 1회로 제한됩니다. 코퍼스는 저장소당 최근 200 PR·런당 총 500건이 상한이고, GitHub 자격증명이 없는 로컬 프리뷰 모드에서는 코퍼스가 0이라 결과가 비어 나옵니다.
  • 백테스트가 보여주는 숫자는 "이 규칙을 켰다면 게이트에 걸렸을 PR 수"이지 실제 사고·손실 건수가 아닙니다. 항상 윈도우(90일)와 코퍼스 크기를 함께 읽으세요.
  • GitHub App 최초 설정(설치 시 OAuth 사용 옵션·Callback URL·client secret 등록)은 조직당 1회 필요하며, 운영자/관리자가 GitHub App 관리 화면과 apps/web 환경변수에서 처리합니다.
03

잠금 규칙 만들기

잠금 규칙(Lock)의 필드 구성, 동작(notify/hold/block)과 스테이지 롤아웃(shadow→notify→enforce), 그리고 내용 기반 detect 3종을 실제 게이트 동작 그대로 설명합니다.

잠금 규칙(Lock)은 "이 경로의 파일이 PR에 들어오면 게이트가 이렇게 판정하라"를 정의하는 항목입니다. PR이 열리거나 새 커밋이 올라오면 게이트웨이가 변경 파일 목록을 규칙의 match 글롭과 대조하고, 걸린 규칙의 action으로 게이트 판정(verdict)을 계산해 GitHub 체크 상태로 씁니다. 규칙 하나가 곧 잠금 하나입니다.

규칙 필드

필드필수의미
id필수문자열규칙 식별자. 저장 후 변경 불가(수정 시 잠금)
match필수글롭 배열대상 경로. picomatch 글롭, 하나라도 걸리면 매칭. 예: packages/billing/**/*.ts
importance필수high | medium표시용 라벨. 게이트 판정에는 영향 없음
action필수notify | hold | block걸렸을 때의 동작(아래 표)
notifyRole필수역할명매칭 시 알림을 받을 역할
approverRole조건부역할명hold면 필수(승인 주체) · block은 표기용 · notify는 불필요
stage선택shadow | notify | enforce롤아웃 단계. 미지정은 enforce로 해석
repoSelector선택repo 글롭 배열적용 저장소 한정(owner/name 글롭). 미지정은 전체 저장소
detect선택객체내용 기반 탐지. 있으면 경로 매칭 AND 내용 매칭 둘 다 충족해야 히트
note선택문자열메모

동작(action) 3종

action게이트해제 방법
notify통과시키되 notifyRole에 알림 발송해제 개념 없음(막지 않음)
hold보류(게이트 실패). approverRole 역할의 사람 승인이 있어야 통과approverRole 승인. 반려가 있으면 새 커밋 전까지 계속 차단
block차단(게이트 실패)승인으로 해제 불가 — 잠금 규칙 자체를 바꿔야 풀림

스테이지 롤아웃 (shadow → notify → enforce)

새 규칙을 곧바로 차단에 투입하지 않고 단계적으로 올립니다. 스테이지마다 게이트 반영 범위가 다릅니다.

stage게이트 반영알림용도
shadow없음(관찰만)없음매칭만 계산해 Evidence로 기록. 오탐 관찰용
notify없음(무반영)notifyRole에 알림발효 전 규칙을 지켜보며 알림만
enforce반영(실제 판정)매칭 시 알림본판정 — notify/hold/block이 게이트에 적용
  • 신규 규칙은 기본 shadow로 시작합니다(웹 설정 폼은 단계를 받지 않고 게이트가 shadow로 저장).
  • 단계 승급·강등은 별도 경로로만 가능하고, 사유가 필수이며 감사 기록(Evidence)에 from→to와 사유가 남습니다. 일반 저장으로 단계를 몰래 내리는 것(fail-open)은 막혀 있습니다.
  • 권장 흐름: shadow에서 오탐 없음을 확인 → notify로 알림 관찰 → enforce로 시행.

내용 기반 detect 3종

detect가 붙은 규칙은 경로가 걸린 것만으로는 히트하지 않고, 실제 코드 변경 내용까지 조건에 맞아야 히트합니다. 지원 패턴은 세 가지입니다.

  • constant_changed — 이름이 붙은 숫자 상수의 값이 바뀐 경우. 옵션 direction(increase | decrease | any, 미지정=any)으로 방향을 한정하고, identifier(상수 이름에 대한 글롭)로 대상 이름을 좁힙니다. 값이 실제로 달라져야 하며 표기만 바뀐 경우(예: 1000 → 1_000)는 히트하지 않습니다.
  • guard_removed — if 조건이나 데코레이터가 줄어든 경우(가드 제거). direction·identifier를 지정할 수 없습니다.
  • signature_changed — export된 함수의 파라미터 시그니처가 깨진 경우(파라미터 삭제 · 최소 호출 인자 증가 · 파라미터 이름 변경). identifier(함수 이름 글롭)로 대상을 좁힐 수 있고, direction은 지정할 수 없습니다. export되지 않은 함수는 대상이 아닙니다.
{
  "id": "RULE-fee-rate",
  "match": ["packages/billing/**/*.ts"],
  "importance": "high",
  "action": "hold",
  "notifyRole": "compliance_officer",
  "approverRole": "billing_lead",
  "detect": {
    "pattern": "constant_changed",
    "direction": "any",
    "identifier": "*_RATE"
  }
}

// guard_removed — 옵션 없이 pattern만
{ "pattern": "guard_removed" }

// signature_changed — 함수 이름 글롭만 허용
{ "pattern": "signature_changed", "identifier": "createInvoice" }

규칙을 만드는 곳

  • 웹 설정의 잠금 룰 화면에서 기본 필드(id, 대상 경로, 중요도, 동작, 알림 역할, 승인 역할)로 규칙을 만들고 수정·삭제·단계 변경을 합니다.
  • detect와 repoSelector는 웹 폼에 입력란이 없습니다 — 레지스트리 API로 설정합니다. 웹 폼에서 그 규칙을 수정해도 detect·repoSelector·note는 그대로 보존됩니다(무음 소실 방지).
  • 저장 전 백테스트(지난 90일)로 이 규칙이 과거 PR에 몇 건 걸렸을지 미리 확인할 수 있습니다.
04

게이트는 어떻게 동작하나

DevHive 게이트는 PR의 devhive/governance 커밋 체크 하나로 드러납니다. PR이 열리거나 새 커밋이 올라오면 판정을 계산해 체크를 통과(success)/차단(failure)으로 확정하고, 모든 판정·승인은 커밋 지문(head SHA)에 결박됩니다.

DevHive의 게이트는 GitHub의 커밋 상태 체크(Check) 하나로 나타납니다. 체크 이름은 devhive/governance이며, PR에 이 체크가 success면 통과, failure면 병합 게이트가 닫힌 상태입니다. 판정은 GitHub 웹훅을 받은 게이트웨이가 잠금 규칙 스냅샷과 변경 파일을 대조해 계산하고, 그 결과(판정)를 체크의 성공/실패로 되씁니다.

PR 열림·새 커밋 → 판정 → 체크

  1. 웹훅 수신: PR이 opened/synchronize(새 커밋 push)/reopened 되면 게이트웨이가 이벤트를 받습니다. base 브랜치 변경(edited)도 별도로 처리합니다.
  2. 게이트 선점: 판정을 계산하기 전에 해당 head SHA의 체크를 즉시 pending(GitHub상 in_progress)으로 먼저 써서 '판정 대기 중' 상태를 만듭니다. 판정 없이 통과처럼 보이는 창을 없앱니다.
  3. 판정 계산: 백그라운드 워커가 변경 파일 목록을 가져와 잠금 규칙과 대조하고, 필요하면 파일 내용을 일시 분석(detect)해 판정(ALLOW/NOTIFY/HOLD/BLOCK)을 냅니다.
  4. 체크 확정: 판정과 현재까지의 승인을 합쳐 최종 게이트 상태를 정하고, head SHA의 devhive/governance 체크를 success 또는 failure로 완료 처리합니다.

판정 → 게이트 상태

판정게이트승인으로 열리나
ALLOW / NOTIFYsuccess (통과)해당 없음 — 이미 통과. NOTIFY는 담당자 알림만 발송
HOLD필요 역할 승인 전까지 failure예 — 필요 역할의 사람 승인이 모두 채워지면 success
BLOCKfailure아니오 — 승인으로 해제 불가. 잠금 규칙 자체를 바꿔야 함
fail-closed (판정 불가)failure아니오 — 분석 자체가 불가하여 홀드

코드 지문(head SHA) 바인딩

모든 판정과 승인은 특정 커밋(head SHA)에 결박됩니다. 승인은 저장될 때 그 시점의 head SHA를 함께 기록하고, 게이트를 다시 계산할 때는 '현재 판정의 SHA와 일치하는 승인'만 유효로 셉니다. 그래서 새 커밋을 push하면 head SHA가 바뀌고, 이전 SHA에 묶인 승인은 자동으로 무효가 됩니다. 워커가 판정을 되쓸 때도 head SHA가 일치하는 행에만 쓰므로, 뒤늦게 도착한 옛 커밋의 판정이 최신 상태를 덮지 못합니다.

새 커밋 push 시 일어나는 일

  • 새 head SHA로 변경 레코드가 갱신되며 이전 판정은 무효화(verdict=NULL), 게이트는 pending으로 리셋됩니다.
  • 이전 SHA에 묶인 DevHive 승인은 SHA 불일치로 더 이상 집계되지 않습니다(자동 무효화).
  • GitHub 네이티브 승인 리뷰도 dismiss API로 실제로 해제하고, PR에 '새 커밋으로 기존 승인이 무효화되었다'는 안내를 남깁니다.
  • 새 SHA로 판정을 처음부터 다시 계산합니다.

우회 차단 — 정직하게 두 가지

게이트를 우회하려는 두 가지 시도를 명시적으로 막습니다. 두 경우 모두 완벽한 방어라기보다, 이 두 회피 경로를 알고 대응하도록 설계되어 있다는 점을 밝힙니다.

  • 파일 rename로 잠금 글롭 밖으로 이동: 잠긴 경로의 파일을 규칙에 안 걸리는 경로로 rename해도, 변경 파일 목록을 전개할 때 rename의 '이전 경로'까지 매칭 대상에 포함합니다. 따라서 이전 경로가 잠금 글롭에 걸리면 그대로 잡힙니다.
  • base 브랜치 스왑: head SHA를 그대로 둔 채 base 브랜치만 바꾸면(예: 더미 base로 success를 받은 뒤 main으로 교체) head 기준 supersede가 걸리지 않습니다. 이를 막기 위해 base 변경(edited의 changes.base)을 감지하면 판정을 강제로 다시 돌리고, 이때는 head SHA가 그대로여서 SHA 바인딩만으로는 승인이 안 지워지므로 기존 승인을 명시적으로 삭제(무효화)합니다.
05

승인과 반려

DevHive의 승인·반려는 보류(HOLD) 게이트를 여닫는 사람 책임 결정입니다. 필요 역할을 가진 사람만, 작성자 본인·봇·에이전트는 제외하고, 모든 결정은 현재 커밋의 코드 지문(head SHA)과 승인자 신원에 이중으로 결박됩니다.

승인과 반려는 게이트 판정이 보류(HOLD)일 때만 의미가 있습니다. 통과 상태는 승인 없이 열리고, 차단(BLOCK)은 승인으로 열 수 없습니다 — 차단은 잠금 규칙 자체를 바꿔야만 풀립니다. 판정이 아직 나오지 않았거나(판정 대기), 판정이 현재 커밋과 다른 head SHA에 묶여 있으면 승인·반려 요청은 모두 거부됩니다.

승인 자격 — 누가 승인할 수 있나

규칙위반 시 동작근거
필요 역할을 가진 사람만판정이 요구하는 승인자 역할(requiredApproverRoles)과 내 역할이 하나도 겹치지 않으면 거부(403, "요구 역할 아님")server.ts:142-146
작성자 본인 승인 금지(SoD)승인자 login이 PR 작성자와 같으면 거부(403, "작성자 본인은 승인할 수 없습니다")server.ts:135-137
봇·에이전트 계정 승인 금지승인자 신원이 사람(human)이 아니면 거부(403, "에이전트/봇 계정은 책임 승인을 할 수 없습니다")server.ts:138-141, identity.ts:21-37
판정 대기·SHA 불일치 시 불가판정이 없거나 판정 SHA가 현재 head와 다르면 거부(409, "판정 대기 중")server.ts:125-127
차단(BLOCK) 잠금승인으로 해제 불가(409) — 잠금 변경 필요server.ts:131-133

직무분리(SoD)와 비인간 작성 PR

승인자는 PR 작성자와 다른 사람이어야 하고, 사람 계정이어야 합니다. 봇 계정(GitHub user.type=Bot 또는 login이 [bot]로 끝남), org 에이전트 레지스트리에 등록된 계정, AI co-author 트레일러(Co-authored-by: claude/copilot/codex 등)가 붙은 커밋 작성자는 사람으로 판정되지 않아 책임 승인을 할 수 없습니다. 신원 판별은 위조 가능한 부분 신호에 기반한 휴리스틱이며, 제품이 보증하는 것은 '에이전트 탐지'가 아니라 '사람 책임 승인 보장'입니다.

반려

  • 사유 필수 — 사유가 비어 있거나 문자열이 아니면 반려가 거부됩니다(400, "반려에는 사유가 필요합니다"). 사유는 현재 head SHA에 바인딩되어 감사에 남습니다.
  • 현재 커밋에 sticky — 반려가 하나라도 있으면 이후 승인이 요구 역할을 모두 충족하더라도 게이트는 열리지 않습니다. 판정은 failure로 고정되고 요약에 "반려됨 … 새 커밋 전까지 차단"이 표시됩니다.
  • 승인·반려 모두 Evidence 체인에 기록됩니다(각각 approval / rejection 항목). 승인자·역할·결정 시점 SHA·게이트 결과가 함께 봉인됩니다.

승인자 신원 결박 (head SHA와 별개의 두 번째 결박)

승인자 등록은 GitHub OAuth로 검증된 본인 login으로만 가능합니다. 등록 요청의 login은 그 사용자가 OAuth로 로그인할 때 기록된 실제 GitHub login과(대소문자 무시) 정확히 일치해야 하며, 일치하지 않으면 not_verified로 거부됩니다. GitHub 없이 구글 등으로만 로그인한 사용자는 검증된 login이 없어 승인자로 등록할 수 없습니다. 이 결박이 막는 구체적 공격은 'PR 작성자가 타인의 login을 자기 승인자 계정으로 등록해 SoD 작성자 본인 검사를 우회하는' 경로입니다 — 남의 이름을 자기주장으로 등록할 수 없습니다.

승인은 정확한 head SHA에 묶인다

모든 승인·반려 행은 결정 시점의 head SHA(코드 지문)에 바인딩되어 저장됩니다. 게이트 집계는 현재 판정 SHA와 같은 SHA의 결정만 셉니다. 따라서 새 커밋이 push되면 head SHA가 바뀌고, 이전 커밋에 대한 승인·반려는 자동으로 무효가 되어 게이트가 다시 닫힙니다. 승인 후 몰래 코드를 덧붙이는 우회를 원천 차단하며, 반려 역시 새 커밋 전까지만 유효한 이유가 바로 이 바인딩입니다.

06

Evidence와 증적 반출

DevHive는 게이트의 모든 판정과 사람의 승인·반려, 규칙 변경, 우회·알림 실패까지 조직별 해시 체인에 추가 전용으로 남긴다. 콘솔에서 무결성 배지로 확인하고, 전체 JSON 또는 변경 단위 Evidence Pack으로 반출해 제3자가 스스로 재검증할 수 있다.

무엇이 기록되나

게이트가 판정을 저장할 때, 승인권자가 승인·반려할 때, 잠금 규칙이나 스테이지가 바뀔 때, 게이트를 통과하지 않은 채 머지가 감지될 때, 알림 전송이 재시도 후에도 실패할 때 — 이 모든 사건이 Evidence 레코드로 한 줄씩 쌓인다. 사람이 지우거나 고칠 수 없는 추가 전용(append-only) 기록이다. 주요 유형은 다음과 같다.

유형언제 남나담기는 값
verdict게이트가 판정을 저장할 때판정 조치·게이트 상태·작성자 신원·shadow 히트
approval / rejection승인권자가 승인 또는 반려할 때결정자·역할·사유·대상 커밋 SHA·게이트 결과
ignored_approvalhead 불일치·비사람·미등록 리뷰어의 승인이 무시될 때리뷰어·사유(stale-review-sha, non-human 등)
bypass_detected게이트 success가 아닌 PR이 머지될 때(우회 감지)마지막 게이트 상태·verdict·머지 SHA
base_changedbase 브랜치가 바뀌어 재판정될 때head SHA·새 base SHA
notify_failure알림을 1회 재시도한 뒤에도 실패할 때대상 역할·잠금 ID·에러
registry_change규칙 put/delete·스테이지 전환·에이전트·설치 연결·승인자 등록 등작업(op)과 대상 값

조직별 sha256 해시 체인

각 레코드의 해시는 (유형 + payload + 타임스탬프)를 키 정렬한 정규 JSON에 직전 레코드의 해시를 이어 붙여 sha256으로 계산한다. 첫 레코드의 직전 해시는 GENESIS_HASH(0을 64자 반복)다. 조직마다 독립된 체인이고, 기록 추가는 조직 단위로 직렬화된다(PostgreSQL advisory lock) — 서로 다른 조직은 동시에 쌓여도 체인이 섞이지 않는다. payload를 정규 JSON으로 해시하므로 필드 순서가 달라도 같은 해시가 나온다. 검증은 GENESIS부터 순서대로 걸어가며 각 레코드의 '직전 해시 연결'과 '해시 재계산'을 대조하고, 어긋나면 그 지점의 seq를 훼손 위치로 돌려준다.

Evidence 페이지 — 무결성 배지와 전체 반출

  • 콘솔의 Evidence 페이지는 조직 전체 체인을 읽어 검증하고, 무결이면 초록 배지(체인 무결 · N건), 훼손이면 빨강 배지(체인 훼손 — seq X)를 띄운다.
  • 목록은 최근 200건만 화면에 표시하지만, 검증과 반출은 항상 체인 전체를 대상으로 한다.
  • 'JSON 반출' 버튼은 해시 대상 필드를 전부 포함한 전체 체인을 파일로 내려준다. 반출본만으로 제3자가 동일한 검증(verifyChain)을 재현할 수 있다 — 서버에 다시 접속할 필요가 없다.

Evidence Pack — 변경(PR) 단위 증적

조직 전체 덤프가 부담스러운 감사·보고 상황을 위해, 콘솔의 변경 상세 화면에서 그 PR 하나에 관한 증적만 묶어 내려받는다. 이 변경에 연결된 레코드만 골라 Rule ID·결정·사유·타임스탬프·해시·체인 검증을 한 문서로 만든다. 기계 판독용 JSON과 사람이 읽는 Markdown 리포트, 두 형식으로 제공한다.

Evidence Pack에 담기는 것

  • 판정: 조치(HOLD/ALLOW 등)·매칭된 Rule ID·필요 승인 역할·엔진 버전·룰셋 해시·입력 해시
  • 결정 이력: 승인/반려·결정자·역할·사유·시각·대상 커밋, 그리고 '현재 코드 유효' 여부 — 승인한 커밋 SHA가 지금의 head SHA와 같은지(코드 지문 바인딩). 다르면 '아니오(구 커밋)'로 표시돼, 옛 커밋에 대한 승인임이 드러난다.
  • 관련 Evidence 레코드의 seq·유형·해시·직전 해시·시각
  • 체인 검증 결과: 관련 레코드만 팩에 담아도 검증은 조직 전체 체인에서 수행되므로, 팩 하나로 '이 조직 체인이 통째로 무결한가'까지 확인된다.
07

백테스트: 켜기 전에 효과 미리보기

규칙을 저장하기 전에, 지난 90일 실제 PR에 그 규칙을 그대로 리플레이해 "이 규칙이면 몇 건이 게이트에 걸렸을지"를 샘플과 함께 보여줍니다. 운영과 똑같은 판정 엔진을 쓰며, 실제 PR·승인·Evidence에는 전혀 손대지 않는 읽기 전용 미리보기입니다.

새 잠금 규칙을 만들 때 가장 불안한 지점은 "이걸 켜면 우리 팀 PR이 얼마나 걸릴까"입니다. 백테스트는 저장 전에 그 답을 실측으로 보여줍니다. 지금 폼에 입력한 규칙을 지난 90일간 조직의 실제 PR들에 하나씩 대입해, 게이트에 걸렸을 PR 수와 그중 일부 샘플(어떤 파일 때문에 걸렸는지 포함)을 돌려줍니다.

왜 믿을 수 있나 — 마케팅 근사치가 아닙니다

백테스트는 별도의 추정 로직을 쓰지 않습니다. 운영 게이트가 PR을 판정할 때 호출하는 바로 그 policy-engine evaluate 함수를 그대로 재사용합니다. 후보 규칙 하나만 담은 스냅샷을 만들어 각 PR의 변경 파일에 대해 실제 판정을 돌리므로, "걸렸다"는 계산은 운영에서 실제로 걸릴 조건과 동일합니다. 단, 스테이지 게이트(shadow→notify→enforce)를 우회하기 때문에 규칙이 enforce 단계라고 가정한 잠재 효과를 보여줍니다 — 즉 그 규칙의 최대 영향 범위입니다.

실행 방법

  1. 설정 화면의 잠금 룰 섹션에서 신규 룰 폼(또는 기존 룰 '수정')을 엽니다.
  2. 룰 ID와 대상 경로(콤마 구분 글롭, 예: payments/**, billing/**)를 입력합니다. 이 두 값이 비어 있으면 버튼은 비활성입니다.
  3. 폼 하단의 '백테스트 (90일)' 버튼을 누릅니다. 조회 중에는 '조회 중…'으로 바뀝니다.
  4. 잠시 후 결과 카드가 나타납니다. 마음에 안 들면 폼 값을 바꿔 다시 돌리고, 확정되면 '룰 생성'(또는 '수정 저장')으로 저장합니다.

결과 읽는 법

결과 카드 맨 위에 "지난 90일, 이 룰이면 N건이 게이트에 걸렸을 것입니다"가 굵게 표시됩니다. 바로 아래 회색 문구가 이 숫자의 성격을 못 박습니다: 이것은 '잠재 게이트 수'이지 실제 사고 수도, 요금 산정 근거도 아닙니다. 함께 리플레이한 코퍼스 크기(파일까지 조회에 성공한 PR 수)와 창(90일)도 같이 표시됩니다. 그 아래로는 실제로 걸린 PR 샘플이 최대 20건까지 나오며, 각 샘플은 저장소·PR 번호·제목과 걸린 파일(최대 5개)을 보여줘 규칙이 의도한 곳을 정확히 짚었는지 눈으로 확인할 수 있습니다.

결과에 붙는 배지의 뜻

배지의미실무 해석
경로 매칭 상한선내용 검사(detect) 규칙을 경로 매칭만으로 리플레이했음실제로 켜면 이 숫자 이하가 됩니다 — 표시된 건 최대치
최근 500건이력이 많아 총 코퍼스 상한 500건까지만 리플레이가장 최근 PR들 기준. 90일 전체를 다 못 본 경우
부분 코퍼스일부 저장소·PR 조회에 실패코퍼스는 조회 성공분만 — 실제 대상은 이보다 많을 수 있음

알아두면 좋은 동작

  • 조회된 이력이 0건이면 '조회된 이력이 없습니다. GitHub App 연결 상태를 확인하십시오' 안내가 나옵니다 — 규칙 문제가 아니라 연결 문제일 수 있습니다.
  • 백테스트는 조직당 1분에 1회로 제한됩니다. 이미 실행 중이거나 직전에 돌렸다면 잠시 후 다시 시도하라는 안내가 나옵니다(판정 워커와 GitHub 토큰을 공유하기 때문의 보호 장치).
  • 기존 규칙을 '수정'해서 백테스트하면, 저장 시 보존되는 detect·저장소 선택자(repoSelector)까지 그대로 결박해 리플레이합니다. 즉 '백테스트한 규칙 = 저장될 규칙'입니다.
  • GitHub 이력 조회가 전부 실패하면 결과 대신 실패 배너가 뜹니다 — 이 경우에도 아무것도 저장되지 않습니다.
08

레퍼런스 & 자주 묻는 질문

DevHive 화면에 뜨는 상태 배지의 색과 뜻, 잠금 규칙의 내용 판정(detect) JSON 필드, 그리고 운영 중 가장 많이 나오는 질문을 실제 동작 기준으로 정리했다.

1. 상태 배지 색의 의미

DevHive의 배지 색은 다섯 개 톤으로 통일돼 있다 — 에메랄드(정상/시행), 슬레이트(중립/관찰·대기), 블루(정보/알림), 앰버(주의/보류), 로즈(위험/차단·반려). 같은 색은 화면이 달라도 같은 뜻이다.

표기색(톤)나타나는 곳
시행 (enforce)에메랄드규칙 스테이지실제 게이트에 반영되는 단계. 히트하면 본판정에 들어간다. stage 미지정 규칙도 이 값으로 해석
관찰 (shadow)슬레이트규칙 스테이지매칭만 계산해 Evidence로만 남긴다. 차단·알림 0
알림 (notify)블루규칙 스테이지매칭 시 담당자에게 알림만 보낸다. 게이트 판정에는 무반영
action: notify블루규칙 조치알림 전용 조치 — 통과시키되 알린다
action: hold앰버규칙 조치사람 승인이 있어야 통과하는 보류 조치
action: block로즈규칙 조치차단 — 승인으로 못 연다. 잠금 규칙 자체를 바꿔야 열림
importance: high로즈규칙 중요도중요도 높음
importance: medium슬레이트규칙 중요도중요도 보통
게이트 success에메랄드PR 체크 상태통과 — 필요 승인 충족 또는 자동 통과(green-lane)
게이트 failure로즈 / 앰버PR 체크 상태차단·보류(승인 미충족)·반려·판정 불가(fail-closed) 등 게이트가 닫힌 상태
게이트 pending슬레이트PR 체크 상태판정 진행 중 — 결과 대기
콘솔: 내 승인 필요로즈웹 콘솔 버킷지금 내 역할의 승인이 필요한 변경
콘솔: 참고 / 대기앰버 / 슬레이트웹 콘솔 버킷다른 사람 승인 대기·관찰(shadow) 참고 항목

2. 내용 판정(detect) JSON 필드 레퍼런스

잠금 규칙에 detect를 붙이면 경로 매칭만이 아니라 '무엇이 어떻게 바뀌었는지'까지 봐야 히트한다 — 즉 경로 매칭 ∧ 내용(팩트) 매칭이 둘 다 충족돼야 한다. 알 수 없는 필드가 들어오면 검증에서 거부돼 규칙 전체가 fail-closed(HOLD)로 표면화된다.

필드허용값필수설명
patternconstant_changed · guard_removed · signature_changed필수잡을 변경의 종류. 상수값 변경 / 가드(방어 코드) 제거 / 함수 시그니처 변경
directionincrease · decrease · any (미지정 = any)선택constant_changed 전용 — 수치 증감 방향. guard_removed·signature_changed에는 지정 불가
identifierpicomatch 글롭 문자열 (미지정 = 전체)선택상수/함수 이름 글롭. constant_changed·signature_changed에서만 사용. guard_removed에는 지정 불가
  • constant_changed: direction·identifier 둘 다 붙일 수 있다. 예) 타임아웃 상수가 늘어나는 변경만 잡기 → direction: increase.
  • signature_changed: identifier(함수명 글롭)만 허용, direction은 거부.
  • guard_removed: direction·identifier 모두 지정 불가 — 가드 제거 자체를 잡는다.
  • 분석 불가 파일(파싱 실패·미지원 언어·내용 확인 불가)은 '매칭'으로 간주한다. 확인 못 했으면 잡는 fail-closed 정책이라, 이때 근거 스니펫에 (분석 불가: …)로 남는다.
  • detect의 근거(스니펫)는 파일당 200자·규칙당 10건 상한으로 Evidence에 담긴다.

3. 자주 묻는 질문

아래는 실제 코드 동작 기준의 답변이다. 재현하려면 각 질문의 조건을 그대로 만들어 PR을 올려 보면 된다.

Q1. 규칙을 만들었는데 왜 안 걸리나요?

가장 흔한 원인은 스테이지입니다. 규칙이 관찰(shadow)이면 매칭만 계산해 Evidence로만 남기고 차단·알림이 0이며, 알림(notify)이면 알림만 가고 게이트에는 반영되지 않습니다. 실제로 게이트를 닫는 것은 시행(enforce) 스테이지뿐입니다. stage를 지정하지 않으면 enforce로 해석되므로, 안 걸린다면 규칙이 shadow/notify로 설정돼 있을 가능성이 큽니다. 그 밖에 (a) repoSelector가 대상 저장소와 안 맞으면 모든 스테이지에서 제외되고, (b) match 글롭이 실제 변경 파일 경로와 안 맞거나, (c) detect가 붙어 있으면 경로는 맞아도 내용(팩트)이 안 맞으면 히트하지 않습니다. 그리고 중요한 전제 — DevHive는 GitHub에 체크 상태(성공/실패)를 씁니다. 머지를 실제로 막으려면 그 체크를 GitHub branch protection의 'required check'로 등록해야 합니다. 이 등록이 안 돼 있으면 게이트가 failure여도 머지를 막지 못합니다.

Q2. 분명히 승인했는데 왜 다시 막히나요?

승인은 결정 시점의 head SHA(코드 지문)에 바인딩됩니다. 승인 뒤 새 커밋이 올라오면 head SHA가 바뀌고, 이전 승인은 현재 판정 SHA와 달라져 무효 처리됩니다(게이트는 승인.sha == 판정.sha인 것만 유효로 봅니다). 즉 '승인 후 코드가 바뀌면 다시 보류'가 정상 동작입니다. Evidence Pack에서는 이 승인이 boundToHead=아니오(구 커밋)로 표기됩니다. 바뀐 코드를 다시 검토하고 재승인하면 열립니다.

Q3. 봇/에이전트가 만든 PR은 어떻게 처리되나요?

C2 사람 책임 승인이 걸립니다. 작성자 신원이 사람(human)이 아니라고 판별되고 잠금 히트가 하나라도 있으면, 게이트가 합성 히트(C2-HUMAN-APPROVAL)를 추가해 판정을 최소 HOLD로 올리고 '사람 책임자' 역할의 승인을 요구합니다. 사람 승인자가 승인해야 열립니다. 단 잠금 히트가 0인 변경에는 C2가 발동하지 않고(통제 대상이 아니면 굳이 사람을 세우지 않음), 이미 BLOCK인 건은 BLOCK을 유지합니다. 신원 판별은 커밋 트레일러·계정 유형 등 위조 가능한 부분 신호(heuristic)이며, 판별 결과는 그대로 Evidence에 남습니다.

Q4. 잘못 승인했으면 어떻게 되돌리나요?

반려(reject)로 뒤집습니다. 반려는 현재 head SHA에 sticky해서, 승인 충족 여부와 무관하게 게이트를 닫습니다 — 즉 잘못된 승인이 있어도 같은 코드에 대한 반려가 우선해 막습니다. 반려에는 사유가 필수이고 현재 head SHA에 바인딩돼 감사에 남습니다. 또는 새 커밋을 올리면 head SHA가 바뀌어 기존 승인·반려가 모두 무효화되고 판정이 초기화됩니다. 다만 action이 block인 규칙은 애초에 승인/반려로 여닫는 대상이 아니며, 잠금 규칙 자체를 바꿔야 풀립니다.

Q5. 기록이 위조되면 알 수 있나요?

Evidence 체인으로 검증됩니다. 각 레코드의 해시는 sha256(정규화 payload+ts+type + 직전 레코드 해시)로 계산돼 GENESIS부터 사슬처럼 연결됩니다. 검증기는 모든 레코드의 prevHash 연결과 해시 재계산을 전수로 확인하고, 어긋나면 훼손 지점(brokenAtSeq)을 돌려줍니다. 그래서 사후에 어느 한 레코드라도 수정하면 그 이후 사슬의 해시가 전부 어긋나 검증이 실패합니다. Evidence Pack 리포트 맨 아래에 체인 무결/훼손 결과가 표기됩니다.

Q6. 판정을 확인할 수 없을 때(분석 실패 등)는 어떻게 되나요?

닫는 쪽(fail-closed)으로 처리합니다. 변경 파일 목록을 확인할 수 없거나, GitHub이 파일 목록을 상한에서 절단했거나, 잘못된 잠금 엔트리가 있으면 판정을 HOLD로 닫습니다. detect 대상 파일을 분석하지 못한 경우도 매칭으로 간주합니다(확인 못 했으면 잡는다). GitHub·DB의 일시 오류는 재시도 대상이라 체크가 pending으로 유지되며, 통과로 새어 나가지 않습니다. 즉 '모르면 통과'가 아니라 '모르면 보류'가 기본값입니다.

09

구독과 결제 관리

14일 평가판에서 유료 플랜으로 전환하고, 카드·청구서·해지를 Stripe 고객 포털에서 관리하는 방법입니다.

유료 플랜 시작하기

  1. 앱의 구독 및 결제 화면(/billing)에서 Team 또는 Business를 선택합니다. 플랜 변경은 조직 owner만 할 수 있고 다른 역할은 현재 상태만 볼 수 있습니다.
  2. Stripe 결제 화면에서 결제 수단과 청구 정보를 입력합니다. 카드 정보는 DevHive 서버가 직접 받거나 저장하지 않습니다.
  3. 결제가 완료되면 DevHive로 돌아옵니다. Stripe의 서명된 웹훅을 확인한 뒤 현재 플랜과 상태가 자동 갱신되므로, 짧은 시간 동안 '확인 중'으로 보일 수 있습니다.
  4. 이후 [결제 관리 열기]에서 카드 변경, 청구서 확인, 구독 해지를 처리합니다. 고객 포털을 나가면 DevHive 결제 화면으로 돌아옵니다.

플랜과 월 기준 가격

플랜가격적합한 팀
평가판$0 · 14일 · 카드 불필요실제 저장소에서 핵심 흐름을 검증하는 팀
Team$29 / 활성 PR 작성자 · 월 · 최소 3석잠금 규칙, 승인, Evidence를 운영하는 일반 팀
Business$49 / 활성 PR 작성자 · 월 · 최소 3석고급 정책 운영과 우선 지원이 필요한 조직
Enterprise맞춤 견적보안 검토, 계약, 대규모 도입 지원이 필요한 조직