<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_ops plan=0 tag=API -->

# Credential Management Operations

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G10.md` is mandatory. Run every verification command, paste actual output/notes, keep the active pair in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence, attempted commands/output, and resume condition only in implementation-owned fields; do not ask the user, invoke user-input tools, create stop files, or classify next state.

## Background

Control Plane currently exposes only hello/status wire behavior and a `serve` CLI command. The approved design requires a host-local bootstrap path plus authenticated principal-scoped create/list/update/rotate/disable/revoke operations, with raw IOP/provider secrets accepted or emitted only at the relevant one-time boundary.

## 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:
  - `credential-management`: host-local bootstrap 및 principal-scoped management operations
- Completion mode: check-on-pass

## Analysis

### Files Read

- `proto/iop/control.proto`
- `proto/gen/iop/control.pb.go`
- `apps/client/lib/gen/proto/iop/control.pb.dart`
- `apps/client/lib/gen/proto/iop/control.pbenum.dart`
- `apps/client/lib/gen/proto/iop/control.pbjson.dart`
- `apps/client/lib/gen/proto/iop/control.pbserver.dart`
- `apps/control-plane/cmd/control-plane/main.go`
- `apps/control-plane/cmd/control-plane/server.go`
- `apps/control-plane/cmd/control-plane/config_test.go`
- `apps/control-plane/internal/wire/client.go`
- `apps/control-plane/internal/wire/client_test.go`
- `apps/client/lib/iop_wire/client_wire_client.dart`
- `apps/client/lib/iop_wire/parser_map.dart`
- `apps/client/test/iop_wire/client_wire_client_test.dart`
- `apps/client/test/iop_wire/parser_map_test.dart`
- `apps/client/test/iop_wire/generated_proto_import_test.dart`
- `configs/control-plane.yaml`
- `agent-contract/inner/client-control-plane-wire.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
- Target: S08 / `credential-management`.
- Evidence Map row: S04-S08의 management authorization 근거 중 S08.
- Checklist는 remote bootstrap 부재, host-local first principal/token issuance, authenticated principal-only token/slot/route mutations, optimistic revision, bootstrap/create/list/update/rotate/disable/revoke, cross-principal rejection, raw secret input/one-time output 및 list/log 비노출을 검증한다.

### Verification Context

- handoff 없음. existing Cobra/config, client WS parser, protobuf generation, client-control-plane contract, local CP/client test profiles에서 검증을 도출했다.
- Tools: Go 1.26.2, protoc 29.3, Go/Dart generators present, Flutter 3.41.5/Dart 3.11.3. `make -n proto proto-dart` confirms canonical generation commands.
- Preconditions: 01 and 02 complete logs. No external provider or running shared service is required; loopback WS and temp SQLite/key files suffice.
- Default Go temp/cache is unusable and `/tmp` noexec, so ignored workspace `.cache` paths are mandatory. Confidence: high.

### Test Coverage Gaps

- ClientServer parser map recognizes only hello request/response; no authenticated management message or authorization test exists.
- CLI has only `serve`; no host-local bootstrap or stdout/log secret hygiene test exists.
- Client Dart parser/method tests know only hello.
- No encryption ingress boundary exists between plaintext provider credential input and opaque store envelope.

### Symbol References

- No rename/remove.
- `rootCmd` adds commands at `main.go:61` and existing `serveCmd` remains.
- `wire.NewClientServer` call sites: `server.go:42` and `client_test.go`; preserve constructor compatibility through options.
- Dart `ClientWireClient` public hello method remains source-compatible; management methods are additive.

### Split Judgment

- Host CLI and authenticated wire operations share the same authorization/service/sealing boundary; splitting would duplicate or temporarily bypass the raw-secret invariant. Keep them atomic.
- Predecessor 01 `01_principal_store`: missing active `complete.log`, no archived candidate.
- Predecessor 02 `02+01_credential_catalog`: missing active `complete.log`, no archived candidate.
- This packet can PASS without projection/auth packet 03 because it authenticates management requests directly against the durable token store; later projection observes the transactionally bumped generation.

### Scope Rationale

- Implement only the sealing ingress needed to hand ciphertext envelopes to the packet-02 store: AES-256-GCM with a host-local external key file and non-secret key id. Do not claim or check Epic-3 `secret-at-rest`; key rotation policy, restart delivery, Edge decrypt, execution-time header injection, and plaintext zeroization evidence remain Epic 3.
- No remote bootstrap operation is added. Bootstrap opens DB/key locally from the Control Plane process command.
- No UI screens are added; Dart wire support is the typed client contract only.
- Roadmap/spec/SDD and central Agent-Ops files remain untouched.

### Final Routing

- `status=routed`, `evaluation_mode=first-pass`, finalizer=`finalize-task-policy.sh`, pair mode.
- Build closures all true; scores `2+2+2+2+2=G10`; base/route=`grade-boundary`, cloud, canonical=`PLAN-cloud-G10.md`.
- Review closures all true; scores `2+2+2+2+2=G10`; `official-review`, cloud, `gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G10.md`.
- `large_indivisible_context=false`; risks=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product` (4), risk boundary matched but grade basis retained; rework=0; integrity failure=false; no capability gap.

## Dependencies and Execution Order

1. `01_principal_store` must create `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`.
2. `02+01_credential_catalog` must create `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`.
3. Implement service/sealer, then WS operations/client, then host-local CLI on the same service.
4. No dependency absent from `05+01,02_management_ops` may be added.

## Implementation Checklist

- [ ] Add a principal-authenticated credential-operation service and host-local key-file sealer that never gives plaintext to the repository.
- [ ] Add typed Client-Control Plane management requests/responses for create/list/update/rotate/disable/revoke with principal and revision authorization.
- [ ] Add a host-local-only bootstrap CLI that emits the first raw IOP token once and has no remote wire equivalent.
- [ ] Update generated Go/Dart code, Dart client/parser methods, config, and the inner wire contract.
- [ ] Prove bootstrap and every operation, cross-principal denial, CAS conflict, ciphertext persistence, and secret non-disclosure with fresh Go/Dart tests.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Centralize authorization and sealing

**Problem:** `apps/control-plane/internal/wire/client.go:37-46` parses only hello messages, and there is no service that authenticates a durable principal token or seals provider input before storage.

**Solution:** Add `credentialops.Service` over the credential store. Authenticate raw IOP tokens by SHA-256 lookup, authorize every target principal against that identity, require expected revision for mutations, and return typed stable errors. Add an AES-256-GCM `Sealer` loaded from a base64 32-byte host-local key file; key id/nonce/ciphertext only cross into `credentialstore`.

Before (`apps/control-plane/internal/wire/client.go:37-46`):

```go
parserMap := proto_socket.ParserMap{
    proto_socket.TypeNameOf(&iop.ClientHelloRequest{}): func(b []byte) (proto.Message, error) {
        req := &iop.ClientHelloRequest{}
        return req, proto.Unmarshal(b, req)
    },
```

After service boundary:

```go
principal, err := svc.Authenticate(ctx, rawIOPToken)
if err != nil { return ErrUnauthenticated }
if principal.ID != input.PrincipalID { return ErrForbidden }
envelope, err := svc.Sealer.Seal(input.ProviderSecret)
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/internal/credentialops/sealer.go`: load/validate key file and seal with random nonce/AAD; never log key/plaintext.
- [ ] `apps/control-plane/internal/credentialops/sealer_test.go`: assert round-trip only in test helper, nonce uniqueness, missing/invalid key failures, ciphertext-only store input.
- [ ] `apps/control-plane/internal/credentialops/service.go`: authenticate, authorize, map lifecycle/CAS errors, and expose typed token/slot/route operations.
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: cover own-principal success, cross-principal denial, lifecycle, CAS, one-time outputs, and captured log redaction.

**Test Strategy:** Write `TestServiceAuthenticatesDigestAndScopesPrincipal`, `TestServiceRejectsCrossPrincipalOperations`, `TestServiceCASConflicts`, `TestSealerRejectsMissingKey`, and `TestProviderSecretIsStoredOnlyAsCiphertext`. Use temp key/SQLite files and injected logger/random reader.

**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops` passes.

### [API-2] Expose typed authenticated wire operations

**Problem:** `apps/control-plane/internal/wire/client.go:62-73` only handles hello and reports ready without an authenticated management surface.

**Solution:** Add additive protobuf messages for typed token/slot/route operations and stable status/error fields. Each management request includes the caller IOP token as input-only auth material; the server passes it directly to `credentialops.Service`, never logs it, and response/list types omit secrets. Only create-token returns a one-time token field. Extend Go/Dart parser maps and Dart client methods.

Before (`apps/control-plane/internal/wire/client.go:62-73`):

```go
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.ClientHelloRequest) (*iop.ClientHelloResponse, error) {
    logger.Info("client hello request received",
        zap.String("client_id", req.ClientId),
        zap.String("client_version", req.ClientVersion),
    )
    return &iop.ClientHelloResponse{Ready: true, Protocol: Protocol}, nil
})
```

After:

```go
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.CredentialOperationRequest) (*iop.CredentialOperationResponse, error) {
    return handleCredentialOperation(context.Background(), operations, req), nil
})
```

**Modified Files and Checklist:**

- [ ] `proto/iop/control.proto`: add additive management operation/request/response/record messages and oneofs.
- [ ] `proto/gen/iop/control.pb.go`: regenerate Go output.
- [ ] `apps/client/lib/gen/proto/iop/control.pb.dart`: regenerate Dart messages.
- [ ] `apps/client/lib/gen/proto/iop/control.pbenum.dart`: regenerate Dart enums.
- [ ] `apps/client/lib/gen/proto/iop/control.pbjson.dart`: regenerate Dart JSON metadata.
- [ ] `apps/client/lib/gen/proto/iop/control.pbserver.dart`: regenerate Dart server helpers.
- [ ] `apps/control-plane/internal/wire/client.go`: add parser/listener/option and error mapping without auth-token logging.
- [ ] `apps/control-plane/internal/wire/client_test.go`: test every operation class, missing/invalid/cross-principal auth, one-time token, and response secrecy.
- [ ] `apps/control-plane/cmd/control-plane/server.go`: construct credentialops service and inject into ClientServer when store is configured.
- [ ] `apps/client/lib/iop_wire/parser_map.dart`: register all management responses.
- [ ] `apps/client/lib/iop_wire/client_wire_client.dart`: add typed management methods while keeping hello behavior.
- [ ] `apps/client/test/iop_wire/parser_map_test.dart`: prove message parsing.
- [ ] `apps/client/test/iop_wire/client_wire_client_test.dart`: prove request/response typing, status errors, and no secret fields on list records.
- [ ] `apps/client/test/iop_wire/generated_proto_import_test.dart`: import/construct new generated messages.

**Test Strategy:** Add Go table test `TestCredentialOperationAuthorizationAndLifecycle` for bootstrap absence plus create/list/update/rotate/disable/revoke. Add Dart tests for each oneof and parser type. Assert malformed/unknown operations fail closed and list responses have no raw secret/token field.

**Verification:** `make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/wire ./apps/control-plane/cmd/control-plane && make client-test` passes.

### [API-3] Add host-local bootstrap only

**Problem:** `apps/control-plane/cmd/control-plane/main.go:49-62` registers only `serve`, so there is no safe first-principal bootstrap path.

**Solution:** Add `principal bootstrap --alias ...` as a local command that loads config, opens the DB directly, refuses when any principal already exists unless an explicit idempotent lookup proves the same request, issues the first raw IOP token once to command stdout, and writes no token through zap/errors. Add key-file path config for slot operations; the tracked YAML stores only an empty path/key id, never key bytes.

Before (`apps/control-plane/cmd/control-plane/main.go:59-62`):

```go
}
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
root.AddCommand(serveCmd())
return root
```

After:

```go
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
root.AddCommand(serveCmd(), principalCmd())
return root
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/cmd/control-plane/main.go`: add credential key-file config/env override and register principal CLI.
- [ ] `apps/control-plane/cmd/control-plane/credential_commands.go`: implement direct host-local bootstrap with explicit stdout writer.
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: test first bootstrap, second refusal, reopen, stdout one-time token, stderr/log redaction, and absence of remote bootstrap.
- [ ] `apps/control-plane/cmd/control-plane/config_test.go`: test key-file path override without reading/logging contents.
- [ ] `configs/control-plane.yaml`: document empty non-secret credential key file/key id settings.
- [ ] `agent-contract/inner/client-control-plane-wire.md`: document management authentication, operations, revision/status/errors, secret fields, and explicit no-remote-bootstrap rule.

**Test Strategy:** Write `TestPrincipalBootstrapEmitsTokenOnce`, `TestPrincipalBootstrapRefusesSecondRoot`, `TestBootstrapLogsNeverContainToken`, and verify protobuf has no bootstrap request via deterministic `rg` command.

**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'Bootstrap|Credential|Config'` passes.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/control-plane/internal/credentialops/sealer.go` | API-1 |
| `apps/control-plane/internal/credentialops/sealer_test.go` | API-1 |
| `apps/control-plane/internal/credentialops/service.go` | API-1 |
| `apps/control-plane/internal/credentialops/service_test.go` | API-1 |
| `proto/iop/control.proto` | API-2 |
| `proto/gen/iop/control.pb.go` | API-2 |
| `apps/client/lib/gen/proto/iop/control.pb.dart` | API-2 |
| `apps/client/lib/gen/proto/iop/control.pbenum.dart` | API-2 |
| `apps/client/lib/gen/proto/iop/control.pbjson.dart` | API-2 |
| `apps/client/lib/gen/proto/iop/control.pbserver.dart` | API-2 |
| `apps/control-plane/internal/wire/client.go` | API-2 |
| `apps/control-plane/internal/wire/client_test.go` | API-2 |
| `apps/control-plane/cmd/control-plane/server.go` | API-2 |
| `apps/client/lib/iop_wire/parser_map.dart` | API-2 |
| `apps/client/lib/iop_wire/client_wire_client.dart` | API-2 |
| `apps/client/test/iop_wire/parser_map_test.dart` | API-2 |
| `apps/client/test/iop_wire/client_wire_client_test.dart` | API-2 |
| `apps/client/test/iop_wire/generated_proto_import_test.dart` | API-2 |
| `apps/control-plane/cmd/control-plane/main.go` | API-3 |
| `apps/control-plane/cmd/control-plane/credential_commands.go` | API-3 |
| `apps/control-plane/cmd/control-plane/credential_commands_test.go` | API-3 |
| `apps/control-plane/cmd/control-plane/config_test.go` | API-3 |
| `configs/control-plane.yaml` | API-3 |
| `agent-contract/inner/client-control-plane-wire.md` | API-3 |
| `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/CODE_REVIEW-cloud-G10.md` | API-1, API-2, API-3 |

## Final Verification

Run from `/config/workspace/iop-s0`; fresh Go/Dart results are required.

1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log && test -f agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log` — both predecessors complete.
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version` — toolchains resolve.
3. `mkdir -p .cache/go-build .cache/go-cache` — executable Go temp/cache paths exist.
4. `make proto && make proto-dart` — generated sources are current.
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/...` — no vet findings.
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire ./apps/control-plane/cmd/control-plane` — service/store/wire/CLI lifecycle and race tests pass.
7. `make client-test` — Dart wire/parser tests pass.
8. `cd apps/client && flutter analyze` — client analysis has no findings.
9. `if rg --sort path -n 'Bootstrap.*Request|bootstrap.*request' proto/iop/control.proto agent-contract/inner/client-control-plane-wire.md; then exit 1; else exit 0; fi` — no remote bootstrap request contract exists.
10. `git diff --check` — no whitespace errors.

After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
