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-resourceaxe.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 시점 queryorg_get_team— 조직 + 멤버 (line memberships, include_sub_orgs 옵션)position_list— 직무/직급 목록leave_get_balance— 직원 휴가 잔여 (근로기준법 자동 적용)leave_get_calendar— 팀 휴가 캘린더 (overlap 감지)leave_get_request— 단일 요청 detailleave_policy_list— 휴가 정책 목록leave_usage_report— 기간별 사용 집계
변경
leave_request— 휴가 신청 (충돌 + 잔여 자동 검증)leave_approve— 승인/반려 (다단계 결재 지원)leave_cancel— 취소 (start_date 전)leave_policy_create— 정책 등록
관리자
employee_create— 신규 입사 + 초기 발령 (optional).payroll_start_date옵셔널 — None 이면hire_datefallback (D-hive-27).employee_update— patch (self vs HR 필드 분리).payroll_start_dateHR 필드.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_CONFLICTraise.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) 가 fact 의 typed schema 권위를 MCP 에 위임. hive 는 frame 패턴 (Frame Schema discovery) 1:1 mirror — 자기가 생산하는 HR/payroll fact kind 를 /schemas 엔드포인트로 노출.
curl -H "Authorization: Bearer $HIVE_MCP_TOKEN" \
https://axe.axelabs.ai/hive/schemas15 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 가 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 < adminread— get/list/searchwrite— leave.request (본인), employee.update (자기 일부)approve— leave.approve, employee.update(hr_fields=True), 성과 review.submitadmin— 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 적용:
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 § 사람에게 전달. 수신자 화면 절차는 /onboard/claude-frame-setup (5분 가이드, MCP URL 만 https://axe.axelabs.ai/hive/mcp 로 치환).
CLI
# DB
hive migrate # shared + 모든 entity
hive register-entity --id axec --legal-name "에이엑스이 코퍼레이션"
hive list-entities
# 토큰 발급
hive mcp-token --sub [email protected] --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_scheduleJSONB (4-year cliff / monthly / milestone / acceleration 모두)performance_link(Phase 2 OKR/KPI 와 lazy coupling)frame_journal_id(cross-service trigger via pg_notify)calculation_inputsJSONB (재현 가능성)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:
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 -d1회 부팅 후 영구./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→ 200https://axe.axelabs.ai/hive/health/ready→ DB ping 200https://axe.axelabs.ai/hive/.well-known/oauth-protected-resource→ RFC 9728 metadatahttps://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) 페이로드 표준:
{
"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월 운영자:
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 → 차이 시
adjustmentevent (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 1101per employee- idempotency = SHA256(period + employee + payment_reference)
- backfill on startup (event_outbox unconsumed)
- ack via
consumed_byJSONB
산정 예시 (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가지 본질 반영:
- 비과세 분리 — 식대 / 차량보조금 월 200K 한도
- 간이세액표 lookup —
shared.simplified_income_tax_table(월 과세 × 부양가족수 → 세액). 누진세 직접 계산 X - 부양가족·자녀세액공제 —
employees.dependents_count+children_count_under_20 - 4대보험 보수월액 분리 —
reported_income_pension_krw/reported_income_health_krw(각자 신고소득) - 정산 —
health_reconciliation_krw/yearend_reconciliation_krw(음수=환급, 양수=추가).payroll_periods.attributes.pending_reconciliations사전 등록 - 퇴사자 처리 —
termination_date일할 또는 정산-only payslip - 라운딩 — 국민·건강·장기 = 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 (예: [email protected]) |
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:[email protected]; 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 로 즉시 발견) - 변수 선언 (
variablesJSONB) — 운영자가 사용 가능 변수 한눈에 확인 - 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 — MCP 클라이언트
- Frame service — 미러 패턴의 reference impl
- /Users/axe/hive/DECISIONS.md — D-hive-1~25