<!-- task=m-policy-history-audit/01_audit_contracts plan=0 tag=API -->

# Plan - API

## 이 파일을 읽는 구현 에이전트에게

`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 필수 마지막 단계다. 코드를 바꾸고 검증을 실행한 뒤 실제 구현 내용, 계획 대비 변경 사항, 주요 설계 결정, 검증 stdout/stderr를 리뷰 스텁에 기록하고 active 파일을 그대로 둔 채 리뷰 준비를 보고한다. 구현 중 사용자만 결정할 수 있는 제품 선택, 사용자 소유 외부 환경 prerequisite, 또는 범위 충돌이 생기면 리뷰 스텁의 `사용자 리뷰 요청` 섹션에 정확한 근거를 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

`정책, 이력, 감사` Milestone의 작은 정책 경계는 문서로 정리되었고, 남은 `schema-evidence`는 코드 수준의 audit schema evidence가 필요하다. 현재 공통 패키지에는 policy engine, job metadata, lifecycle event helper가 있지만 terminal/bootstrap/execution audit event의 구조화 계약은 없다. 이 계획은 앱별 persistence나 wire 변경 전에 `packages/go/audit` 공통 계약과 검증 가능한 테스트를 추가한다.

## 사용자 리뷰 요청 흐름

구현 중 차단 사유가 있으면 active `CODE_REVIEW-local-G05.md`의 `사용자 리뷰 요청` 섹션을 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 채운다. code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-portal-ops/milestones/policy-history-audit.md`
- Task ids:
  - `schema-evidence`: policy/audit 상세 schema는 구현 단계에서 명확한 evidence와 함께 확정한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/control-plane-portal-ops/PHASE.md`
- `agent-roadmap/phase/control-plane-portal-ops/milestones/policy-history-audit.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `go.mod`
- `packages/go/policy/engine.go`
- `packages/go/jobs/jobs.go`
- `packages/go/events/events.go`
- `packages/go/metadata/metadata.go`
- `proto/iop/runtime.proto`
- `proto/iop/job.proto`
- `proto/iop/control.proto`

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`를 읽었다. platform-common 변경은 `agent-test/local/platform-common-smoke.md`로 라우팅된다.
- 적용 명령: 공통 패키지 변경 시 변경 패키지 테스트 또는 `go test ./packages/go/... ./proto/gen/...`; 보조 회귀는 `go test ./...`.
- 이 계획은 proto 원본을 바꾸지 않으므로 `make proto`는 요구하지 않는다.
- 명령 레이아웃 확인: `go test ./packages/go/...`와 `go test -count=1 ./packages/go/...`가 현재 checkout에서 성공했다.

### 테스트 커버리지 공백

- 새 `packages/go/audit` schema package: 기존 테스트 없음. `audit_test.go`를 새로 작성해 필수 필드 검증, 보안 기본값, event type 상수, metadata defensive copy를 확인한다.
- 기존 `policy`, `jobs`, `events`, `metadata` 패키지: 이번 계획에서는 수정하지 않는다. `go test -count=1 ./packages/go/...`로 인접 공통 패키지 회귀만 확인한다.

### 심볼 참조

- renamed/removed symbols: none.
- 새 public symbols는 `iop/packages/go/audit` 아래에만 추가한다. 기존 call site는 없다.

### 분할 판단

- split decision policy를 계획 파일 선택 전에 평가했다.
- 전체 Milestone은 Edge capture, Control Plane projection, Node guardrail reporting까지 확장될 수 있어 도메인 split이 필요하다.
- 이 active plan은 첫 독립 단위 `agent-task/m-policy-history-audit/01_audit_contracts`이며 predecessor는 없다.
- 이 계획은 platform-common 소유의 schema contract와 tests만 만든다. Edge/Control Plane/Node integration은 이 계약을 소비하는 후속 계획으로 남긴다.

### 범위 결정 근거

- 포함: `packages/go/audit/audit.go`, `packages/go/audit/audit_test.go`.
- 제외: `proto/iop/*.proto`와 `proto/gen/**`; 앱 간 wire contract를 바꾸기 전 Go schema evidence만 만든다.
- 제외: `apps/edge/**`, `apps/control-plane/**`, `apps/node/**`; persistence, relay, projection 구현은 후속 계획에서 다룬다.
- 제외: 실제 retention cleanup scheduler와 DB schema; 이번 계획은 event schema와 기본 retention constants만 확정한다.

### 빌드 등급

- `local-G05`: 새 공통 public package지만 범위가 작고, security-sensitive 기본값은 deterministic unit test로 검증 가능하다.

## 구현 체크리스트

- [ ] `packages/go/audit/audit.go`에 audit event schema, event type constants, source/decision constants, retention defaults, validation helpers를 추가한다.
- [ ] `packages/go/audit/audit_test.go`에 schema validation, redaction-safe defaults, metadata copy, required event type coverage 테스트를 작성한다.
- [ ] `go test -count=1 ./packages/go/audit ./packages/go/policy ./packages/go/jobs ./packages/go/events ./packages/go/metadata`를 실행해 새 package와 인접 공통 패키지를 검증한다.
- [ ] `go test -count=1 ./packages/go/...`를 실행해 platform-common 전체 회귀를 검증한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [API-1] Audit Schema Contract

### 문제

- [packages/go/policy/engine.go](/config/workspace/iop/packages/go/policy/engine.go:5)는 policy spec과 validation 계약만 제공하고 audit decision/event schema를 만들지 않는다.
- [packages/go/jobs/jobs.go](/config/workspace/iop/packages/go/jobs/jobs.go:15)는 execution job metadata만 표현하며 terminal/bootstrap audit event field가 없다.
- [packages/go/events/events.go](/config/workspace/iop/packages/go/events/events.go:11)는 node lifecycle event helper만 제공하고 audit event taxonomy는 없다.
- [packages/go/metadata/metadata.go](/config/workspace/iop/packages/go/metadata/metadata.go:3)는 metadata map helper만 제공하며 secret redaction 기본값이나 audit retention 기준이 없다.

### 해결 방법

새 `packages/go/audit` package를 추가한다. 앱 내부 package를 import하지 않고 standard library만 사용한다.

```go
package audit

import (
	"errors"
	"time"
)

type Source string
type EventType string
type PolicyDecision string

const (
	SourceControlPlane Source = "control_plane"
	SourceEdge         Source = "edge"
	SourceNode         Source = "node"

	DecisionAllow PolicyDecision = "allow"
	DecisionDeny  PolicyDecision = "deny"
	DecisionWarn  PolicyDecision = "warn"

	DefaultEdgeLocalRetentionDays             = 30
	DefaultControlPlaneMetadataRetentionDays  = 90
	MetadataRedactionMarker                  = "redaction_marker"
	MetadataCredentialReference              = "credential_ref"
)

type Event struct {
	EventID          string
	Type             EventType
	Source           Source
	ActorID          string
	RequestID        string
	RunID            string
	SessionID        string
	EdgeID           string
	NodeID           string
	Adapter          string
	Target           string
	CredentialRef    string
	PolicyDecision   PolicyDecision
	PolicyRule       string
	Timestamp        time.Time
	Status           string
	Error            string
	Metadata         map[string]string
}
```

필수 event type constants:

```go
const (
	TypeExecutionRequested       EventType = "execution.requested"
	TypeExecutionPolicyEvaluated EventType = "execution.policy_evaluated"
	TypeExecutionDispatched      EventType = "execution.dispatched"
	TypeExecutionStarted         EventType = "execution.started"
	TypeExecutionCompleted       EventType = "execution.completed"
	TypeExecutionFailed          EventType = "execution.failed"
	TypeExecutionCanceled        EventType = "execution.canceled"

	TypeTerminalSessionRequested EventType = "terminal.session_requested"
	TypeTerminalSessionStarted   EventType = "terminal.session_started"
	TypeTerminalCommandSubmitted EventType = "terminal.command_submitted"
	TypeTerminalCommandCompleted EventType = "terminal.command_completed"
	TypeTerminalSessionClosed    EventType = "terminal.session_closed"

	TypeBootstrapEnrollmentRequested EventType = "bootstrap.enrollment_requested"
	TypeBootstrapCredentialIssued    EventType = "bootstrap.credential_issued"
	TypeBootstrapNodeRegistered      EventType = "bootstrap.node_registered"
	TypeBootstrapEnrollmentFailed    EventType = "bootstrap.enrollment_failed"
	TypeBootstrapRevoked             EventType = "bootstrap.revoked"
)
```

Validation helper:

```go
func (e Event) Validate() error {
	if e.Type == "" {
		return errors.New("audit event type is required")
	}
	if e.Source == "" {
		return errors.New("audit event source is required")
	}
	if e.Timestamp.IsZero() {
		return errors.New("audit event timestamp is required")
	}
	return nil
}

func (e Event) CopyMetadata() map[string]string {
	if e.Metadata == nil {
		return nil
	}
	out := make(map[string]string, len(e.Metadata))
	for k, v := range e.Metadata {
		out[k] = v
	}
	return out
}
```

### 수정 파일 및 체크리스트

- [ ] `packages/go/audit/audit.go`: schema types, constants, validation helper, metadata copy helper를 추가한다.
- [ ] `packages/go/audit/audit_test.go`: public schema behavior를 테스트한다.

### 테스트 작성

- 작성한다: `packages/go/audit/audit_test.go`.
- 테스트 후보:
  - `TestEventValidateRequiresTypeSourceTimestamp`
  - `TestEventValidateAcceptsStructuredExecutionAuditEvent`
  - `TestCopyMetadataDefensivelyCopiesMap`
  - `TestAuditPolicyDefaultsAvoidRawTerminalTranscript`
  - `TestRequiredEventTypesMatchMilestoneDraft`

### 중간 검증

```bash
go test -count=1 ./packages/go/audit ./packages/go/policy ./packages/go/jobs ./packages/go/events ./packages/go/metadata
```

기대 결과: 새 audit package 테스트와 인접 공통 패키지가 모두 통과한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/audit/audit.go` | API-1 |
| `packages/go/audit/audit_test.go` | API-1 |

## 최종 검증

```bash
go test -count=1 ./packages/go/...
```

기대 결과: platform-common 전체 Go 테스트가 fresh run으로 통과한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
