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

# Stream Gate Commit Boundary와 Release Integration 구현 계획

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

- 선행 `06+01_evidence_tail`의 `complete.log`를 먼저 확인한 뒤 구현 체크리스트와 검증 명령을 수행한다.
- `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 기록하고 active 파일을 유지한 채 review-ready만 보고한다.
- 막히면 정확한 blocker, 시도한 명령과 출력, 재개 조건만 구현 소유 evidence에 기록한다.
- 사용자 질문·선택지·user-input 도구·control-plane stop 파일·상태 분류·archive/complete 작업은 하지 않는다.

## 배경

재검토에서 evidence cursor와 sink 성공, attempt replacement·empty success·overflow cancel의 상태 경계를 명확히 했다. 이 계획은 request-scope `CommitBoundary`와 `EvidenceTail` 사이에 progress-aware `StreamReleaser`를 두어 partial write를 정확히 confirm한다. S19의 cancel→terminal/no-release까지 여기서 검증한 뒤 foundation evidence와 함께 `evidence-tail`, `commit-boundary` Roadmap Task를 동시에 완료한다.

## Archive Evidence Snapshot

- 선행 완료: `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
- 선행 대기: `06+01_evidence_tail`의 revised `complete.log`.
- 구현 기준: release partial-progress 연동, S19 attempt cancel, attempt generation/reset, idle pending discard, empty success, state-only recovery eligibility.
- 구현자는 위 선행 경로 외의 `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`: S01/S02 predecessor evidence와 S19 overflow cancel/terminal 통합
  - `commit-boundary`: staged response-start, irreversible release state와 single terminal
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/event.go`
- `packages/go/streamgate/filter_contract.go`
- `packages/go/streamgate/terminal.go`
- `packages/go/streamgate/evaluation_contract.go`
- `packages/go/streamgate/parallel_evaluation.go`
- `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-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-spec/index.md`
- `agent-contract/index.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/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`
- `.gitignore`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`, 구현 잠금 `해제`, 사용자 리뷰 `없음`
- 대상: S01, S02, S03, S04, S05, S17, S19 / Milestone Tasks `evidence-tail`, `commit-boundary`
- S01/S02의 rune state는 predecessor `06` complete evidence로 확인한다.
- 이 plan은 S03 terminal-before-evidence, S04 idle no-release, S05 commit/strategy matrix, S17 original start hidden/final start once, S19 overflow cancel-before-terminal/no partial release를 fake sink/controller 통합 fixture로 검증한다.
- opening role/event는 현재 normalized event에 별도 kind가 없다. Core의 단일 `CommitResponseStart` 호출을 host가 status/header와 endpoint opening event의 atomic commit signal로 사용하며, fake sink는 이 호출이 first safe release 전 0회이고 selected attempt에서 1회인지 검증한다.

### 테스트 환경 규칙

- `test_env`: local
- `agent-test/local/rules.md`와 `agent-test/local/platform-common-smoke.md`를 읽었다.
- `PATH`의 host 설치 `go`만 사용하고 별도 `GOTOOLCHAIN`이나 고정 버전 경로를 쓰지 않는다.
- 현재 `./packages/go/streamgate` fresh/race/vet와 `make proto`는 PASS한다.
- Edge/Node transport unit은 host GOROOT의 `crypto/internal/fips140cache` 누락으로 setup failure다. 우회하지 않고 재실행하며, 계속 실패하면 정확한 오류와 host Go 설치 복구를 재개 조건으로 기록한다.
- host handler를 아직 채택하지 않으므로 실제 Edge/OpenAI full-cycle은 `vertical-slice` Task에 남긴다.

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

- staged response-start와 first safe event의 ordered single commit이 없다.
- sink가 start 또는 일부 release 뒤 실패할 때 downstream progress와 evidence cursor를 일치시키는 검증이 없다.
- request-scope boundary에서 uncommitted attempt replacement가 original start를 폐기하는 API가 없다.
- body 없는 terminal success, idle pending discard와 terminal exactly once가 없다.
- S19 overflow에서 attempt cancel이 terminal보다 먼저 실행되고 release가 0인지 검증이 없다.
- commit state만 보는 exact/schema/continuation eligibility와 sink 반환 state의 regression 검증이 없다.

### 심볼 참조

- rename/remove 없음.
- 새 public symbol: `CommitBoundary`, `ReleaseProgress`, `StreamReleaser`, `AttemptCanceller`, `NewCommitBoundary`, `NewStreamReleaser`.
- predecessor `PreparedRelease`, `ReleaseConfirmation`, `EvidenceTailSignal`을 소비한다.
- `ReleaseSink` 구현체와 기존 event/terminal contracts는 변경하지 않는다.
- 매칭되는 active agent-spec/agent-contract 문서는 없으며 외부 API·wire·config schema를 바꾸지 않는다.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 적용했다.
- `06+01_evidence_tail` foundation과 `07+01,06_commit_boundary` sink/attempt integration은 ownership 및 verification boundary로 분리됐다.
- 이 plan 안에서는 commit state, partial release confirmation, terminal/attempt ordering이 하나의 irreversible request lifecycle을 공유한다. 별도 PASS 단위로 나누면 S19와 cursor consistency를 어느 쪽도 닫지 못하므로 한 subtask로 유지한다.
- sibling `08+02,05,06_filter_registry`는 `06` 뒤 병렬 가능하며 `07`에 의존하지 않는다.

### 범위 결정 근거

- 새 `packages/go/streamgate/commit_boundary.go`, `commit_boundary_test.go`, `stream_release.go`, `stream_release_test.go`를 수정한다.
- Edge/OpenAI handler의 eager write 제거와 production `ReleaseSink`/`AttemptCanceller` adapter는 `vertical-slice`/consumer adoption 범위다.
- concrete Arbiter, recovery budget/dispatch/rebuild, semantic filter, config/proto/API는 구현하지 않는다.
- `CommitAllowsStrategy`는 commit state 한 축만 판정하며 capability, terminal/tool side effect와 budget을 대신 판정하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - 근거: 네 신규 파일, predecessor gate와 S03/S04/S05/S17/S19 integration fixture로 request lifecycle 범위가 닫힌다.
  - scores: scope coupling 2, state/concurrency 2, blast/irreversibility 1, evidence diagnosis 0, verification complexity 1
  - result: `local`, `G06`, `PLAN-local-G06.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - 근거: fake sink/controller ordering, partial progress, attempt replacement와 race test로 모든 irreversible transition을 판정할 수 있다.
  - scores: scope coupling 2, state/concurrency 2, blast/irreversibility 1, evidence diagnosis 1, verification complexity 1
  - result: `local`, `G07`, `CODE_REVIEW-local-G07.md`

## 구현 체크리스트

- [ ] `API-1` attempt-scoped staged response-start, monotonic sink state, empty success와 exactly-once terminal을 가진 request-scope `CommitBoundary`를 구현·테스트한다.
- [ ] `API-2` prepared evidence와 sink `ReleaseProgress`를 연결해 zero/partial/full write만 cursor에 confirm하고 terminal-before-evidence의 selected outcome만 commit하는 `StreamReleaser`를 구현·테스트한다.
- [ ] `API-3` uncommitted attempt replacement, idle pending discard, S19 overflow cancel→terminal/no-release와 state-only recovery matrix를 구현·테스트한다.
- [ ] 호스트 `go` 기준 focused/fresh/race/vet 및 공통 profile 명령을 실행하고 blocker를 포함한 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Request-scope staging과 monotonic sink state

- 문제:
  - `packages/go/streamgate/terminal.go:10-36`은 commit enum만 정의하고 전이 주체가 없다.
  - `packages/go/streamgate/terminal.go:541-561`은 sink seam만 제공해 eager/duplicate start와 terminal을 막지 못한다.
- 해결 방법:
  - `CommitBoundary`가 sink, mutex, request state, current attempt ID, optional staged start와 terminal reservation을 소유한다.
  - state lock 아래에서 operation을 reserve하고 외부 `ReleaseSink` 호출 동안 mutex를 보유하지 않는다. concurrent release/terminal은 in-flight token으로 직렬화해 re-entrant host callback과 deadlock을 피한다.
  - `BeginAttempt`/`StageResponseStart`는 `transport_uncommitted`에서만 현재 attempt의 start를 defensive copy하고 sink를 호출하지 않는다.
  - `ReleaseSafe`는 staged start를 먼저 한 번 commit한 뒤 ordered events를 쓰며 `ReleaseProgress{ReleasedEvents, State}`를 오류와 함께 반환한다.
  - sink 반환 state가 expected monotonic transition보다 뒤로 가거나 unknown이면 stable error로 거부하되 이미 성공한 write progress는 보존한다.
  - body 없는 success terminal은 staged start를 commit한 뒤 terminal을 commit한다. error terminal은 uncommitted staged start를 버리고 terminal만 commit한다. terminal reservation으로 concurrent 호출 중 하나만 sink에 도달한다.
- Before (`terminal.go:550-561`):

```go
type ReleaseSink interface {
	CommitResponseStart(ctx context.Context, rs ResponseStart) (CommitState, error)
	Release(ctx context.Context, ev ReleaseEvent) (CommitState, error)
	CommitTerminal(ctx context.Context, tr TerminalResult) (CommitState, error)
}
```

- After (`commit_boundary.go`):

```go
func (b *CommitBoundary) BeginAttempt(attemptID string) error
func (b *CommitBoundary) StageResponseStart(attemptID string, start ResponseStart) error
func (b *CommitBoundary) ReleaseSafe(ctx context.Context, attemptID string, events []ReleaseEvent) (ReleaseProgress, error)
func (b *CommitBoundary) CommitTerminal(ctx context.Context, attemptID string, result TerminalResult) error
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/commit_boundary.go`: constructor, attempt/start staging, non-locking sink reservation, transition validation, progress, empty success, terminal reservation
  - [ ] `packages/go/streamgate/commit_boundary_test.go`: eager absence, start/order, returned-state matrix, start/body failure, empty success, concurrent/re-entrant terminal
- 테스트 작성:
  - `TestCommitBoundaryStagesUntilFirstSafeRelease`: stage만으로 sink call 0, first release에서 start가 먼저 1회임을 검증한다.
  - `TestCommitBoundaryReportsPartialSinkProgress`: start/body 단계별 실패와 returned state/progress를 검증한다.
  - `TestCommitBoundaryCommitsEmptySuccessAndTerminalOnce`: body 없는 success와 concurrent terminal exactly once를 검증한다.
- 중간 검증:
  - `go test -race -count=1 ./packages/go/streamgate -run '^TestCommitBoundary(StagesUntilFirstSafeRelease|ReportsPartialSinkProgress|CommitsEmptySuccessAndTerminalOnce)$'`
  - 기대 결과: PASS, race 없음

### [API-2] Evidence prepare/confirm와 sink progress 통합

- 문제:
  - predecessor의 prepared release를 sink 결과와 연결하는 owner가 없으면 일부 event만 전송된 뒤 cursor가 중복되거나 유실될 수 있다.
- 해결 방법:
  - `StreamReleaser`는 `EvidenceTail`과 `CommitBoundary`를 조합하지만 raw payload를 복제하지 않는다.
  - `ReleaseEpoch`은 tail `PrepareRelease`, boundary `ReleaseSafe`, tail `ConfirmRelease` 순서로 실행한다.
  - `ReleaseTerminalEpoch`은 final evaluation이 release를 허용한 경우 같은 progress confirmation 뒤 success terminal을 commit한다. terminal error action은 `FailPending` 경로로 분리해 두 outcome이 한 request에서 함께 실행되지 않게 한다.
  - boundary가 오류와 함께 progress를 반환해도 그 event 수만 confirmation에 반영하고 나머지는 pending으로 유지한다. confirmation 자체가 실패하면 terminal-safe error를 반환하고 자동 재전송하지 않는다.
  - 같은 prepared token을 concurrent/repeated 호출해도 한 번만 적용하고 event order를 유지한다.
- Before (`evidence_tail.go`, predecessor contract):

```go
func (t *EvidenceTail) PrepareRelease(epochID uint64) (PreparedRelease, error)
func (t *EvidenceTail) ConfirmRelease(token string, confirmation ReleaseConfirmation) error
```

- After (`stream_release.go`):

```go
func (r *StreamReleaser) ReleaseEpoch(
	ctx context.Context,
	attemptID string,
	epochID uint64,
) (ReleaseProgress, error)

func (r *StreamReleaser) ReleaseTerminalEpoch(
	ctx context.Context,
	attemptID string,
	epochID uint64,
	result TerminalResult,
) (ReleaseProgress, error)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/stream_release.go`: prepare→sink→confirm orchestration, terminal epoch, partial progress, token single-use, stable errors
  - [ ] `packages/go/streamgate/stream_release_test.go`: zero/partial/full sink progress, terminal pass/error exclusivity, event order, duplicate/concurrent call, cursor/look-behind result
- 테스트 작성:
  - `TestStreamReleaserConfirmsExactlySinkProgress`: zero/partial/full table에서 cursor와 pending이 sink progress와 일치함을 검증한다.
  - `TestStreamReleaserTerminalBeforeEvidenceExposesOneOutcome`: final evaluation 전 sink call 0이고 pass는 safe start/output+success terminal, error는 pending/start 없이 error terminal 하나만 노출함을 검증한다.
  - `TestStreamReleaserDoesNotDuplicatePreparedRelease`: duplicate/concurrent token 사용이 downstream event를 중복하지 않음을 검증한다.
- 중간 검증:
  - `go test -race -count=1 ./packages/go/streamgate -run '^TestStreamReleaser(ConfirmsExactlySinkProgress|TerminalBeforeEvidenceExposesOneOutcome|DoesNotDuplicatePreparedRelease)$'`
  - 기대 결과: PASS, race 없음

### [API-3] Attempt replacement, idle와 overflow terminal

- 문제:
  - S17 exact replay에서 original staged start를 버리고 selected retry attempt start만 노출하는 전이가 없다.
  - S04/S19는 pending을 release하지 않고 attempt cancel 뒤 terminal을 한 번만 commit해야 한다.
- 해결 방법:
  - `ReplaceUncommittedAttempt(oldID,newID)`는 state가 uncommitted이고 old attempt가 current일 때만 staged start를 폐기하고 새 attempt generation을 연다. stream-open/terminal에서는 거부한다.
  - `CommitAllowsStrategy`는 exact/schema=uncommitted, continuation=uncommitted|stream-open만 반환하고 다른 eligibility는 후속 coordinator에 남긴다.
  - `AttemptCanceller`는 current provider transport cancel의 좁은 host seam이다. `FailPending`은 tail pending/prepared release를 terminal discard하고 cancel을 먼저 호출한 뒤 boundary error terminal을 commit한다.
  - cancel 실패도 release/next attempt 없이 같은 single terminal로 끝내며 stable bounded cause만 append한다.
  - overflow fixture는 `EvidenceTailSignalOverflow` 외에는 release input을 허용하지 않고 cancel→terminal order, release 0, original start 0을 검증한다.
- Before (`filter_contract.go:322-336`):

```go
type RecoveryStrategy string
```

- After (`commit_boundary.go`, `stream_release.go`):

```go
func (b *CommitBoundary) ReplaceUncommittedAttempt(oldAttemptID, newAttemptID string) error
func (b *CommitBoundary) CommitAllowsStrategy(strategy RecoveryStrategy) bool

type AttemptCanceller interface {
	Cancel(context.Context) error
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/commit_boundary.go`: attempt replacement와 state-only strategy matrix
  - [ ] `packages/go/streamgate/commit_boundary_test.go`: original/final start exposure, invalid post-open replace, strategy matrix
  - [ ] `packages/go/streamgate/stream_release.go`: idle/overflow pending discard, cancel-before-terminal, cancel failure terminal
  - [ ] `packages/go/streamgate/stream_release_test.go`: S04/S17/S19 fixtures와 call ordering
- 테스트 작성:
  - `TestCommitBoundaryReplacesOnlyUncommittedAttempt`: original start가 숨겨지고 final attempt start/output만 한 번 노출됨을 검증한다.
  - `TestCommitBoundaryCommitStrategyMatrix`: commit state별 세 recovery strategy 표를 검증한다.
  - `TestStreamReleaserIdleReleasesNoPending`: idle에서 release 0, cancel→terminal 순서를 검증한다.
  - `TestStreamReleaserOverflowCancelsBeforeSingleTerminal`: S19 hard overflow가 release/start 없이 cancel 뒤 terminal 한 번으로 끝나는지 검증한다.
- 중간 검증:
  - `go test -race -count=1 ./packages/go/streamgate -run '^Test(CommitBoundary(ReplacesOnlyUncommittedAttempt|CommitStrategyMatrix)|StreamReleaser(IdleReleasesNoPending|OverflowCancelsBeforeSingleTerminal))$'`
  - 기대 결과: PASS, race 없음

## 의존 관계 및 구현 순서

1. `01_event_contract_types`는 `agent-task/archive/2026/07/m-stream-evidence-gate-core/01_event_contract_types/complete.log`로 충족됐다.
2. `06+01_evidence_tail`은 revised active plan 상태다. 구현 시작 전 active 또는 archive `complete.log`를 확인하며 없으면 진행하지 않는다.
3. `API-1` request boundary, `API-2` evidence progress integration, `API-3` attempt/terminal ordering 순서로 구현한다.
4. PASS 시 predecessor S01/S02 evidence와 이 plan의 S19 integration evidence를 함께 확인한 뒤 Roadmap `evidence-tail`, `commit-boundary` 두 Task 완료 이벤트를 보고한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/commit_boundary.go` | API-1, API-3 |
| `packages/go/streamgate/commit_boundary_test.go` | API-1, API-3 |
| `packages/go/streamgate/stream_release.go` | API-2, API-3 |
| `packages/go/streamgate/stream_release_test.go` | API-2, 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/commit_boundary.go packages/go/streamgate/commit_boundary_test.go packages/go/streamgate/stream_release.go packages/go/streamgate/stream_release_test.go`
   - 기대 결과: stdout 없음
3. `go test -count=1 ./packages/go/streamgate -run '^Test(CommitBoundary|StreamReleaser)'`
   - 기대 결과: PASS
4. `go test -race -count=1 ./packages/go/streamgate`
   - 기대 결과: PASS, race 없음
5. `go vet ./packages/go/streamgate`
   - 기대 결과: PASS, stdout 없음
6. `make proto`
   - 기대 결과: 성공, proto 생성물 추가 diff 없음
7. `go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`
   - 기대 결과: PASS. host GOROOT mismatch가 계속되면 우회하지 말고 실제 오류와 host Go 설치 복구 조건을 기록
8. `git diff --check -- packages/go/streamgate proto/gen/iop`
   - 기대 결과: stdout 없음
9. `git status --short -- proto/gen/iop`
   - 기대 결과: stdout 없음

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
