<!-- task=m-agent-readable-repository-refactor/13_inventory_query plan=3 tag=REVIEW_REVIEW_REVIEW_IQRY -->

# Inventory query 가독성 ratchet 및 scalar regression 보완 계획

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

구현과 검증을 마친 뒤 `CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 명령 출력을 채우고 active 파일을 그대로 둔 채 리뷰 준비 상태를 보고한다. 종결, 로그 아카이브, `complete.log` 작성은 code-review 전용이다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실제 구현을 막는 경우에만 review stub의 `사용자 리뷰 요청`에 연결 근거를 기록하고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`/로그/`complete.log`를 만들지 않는다. 환경·secret·서비스 차단, 일반 범위 변경, 후속 에이전트가 채울 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

세 번째 구현은 provider map traversal, schema 분류, help 경계와 전체 Go 회귀를 통과했다. 그러나 scalar-map regression fixture가 실행 전에 덮어써져 필수 동작을 검증하지 못했고, Milestone의 공식 worktree readability ratchet은 새 LOC/함수 크기 위반 4건으로 실패했다. Provider query 동작은 유지하면서 같은 package 안에서 책임과 테스트를 분리해 두 공백을 함께 닫는다.

## 사용자 리뷰 요청 흐름

선택된 Milestone의 `구현 잠금 > 결정 필요`만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review stub에 기록한다. 구현 중 직접 사용자 prompt는 금지하며, 요청 검증과 실제 `USER_REVIEW.md` 작성은 code-review가 담당한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)
- Task ids:
  - `inventory-query`: env/model/node/provider selector로 canonical inventory의 bounded 결과를 얻는 결정적 query 경로
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Prior task: `agent-task/m-agent-readable-repository-refactor/13_inventory_query`
- Archived plan: `agent-task/m-agent-readable-repository-refactor/13_inventory_query/plan_local_G06_2.log`
- Archived review: `agent-task/m-agent-readable-repository-refactor/13_inventory_query/code_review_local_G06_2.log`
- Verdict: FAIL
- Required: 2 — scalar provider map regression이 fixture overwrite로 미실행, worktree readability ratchet의 새 위반 4건
- Suggested: 0
- Nit: 0
- Affected files: `scripts/inventory-query/main.go`, `scripts/inventory-query/main_test.go`
- Verification evidence: fresh targeted/package/full Go test, canonical dev/dev-corp provider selector, compiled binary 0/1/2/help, gofmt와 `git diff --check`는 PASS했다. `python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-inventory-readability-review.json`은 `main.go` 558 LOC, `queryProviders` 141 LOC, `main_test.go` 830 LOC, `TestQueryProviderMatchesMapScalarAndNestedEntity` 94 LOC의 새 위반으로 exit 4였다.
- Roadmap carryover: `inventory-query`는 PASS 전까지 미완료다.
- Allowed narrow reread: 위 archived plan/review 두 파일만 필요할 때 읽는다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/README.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- `agent-spec/index.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-contract/index.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `scripts/inventory-query/main.go`
- `scripts/inventory-query/main_test.go`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md`
- `agent-task/m-agent-readable-repository-refactor/13_inventory_query/plan_cloud_G07_1.log`
- `agent-task/m-agent-readable-repository-refactor/13_inventory_query/code_review_cloud_G07_1.log`
- `agent-task/m-agent-readable-repository-refactor/13_inventory_query/plan_local_G06_2.log`
- `agent-task/m-agent-readable-repository-refactor/13_inventory_query/code_review_local_G06_2.log`

### SDD 기준

Milestone은 `SDD: 불필요`다. 외부 API/proto/config schema나 runtime 책임 경계를 바꾸지 않고 동일 package 안에서 책임 분리와 저장소 가독성을 개선하는 동작 보존형 리팩터링이라는 기록 사유를 유지한다.

### 테스트 환경 규칙

`test_env=local`이다. `agent-test/local/rules.md`, `agent-test/local/testing-smoke.md`, `agent-test/local/platform-common-smoke.md`를 읽었다. 변경은 외부 provider/runtime이 아니라 read-only local CLI와 테스트 분리이므로 fresh targeted/package/full Go test, 직접 compiled binary의 canonical selector와 0/1/2/help, worktree readability ratchet, `git diff --check`를 적용한다. 외부 host, Docker, provider endpoint는 필요하지 않으며 임시 binary/cache/audit JSON은 `/tmp`에만 둔다.

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

- `providers` map scalar value case는 fixture를 만든 뒤 entity fixture로 덮어써 실제 query/assert가 없다.
- entity `id`, entity `provider`, key+value dedupe는 개별 실행되지만 한 함수에 순차 mutation으로 묶여 case 독립성과 실패 위치가 약하다.
- Provider query의 canonical dev/dev-corp 동작과 compiled exit 계약은 현재 테스트와 직접 실행으로 커버된다.
- 저장소 readability ratchet은 이전 계획의 최종 검증에서 누락됐고 현재 checkout에서 새 위반 4건을 탐지한다.

### 심볼 참조

외부 rename/remove는 없다. `queryProviders`의 직접 참조는 `scripts/inventory-query/main.go`와 `scripts/inventory-query/main_test.go` 안의 package-local 호출에 한정된다. 같은 `main` package 안에서 파일과 private helper만 재배치하므로 소비 문서와 CLI 계약은 바뀌지 않는다.

### 분할 판단

split decision policy를 먼저 적용했다. 두 Required는 같은 provider traversal과 regression을 분해해야 같은 readability audit에서 닫히며, 별도 API/도메인/검증 환경 경계가 없다. 나누면 첫 작업의 intermediate state가 계속 ratchet을 실패하고 같은 파일을 반복 이동하므로 기존 `13_inventory_query` 단일 follow-up이 더 안전하다.

### 범위 결정 근거

`agent-test/*/inventory.yaml`, `agent-test/README.md`, deploy skill, agent-spec, agent-contract, roadmap, API/proto/config/runtime 코드는 수정하지 않는다. CLI JSON/exit/help/schema 동작을 바꾸지 않고 `scripts/inventory-query` package 내부의 provider traversal과 regression 배치만 정리한다. readability baseline 예외를 추가하거나 threshold를 낮추지 않으며 repo-local 생성물을 남기지 않는다.

### 빌드 등급

`local-G06`. 같은 Go package의 private helper/test 분리이고 exact unit/binary/audit 명령으로 완전히 판정할 수 있어 외부 또는 interactive 판단이 필요하지 않다.

## 구현 체크리스트

- [ ] provider traversal을 같은 package의 명명된 collector/helper 책임으로 분해해 현재 query 동작을 유지하면서 새 production LOC/함수 크기 위반을 제거한다.
- [ ] scalar/entity/dedupe provider map regression을 독립 실행하고 focused test file로 분리해 새 test LOC/함수 크기 위반을 제거한다.
- [ ] fresh targeted/package/full Go test, canonical selector, compiled 0/1/2/help, worktree readability audit와 diff 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_IQRY-1] Provider collector 책임 분리

#### 문제

`scripts/inventory-query/main.go:317-456`의 `queryProviders`가 entry match, singular provider, collection map/list, direct provider와 generic recursion을 한 함수에 보유한다. 그 결과 `main.go`는 558 LOC, 함수는 141 LOC로 worktree ratchet의 새 warning/split-review 위반이다.

#### 해결 방법

같은 `main` package에 `provider_query.go`를 만들고 `providerCollector` 또는 동등한 명명된 상태 객체로 `selector`, `matches`, `seen`을 묶는다. `queryProviders`는 collector 생성, traversal, 정렬만 담당하고 map/list collection과 singular/direct provider 처리는 작은 메서드로 분리한다. Map과 list entry는 같은 entry matcher를 사용해 key, scalar, entity `id`/`provider`를 평가하고 기존 path/value/sort/dedupe 계약을 유지한다.

Before (`scripts/inventory-query/main.go:317`):

```go
func queryProviders(data map[string]interface{}, selector string) []match {
	var matches []match
	seen := make(map[string]bool)
	// closure와 모든 traversal branch
}
```

After:

```go
func queryProviders(data map[string]interface{}, selector string) []match {
	collector := newProviderCollector(selector)
	collector.collect(data, "")
	sortMatches(collector.matches)
	return collector.matches
}
```

#### 수정 파일 및 체크리스트

- [ ] `scripts/inventory-query/main.go` — 기존 provider traversal block을 제거하고 공통 CLI/query 경계만 유지.
- [ ] `scripts/inventory-query/provider_query.go` — 명명된 collector와 entry/collection traversal helper 추가.

#### 테스트 작성

새 동작 테스트는 추가하지 않는다. 기존 provider query 테스트와 다음 항목의 scalar regression이 refactor 동작 보존을 검증한다.

#### 중간 검증

```bash
gofmt -d scripts/inventory-query/main.go scripts/inventory-query/provider_query.go
GOCACHE=/tmp/iop-inventory-query-readability-gocache go test -count=1 ./scripts/inventory-query -run 'TestQueryProvider'
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-inventory-readability-mid.json
```

gofmt가 무출력이고 provider tests와 readability audit이 exit 0이어야 한다.

### [REVIEW_REVIEW_REVIEW_IQRY-2] Scalar regression과 test topology 보완

#### 문제

`scripts/inventory-query/main_test.go:608-621`은 scalar fixture를 entity fixture로 덮어쓴 뒤에만 query를 실행한다. 같은 test가 94 LOC이고 `main_test.go`도 830 LOC라 두 건 모두 새 readability 위반이다.

#### 해결 방법

기존 긴 test를 `provider_query_test.go`로 이동하고 table-driven subtest와 작은 assertion helper로 재작성한다. Scalar value, entity `id`, entity `provider`, key와 value가 모두 selector인 dedupe case를 각각 새 fixture에서 실행하고 entry path, 실제 value, path 중복 없음, 정렬을 assert한다. `main_test.go`에서는 기존 함수 전체를 제거한다.

Before (`scripts/inventory-query/main_test.go:608`):

```go
activeGroup["providers"] = map[string]interface{}{
	"scalar-provider": "gx10-vllm",
}
activeGroup["providers"] = map[string]interface{}{
	"some-other-key": map[string]interface{}{"id": "gx10-vllm"},
}
matches := queryProviders(data, "gx10-vllm")
```

After:

```go
for _, testCase := range cases {
	t.Run(testCase.name, func(t *testing.T) {
		data := parseFixture(t)
		setProviderEntries(t, data, testCase.entries)
		assertProviderEntryMatch(t, queryProviders(data, selector), testCase)
	})
}
```

#### 수정 파일 및 체크리스트

- [ ] `scripts/inventory-query/main_test.go` — 덮어쓰는 긴 regression 제거.
- [ ] `scripts/inventory-query/provider_query_test.go` — 독립 scalar/entity/dedupe cases와 focused helper 추가.

#### 테스트 작성

작성한다. `TestQueryProviderMatchesMapEntries`에서 scalar, entity `id`, entity `provider`, key+value dedupe를 table로 실행하고 각 case의 exact entry path/value와 중복 제거를 assert한다.

#### 중간 검증

```bash
gofmt -d scripts/inventory-query/main_test.go scripts/inventory-query/provider_query_test.go
GOCACHE=/tmp/iop-inventory-query-readability-gocache go test -count=1 ./scripts/inventory-query -run 'TestQueryProviderMatchesMapEntries$'
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-inventory-readability-test.json
```

gofmt가 무출력이고 test와 readability audit이 exit 0이어야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `scripts/inventory-query/main.go` | REVIEW_REVIEW_REVIEW_IQRY-1 |
| `scripts/inventory-query/provider_query.go` | REVIEW_REVIEW_REVIEW_IQRY-1 |
| `scripts/inventory-query/main_test.go` | REVIEW_REVIEW_REVIEW_IQRY-2 |
| `scripts/inventory-query/provider_query_test.go` | REVIEW_REVIEW_REVIEW_IQRY-2 |
| `CODE_REVIEW-local-G06.md` | 모든 항목의 구현/검증 evidence |

## 최종 검증

```bash
export GOCACHE=/tmp/iop-inventory-query-readability-gocache
mkdir -p "$GOCACHE"
gofmt -d scripts/inventory-query/main.go scripts/inventory-query/provider_query.go scripts/inventory-query/main_test.go scripts/inventory-query/provider_query_test.go
go test -count=1 ./scripts/inventory-query -run 'Test(QueryProviderMatchesMapEntries|QueryProviderAcrossCanonicalShapes|ValidateInventoryRejectsInvalidProjectionObjectTypes|ValidateInventoryRequiresProviderModelAndNodes|RunClassifiesProviderSchemaError|RunHelpUsesInjectedStreams)$'
go test -count=1 ./scripts/inventory-query
go build -o /tmp/iop-inventory-query-readability ./scripts/inventory-query
/tmp/iop-inventory-query-readability --env dev --provider gx10-vllm > /tmp/iop-inventory-readability-dev-provider.json
/tmp/iop-inventory-query-readability --env dev-corp --provider corp-dgx-spark-01-ornith > /tmp/iop-inventory-readability-corp-provider.json
python3 - <<'PY'
import json

with open("/tmp/iop-inventory-readability-dev-provider.json", encoding="utf-8") as handle:
    dev = json.load(handle)
with open("/tmp/iop-inventory-readability-corp-provider.json", encoding="utf-8") as handle:
    corp = json.load(handle)

dev_paths = {item["path"]: item["value"] for item in dev["matches"]}
corp_paths = {item["path"]: item["value"] for item in corp["matches"]}
assert isinstance(dev_paths["model.active_edge_model_group.providers.gx10-vllm"], dict)
assert isinstance(dev_paths["nodes[1].provider"], dict)
assert corp_paths["model.aliases.ornith:35b.providers[0]"] == "corp-dgx-spark-01-ornith"
assert isinstance(corp_paths["nodes[0].provider"], dict)
PY
set +e
/tmp/iop-inventory-query-readability --help >/tmp/iop-inventory-readability-help.out 2>/tmp/iop-inventory-readability-help.err
help_rc=$?
/tmp/iop-inventory-query-readability --env dev --node definitely-not-a-node >/tmp/iop-inventory-readability-zero.out 2>/tmp/iop-inventory-readability-zero.err
zero_rc=$?
/tmp/iop-inventory-query-readability --env unsupported >/tmp/iop-inventory-readability-error.out 2>/tmp/iop-inventory-readability-error.err
error_rc=$?
set -e
test "$help_rc" -eq 0
test "$zero_rc" -eq 1
test "$error_rc" -eq 2
test -s /tmp/iop-inventory-readability-help.out
test ! -s /tmp/iop-inventory-readability-help.err
test ! -s /tmp/iop-inventory-readability-zero.out
test ! -s /tmp/iop-inventory-readability-error.out
python3 scripts/readability_audit.py --check --input-mode worktree --output /tmp/iop-inventory-readability-final.json
go test -count=1 ./...
git diff --check
```

모든 명령은 exit 0, gofmt/diff-check는 무출력이어야 한다. Readability audit은 baseline 예외 추가 없이 새 위반 0건이어야 하며 Go test는 `-count=1` fresh 실행을 사용한다. binary/cache/audit JSON은 `/tmp`에만 둔다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
