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

# Inventory bounded query 경로 구현 계획

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

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

## 배경

두 환경의 `inventory.yaml`은 canonical machine-readable source지만 수백 줄 전체를 읽어야 env/model/node/provider 한 항목을 찾을 수 있다. 저장소 안에서 같은 selector가 항상 같은 순서와 형식의 bounded JSON을 내도록 하여 에이전트의 기본 read path를 줄여야 한다.

## 사용자 리뷰 요청 흐름

선택된 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

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-test/README.md`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/dev/inventory.yaml`
- `agent-test/dev-corp/inventory.yaml`
- `agent-ops/skills/project/dev-runtime-deploy/SKILL.md`
- `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md`
- `go.mod`
- `Makefile`

### SDD 기준

Milestone은 `SDD: 불필요`다. 기록 사유는 외부 API/proto/config schema나 runtime 책임 경계를 바꾸지 않는 동작 보존형 저장소 리팩터링이라는 점이다. 이 계획도 inventory schema를 바꾸지 않고 read-only query만 추가한다.

### 테스트 환경 규칙

`test_env=local`이다. `agent-test/local/rules.md`, `testing-smoke.md`, `platform-common-smoke.md`를 읽었고, 새 Go 도구에는 fresh targeted test와 전체 `go test -count=1 ./...`를 적용한다. 현재 checkout 내부의 tracked inventory만 읽으므로 원격 runner, 외부 host, runtime port 프리플라이트는 적용하지 않는다. Go는 `/config/.local/bin/go`, `go1.26.2 linux/arm64`, repo root는 `/config/workspace/iop`, 기준 HEAD는 `cedb7fd41b94515765e20c3d9eaedf24fad7d2e0`이며 계획 작성 시 dirty 파일 5개는 사용자 변경과 선행 evidence-link 수정이다. 테스트 규칙의 구조 공백이나 `<확인 필요>`는 없다.

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

- 신규 selector parsing, 정확 일치, 정렬, 중복 제거, zero-match/invalid exit code는 기존 구현과 테스트가 없다. `scripts/inventory-query/main_test.go`에 정상·경계·오류 fixture test를 작성한다.
- 실제 `dev`/`dev-corp` inventory shape 연결은 unit fixture만으로 부족하므로 최종 검증에서 두 canonical 파일에 대해 실제 명령을 실행한다.
- inventory 내용 자체와 원격 접속 가능성은 이 read-only 도구의 동작 범위가 아니므로 검증하지 않는다.

### 심볼 참조

none. 기존 심볼의 rename/remove는 없다.

### 분할 판단

공유 task group은 `m-agent-readable-repository-refactor`이고 이번 잔여 작업은 `13_inventory_query`, `14+13_skill_entrypoints`, `15+13,14_read_set_evidence`, `16+13,14,15_regression`으로 분할한다. 이 계획은 독립적인 첫 subtask라 predecessor가 없다. 새 CLI의 parser/index/output/test가 하나의 계약이므로 다시 쪼개면 exit/output 계약이 중간 상태가 되어 단일 plan이 더 안전하다.

### 범위 결정 근거

`agent-test/dev/inventory.yaml`과 `agent-test/dev-corp/inventory.yaml`의 key, 값, secret 경로, 환경별 의미는 수정하지 않는다. deploy 동작·원격 배포·provider capacity 판정도 바꾸지 않고, 두 deploy skill은 전체 inventory 선독 대신 query 명령을 우선하도록 읽기 절차만 바꾼다. 새 third-party dependency, shell wrapper, generated artifact는 추가하지 않는다.

### 빌드 등급

`local-G06`. 두 실 inventory shape를 수용하는 새 CLI와 공개 exit/output 계약 및 경계 테스트가 필요하지만 외부 runtime 변경은 없다.

## 구현 체크리스트

- [ ] `scripts/inventory-query`에 env/model/node/provider selector, stable JSON, 명시적 exit code를 갖는 read-only Go CLI를 구현한다.
- [ ] fixture 기반 정상·경계·오류 테스트와 두 canonical inventory 실명령 검증을 추가한다.
- [ ] `agent-test/README.md`와 dev/dev-corp deploy skill이 bounded query를 우선하고 실패 시에만 canonical inventory 전체 읽기로 fallback하도록 갱신한다.
- [ ] targeted test, 실제 inventory query, 전체 Go test와 diff 검증을 fresh 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [IQRY-1] 결정적 inventory query CLI와 테스트

#### 문제

`agent-test/README.md:38-44`는 structured inventory 원칙만 정의하고 bounded read 경로는 제공하지 않는다. `dev-runtime-deploy/SKILL.md:29-35`와 `dev-corp-runtime-deploy/SKILL.md:27-31`는 수백 줄 inventory 전체 선독을 요구한다.

#### 해결 방법

새 `scripts/inventory-query` Go command를 표준 진입점으로 둔다.

Before (`agent-ops/skills/project/dev-runtime-deploy/SKILL.md:29-31`):

```markdown
- [ ] dev 환경은 `agent-test/dev/inventory.yaml`을 먼저 읽는다.
```

After:

```markdown
- [ ] `go run ./scripts/inventory-query --env dev`와 필요한 정확 selector를 먼저 실행한다.
- [ ] query가 schema/file 오류로 실패할 때만 canonical inventory 전체 읽기로 fallback한다.
```

CLI 계약:

- 필수 `--env dev|dev-corp`.
- selector는 `--model`, `--node`, `--provider` 중 최대 하나만 허용한다.
- selector 없음은 `test_env`, `profile`, `last_updated`, `source`, `edge`, `build` top-level key만 포함한 env projection을 반환하고 `model`, `nodes` 전체는 제외한다.
- selector 결과는 `{"env","kind","selector","matches":[{"path","value"}]}`이고 `path` 오름차순으로 정렬한다.
- node는 top-level `nodes[]`의 `id` 또는 `alias` 정확 일치, provider는 이름이 `providers`인 mapping의 key 또는 그 하위 entity의 `id`/`provider` 정확 일치, model은 `model.aliases` key 및 `model` subtree entity의 `id`/`alias`/`model_id` 정확 일치로 한정한다.
- YAML path로 중복 제거한다. substring/정규식/대소문자 접기는 하지 않는다.
- 성공 0, 정상 schema에서 zero match 1, flag/file/YAML/schema 오류 2를 사용한다.
- stderr에는 오류만, stdout에는 성공 JSON만 출력한다. secret 원문을 별도로 탐색하거나 로그에 확장하지 않는다.
- `gopkg.in/yaml.v3`는 기존 `go.mod` dependency를 재사용한다.

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

- [ ] `scripts/inventory-query/main.go` — flag, YAML load, projection/index, stable JSON, exit code 구현.
- [ ] `scripts/inventory-query/main_test.go` — pure helper와 CLI decision의 table test 구현.
- [ ] `agent-test/README.md` — query 사용법·selector·exit code·fallback 기준 추가.
- [ ] `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` — dev bounded query 우선.
- [ ] `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md` — dev-corp bounded query 우선.

#### 테스트 작성

작성한다. `scripts/inventory-query/main_test.go`에 `TestQueryEnvironmentProjectionIsBounded`, `TestQueryNodeByIDAndAlias`, `TestQueryModelAcrossAliasShapes`, `TestQueryProviderSortsAndDeduplicatesMatches`, `TestQueryZeroMatch`, `TestParseFlagsRejectsInvalidCombinations`, `TestQueryIsDeterministic`를 둔다. dev/dev-corp 구조 차이를 가진 inline YAML fixture를 사용하고 projection에서 `model`/`nodes`가 빠지는지, exact match와 path 정렬, exit 분류를 assert한다.

#### 중간 검증

```bash
gofmt -d scripts/inventory-query/main.go scripts/inventory-query/main_test.go
go test -count=1 ./scripts/inventory-query
```

두 명령 모두 exit 0이고 `gofmt -d` 출력이 없어야 한다. Go test cache는 허용하지 않는다.

### [IQRY-2] 실제 inventory와 소비 문서 연결

#### 문제

fixture만 통과해도 현재 tracked dev/dev-corp YAML 구조에서 query가 실패할 수 있고, 기존 skill이 계속 전체 파일을 먼저 읽으면 bounded 경로가 기본 동작이 되지 않는다.

#### 해결 방법

README에 실제 invocation을 고정하고 두 deploy skill의 “먼저 확인할 것”과 environment 확정 단계에서 query 결과를 사용한다. 실제 파일을 대상으로 env projection과 대표 selector를 두 번 실행해 byte-identical 결과를 확인한다.

Before (`agent-test/README.md:36-45`):

```markdown
- 실제 노드, provider ... 상세 인벤토리는 ... `inventory.yaml`에 둔다.
```

After:

```markdown
- 전체 inventory를 읽기 전에 `go run ./scripts/inventory-query --env <env> [selector]`로 필요한 bounded 결과를 조회한다.
```

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

- [ ] `agent-test/README.md` — dev/dev-corp 예시와 fallback 정책 명시.
- [ ] `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` — model/node/provider별 정확 query 명령 명시.
- [ ] `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md` — model/node/provider별 정확 query 명령 명시.

#### 테스트 작성

추가 문서 전용 테스트는 작성하지 않는다. IQRY-1의 parser 테스트와 아래 canonical inventory 실명령이 문서 명령 및 현재 shape를 직접 검증한다.

#### 중간 검증

```bash
go run ./scripts/inventory-query --env dev > /tmp/iop-inventory-dev-1.json
go run ./scripts/inventory-query --env dev > /tmp/iop-inventory-dev-2.json
cmp /tmp/iop-inventory-dev-1.json /tmp/iop-inventory-dev-2.json
go run ./scripts/inventory-query --env dev-corp > /tmp/iop-inventory-dev-corp.json
python3 -m json.tool /tmp/iop-inventory-dev-1.json >/dev/null
python3 -m json.tool /tmp/iop-inventory-dev-corp.json >/dev/null
```

모두 exit 0이어야 하고 `cmp` 차이가 없어야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `scripts/inventory-query/main.go` | IQRY-1 |
| `scripts/inventory-query/main_test.go` | IQRY-1 |
| `agent-test/README.md` | IQRY-1, IQRY-2 |
| `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` | IQRY-1, IQRY-2 |
| `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md` | IQRY-1, IQRY-2 |

## 최종 검증

```bash
gofmt -d scripts/inventory-query/main.go scripts/inventory-query/main_test.go
go test -count=1 ./scripts/inventory-query
go run ./scripts/inventory-query --env dev > /tmp/iop-inventory-dev-1.json
go run ./scripts/inventory-query --env dev > /tmp/iop-inventory-dev-2.json
cmp /tmp/iop-inventory-dev-1.json /tmp/iop-inventory-dev-2.json
go run ./scripts/inventory-query --env dev-corp > /tmp/iop-inventory-dev-corp.json
python3 -m json.tool /tmp/iop-inventory-dev-1.json >/dev/null
python3 -m json.tool /tmp/iop-inventory-dev-corp.json >/dev/null
go test -count=1 ./...
git diff --check
```

모든 명령은 exit 0, gofmt/diff-check는 무출력이어야 한다. 모든 Go test는 fresh 실행하며 cache 결과를 허용하지 않는다.

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