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

# Stream Gate Filter Registry SDD 계약 보완 계획

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

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

## 배경

첫 구현은 focused/race/vet을 통과했지만 승인된 SDD의 hold contract, filter-scoped immutable policy, provider별 active-set 의미를 구현하지 않았다. 기존 테스트도 target-specific applicability와 coordinator 경로를 실행하지 않아 이 결함을 탐지하지 못했다. 이 follow-up은 SDD S11을 판정할 수 있는 최소 public API와 회귀 증거를 같은 두 파일 안에서 복구한다.

## Archive Evidence Snapshot

- 직전 loop: `agent-task/m-stream-evidence-gate-core/08+02,05,06_filter_registry/plan_local_G05_0.log`, `agent-task/m-stream-evidence-gate-core/08+02,05,06_filter_registry/code_review_local_G06_0.log`
- 판정: `FAIL`; Required 4, Suggested 0, Nit 0.
- Required 요약: SDD `FilterHoldRequirement` 불일치, filter identity 없는 mutable policy snapshot, target 이전 `Applies` 축소와 mutable ID/disabled capability, 의미 없는 context/provider/coordinator 테스트와 미작성 구현 evidence.
- 영향 파일: `packages/go/streamgate/filter_registry.go`, `packages/go/streamgate/filter_registry_test.go`
- 실제 검증: streamgate focused/race/vet, gofmt, diff hygiene는 PASS. Edge/Node transport는 host `/config/opt/go/src/crypto/internal/fips140cache` 누락으로 setup failure.
- 선행 완료: `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`, `agent-task/archive/2026/07/m-stream-evidence-gate-core/06+01_evidence_tail/complete.log`

## 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/filter_registry.go`
- `packages/go/streamgate/filter_registry_test.go`
- `packages/go/streamgate/evidence_tail.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/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/08+02,05,06_filter_registry/plan_local_G05_0.log`
- `agent-task/m-stream-evidence-gate-core/08+02,05,06_filter_registry/code_review_local_G06_0.log`
- 위 `Archive Evidence Snapshot`의 선행 `complete.log` 3개

### SDD 기준

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/stream-evidence-gate-core/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 `없음`.
- 대상 Acceptance Scenario: S11 / Milestone Task `filter-registry`.
- Evidence Map S11은 config-generation isolation, filter별 policy precedence, required capability candidate admission, provider-switch active-set 재계산, duplicate/disabled identity를 요구한다.
- 따라서 REVIEW_API-1은 SDD `Filter`/hold/identity 계약, REVIEW_API-2는 generation·candidate·policy isolation, REVIEW_API-3은 attempt/epoch coordinator 전달과 S11 회귀 evidence를 각각 닫는다.

### 테스트 환경 규칙

- `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`, `GOROOT=/config/opt/go`다. 별도 `GOROOT`/`GOTOOLCHAIN` override를 쓰지 않는다.
- fresh focused/package race/vet, `gofmt -d`, import boundary, diff/proto hygiene를 필수 evidence로 사용한다. Go test cache는 허용하지 않고 `-count=1`을 사용한다.
- profile의 transport command는 현재 host 표준 라이브러리 `crypto/internal/fips140cache` 누락으로 setup failure다. 우회하지 않고 재실행하며, 지속 시 원문과 host Go 설치 복구 조건을 기록한다.
- 이번 write-set은 proto/config/wire를 바꾸지 않는다. `make proto`는 생성 정합성 확인 뒤 `proto/gen/iop` 추가 diff가 없어야 한다.
- 매칭 active agent-spec과 agent-contract 문서는 없다. 외부 API, wire, config schema는 변경하지 않는다.

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

- `Filter`가 선행 `FilterHoldRequirement`를 반환하고 invalid/defensive subscription snapshot을 보존하는 검증이 없다.
- 두 filter가 같은 selector를 독립적으로 override하는 filter-scoped policy와 caller policy-slice mutation isolation 검증이 없다.
- registry/request generation mismatch와 snapshot 생성 뒤 filter ID mutation의 begin/resolve/bind 거부 검증이 없다.
- provider-specific `Applies`, provider-specific disable, disabled required capability exclusion을 한 candidate 표에서 검증하지 않는다.
- `TestResolvedFilterAdapterPassesFullContext`는 `GateCoordinator`를 거치지 않고 endpoint/family/correlation/batch identity도 확인하지 않는다.
- `baseBuilderCopy`는 immutable setter 반환값을 버려 test fixture 자체가 full context를 복사하지 않는다.

### 심볼 참조

- 제거 대상 `FilterEvaluationRequirement` 참조는 `packages/go/streamgate/filter_registry.go`와 `packages/go/streamgate/filter_registry_test.go`에만 있다.
- 대체 계약 `FilterHoldRequirement`는 `packages/go/streamgate/evidence_tail.go`와 해당 test에 이미 구현돼 있다.
- registry public symbol의 production caller는 아직 없다. 새 third-party dependency는 없다.

### 분할 판단

- split decision policy를 routed filename 확정 전에 적용했다.
- 기존 subtask `08+02,05,06_filter_registry`를 그대로 유지한다. REVIEW_API-1~3은 하나의 `Filter` public signature와 같은 registry snapshot/resolution helper를 동시에 바꾸며, 어느 한 slice만 먼저 PASS하면 S11에 소비 불가능한 중간 API가 남는다.
- production/test는 같은 두 파일이고 ownership/test profile도 하나라 별도 sibling은 인위적이다.
- predecessor `02`, `05`, `06`은 위 archived `complete.log`로 모두 충족됐다.

### 범위 결정 근거

- 수정은 `packages/go/streamgate/filter_registry.go`, `packages/go/streamgate/filter_registry_test.go`로 제한한다.
- 선행 계약 원본인 `evidence_tail.go`, `evaluation_contract.go`, `parallel_evaluation.go`는 소비만 하며 수정하지 않는다.
- `packages/go/config`, proto, Edge/Node admission/dispatch caller, semantic filter, Arbiter와 recovery는 후속 task 범위라 제외한다.
- task artifact 외 filesystem mutation은 하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - scope: 두 registry 파일과 S11 불변조건으로 폐쇄됨.
  - context: SDD, hold/evaluation seam, 현재 source/test를 한 세션에서 유지 가능.
  - verification: deterministic focused/race/vet와 S11 regression으로 판정 가능.
  - evidence: 현재 코드, 통과한 잘못된 테스트, transport setup 오류를 직접 재확인함.
  - ownership: task write-set과 병행 sibling 경계가 명확함.
  - decision: Milestone 결정 필요 없음.
  - scores: scope coupling 2, state/concurrency 1, blast/irreversibility 1, evidence diagnosis 1, verification complexity 1
  - result: `local`, `G06`, `PLAN-local-G06.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - scope: S11의 hold/policy/identity/attempt/coordinator 표로 판정 범위가 고정됨.
  - context: 관련 계약과 두 파일을 함께 검토 가능.
  - verification: 새 negative regression과 package race로 직접 판정 가능.
  - evidence: 기존 green test와 실제 계약 불일치를 구별해야 하지만 source/SDD 증거가 재현 가능함.
  - ownership: 이 task와 외부 host repair 책임이 분리됨.
  - decision: 사용자 제품 결정 없이 검토 가능.
  - scores: scope coupling 2, state/concurrency 1, blast/irreversibility 1, evidence diagnosis 2, verification complexity 1
  - result: `local`, `G07`, `CODE_REVIEW-local-G07.md`

## 구현 체크리스트

- [ ] `REVIEW_API-1` public `Filter`/hold/identity 계약을 SDD와 선행 evidence-tail 계약에 맞추고 invalid/mutated snapshot 회귀를 구현한다.
- [ ] `REVIEW_API-2` filter-scoped generation-bound policy와 candidate/attempt-local enabled resolution 및 required capability preflight를 구현·테스트한다.
- [ ] `REVIEW_API-3` full `FilterContext`와 동일 immutable batch가 epoch adapter와 `GateCoordinator`를 통과하는 통합 회귀를 구현한다.
- [ ] host Go 기준 focused/fresh/race/vet, proto/transport profile과 deterministic hygiene 명령을 실행하고 blocker를 포함한 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_API-1] SDD Filter/hold와 stable identity 복구

- 문제:
  - `filter_registry.go:11-35`의 별도 `FilterEvaluationRequirement`는 channel/mode/subscribed kinds/rune·buffer bound를 잃고 `Validate()`가 항상 성공한다.
  - `filter_registry.go:302-317`의 public `Filter` signature가 계획과 선행 `FilterHoldRequirement` 계약과 다르다.
  - `filter_registry.go:516`, `638`, `692`는 captured registration id 대신 mutable `filter.ID()`를 다시 읽는다.
- 해결 방법:
  - `FilterEvaluationRequirement`를 제거하고 `Filter.HoldRequirement(FilterContext) FilterHoldRequirement`를 사용한다.
  - registration/request/attempt resolution에서 `FilterHoldRequirement.Validate()`를 호출하고 requirement value를 snapshot한다.
  - public identity accessor와 adapter ID는 `registrationID.String()`만 반환하며, begin/resolve/bind 경계에서 underlying `Filter.ID()`가 captured id와 다르면 `ErrRegistrationFilterIDMismatch`로 거부한다.
- Before (`filter_registry.go:302-317`):

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

- After:

```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`: wrong requirement 제거, validated snapshot과 captured identity 경계
  - [ ] `packages/go/streamgate/filter_registry_test.go`: real hold requirement, invalid requirement와 post-snapshot identity mutation 회귀
- 테스트 작성:
  - `TestFilterRegistryUsesValidatedHoldRequirement`: rolling/none subscription snapshot과 invalid zero requirement 거부.
  - `TestFilterRegistryRejectsIdentityMutationAfterSnapshot`: BeginRequest, ResolveAttempt, BindEpoch 중 최초 경계에서 stable mismatch 거부.
- 중간 검증:
  - `go test -count=1 -v ./packages/go/streamgate -run '^TestFilterRegistry(UsesValidatedHoldRequirement|RejectsIdentityMutationAfterSnapshot)$'`
  - 기대 결과: 두 test가 실제 선택돼 PASS.

### [REVIEW_API-2] Filter-scoped policy와 target-aware active set

- 문제:
  - `filter_registry.go:343-353` policy에는 filter id가 없어 selector override가 모든 registration에 전역 적용된다.
  - `filter_registry.go:953-957`는 caller `policies` slice를 그대로 저장하고 `BeginRequest`는 registry/request generation mismatch를 허용한다.
  - `filter_registry.go:827-845`는 actual target이 빈 context에서 `Applies`를 먼저 실행하고 disabled filter도 resolved/required capability에 넣는다. `ResolveAttempt`는 이 축소 집합만 순회한다.
- 해결 방법:
  - `FilterPolicyLayer`에 validated `filterID StableToken`을 추가하고 constructor가 filter id, selector type/key, policy value를 함께 고정한다.
  - duplicate key는 `(filter_id, selector_type, canonical selector key)` 기준으로 거부하고 unknown filter id, default의 non-empty key, non-default empty key를 거부한다. snapshot은 policy slice/value를 복사한다.
  - `BeginRequest`는 generation 일치를 검증하고 registration/reference/policy만 request snapshot에 고정한다.
  - candidate helper가 target context에서 `Applies`와 effective enabled policy를 계산한다. `EligibleCandidates`는 enabled+applicable filter의 capability만 enforcement와 무관하게 요구하고, `ResolveAttempt`는 같은 helper로 enabled filter만 stable-id 순 반환한다.
- Before (`filter_registry.go:343-353`):

```go
type FilterPolicyLayer struct {
	selectorType PolicySelectorType
	selectorKey  string
	enabled      bool
	enforcement  FilterEnforcement
	timeout      time.Duration
	priority     int
}
```

- After:

```go
type FilterPolicyLayer struct {
	filterID     StableToken
	selectorType PolicySelectorType
	selectorKey  string
	enabled      bool
	enforcement  FilterEnforcement
	timeout      time.Duration
	priority     int
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry.go`: filter-scoped policy validation/copy, generation guard, shared candidate resolver
  - [ ] `packages/go/streamgate/filter_registry_test.go`: two-filter precedence, input mutation, generation mismatch, provider Applies/disable/capability 표
- 테스트 작성:
  - `TestFilterRegistryPolicyIsFilterScopedAndSnapshotImmutable`
  - `TestFilterRegistryRejectsRequestGenerationMismatch`
  - `TestFilterRegistryReresolvesProviderSpecificApplies`
  - `TestFilterRegistryDisabledFilterDoesNotRequireCapability`
- 중간 검증:
  - `go test -race -count=1 -v ./packages/go/streamgate -run '^TestFilterRegistry(PolicyIsFilterScopedAndSnapshotImmutable|RejectsRequestGenerationMismatch|ReresolvesProviderSpecificApplies|DisabledFilterDoesNotRequireCapability)$'`
  - 기대 결과: 두 filter/provider/candidate 표가 PASS하고 race 없음.

### [REVIEW_API-3] Epoch adapter와 GateCoordinator 통합 evidence

- 문제:
  - `filter_registry_test.go:197-211`은 immutable builder 반환값을 버린다.
  - `filter_registry_test.go:575-624`는 provider-specific `Applies`를 설정하지 않아 re-resolution을 검증하지 않는다.
  - `filter_registry_test.go:661-668`은 `GateCoordinator` 대신 evaluator를 직접 호출하고 endpoint/family/correlation과 동일 batch 전달을 확인하지 않는다.
- 해결 방법:
  - fixture builder 반환값을 매번 재할당하고 모든 context field와 slice-bearing input의 mutation isolation을 assert한다.
  - real `FilterHoldRequirement`로 epoch readiness를 만들고 `GateCoordinator.Evaluate` 또는 `Submit`을 통해 adapter를 실행한다.
  - filter decision id를 captured stable id와 일치시키고 attempt/epoch/environment/endpoint/family/model group/model/provider/path/stream/commit/side-effect/terminal/correlation 및 batch captured-at/event snapshot을 확인한다.
- Before (`filter_registry_test.go:661-668`):

```go
ef, err := resolved[0].BindEpoch(7, true, true, true)
_, err = ef.Evaluator().Evaluate(context.Background(), batch)
```

- After:

```go
ef, err := resolved[0].BindEpoch(7, true, true, true)
_, err = coordinator.Evaluate(context.Background(), batch, []EpochFilter{ef})
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry_test.go`: valid builder, provider-specific fixture, coordinator/full-context/batch assertion
  - [ ] `packages/go/streamgate/filter_registry.go`: adapter가 captured identity/context/requirement snapshot만 소비하는지 필요한 최소 보정
- 테스트 작성:
  - `TestFilterContextIsImmutable`를 exhaustive scalar/slice mutation fixture로 보강한다.
  - `TestResolvedFilterAdapterPassesFullContextThroughCoordinator`를 추가하고 direct evaluator 호출을 제거한다.
- 중간 검증:
  - `go test -race -count=1 -v ./packages/go/streamgate -run '^Test(FilterContextIsImmutable|ResolvedFilterAdapterPassesFullContextThroughCoordinator)$'`
  - 기대 결과: coordinator arbitration까지 PASS하고 race 없음.

## 의존 관계 및 구현 순서

1. `REVIEW_API-1`에서 public hold/identity 계약을 고정한다.
2. 같은 계약을 사용해 `REVIEW_API-2` candidate/policy resolution을 단일 helper로 정리한다.
3. `REVIEW_API-3`에서 실제 coordinator 경로와 전체 S11 회귀를 닫는다.
4. predecessor `02`, `05`, `06`은 `Archive Evidence Snapshot`의 `complete.log`로 충족됐다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/streamgate/filter_registry.go` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |
| `packages/go/streamgate/filter_registry_test.go` | REVIEW_API-1, REVIEW_API-2, 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/filter_registry.go packages/go/streamgate/filter_registry_test.go`
   - 기대 결과: stdout 없음.
3. `go test -count=1 -v ./packages/go/streamgate -run '^TestFilterRegistry(UsesValidatedHoldRequirement|RejectsIdentityMutationAfterSnapshot|PolicyIsFilterScopedAndSnapshotImmutable|RejectsRequestGenerationMismatch|ReresolvesProviderSpecificApplies|DisabledFilterDoesNotRequireCapability)$'`
   - 기대 결과: 모든 named regression이 실제 선택돼 PASS.
4. `go test -race -count=1 -v ./packages/go/streamgate -run '^Test(FilterContextIsImmutable|ResolvedFilterAdapterPassesFullContextThroughCoordinator)$'`
   - 기대 결과: full context/coordinator test PASS, race 없음.
5. `go test -count=1 ./packages/go/streamgate -run '^Test(FilterContext|FilterRegistry|FilterBase|ResolvedFilter)'`
   - 기대 결과: focused suite PASS.
6. `go test -race -count=1 ./packages/go/streamgate`
   - 기대 결과: package PASS, race 없음.
7. `go vet ./packages/go/streamgate`
   - 기대 결과: stdout 없음.
8. `go list -f '{{range .Imports}}{{println .}}{{end}}' ./packages/go/streamgate | LC_ALL=C sort`
   - 기대 결과: 표준 라이브러리만 출력되고 `apps/`, `packages/go/config`, proto import 없음.
9. `make proto`
   - 기대 결과: 성공하고 생성물 추가 diff 없음.
10. `go test -count=1 ./apps/edge/internal/transport ./apps/node/internal/transport`
    - 기대 결과: PASS. host GOROOT 오류가 지속되면 우회하지 않고 원문과 host Go 설치 복구 조건을 기록.
11. `git diff --check -- packages/go/streamgate proto/gen/iop`
    - 기대 결과: stdout 없음.
12. `git status --short -- proto/gen/iop`
    - 기대 결과: stdout 없음.

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