<!-- task=m-stream-evidence-gate-core/09_decision_arbiter plan=2 tag=REVIEW_API -->

# Stream Evidence Gate Core — Decision Arbiter 리뷰 보완 계획

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

- 아래 Required 범위만 수정한다. 새 action, recovery 실행, provider wiring, policy 조합은 추가하지 않는다.
- 구현 전 `agent-ops/rules/project/rules.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-test/local/rules.md`를 확인한다.
- 각 항목 완료 시 `CODE_REVIEW-cloud-G05.md`의 구현 에이전트 소유 섹션에 실제 변경과 명령 출력을 기록한다.
- 구현 완료 후 active 파일을 유지하고 review-ready로 보고한다. commit/push, roadmap 직접 갱신, 사용자 질문은 범위 밖이다.

## 배경

최초 구현은 S13의 기본 arbitration 흐름과 패키지 회귀 검증을 통과했지만, replacement result 생성자가 금지된 recovery intent를 조용히 버리는 계약 오류가 있다. 또한 원 계획이 완료됐다고 표시한 replacement 충돌·입력 순서 독립성, ready/non-ready priority snapshot, `Evaluate`/`Submit` result 전파를 테스트가 실제로 증명하지 못했다.

이 보완은 공개 surface를 다시 설계하지 않는다. 기존 `ArbitrationResult`, priority propagation, coordinator 반환 계약을 엄격하게 검증하고 누락된 실행 증거를 추가한다.

## 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:
  - `decision-arbiter`: base disposition과 모든 active filter outcome을 deterministic하게 합성해 single action을 반환한다.
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- `agent-task/m-stream-evidence-gate-core/09_decision_arbiter/plan_local_G07_1.log`: 최초 구현 범위와 완료 주장을 보존한 계획 증거다.
- `agent-task/m-stream-evidence-gate-core/09_decision_arbiter/code_review_cloud_G07_1.log`: 종합 `FAIL`; Required 2건, Suggested 0건, Nit 0건. replacement action의 금지 intent 수용과 필수 `ruleID` 검증 불일치, replacement/conflict/order·priority snapshot·coordinator result 전파 테스트 누락을 기록한다. 집중 재현은 `replacement action accepted a forbidden recovery intent`로 실패했고, 패키지 전체/race/proto/transport 검증은 통과했다.

## Analysis Result

### 읽은 파일

- `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-ops/rules/project/domain/platform-common/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `packages/go/streamgate/decision_arbiter.go`
- `packages/go/streamgate/decision_arbiter_test.go`
- `packages/go/streamgate/evaluation_contract.go`
- `packages/go/streamgate/evaluation_contract_test.go`
- `packages/go/streamgate/filter_registry.go`
- `packages/go/streamgate/filter_registry_test.go`
- `packages/go/streamgate/parallel_evaluation.go`
- `packages/go/streamgate/parallel_evaluation_test.go`
- 위 `Archive Evidence Snapshot`의 두 로그

### 계약·코드·테스트 분석

- 승인된 SDD S13과 Evidence Map은 닫힌 action, stable filter/rule ID, effective priority 결정성, complete barrier 뒤 단일 arbiter result를 요구한다.
- `NewArbitrationResult`는 replacement가 있으면 action 검증 전에 intent 복사본을 `nil`로 덮어쓴다. 따라서 원 입력에 intent와 replacement가 함께 있어도 forbidden-field 오류가 사라진다.
- recover/replacement는 선택된 rule의 안정 식별을 보존해야 하므로 constructor와 `Validate`가 비어 있는 `ruleID`를 같은 방식으로 거절해야 한다.
- 현재 arbiter 테스트는 recovery 후보 정렬은 다루지만 실제 replacement 후보, recover/replacement 충돌, 입력 순열별 동일 결과를 실행하지 않는다.
- `EpochFilter`/`EpochFilterOutcome` 테스트는 음수 priority를 거절하지만 정상 ready/non-ready snapshot 값을 assertion하지 않는다. registry test도 bind 후 priority 보존을 확인하지 않는다.
- coordinator 테스트는 arbiter 호출 횟수는 확인하지만 `Evaluate`와 `Submit`이 arbiter가 만든 동일한 result 값을 반환하는지 확인하지 않는다.

### 심볼·호출부 영향

- 수정 대상 production symbol은 `NewArbitrationResult`와 그 `Validate` 계약뿐이다. 기존 action 이름과 coordinator signature는 유지한다.
- result 생성과 검증 call-site는 같은 `streamgate` 패키지에 닫혀 있고, 누락된 증거는 기존 unit/contract test seam에서 추가할 수 있다.
- 이름 변경이나 새 wire/config schema가 없으므로 proto와 transport는 회귀 검증만 수행한다.

### 분할·범위 판단

- constructor 계약 수정과 그 결과를 사용하는 arbitration/coordinator 회귀 테스트는 한 패키지의 단일 닫힌 결과 계약이다. 분리하면 수정 또는 증거 한쪽만 남아 판정할 수 없으므로 하나의 계획으로 유지한다.
- recovery 실행/eligibility, retry/rebuild/dispatch, policy schedule, consumer closure, provider별 분기는 범위 밖이다.
- 사용자 결정, 외부 환경, capability gap은 없다. 경계 변형과 입력 순열, race 검증으로 loop-risk를 닫는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- finalizer/mode: `finalize-task-policy.sh`, `pair`
- Build: local-fit=true, scope=1, state=1, blast=1, evidence=1, verification=1 → `local G05`
- Review: official-review, scope=1, state=1, blast=1, evidence=1, verification=1 → `cloud G05`
- Review adapter/model/effort: `codex`, `gpt-5.6-sol`, `xhigh`
- Archive sequence: 현재 plan/review log count는 각각 2이며 이 쌍의 다음 suffix는 `_2.log`다.

## 구현 체크리스트

- [x] [FIX-1] `ArbitrationResult`가 원 입력 기준으로 action별 금지 필드와 recover/replacement의 필수 `ruleID`를 일관되게 검증하도록 수정한다.
- [x] [TEST-2] replacement arbitration, recover/replacement 충돌, 입력 순서 독립성과 constructor 회귀를 자동화한다.
- [x] [TEST-3] ready/non-ready effective priority snapshot과 `Evaluate`/`Submit` arbiter result 전파를 자동화한다.
- [x] [VERIFY-4] 집중/전체/race/proto/transport/diff 검증을 fresh 실행하고 실제 출력을 기록한다.
- [x] `CODE_REVIEW-cloud-G05.md`의 모든 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [FIX-1] action별 `ArbitrationResult` 검증을 원 입력에 적용

문제:

- `decision_arbiter.go:100`은 replacement 복사 과정에서 `intentCopy = nil`을 대입해 잘못된 원 입력을 정상화한다.
- recover/replacement가 빈 `ruleID`를 허용해 선택된 rule 식별 계약을 보장하지 못한다.

수정:

- defensive copy와 validation을 분리하고 action별 forbidden-field 검사는 전달된 원 intent/replacement 존재 여부를 기준으로 한다.
- recover는 filter/rule/intent를, replacement는 filter/rule/replacement를 요구한다. 다른 action의 기존 필수/금지 필드는 유지한다.
- constructor와 `Validate`가 같은 invalid state를 거절하도록 한다.

변경 전/후:

```go
// before: invalid intent is erased when a replacement exists
if replacement != nil {
    replacementCopy = replacement.Clone()
    intentCopy = nil
}

// after: copy without erasing caller input; validation rejects forbidden combinations
```

수정 파일:

- `packages/go/streamgate/decision_arbiter.go`
- `packages/go/streamgate/decision_arbiter_test.go`

### [TEST-2] replacement와 결정성 회귀 증거

- replacement action에 recovery intent가 함께 들어오면 constructor가 실패하는 테스트를 추가한다.
- recover/replacement의 빈 `ruleID`와 action별 forbidden field를 table-driven case로 고정한다.
- blocking recover/replacement 후보가 함께 있을 때 priority desc/filter id asc로 단일 승자를 고르는지 검증한다.
- 같은 outcome 집합의 입력 순서를 뒤집거나 순열화해도 action, filter ID, rule ID, payload가 같음을 비교한다.
- terminal failure 우선순위와 observe-only 제외의 기존 의미를 유지한다.

수정 파일:

- `packages/go/streamgate/decision_arbiter_test.go`

### [TEST-3] priority snapshot과 result 전파 증거

- `EpochFilter`와 ready/non-ready `EpochFilterOutcome`이 정상 priority 값을 보존하는지 accessor로 assertion한다.
- `ResolvedFilter.BindEpoch`가 registry-effective priority를 ready/non-ready 경로 모두에 전달하는지 확인한다.
- fixture arbiter가 고유한 valid result를 반환하게 하고 `GateCoordinator.Evaluate`와 `Submit` 반환값이 그 result와 동일한지 검증한다.
- 기존 complete barrier, at-most-once, cancel no-call assertion은 유지한다.

수정 파일:

- `packages/go/streamgate/evaluation_contract_test.go`
- `packages/go/streamgate/filter_registry_test.go`
- `packages/go/streamgate/parallel_evaluation_test.go`

### [VERIFY-4] 최종 검증

- 포맷 후 모든 테스트는 cache bypass(`-count=1`)로 실행한다.
- 집중 테스트와 전체/race 검증이 모두 통과한 뒤에만 review-ready로 보고한다.
- 생성물 drift, transport 회귀, whitespace 오류가 없어야 한다.

## 수정 파일 요약

| 파일 | 변경 |
|---|---|
| `decision_arbiter.go` | action별 forbidden/required field 검증 일관화 |
| `decision_arbiter_test.go` | constructor, replacement conflict, 입력 순서 결정성 회귀 |
| `evaluation_contract_test.go` | ready/non-ready priority snapshot 증거 |
| `filter_registry_test.go` | bind 시 effective priority 보존 증거 |
| `parallel_evaluation_test.go` | `Evaluate`/`Submit` result 전파 증거 |
| `CODE_REVIEW-cloud-G05.md` | 실제 구현·검증 증거 |

## 최종 검증

```bash
command -v go
readlink -f "$(command -v go)"
go version
go env GOROOT
gofmt -w packages/go/streamgate/decision_arbiter.go packages/go/streamgate/decision_arbiter_test.go packages/go/streamgate/evaluation_contract_test.go packages/go/streamgate/filter_registry_test.go packages/go/streamgate/parallel_evaluation_test.go
go test -count=1 ./packages/go/streamgate -run 'Test(ArbitrationResult|DecisionArbiter|EpochFilter|EpochFilterOutcome|ResolvedFilter|GateCoordinator)'
go test -count=1 ./packages/go/streamgate
go test -race -count=1 ./packages/go/streamgate
make proto
go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport
rg --sort path -n 'ArbitrationActionReplacement|NewArbitrationResult|Priority\(\)|Arbitrate\(' packages/go/streamgate
git diff --check
```

예상 결과: 모든 명령이 exit 0이고 replacement forbidden-field 재현이 통과하며, outcome 순열과 ready/non-ready 경로가 동일한 priority/result 계약을 증명한다. race report, 생성물 drift, whitespace 오류가 없어야 한다.

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