<!-- canonical: https://docs.axelabs.ai/ops/runbook/rbac-bootstrap -->
<!-- source: content/ops/runbook/rbac-bootstrap.mdx -->

---
title: RBAC bootstrap / lockout
description: hive RBAC (D-hive-32) 의 owner quorum·break-glass·legal escalation 운영 매뉴얼. 모든 owner 가 사라졌거나 단일 owner lockout 위험 시 복구 절차.
playbook: true
---

# RBAC bootstrap / lockout 운영 매뉴얼 (D-hive-32)

hive 의 권한 모델은 verb-orthogonal RBAC ([hive 서비스 페이지](/services/hive#권한-모델--rbac-d-hive-32-2026-06-10)). 본 런북은 그 **bootstrap / lockout 안전망**의 운영 절차 — owner 가 모자라거나(quorum) 전부 사라졌을 때(lockout) 어떻게 권한을 회복하는가.

## AI 요청 프롬프트

```
https://docs.axelabs.ai/ops/runbook/rbac-bootstrap 따라 hive RBAC lockout 복구해줘.

진행:
1. 상황 식별 — 단일 owner 위험(quorum) vs owner 0명(full lockout) vs 일반 role 부여 차단
2. 1차 = CLI break-glass (`hive role-assign`) — owner 1명 이상 남아있으면 이걸로 충분
3. owner 0명이면 DB break-glass (Layer 2) — 직접 SQL INSERT, 그 후 access_audit_log 수동 보강
4. 모든 step 결과를 받고 다음 — 특히 owner 신원 확인 후 grant
5. 복구 후 owner ≥2 재확인 (`hive role-list`) + (선택) /ops/updates Ship Log 한 줄
```

본인 AI session = Claude Code / Cursor / Claude.app / 기타. 본문은 사람이 직접 read 도 가능.

> **전제 — 실행 위치**: 프로덕션 CLI 는 컨테이너 안에서 돈다. 모든 `hive <cmd>` 는 호스트에서
> `docker exec hive-mcp-blue hive <cmd>` 로 감싼다 (active blue 기준 — green 이 active 면 컨테이너명만 교체). `hive` 는 bare console-script entrypoint (`pyproject [project.scripts] hive = "hive.cli:main"`).

## 1. 모델 요약 — 무엇을 보호하는가

| 구성 | 값 | 소스 |
|---|---|---|
| owner role 이름 | `owner` (구 `admin` noun 대체) | `rbac.OWNER_ROLE` |
| owner 최소 인원 | **2** (quorum) | `rbac.OWNER_MIN_COUNT = 2` |
| quorum 강제 시점 | **revoke 시에만** — 마지막에서 2번째 owner 해지 차단 | `rbac.revoke_role` → `OwnerQuorumError` |
| assign 시점 | 강제 없음 — idempotent, 무제한 grant 가능 | `rbac.assign_role` |
| 역할 데이터 | `shared.user_roles` (human) / `shared.system_actor_permissions` (system) | alembic `0006_rbac_shared` |

핵심: **quorum 은 해지에서만 막힌다.** owner 를 **추가**하는 길(assign)은 절대 막히지 않으므로, owner 가 1명이라도 남아 있으면 lockout 이 아니다 — CLI 로 2번째 owner 를 즉시 채워 quorum 을 복구하면 된다. lockout 은 owner 가 **0명**일 때만 발생하고, 그때만 Layer 2(DB break-glass)가 필요하다.

## 2. Layer 1 — owner quorum (≥2) 자동 안전망

`revoke_role(role_name="owner")` 은 해지 후 active owner 가 `OWNER_MIN_COUNT` 미만이 되면
`OwnerQuorumError` 를 던지고 트랜잭션을 롤백한다 (`rbac.py` `revoke_role`, `active - 1 < OWNER_MIN_COUNT`). 즉 **마지막 1~2번째 owner 는 시스템이 스스로 보호**한다.

확인:

```bash
docker exec hive-mcp-blue hive role-list --entity axec
# members 배열에서 role=owner 의 active 인원 수 확인 — 2명 미만이면 즉시 보강(아래)
```

## 3. Break-glass — owner 보강 / lockout 복구

### Layer 2a — CLI break-glass (`hive role-assign`) — **owner 가 1명 이상 남아있을 때 1차 경로**

```bash
docker exec hive-mcp-blue hive role-assign \
  --entity axec --email ai@axellc.com --role owner
```

`assign_role` 은 idempotent (이미 active 면 그대로 반환). 이 CLI 경로는 quorum 검사를 받지 않으므로 owner 1명만 남은 위험 상태에서도 즉시 2번째 owner 를 채울 수 있다.

> ⚠️ **audit provenance — 정확히 이해할 것 (자주 오해되는 지점)**
> CLI `role-assign` 의 grant 는 **`shared.access_audit_log` 에 기록되지 않는다.**
> `assign_role` (`rbac.py`) 은 오직 `shared.user_roles` 에 한 행을 INSERT 하고,
> 그 행의 `granted_by = 'cli:role-assign'` + `granted_at = now()` 가 **유일한
> (row-level) audit provenance** 다. `access_audit_log` 는 **MCP 도구 래퍼
> `rbac.audited_session` 만** 쓴다 (`ok`/`denied`/`error` INSERT) — CLI 는 이
> 래퍼를 거치지 않고 DB trigger 도 없다 (alembic `0006` 에 trigger 없음).
> 따라서 CLI grant 는 `access_audit_log` 행을 **남기지 않는다.**
> 통합 break-glass audit trail 을 원하면 Layer 2b 와 동일하게 아래
> `access_audit_log` INSERT 를 수동으로 추가하라 (권장).

확인 — grant 가 `user_roles` 에 남았는지 (row-level provenance):

```bash
docker exec hive-postgres psql -U hive -d hive -c \
  "SELECT user_email, role_id, granted_by, granted_at
     FROM shared.user_roles
    WHERE entity_id='axec' AND revoked_at IS NULL
    ORDER BY granted_at DESC LIMIT 5;"
```

### Layer 2b — DB break-glass (owner 0명 = full lockout)

owner 가 한 명도 남아 있지 않으면 `grant hive.role` 권한을 가진 주체가 없어 CLI/MCP grant 자체가 거부될 수 있다. 이때만 DB 에 직접 INSERT 한다.

```bash
# 1) owner role 의 id 확인 (entity 별로 seed 됨)
docker exec hive-postgres psql -U hive -d hive -c \
  "SELECT id, name FROM shared.roles WHERE entity_id='axec' AND name='owner';"

# 2) owner 직접 부여 — partial unique index uq_user_roles_active
#    (entity_id, user_email, role_id) WHERE revoked_at IS NULL 가 중복 active 를 막음
docker exec hive-postgres psql -U hive -d hive -c \
  "INSERT INTO shared.user_roles (entity_id, user_email, role_id, granted_by)
   SELECT 'axec', 'ai@axellc.com', r.id, 'break-glass:db'
     FROM shared.roles r
    WHERE r.entity_id='axec' AND r.name='owner'
   ON CONFLICT DO NOTHING;"
```

복구는 항상 **owner ≥2** 로 끝내라 (단일 owner 면 다시 같은 lockout 위험). 2명 채운 뒤 `hive role-list` 로 재확인.

#### DB break-glass 의 audit trail — 수동 보강 (필수 규율)

DB INSERT 도, CLI grant 와 마찬가지로 `access_audit_log` 에 자동 기록되지 않는다. row-level provenance 는 `user_roles.granted_by = 'break-glass:db'` 가 담당하지만, 통합 audit trail 을 위해 같은 트랜잭션 맥락에서 `access_audit_log` 에 수동 행을 남긴다:

```bash
docker exec hive-postgres psql -U hive -d hive -c \
  "INSERT INTO shared.access_audit_log
     (entity_id, actor_kind, actor_id, verb, resource, tool_name, status, detail)
   VALUES
     ('axec','human','break-glass','grant','hive.role','break-glass:db','ok',
      '{\"reason\":\"owner lockout recovery\",\"granted_to\":\"ai@axellc.com\"}'::jsonb);"
```

`status` 는 `ok|error|denied` 만 허용 (CHECK 제약 `ck_access_audit_status`), `verb` 는 6 verb CHECK 를 따른다.

### Layer 3 — Legal escalation

owner 신원·권한 회복을 DB 레벨로도 정당화할 수 없는 경우 (예: 회사 지배구조 분쟁, 운영자 전원 부재) → **회사 법무 절차**. break-glass 는 어디까지나 신뢰된 운영자가 신원 확인된 owner 를 회복시키는 길이지, 권한 분쟁의 판정 수단이 아니다.

## 4. 복구 후 체크리스트

1. `hive role-list --entity <entity>` → owner active 인원 **≥2** 확인.
2. break-glass 를 썼다면 `granted_by` (`cli:role-assign` / `break-glass:db`) 가 `user_roles` 에 남았는지 확인 + (DB break-glass 면) `access_audit_log` 수동 행 확인.
3. quorum 이 정상화됐으면 임시 owner 는 정상 거버넌스로 revoke (단, 항상 ≥2 유지하며 단계적으로).
4. (선택) `/ops/updates` Ship Log 또는 known-gaps 에 lockout 사건 한 줄 기록.

## 5. seed (참고) — 신규 entity 의 owner ≥2 보장

신규 entity onboarding 시 처음부터 owner 2명을 강제하면 lockout 자체가 잘 안 생긴다. `seed_roles` 는 `OWNER_MIN_COUNT` 미만의 distinct owner email 을 받으면 거부한다.

```bash
docker exec hive-mcp-blue hive seed-roles \
  --entity axec --owner ke@axellc.com --owner ai@axellc.com
# idempotent. customers.yaml user_entity_map 사용자에 member 자동 부여 (--no-members 로 끄기).
```
