<!-- task=m-iop-agent-cli-runtime/16+13,15_bootstrap_composition plan=1 tag=REVIEW_API -->

# Repair Bootstrap Dependency Identity Validation

## For the Implementing Agent

Filling implementation-owned sections in `CODE_REVIEW-*-G??.md` is mandatory. Run every verification command, paste actual notes and stdout/stderr, keep the active files in place, and report ready for review. Finalization belongs only to the code-review skill. 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, classify the next state, archive logs, or write `complete.log`.

## Background

The first bootstrap composition review found that constructor validation does not preserve one component identity through the `bootstrap` and `host` boundary. Typed-nil components can panic, host-resolved names can collide after validation, and duplicate errors do not expose the documented sentinel. This follow-up repairs that single construction invariant without changing lifecycle policy or wiring concrete application services.

## Archive Evidence Snapshot

- Prior pair after archive:
  - `agent-task/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/plan_local_G05_0.log`
  - `agent-task/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/code_review_cloud_G05_0.log`
- Verdict: `FAIL`
- Findings: Required `1`, Suggested `0`, Nit `0`.
- Required: reject typed-nil components without panic, preserve one resolved component name into `host.Host`, and make duplicate errors match `ErrDuplicateName`.
- Affected files: `apps/agent/internal/bootstrap/module.go`, `apps/agent/internal/bootstrap/module_test.go`.
- Verification evidence: fresh focused tests, race tests, vet, formatting, and `git diff --check` passed; one focused reviewer probe failed all three missing validation variants.
- Roadmap carryover: none. This remains a bootstrap prerequisite and does not complete a Milestone Task id.

## Analysis

### Files Read

- `apps/agent/internal/bootstrap/module.go`
- `apps/agent/internal/bootstrap/module_test.go`
- `apps/agent/internal/host/ports.go`
- `apps/agent/internal/host/host.go`
- `apps/agent/internal/host/host_test.go`
- `agent-task/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/PLAN-local-G05.md`
- `agent-task/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/CODE_REVIEW-cloud-G05.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`
- Status: `[승인됨]`; SDD lock: `해제`.
- Targeted Acceptance Scenarios: none. This compatibility prerequisite does not claim S10, S11, S12, S15, or another Milestone Task.
- Evidence Map: no row is closed by this packet. The host-specific construction fix preserves the approved standalone lifecycle boundary for later CLI, local-control, project-log, and client-process packets.
- Plan impact: no `Roadmap Targets` section is included, and verification is limited to deterministic construction/lifecycle regression evidence.

### Verification Context

- Handoff: `update-test mode=resolve-context` was applied read-only for `env=local`, `domain=agent`, `verification-type=unit`.
- Sources: `agent-test/local/rules.md` and `agent-ops/rules/project/domain/testing/rules.md`.
- Rules state: usable. No Agent-specific local profile matches this package-level unit task.
- Environment preflight:
  - repo/workdir: `/config/workspace/iop-s0`
  - Go: `/config/.local/bin/go`; `go version go1.26.2 linux/arm64`; `GOROOT=/config/opt/go`
  - sync basis: current worktree including intentional uncommitted changes
  - external service/credential: none
- Commands and criteria:
  - fresh focused and package tests must pass with `-count=1`;
  - race tests and `go vet` must pass for `host` and `bootstrap`;
  - `gofmt -d` and `git diff --check` must produce no output.
- Constraints: use deterministic fakes, do not record secrets, and do not add repository-local verification tools.
- Gaps: the local rule describes a Go 1.24 module while the resolved tool is Go 1.26.2; no Agent-specific local profile exists. The repository-native focused/race/vet commands provide the applicable oracle.
- Confidence: medium from the environment handoff, raised to high for this packet by fresh repository-native package execution and the deterministic failing reviewer probe.
- External Verification Preflight: not applicable; no verification leaves the current checkout.

### Test Coverage Gaps

- Typed-nil `host.Component`: not covered; current code panics before returning a validation error.
- Duplicate sentinel identity: not covered; current string-only assertion cannot prove `errors.Is(err, ErrDuplicateName)`.
- Resolved-name preservation: not covered; current tests use only explicit non-empty `host.Namer` values and miss bootstrap/host fallback divergence.
- Lifecycle start, rollback, reverse close, error identity, and race behavior: already covered and must remain unchanged.

### Symbol References

- `ErrDuplicateName`: declared only in `apps/agent/internal/bootstrap/module.go`; no production callers exist yet.
- `resolveComponentName`: local to `apps/agent/internal/bootstrap/module.go`.
- `host.resolveName`: local to `apps/agent/internal/host/host.go`.
- No symbol is renamed or removed. The follow-up may add only package-local validation and wrapper helpers.

### Split Judgment

Keep one plan. Typed-nil rejection, duplicate sentinel identity, and preserved fallback names are three observed variants of one indivisible constructor-validation invariant and share one deterministic package test boundary.

### Scope Rationale

Modify only `apps/agent/internal/bootstrap/module.go` and its tests. Preserve `host.Host` lifecycle behavior and tests; do not change CLI commands, concrete providers, sockets, logs, client processes, contracts, configuration, or roadmap state.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- build closures: scope/context/verification/evidence/ownership/decision all `true`
- build grade scores: scope `1`, state `0`, blast `0`, evidence `1`, verification `1`; base/route `local-fit`; lane/grade `local-G03`; filename `PLAN-local-G03.md`
- review closures: scope/context/verification/evidence/ownership/decision all `true`
- review grade scores: scope `1`, state `0`, blast `0`, evidence `1`, verification `1`; route `official-review`; lane/grade `cloud-G03`; filename `CODE_REVIEW-cloud-G03.md`
- large_indivisible_context: `false`
- positive loop risks: `boundary_contract` (count `1`)
- recovery signals: `review_rework_count=1`, `evidence_integrity_failure=false`
- risk boundary: `false`; recovery boundary: `false`
- capability-gap evidence: none

## Implementation Checklist

- [x] Repair bootstrap dependency identity validation and add deterministic regressions for typed nils, sentinel matching, and preserved host names.
- [x] Run fresh focused, package, race, vet, formatting, and diff verification.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Preserve One Validated Component Identity

#### Problem

`apps/agent/internal/bootstrap/module.go:38-50` checks only a nil interface, validates a bootstrap-only fallback name, appends the original component, and returns a string-only duplicate error. A typed-nil pointer therefore reaches `Name()` and panics. Because `apps/agent/internal/bootstrap/module.go:59-63` and `apps/agent/internal/host/host.go:53-58` use different fallback sequences, a pair accepted by `NewModule` can become duplicate host identities. `apps/agent/internal/bootstrap/module.go:85-87` also exports a sentinel that no returned error wraps.

#### Solution

Reject nil-capable typed values before calling component methods, resolve each name once, wrap the duplicate sentinel, and pass a package-local wrapper into `host.NewHost` so `host.Host` consumes the exact validated name while lifecycle calls still delegate to the original component.

Before (`apps/agent/internal/bootstrap/module.go:38-50`):

```go
for i, c := range components {
	if c == nil {
		return nil, fmt.Errorf("bootstrap: component at index %d is nil", i)
	}
	name := resolveComponentName(c, i)
	if name == "" {
		return nil, fmt.Errorf("bootstrap: component at index %d has an empty name", i)
	}
	if _, dup := seen[name]; dup {
		return nil, fmt.Errorf("bootstrap: duplicate component name %q", name)
	}
	seen[name] = struct{}{}
	validated = append(validated, c)
}
```

After:

```go
for i, component := range components {
	if isNilComponent(component) {
		return nil, fmt.Errorf("bootstrap: component at index %d is nil", i)
	}
	name := resolveComponentName(component, i)
	if _, duplicate := seen[name]; duplicate {
		return nil, fmt.Errorf("%w %q", ErrDuplicateName, name)
	}
	seen[name] = struct{}{}
	validated = append(validated, resolvedComponent{
		Component: component,
		name:      name,
	})
}
```

Add the standard-library import:

```go
import "reflect"
```

The package-local `resolvedComponent` must embed or delegate `host.Component` and implement `Name() string` with the captured name. `isNilComponent` must inspect only nil-capable kinds and must not call a method on a typed-nil value.

#### Modified Files and Checklist

- [x] `apps/agent/internal/bootstrap/module.go` — reject typed nils, wrap `ErrDuplicateName`, and preserve the resolved name through host construction.
- [x] `apps/agent/internal/bootstrap/module_test.go` — add deterministic boundary regressions while retaining lifecycle delegation and rollback coverage.

#### Test Strategy

Write regressions in `apps/agent/internal/bootstrap/module_test.go`:

- extend `TestNewModuleRejectsInvalidDependencies` with a typed-nil `*testComponent` and require an error without panic;
- add `TestNewModuleDuplicateNameMatchesSentinel` and require `errors.Is(err, ErrDuplicateName)`;
- add `TestModulePreservesResolvedComponentNames` with a component that does not implement `host.Namer` plus an explicit name that previously collided after host resolution, then require distinct `Status().Started` identities and normal reverse shutdown.

Do not add timing or external-process fixtures.

#### Verification

```bash
go test -count=1 -run '^(TestNewModuleRejectsInvalidDependencies|TestNewModuleDuplicateNameMatchesSentinel|TestModulePreservesResolvedComponentNames)$' ./apps/agent/internal/bootstrap
```

Expected: all constructor identity regressions pass.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/bootstrap/module.go` | REVIEW_API-1 |
| `apps/agent/internal/bootstrap/module_test.go` | REVIEW_API-1 |
| `agent-task/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/CODE_REVIEW-cloud-G03.md` | REVIEW_API-1 evidence |

## Final Verification

```bash
gofmt -w apps/agent/internal/bootstrap/module.go apps/agent/internal/bootstrap/module_test.go
go test -count=1 -run '^(TestNewModuleRejectsInvalidDependencies|TestNewModuleDuplicateNameMatchesSentinel|TestModulePreservesResolvedComponentNames)$' ./apps/agent/internal/bootstrap
go test -count=1 ./apps/agent/internal/host ./apps/agent/internal/bootstrap
go test -count=1 -race ./apps/agent/internal/host ./apps/agent/internal/bootstrap
go vet ./apps/agent/internal/host ./apps/agent/internal/bootstrap
gofmt -d apps/agent/internal/bootstrap/module.go apps/agent/internal/bootstrap/module_test.go apps/agent/internal/host/host.go apps/agent/internal/host/host_test.go apps/agent/internal/host/ports.go
git diff --check
```

Expected: fresh focused/package/race tests pass; vet succeeds; formatting and diff checks produce no output. Go test cache output is not accepted because every test command uses `-count=1`. Full-cycle execution is not applicable because this packet does not wire or expose a user-facing entrypoint.

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