<!-- task=m-stream-evidence-gate-core/10+09_policy_hook plan=1 tag=API -->
# Stream Evidence Gate Core — Multi-requirement Policy Hook 기반 구현 계획

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

- 이 계획은 `09_decision_arbiter` PASS 뒤 실행한다. 허용된 active/archive pattern에서 index `09`의 `complete.log`가 정확히 하나인지 확인하고 아니면 구현하지 말고 review stub에 blocker를 기록한다.
- 구현 전 project/domain/test rules를 다시 확인하고, 체크리스트 순서대로 수행한다.
- `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 변경과 검증 출력으로 채운 뒤 active 파일을 유지하고 review-ready로 보고한다.
- 이 작업은 `policy-hook`의 생산 기반만 만든다. Roadmap Task 완료는 후속 `11+09,10_policy_hook_contract`가 S06/S07 closure를 통과할 때만 요청한다.

## 배경

현재 `EvidencePlan`은 채널마다 가장 강한 blocking requirement 하나만 남긴다. `EvidenceTail.Append`도 그 단일 mode만 실행한다. 따라서 rolling과 terminal schema가 함께 있으면 rolling을 조기 평가할 수 없고, fragment와 terminal trigger는 충돌로 거절된다. 이는 S07의 mixed request 요구와 맞지 않는다.

또한 `ResolvedFilter.BindEpoch`는 `subscribedEventPresent`, `triggerReady`, `blocksRelease`를 caller boolean으로 받아 registry snapshot과 tail trigger가 서로 어긋날 수 있다. 이 작업은 filter별 trigger schedule을 보존하면서 채널당 pending buffer와 commit path는 하나만 유지하고, epoch applicability를 Core가 typed 값으로 파생하도록 바꾼다.

## Archive Evidence Snapshot

- `agent-task/m-stream-evidence-gate-core/10+09_policy_hook/plan_local_G05_0.log`: verdict 없는 최초 계획. 기존 strongest-wins 구조를 유지한 채 registry-to-plan helper만 추가하려 해 S07 mixed schedule을 충족하지 못하고 caller boolean drift도 남겨 대체했다.
- `agent-task/m-stream-evidence-gate-core/10+09_policy_hook/code_review_cloud_G06_0.log`: 구현 증거와 판정이 없는 최초 review stub. 위 계획과 함께 이력으로만 보존한다.

## Analysis Result

### 읽은 파일

- 승인 SDD와 active Milestone 문서
- project platform-common domain rule, local test rule/profile
- `packages/go/streamgate/filter_contract.go`, `filter_contract_test.go`
- `packages/go/streamgate/evidence_tail.go`, `evidence_tail_test.go`
- `packages/go/streamgate/filter_registry.go`, `filter_registry_test.go`
- `packages/go/streamgate/evaluation_contract.go`, `evaluation_contract_test.go`
- `packages/go/streamgate/parallel_evaluation.go`, `parallel_evaluation_test.go`
- `packages/go/streamgate/consumer_contract_test.go`, `event.go`, `go.mod`

### 구조적 결함과 요구사항

- `NewEvidencePlan`은 mode strength로 채널당 requirement 하나를 고르고 terminal/fragment trigger가 다르면 conflict로 거절한다.
- `EvidenceTail.Append`은 `BlockingRequirementFor(channel)`의 단일 mode로 threshold/terminal/fragment epoch를 만든다.
- 기존 `TestEvidencePlanComposesOnlyBlockingRequirements`와 `TestEvidencePlanComposesMixedRequirementsDeterministically`는 strongest-wins와 mixed-trigger rejection을 기대하므로 S07에 맞게 바꿔야 한다.
- S07은 같은 request에서 rolling 조기 violation, terminal 전 schema blocking deferred, fragment completion gate, 평상시 provider-error not-applicable을 동시에 요구한다.
- 중복 buffer/commit 없이 이를 만족하려면 storage envelope와 filter별 evaluation schedule을 분리해야 한다.
- `FilterHoldBinding`의 caller-provided `BlocksRelease`와 `BindEpoch`의 세 boolean은 policy source of truth를 분산한다.

### 설계 경계

- `EvidencePlan`은 filter별 binding/schedule을 stable filter id 순서로 보존하고 channel storage envelope를 별도로 계산한다.
- `EvidenceTail`은 채널당 기존 pending/look-behind/release cursor/token/confirm path 하나만 유지한다. filter별 tail이나 commit path를 만들지 않는다.
- `EvidenceEpoch` 또는 동등 immutable epoch 결과가 filter id별 typed applicability(`subscribed event present`, `trigger ready`)를 보존한다.
- `blocksRelease`는 `effective enforcement == blocking && requirement mode != none`에서 Core가 파생한다. caller가 임의 boolean을 전달하는 API를 제거한다.
- 실제 detector decision/intent와 consumer family parity는 후속 11의 범위다.

### 분할·의존성

- 자연 경계에 따라 생산 multi-schedule/tail/API와 S06/S07 consumer closure를 분리했다.
- 이 디렉터리는 index `09`만 선행 의존하며 생산 기반과 unit regression을 소유한다.
- `11+09,10_policy_hook_contract`는 index `09`, `10`의 PASS 뒤 public integration/consumer fixtures와 `policy-hook` Roadmap Completion을 소유한다.
- 이 계획에는 `Roadmap Targets`를 두지 않아 부분 구현이 `policy-hook`을 조기 완료시키지 않는다.

### 범위 밖

- detector production 알고리즘, provider SDK/transport/config/wire 변경.
- recovery mutation/retry/rebuild/dispatch와 budget/eligibility 집행.
- 별도 buffer, 별도 release token, second commit path.
- S06 OpenAI/agent semantic matrix와 Roadmap Task 체크.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`.
- finalizer/mode: `finalize-task-policy.sh`, `pair`.
- Build: local-fit=true, scope=2, state=2, blast=1, evidence=1, verification=2 → `local G08`.
- Review: official-review, scope=2, state=2, blast=1, evidence=1, verification=2 → `cloud G08`.
- Review adapter/model/effort: `codex`, `gpt-5.6-sol`, `xhigh`.
- Loop-risk는 mode × event × enforcement × terminal/hard-bound × token lifecycle이다. 기존 4천 줄 이상의 tail regression, 새 matrix, race 검증이 필요하며 capability gap/unknown은 없다.

## 구현 체크리스트

- [ ] [API-1] resolved filter별 schedule을 보존하는 `EvidencePlan`과 deterministic channel storage envelope를 구현한다.
- [ ] [API-2] `EvidenceTail`이 filter별 epoch applicability를 만들고 caller boolean 없이 `EpochFilter`를 bind하도록 연결한다.
- [ ] [TEST-3] rolling/terminal/fragment/none 혼합 schedule, bounds, hard-bound, single-buffer/token/commit 회귀를 자동화한다.
- [ ] [VERIFY-4] index 09 선행 증거와 local 집중/전체/race 검증을 기록하고 후속 11이 소비할 생산 기반을 고정한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 의존 관계 및 구현 순서

1. active `09_*/complete.log`, `09+*/complete.log`를 확인하고 없을 때만 같은 task group의 archive pattern에서 index 09 `complete.log`를 확인한다.
2. 정확히 하나가 아니면 중단한다. 정확히 하나면 API-1 → API-2 → TEST-3 → VERIFY-4 순으로 실행한다.
3. directory 이름에 없는 추가 dependency를 만들지 않는다.

### [API-1] multi-requirement plan과 channel storage envelope

- `NewEvidencePlanFromResolvedFilters([]ResolvedFilter)` 또는 동등 public seam을 추가해 stable filter id 순서의 immutable bindings를 만든다. duplicate/invalid/unmatched snapshot은 명시적으로 거절한다.
- `EvidencePlan`은 채널마다 모든 filter schedule을 유지한다. 기존 `BlockingRequirementFor`가 남는다면 release trigger의 대표값이 아니라 storage envelope accessor로 의미를 좁히고 이름/문서를 정합화한다.
- channel event kinds는 모든 active filter subscription의 deterministic union이다. observe-only와 none-mode도 subscription에는 남지만 blocking set에는 들어가지 않는다.
- shared `max_buffer_runes`는 blocking requirement들의 최소 양의 hard bound다. rolling look-behind 보존량은 rolling requirement들의 최대 `evidence_runes`다.
- 최대 rolling evidence가 shared hard bound보다 크면 어느 정책도 위반하지 않는 single buffer를 만들 수 없으므로 plan construction error로 거절한다. 값을 조용히 clamp하거나 더 큰 bound로 완화하지 않는다.
- rolling threshold, terminal trigger, fragment trigger는 filter별 원값을 보존한다. 서로 다른 mode/trigger가 있다는 이유만으로 거절하거나 strongest 하나로 소실하지 않는다.
- `blocksRelease`는 resolved effective enforcement와 requirement mode에서만 파생한다. `FilterHoldBinding`의 public caller boolean은 제거하거나 검증된 내부 생성 경로로 제한한다.

수정 파일:

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

### [API-2] Core-derived epoch applicability와 bind

- `EvidenceEpoch` 또는 인접 immutable type에 stable filter id별 applicability를 둔다. 값은 subscribed-event-present와 trigger-ready를 구분하고 unknown/duplicate id를 거절한다.
- `ResolvedFilter.BindEpoch`는 raw booleans 대신 해당 typed applicability/epoch를 받고, static `blocksRelease`와 09의 effective priority를 registry snapshot에서 파생한다.
- 가능하면 `EvidencePlan.BindEpochFilters(epoch, resolvedFilters)` 같은 단일 seam으로 exact plan/filter id set을 검증하고 stable order의 `[]EpochFilter`를 반환한다.
- event/trigger 규칙은 다음 표를 고정한다.

| epoch 원인 | rolling | terminal gate | fragment gate | none/provider-error |
|---|---|---|---|---|
| content delta | subscribed이고 개별 threshold 도달 시 ready, 미달이면 deferred | subscribed content가 있으면 deferred | 해당 fragment event면 completion 전 deferred | provider-error가 아니면 not-applicable |
| fragment complete | 개별 threshold 규칙 | terminal 전 deferred | 해당 fragment id만 ready | not-applicable |
| normal terminal | final batch에 applicable evidence가 있으면 ready | configured terminal trigger면 ready | 관련 fragment는 hard-bound final 평가로 ready, 없으면 not-applicable | provider-error-only는 not-applicable |
| provider error | content-only는 not-applicable | provider-error 미구독이면 not-applicable | provider-error 미구독이면 not-applicable | provider-error subscribed filter만 ready |
| hard buffer bound | applicable blocking filter를 final evaluation-ready로 만들어 deferred 무한 연장을 금지 | 동일 | 동일 | event subscription 규칙 유지 |

- rolling이 조기 ready여도 blocking terminal gate가 deferred면 09 arbiter 결과는 hold다. 이는 rolling 조기 violation을 가능하게 하면서 schema release는 terminal까지 막는다.
- observe-only deferred는 release를 막지 않는다. blocking none mode는 생성할 수 없도록 validation한다.
- 하나의 append가 여러 filter를 ready로 만들더라도 `EvidenceBatch`, release-capable epoch/token, prepare/confirm/commit cursor는 각각 하나다.
- 기존 invalidation, partial confirm, fragment cleanup, continuation/reset, Unicode rune suffix 동작을 유지한다.

### [TEST-3] mixed schedule와 lifecycle regression

- 기존 strongest-wins/mixed-trigger-rejection 기대를 S07 multi-schedule 기대값으로 교체한다.
- rolling+terminal, rolling+fragment, terminal+fragment, 네 mode 전체 조합에서 filter schedule 수, union event kinds, per-filter readiness/deferred/not-applicable을 검증한다.
- blocking/observe-only/none 조합과 caller가 `blocksRelease`를 위조할 수 없는 API를 검증한다.
- hard bound는 최소 양의 bound, look-behind는 최대 rolling evidence이고 incompatible aggregate는 deterministic error인지 확인한다.
- early rolling epoch는 terminal/schema filter를 deferred로 포함해 release token을 사용하지 않고, terminal epoch는 같은 pending buffer에서 단 한 번 prepare/confirm되는지 확인한다.
- fragment interleave/partial confirm/token invalidation/reset/continuation/Korean rune suffix 기존 regression을 fresh 전체 test로 유지한다.

### [VERIFY-4] 생산 기반 검증

- 선행 log 경로, 실제 API 결정, 전체 출력과 exit status를 review stub에 기록한다.
- PASS여도 `policy-hook` Roadmap Completion을 요청하지 않는다. 후속 11이 index 10 complete evidence와 public contract를 소비한다.

## 수정 파일 요약

| 파일 | 변경 |
|---|---|
| `evidence_tail.go` | filter별 schedule, channel envelope, epoch applicability, single-buffer lifecycle |
| `filter_registry.go` | resolved-to-plan seam, Core-derived blocks/bind |
| `evidence_tail_test.go` | mixed trigger/bounds/token/commit regression |
| `filter_registry_test.go` | registry composition, enforcement/subscription/id validation |
| `CODE_REVIEW-cloud-G08.md` | 선행·구현·검증 증거 |

## 최종 검증

```bash
bash -lc 'shopt -s nullglob; logs=(agent-task/m-stream-evidence-gate-core/09_*/complete.log agent-task/m-stream-evidence-gate-core/09+*/complete.log); if (( ${#logs[@]} == 0 )); then logs=(agent-task/archive/*/*/m-stream-evidence-gate-core/09_*/complete.log agent-task/archive/*/*/m-stream-evidence-gate-core/09+*/complete.log); fi; printf "%s\n" "${logs[@]}" | sort; test "${#logs[@]}" -eq 1'
gofmt -w packages/go/streamgate/evidence_tail.go packages/go/streamgate/evidence_tail_test.go packages/go/streamgate/filter_registry.go packages/go/streamgate/filter_registry_test.go
go test -count=1 ./packages/go/streamgate -run 'Test(EvidencePlan|EvidenceTail|FilterRegistry|ResolvedFilter)'
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
git diff --check
```

예상 결과: index 09 predecessor가 정확히 하나이고 모든 명령이 exit 0이며 mixed schedule이 single buffer/token/commit path를 공유한다.

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