이 노트에 대하여

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

히스토리

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

관련메타

관련노트

한 줄

운영 인시던트를 한 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에 박은 다섯 원칙재현성, 무결성, 순수성, 추적가능성, 불변성 — 은 원래 보안사고/포렌식 자리의 원천 명제다. 이 원칙을 운영 인시던트 도메인으로 옮기면 두 자리에 동시에 박힌다.

원칙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 — anomaly → signal → memo → evaluation. 가설/ 확률 명시 자리. 상위 패턴.
  • voscli (회사 도메인 친자) — read-only 회수 + record 운영. incidentcli는 voscli가 읽음 으로 끝내는 자리에서 한 단 더 가서 호출 까지 간다. 그래서 수문장 가드가 한 단 더 깊다.
  • denotecli — Denote 노트 CLI. record가 Denote ID를 식별자로 받아 노트 생태계와 cross-link.
  • gitcli — git 타임라인. 직전 배포 후보 추출 자리 (preflight에서 활용).
  • 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 (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 MVPincidentcli report <id> 명령. record 한 건 + evidence summary 한 묶음을 사람이 읽는 markdown으로 자동 렌더링. ROADMAP 상 v0.5 자리이지만 단일 record 측은 v0.2 안에 흡수 가능. 기존 retrofit record로 회귀 검증 가능.

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