diff --git a/README.md b/README.md index 91b045e0..a2cdaa3c 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ └──────────────┬────────────┘ │ provisioning ▼ -┌──────────────────────────────────────────────────────┐ -│ Kubernetes (EKS) · Strimzi Kafka │ -│ Debezium(source) → Kafka Topic → JDBC Connect(sink) │ -└────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────┐ +│ Kubernetes (EKS) · Strimzi Kafka · Kafka Connect │ +│ Debezium(source) → Kafka Topic → JDBC Connect(sink) │ +└────────────────────────────────────────────────────────────┘ ``` - **operations-backend** (Spring Boot 모놀리스) — 플랫폼 API, 인증, datasource·pipeline 도메인, K8s/Kafka 자동화. 에이전트 전용 내부 API `/internal/ops/**`는 외부에 노출하지 않습니다. @@ -59,7 +59,8 @@ | Frontend | React, Vite, TypeScript, Tailwind CSS | | Data plane | Apache Kafka (Strimzi), Debezium, Kafka Connect (JDBC) | | Datastore | PostgreSQL(metadb), pgvector(agentdb), 테넌트 DB(PostgreSQL/MariaDB) | -| Infra | AWS EKS, ArgoCD(GitOps), Harbor, Jenkins(CI/CD) | +| Build/CI/CD | Gradle multi-module(`operations-backend`, `timestamptz-converter`), Jenkins(Kaniko), Harbor, Argo CD GitOps | +| Infra | AWS EKS(Terraform), Strimzi, ingress-nginx+cert-manager, Sealed Secrets | | Observability | Prometheus, Grafana, Loki, Tempo | ## 빠른 시작 @@ -108,11 +109,20 @@ bifrost/ │ ├─ operations-backend/ Spring Boot — 플랫폼 API·K8s/Kafka 자동화 │ ├─ ai-service/ FastAPI — RCA 에이전트·LLM │ └─ frontend/ React UI -├─ infra/ Terraform·Helm·K8s manifest +├─ connect-plugins/ Kafka Connect 커스텀 컨버터 Gradle 모듈 +├─ infra/ Terraform·K8s manifest·CI/CD bootstrap ├─ scripts/ 로컬/배포 스크립트 (local-up.sh 등) └─ docker-compose.yml 로컬 의존 인프라 ``` +## CI/CD·배포 + +- Jenkins `bifrost-ci`는 `jenkins-values.yaml`의 JCasC/job-dsl로 생성되는 단일 Pipeline job이며, SCM 브랜치는 `*/main`, scriptPath는 루트 `Jenkinsfile`이다. +- `main` push/webhook 빌드에서 직전 성공 커밋 대비 변경된 앱 서비스(`services/ai-service`, `services/operations-backend`, `services/frontend`)만 Kaniko로 빌드해 Harbor `harbor.harbor.svc.cluster.local/library/bifrost-:`와 `:latest`에 push한다. +- `operations-backend`는 멀티모듈 Gradle 빌드라 Kaniko 컨텍스트가 레포 루트이고, `ai-service`와 `frontend`는 각 서비스 디렉터리를 컨텍스트로 쓴다. +- `infra/docker/kafka-connect` 또는 `connect-plugins/` 변경 시 Kafka Connect 커스텀 이미지도 루트 컨텍스트로 빌드해 고정 태그 `1.0.0-converter`, git sha, `latest`로 Harbor에 push한다. 앱 서비스처럼 GitOps tag를 자동 변경하지는 않는다. +- 앱 서비스 배포는 Jenkins가 `gitops` 브랜치의 `charts//values.yaml` `image.tag`만 커밋하고, Argo CD `0-root-bifrost-root` app-of-apps가 `argocd/apps/`의 10개 child Application을 polling/reconcile해 적용한다. + ## 개발 브랜치·커밋·PR 컨벤션은 [docs/team/git-convention.md](docs/team/git-convention.md)를 따릅니다. diff --git a/docs/README.md b/docs/README.md index cce2abaf..167580b8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -60,6 +60,8 @@ Spring Boot Operations Backend | Agent run 상태(run·state·event·approval facade·report) | **FastAPI**(`agentdb`) | [contract-state-schema §14](./design/backend-fastapi/contract/contract-state-schema.md#14-contract-state-schema) | | Knowledge 코퍼스(RAG runbook·문서) | **FastAPI**(Vector Store) | [server-design §9](./design/backend-fastapi/server-design.md#2-server-design) | | Tool 매핑(논리 tool→operation) | **FastAPI** Tool Client Registry | [tool-catalog.md §8·§9](./design/backend-fastapi/tool-catalog.md#4-tool-catalog) | +| CI 파이프라인·이미지 빌드 | **Jenkinsfile** + Jenkins JCasC | [infra/cicd README](../infra/cicd/README.md#ci-파이프라인-job-bifrost-ci) | +| GitOps 배포 구조 | **gitops 브랜치** `argocd/`, `charts/`, `databases/`, `infra/`, `secrets/` | [infra.md §7](./design/infra.md#7-cicd) | | API 에러코드 | 표면별(각자 소유) | [Spring](./api/springboot.md) · [FastAPI](./api/fastapi.md) | | 인프라 현황(클러스터·용량) | 인프라 | [infra.md §2](./design/infra.md#2-리소스-계획현황-resource-plan) | diff --git a/docs/adr/0004-monorepo-monolith.md b/docs/adr/0004-monorepo-monolith.md index c7a16ddb..f0540e85 100644 --- a/docs/adr/0004-monorepo-monolith.md +++ b/docs/adr/0004-monorepo-monolith.md @@ -18,18 +18,18 @@ Frontend ─┬─► Spring Boot Operations Backend (플랫폼 본체, 단일 ## Decision 1. **모노레포** 유지 (폴리레포 폐기). -2. **Spring Boot는 단일 모놀리스** `services/operations-backend`로 통합한다. +2. **Spring Boot는 단일 모놀리스** `services/operations-backend`로 통합한다. 현재 Gradle 모듈은 `services:operations-backend`와 Kafka Connect 이미지에 동봉되는 `connect-plugins:timestamptz-converter`만 포함한다. - `core-service` + `orchestrator-service` → `services/operations-backend` 병합. - base 패키지 `com.platform.*` → **`com.bifrost.ops`** 통일. - 패키지 구조는 설계 [springboot/DETAILS.md §5](../design/backend-springboot/server.md#5-패키지-구조)를 따른다. -3. **Agent는 FastAPI(Python)** 로 별도 서비스 유지(`services/ai-service`). Spring Boot의 `/internal/ops`만 호출하며 K8s/Kafka에 직접 접근하지 않는다. (현재 Java 스캐폴드 → FastAPI 전환은 별도 이슈) +3. **Agent는 FastAPI(Python)** 로 별도 서비스 유지(`services/ai-service`). Spring Boot의 `/internal/ops`만 호출하며 K8s/Kafka에 직접 접근하지 않는다. 4. **`libs/common-dto` 흡수** — 모놀리스 내부 패키지로 옮기고 Gradle 모듈 제거. Spring ↔ FastAPI 계약은 공유 lib 대신 `/internal/ops` HTTP(JSON)/OpenAPI로 관리(언어가 갈리므로 공유 jar 불가). ## Consequences ### 긍정적 - 서비스 간 호출/인증 경계 제거, `PipelineStatusService` in-process 단일 writer 그대로 구현. -- 단일 빌드·배포물(Dockerfile/Helm 1개), 통합 테스트 단순화. +- Spring 도메인은 단일 Gradle 모듈·단일 Dockerfile·단일 Helm release로 유지된다. 플랫폼 전체 배포 단위는 GitOps 브랜치의 `frontend`·`operations-backend`·`ai-service` 차트와 Kafka Connect 커스텀 이미지로 나뉜다. - 설계 문서(§2, §5)와 코드 구조 일치. ### 부정적 diff --git a/docs/api/fastapi.md b/docs/api/fastapi.md index 45ee5d97..73c7b014 100644 --- a/docs/api/fastapi.md +++ b/docs/api/fastapi.md @@ -214,7 +214,7 @@ Spring Boot 내부 운영 API와 governance/mutation 계약은 [Spring Boot API | `GET` | `/api/v1/approvals/{approval_id}` | 구현됨 | 단일 approval 상세(`ApprovalSummary`). 없으면 `APPROVAL_NOT_FOUND` envelope | | `POST` | `/api/v1/agent/runs/{run_id}/approvals/{approval_id}/decision` | 미구현 | route 없음 | -현재 FastAPI approval route는 local approval-link repository만 갱신한다. Spring Boot approval facade와 mutation single-use 검증 표면은 존재하지만, FastAPI executor는 Spring mutation 호출에 `X-Approval-Id`를 전달하지 않으므로 현재 실행 경로와 연결되어 있지 않다. +현재 FastAPI approval route는 local approval-link repository를 갱신하고, 사용자 승인 후 Spring MutationGate용 pre-approved 레코드를 생성해 `spring_approval_id`를 저장할 수 있다. Executor는 approved action의 `approval_id`와 `change_ticket_id`를 `ToolContext`에 실어 Spring mutation 호출 시 `X-Approval-Id`/`X-Change-Ticket-Id`로 전달한다. ## 11. Change Management API @@ -274,7 +274,7 @@ Incident, event, monitoring 목록/상세 조회는 Spring Boot 플랫폼 API가 `POST /api/v1/tools/{tool_name}/execute`는 read-only slash command 실행 전용이다. tool이 없으면 HTTP 404 `TOOL_NOT_FOUND`, 대상 tool이 `RiskLevel.READ_ONLY`가 아니거나 `requires_approval`이면 HTTP 400 `POLICY_DENIED`로 거부한다. 실행은 `slash_command` agent 컨텍스트(`run_id=slash_...`)로 registry를 호출하고, tool 실패 시 Spring tool error code를 HTTP 400 envelope으로 그대로 전달한다. mutation tool은 이 route로 실행할 수 없다. -FastAPI tool registry는 Agent의 논리 tool 목록이다. Spring Boot `GET /internal/ops/admin/tool-catalog`는 실제 Spring runtime endpoint catalog이며 현재 read operation과 approval-gated mutation operation을 함께 반환한다. 두 catalog의 목적과 범위는 다르다. +FastAPI tool registry는 Agent의 논리 tool 목록이다. 현재 23개 logical tool(read-only 19개, approval-gated mutation 4개)을 반환하며, `get_kafka_lag`처럼 Spring operation 하나에 여러 논리 alias가 붙을 수 있다. Spring Boot `GET /internal/ops/admin/tool-catalog`는 실제 Spring runtime endpoint catalog이며 현재 22개 operation(read operation 18개, mutation 4개)을 반환한다. Spring mutation의 approval 필요 여부는 `PolicyGuard`가 결정하므로 FastAPI registry의 `requires_approval` flag와 목적이 다르다. ## 16. Feedback / Audit UI API diff --git a/docs/api/internal-ops-read-tools.md b/docs/api/internal-ops-read-tools.md index b9b487d6..9af64fda 100644 --- a/docs/api/internal-ops-read-tools.md +++ b/docs/api/internal-ops-read-tools.md @@ -16,7 +16,7 @@ ## Runtime Tool Catalog -`GET /internal/ops/admin/tool-catalog`가 현재 Spring Boot에서 구현된 agent-callable internal-ops tool catalog를 반환한다. 현재 catalog에는 read operation과 approval-gated mutation operation이 함께 포함된다. +`GET /internal/ops/admin/tool-catalog`가 현재 Spring Boot에서 구현된 agent-callable internal-ops tool catalog를 반환한다. 현재 catalog에는 18개 read operation과 4개 mutation operation이 함께 포함된다. mutation의 approval 필요 여부는 `PolicyGuard`가 operation risk와 workspace policy로 결정한다. | Operation | Spring endpoint | Status | Result 요약 | | --- | --- | --- | --- | @@ -35,6 +35,9 @@ | `get_pipeline_topology` | `GET /internal/ops/projects/{projectId}/pipelines/{pipelineId}/topology` | implemented | pipeline topology | | `get_connector_status` | `GET /internal/ops/projects/{projectId}/kafka/connectors/{connectorName}/status` | implemented | `PipelineProvisionStatus` | | `list_connectors` | `GET /internal/ops/projects/{projectId}/kafka/connectors/status` | implemented | connector status list | +| `list_datasources` | `GET /internal/ops/projects/{projectId}/datasources` | implemented | datasource 목록, 역할, connection/readiness 상태 | +| `get_cluster_info` | `GET /internal/ops/projects/{projectId}/kafka/cluster` | implemented | Kafka broker/controller/topic partition 상태 | +| `sql_read` | `POST /internal/ops/projects/{projectId}/datasources/{datasourceId}/query` | implemented | datasource read-only SELECT 결과 | | `restart_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | implemented | approval/idempotency-gated mutation | | `pause_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | implemented | approval/idempotency-gated mutation | | `resume_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | implemented | approval/idempotency-gated mutation | @@ -59,4 +62,4 @@ Health/metadata endpoints(`health`, `ready`, `version`)도 `/internal/ops`에 ## 구현 범위 밖 -FastAPI tool registry에는 Agent 설계용 논리 tool alias도 포함된다. Spring `tool-catalog`는 runtime에서 실제 호출 가능한 operation 이름을 반환하므로 FastAPI의 `get_metrics`/`get_deployments`는 각각 Spring `query_metrics`/`get_recent_changes`에 대응하고, `get_kafka_lag`는 `get_consumer_lag`의 FastAPI alias다. 실행 가능한 Spring mutation subset은 [Spring Boot API §6.4](./springboot.md#64-mutation-endpoints)에 별도로 문서화되어 있다. +FastAPI tool registry에는 Agent 설계용 논리 tool alias도 포함된다. Spring `tool-catalog`는 runtime에서 실제 호출 가능한 operation 이름을 반환하므로 FastAPI의 `get_metrics`/`get_deployments`/`get_alerts`/`get_traces`는 각각 Spring `query_metrics`/`get_recent_changes`/`list_alerts`/`query_traces`에 대응하고, `get_kafka_lag`는 `get_consumer_lag`의 FastAPI alias다. 실행 가능한 Spring mutation subset은 [Spring Boot API §6.4](./springboot.md#64-mutation-endpoints)에 별도로 문서화되어 있다. diff --git a/docs/api/springboot.md b/docs/api/springboot.md index 5d97ad8c..4b8e1c76 100644 --- a/docs/api/springboot.md +++ b/docs/api/springboot.md @@ -216,7 +216,7 @@ OWNER 정책의 현재 코드 사실: ## Controller Coverage -이 파일은 account/workspace/members/settings/monitoring/runtime metadata schema를 상세 관리하고, 아래 18개 `@RestController`는 endpoint family 수준으로 커버한다. `@RestControllerAdvice`인 `GlobalExceptionHandler`는 controller coverage 카운트에서 제외한다. +이 파일은 account/workspace/members/settings/monitoring/runtime metadata schema를 상세 관리하고, 아래 22개 `@RestController`는 endpoint family 수준으로 커버한다. `@RestControllerAdvice`인 `GlobalExceptionHandler`는 controller coverage 카운트에서 제외한다. | Controller | Base path | 상세 수준 | 근거 | | --- | --- | --- | --- | @@ -232,9 +232,13 @@ OWNER 정책의 현재 코드 사실: | `ClusterController` | `/api/v1/clusters` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/cluster/ClusterController.java:17-44` | | `InternalController` | `/internal` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalController.java:26-88` | | `InternalOpsController` | `/internal/ops` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsController.java:24-98` | -| `InternalOpsObservabilityController` | `/internal/ops` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsObservabilityController.java:52-490` | -| `InternalOpsPipelineController` | `/internal/ops/projects/{projectId}/pipelines` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsPipelineController.java:30-90` | -| `InternalOpsMutationController` | `/internal/ops/projects/{projectId}` | mutation 상세 | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsMutationController.java:52-454` | +| `InternalOpsObservabilityController` | `/internal/ops` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsObservabilityController.java:71-633` | +| `InternalOpsPipelineController` | `/internal/ops/projects/{projectId}/pipelines` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsPipelineController.java:54-156` | +| `InternalOpsDatasourceController` | `/internal/ops/projects/{projectId}/datasources` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsDatasourceController.java:32-45` | +| `InternalOpsKafkaController` | `/internal/ops/projects/{projectId}/kafka/cluster` | family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsKafkaController.java:43-61` | +| `InternalOpsSqlController` | `/internal/ops/projects/{projectId}/datasources/{datasourceId}/query` | read 상세 | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsSqlController.java:40-68` | +| `InternalOpsSafeInjectionController` | `/internal/ops/projects/{projectId}/safe-injection` | test/fault-injection family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsSafeInjectionController.java:21-68` | +| `InternalOpsMutationController` | `/internal/ops/projects/{projectId}` | mutation 상세 | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/controller/InternalOpsMutationController.java:53-588` | | `ApprovalController` | `/internal/ops/approvals` | governance 상세 | `services/operations-backend/src/main/java/com/bifrost/ops/governance/approval/controller/ApprovalController.java:52-331` | | `ChangeTicketController` | `/internal/ops/change-tickets` | governance 상세 | `services/operations-backend/src/main/java/com/bifrost/ops/governance/changemanagement/controller/ChangeTicketController.java:40-168` | | `KafkaConnectorPocController` | `/internal/poc/kafka-connectors` | WIP/PoC family catalog | `services/operations-backend/src/main/java/com/bifrost/ops/internalops/poc/KafkaConnectorPocController.java:23-55` | @@ -250,9 +254,10 @@ Family catalog 요약: | Monitoring | overview/resource-events/incidents list/detail/detail facade/report list/report body 200 | `WorkspaceAccessGuard.requireAccess` | | Cluster | Kafka/connect/throughput 조회 200 | 인증 사용자, workspace scope 없음 | | Internal `/internal` | tenant provision 200, tenant delete 202, pipeline provision 202 또는 422, status 200, delete 202 | internal control-plane surface | -| Internal `/internal/ops` read | health/version/tool-catalog 200, ready 200 또는 503, connector status/list/topology/lag/log search/list_alerts/traces/incident summary 200 | agent-facing 내부 API. `internal.ops.token` 설정 시 `X-Internal-Token` service identity header가 필요하며, public frontend ingress는 이 경로를 프록시하지 않는다 | -| Internal `/internal/ops` mutation | connector restart/pause/resume, Kafka Connect consumer group restart: 성공 200, gate 실패 400/403/409, Connect 실패 502/504 | service identity gate 통과 후 controller가 `X-Agent-Run-Id`·`X-Agent-Step-Id`·`X-Idempotency-Key`·`X-Approval-Id`를 검증하고 `OpsEnvelope`로 응답 | -| Internal governance | approvals create 201/list/get/decision/validate 200, change-ticket create 201/approve 200/get/validate 200 | service identity gate 대상이다. approval decision과 change-ticket create/approve는 추가로 Spring Security principal이 필요하다 | +| Internal `/internal/ops` read | health/version/tool-catalog 200, ready 200 또는 503, connector status/list, datasource list, SQL read, Kafka cluster info, topology/lag/log search/metrics/alerts/traces/task trace/incident summary 200 | agent-facing 내부 API. `internal.ops.token` 설정 시 `X-Internal-Token` service identity header가 필요하며, public frontend ingress는 이 경로를 프록시하지 않는다 | +| Internal `/internal/ops` safe injection | connector fault injection create 200, cleanup 200, residuals 200 | 테스트/장애 주입용 내부 표면이며 runtime tool catalog에는 포함되지 않는다 | +| Internal `/internal/ops` mutation | connector restart/pause/resume, Kafka Connect consumer group restart: 성공 200, gate 실패 400/403/409, Connect 실패 502/504 | service identity gate 통과 후 controller가 `X-Agent-Run-Id`·`X-Agent-Step-Id`·`X-Idempotency-Key`를 필수 검증하고, `PolicyGuard` 결정에 따라 `X-Approval-Id` 또는 `X-Change-Ticket-Id`를 `MutationGate`에서 검증한다 | +| Internal governance | approvals create/preapproved 201, list/get/decision/validate 200, change-ticket create 201/approve 200/get/validate 200 | service identity gate 대상이다. approval decision과 change-ticket create/approve는 추가로 Spring Security principal이 필요하다 | | PoC | connector list/get/sample 200, get/delete not found 404, delete accepted 202 | 임시 PoC surface, 제거 또는 내부망 제한 필요 | ## 권한 매트릭스 @@ -270,7 +275,7 @@ Family catalog 요약: | `/api/v1/workspaces/{wsId}/kafka/principals` list | authenticated | workspace access | | `/api/v1/workspaces/{wsId}/kafka/principals` create/deactivate/revoke/rotate/secret | authenticated | OWNER/ADMIN 또는 workspace owner. Secret 조회는 principal `ACTIVE`와 Secret 네이밍/키 무결성까지 확인 | | `/api/v1/clusters/**` | authenticated | workspace scope 없음 | -| `/internal/ops/**` | optional service identity gate | `internal.ops.token` 설정 시 `X-Internal-Token`과 정확히 일치해야 한다. 토큰이 비어 있으면 로컬/기존 배포 호환을 위해 게이트가 비활성화된다. mutation은 추가로 agent headers/idempotency/approval/ownership을 코드에서 검증 | +| `/internal/ops/**` | optional service identity gate | `internal.ops.token` 설정 시 `X-Internal-Token`과 정확히 일치해야 한다. 토큰이 비어 있으면 로컬/기존 배포 호환을 위해 게이트가 비활성화된다. mutation은 추가로 agent headers/idempotency/policy approval 또는 change ticket/ownership을 코드에서 검증 | | `/internal/ops/approvals/{id}/decision` | service identity gate + method-level principal check | `SecurityContext`의 `AuthenticatedUser`가 `tenantId`/`decidedBy`와 일치하고 approval actor와 같아야 함 | ## Alias 제거 @@ -289,9 +294,9 @@ Family catalog 요약: - `X-Agent-Id` - `X-Actor-Type` - `X-Actor-Id` -- mutation 계열: `X-Idempotency-Key`, `X-Approval-Id` +- mutation 계열: `X-Idempotency-Key`, 정책상 필요한 경우 `X-Approval-Id` 또는 `X-Change-Ticket-Id` -현재 `InternalOpsMutationController`가 필수로 검사하는 헤더는 `X-Agent-Run-Id`, `X-Agent-Step-Id`, `X-Idempotency-Key`다. `X-Approval-Id`가 없으면 mutation은 403 `APPROVAL_REQUIRED`로 차단된다. `X-Agent-Name`, `X-Actor-Type`, `X-Actor-Id`는 FastAPI `ToolContext`가 전송하지만 Spring mutation controller의 필수 헤더 검증 대상은 아니다. +현재 `InternalOpsMutationController`가 필수로 검사하는 헤더는 `X-Agent-Run-Id`, `X-Agent-Step-Id`, `X-Idempotency-Key`다. `X-Approval-Id`와 `X-Change-Ticket-Id`는 UUID 형식만 controller에서 확인하고, 실제 필수 여부는 `PolicyGuard` 결정 후 `MutationGate`가 fail-closed로 판단한다. `X-Agent-Name`, `X-Actor-Type`, `X-Actor-Id`는 FastAPI `ToolContext`가 전송하지만 Spring mutation controller의 필수 헤더 검증 대상은 아니다. ## 4. Internal Ops Success Envelope @@ -331,7 +336,7 @@ Family catalog 요약: ### 6.1 Runtime tool catalog -`GET /internal/ops/admin/tool-catalog`는 agent가 호출 가능한 Spring internal-ops runtime tool catalog를 반환한다. 현재 catalog에는 read operation과 approval-gated mutation operation이 함께 포함된다. +`GET /internal/ops/admin/tool-catalog`는 agent가 호출 가능한 Spring internal-ops runtime tool catalog를 반환한다. 현재 catalog에는 18개 read operation과 4개 mutation operation, 총 22개 operation이 포함된다(`InternalOpsController.java:67-96`). | Operation | Method | Path | Result | | --- | --- | --- | --- | @@ -350,10 +355,13 @@ Family catalog 요약: | `get_pipeline_topology` | `GET` | `/internal/ops/projects/{projectId}/pipelines/{pipelineId}/topology` | `PipelineTopologyResult` | | `get_connector_status` | `GET` | `/internal/ops/projects/{projectId}/kafka/connectors/{connectorName}/status` | `PipelineProvisionStatus` | | `list_connectors` | `GET` | `/internal/ops/projects/{projectId}/kafka/connectors/status` | connector status list | -| `restart_connector` | `POST` | `/internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | mutation result, approval/idempotency required | -| `pause_connector` | `POST` | `/internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | mutation result, approval/idempotency required | -| `resume_connector` | `POST` | `/internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | mutation result, approval/idempotency required | -| `restart_consumer_group` | `POST` | `/internal/ops/projects/{projectId}/kafka/consumer-groups/{consumerGroup}/restart` | mutation result, approval/idempotency required | +| `list_datasources` | `GET` | `/internal/ops/projects/{projectId}/datasources` | datasource 목록 | +| `get_cluster_info` | `GET` | `/internal/ops/projects/{projectId}/kafka/cluster` | Kafka cluster broker/topic/group summary | +| `sql_read` | `POST` | `/internal/ops/projects/{projectId}/datasources/{datasourceId}/query` | read-only SQL query result | +| `restart_connector` | `POST` | `/internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | mutation result, idempotency required, approval may be required by policy | +| `pause_connector` | `POST` | `/internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | mutation result, idempotency required, current policy allows | +| `resume_connector` | `POST` | `/internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | mutation result, idempotency required, current policy allows | +| `restart_consumer_group` | `POST` | `/internal/ops/projects/{projectId}/kafka/consumer-groups/{consumerGroup}/restart` | mutation result, idempotency required, approval may be required by policy | `get_incident_summary`의 정본 경로는 project-scoped `GET /internal/ops/projects/{projectId}/incidents/{incidentId}/summary`다. project scope가 없는 legacy 경로 `GET /internal/ops/incidents/{incidentId}/summary`는 HTTP 400 + `OpsEnvelope.error(code=VALIDATION_FAILED, required_action="use_project_scoped_path")`로 거부한다(`InternalOpsObservabilityController.java:476-485`). @@ -364,6 +372,7 @@ Family catalog 요약: | Method | Path | Status | Request | Result | | --- | --- | --- | --- | --- | | `POST` | `/internal/ops/approvals` | `201` | `tenantId`, `toolName`, `paramsHash`(64 hex), `requiredApprover`, `expiresInMinutes`(1..1440) | `approvalId`, `tenantId`, `actor`, `operation`, `status`, `expiresAt`, `createdAt`, `paramsHash` | +| `POST` | `/internal/ops/approvals/preapproved` | `201` | `tenantId`, `toolName`, `paramsHash`(64 hex), `requiredApprover`, `expiresInMinutes`(1..1440) | `approvalId`, `tenantId`, `actor`, `operation`, `status="approved"`, `expiresAt`, `createdAt`, `paramsHash` | | `POST` | `/internal/ops/approvals/{approvalId}/decision` | `200` | `decision`(`approved`/`rejected`), `tenantId`, `decidedBy`, `comment` | `approvalId`, `tenantId`, `actor`, `operation`, `status`, `expiresAt`, `usedAt`, `createdAt` | | `POST` | `/internal/ops/approvals/{approvalId}/validate` | `200` | `tenantId`, `paramsHash` | `approvalId`, `status="validated"`, `usedAt` | | `GET` | `/internal/ops/approvals/{approvalId}?tenantId=` | `200` | query `tenantId` | `ApprovalResult` | @@ -399,9 +408,10 @@ Mutation 처리 순서: 2. `{projectId}` workspace namespace 조회와 connector/pipeline ownership 확인. workspace 없음은 `RESOURCE_NOT_FOUND`, 소유 불일치는 `RESOURCE_NOT_OWNED_BY_PROJECT`. `restart_consumer_group`은 이 단계에서 지원 consumer group(`connect-` prefix)·존재 여부도 먼저 검증한다. 3. `X-Approval-Id`/`X-Change-Ticket-Id` 형식 검사. 정책이 요구하는 증빙이 없으면 gate에서 fail-closed로 차단한다. 4. `IdempotencyGuard.check(idempotencyKey, tenantId, operation, paramsHash)`. -5. `PolicyGuard` 결정에 따라 `ApprovalValidator.validateAndConsume(approvalId, tenantId, operation, paramsHash)` 또는 `ChangeTicketValidator.validate(changeTicketId, tenantId, operation)`를 수행한다. -6. Kafka Connect REST mutation 실행. -7. 성공/실패 response snapshot을 idempotency row에 저장. +5. `PolicyGuard` 결정에 따라 allow, approval, change-ticket, deny 중 하나로 분기한다. 현재 `restart_connector`와 `restart_consumer_group`은 high risk라 workspace `aiProdLock=true`일 때 approval을 요구하고, `pause_connector`와 `resume_connector`는 approval 없이 통과한다. `update_connector`/`reset_offsets`는 change-management operation으로 분류되어 있지만 현재 runtime catalog endpoint는 없다(`PolicyGuard.java:19-57`). +6. 정책이 요구하면 `ApprovalValidator.validateAndConsume(approvalId, tenantId, operation, paramsHash)` 또는 `ChangeTicketValidator.validate(changeTicketId, tenantId, operation)`를 수행한다. +7. Kafka Connect REST mutation 실행. +8. 성공/실패 response snapshot을 idempotency row에 저장. Idempotency 결과: @@ -412,6 +422,7 @@ Idempotency 결과: | 같은 key 실행 중 | `409 CONFLICT` | `idempotency key is already processing` | | 같은 key + 다른 operation/params | `409 CONFLICT` | approval 검증 전 차단 | | replay approval id가 요청 `X-Approval-Id`와 다름 | `403 APPROVAL_SCOPE_MISMATCH` | cached result 재사용 차단 | +| replay change ticket id가 요청 `X-Change-Ticket-Id`와 다름 | `403 CHANGE_TICKET_REQUIRED` | cached result 재사용 차단 | Kafka Connect REST timeout은 HTTP 504 + `TIMEOUT`, 그 외 Connect REST 실패는 HTTP 502 + `UPSTREAM_UNAVAILABLE`로 `OpsEnvelope.error`에 저장되고 replay된다. `restart_consumer_group`은 `connect-` prefix의 Kafka Connect-managed sink connector consumer group만 지원하며, 미지원/미존재 group은 `CONSUMER_GROUP_NOT_FOUND` 또는 `UPSTREAM_UNAVAILABLE`로 반환된다. @@ -433,4 +444,4 @@ Spring Boot에는 [§6.2](#62-approval-facade)의 approval facade와 mutation ## 25. Admin API -`GET /internal/ops/admin/tool-catalog`는 구현된 runtime tool catalog를 반환한다. 현재 반환 항목은 [§6.1](#61-runtime-tool-catalog)의 read operation과 approval-gated mutation operation이다. +`GET /internal/ops/admin/tool-catalog`는 구현된 runtime tool catalog를 반환한다. 현재 반환 항목은 [§6.1](#61-runtime-tool-catalog)의 18개 read operation과 4개 mutation operation이다. diff --git a/docs/design/backend-fastapi/agent-principles.md b/docs/design/backend-fastapi/agent-principles.md index 8c0ae80b..55b098af 100644 --- a/docs/design/backend-fastapi/agent-principles.md +++ b/docs/design/backend-fastapi/agent-principles.md @@ -94,7 +94,7 @@ State에는 원문을 inline으로 넣지 않는다. 현재 Retrieval은 synthet 검증 실패는 예외가 아니라 workflow의 일부라는 설계 원칙은 유지한다. 현재 구현은 Verifier `fail`/`needs_revision`을 Supervisor에 기록해 책임 Agent로 loopback하고, 예산 초과 시 검증 안 된 결과를 Report로 보내지 않고 `failed`로 종료한다. 자세한 현재 분기 상태는 [§15 Workflow Control](contract/contract-workflow-control.md#15-contract-workflow-control)에 둔다. -#### 3.5 최소 충분 실행 [계획 §1] +#### 3.5 최소 충분 실행 자연어 질의는 의도에 필요한 만큼의 agent와 tool만 호출해야 한다. 단순 조회·지식 질의가 정해진 stage chain을 끝까지 타거나 ReAct 루프로 불필요한 tool을 반복 호출하면 비용·지연·오염(이전 장애 맥락이 단순 질의에 섞임)이 커진다. @@ -102,9 +102,9 @@ State에는 원문을 inline으로 넣지 않는다. 현재 Retrieval은 synthet - Router가 `execution_depth`와 tool budget(`max_tool_calls`, `allow_react_loop`)을 정해 실행 깊이를 통제한다. depth는 `direct_answer`/`single_lookup`/`bounded_lookup`/`incident_diagnosis`/`remediation_planning`/`action_execution`로 나뉜다. - 지식/용어 질의는 운영 tool 없이 단락하고, ReAct 루프는 인시던트 분석이나 식별자 chaining이 필요할 때만 켠다. -- 대화 이력은 agent별로 필터링한다(per-agent history input filter). 자세한 역할별 정의는 [§13 Agent Roles](contract/contract-agent-roles.md#13-contract-agent-roles) Router 절, depth→허용 stage 매핑은 [§15 Workflow Control](contract/contract-workflow-control.md#15-contract-workflow-control)을 정본으로 한다. +- 대화 이력은 depth별 `history_policy`로 제한한다. 자세한 역할별 정의는 [§13 Agent Roles](contract/contract-agent-roles.md#13-contract-agent-roles) Router 절, depth→허용 stage 매핑은 [§15 Workflow Control](contract/contract-workflow-control.md#15-contract-workflow-control)을 정본으로 한다. -현재 구현은 mode만으로 stage chain을 고정하므로 depth·budget 제어는 아직 없다(roadmap §1). 근거: [rca-standards-review.md](../rca-standards-review.md) §6·§7(roadmap §1). +현재 구현은 `RouterOutput`에 `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy`를 싣고, `transitions.py`가 depth별 stage와 tool budget을 정한다. `direct_answer`/`single_lookup`/`bounded_lookup`은 `planner -> retrieval -> report`의 짧은 simple-query 경로를 타며 Verifier를 건너뛴다. `incident_diagnosis`는 `correlation -> planner -> retrieval -> classifier -> rca -> verifier -> report`, `remediation_planning`은 RCA 뒤 `remediation -> policy_guard -> approval_gate`를 추가한 뒤 Verifier/Report로 간다. 남은 갭은 Router 휴리스틱 품질과 호출량 회귀 테스트다. ### 4. Alert와 Incident 상관관계 @@ -202,6 +202,8 @@ RCA는 세 가지를 반드시 분리한다. Confidence는 “원인 확정도”가 아니라 “현재 evidence 기준 운영상 판단 신뢰도”다. +현재 catalog는 `RootCause` 35개를 정의하지만 RCA evidence profile은 unknown 계열 3개를 제외한 actionable root cause 32개에 붙어 있다. `EvidenceProfile`은 `required`/`supporting`/`negative`/`exclusion` 규칙과 `min_confidence_for_action=0.80`, `needs_more_evidence_band=(0.60, 0.79)` 기본값을 가진다. + 초기 해석: | Confidence | 의미 | @@ -210,9 +212,9 @@ Confidence는 “원인 확정도”가 아니라 “현재 evidence 기준 운 | `0.60 - 0.79` | 유력하지만 추가 확인 필요 | | `< 0.60` | 확정 불가 | -이 값은 운영 데이터로 보정한다. 필수 evidence가 빠진 후보는 높은 confidence를 받을 수 없다. +RCA의 현재 폴백 기준은 `MIN_CONFIDENT_ROOT_CAUSE=0.60`이다. 최상위 후보가 이 값보다 낮으면 `UNKNOWN_WITH_EVIDENCE_GAP`으로 보류한다. `0.80`은 evidence profile의 기본 조치 가능 신뢰도 기준이며, 최종 운영 임계값은 ECE/AC@k와 resolved incident 데이터로 보정해야 한다. -#### 7.1 인과 ≠ 상관 · temporality 게이트 [계획 §12] +#### 7.1 인과 ≠ 상관 · temporality 게이트 "근본 원인"으로 지정하려면 단순 상관(동시 발생)을 넘어 인과 근거가 필요하다. 순수 통계적 상관 신호만으로는 인과를 주장하지 않는다(Pearl 인과 사다리 1단계 association, Bradford Hill Temporality). @@ -220,10 +222,10 @@ Confidence는 “원인 확정도”가 아니라 “현재 evidence 기준 운 - **Temporality를 인과 증거의 필수 게이트**로 둔다. change/배포 이벤트가 증상 발생에 시간적으로 선행할 때만 인과 증거(required)로 승격한다. - 단순 동시 발생(co-occurrence)은 association으로 보고 `supporting`까지만 인정한다. -- required 증거는 "근본원인 → 증상" 인과 사슬 순서로 구성하고, 사슬 일관성으로 confidence를 산정한다. +- required 증거는 "근본원인 → 증상" 인과 사슬 순서로 구성하고, 사슬 일관성을 explanation에 반영한다. - `negative` 증거(대안 원인 시그니처)는 조잡한 counterfactual 검사로 보고 명시적으로 강화한다. -현재 `EvidenceRule`은 `kind`·`semantic_allowed`만 가져 동시발생과 인과 증거를 구조적으로 구분하지 못한다. `causality_type`·`temporality_required`·`causal_chain_step` 추가는 roadmap §12다. 근거: [rca-standards-review.md](../rca-standards-review.md) §4.2·§7(roadmap §12). +현재 `EvidenceRule`은 `kind`, `semantic_allowed`, `causality_type`, `temporality_required`, `causal_chain_step`을 가진다. `rca.py`는 `temporality_required=True`인 required evidence가 시간 선행 표현을 갖지 못하면 required 충족에서 제외하고 supporting으로 강등한다. 남은 갭은 이 필드를 모든 profile에 일관되게 보강하고 평가셋으로 confidence 보정 효과를 검증하는 것이다. ### 8. 대응과 권한 diff --git a/docs/design/backend-fastapi/catalog/catalog-evidence-matrix.md b/docs/design/backend-fastapi/catalog/catalog-evidence-matrix.md index b6adbca1..54e541dd 100644 --- a/docs/design/backend-fastapi/catalog/catalog-evidence-matrix.md +++ b/docs/design/backend-fastapi/catalog/catalog-evidence-matrix.md @@ -13,7 +13,7 @@ RCA Agent는 점수만 보고 원인을 확정하지 않는다. Required evidenc ### 2. Evidence 유형 -**[현재]** 코드의 `EvidenceRule`(`app/catalogs/types.py`)은 `kind`(`required`/`supporting`/`negative`/`exclusion`)와 `semantic_allowed` 두 분류 필드만 갖는다. 즉 현재 evidence는 "역할(kind)"로만 구분되고, 인과/상관 구분은 구조적으로 없다. +**[현재]** 코드의 `EvidenceRule`(`app/catalogs/types.py`)은 `kind`(`required`/`supporting`/`negative`/`exclusion`), `semantic_allowed`, `causality_type`(`causal`/`correlational`/`temporal`), `temporality_required`, `causal_chain_step`을 가진다. 일부 profile은 시간 선행성 있는 evidence만 required로 인정하도록 `temporality_required=True`를 사용한다. | 유형 (`kind`) | 의미 | | --- | --- | @@ -26,35 +26,36 @@ RCA Agent는 점수만 보고 원인을 확정하지 않는다. Required evidenc ### 3. Confidence 기준 -**[현재]** 코드 기준값은 다음과 같다. `EvidenceProfile.min_confidence_for_action=0.80`, `needs_more_evidence_band=(0.60, 0.79)`, `RootCause.default_confidence_cap=0.79`(필수 evidence 부분 충족 시 상한)이다. RCA Agent(`app/agents/rca.py`)는 최상위 후보가 `MIN_CONFIDENT_ROOT_CAUSE=0.60` 미만이면 `UNKNOWN_WITH_EVIDENCE_GAP`으로 폴백한다. +**[현재]** 코드 기준값은 다음과 같다. `EvidenceProfile.min_confidence_for_action=0.80`, `needs_more_evidence_band=(0.60, 0.79)`, `RootCause.default_confidence_cap=0.88`이다. RCA Agent(`app/agents/rca.py`)는 required evidence 부분 충족 scoring에서 action-ready가 아닌 낮은 confidence로 상한을 두며, 최상위 후보가 `MIN_CONFIDENT_ROOT_CAUSE=0.60` 미만이면 `UNKNOWN_WITH_EVIDENCE_GAP`으로 폴백한다. | Confidence | 의미 | 처리 | | --- | --- | --- | | `>= 0.80` | 강한 후보 (`min_confidence_for_action`) | 대응안 생성 가능 | -| `0.60 - 0.79` | 유력하지만 추가 확인 필요 (`needs_more_evidence_band`, `default_confidence_cap=0.79`) | 추가 evidence 또는 제한적 대응 | +| `0.60 - 0.79` | 유력하지만 추가 확인 필요 (`needs_more_evidence_band`) | 추가 evidence 또는 제한적 대응 | | `< 0.60` | 확정 불가 (`MIN_CONFIDENT_ROOT_CAUSE`) | unknown 또는 추가 조사 | 기준은 과거 incident replay로 보정한다. 임의로 threshold를 바꾸지 않는다. -### 3.1 인과/상관 증거 구분 [계획 §12] +### 3.1 인과/상관 증거 구분 -> 아래는 to-be 설계이며 현재 코드에는 없다. `EvidenceRule`을 인과 사다리에 매핑하기 위한 확장이다. 근거는 [RCA 표준 검토 §4.2](../../rca-standards-review.md)(Bradford Hill의 Temporality, Pearl의 인과 사다리)와 §7 로드맵 item 12를 따른다. +> 아래 필드는 현재 코드에 구현되어 있다. 남은 작업은 모든 profile에 일관되게 태그를 보강하고 평가셋으로 confidence 보정 효과를 검증하는 것이다. 근거는 [RCA 표준 검토 §4.2](../../rca-standards-review.md)(Bradford Hill의 Temporality, Pearl의 인과 사다리)와 §7 로드맵 item 12를 따른다. -현재 evidence는 `kind`(역할)로만 구분되므로, 단순 동시발생(co-occurrence)과 시간 선행성(temporality)이 있는 인과 증거를 구조적으로 구분하지 못한다. `EvidenceRule`에 다음 필드를 추가한다. +현재 evidence는 `kind`(역할)에 더해 다음 인과 관련 필드로 단순 상관, 인과 연결, 시간 선행성을 구분한다. -| 추가 필드 | 값 | 의미 | +| 필드 | 값 | 의미 | | --- | --- | --- | -| `causality_type` | `association` / `temporal` / `counterfactual` | Pearl 인과 사다리 단계. `association`은 상관(rung-1)이며 인과 주장 불가 | -| `temporality_required` | bool | true이면 원인 신호가 증상 발생에 **선행**한다는 시간 근거가 있어야 인정 | -| `causal_chain_step` | int | "근본원인 → 증상" 인과 사슬에서의 순서. 사슬 일관성으로 confidence를 산정 | +| `causality_type` | `causal` / `correlational` / `temporal` | causal은 직접 인과 근거, correlational은 상관 근거, temporal은 시간 선행성 근거 | +| `temporality_required` | bool | true이면 원인 신호가 증상 발생에 **선행**한다는 시간 근거가 있어야 required match로 인정 | +| `causal_chain_step` | int | "근본원인 → 증상" 인과 사슬에서의 순서. explanation에 causal chain으로 반영 | -**required 승격 규칙 [계획 §12]** +**required 인정 규칙** -1. 단순 동시발생(`causality_type=association`)은 인과 주장이 불가하므로 `supporting`까지만 인정한다. -2. `temporality_required=True`를 만족하는 증거(원인이 증상에 시간상 선행)만 `required` 인과 증거로 승격한다. +1. 단순 동시발생(`causality_type=correlational`)은 인과 주장이 약하므로 supporting 근거로 제한한다. +2. `temporality_required=True`인 required evidence는 원인이 증상에 시간상 선행해야 required match로 인정한다. - 예: change/배포 이벤트 타임스탬프가 증상 발생에 선행할 때만 `RECENT_*_REGRESSION`의 변경 evidence를 required 인과로 인정한다. -3. `negative` 증거는 "대안 원인 시그니처가 있으면 후보 약화"라는 조잡한(crude) counterfactual 검사임을 명시한다. 강한 인과 반증이 아니라 후보 배제용 약한 신호다. -4. required 증거는 `causal_chain_step` 순서로 "근본원인 → 증상" 사슬을 구성하고, 사슬이 여러 신호에서 일관되게 성립하면 confidence를 상향한다. +3. 시간 선행성이 없는 `temporality_required=True` match는 RCA scoring에서 required 충족으로 보지 않고 supporting evidence로 강등한다. +4. `negative` 증거는 대안 원인 시그니처가 있으면 후보를 약화하는 후보 배제용 신호다. +5. required/supporting 증거는 `causal_chain_step` 순서로 "근본원인 → 증상" 사슬 explanation에 반영한다. ### 4. Source Root Cause @@ -106,6 +107,7 @@ RCA Agent는 점수만 보고 원인을 확정하지 않는다. Required evidenc | 여러 pipeline에서 같은 source endpoint 연결 실패 | Supporting | shared dependency timeout | | 고객사 source 내부 지표 정상이나 network path error 존재 | Supporting | network error code 증가 | | auth error 또는 query error만 존재 | Negative | network 후보 약화 | +| sink dependency 연결 실패 또는 sink connector 오류 | Negative | sink-context면 source reachability 약화(#962) | ### 5. Pipeline / Connector Root Cause @@ -208,8 +210,8 @@ RCA Agent는 점수만 보고 원인을 확정하지 않는다. Required evidenc | Evidence | 유형 | 예시 | | --- | --- | --- | -| sink write timeout 증가 | Required | sink connector write timeout | -| sink dependency connection error | Required | reachability or pool error | +| sink dependency 연결 실패 또는 connection timeout | Required | connection refused, no route to host, connection timeout, pool error | +| sink write timeout 증가 | Supporting | sink connector write timeout | | source read 정상 | Supporting | upstream 정상 | | sink write latency 증가 | Supporting | write duration p95 증가 | | source extract timeout | Negative | source 후보 우선 | @@ -312,7 +314,7 @@ RCA Agent는 점수만 보고 원인을 확정하지 않는다. Required evidenc | Evidence | 유형 | 예시 | | --- | --- | --- | | image rollout 이후 error/latency/restart 증가 | Required | deployment rollout event | -| 이전 image 대비 config/runtime 차이 | Required | image tag diff | +| image version update | Required | image tag/version changed, runtime/config delta | | rollback 후 개선 | Supporting | after evidence | | rollout 전부터 문제 지속 | Negative | image regression 후보 약화 | @@ -391,4 +393,3 @@ RCA Agent는 점수만 보고 원인을 확정하지 않는다. Required evidenc 보정값 변경은 catalog version과 함께 기록한다. --- - diff --git a/docs/design/backend-fastapi/catalog/catalog-incident-root-cause-map.md b/docs/design/backend-fastapi/catalog/catalog-incident-root-cause-map.md index 1faf1a39..3301ba3f 100644 --- a/docs/design/backend-fastapi/catalog/catalog-incident-root-cause-map.md +++ b/docs/design/backend-fastapi/catalog/catalog-incident-root-cause-map.md @@ -33,7 +33,7 @@ | Incident type | 우선 root cause 후보 | | --- | --- | | `PIPELINE_TASK_FAILED` | `PIPELINE_TASK_RETRY_EXHAUSTED`, `PIPELINE_CONFIG_INVALID`, `DEPLOYMENT_REGRESSION`, `RECENT_CONFIG_CHANGE_REGRESSION` | -| `CONNECTOR_TASK_FAILED` | `CONNECTOR_TASK_FAILED`, `SCHEMA_MISMATCH`, `PIPELINE_CONFIG_INVALID`, `SOURCE_DB_CONNECTION_TIMEOUT`, `SINK_DB_CONNECTION_TIMEOUT` | +| `CONNECTOR_TASK_FAILED` | `CONNECTOR_TASK_FAILED`, `SCHEMA_MISMATCH`, `PIPELINE_CONFIG_INVALID`, `SOURCE_AUTH_EXPIRED`, `SINK_AUTH_EXPIRED`, `SINK_DB_CONNECTION_TIMEOUT`, `SOURCE_DB_CONNECTION_TIMEOUT`, `SOURCE_NETWORK_REACHABILITY`, `SINK_CONSTRAINT_VIOLATION` | | `CONNECTOR_WORKER_UNHEALTHY` | `CONNECTOR_WORKER_REBALANCE_LOOP`, `POD_CRASH_LOOP`, `NODE_PRESSURE` | | `PIPELINE_RETRY_BACKOFF` | `PIPELINE_TASK_RETRY_EXHAUSTED`, `SOURCE_DB_CONNECTION_TIMEOUT`, `SINK_DB_CONNECTION_TIMEOUT`, `BROKER_RESOURCE_PRESSURE` | | `SCHEMA_MISMATCH` | `SCHEMA_MISMATCH`, `RECENT_SCHEMA_CHANGE_REGRESSION`, `SINK_CONSTRAINT_VIOLATION` | diff --git a/docs/design/backend-fastapi/overview.md b/docs/design/backend-fastapi/overview.md index 070efe70..6c352970 100644 --- a/docs/design/backend-fastapi/overview.md +++ b/docs/design/backend-fastapi/overview.md @@ -40,7 +40,7 @@ flowchart TB SUP --> LLM[LLM Provider
역할별 tier] ``` -워크플로는 **evidence 기반 판단·생성 8개 LLM agent**와 **룰/도구 실행 결정론적 단계**로 나뉘고, Supervisor가 분기·재시도·승인 게이트·루프 가드를 제어한다. +워크플로는 **evidence 기반 판단·생성 8개 LLM agent**와 **룰/도구 실행 결정론적 단계**로 나뉘고, Supervisor가 mode·execution depth별 분기, 재시도, 승인 게이트, 루프 가드를 제어한다. **LLM agent (8)**: `Router`(mode 재판정) → `Planner`(수집 계획) → `Retrieval`(RAG·read tool) → `Classifier`(유형 분류) → `RCA`(원인 후보 선택) → `Remediation`(조치 후보) → `Verifier`(검증 차단기) → `Report`(최종 응답). **결정론적 단계**: `Correlation Engine`(alert 병합) · `Policy Guard`(allow/approval/change/deny) · `Approval/Change Gate` · `Executor`(승인된 tool 실행). @@ -54,7 +54,7 @@ flowchart LR V -->|fail/needs_revision| P ``` -현재 구현은 router가 사용자 메시지마다 mode를 다시 판정하고, mode별 transition table을 실행한다. `action_execution`/`approval_decision`은 같은 run의 이전 action 후보와 policy 결정을 State patch에서 복원해 재사용한다. `verifier` 결과가 `fail`/`needs_revision`이면 Supervisor가 책임 Agent로 loopback을 등록하고, `fail_loops`/`gap_loops` 예산 초과 시 Report로 보내지 않고 run을 `failed`로 종료한다. +현재 구현은 router가 사용자 메시지마다 mode와 execution depth를 다시 판정하고, mode+depth별 transition table을 실행한다. 단순 조회 depth(`direct_answer`/`single_lookup`/`bounded_lookup`)는 `planner -> retrieval -> report`로 끝나며 Verifier를 건너뛴다. `incident_analysis`는 기본적으로 `correlation -> planner -> retrieval -> classifier -> rca -> verifier -> report`를 타고, remediation depth/요청이 있으면 RCA 뒤 `remediation -> policy_guard -> approval_gate`가 추가된다. `action_execution`/`approval_decision`은 같은 run의 이전 action 후보와 policy 결정을 State patch에서 복원해 재사용한다. `verifier` 결과가 `fail`/`needs_revision`이면 Supervisor가 책임 Agent로 loopback을 등록하고, `fail_loops`/`gap_loops` 예산 초과 시 Report로 보내지 않고 run을 `failed`로 종료한다. ## 데이터 — agentdb ERD @@ -76,11 +76,12 @@ erDiagram | 항목 | 내용 | | --- | --- | | mode | `simple_query` / `incident_analysis`(기본 `diagnose_only`) / `action_execution` / `approval_decision` — 매 메시지 재판정 | +| execution depth | `direct_answer` / `single_lookup` / `bounded_lookup` / `incident_diagnosis` / `remediation_planning` / `action_execution` — stage와 tool budget을 제한 | | evidence-first | State엔 원문 inline 금지(`evidence_id`/`store_ref`/`summary`만), 수집 단계 redaction | -| catalog 제한 | 장애유형·root cause·evidence·runbook·policy 밖 생성 금지, 불충분 시 `UNKNOWN_WITH_EVIDENCE_GAP` | +| catalog 제한 | 장애유형·root cause·evidence·runbook·policy 밖 생성 금지, 불충분 시 `UNKNOWN_WITH_EVIDENCE_GAP`. 현재 root cause catalog는 35개 ID이고 actionable 32개에 evidence profile이 붙는다 | | Verifier 차단기 | `pass`만 Report로 진행한다. `fail`은 `fail_loops`, `needs_revision`은 `gap_loops` 예산 안에서 책임 Agent로 loopback하며, 예산 초과 시 Report stage 없이 `failed` 종료한다 | | 종료 보장 | 현재 구현은 step/gap/fail/scope/revise_action counter guard를 중앙 집행한다. token/time budget은 policy field만 있고 guard check에는 없다 | -| SoT / MCP | Approval 원본·실행 allowlist = **Spring**. FastAPI approval link는 현재 in-memory facade 상태이며 executor는 Spring mutation에 `X-Approval-Id`를 전달하지 않는다 · MCP v1 미사용 | +| SoT / MCP | Approval 원본·실행 allowlist = **Spring**. FastAPI approval link는 local facade이며 사용자 승인 후 Spring pre-approved approval id를 저장할 수 있다. Executor는 approved action의 `approval_id`/`change_ticket_id`를 Spring mutation header로 전달한다 · MCP v1 미사용 | | 분산 추적(#372) | OTel 자동 계측(FastAPI·httpx) + runner 가 여는 루트 span `agent.run`. run 은 `BackgroundTasks` 로 요청 span 밖에서 돌아 자동 계측만으론 안 묶이므로 루트 span 을 직접 연다. `httpx` 가 Spring 호출에 `traceparent` 주입 → Spring(#366)이 추출해 **한 trace**. `AI_OTLP_TRACING_ENDPOINT` 설정 시에만 활성(로컬/CI 비활성). Collector tail-sampling(#370) 공유 | ## 더 읽기 diff --git a/docs/design/backend-fastapi/tool-catalog.md b/docs/design/backend-fastapi/tool-catalog.md index 378072c6..90e84c47 100644 --- a/docs/design/backend-fastapi/tool-catalog.md +++ b/docs/design/backend-fastapi/tool-catalog.md @@ -120,7 +120,7 @@ Tool result는 Agent State에 들어가기 전에 표준화한다. | RCA | evidence 조회 결과 참조, 추가 read 요청 | | Remediation | action template 조회, mutation 실행 금지 | | Policy Guard | policy/runbook/action catalog 조회, approval 필요 여부 판단 | -| Executor | runtime tool 실행. 현재 FastAPI executor는 `X-Idempotency-Key`만 만들고 `X-Approval-Id`를 Spring으로 전파하지 않아, Spring mutation은 approval 누락 시 403 `APPROVAL_REQUIRED`로 차단된다 | +| Executor | runtime tool 실행. 승인된 mutation은 action별 `approval_id`와 `change_ticket_id`를 `ToolContext`에 실어 Spring `X-Approval-Id`/`X-Change-Ticket-Id`로 전파한다 | | Verifier | read-only after-check tool | | Report | tool 직접 호출 금지 | @@ -128,7 +128,7 @@ Report는 tool을 직접 호출하지 않는다. 검증된 State만 사용한다 ### 8. Runtime Tool Catalog -현재 FastAPI `ToolClientRegistry`에는 20개 논리 tool definition이 있다. 이 표는 FastAPI registry의 실제 목록이고, Spring `GET /internal/ops/admin/tool-catalog`와 동일하지 않다. +현재 FastAPI `ToolClientRegistry`에는 23개 논리 tool definition이 있다. 이 표는 FastAPI registry의 실제 목록이고, Spring `GET /internal/ops/admin/tool-catalog`와 동일하지 않다. 구성은 read-only 19개와 approval-gated mutation 4개다. | Tool | Operation | Method | Path template | Risk | Approval | | --- | --- | --- | --- | --- | --- | @@ -137,6 +137,9 @@ Report는 tool을 직접 호출하지 않는다. 검증된 State만 사용한다 | `get_deployments` | `get_recent_changes` | `GET` | `/internal/ops/projects/{project_id}/pipelines/changes` | `read_only` | no | | `get_connector_status` | `get_connector_status` | `GET` | `/internal/ops/projects/{project_id}/kafka/connectors/{connector_name}/status` | `read_only` | no | | `list_connectors` | `list_connectors` | `GET` | `/internal/ops/projects/{project_id}/kafka/connectors/status` | `read_only` | no | +| `list_datasources` | `list_datasources` | `GET` | `/internal/ops/projects/{project_id}/datasources` | `read_only` | no | +| `get_cluster_info` | `get_cluster_info` | `GET` | `/internal/ops/projects/{project_id}/kafka/cluster` | `read_only` | no | +| `sql_read` | `sql_read` | `POST` | `/internal/ops/projects/{project_id}/datasources/{datasource_id}/query` | `read_only` | no | | `get_consumer_lag` | `get_consumer_lag` | `GET` | `/internal/ops/projects/{project_id}/kafka/consumer-groups/{consumer_group}/lag` | `read_only` | no | | `get_consumer_groups` | `get_consumer_groups` | `GET` | `/internal/ops/projects/{project_id}/kafka/consumer-groups` | `read_only` | no | | `get_kafka_lag` | `get_consumer_lag` | `GET` | `/internal/ops/projects/{project_id}/kafka/consumer-groups/{consumer_group}/lag` | `read_only` | no. Alias for `get_consumer_lag` | @@ -153,7 +156,7 @@ Report는 tool을 직접 호출하지 않는다. 검증된 State만 사용한다 | `get_alerts` | `list_alerts` | `GET` | `/internal/ops/projects/{project_id}/observability/alerts` | `read_only` | no | | `analyze_event_log` | `analyze_event_log` | `GET` | `/internal/ops/projects/{project_id}/observability/events/summary` | `read_only` | no | -Spring runtime catalog는 현재 18개 operation을 반환한다(read operation + approval-gated mutation). FastAPI registry는 사용자 친화 alias(`get_metrics`, `get_deployments`, `get_alerts`, `get_traces`, `get_kafka_lag`)와 Spring catalog operation 이름을 분리할 수 있다. +Spring runtime catalog는 현재 22개 operation을 반환한다(read operation 18개 + mutation 4개). FastAPI registry는 사용자 친화 alias(`get_metrics`, `get_deployments`, `get_alerts`, `get_traces`, `get_kafka_lag`)와 Spring catalog operation 이름을 분리할 수 있다. Spring mutation의 approval 필요 여부는 `PolicyGuard`가 operation risk와 workspace policy로 결정한다. | Spring catalog operation | FastAPI registry 대응 | | --- | --- | @@ -171,25 +174,28 @@ Spring runtime catalog는 현재 18개 operation을 반환한다(read operation | `get_pipeline_topology` | `get_pipeline_topology` | | `get_connector_status` | `get_connector_status` | | `list_connectors` | `list_connectors` | +| `list_datasources` | `list_datasources` | +| `get_cluster_info` | `get_cluster_info` | +| `sql_read` | `sql_read` | | `restart_connector` | `restart_connector` | | `pause_connector` | `pause_connector` | | `resume_connector` | `resume_connector` | | `restart_consumer_group` | `restart_consumer_group` | -현재 path/operation 매핑과 result schema는 완전히 호환되지 않는다. FastAPI `ToolResult`는 strict schema를 검증하므로 그대로 호출하면 일부 read tool은 response parsing 실패가 날 수 있다. 예: Spring `get_consumer_lag`는 `consumerGroup`/`totalLag`/`source`를 반환하지만 FastAPI schema는 `consumer_group`/`total_lag`와 optional `partitions`를 요구한다. Spring `search_logs`는 `logs`/`total`/`note`를 반환하지만 FastAPI schema는 `match_count`/`summary`를 요구한다. Spring `get_connector_status`는 `connectorName`/`connectorState`/`tasks[].id`를 반환하지만 FastAPI schema는 `connector_name`/`state`/`tasks[].task_id`를 요구한다. Spring `get_incident_summary`는 `incidentId`/`status`/`note`만 반환하지만 FastAPI schema는 `incident_id`/`severity`/`trigger_event`/`related_event_count`/`grouping_key`를 요구한다. Spring `list_project_pipelines`는 bare `PipelineResponse[]`를 반환하지만 FastAPI schema는 `{"pipelines": [...]}` object를 요구한다. Spring `get_pipeline_topology`는 `pipelineId`/`sourceDbId`/`topic`/`sourceConnector`/`sinkConnector` 형태지만 FastAPI schema는 `pipeline_id`/nested `source`/`topics`/connector `cr_name` 형태를 요구한다. +FastAPI result schema는 Spring 응답의 camelCase와 일부 flat field를 수용하도록 `SpringResponseModel` alias/normalizer를 사용한다. 예: `search_logs`는 `total`/`note`를 `match_count`/`summary`로 보강하고, `get_connector_status`는 `connectorState`와 `tasks[].id`를 수용하며, `get_pipeline_topology`는 `sourceDbId`/`sinkDbId`/`topic`을 nested `source`/`sink`/`topics`로 normalize한다. `SpringOpsClient`는 bare list 응답을 필요한 wrapper 형태로 감싼다. -FastAPI `get_kafka_lag`는 별도 Spring operation이 아니라 `get_consumer_lag` alias다. FastAPI `get_connector_task_trace`는 Spring endpoint를 가리키지만 현재 Spring admin `tool-catalog`에는 별도 operation으로 노출되지 않는다. +FastAPI `get_kafka_lag`는 별도 Spring operation이 아니라 `get_consumer_lag` alias다. `get_connector_task_trace`는 Spring admin `tool-catalog`에도 별도 operation으로 노출된다. ### 9. Mutation Runtime Tool Catalog -현재 Spring에 구현된 mutation은 Kafka Connect 계열 4개뿐이다. Spring endpoint는 모두 `X-Agent-Run-Id`, `X-Agent-Step-Id`, `X-Idempotency-Key`, `X-Approval-Id`가 필요하고 `ApprovalValidator`와 `IdempotencyGuard`를 통과해야 한다. FastAPI executor의 현재 `ToolContext`는 `X-Approval-Id`를 전송하지 않는다. +현재 Spring에 구현된 mutation은 Kafka Connect 계열 4개뿐이다. Spring endpoint의 필수 agent header는 `X-Agent-Run-Id`, `X-Agent-Step-Id`, `X-Idempotency-Key`다. `X-Approval-Id`와 `X-Change-Ticket-Id`는 nullable이며, `PolicyGuard` 결과가 `REQUIRE_APPROVAL`이면 `ApprovalValidator`, `REQUIRE_CHANGE_MANAGEMENT`이면 `ChangeTicketValidator`를 통과해야 한다. FastAPI executor는 approved action의 governance 식별자를 `ToolContext`로 전달하며, 정책상 필요한 식별자가 없으면 Spring gate 또는 registry 승인/변경관리 요구로 차단된다. | Agent 논리 tool | Spring operation | 정책 | 현재 구현 | | --- | --- | --- | --- | -| `restart_connector` | `restart_connector` | approval | 구현됨 | -| `pause_connector` | `pause_connector` | approval | 구현됨 | -| `resume_connector` | `resume_connector` | approval | 구현됨 | -| `restart_consumer_group` | `restart_consumer_group` | approval | 구현됨. `connect-` prefix의 Kafka Connect-managed sink connector consumer group만 지원 | +| `restart_connector` | `restart_connector` | `PolicyGuard`: high risk, aiProdLock이면 approval 필요 | 구현됨 | +| `pause_connector` | `pause_connector` | `PolicyGuard`: medium risk, 기본 allow | 구현됨 | +| `resume_connector` | `resume_connector` | `PolicyGuard`: low risk, 기본 allow | 구현됨 | +| `restart_consumer_group` | `restart_consumer_group` | `PolicyGuard`: high risk, aiProdLock이면 approval 필요 | 구현됨. `connect-` prefix의 Kafka Connect-managed sink connector consumer group만 지원 | 현재 구현에 없는 mutation: @@ -207,7 +213,7 @@ FastAPI `get_kafka_lag`는 별도 Spring operation이 아니라 `get_consumer_la | Action | action_type | 처리 | | --- | --- | --- | -| `collect_source_timeout_evidence` | `workflow_action` | 설계상 `search_logs`, `get_connector_status`, `get_pipeline_topology`, `list_project_pipelines` 조합이지만 현재 `get_pipeline_topology`/`list_project_pipelines`는 Spring result shape과 FastAPI schema가 맞지 않아 adapter 없이는 직접 실행 후보로 보면 안 된다 | +| `collect_source_timeout_evidence` | `workflow_action` | 설계상 `search_logs`, `get_connector_status`, `get_pipeline_topology`, `list_project_pipelines` 조합으로 변환 | | `collect_connector_trace` | `workflow_action` | `get_traces`(Tempo 분산 trace), `get_connector_task_trace`(task 예외 stack trace, #373), `search_logs`, `get_connector_status` 실행 계획으로 변환 | | `collect_lag_evidence` | `workflow_action` | `get_consumer_lag`, `get_connector_status`, `search_logs`, `get_alerts` 실행 계획으로 변환 | | `collect_additional_evidence` | `workflow_action` | Planner가 현재 registry에 있는 read-only tool만 사용해 추가 retrieval plan 생성 | diff --git a/docs/design/backend-springboot/governance.md b/docs/design/backend-springboot/governance.md index d9da86b7..31900825 100644 --- a/docs/design/backend-springboot/governance.md +++ b/docs/design/backend-springboot/governance.md @@ -10,11 +10,11 @@ | 범위 | 구현 | | --- | --- | -| runtime tools | `/internal/ops/admin/tool-catalog`의 read operation + approval-gated mutation operation과 health/ready/version | +| runtime tools | `/internal/ops/admin/tool-catalog`의 18개 read operation + 4개 mutation operation과 health/ready/version | | governance facade | `/internal/ops/approvals/**`, `/internal/ops/change-tickets/**` | | mutation subset | connector restart/pause/resume, Kafka Connect-managed consumer group restart | -`/internal/ops/**`는 agent-facing 내부 API이며 public frontend ingress에 노출하지 않는다. `SecurityConfig`는 `internal.ops.token`이 설정된 경우 `X-Internal-Token` service identity header 일치를 요구한다. 토큰이 비어 있으면 로컬/기존 배포 호환을 위해 게이트를 비활성화한다. Mutation controller가 추가로 적용하는 gate는 agent header, workspace/resource ownership, approval, idempotency, Kafka Connect REST 결과 mapping이다. +`/internal/ops/**`는 agent-facing 내부 API이며 public frontend ingress에 노출하지 않는다. `SecurityConfig`는 `internal.ops.token`이 설정된 경우 `X-Internal-Token` service identity header 일치를 요구한다. 토큰이 비어 있으면 로컬/기존 배포 호환을 위해 게이트를 비활성화한다. Mutation controller가 추가로 적용하는 gate는 agent header, workspace/resource ownership, idempotency, `PolicyGuard` 기반 approval 또는 change-ticket, Kafka Connect REST 결과 mapping이다. ### 2. Mutation 처리 순서 @@ -25,15 +25,18 @@ [2] X-Agent-Run-Id / X-Agent-Step-Id / X-Idempotency-Key 필수 header 검사 [3] workspace namespace 기반 project 조회 [4] connector 또는 consumer group ownership 검사 -[5] X-Approval-Id 존재 검사 +[5] X-Approval-Id / X-Change-Ticket-Id UUID 형식 검사 [6] IdempotencyGuard.check(tenantId, operation, paramsHash) -[7] ApprovalValidator.validateAndConsume(approvalId, tenantId, operation, paramsHash) -[8] Kafka Connect REST mutation 실행 -[9] idempotency row에 response snapshot 저장 -[10] OpsEnvelope 반환 +[7] MutationGate.executeChecked +[8] PolicyGuard.evaluate 결과에 따라 allow, approval, change-ticket, deny 분기 +[9] 필요한 경우 ApprovalValidator 또는 ChangeTicketValidator 검증 +[10] before evidence 저장 후 Kafka Connect REST mutation 실행 +[11] after/error evidence와 audit event 기록 +[12] idempotency row에 response snapshot 저장 +[13] OpsEnvelope 반환 ``` -모든 failure가 audit/evidence를 남기는 구조는 아직 구현되어 있지 않다. `OpsEnvelope.evidence` 기본값은 빈 배열이고 `auditEventId` 값은 null이라 JSON 응답에서는 `audit_event_id` field가 생략된다. +`MutationGate` 안에서 발생한 실행 성공/실패는 evidence와 audit event를 남긴다. 다만 controller가 반환하는 `OpsEnvelope.evidence` 기본값은 빈 배열이고 `auditEventId` 값은 null이라 JSON 응답에서는 `audit_event_id` field가 생략된다. 헤더/ownership/idempotency 선검증에서 차단된 요청은 `MutationGate`에 들어가기 전 반환될 수 있다. ### 3. Header와 Envelope @@ -44,8 +47,9 @@ | `X-Agent-Run-Id` | mutation 필수 | | `X-Agent-Step-Id` | mutation 필수 | | `X-Idempotency-Key` | mutation 필수 | -| `X-Approval-Id` | mutation 필수. 누락 시 403 `APPROVAL_REQUIRED` | -| `X-Agent-Name`, `X-Agent-Id`, `X-Actor-Type`, `X-Actor-Id` | FastAPI가 보낼 수 있으나 mutation controller 필수 검증 대상은 아님 | +| `X-Approval-Id` | approval 정책이 필요한 mutation에서 사용. 누락 시 gate에서 403 `APPROVAL_REQUIRED` | +| `X-Change-Ticket-Id` | change-management 정책이 필요한 mutation에서 사용. 누락 시 gate에서 403 `CHANGE_TICKET_REQUIRED` | +| `X-Agent-Name`, `X-Agent-Id`, `X-Actor-Type`, `X-Actor-Id` | FastAPI가 보낼 수 있다. `X-Agent-Id`는 audit actor 후보이며, 필수 헤더 검증 대상은 아님 | 응답은 `OpsEnvelope`다. JSON field는 `request_id`, `audit_event_id`, `error.required_action`처럼 snake_case로 직렬화된다. @@ -56,6 +60,7 @@ Approval source of truth는 Spring Boot `approval` 테이블이다. | Endpoint | 구현 | | --- | --- | | `POST /internal/ops/approvals` | `tenantId`, `toolName`, `paramsHash`, `requiredApprover`, `expiresInMinutes`로 approval 생성. unknown field 거부 | +| `POST /internal/ops/approvals/preapproved` | ai-service HITL 통과 후 내부 caller가 Spring mutation용 `APPROVED` approval을 생성 | | `POST /internal/ops/approvals/{approvalId}/decision` | `decision`, `tenantId`, `decidedBy`, `comment` 처리. `SecurityContext` principal이 필요 | | `POST /internal/ops/approvals/{approvalId}/validate` | `tenantId`, `paramsHash`로 single-use 검증/소비 | | `GET /internal/ops/approvals/{approvalId}?tenantId=` | 단건 조회 | @@ -97,16 +102,16 @@ Kafka Connect REST timeout은 504 `TIMEOUT`, 그 외 상류 실패는 502 `UPSTR | Operation | Path | Gate | | --- | --- | --- | -| `restart_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | approval + idempotency | -| `pause_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | approval + idempotency | -| `resume_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | approval + idempotency | -| `restart_consumer_group` | `POST /internal/ops/projects/{projectId}/kafka/consumer-groups/{consumerGroup}/restart` | approval + idempotency | +| `restart_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | idempotency + policy. high risk이므로 `aiProdLock=true`이면 approval | +| `pause_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | idempotency + policy allow | +| `resume_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | idempotency + policy allow | +| `restart_consumer_group` | `POST /internal/ops/projects/{projectId}/kafka/consumer-groups/{consumerGroup}/restart` | idempotency + policy. high risk이므로 `aiProdLock=true`이면 approval | 이외 deployment scale, rollback, backfill, topic config patch, KafkaUser ACL patch, pod exec, arbitrary SQL, secret raw read 같은 operation은 현재 Spring endpoint가 없다. Secret 원문 read는 정책상 금지이며 Kafka principal secret API도 `MASKED_REFERENCE_ONLY`만 반환한다. ### 8. Runtime tool catalog -`GET /internal/ops/admin/tool-catalog`는 read operation과 approval-gated mutation operation을 함께 반환한다. 상세 목록은 [internal-ops-read-tools.md](../../api/internal-ops-read-tools.md)를 따른다. +`GET /internal/ops/admin/tool-catalog`는 18개 read operation과 4개 mutation operation을 함께 반환한다. 상세 목록은 [Spring Boot API §6.1](../../api/springboot.md#61-runtime-tool-catalog)을 따른다. ### 9. 현재 미구현/계획 상태 @@ -116,9 +121,9 @@ Kafka Connect REST timeout은 504 `TIMEOUT`, 그 외 상류 실패는 502 `UPSTR | --- | --- | | `/internal/ops/**` public ingress 노출 | 미노출. frontend nginx는 `/internal/ops/**`를 프록시하지 않고, FastAPI가 내부 서비스 경로로 호출 | | service identity secret rotation/JWKS화 | 현재는 단일 shared secret(`internal.ops.token`/`AI_INTERNAL_OPS_TOKEN`) 일치 검사 | -| policy matrix lookup으로 allow/approval/change/deny 결정 | mutation endpoint가 제한되어 있어 별도 policy engine lookup 없음 | -| before/after evidence writer | mutation 응답 evidence는 빈 배열 | -| audit_event append-only 기록 | `auditEventId` 값이 null이라 JSON 응답에서 `audit_event_id` field 생략 | +| policy matrix lookup으로 allow/approval/change/deny 결정 | `PolicyGuard` 코드 상수로 결정한다. DB 기반 policy matrix는 없음 | +| before/after evidence writer | `MutationGate`가 evidence row를 저장하지만 mutation 응답 `evidence` 배열에는 연결하지 않음 | +| audit_event append-only 기록 | `MutationGate`가 audit row를 저장하지만 `OpsEnvelope.auditEventId` 값은 null이라 JSON 응답에서 `audit_event_id` field 생략 | | Kubernetes/Prometheus/Schema Registry mutation | endpoint 없음 | | threshold governance **[계획 §3]** | **[현재]** consumer lag 임계값은 `workspace_settings.lag_warning_threshold`/`lag_critical_threshold`(미설정 시 기본 5,000), error rate 임계값은 `PipelineStatusServiceImpl`의 코드 상수(`0.5%`/`2.0%`), RCA threshold(0.60/0.80 등)는 ai-service 코드 기본값으로 흩어져 있고 변경 근거·버전·owner·보정 시각이 남지 않는다. **[계획]** [data-model §3.10.4 `threshold_registry`](./data-model.md#4-data-model)로 `threshold_name`·`value`·`version`·`basis`·`owner`·`last_calibrated_at`·`dataset_version`·`rollback_value`를 일원화하고, 값 자체는 [spec.md 부록 B](../../spec.md#부록-b--리소스-상태값-정의-및-자동-기준-단일-출처)를 단일 출처로 인용한다(중복 정의 금지). 외부 기준은 [rca-standards-review.md §5.2·§7(item3)](../rca-standards-review.md) | @@ -126,9 +131,9 @@ Kafka Connect REST timeout은 504 `TIMEOUT`, 그 외 상류 실패는 502 `UPSTR 현재 구현 기준 regression 대상: -- tool catalog는 read operation과 approval-gated mutation operation을 함께 포함하며 FastAPI 전용 alias와 미구현 operation은 포함하지 않는다. +- tool catalog는 18개 read operation과 4개 mutation operation을 함께 포함하며 FastAPI 전용 alias와 미구현 operation은 포함하지 않는다. - mutation은 `X-Agent-Run-Id`, `X-Agent-Step-Id`, `X-Idempotency-Key` 누락 시 400 `VALIDATION_FAILED`. -- mutation은 `X-Approval-Id` 누락 시 403 `APPROVAL_REQUIRED`. +- high-risk mutation은 `aiProdLock=true`일 때 `X-Approval-Id` 누락 시 403 `APPROVAL_REQUIRED`. - approval params hash/tenant/operation 불일치와 expired/used 상태가 차단된다. - idempotency replay가 중복 Connect REST 호출을 만들지 않는다. - 같은 key + 다른 params는 409 `CONFLICT`. diff --git a/docs/design/backend-springboot/overview.md b/docs/design/backend-springboot/overview.md index 9241e3f6..1bf530a4 100644 --- a/docs/design/backend-springboot/overview.md +++ b/docs/design/backend-springboot/overview.md @@ -14,7 +14,7 @@ Bifrost의 **플랫폼 본체이자 운영 제어의 최종 집행자**. 한 서 | Pipeline | EDA/CDC 생성 마법사 → **KafkaConnector CR 프로비저닝**·pause/resume/delete·생명주기 | FR-003~005 | | 모니터링 read | produce/consume·consumer lag·connector·CDC sync·messages·cluster | FR-006~009·023 | | 이벤트·인시던트 | 이벤트 로그. 현재 poller는 incident 자동 생성을 호출하지 않으며 `IncidentService` 메서드는 연결 보강 대상 | FR-019~021·024·026(탐지) | -| 운영 조치 집행 | 현재는 agent action 중 Kafka Connect connector restart/pause/resume, managed consumer group restart를 **approval·idempotency·ownership** 검증 후 실행. policy/audit/evidence/change gate는 보강 대상 | FR-022(실행) | +| 운영 조치 집행 | 현재는 agent action 중 Kafka Connect connector restart/pause/resume, managed consumer group restart를 **ownership·idempotency·PolicyGuard·approval 또는 change-ticket·audit/evidence** 경로로 실행. response envelope에는 audit/evidence id를 아직 연결하지 않음 | FR-022(실행) | | 실시간 | 플랫폼 SSE — `pipeline_status_changed`·`connector_state_changed`·`incident_opened` | — | ## 아키텍처 (구성) diff --git a/docs/design/backend-springboot/server.md b/docs/design/backend-springboot/server.md index c7d3429a..431396de 100644 --- a/docs/design/backend-springboot/server.md +++ b/docs/design/backend-springboot/server.md @@ -24,8 +24,9 @@ Spring Boot Operations Backend는 Bifrost의 **플랫폼 본체이자 실제 운 - platform JWT 인증과 workspace access/OWNER·ADMIN 권한 검증 - `/internal/ops` runtime tool catalog와 read/mutation endpoint 제공 - internal mutation의 project/resource ownership 검증 -- internal mutation의 approval id와 params hash 검증 +- internal mutation의 `PolicyGuard` 결정, approval/change-ticket, params hash 검증 - idempotency 처리 +- mutation before/after evidence write와 audit event 기록(`MutationGate` 경유 실행 경로) - Kafka Connect REST connector restart/pause/resume 호출 - Kafka Connect-managed consumer group restart 요청 처리 - Database/Pipeline/Kafka principal/Monitoring platform API 제공 @@ -33,8 +34,7 @@ Spring Boot Operations Backend는 Bifrost의 **플랫폼 본체이자 실제 운 설계상 보강 대상이지만 현재 코드 계약으로 쓰면 안 되는 항목: -- change ticket execution window, rollback plan, impact analysis, operation scope 검증. -- mutation before/after evidence writer와 audit event append-only 기록. +- runtime catalog에 없는 change-management mutation endpoint. - Kubernetes/Prometheus/Schema Registry mutation 또는 KafkaRebalance approve/refresh endpoint. Spring Boot가 담당하지 않는다. @@ -59,9 +59,9 @@ Spring Boot는 FastAPI Agent의 판단을 신뢰하지 않는다. FastAPI가 이 | service identity | `internal.ops.token` 설정 시 `X-Internal-Token` 일치를 요구한다. 토큰이 비어 있으면 로컬/기존 배포 호환을 위해 게이트가 비활성화된다 | | user/project scope | 요청 사용자가 project 권한을 갖는가 | | resource ownership | resource가 해당 project 소유 또는 허용 범위인가 | -| operation allowlist | 현재는 controller endpoint 자체가 allowlist. runtime catalog는 read 8개만 노출 | -| approval | approval id, operation, tenant, params hash, expiry, single-use 검증 | -| change management | facade는 있으나 mutation gate에는 아직 연결되지 않음 | +| operation allowlist | 현재는 controller endpoint와 `/internal/ops/admin/tool-catalog` 22개 항목이 agent-callable boundary | +| approval | `PolicyGuard`가 요구할 때 approval id, operation, tenant, params hash, expiry, single-use 검증 | +| change management | `PolicyGuard`가 `REQUIRE_CHANGE_MANAGEMENT`를 반환하면 `X-Change-Ticket-Id`로 status/window/rollback/impact/scope 검증. 현재 catalog mutation 4개는 change-management classification이 아님 | | idempotency | 중복 mutation replay/conflict 처리 | ### 4. 내부 계층 @@ -85,14 +85,14 @@ Controller | Auth / Scope | service identity, project, resource 권한 검증 | | Policy | operation 위험도와 허용 범위 판단 | | Approval | approval id, approver, scope, params hash 검증 | -| Change Management | change ticket 존재, tenant ownership, OPEN status 검증 | +| Change Management | change ticket 존재, tenant ownership, APPROVED status, execution window, rollback/impact/scope 검증 | | Idempotency | 중복 실행 방지와 replay response | | Operation Service | 운영 의도 단위 use case 처리 | | Resource Adapter | Fabric8, Kafka, Prometheus 등 외부 client | | Evidence Writer | raw result 저장과 evidence reference 생성 | | Audit | 모든 요청, 실행, 차단 사유 기록 | -위 계층 표는 목표 구조다. 현재 internal mutation 구현은 header 검증 → project/resource ownership → idempotency → approval → Kafka Connect REST → response snapshot 순서의 좁은 subset이며, evidence writer/audit layer와 change-management gate는 아직 연결되어 있지 않다. +위 계층 표는 목표 구조다. 현재 internal mutation 구현은 header 검증 → project/resource ownership → idempotency → `MutationGate` policy/approval 또는 change-ticket 검증 → before evidence → Kafka Connect REST → after/error evidence와 audit 기록 → response snapshot 순서의 subset이다. 다만 `OpsEnvelope.evidence`와 `audit_event_id`에는 아직 생성된 evidence/audit id를 싣지 않는다. ### 5. 패키지 구조 @@ -184,9 +184,9 @@ agent의 tool catalog·policy matrix는 이 정책을 **미러링한 사전 판 ### 7.1 Operation Allowlist (현재 집행 경계) -Spring API의 현재 controller family는 [Controller Coverage](../../api/springboot.md#controller-coverage)에 정리한다. `GET /internal/ops/admin/tool-catalog`는 구현된 agent-callable runtime operation catalog를 반환하며, read operation과 approval-gated mutation operation을 함께 포함한다. +Spring API의 현재 controller family는 [Controller Coverage](../../api/springboot.md#controller-coverage)에 정리한다. `GET /internal/ops/admin/tool-catalog`는 구현된 agent-callable runtime operation catalog를 반환하며, 18개 read operation과 4개 mutation operation을 함께 포함한다. -**현재 runtime catalog 18개** +**현재 runtime catalog 22개** | Operation | Endpoint | | --- | --- | @@ -195,6 +195,7 @@ Spring API의 현재 controller family는 [Controller Coverage](../../api/spring | `search_logs` | `POST /internal/ops/projects/{projectId}/observability/logs/search` | | `query_metrics` | `GET /internal/ops/projects/{projectId}/observability/metrics` | | `query_traces` | `GET /internal/ops/projects/{projectId}/connectors/{connectorName}/traces` | +| `get_connector_task_trace` | `GET /internal/ops/projects/{projectId}/connectors/{connectorName}/task-trace` | | `list_alerts` | `GET /internal/ops/projects/{projectId}/observability/alerts` | | `analyze_event_log` | `GET /internal/ops/projects/{projectId}/observability/events/summary` | | `get_incident_summary` | `GET /internal/ops/projects/{projectId}/incidents/{incidentId}/summary` | @@ -204,6 +205,9 @@ Spring API의 현재 controller family는 [Controller Coverage](../../api/spring | `get_pipeline_topology` | `GET /internal/ops/projects/{projectId}/pipelines/{pipelineId}/topology` | | `get_connector_status` | `GET /internal/ops/projects/{projectId}/kafka/connectors/{connectorName}/status` | | `list_connectors` | `GET /internal/ops/projects/{projectId}/kafka/connectors/status` | +| `list_datasources` | `GET /internal/ops/projects/{projectId}/datasources` | +| `get_cluster_info` | `GET /internal/ops/projects/{projectId}/kafka/cluster` | +| `sql_read` | `POST /internal/ops/projects/{projectId}/datasources/{datasourceId}/query` | | `restart_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | | `pause_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | | `resume_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | @@ -213,12 +217,12 @@ Spring API의 현재 controller family는 [Controller Coverage](../../api/spring | Operation | Endpoint | Gate | | --- | --- | --- | -| `restart_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | approval + idempotency | -| `pause_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | approval + idempotency | -| `resume_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | approval + idempotency | -| `restart_consumer_group` | `POST /internal/ops/projects/{projectId}/kafka/consumer-groups/{consumerGroup}/restart` | approval + idempotency | +| `restart_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/restart` | idempotency + policy. high risk이므로 `aiProdLock=true`이면 approval | +| `pause_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/pause` | idempotency + policy allow | +| `resume_connector` | `POST /internal/ops/projects/{projectId}/connectors/{connectorName}/resume` | idempotency + policy allow | +| `restart_consumer_group` | `POST /internal/ops/projects/{projectId}/kafka/consumer-groups/{consumerGroup}/restart` | idempotency + policy. high risk이므로 `aiProdLock=true`이면 approval | -FastAPI registry의 `get_metrics`/`get_deployments`는 Spring catalog의 `query_metrics`/`get_recent_changes`에 대응하고, `get_kafka_lag`는 `get_consumer_lag`의 FastAPI alias다. Deployment scale, rebalance, pipeline backfill/rollback, connector config patch, topic mutation, pod exec, arbitrary SQL, secret raw read는 현재 Spring runtime catalog/endpoint가 아니다. Secret raw read는 정책상 금지이며 Kafka principal secret API도 reference와 masked value만 반환한다. +FastAPI registry의 `get_metrics`/`get_deployments`는 Spring catalog의 `query_metrics`/`get_recent_changes`에 대응하고, `get_kafka_lag`는 `get_consumer_lag`의 FastAPI alias다. Deployment scale, rebalance, pipeline backfill/rollback, connector config patch, topic mutation, pod exec, arbitrary SQL mutation, secret raw read는 현재 Spring runtime catalog/endpoint가 아니다. `sql_read`는 datasource-scoped read-only query endpoint다. Secret raw read는 정책상 금지이며 Kafka principal secret API도 reference와 masked value만 반환한다. ### 8. Approval과 Change Management @@ -231,12 +235,11 @@ Approval 검증: 5. expiry 확인 6. single-use consumed 처리 -Facade `POST /internal/ops/approvals/{approvalId}/validate`는 `tenantId`와 `paramsHash`만 받아 tenant ownership과 params hash/expiry/single-use/status를 검증한다. action id field는 현재 entity/DTO에 없다. -8. single-use 여부 확인 +Facade `POST /internal/ops/approvals/{approvalId}/validate`는 `tenantId`와 `paramsHash`만 받아 tenant ownership과 params hash/expiry/single-use/status를 검증한다. mutation runtime path는 `ApprovalValidator.validateAndConsume(approvalId, tenantId, operation, paramsHash)` overload로 operation scope까지 검증한다. action id field는 현재 entity/DTO에 없다. Change Management 검증: -현재 facade는 change ticket 존재, tenant 소속, status `OPEN`만 검증한다. execution window, rollback plan, impact analysis, requested operation scope 검증은 현재 entity/controller 필드에 없다. +현재 facade와 runtime validator는 change ticket 존재, tenant 소속, `APPROVED` 상태, approver/requester metadata, execution window, rollback plan, impact analysis, requested operation scope를 검증한다. `MutationGate`는 `PolicyGuard`가 `REQUIRE_CHANGE_MANAGEMENT`를 반환한 operation에서만 이 검증을 실행한다. 현재 runtime catalog mutation 4개는 `update_connector`/`reset_offsets` 같은 change-management operation이 아니므로 change-ticket gate로 분기하지 않는다. ### 9. Idempotency @@ -255,7 +258,7 @@ Mutation timeout이 발생해도 Spring은 자동 재시도하지 않는다. Kaf ### 10. Evidence와 Audit -현재 internal mutation 응답의 `evidence`는 빈 배열이고 `auditEventId` 값은 null이라 JSON 응답에서는 `audit_event_id` field가 생략된다. 성공, 실패, 차단을 모두 append-only audit으로 기록하는 것은 구현 보강 대상이다. +`MutationGate`는 before/after/error evidence와 audit event를 저장한다. 현재 internal mutation 응답의 `evidence`는 빈 배열이고 `auditEventId` 값은 null이라 JSON 응답에서는 `audit_event_id` field가 생략된다. 저장된 evidence/audit id를 `OpsEnvelope`에 연결하는 것은 구현 보강 대상이다. 기록 항목: @@ -314,19 +317,19 @@ Mutation timeout이 발생해도 Spring은 자동 재시도하지 않는다. Kaf 4. Secret 원문을 반환하지 않는다. 5. Kubernetes RBAC은 namespace/resource 단위 최소 권한으로 둔다. 6. Kafka credential은 operation별 service account로 제한한다. -7. 현재 mutation은 idempotency를 필수로 한다. audit append-only 기록은 보강 대상이다. +7. 현재 mutation은 idempotency를 필수로 한다. audit/evidence row는 `MutationGate`가 기록하지만 response envelope에는 id가 연결되지 않는다. ### 13. 테스트 기준 - read operation은 approval 없이 성공해야 한다. -- mutation은 approval 없이 실패해야 한다. +- high-risk mutation은 `aiProdLock=true`일 때 approval 없이 실패해야 한다. medium/low risk catalog mutation은 현재 policy allow다. - params hash 불일치 approval은 실패해야 한다. -- change window 밖 execution 차단은 change-management 확장 후 테스트 대상이다. 현재 mutation gate에는 change ticket/window가 없다. +- `REQUIRE_CHANGE_MANAGEMENT` operation은 change window 밖 execution을 차단해야 한다. 현재 catalog mutation 4개에는 해당 classification이 없다. - idempotency replay가 중복 실행을 만들지 않아야 한다. -- before/after evidence reference 생성은 구현 보강 대상이다. 현재 regression 기준에서는 envelope field가 비어 있음을 확인한다. +- before/after evidence row 생성은 `MutationGate` regression으로 확인하고, 현재 API regression 기준에서는 envelope field가 비어 있음을 확인한다. - forbidden operation은 endpoint가 없거나 policy deny되어야 한다. - `internal.ops.token`이 설정된 환경에서는 FastAPI service identity header가 없거나 다르면 실패해야 한다. 토큰 미설정 로컬/기존 환경은 호환을 위해 허용된다. ### 14. 결론 -Spring Boot Operations Backend는 Bifrost 운영 제어의 최종 집행자다. 현재 internal mutation controller는 header/ownership, `X-Approval-Id`, idempotency, approval validation, Kafka Connect 호출, idempotency snapshot을 처리한다. policy engine, change management gate, audit/evidence write-through는 `MutationGate`/설계 표면에는 있으나 현재 mutation runtime path에는 연결되어 있지 않다. +Spring Boot Operations Backend는 Bifrost 운영 제어의 최종 집행자다. 현재 internal mutation controller는 header/ownership, idempotency, `MutationGate` policy/approval 또는 change-ticket 검증, Kafka Connect 호출, idempotency snapshot을 처리한다. audit/evidence write-through는 runtime path에 연결되어 있지만, response envelope에는 해당 id를 아직 노출하지 않는다. diff --git a/docs/design/infra.md b/docs/design/infra.md index c1f3c2fc..c8b9e35c 100644 --- a/docs/design/infra.md +++ b/docs/design/infra.md @@ -38,7 +38,7 @@ flowchart TB ## Namespace -`platform-kafka`(Kafka·Connect·CR) · `bifrost-system`(FE·FastAPI·SpringBoot) · `registry`(Harbor) · `cicd`(Jenkins) · `argocd` · `monitoring`(Prometheus·Alertmanager·Grafana·Loki·Tempo·exporters) · `metadb`(Spring 메타·evidence) · `agentdb`(FastAPI run·pgvector 벡터) · `tenantdb`(고객 source/sink) +`platform-kafka`(Kafka·Connect·CR) · `bifrost-system`(FE·FastAPI·SpringBoot) · `harbor`(registry) · `jenkins`(CI) · `argocd` · `monitoring`(Prometheus·Alertmanager·Grafana·Loki·Tempo·exporters) · `metadb`(Spring 메타·evidence) · `agentdb`(FastAPI run·pgvector 벡터) · `tenantdb`(고객 source/sink) > 현재 배포·진행 상태와 운영 점검 항목은 [§2 리소스 계획·현황](#2-리소스-계획현황-resource-plan)이 단일 출처다(요약에서 중복 기술하지 않는다). @@ -236,11 +236,12 @@ Kafka cluster 자체와 Strimzi Operator는 bootstrap 단계에서는 수동 적 > **#123 구현 (브랜치 전략)**: > - **빌드/프로비저닝 = 코드라인, 배포 = gitops.** terraform은 클러스터 프로비저닝이라 코드라인(`terraform apply`), helm/manifest는 배포물이라 gitops. (※ "infra"가 클러스터 프로비저닝(terraform)과 in-cluster 리소스(helm/manifest) 두 종류라 구분.) -> - **CI 트리거 = `main` 머지**(릴리스). `develop`은 개발 통합이라 배포 트리거 아님 — Jenkinsfile은 `when { branch 'main' }`로 게이트(파일은 develop에도 존재, 무해). -> - **CI**(`Jenkinsfile`, repo 루트): **직전 성공 빌드 대비 변경 감지 → 바뀐 서비스만** Kaniko 빌드(docker 미사용; Dockerfile은 Kaniko 입력이라 유지) → Harbor `.../library/bifrost-:` push(HTTP `--insecure`) → 그 서비스의 gitops `charts//values.yaml` `image.tag`만 commit. -> - **CD = `gitops` 브랜치**(long-lived, **merge 금지**). ArgoCD가 **polling/reconcile로 감지(webhook 미사용)** → auto-sync. gitops 구조: `charts/`(operations-backend·ai-service·frontend helm) · `databases/`(metadb·agentdb·tenantdb raw, directory app·prune off) · `infra/`(CI/CD nginx Ingress: jenkins/harbor/argocd) · `argocd/`(app-of-apps). -> - **ArgoCD 앱 구성 (#232 정리, 11→8)**: `bifrost-root`(app-of-apps, `argocd/apps/` 감시) 아래 — `monitoring`(kps+loki+tempo multi-source) · `bifrost-services`(frontend+ops-backend+ai-service multi-source, bifrost-system) · `databases`(metadb+agentdb, prune off) · `tenantdb`(분리, prune off) · `cert-manager` · `ingress-nginx` · `cicd-ingress`. 파일 추가/삭제 = 앱 추가/삭제(root prune=true). -> - **부트스트랩(완료)**: Jenkins job·Kaniko agent·`harbor-push-secret`·`github-pat` cred·main webhook·ArgoCD repo connect·`kubectl apply -f argocd/root.yaml`·앱 시크릿 배선 완료. (단, `operations-backend-secrets`·`ai-llm-secret`·복제 `harbor-push-secret`은 GitOps 밖 수동 secret — 네임스페이스 삭제 내성 위해 GitOps화 권장.) +> - **CI 트리거 = `main` job**(릴리스). `develop`은 개발 통합이라 배포 트리거가 아니다. Jenkins JCasC가 `bifrost-ci` Pipeline job을 SCM `*/main`으로 생성하므로 main 한정은 job 설정이 담당한다. +> - **CI**(`Jenkinsfile`, repo 루트): **직전 성공 빌드 대비 변경 감지 → 바뀐 앱 서비스만** Kaniko 빌드(docker 미사용; Dockerfile은 Kaniko 입력이라 유지) → Harbor `.../library/bifrost-:`와 `:latest` push(HTTP `--insecure`) → 그 서비스의 gitops `charts//values.yaml` `image.tag`만 commit. `operations-backend`는 멀티모듈 Gradle 때문에 루트 컨텍스트를 쓰고, `ai-service`·`frontend`는 서비스 디렉터리 컨텍스트를 쓴다. +> - **Kafka Connect 이미지**: `infra/docker/kafka-connect/` 또는 `connect-plugins/` 변경 시 Jenkins가 루트 컨텍스트로 `bifrost-kafka-connect`를 빌드해 `1.0.0-converter`·git sha·`latest`를 Harbor에 push한다. 앱 서비스처럼 gitops tag를 자동 bump하지 않으며, `KafkaConnect.spec.image`의 고정 태그와 함께 관리한다. +> - **CD = `gitops` 브랜치**(long-lived, **merge 금지**). ArgoCD가 **polling/reconcile로 감지(webhook 미사용)** → auto-sync. gitops 구조: `charts/`(operations-backend·ai-service·frontend helm) · `databases/`(metadb·agentdb·tenantdb raw, directory app·prune off) · `infra/`(CI/CD nginx Ingress: jenkins/harbor/argocd) · `secrets/`(bifrost-system SealedSecret) · `argocd/`(app-of-apps). +> - **ArgoCD 앱 구성 (#232 이후 현재)**: `0-root-bifrost-root`(app-of-apps, `argocd/apps/` 감시) 아래 **10개 child Application** — cert-manager · ingress-nginx · sealed-secrets · cicd-ingress · bifrost-secrets · bifrost-services(frontend+ops-backend+ai-service multi-source, bifrost-system) · databases(metadb+agentdb, prune off) · tenantdb(분리, prune off) · monitoring(kps+loki+tempo multi-source) · otel-collector. `projects.yaml`은 AppProject 정의다. +> - **부트스트랩(완료)**: Jenkins job·Kaniko agent·`harbor-push-secret`·`github-pat` cred·main webhook·ArgoCD repo connect·`kubectl apply -f argocd/root.yaml` 완료. `bifrost-system` 앱 시크릿은 SealedSecret 앱으로 GitOps 관리되며, Jenkins의 `github-pat` 같은 부트스트랩 credential은 클러스터 외부 secret으로 남는다. ### 8. Observability와 Evidence @@ -464,8 +465,8 @@ Listener: | --- | --- | --- | | `harbor` | Harbor 8 pod (core·database·jobservice·nginx·portal·redis·registry·trivy) | 정상. PVC: registry 50Gi·db 10Gi·trivy 10Gi·redis 5Gi·jobservice 5Gi. 외부 UI `https://harbor.skala-ai.com` | | `jenkins` | `jenkins-0` (StatefulSet, JCasC #194) | 정상. PVC 20Gi. 외부 UI `https://jenkins.skala-ai.com`, main 머지 webhook | -| `argocd` | Argo CD 7 pod | 정상. **app-of-apps 가동(`bifrost-root` + 7 child 앱)**. 외부 UI `https://argocd.skala-ai.com` | -| `platform-kafka` | `platform-connect` (KafkaConnect CR) + entity-operator | Connect 가동. 남은 보강: 2 replica 증설, connector plugin 포함 커스텀 이미지(Harbor), KafkaConnector/KafkaUser CR(IaC) | +| `argocd` | Argo CD 7 pod | 정상. **app-of-apps 가동(`0-root-bifrost-root` + 10 child Application)**. 외부 UI `https://argocd.skala-ai.com` | +| `platform-kafka` | `platform-connect` (KafkaConnect CR, replicas 2) + entity-operator | Connect 가동. 커스텀 Harbor 이미지 `bifrost-kafka-connect:1.0.0-converter` 사용. 남은 보강: KafkaConnector/KafkaUser CR(IaC) 정식화 | trivy(취약점 스캐너)는 선택적이라 불필요 시 비활성화해 리소스를 줄일 수 있다. @@ -500,11 +501,11 @@ MVP로는 적절하다. 다만 리소스 여유가 생기면 controller와 broke #### 4.5 Kafka Connect — 배포됨(보강 필요) -KafkaConnect CR `platform-connect`가 가동 중이다([§3.5](#35-deliveryconnect-배포-현황)). 남은 보강: (a) 목표 replicas 2로 증설, (b) connector plugin 포함 custom image를 Harbor 기반으로 재정의, (c) source/sink **KafkaConnector CR**와 워크스페이스 **KafkaUser CR** 생성(IaC 정식화). +KafkaConnect CR `platform-connect`가 replicas 2로 가동 중이고, connector plugin 포함 custom image `harbor.harbor.svc.cluster.local/library/bifrost-kafka-connect:1.0.0-converter`를 사용한다([§3.5](#35-deliveryconnect-배포-현황)). 남은 보강은 source/sink **KafkaConnector CR**와 워크스페이스 **KafkaUser CR** 생성(IaC 정식화)이다. #### 4.6 CI/CD와 Registry — GitOps 연동 완료 -Harbor·Jenkins·Argo CD가 정상 가동하며 **GitOps로 연동**됐다([§3.5](#35-deliveryconnect-배포-현황)). Argo CD app-of-apps(`bifrost-root`)가 gitops 브랜치를 reconcile하고, Jenkins build→Harbor push→gitops `image.tag` 갱신→ArgoCD 배포 파이프라인이 main 머지 트리거로 가동한다(#123). 외부 노출은 NLB+ingress-nginx+LE(#232). +Harbor·Jenkins·Argo CD가 정상 가동하며 **GitOps로 연동**됐다([§3.5](#35-deliveryconnect-배포-현황)). Argo CD app-of-apps(`0-root-bifrost-root`)가 gitops 브랜치를 reconcile하고, Jenkins build→Harbor push→gitops `image.tag` 갱신→ArgoCD 배포 파이프라인이 main 전용 `bifrost-ci` job으로 가동한다(#123). 외부 노출은 NLB+ingress-nginx+LE(#232). #### 4.7 Observability — 배포 완료 (#124) @@ -639,7 +640,7 @@ Strimzi Operator와 Kafka cluster 자체도 최종적으로 GitOps에 포함하 | Agent Run Store | `agentdb` | **FastAPI 전용 PostgreSQL**(`pgvector/pgvector:pg16`) — run/state/event/approval/report. Spring metadb와 분리(서비스 경계, [fastapi §9](./backend-fastapi/server-design.md#2-server-design)) | | Knowledge Vector Store | `agentdb` | RAG 코퍼스 임베딩 — **pgvector 확장**으로 같은 agentdb 인스턴스에 co-locate. 스케일 시 Qdrant/Milvus 등으로 외부화 | -> **agentdb**: 별도 `agentdb` 네임스페이스 + 전용 pgvector 인스턴스. 인프라는 **빈 DB + pgvector 확장**까지 프로비저닝, 테이블 스키마(server-design §9.2: `agent_run`·`state_patch`·`run_event`·`approval_link`·`report_snapshot`)는 **앱 alembic 마이그레이션(FastAPI #134, initContainer 자동 적용 #255)** 소유. 매니페스트: gitops `databases/agentdb/` (ArgoCD `databases` 앱). +> **agentdb**: 별도 `agentdb` 네임스페이스 + 전용 pgvector 인스턴스. 인프라는 **빈 DB + pgvector 확장**까지 프로비저닝, 테이블 스키마(server-design §9.2: `agent_run`·`state_patch`·`run_event`·`approval_link`·`report_snapshot`)는 **앱 alembic 마이그레이션(FastAPI #134, initContainer 자동 적용 #255)** 소유. 매니페스트: gitops `databases/agentdb/` (ArgoCD `3-data-databases` 앱). FastAPI Agent는 Kubernetes/Kafka credential을 갖지 않는다. Spring Boot Operations Backend가 필요한 read/mutation 권한을 제한적으로 가진다. @@ -650,12 +651,12 @@ FastAPI Agent는 Kubernetes/Kafka credential을 갖지 않는다. Spring Boot Op #### 6.7 Observability > **✅ 배포 완료 (#124, gitops ArgoCD)**: `monitoring` namespace에 **kube-prometheus-stack**(Prometheus·Grafana·Alertmanager·node-exporter·kube-state-metrics·operator) + **Loki+Promtail**(loki-stack) + **Tempo**(OTLP 4317/4318). Strimzi **kafkaExporter** + Connect **JMX** 활성 → `kafka_*`·`debezium_metrics_*` 수집. ops-backend `PROMETHEUS_URL=http://kps-prometheus.monitoring:9090`로 연결되어 **#126 파이프라인 차트가 실데이터**. Grafana datasource = Prometheus·Loki·Tempo. -> ArgoCD 앱: **`monitoring` 단일 multi-source 앱**(kps + loki-stack + tempo). gitops `argocd/apps/monitoring.yaml`. Grafana 기본 datasource = Prometheus(Loki는 loki-stack 비기본), loki StatefulSet은 `ignoreDifferences`(SSA 빈 컬렉션) 적용. Prometheus는 전 namespace ServiceMonitor/PodMonitor 수집. +> ArgoCD 앱: **`4-observability-monitoring` 단일 multi-source 앱**(kps + loki-stack + tempo). gitops `argocd/apps/4-observability-monitoring.yaml`. Grafana 기본 datasource = Prometheus(Loki는 loki-stack 비기본), loki StatefulSet은 `ignoreDifferences`(SSA 빈 컬렉션) 적용. Prometheus는 전 namespace ServiceMonitor/PodMonitor 수집. > **Grafana 접근**: `kps-grafana`(ClusterIP)는 **의도적으로 내부 전용**(외부 서브도메인 미부여). adminPassword가 약하고(`admin`) 외부 노출 실익이 낮아, `kubectl port-forward` 또는 ArgoCD 경유로만 접근한다. 외부 노출이 필요하면 비밀번호 강화 후 ingress-nginx+LE로 `grafana.skala-ai.com` 추가. > **잔여**: ops-backend `search_logs`(Loki) 실연동. > **Trace 파이프라인(#366/#370/#372)**: ops-backend가 파이프라인 작업(생성·상태전이·프로비저닝·폴링) span을, ai-service(FastAPI)가 에이전트 run span(`agent.run`)을 OTLP HTTP로 송신한다. dev/prod에서는 **`otel-collector`(contrib, `monitoring` ns) 경유 tail-sampling** — 전량 수신 후 **에러·지연(>1s) trace만 Tempo에 보존**하고 정상 trace는 드롭해 저장량을 줄인다(`argocd/apps/5-otel-collector.yaml`, gitops `monitoring/otel-collector/`). 데이터플레인 span(#371)도 같은 Collector로 모인다. **ai-service↔ops-backend 는 `traceparent` 전파로 한 trace 로 이어진다**(#372): FastAPI `httpx` 호출이 헤더를 주입하고 Spring(Micrometer)이 추출. ai-service run 은 `BackgroundTasks` 로 실행되므로 runner 가 루트 span 을 직접 연다. > -> 배치: 전부 `monitoring` namespace, **`app` 노드풀**(taint 없음)에 스케줄. DaemonSet(node-exporter·Promtail)은 data 풀 taint를 toleration 처리해 전 노드에 배치. +> 배치: 전부 `monitoring` namespace, 현재 단일 `data` 노드풀(taint 없음)에 스케줄. DaemonSet(node-exporter·Promtail)은 전 노드에 배치. > **모니터링은 Spring Boot만이 아니라 클러스터 전체를 관측한다** — 노드·k8s 오브젝트·컨테이너·Kafka·DB·Spring·FastAPI·frontend가 모두 수집 대상이다. | 리소스 | 권장 replica | 수집(scrape) 대상 | @@ -746,18 +747,16 @@ KafkaNodePool brokers - `plain` listener 제거 또는 사용 범위 제한 - `tenantdb` LoadBalancer 노출 필요성 재검토 - Kafka PDB/anti-affinity 명시 여부 확인 -- Kafka Connect plugin image를 Harbor 기반으로 재정의 - 기존 DB workload가 운영용인지 데모용인지 구분 #### 남은 작업 > GitOps 연동·CI/CD 파이프라인·앱/모니터링 배포·외부 노출(#232)은 완료. 아래가 남은 작업이다. -1. Kafka Connect replicas 2 증설 + connector plugin image(Harbor) 재정의 -2. KafkaUser / KafkaConnector / 내부 KafkaTopic **IaC 정식화**(파이프라인 데이터 토픽은 CR 비추적 정책) -3. Evidence Store / Audit Store 스키마 구성 -4. Loki 로그 소비처(ops-backend `search_logs` 실연동) · Tempo OTLP 앱 계측은 ops-backend(#366)·ai-service(#372) 모두 적용, traceparent 전파로 한 trace 연결 -5. Cruise Control / KafkaRebalance 활성화(선택) -6. NetworkPolicy 강제(VPC CNI policy agent 선행) / RBAC 정리 -7. **수동 시크릿 GitOps화**(SealedSecrets/External Secrets) — 네임스페이스 삭제 내성 -8. Kafka 운영 점검: `auto.create.topics` off · plain listener 제한 · PDB/anti-affinity +1. KafkaUser / KafkaConnector / 내부 KafkaTopic **IaC 정식화**(파이프라인 데이터 토픽은 CR 비추적 정책) +2. Evidence Store / Audit Store 스키마 구성 +3. Loki 로그 소비처(ops-backend `search_logs` 실연동) · Tempo OTLP 앱 계측은 ops-backend(#366)·ai-service(#372) 모두 적용, traceparent 전파로 한 trace 연결 +4. Cruise Control / KafkaRebalance 활성화(선택) +5. NetworkPolicy 강제(VPC CNI policy agent 선행) / RBAC 정리 +6. Kafka 운영 점검: `auto.create.topics` off · plain listener 제한 · PDB/anti-affinity +7. 부트스트랩 credential(`github-pat` 등) 운용 방식 정리 diff --git a/docs/design/rca-standards-review.md b/docs/design/rca-standards-review.md index feca6c38..711e7e3f 100644 --- a/docs/design/rca-standards-review.md +++ b/docs/design/rca-standards-review.md @@ -11,9 +11,9 @@ ## 이 문서의 용도 -이 문서는 발표 방어용 해설이 아니라 **구현 개선을 위한 기준 문서**다. 현재 RCA·장애대응 에이전트는 이미 카탈로그 기반 RCA, 증거 매트릭스, UNKNOWN 폴백, 승인 게이트 같은 핵심 안전장치를 갖추고 있지만, 실제 운영 수준으로 보려면 아직 보강해야 할 부분이 많다. +이 문서는 발표 방어용 해설이 아니라 **구현 개선을 위한 기준 문서**다. 현재 RCA·장애대응 에이전트는 이미 카탈로그 기반 RCA, 증거 매트릭스, UNKNOWN 폴백, 승인 게이트 같은 핵심 안전장치를 갖추고 있고, 이후 자동 롤백·run 재현성 manifest·gold set·SLI/SLO routing 같은 일부 운영 보강도 코드에 들어갔다. 다만 실제 운영 수준으로 보려면 아직 남은 gap을 정직하게 분리해야 한다. -따라서 이 문서의 1차 목적은 공격받을 만한 포인트를 숨기는 것이 아니라, 오히려 명확히 드러내고 코드 개선 작업으로 전환하는 것이다. 자동 롤백, run 단위 재현성, confidence 캘리브레이션, RCA 평가셋, 사용자 영향 SLI/SLO, burn-rate 알림, 인과/상관 증거 구분은 모두 발표 문구가 아니라 실제 구현해야 할 개선 항목이다. +따라서 이 문서의 1차 목적은 공격받을 만한 포인트를 숨기는 것이 아니라, 오히려 명확히 드러내고 코드 개선 작업으로 전환하는 것이다. 자동 롤백, run 단위 재현성, confidence 캘리브레이션, RCA 평가셋, 사용자 영향 SLI/SLO, burn-rate 알림, 인과/상관 증거 구분은 발표 문구가 아니라 실제 구현과 검증 대상으로 관리한다. 구현된 항목은 현재 동작과 한계를 함께 적고, 남은 항목은 로드맵으로 유지한다. 발표에서는 이 문서를 "우리가 이미 완벽하다"는 근거로 쓰지 않는다. 대신 **현재 한계와 개선 방향을 표준 기준으로 식별했고, 그 기준에 맞춰 코드 개선을 진행한다**는 설명에 사용한다. 구현이 완료된 항목은 발표에서 실제 개선 결과로 제시하고, 아직 남은 항목은 로드맵과 검증 계획으로 제시한다. @@ -23,7 +23,7 @@ > 지금 구현이 산업 표준과 학술 근거에 비춰 충분히 안전하고, 재현 가능하고, 설명 가능한가? -결론부터 말하면, 현재 구조는 "로그를 LLM에 던지고 그럴듯한 답을 받는 위험한 방식"은 아니다. Bifrost는 이미 카탈로그, 증거 매트릭스, confidence, UNKNOWN 폴백, 승인 게이트를 갖추고 있다. 다만 운영 자동화·재현성·평가·알림 기준 쪽에는 아직 보강해야 할 부분이 많다. +결론부터 말하면, 현재 구조는 "로그를 LLM에 던지고 그럴듯한 답을 받는 위험한 방식"은 아니다. Bifrost는 이미 카탈로그, 증거 매트릭스, confidence, UNKNOWN 폴백, 승인 게이트를 갖추고 있다. 자동 롤백, run 재현성 manifest, 운영자 피드백 기반 gold set, threshold registry, SLI/SLO burn-rate routing도 기본 구현이 들어갔다. 다만 실행 모델 alias 사용, instrumentation 완성도, 정기 평가·캘리브레이션, baseline 기반 SLO 보정처럼 아직 보강해야 할 부분이 남아 있다. ### 표준이 보는 안전한 RCA 방식 @@ -43,8 +43,8 @@ 현재 RCA 구조는 표준이 권고하는 방향과 상당 부분 맞아 있다. - RCA는 로그를 그대로 LLM에 던지지 않는다. -- 8계층 35개 root cause 카탈로그로 후보를 제한한다. -- required/supporting/negative 증거 매트릭스로 후보별 근거를 평가한다. +- 8계층 35개 root cause 카탈로그로 후보를 제한하고, actionable 32개 root cause에 evidence profile을 붙인다. +- required/supporting/negative 증거 매트릭스로 후보별 근거를 평가하며, 일부 규칙은 `causality_type`·`temporality_required`·`causal_chain_step`을 가진다. - confidence가 낮으면 `UNKNOWN_WITH_EVIDENCE_GAP`으로 빠진다. - LLM은 최종 판단자가 아니라 근접 후보의 타이브레이커로만 쓰인다. - 조치 실행은 정책 게이트, 승인 게이트, 변경 게이트, 멱등성 키, append-only 감사 이력을 거친다. @@ -55,43 +55,42 @@ 문제는 "기본 구조가 있다"와 "운영 수준으로 충분하다"는 다르다는 점이다. 현재 공격받을 만한 포인트는 명확하다. -- 조치가 실패했을 때 `rollback_plan`은 검증하지만, 자동 롤백 실행은 없다. -- 모델이 `gpt-4o` 같은 별칭으로 남아 있어 같은 인시던트 판단을 나중에 재현하기 어렵다. -- prompt, catalog, evidence matrix, runbook, corpus, 평가셋 버전이 run 단위로 충분히 고정되어 있지 않다. -- confidence가 실제 정답률과 맞는지 ECE 같은 지표로 검증하지 않는다. -- RCA 후보 랭킹이 실제 인시던트 정답셋에서 AC@k 기준으로 얼마나 맞는지 평가하지 않는다. -- 임계값이 코드 상수·기본값으로 흩어져 있고, `threshold_version`, 보정 근거, 마지막 보정 시각, owner가 남지 않는다. -- 평가셋을 만들더라도 trigger/symptom/root cause/contributing factor를 구분하는 라벨링 프로토콜이 아직 없다. -- 알림은 consumer lag, error rate, connector failed 같은 원인 기반 정적 임계값에 많이 의존한다. +- 조치 실패 시 자동 롤백 기본 경로가 생겼지만, 현재는 executor 직후 실패/BLOCKED mutation의 inverse tool 기반 롤백이다. 문서상 목표였던 Verifier 실패 후 runbook `rollback_plan` 실행과 보상(saga) 일반화는 별도 검증·확장이 필요하다. +- run record에는 모델 snapshot manifest와 prompt/catalog/evidence matrix/runbook/corpus/eval/code commit 정보가 저장된다. 다만 실제 LLM 호출 경로는 여전히 `gpt-4o` 같은 별칭을 반환하는 `model_for_agent()`를 쓰므로, 실행 모델 자체를 snapshot ID로 강제하는지는 별도 gap이다. +- prompt, catalog, evidence matrix, runbook, corpus, 평가셋 버전은 run reproducibility manifest에 고정된다. 남은 문제는 manifest 조회·재현 절차 검증과 실제 모델 호출 ID의 snapshot 강제 여부다. +- 35건 seed oracle replay의 AC@k/ECE 결과는 보존되어 있지만, resolved incident 기반 정기 평가·캘리브레이션 job은 아직 없다. +- threshold registry/API는 생겼고 이름·값·버전·근거·owner·보정 시각·dataset·rollback 값을 가진다. 다만 RCA scoring과 일부 Spring 설정은 아직 코드 상수·기본값을 직접 참조하므로 registry가 완전한 source of truth는 아니다. +- RCA gold set 저장소, 운영자 피드백 승격, trigger/symptom/root cause/contributing factor 라벨링 guide/API는 생겼다. 남은 문제는 운영 UI·검수 일관성·정기 평가 job이다. +- 알림은 사용자 영향 SLI와 SLO burn-rate 기반 page/ticket/diagnostic routing을 도입했다. 다만 Prometheus 비활성·측정 불가 시 static fallback이 남고, 최종 SLO 수치는 baseline 데이터로 보정해야 한다. - RCA 결과에서 트리거와 근본원인이 충분히 분리되어 있지 않다. -- 단순 상관 증거와 인과 증거의 차이가 evidence rule에 명시적으로 반영되어 있지 않다. -- 자연어 단순 질의에도 Router, Planner, Retrieval, Verifier, Report와 ReAct tool loop가 과도하게 호출될 수 있다. -- agent/tool 호출 수, 단계별 latency/cost, handoff 이유 같은 운영 관측 지표가 run 단위로 집계되지 않는다. +- 단순 상관 증거와 인과 증거의 차이를 표현하는 evidence rule 필드는 생겼지만, 모든 profile에 일관되게 보강하고 평가로 검증해야 한다. +- 자연어 단순 질의의 depth-aware 짧은 경로는 생겼지만, Router 휴리스틱 품질과 agent/tool 호출량 회귀 테스트가 아직 부족하다. +- run telemetry schema와 stage latency/agent 집계는 생겼다. 다만 tool/LLM/handoff call site instrumentation, 실제 cost_by_stage, budget_used 집계는 아직 부분 구현이다. ### 코드 개선 방향 따라서 다음 단계는 문서상 설명을 더하는 것이 아니라, 실제 코드와 데이터 모델을 보강하는 것이다. 1. **실패 시 되돌릴 수 있게 만든다.** - - 조치 전 상태를 저장하고, Verifier가 실패를 판단하면 `rollback_plan`을 실행한다. + - 현재 executor 직후 실패/BLOCKED mutation을 inverse tool로 되돌리는 기본 경로가 있다. Verifier 실패 후 runbook `rollback_plan` 실행과 더 넓은 보상 트랜잭션은 남은 과제로 둔다. 2. **같은 판단을 재현할 수 있게 만든다.** - - run마다 모델, 프롬프트, 카탈로그, evidence matrix, runbook, corpus manifest, 평가셋, 코드 commit을 저장한다. + - run마다 모델 snapshot manifest, 프롬프트, 카탈로그, evidence matrix, runbook, corpus manifest, 평가셋, 코드 commit을 저장한다. 실제 LLM 호출 모델을 alias가 아닌 snapshot으로 강제하는지는 추가 검증한다. 3. **RCA가 실제로 맞는지 측정한다.** - 자체 resolved incident 데이터로 AC@1/AC@3/AC@5/Avg@5를 계산한다. 4. **confidence를 실제 정답률에 맞춘다.** - ECE로 과신 구간을 찾고, UNKNOWN 기준과 confidence cap을 조정한다. 5. **임계값을 버전 관리되는 운영 파라미터로 바꾼다.** - - 임계값마다 이름, 값, 버전, 근거, 평가셋 버전, 마지막 보정 시각, owner, rollback 값을 저장한다. + - 임계값마다 이름, 값, 버전, 근거, 평가셋 버전, 마지막 보정 시각, owner, rollback 값을 저장하는 registry를 source of truth로 연결한다. 6. **알림을 사용자 영향 중심으로 바꾼다.** - - 데이터 신선도, end-to-end latency, 처리 성공률, 데이터 완전성 같은 SLI를 정의하고 SLO burn-rate 기반 page 알림을 추가한다. + - 데이터 신선도, end-to-end latency, 처리 성공률, 데이터 완전성 같은 SLI와 SLO burn-rate 기반 page/ticket routing을 운영 데이터로 보정한다. 7. **원인 지표는 page가 아니라 진단 근거로 재배치한다.** - - consumer lag, connector FAILED, replication lag 같은 정적 임계값은 사용자 영향이 있으면 page/ticket으로 올리고, 없으면 RCA evidence 또는 diagnostic signal로 낮춘다. + - consumer lag, connector FAILED, replication lag 같은 정적 임계값은 사용자 영향이 있으면 page/ticket으로 올리고, 없으면 diagnostic signal 또는 RCA evidence로 낮춘다. static fallback은 측정 불가 상황의 안전망으로만 둔다. 8. **RCA 설명에서 인과와 상관을 구분한다.** - - 시간 선행성(temporality)이 있는 증거만 required 인과 증거로 승격하고, 단순 동시 발생은 supporting까지만 인정한다. + - `temporality_required=True` 규칙은 시간 선행성이 있을 때만 required evidence로 인정하고, 단순 동시 발생은 supporting으로 낮춘다. 9. **자연어 질의는 필요한 agent만 호출하게 만든다.** - - Router가 `execution_depth`와 tool budget을 정하고, 단순 조회는 direct/single/bounded lookup으로 끝내며, ReAct는 인시던트 분석이나 식별자 chaining이 필요할 때만 켠다. + - 구현된 `execution_depth`와 tool budget 경로를 회귀 테스트로 고정한다. 단순 조회는 capped flat plan으로 direct/single/bounded lookup에서 끝내고, ReAct loop는 `incident_diagnosis`와 `remediation_planning`에서만 켠다. 10. **agent 실행을 관측 가능하게 만든다.** - - run마다 호출된 agent/tool, 호출 수, 단계별 latency, 실패·재시도·handoff 이유, 비용을 기록한다. + - run마다 호출된 agent/tool, 호출 수, 단계별 latency, 실패·재시도·handoff 이유, 비용을 기록한다. 현재는 schema/collector가 있고 실제 tool·LLM·handoff instrumentation은 보강 대상이다. ### 기준을 정하는 방식 @@ -136,13 +135,13 @@ | 구분 | 판정 | 내용 | |---|---|---| | 강점 | 충족 | 증거기반 RCA, UNKNOWN abstain, 사람 승인 게이트, 멱등성 키, 완전 감사로그, 카탈로그 버전 추적 | -| 갭 | 개선 필요 | 조치 실패 시 자동 롤백 부재, 모델 ID 스냅샷 핀고정 부재, 알림의 정적 임계값 의존, 트리거와 근본원인 미분리, 상관/인과 증거 구분 부재, 신뢰도 캘리브레이션(ECE) 미측정 | +| 갭 | 개선 필요 | 자동 롤백은 기본 구현됐으나 runbook rollback/saga 일반화가 필요하고, run 재현성 manifest는 저장되나 실제 LLM 호출 모델 alias 강제 해소가 남아 있다. SLI/SLO burn-rate routing은 구현됐지만 baseline 보정·측정 불가 fallback 검증이 필요하다. 트리거와 근본원인 미분리, 상관/인과 증거 구분 전수 보강, resolved incident 기반 정기 캘리브레이션은 계속 gap이다. | ### 1.3 우선 개선 항목 -1. **조치 실패 시 자동 롤백**: AWS OPS06-BP04 권고와 불일치. -2. **모델 ID 스냅샷 핀고정**: 재현성 보강 필요. -3. **알림 SLO화**: 현재는 대부분 정적 임계값이며 SLO/burn-rate 미적용. +1. **조치 실패 시 자동 롤백**: executor 실패/BLOCKED inverse rollback은 구현됨. Verifier 실패 후 runbook rollback과 보상(saga) 일반화는 보강 필요. +2. **모델 ID 스냅샷 핀고정**: run manifest 저장은 구현됨. 실제 LLM 호출 모델 alias를 snapshot ID로 강제하는 재현성 보강 필요. +3. **알림 SLO화**: SLI/SLO burn-rate routing은 구현됨. baseline 기반 SLO 수치 보정과 fallback 검증 필요. 4. **RCA 설명력 보강**: 트리거와 근본원인 분리, 상관/인과 증거 구분, 신뢰도 캘리브레이션 필요. --- @@ -152,13 +151,16 @@ ### 2.1 RCA 파이프라인 ```text -Classifier(인시던트 분류) +Router(mode/depth 판정) + -> Planner(수집 계획) -> Retrieval(증거 수집) + -> Classifier(인시던트 분류) -> RCA -> Remediation(조치 제안) -> Policy/Approval/Change Gate -> Executor(실행) -> Verifier + -> Report ``` | 구성요소 | 코드 위치 | 역할 | @@ -166,14 +168,14 @@ Classifier(인시던트 분류) | RCA 에이전트 | [services/ai-service/app/agents/rca.py](../../services/ai-service/app/agents/rca.py) | `run_rca()` | | 프롬프트 | [services/ai-service/app/prompts/rca.py](../../services/ai-service/app/prompts/rca.py) | RCA 출력 제약 | | 근본원인 카탈로그 | [services/ai-service/app/catalogs/root_causes.py](../../services/ai-service/app/catalogs/root_causes.py) | **8계층 35개** `root_cause_id` | -| 증거 매트릭스 | [services/ai-service/app/catalogs/evidence_matrix.py](../../services/ai-service/app/catalogs/evidence_matrix.py) | required/supporting/negative 증거 규칙 | +| 증거 매트릭스 | [services/ai-service/app/catalogs/evidence_matrix.py](../../services/ai-service/app/catalogs/evidence_matrix.py) | actionable **32개** evidence profile, required/supporting/negative 증거 규칙 | **판별 로직 요약** 1. 후보별 required/supporting/negative 증거를 lexical + semantic 매칭한다. 2. confidence를 계산한다. - - required 전부 충족: 0.82~0.92 - - required 부분 충족: `default_confidence_cap`(기본 0.79)로 상한 + - required 전부 충족: 기본 cap 기준 최대 0.88(`0.88 + supporting_ratio * 0.04`로 시작하지만 `RootCause.default_confidence_cap` 기본 0.88에 제한됨) + - required 부분 충족: `default_confidence_cap`(RootCause 기본 0.88, evidence 미충족 scoring에서는 action-ready가 아닌 낮은 confidence)와 missing evidence로 상한 - negative 증거: 건당 -0.10 3. 최상위 후보가 `MIN_CONFIDENT_ROOT_CAUSE`(0.60) 미만이면 `UNKNOWN_WITH_EVIDENCE_GAP`로 폴백한다. 4. LLM은 상위 2후보 신뢰도차가 0.10 미만일 때만 타이브레이커로 호출한다. @@ -193,7 +195,8 @@ Classifier(인시던트 분류) - `low/read-only` 조치는 자동 실행한다. - `medium` 이상은 사람 승인이 필수다. -- `rollback_plan` 필드는 존재하고 Change Gate에서 검증하지만, **자동 롤백 실행·보상(saga)은 없다**. +- executor는 조치 전 `pre_change_snapshot`을 남기고, FAILED/BLOCKED mutation에 대해 inverse tool 기반 자동 롤백을 시도한다. low/medium 원조치의 롤백은 자동 실행하고, high/forbidden 원조치의 롤백은 승인 대기로 남긴다. +- 남은 gap은 Verifier 실패 판정을 기준으로 runbook의 `rollback_plan`을 실행하는 일반 rollback stage와 보상(saga) 범위 확장이다. - 재시도는 Verifier 루프백 1회(`max_fail_loops=1`)다. ### 2.3 버전 관리 @@ -202,39 +205,40 @@ Classifier(인시던트 분류) |---|---|---| | 카탈로그 버전 | `catalog_version="0.1.0"` ([core/config.py](../../services/ai-service/app/core/config.py)), run 레코드마다 저장, health API 노출 | 없음 | | 상태 이력 | `StatePatch operation=VERSION` append-only 이력 | 없음 | -| 모델 매핑 | [llm/model_router.py](../../services/ai-service/app/llm/model_router.py)에서 `AGENT_TIER`(lightweight/analysis) -> `TIER_MODEL_DEFAULT`(gpt-4o-mini/gpt-4o) | `gpt-4o` 별칭 사용. 날짜 스냅샷 핀고정 아님 | -| 코퍼스 | `corpus/manifest.json` | 자체 버전 미관리 | +| 모델 매핑 | [llm/model_router.py](../../services/ai-service/app/llm/model_router.py)에서 `AGENT_TIER`(lightweight/analysis) -> `TIER_MODEL_DEFAULT`(gpt-4o-mini/gpt-4o)를 사용하고, run manifest에는 snapshot mapping을 저장한다 | 실제 LLM 호출 모델 선택은 여전히 alias 반환 경로를 사용하므로 snapshot ID 강제 여부 검증 필요 | +| run 재현성 manifest | run마다 `model_id`, `model_tier_map`, prompt hash/version, catalog/evidence matrix/runbook/corpus/eval/code commit, temperature를 저장 | manifest 조회·재현 절차와 실제 호출 모델 snapshot 강제는 추가 검증 필요 | +| 코퍼스 | `corpus/manifest.json`과 manifest hash를 run reproducibility에 저장 | 운영 corpus baseline/version 운영 절차는 보강 필요 | ### 2.4 장애 알림 기준 | 항목 | 현재 기준 | |---|---| | 생성 위치 | [operations-backend ... IncidentService.java](../../services/operations-backend/src/main/java/com/bifrost/ops/incident/IncidentService.java) — `onThresholdViolation()` | -| ERROR | 즉시 CRITICAL 인시던트 | -| WARN | 동일 리소스 30분 내 2건 이상이면 WARNING | -| 에스컬레이션 | WARNING + ERROR -> CRITICAL | +| ERROR | SLO burn-rate decision을 우선 적용한다. 사용자 영향이 확인되면 page/CRITICAL, 측정 불가나 fallback이면 ERROR 기준 page/CRITICAL | +| WARN | 동일 리소스 30분 내 2건 이상이면 인시던트 후보가 되며, SLO decision에 따라 ticket/WARNING 또는 diagnostic으로 분기 | +| 에스컬레이션 | 기존 인시던트는 SLO decision severity와 route를 반영하고, CRITICAL 조건이면 상향 | | 임계값 | `docs/spec.md` 부록 B 기준: consumer lag >= 5,000(WARN) / >= 50,000(CRIT), error rate > 0.5% / > 2%, connector FAILED, replication lag >= 1s / >= 5s, pipeline 생성 5분 초과 등 | -| 주요 갭 | 대부분 정적 임계값 | +| 주요 갭 | SLI/SLO burn-rate routing은 구현됐지만 Prometheus 비활성·측정 불가 시 static fallback이 남고, 최종 SLO 수치는 baseline 데이터로 보정해야 함 | ### 2.5 코드 기준 미구현·부분 구현 목록 -아래 표는 2026-06-19 현재 코드 검색 기준이다. "부분 구현"은 필드·테이블·기초 안전장치는 있으나 이 문서가 목표로 하는 운영 수준의 동작은 아직 없는 상태를 뜻한다. +아래 표는 2026-06-22 코드 기준이다. "부분 구현"은 필드·테이블·기초 안전장치는 있으나 이 문서가 목표로 하는 운영 수준의 동작은 아직 없는 상태를 뜻한다. 보안 redaction과 raw evidence store는 이미 일부 구현되어 있다. [evidence/redaction.py](../../services/ai-service/app/evidence/redaction.py), [evidence_repository.py](../../services/ai-service/app/persistence/evidence_repository.py), [state.py](../../services/ai-service/app/schemas/state.py)가 원문 로그·시크릿을 State에 직접 넣지 않는 방향을 갖고 있으므로, 이 문서의 주요 미구현 갭에서는 제외한다. | 영역 | 현재 코드 상태 | 미구현·부분 구현 갭 | 개선 시 필요한 산출물 | |---|---|---|---| -| 자연어 질의 실행 깊이 제어 | [RouterOutput](../../services/ai-service/app/schemas/outputs.py)은 `mode`, `remediation_requested`, `reuse_existing_analysis`, `required_flow`만 가진다. [transitions.py](../../services/ai-service/app/supervisor/transitions.py)는 `simple_query`도 고정 stage chain을 탄다. | `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy`가 없다. direct/single/bounded lookup fast path가 없다. | depth-aware RouterOutput, mode+depth 기반 stage selection, 대표 자연어 질의별 agent/tool 호출량 테스트 | -| agent 실행 관측성 | `agent_run`, `state_patch`, `run_event` 테이블은 있다. `Settings`에는 전체 run budget(`max_llm_calls_per_run`, `max_tokens_per_run`)이 있다. | run 단위 `called_agents`, `called_tools`, `tool_call_count`, `latency_by_stage`, `cost_by_stage`, `handoff_reason`, `budget_used` 집계가 없다. | run telemetry schema, stage timing/cost collector, agent/tool count 회귀 테스트 | -| run 단위 재현성 | [agent_run](../../services/ai-service/alembic/versions/001_create_agent_run_store.py)은 `catalog_version`만 저장한다. [model_router.py](../../services/ai-service/app/llm/model_router.py)는 agent tier별 기본 모델 별칭을 반환한다. | `model_id` 스냅샷, provider model revision, prompt hash/version, evidence matrix version, runbook version, corpus manifest hash, eval dataset version, code commit SHA가 run record에 없다. | run metadata 확장 migration, prompt/catalog/runbook/corpus manifest hash 저장, 재현성 조회 API | -| 자동 롤백 | `rollback_plan`은 [ActionCandidateOutput](../../services/ai-service/app/schemas/outputs.py)과 change ticket에 존재하고, [change_gate.py](../../services/ai-service/app/workflow/stages/change_gate.py)는 필수 메타데이터로 검증한다. | Verifier 실패 후 `rollback_plan`을 실행하는 rollback stage, `pre_change_snapshot`, `rollback_action_id`, `rollback_status`, rollback audit event가 없다. | rollback executor stage, risk-tiered rollback policy, rollback 결과 검증·감사로그 | -| threshold governance | RCA는 [rca.py](../../services/ai-service/app/agents/rca.py)의 `MIN_CONFIDENT_ROOT_CAUSE=0.60`, `LLM_TIE_MARGIN=0.10`을 사용한다. [types.py](../../services/ai-service/app/catalogs/types.py)는 `default_confidence_cap=0.79`, `min_confidence_for_action=0.80`을 기본값으로 둔다. Spring은 [PipelineStatusServiceImpl.java](../../services/operations-backend/src/main/java/com/bifrost/ops/pipeline/status/PipelineStatusServiceImpl.java)의 error rate 0.5%/2.0%, workspace lag threshold 기본값을 사용한다. | 임계값 이름·버전·근거·owner·보정 데이터셋·마지막 보정 시각·rollback 값이 없다. 코드 상수와 DB 기본값이 섞여 있어 "왜 이 값인가"를 추적하기 어렵다. | threshold registry/config table, `threshold_version`, calibration report linkage, 변경 이력·rollback 값 | -| RCA gold set·라벨링 | `services/ai-service/tests/test_rca_classification_accuracy.py` 같은 fixture 기반 회귀 테스트는 있다. | resolved incident 기반 gold set 저장소와 `accepted_root_cause_id`, `trigger`, `symptom`, `contributing_factor`, `human_verdict` 라벨링 프로토콜이 없다. | gold set schema, 운영자 검수 UI/API, 라벨링 가이드, inter-review consistency check | -| AC@k·ECE 평가 리포트 | RCA 후보와 confidence는 산출하지만, 평가 job/report는 없다. | AC@1/AC@3/AC@5/Avg@5, confidence bin별 accuracy/gap/ECE, UNKNOWN 오답 회피율을 정기 산출하지 않는다. | offline eval script, monthly calibration report, threshold recommendation artifact | -| online feedback·drift 감시 | incident 저장과 report snapshot은 있으나, 운영자 채택·수정·override를 평가 신호로 묶는 구조는 제한적이다. | confidence distribution drift, UNKNOWN 비율 급증, 특정 root cause 과다 예측, operator override 증가를 감시하지 않는다. | online feedback event, drift dashboard, threshold 재보정 trigger | -| 사용자 영향 SLI/SLO | [IncidentService.java](../../services/operations-backend/src/main/java/com/bifrost/ops/incident/IncidentService.java)는 threshold violation 중심으로 인시던트를 만든다. lag/error rate/connector state 신호는 있다. | `good_event/total_event` 기반 freshness, end-to-end latency, success, completeness SLI와 SLO burn-rate page 조건이 없다. | SLI metric schema, SLO config, burn-rate alert rule, page/ticket/diagnostic routing | -| 인과/상관 증거 태그 | [EvidenceRule](../../services/ai-service/app/catalogs/types.py)은 `kind`와 `semantic_allowed`만 가진다. | `causality_type`, `temporality_required`, `causal_chain_step`이 없어 단순 동시발생과 인과 증거를 구조적으로 구분하지 못한다. | evidence rule schema 확장, RCA scoring 수정, 인과 사슬 explanation | -| KEDB형 운영 지식화 | root cause catalog, evidence matrix, runbook catalog는 있다. | root cause별 owner, known symptoms, verified fixes, rollback, recurrence count, last_seen, incident links가 KEDB 레코드로 축적되지 않는다. | KEDB schema/API, RCA 결과와 runbook·owner·재발 이력 연결 | +| 자연어 질의 실행 깊이 제어 | [RouterOutput](../../services/ai-service/app/schemas/outputs.py)은 `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy`를 가진다. [transitions.py](../../services/ai-service/app/supervisor/transitions.py)는 simple-query lookup depth를 `planner -> retrieval -> report`로 줄이고 Verifier를 제외한다. | Router는 휴리스틱 기반이라 오분류 가능성이 있고, 대표 자연어 질의별 agent/tool 호출량 회귀 테스트가 부족하다. direct_answer도 transition상 planner/retrieval/report stage를 지난다. | depth 분류 테스트, tool budget 회귀 테스트, direct answer fast-path 품질 검증 | +| agent 실행 관측성 | `run_telemetry` schema, collector, state/repository 저장 경로가 있다. stage latency와 called_agents 요약은 저장된다. | 실제 workflow에서 tool/LLM/handoff call site instrumentation이 아직 부분적이다. `cost_by_stage`, `budget_used`는 별도 보강이 필요하다. | tool/LLM/handoff instrumentation, cost/budget 집계, agent/tool count 회귀 테스트 | +| run 단위 재현성 | run reproducibility manifest는 `model_id`, `model_tier_map`, prompt hash/version, catalog/evidence matrix/runbook/corpus/eval/code commit, temperature를 저장한다. [model_router.py](../../services/ai-service/app/llm/model_router.py)는 manifest용 snapshot mapping도 제공한다. | 실제 LLM 호출 모델 선택은 여전히 agent tier별 기본 모델 별칭을 반환하는 경로를 쓴다. manifest 조회·재현 절차 검증도 필요하다. | 실행 모델 snapshot 강제 여부 결정, 재현성 조회 API, 과거 run 재현 리허설 | +| 자동 롤백 | executor는 `pre_change_snapshot`을 남기고, FAILED/BLOCKED mutation에 대해 inverse tool 기반 자동 롤백을 실행한다. 결과는 rollback status/audit event/state patch로 남는다. | 현재 구현은 executor 직후 상태 기반 inverse rollback이다. Verifier 실패 후 runbook `rollback_plan` 실행과 보상(saga) 일반화는 남아 있다. | runbook rollback stage, risk-tiered rollback policy 확장, rollback 결과 검증·감사로그 | +| threshold governance | threshold registry/API는 `threshold_name`, `value`, `version`, `basis`, `owner`, `last_calibrated_at`, `dataset_version`, `rollback_value`를 가진다. RCA/Spring 주요 threshold 초기값도 등록되어 있다. | RCA scoring은 여전히 [rca.py](../../services/ai-service/app/agents/rca.py)의 `MIN_CONFIDENT_ROOT_CAUSE=0.60`, `LLM_TIE_MARGIN=0.10`과 [types.py](../../services/ai-service/app/catalogs/types.py)의 기본값을 직접 참조한다. registry가 모든 runtime threshold의 source of truth는 아니다. | runtime threshold registry 연결, calibration report linkage, 변경 이력 기반 rollback 운영 | +| RCA gold set·라벨링 | gold set schema/API, labeling guide, 운영자 feedback 저장, ai-service gold set 승격 경로가 있다. `accepted_root_cause_id`, `trigger`, `symptom`, `contributing_factors`, `human_verdict`가 모델에 포함된다. | 운영 UI, inter-review consistency, resolved incident 기반 정기 scheduled eval job/monthly calibration artifact는 남아 있다. | 운영자 검수 UI/API 보강, 라벨링 가이드 운영, 정기 eval/calibration job | +| AC@k·ECE 평가 리포트 | [scripts/rca_eval_campaign.py](../../services/ai-service/scripts/rca_eval_campaign.py)와 [results-20260622](../test/results-20260622/) JSON 결과가 있다. 35건 seed oracle incident-type replay 기준 current/floor AC@k·Avg@5·ECE·기권율을 보존한다. | resolved incident 기반 정기 평가 job, monthly calibration report, threshold recommendation artifact는 아직 없다. | scheduled eval job, monthly calibration report, threshold recommendation artifact | +| online feedback·drift 감시 | online feedback event table/API와 drift report가 있다. accept/modify/override, confidence PSI, UNKNOWN ratio, root cause concentration, override ratio를 계산한다. | dashboard, 운영 기준 확정, 자동 threshold 재보정 연결은 남아 있다. | drift dashboard, threshold 재보정 trigger 운영화 | +| 사용자 영향 SLI/SLO | `good_event/total_event` 기반 freshness, end-to-end latency, success, completeness, provisioning SLI 정의와 측정 API가 있다. SLO burn-rate service는 page/ticket/diagnostic routing과 severity_reason을 산출하고 [IncidentService.java](../../services/operations-backend/src/main/java/com/bifrost/ops/incident/IncidentService.java)에 반영된다. | Prometheus 비활성·측정 불가 시 UNKNOWN/static fallback이 남고, 최종 SLO 수치와 low-traffic 보정은 운영 baseline으로 확정해야 한다. | baseline 수집, SLO config 보정, fallback/라우팅 회귀 테스트 | +| 인과/상관 증거 태그 | [EvidenceRule](../../services/ai-service/app/catalogs/types.py)은 `kind`, `semantic_allowed`, `causality_type`, `temporality_required`, `causal_chain_step`을 가진다. [rca.py](../../services/ai-service/app/agents/rca.py)는 시간 선행성이 없는 `temporality_required` required match를 supporting으로 강등한다. | 현재 temporal rule은 일부 profile에만 적용되어 있고, 전체 evidence matrix의 인과/상관 태그 일관성과 평가 기반 confidence 보정이 아직 부족하다. | evidence rule 태그 전수 보강, RCA scoring/eval fixture 확장, 인과 사슬 explanation 검증 | +| KEDB형 운영 지식화 | KEDB schema/API/repository와 static KEDB report surface가 있다. root cause별 owner, known symptoms, verified fixes, rollback procedure, recurrence count, last_seen, incident links를 저장할 수 있다. | RCA 결과에서 recurrence/incident links를 자동 누적하는 경로는 별도 검증이 필요하다. | RCA 결과와 runbook·owner·재발 이력 자동 연결 | --- @@ -262,30 +266,30 @@ Classifier(인시던트 분류) | 표준 | 핵심 내용 | 우리 상태 | |---|---|---| -| [AWS Well-Architected OPS06-BP04](https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_mit_deploy_risks_auto_testing_and_rollback.html) | 목표 미달성·테스트 실패 등 사전정의 조건에서 롤백이 수동개입 없이 자동 트리거되어야 하며, 수동 단계는 안티패턴 | 자동 롤백 미구현 | +| [AWS Well-Architected OPS06-BP04](https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_mit_deploy_risks_auto_testing_and_rollback.html) | 목표 미달성·테스트 실패 등 사전정의 조건에서 롤백이 수동개입 없이 자동 트리거되어야 하며, 수동 단계는 안티패턴 | executor 실패/BLOCKED inverse rollback 기본 구현. Verifier 실패 후 runbook rollback 일반화는 보강 필요 | | [Google SRE — Automation](https://sre.google/sre-book/automation-at-google/) | 자동화 5단계 성숙도, 멱등성, blast-radius 제한(rate limiting), Diskerase 교훈(sanity check) | 멱등성 키 있음. rate limit·sanity check는 보강 여지 | | [NIST AI 100-1](https://nvlpubs.nist.gov/nistpubs/ai/nist.ai.100-1.pdf) | fail safely(MEASURE 2.6), supersede/disengage/deactivate(MANAGE 2.4), rollover/fallback | 승인 게이트·UNKNOWN 정지 | **답변 한 줄** -> 승인 게이트·멱등성·감사로그는 NIST·SRE 권고를 충족한다. 단, AWS가 권고하는 "검증 실패 시 자동 롤백"은 현재 미구현이며 automation maturity 로드맵상 다음 단계로 둔다. +> 승인 게이트·멱등성·감사로그에 더해 executor 실패/BLOCKED 조치의 inverse 자동 롤백은 구현했다. 단, AWS가 권고하는 넓은 의미의 "검증 실패 시 자동 롤백"을 완전히 충족하려면 Verifier 실패 후 runbook rollback과 보상(saga) 범위를 더 넓혀야 한다. ### Q3. 버전 관리 | 표준 | 핵심 내용 | 우리 상태 | |---|---|---| -| [ISO/IEC 42001:2023](https://www.iso.org/standard/42001) | 인증 가능 AIMS, 리스크 기반 라이프사이클·변경관리 | `catalog_version` 있음. 모델·프롬프트·평가셋 버저닝 보강 필요 | +| [ISO/IEC 42001:2023](https://www.iso.org/standard/42001) | 인증 가능 AIMS, 리스크 기반 라이프사이클·변경관리 | run reproducibility manifest에 모델 snapshot map·프롬프트·카탈로그·평가셋·코드 commit을 저장. 실제 LLM 호출 모델 snapshot 강제와 재현 절차 검증은 보강 필요 | | [NIST AI 100-1](https://nvlpubs.nist.gov/nistpubs/ai/nist.ai.100-1.pdf) | 정확도 측정은 대표 테스트셋 + 문서화된 방법론과 짝을 이루어야 하며, 일반화 한계 문서화(MEASURE 2.5)가 필요 | 평가셋·캘리브레이션 미비 | **답변 한 줄** -> 카탈로그·상태·모델 매핑은 버전 추적된다. 단, 모델이 날짜 스냅샷으로 핀고정돼 있지 않아 재현성 보강이 필요하다. +> run manifest에는 카탈로그·상태·모델 snapshot map·프롬프트·평가셋·코드 commit이 저장된다. 단, 실제 LLM 호출은 아직 alias 기반 모델 선택 경로를 쓰므로, 실행 모델까지 날짜 스냅샷으로 강제하는 재현성 보강이 필요하다. ### Q4. 장애 알림 기준 #### 현재 현황 -현재 알림은 §2.4처럼 명확한 정적 임계값 + ERROR/WARN 게이팅 + 에스컬레이션으로 구성되어 있다. 대부분은 connector failed, consumer lag, replication lag 같은 **원인 기반(cause-based)** 신호다. +현재 알림은 §2.4처럼 사용자 영향 SLI/SLO burn-rate decision을 우선 적용하고, 측정 불가나 Prometheus 비활성 상황에서는 정적 임계값 기반 fallback을 사용한다. connector failed, consumer lag, replication lag 같은 **원인 기반(cause-based)** 신호는 사용자 영향이 없으면 diagnostic signal로 낮추고, 사용자 영향 SLO가 깨질 때 page/ticket으로 올린다. #### 표준 근거 @@ -301,16 +305,16 @@ Classifier(인시던트 분류) #### 권고/갭 -우리 알림은 대부분 **원인 기반·정적 임계값**이라 SRE가 권고하는 **증상(사용자 영향) 기반** 알림이 약하다. +우리 알림은 사용자 영향 SLI/SLO burn-rate routing을 도입했지만, 아직 운영 baseline으로 SLO 수치를 확정하지 않았고 Prometheus 비활성·측정 불가 시 static fallback이 남는다. -1. 파이프라인의 사용자 영향 SLI(예: end-to-end 데이터 신선도/지연)를 정의한다. -2. 그 위에 burn-rate(멀티윈도우) page 알림을 추가한다. -3. 현 정적 임계값(lag/error rate)은 ticket(비-page) 수준으로 강등해 알림 피로를 억제한다. -4. severity를 Impact x Urgency로 산정하도록 명문화한다. 현재는 ERROR/WARN 단일축이다. +1. 파이프라인의 사용자 영향 SLI(예: end-to-end 데이터 신선도/지연)는 정의·측정 API를 갖췄다. +2. burn-rate(멀티윈도우) page/ticket rule을 추가했고 IncidentService의 severity/route/reason에 반영한다. +3. 현 정적 임계값(lag/error rate)은 사용자 영향이 없으면 diagnostic signal로 낮춘다. 다만 fallback 경로가 기대대로 제한되는지 회귀 테스트가 필요하다. +4. severity는 SLO burn-rate와 affected resource count를 포함한 `severity_reason`을 남긴다. 최종 impact/urgency 수치화는 운영 baseline으로 보정한다. **답변 한 줄** -> 현재는 원인 기반 정적 임계값이다. SRE 표준에 맞춰 사용자 영향 SLI 기반 burn-rate 알림을 상위에 두고, 원인 신호는 티켓 수준으로 내려 알림 피로를 줄이는 것이 다음 단계다. +> 사용자 영향 SLI 기반 burn-rate 알림을 상위에 두는 기본 경로는 구현됐다. 다음 단계는 운영 baseline으로 SLO 수치를 보정하고, 측정 불가 fallback과 low-traffic pipeline의 precision/recall을 검증하는 것이다. --- @@ -405,19 +409,19 @@ evidence matrix를 인과 사다리에 매핑한다. | 개선 항목 | 참고 기준 | Bifrost 적용 방식 | 산출물/완료 조건 | |---|---|---|---| -| 조치 실패 시 자동 롤백 | [AWS OPS06-BP04](https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_mit_deploy_risks_auto_testing_and_rollback.html), [Argo Rollouts AnalysisRun](https://argo-rollouts.readthedocs.io/en/stable/features/analysis/) | Executor가 조치 전 상태를 저장하고, Verifier가 성공 조건을 확인한다. 실패하면 runbook의 `rollback_plan`을 실행한다. `low/read-only`와 일부 `medium` 조치부터 자동화하고, `high` 위험 조치는 rollback 실행도 승인 대상으로 둔다. | `pre_change_snapshot`, `rollback_action_id`, `rollback_status`, `rollback_audit_event_id`가 run 결과에 남는다. 실패 조치가 수동 RCA 재실행 없이 원복된다. | -| run 단위 버전 고정 | [NIST AI RMF 1.0](https://nvlpubs.nist.gov/nistpubs/ai/nist.ai.100-1.pdf), [ISO/IEC 42001](https://www.iso.org/standard/42001), [MLflow Model Registry](https://mlflow.org/docs/latest/ml/model-registry/), [Langfuse Prompt Management](https://langfuse.com/docs/prompt-management/get-started) | RCA run마다 모델, 프롬프트, 카탈로그, evidence matrix, runbook, corpus manifest, 평가셋, 코드 commit을 저장한다. `gpt-4o` 같은 별칭만 저장하지 않고 가능한 경우 날짜 스냅샷 또는 provider model revision을 남긴다. | `run_id`로 당시 판단을 재현할 수 있다. 최소 필드: `model_id`, `prompt_version`, `prompt_hash`, `catalog_version`, `evidence_matrix_version`, `runbook_version`, `eval_dataset_version`, `corpus_manifest_hash`, `code_commit_sha`, `temperature`. | +| 조치 실패 시 자동 롤백 | [AWS OPS06-BP04](https://docs.aws.amazon.com/wellarchitected/latest/operational-excellence-pillar/ops_mit_deploy_risks_auto_testing_and_rollback.html), [Argo Rollouts AnalysisRun](https://argo-rollouts.readthedocs.io/en/stable/features/analysis/) | Executor가 조치 전 상태를 저장하고, FAILED/BLOCKED mutation은 inverse tool로 자동 롤백한다. `low/read-only`와 일부 `medium` 조치는 자동화하고, `high` 위험 조치는 rollback 실행도 승인 대상으로 둔다. | `pre_change_snapshot`, `rollback_action_id`, `rollback_status`, `rollback_audit_event_id`가 run 결과에 남는다. 남은 조건: Verifier 실패 후 runbook `rollback_plan` 실행과 보상(saga) 일반화. | +| run 단위 버전 고정 | [NIST AI RMF 1.0](https://nvlpubs.nist.gov/nistpubs/ai/nist.ai.100-1.pdf), [ISO/IEC 42001](https://www.iso.org/standard/42001), [MLflow Model Registry](https://mlflow.org/docs/latest/ml/model-registry/), [Langfuse Prompt Management](https://langfuse.com/docs/prompt-management/get-started) | RCA run마다 모델 snapshot map, 프롬프트, 카탈로그, evidence matrix, runbook, corpus manifest, 평가셋, 코드 commit을 저장한다. | `run_id`로 당시 manifest를 조회할 수 있다. 남은 조건: 실제 LLM 호출 모델이 alias가 아니라 snapshot/provider revision으로 강제되는지 검증하고, 재현성 조회 API와 재현 리허설을 마련한다. | | confidence 캘리브레이션(ECE) | [On Calibration of Modern Neural Networks](https://arxiv.org/abs/1706.04599), [PACE-LM](https://arxiv.org/abs/2309.05833) | resolved incident를 모아 confidence 구간별 실제 정답률을 측정한다. confidence 0.8 구간이 실제로 80% 전후로 맞는지 확인하고, 과신 구간은 confidence cap 또는 UNKNOWN 기준 상향으로 보정한다. | 월 1회 calibration report 생성. 구간별 `count`, `avg_confidence`, `accuracy`, `gap`, `ECE`를 기록한다. | | AC@k / Avg@k RCA 평가 | [RCAEval](https://arxiv.org/html/2412.17015v1) | RCA가 단일 정답만 맞히는지 보지 않고, 상위 후보 랭킹 안에 정답이 들어오는지 본다. Bifrost resolved incident마다 `accepted_root_cause_id`와 후보 랭킹을 저장한다. | `AC@1`, `AC@3`, `AC@5`, `Avg@5`를 root cause 계층별로 산출한다. `data_quality`, `connector`, `schema`, `infra`, `change` 계층별 약점을 볼 수 있다. | | UNKNOWN 기준 조정 | [Know Your Limits: Abstention Survey](https://arxiv.org/abs/2407.18418), [PACE-LM](https://arxiv.org/abs/2309.05833) | `UNKNOWN_WITH_EVIDENCE_GAP`은 실패가 아니라 안전장치로 둔다. confidence threshold와 required evidence completeness를 함께 본다. UNKNOWN일 때는 "추가로 필요한 증거"를 명시한다. | UNKNOWN 비율, UNKNOWN 중 실제 오답 회피율, UNKNOWN 후 추가 수집으로 원인 확정된 비율을 추적한다. | -| 사용자 영향 SLI 정의 | [Google SRE Workbook — Alerting on SLOs](https://sre.google/workbook/alerting-on-slos/), [Google SRE Book — Monitoring](https://sre.google/sre-book/monitoring-distributed-systems/) | 내부 Kafka 지표보다 사용자 관점의 데이터 파이프라인 품질을 상위 지표로 둔다. 후보 SLI는 데이터 신선도, end-to-end latency, 처리 성공률, 데이터 완전성, 중복률/누락률이다. | `good_event / total_event` 정의가 문서화된다. 예: source 이벤트가 허용 지연 내 sink에 정확히 1회 반영되면 good event. | -| SLO burn-rate page 알림 | [Google SRE Workbook — Alerting on SLOs](https://sre.google/workbook/alerting-on-slos/), [Datadog Burn Rate Alerts](https://docs.datadoghq.com/service_level_objectives/burn_rate/) | page는 consumer lag 같은 원인 지표가 아니라 사용자 영향 SLO가 빠르게 깨질 때 발생한다. Google SRE의 시작값을 기준으로 `1h/5m @14.4x`, `6h/30m @6x`는 page 후보, `3d/6h @1x`는 ticket 후보로 둔다. | `IncidentSeverity=CRITICAL`은 SLO burn-rate page 조건과 연결한다. low-traffic pipeline은 precision/recall/detection/reset time을 보고 별도 보정한다. | -| 정적 임계값 재분류 | [Google SRE Book — Monitoring](https://sre.google/sre-book/monitoring-distributed-systems/), [PagerDuty Severity Levels](https://response.pagerduty.com/before/severity_levels/) | 기존 consumer lag, connector FAILED, replication lag, error rate 임계값은 버리지 않는다. 다만 사용자 영향이 없으면 page가 아니라 ticket 또는 RCA 진단 신호로 낮춘다. | 알림 라우팅이 `page`, `ticket`, `diagnostic_signal`로 분리된다. 원인 지표는 RCA evidence로 남고, page는 사용자 영향 SLO 위반에 집중된다. | -| severity 산정 | [Atlassian/ITIL Problem Management](https://www.atlassian.com/itsm/problem-management/process), [PagerDuty Severity Levels](https://response.pagerduty.com/before/severity_levels/) | 현재 ERROR/WARN 단일축을 Impact x Urgency로 보강한다. 영향 범위(몇 개 pipeline/workspace/sink가 영향받는가)와 긴급도(error budget 소진 속도, 복구 가능 시간)를 함께 본다. | `severity_reason`에 impact, urgency, SLO burn-rate, affected_resource_count가 남는다. | +| 사용자 영향 SLI 정의 | [Google SRE Workbook — Alerting on SLOs](https://sre.google/workbook/alerting-on-slos/), [Google SRE Book — Monitoring](https://sre.google/sre-book/monitoring-distributed-systems/) | 내부 Kafka 지표보다 사용자 관점의 데이터 파이프라인 품질을 상위 지표로 둔다. 데이터 신선도, end-to-end latency, 처리 성공률, 데이터 완전성, provisioning 성공률을 `good_event / total_event`로 정의하고 측정 API를 제공한다. | 기본 SLI 정의·측정 API는 구현됨. 남은 조건: baseline 데이터로 최종 objective와 low-traffic 보정 기준을 확정한다. | +| SLO burn-rate page 알림 | [Google SRE Workbook — Alerting on SLOs](https://sre.google/workbook/alerting-on-slos/), [Datadog Burn Rate Alerts](https://docs.datadoghq.com/service_level_objectives/burn_rate/) | page는 consumer lag 같은 원인 지표가 아니라 사용자 영향 SLO가 빠르게 깨질 때 발생한다. Google SRE의 시작값을 기준으로 `1h/5m @14.4x`, `6h/30m @6x`는 page, `3d/6h @1x`는 ticket 후보로 구현했다. | `IncidentSeverity=CRITICAL`은 SLO burn-rate page 조건과 연결된다. 남은 조건: 측정 불가 fallback과 low-traffic pipeline precision/recall/detection/reset time 검증. | +| 정적 임계값 재분류 | [Google SRE Book — Monitoring](https://sre.google/sre-book/monitoring-distributed-systems/), [PagerDuty Severity Levels](https://response.pagerduty.com/before/severity_levels/) | 기존 consumer lag, connector FAILED, replication lag, error rate 임계값은 버리지 않는다. 다만 사용자 영향이 없으면 page가 아니라 diagnostic signal 또는 RCA evidence로 낮춘다. | 알림 라우팅이 `page`, `ticket`, `diagnostic`으로 분리된다. 원인 지표는 RCA evidence로 남고, page는 사용자 영향 SLO 위반에 집중된다. | +| severity 산정 | [Atlassian/ITIL Problem Management](https://www.atlassian.com/itsm/problem-management/process), [PagerDuty Severity Levels](https://response.pagerduty.com/before/severity_levels/) | ERROR/WARN 단일축을 SLO burn-rate 기반 Impact x Urgency로 보강한다. 영향 범위와 긴급도(error budget 소진 속도, 복구 가능 시간)를 함께 본다. | `severity_reason`에 impact, urgency, SLO burn-rate, affected_resource_count가 남는다. baseline 기반 severity threshold 보정은 남아 있다. | ### 5.3 Bifrost용 후보 SLI/SLO 정의 -아래 값은 최종 확정 수치가 아니라 **운영 데이터로 보정하기 전의 설계 후보**다. 구현 전에는 "Google SRE 방식으로 SLI를 정의하고, 2~4주 baseline 데이터로 SLO 수치를 확정한다"는 원칙을 둔다. 구현 후 발표에서는 실제 수집한 baseline, 선택한 SLO, 알림 라우팅 결과를 함께 설명한다. +아래 값은 최종 확정 수치가 아니라 **운영 데이터로 보정하기 전의 설계 후보**다. 현재는 SLI 정의와 초기 측정·routing API가 구현되어 있으므로, 다음 단계는 2~4주 baseline 데이터로 SLO 수치를 확정하고 발표에서는 실제 수집한 baseline, 선택한 SLO, 알림 라우팅 결과를 함께 설명하는 것이다. | SLI | good event 정의 | bad event 예시 | 관련 원인 지표 | |---|---|---|---| @@ -429,14 +433,14 @@ evidence matrix를 인과 사다리에 매핑한다. ### 5.4 page/ticket/diagnostic 라우팅 기준 -| 신호 | 현재 처리 | 개선 후 처리 | +| 신호 | 현재 처리 | 남은 보정 | |---|---|---| -| 사용자 영향 SLO burn-rate page 조건 충족 | 별도 SLO 기준 없음 | `CRITICAL` incident + page | -| 사용자 영향 SLO burn-rate ticket 조건 충족 | 별도 SLO 기준 없음 | `WARNING` incident + ticket | -| consumer lag WARN/CRIT | 임계값 기반 incident | SLO 영향이 있으면 page/ticket, 영향이 없으면 diagnostic evidence | -| connector FAILED | 원인 기반 incident | affected pipeline의 SLI 악화가 있으면 page/ticket, 없으면 ticket + RCA evidence | -| replication lag | 임계값 기반 incident | 데이터 신선도 SLI에 영향 있으면 ticket/page, 단기 회복이면 diagnostic evidence | -| pipeline 생성 5분 초과 | 정적 임계값 | provisioning SLO 위반으로 ticket/page 분류 | +| 사용자 영향 SLO burn-rate page 조건 충족 | `CRITICAL` incident + page route | baseline으로 objective와 burn-rate threshold 보정 | +| 사용자 영향 SLO burn-rate ticket 조건 충족 | `WARNING` incident + ticket route | low-traffic pipeline의 precision/recall 검증 | +| consumer lag WARN/CRIT | SLO 영향이 있으면 page/ticket, 영향이 없으면 diagnostic signal. 측정 불가 시 static fallback | fallback이 page noise를 만들지 않는지 검증 | +| connector FAILED | affected pipeline의 SLI 악화가 있으면 page/ticket, 없으면 diagnostic/RCA evidence | connector-only signal의 ticket/diagnostic 기준 보정 | +| replication lag | 데이터 신선도 SLI에 영향 있으면 ticket/page, 단기 회복이면 diagnostic evidence | freshness objective baseline 확정 | +| pipeline 생성 5분 초과 | provisioning SLI 기준으로 ticket/page 후보. SLI 측정 불가 시 static fallback | provisioning objective baseline 확정 | ### 5.5 구현·발표 반영 문장 @@ -444,29 +448,30 @@ evidence matrix를 인과 사다리에 매핑한다. --- -## 6. 자연어 질의 Agent 과다 호출 문제 +## 6. 자연어 질의 Agent 호출량 제어 ### 6.1 문제 정의 -현재 사용자가 자연어로 "상태 한번 봐줘", "지금 잘 돌아가?", "lag 확인해줘"처럼 묻는 경우, 실제 의도보다 많은 agent와 tool이 호출될 수 있다. 이 문제는 단순히 LLM이 말을 많이 해서 생기는 문제가 아니라, **현재 workflow가 자연어 질의를 최소 실행 경로로 줄이지 못하고 정해진 stage chain을 끝까지 타도록 설계되어 있기 때문**이다. +사용자가 자연어로 "상태 한번 봐줘", "지금 잘 돌아가?", "lag 확인해줘"처럼 묻는 경우, 실제 의도보다 많은 agent와 tool이 호출될 수 있다. 이 문제는 단순히 LLM이 말을 많이 해서 생기는 문제가 아니라, workflow가 자연어 질의를 최소 실행 경로로 줄이고 tool budget을 지키는지의 문제다. + +현재 코드는 `execution_depth`와 depth별 tool budget을 도입해 과다 호출 문제의 1차 구조를 해결했다. 단순 조회 depth는 Verifier를 건너뛰고 `planner -> retrieval -> report`로 끝난다. 남은 문제는 Router 휴리스틱이 의도를 잘 분류하는지, Planner/ReAct가 budget을 실제로 넘지 않는지, 대표 질의별 호출량이 테스트로 고정되어 있는지다. 서비스 관점에서 문제는 세 가지다. -1. 사용자는 단순 조회를 기대했는데 Router, Planner, Retrieval, Verifier, Report가 모두 뜬다. -2. Retrieval 안에서 ReAct tool loop가 추가로 여러 read-only tool을 호출할 수 있다. +1. Router 휴리스틱이 단순 조회를 인시던트 분석으로 오분류하면 여전히 무거운 stage를 탈 수 있다. +2. Retrieval/ReAct가 허용된 `max_tool_calls`와 `allow_react_loop`를 모든 경로에서 지키는지 회귀 테스트가 필요하다. 3. 같은 질문에 대화 히스토리가 붙으면서 Router/Planner가 이전 장애 맥락까지 보고 더 무거운 mode/tool을 고를 수 있다. -### 6.2 현재 코드 기준 원인 +### 6.2 현재 코드 기준 상태 -| 원인 | 코드 근거 | 영향 | +| 항목 | 코드 근거 | 현재 상태와 남은 영향 | |---|---|---| -| `simple_query`도 고정 stage chain을 탄다 | [transitions.py](../../services/ai-service/app/supervisor/transitions.py)의 `SIMPLE_QUERY_STAGES = ("planner", "retrieval", "verifier", "report")` | 단순 지식/상태 질의도 최소 4단계를 실행한다. | -| Router는 mode만 고르고 "최소 실행 깊이"를 고르지 않는다 | [agents/router.py](../../services/ai-service/app/agents/router.py)는 `mode`, `remediation_requested`, `reuse_existing_analysis`, `required_flow`만 반환한다 | "지식 답변만", "단일 tool 조회", "incident RCA" 같은 execution depth가 분리되지 않는다. | -| Planner prompt가 tool을 많이 고르도록 유도한다 | [prompts/planner.py](../../services/ai-service/app/prompts/planner.py)의 규칙: "상세·현황 류는 보통 2~4개를 함께 쓴다", "깊게 조회하라" | 자연어 현황 질의가 과도한 multi-tool plan으로 확장된다. | -| Retrieval이 ReAct 루프를 flat plan 위에 추가한다 | [agents/retrieval.py](../../services/ai-service/app/agents/retrieval.py)는 `mode in (SIMPLE_QUERY, INCIDENT_ANALYSIS)`이고 tool-calling 가능하면 `run_tool_loop(...)`를 실행한다 | Planner가 고른 tool 외에도 LLM이 추가 tool을 반복 호출한다. 이후 `remaining_steps`까지 실행해 중복·확장이 생길 수 있다. | -| ReAct 루프 max_steps가 6이고 tool budget이 intent별로 다르지 않다 | [agents/agentic.py](../../services/ai-service/app/agents/agentic.py)의 `run_tool_loop(..., max_steps=6)` | 단순 질의와 인시던트 분석이 같은 최대 tool loop 예산을 공유한다. | -| 대화 히스토리가 Router/Planner 입력에 그대로 붙는다 | [workflow/runner.py](../../services/ai-service/app/workflow/runner.py)의 `_contextualize(...)`가 이전 대화 블록을 현재 질문 앞에 붙이고, 그 결과가 Router/Planner/Retrieval에 전달된다 | "그거 봐줘" 같은 후속 질문은 좋아지지만, 이전 장애 키워드가 현재 단순 질의를 incident로 오염시킬 수 있다. | -| Verifier가 simple_query에도 들어간다 | `SIMPLE_QUERY_STAGES`에 `verifier`가 포함되어 있고, [runner.py](../../services/ai-service/app/workflow/runner.py)는 report 후에도 `run_verifier(... report_body=answer)`를 호출한다 | 단순 조회·지식 답변에도 검증 agent가 추가 호출된다. | +| `simple_query` depth-aware 경로 | [transitions.py](../../services/ai-service/app/supervisor/transitions.py)의 `SIMPLE_QUERY_LOOKUP_STAGES = ("planner", "retrieval", "report")`와 `_LOOKUP_DEPTHS` | 단순 조회는 Verifier를 건너뛰지만, direct answer도 stage상 planner/retrieval/report를 지난다. | +| Router가 실행 깊이를 고른다 | [agents/router.py](../../services/ai-service/app/agents/router.py)는 `_classify_depth()`로 `execution_depth`를 정하고 `depth_budget()` 결과를 `RouteDecision`에 넣는다 | 구현은 휴리스틱 기반이므로 오분류 회귀 테스트가 필요하다. | +| Planner prompt가 최소 충분 조회를 권장한다 | [prompts/planner.py](../../services/ai-service/app/prompts/planner.py)의 규칙: 기본은 가장 좁은 tool 1개이며, 단순 조회·현황 질의는 1~2개로 끝낸다 | prompt는 이미 보수적이다. 남은 리스크는 Router 오분류와 대표 자연어 질의별 호출량 회귀 테스트 부재다. | +| Retrieval/ReAct budget | [agents/retrieval.py](../../services/ai-service/app/agents/retrieval.py)는 `max_tool_calls==0`이면 운영 tool 호출을 건너뛰고, `allow_react_loop`가 켜진 depth에서만 ReAct를 돈다 | budget 집행은 구현됐지만 대표 자연어 질의별 호출량 테스트가 필요하다. | +| 대화 히스토리 제한 | [workflow/runner.py](../../services/ai-service/app/workflow/runner.py)는 depth budget의 `history_policy`를 사용한다 | `HistoryPolicy.NONE` 경로는 생겼지만, summary/full 정책의 오염 방지 품질은 별도 검증이 필요하다. | +| Verifier 제외 | simple-query lookup depth는 `SIMPLE_QUERY_LOOKUP_STAGES`를 타므로 Verifier를 포함하지 않는다 | 운영 변경·RCA 결론 경로에서는 Verifier가 유지된다. | | 설계 문서와 현재 구현 설명이 일부 불일치한다 | [contract-agent-roles.md](./backend-fastapi/contract/contract-agent-roles.md)는 Retrieval이 depends_on 순차 chain을 아직 해석하지 않는다고 쓰지만, 현재 [retrieval.py](../../services/ai-service/app/agents/retrieval.py)는 `_run_plan_steps(...)`에서 `depends_on` wave 실행을 구현한다 | 문서가 실제 agent 구조 검증의 기준으로 쓰이기 어렵다. 문서와 코드 동기화가 필요하다. | ### 6.3 외부 기준에서 본 올바른 방향 @@ -477,27 +482,27 @@ evidence matrix를 인과 사다리에 매핑한다. | [OpenAI Agents SDK — Guardrails](https://openai.github.io/openai-agents-python/guardrails/) | expensive model/tool 실행 전에 blocking guardrail을 둬 불필요한 비용·tool 실행을 막는다 | Router 앞 또는 Router 직후에 deterministic `query_scope_guard`를 두고, 지식 질의/단일 조회/인시던트 분석/조치 실행을 비용순으로 차단한다. | | [Anthropic Tool Use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview) | tool trigger boundary는 prompt와 `tool_choice`로 조절한다. "Use your judgment..."는 보수적 tool 사용을 유도한다 | Planner/ReAct prompt를 "필요한 만큼"에서 "최소 충분 tool"로 바꾸고, simple_query는 tool_choice none/auto/forced를 intent별로 분리한다. | | [AutoGen Termination](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/termination.html) | multi-agent run은 무한히 이어질 수 있으므로 message/token/timeout/handoff 등 termination condition이 필요하다 | 현재 step/loop guard는 있지만, simple_query 전용 `max_agents=2`, `max_tool_calls=1~2`, `no_tool_direct_answer` 같은 intent별 termination이 필요하다. | -| [ReAct](https://arxiv.org/abs/2210.03629) | reasoning과 acting을 interleave해 외부 지식을 쓰되, action은 필요한 정보를 얻기 위한 수단이다 | Bifrost의 ReAct 루프는 타당하지만 모든 simple_query에 켜면 과하다. 식별자 chaining이나 실제 운영 데이터가 필요한 경우에만 켜고, 지식/RAG 질의는 단락해야 한다. | +| [ReAct](https://arxiv.org/abs/2210.03629) | reasoning과 acting을 interleave해 외부 지식을 쓰되, action은 필요한 정보를 얻기 위한 수단이다 | Bifrost의 ReAct 루프는 incident_diagnosis/remediation_planning처럼 chaining과 진단 깊이가 필요한 경로에만 켠다. simple_query의 식별자 조회는 capped flat plan으로 처리하고, 지식/RAG 질의는 단락한다. | -### 6.4 해결 방향 +### 6.4 해결 방향과 현재 구현 상태 -#### 6.4.1 Router 출력에 `execution_depth`를 추가 +#### 6.4.1 Router 출력의 `execution_depth` -현재 Router의 mode만으로는 실행 깊이를 통제할 수 없다. RouterOutput에 다음 필드를 추가한다. +현재 Router는 mode와 함께 다음 필드를 반환한다. 외부 mock이나 구버전 출력에서 depth가 비어 있으면 runner가 mode 기준 기본 depth로 보정한다. | 필드 | 값 | 의미 | |---|---|---| | `execution_depth` | `direct_answer` | RAG 또는 cached context로 바로 답변. 운영 tool 호출 없음 | | | `single_lookup` | read-only tool 1개만 호출 | -| | `bounded_lookup` | read-only tool 2~3개까지 호출 | +| | `bounded_lookup` | read-only tool 2개까지 호출 | | | `incident_diagnosis` | classifier/RCA까지 실행 | -| | `remediation_planning` | RCA 뒤 remediation/policy_guard까지 실행 | +| | `remediation_planning` | RCA 뒤 remediation, policy_guard, approval_gate, verifier, report까지 실행 | | | `action_execution` | 승인/변경관리/실행 경로 | | `max_tool_calls` | int | 해당 turn에서 허용되는 read-only tool 호출 수 | | `allow_react_loop` | bool | ReAct tool loop 허용 여부 | | `history_policy` | `none`/`summary`/`full` | 다음 agent에 전달할 대화 이력 범위 | -서비스 기준 기본값: +현재 서비스 기준 기본값: | 사용자 의도 | 권장 depth | max_tool_calls | allow_react_loop | |---|---|---|---| @@ -508,14 +513,15 @@ evidence matrix를 인과 사다리에 매핑한다. | "원인과 조치 후보 알려줘" | `remediation_planning` | 4~6 | true | | "재시작해줘/승인할게" | `action_execution` | 0 read-only, mutation은 governance 경로 | false | -#### 6.4.2 `simple_query`를 세 갈래로 분리 +#### 6.4.2 `simple_query` lookup 경로 -현재 `simple_query`는 planner→retrieval→verifier→report를 항상 탄다. 아래처럼 분리한다. +현재 `simple_query`는 depth에 따라 tool budget을 달리하지만 stage 경로는 lookup depth에서 공통적으로 `planner -> retrieval -> report`다. `direct_answer`는 `max_tool_calls=0`으로 운영 tool을 호출하지 않는다. ```text simple_query.direct_answer - -> knowledge retrieval - -> deterministic/report answer + -> planner + -> retrieval(max_tool_calls=0) + -> report simple_query.single_lookup -> planner @@ -530,36 +536,36 @@ simple_query.bounded_lookup Verifier는 모든 simple_query에 넣지 않는다. 운영 변경, RCA 결론, 사용자 영향 판단처럼 검증 가치가 큰 출력에만 둔다. 단순 조회 답변은 schema validation과 evidence citation check로 대체한다. -#### 6.4.3 Planner prompt를 "최소 충분 조회"로 변경 +#### 6.4.3 Planner prompt의 "최소 충분 조회" 원칙 -현재 prompt는 "상세·현황 류는 보통 2~4개"를 권장한다. 이를 아래 원칙으로 바꾼다. +현재 prompt는 이미 아래 원칙을 적용한다. - 기본은 **가장 좁은 tool 1개**다. - 여러 tool은 사용자가 "원인 분석", "상관관계", "상세 진단"을 요청했거나 단일 tool로 답변 불가능할 때만 선택한다. -- 선택 이유와 예상 답변 가능성을 함께 출력한다. -- Router가 넘긴 `max_tool_calls`를 초과하면 안 된다. +- 출력은 선택한 `tools` 배열과 짧은 `reason` 두 키만 담은 JSON object 하나다. +- 선택 tool 수 상한은 prompt 원칙이 아니라 LLM 출력 이후 코드(planner)에서 결정론적으로 제한한다. #### 6.4.4 ReAct 루프를 조건부로만 켠다 -`run_tool_loop`는 chaining이 필요한 경우 유용하다. 예를 들어 topology에서 connector 이름을 찾고 이어서 connector status를 확인하는 흐름은 ReAct가 잘 맞는다. 하지만 모든 `simple_query`에 켜면 과도하다. +`run_tool_loop`는 chaining이 필요한 경우 유용하다. 하지만 모든 `simple_query`에 켜면 과도하다. 현재 simple_query의 식별자 조회는 planner가 만든 capped flat plan과 depends_on 실행으로 처리하고, ReAct loop는 incident/remediation depth에서만 허용한다. -조건: +현재 조건: - `allow_react_loop=true`일 때만 실행한다. -- `execution_depth in {"incident_diagnosis", "remediation_planning"}` 또는 식별자 chaining이 필요한 `bounded_lookup`에서만 허용한다. -- `simple_query.single_lookup`에서는 끈다. -- `max_steps`를 mode별로 다르게 둔다. +- `execution_depth in {"incident_diagnosis", "remediation_planning"}`에서만 허용한다(`_DEPTH_BUDGET` 기준). +- `simple_query.single_lookup`과 `bounded_lookup`에서는 끈다(`allow_react_loop=false`). +- `max_steps`(react_max_steps)는 mode별로 다르게 둔다. - direct/single: 0 - - bounded: 2 - - incident/remediation: 4~6 + - bounded: 2 (단 `allow_react_loop=false`라 실제 루프는 돌지 않음) + - incident/remediation: 6 -#### 6.4.5 대화 히스토리 input filter 도입 +#### 6.4.5 대화 히스토리 input filter -현재 `_contextualize(...)`는 이전 대화를 그대로 현재 질문 앞에 붙인다. 이 방식은 후속 질문에는 좋지만 Router/Planner에 불필요한 장애 키워드를 주입할 수 있다. +현재 depth budget은 `history_policy`를 함께 반환하고, runner가 일부 경로에서 이 정책을 사용한다. 이 방식은 후속 질문에는 좋지만 Router/Planner에 불필요한 장애 키워드를 주입할 수 있으므로 정책별 품질 검증이 필요하다. 개선: -- Router에는 "최근 사용자 발화 + 짧은 thread summary + 직전 run mode/action 상태"만 전달한다. +- Router에는 "최근 사용자 발화 + 짧은 thread summary + 직전 run mode/action 상태"만 전달하는 방향을 유지한다. - Planner에는 Router가 추출한 `normalized_intent`, `entities`, `execution_depth`만 전달한다. - Retrieval/ReAct에는 필요한 tool 입력만 전달하고, 전체 대화 히스토리는 기본 차단한다. - Report만 사용자 친화성을 위해 요약된 history를 볼 수 있게 한다. @@ -568,22 +574,21 @@ Verifier는 모든 simple_query에 넣지 않는다. 운영 변경, RCA 결론, #### 판정 -현재 Bifrost의 agent 구조는 **큰 방향은 맞지만, 자연어 질의 경로에서는 과분해되어 있다**. +현재 Bifrost의 agent 구조는 **큰 방향은 맞고, 자연어 질의 과분해를 줄이는 depth-aware 경로가 구현되어 있다**. 남은 위험은 routing 휴리스틱과 호출량 회귀 검증이다. 맞는 부분: - Router / Planner / Retrieval / Classifier / RCA / Remediation / Verifier / Report 역할 분리는 서비스 성격에 맞다. - Policy Guard, Executor, Approval Gate, Change Gate를 LLM 밖의 결정론적 단계로 둔 것은 옳다. - Tool registry allowlist와 Spring Boot 내부 API 위임 구조는 안전하다. -- ReAct loop는 "식별자 발견 -> 후속 tool 호출" 같은 운영 조회 chaining에는 적합하다. +- ReAct loop는 깊은 인시던트 진단이나 remediation planning에서 "식별자 발견 -> 후속 tool 호출" 같은 운영 조회 chaining을 처리할 때 적합하다. 문제 있는 부분: -- Router가 "어떤 agent까지 실행할지"를 충분히 결정하지 못한다. -- `simple_query`가 너무 무거운 기본 경로를 갖는다. -- Planner와 ReAct가 둘 다 tool 선택권을 가져 중복된다. -- Verifier가 단순 조회에도 들어가 agent 수를 늘린다. -- 대화 히스토리가 agent별로 필터링되지 않는다. +- Router의 depth 휴리스틱이 실제 사용자 질의를 충분히 잘 분류하는지 검증이 부족하다. +- `simple_query.direct_answer`도 stage상 planner/retrieval/report를 지나므로 no-stage fast path는 별도 개선 여지가 있다. +- Planner와 ReAct가 둘 다 tool 선택권을 갖는 경로는 budget 회귀 테스트로 묶어야 한다. +- 대화 히스토리 정책이 summary/full 경로에서 이전 장애 맥락 오염을 막는지 검증이 필요하다. - 설계 문서의 일부 설명이 현재 코드와 맞지 않는다. 서비스에 맞는 목표 구조: @@ -602,22 +607,22 @@ Router / Query Scope Guard | depth | 허용 stage | |---|---| -| `direct_answer` | knowledge retrieval, report | +| `direct_answer` | planner, retrieval(max_tool_calls=0), report | | `single_lookup` | planner, retrieval, report | | `bounded_lookup` | planner, retrieval, report | | `incident_diagnosis` | correlation, planner, retrieval, classifier, rca, verifier, report | -| `remediation_planning` | incident_diagnosis + remediation, policy_guard | +| `remediation_planning` | correlation, planner, retrieval, classifier, rca, remediation, policy_guard, approval_gate, verifier, report | | `action_execution` | policy_guard, approval_gate, change_gate, executor, verifier, report | ### 6.6 코드 개선 체크리스트 | 우선순위 | 개선 | 코드 영역 | 완료 기준 | |---|---|---|---| -| 높음 | RouterOutput에 `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy` 추가 | [schemas/outputs.py](../../services/ai-service/app/schemas/outputs.py), [agents/router.py](../../services/ai-service/app/agents/router.py), [prompts/router.py](../../services/ai-service/app/prompts/router.py) | 자연어 질의별 실행 깊이가 테스트로 고정됨 | -| 높음 | `stages_for_mode`를 depth-aware로 변경 | [supervisor/transitions.py](../../services/ai-service/app/supervisor/transitions.py), [workflow/runner.py](../../services/ai-service/app/workflow/runner.py) | simple direct/single lookup이 classifier/rca/verifier를 호출하지 않음 | +| 구현됨·검증 필요 | RouterOutput에 `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy` 추가 | [schemas/outputs.py](../../services/ai-service/app/schemas/outputs.py), [agents/router.py](../../services/ai-service/app/agents/router.py), [prompts/router.py](../../services/ai-service/app/prompts/router.py) | 자연어 질의별 실행 깊이가 테스트로 고정됨 | +| 구현됨·검증 필요 | `stages_for_mode`를 depth-aware로 변경 | [supervisor/transitions.py](../../services/ai-service/app/supervisor/transitions.py), [workflow/runner.py](../../services/ai-service/app/workflow/runner.py) | simple direct/single lookup이 classifier/rca/verifier를 호출하지 않음 | | 높음 | Planner prompt를 최소 충분 조회 원칙으로 수정 | [prompts/planner.py](../../services/ai-service/app/prompts/planner.py), [agents/planner.py](../../services/ai-service/app/agents/planner.py) | "현황" 질의가 기본 1~2개 tool로 제한됨 | -| 높음 | ReAct loop를 `allow_react_loop`와 `max_tool_calls`로 제한 | [agents/retrieval.py](../../services/ai-service/app/agents/retrieval.py), [agents/agentic.py](../../services/ai-service/app/agents/agentic.py) | simple_query에서 의도치 않은 3개 이상 tool 호출이 발생하지 않음 | -| 중간 | agent별 history input filter 도입 | [workflow/runner.py](../../services/ai-service/app/workflow/runner.py) | Router/Planner가 전체 대화 원문 대신 요약 intent만 받음 | +| 구현됨·검증 필요 | ReAct loop를 `allow_react_loop`와 `max_tool_calls`로 제한 | [agents/retrieval.py](../../services/ai-service/app/agents/retrieval.py), [agents/agentic.py](../../services/ai-service/app/agents/agentic.py) | simple_query에서 의도치 않은 3개 이상 tool 호출이 발생하지 않음 | +| 부분 구현 | agent별 history input filter 도입 | [workflow/runner.py](../../services/ai-service/app/workflow/runner.py) | Router/Planner가 전체 대화 원문 대신 요약 intent만 받음 | | 중간 | no-tool/direct-answer fast path 추가 | [workflow/runner.py](../../services/ai-service/app/workflow/runner.py), [agents/retrieval.py](../../services/ai-service/app/agents/retrieval.py) | 용어/문서 질의는 운영 API 호출 없이 답변 | | 중간 | 호출량 회귀 테스트 추가 | `tests/test_router_llm_routing.py`, `tests/test_planner_llm_routing.py`, `tests/test_agentic_loop.py`, `tests/test_routes_agent.py` | 대표 자연어 질의별 agent count/tool count가 assertion됨 | | 낮음 | 설계 문서와 구현 차이 정리 | [contract-agent-roles.md](./backend-fastapi/contract/contract-agent-roles.md), [agent-principles.md](./backend-fastapi/agent-principles.md) | Retrieval depends_on/ReAct/Verifier 현재 구현 설명이 코드와 일치 | @@ -628,19 +633,19 @@ Router / Query Scope Guard | 단계 | 개선 항목 | 주요 코드/문서 영역 | Bifrost 산출물 | 완료 기준 | |---|---|---|---|---| -| 1 | 자연어 질의 실행 깊이 제어 | `RouterOutput`, `transitions.py`, `runner.py`, `planner.py`, `retrieval.py`, `agentic.py` | `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy`, direct/single/bounded lookup flow | 단순 질의가 필요한 agent/tool만 호출하고, 대표 질의별 호출량이 테스트로 고정됨 | -| 2 | agent 실행 관측성 | `agent_run`, `run_event`, `runner.py`, tool registry, tracing 설정 | `called_agents`, `called_tools`, `tool_call_count`, `latency_by_stage`, `cost_by_stage`, `handoff_reason`, `budget_used` 저장 | 자연어 질의/인시던트 분석별 호출량·latency·비용을 run_id로 설명 가능 | -| 3 | threshold governance | RCA threshold config, Spring workspace settings, threshold registry migration | `threshold_name`, `value`, `version`, `basis`, `owner`, `last_calibrated_at`, `dataset_version`, `rollback_value` | 0.60/0.80/0.5%/2.0% 같은 값의 변경 근거와 이력이 조회됨 | -| 4 | run 단위 재현성 스키마 확장 | `services/ai-service/app/workflow/**`, run/state schema, `services/ai-service/app/llm/model_router.py` | RCA run record에 `model_id`, `prompt_version/hash`, `catalog_version`, `evidence_matrix_version`, `runbook_version`, `corpus_manifest_hash`, `code_commit_sha` 저장 | 과거 run 하나를 골라 당시 입력·버전·후보 랭킹을 재구성할 수 있음 | -| 5 | 자동 롤백 실행 경로 | `workflow/stages/executor.py`, `workflow/stages/verifier.py`, remediation runbook catalog, operations-backend action API | Executor/Verifier 뒤 `rollback_plan` 실행 단계, rollback audit event | medium 이하 승인 조치에서 성공 조건 미달 시 자동 원복됨 | -| 6 | RCA gold set·라벨링 프로토콜 | incident persistence, RCA output schema, 운영 검수 UI/API, `docs/design/backend-fastapi/contract/**` | `incident_id`, `accepted_root_cause_id`, `trigger`, `symptom`, `contributing_factor`, `evidence_ids`, `human_verdict` | 최소 30~50건의 초기 평가셋 확보. 라벨링 가이드로 trigger/root cause 혼동을 줄임 | +| 1 | 자연어 질의 실행 깊이 제어 | `RouterOutput`, `transitions.py`, `runner.py`, `planner.py`, `retrieval.py`, `agentic.py` | `execution_depth`, `max_tool_calls`, `allow_react_loop`, `history_policy`, direct/single/bounded lookup flow | 기본 구현은 완료. 단순 질의가 필요한 agent/tool만 호출하는지 대표 질의별 호출량 테스트로 고정 필요 | +| 2 | agent 실행 관측성 | `run_telemetry`, `state_patch`, `runner.py`, tool registry, tracing 설정 | schema/collector와 stage latency·agent 집계는 구현됨. tool/LLM/handoff instrumentation, `cost_by_stage`, `budget_used`는 보강 필요 | 자연어 질의/인시던트 분석별 호출량·latency·비용을 run_id로 설명 가능 | +| 3 | threshold governance | RCA threshold config, Spring workspace settings, threshold registry migration | threshold registry/API는 구현됨. RCA scoring과 일부 Spring 설정을 registry source of truth로 연결해야 함 | 0.60/0.80/0.5%/2.0% 같은 값의 변경 근거와 이력이 조회되고 runtime이 그 값을 사용함 | +| 4 | run 단위 재현성 스키마 확장 | `services/ai-service/app/workflow/**`, run/state schema, `services/ai-service/app/llm/model_router.py` | RCA run record에 `model_id`, `model_tier_map`, `prompt_version/hash`, `catalog_version`, `evidence_matrix_version`, `runbook_version`, `corpus_manifest_hash`, `eval_dataset_version`, `code_commit_sha` 저장 구현됨 | 실제 LLM 호출 모델 snapshot 강제 여부와 과거 run 재현 리허설 검증 | +| 5 | 자동 롤백 실행 경로 | `workflow/stages/executor.py`, `workflow/stages/rollback.py`, remediation runbook catalog, operations-backend action API | executor 실패/BLOCKED inverse rollback은 구현됨. Verifier 실패 후 runbook `rollback_plan` 실행은 보강 필요 | medium 이하 승인 조치에서 성공 조건 미달 시 자동 원복됨. high-risk rollback은 승인 대기로 남음 | +| 6 | RCA gold set·라벨링 프로토콜 | incident persistence, RCA output schema, 운영 검수 UI/API, `docs/design/backend-fastapi/contract/**` | gold set schema/API, 라벨링 guide, 운영자 feedback 승격은 구현됨 | 최소 30~50건의 운영 평가셋 확보. 운영 UI와 inter-review consistency check 보강 | | 7 | RCA 성능 리포트 | ai-service eval script/report, RCA result storage, catalog taxonomy | AC@1/AC@3/AC@5/Avg@5, 계층별 성능, UNKNOWN 비율 | confidence threshold와 UNKNOWN 기준을 데이터로 조정 가능 | | 8 | ECE 캘리브레이션 | confidence scoring module, evaluation report, RCA threshold config | confidence bin별 accuracy/gap/ECE 리포트 | 과신 구간 확인 후 confidence cap 또는 UNKNOWN threshold 재설정 | -| 9 | online feedback·drift 감시 | report snapshot, incident review API/UI, run metrics dashboard | operator override, 채택률, UNKNOWN 비율, root cause 분포, confidence distribution drift | 월별 리포트 외에도 threshold 재보정 trigger가 운영 지표로 발생 | -| 10 | 사용자 영향 SLI 정의 | operations-backend monitoring/incident service, metrics schema, Prometheus queries, `docs/design/backend-springboot/monitoring.md` | freshness, latency, success, completeness, provisioning SLI 명세 | `good_event/total_event` 계산식이 Prometheus/DB 쿼리로 구현 가능 | -| 11 | SLO burn-rate 알림 도입 | `IncidentService.java`, alert rule config, severity mapping, notification routing | page/ticket alert rule, severity mapping | page는 SLO burn-rate 위반 중심, lag/FAILED 등 원인 지표는 ticket/diagnostic으로 분리 | -| 12 | 인과/상관 증거 태그 | `catalogs/evidence_matrix.py`, `catalogs/root_causes.py`, `agents/rca.py`, RCA prompt/output schema | `EvidenceRule.causality_type`, `temporality_required`, `causal_chain_step` | 단순 동시발생은 supporting까지만 반영, 선행성 있는 증거만 required로 승격 | -| 13 | KEDB형 카탈로그 확장 | root cause catalog, remediation runbooks, policy matrix, owner metadata | root cause별 원인, 증상, 대응, rollback, owner, 직접조치정책, 재발 이력 | RCA 결과가 조치·rollback·운영 소유자·재발 방지까지 이어짐 | +| 9 | online feedback·drift 감시 | report snapshot, incident review API/UI, run metrics dashboard | online feedback event와 drift report는 구현됨. dashboard, 운영 기준, 자동 재보정 연결은 보강 필요 | 월별 리포트 외에도 threshold 재보정 trigger가 운영 지표로 발생 | +| 10 | 사용자 영향 SLI 정의 | operations-backend monitoring/incident service, metrics schema, Prometheus queries, `docs/design/backend-springboot/monitoring.md` | freshness, latency, success, completeness, provisioning SLI 정의·측정 API는 구현됨 | baseline 데이터로 `good_event/total_event` objective와 low-traffic 보정 확정 | +| 11 | SLO burn-rate 알림 도입 | `IncidentService.java`, alert rule config, severity mapping, notification routing | page/ticket/diagnostic routing과 severity_reason 저장은 구현됨 | page는 SLO burn-rate 위반 중심, lag/FAILED 등 원인 지표는 diagnostic/fallback으로 분리되는지 검증 | +| 12 | 인과/상관 증거 태그 | `catalogs/evidence_matrix.py`, `catalogs/root_causes.py`, `agents/rca.py`, RCA prompt/output schema | `EvidenceRule.causality_type`, `temporality_required`, `causal_chain_step` | 필드와 temporal required 강등 로직은 구현됨. 전체 profile 태그 일관성·평가 fixture 보강 필요 | +| 13 | KEDB형 카탈로그 확장 | root cause catalog, remediation runbooks, policy matrix, owner metadata | KEDB schema/API/repository와 report surface는 구현됨 | RCA 결과가 조치·rollback·운영 소유자·재발 방지까지 자동 누적되는 경로 검증 | --- @@ -651,12 +656,12 @@ Router / Query Scope Guard | 항목 | 상태 | 대응 | |---|---|---| | Bifrost SLO 수치 확정 | 외부 표준은 방법론과 시작값을 제공하지만, 최종 수치는 서비스 특성에 맞춰야 함 | 2~4주 baseline 데이터로 freshness/latency/success/completeness 목표치 확정 | -| RCA gold set 확보 | 현재는 resolved incident 정답셋이 부족함 | 운영자가 확정한 `accepted_root_cause_id`, trigger, evidence를 축적 | -| RCA 라벨링 기준 확정 | trigger, symptom, root cause, contributing factor가 섞이면 AC@k/ECE가 왜곡됨 | 라벨링 가이드와 운영자 검수 UI/API 필요 | +| RCA gold set 확보 | gold set schema/API와 운영자 feedback 승격은 구현됨. 실제 resolved incident 정답셋 규모가 부족함 | 운영자가 확정한 `accepted_root_cause_id`, trigger, evidence를 축적 | +| RCA 라벨링 기준 확정 | 라벨링 guide/API는 구현됨. trigger, symptom, root cause, contributing factor 혼동이 AC@k/ECE를 왜곡하지 않는지 운영 검수 일관성 확인 필요 | 운영자 검수 UI와 inter-review consistency check 보강 | | UNKNOWN 임계값 확정 | 현재 0.60은 bootstrap 기준 | ECE와 AC@k 결과로 threshold 재조정 | -| threshold registry 설계 | 현재 임계값이 코드 상수·DB 기본값·문서에 흩어져 있음 | threshold name/version/basis/owner/calibration metadata 스키마 확정 | -| agent 실행 관측성 | run_event는 있으나 호출량·비용·latency 집계는 부족함 | `called_agents`, `called_tools`, `tool_call_count`, `latency_by_stage`, `cost_by_stage` 저장 | -| online drift 기준 | 월별 offline report만으로는 운영 변화 감지가 늦음 | UNKNOWN 비율, override 비율, confidence 분포, root cause 분포 drift 기준 정의 | +| threshold registry 설계 | registry/API와 metadata 스키마는 구현됨. runtime threshold가 전부 registry를 source of truth로 쓰지는 않음 | RCA/Spring threshold 연결과 calibration 기반 변경 절차 확정 | +| agent 실행 관측성 | telemetry schema/collector는 구현됨. tool/LLM/handoff call site와 비용·budget 집계가 부족함 | `called_agents`, `called_tools`, `tool_call_count`, `latency_by_stage`, `cost_by_stage`, `budget_used` 저장 완성 | +| online drift 기준 | online feedback event와 drift report는 구현됨. 운영 기준과 재보정 trigger 연결이 미완성 | UNKNOWN 비율, override 비율, confidence 분포, root cause 분포 drift 기준 운영화 | | 정적 임계값 vs 이상탐지 | ML 이상탐지 보강 여부는 미결정 | 별도 PoC 필요. 현 단계 우선순위 낮음 | --- diff --git a/docs/presentation/03-ai-metrics.md b/docs/presentation/03-ai-metrics.md index 54afd928..f074a94c 100644 --- a/docs/presentation/03-ai-metrics.md +++ b/docs/presentation/03-ai-metrics.md @@ -17,15 +17,15 @@ | 지표 | 값 | 의미 | 측정 기준 | |---|---|---|---| -| **AI 진단 정확도** | **AC@5 80% · AC@3 80% · AC@1 66%** | 상위 후보 랭킹에 정답 포함(RCAEval 표준) | gold set 35건, 관측증거-only·LLM 비활성(보수적 floor) | -| **확신 시 정확도** | **82%** (23/28) | 기권하지 않은 답변의 정답률 | 불확실 7건은 기권 | -| **환각** | **≈ 0** | 날조 0건. 불확실 시 기권 | 기권 20%(7/35) 전부 정직한 보류 | -| **신뢰도 캘리브레이션** | **ECE 0.073** | 신뢰도≈실제 정답률 (<0.10 양호) | 10-bin | -| **정보 유출** | **0건** | 원문 로그·시크릿 미전달(redaction) | (유도공격 차단 — 별도 검증 권장) | -| **승인 없는 자동 실행** | **0건** | 변경은 승인 게이트(HITL) 통과만 | 직접 실행 경로 없음 | +| **AI 진단 정확도** | **current AC@1/3/5 100.0% · floor AC@1 71.43% / AC@5 85.71%** | 상위 후보 랭킹에 정답 포함(RCAEval 표준) | gold set 35건, oracle incident-type seed replay. classifier end-to-end·production holdout 아님 | +| **보류** | **current 0/35 · floor 5/35** | 증거 부족 시 UNKNOWN으로 기권 | `rca_campaign_current.json`, `rca_campaign_floor.json` | +| **환각** | **카탈로그 밖 root cause 생성 0건** | 후보는 카탈로그 root cause로 제한 | seed replay 결과 기준 | +| **신뢰도 캘리브레이션** | **current ECE 0.1595 · floor ECE 0.0832** | 신뢰도 구간 vs 실제 정답률 격차 | 10-bin | +| **정보 유출** | **공격 건수 미측정** | redaction/요약 방어 코드는 있으나 공개 공격 하네스 없음 | 별도 보안 테스트 필요 | +| **승인 없는 자동 실행** | **공격 건수 미측정** | 변경은 승인 게이트(HITL) 통과만 | 별도 오조치 유도 하네스 필요 | > ⚠️ 기존 슬라이드의 "89.6% / 367 케이스"는 본 캠페인과 데이터셋·산출법이 달라 **출처 확인 전 사용 금지**. 위 재현 가능한 수치 사용 권장. -> ⚠️ "정보유출 0(32건)·자동실행 0(20건)"의 유도공격 건수는 별도 보안 테스트 산출물이 있으면 링크, 없으면 "0건 차단"만 단정. +> ⚠️ "정보유출 0(32건)·자동실행 0(20건)"의 유도공격 건수는 별도 보안 테스트 산출물이 없으면 미측정으로 표기한다. ## 슬라이드 B — "정확도 한 숫자"를 넘는 RCA 표준 평가축 @@ -58,8 +58,8 @@ flowchart LR ## 발표자 노트 -- p.16의 4숫자는 그대로 살리되, **"왜 믿을 수 있나"의 측정 방법**(슬라이드 B)을 한 장 더해 깊이를 준다. -- "정확도 89.6% 어떻게 쟀나?"는 거의 확실히 나오는 질문 → top-5/367케이스/정답 증거 기준을 명확히. 산출 스크립트를 백업 슬라이드로. +- p.16의 4숫자는 current/floor JSON 기준으로 교체하고, **"왜 믿을 수 있나"의 측정 방법**(슬라이드 B)을 한 장 더해 깊이를 준다. +- "정확도 89.6% 어떻게 쟀나?" 질문에는 이 저장소에 89.6%/367의 재현 근거가 없으므로 사용하지 않는다고 답한다. 백업 슬라이드는 `rca_eval_campaign.py`와 `results-20260622/*.json`을 기준으로 한다. - "앞으로 더 좋아지나?"는 → 슬라이드 C(#964 피드백 루프 → AC@k/ECE 캘리브레이션). ## 캡처/시각 필요 diff --git "a/docs/test/agent-\352\262\200\354\246\235\352\262\260\352\263\274-\352\270\260\354\244\200\354\266\234\354\262\230-20260622.md" "b/docs/test/agent-\352\262\200\354\246\235\352\262\260\352\263\274-\352\270\260\354\244\200\354\266\234\354\262\230-20260622.md" index d17d51de..67696872 100644 --- "a/docs/test/agent-\352\262\200\354\246\235\352\262\260\352\263\274-\352\270\260\354\244\200\354\266\234\354\262\230-20260622.md" +++ "b/docs/test/agent-\352\262\200\354\246\235\352\262\260\352\263\274-\352\270\260\354\244\200\354\266\234\354\262\230-20260622.md" @@ -143,7 +143,7 @@ | 통제 | 메커니즘 | 코드 | |---|---|---| | 카탈로그-바운드 RCA | 35개 root cause 외 생성 불가, 미충족 시 기권 | `rca.py`, `verifier.py`(catalog-외 id FAIL) | -| 신뢰도 3구간 게이트 | ≥0.80 조치 / 0.60~0.79 추가확인 / <0.60 UNKNOWN | `rca.py` `MIN_CONFIDENT_ROOT_CAUSE=0.60`, evidence_matrix `min_confidence_for_action=0.80`·`cap=0.79` | +| 신뢰도 3구간 게이트 | ≥0.80 조치 / 0.60~0.79 추가확인 / <0.60 UNKNOWN | `rca.py` `MIN_CONFIDENT_ROOT_CAUSE=0.60`, `EvidenceProfile.min_confidence_for_action=0.80`, `needs_more_evidence_band=(0.60, 0.79)` | | Evidence 게이트 | required/supporting/negative 분리, required 미충족 시 conf cap | `rca.py` `_evaluate_candidate` | | 무승인 실행 차단(HITL) | mutation 4종 전부 승인 필요, 미승인 시 `waiting_for_approval`·BLOCKED(Spring 호출 전) | `approval_gate.py`, `registry.py:561-571`, policy_matrix | | 정보유출 차단 | 자격증명 마스킹·요약 화이트리스트·evidence는 id만 | `evidence/redaction.py`, `tools/result.py` | diff --git "a/docs/test/agent-\354\232\264\354\230\201\355\205\214\354\212\244\355\212\270-\354\235\270\353\262\244\355\206\240\353\246\254.md" "b/docs/test/agent-\354\232\264\354\230\201\355\205\214\354\212\244\355\212\270-\354\235\270\353\262\244\355\206\240\353\246\254.md" index a9ebf1cd..e2a11f1c 100644 --- "a/docs/test/agent-\354\232\264\354\230\201\355\205\214\354\212\244\355\212\270-\354\235\270\353\262\244\355\206\240\353\246\254.md" +++ "b/docs/test/agent-\354\232\264\354\230\201\355\205\214\354\212\244\355\212\270-\354\235\270\353\262\244\355\206\240\353\246\254.md" @@ -10,19 +10,21 @@ | 수치 | 상태 | 근거 | |---|---|---| -| **top-1 89.6% / top-5 100% / 367 케이스** | ⛔ **사용 금지** | 코드 어디에도 없음(grep 0건). [docs/test/rca-test-campaign-20260622.md](docs/test/rca-test-campaign-20260622.md)가 "산출법·데이터셋이 달라 출처 검증 전까지 사용 금지"로 명시 | -| **AC@1 65.7%(23/35) / AC@3=AC@5 80%(28/35) / Avg@5 0.724 / ECE 0.073 / 기권 7/35** | ✅ **재현 가능** | `scripts/rca_eval_campaign.py` 실행값 = 캠페인 문서 실측치와 일치 | -| 확신 시 정확도(precision) 82%(23/28), 환각 ≈0, 정보유출 0, 무승인 실행 0 | ✅ 문서 단일 출처 | [docs/presentation/03-ai-metrics.md](docs/presentation/03-ai-metrics.md) | -| 라이브 NL 라우팅 routing@1 83.3%(18케이스) | ✅ 실측 존재 | `eval/reports/nl_tool_routing_live_20260622T060606Z.json` | +| **top-1 89.6% / top-5 100% / 367 케이스** | ⛔ **사용 금지** | 코드·커밋된 결과물 어디에도 없음. [rca-test-campaign-20260622.md](rca-test-campaign-20260622.md)가 "출처 검증 전까지 사용하지 말 것"으로 명시 | +| **현재 `rca_eval_campaign.py` 재현값** | ✅ **재현 가능** | `docs/test/results-20260622/rca_campaign_current.json`: oracle incident-type replay 35건, AC@1/3/5 100.0%, Avg@5 1.0000, ECE 0.1595, 기권 0/35 | +| **보존 floor 결과물** | ✅ **커밋된 결과물 존재** | `docs/test/results-20260622/rca_campaign_floor.json`: AC@1 71.43%, AC@3/5 85.71%, Avg@5 0.781, ECE 0.0832, 기권 5/35 | +| AC@1 65.7%(23/35) / AC@3=AC@5 80%(28/35) / Avg@5 0.724 / ECE 0.073 / 기권 7/35 | ⚠️ **과거 초안 수치** | 현재 커밋된 JSON과 불일치. 발표 지표로는 current/floor JSON 값을 우선 사용 | +| 확신 시 정확도 82%(23/28), 환각 ≈0, 정보유출 0, 무승인 실행 0 | ⚠️ **부분 근거** | 82%(23/28)는 위 과거 초안에서 파생. 정보유출·무승인 실행 공격 건수 하네스는 ai-service에 없음 | +| 라이브 NL 라우팅 routing@1 83.3%(18케이스) | ⚠️ **커밋 근거 없음** | `eval/reports/`는 `.gitignore`만 커밋됨. 소스에는 NL 라우팅 라벨 18건과 dry-run/채점 하네스만 존재 | | Macro-F1 / Wilson 95% CI / NO_FAULT | ⚠️ **미구현** | 코드에 산출 로직 없음 — 발표에 쓰려면 별도 계산 필요 | -→ 첨부된 팀원 보고서(2026-06-16/18)의 89.6%/367은 **구버전**. 오늘자 재측정의 목적이 바로 이 수치를 재현 가능한 값으로 교체하는 것. +→ 첨부된 팀원 보고서(2026-06-16/18)의 89.6%/367은 이 저장소의 코드·커밋된 결과물로 재현되지 않는다. 오늘자 재측정의 목적은 이 수치를 재현 가능한 값으로 교체하는 것. --- ## 1. 지금 바로 수치를 뽑는 법 (즉시 실행 하네스) -작업 디렉토리: `cd /Users/hvvnnn/Desktop/dev/bifrost/services/ai-service` +작업 디렉토리: `cd /Users/gwonsebin/bifrost-docsync/services/ai-service` | # | 하네스 | 명령 | 산출 수치 | 파괴성 | |---|---|---|---|---| @@ -33,14 +35,14 @@ | 5 | 회귀 단위 수치 | `.venv/bin/python -m pytest tests/test_eval_accuracy.py tests/test_calibration.py tests/test_rca_classification_accuracy.py -q` | AC@k/ECE/분류 회귀 통과 | 비파괴 | | 6 | **RCA 라이브 fault 주입**(파괴적) | `.venv/bin/python -m eval.online.live_eval --live --confirm --faults sink_db_down` | 실주입 AC@k, captured, 복구확인 | ⚠️ 파괴적(이중 가드 `--live`+`--confirm`) | -- 케이스 데이터: gold set 35건([app/evaluation/seed_gold_set.py](services/ai-service/app/evaluation/seed_gold_set.py) `SEED_ENTRIES`), 라이브 fault 13개(auto 2 / manual 5 / unsafe 6, [eval/online/live_fault_specs.py](services/ai-service/eval/online/live_fault_specs.py)), NL 라우팅 18케이스([eval/online/nl_tool_routing.py](services/ai-service/eval/online/nl_tool_routing.py) `ROUTING_CASES`). +- 케이스 데이터: gold set 35건([app/evaluation/seed_gold_set.py](../../services/ai-service/app/evaluation/seed_gold_set.py) `SEED_ENTRIES`), 라이브 fault 13개(auto 2 / manual 5 / unsafe 6, [eval/online/live_fault_specs.py](../../services/ai-service/eval/online/live_fault_specs.py)), safe-live fault 5개([eval/online/safe_live_fault_specs.py](../../services/ai-service/eval/online/safe_live_fault_specs.py)), NL 라우팅 18케이스([eval/online/nl_tool_routing.py](../../services/ai-service/eval/online/nl_tool_routing.py) `ROUTING_CASES`). - 라이브 자동 주입 가능 fault는 `sink_db_down`/`source_db_down` 2건뿐. 나머지는 안전상 수동/금지. selfHeal·dedup 제약으로 사전 OPEN incident resolve 필요할 수 있음(캠페인 문서 Part B). --- ## 2. 카테고리 A — RCA 판단 정확도·신뢰도 -RCA 파이프라인: Classifier → (incident→rootcause map) → RCA evaluator → Verifier → Report. **자유 생성 금지, 카탈로그 후보를 evidence로 점수화·선택·보류.** 카탈로그 규모: failure_types 32 · root_causes 35 · evidence_profiles 42 · incident→rootcause map 32. +RCA 파이프라인: Classifier → (incident→rootcause map) → RCA evaluator → Verifier → Report. **자유 생성 금지, 카탈로그 후보를 evidence로 점수화·선택·보류.** 카탈로그 규모: failure_types 33 · root_causes 35 · evidence_profiles 32 · incident_rootcause_map 33. | 항목 | 검증 대상 | 코드 | 운영 테스트 방법 | 산출 수치 | |---|---|---|---|---| @@ -50,7 +52,7 @@ RCA 파이프라인: Classifier → (incident→rootcause map) → RCA evaluator | **A4 환각 0 (catalog 밖 차단)** | 카탈로그 밖 root_cause_id를 RCA/Report가 거부 | [verifier.py](services/ai-service/app/agents/verifier.py)`_verify_incident_analysis`(95, non_catalog FAIL 113)·report 검증(208) | RCA 출력 root_cause_id가 전부 35개 카탈로그 안인지 전수 검사 | 카탈로그-외 생성 건수(목표 0) | | **A5 Evidence 게이트** | required 전부 충족해야 ≥0.82, negative 감점, semantic-only 폐기 | [rca.py](services/ai-service/app/agents/rca.py)`_evaluate_candidate`(241)·`_match_rules`(304) | required 전부/일부/+negative/supporting만 4변형 입력으로 confidence 밴드 검증 | required-cap(0.79) 위반 건수, false-accept rate | | **A6 Disambiguation**(증상 강등) | CONNECTOR_TASK_FAILED 같은 증상보다 입증된 심층 원인을 위로 | [rca.py](services/ai-service/app/agents/rca.py)`_demote_symptom_below_confirmed_root_cause`(677), `_CAUSAL_DEPTH_MARGIN=0.03` | 하네스 6 `sink_db_down` 주입 → top이 SINK_DB_CONNECTION_TIMEOUT, 증상은 2위 보존 | disambiguation 정확도, confusion matrix | -| **A7 장애유형 분류** | 관측증거→failure_types 32개 중 선택 | [classifier.py](services/ai-service/app/agents/classifier.py)`run_classifier`(55)·`_score_failure_type`(128) | offline 라벨셋(증거→정답 incident_type) | top-1, Macro-F1*, UNKNOWN율 | +| **A7 장애유형 분류** | 관측증거→failure_types 33개 중 선택 | [classifier.py](services/ai-service/app/agents/classifier.py)`run_classifier`(55)·`_score_failure_type`(128) | offline 라벨셋(증거→정답 incident_type) | top-1, Macro-F1*, UNKNOWN율 | | **A8 Knowledge RAG** | pgvector 코사인 검색 top-k·min_score | [vector_store.py](services/ai-service/app/knowledge/vector_store.py)`search_by_embedding`(159), 설정 `knowledge_search_limit=3`·`min_score=0.05` | 알려진 질의로 EVIDENCE_COLLECTED(type=KNOWLEDGE) score 확인 | RAG recall@3, 무관질의 빈결과율 | 주의: `rca_eval_campaign.py`는 incident_type을 역매핑으로 주입(분류 제외) → **RCA 단독** 정확도. end-to-end(classifier→RCA)는 별도(`test_rca_classification_accuracy.py` 패턴). RCA confidence는 이산 밴드(0.82+/0.60~0.79/≤0.59)에 몰려 ECE 해석에 N≥30 표본 권장. RAG는 RCA 점수에 직접 기여 안 함(observed evidence만 게이팅) — RCA 정확도와 혼동 금지. `*` Macro-F1은 코드 미구현. @@ -171,11 +173,11 @@ DoD(시연 합격선, [docs/scenario.md](docs/scenario.md)): 자동감지(lag≥ ## 8. 권장 실행 순서 -1. **하네스 1**(`rca_eval_campaign.py`) — 발표 핵심 정확도 floor 즉시 확보(AC@1 65.7%/AC@5 80%/ECE 0.073). +1. **하네스 1**(`rca_eval_campaign.py`) — 발표 핵심 정확도 즉시 확보. 현재 커밋 기준은 oracle incident-type replay 35건 AC@1/3/5 100.0%, ECE 0.1595이며 보존 floor JSON은 AC@1 71.43%, AC@5 85.71%, ECE 0.0832. 2. **하네스 3**(NL 라우팅 라이브) — routing@1·latency, 자격증명 있으면 즉시. 3. **C2/C4 negative control** + **C6 mutation 4/4** + **C7 `/ready`** — "실데이터·빈성공 아님·무접촉 안전" 묶음. 4. **B-1/B-2 새 스크립트**(§7) — 유출 0·무승인 0 발표 수치. 5. **D1 telemetry** + **D5 동시성** — latency/부하 곡선. 6. (승인 후, 파괴적) **하네스 6** `--live --confirm --faults sink_db_down` — 라이브 RCA + A6 disambiguation. -**발표 문구**: 89.6%/367·Macro-F1·Wilson CI·"유도공격 32/20"은 재현 근거 확보 전 사용 금지. 재현 가능한 값(AC@5 80%, precision 82%, ECE 0.073, 환각 0, 라이브 routing@1 83.3%)으로 대체. +**발표 문구**: 89.6%/367·65.7%/80%/0.073·precision 82%·라이브 routing@1 83.3%·Macro-F1·Wilson CI·"유도공격 32/20"은 커밋된 재현 근거 확보 전 단정 금지. 현재는 `rca_campaign_current.json`과 `rca_campaign_floor.json`의 조건부 수치, 그리고 NL 라우팅 18케이스 하네스 존재까지만 단정. diff --git a/docs/test/rca-exhaustive-test-20260622.md b/docs/test/rca-exhaustive-test-20260622.md index 48f1ca93..4faa8f1e 100644 --- a/docs/test/rca-exhaustive-test-20260622.md +++ b/docs/test/rca-exhaustive-test-20260622.md @@ -33,7 +33,16 @@ - 지표: AC@1/AC@3/AC@5, Avg@5(RCAEval 표준), ECE(Guo et al.), 기권율 - 재현: `cd services/ai-service && .venv/bin/python scripts/rca_eval_campaign.py` -### 결과 (실행 실측, 2026-06-22) +### 결과 (커밋된 결과물 기준, 2026-06-22) + +아래 current/floor 두 JSON만 커밋된 재현 근거로 사용한다. 이어지는 "gold set 35건" 표는 과거 초안 실행 로그로 남기되, 현재 발표 지표나 재현값으로 단정하지 않는다. + +| 결과물 | 조건 | AC@1 | AC@3 / AC@5 | Avg@5 | ECE | 기권 | +|---|---|---|---|---|---|---| +| `results-20260622/rca_campaign_current.json` | 35 seed oracle incident-type replay, symptom/trigger/contributing_factors only, classifier end-to-end 아님 | **100.0%** (35/35) | **100.0%** (35/35) | **1.0000** | **0.1595** | **0/35** | +| `results-20260622/rca_campaign_floor.json` | develop 기준 보존 floor 결과물 | **71.43%** (25/35) | **85.71%** (30/35) | **0.781** | **0.0832** | **5/35** | + +### 과거 초안 집계 (현재 커밋 JSON과 불일치, 발표 사용 금지) | 지표 | 값 | |---|---| @@ -56,11 +65,11 @@ | change | 4 | 0.250 | 0.750 | 0.750 | 0.458 | | data_quality | 4 | 0.500 | 0.500 | 0.500 | 0.500 | -> 해석: 확신 답변 28건 중 23건 정답(**82% precision**), 불확실 7건은 정직한 기권. 약점(change·data_quality·source AC@1)은 temporal/metric 증거 의존도가 높아 **텍스트-only floor**에서 낮게 나온 것 — 프로덕션 retrieval(metric/trace/temporal) + LLM 타이브레이커 시 상향. +> 해석: 이 65.7%/80.0%/82%/7기권 묶음은 현재 커밋된 current/floor JSON과 맞지 않는 과거 초안 수치다. 발표와 외부 공유에는 위 current/floor JSON 값을 사용한다. ### gold set 35건 — 정의 + 케이스별 실측 결과 -출처: `app/evaluation/seed_gold_set.py`(카탈로그 root_cause_id별 2~3건 대표). **결과는 위 재현 스크립트의 케이스별 출력**. +출처: `app/evaluation/seed_gold_set.py`(카탈로그 root_cause_id별 2~3건 대표). **아래 결과 행은 과거 초안 실행 로그이며, 현재 커밋된 `rca_campaign_current.json`/`rca_campaign_floor.json`의 행별 결과와 불일치한다**. 적중 표기: ✅ = top-1 정답 / △@3 = 상위 3 내(인접 sibling) / 기권 = top이 UNKNOWN(증거 부족 정직한 보류). | # | entry_id | 계층 | 기대 root cause | 증상(symptom) | top 예측 | conf | 적중 | @@ -158,7 +167,7 @@ - L1-live(sink FAILED): open `bfd38509`(동일 datasource grouping_key)로 **dedup** → 중복 인시던트 억제(정상). - L3(consumer lag): **#926 정책상 paused 컨슈머는 lag 평가 제외**(오탐 방지). 로그상 폴러 정상. - **자동 감지→인시던트→자동 RCA capability는 과거 #962/#957 실인시던트로 입증**: `bfd38509`→**SINK_DB_CONNECTION_TIMEOUT @0.82**, `5aed2e00`·`ea315de3`→consumer lag. -- **카탈로그 전수 정확도는 Part A(35 실측)**: AC@5 80% · Avg@5 0.724 · ECE 0.073 · 환각 0. (단 자가작성 텍스트 gold set·LLM-off floor → SOTA 직접비교 부적절, 실데이터·LLM-on 재측정 필요.) +- **카탈로그 전수 정확도는 Part A(35 실측)**: 현재 커밋 기준 current replay는 AC@1/3/5 100.0% · Avg@5 1.0000 · ECE 0.1595 · 기권 0/35이고, 보존 floor JSON은 AC@1 71.43% · AC@5 85.71% · Avg@5 0.781 · ECE 0.0832 · 기권 5/35. 단 둘 다 seed replay 조건부 값이라 SOTA 직접비교나 production holdout 값으로 해석하지 않는다. - **① lag 모니터 = works-as-designed**(코드 변경 불필요). paused 제외는 #926 의도. 유일 후보는 'resolved 후 edge-trigger 재무장'이나 현 데이터상 결함 근거 없음. - **개선 제언**: - (1) **비파괴 주입 하네스**(메트릭/이벤트 신호 직접 주입) — 운영 무영향으로 **전체 35 gold set을 라이브 감지→RCA 경로**에 흘림(라이브 부분-커버 한계의 근본 해소). @@ -224,16 +233,16 @@ kubectl -n bifrost-system delete job rca-eval-llm # 정리 | 구성 | AC@1 | AC@3/5 | Avg@5 | ECE | 기권 | |---|---|---|---|---|---| -| floor (LLM off) | 65.7% | 80.0% | 0.724 | 0.073 | 7 | -| **+ lexicon** | **71.4%** | **85.7%** | **0.781** | 0.083 | 5 | -| LLM-on (배포 룰) | 65.7% | 80.0% | 0.724 | 0.070 | 7 | +| 과거 floor 초안 (LLM off) | 65.7% | 80.0% | 0.724 | 0.073 | 7 | +| **커밋된 보존 floor JSON** | **71.43%** | **85.71%** | **0.781** | **0.0832** | **5** | +| 과거 LLM-on 초안 (배포 룰) | 65.7% | 80.0% | 0.724 | 0.070 | 7 | -- **증거 recall 보강 = AC@1 +5.7pp(23→25)·Avg@5 0.724→0.781·기권 7→5**. gs_seed_002(SOURCE_AUTH)·gs_seed_031(UPSTREAM_VOLUME) 기권→정답@0.82. -- **LLM 타이브레이커 ≈ 정확도 무변화**(근접 동률 드물어 미발화, ECE만 0.073→0.070) → **정확도 본질은 LLM이 아니라 증거**(팀원 라이브 진단과 정량 일치). +- **증거 recall 보강 결과로 보존된 커밋 결과물은 AC@1 71.43%·Avg@5 0.781·기권 5/35**. 65.7%/0.724/7기권과 LLM-on 0.070은 커밋된 JSON 결과물이 없으므로 과거 초안으로만 취급한다. +- **과거 LLM-on 초안**은 근접 동률 미발화로 정확도 변화가 거의 없었다는 기록이나, 커밋된 JSON 결과물이 없어 발표 지표로 쓰지 않는다. - **정직 경계**: gs_seed_018(SINK_AUTH)은 불변 — 추가 튜닝 안 함(과적합 회피). 인과 temporality 게이팅·캘리브레이션은 evidence_matrix에 **이미 구현**(`causality_type`/`temporality_required`)이라 룰 재튜닝은 운영 정밀도 위험으로 배제. - **다음 레버**: 실 evidence(metric/trace/temporal) 공급(#828/#831/#835)·disambiguation·실 gold set 확대(#964) — 모두 팀 in-flight. -> **라이브 0.85 목표**: 방금 lexicon(+5.7pp)은 **offline floor** 개선이라 라이브엔 거의 영향 없음. 라이브 top-1이 0.85를 넘으려면 **증거 전달(metric/trace/temporal) + disambiguation**(#828/#831/#835 + 다음 1순위)을 완성해야 함 — lexicon으론 불가. offline 89.6%는 "증거가 다 주어졌을 때"의 상한이고, 라이브는 그 증거를 RCA가 스스로 모아야 하는 더 어려운 문제. (현 라이브 ≈12.1%) +> **라이브 목표 해석**: lexicon 보강은 offline seed replay 개선으로만 해석한다. 라이브 top-1, offline 89.6%, 현 라이브 ≈12.1%는 이 저장소의 커밋된 결과물·하네스 출력으로 재현되지 않으므로 발표 지표로 쓰지 않는다. > 재현: floor `cd services/ai-service && .venv/bin/python scripts/rca_eval_campaign.py` · LLM-on `RCA_EVAL_USE_LLM=1`(배포 pod/Job, §Part C). 시각화: `docs/test/시각화-part-a-개선실험.html`. diff --git a/docs/test/rca-fault-injection-20260621.md b/docs/test/rca-fault-injection-20260621.md index d35952e9..7b6c0dd4 100644 --- a/docs/test/rca-fault-injection-20260621.md +++ b/docs/test/rca-fault-injection-20260621.md @@ -1,5 +1,7 @@ # RCA·인시던트 장애주입 테스트 보고서 +> 문서 성격: 2026-06-21 라이브 장애주입 관측 기록이다. 현재 발표용 정량 정확도는 `docs/test/results-20260622/rca_campaign_current.json`과 `docs/test/results-20260622/rca_campaign_floor.json`을 기준으로 삼고, 이 문서의 `0.90`, `0.82`, lag `60110` 등은 당시 개별 인시던트 관측값으로만 해석한다. + | 항목 | 내용 | |---|---| | 일자 | 2026-06-21 | diff --git a/docs/test/rca-test-campaign-20260622.md b/docs/test/rca-test-campaign-20260622.md index 8b8b4b2f..53ee9586 100644 --- a/docs/test/rca-test-campaign-20260622.md +++ b/docs/test/rca-test-campaign-20260622.md @@ -80,9 +80,9 @@ RCA 근본원인 카탈로그를 **8계층 35개**로 정의하고, 33개 인시 - 오답 0건: 35개 seed 모두 top-1 정답 - 해석 제한: replay 조건상 incident type은 oracle로 주입되며, 100% 값은 production unseen 일반화 수치가 아님 -### 3.1.1 보존 floor (develop 기준 기존 발표 수치) +### 3.1.1 보존 floor (develop 기준 커밋된 결과물) -기존 develop 배포 이미지 `4a7ca906` 기준 보수적 floor는 본문 재측정값과 함께 인용해야 한다. 문서 본문의 과거 수치는 AC@1 65.7%, AC@3 80.0%, AC@5 80.0%, Avg@5 0.724, ECE 0.073, 기권 7/35였고, 보존 JSON `docs/test/results-20260622/rca_campaign_floor.json`은 AC@1 71.43%, AC@3 85.71%, AC@5 85.71%, Avg@5 0.781, ECE 0.0832, 기권 5/35를 기록한다. +기존 develop 배포 이미지 `4a7ca906` 기준 보수적 floor는 본문 재측정값과 함께 인용해야 한다. 커밋된 보존 JSON `docs/test/results-20260622/rca_campaign_floor.json`은 AC@1 71.43%, AC@3 85.71%, AC@5 85.71%, Avg@5 0.781, ECE 0.0832, 기권 5/35를 기록한다. 문서 초안에 남아 있던 AC@1 65.7%, AC@3/5 80.0%, Avg@5 0.724, ECE 0.073, 기권 7/35는 현재 커밋된 결과물과 불일치하므로 발표 지표로 쓰지 않는다. > 핵심: 현재 branch replay 수치와 보존 floor는 산출 시점과 코드 상태가 다르다. 100% 값을 단독으로 쓰지 말고 oracle incident-type, seed 내부 replay, floor 범위를 함께 표시한다. @@ -99,11 +99,11 @@ RCA 근본원인 카탈로그를 **8계층 35개**로 정의하고, 33개 인시 ## 4. 해석 & 슬라이드 03 반영 권고 - **방법론 명시 필수**: 현재 재측정 수치는 **관측증거-only + LLM 비활성 + oracle incident-type**의 seed replay 결과다. 프로덕션 Retrieval은 metric/trace/temporal 증거를 추가 수집할 수 있지만 classifier 포함 end-to-end 및 unseen production holdout은 별도 측정이 필요하다. -- **해석 제한**: 100% 값은 기존 35 seed의 조건부 replay 결과이며 본 문서의 floor(AC@1 65.7%, 보존 JSON 기준 71.43%)와 함께 표시해야 한다. +- **해석 제한**: 100% 값은 기존 35 seed의 조건부 replay 결과이며 커밋된 보존 floor(AC@1 71.43%)와 함께 표시해야 한다. - **권장 슬라이드 수치(재현 가능)**: - - AI 진단 정확도: **보존 floor AC@1 65.7~71.4%, 현재 oracle replay AC@1 100.0% (35/35, 조건부)** - - 환각: **≈ 0** (날조 0건, 불확실 20%는 정직한 기권) - - 신뢰도 캘리브레이션: **floor ECE 0.073~0.0832, 현재 replay ECE 0.1595** + - AI 진단 정확도: **보존 floor AC@1 71.43%, 현재 oracle replay AC@1 100.0% (35/35, 조건부)** + - 환각: **카탈로그 밖 root cause 생성 0건** (현재 replay 기권 0/35, 보존 floor 기권 5/35) + - 신뢰도 캘리브레이션: **floor ECE 0.0832, 현재 replay ECE 0.1595** - **약점(정직 공개)**: 현재 seed replay에서는 취약 계층이 없지만 `change`·`data_quality` 계층은 production에서 temporal/metric 증거 의존도가 높다. → Retrieval의 metric/temporal 증거 강화 + gold set 확대(#964 피드백 루프)가 개선 경로. - ⚠️ 기존 슬라이드의 "89.6% / 367 케이스"는 본 캠페인과 산출 방식·데이터셋이 달라 **출처 검증 전까지 사용하지 말 것**. 본 문서의 재현 가능한 수치로 대체 권장. diff --git "a/docs/test/\354\213\234\352\260\201\355\231\224-part-a-\352\260\234\354\204\240\354\213\244\355\227\230.html" "b/docs/test/\354\213\234\352\260\201\355\231\224-part-a-\352\260\234\354\204\240\354\213\244\355\227\230.html" index e262688b..024ced92 100644 --- "a/docs/test/\354\213\234\352\260\201\355\231\224-part-a-\352\260\234\354\204\240\354\213\244\355\227\230.html" +++ "b/docs/test/\354\213\234\352\260\201\355\231\224-part-a-\352\260\234\354\204\240\354\213\244\355\227\230.html" @@ -78,11 +78,11 @@

4. 측정 방법론 — 글로벌 벤더/학계 표준 대비 (판정)

판정 팀의 측정 방법은 Datadog Watchdog·Dynatrace Davis 등 벤더 공개 수준보다 정량적으로 투명하고, RCAEval·PACE-LM 등 학계 SOTA 평가와 직접 대응한다(특히 offline/live 분리·negative control·Wilson CI)
-단 표준 경고도 동일 적용: 합성 셋 성능 ≠ 실제(ASE'24) — 89.6%/71.4%는 오라클/floor 상한이지 프로덕션 값 아님. 실 gold set·실 evidence 측정이 다음 + 단 표준 경고도 동일 적용: 합성 셋 성능 ≠ 실제(ASE'24) — current 100.0%와 floor 71.43%는 seed replay 조건부 값이지 프로덕션 값 아님. 실 gold set·실 evidence 측정이 다음
한 줄 결론 -방법론은 벤더급으로 검증됨. 그 위에서 증거 recall 보강만으로 AC@1 65.7%→71.4%를 과적합 없이 달성했고, LLM이 아니라 증거 전달이 본질임을 분리 측정으로 정량 확인했다(다음 레버 = 실 evidence·disambiguation, 팀 in-flight와 정합) + 방법론은 벤더급으로 검증됨. 다만 현재 커밋된 정량 근거는 current oracle replay AC@1 100.0%와 보존 floor AC@1 71.43%이며, LLM이 아니라 증거 전달이 본질이라는 해석은 실 evidence·disambiguation 측정으로 추가 검증해야 한다
diff --git a/infra/README.md b/infra/README.md index 3e9eb0f6..3aa65abe 100644 --- a/infra/README.md +++ b/infra/README.md @@ -1,14 +1,15 @@ # Infra -플랫폼의 인프라 정의. Terraform (AWS), Helm chart, K8s 매니페스트. +플랫폼의 인프라 정의. Terraform(AWS EKS), Kubernetes 매니페스트, CI/CD bootstrap, Kafka Connect 커스텀 이미지 정의를 둔다. 애플리케이션 Helm chart와 GitOps Application은 `gitops` 브랜치가 배포 정본이다. ## 구성 ``` infra/ -├─ terraform/ AWS 리소스 (VPC, EKS, ECR, Route53) -├─ k8s/ Strimzi, Kafka, Monitoring -├─ helm/ 전체 서비스 묶음 (umbrella chart) +├─ terraform/ AWS EKS 클러스터와 노드그룹 (기존 VPC/서브넷 사용) +├─ k8s/ Strimzi, Kafka, Connect, Monitoring, bootstrap ingress manifest +├─ cicd/ Jenkins/Argo CD/Harbor Helm values와 수동 bootstrap 스크립트 +├─ local/ 로컬·kind 검증용 Kafka/Connect/tenant DB 보조 파일 └─ docker/ 커스텀 Docker 이미지 (kafka-connect 등) ``` @@ -59,18 +60,22 @@ kubectl create namespace platform-kafka # 3. Kafka 클러스터 kubectl apply -f k8s/kafka/kafka-cluster.yaml -# 4. Kafka Connect (Debezium 포함 커스텀 이미지 사용) -# 먼저 docker/kafka-connect 빌드 후 ECR push -docker build -t platform-kafka-connect:latest docker/kafka-connect/ -# ... push to ECR ... - +# 4. Kafka Connect (Debezium/JDBC/timestamptz converter 포함 Harbor 이미지 사용) kubectl apply -f k8s/kafka/kafka-connect.yaml ``` +Kafka Connect 이미지는 루트 `Jenkinsfile`이 `infra/docker/kafka-connect/` 또는 `connect-plugins/` 변경을 감지할 때 Kaniko로 빌드해 Harbor `harbor.harbor.svc.cluster.local/library/bifrost-kafka-connect:1.0.0-converter`에 push한다. 로컬 수동 빌드는 검증용이며 운영 배포 정본은 Jenkins build와 `infra/k8s/kafka/kafka-connect.yaml`의 `spec.image`다. + +### CI/CD와 GitOps + +`infra/cicd/deploy.sh`는 Jenkins, Argo CD, Harbor를 Helm으로 bootstrap하는 스크립트다. 현재 앱 배포 흐름은 `main` 전용 Jenkins job이 변경 서비스를 Kaniko로 빌드해 Harbor에 push하고, `gitops` 브랜치의 `charts//values.yaml` tag를 갱신하면 Argo CD app-of-apps가 reconcile하는 구조다. + +외부 노출의 현재 정본은 `gitops` 브랜치 `infra/` chart다. `harbor.skala-ai.com`, `jenkins.skala-ai.com`, `argocd.skala-ai.com`, `bifrost.skala-ai.com`은 단일 NLB → ingress-nginx → cert-manager(Let's Encrypt)로 TLS 종료한다. 이 브랜치의 `infra/k8s/ingress/*-ingress.yaml` ALB manifest는 bootstrap/이력용이며 현재 GitOps 노출 정본이 아니다. + ## 모니터링 ```bash kubectl apply -f k8s/monitoring/ ``` -Prometheus + Grafana + Loki 설치. +Prometheus + Grafana + Loki 설치. GitOps 운영 환경에서는 `gitops` 브랜치의 `4-observability-monitoring` Argo CD 앱이 kube-prometheus-stack, Loki, Tempo를 관리한다. diff --git a/infra/cicd/README.md b/infra/cicd/README.md index babf03dd..b68a46a0 100644 --- a/infra/cicd/README.md +++ b/infra/cicd/README.md @@ -1,7 +1,8 @@ # CICD 스택 — Jenkins / ArgoCD / Harbor -EKS 클러스터에 Jenkins, ArgoCD, Harbor를 Helm으로 배포하고 -ALB Ingress로 외부에 노출하는 구성. +EKS 클러스터에 Jenkins, ArgoCD, Harbor를 Helm으로 bootstrap하고, Jenkins `bifrost-ci`와 Argo CD GitOps 배포 흐름을 구성한다. + +현재 외부 노출 정본은 `gitops` 브랜치 `infra/` Helm chart다. 단일 NLB → ingress-nginx → cert-manager(Let's Encrypt)로 `harbor.skala-ai.com`, `jenkins.skala-ai.com`, `argocd.skala-ai.com`을 노출한다. 이 브랜치의 `infra/k8s/ingress/*-ingress.yaml` ALB manifest와 `deploy.sh ingress`는 초기 bootstrap/이력용이다. ## 파일 구조 @@ -14,22 +15,20 @@ infra/ │ └── harbor-values.yaml # Harbor Helm values └── k8s/ └── ingress/ - ├── harbor-ingress.yaml # Harbor ALB (port 80, group.order 10) - ├── jenkins-ingress.yaml # Jenkins ALB (port 8080, group.order 20) - └── argocd-ingress.yaml # ArgoCD ALB (port 8081, group.order 30) + ├── harbor-ingress.yaml # legacy/bootstrap ALB manifest + ├── jenkins-ingress.yaml # legacy/bootstrap ALB manifest + └── argocd-ingress.yaml # legacy/bootstrap ALB manifest ``` -## ALB 구성 - -세 Ingress가 **`cicd-shared-alb`** 단일 ALB를 공유 (`group.name: cicd-shared`). +## 외부 노출 -| 서비스 | 포트 | group.order | -|--------|------|-------------| -| Harbor | 80 | 10 | -| Jenkins | 8080 | 20 | -| ArgoCD | 8081 | 30 | +현재 운영 노출은 `gitops` 브랜치의 `infra` chart가 관리한다. -Public Subnets: `subnet-0d8a1dcc1e064b04d`, `subnet-05e64cd452e9a3c55` +| 서비스 | URL | 내부 서비스 | +|--------|-----|-------------| +| Harbor | `https://harbor.skala-ai.com` | `harbor:80` | +| Jenkins | `https://jenkins.skala-ai.com` | `jenkins:8080` | +| Argo CD | `https://argocd.skala-ai.com` | `argocd-server:80` | ## 배포 @@ -67,31 +66,25 @@ CI 파이프라인은 Jenkins의 **`bifrost-ci`** Pipeline job이 실행한다. > `disableConcurrentBuilds`는 `Jenkinsfile`의 `options{}`가 런타임에 설정한다. -GitHub webhook은 `http://:8080/github-webhook/` (push 이벤트)로 등록돼 있어야 한다. - -## 현재 배포 버전 +GitHub webhook은 `https://jenkins.skala-ai.com/github-webhook/` (push 이벤트)로 등록돼 있어야 한다. -| 차트 | 버전 | App 버전 | -|------|------|---------| -| jenkins/jenkins | 5.9.22 | 2.555.2 | -| argo/argo-cd | 9.5.17 | v3.4.3 | -| harbor/harbor | 1.18.0 | 2.14.0 | +## Jenkinsfile 배포 흐름 -## 접근 정보 +1. `bifrost-ci`는 JCasC에서 `*/main`만 checkout하므로 `Jenkinsfile` 내부에 branch gate를 두지 않는다. +2. 직전 성공 빌드 대비 변경 파일을 보고 `services/ai-service`, `services/operations-backend`, `services/frontend` 중 변경된 서비스만 `TO_BUILD`에 넣는다. +3. 앱 서비스는 서비스별 Kaniko 컨테이너에서 Harbor `harbor.harbor.svc.cluster.local/library/bifrost-:`와 `:latest`로 push한다. `operations-backend`는 멀티모듈 Gradle 때문에 빌드 컨텍스트가 레포 루트이고, 다른 앱 서비스는 각 서비스 디렉터리다. +4. 앱 서비스가 빌드되면 `gitops` 브랜치를 clone하고 `charts//values.yaml`의 `image.tag`만 git sha로 갱신해 push한다. Argo CD는 polling/reconcile로 이를 배포한다. +5. `infra/docker/kafka-connect/` 또는 `connect-plugins/` 변경 시 Kafka Connect 커스텀 이미지를 별도 Kaniko 컨테이너에서 빌드해 `bifrost-kafka-connect:1.0.0-converter`, git sha, `latest`로 Harbor에 push한다. 이 단계는 GitOps tag를 자동 변경하지 않는다. -ALB DNS: `cicd-shared-alb-1042102888.ap-northeast-2.elb.amazonaws.com` +## 현재 배포 버전 -| 서비스 | URL | -|--------|-----| -| Harbor | http://cicd-shared-alb-...:80 | -| Jenkins | http://cicd-shared-alb-...:8080 | -| ArgoCD | http://cicd-shared-alb-...:8081 | +`deploy.sh`가 사용하는 chart version 기본값이다. 실제 app version은 chart 릴리스에 종속되므로 여기서 별도 고정하지 않는다. -```bash -# DNS 확인 -kubectl get ingress -n harbor harbor-alb \ - -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' -``` +| 차트 | 버전 | +|------|------| +| jenkins/jenkins | 5.9.22 | +| argo/argo-cd | 9.5.17 | +| harbor/harbor | 1.18.0 | ## 기본 계정