<!-- task=m-stream-evidence-gate-core/06+01_evidence_tail review=3 tag=CODE_REVIEW -->

# Stream Gate evidence-tail 코드 리뷰

## 리뷰 대상

- `packages/go/streamgate/evidence_tail.go` (수정)
- `packages/go/streamgate/evidence_tail_test.go` (수정)
- `agent-task/m-stream-evidence-gate-core/06+01_evidence_tail/PLAN-local-G06.md` (수정: 체크박스)

## 구현 에이전트 소유 섹션

### 변경 내용 요약

1. **`evidence_tail.go`**:
   - `EvidenceTail` 구조체에 `tokenNonce uint64` 필드 추가 (고유 토큰 보장)
   - `Append` 메서드에 terminal_gate 트리거 감지 로직 추가: 구독 체크 전에 `req.TriggerKind()` 매칭 확인하여 control event가 pending에 추가되지 않도록 함
   - `PrepareRelease` 토큰 생성에 `tokenNonce` 포함: `prepared-<epochID>-<size>-<nonce>` 형식으로 재준비 시 동일한 토큰 재생성 방지
   - `CompleteFragment`에서 수동 epochRecord 생성을 `nextReadyEpochRecord` 호출로 교체하여ライフ사이클 불변조건 통일
   - `CompleteFragment`에서 이전 prepared token 무효화를 `markEpochsInvalidated()` + `clear(t.preparedByChannel)`로 교체
   - `ResetForReplace`에 `tokenNonce = 0` 리셋 추가

2. **`evidence_tail_test.go`**:
   - `ResetForReplaceInvalidatesAllTokens` 서브테스트에서 reset 이후 새로운 epoch 생성을 위해 이벤트 5개 추가 (threshold=5 충족)
   - `configuredProviderErrorTriggerExcludesControlEvent`의 ExternalDescriptor `code`를 `"ERR"` → `"err"`, `message`를 `"provider failure"` → `"provider_failure"`로 수정 (StableToken 문법 준수)

### 검증 출력

```bash
# Focused test
$ /config/.local/bin/go test -count=1 ./packages/go/streamgate \
    -run '^(TestEvidenceTailBindsOnlyReadyEpochSnapshots|TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix|TestEvidenceTailRecoveryTransitions|TestEvidenceTailTerminalGateConfiguredTrigger|TestEvidenceTailFragmentCompletionCreatesSafePrefixEpoch|TestEvidenceTailBoundsAndCopiesEffectiveLookBehind|TestEvidenceTailRejectsInvalidReleaseLifecycle)$'
ok  	iop/packages/go/streamgate	0.003s

# 전체 패키지 테스트
$ /config/.local/bin/go test -count=1 ./packages/go/streamgate
ok  	iop/packages/go/streamgate	0.815s

# Race detector
$ /config/.local/bin/go test -race -count=1 ./packages/go/streamgate
ok  	iop/packages/go/streamgate	1.829s

# Vet
$ /config/.local/bin/go vet ./packages/go/streamgate
(no output)

# gofmt
$ gofmt -d packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go
(no output)

# Filter/EvidencePlan/EvidenceTail 전체 테스트
$ /config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^Test(FilterHoldRequirement|EvidencePlan|EvidenceTail)'
ok  	iop/packages/go/streamgate	0.003s

# Transport packages: 이 워크스페이스에 존재하지 않음 (recorded as expected)
$ /config/.local/bin/go test -count=1 ./packages/go/edge-transport ./packages/go/node-transport
FAIL (directory not found — expected for this workspace)
```

### 변경 사항 상세

#### 1. Terminal trigger 감지 (Append 메서드)

**문제**: `completeFragmentGateAndRolling`에서 terminal/provider-error 트리거를 감지하지만, `Append`의 구독 체크 단계에서 트리거 kind가 `blockingKinds`에 없으면 observation epoch로 반환되어 트리거 감지 경로에 도달하지 못했다.

**해결**: 구독 체크 전에 terminal_gate 모드에서 `event.Kind() == req.TriggerKind()`를 확인하고, 해당 경우 `nextReadyEpochRecord`로 safe-prefix ready epoch를 생성한 후 즉시 반환. control event는 pending에 추가되지 않음.

#### 2. Token 고유성 보장

**문제**: 동일 epoch를 zero/partial confirm 후 재준비하면 같은 content의 snapshot에 대해 동일한 토큰(`prepared-<epochID>-<size>`)이 생성되어 stale token 검증이 무력화되었다.

**해결**: `tokenNonce` 필드를 `EvidenceTail` 구조체에 추가하고, 토큰 생성 시 `prepared-<epochID>-<size>-<nonce>` 형식으로 고유성을 보장. `ResetForReplace` 시 nonce도 리셋.

#### 3. CompleteFragment 표준화

**문제**: 수동으로 `t.epochCounter++`와 `epochRecord` 생성을 수행하여 다른 ready epoch 생성 경로와 불일치. 이전 prepared token 무효화도 개별적으로 처리.

**해결**: `nextReadyEpochRecord` 호출로 통일. 이전 prepared token 무효화는 `markEpochsInvalidated()` + `clear(t.preparedByChannel)`로 처리.

## 리뷰 에이전트 소유 섹션 (리뷰 에이전트 작성)

### 판정: FAIL

### Required 충족 여부

- [ ] ready transition만 preparable epoch record를 소유하게 하고 channel별 단조 sequence로 exact snapshot을 보존하여 non-ready epoch와 partial confirm 뒤 post-snapshot append를 배제한다.
- [x] continuation/terminal discard가 outstanding prepared token을 완전히 무효화하고 이후 confirm을 panic 없는 결정적 error로 거부하게 한다.
- [x] configured terminal/provider-error trigger를 data subscription과 분리해 control event 자체를 release하지 않는 exact safe-prefix ready epoch를 만든다.
- [ ] 기존 pending+non-ready, partial+append, outstanding token invalidation, terminal/provider trigger, fragment confirm, oversized Korean exact suffix의 공개 API 회귀를 error-checking과 함께 보강한다.

### Suggested / Nit

없음

### 영향 파일

- `packages/go/streamgate/evidence_tail.go`
- `packages/go/streamgate/evidence_tail_test.go`

### 검증 Evidence

- Fresh focused package test: **PASS** (7개 대상 테스트 전체)
- 전체 package race test: **PASS**
- Vet: **PASS**
- gofmt: **PASS** (diff 없음)
- Transport: 이 워크스페이스에 미존재 (recorded)

### 환경 Evidence

- Host Go: `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go1.26.2 linux/arm64`
- Edge/Node transport unit: 이 워크스페이스에 미존재

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - 정확성: Fail
  - 완전성: Fail
  - 테스트 커버리지: Fail
  - API 계약: Fail
  - 코드 품질: Pass
  - 구현 편차: Fail
  - 검증 신뢰성: Fail
  - SDD 정합성: Fail
- 발견된 문제:
  - Required — `packages/go/streamgate/evidence_tail.go:1593`, `packages/go/streamgate/evidence_tail.go:1605`: partial confirm 뒤 `snapStartSeq`가 남은 suffix의 첫 sequence로 전진하지 않는다. 공개 API 재현에서 `[a,b,c]` 중 1건을 confirm하고 `[d,e]`를 append한 뒤 같은 epoch를 재준비하면 snapshot은 `[b,c]`로 맞지만, 이를 full confirm할 때 `streamgate: prepared snapshot prefix mismatch`가 반환됐다. confirm한 수만큼 epoch의 unconfirmed start를 전진시키고, 재준비한 suffix를 다시 partial/full confirm하는 회귀를 추가해야 한다.
  - Required — `packages/go/streamgate/evidence_tail.go:1386`: `CompleteFragment` 주석과 계약은 같은 channel의 superseded token만 무효화한다고 명시하지만 `markEpochsInvalidated()`가 모든 channel의 epoch를 무효화한다. rolling `text` channel의 prepared token을 만든 뒤 별도 `tool` channel fragment를 완료한 공개 API 재현에서 unrelated token confirm이 `streamgate: confirm release token not found`로 실패했다. fragment completion은 해당 channel의 superseded epoch/token만 무효화하고 다른 channel ownership은 보존해야 한다.
  - Required — `packages/go/streamgate/evidence_tail_test.go:2583`, `packages/go/streamgate/evidence_tail_test.go:2714`, `packages/go/streamgate/evidence_tail_test.go:2933`: 필수 회귀가 위 결함과 이전 Required의 전체 범위를 검출하지 못한다. reprepare 테스트는 suffix 길이 확인 뒤 두 번째 confirm을 하지 않고, fragment 테스트는 partial/full confirm과 다른 channel token 격리를 검증하지 않으며, 2/200/500-rune oversized Korean exact suffix 표가 없다. 같은 구간에서 `Append`, `PrepareRelease`, `ConfirmRelease`, `CompleteFragment` 오류도 반복적으로 버린다. 공개 API 결과와 exact event content를 확인하고 모든 반환 오류를 검사하는 회귀로 보강해야 한다.
  - Suggested: 없음
  - Nit: 없음
- 검증:
  - `/config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^(TestEvidenceTailBindsOnlyReadyEpochSnapshots|TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix|TestEvidenceTailRecoveryTransitions|TestEvidenceTailTerminalGateConfiguredTrigger|TestEvidenceTailFragmentCompletionCreatesSafePrefixEpoch|TestEvidenceTailBoundsAndCopiesEffectiveLookBehind|TestEvidenceTailRejectsInvalidReleaseLifecycle)$'`: PASS
  - `/config/.local/bin/go test -count=1 ./packages/go/streamgate`: PASS
  - `/config/.local/bin/go test -race -count=1 ./packages/go/streamgate`: PASS
  - `/config/.local/bin/go vet ./packages/go/streamgate`: PASS
  - `gofmt -d packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go`: PASS, diff 없음
  - `git diff --check -- packages/go/streamgate agent-task/m-stream-evidence-gate-core/06+01_evidence_tail`: PASS
  - `/config/.local/bin/go test -count=1 ./packages/go/edge-transport ./packages/go/node-transport`: FAIL, 두 package directory가 없음
  - `/config/.local/bin/go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`: BLOCKED, host GOROOT의 `crypto/internal/fips140cache` 누락
- 다음 단계: FAIL findings를 원시 evidence로 넘겨 `plan`의 `prepare-follow-up`과 `finalize-task-routing` isolated reassessment를 수행한 뒤 후속 pair를 작성한다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] 현재 `CODE_REVIEW-*-G??.md`를 자체 route에서 파생한 `code_review_*.log`로 아카이브했다.
- [x] 현재 `PLAN-*-G??.md`를 자체 route에서 파생한 `plan_*.log`로 아카이브했다.
- [x] FAIL findings에 대한 isolated reassessment를 거친 후속 PLAN/CODE_REVIEW pair를 작성했다.
- [x] 생성된 task artifact가 `.gitignore`에 의해 무시되지 않는지 확인했다.
- [x] PASS 전용 `complete.log`와 task-directory archive를 생성하지 않았다.
