<!-- task=m-cross-os-runner-bootstrap/03+01_windows_bootstrap plan=1 tag=REVIEW_BOOTSTRAP_WINDOWS -->

# PLAN: Windows PowerShell bootstrap 리뷰 후속 수정

## 이 파일을 읽는 구현 에이전트에게

이 계획은 이전 리뷰의 Required 2건만 수정한다. 구현 후 active `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 stdout/stderr로 채우고, active 파일을 그대로 둔 채 리뷰를 요청한다. 최종 판정, 로그 아카이브, `complete.log`, task directory archive 이동은 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 선택, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌이 생기면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거와 재개 조건을 기록하고 중단한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

1차 구현은 PowerShell bootstrap asset, Core `.ps1` serving route, drift test, content assertion을 추가했고 재실행한 Go/Dart 검증도 통과했다. 그러나 Windows background 실행과 token-bearing config 파일 보호에서 실제 Windows runtime 결함 가능성이 남았다. 이 후속은 기존 구현 범위를 넓히지 않고 PowerShell bootstrap script와 해당 content tests만 보강한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 온 형식이며, 직접 사용자 프롬프트는 금지된다. `USER_REVIEW.md` 작성 여부는 다음 code-review가 판단한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-product-surface/milestones/cross-os-runner-bootstrap.md`
- Task ids:
  - `[windows-bootstrap]`: Windows PowerShell bootstrap
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/cli-smoke.md`
- `agent-test/local/agent-smoke.md`
- `agent-task/archive/2026/06/m-cross-os-runner-bootstrap/01_core_command/complete.log`
- `agent-task/m-cross-os-runner-bootstrap/03+01_windows_bootstrap/plan_cloud_G07_0.log`
- `agent-task/m-cross-os-runner-bootstrap/03+01_windows_bootstrap/code_review_cloud_G07_0.log`
- `apps/runner/assets/script/shell/oto_agent_bootstrap.sh`
- `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1`
- `services/core/internal/httpserver/oto_agent_bootstrap.ps1`
- `services/core/internal/httpserver/bootstrap_provider.go`
- `services/core/internal/httpserver/routes.go`
- `services/core/internal/httpserver/runner_handlers.go`
- `services/core/internal/httpserver/server_test.go`
- `apps/runner/test/oto_agent_bootstrap_script_test.dart`
- `apps/runner/test/oto_agent_bootstrap_ps1_test.dart`
- `README.md`

### 테스트 환경 규칙

- test_env: local.
- `agent-test/local/rules.md`를 읽었고, 기본 local 검증은 원격 runner 기준이지만 현재 checkout에서 Go/Dart 명령을 재실행해 증거를 확인했다.
- matching profiles: `agent-test/local/cli-smoke.md`, `agent-test/local/agent-smoke.md`.
- 적용 명령: `cd services/core && go test ./...`, `cd apps/runner && dart test test/oto_agent_bootstrap_script_test.dart`, `cd apps/runner && dart test test/oto_agent_bootstrap_ps1_test.dart`, `cd apps/runner && dart analyze`.
- 후속에서는 Go test cache를 피하기 위해 `go test -count=1 ./...`를 사용한다.

### 테스트 커버리지 공백

- Windows `.ps1` route와 embedded drift는 Go test가 커버한다.
- Windows command URL/quoting은 기존 `server_test.go` cases 11, 12가 커버한다.
- PowerShell script content test는 required flags, asset pattern, HTTPS, config key, token output 비노출을 커버한다.
- 공백: background mode에서 stdout/stderr redirect 대상이 서로 달라야 한다는 계약을 테스트하지 않는다.
- 공백: token-bearing config 파일에 Windows permission hardening을 적용한다는 계약을 테스트하지 않는다.

### 심볼 참조

- renamed/removed symbol 없음.
- 새 provider/handler 참조는 `registerRoutes`, `handleServeBootstrapPs1`, `embeddedBootstrapPs1Provider`, `server_test.go`에서 확인했다.

### 분할 판단

- 현재 디렉터리 `03+01_windows_bootstrap`는 split subtask이며 predecessor `01_core_command`는 `agent-task/archive/2026/06/m-cross-os-runner-bootstrap/01_core_command/complete.log`로 충족됐다.
- 후속은 같은 PowerShell bootstrap script와 content tests 안의 리뷰 수정 2건이다. stdout/stderr redirect와 config ACL은 같은 Windows bootstrap runtime 계약 안에 있어 별도 subtask로 나누면 동일 파일 동기화만 늘어난다.

### 범위 결정 근거

- 수정 범위는 `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1`, mirrored `services/core/internal/httpserver/oto_agent_bootstrap.ps1`, `apps/runner/test/oto_agent_bootstrap_ps1_test.dart`로 제한한다.
- Go route/provider code와 Windows command generation은 1차 구현 및 선행 subtask 검증이 통과했으므로 이번 후속에서 구조 변경하지 않는다.
- 실제 Windows integration smoke, release packaging, checksum/signature, Windows service manager 등록은 기존 계획의 범위 제외를 유지한다.

### 빌드 등급

- `cloud-G07`: PowerShell process control, stdout/stderr redirection, credential-bearing config permission 의미가 중심이고 실제 Windows runtime 검증을 deterministic content/handler evidence로 보완해야 한다.

## 구현 체크리스트

- [ ] PowerShell background 실행에서 stdout/stderr redirect target을 분리하고 content regression으로 고정한다.
- [ ] token-bearing Windows config 파일에 chmod 600 equivalent ACL hardening을 적용하고 content regression으로 고정한다.
- [ ] runner/Core PS1 asset을 byte-identical하게 동기화하고 focused 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_BOOTSTRAP_WINDOWS-1] Background log redirection 수정

문제: `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1:100` 및 mirrored `services/core/internal/httpserver/oto_agent_bootstrap.ps1:100`에서 `Start-Process`가 `-RedirectStandardOutput`과 `-RedirectStandardError`에 같은 `$logFile`을 넘긴다. Windows PowerShell에서 같은 redirect target은 process start 실패로 이어질 수 있어 기본 background bootstrap이 깨질 수 있다.

해결 방법:

Before:

```powershell
# apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1:99
$logFile = Join-Path $logDir 'oto-agent.log'
$proc = Start-Process -FilePath $otoBin `
    -ArgumentList "agent run --config `"$configPath`"" `
    -RedirectStandardOutput $logFile `
    -RedirectStandardError $logFile `
    -WindowStyle Hidden `
    -PassThru
```

After:

```powershell
$stdoutLog = Join-Path $logDir 'oto-agent.out.log'
$stderrLog = Join-Path $logDir 'oto-agent.err.log'
$proc = Start-Process -FilePath $otoBin `
    -ArgumentList "agent run --config `"$configPath`"" `
    -RedirectStandardOutput $stdoutLog `
    -RedirectStandardError $stderrLog `
    -WindowStyle Hidden `
    -PassThru
```

`Write-Host` 안내도 단일 `$logFile` 대신 두 파일 경로를 출력하게 맞춘다. token 값은 출력하지 않는다.

수정 파일 및 체크리스트:

- `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1`
- `services/core/internal/httpserver/oto_agent_bootstrap.ps1`
- `apps/runner/test/oto_agent_bootstrap_ps1_test.dart`

테스트 작성: `oto_agent_bootstrap_ps1_test.dart`에 `-RedirectStandardOutput $stdoutLog`, `-RedirectStandardError $stderrLog`, `oto-agent.out.log`, `oto-agent.err.log`가 존재하고 `-RedirectStandardOutput $logFile` / `-RedirectStandardError $logFile` 패턴이 없음을 assertion한다.

중간 검증: `cd apps/runner && dart test test/oto_agent_bootstrap_ps1_test.dart`가 `All tests passed!`로 종료해야 한다.

### [REVIEW_BOOTSTRAP_WINDOWS-2] Config ACL hardening 추가

문제: `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1:93` 및 mirrored `services/core/internal/httpserver/oto_agent_bootstrap.ps1:93`에서 enrollment token이 들어간 config를 쓴 뒤 Windows ACL/permission을 제한하지 않는다. agent 도메인 규칙은 생성 config 파일을 `chmod 600` 의미로 보호해야 한다.

해결 방법:

Before:

```powershell
# apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1:93
[System.IO.File]::WriteAllText($configPath, $configContent)
```

After:

```powershell
[System.IO.File]::WriteAllText($configPath, $configContent)
$acl = Get-Acl $configPath
$acl.SetAccessRuleProtection($true, $false)
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    $currentUser,
    'FullControl',
    'Allow'
)
$acl.SetAccessRule($accessRule)
Set-Acl -Path $configPath -AclObject $acl
```

필요하면 위 구조를 작은 helper 함수로 빼도 되지만, 새 의존성은 추가하지 않는다. runner asset 수정 후 Core embedded copy는 byte-identical하게 갱신한다.

수정 파일 및 체크리스트:

- `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1`
- `services/core/internal/httpserver/oto_agent_bootstrap.ps1`
- `apps/runner/test/oto_agent_bootstrap_ps1_test.dart`

테스트 작성: `oto_agent_bootstrap_ps1_test.dart`에 `SetAccessRuleProtection($true, $false)`, `WindowsIdentity`, `FileSystemAccessRule`, `Set-Acl` 중 실제 구현에 맞는 permission hardening assertion을 추가한다. token output 비노출 assertion은 유지한다.

중간 검증: `cd apps/runner && dart test test/oto_agent_bootstrap_ps1_test.dart`가 `All tests passed!`로 종료해야 한다.

### [REVIEW_BOOTSTRAP_WINDOWS-3] Drift 및 focused 검증

문제: runner PS1 asset과 Core embedded PS1 copy가 하나라도 빠지면 `/bootstrap/oto-agent.ps1` route가 오래된 script를 serve할 수 있다.

해결 방법: runner asset을 source of truth로 보고 Core embedded copy를 동일 내용으로 갱신한다. 기존 `TestEmbeddedBootstrapPs1MatchesRunnerAsset`가 byte drift를 잡는다.

수정 파일 및 체크리스트:

- `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1`
- `services/core/internal/httpserver/oto_agent_bootstrap.ps1`
- `services/core/internal/httpserver/server_test.go`는 drift test가 이미 있으므로 실패할 때만 수정한다.

테스트 작성: 기존 Go drift test를 유지한다. 별도 Go 테스트 추가는 필요하지 않다.

중간 검증: `cd services/core && go test -count=1 ./...`가 통과해야 한다.

## 수정 파일 요약

| 경로 | 항목 |
|------|------|
| `apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1` | REVIEW_BOOTSTRAP_WINDOWS-1, REVIEW_BOOTSTRAP_WINDOWS-2, REVIEW_BOOTSTRAP_WINDOWS-3 |
| `services/core/internal/httpserver/oto_agent_bootstrap.ps1` | REVIEW_BOOTSTRAP_WINDOWS-1, REVIEW_BOOTSTRAP_WINDOWS-2, REVIEW_BOOTSTRAP_WINDOWS-3 |
| `apps/runner/test/oto_agent_bootstrap_ps1_test.dart` | REVIEW_BOOTSTRAP_WINDOWS-1, REVIEW_BOOTSTRAP_WINDOWS-2 |
| `services/core/internal/httpserver/server_test.go` | REVIEW_BOOTSTRAP_WINDOWS-3 only if existing drift test needs maintenance |

## 최종 검증

- `cd apps/runner && dart test test/oto_agent_bootstrap_ps1_test.dart`
- `cd apps/runner && dart test test/oto_agent_bootstrap_script_test.dart`
- `cd apps/runner && dart analyze`
- `cd services/core && go test -count=1 ./...`
- `rg --sort path -n "RedirectStandardOutput|RedirectStandardError|SetAccessRuleProtection|FileSystemAccessRule|Set-Acl|oto-agent\\.out\\.log|oto-agent\\.err\\.log" apps/runner/assets/script/powershell/oto_agent_bootstrap.ps1 services/core/internal/httpserver/oto_agent_bootstrap.ps1 apps/runner/test/oto_agent_bootstrap_ps1_test.dart`

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
