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

# Stream Gate Candidate-scoped Registry와 Evidence 신뢰 복구 계획

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

- 아래 구현 체크리스트와 검증을 모두 수행하고 `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 파일을 사용하거나 다음 상태를 분류하지 않는다.

## 배경

두 번째 리뷰에서도 green test와 달리 candidate별 active filter/capability 계산과 stable identity 경계가 충족되지 않았다. provider 전용 filter capability가 없는 candidate가 admission되고 disabled filter의 hold contract가 실행되는 반례가 재현됐다. 이 follow-up은 request-global active set을 제거하고 candidate/attempt 공통 resolver 및 실제 조건을 검증하는 회귀 evidence로 SDD S11을 닫는다.

## Archive Evidence Snapshot

- 직전 loop: `agent-task/m-stream-evidence-gate-core/08+02,05,06_filter_registry/plan_local_G06_1.log`, `agent-task/m-stream-evidence-gate-core/08+02,05,06_filter_registry/code_review_local_G07_1.log`
- 판정: `FAIL`; Required 3, Suggested 0, Nit 0.
- Required 요약: target 없는 request-global capability/hold 계산, Begin/Resolve stable identity fence 누락, provider-specific Applies·policy slice mutation·full context/batch/hold snapshot을 실제로 검증하지 않는 green tests.
- 영향 파일: `packages/go/streamgate/filter_registry.go`, `packages/go/streamgate/filter_registry_test.go`
- 실제 검증: focused/package race/vet/gofmt/import/proto/diff는 PASS. reviewer reproducer는 provider capability 없는 candidate admission과 disabled filter hold 실행 두 반례로 FAIL. 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/filter_contract.go`
- `packages/go/streamgate/evaluation_contract.go`
- `packages/go/streamgate/parallel_evaluation.go`
- `packages/go/streamgate/event.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_G06_1.log`
- `agent-task/m-stream-evidence-gate-core/08+02,05,06_filter_registry/code_review_local_G07_1.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, policy precedence, required capability admission, provider-switch re-resolution과 duplicate/disabled identity 회귀를 요구한다.
- 따라서 `REVIEW_API-1`은 candidate-local active set/capability, `REVIEW_API-2`는 generation 전 구간 stable identity, `REVIEW_API-3`은 S11과 coordinator seam을 판정할 수 있는 실제 assertion을 각각 담당한다.

### 테스트 환경 규칙

- `test_env`: local.
- `agent-test/local/rules.md`와 `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, `make proto`, proto/diff hygiene를 적용한다. Go test는 `-count=1`로 cache를 사용하지 않는다.
- Edge/Node transport profile은 host 표준 라이브러리 `crypto/internal/fips140cache` 누락으로 setup failure다. 우회하지 않고 재실행하며 지속 시 원문과 host Go 설치 복구 조건을 기록한다.
- 이번 write-set은 proto/config/wire와 production admission caller를 바꾸지 않으므로 full-cycle 실제 구동은 후속 `vertical-slice` 범위다.
- 매칭되는 active agent-spec과 agent-contract 문서는 없다.

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

- provider 전용 filter capability를 해당 provider candidate에서 계산하지 않아 unsupported candidate가 통과하지만 기존 named test가 이 조건을 만들지 않는다.
- disabled filter의 `Applies`/`HoldRequirement`가 호출되지 않아야 하나 invalid hold를 사용한 회귀가 없다.
- identity mutation test는 generation mismatch를 BeginRequest identity rejection으로 오인하고 ResolveAttempt 전 ID를 원복한다.
- policy immutability test는 전달한 slice element가 아니라 지역 변수만 재할당한다.
- provider re-resolution test는 provider-specific `appliesFn`을 설정하지 않는다.
- hold test는 channel/mode/subscriptions/evidence/max-buffer snapshot을, coordinator test는 endpoint/family/correlation과 동일 batch captured-at/event snapshot을 확인하지 않는다.

### 심볼 참조

- request-global `RequestFilterSnapshot.ResolvedFilters`, `RequestFilterSnapshot.RequiredCapabilities`, registry-global `FilterRegistrySnapshot.RequiredFilterCapabilities`는 production caller가 없고 `filter_registry_test.go`만 참조한다.
- 이 세 surface를 candidate-aware `RequestFilterSnapshot.RequiredFilterCapabilities(AttemptTarget)`와 `ResolveAttempt(AttemptTarget)`로 수렴시키며 모든 test call site를 함께 갱신한다.
- `FilterEvaluationRequirement` stale reference는 없다. 새 third-party dependency도 없다.

### 분할 판단

- split decision policy를 routed filename 확정 전에 적용했다.
- 기존 subtask `08+02,05,06_filter_registry`를 유지한다. candidate resolver, identity fence와 evidence 회귀는 같은 `FilterRegistrySnapshot`/`RequestFilterSnapshot` public surface와 같은 두 파일을 동시에 바꾼다.
- API와 test를 분리하면 S11을 판정할 수 없는 중간 상태가 생기며, 별도 ownership·환경·검증 경계가 없다.
- predecessor `02`, `05`, `06`은 `Archive Evidence Snapshot`의 archived `complete.log`로 모두 충족됐다.

### 범위 결정 근거

- 수정은 `packages/go/streamgate/filter_registry.go`, `packages/go/streamgate/filter_registry_test.go`로 제한한다.
- `evidence_tail.go`, `filter_contract.go`, `evaluation_contract.go`, `parallel_evaluation.go`는 확정된 계약을 소비만 하고 수정하지 않는다.
- `packages/go/config`, proto, Edge/Node admission/dispatch, semantic filter, Arbiter와 recovery는 후속 task 범위라 제외한다.
- task artifact 외 문서와 roadmap은 수정하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build:
  - closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - 근거: 두 registry 파일, S11과 재현된 두 반례로 범위가 닫히고 사용자 결정 없이 focused/race 검증이 가능하다.
  - grade scores: scope coupling 1, state/concurrency 1, blast/irreversibility 1, evidence diagnosis 2, verification complexity 1
  - lane/result: `local`, `G06`, `PLAN-local-G06.md`
- review:
  - closures: scope/context/verification/evidence/ownership/decision 모두 `true`
  - 근거: candidate/identity/immutable evidence 표와 실제 negative regression으로 green-test 신뢰를 독립 판정할 수 있다.
  - grade scores: scope coupling 1, state/concurrency 1, blast/irreversibility 1, evidence diagnosis 2, verification complexity 1
  - lane/result: `local`, `G06`, `CODE_REVIEW-local-G06.md`

## 구현 체크리스트

- [ ] `REVIEW_API-1` request-global active set을 제거하고 candidate/attempt target에서 enabled → Applies → hold/capability를 계산하는 공통 resolver를 구현·테스트한다.
- [ ] `REVIEW_API-2` snapshot/BeginRequest/ResolveAttempt/BindEpoch stable identity fence와 matching-generation 회귀를 구현한다.
- [ ] `REVIEW_API-3` policy slice·hold fields·provider applicability·full context와 동일 immutable batch를 실제로 assert하는 S11/coordinator evidence를 복구한다.
- [ ] host Go 기준 focused/fresh/race/vet, proto/transport profile과 deterministic hygiene 명령을 실행하고 blocker를 포함한 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_API-1] Candidate-scoped active set과 capability preflight

- 문제:
  - `filter_registry.go:875-899`는 actual target이 빈 request context에서 `Applies`, `HoldRequirement`, capability를 고정한다.
  - `filter_registry.go:1131-1150`은 모든 candidate에 같은 `requiredCaps`를 적용해 provider/model policy와 applicability를 반영하지 못한다.
  - `filter_registry.go:881-887`, `1179-1188`은 policy enablement보다 filter contract를 먼저 호출해 disabled filter가 요청을 실패시킬 수 있다.
- 해결 방법:
  - `BeginRequest`는 generation, registration/reference/policy와 request scalar context만 고정한다.
  - private target resolver 하나가 captured identity 확인 → effective policy → disabled 조기 제외 → target-aware `Applies` → validated `FilterHoldRequirement` 순으로 resolved filter와 capability set을 만든다.
  - `RequiredFilterCapabilities(target)`, `EligibleCandidates`, `ResolveAttempt`가 같은 resolver를 사용한다. request/registry-global active/capability surface는 제거한다.
- Before (`filter_registry.go:1131-1148`):

```go
for _, c := range candidates {
	allPresent := true
	for req := range s.requiredCaps {
		if !c.HasCapability(req) {
			allPresent = false
			break
		}
	}
}
```

- After:

```go
for _, candidate := range candidates {
	_, required, err := s.resolveTarget(candidate)
	if err != nil {
		return nil, err
	}
	if candidateHasAll(candidate, required) {
		eligible = append(eligible, candidate)
	}
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry.go`: request-global cache 제거, candidate resolver와 capability API, enabled-before-contract ordering
  - [ ] `packages/go/streamgate/filter_registry_test.go`: provider/model applicability·disable·capability table과 disabled invalid-hold no-call 회귀
- 테스트 작성:
  - `TestFilterRegistryCandidateScopedActiveResolution`: provider-a filter/capability가 provider-a에만 적용되고 missing capability candidate를 제외한다.
  - `TestFilterRegistryDisabledFilterSkipsContractCalls`: disabled filter의 `Applies`/`HoldRequirement`가 호출되지 않고 invalid hold가 request/attempt를 실패시키지 않는다.
- 중간 검증:
  - `go test -race -count=1 -v ./packages/go/streamgate -run '^TestFilterRegistry(CandidateScopedActiveResolution|DisabledFilterSkipsContractCalls|ReresolvesProviderSpecificApplies)$'`
  - 기대 결과: 실제 provider/model/capability subtest가 모두 선택돼 PASS하고 race가 없다.

### [REVIEW_API-2] Stable registration identity 전 구간 fence

- 문제:
  - `filter_registry.go:965-978`, `877-899`, `1178-1199`는 captured registration id와 live `Filter.ID()`를 비교하지 않는다.
  - `filter_registry_test.go:218-228`은 BeginRequest에서 generation mismatch를 identity 오류로 오인하고 ResolveAttempt 전에 ID를 원복한다.
- 해결 방법:
  - 공통 identity validator가 snapshot 생성, BeginRequest, target resolve, BindEpoch에서 live/captured id를 비교하고 `ErrRegistrationFilterIDMismatch`를 stage 문맥과 함께 반환한다.
  - 각 경계는 filter의 `Applies`, `HoldRequirement`, `Evaluate`보다 먼저 identity를 검사한다.
  - 각 test case는 새 filter/snapshot/request를 사용하고 matching generation을 유지해 최초 실패 경계를 분리한다.
- Before (`filter_registry_test.go:217-228`):

```go
f.SetID("f-mutated")
snap2, _ := NewFilterRegistrySnapshot("gen-2", []FilterRegistration{reg}, nil)
if _, err := snap2.BeginRequest(rc); err == nil {
	t.Errorf("expected BeginRequest rejection after mutation")
}
f.SetID("f-stable")
resolved2, _ := rsnap.ResolveAttempt(target)
```

- After:

```go
for _, boundary := range identityMutationBoundaries {
	t.Run(boundary.name, func(t *testing.T) {
		boundary.mutate()
		err := boundary.invokeWithMatchingGeneration()
		if !errors.Is(err, ErrRegistrationFilterIDMismatch) {
			t.Fatalf("error = %v", err)
		}
	})
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry.go`: 공통 identity validator와 snapshot/begin/resolve/bind 적용
  - [ ] `packages/go/streamgate/filter_registry_test.go`: construction/begin/resolve/bind mutation table, matching generation과 최초 경계 assertion
- 테스트 작성:
  - `TestFilterRegistryRejectsIdentityMutationAtBoundaries`: snapshot, BeginRequest, ResolveAttempt, BindEpoch가 모두 stable mismatch를 정확히 거부한다.
- 중간 검증:
  - `go test -race -count=1 -v ./packages/go/streamgate -run '^TestFilterRegistryRejectsIdentityMutationAtBoundaries$'`
  - 기대 결과: 네 boundary subtest가 실제 선택돼 `ErrRegistrationFilterIDMismatch`로 PASS한다.

### [REVIEW_API-3] S11과 coordinator evidence 신뢰 복구

- 문제:
  - `filter_registry_test.go:268-290`은 전달 slice를 mutation하지 않고 지역 policy 변수만 재할당한다.
  - `filter_registry_test.go:411-431`은 provider-specific `appliesFn`이 없다.
  - `filter_registry_test.go:180-203`은 hold snapshot fields를, `497-571`은 endpoint/family/correlation과 batch event/captured-at identity를 assert하지 않는다.
- 해결 방법:
  - caller-owned `[]FilterPolicyLayer`의 실제 element를 snapshot 생성 뒤 overwrite하고 old request/snapshot 결과가 변하지 않음을 확인한다.
  - provider별 `Applies`와 provider별 policy disable을 같은 candidate 표에서 구성한다.
  - hold의 channel/mode/subscriptions/evidence/max-buffer를 `ResolvedFilter`에서 확인하고 accessor copy mutation 격리를 검증한다.
  - coordinator filter와 arbiter가 받은 `EvidenceBatch`의 `CapturedAt`, events와 terminal/commit state가 입력 batch와 같고 full FilterContext scalar가 모두 전달됐는지 확인한다.
- Before (`filter_registry_test.go:411-420`):

```go
f := newMockFilter("f-1")
reg, _ := NewFilterRegistration(f, "cap-observe", true, FilterEnforcementBlocking, ts, 10)
...
rA, _ := rsnap.ResolveAttempt(targetA)
rB, _ := rsnap.ResolveAttempt(targetB)
```

- After:

```go
f.appliesFn = func(ctx FilterContext) bool {
	return ctx.ActualProvider() == "prov-x"
}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/streamgate/filter_registry_test.go`: actual policy slice mutation, hold snapshot fields, provider-specific Applies, full context와 same-batch assertions
  - [ ] `packages/go/streamgate/filter_registry.go`: test가 드러내는 defensive snapshot 누락만 최소 보정
- 테스트 작성:
  - 기존 `TestFilterRegistryPolicyIsFilterScopedAndSnapshotImmutable`, `TestFilterRegistryUsesValidatedHoldRequirement`, `TestFilterRegistryReresolvesProviderSpecificApplies`, `TestResolvedFilterAdapterPassesFullContextThroughCoordinator`를 실제 조건/assertion으로 보강한다.
- 중간 검증:
  - `go test -race -count=1 -v ./packages/go/streamgate -run '^Test(FilterRegistry(PolicyIsFilterScopedAndSnapshotImmutable|UsesValidatedHoldRequirement|ReresolvesProviderSpecificApplies)|ResolvedFilterAdapterPassesFullContextThroughCoordinator)$'`
  - 기대 결과: policy/hold/provider/full-context/same-batch assertion이 모두 PASS하고 race가 없다.

## 의존 관계 및 구현 순서

1. `REVIEW_API-1`에서 candidate resolver와 public capability surface를 고정한다.
2. `REVIEW_API-2`의 identity validator를 모든 resolver 경계에 적용한다.
3. `REVIEW_API-3`에서 S11/coordinator evidence가 실제 API 경로를 판정하도록 회귀를 닫는다.
4. predecessor `02`, `05`, `06`은 archived `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 -race -count=1 -v ./packages/go/streamgate -run '^TestFilterRegistry(CandidateScopedActiveResolution|DisabledFilterSkipsContractCalls|RejectsIdentityMutationAtBoundaries)$'`
   - 기대 결과: candidate/disabled/identity negative regression과 모든 subtest가 실제 선택돼 PASS.
4. `go test -race -count=1 -v ./packages/go/streamgate -run '^Test(FilterRegistry(PolicyIsFilterScopedAndSnapshotImmutable|UsesValidatedHoldRequirement|ReresolvesProviderSpecificApplies|RejectsRequestGenerationMismatch)|ResolvedFilterAdapterPassesFullContextThroughCoordinator|FilterContextIsImmutable)$'`
   - 기대 결과: S11 snapshot/provider/coordinator regression이 실제 선택돼 PASS.
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`의 구현 에이전트 소유 섹션을 채운다.
