---
name: iop-user-crud-ops
version: 1.0.0
description: UI 표면이 없는 단계에서 OpenAI-compatible 사용자/principal CRUD를 private Edge config와 token 매핑으로 운영하는 절차
---

# iop-user-crud-ops

## 목적

IOP 내부 사용자 CRUD UI/API가 완성되기 전까지 OpenAI-compatible 사용자를 `principal_ref`, `principal_alias`, `token_ref` 매핑으로 등록, 조회, 수정, 비활성화, 삭제한다.
현재 IOP는 사용자/테넌트 source of truth를 소유하지 않으므로, 이 스킬은 외부 principal 참조값과 IOP token 매핑을 운영하는 임시 CRUD 절차다.

## 언제 호출할지

- UI 없이 OpenAI-compatible 사용자를 추가, 조회, 수정, 비활성화, 삭제해야 할 때
- 특정 `principal_ref`에 token을 추가하거나 기존 token을 회전, 폐기해야 할 때
- 운영자가 Grafana 사용량 label에 노출될 `principal_alias`, `token_ref`를 관리해야 할 때
- 사용자 CRUD API/UI가 없어 private Edge config와 operator-private 기록으로 임시 운영해야 할 때

## 입력

- `operation`: `create`, `read`, `list`, `update`, `disable`, `delete`, `rotate-token` 중 하나 (필수)
- `env`: 대상 환경. 예: `local`, `dev`, `dev-corp`, `field` (필수)
- `config_path`: 대상 Edge private config 또는 운영 overlay 경로 (필수)
- `principal_ref`: 외부 사용자/테넌트/운영 시스템의 안정 참조값 (create/read/update/disable/delete 필수)
- `principal_alias`: Grafana와 운영 표면에 보여줄 낮은 cardinality alias (create/update 선택, create에서는 없으면 안전한 별칭 생성)
- `token_ref`: 앱/통합/용도별 token 참조값 (create/rotate-token 선택)
- `operator_record_path`: raw token 없이 user/token 상태를 남길 operator-private 기록 위치 (선택)
- `raw_token_delivery`: raw token을 operator에게 1회 전달할 비공개 경로 또는 절차 (새 token 발급 시 필수)

## 먼저 확인할 것

- [ ] 대상 환경의 실제 source of truth가 tracked `configs/*.yaml` 예시가 아니라 private Edge config 또는 운영 overlay인지 확인한다.
- [ ] 현재 구현에는 별도 `users[]` config, 사용자 CRUD API, Control Plane UI source of truth가 없음을 확인한다.
- [ ] `principal_ref`가 raw email, provider token, provider identity, secret이 아니라 외부 시스템 참조값인지 확인한다.
- [ ] `principal_alias`와 `token_ref`가 metric label에 적합한 낮은 cardinality ASCII 값인지 확인한다.
- [ ] 새 raw token 발급이 필요하면 `agent-ops/skills/project/openai-usage-token-issue/SKILL.md` 절차와 누출 금지 기준을 함께 적용한다.
- [ ] `disable`, `delete`, `rotate-token`은 기존 caller를 끊을 수 있으므로 대상 `principal_ref`와 `token_ref`가 모호하지 않은지 확인한다.

## 실행 절차

1. **대상 상태 확인**
   - `config_path`의 `openai.principal_tokens[]`를 구조화 파서로 읽는다.
   - 같은 `principal_ref`에 연결된 `token_ref`, `principal_alias` 목록을 확인한다.
   - `token_ref`와 `token_hash_sha256` 중복, 64자 hex hash 형식, 빈 `principal_ref` 여부를 확인한다.

2. **작업별 변경안 작성**
   - `create`: 기존 `principal_ref`가 없으면 alias를 정하고, 필요한 token entry를 추가한다. 새 raw token이 필요하면 `openai-usage-token-issue` 절차로 생성한 hash/reference만 config에 넣는다.
   - `read`/`list`: raw token과 전체 hash 없이 `principal_ref`, `principal_alias`, `token_ref` 목록과 활성 config 반영 여부만 보고한다.
   - `update`: alias 변경은 같은 `principal_ref`의 모든 관련 token entry에 일관되게 반영한다. `principal_ref` 변경은 외부 source of truth의 rename 근거가 있을 때만 수행한다.
   - `disable`: 현 config schema에는 `status` 필드가 없으므로 active config에서 대상 token entry를 제거하고, 필요하면 operator-private 기록에 disabled/tombstone 상태를 남긴다.
   - `delete`: active config에서 대상 principal의 token entry를 제거한다. 감사 목적의 tombstone은 operator-private 기록에만 남긴다.
   - `rotate-token`: 새 token entry를 추가하고 운영자가 전환을 확인한 뒤 이전 token entry를 제거한다. 앱/통합별 continuity가 필요하면 `token_ref` 재사용 여부를 먼저 확정한다.

3. **private 기록 갱신**
   - `operator_record_path`가 있으면 raw token 없이 `principal_ref`, `principal_alias`, `token_ref`, hash fingerprint 또는 hash prefix, status, 변경 사유, 변경 시각만 남긴다.
   - tracked `docs/`, `agent-roadmap/`, `agent-spec/`, `configs/` 예시에는 private principal 원문, raw token, provider credential을 쓰지 않는다.

4. **config 검증과 반영**
   - Edge config load/check 명령 또는 해당 환경의 검증 절차로 변경 config가 유효한지 확인한다.
   - `openai.principal_tokens[]` 변경은 restart-required로 분류되므로 live apply 완료로 보고하지 않는다.
   - 환경별 배포 스킬이 적용되는 대상이면 해당 배포 스킬의 restart/검증 절차를 따른다.

5. **동작 검증**
   - create/rotate-token은 새 bearer token으로 OpenAI-compatible 인증이 성공하는지 확인한다. 검증 출력에 raw token을 찍지 않는다.
   - disable/delete는 폐기된 token이 `401 unauthorized`가 되는지 확인한다.
   - 가능한 경우 usage metric 또는 Grafana query에서 `principal_ref`, `principal_alias`, `token_ref` label이 기대값으로 나오는지 확인한다.
   - raw token 누출 검색은 token 원문이 출력되지 않는 조용한 방식으로 수행한다.

6. **결과 보고**
   - 작업 종류, 대상 환경, `principal_ref`, `principal_alias`, 영향을 받은 `token_ref`, config 반영 방식, restart 필요 여부, 검증 결과만 보고한다.
   - raw token, 전체 token hash, provider token, private endpoint는 보고하지 않는다.

## 실행 결과 검증

- [ ] active config의 `openai.principal_tokens[]`가 중복 없는 `token_ref`, 64자 hex `token_hash_sha256`, 비어 있지 않은 `principal_ref`를 가진다.
- [ ] 새 token을 발급한 경우 raw token이 operator에게 1회만 전달되고 tracked 파일과 최종 보고에 남지 않았다.
- [ ] `disable`/`delete` 후 해당 token은 인증에 실패하고, 남겨야 할 audit/tombstone은 operator-private 기록에만 남았다.
- [ ] `openai.principal_tokens[]` 변경이 restart-required임을 보고했고, live apply로 오인하지 않았다.
- [ ] 최종 보고에 raw token, 전체 hash, provider credential, raw email 같은 민감값이 포함되지 않았다.
- 검증 실패 시: 변경 반영을 중단하거나 이전 private config로 되돌리고, 실패한 principal/token_ref와 실패 단계만 민감값 없이 보고한다.

## 출력 형식

```text
IOP user CRUD ops
- operation: <create|read|list|update|disable|delete|rotate-token>
- env: <env>
- principal_ref: <principal_ref>
- principal_alias: <principal_alias>
- token_refs: <affected token_ref list>
- config_path: <private config or operator overlay path>
- operator_record: <path or none>
- restart_required: yes
- verification: <auth-pass/auth-deny/config-check/metrics result>
- raw_token_reported: no
- notes: <주의사항 또는 후속 작업>
```

## 금지 사항

- 현재 구현에 없는 `users[]`, `status`, tenant/org CRUD schema를 Edge config에 임의로 추가하지 않는다.
- raw token, 전체 token hash, provider token, private endpoint, raw email을 tracked 파일이나 최종 보고에 남기지 않는다.
- `metadata.user` 또는 caller-provided `metadata.iop_principal_*`를 사용자 identity source로 취급하지 않는다.
- `openai.principal_tokens[]` 변경을 live apply로 처리됐다고 보고하지 않는다.
- 대상 `principal_ref` 또는 `token_ref`가 모호한 상태에서 disable/delete를 진행하지 않는다.
- 사용자 CRUD UI/API, billing, 조직 IAM, rate limit enforcement까지 이 스킬 책임으로 확장하지 않는다.
