# SDD: Control Plane 운영 보안·감사 표면

## 위치

- Milestone: [control-plane-security-audit-surface.md](../../../phase/control-plane-product-surface/milestones/control-plane-security-audit-surface.md)
- Phase: [PHASE.md](../../../phase/control-plane-product-surface/PHASE.md)

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목:
  - [x] [D01] 최소 인증 모델과 권한 역할의 Phase 포함 범위를 operator/admin 단일 역할과 표시 가능한 token reference 기준으로 확정했다.
  - [x] [D02] 감사 로그는 운영 action metadata 감사 범위로 두고, Core in-memory/event surface 또는 테스트 가능한 adapter 경계까지만 이번 Milestone 범위로 확정했다.
  - [x] [D03] 감사 UI는 운영 판단용 metadata만 노출하고, 로그/아티팩트/토큰류 원문과 민감 error payload는 복제 노출하지 않는 기준으로 확정했다.

## 문제 / 비목표

- 문제: Control Plane write action 표면은 runner self-update, execution cancel/report/log/artifact 같은 운영 변경을 노출하지만, 현재 설계에는 operator identity, 권한 실패 의미, 감사 흔적의 source of truth, 민감 정보 노출 경계가 고정되어 있지 않다.
- 비목표:
  - production identity provider, 조직/테넌트 모델, 장기 secret rotation을 이 Milestone에서 완성하지 않는다.
  - durable audit storage 구현을 기본 범위로 확정하지 않는다. file/DB 기반 감사 저장, retention, 삭제 정책은 후속 Milestone으로 분리한다.
  - 원격 빌드 로그 원문, artifact 파일 내용, raw log line, production-grade 사용자 로그인 히스토리를 감사 로그 범위에 포함하지 않는다.
  - 기존 runner production job 운영 정책 전체를 재설계하지 않는다.

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [control-plane-security-audit-surface.md](../../../phase/control-plane-product-surface/milestones/control-plane-security-audit-surface.md) | Milestone 상태, 기능 Task, 구현 잠금의 장기 원장이다. |
| Code | `services/core/internal/httpserver/routes.go` | Core HTTP write route의 현재 source of truth다. |
| Code | `services/core/internal/httpserver/runner_cicd_handlers.go` | runner self-update, execution cancel/report/log/artifact write handler의 현재 검증·상태 변경 기준이다. |
| Code | `apps/client/lib/src/app/core_connection_client.dart` | Flutter client가 Core write endpoint를 호출하고 success/deferred/failed로 해석하는 기준이다. |
| Code | `packages/flutter/oto_console/lib/src/oto_console_contract.dart` | console config, auth token reference, action draft/result contract의 UI 패키지 기준이다. |
| External Provider | 없음 | 이 SDD는 외부 provider write를 추가하지 않는다. |
| User Decision | D01 | production identity provider는 후속으로 분리하고, operator/admin 단일 역할과 표시 가능한 token reference만 이번 Milestone 기준으로 삼는다. |
| User Decision | D02 | 감사 로그는 operator/runner write action의 actor, action, target, outcome, timestamp, optional reason metadata로 한정하고, durable persistence는 후속 Milestone으로 분리한다. |
| User Decision | D03 | UI에는 action, actor reference, target id, outcome, timestamp, optional reason만 노출하고 token, credential, private URL credential, raw log line, artifact 내용/full path, 민감 error payload는 노출하지 않는다. |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| SDD draft | SDD 문서가 생성되고 사용자 결정 항목이 식별됨 | User review pending | `SDD.md`, `USER_REVIEW.md` |
| User review pending | D01-D03이 사용자 답변으로 확정됨 | SDD approved candidate | `USER_REVIEW.md` 해결 로그 |
| SDD approved candidate | Acceptance Scenario와 Evidence Map이 Milestone 기능 Task와 연결됨 | Plan ready | `SDD.md`, Milestone `구현 잠금` |
| Operator action requested | UI 또는 adapter가 Core write endpoint를 호출함 | Authorized, rejected, deferred, or failed | `core_connection_client.dart`, Core HTTP response |
| Authorized action accepted | Core가 기존 상태 검증과 권한 기준을 통과함 | Audit event candidate | `runner_cicd_handlers.go`, D01 |
| Action rejected or failed | 권한, ownership, invalid input, missing runner/execution, terminal state 등으로 거부됨 | Audit event candidate | Core error response, D01-D03 |
| Audit event candidate | action outcome과 actor/target/reason metadata가 생성됨 | Captured in in-memory/event surface or withheld | D02-D03 |

## Interface Contract

- 계약 원문: 없음
- 입력:
  - `actor`: operator 또는 runner actor identity. 구체 모델은 D01에서 확정한다.
  - `role`: write action 허용 여부를 판정할 최소 역할. 구체 범위는 D01에서 확정한다.
  - `action`: `create-job`, `create-execution`, `self-update-runner`, `cancel-execution`, `report-execution`, `append-log`, `append-artifact` 중 하나.
  - `target`: runner id, job id, execution id, artifact/log 대상 등 action 대상 식별자.
  - `reason`: cancel/self-update 같은 operator action에 남길 선택적 사유.
  - `request_body`: 기존 JSON request body 또는 후속 schema 확장안.
- 출력:
  - `status`: `accepted`, `deferred`, `succeeded`, `rejected`, `failed` 중 UI가 표시할 결과 상태.
  - `error_message`: 거부 또는 실패 사유. 민감 정보는 D03 기준으로 마스킹한다.
  - `audit_event`: action, actor, target, outcome, reason, timestamp, request id 후보를 담는 감사 이벤트. 이번 Milestone에서는 Core in-memory/event surface 또는 테스트 가능한 adapter 경계까지만 다룬다.
  - `ui_state`: disabled, submitting, succeeded, deferred, failed, empty/error state 중 operator가 관찰할 상태.
- UI 노출:
  - `action`: 운영 action 이름.
  - `actor_reference`: operator/admin 또는 runner actor의 표시 가능한 참조값.
  - `target_id`: runner, job, execution 등 대상 식별자.
  - `outcome`: `accepted`, `rejected`, `deferred`, `failed` 등 결과.
  - `timestamp`: action 발생 또는 기록 시각.
  - `reason`: operator가 입력한 선택적 사유.
- 금지:
  - auth token, enrollment token, credential 원문, private release URL credential을 SDD, audit event, UI text, 테스트 fixture에 남기지 않는다.
  - execution owner mismatch, terminal state rejection, unknown runner 같은 기존 Core 보호 조건을 우회하지 않는다.
  - 원격 빌드 로그 원문, raw log line, artifact 파일 내용, artifact full path를 audit event에 복제하지 않는다.
  - 사용자 로그인 히스토리 전체나 production identity provider event를 이번 Milestone의 audit source of truth로 삼지 않는다.
  - durable audit persistence 구현을 기본 완료 기준으로 삼지 않는다.
  - artifact path, log line, token reference, credential-like error payload 같은 민감 후보를 감사 UI에 그대로 노출하지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `security-sdd` | SDD가 필요한 운영 보안·감사 Milestone이고 D01-D03이 미확정이다. | SDD와 사용자 리뷰가 작성된다. | SDD는 잠금 상태로 남고, 사용자 결정 항목은 `USER_REVIEW.md`에만 분리된다. |
| S02 | `security-sdd` | 사용자 결정 D01-D03이 해결되었다. | SDD를 갱신하고 gate를 확인한다. | SDD 상태는 `[승인됨]`, SDD 잠금은 `해제` 후보가 되며, Milestone 잠금 해제 조건을 검증할 수 있다. |
| S03 | `auth-scope` | 최소 인증 모델과 역할 범위가 확정되어 있다. | Core write endpoint와 Flutter action surface의 권한 허용/거부 흐름을 계획한다. | 기능 Task는 actor, role, unauthorized/forbidden UX, disabled/error 상태의 검증 기준을 포함한다. |
| S04 | `audit-scope` | 감사 저장 책임과 UI 노출/마스킹 기준이 확정되어 있다. | action outcome을 감사 이벤트 후보와 UI 표면으로 계획한다. | 기능 Task는 event field, in-memory/event surface boundary, masking rule, durable persistence 후속 분리 여부의 검증 기준을 포함한다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | `Spec Completion` 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | `SDD.md` 생성, `USER_REVIEW.md` 생성, Milestone Task id 매핑 확인 | `agent-task/m-control-plane-security-audit-surface/...` | `security-sdd`: SDD 초안과 사용자 리뷰 파일이 생성되어 잠금 사유가 명확하다. |
| S02 | 해결된 `user_review_N.log`, 갱신된 SDD 상태/잠금, check-gate 결과 | `agent-task/m-control-plane-security-audit-surface/...` | `security-sdd`: D01-D03 답변이 SDD에 반영되고 gate가 pass 또는 남은 차단 항목을 보고한다. |
| S03 | Core handler/route 테스트 또는 adapter/widget 테스트, 권한 실패 UI 확인, 민감 정보 미노출 검색 | `agent-task/m-control-plane-security-audit-surface/...` | `auth-scope`: 권한 허용/거부 및 disabled/error 상태가 코드와 테스트 근거로 검증된다. |
| S04 | audit event field 검증, storage boundary 검증, UI masking/widget 테스트 또는 문자열 검색 | `agent-task/m-control-plane-security-audit-surface/...` | `audit-scope`: 감사 이벤트 범위와 마스킹 기준이 구현·테스트·검색 근거로 검증된다. |

## Cross-repo Dependencies

- 없음

<!-- 필요한 경우:
- `<project>:agent-roadmap/phase/<phase>/milestones/<milestone>.md`
- `.agent-roadmap-sync/locks.yaml` entry: `<id>`
-->

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 plan/code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에 분리했고 해결 로그로 이동한다.

## 사용자 리뷰 이력

- D01: 추천안 승인. 이번 Milestone은 production identity provider를 붙이지 않고, operator/admin 단일 역할과 `OtoConsoleConfig.authTokenReference` 기반의 표시 가능한 token reference만 기준으로 설계한다.
- D02: 추천안 승인. 감사 로그는 원격 빌드 로그 원문이나 production 로그인 히스토리가 아니라, Control Plane write action의 actor/action/target/outcome/timestamp/optional reason metadata로 한정하고 Core in-memory/event surface 또는 테스트 가능한 adapter 경계까지만 이번 Milestone 범위로 둔다.
- D03: 추천안 승인. 감사 UI는 운영 판단에 필요한 action, actor reference, target id, outcome, timestamp, optional reason만 보여주고, 로그/아티팩트/토큰류 원문과 민감 error payload는 복제 노출하지 않는다.

## 작업 컨텍스트

- 표준선: 기존 Core HTTP route와 Flutter action result contract를 우선 source of truth로 삼고, API/schema 확장은 D01-D03 결정 이후 필요한 최소 범위로만 분리한다.
- 표준선: `OtoConsoleConfig.authTokenReference`는 표시 가능한 reference로만 취급하고 token 원문은 UI, 감사 이벤트, 문서, 테스트 fixture에 남기지 않는다.
- 표준선: 원격 빌드 로그 원문과 artifact 내용은 기존 execution log/artifact 도메인의 데이터이며, audit event에는 append/report 같은 action metadata만 남긴다.
- 표준선: 감사 UI는 운영 판단용 metadata만 보여주며, credential-like substring과 민감 후보 payload는 마스킹 또는 비노출 대상으로 둔다.
- 후속 SDD: durable audit storage가 필요해지면 별도 persistence/schema SDD 또는 후속 Milestone으로 분리한다.
