<!-- task=m-stream-evidence-gate-core/08+02,05,06_filter_registry plan=0 tag=API -->

# Stream Gate Filter Contract와 Registry Snapshot 구현 계획

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

- 선행 `02`, `05`, `06`의 `complete.log`를 확인하고 구현 체크리스트와 검증 명령을 수행한다.
- `CODE_REVIEW-*-G??.md` 구현 소유 섹션에 실제 변경 내용과 stdout/stderr를 기록한 뒤 active 파일을 유지하고 review-ready만 보고한다.
- 막히면 blocker, 시도 명령/출력, 재개 조건만 implementation evidence에 기록한다.
- 사용자 질문, user-input 도구, stop 파일, 다음 상태 분류, log archive와 `complete.log` 작성은 금지된다.

## 배경

재검토에서 public Filter와 parallel evaluator seam, policy와 generation의 책임 경계를 명확히 했다. 이 계획은 SDD Filter를 독립 계약으로 정의하고 `ResolvedFilter` adapter만 기존 parallel evaluator seam을 구현한다. Registry implementation, policy, generation은 하나의 immutable snapshot에서 함께 고정한다.

## Archive Evidence Snapshot

- 선행 완료: `agent-task/archive/2026/07/m-stream-evidence-gate-core/02+01_evaluation_contract/complete.log`, `agent-task/archive/2026/07/m-stream-evidence-gate-core/05+02_parallel_evaluation_coordinator/complete.log`
- 선행 대기: `06+01_evidence_tail`의 `FilterHoldRequirement` complete evidence.
- 구현 기준: SDD Filter signature, generation-bound policy, `FilterEvaluator` adapter, enforcement와 required capability 분리.
- 구현자는 위 선행 경로 외의 `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:
  - `filter-registry`: immutable generation/policy snapshot과 attempt/epoch-local active filter resolution
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/streamgate/event.go`
- `packages/go/streamgate/filter_contract.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/02+01_evaluation_contract/complete.log`
- `agent-task/archive/2026/07/m-stream-evidence-gate-core/05+02_parallel_evaluation_coordinator/complete.log`
- `.gitignore`

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`, 구현 잠금 `해제`, 사용자 리뷰 `없음`
- 대상: S11 / Milestone Task `filter-registry`
- SDD Filter는 stable `ID`, attempt-level `Applies`, `FilterHoldRequirement`/event subscription, `Evaluate(context.Context, FilterContext, EvidenceBatch)`를 제공한다.
- `FilterRegistrySnapshot`은 implementation, policy와 generation을 함께 고정하고 admission 전 `RequiredFilterCapabilities`, actual target 확정 뒤 provider/model별 active set, priority, hold/enforcement를 resolve한다.
- Evidence Map의 config-generation isolation, policy precedence, required capability no-candidate, provider switch, duplicate id를 `API-1`~`API-3` 표 기반 test로 닫는다.

### 테스트 환경 규칙

- `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 설치 복구를 재개 조건으로 기록한다.
- config schema와 production admission call site를 바꾸지 않으므로 full-cycle은 `vertical-slice` Task에 남긴다.

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

- SDD signature와 동일한 immutable `FilterContext`, `Filter`와 shared `FilterBase`가 없다.
- registry registration과 policy가 같은 generation snapshot에 묶이지 않는다.
- duplicate/mutated stable ID, invalid hold requirement와 context-aware applies를 거부/검증하는 테스트가 없다.
- environment→model group→model→provider precedence와 reload isolation이 없다.
- enforcement와 독립적인 required capability candidate preflight가 없다.
- provider switch re-resolution 및 resolved adapter가 full FilterContext/epoch를 기존 `GateCoordinator`에 전달하는 검증이 없다.

### 심볼 참조

- rename/remove 없음.
- 새 public symbol: `FilterContext`, `Filter`, `FilterBase`, `FilterRegistration`, `FilterPolicyLayer`, `FilterRegistrySnapshot`, `RequestFilterSnapshot`, `AttemptTarget`, `ResolvedFilter`.
- 기존 `FilterEvaluator`, `EpochFilter`, `FilterEnforcement`, `GateCoordinator`는 변경하지 않고 `ResolvedFilter`의 epoch adapter가 소비한다.
- 새 third-party dependency와 `packages/go/config` import는 없다.
- 매칭되는 active agent-spec/agent-contract 문서는 없다. 외부 API·wire·config schema는 변경하지 않는다.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 적용했다.
- archived `02+01_evaluation_contract`, `05+02_parallel_evaluation_coordinator`는 각 `complete.log`로 충족됐다.
- `06+01_evidence_tail`은 revised active plan 상태라 아직 미충족이다. 이 plan은 predecessor의 `FilterHoldRequirement`를 소비한다.
- Filter contract/adapter와 registry policy resolution은 API와 resolver 경계로 나눌 수 있으나, S11 PASS는 generation snapshot 안에서 둘이 함께 identity/policy immutability를 보장해야 한다. 두 신규 파일의 table test로 한 번에 판정 가능하고 별도 중간 public contract를 만들 이유가 없어 현재 subtask를 유지한다.

### 범위 결정 근거

- 새 `packages/go/streamgate/filter_registry.go`, `packages/go/streamgate/filter_registry_test.go`만 수정한다.
- `packages/go/config`, YAML, Edge/Node admission/dispatch call site는 `vertical-slice`와 consumer Milestone으로 남긴다.
- semantic detector, Arbiter, recovery coordinator, observation sink는 구현하지 않는다.
- registry는 host가 제공한 sanitized target/capability value만 해석하며 provider 선택, raw request, auth를 소유하지 않는다.
- requirement mixed-mode channel composition의 semantic policy는 `policy-hook`에 남기고 registry는 각 resolved filter의 validated requirement snapshot만 반환한다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - 근거: SDD Filter/adapter와 generation-bound registry를 두 신규 파일 및 S11 table fixture로 닫을 수 있다.
  - scores: scope coupling 2, state/concurrency 1, blast/irreversibility 1, evidence diagnosis 0, verification complexity 1
  - result: `local`, `G05`, `PLAN-local-G05.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - 근거: full context propagation, identity/policy immutability, precedence, capability/provider switch를 deterministic/race test로 판정할 수 있다.
  - scores: scope coupling 2, state/concurrency 1, blast/irreversibility 1, evidence diagnosis 1, verification complexity 1
  - result: `local`, `G06`, `CODE_REVIEW-local-G06.md`

## 구현 체크리스트

- [ ] `API-1` SDD signature의 immutable `FilterContext`, `Filter`, shared `FilterBase`와 registration identity validation을 구현·테스트한다.
- [ ] `API-2` filter implementation identity/reference와 policy/generation value를 함께 고정하는 `FilterRegistrySnapshot`과 deterministic precedence/request isolation을 구현·테스트한다.
- [ ] `API-3` enforcement와 독립적인 required capability preflight, provider re-resolution과 epoch `FilterEvaluator` adapter를 구현·테스트한다.
- [ ] 호스트 `go` 기준 focused/fresh/race/vet 및 공통 profile 명령을 실행하고 blocker를 포함한 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] SDD FilterContext와 Filter contract

- 문제:
  - `packages/go/streamgate/evaluation_contract.go:10-22`의 `FilterEvaluator`는 parallel execution용 최소 seam이라 attempt applicability, hold requirement와 full context가 없다.
  - 기존 `EpochFilter`는 실행 binding일 뿐 registry registration identity를 소유하지 않는다.
- 해결 방법:
  - `FilterContext`는 config generation, attempt/epoch, environment, endpoint/family, model group/actual model/provider/execution path, stream, commit/terminal/tool-side-effect와 stable correlation을 private field/defensive accessor로 제공한다. attempt resolve 단계에는 epoch가 명시적 unset이고 `BindEpoch`만 positive epoch를 넣은 새 copy를 만든다.
  - SDD와 동일하게 `Filter`는 `ID`, `Applies(FilterContext)`, `HoldRequirement(FilterContext)`, `Evaluate(context.Context, FilterContext, EvidenceBatch)`를 가진다.
  - `FilterBase`는 validated stable ID helper만 제공하고 enablement, enforcement, deadline, priority는 보유하지 않는다.
  - `FilterRegistration`은 filter, required capability ID, default enabled/enforcement/timeout/priority를 registry-owned immutable value로 snapshot한다.
  - duplicate ID, registration/filter ID mismatch, post-registration ID mutation, invalid requirement/context를 constructor와 resolve 경계에서 거부한다.
- Before (`evaluation_contract.go:14-22`):

```go
type FilterEvaluator interface {
	ID() string
	Evaluate(context.Context, EvidenceBatch) (FilterDecision, error)
}
```

- After (`filter_registry.go`):

```go
type Filter interface {
	ID() string
	Applies(FilterContext) bool
	HoldRequirement(FilterContext) FilterHoldRequirement
	Evaluate(context.Context, FilterContext, EvidenceBatch) (FilterDecision, error)
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry.go`: immutable context, Filter/Base, registration constructors와 identity/requirement validation
  - [ ] `packages/go/streamgate/filter_registry_test.go`: context defensive copy, valid helper, duplicate/mismatch/mutation, invalid registration/requirement
- 테스트 작성:
  - `TestFilterContextIsImmutable`: caller map/slice mutation과 accessor mutation이 snapshot에 영향을 주지 않음을 검증한다.
  - `TestFilterRegistryRejectsDuplicateAndMutableIdentity`: duplicate/mismatch/post-registration ID mutation을 거부한다.
  - `TestFilterBaseDoesNotOwnRuntimePolicy`: helper에 enablement/enforcement mutation surface가 없음을 package/API assertion으로 검증한다.
- 중간 검증:
  - `go test -count=1 ./packages/go/streamgate -run '^Test(FilterContextIsImmutable|FilterRegistryRejectsDuplicateAndMutableIdentity|FilterBaseDoesNotOwnRuntimePolicy)$'`
  - 기대 결과: PASS

### [API-2] Generation-bound policy snapshot과 precedence

- 문제:
  - policy를 request 시작 때 별도 인자로 받으면 같은 generation을 다른 policy set과 조합할 수 있어 reload isolation을 구조적으로 보장하지 못한다.
- 해결 방법:
  - `NewFilterRegistrySnapshot(generation, registrations, policies)`가 generation, registration metadata, selector와 policy를 한 번에 validate/deep-copy하고 filter interface reference와 stable ID를 해당 generation에 고정한다. 임의 interface 구현을 deep-copy한다고 주장하지 않는다.
  - `BeginRequest(RequestFilterContext)`는 policy 인자를 받지 않고 registry-owned snapshot만 참조하는 immutable `RequestFilterSnapshot`을 만든다.
  - precedence는 default < environment < model-group < model < provider이며 같은 selector/specificity의 duplicate rule을 거부한다. explicit disable은 낮은 enable을 덮는다.
  - `FilterPolicy`는 enabled, enforcement, timeout, priority만 소유하고 filter method가 이를 바꿀 수 없다.
  - 새 config generation은 새 `FilterRegistrySnapshot`으로만 생성하며 기존 request snapshot은 기존 filter reference/identity와 policy/generation을 유지한다. Filter 구현은 등록 후 ID와 선언 계약을 mutation하지 않는 interface contract를 따른다.
- Before (초기 계획의 폐기된 API):

```go
func (s RegistrySnapshot) BeginRequest(
	ctx RequestFilterContext,
	policies []FilterPolicyLayer,
) (RequestFilterSnapshot, error)
```

- After (`filter_registry.go`):

```go
func NewFilterRegistrySnapshot(
	generation string,
	registrations []FilterRegistration,
	policies []FilterPolicyLayer,
) (FilterRegistrySnapshot, error)

func (s FilterRegistrySnapshot) BeginRequest(
	ctx RequestFilterContext,
) (RequestFilterSnapshot, error)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry.go`: generation/policy ownership, deep-copy, selector validation, deterministic precedence, request snapshot
  - [ ] `packages/go/streamgate/filter_registry_test.go`: reload isolation, same-generation policy immutability, precedence table, explicit disable, duplicate selector
- 테스트 작성:
  - `TestFilterRegistryRequestSnapshotIsolatesReload`: old request의 generation/implementation/policy가 새 snapshot과 섞이지 않음을 검증한다.
  - `TestFilterRegistryPolicyPrecedence`: environment/model-group/model/provider on/off/enforcement/timeout/priority 표를 검증한다.
  - `TestFilterRegistryRejectsDuplicatePolicySelector`: 같은 specificity/selector ambiguity를 거부한다.
- 중간 검증:
  - `go test -race -count=1 ./packages/go/streamgate -run '^TestFilterRegistry(RequestSnapshotIsolatesReload|PolicyPrecedence|RejectsDuplicatePolicySelector)$'`
  - 기대 결과: PASS, race 없음

### [API-3] Capability preflight, attempt re-resolution과 epoch adapter

- 문제:
  - actual provider 변경 시 active set을 다시 계산하고 required capability가 없는 후보를 dispatch 전에 제외하는 API가 없다.
  - SDD Filter의 full context signature를 현재 `GateCoordinator`의 minimal `FilterEvaluator` seam에 연결하는 adapter가 없다.
- 해결 방법:
  - `RequiredFilterCapabilities`는 request input으로 snapshot하고 enforcement와 독립된 stable capability set으로 검증한다.
  - `EligibleCandidates`는 actual candidate capability set이 모든 required capability를 포함하는지 확인해 unsupported candidate를 dispatch 전 제외하고 no-candidate stable error를 반환한다.
  - `ResolveAttempt(AttemptTarget)`은 같은 request generation에서 actual model/provider/path로 `FilterContext`를 만들고 policy와 `Applies`를 다시 평가해 stable ID 순 `ResolvedFilter`를 반환한다.
  - `ResolvedFilter.BindEpoch(epochID, subscribed, triggerReady, blocksRelease)`는 positive epoch를 검증하고 full FilterContext의 새 epoch copy를 가진 private adapter를 만들어 기존 `NewEpochFilter`를 호출한다. adapter `Evaluate(ctx,batch)`만 내부 Filter의 3-argument Evaluate로 전달한다.
  - resolved enforcement/deadline/priority/requirement는 snapshot value이며 filter runtime mutation으로 변경되지 않는다.
- Before (`evaluation_contract.go:77-104`):

```go
func NewEpochFilter(
	evaluator FilterEvaluator,
	subscribedEventPresent, triggerReady, blocksRelease bool,
	enforcement FilterEnforcement,
	timeout time.Duration,
) (EpochFilter, error)
```

- After (`filter_registry.go`):

```go
func (s RequestFilterSnapshot) EligibleCandidates(candidates []AttemptTarget) ([]AttemptTarget, error)
func (s RequestFilterSnapshot) ResolveAttempt(target AttemptTarget) ([]ResolvedFilter, error)
func (r ResolvedFilter) BindEpoch(
	epochID uint64,
	subscribedEventPresent, triggerReady, blocksRelease bool,
) (EpochFilter, error)
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry.go`: required capability set, candidate preflight, attempt context/re-resolution, stable order, epoch adapter
  - [ ] `packages/go/streamgate/filter_registry_test.go`: required observe/blocking capability, no-candidate, provider switch, full context/epoch propagation, enforcement/deadline normalization
- 테스트 작성:
  - `TestFilterRegistryRequiredCapabilityPreflightIgnoresEnforcement`: observe-only와 blocking capability가 동일하게 candidate admission을 제한함을 검증한다.
  - `TestFilterRegistryReresolvesProviderPerAttempt`: 같은 generation에서 provider별 applies/policy/requirement set이 다시 계산됨을 검증한다.
  - `TestResolvedFilterAdapterPassesFullContext`: GateCoordinator 실행에서 attempt/epoch/target context와 같은 immutable batch가 Filter에 전달됨을 검증한다.
- 중간 검증:
  - `go test -race -count=1 ./packages/go/streamgate -run '^Test(FilterRegistry(RequiredCapabilityPreflightIgnoresEnforcement|ReresolvesProviderPerAttempt)|ResolvedFilterAdapterPassesFullContext)$'`
  - 기대 결과: PASS, race 없음

## 의존 관계 및 구현 순서

1. `02+01_evaluation_contract`는 `agent-task/archive/2026/07/m-stream-evidence-gate-core/02+01_evaluation_contract/complete.log`로 충족됐다.
2. `05+02_parallel_evaluation_coordinator`는 `agent-task/archive/2026/07/m-stream-evidence-gate-core/05+02_parallel_evaluation_coordinator/complete.log`로 충족됐다.
3. `06+01_evidence_tail`은 revised active plan 상태다. 구현 시작 전 active 또는 archive `complete.log`를 확인하며 없으면 진행하지 않는다.
4. `API-1` Filter/context, `API-2` registry snapshot, `API-3` resolution/adapter 순으로 구현한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/filter_registry.go` | API-1, API-2, API-3 |
| `packages/go/streamgate/filter_registry_test.go` | API-1, 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/filter_registry.go packages/go/streamgate/filter_registry_test.go`
   - 기대 결과: stdout 없음
3. `go test -count=1 ./packages/go/streamgate -run '^Test(FilterContext|FilterRegistry|FilterBase|ResolvedFilter)'`
   - 기대 결과: 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
