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

# Stream Gate ready epoch authority와 invalidation 폐쇄 계획

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

이 계획의 체크박스는 구현 진행 상황을 추적한다. 구현과 검증을 완료한 항목만 `[x]`로 바꾸고, 항목의 문구·순서·식별자는 변경하지 않는다.
작업 종료 전 active `CODE_REVIEW-local-G07.md`의 구현 에이전트 소유 섹션을 실제 변경과 명령 출력으로 채운다.
archive 이동, `complete.log`, 코드리뷰 판정, 다음 상태 분류는 리뷰 에이전트 책임이다.
명시된 write-set 밖 파일은 수정하지 않는다.

## 배경

현재 green test는 ready가 아닌 epoch가 기존 pending release 권한을 얻는 경로, partial confirm 뒤 같은 epoch snapshot에 새 append가 섞이는 경로, recovery invalidation 뒤 outstanding token confirm panic을 검출하지 못한다. configured terminal/provider-error trigger도 subscription 분기 뒤에서 처리되어 ready signal 없이 release 가능한 epoch를 만들 수 있다. 이번 루프는 동일한 epoch/channel lifecycle과 공개 API 회귀를 함께 고쳐 commit-boundary sibling의 책임을 침범하지 않는다.

## Archive Evidence Snapshot

- 이전 plan: `agent-task/m-stream-evidence-gate-core/06+01_evidence_tail/plan_local_G06_2.log`
- 이전 review: `agent-task/m-stream-evidence-gate-core/06+01_evidence_tail/code_review_local_G07_2.log`
- 판정: `FAIL`
- Required:
  - ready transition만 preparable epoch record를 소유하게 하고 channel별 단조 sequence로 exact snapshot을 보존하여 non-ready epoch와 partial confirm 뒤 post-snapshot append를 배제한다.
  - continuation/terminal discard가 outstanding prepared token을 완전히 무효화하고 이후 confirm을 panic 없는 결정적 error로 거부하게 한다.
  - 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`, active review stub.
- 현재 검증 evidence:
  - fresh focused package, 전체 package race, vet, gofmt, diff hygiene는 PASS했다.
  - 공개 API 격리 재현은 비구독 epoch가 `[a]`를 준비하고, 3건 snapshot의 1건 partial confirm 뒤 같은 epoch가 `[c d e]`를 준비하며, continuation 뒤 outstanding token confirm이 panic하고, configured terminal trigger가 `signal="none"`인 채 `[pending]`을 준비함을 확인했다.
- 환경 evidence: host `go`는 `/config/opt/go/bin/go`의 `go1.26.2`; Edge/Node transport unit은 host GOROOT의 `crypto/internal/fips140cache` 누락으로 setup failure다.
- Roadmap carryover: 이 subtask는 S01/S02 core와 S19 tail overflow signal만 준비한다. attempt cancel→terminal/no-release 통합과 `evidence-tail` Roadmap Task 완료는 dependent `07+01,06_commit_boundary`가 소유하므로 `Roadmap Targets`를 두지 않는다.
- 구현자는 위 두 task log와 명시된 predecessor `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log` 외의 `agent-task/archive/**`를 탐색하지 않는다.

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/platform-common/testing.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/m-stream-evidence-gate-core.md`
- `agent-roadmap/phase/stream-gate-core.md`
- `agent-roadmap/sdd/m-stream-evidence-gate-core.md`
- `agent-spec/index.md`
- `agent-contract/index.md`
- `agent-task/m-stream-evidence-gate-core/06+01_evidence_tail/plan_local_G06_2.log`
- `agent-task/m-stream-evidence-gate-core/06+01_evidence_tail/code_review_local_G07_2.log`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
- `packages/go/streamgate/evidence_tail.go`
- `packages/go/streamgate/evidence_tail_test.go`
- `packages/go/streamgate/event.go`의 관련 event conversion/clone 구간
- `packages/go/streamgate/filter_contract.go`의 관련 requirement/trigger 구간

`agent-spec/index.md`와 `agent-contract/index.md`에는 이 내부 evidence-tail API에 매칭되는 별도 문서가 없다.

### SDD 기준

- SDD status는 `approved`이며 구현 잠금은 해제되어 있다.
- S01/S02의 acquire→examine→release 및 prepare→downstream write→confirm 순서는 ready epoch가 exact release capability를 소유하고 stale/recovery token을 fail-closed해야 함을 요구한다.
- S19는 tail overflow와 terminal/no-release 경계를 요구한다. 이 작업은 tail 내부 signal/epoch만 고치며 attempt-wide terminal transition은 dependent commit-boundary 작업에 남긴다.
- SDD checklist shape의 evidence rune 경계 `2/200/500`, Unicode suffix, hard overflow 및 recovery transition을 공개 API 회귀로 고정한다.
- `Roadmap Targets`는 두지 않는다. 최종 roadmap completion ownership은 dependent `07+01,06_commit_boundary`에 있다.

### 테스트 환경 규칙

- local 규칙에 따라 host toolchain을 사용한다.
- 실제 host binary: `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go version go1.26.2 linux/arm64`, `GOROOT=/config/opt/go`.
- `packages/go/streamgate` 변경은 focused test, 전체 package race, vet, gofmt, diff hygiene가 필수다.
- platform-common smoke의 transport unit은 실행하되, host GOROOT의 `crypto/internal/fips140cache` 누락이 계속되면 정확한 setup failure와 복구 조건을 기록한다. 별도 toolchain 우회는 하지 않는다.
- proto source는 변경하지 않는다. `make proto`를 실행한 경우 `git status --short -- proto/gen/iop`가 clean인지 확인하고 생성물 변경을 남기지 않는다.

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

- 비구독/observe-only case는 기존 pending이 없는 상태만 보아 non-ready epoch의 release capability를 검출하지 못한다.
- reprepare case는 zero confirm+post-append와 partial confirm을 분리해 partial+post-append 오염을 놓친다.
- recovery case는 이미 confirm된 token을 보거나 internal state만 확인해 outstanding token의 no-panic error 계약을 고정하지 못한다.
- terminal gate case는 configured terminal/provider-error trigger를 data subscription과 분리한 public lifecycle을 검증하지 않는다.
- fragment completion case는 prepare error를 버리고 partial/full confirm과 다음 safe-prefix release를 끝까지 검증하지 않는다.
- look-behind case는 ASCII 10-rune 상한만 확인하며 2/200/500-rune oversized Korean event의 exact suffix를 검증하지 않는다.

### 심볼 참조

- 공개 signature 변경은 없다: `Append`, `PrepareRelease`, `ConfirmRelease`, `PrepareContinuation`, `DiscardPendingForTerminal`, `CompleteFragment`, `EffectiveLookBehind`.
- `epochRecord.snapStartSeq`, `snapEndSeq`, `snapshotSize`, `prepared`, `token`, `invalidated`와 `channelState.pendingEntries`가 핵심 내부 state다.
- `pendingEntry.sequence`의 모든 producer/consumer를 함께 갱신한다.
- 관련 호출자는 현재 package test와 dependent commit-boundary 계획에 있다. 이번 작업은 caller 파일을 수정하지 않는다.
- legacy `RegisterFragment`, `tokenToChannel`, `t.Skip`을 새로 만들지 않는다. opaque token은 외부 파싱 계약으로 승격하지 않는다.

### 분할 판단

분할하지 않는다. ready epoch snapshot, recovery invalidation, terminal trigger는 모두 `epochRecord`와 channel sequence ownership을 공유하고 같은 공개 API 회귀가 서로의 불변조건을 검증한다. 별도 subtask로 나누면 동일 두 파일과 동일 state representation을 병렬 수정하게 되어 독립 write-set이 성립하지 않는다.

### 범위 결정 근거

- 수정 범위는 `packages/go/streamgate/evidence_tail.go`, `packages/go/streamgate/evidence_tail_test.go`, 이 task directory의 active plan/review evidence뿐이다.
- `ReleaseSink`, `CommitBoundary`, registry, transport 구현, proto/config, roadmap/SDD/spec/contract, `WORK_LOG.md`는 수정하지 않는다.
- public API와 event wire shape는 유지하고 내부 epoch capability 표현과 회귀만 바꾼다.
- sibling `07+01,06_commit_boundary`가 소유한 attempt cancel→terminal/no-release 통합을 선행 구현하지 않는다.

### 최종 라우팅

- 격리 입력: 현재 두 소스 파일, 공개 API 재현, 승인된 SDD의 해당 불변조건, local 검증 규칙, sibling ownership 경계만 사용했다.
- scope closure: 네 Required가 하나의 epoch/channel lifecycle과 두 파일 write-set으로 닫힌다.
- context closure: 관련 source/test/SDD 구간과 predecessor evidence로 필요한 맥락이 닫힌다.
- verification closure: 결정적 공개 API 회귀, focused/race/vet/gofmt/diff 및 transport setup evidence로 닫힌다.
- evidence closure: green test가 놓친 경로는 격리 public probe로 직접 재현되어 신뢰 가능한 실패 evidence가 있다.
- ownership closure: source hash와 task-local next state를 기준으로 하며 dependent commit-boundary 책임을 제외했다.
- decision closure: 사용자 또는 제품 결정을 요구하는 선택지가 없다.
- plan complexity score: scope `1`, context `2`, verification `1`, evidence `1`, ownership `1` → `local G06`, `PLAN-local-G06.md`.
- review complexity score: scope `1`, context `2`, verification `1`, evidence `2`, ownership `1` → `local G07`, `CODE_REVIEW-local-G07.md`.

## 구현 체크리스트

- [x] `REVIEW_REVIEW_REVIEW_API-1` ready transition만 preparable epoch를 소유하게 하고 단조 sequence로 exact snapshot을 고정하여 non-ready/stale epoch와 partial confirm 뒤 post-snapshot append를 배제한다.
- [x] `REVIEW_REVIEW_REVIEW_API-2` continuation/terminal discard의 outstanding token을 panic 없이 무효화하고 configured terminal/provider-error trigger가 control event를 포함하지 않는 exact safe-prefix ready epoch를 만들게 한다.
- [x] `REVIEW_REVIEW_REVIEW_API-3` 공개 API 회귀를 기존 pending+non-ready, partial+append, outstanding token recovery, terminal/provider trigger, fragment partial/full confirm, 2/200/500 oversized Korean exact suffix까지 error-checking과 함께 완성한다.
- [x] 호스트 `go` 기준 fresh focused/race/vet, gofmt/diff hygiene와 transport setup 결과를 실제 출력으로 기록한다.
- [x] `CODE_REVIEW-local-G07.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_REVIEW_API-1] Ready-only exact snapshot authority

**대상**

- `packages/go/streamgate/evidence_tail.go:1054`
- `packages/go/streamgate/evidence_tail.go:1103`
- `packages/go/streamgate/evidence_tail.go:1310-1482`
- `packages/go/streamgate/evidence_tail.go:1530-1556`
- `packages/go/streamgate/evidence_tail_test.go:2437`
- `packages/go/streamgate/evidence_tail_test.go:2600`

**문제**

`nextEpoch`가 signal readiness와 무관하게 현재 pending slice 길이를 snapshot으로 기록하고, sequence가 slice 길이를 재사용한다. `PrepareRelease`는 이 값을 현재 slice index로 해석하므로 non-ready epoch가 기존 pending을 release하고 partial confirm 뒤 append된 event가 원 snapshot에 섞인다.

**Before**

```go
epoch, err := t.nextEpoch(channel, req.Mode(), false, "event_not_subscribed")

entry := pendingEntry{
    sequence: len(cs.pendingEntries),
}

snapStart := record.snapStartSeq
snapEnd := record.snapEndSeq
for i := snapStart; i < snapEnd; i++ {
    releaseEvents = append(releaseEvents, ...)
}
```

**After**

```go
epoch := t.nextObservationEpoch(channel, req.Mode(), false, "event_not_subscribed")
// observation epoch has no release record

entry := pendingEntry{
    sequence: cs.nextSequence,
}
cs.nextSequence++

record := newReadyEpochRecord(channel, firstSequence, lastSequenceExclusive)
releaseEvents, err := cs.unconfirmedSnapshot(record)
```

이름은 구현에 맞게 정하되 다음 불변조건을 지킨다.

- signal이 ready/trigger인 transition만 `PrepareRelease` 가능한 record를 가진다.
- channel sequence는 confirm/slice compaction 뒤 재사용하지 않는다.
- epoch snapshot은 sequence identity로 원 prefix를 고정하고 current pending prefix와 불일치하면 fail-closed한다.
- zero/partial confirm은 같은 epoch의 unconfirmed suffix만 재준비하며 post-snapshot append를 포함하지 않는다.
- empty/non-ready/overflow/stale epoch는 pending 존재 여부와 무관하게 prepare를 거부한다.
- confirm count는 prepared snapshot bounds와 current sequence prefix를 모두 검증한다.

**검증**

```bash
/config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^(TestEvidenceTailBindsOnlyReadyEpochSnapshots|TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix|TestEvidenceTailRejectsInvalidReleaseLifecycle)$'
```

### [REVIEW_REVIEW_REVIEW_API-2] Recovery invalidation과 configured trigger

**대상**

- `packages/go/streamgate/evidence_tail.go:1048-1165`
- `packages/go/streamgate/evidence_tail.go:1397-1482`
- `packages/go/streamgate/evidence_tail.go:1494-1528`
- `packages/go/streamgate/evidence_tail_test.go`

**문제**

recovery transition은 pending과 channel token map을 지우지만 epoch record token을 남긴다. `ConfirmRelease`가 invalidated/prepared ownership과 pending bounds를 확인하지 않아 stale outstanding token이 panic한다. terminal/provider-error trigger는 subscription 분기 뒤에서 평가되어 configured control trigger가 ready signal 없이 release capability를 만들 수 있다.

**Before**

```go
for _, record := range t.epochs {
    record.invalidated = true
}
t.preparedByChannel = make(map[string]string)

for i := 0; i < n; i++ {
    confirmed[i] = cloneNormalizedEvent(cs.pendingEntries[i].event)
}
```

**After**

```go
for _, record := range t.epochs {
    record.invalidated = true
    record.prepared = false
    record.token = ""
    record.snapshotSize = 0
}
clear(t.preparedByChannel)

if !record.prepared || record.invalidated || t.preparedByChannel[channel] != token {
    return errStalePreparedRelease
}
if !cs.matchesPreparedPrefix(record, n) {
    return errPreparedSnapshotMismatch
}
```

configured trigger는 data subscription과 독립적으로 먼저 식별하되 UTF-8 validation과 channel/plan validation을 우회하지 않는다.

```go
if event.Kind() == req.TriggerKind() {
    return t.newReadyEpochForCurrentSafePrefix(channel, req.Mode(), "configured_trigger")
}
```

- trigger control event 자체를 pending/release snapshot에 추가하지 않는다.
- terminal과 provider-error 두 configured trigger를 모두 검증한다.
- outstanding token은 continuation과 terminal discard 뒤 모두 panic 없이 stable error를 반환한다.
- 이미 consumed token, zero/partial confirm, overlapping token의 기존 계약을 유지한다.

**검증**

```bash
/config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^(TestEvidenceTailRecoveryTransitions|TestEvidenceTailTerminalGateConfiguredTrigger|TestEvidenceTailRejectsInvalidReleaseLifecycle)$'
```

### [REVIEW_REVIEW_REVIEW_API-3] Public regression trust closure

**대상**

- `packages/go/streamgate/evidence_tail_test.go:2437`
- `packages/go/streamgate/evidence_tail_test.go:2600`
- `packages/go/streamgate/evidence_tail_test.go:2731`
- `packages/go/streamgate/evidence_tail_test.go:2950`

**요구 테스트**

1. 기존 blocking pending을 만든 뒤 비구독/observe-only event epoch가 `PrepareRelease`를 거부하는지 public API로 확인한다.
2. `[a,b,c]`를 prepare하고 1건 confirm한 뒤 `[d,e]`를 append해 같은 epoch reprepare가 `[b,c]` 또는 실제 원 snapshot의 정확한 unconfirmed suffix만 반환하는지 event content까지 확인한다.
3. outstanding token을 만든 뒤 `PrepareContinuation`/`DiscardPendingForTerminal` 각각에서 `ConfirmRelease`가 panic 없이 error인지 확인한다.
4. text pending 뒤 configured terminal 및 provider-error trigger가 `EvidenceTailSignalTrigger`와 ready epoch를 반환하고 release snapshot에 control event가 없는지 확인한다.
5. interleaved fragment ID에서 contiguous completed prefix를 prepare하고 partial/full confirm한 뒤 다음 completion이 정확한 suffix만 release하는지 확인한다.
6. rolling evidence `2`, `200`, `500` 각각에 oversized Korean event를 confirm하고 `EffectiveLookBehind`의 exact rune suffix와 defensive copy를 확인한다.
7. 생성자, `Append`, `PrepareRelease`, `ConfirmRelease`, `CompleteFragment` error를 버리지 않는다. required regression에 `t.Skip`을 두지 않는다.

**검증**

```bash
/config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^(TestEvidenceTailBindsOnlyReadyEpochSnapshots|TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix|TestEvidenceTailRecoveryTransitions|TestEvidenceTailTerminalGateConfiguredTrigger|TestEvidenceTailFragmentCompletionCreatesSafePrefixEpoch|TestEvidenceTailBoundsAndCopiesEffectiveLookBehind)$'
```

## 의존 관계 및 구현 순서

1. channel별 monotonic sequence와 ready epoch record 표현을 먼저 고정한다.
2. `PrepareRelease`/`ConfirmRelease`가 sequence prefix와 token ownership을 검증하도록 바꾼다.
3. recovery invalidation과 configured trigger를 새 record 불변조건에 맞춘다.
4. 공개 API 회귀를 추가하고 각 단계 focused test를 실행한다.
5. 전체 package race/vet와 hygiene를 실행하고 active review evidence를 채운다.

## 수정 파일 요약

| 파일 | 변경 |
|---|---|
| `packages/go/streamgate/evidence_tail.go` | ready-only epoch capability, monotonic exact snapshot, confirm bounds/prefix, recovery invalidation, configured trigger |
| `packages/go/streamgate/evidence_tail_test.go` | 공개 API lifecycle 및 Unicode exact-bound 회귀 |
| `agent-task/m-stream-evidence-gate-core/06+01_evidence_tail/CODE_REVIEW-local-G07.md` | 구현 완료 체크와 실제 검증 evidence |

## 최종 검증

```bash
/config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^(TestEvidenceTailBindsOnlyReadyEpochSnapshots|TestEvidenceTailRepreparesOnlyUnconfirmedSnapshotSuffix|TestEvidenceTailRecoveryTransitions|TestEvidenceTailTerminalGateConfiguredTrigger|TestEvidenceTailFragmentCompletionCreatesSafePrefixEpoch|TestEvidenceTailBoundsAndCopiesEffectiveLookBehind|TestEvidenceTailRejectsInvalidReleaseLifecycle)$'
/config/.local/bin/go test -count=1 ./packages/go/streamgate -run '^Test(FilterHoldRequirement|EvidencePlan|EvidenceTail)'
/config/.local/bin/go test -race -count=1 ./packages/go/streamgate
/config/.local/bin/go vet ./packages/go/streamgate
gofmt -d packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go
git diff --check -- packages/go/streamgate agent-task/m-stream-evidence-gate-core/06+01_evidence_tail
git status --short -- proto/gen/iop
rg -n --sort path 't\.Skip|RegisterFragment|tokenToChannel|prepared-[%]d' packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go
/config/.local/bin/go test -count=1 ./packages/go/edge-transport ./packages/go/node-transport
```

transport command가 host GOROOT setup failure면 package 누락 경로, exit code, 복구 조건을 review evidence에 그대로 기록한다. 다른 Go toolchain으로 우회하지 않는다.
