<!-- task=m-agent-task-runtime-target-selector/02+01_quota_input plan=0 tag=API -->

# Go Usage Checker 기반 Quota Input Bridge

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

`01_selector_contract`의 `complete.log`를 확인한 뒤 구현한다. 구현과 검증을 완료한 뒤 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 출력으로 채운다. active PLAN/CODE_REVIEW 파일은 그대로 두고 리뷰 준비 완료만 보고한다. 차단되면 구현 소유 evidence 필드에 정확한 차단 원인, 시도한 명령과 출력, 재개 조건만 기록한다. 사용자에게 질문하거나 user-input 도구를 호출하거나 control-plane stop 파일을 만들거나 다음 상태를 판정하지 않는다. 로그 archive, `complete.log`, 최종 판정은 code-review skill만 수행한다.

## 배경

Node의 기존 Go status checker는 Antigravity overall quota와 모델별 usage를 이미 구조화한다. selector가 이 parser를 재구현하지 않도록 `iop-node` 내부 runtime용 JSON probe를 추가하고, required cap을 `available|exhausted|unknown`으로 정규화해야 한다. 이 plan은 selector 계약 완료 후 S04를 실제 Go parser evidence와 연결한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md)
- Task ids:
  - `quota-input`: Go usage checker 기반 required-cap quota snapshot과 selector admission
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/philosophy.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-task-runtime-target-selector.md`
- `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-ops/skills/project/orchestrate-agent-task-loop/scripts/execution_target_policy.py`
- `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_execution_target_policy.py`
- `apps/node/cmd/node/main.go`
- `apps/node/cmd/node/main_test.go`
- `apps/node/internal/adapters/cli/status/status.go`
- `apps/node/internal/adapters/cli/status/status_test.go`
- `apps/node/internal/adapters/cli/status/parser.go`
- `apps/node/internal/adapters/cli/status/parser_test.go`
- `apps/node/internal/adapters/cli/status/antigravity.go`

### SDD 기준

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/agent-task-runtime-target-selector/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 `해제`, 사용자 리뷰 없음
- 대상: S04→`quota-input`
- Evidence Map: Go usage checker JSON bridge, required-cap tri-state, unknown work-unit 1회 admission, local `unbounded/not_applicable` 테스트가 필요하다.
- 구현 체크리스트는 Go parser output→required cap resolver→hidden JSON command→selector black-box 순서로 evidence를 연결한다.

### 테스트 환경 규칙

- `test_env=local`
- `agent-test/local/rules.md`와 `agent-test/local/node-smoke.md`를 읽었다.
- node-smoke profile의 transport/bootstrap 명령은 이번 status/cmd 범위와 직접 일치하지 않아, 프로젝트 node rule의 대상 package test를 fallback으로 사용한다.
- Go 테스트는 cache를 허용하지 않고 `-count=1`을 사용한다.
- 테스트 환경 프리플라이트:
  - runner/repo: 현재 local checkout `/config/workspace/iop`, branch `main`, HEAD `3f01d08cebdafa1bddd1089e6940677dcb6c90b1`
  - dirty state: 사용자의 `agent-ops/skills/common/**` 변경과 이 요청의 policy/plan 변경이 공존한다. 범위 외 변경은 수정하지 않는다.
  - Go: `/config/.local/bin/go` → `/config/opt/go/bin/go`, `go1.26.2 linux/arm64`, `GOROOT=/config/opt/go`
  - 차단 evidence: `go test -count=1 ./apps/node/internal/adapters/cli/status ./apps/node/cmd/node`가 `crypto/internal/fips140cache is not in std`로 setup 실패한다.
  - 재개 조건: `/config/opt/go`의 표준 라이브러리 source를 go1.26.2 설치와 일치하도록 복구한 뒤 동일 preflight와 package test를 재실행한다. 별도 `GOTOOLCHAIN`이나 다른 Go binary로 기준을 바꾸지 않는다.
  - external provider/credential/port: 단위 테스트는 fake checker를 사용하므로 필요 없다.

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

- 기존 parser 테스트는 overall `DailyLimit`과 `model_usage_*` metadata를 검증하지만 required cap set의 tri-state precedence는 검증하지 않는다.
- 기존 node command 테스트는 `quota-probe` JSON schema, raw output 비노출, checker 오류→unknown 변환을 검증하지 않는다.
- selector policy 테스트는 local target matrix만 검증하며 Go probe snapshot과 unknown 1회 admission을 연결하지 않는다.

### 심볼 참조

- renamed/removed symbol: none

### 분할 판단

- split 정책을 파일 선택 전에 적용했다.
- task group: `m-agent-task-runtime-target-selector`
- 현재 subtask: `02+01_quota_input`
- predecessor `01`: active `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/`에 있으나 `complete.log`는 아직 없다. 구현 시작 조건은 해당 predecessor의 active 또는 archive `complete.log` 단일 매치다.
- Python selector 계약과 Node Go checker는 ownership·언어·검증 경계가 달라 분리했다. 이 subtask 안의 status normalizer와 hidden command는 같은 Go JSON bridge를 이루며 한 package test batch에서 검증되므로 추가 분할하지 않는다.

### 범위 결정 근거

- 기존 PTY parser regex와 `UsageStatus` wire/runtime DTO는 바꾸지 않는다.
- Node config/proto, Edge console output, 사용자 bootstrap UX에는 새 schema를 추가하지 않는다.
- probe는 raw provider output과 secret을 JSON에 포함하지 않는다.
- dispatcher batch snapshot reuse, persisted route, failover는 후속 integration Epic 범위다. 이 plan은 selector의 S04 admission hook을 black-box로 검증하는 데까지만 닫는다.

### 최종 라우팅

- evaluation_mode: `first-pass`
- finalizer: `finalize-task-policy.sh`
- finalizer_mode: `pair`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`
- build closure basis: status parser, 새 normalizer, hidden command와 selector consumer 경계가 확정됐고, 현재 Go 설치 차단은 정확한 preflight/복구 조건과 동일 package test로 판정 가능하다.
- build base route: `local-G05`
- build grade scores: scope_coupling=2, state_concurrency=0, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1
- build loop-risk: `boundary_contract`, `structured_interpretation`, `variant_product`가 매칭되어 `triggered: true`; signature 개수는 grade에 더하지 않고 cloud minimum `G07`만 적용
  - `boundary_contract`: Go usage checker·quota-probe JSON·Python selector 사이의 validation/compatibility 계약
  - `structured_interpretation`: PTY usage parser와 exhausted·missing·conflict 상태 precedence
  - `variant_product`: overall/model cap 관측 여부 × 0/양수/충돌 × required-cap 조합 검증
- build final: `cloud-G07`, `PLAN-cloud-G07.md`
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`
- review closure basis: Go parser/normalizer/command와 selector black-box evidence를 고정 명령으로 대조할 수 있다.
- review base route: `local-G05`
- review grade scores: scope_coupling=2, state_concurrency=0, blast_irreversibility=1, evidence_diagnosis=1, verification_complexity=1
- review capability floor: official review minimum `cloud-G09`
- review final: `cloud-G09`, `CODE_REVIEW-cloud-G09.md`

## 구현 체크리스트

- [ ] 기존 `UsageStatus`의 overall/model remaining을 required cap set에 맞춰 `available|exhausted|unknown` snapshot으로 정규화한다.
- [ ] raw output을 노출하지 않는 hidden `iop-node quota-probe` JSON command를 추가하고 checker/parse 오류를 `unknown` evidence로 반환한다.
- [ ] overall/model cap 0·양수·누락·충돌, local unbounded, unknown work-unit 1회 admission을 Go와 selector black-box 테스트로 검증한다.
- [ ] Go preflight를 재실행하고 `go test -count=1 ./apps/node/internal/adapters/cli/status ./apps/node/cmd/node` 및 selector 테스트를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 의존 관계 및 구현 순서

1. `agent-task/m-agent-task-runtime-target-selector/01_selector_contract/complete.log` 또는 같은 task group/index의 단일 archive `complete.log`가 있어야 한다. 현재는 미충족이다.
2. Go normalizer와 hidden command를 구현한다.
3. predecessor selector의 `--quota-probe-command`/snapshot 계약을 실제 probe와 연결해 S04 black-box 검증을 수행한다.

## [API-1] Required-cap quota snapshot 정규화

### 문제

`apps/node/internal/adapters/cli/status/status.go:13`의 `UsageStatus`는 overall remaining을 `DailyLimit`에, 모델별 used percentage를 `Metadata["model_usage_*"]`에 보존하지만 selector가 소비할 tri-state와 required cap evidence가 없다.

### 해결 방법

현재 기준:

```go
// status.go:13
type UsageStatus struct {
    RawOutput  string
    DailyLimit string
    Metadata   map[string]string
}
```

`status/quota.go`에 immutable JSON DTO와 pure normalizer를 추가한다.

```go
type QuotaSnapshot struct {
    SchemaVersion string         `json:"schema_version"`
    SnapshotID    string         `json:"snapshot_id"`
    Status        string         `json:"status"`
    Source        string         `json:"source"`
    CheckedAt     string         `json:"checked_at"`
    RequiredCaps  []QuotaCapView `json:"required_caps"`
    ReasonCodes   []string       `json:"reason_codes"`
}
```

- `overall`은 `DailyLimit`, `model:<name>`은 `model_usage_*_name`과 `*_used_percent`를 case-insensitive exact-name으로 대조해 remaining으로 변환한다.
- required cap 하나라도 confirmed 0이면 다른 누락보다 우선해 `exhausted`다.
- 모든 required cap이 관측되고 양수면 `available`이다.
- required cap 누락, 중복 충돌, 잘못된 percent, nil/parse/checker 오류는 `unknown`이다.
- snapshot id는 checked-at과 정규화된 cap evidence의 deterministic hash로 만들고 raw PTY output은 포함하지 않는다.

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

- [ ] `apps/node/internal/adapters/cli/status/quota.go` — DTO, cap resolver, tri-state normalizer를 추가한다.
- [ ] `apps/node/internal/adapters/cli/status/quota_test.go` — required cap precedence와 metadata bridge 테스트를 추가한다.

### 테스트 작성

작성한다. `TestNormalizeQuotaSnapshotExhaustedWinsMissing`, `...AvailableWhenAllPositive`, `...UnknownForMissingConflictAndParseFailure`, `...UsesAntigravityModelMetadata`를 추가한다. 마지막 테스트는 `ParseStatusOutput` fixture 결과를 직접 입력해 parser 중복 구현이 없음을 검증한다.

### 중간 검증

```bash
go test -count=1 ./apps/node/internal/adapters/cli/status
```

기대 결과: parser 회귀와 quota tri-state 테스트가 모두 PASS한다. 현재 host Go가 복구되지 않았으면 실제 setup failure 출력과 재개 조건을 review stub에 기록한다.

## [API-2] Hidden `iop-node quota-probe` JSON command

### 문제

`apps/node/cmd/node/main.go:33`은 serve/version/config/setup만 등록한다. selector가 existing Go checker를 좁은 JSON으로 호출할 runtime command가 없다.

### 해결 방법

현재 기준:

```go
// main.go:33
root.AddCommand(serveCmd(), versionCmd(), configCmd(), setupCmd())
```

`quota_probe.go`의 hidden Cobra command를 추가하고 root에 등록한다.

```go
root.AddCommand(serveCmd(), versionCmd(), configCmd(), setupCmd(), quotaProbeCmd())
```

- 입력: `--target`, `--command`, 반복 `--required-cap`, 선택 `--checked-at`
- `status.CheckUsage`를 호출하고 API-1 normalizer 결과 한 개만 stdout JSON으로 쓴다.
- checker/parse 실패는 command 자체 crash나 generic stderr 분류가 아니라 `status=unknown`, typed reason code를 가진 snapshot으로 반환한다.
- invalid flag/required-cap schema만 non-zero 입력 오류로 처리한다.
- command는 `Hidden: true`로 두어 사용자 bootstrap/운영 기본 UX에 노출하지 않는다.
- 테스트는 package-level checker function injection으로 외부 `agy`를 실행하지 않는다.

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

- [ ] `apps/node/cmd/node/main.go` — hidden probe command를 root에 등록한다.
- [ ] `apps/node/cmd/node/quota_probe.go` — Cobra command와 JSON encoder를 추가한다.
- [ ] `apps/node/cmd/node/quota_probe_test.go` — fake checker 기반 command schema/error tests를 추가한다.

### 테스트 작성

작성한다. available/exhausted/unknown JSON, required cap 전달, raw output 비노출, invalid cap non-zero, checker error unknown을 검증한다.

### 중간 검증

```bash
go test -count=1 ./apps/node/cmd/node
```

기대 결과: 기존 operational command 테스트와 hidden quota probe 테스트가 모두 PASS한다.

## [API-3] Selector S04 black-box 연결

### 문제

Go snapshot만으로는 S04의 local `unbounded/not_applicable`과 cloud unknown work-unit 1회 admission이 selector 결과에 반영됐다는 evidence가 부족하다.

### 해결 방법

predecessor가 확정한 selector probe hook을 사용해 fake `iop-node quota-probe` JSON을 주입한다.

- overall 또는 model required cap 0 snapshot은 candidate rejection `quota_exhausted`를 만든다.
- 모두 양수인 snapshot은 eligible `quota_available`을 만든다.
- missing/conflict snapshot은 최초 work unit admission에서만 eligible unknown이며 prior decision에 unknown admission 사용 evidence가 있으면 재-admission하지 않는다.
- local target은 probe process를 호출하지 않고 `mode=unbounded`, `status=not_applicable`이다.
- generic stderr text만 제공하는 fake는 quota exhausted로 승격되지 않는다.

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

- [ ] `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` — S04 probe/admission black-box cases를 추가한다.
- [ ] `apps/node/cmd/node/quota_probe_test.go` — selector가 소비하는 JSON fixture와 command output이 동일 schema인지 검증한다.

### 테스트 작성

작성한다. `test_quota_probe_tri_state`, `test_unknown_is_admitted_once_per_work_unit`, `test_local_route_does_not_call_probe`, `test_generic_stderr_is_not_quota_evidence`를 추가한다.

### 중간 검증

```bash
python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_execution_target*.py'
```

기대 결과: predecessor S01~S03 회귀와 S04 selector admission 테스트가 모두 PASS한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/adapters/cli/status/quota.go` | API-1 |
| `apps/node/internal/adapters/cli/status/quota_test.go` | API-1 |
| `apps/node/cmd/node/main.go` | API-2 |
| `apps/node/cmd/node/quota_probe.go` | API-2 |
| `apps/node/cmd/node/quota_probe_test.go` | API-2, API-3 |
| `agent-ops/skills/project/orchestrate-agent-task-loop/tests/test_select_execution_target.py` | API-3 |

## 최종 검증

```bash
command -v go && readlink -f "$(command -v go)" && go version && go env GOROOT
go test -count=1 ./apps/node/internal/adapters/cli/status ./apps/node/cmd/node
python3 -m unittest discover -s agent-ops/skills/project/orchestrate-agent-task-loop/tests -p 'test_execution_target*.py'
```

기대 결과: host Go/GOROOT가 일치하고 Go parser/probe 및 Python selector S01~S04 테스트가 모두 PASS한다. 현재 checkout에서는 Go 표준 라이브러리의 `crypto/internal/fips140cache` 누락을 먼저 복구해야 한다. 외부 provider 호출과 Go test cache는 사용하지 않는다.

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