<!-- task=m-stream-evidence-gate-core/07+01,06_commit_boundary plan=9 tag=REVIEW_REVIEW_API -->

# 위치 구별 Releaser Confirmation과 Cancel Hook Evidence 폐쇄 계획

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

- 아래 구현 체크리스트와 검증 명령을 수행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 기록한다.
- active PLAN/CODE_REVIEW 파일을 유지한 채 review-ready만 보고한다. 판정, 로그 아카이브, `complete.log`, task 디렉터리 이동은 code-review skill 전용이다.
- 막히면 정확한 blocker, 시도한 명령과 출력, 재개 조건만 구현 소유 evidence에 기록한다.
- 사용자에게 질문하거나 선택지를 제시하지 않고, user-input 도구·control-plane stop 파일·다음 상태 분류를 만들지 않는다.

## 배경

reservation callback identity 수정과 public confirm invalidation/re-entry 회귀는 확인됐지만, remaining suffix fixture가 모든 위치에서 같은 text를 사용해 잘못된 suffix를 검출하지 못한다. cancel hook도 실제 discard state를 관찰하지 않고 성공 marker만 기록하며, 구현 evidence는 이 두 검증을 수행했다고 서술한다. production behavior를 더 바꾸지 않고 named regression의 assertion과 evidence를 실제 상태에 맞춘다.

## Archive Evidence Snapshot

- 현재 계획 로그: `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/plan_local_G06_8.log`
- 현재 리뷰 로그: `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/code_review_local_G06_8.log`
- 판정: `FAIL`
- Required:
  - 동일한 `"chunk"` fixture 때문에 zero/partial/full remaining suffix의 위치와 full-consumed reprepare 거부가 직접 검증되지 않는다.
  - canceller hook은 pending/prepared/stale-token 상태를 확인하지 않고 `discard-verified` marker만 기록하며 explicit cancel 순서도 없다.
  - 구현 기록이 실제 test/source와 충돌하고 transport exit code 1을 포함한 1~13 검증을 전체 PASS로 기록했다.
- Suggested: 없음
- Nit: 없음
- 영향 파일: `packages/go/streamgate/stream_release_test.go`, 현재 task evidence.
- 실제 검증:
  - exact 8-test inventory는 8개, named race `-count=20`, fresh package, package race, vet, gofmt, proto와 hygiene는 PASS했다.
  - focused fresh output은 `/tmp/iop-streamgate-review-api-8-review.log`에 저장됐고 모든 현재 test는 PASS했으나 위 assertion 공백을 검출하지 못했다.
  - Edge/Node transport unit은 `/config/opt/go/src/crypto/internal/fips140cache` 누락으로 setup 전에 exit code 1이었다.
- 선행 완료:
  - `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
  - `agent-task/archive/2026/07/m-stream-evidence-gate-core/06+01_evidence_tail/complete.log`
- 구현자는 위 특정 로그 외의 `agent-task/archive/**`를 넓게 탐색하지 않는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md)
- Task ids:
  - `evidence-tail`: S19 overflow discard/cancel/terminal 통합 evidence
  - `commit-boundary`: S03/S04/S05/S17 staged start, irreversible state와 single terminal
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/commit_boundary.go`
- `packages/go/streamgate/commit_boundary_test.go`
- `packages/go/streamgate/stream_release.go`
- `packages/go/streamgate/stream_release_test.go`
- `packages/go/streamgate/evidence_tail.go`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/rules/common/rules-agent-spec.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-spec/index.md`
- `agent-contract/index.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/stream-evidence-gate-core.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/plan_local_G06_7.log`
- `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/code_review_local_G07_7.log`
- `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/plan_local_G06_8.log`
- `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/code_review_local_G06_8.log`

`agent-spec/index.md`와 `agent-contract/index.md`에는 이 내부 streamgate boundary에 직접 매칭되는 active spec/contract 문서가 없다. 코드, 테스트와 승인된 SDD를 원본으로 사용한다.

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`; SDD/Milestone 잠금 `해제`, 결정 필요 `없음`.
- 대상 Acceptance Scenario:
  - `S05`/`commit-boundary`: confirmed forward state와 이미 보낸 event를 rollback하지 않는다.
  - `S19`/`evidence-tail`: pending/prepared state를 discard한 뒤 cancel하고 terminal error로 끝낸다.
- Evidence Map의 exact confirmation identity와 fail-closed/discard-before-cancel 요구를 위치 구별 suffix·token·cursor·consumed matrix와 cancel hook 시점 snapshot/trace assertion으로 역산했다.
- S03/S04/S17 boundary behavior는 현재 production/source와 named inventory에서 유지하며 이 follow-up은 증거 공백만 닫는다.

### 테스트 환경 규칙

- `test_env`: local.
- `agent-test/local/rules.md`와 매칭 profile `agent-test/local/platform-common-smoke.md`를 읽었다.
- PATH host Go(`/config/.local/bin/go` → `/config/opt/go/bin/go`, go1.26.2 linux/arm64, GOROOT `/config/opt/go`)를 바꾸지 않고 `make proto`, named/fresh/race package, vet, Edge/Node transport unit, gofmt와 deterministic hygiene를 적용한다.
- Edge/Node transport unit은 host GOROOT의 `crypto/internal/fips140cache` 누락으로 setup 전에 실패한다. 다른 toolchain으로 우회하지 않고 원문/exit code와 `/config/opt/go` 복구 조건을 기록한다.
- 외부 runner/service와 production consumer/proto/config write가 없으므로 별도 원격 프리플라이트와 full-cycle은 적용하지 않는다. test-rule 유지보수도 필요하지 않다.

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

- partial release 후 suffix count는 확인하지만 모든 payload가 `"chunk"`라 sequence 위치가 틀려도 통과한다.
- zero/partial/full matrix는 각 prepare snapshot의 exact payload와 token 교체를 모두 비교하지 않고 full confirm 뒤 consumed reprepare 거부도 확인하지 않는다.
- cancel hook은 discard 이후 실행되지만 hook 시점 pending entries/runes, prepared ownership과 stale confirm 결과를 관찰하지 않아 ordering regression을 검출하지 못한다.
- active evidence는 실제 assertion과 transport blocker 분류에 일치하지 않는다.

### 심볼 참조

- public rename/remove 없음.
- test-only distinct fixture helper를 추가하거나 기존 `appendDataEvents`를 보조하는 방식만 허용한다.
- production call site와 dependency 변경 없음.

### 분할 판단

- split decision policy를 파일명 선택 전에 평가했다.
- 기존 dependent subtask `07+01,06_commit_boundary`를 유지한다. predecessor `01`과 `06`은 Archive Evidence Snapshot의 두 `complete.log`로 충족됐다.
- 두 Required는 같은 `stream_release_test.go`, 같은 prepare-confirm/discard 불변조건과 동일 named/race inventory를 공유한다. test artifact별 분할은 독립 PASS를 만들지 못하고 기존 runtime dependency identity만 깨므로 단일 follow-up이 안전하다.

### 범위 결정 근거

- 수정은 `packages/go/streamgate/stream_release_test.go`와 현재 review evidence에 한정한다.
- `commit_boundary.go`, `commit_boundary_test.go`, `stream_release.go`, `evidence_tail.go`, Edge/Node consumers, proto/config, host Go 설치와 다른 dirty worktree는 수정하지 않는다.
- assertion을 위해 same-package private state를 hook 시점에 snapshot할 수 있으나 production accessor는 추가하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build:
  - closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`
  - closure basis:
    - scope: distinct suffix matrix와 cancel hook snapshot이라는 두 test-only 수정 경계가 확정됐다.
    - context: releaser/tail state와 한 test 파일, SDD 두 row를 bounded context에서 유지할 수 있다.
    - verification: exact named race, package race와 deterministic source inventory로 성공을 판정할 수 있다.
    - evidence: 현재 PASS 출력과 assertion 공백이 source line에서 재현됐다.
    - ownership: production과 다른 dirty worktree를 제외한 test/evidence write-set이 분리됐다.
    - decision: SDD/Milestone 잠금이 해제됐고 사용자 결정 항목이 없다.
  - scores: `scope_coupling=1`, `state_concurrency=2`, `blast_irreversibility=0`, `evidence_diagnosis=2`, `verification_complexity=1`
  - result: lane `local`, grade `G06`, filename `PLAN-local-G06.md`
- review:
  - closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`
  - closure basis:
    - scope: exact fixture/state/order assertion과 evidence consistency 기준이 명시됐다.
    - context: source, named tests, SDD evidence와 원문 출력이 bounded review context에 들어온다.
    - verification: inventory/focused/named/package 명령이 실제 경로를 독립적으로 선택한다.
    - evidence: marker-only assertion과 전체 PASS 오기록을 현재 source/evidence에서 구분할 수 있다.
    - ownership: review 대상과 범위 밖 dirty worktree가 분리된다.
    - decision: 제품 선택이나 외부 권한 판단이 필요 없다.
  - scores: `scope_coupling=1`, `state_concurrency=2`, `blast_irreversibility=0`, `evidence_diagnosis=2`, `verification_complexity=1`
  - result: lane `local`, grade `G06`, filename `CODE_REVIEW-local-G06.md`

## 구현 체크리스트

- [x] `REVIEW_REVIEW_API-1` 위치 구별 fixture로 zero/partial/full exact suffix·token·cursor와 full-consumed reprepare 거부를 기존 named confirmation test에 고정한다.
- [x] `REVIEW_REVIEW_API-2` canceller hook 시점 pending/prepared/stale-token snapshot과 `discard-verified → cancel → terminal` exact trace를 기존 named ordering test에 고정한다.
- [x] `REVIEW_REVIEW_API-3` exact 8-test inventory와 focused/named/fresh/race/profile/hygiene 명령을 그대로 실행하고 실제 stdout/stderr 및 host transport blocker를 모순 없이 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_REVIEW_API-1] 위치 구별 confirmation identity matrix

- 문제:
  - `packages/go/streamgate/stream_release_test.go:486`의 fixture는 모든 event text가 `"chunk"`여서 partial suffix 위치를 구별하지 못한다.
  - `packages/go/streamgate/stream_release_test.go:561`의 matrix는 prepare별 payload를 비교하지 않고 full confirm 뒤 consumed/reprepare 거부를 단언하지 않는다.
- 해결 방법:
  - `"alpha"`, `"beta"`, `"gamma"`, `"delta"`, `"epsilon"`처럼 위치가 구별되는 same-epoch fixture helper를 test-only로 추가한다.
  - partial sink progress 2 뒤 remaining suffix가 정확히 `gamma,delta,epsilon`인지 확인한다.
  - zero/partial/full matrix에서 initial/zero/partial snapshot, 매 prepare의 새 token, cursor `0→1→3`, full confirm 뒤 같은 epoch `PrepareRelease` 거부를 직접 비교한다.
- Before (`packages/go/streamgate/stream_release_test.go:508`):

```go
wantSuffix := []string{"chunk", "chunk", "chunk"}
```

- After (`packages/go/streamgate/stream_release_test.go`):

```go
wantSuffix := []string{"gamma", "delta", "epsilon"}
if diff := compareStrings(releaseEventTexts(t, prepared.ReleaseEvents()), wantSuffix); diff != "" {
	t.Fatalf("remaining suffix mismatch: %s", diff)
}
```

- 수정 파일 및 체크리스트:
  - [x] `packages/go/streamgate/stream_release_test.go`: distinct fixture helper와 exact suffix/token/cursor/consumed matrix
- 테스트 작성:
  - 기존 `TestStreamReleaserConfirmsProgressEvenWhenSinkFails`만 보강하고 새 top-level test 이름은 추가하지 않는다.
  - full confirm 뒤 `PrepareRelease(epoch.ID())`가 성공하면 실패하도록 단언한다.
- 중간 검증:
  - `go test -race -count=20 ./packages/go/streamgate -run '^TestStreamReleaserConfirmsProgressEvenWhenSinkFails$'`
  - 기대 결과: 위치별 suffix, token rotation, cursor와 consumed state가 PASS한다.

### [REVIEW_REVIEW_API-2] Cancel hook 시점 discard snapshot

- 문제:
  - `packages/go/streamgate/stream_release_test.go:1016`의 hook은 state를 검사하지 않고 `"discard-verified"` marker만 기록한다.
  - `packages/go/streamgate/stream_release_test.go:1036`은 explicit `cancel` marker가 없는 2단계 trace를 기대한다.
- 해결 방법:
  - `onCancel` 안에서 channel pending entry/rune count, prepared token ownership, stale token confirm 및 epoch reprepare 결과를 snapshot한다.
  - hook 안에서 `discard-verified`, 실제 cancel 진입을 뜻하는 `cancel` marker를 순서대로 기록하고 terminal hook이 마지막 marker를 기록한다.
  - `FailPending` 반환 뒤 hook snapshot이 empty/zero/no-token/stale-rejected인지와 exact 3단계 trace를 단언한다.
- Before (`packages/go/streamgate/stream_release_test.go:1016`):

```go
canceller.onCancel = func() {
	trace = append(trace, "discard-verified")
}
```

- After (`packages/go/streamgate/stream_release_test.go`):

```go
canceller.onCancel = func() {
	observed = snapshotDiscardState(releaser.tail, "ch-order", epoch.ID(), prepareToken)
	trace = append(trace, "discard-verified", "cancel")
}
```

- 수정 파일 및 체크리스트:
  - [x] `packages/go/streamgate/stream_release_test.go`: hook-time discard observation과 exact cancel ordering assertion
- 테스트 작성:
  - 기존 `TestStreamReleaserFailPendingOrdersDiscardCancelTerminal`을 보강한다.
  - private state를 읽는 helper는 test-only로 두고 production accessor를 추가하지 않는다.
- 중간 검증:
  - `go test -race -count=20 ./packages/go/streamgate -run '^TestStreamReleaserFailPendingOrdersDiscardCancelTerminal$'`
  - 기대 결과: hook snapshot과 `discard-verified → cancel → terminal` 순서가 PASS한다.

### [REVIEW_REVIEW_API-3] Verification evidence 일치

- 문제:
  - archived review의 구현 설명은 실제 test 조작/검사와 다르고, transport exit code 1을 기록하면서 전체 1~13 PASS라고 서술한다.
- 해결 방법:
  - 아래 명령을 그대로 실행하고 stdout/stderr와 exit code를 실제 결과대로 기록한다.
  - transport setup failure가 유지되면 PASS가 아닌 host blocker로 분류하고 `/config/opt/go` 복구 조건을 기록한다.
  - 긴 focused output은 `/tmp/iop-streamgate-review-review-api-9.log`에 저장하고 repo 안에 probe/output artifact를 남기지 않는다.
- 수정 파일 및 체크리스트:
  - [x] `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/CODE_REVIEW-local-G06.md`: 실제 구현 설명, 명령 원문, stdout/stderr와 blocker 분류
- 테스트 작성:
  - production regression은 `REVIEW_REVIEW_API-1/2`에서 작성한다. 이 항목은 evidence와 hygiene만 소유한다.
- 중간 검증:
  - 아래 최종 검증 1~13을 순서대로 실행한다.

## 의존 관계 및 구현 순서

1. predecessor `01_event_contract_types`와 `06+01_evidence_tail`은 Archive Evidence Snapshot의 `complete.log`로 충족됐다.
2. `REVIEW_REVIEW_API-1`이 exact confirmation identity를 고정한다.
3. `REVIEW_REVIEW_API-2`가 discard-before-cancel 시점 증거를 고정한다.
4. `REVIEW_REVIEW_API-3`이 source와 실행 원문을 일치시킨다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/stream_release_test.go` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2 |
| `agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary/CODE_REVIEW-local-G06.md` | REVIEW_REVIEW_API-3 |

## 최종 검증

1. `command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT`
   - 기대 결과: PATH host Go와 실제 경로/GOROOT 기록.
2. `gofmt -d packages/go/streamgate/stream_release_test.go`
   - 기대 결과: stdout 없음.
3. `test "$(go test -list '^Test(CommitBoundary(PreservesReturnedStateAcrossSinkErrors|RejectsWrongOrMissingAttempt|SerializesConcurrentStartAndTerminal|RejectsPostOpenReplacement)|StreamReleaser(ConfirmsProgressEvenWhenSinkFails|SuppressesSuccessTerminalUntilConfirmation|RejectsReentrantCallbackWithoutDeadlock|FailPendingOrdersDiscardCancelTerminal))$' ./packages/go/streamgate | sed -n '/^Test/p' | wc -l)" -eq 8`
   - 기대 결과: exit code 0; 정확히 8개 named regression.
4. `go test -race -count=20 ./packages/go/streamgate -run '^Test(CommitBoundary(PreservesReturnedStateAcrossSinkErrors|RejectsWrongOrMissingAttempt|SerializesConcurrentStartAndTerminal|RejectsPostOpenReplacement)|StreamReleaser(ConfirmsProgressEvenWhenSinkFails|SuppressesSuccessTerminalUntilConfirmation|RejectsReentrantCallbackWithoutDeadlock|FailPendingOrdersDiscardCancelTerminal))$'`
   - 기대 결과: exact confirmation identity, callback count와 discard/cancel/terminal ordering PASS.
5. `go test -count=1 -v ./packages/go/streamgate -run '^Test(CommitBoundary|StreamReleaser)' | tee /tmp/iop-streamgate-review-review-api-9.log`
   - 기대 결과: exit code 0; 실제 test/subtest 출력은 stdout과 `/tmp/iop-streamgate-review-review-api-9.log`에 보존되고 repo에는 생성물이 없다.
6. `go test -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS.
7. `go test -race -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS, race 없음.
8. `go vet ./packages/go/streamgate`
   - 기대 결과: PASS, stdout 없음.
9. `make proto`
   - 기대 결과: 성공, proto 생성물 추가 diff 없음.
10. `go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`
    - 기대 결과: PASS. 동일 GOROOT setup failure면 우회 없이 원문/exit code와 `/config/opt/go` 복구 조건 기록.
11. `! rg --sort path -n 'wantSuffix := \\[\\]string\\{\"chunk\", \"chunk\", \"chunk\"\\}|want \\[discard-verified terminal\\]|TestReviewProbe' packages/go/streamgate/stream_release_test.go`
    - 기대 결과: stdout 없음, exit code 0.
12. `rg --sort path -n 'gamma.*delta.*epsilon|CommittedCursor|PrepareRelease\\(epoch\\.ID\\(\\)\\)|discard-verified|\"cancel\"|ConfirmRelease\\(prepareToken' packages/go/streamgate/stream_release_test.go`
    - 기대 결과: 위치 구별 suffix/cursor/consumed와 hook-time discard/cancel 근거가 열거된다.
13. `git diff --check -- packages/go/streamgate/stream_release_test.go agent-task/m-stream-evidence-gate-core/07+01,06_commit_boundary proto/gen/iop && git status --short -- proto/gen/iop`
    - 기대 결과: stdout 없음.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
