<!-- canonical: https://docs.axelabs.ai/services/hive -->
<!-- source: content/services/hive.mdx -->

---
title: Hive
description: HR backend — 조직 · 휴가 · 성과 · 보상. Postgres schema-per-entity + bitemporal employment_records + ltree 조직 트리 + JSONB 커스텀 필드 + append-only audit. Mirrors frame architecture.
---

# Hive

**한 줄 소개**: 통합 HR MCP 서버. PostgreSQL schema-per-entity 격리, bitemporal employment_records, ltree 조직 트리, JSONB 커스텀 필드, append-only audit log. Blueprint 에이전트와 직원/매니저가 MCP 도구로 접근.

## 기술 스택

| 항목 | 값 |
|---|---|
| 언어 | Python 3.12+ |
| 프레임워크 | FastMCP (stdio + HTTP/SSE) |
| DB | PostgreSQL 16 (with `ltree`, `pgcrypto`) |
| ORM | SQLAlchemy 2.0 Core + psycopg v3 |
| Migration | Alembic dual-env (shared + entity) |
| 인증 | PyJWT (HS256 hive JWT + RS256 Microsoft JWKS) |
| LLM | Anthropic Claude (OKR 추천, 평가 초안, 이직 리스크) |
| HTTP server | Starlette + uvicorn |

## 포트

| 포트 | 용도 |
|---|---|
| 3800 | PostgreSQL 16 (hive-postgres) |
| 3810 | MCP HTTP blue (active) |
| 3811 | MCP HTTP green (passive) |
| 3812 | axe-hive-proxy (Caddy, blue/green selector) |

## URL 컨벤션

```
{customer}.axelabs.ai/hive               ← HTTP root (path-based mount)
{customer}.axelabs.ai/hive/mcp           ← MCP endpoint (canonical resource URI)
{customer}.axelabs.ai/hive/health/ready  ← readiness
{customer}.axelabs.ai/hive/.well-known/oauth-protected-resource
```

axe.axelabs.ai/hive/mcp 가 현재 axe customer 의 endpoint. realchoice 는 onboarding 시 활성.

## 5 가지 도메인

| 영역 | 상태 | Phase |
|---|---|---|
| 조직 관리 (org tree, employees, positions, employment_records) | live | 1 |
| 휴가 관리 (근로기준법 §60 연차 자동) | live | 1 |
| KPI / OKR | 미구현 | 2 |
| 성과 평가 (360도, AI 보조) | 미구현 | 2 |
| 급여 · 인센티브 (스톡옵션 포함) | 미구현 | 3 |

## MCP Tools (Phase 1, 19개)

**조회**
- `employee_get` — 단일 직원 (id / email / employee_no)
- `employee_search` — 이름·이메일·번호 부분일치 + status/org 필터
- `org_get_chart` — ltree 서브트리 (or 전체) + as_of_date 시점 query
- `org_get_team` — 조직 + 멤버 (line memberships, include_sub_orgs 옵션)
- `position_list` — 직무/직급 목록
- `leave_get_balance` — 직원 휴가 잔여 (근로기준법 자동 적용)
- `leave_get_calendar` — 팀 휴가 캘린더 (overlap 감지)
- `leave_get_request` — 단일 요청 detail
- `leave_policy_list` — 휴가 정책 목록
- `leave_usage_report` — 기간별 사용 집계

**변경**
- `leave_request` — 휴가 신청 (충돌 + 잔여 자동 검증)
- `leave_approve` — 승인/반려 (다단계 결재 지원)
- `leave_cancel` — 취소 (start_date 전)
- `leave_policy_create` — 정책 등록

**관리자**
- `employee_create` — 신규 입사 + 초기 발령 (optional). `payroll_start_date` 옵셔널 — None 이면 `hire_date` fallback (D-hive-27).
- `employee_update` — patch (self vs HR 필드 분리). `payroll_start_date` HR 필드.
- `compensation_plan_list(plan_kind?)` — entity 의 active compensation_plan 목록 조회 (read scope, D-hive-30 M 트랙). `compensation_award_create` 호출 전 `plan_code` 확보용.
- `compensation_award_create(employee_no, plan_code, annual_amount_krw, effective_from, effective_until?, award_code?)` — 직원에게 compensation_award 부여 (D-hive-17 후행 leg, D-hive-30). employee/plan 존재 + active overlap 검증. overlap 시 `COMPENSATION_AWARD_CONFLICT` raise. `payroll_compute_period` 가 active salary award 없으면 skip → 본 도구가 페이슬립 산정의 선행.
- `org_create` — 조직 신설 (ltree path 자동)
- `org_move_employee` — 인사 발령 (membership + bitemporal record)
- `position_create` — 직무/직급 신설

### Schema discovery — `GET /hive/schemas`

Blueprint 의 artifact + PARA 지식 레이어 ([D-bp-artifact-1](/ops/decisions)) 가 fact 의 typed schema 권위를 MCP 에 위임. hive 는 frame 패턴 ([Frame Schema discovery](/services/frame#schema-discovery--get-frameschemas)) 1:1 mirror — 자기가 생산하는 HR/payroll fact kind 를 `/schemas` 엔드포인트로 노출.

```bash
curl -H "Authorization: Bearer $HIVE_MCP_TOKEN" \
  https://axe.axelabs.ai/hive/schemas
```

15 schemas 현재 노출 (employee / employee_onboarding / org_chart / position / leave_balance / leave_calendar / leave_request / leave_policy / leave_usage_report / payroll_period / payslip / payroll_remittance / payroll_send_log / payroll_dispatch_log / notify_template). 추가 schema 는 `src/hive/mcp/schemas.py` (SOT) 에 등재.

- 인증: required (frame 동일)
- versioning: schema_id 의 `@{version}` suffix
- 소스 권위: `src/hive/mcp/schemas.py` — 외부 schema registry 없음
- envelope 형식: frame 과 동일 (`{ version, service, schemas }`) — Blueprint 의 [B-bp-artifact-schema-discovery](/ops/backlog) 가 multi-MCP 통합 fetch

## 데이터 모델

### Shared schema

| 테이블 | 역할 |
|---|---|
| `entity` | 등록 entity 메타 (axec, axev, realchoice) |
| `custom_field_definitions` | 회사별 커스텀 필드 정의 (D-hive-6) |
| `idempotency_record` | 멱등 키 (24h TTL) |
| `event_outbox` | pg_notify 이벤트 outbox (D-hive-7) |

### Per-entity schema

| 테이블 | 역할 |
|---|---|
| `organizations` | 조직 (ltree path, GIST index) |
| `positions` | 직무·직급·직책 매트릭스 |
| `employees` | 임직원 마스터 (national_id/home_address/bank_account_no = PII 암호화). `hire_date` = 위임/근로계약 발효일 (frame.executive_officer.appointed_date 정합), `payroll_start_date` = 보수 개시일 (NULL → hire_date fallback, D-hive-27). |
| `organization_memberships` | 매트릭스 다중 소속 (line/dotted/project) |
| `employment_records` | bitemporal 발령 이력 (valid_from/to + recorded/superseded_at) |
| `leave_policies` | 휴가 정책 (basis + rules JSONB) |
| `leave_requests` | 신청 (approval_chain JSONB, 다단계 결재) |
| `leave_balances` | 잔여 캐시 (granted/used/pending/remaining) |
| `custom_field_violations` | custom field 검증 실패 audit |
| `audit_log` | 쓰기 추적 (trigger 자동) |
| `llm_call` | Anthropic API 호출 audit |

## 권한 모델 — Scope

```
read < write < approve < admin
```

- `read` — get/list/search
- `write` — leave.request (본인), employee.update (자기 일부)
- `approve` — leave.approve, employee.update(hr_fields=True), 성과 review.submit
- `admin` — employee.create, org 변경, policy 변경, payroll.run

권한은 **entity 별로** 부여 (예: `{"axec": ["read","write","approve"], "axev": ["read"]}`).

OIDC 인증 (Microsoft Entra) 기본 = `["read", "write", "approve"]`. **`admin`** 은 Blueprint `EntityRole` (`/api/internal/entity-roles`) 가 해당 (email, entity) 에 대해 `role='admin'` 또는 `'owner'` 반환할 때 **자동 부여** (D-bp-entity-17, 2026-05-22). `auth_oidc.py` 가 sign-in 마다 Blueprint 내부 API 조회 (HMAC bearer `BLUEPRINT_INTERNAL_API_KEY`, 5분 cache, fail-open — Blueprint 장애 시 default scope 유지하여 lockout 회피). HS256 service token 으로 admin 직접 발급은 break-glass / dev 용도로만.

## 한국 근로기준법 §60 연차

연차 leave_policy 생성 시 `rules=None` 이면 statutory 적용:

```yaml
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 }
```

회사별 변형은 동일 schema 에 JSONB override 만 변경 (코드 변경 X).

## OAuth-RP 흐름

frame 과 동일. customers.yaml `sso.apps.hive_mcp` 에서 client_id / application_id_uri 읽어 Microsoft Entra ID id_token RS256 검증.

```
Claude Code → POST /hive/mcp (no auth)
hive        → 401 + WWW-Authenticate: Bearer resource_metadata="..."
client      → GET /hive/.well-known/oauth-protected-resource
hive        → {resource: "https://axe.axelabs.ai/hive/mcp", authorization_servers: [Microsoft]}
client      → Microsoft OAuth flow (PKCE)
Microsoft   → access_token (aud = Application ID URI)
client      → POST /hive/mcp Authorization: Bearer <access_token>
hive        → JWKS RS256 verify + aud + iss + email extract → entity 매핑
hive        → MCP session 생성 → tool 호출
```

### Custom Connector 등록 (신규 사용자)

운영자: `axe secret send AZURE_HIVE_MCP_CLIENT_SECRET --service hive --to <local-part>` → URL 발급 → `/api/admin/broadcast-dm` 자동 Teams DM. 전체 흐름은 [/architecture/secrets § 사람에게 전달](/architecture/secrets#사람에게-전달--bitwarden-send). 수신자 화면 절차는 [/onboard/claude-frame-setup](/onboard/claude-frame-setup) (5분 가이드, MCP URL 만 `https://axe.axelabs.ai/hive/mcp` 로 치환).

## CLI

```bash
# DB
hive migrate                        # shared + 모든 entity
hive register-entity --id axec --legal-name "에이엑스이 코퍼레이션"
hive list-entities

# 토큰 발급
hive mcp-token --sub ai@axellc.com --customer axe \
    --entity axec:read,write,approve,admin \
    --entity axev:read,write,approve \
    --ttl 2592000

# 정책 시드
hive seed-policies --entity axec    # 연차/병가/경조사 자동 등록

# 서버
hive mcp-serve                      # stdio
hive mcp-serve-http                 # HTTP (uvicorn :3810)
hive mcp-health --port 3810
```

## 운영 노트

- **Blue/green deploy** 지원 (3810/3811 동시 실행, docker network alias swap via axe-hive-proxy)
- **Append-only**: audit_log trigger 가 모든 mutable table 의 변경 자동 기록
- **PII**: pgp_sym_encrypt + entity 별 passphrase (`HIVE_PII_PASSPHRASE_<ENTITY>`)
- **이벤트**: pg_notify(`hive_events`, ...) — frame 이 LISTEN 하여 payroll.finalized → 인건비 분개
- **환경변수 = env_file 단일 출처 (D-ops-18, 2026-05-21)**: `docker-compose.yml` 의 `environment:` 블록은 literal config 만 (host/port/log_level 등). 비밀 vars (`HIVE_DB_PASSWORD`, `HIVE_JWT_SECRET`, `AZURE_HIVE_MCP_CLIENT_SECRET`, `HIVE_PII_PASSPHRASE_*`, `HIVE_MAILER_*`, `AZURE_TENANT_ID`) 는 모두 env_file `.env.local` 에서 직접 dump → `axe secret pull hive` 가 vault 의 manifest 값으로 .env.local atomic write

## Actor model — `actor_kind` 통합 (D-hive-14)

`employees.actor_kind` 컬럼으로 human / AI agent / service account / external contractor 를 단일 테이블에 통합. HR 자동화 모듈은 `WHERE actor_kind = 'human'` 으로 도메인 적용 분기.

| actor_kind | 적용 |
|---|---|
| `human` | 휴가, 4대보험, 근기법 §60, salary/equity, PII 컬럼 |
| `ai_agent` | api_budget / compute_subscription, 성과 평가 (uptime 등), OKR, audit 통합 |
| `service_account` | 자동화 system 행위자 (audit only) |
| `contractor_external` | 외부 계약직 (commission/project-fee, PII 없음) |

audit_log / org chart / access control 은 모든 actor_kind 공통.

## 인원명단 SOT (D-hive-15)

**Hive = 인원명단 superset SOT**. 100명 회사가 되면 Blueprint/Frame 사용자는 부분집합 (소수 정예). 다중 entity 표현 (`person × entity × employment_kind` 3차원) 은 Hive 만 가능.

```
Microsoft Entra ID  ──(AAD anchor)──▶  Hive  ──(hive.employee.* events)──▶  Blueprint
   (identity SOT)                  (employment SOT)              (platform-access SOT)
```

- Hire 결정 → `hive.employee.hired` → Blueprint LISTEN → User row create + AAD link
- Term 결정 → `hive.employee.terminated` → Blueprint LISTEN → soft-delete + AAD disable
- 역방향 없음 (Blueprint User 가 만들어졌다고 Hive employee 자동 등록 X — silent leak 방지)

## AI agent dual-origin (D-hive-16)

AI agent 의 lifecycle 은 인간과 비대칭. **Blueprint Agent 배포가 시작점**, Hive 가 고용관계 매핑 확정.

```
Blueprint (Agent 배포) → blueprint.agent.created
  → Hive 운영자 prompt (어느 entity 비용 부담?)
  → hive employee-create --actor-kind ai_agent --blueprint-agent-id <id> --entity <axec|axev>
  → hive.employee.hired (actor_kind=ai_agent) → Blueprint LISTEN → user_entity_map + cost_center 확정
```

**SOT 책임 분리**:
- AI agent employment 관계 (entity, cost_center, position) — **Hive**
- AI agent runtime config (model, prompt, tools, secrets) — **Blueprint**
- Cross-link: `employees.attributes.blueprint_agent_id` (soft FK)

모델 업그레이드 (4.6 → 4.7) = 같은 employee_id 유지 + `employment_records.record_kind = 'position_change'` 로 bitemporal 기록.

## 보상 — 5-table 유연 모델 (D-hive-17)

7차원 다양성 (종류·시점·산정·통화·세무·성과연동·거버넌스) 을 컴포지션 가능한 5개 테이블로:

```
compensation_plans      회사 정책 (BASE_SALARY_2026, ESOP_2025, AI_COMPUTE_2026)
       │ 1:N
       ▼
compensation_awards     개인 약정 (한진우 salary 6.5M, ai@ api_budget $500/mo)
       │ 1:N
       ▼
compensation_events     실제 이벤트 (월별 지급, vesting tick, exercise, api charge)
       │ frame_journal_id back-write (pg_notify hive.payroll.event → frame 자동 분개)

payroll_periods         월별/주별 사이클
payslips                employee × period 명세서 (line_items JSONB)
```

**plan_kind (15+1)**: salary / bonus / commission / allowance / overtime / equity / severance / benefit / reimbursement / profit_sharing / retention / relocation / **api_budget / compute_subscription** / other

**amount_rule_kind**: fixed / formula (`expr` 평가) / tiered (구간별 비율) / pool (이익 풀 분배) / discretionary

**유연성 핵심**:
- Plan ↔ Award 분리 (정책 변경 시 plan 만, award 는 frozen snapshot)
- JSONB rules (모든 산정 방식 schema 변경 없이 확장)
- `vesting_schedule` JSONB (4-year cliff / monthly / milestone / acceleration 모두)
- `performance_link` (Phase 2 OKR/KPI 와 lazy coupling)
- `frame_journal_id` (cross-service trigger via pg_notify)
- `calculation_inputs` JSONB (재현 가능성)
- `requires_resolution` + `resolution_id` (임원 보상 frame.internal_resolution 강제 link)

Phase 1 = schema 만 (5 테이블 비어 있음). Phase 3 = MCP tools (`compensation.award.create`, `payroll.run_period` 등) + frame LISTEN handler.

## frame 와의 차이점

| 항목 | frame | hive |
|---|---|---|
| 도메인 | 회계 (KSME) | HR (조직/휴가/성과/보상) |
| 격리 단위 | schema-per-entity (axec, axev) | schema-per-entity (axec, axev) — 동일 |
| 시간 모델 | fiscal_period + audit | bitemporal employment_records + audit |
| Scope | read/write/close/reopen/admin | read/write/approve/admin |
| Append-only | journal_line, raw_transaction (trigger) | audit_log, llm_call (trigger). leave_requests 는 state flow |
| Custom fields | account_template (KSME standard) | custom_field_definitions (JSONB 자유 확장) |
| Actor 모델 | (single, operator/user) | actor_kind 4종 통합 (human/ai_agent/service_account/contractor_external) |
| 인원 SOT | (없음 — frame 은 executive_officer 만) | **인원명단 superset SOT** |
| 보상 모델 | journal_line 분개만 | compensation_plans/awards/events + payslips (Phase 3) |

## Frame 연동 — Canonical ownership (D-hive-13)

한 사실은 단 하나의 서비스가 canonical owner. Hive 는 운영 독립성을 위해 frame 의 entity 메타 일부만 캐시.

| 사실 | Canonical | Hive 보유 |
|---|---|---|
| 법인명·사업자번호·회계연도 | Frame | `shared.entity` (canonical column) |
| 대표자·사업장 주소·업종·영문명 | Frame | `shared.entity.attributes.frame.*` JSONB (cache) |
| 법인등기상 임원·주주 | Frame | **미보유**. 직원 겹칠 시 `employees.attributes.frame_links` 명시적 link |
| 자본구조 (자본금·주식수) | Frame | **참조 안 함** (HR 도메인 외) |
| 직원·조직·휴가·평가·급여 | **Hive** | (Frame 이 인건비 분개 시 Hive MCP 호출) |

**Bootstrap**:
```bash
hive register-entity --id axec --legal-name "액스코퍼레이션 주식회사"
hive sync-entity-meta --entity axec --from-frame   # 대표자/주소/업종 cache
```

**재동기 시점**: 등기 변경, 대표 교체 등 frame 측 변동 시 운영자가 위 명령 1회 실행. `shared.entity.frame_synced_at` = 마지막 watermark. 자동화 (frame → hive 역방향 pg_notify) 는 Phase 2 검토.

**금지**: MCP tool path 에서 frame Postgres 직접 read (D-hive-12). Frame 의 officer roster 를 직원으로 자동 import (다른 도메인).

## 외부 노출 (2026-05-21 라이브)

```
claude.ai → Cloudflare edge (axe.axelabs.ai)
         → axelabs-tunnel (d8efecdd-... cloudflared container)
            └─ ingress: path ^/hive(/.*)?$ → host.docker.internal:3812
         → axe-hive-proxy (Caddy on :3812)
            └─ reverse_proxy hive-mcp:3810 (docker network alias)
         → hive-mcp-blue (active) / hive-mcp-green (passive)
         → hive-postgres :3800
```

**자동화**:
- `/Users/axe/.axe/hive-proxy/` — compose dir (frame-proxy 패턴 mirror). `docker compose up -d` 1회 부팅 후 영구.
- `/Users/axe/.axe/tunnels/axelabs/config.yml` — `^/hive(/.*)?$` 규칙 1줄 추가됨. cloudflared restart 1회 (~5초 다운타임).
- DNS 추가 불필요 — `axe.axelabs.ai` 가 이미 apex (path-based routing D3).
- Blue/green swap = `axe deploy hive` 가 hive-mcp docker alias 이동 + `caddy reload` 실행. cloudflared 무관 (다운타임 0).

**검증된 외부 endpoints**:
- `https://axe.axelabs.ai/hive/health` → 200
- `https://axe.axelabs.ai/hive/health/ready` → DB ping 200
- `https://axe.axelabs.ai/hive/.well-known/oauth-protected-resource` → RFC 9728 metadata
- `https://axe.axelabs.ai/hive/mcp` → 401 + WWW-Authenticate (PKCE challenge)
- `https://axe.axelabs.ai/hive/metrics` → Prometheus exposition

## Self-service onboarding (D-hive-18)

PII (주민번호·주소·계좌·전화) 는 **직원 본인이 incremental 입력**. 개인정보보호법 정보주체 본인 입력 원칙 + 운영자 부담 분산.

**3개 MCP tools**:

| Tool | 기능 |
|---|---|
| `employee_onboarding_status` | 본인의 부족 PII 필드 list. `next_step` 안내. ai_agent 는 `status="n/a"` |
| `employee_complete_onboarding` | partial PII 입력. 형식 검증 + PIIString Fernet 자동 + `onboarding_log` audit |
| `employee_verify_document` | 통장 사본 / 신분증 PDF·이미지 → Anthropic vision → declared_fields 비교 → 일치 시 자동 저장. **예금주/소유자명 = employees.name 일치 critical 검증 (다른 사람 문서 차단)** |

**필수 필드 매트릭스** (`actor_kind × employment_type`):

| 조합 | 필수 |
|---|---|
| human × full_time / part_time | national_id, home_address, bank_account_no, phone |
| human × advisor / contractor / intern | national_id, bank_account_no |
| human × other (겸직 무급) | phone (본업 entity 가 canonical) |
| ai_agent / service_account | 모두 N/A |

`shared.entity.attributes.onboarding_requirements` 로 entity 별 override.

**Self 인증**: `token.sub == employees.email` 시 self 허용, 다른 사람 → admin scope 필수.

**문서 폐기 정책**: blob 메모리 즉시 폐기, audit_log 에 `content_sha256` + actor + extracted (마스킹) 만 잔존. PDF 영구 저장 X.

## 계좌번호 cross-service 정책 (D-hive-19)

직원 계좌번호 전체는 **Hive only canonical**. Frame 미보유.

| 위치 | 상태 |
|---|---|
| `employees.bank_account_no` DB | Fernet ciphertext (`enc::v1::...`) |
| `payroll.export_remittance` 실행 중 메모리 | 평문 < 1초 |
| MCP response in transit | TLS 안 base64 |
| 운영자 머신 다운로드 XLSX | 평문 — 24h 폐기 권고 |
| audit_log | `content_sha256` 만 |
| Frame container | **미보유** |

**Cross-service event** (`hive.payroll.finalized`) 페이로드 표준:

```json
{
  "employees_paid": [
    {
      "employee_id": 2,
      "employee_name": "한진우",
      "net_amount_krw": 3500000,
      "bank_last4": "9012",       /* 매칭 hint, 전체 X */
      "bank_name": "국민은행"
    }
  ]
}
```

Frame 가 회사 통장 raw_transaction 의 출금 row 와 last4 + 금액 + 일자 매칭 → 자동 인건비 분개.

## Payroll (D-hive-17/19/20) — Phase 3 live

5 도구 + 한국 세율 lifecycle + Frame 자동 분개:

| Tool | Scope | 기능 |
|---|---|---|
| `payroll_compute_period(period_label="2026-06")` | admin | **Full re-run, UPSERT-with-gates (D-hive-29)** — 전 active 직원 대상. 직원별 case ABCDE: A 없음→INSERT, B pending+send_log=0→UPDATE (산정 fields 덮어쓰기·운영 메타 보존), C pending+발송됨→skip, D paid→skip, E reversed→skip. summary 에 `action`/`skipped_reason` 포함. 임원=고용·산재 미적용. AI agent skipped. 사후 정정 (skip 케이스) 은 `compensation_event_adjustment_create` |
| `compensation_event_adjustment_create(employee_id, period_label, line_kind, delta_krw, reason)` | admin | **페이슬립 사후 정정 (D-hive-17/29 + 룰 H)** — **case C (pending + 발송됨) 만 허용**. case A→`PAYSLIP_NOT_FOUND` / B→`PAYSLIP_NOT_DISPATCHED` (compute_period 안내) / D→`PAYSLIP_ALREADY_PAID` (운영자 수동) / E→`PAYSLIP_REVERSED` raise. 정정 시: payslip line append + 합계 보정 + `compensation_events` audit row + `hive.payroll.event` (subtype=adjustment) 발행. `delta_krw` 부호 = line.amount_krw 부호 (공제 460원 증가 = -460). 직원 메일 ≠ DB → 운영자 재발송 책임 |
| `payroll_get_my_payslip(period_label)` | read (self) | 본인 명세서 (line_items 표시) |
| `payroll_get_payslip(period_label, employee_id)` | admin | 다른 직원 조회 |
| `payroll_export_remittance(period_id)` | admin | 일괄 송금 XLSX (KB 폼). 평문 계좌 24h 폐기 권고 |
| `payroll_mark_paid(period_id, payment_reference)` | admin | status=paid + hive.payroll.finalized event 발행 |

### 한국 세율 lifecycle (D-hive-20)

`shared.tax_rate_schedule` — effective_from/until + JSONB rates + source_url. 매년 1월 운영자:

```bash
hive.tax.update_rates(
  category="income_tax",
  effective_from="2027-01-01",
  rates={"brackets": [...]},
  source_url="https://www.nts.go.kr/...",
)
```

→ 기존 active row effective_until 자동 마감 + 신규 INSERT. payroll 호출 시 period_end 기준 시점별 lookup → 과거 재계산 정합성.

**산식 정확성 3-layer**:
- L1 — `tests/payroll/golden_kr_YEAR.json` (국세청 간이세액표 fixture, 자동 CI)
- L2 — 호수회계법인 정기 cross-check → 차이 시 `adjustment` event (append-only)
- L3 — 직원 self payslip 본인 검수 → dispute → 재계산

### Frame LISTEN handler (Tier 3)

frame 측 daemon `frame listen-hive-payroll`:
- hive-postgres `LISTEN hive_events` (host.docker.internal:3800)
- `hive.payroll.finalized` 받으면 자동 분개 `Dr 5101 / Cr 1101` per employee
- idempotency = SHA256(period + employee + payment_reference)
- backfill on startup (event_outbox unconsumed)
- ack via `consumed_by` JSONB

### 산정 예시 (axev 2026-05, payroll v2 — 셀별 정확 일치 검증 완료)

| 직원 | 직급 | 지급합계 | 과세표준 | 공제합계 | 실수령 |
|---|---|---|---|---|---|
| 강태훈 | 대표 (임원) | 4,166,667 | 3,966,667 | 559,390 | **3,607,277** |
| 한진우 | 수석 | 4,166,667 | 3,966,667 | 595,090 | **3,571,577** |
| 강수훈 | 파트너 (4/1 퇴사) | — | — | — | **24,660** (건보 환급) |

산식 (D-hive-21): 식대 200K 비과세 → 과세 3,966,667 / 4대보험 보수월액 별도 신고치 (연금 4,186,222, 건보 4,022,567) / 소득세 = 국세청 2026 간이세액표 lookup / 임원 고용·산재 0.

## Payroll v2 — 한국 도메인 본질 (D-hive-21)

기존 v1 의 단순 누진세 계산은 한국 표준 아님. v2 는 7가지 본질 반영:

1. **비과세 분리** — 식대 / 차량보조금 월 200K 한도
2. **간이세액표 lookup** — `shared.simplified_income_tax_table` (월 과세 × 부양가족수 → 세액). 누진세 직접 계산 X
3. **부양가족·자녀세액공제** — `employees.dependents_count` + `children_count_under_20`
4. **4대보험 보수월액 분리** — `reported_income_pension_krw` / `reported_income_health_krw` (각자 신고소득)
5. **정산** — `health_reconciliation_krw` / `yearend_reconciliation_krw` (음수=환급, 양수=추가). `payroll_periods.attributes.pending_reconciliations` 사전 등록
6. **퇴사자 처리** — `termination_date` 일할 또는 정산-only payslip
7. **라운딩** — 국민·건강·장기 = 10원 단위 절상, 고용·소득세 = 원 단위 반올림

검증: `tests/payroll/golden_axec_2026_04.json` — 첨부 급여대장과 모든 셀 정확 일치.

## 급여명세서 메일 발송 (D-hive-22/23) — Phase 3.6

### 채널: Microsoft Graph SendMail

M365 SMTP AUTH 의 deprecation (사용자별 app password 필요) 회피. Azure AD App + `Mail.Send` Application permission + client_credentials → access token → `POST /v1.0/users/{from}/sendMail`. DKIM 자동 서명.

| 환경변수 | 용도 |
|---|---|
| `AZURE_TENANT_ID` | M365 tenant GUID |
| `HIVE_MAILER_CLIENT_ID` | `hive-payroll-mailer` AppId |
| `HIVE_MAILER_CLIENT_SECRET` | client secret (vault) |
| `HIVE_MAILER_FROM` | 발송 mailbox (예: `ai@axellc.com`) |
| `HIVE_MAILER_FROM_NAME` | display name |

### DKIM / DMARC DNS (D-hive-23)

axellc.com Cloudflare zone 에 추가:

```
selector1._domainkey.axellc.com  CNAME  selector1-axellc-com._domainkey.<tenant>.n-v1.dkim.mail.microsoft
selector2._domainkey.axellc.com  CNAME  selector2-axellc-com._domainkey.<tenant>.n-v1.dkim.mail.microsoft
_dmarc.axellc.com                TXT    "v=DMARC1; p=none; rua=mailto:ai@axellc.com; aspf=s; adkim=s"
```

⚠ Microsoft 가 2024년부터 DKIM 도메인을 `.onmicrosoft.com` 에서 `.n-v1.dkim.mail.microsoft` 로 변경. 정확한 값은 M365 Defender DKIM 화면에서 확인.

## 메시지 템플릿 관리 (D-hive-24)

```
shared.message_templates       ◀── AXE 공통 default (fallback 또는 clone source)
 └ {entity}.message_templates  ◀── per-entity customize (override)
```

- Jinja2 `SandboxedEnvironment` + `StrictUndefined` (변수 누락 = render error 로 즉시 발견)
- 변수 선언 (`variables` JSONB) — 운영자가 사용 가능 변수 한눈에 확인
- partial unique index — 동일 (code, language) 의 active 2개 동시 불가
- effective_from/until lifecycle — 산식 패턴과 동일 (시점별 적용)

### Custom template 강제 (D-hive-25)

`send_payslip_email` lookup 이 `require_entity_override=True` — shared default fallback **차단**. entity 측 active template 없으면 `TemplateNotCustomizedError`. 운영자가 한 번은 customize 거쳐야 발송 가능.

**해결 흐름** (1회):
```
notify_template_clone_from_shared(entity_id="axev", code="payslip_email")
notify_template_update(entity_id, scope="entity", template_id, body_template="...")
notify_template_preview(entity_id, template_id, scope="entity", sample_payslip_id)
notify_template_activate(entity_id, scope="entity", template_id)
```

## 발송·송금 audit log (D-hive-25)

| Table | Row 발생 시점 | 핵심 컬럼 |
|---|---|---|
| `{entity}.payslip_dispatch_log` | `export_remittance` 호출 → `mark_paid` 시 동일 row update | `period_id` / `dispatch_kind` ('remittance_export' or 'paid_finalized') / `employees_snapshot` (bank_last4 only — D-hive-19) / `total_amount_krw` / `file_sha256` / `payment_reference` / `finalized_at` |
| `{entity}.payslip_send_log` | `send_payslip_email` 1회 = 1 row (sent/dry_run/failed 모두) | `payslip_id` / `to_email` / `subject` / **`body_sha256`** / `template_id` + `template_source` / `channel` ('graph_api'/'smtp'/'dry_run') / `status` / `error_code` / `error_message` |

`body_sha256` = render 결과 본문 해시. 동일 메일 재발송 검증·변경 감지·분쟁 시 회신 증거.

## MCP Tools 추가 (Phase 3.6)

| 도구 | 권한 | 용도 |
|---|---|---|
| `notify_template_list(entity_id, scope='both', code?, include_drafts?)` | read | 템플릿 목록 (entity / shared / both) |
| `notify_template_get_active(entity_id, code, language='ko')` | read | 현재 active 1건 (룩업 우선순위 적용) |
| `notify_template_clone_from_shared(entity_id, code, language?, effective_from?)` | admin | shared active → entity draft 복사 |
| `notify_template_create(entity_id, scope, code, subject_template, body_template, ...)` | admin | 신규 draft |
| `notify_template_update(entity_id, scope, template_id, subject_template?, body_template?)` | admin | draft 수정 (active 는 immutable) |
| `notify_template_preview(entity_id, template_id+scope OR code, sample_payslip_id?)` | read | render preview |
| `notify_template_activate(entity_id, scope, template_id)` | admin | draft → active (기존 자동 마감) |
| `payroll_send_payslip(entity_id, payslip_id, dry_run?, attach_pdf?)` | admin | 단일 발송 (Graph API) |
| `payroll_send_all_payslips(entity_id, period_id, dry_run?, only_employees?, attach_pdf?)` | admin | 일괄 발송 |
| `payroll_list_send_log(entity_id, period_id?, employee_id?, status?, limit?)` | read | 메일 발송 audit |
| `payroll_list_dispatch_log(entity_id, period_id?, dispatch_kind?, limit?)` | read | 송금 의사 snapshot 조회 |
| `payroll_preview_payslip_pdf(entity_id, payslip_id)` | admin | PDF base64 미리보기 |

## 관련 문서

- [Blueprint service](/services/blueprint) — MCP 클라이언트
- [Frame service](/services/frame) — 미러 패턴의 reference impl
- [/Users/axe/hive/DECISIONS.md](https://github.com/soohunkang/hive/blob/main/DECISIONS.md) — D-hive-1~25
