---
title: "§incidentcli 운영 인시던트 에이전트 담당자 군대의 수문장"
url: "https://notes.junghanacs.com/botlog/20260519T175212"
author: "Junghan Kim"
date: "2026-05-19T17:52:00+09:00"
lastmod: "2026-05-19T17:59:00+09:00"
tags: ["abduction","agent","botlog","records"]
description: "incidentcli는 운영 인시던트를 한 record 객체에 묶고 자동 액션이 외부로 나가기 직전에 수문장 preflight 게이트를 두는 CLI 도구다. 보안사고 다섯 원칙을 운영 인시던트 도메인으로 옮긴 첫 사례이며, 에이전트 담당자 군대의 외부/내부 두 가드를 함께 닫는 자리를 박는다."
---

> [!abstract] 이 노트에 대하여
> 
> incidentcli는 장애를 빨리 끄는 도구가 아니다. 알람 발화 → 자동 분류 → 가설 + 확률 → 수문장 검증 → 사람 결정 → 자동 액션 → 종결을 같은 record 한 객체에 박아 누가/언제/왜/무엇을 했는지를 5원칙(재현성·무결성·순수성·추적가능성·불변성)이 통과한 형태로 남긴다. abductcli의 anomaly → signal → memo → evaluation 흐름을 운영 인시던트 도메인에 고정한 자리이고, voscli의 read-only 회수 + record 운영 패턴을 호출 쪽으로 한 단 더 올렸다. 이 노트는 그 문제의식과 현재 골격, 다음 자리를 공개용으로 정리한다.

## 히스토리

-   [2026-05-19 Tue 17:52] 생성 — v0.2-1 close 직후, 공개용 첫 소개 노트로 골격 정리.

## 관련메타

-   [† #힣 보안사고 대응 다섯가지 원칙 — 포렌식 증류](https://notes.junghanacs.com/notes/20251121T095117) — 5원칙의 원천 자리. 이 도구는 그 원칙을 운영 인시던트 도메인으로 옮긴 첫 사례.
-   [† #논리](https://notes.junghanacs.com/meta/20250424T230403) — 가설 + 확률 명시는 추론 정밀도 자리와 닿아 있다.
-   [† #워크플로우 #자동화](https://notes.junghanacs.com/meta/20231012T121700) — 자동 액션을 사전 확정 runbook 안에서만 발화하게 묶는 자리.

## 관련노트

-   [§logickocli 한국어 자연어 추론 표준 논리 좌표계](https://notes.junghanacs.com/botlog/20260513T103033) — 같은 “자연어 → 정규화된 좌표계” 패턴. 한쪽은 논증, 한쪽은 인시던트.
-   [§butlercli 집사 — 생활 운영 루프](https://notes.junghanacs.com/botlog/20260518T094818) — 같은 “요청을 record로 묶어 추적”의 가정용 자매.
-   [§denotecli day-query 통합 타임라인](https://notes.junghanacs.com/botlog/20260222T090000) — incidentcli record는 Denote ID를 식별자로 받아 노트 생태계와 cross-link.

## 한 줄

운영 인시던트를 **한 record 객체** 에 묶고, 자동 액션이 외부로 나가기 직전에 **수문장 preflight** 게이트를 두는 CLI 도구다.

## 왜 만드는가

운영 인시던트는 보통 다음 순으로 무너진다.

1.  알람이 발화한다 — 어디서 무엇이 왜 떨어졌는지 채널마다 흩어져 박힌다.
2.  사람이 깬다 — 누가 먼저 봤는지, 왜 그렇게 판단했는지 일부만 남는다.
3.  자동 액션이 들어간다 — 어떤 기준으로 들어갔는지, 그게 안전한 액션이었는지 **근거가 record로 남지 않는다.**
4.  종결한다 — 비슷한 사건이 다시 와도 같은 결정을 같은 정밀도로 다시 내릴 보장이 없다.

이 흐름의 문제는 “장애가 길어졌다”가 아니라 **다음번에 같은 결정을 다시 내릴 수 있다는 신뢰가 안 쌓인다** 는 자리다. 한 번 잘 끈 것보다, 다음에도 같은 정밀도로 끌 수 있는 구조가 쌓이지 않는다. 그래서 일이 끝나도 **조직의 기억** 이 생기지 않는다.

incidentcli는 이걸 바꾸기 위해 **record 한 객체** 를 1차 단위로 박는다.

-   어떤 알람이 어디서 어떻게 발화했는지 (\`evidence[]\`)
-   무엇이 확정이고 무엇이 추정인지 (\`hypotheses[].probability\`)
-   누가 언제 왜 결정했는지 (\`decisions[]\`)
-   어떤 자동 액션이 어떤 사전 승인된 runbook 안에서 발화했는지 (\`actions[]\`)
-   외부 호출 전에 어떤 검증을 통과했는지 (\`preflight[]\`)

이 record가 **append-only** 로 누적되면 같은 사건의 결정 정밀도가 비교 가능해진다. 한 번의 사건이 단발 일화로 끝나는 게 아니라 “보편 원칙”으로 한 단계 증류된다. 이게 가능해지는 게 1차 목표다.

## 다섯 원칙이 운영 인시던트에서 어떻게 작동하는가

[2025-11-21에 박은 다섯 원칙](https://notes.junghanacs.com/notes/20251121T095117) — **재현성, 무결성, 순수성, 추적가능성, 불변성** — 은 원래 보안사고/포렌식 자리의 원천 명제다. 이 원칙을 운영 인시던트 도메인으로 옮기면 두 자리에 동시에 박힌다.

| 원칙  | record 측 (외부 가드)                                           | 담당자 측 (내부 가드)                     |
|-----|------------------------------------------------------------|-----------------------------------|
| 재현성 | 사전 확정 runbook 안에서만, 같은 트리거 = 같은 액션             | 같은 격리 환경, 같은 image-version, 같은 입력으로 같은 결정 |
| 무결성 | record는 append-only. evidence는 SHA-256 봉인. 시간 역전 거부   | 호스트와 분리. 자격증명 직접 노출 금지, 주입 모델 |
| 순수성 | 가설은 확률을 명시. 확정과 추정을 표면화해 섞지 않는다          | 정의되지 않은 입력 채널 거부. 쓰레기 신호에 휘둘리지 않음 |
| 추적가능성 | 모든 결정에 \`{who, when, why}\`. 식별자는 Denote ID로 노트 생태계와 cross-link | 결정 trace가 런타임 로그(JSONL)로 박혀 record와 연결 |
| 불변성 | \`schema_version\`을 둬, 도구가 변해도 과거 record가 호환된다   | 런타임 \`image_version\`. 런타임이 바뀌어도 결정 재현 가능 |

이 표가 핵심이다. 한 면만 닫으면 다른 면으로 사고가 새어 나간다. **외부 가드** 만 두면 오염된 담당자가 잘못 결정한 게 자동 액션으로 외부에 나간다. **내부 가드** 만 두면 깨끗한 담당자가 검증 미달인 코드를 그대로 통과시킨다. 그래서 두 면을 같이 닫는다.

## 수문장의 두 면

| 면             | 책임                                         | 위치           |
|---------------|--------------------------------------------|--------------|
| 외부 가드 (수문장의 손) | preflight 검증 게이트 → 자동 액션 거부 → 미통과 시 개선 요구 발송 | incidentcli 본체 |
| 내부 가드 (수문장의 결) | 담당자 자체가 오염원이 되지 않도록 격리. 정의된 외부 호출 인터페이스만 뚫림 | 격리된 담당자 런타임 |

외부 가드의 preflight 항목 (자동 액션이 외부로 나가기 직전 통과해야 하는 검증):

-   안정 릴리즈 후보가 존재하는가
-   그 릴리즈의 테스트가 통과한 기록이 있는가
-   영향 받은 인터페이스가 **테스트로 검증** 되었는가 — 미통과면 자동 액션 거부 + 개선 요구 발송
-   외부 종속 시스템 측 응답 정합성
-   해당 runbook이 사전 리더 회의 승인 상태인가

여기서 중요한 자리: incidentcli는 **백엔드 테스트 코드 자체를 담지 않는다.** 백엔드 팀이 가진 검증 테스트를 **호출** 만 하고, 그 결과가 기준치 미달이면 **액션을 안 발화하고 대신 “테스트 강화하라” 메시지를 보낸다.** 이게 진짜 수문장 자리다. 코드의 책임을 옮기는 게 아니라 **호출 결과를 정직하게 record에 박는다.**

## 분기 — origin

같은 알람이 와도 원인이 어디냐에 따라 워크플로가 갈린다.

| origin (canonical snake_case) | 워크플로                              |
|-------------------------------|-----------------------------------|
| `internal_backend`            | 안정 릴리즈 검증 + 백엔드 테스트 통과 → 승인 → 롤백 |
| `external_vendor_a`           | 외부 시스템 측 정보 조회 → 내부 승인 → 해당 측 채널로 리포트 |
| `external_vendor_b`           | 외부 OEM 측 응답 확인 → 우회 / 대기   |
| `unknown`                     | 사람 결정 자리. 자동 분기 액션 금지   |

분기 판정 자체는 `incidentcli classify --origin` 명령이 hypothesis 기반으로 박는다. \`certainty.level = confirmed\` 가 아니면 \`unknown\` 으로 후퇴하고 사람 결정을 호출한다. 추측을 확정처럼 박는 순간 신뢰를 잃기 때문에, **origin = unknown 인 record는 close 거부** 한다 — classify 먼저.

## record schema의 일반 모양

-   **id**: `YYYYMMDDTHHMMSS` (Denote ID). 사후 식별자.
-   **opened_at**: 사건 시각 KST ISO8601 `+09:00`. **id ≠ opened_at** — 분리.
-   **timeline.{first_down_at, first_alert_at, trigger_commit_at, trigger_apply_at, revert_commit_at, source_tz, note}**: id와 opened_at 사이의 인과 봉합 자리. commit ≠ apply 같은 자리는 별도로 봉합.
-   **origin**: canonical snake_case 4 값.
-   **certainty.{level, probability, evidence_ref}**: confirmed / estimated / rejected.
-   **hypotheses[]**: 텍스트 + 확률 + source. **확률 명시 필수.**
-   **preflight[]**: 수문장 검증 항목 결과.
-   **actions[]**: 자동 또는 사람 액션. v0.4 이후 \`{runbook_id, approved_at, preflight_ref, executed_at, result, who}\` 필수.
-   **decisions[]**: 사람 결정 \`{who, when, why}\`.
-   **evidence[]**: 경로 + `sha256` (full 64 hex만) + `source` + `provenance.{source_tz, fetched_at}`.
-   **runtime.{image_version, host_isolated, note}**: 담당자 런타임 봉인 (v0.3 자리).
-   **schema_version**: `"1.0"`. 도구가 변해도 record 호환.

형식은 JSON. 누적이 무거워지면 PostgreSQL JSONB 컬럼으로 lossless 직행. 사람이 읽는 자리는 `jq` / `yq` 로 충분하다.

## 시간축 — KST 단일 정착

incidentcli의 모든 입력/저장/출력은 **Asia/Seoul (UTC+9) 단일 시간축**. 외부 시스템 시간대(UTC / CST / 외부 OEM 측 다양)는 collector가 KST로 정규화하고, **원본 시간대(\`source_tz\`)는 evidence provenance에 박는다.** 추정 시간대 그대로 저장 금지 — silent 1시간 / 9시간 shift가 가장 흔한 무결성 사고 자리.

CLI 입력은 Denote 형식 `YYYYMMDDTHHMMSS` (한 토큰, 정렬 가능, 파일명 호환). record 저장은 ISO8601 `+09:00`. 두 표현은 1:1 변환 가능.

## 자매 도구

incidentcli는 다음 도구 가족의 막내다. 코드를 차용하지 않고 **원칙** 만 차용한다.

-   [abductcli](https://github.com/junghan0611/abductcli) — anomaly → signal → memo → evaluation. 가설/확률 명시 자리. 상위 패턴.
-   **voscli (회사 도메인 친자)** — read-only 회수 + record 운영. incidentcli는 voscli가 **읽음** 으로 끝내는 자리에서 한 단 더 가서 **호출** 까지 간다. 그래서 수문장 가드가 한 단 더 깊다.
-   [denotecli](https://github.com/junghan0611/denotecli) — Denote 노트 CLI. record가 Denote ID를 식별자로 받아 노트 생태계와 cross-link.
-   [gitcli](https://github.com/junghan0611/gitcli) — git 타임라인. 직전 배포 후보 추출 자리 (preflight에서 활용).
-   [gogcli](https://github.com/junghan0611/gogcli) — Google Workspace 통합. 결정 알림 라인 자리.

원칙 차용 — **append-only, fast-fail, Denote ID cross-link.** 친자 관계는 코드가 아니라 원칙에서 성립한다.

## fast-fail — 면피 코드 금지

이 도구의 1차 사용자는 **사람이 아니라 담당자 에이전트** 다. 그래서 에러 처리 정책이 다르다.

> 에러처리는 ‘경고’가 아니라 그냥 박살나야 한다. 스킬은 면피 코드가 있으면 안 된다. 에이전트가 쓰는 거라서, 대충 경고는 에이전트가 우회해 버린다. 박살나야 한다.

박살나야 하는 자리:

-   스키마 무결성 — 필수 필드 누락, `schema_version` 미지정
-   runbook 무결성 — 등록되지 않은 runbook 호출 시도
-   append-only 위반 — 이미 박힌 필드 수정 시도
-   시간축 무결성 — `closed_at < opened_at`, KST가 아닌 offset, Denote ID 길이 위반
-   evidence 무결성 — SHA-256 mismatch, `source_tz` 누락
-   preflight 무결성 — 미통과인데 자동 액션 시도
-   origin 무결성 — `unknown` 인데 자동 분기 액션 시도

CLI는 exit non-zero. record 자체에 “일단 박고 나중에 정리하지”라는 자리가 없다.

## 격리 런타임 — 본인 하네스 비종속

담당자 에이전트 자체가 오염원이 되지 않도록 격리된 런타임 위에서 돈다. 채택한 자리는 [pi-chat](https://github.com/earendil-works/pi-chat) (Gondolin 마이크로 VM) — 아르민 / 마리오 측 메인라인 추종.

격리 런타임이 보장하는 자리:

-   호스트 분리 — `/workspace` (인시던트 별) + `/shared` (계정 공용) 밖 쓰기 차단
-   명시적 outbound — 정의된 외부 호출 인터페이스 외 임의 HTTP 차단
-   자격증명 주입 모델 — 담당자에게 secret 값을 직접 노출하지 않고 작업환경에 주입만
-   런타임 `image_version` — 결정 재현 가능 (5원칙 불변성 자리)
-   결정 trace JSONL — 담당자 결정이 런타임 로그로 박혀 record와 cross-link

여기서 의도적으로 **내 (GLG) 개인 스킬셋, 분신 호출 경로, 개인 자격증명에 종속시키지 않는다.** incidentcli는 완성되면 **독립 에이전트** 로 서야 한다. 본인 개인 도구체인이 깊숙이 관여하면 도구가 사람에게 묶인다. pi 런타임 + pi-chat surface 자체는 채택하되, 그 위에 회사 도메인 skill + 회사 자격증명만 주입하는 모델로 간다.

## 현재 상태 — v0.2-1 진입

-   **v0.0 close (2026-05-19)** — repo skeleton, 정체성 정렬 (수문장 / 분기 / 격리 / 본인 하네스 비종속), 정보채널 정찰, 핵심 결정 5건 (언어 = Go, record = JSON, 시간축 = KST 단일, id ≠ opened_at 분리, sha256 vs sha256_prefix 분리) 박힘.
-   **v0.1 close (2026-05-19)** — 운영 사이클 5 명령 (`init` / `attach` / `classify` / `decide` / `close`) + `validate` + `ingest` + `tz parse/format` 박힘. `internal/tz/` (KST 단일 + Denote ID), `internal/record/` (load + Validate + AppendDecision + AppendEvidence + Save, append-only), 첫 read-only collector (5 테이블 summary + redaction). 인터페이스 16 시나리오 PASS.
-   **v0.2-1 done (2026-05-19)** — 두 번째 collector 박힘. 정의된 외부 채널에서 회수한 raw JSON을 받아 PII 마스킹된 sanitized summary로 변환. 입력 형식 3종 자동 감지, 시간창 필터, LIMIT 5 timeline sample, body truncate 200 chars. 사람 이름은 `[REDACTED]`, bot/role 식별자는 보존 (origin signal 유지). 라이브 외부 API 호출 없음 — 외부 채널은 collector 밖.

## ROADMAP 한 줄씩

-   **v0.2-2 ~ v0.2-5** — 정보채널 자동화 (다른 collector + 외부 attach 어댑터 + 수문장 preflight 본체 + 개선 요구 draft + SHA-256 manifest 자동 봉인). 한 텀에 다 박지 않고 쪼개 간다.
-   **v0.3** — 담당자 자동 운영 + 격리 런타임 통합. alert webhook → 격리 런타임 담당자 호출 → record 80% 자동 채움. 사람은 decide + action 만.
-   **v0.4** — 자동 액션 (수문장 preflight 통과 후 사전 확정 runbook 발화). “사람을 안 깨우는 골든타임”의 첫 실증.
-   **v0.5** — 누적 / 비교 / postmortem 정착. `report --weekly --quarterly`, `compare <id1> <id2>`. 사례 → 보편 원칙 증류 한 바퀴 더.

## 비목표

-   실시간 모니터링 대시보드 — 알람 발송·전파 시스템 측 책임.
-   알람 발송·전파 — 운영 모니터링 인프라 측 책임. incidentcli는 발화 신호를 **받아** record만 운영.
-   추상 incident management 플랫폼 — PagerDuty 대체가 아니다.
-   사람 호출 자동화 — 전화·SMS는 별도 라인.
-   자동 RCA / AI 자동 해결 — 자동 액션은 **사전 확정 runbook 범위 안에서만**.
-   백엔드 테스트 코드 / 롤백 스크립트 본체 / 외부 시스템 검증 로직 — 각 측 책임. incidentcli는 **호출만** 한다.
-   본인 (GLG) 개인 스킬셋 / 익스텐션 / 분신 군대에 종속 — 완성 후 독립 에이전트로 서야 한다.

## 다음 한 걸음

다음 텀은 두 자리 중 하나로 결정.

-   **v0.2-2** — 세 번째 collector. git timeline 측 회수 (`git_trace`) + 배포 trace 측 회수 (`cicd`). 한 텀에 collector 둘 또는 분리 한 텀씩.
-   **report MVP** — `incidentcli report <id>` 명령. record 한 건 + evidence summary 한 묶음을 사람이 읽는 markdown으로 자동 렌더링. ROADMAP 상 v0.5 자리이지만 단일 record 측은 v0.2 안에 흡수 가능. 기존 retrofit record로 회귀 검증 가능.

분신 호출 정책은 유지 — “힣이 명시적으로 요청한 경우만”. 다음 텀은 GLG 결정 받은 뒤 시작.
