<!-- task=m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap plan=1 tag=REVIEW_API -->

# Harden Host-Local Principal Bootstrap

## For the Implementing Agent

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

## Background

The first implementation added the intended host-local command and passed its success-path verification, but review found three contract gaps. The empty-store check is not atomic with creation, stdout write failures are ignored, and the committed test does not guard the source-level plaintext-wire boundary required by the plan.

## Archive Evidence Snapshot

- Current pair after archive: `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/plan_local_G06_0.log`, `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/code_review_cloud_G06_0.log`.
- Verdict: FAIL with three Required findings, zero Suggested findings, and zero remaining Nits.
- Required scope: atomically admit only one first-principal caller across different aliases; propagate raw-token stdout failures; add a real source-level absence regression for proto, ClientServer, and Dart management surfaces.
- Fresh reviewer evidence: focused bootstrap tests, `go test -count=1 ./apps/control-plane/...`, `go vet ./apps/control-plane/...`, predecessor checks, structural search, `git diff --check`, and a compiled first-bootstrap/reopen-refusal cycle all passed; `evidence_integrity_failure=false`.
- Roadmap carryover: this packet remains preparatory and must not check `credential-management`; S08 still requires the later encrypted-store and confidential transport rollout.

## Analysis

### Files Read

- `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/PLAN-local-G06.md`
- `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/CODE_REVIEW-cloud-G06.md`
- `apps/control-plane/cmd/control-plane/main.go`
- `apps/control-plane/cmd/control-plane/credential_commands.go`
- `apps/control-plane/cmd/control-plane/credential_commands_test.go`
- `apps/control-plane/internal/credentialstore/store.go`
- `apps/control-plane/internal/credentialstore/schema.go`
- `apps/control-plane/internal/credentialstore/principal.go`
- `apps/control-plane/internal/credentialstore/principal_test.go`
- `apps/control-plane/internal/credentialstore/projection.go`
- `apps/control-plane/internal/wire/client.go`
- `proto/iop/control.proto`
- `apps/client/lib/iop_wire/client_wire_client.dart`
- `apps/client/lib/iop_wire/parser_map.dart`
- `agent-contract/inner/client-control-plane-wire.md`
- `agent-contract/inner/control-plane-edge-wire.md`
- `agent-spec/control/control-plane-operations.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 `[승인됨]`, lock released, no `USER_REVIEW.md`.
- S01 / `principal-store`: first issuance returns one raw token while persisting only its digest. The S01-S03 Evidence Map row requires lifecycle/persistence evidence, so the checklist retains concurrency, one-time output, restart, and redaction verification without reopening the already completed roadmap Task.
- S08 / `credential-management`: host-local bootstrap must not activate a remote bootstrap surface. The S04-S08 Evidence Map row requires management isolation evidence, so the checklist adds a committed source-level proto/ClientServer/Dart absence regression.
- This follow-up does not complete all S08 management operations and therefore intentionally omits `Roadmap Targets`.

### Verification Context

- No external handoff was supplied. Repository-native evidence came from the active pair, domain/local test rules, current source/tests, approved SDD, wire contracts, and the exact predecessor `complete.log` files listed above.
- Environment preflight: `/config/.local/bin/go`, Go 1.26.2 linux/arm64, `GOROOT=/config/opt/go`, workspace `/config/workspace/iop-s0`, intentional dirty worktree with sibling milestone changes.
- Fresh reviewer commands passed: focused bootstrap tests, full Control Plane regression, Control Plane vet, predecessor count, deterministic wire search, `git diff --check`, and a workspace-local compiled CLI first-bootstrap/reopen-refusal cycle with token output retained only in shell memory.
- Required verification stays in the current checkout, uses temporary SQLite state, and requires no external service, credential, device, or user authorization. Fresh `-count=1`/`-count=50` results are required; cached Go output is not accepted. Confidence: high.

### Test Coverage Gaps

- Existing-principal refusal is covered only sequentially. There is no regression for two different aliases racing against an empty store, and the current alias-unique transaction permits both PostgreSQL transactions to commit.
- Successful stdout emission is covered, but no failing writer proves the CLI returns a non-zero error instead of silently losing the one-time token.
- Cobra registration and flags are covered, but the current `TestBootstrapIsNotExposedViaWire` does not read proto, ClientServer, or Dart sources and cannot detect a new remote management surface.
- Restart persistence, digest-only storage, stderr redaction, missing alias, missing DB config, focused package regression, and compiled sequential CLI behavior are covered.

### Symbol References

- No symbol is renamed or removed.
- Add `credentialstore.Store.CreateFirstPrincipalWithToken`; the only production call site is `apps/control-plane/cmd/control-plane/credential_commands.go`. `CreatePrincipalWithToken` remains for later authenticated principal creation and existing callers.

### Split Judgment

- Keep one packet. Empty-store admission and principal/token creation form one transaction invariant, while writer failure and wire-absence tests define the same CLI success and exposure boundary; none is an independently complete host-local bootstrap fix.
- Runtime predecessors encoded by `06+01,02_host_local_bootstrap` 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 the credential-store first-bootstrap primitive, its tests, the host-local command, its tests, and the active review evidence file.
- Do not modify protobuf, ClientServer, Dart client/parser, configs, server wiring, credential slot encryption, management RPCs, or secure transport. Those sources are read-only inputs to the absence guard.
- Do not complete or claim roadmap Task `credential-management`; later management, at-rest sealer, and confidential transport packets remain required.

### Final Routing

- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, mode=`pair`; all build/review scope, context, verification, evidence, ownership, and decision closures are true; capability gap: none.
- Build scores: `scope_coupling=1`, `state_concurrency=2`, `blast_irreversibility=2`, `evidence_diagnosis=1`, `verification_complexity=1` => G07; base/final route=`local-fit`, lane=`local`, canonical=`PLAN-local-G07.md`.
- Review scores: `1+2+2+1+1=G07`; route=`official-review`, lane=`cloud`, adapter=`codex`, model=`gpt-5.6-sol`, reasoning=`xhigh`, canonical=`CODE_REVIEW-cloud-G07.md`.
- `large_indivisible_context=false`; positive risks=`temporal_state`, `concurrent_consistency`, `boundary_contract` (3); `review_rework_count=1`; `evidence_integrity_failure=false`; risk/recovery boundaries are false.

## Dependencies and Execution Order

1. `01_principal_store` is satisfied by `agent-task/archive/2026/08/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`.
2. `02+01_credential_catalog` is satisfied by `agent-task/archive/2026/08/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`.
3. Implement the store-owned atomic first-principal primitive before switching the CLI and adding failure/boundary regressions.

## Implementation Checklist

- [ ] Replace the command's list-then-create sequence with one store-owned atomic first-principal operation and prove concurrent different aliases yield exactly one committed principal/token.
- [ ] Propagate raw-token stdout write failures and prove a failing writer cannot produce a successful command result or leak the token into the error.
- [ ] Add a committed source-level absence regression across proto, ClientServer, and Dart management surfaces while retaining the CLI-only command-structure check.
- [ ] Run fresh focused, repeated race, Control Plane regression, vet, deterministic absence, compiled CLI, formatting, and diff verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Make first-principal admission atomic

**Problem:** `apps/control-plane/cmd/control-plane/credential_commands.go:61-73` calls `ListPrincipals` and `CreatePrincipalWithToken` separately. `apps/control-plane/internal/credentialstore/principal.go:119-156` only protects duplicate aliases, so two empty-store callers with distinct aliases can both commit.

**Solution:** Add `Store.CreateFirstPrincipalWithToken`. Refactor the existing insert/token-generation body into one transaction helper, and in the first-only path acquire the durable `principal_projection_state` singleton row with a no-op `UPDATE` before counting all principals. Verify one affected singleton row, reject any non-empty store with `ErrPrincipalAlreadyExists`, then insert the principal/token and bump generation in that same transaction. Both SQLite and PostgreSQL serialize competing first-bootstrap writers on that row; keep `CreatePrincipalWithToken` unchanged for later principal creation. Switch the CLI to the new method and use `cmd.Context()`.

Before (`apps/control-plane/cmd/control-plane/credential_commands.go:61-73`):

```go
principals, err := store.ListPrincipals(ctx)
if err != nil {
    return fmt.Errorf("list principals: %w", err)
}
if len(principals) > 0 {
    return fmt.Errorf("principal already exists: refuse to overwrite the first principal")
}

issued, err := store.CreatePrincipalWithToken(ctx, credentialstore.CreatePrincipalInput{Alias: alias})
```

After:

```go
issued, err := store.CreateFirstPrincipalWithToken(cmd.Context(), credentialstore.CreatePrincipalInput{Alias: alias})
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/internal/credentialstore/principal.go`: add the store-owned singleton-locked first-principal transaction without changing ordinary principal creation.
- [ ] `apps/control-plane/internal/credentialstore/principal_test.go`: add sequential existing-store and concurrent different-alias regressions that assert exactly one success and one persisted principal/token.
- [ ] `apps/control-plane/cmd/control-plane/credential_commands.go`: replace the split list/create flow with the atomic method and command context.

**Test Strategy:** Add `TestCreateFirstPrincipalWithTokenRejectsNonEmptyStore` and `TestCreateFirstPrincipalWithTokenAllowsExactlyOneConcurrentCaller` with a file-backed SQLite store, a synchronized start, distinct aliases, final row counts, and no raw-token logging. Retain all ordinary `CreatePrincipalWithToken` tests.

**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=50 -race ./apps/control-plane/internal/credentialstore -run 'CreateFirstPrincipalWithToken'` exits zero with exactly-one assertions stable across repetitions.

### [REVIEW_API-2] Fail closed when raw-token stdout delivery fails

**Problem:** `apps/control-plane/cmd/control-plane/credential_commands.go:77` ignores both the byte count and error from `fmt.Fprintln`, so a broken stdout writer still yields a successful exit after the one-time token is committed.

**Solution:** Check the `fmt.Fprintln` error and return a redacted wrapped error. Never include the token or writer buffer in that error.

Before (`apps/control-plane/cmd/control-plane/credential_commands.go:77-78`):

```go
fmt.Fprintln(cmd.OutOrStdout(), issued.RawToken)
return nil
```

After:

```go
if _, err := fmt.Fprintln(cmd.OutOrStdout(), issued.RawToken); err != nil {
    return fmt.Errorf("write raw token: %w", err)
}
return nil
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/cmd/control-plane/credential_commands.go`: propagate a redacted stdout write error.
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: inject a deterministic failing writer and assert non-success plus token-free error/stderr.

**Test Strategy:** Add `TestPrincipalBootstrapReturnsStdoutWriteError` with a local writer that returns `(0, sentinelError)`. Assert `errors.Is`, a non-nil command error, no raw token in the error/stderr, and no false success.

**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 'PrincipalBootstrap|Bootstrap'` exits zero.

### [REVIEW_API-3] Make the plaintext-wire absence guard real

**Problem:** `apps/control-plane/cmd/control-plane/credential_commands_test.go:332-370` checks only Cobra flags and `Run`. A new management protobuf, ClientServer listener, or Dart method/parser does not affect those assertions, contrary to API-3.

**Solution:** Keep the command-structure test, then add a repository-root-relative source guard using `runtime.Caller` and `os.ReadFile`. Fail on bootstrap/principal-management message names in `proto/iop/control.proto`, credential store/service imports or management listeners in `apps/control-plane/internal/wire/client.go`, and bootstrap/principal/credential management methods or parsers in the two Dart wire files. Allow the already reserved secret-free `PrincipalProjection*` types and do not modify any guarded production source.

Before (`apps/control-plane/cmd/control-plane/credential_commands_test.go:332-336`):

```go
func TestBootstrapIsNotExposedViaWire(t *testing.T) {
    // This test verifies through the command structure, not by scanning source.
    root := rootCmd()
```

After:

```go
func TestBootstrapCommandRemainsCLIOnly(t *testing.T) { /* Cobra assertions */ }
func TestBootstrapManagementIsAbsentFromWireSources(t *testing.T) { /* exact source allowlist/denylist */ }
```

**Modified Files and Checklist:**

- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: separate command-structure assertions from exact proto/Go/Dart source absence assertions.

**Test Strategy:** Add `TestBootstrapManagementIsAbsentFromWireSources` with explicit file paths and deny patterns that do not reject the reserved `PrincipalProjection*` foundation. The test must fail if a credential-management request/listener or Dart management method/parser is introduced on the current Client wire.

**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.*(CLI|Wire|Source)|PrincipalBootstrap'` exits zero.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/control-plane/internal/credentialstore/principal.go` | REVIEW_API-1 |
| `apps/control-plane/internal/credentialstore/principal_test.go` | REVIEW_API-1 |
| `apps/control-plane/cmd/control-plane/credential_commands.go` | REVIEW_API-1, REVIEW_API-2 |
| `apps/control-plane/cmd/control-plane/credential_commands_test.go` | REVIEW_API-2, REVIEW_API-3 |
| `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/CODE_REVIEW-cloud-G07.md` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |

## Final Verification

Run from `/config/workspace/iop-s0`; all Go tests must be fresh.

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 runtime predecessors exist exactly once.
2. `gofmt -d apps/control-plane/internal/credentialstore/principal.go apps/control-plane/internal/credentialstore/principal_test.go apps/control-plane/cmd/control-plane/credential_commands.go apps/control-plane/cmd/control-plane/credential_commands_test.go` — no output.
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=50 -race ./apps/control-plane/internal/credentialstore -run 'CreateFirstPrincipalWithToken'` — repeated concurrent first-principal regression passes.
4. `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 'PrincipalBootstrap|Bootstrap'` — stdout failure, CLI-only, source absence, restart, refusal, and redaction tests pass.
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — Control Plane regression passes.
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/...` — no findings.
7. `if rg --sort path -n '(Bootstrap(Request|Response)|Credential(Operation|Management)|PrincipalManagement|CreatePrincipalRequest|IssueTokenRequest|AddRequestListenerTyped.*(Bootstrap|Credential|Principal))' proto/iop/control.proto apps/control-plane/internal/wire/client.go apps/client/lib/iop_wire/client_wire_client.dart apps/client/lib/iop_wire/parser_map.dart; then exit 1; else exit 0; fi` — no plaintext Client management surface is present.
8. Run the compiled host-local full cycle below — first bootstrap succeeds with one token held only in shell memory, reopened retry refuses, and exact temporary files are removed without recording the token.

```bash
set -euo pipefail
bootstrap_verify_dir=$(mktemp -d /config/workspace/iop-s0/.cache/bootstrap-followup.XXXXXX)
case "$bootstrap_verify_dir" in
  /config/workspace/iop-s0/.cache/bootstrap-followup.*) ;;
  *) exit 97 ;;
esac
bootstrap_verify_bin="$bootstrap_verify_dir/control-plane"
bootstrap_verify_db="$bootstrap_verify_dir/bootstrap.db"
cleanup_bootstrap_verify() {
  for bootstrap_verify_path in "$bootstrap_verify_db-shm" "$bootstrap_verify_db-wal" "$bootstrap_verify_db" "$bootstrap_verify_bin"; do
    if [ -e "$bootstrap_verify_path" ]; then
      unlink "$bootstrap_verify_path"
    fi
  done
  rmdir "$bootstrap_verify_dir"
}
trap cleanup_bootstrap_verify EXIT
GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go build -o "$bootstrap_verify_bin" ./apps/control-plane/cmd/control-plane
bootstrap_verify_token=$(IOP_DATABASE_URL="$bootstrap_verify_db" "$bootstrap_verify_bin" --config configs/control-plane.yaml principal bootstrap --alias verify-first)
test "${#bootstrap_verify_token}" -eq 64
case "$bootstrap_verify_token" in
  *[!0-9a-f]*) exit 98 ;;
esac
set +e
bootstrap_verify_retry=$(IOP_DATABASE_URL="$bootstrap_verify_db" "$bootstrap_verify_bin" --config configs/control-plane.yaml principal bootstrap --alias verify-second 2>&1)
bootstrap_verify_status=$?
set -e
test "$bootstrap_verify_status" -ne 0
rg -q 'already exists' <<<"$bootstrap_verify_retry"
if grep -F -q -- "$bootstrap_verify_token" <<<"$bootstrap_verify_retry"; then
  exit 96
fi
printf '%s\n' 'PASS: compiled bootstrap emitted once and reopened retry refused without token echo'
```

9. `git diff --check` — no whitespace errors.

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