<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_core plan=4 tag=REVIEW_REVIEW_API -->

# Credential Management Core Security and Token Completion

## For the Implementing Agent

Filling implementation-owned sections of `CODE_REVIEW-cloud-G06.md` is mandatory. Run every verification command, paste actual output, keep active files 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 conditions only in implementation-owned fields; do not ask the user, call user-input tools, create stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The management core now seals supplied provider secrets and hides encrypted envelopes, but it still propagates arbitrary sealer errors and envelope key metadata across the secret-blind service boundary. Its token surface lacks authenticated issuance/list operations, and checked test evidence does not actually cover buffer zeroization, failing-sealer no-mutation, token revocation, or stale route CAS. This follow-up closes that compact security and lifecycle boundary without activating network management or claiming S08 completion.

## Archive Evidence Snapshot

- Current pair after archive: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/plan_local_G07_3.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/code_review_cloud_G07_3.log`.
- Verdict: FAIL; Required 3, Suggested 0, Nit 0; `review_rework_count=2`, `evidence_integrity_failure=true`.
- Required repairs: prevent sensitive sealer and envelope-validation/key errors from escaping, add authenticated one-time token issuance and secret-blind token listing, and replace vacuous zeroization/sealer-failure/stale-CAS/token-lifecycle evidence.
- Fresh reviewer verification: both focused suites, the full credentialops race suite, Control Plane regression tests, predecessor checks, formatting, structural absence, and `git diff --check` passed; a focused reviewer probe failed because `CreateSlot` returned a sealer error containing `REVIEWER_PROVIDER_SECRET_SENTINEL`.
- Roadmap carryover: this remains a preparatory S08 foundation. PASS must not check `credential-management`; concrete at-rest sealing and confidential Client transport remain later S09/S10 work.

## Analysis

### Files Read

- `apps/control-plane/internal/credentialops/service.go`
- `apps/control-plane/internal/credentialops/service_test.go`
- `apps/control-plane/internal/credentialstore/principal.go`
- `apps/control-plane/internal/credentialstore/principal_test.go`
- `apps/control-plane/internal/credentialstore/slot.go`
- `apps/control-plane/internal/credentialstore/route.go`
- `apps/control-plane/internal/credentialstore/secret_envelope.go`
- `apps/control-plane/internal/wire/client.go`
- `proto/iop/control.proto`
- `agent-contract/inner/client-control-plane-wire.md`
- `agent-spec/control/control-plane-operations.md`
- `agent-ops/rules/project/domain/control-plane/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/control-plane-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`
- `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-task/archive/2026/08/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`
- `agent-task/archive/2026/08/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`

### SDD Criteria

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md`; status approved, lock released, no user review.
- Targeted foundation criterion: S08 / `credential-management`; Evidence Map row S04-S08 requires management authorization, principal isolation, lifecycle revisions, and secret-blind results.
- The checklist derives one-time additional-token issuance, digest-free listing, cross-principal token scoping, CAS behavior, provider-secret error redaction, and no-mutation evidence from that row. `Roadmap Targets` remains omitted because production sealing and confidential remote management required by S09/S10 are outside this packet.

### Verification Context

- No external verification handoff was supplied. Repository-native evidence came from local test rules, the Go module, credentialops/store tests, the approved SDD, the Client wire contract, and fresh reviewer commands.
- Preflight: `/config/.local/bin/go`, Go 1.26.2 linux/arm64, `GOROOT=/config/opt/go`, repository root `/config/workspace/iop-s0`, intentional dirty worktree preserved.
- Preconditions: archived `01_principal_store` and `02+01_credential_catalog` each have exactly one `complete.log`.
- Fresh baseline: focused and full race credentialops tests, Control Plane regression, formatting, vet, structural absence, and diff checks pass. A temporary focused probe proved sensitive sealer-error propagation and was removed after execution.
- No remote runner, external provider, credential, TLS endpoint, UI, protobuf generation, or full-cycle user flow is required because the service remains dormant and transport-neutral. Confidence: high.

### Test Coverage Gaps

- No service/store method issues an additional token for the authenticated principal or lists all of that principal's token metadata without digests.
- `TestServiceTokenLifecycle` creates an unrelated principal directly through the store and covers only self-disable; it never tests service issuance/list, cross-principal token refs, revocation, or token stale CAS.
- `fakeSealer.Seal` copies plaintext, so no test observes the service-owned buffer after deferred clearing.
- Missing-sealer tests cover only `nil`; they do not cover a sealer returning a sensitive error, an envelope rejected with key metadata, or prove create/rotate persistence remains unchanged on failure.
- `TestServiceRejectsStaleRevision` discards the route update error instead of producing and asserting a stale route revision.

### Symbol References

- No symbol is renamed or removed.
- `credentialops.DisableToken` and `credentialops.RevokeToken` are referenced only by `service_test.go`; no production caller exists.
- `credentialstore.CreatePrincipalWithToken` remains the principal-bootstrap operation. New additional-token issuance/list methods are additive and are consumed only by `credentialops.Service` and focused store/service tests in this packet.

### Split Judgment

- Keep one packet: additional-token issuance/list, token lifecycle authorization, stable sealing errors, and zeroization/no-mutation evidence share the same secret-blind service/store boundary. Splitting would leave either the checked management surface or its security evidence incomplete.
- Runtime predecessors 01 and 02 are satisfied by `agent-task/archive/2026/08/m-principal-provider-credential-slot-routing/01_principal_store/complete.log` and `agent-task/archive/2026/08/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`.

### Scope Rationale

- Modify only credentialstore principal token operations and credentialops service/tests. Reuse the existing schema, token digest generation, CAS rules, projection-generation bump, and safe DTO conversions.
- Do not change protobuf, ClientServer, Dart, CLI, config, server wiring, contracts, schema, concrete encryption/key loading, remote management, or credential reveal behavior.
- Keep raw IOP tokens confined to the authenticated service input and the one-time `CreateToken` result; list and lifecycle DTOs remain digest-free.

### Final Routing

- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
- Build closures all true; scores `1+1+1+2+1=G06`; base=`local-fit`, route=`recovery-boundary`, lane=`cloud`, canonical=`PLAN-cloud-G06.md`.
- Review closures all true; scores `1+1+1+2+1=G06`; route=`official-review`, lane=`cloud`, model=`gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G06.md`.
- `large_indivisible_context=false`; risks=`temporal_state`, `boundary_contract` (2); `review_rework_count=2`; `evidence_integrity_failure=true`; recovery boundary matched; capability gap none.

## Dependencies and Execution Order

1. Archived predecessors `01_principal_store` and `02+01_credential_catalog` are complete.
2. Add store additional-token issuance/list primitives, expose them through the authenticated service, then repair security and lifecycle tests.
3. Keep the plaintext Client wire unchanged.

## Implementation Checklist

- [ ] Add atomic additional-token issuance and principal-scoped token listing in credentialstore, then expose one-time `CreateToken` and digest-free `ListTokens` through the authenticated service.
- [ ] Replace raw sealer and envelope-validation/key error propagation with stable secret-blind errors while preserving owned-buffer zeroization and zero store mutation on create/rotate failure.
- [ ] Replace the incomplete token, zeroization, failing-sealer, cross-principal, and stale-CAS tests with deterministic service/store assertions for the complete known defect set.
- [ ] Run fresh formatting, vet, race, Control Plane regression, predecessor, structural-absence, probe-removal, and diff verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_API-1] Complete authenticated token issuance and listing

**Problem:** `apps/control-plane/internal/credentialops/service.go:521-547` exposes only token disable/revoke, and `apps/control-plane/internal/credentialstore/principal.go:99-161` can issue a token only while creating a new principal. The current checklist requires authenticated token create/list/lifecycle behavior, but `TestServiceTokenLifecycle` at `service_test.go:313-335` creates an unrelated principal and never tests issuance, listing, revocation, cross-principal refs, or stale token CAS.

**Solution:** Add store methods that atomically generate and insert another active token for an existing principal, bump projection generation, return raw token only in a distinct one-time result, and list all token metadata for one principal. Add `Service.CreateToken` and `Service.ListTokens`; authenticate first, return a one-time raw token only from create, convert list/lifecycle results to the existing digest-free `TokenRecord`, and preserve store CAS/not-found mapping.

Before (`apps/control-plane/internal/credentialops/service.go:521-547`):

```go
func (s *Service) DisableToken(ctx context.Context, rawIOPToken []byte, tokenRef string, revision int64) (TokenRecord, error) {
    principalID, _, err := s.authenticate(ctx, rawIOPToken)
    // ...
}
```

After:

```go
type IssuedToken struct {
    Token    TokenRecord
    RawToken string
}

func (s *Service) CreateToken(ctx context.Context, rawIOPToken []byte) (IssuedToken, error) {
    principalID, _, err := s.authenticate(ctx, rawIOPToken)
    // issue for principalID; return raw token once and a digest-free DTO
}

func (s *Service) ListTokens(ctx context.Context, rawIOPToken []byte) ([]TokenRecord, error) {
    principalID, _, err := s.authenticate(ctx, rawIOPToken)
    // list only principalID and remove digests from every DTO
}
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/internal/credentialstore/principal.go`: add existing-principal token issuance/list primitives with one-time raw output, principal scoping, and projection-generation bump.
- [ ] `apps/control-plane/internal/credentialstore/principal_test.go`: cover additional issuance, list ordering/statuses, raw-token non-persistence, missing principal, and generation mutation.
- [ ] `apps/control-plane/internal/credentialops/service.go`: add authenticated `CreateToken`/`ListTokens` and one-time versus digest-free DTO separation.
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: cover create/list/disable/revoke, cross-principal token refs, stale token CAS, and raw/digest absence.

**Test Strategy:** Add `TestAdditionalTokenIssueAndList` in `principal_test.go` and `TestServiceTokenCreateListLifecycle` plus `TestServiceRejectsCrossPrincipalTokenReferences` in `service_test.go`. Use two service-issued additional tokens so one active caller can disable/revoke another token; assert only create returns raw material, list/lifecycle DTOs contain no raw token or digest, stale mutations do not change revision, and other principals receive `ErrNotFound`.

**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/credentialstore ./apps/control-plane/internal/credentialops -run 'AdditionalToken|TokenCreateListLifecycle|CrossPrincipalToken|StaleRevision'` exits 0.

### [REVIEW_REVIEW_API-2] Secret-handling failure redaction and trustworthy security evidence

**Problem:** `apps/control-plane/internal/credentialops/service.go:233-246,279-291` wraps arbitrary sealer errors and returns unrecognized store envelope errors unchanged, so provider plaintext or `key_id`/version metadata can escape through service errors. `apps/control-plane/internal/credentialstore/secret_envelope.go:76` renders unknown key metadata. `service_test.go:52-59` copies plaintext before the deferred clear, `service_test.go:551-617` covers only a nil sealer, and `service_test.go:537-544` discards the route-update error; the checked redaction, zeroization, failure, and stale-CAS evidence is therefore absent.

**Solution:** Add stable secret-blind errors that never wrap or render injected sealer failures or store envelope validation/key details. Keep owned copies and deferred `clear` on both create and rotate. Replace the fake-only evidence with a retaining sealer that observes the same owned slice after return, a failing sealer whose error includes unique secret/ciphertext metadata sentinels, and an unknown-key envelope fixture; assert all retained bytes are zero, returned errors/logs contain no sentinels or key metadata, and store counts/revisions/envelopes remain unchanged. Perform one successful route update before replaying its old revision and assert `ErrStaleRevision`.

Before (`apps/control-plane/internal/credentialops/service.go:233-235`):

```go
sealed, err := s.sealer.Seal(ctx, owned, sealCtx)
if err != nil {
    return SlotRecord{}, fmt.Errorf("credentialops: seal: %w", err)
}
```

After:

```go
sealed, err := s.sealer.Seal(ctx, owned, sealCtx)
if err != nil {
    return SlotRecord{}, ErrSecretHandlingFailed
}
// Map envelope validation/key failures to the same stable public class.
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/internal/credentialops/service.go`: return stable secret-blind errors for sealer and envelope-validation/key failures from create/rotate without weakening owned-buffer clearing or typed error behavior.
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: add retaining/failing/unknown-key fixtures and complete zeroization, no-mutation, redaction, token/slot/route stale-CAS, and revoke assertions; remove dead setup and discarded errors.

**Test Strategy:** Add `TestServiceZeroizesOwnedProviderSecret`, `TestServiceSecretHandlingFailureIsBlindAndDoesNotMutate`, and strengthen `TestServiceRejectsStaleRevision`. Retain the exact sealer input slice, inspect it only after service return, run create and rotate success/failure/unknown-key variants, and use unique sentinels in sealer errors and envelope key metadata to prove responses/errors/logs remain secret-blind.

**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 -run 'Zeroizes|SecretHandlingFailure|SecretBlind|Redact|StaleRevision|RequiresSealer'` exits 0.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/control-plane/internal/credentialstore/principal.go` | REVIEW_REVIEW_API-1 |
| `apps/control-plane/internal/credentialstore/principal_test.go` | REVIEW_REVIEW_API-1 |
| `apps/control-plane/internal/credentialops/service.go` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2 |
| `apps/control-plane/internal/credentialops/service_test.go` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2 |
| `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/CODE_REVIEW-cloud-G06.md` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2 |

## Final Verification

Run from `/config/workspace/iop-s0`; fresh Go results are required and cached output is not acceptable.

1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1` — both predecessors remain uniquely complete.
2. `mkdir -p .cache/go-build .cache/go-cache` — workspace-local Go temp/cache paths exist.
3. `test -z "$(gofmt -d apps/control-plane/internal/credentialstore/principal.go apps/control-plane/internal/credentialstore/principal_test.go apps/control-plane/internal/credentialops/service.go apps/control-plane/internal/credentialops/service_test.go)"` — no Go formatting drift.
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/credentialops` — no vet findings.
5. `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/credentialstore ./apps/control-plane/internal/credentialops` — all store/service lifecycle, redaction, and race tests pass.
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — the Control Plane regression suite passes.
7. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` — no plaintext Client management operation exists.
8. `test ! -e apps/control-plane/internal/credentialops/reviewer_probe_test.go` — no reviewer probe remains.
9. `git diff --check` — no whitespace errors.

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