Hive
한 줄 소개: 인사(HR) backend. 조직도 · 휴가 · 성과 평가 · 급여 · 보상을 한 곳에서 다루고, 사람은 화면으로 AI 에이전트는 MCP 80개 도구로 같은 데이터를 본다. 한국 근로기준법과 개인정보보호법이 요구하는 경계를 애플리케이션 규칙이 아니라 데이터베이스 제약으로 걸어 둔 것이 특징이다.
주소
| 용도 | 주소 |
|---|---|
| MCP 엔드포인트 | https://<workspace>.axelabs.ai/hive/mcp |
| 스키마 목록 (공개 규격) | https://<workspace>.axelabs.ai/hive/schemas |
| liveness | https://<workspace>.axelabs.ai/hive/health |
| readiness (DB 포함) | https://<workspace>.axelabs.ai/hive/health/ready |
<workspace> 는 조직에 배정된 워크스페이스 이름이다.
다루는 도메인
| 도메인 | 무엇 | 상태 |
|---|---|---|
| 조직 | 조직 트리, 임직원 마스터, 직무·직급, 발령 이력 | 운영 중 |
| 휴가 | 정책·신청·결재·잔여·팀 캘린더. 연차는 근로기준법 §60 자동 | 운영 중 |
| 성과 | 성과 근거 적재 → 월간 문답 → 분기 평가 → 이의제기 → 설명팩 | 운영 중 |
| 급여 | 월 산정, 명세서, 송금 명세, 발송, 퇴사 정산 | 운영 중 |
| 보상 | 정책 ↔ 개인 약정 ↔ 실제 이벤트 3단 모델 (스톡옵션 포함) | 운영 중 |
| KPI / OKR | 목표 관리 | 미구현 |
설계에서 고집한 것
조직마다 데이터가 물리적으로 갈린다
법인·조직마다 별도 스키마를 쓴다. 한 조직의 조회가 다른 조직의 데이터에 닿는 경로가 애초에 없다. 조직 간 참조가 필요한 자리(예: 다른 법인을 위해 한 기여)는 교차 조인이 아니라 명시적인 소속·귀속 필드로 표현하고, 권한이 있는 스키마만 골라 합친다.
발령 이력은 두 개의 시간축을 갖는다
“언제부터 유효했나”(업무 시간)와 “언제 그렇게 기록됐나”(기록 시간)를 따로 들고 있다. 그래서 과거 시점의 조직도를 그 시점에 알고 있던 그대로 재현할 수 있고, 소급 발령을 이력 조작 없이 기록할 수 있다.
모든 쓰기는 감사 로그에 남는다
변경 추적은 애플리케이션 코드가 아니라 데이터베이스 트리거가 한다. 거부된 시도도 기록된다 — 누가 무엇에 어떤 동작을 시도해 막혔는지가 보안 모니터링의 1차 자료다.
민감정보는 열별로 암호화된다
주민등록번호·주소·계좌번호는 조직별 키로 암호화해 저장한다. 급여 송금 명세를 뽑는 그 순간에만 평문이 되고, 감사 로그에는 값이 아니라 내용 해시만 남는다. 계좌번호 전체를 보유하는 서비스는 Hive 하나뿐이고, 다른 서비스로 나가는 이벤트에는 매칭용 뒤 4자리만 실린다.
본인 정보는 본인이 넣는다
입사자의 민감정보는 관리자가 대신 입력하지 않고 본인이 나눠서 채운다(개인정보보호법의 정보주체 본인 입력 원칙). 통장 사본·신분증을 올리면 문서에서 읽은 값과 신고한 값을 대조하고, 예금주 이름이 본인과 다르면 거부한다. 원본 문서는 저장하지 않는다.
권한 — 동사 × 자원
권한이 read < write < admin 같은 한 줄짜리 등급이 아니다. 동사(verb) 와 자원(resource) 의 조합이다.
| 축 | 값 |
|---|---|
| 동사 | read · write · approve · configure · grant · audit (+ 평가 사이클 전용 close · reopen) |
| 자원 | 점으로 구분한 경로 — hive.employee · hive.payroll.period · hive.payroll.* · * |
| 효과 | allow / deny |
판정 규칙: 가장 구체적인 매칭이 이긴다 → 같은 구체성이면 거부가 이긴다 → 매칭이 없으면 거부(default deny). 조회 권한이 있다고 승인 권한이 따라오지 않고, 급여를 계산할 수 있다고 발송까지 할 수 있는 것이 아니다.
권한에는 조건을 붙일 수 있다. 조건은 실행 가능한 표현식이 아니라 닫힌 문법의 선언형 JSON이다 — 허용된 연산자와 허용된 이름공간만 쓸 수 있고, 코드 평가가 없다. 판단에 필요한 값이 없으면 조건은 충족될 수 없다(fail-closed). 본인 명세서만 조회하게 하는 조건은 이렇게 생겼다.
{"eq": [{"var": "actor.email"}, {"var": "resource.employee_email"}]}기본 역할
조직마다 아래 역할이 시드된다. 필요하면 조직이 직접 조합해 새 역할을 만든다.
| 역할 | 권한 | 용도 |
|---|---|---|
owner | 모든 자원에 모든 동사 | 전권. 최소 2명 유지 강제 — 마지막 한 명은 해지되지 않는다 |
member | 조회 + 본인 정보 수정 + 휴가 신청 + 본인 명세서 | 일반 구성원 |
hr_manager | 임직원 CRUD · 조직/직무 설정 · 휴가 승인·정책 | 인사 운영 |
payroll_operator | 급여 기간·명세서·송금·발송 · 알림 템플릿 | 급여 운영 |
auditor | 전 자원 조회 + 감사 로그 | 감사 — 수정 권한 없음 |
owner 최소 2명 규칙은 계정 하나가 사라져 조직 전체가 잠기는 것을 막는 안전망이다.
사람이 아닌 행위자
cron·이벤트 리스너 같은 자동화 주체는 사람 역할을 빌리지 않는다. 별도 테이블에 필요한 권한만 명시적으로 부여받고, 명시 부여가 없으면 거부된다.
AI 에이전트 프로필
AI 에이전트도 사람 역할을 빌리지 않는다. ai_agent 는 이름이 붙은 최소권한 묶음 하나 — 프로필 — 만 갖는다. 프로필은 코드에 선언돼 있다. INSERT 한 줄로 조용히 넓힐 수 없는 것이 요점이다. 어느 프로필을 쓰는지는 법인마다 그 법인의 직원 행이 정한다 — 토큰 하나가 여러 법인을 스코프에 담아도 프로필은 법인별로 따로 배정돼야 한다. 누가 어느 프로필로 발급됐는지는 임직원 행의 ai_agent_profile 컬럼에 남는다.
| 프로필 | 조회 | 쓰기 |
|---|---|---|
ai_operator | 임직원 · 조직 · 직무 · 휴가 · 휴가 정책 · 알림 템플릿 · 성과 근거 · 성과 문답 | 성과 근거 · 성과 문답 둘뿐 |
ai_cfo | 임직원 · 조직 · 급여 명세서 · 발송·송금 대장 · 휴가 · 알림 템플릿 | 없음 — 조회 전용 |
ai_operator 는 조직·휴가·템플릿을 읽어 질문에 답하고 성과 문답 루프만 기록한다. 채용·인사 필드·조직 개편은 인사 담당자의 일이라 임직원 레코드는 읽기 전용이다. ai_cfo 는 급여 결과를 읽어 대사·보고만 한다. 산정·확정·송금 명세·발송이라는 비가역 경로에 에이전트를 들이지 않는 것이 이 프로필의 핵심이라 쓰기가 하나도 없다.
어느 프로필도 approve · close · reopen · configure · grant · audit 를 갖지 않는다. 승인·종료·재개·설정·권한 부여는 사람의 결정이고 감사는 사람의 기록이다. 평가 확정의 인간 게이트가 하는 말을 권한 층이 한 단계 앞에서 똑같이 하는 셈이다.
인가에 쓰이는 프로필은 요청한 법인의 employees.ai_agent_profile 행에서 읽는다 — 토큰의 extra.ai_agent_profile 클레임(extra.actor_kind 옆자리)은 비교·로그용일 뿐이고 어긋나면 행이 이긴다. 토큰 스코프에 그 법인이 없거나, 그 법인에 actor_kind='ai_agent'·status='active' 인 행이 없으면 권한 0줄이다. 프로필이 없거나 이 빌드가 모르는 이름이면 권한이 0줄이다. 역할도, 레거시 등급 폴백도 없다. 행위자 종류 자체도 토큰이 아니라 임직원 원장이 정한다 — 요청한 법인의 직원 행 actor_kind 가 ai_agent 면 토큰이 그 클레임을 담았든 빠뜨렸든 프로필 번들로만 인가되고(레거시 최고 등급이라도 여기서 아무것도 사지 못한다, 판정 출처 ai_agent_profile), 그 행이 더 이상 살아 있지 않으면(비활성이거나 소프트 삭제) 번들을 잃는 데서 그치지 않고 사람 경로로도 내려가지 않은 채 곧장 거부된다(판정 출처 ai_agent_inactive) — 원장은 종류를 상태로 걸러내지 않고 상태와 함께 읽으므로 퇴사한 에이전트도 여전히 에이전트다. 반대로 토큰만 ai_agent 를 주장하는데 원장 행이 그렇지 않거나 아예 없으면 사람 경로로 내려가지 않고 곧장 거부된다(판정 출처 actor_kind_mismatch). 프로필은 인가 때마다 원장에서 새로 읽는다 — 캐시가 없으므로 퇴사·비활성 처리는 다음 호출부터 즉시 반영된다.
행이 사라져도 종류는 남는다 — 각 법인의 직원 표에 걸린 트리거가 에이전트 주체(소문자 이메일)를 shared.agent_subject 에 등록하고, 이름이 바뀌거나 종류가 바뀌거나 행이 지워지면 지우는 대신 툼스톤을 남긴다. 그 레지스트리에 한 번이라도 오른 주체는 요청한 법인에 자기 행이 없으면 사람 경로로 내려가지 않고 곧장 거부된다(판정 출처 ai_agent_unmapped · ai_agent_tombstoned). 레거시 스코프는 신원이 아니라 직원 행 위의 덧칠이다 — 요청한 법인에 활성 직원 행이 있어야만 적용되고(없으면 legacy_requires_ledger_row), 유일한 예외는 아직 직원이 0명인 법인의 최초 채용(경고로 기록되는 부트스트랩 예외)이다. 그 법인의 제 행이 경로를 정한다 — 레지스트리 적중이 살아 있는 지역 행을 뒤집지 못하고 충돌은 경고로만 남지만, 레지스트리가 한 번이라도 에이전트로 본 주체는 그 법인에 사람 행이 있어도 레거시 덧칠을 받지 못한다(legacy_refused_agent_subject) — 발급 시 admin 으로 실린 스코프가 재분류나 동명 고용으로 되살아나지 않게 하려는 것이고, 그 법인이 직접 부여한 롤은 그대로 유효하다. 같은 주체에 대소문자만 다른 행이 둘이면 어느 쪽도 고르지 않고 거부한다(ledger_ambiguous) — 직원 이메일은 법인 안에서 대소문자를 접어 유일해야 하며, 마이그레이션이 충돌을 발견하면 주소를 나열하며 멈춘다.
데이터베이스 제약 두 개가 이 표를 지킨다. 알려진 프로필 이름만 허용하고(ck_employee_ai_agent_profile), 프로필은 actor_kind 가 ai_agent 인 행에만 붙는다(ck_employee_ai_agent_profile_actor_kind). 두 번째 제약은 사람 행에 프로필이 붙어 조용히 에이전트 번들을 얻는 경로를 막는다. 조직 스키마 마이그레이션 0015_entity 가 컬럼과 두 제약을 함께 올린다.
프로필은 자신을 재원할 수 있는 보상 정책 종류(api_budget · compute_subscription)도 함께 지정한다. AI 에이전트는 급여·주식보상 위에 올라가지 않는다는 보상 3단 모델의 규칙을 권한 쪽에서 되풀이한 것이고, 실제로 막히는 지점은 개인 약정 부여(compensation_award_create)다.
아직 actor_kind 를 ai_agent 로 발급하는 경로는 없다. 먼저 잠그고 나중에 여는 순서다. (hive 다음 ship, 2026-09)
사람 · AI · 계약직을 한 테이블에서
임직원 테이블은 행위자 종류(actor_kind)를 들고 있다. 도메인 규칙은 이 값으로 갈린다.
| 종류 | 적용되는 것 |
|---|---|
human | 휴가, 4대보험, 근로기준법 §60, 급여·주식보상, 민감정보 컬럼 |
ai_agent | API 예산 · 컴퓨트 구독, 성과 평가, 감사 통합 (근로법 프레임 미적용) |
service_account | 자동화 주체 — 감사 기록만 |
contractor_external | 외부 계약직 — 수수료·프로젝트 피, 민감정보 없음 |
조직도·감사 로그·접근 제어는 종류와 무관하게 공통이다. AI 에이전트를 인사 시스템에 올리되 인간 노동법 프레임을 그대로 이식하지 않는 것이 여기서의 원칙이다.
연차 — 근로기준법 §60
연차 정책을 만들 때 규칙을 비우면 법정 기준이 적용된다.
rules:
first_year_monthly: { grant_per_month: 1, max_first_year: 11 }
after_first_year: { base: 15, bonus_every_n_years: 2, max: 25 }
expiry: { kind: "1_year_from_grant" }
carry_over: { allowed: false, payout_on_expiry: true }회사별 변형은 같은 구조에 값만 바꾼다 — 코드를 고치지 않는다.
성과 평가 — AI 는 초안까지, 확정은 사람
Hive 는 평가 점수를 스스로 매기지 않는다. 초안은 외부 평가 에이전트가 만들고, Hive 는 그것을 기록하고 검증한다. 이 경계는 규범이 아니라 구조다.
- 인간 확정 게이트 — 평가가
confirmed상태가 되려면 확정자(사람)가 채워져 있어야 한다는 제약이 데이터베이스에 걸려 있다. 사람의 확정 없이는 어떤 등급도 보상 단계로 넘어가지 못한다. 자동인 것은 근거 수집·초안 서술·인용·재현성 로그·설명팩 조립까지고, 사람인 것은 개시·확정·종료·이의 재검토다. 그래서 개인정보보호법 제37조의2 가 말하는 “완전히 자동화된 결정” 에 해당하지 않는다. - 인용 없는 등급 금지 — 각 차원의 등급은 근거 인용 없이 부여할 수 없고, 인용한 문장이 실제 원문에 있는지 서버가 대조한다(있지도 않은 문장을 인용하는 것을 차단).
- 근거 동결 — 사이클 시작 시 인용 가능한 근거의 마감선을 못 박는다. 평가 직전에 근거를 몰아 만드는 패턴이 구조적으로 불가능하다.
- 채점 기준 잠금 — 루브릭은 버전과 지문(해시)으로 잠긴다. 나중에 기준을 바꿔도 과거 평가는 그때의 기준으로 재현된다.
- 이의제기 — 정정 요구 · 설명 요구 · 자동결정 거부 세 갈래가 모두 경로로 존재한다.
review_explanation_pack은 설명 요구에 답할 자료(인용 원문 · 적용 기준 · 사람의 개입 내역 · 이의 이력 · 재현성 지표)를 한 번에 조립한다. - 평가 대상이 AI 인 경우 — 다른 계열 모델로 구성한 교차 심사를 강제한다(모델이 자기 계열을 후하게 보는 편향 방어). 감정·태도·인성 차원은 배제한다.
- 보상과의 결합은 참조까지 — 확정 등급은 보상 약정에 참조로만 기록된다. 등급에서 금액이 자동으로 튀어나오는 산식은 없다.
보상 — 3단 모델
보상의 종류·시점·산정 방식·통화·세무·성과연동·거버넌스가 제각각이라, 하나의 급여 테이블로는 표현되지 않는다. 그래서 세 층으로 나눈다.
compensation_plans 회사 정책 (예: 2026 기본급 체계, 2025 스톡옵션 플랜)
│ 1:N
compensation_awards 개인 약정 (누구에게, 얼마를, 언제부터 언제까지)
│ 1:N
compensation_events 실제 이벤트 (월 지급, 베스팅 tick, 행사, API 과금)- 정책이 바뀌어도 이미 부여된 약정은 당시 조건 그대로 얼어 있다.
- 산정 방식은 고정액 · 수식 · 구간별 · 풀 분배 · 재량 다섯 가지를 조합한다.
- 베스팅 스케줄(4년 클리프 · 월 단위 · 마일스톤 · 가속)은 스키마 변경 없이 표현된다.
- 임원 보상은 이사회 결의와의 연결을 요구하도록 걸 수 있다.
- 급여 확정 이벤트는 회계 서비스(Frame)로 신호가 나가 인건비 분개가 자동으로 만들어진다.
보상 종류는 급여·상여·수당·초과근로·주식보상·퇴직금·복리후생·실비정산·이익배분·리텐션·이전지원 외에 AI 에이전트용 API 예산 · 컴퓨트 구독을 1급으로 포함한다.
스키마 공개 — GET /hive/schemas
Hive 가 생산하는 사실(fact)의 타입 규격을 엔드포인트로 노출한다. 소비하는 쪽이 필드 이름을 추측하지 않게 하기 위한 것이다.
curl -H "Authorization: Bearer $TOKEN" \
https://<workspace>.axelabs.ai/hive/schemas응답 봉투는 { version, service, schemas, citation_arg_contract } 형태다.
schemas— 임직원 · 온보딩 · 조직도 · 직무 · 휴가(잔여/캘린더/신청/정책/사용집계) · 급여(기간/명세서/송금/발송로그/디스패치로그) · 알림 템플릿 · 감사 로그. 버전은 스키마 id 의@버전접미사로 구분한다.citation_arg_contract— “이 사실을 다시 조회하려면 어느 도구의 어느 인자에 무엇을 넣어야 하는가” 의 매핑. 인용 필드 이름과 도구 인자 이름이 항상 같지는 않아서(예: 명세서의 기간 식별자, 조직도의 루트 지정), 그 변환을 규격으로 못 박았다. 결과 payload 전용 필드는 인자로 넘기면 안 되는 것으로 따로 표시된다.
이 계약은 라이브 서버의 함수 시그니처에 고정돼 있고, 어긋나면 테스트가 깨진다.
MCP 도구 80개
https://<workspace>.axelabs.ai/hive/mcp 를 MCP 커넥터로 등록하면 아래 도구가 그대로 잡힌다. AXE CLI 를 쓰면 axe hive tools / axe hive call <도구> 로 같은 것을 호출한다.
조직 (9)
| 도구 | 설명 |
|---|---|
employee_get | 단일 임직원 조회 (id · 이메일 · 사번) |
employee_search | 이름·이메일·사번 부분일치 + 상태·조직 필터 |
employee_create | 신규 등록 + 초기 발령 |
employee_update | 필드 수정 (본인 가능 필드와 인사 전용 필드 분리) |
org_get_chart | 조직 트리 — 부분 트리 · 특정 시점 조회 |
org_get_team | 조직 + 소속 인원 (하위 조직 포함 옵션) |
org_create | 조직 신설 |
org_move_employee | 인사 발령 (소속 변경 + 이력 기록) |
org_set_manager | 조직장 지정·해제 |
직무 · 온보딩 (5)
| 도구 | 설명 |
|---|---|
position_list · position_create | 직무·직급·직책 조회 / 신설 |
employee_onboarding_status | 본인(또는 관리자가 타인)의 미입력 필수 항목 |
employee_complete_onboarding | 본인 민감정보 입력 — 일부만 나눠 입력 가능 |
employee_verify_document | 통장 사본·신분증 대조 검증. 명의 불일치는 거부 |
휴가 (9)
| 도구 | 설명 |
|---|---|
leave_request | 신청 — 중복·잔여 자동 검증 |
leave_approve | 승인·반려 (다단계 결재) |
leave_cancel | 취소 (신청 철회 / 승인분 취소) |
leave_get_balance | 잔여 (부여·사용·대기·잔여) |
leave_get_calendar | 팀 캘린더 — 겹치는 신청 표시 |
leave_get_request | 단일 신청 상세 |
leave_policy_create · leave_policy_list | 정책 등록 / 목록 |
leave_usage_report | 기간별 사용 집계 |
급여 · 보상 (19)
| 도구 | 설명 |
|---|---|
payroll_compute_period | 월 급여 산정 — 전 재직자 대상 전체 재실행. 산정 전 KR 7-category 세율 커버리지 프리플라이트: 빠진 category 는 전부 한 번에 TAX_RATE_NOT_FOUND 로 나열, 작년 행이 올해를 덮고 있으면 TAX_RATE_STALE 로 차단 (allow_stale_rates=True 는 해당 연도 요율 무변경을 운영자가 확인했을 때만) |
payroll_get_my_payslip | 본인 명세서 |
payroll_get_payslip · payroll_list_payslips | 타인 명세서 / 기간별 목록 (목록은 부작용 없는 조회 전용) |
payroll_payslip_void | 확정 전 명세서 무효화 |
payroll_termination_settlement | 퇴사 정산 — 미사용 연차 수당 · 보험 정산 · 연말정산 소급 |
payroll_export_remittance | 일괄 송금 명세 내보내기 |
payroll_mark_paid | 송금 완료 처리 → 회계 자동 분개 신호 발행 |
payroll_send_payslip · payroll_send_all_payslips | 명세서 메일 발송 (단건 / 일괄, 미리보기 모드 지원) |
payroll_preview_payslip_pdf | PDF 미리보기 (발송 없음) |
payroll_list_send_log · payroll_list_dispatch_log | 발송 감사 / 송금 스냅샷 조회 |
compensation_plan_list · compensation_award_create | 보상 정책 목록 / 개인 약정 부여 |
compensation_event_adjustment_create · _list · _update · _delete | 명세서 사후 정정 라인 (soft-delete) |
알림 템플릿 (7)
notify_template_list · notify_template_get_active · notify_template_create · notify_template_update · notify_template_clone_from_shared · notify_template_preview · notify_template_activate
메일 문안은 공통 기본값과 조직별 재정의로 나뉜다. 활성 템플릿은 수정 불가(불변) 이고, 고치려면 새 초안을 만들어 활성화한다.
성과 근거 (12)
| 도구 | 설명 |
|---|---|
performance_source_ingest | 성과 원문 1건 + 원자 기여 N건을 한 트랜잭션으로 적재. 원문은 불멸 보존, 정정은 새 버전으로 |
performance_get_contributions | 임직원·기간별 기여 조회 — 평가 에이전트의 1급 진입점 |
performance_get_source | 원문 재조회 (정정 이력 추적 포함) |
performance_list_sources | 어떤 근거가 존재하는지 메타만 스캔 |
performance_inquiry_create · _list | 확인 질의 생성 / 목록 |
performance_inquiry_send | 구성원별 1통 메일 발송 (미리보기 모드 · 중복 발송 방지 대장) |
performance_inquiry_remind | D+3 리마인더 — 회신 없이 목표(3일)를 초과한 발송분을 원 수신자에게 같은 회신 토큰으로 재발송(72시간 정각은 아직 목표 안이라 리포트의 sla_breach 도, 재알림도 켜지지 않고 — 초과한 순간부터 둘이 같이 켜진다). 하루 1통은 발송 전에 행을 원자적으로 클레임해서 보장한다: attributes 에 last_reminded_on·reminder_state=pending 을 조건부 병합(jsonb ` |
performance_inquiry_record_reply | 회신을 근거로 환류 |
performance_inquiry_poll_replies | 회신 자동 수집 — 발신자가 원 수신자와 일치할 때만 기록 |
performance_inquiry_cancel | 구버전·중복 질의 철회 — 사유 필수. 회신이 온 질의는 거부한다(회신은 이미 근거다 — 정정은 원문 대체 경로). 이미 철회된 id 는 원 사유를 지킨 채 보고만 하고, 없는 id 는 오류다. 행에 철회 시각·행위자·사유·직전 상태가 남고 감사 로그에도 같이 남는다. 메일은 나가지 않는다. 문답 자원(hive.performance.inquiry) 쓰기 (hive 다음 ship, 2026-09) |
performance_inquiry_list_send_log | 발송 대장 조회 |
응답지연은 별도 리포트 도구가 아니라 performance_inquiry_list 에 얹혀 있다 (hive 다음 ship, 2026-09). 응답에 조회 기준시각(as_of)과 집계 블록(sla — 목표 시간 · 대기 건수 · 대기 중 목표 초과 · 최장 대기 시간 · 회신 건수 · 회신 중 목표 초과 · 평균 회신 시간)이 함께 오고, 행마다 경과 시간(age_hours)과 그 기준(age_basis — 회신 완료면 확정 지연, 대기 중이면 진행 지연, 둘 다 아니면 없음) · 목표 시간 · 초과 여부가 붙는다. 미발송(draft) · 철회(cancelled) · 만료(expired)는 지연 개념이 없어 집계에서 빠진다 — 구버전 질의가 sent 로 남아 대기시간을 무한정 부풀리던 왜곡이 performance_inquiry_cancel 로 걷힌다.
리마인더는 발송 대장에 쓰지 않는다. 그 대장은 실제 원 발송의 중복 방지 키라서, 재알림이 거기 섞이면 다음 발송이 “이미 보냈다” 로 오판해 진짜 질의가 안 나간다. 재알림의 멱등은 질의 행 attributes 의 하루 단위 클레임 표식(last_reminded_on · reminder_state · reminder_claimed_at · reminder_claim_token · reminder_count)이 따로 맡고, 철회(performance_inquiry_cancel)도 같은 attributes 를 병합으로만 써서 서로의 키를 지우지 않는다. 클레임을 커밋한 직후 발송 전에 죽은 행은 최대 30분 pending 으로 머문 뒤 회수되지만, 발송을 시작한 뒤(sending)의 행은 회수 대상이 아니라 화해 대상이다. 실제 발송은 그 행들을 잠근 채(SELECT … FOR UPDATE) 한 트랜잭션 안에서 돈다 — 잠금 아래에서 자격을 다시 검증하고, 살아남은 행으로만 본문을 짓고, 보내고, 결과를 같은 트랜잭션에 적는다. 그래서 발송 중에 들어온 회신·철회는 그 잠금 앞에서 기다렸다가 발송이 끝난 뒤 그대로 성립하고, 잠금보다 먼저 커밋된 회신·철회의 질의는 메일에 아예 실리지 않는다(superseded_ids) — 닫힌 질의로 메일이 나가는 창도, 그것을 사후에 적던 고아 발송 기록도 없다. 표식 쓰기만 실패해 트랜잭션이 롤백된 행은 sending 에 남아 재발송되지 않고 화해 대상이 된다. 화해는 사람이 한다: 발신함에서 그 회신 토큰이 든 제목의 메일이 실제로 나갔는지 확인하고, 안 나갔으면 그 행의 reminder_state 를 failed 로 되돌려 다음 호출의 대상으로 만든다(자동 화해 도구는 아직 없다).
평가 사이클 (15)
| 도구 | 설명 |
|---|---|
review_rubric_lock · review_rubric_get | 채점 기준 잠금 / 조회 |
review_cycle_open | 사이클 개시 |
review_cycle_freeze_evidence | 근거 마감선 동결 |
review_cycle_list | 사이클 목록 |
review_cycle_close · review_cycle_reopen | 종료 / 재개 (권한이 서로 분리돼 있다) |
evaluation_draft | 외부 평가 에이전트의 초안을 기록 + 검증 (AI 는 여기까지) |
evaluation_get | 상세 + 인용 원문 링크 (피평가자 본인 열람 가능) |
evaluation_confirm | 사람의 확정 — 이 호출 없이는 어떤 등급도 보상으로 가지 않는다 |
evaluation_supersede | 재판정 새 버전 (원본은 남는다) |
review_appeal_file · review_appeal_resolve · review_appeal_list | 이의제기 접수 / 재검토 / 목록 |
review_explanation_pack | 설명 요구권 대응 자료 자동 조립 |
권한 · 감사 (4)
role_assign · role_revoke · role_list · audit_log_list
개발 — 데이터베이스를 건드리는 테스트에는 기본값이 없다
Hive 테스트는 대부분 데이터베이스 없이 돈다. 실제로 쓰는 것은 세 모듈뿐이다 — 공용 스키마를 지우고 다시 만드는 권한 테스트, 자기 데이터베이스를 만들었다 지우는 급여 정정 테스트, 특정 월 급여를 지우고 다시 계산하는 골든 테스트. 이 셋은 기본 접속 주소를 갖지 않는다. 예전에는 기본값이 있었고 그 값이 하필 라이브 클러스터였다. 환경변수를 깜빡한 것만으로 파괴적 테스트가 운영 데이터를 향했다는 뜻이다.
지금의 계약은 세 줄이다.
HIVE_ALLOW_DB_MUTATING_TESTS=1— 의도적 옵트인.- 접속 주소를 환경변수로 명시 — 권한·정정 테스트는
HIVE_RBAC_TEST_DB_URL과HIVE_TEST_ADMIN_URL, 골든 테스트는HIVE_DB_*. - 그 주소가 라이브 클러스터가 아닐 것.
1·2 가 없으면 무엇을 설정해야 하는지 알려주고 건너뛴다. 데이터베이스가 없는 환경에서도 나머지 스위트는 그대로 초록이다. 3 을 어기면 조용히 넘어가지 않고 import 시점에 LiveClusterRefusedError 로 큰 소리로 실패한다 — 접속은 시도조차 하지 않는다. 파괴적 스위트를 라이브에 겨눈 설정은 “데이터베이스가 없음” 이 아니라 사고이므로, 그 둘의 반응이 같아서는 안 된다.
라이브 판정은 라이브 hive-postgres 가 공개하는 그 포트이거나, 호스트 이름을 실제로 해석해 그 엔드포인트에 닿는 경우다. loopback 별칭으로 우회되지 않는다.
일회용 클러스터를 띄우는 한 줄(볼륨 없는 postgres:16-alpine 을 호스트 loopback 의 임시 포트에 묶고 일회용 자격증명을 주는 docker run)은 tests/db_guard.py 의 docstring 에 붙어 있다 — 이 공개 페이지에는 주소·자격증명 리터럴을 싣지 않는다.
볼륨을 붙이지 않았으므로 docker rm -f 가 데이터까지 가져간다. 골든 테스트만은 빈 클러스터로 되지 않는다 — 실제 급여 데이터를 대조하므로 복원한 사본이 필요하고, 라이브는 이 게이트가 애초에 거부한다.
거부·건너뛰기 문구는 host:port/dbname 만 찍는다. 그 문구가 CI 로그에 그대로 남기 때문에 사용자명과 비밀번호는 어떤 경로로도 출력되지 않는다. (hive 다음 ship, 2026-09)
다른 서비스와의 관계
- Frame (회계) — 급여가 확정되면 신호가 나가 인건비 분개가 자동으로 만들어진다. 법인 기본 정보의 정본은 Frame 이고 Hive 는 사본만 갖는다. 반대로 인원 명부의 정본은 Hive 다.
- Blueprint (워크스페이스) — 입·퇴사 결정이 계정 생성·정지로 이어진다. 역방향은 없다 — 워크스페이스에 계정이 생겼다고 임직원으로 자동 등록되지 않는다.
- AXE CLI —
axe hive tools·axe hive call <도구>로 커넥터 없이 같은 도구를 호출한다.
서비스 사이의 데이터 이동은 명시적인 신호 채널로만 한다. 남의 데이터베이스를 직접 읽는 경로는 없다.