<!-- task=m-principal-provider-credential-slot-routing/10+08,09_verification_contracts plan=0 tag=TEST -->

# Credential slot qualification과 계약·운영 동기화 계획

## For the Implementing Agent

`CODE_REVIEW-*-G??.md`의 구현 담당 섹션 작성은 필수다. 검증을 실제로 실행하고 notes/stdout/stderr를 채운 뒤 active 파일을 유지하고 review 준비 완료를 보고한다. 외부 live one-shot 권한이나 credential이 없으면 임의 대체/재시도하지 말고 exact preflight/명령/출력/재개 조건만 evidence에 기록한다. 사용자 질문, user-input 호출, stop 파일/next-state 분류, archive, `complete.log` 작성은 official review만 수행한다.

## Background

Secret delivery와 migration이 끝나도 일반 CI에서 전체 slot path를 재현하는 fixture, 승인된 대표 credential 한 건의 one-shot evidence, canonical wire/public contract와 redaction/revocation runbook이 없으면 운영 완료를 판정할 수 없다. 이 작업은 구독 없는 deterministic three-process smoke를 상시화하고 live mode를 명시적 opt-in 한 번으로 제한한 뒤 구현과 contract/spec의 drift를 닫는다.

## Archive Evidence Snapshot

- `agent-task/m-principal-provider-credential-slot-routing/08+03,04,07_secure_delivery/complete.log`: 현재 missing; secure transport/lease/injection/revocation 구현 완료 근거가 필요하다.
- `agent-task/m-principal-provider-credential-slot-routing/09+08_migration_attribution/complete.log`: 현재 missing; mode/attribution 구현 완료 근거가 필요하다.
- 두 predecessor가 PASS한 뒤에만 S17의 “구현 완료” 전제를 충족한다. 구현자는 archive를 별도로 탐색하지 않는다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
- Task ids:
  - `slot-smoke`: Seulgi/vendor fixture와 대표 slot one-shot qualification
  - `contract-ops`: wire/public contract, spec, redaction/revocation 운영 절차 동기화
- Completion mode: check-on-pass

## Analysis

### Files Read

- `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
- `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md`
- `agent-contract/index.md`
- `agent-contract/inner/client-control-plane-wire.md`
- `agent-contract/inner/control-plane-edge-wire.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-spec/index.md`
- `agent-spec/control/control-plane-operations.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `agent-spec/input/openai-compatible-surface.md`
- `scripts/e2e-provider-capacity-smoke.sh`
- `scripts/e2e-control-plane-edge-wire.sh`
- `scripts/e2e-openai-ollama.sh`
- `packages/go/config/protocol_profile.go`
- `packages/go/config/protocol_profile_test.go`
- `configs/edge.yaml`
- `docs/edge-local-dev-guide.md`
- `docs/openai-usage-grafana.md`
- `Makefile`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`

### SDD Criteria

- 승인 SDD S16/`slot-smoke`, S17/`contract-ops`와 Evidence Map S16-S17을 대상으로 한다.
- S16 deterministic fixture에는 Seulgi Chat/Messages와 vendor/model/slot/profile 조합이 있고 CI는 외부 credential/구독 없이 PASS해야 한다. live one-shot은 한 대표 slot만 호출하며 provider/model/safe slot ref/date/revision/result만 남긴다.
- S17은 persistence/management/secure wire/auth 구현 완료 뒤 contract/spec index가 canonical 문서를 가리키고 redaction/revocation 절차가 구현과 일치하며 repository/task evidence에 raw secret이 없음을 요구한다.
- 따라서 default local smoke, `--live` hard gate, sanitized JSON schema, contract/spec drift audit와 secret scan을 completion checklist로 고정했다.

### Verification Context

- handoff는 없었다. `agent-test/local/edge-smoke.md`, 기존 process smoke scripts, SDD Evidence Map과 read-only host probes가 source다.
- External Verification Preflight: runner는 current host `/config/workspace/iop-s0`; branch `agent/dispatcher-work-log-artifact-loop`, HEAD `7da9d3884dbe2f32a9dc9aacbc50e236c15562e5`, tracking branch와 divergence 표시는 없지만 worktree는 credential/roadmap/contract/proto 관련 기존 변경으로 dirty다. 구현자는 이를 보존하고 자신의 exact claims만 편집한다.
- OS/arch `Linux 6.10.14-linuxkit aarch64`; tools: Go `1.26.2`, `/bin/openssl` 3.0.13, `protoc`, `flutter`, `sops`, `jq`, `curl`, `git`, `bash`, `sha256sum`, `netstat`, `nc`. `shellcheck`는 현재 없다; absence를 실패로 만들지 않고 `bash -n`을 필수로 사용한다.
- ports/process: `netstat -ltn`에서 18000/19080/19081 listener가 없었다. Smoke는 random loopback ports와 `/tmp/iop-credential-slot-smoke.*`에 fresh binaries/certs/config/DB를 만들고 cleanup한다.
- credential preflight는 내용을 복호화하지 않고 `/config/.config/sops/iop/openai.sops.yaml`, `anthropic.sops.yaml`, `gemini.sops.yaml`, `kimi.sops.yaml`, `/config/.config/sops/age/keys.txt` readable만 확인했다. 기본 live 후보는 OpenAI Chat one-shot이며 external host는 `api.openai.com`; model/availability는 실행 시 preflight한다.
- live call은 명시적 external authorization과 `IOP_ALLOW_LIVE_PROVIDER=1`이 필요하고 자동 재시도하지 않는다. 권한이 없거나 source/host/model이 unavailable이면 구현자는 deterministic 결과를 완료하고 exact blocker를 review evidence에 남겨 official reviewer의 `external-execution` 판정을 받는다.
- binaries/artifacts는 source에서 `/tmp`로 fresh build하며 repo에 runtime evidence를 만들지 않는다. sanitized one-line JSON stdout만 active review에 붙인다.
- confidence: deterministic path high, live availability medium.

### Test Coverage Gaps

- 기존 capacity/wire/OpenAI smokes는 principal credential slot create→projection→lease→Node injection→usage/revoke 전 cycle을 실행하지 않는다.
- Seulgi built-in profiles는 unit tests가 있으나 두 distinct slot/route와 secret-free fixture schema가 없다.
- live smoke rules는 direct-upstream credentials를 설명하지만 Control Plane slot을 통한 one-shot IOP path와 sanitized slot evidence가 없다.
- current contracts/specs still describe projection as dormant/plain transport and caller provider-auth forwarding, so 08/09 implementation과 drift한다.
- repository-wide secret scan command와 runbook의 incident revoke/rotate/lease-expiry verification sequence가 없다.

### Symbol References

- production symbol rename/remove는 없다.
- new Make target `test-credential-slot-smoke` is added to `.PHONY` and invokes exactly `./scripts/e2e-credential-slot-smoke.sh`; no other Make target references it.
- contract/spec ids remain unchanged, so indexes update descriptions/status only where necessary and do not add duplicate canonical pointers.

### Split Judgment

- stable child contract is qualification/evidence rather than production code: a deterministic full-cycle smoke plus canonical documentation can PASS independently after 08/09.
- predecessor `08` is missing at `agent-task/m-principal-provider-credential-slot-routing/08+03,04,07_secure_delivery/complete.log`.
- predecessor `09` is missing at `agent-task/m-principal-provider-credential-slot-routing/09+08_migration_attribution/complete.log`.
- `slot-smoke` and `contract-ops` are coupled because S17 explicitly assumes implementation complete and the same redaction/evidence schema governs both deterministic and live qualification. Separating docs first would canonize an unverified contract.

### Scope Rationale

- 이 packet은 production Go behavior를 바꾸지 않는다. smoke가 08/09 defect를 발견하면 이 plan에서 우회하지 않고 code-review가 후속 implementation plan을 만들게 evidence를 남긴다.
- live provider call은 한 번, 한 prompt, Chat 또는 Messages 하나, output cap 128 이하로 제한한다. model discovery 외 재시도, 장문, tool call, response body 저장은 금지한다.
- secret, ciphertext, key path content, alias, lease id, prompt/response body는 qualification record에 남기지 않는다.
- tracked public docs에는 현재 운영 절차만 두고 private environment endpoints/credentials를 넣지 않는다.

### Final Routing

- evaluation_mode `first-pass`; finalizer `finalize-task-policy.sh` pair.
- build closures 모두 true; scores `2,0,1,2,2` = G07, base/route `local-fit`; `large_indivisible_context=false`; positive risks `boundary_contract`, `variant_product` (2); recovery 0/false; canonical `PLAN-local-G07.md`.
- review closures 모두 true; scores `2,0,1,2,2` = G07; `official-review`, `CODE_REVIEW-cloud-G07.md`, Codex `gpt-5.6-sol` xhigh.
- capability gap은 없다. live authorization/availability는 구현 범위 결정을 막지 않고 declared external evidence gate로 처리한다.

## Implementation Checklist

- [ ] Add a credential-free deterministic Seulgi/vendor/slot fixture and full Control Plane-Edge-Node credential-slot smoke with TLS, attribution, and revoke checks.
- [ ] Add a separately gated, no-retry representative live one-shot that consumes a SOPS secret through stdin and emits only sanitized qualification JSON.
- [ ] Synchronize canonical inner/outer contracts, implementation specs, indexes, and the public redaction/revocation operating guide to the completed 08/09 behavior.
- [ ] Run fresh deterministic smoke, contract/spec pointer checks, repository/task secret scans, and either capture the authorized live one-shot or exact external-execution blocker evidence.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [TEST-1] Deterministic full-cycle credential slot smoke

**Problem:** `scripts/e2e-provider-capacity-smoke.sh` builds a useful loopback CP/Edge/Node stack but its config contains only a static Node token/provider and never creates a principal slot, applies a credential projection, acquires a lease, or revokes it.

**Solution:** Add a dedicated smoke that generates a test CA/three peer identities, external key manifest, SQLite CP DB, random ports and fake provider. Bootstrap one principal, create two same-model slots/routes including `seulgi_chat`/`seulgi_messages` fixtures, start credential-plane enabled CP/Edge/Node, call one route, and assert upstream auth, safe attribution and no fallback. Revoke the selected slot and assert a second request reaches neither lease nor upstream. All raw values are random in `/tmp` and scrubbed during cleanup.

Before (`Makefile:82-88`):

```make
test-e2e:
	./scripts/e2e-smoke.sh
	./scripts/e2e-openai-ollama.sh
	./scripts/e2e-control-plane-edge-wire.sh
```

After:

```make
test-credential-slot-smoke:
	./scripts/e2e-credential-slot-smoke.sh
```

**Modified Files and Checklist:**

- [ ] Add `scripts/fixtures/credential-slot-vendors.json` with secret-free OpenAI, Anthropic, Seulgi Chat and Seulgi Messages profiles/models/two-slot routes.
- [ ] Add `scripts/e2e-credential-slot-smoke.sh` with strict cleanup, random ports, fresh binaries/certs, deterministic fake provider, CP CLI bootstrap and exact assertions.
- [ ] Add `Makefile` target without adding this costlier full-cycle smoke to unrelated default unit targets.

**Test Strategy:** Default script mode is deterministic and network-isolated. Assertions cover profile auth variants, route-to-slot uniqueness, safe `credential_slot_ref`/revision metric, ciphertext-only DB grep, revoked second request, plaintext/no-cert failure and zero raw secret in captured logs/responses.

**Verification:** `bash -n scripts/e2e-credential-slot-smoke.sh && jq -e '.profiles | length >= 4 and any(.[]; .id == "seulgi_chat") and any(.[]; .id == "seulgi_messages")' scripts/fixtures/credential-slot-vendors.json >/dev/null && make test-credential-slot-smoke` must pass.

### [TEST-2] Explicit representative live one-shot

**Problem:** S16 requires one representative credential slot through the implemented IOP path, but existing live guidance calls providers directly and can leak broad output if reused as task evidence.

**Solution:** Add `--live --secret-stdin` to the new script behind `IOP_ALLOW_LIVE_PROVIDER=1`. It consumes exactly one plaintext secret from stdin, creates a temporary CP slot, sends one short non-stream Chat request through the TLS stack, disables retry, discards response content after structural success, and prints one JSON record with `provider`, `model`, `credential_slot_ref`, `credential_revision`, UTC `date`, and `result`. Default/current candidate is OpenAI `gpt-5-mini`, max completion tokens 128; caller may explicitly select an equally bounded configured fixture.

Before: no Edge/slot live command exists.

After:

```bash
SOPS_AGE_KEY_FILE=/config/.config/sops/age/keys.txt \
  sops --decrypt --input-type yaml --output-type binary /config/.config/sops/iop/openai.sops.yaml \
  | IOP_ALLOW_LIVE_PROVIDER=1 ./scripts/e2e-credential-slot-smoke.sh \
      --live --secret-stdin --profile openai --model gpt-5-mini
```

**Modified Files and Checklist:**

- [ ] Complete live gate, stdin-only secret handling, one-attempt timeout/cost cap and sanitized output in `scripts/e2e-credential-slot-smoke.sh`.
- [ ] Record only the sanitized stdout and exact command/exit status in `agent-task/m-principal-provider-credential-slot-routing/10+08,09_verification_contracts/CODE_REVIEW-cloud-G07.md`; never paste stderr that contains request headers.

**Test Strategy:** Self-test live mode with a loopback fake provider and sentinel secret, asserting sentinel absent from stdout/stderr/temp-after-cleanup and a second provider call cannot occur. The actual external call is run once only with explicit authorization; no automatic retry on failure.

**Verification:** `./scripts/e2e-credential-slot-smoke.sh --self-test-live-gate` must pass without network. If authorized, run the exact pipeline above once and require one sanitized JSON line/exit 0; otherwise record the authorization blocker and resume command exactly.

### [TEST-3] Canonical contract/spec/runbook synchronization

**Problem:** current canonical docs call projection dormant and CP-Edge/Edge-Node plain, describe caller provider-auth forwarding, and omit lease/revocation/redaction operations. That contradicts the post-08/09 implementation.

**Solution:** Update existing canonical documents in place. Define TLS identity/fail-closed behavior, projection apply, lease sensitive-field and expiry/revision semantics, managed/legacy mode, slot attribution, unchanged public schemas, and operator create/rotate/revoke/redact steps. Keep raw example values as placeholders and retain stable contract/spec ids/index pointers.

Before (`agent-contract/inner/control-plane-edge-wire.md` current summary):

```text
The generated projection messages are reserved only; current plain connector has no listener or runtime cache writer.
```

After:

```text
Credential-plane mode uses mTLS; fresh projection apply and scoped lease request/response are active, and sensitive payload bodies are never debug-logged.
```

**Modified Files and Checklist:**

- [ ] Update `agent-contract/inner/client-control-plane-wire.md`, `agent-contract/inner/control-plane-edge-wire.md`, `agent-contract/inner/edge-node-runtime-wire.md`, and `agent-contract/inner/edge-config-runtime-refresh.md`.
- [ ] Update `agent-contract/outer/openai-compatible-api.md` and `agent-contract/outer/anthropic-compatible-api.md` for explicit managed/legacy auth and caller provider-token rejection without changing response schemas.
- [ ] Update `agent-contract/index.md` only where current descriptions/status need synchronization; preserve canonical ids/paths.
- [ ] Update `agent-spec/control/control-plane-operations.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`, and `agent-spec/input/openai-compatible-surface.md` from actual code/test evidence.
- [ ] Update `agent-spec/index.md` status/summary only if matching spec status changes; do not add duplicate entries.
- [ ] Update `docs/edge-local-dev-guide.md` with external key/TLS startup, safe slot management, rotation/revoke, bounded lease expiry confirmation and incident redaction checklist.

**Test Strategy:** No new code test. Add deterministic pointer/drift searches to final verification and manually compare message/config names against generated proto/Go symbols. Every placeholder is syntactically obvious and contains no usable credential.

**Verification:** index-pointer commands and repository secret scan in Final Verification must pass with no broken canonical target or raw sentinel.

## Dependencies and Execution Order

1. Do not start until both `agent-task/m-principal-provider-credential-slot-routing/08+03,04,07_secure_delivery/complete.log` and `agent-task/m-principal-provider-credential-slot-routing/09+08_migration_attribution/complete.log` exist.
2. Implement TEST-1, then TEST-2 self-test/live gate, then TEST-3 from verified source and smoke behavior.
3. Run deterministic verification before the single optional external attempt. Never rerun the live attempt automatically.

## Modified Files Summary

| File | Items |
|------|-------|
| `scripts/fixtures/credential-slot-vendors.json` | TEST-1 |
| `scripts/e2e-credential-slot-smoke.sh` | TEST-1, TEST-2 |
| `Makefile` | TEST-1 |
| `agent-contract/inner/client-control-plane-wire.md` | TEST-3 |
| `agent-contract/inner/control-plane-edge-wire.md` | TEST-3 |
| `agent-contract/inner/edge-node-runtime-wire.md` | TEST-3 |
| `agent-contract/inner/edge-config-runtime-refresh.md` | TEST-3 |
| `agent-contract/outer/openai-compatible-api.md` | TEST-3 |
| `agent-contract/outer/anthropic-compatible-api.md` | TEST-3 |
| `agent-contract/index.md` | TEST-3 |
| `agent-spec/control/control-plane-operations.md` | TEST-3 |
| `agent-spec/runtime/edge-node-execution.md` | TEST-3 |
| `agent-spec/runtime/provider-pool-config-refresh.md` | TEST-3 |
| `agent-spec/input/openai-compatible-surface.md` | TEST-3 |
| `agent-spec/index.md` | TEST-3 |
| `docs/edge-local-dev-guide.md` | TEST-3 |
| `agent-task/m-principal-provider-credential-slot-routing/10+08,09_verification_contracts/CODE_REVIEW-cloud-G07.md` | TEST-1, TEST-2, TEST-3 |

## Final Verification

Run deterministic commands from `/config/workspace/iop-s0`. External command runs only once after explicit authorization.

```bash
command -v go && go version && go env GOROOT
command -v bash && command -v jq && command -v openssl && openssl version
command -v sops && command -v curl && command -v netstat
git status --short --branch
bash -n scripts/e2e-credential-slot-smoke.sh
jq -e '.profiles | length >= 4 and any(.[]; .id == "seulgi_chat") and any(.[]; .id == "seulgi_messages")' scripts/fixtures/credential-slot-vendors.json >/dev/null
./scripts/e2e-credential-slot-smoke.sh --self-test-live-gate
make test-credential-slot-smoke
go test -count=1 ./apps/control-plane/... ./apps/edge/... ./apps/node/... ./packages/go/...
python3 - <<'PY'
from pathlib import Path
for index in (Path('agent-contract/index.md'), Path('agent-spec/index.md')):
    text = index.read_text()
    for token in text.split('`')[1::2]:
        if token.startswith(('agent-contract/', 'agent-spec/')) and not Path(token).exists():
            raise SystemExit(f'broken pointer: {index}: {token}')
PY
rg --sort path -n --hidden --glob '!agent-task/archive/**' --glob '!*.sops.yaml' --glob '!go.sum' --glob '!PLAN-*.md' --glob '!CODE_REVIEW-*.md' 'BEGIN AGE PRIVATE KEY|sk-[A-Za-z0-9_-]{20,}' agent-contract agent-spec docs scripts agent-task/m-principal-provider-credential-slot-routing/10+08,09_verification_contracts
```

Expected: syntax/fixture/self-test/full-cycle/packages/pointer checks pass; final secret scan exits 1 with no match. If explicit live authorization is present, execute the one pipeline in TEST-2 exactly once and paste only its sanitized JSON plus exit status into the review; otherwise record the external-execution blocker and exact resume condition. After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
