<!-- task=01_setup_cli_core plan=3 tag=SETUPCORE-FIX3 -->

# Code Review Reference - SETUPCORE-FIX3

> **[IMPLEMENTING AGENT — READ FIRST]** 이 파일의 구현 에이전트 소유 섹션을 채우기 전에는 작업이 완료된 것이 아니다.
> `코드리뷰 전용 체크리스트`는 리뷰 에이전트만 수정한다.

## 개요

date=2026-05-20
task=01_setup_cli_core, plan=3, tag=SETUPCORE-FIX3

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [SETUPCORE-FIX3-1] 기존 data dir 부모 권한 교정 | [x] |
| [SETUPCORE-FIX3-2] 기존 parent 교정 테스트 추가 | [x] |

## 구현 체크리스트

- [x] [SETUPCORE-FIX3-1] 기존 data dir 부모가 존재해도 서비스 사용자가 traverse 가능하도록 권한 교정 정책을 구현한다.
- [x] [SETUPCORE-FIX3-2] 기존 `0750` parent를 setup 재실행으로 교정하는 단위 테스트를 추가한다.
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [x] PASS이면 `complete.log`를 작성하고 task 디렉터리를 archive로 이동한다.

## 계획 대비 변경 사항

- `ensureTraversable(path)` 헬퍼를 추가했다. `os.MkdirAll`은 이미 존재하는 디렉터리 mode를 교정하지 않으므로, parent 생성 직후 `os.Stat`으로 현재 perm을 읽고 `0o011`(g+x, o+x) bit가 누락되어 있으면 `os.Chmod(path, cur|0o011)`로 최소 교정한다. fresh 생성에서는 이미 `0o755`이므로 no-op이고, 기존 `0o750`/`0o700` 등의 parent는 `0o751`/`0o711`로 보정된다.
- read bit는 강제로 켜지 않는다. systemd 서비스 사용자에게 필요한 것은 leaf로의 traverse뿐이므로, `o+r` 없이 `o+x`만 부여해 운영자가 의도한 가시성 제한을 그대로 둔다.
- `chmod` 호출은 `os.Chmod`로 직접 수행한다. runner를 거치지 않은 이유: 파일 시스템 mode 변경은 외부 명령보다 표준 라이브러리가 멱등적·결정적이고, 기존 `ensureDirs`의 `MkdirAll`과 동일한 직접 호출 경로를 유지해 dry-run 가드(외부 호출 0)와 분리된다. dry-run 경로는 `ensureDirs` 자체를 호출하지 않으므로 영향 없다.

## 주요 설계 결정

- traverse 보정은 immediate parent 한 단계에만 적용한다. `/var/lib`, `/var`, `/` 같은 상위 OS 표준 디렉터리는 일반 배포본에서 이미 `0755`로 제공되므로 건드리지 않는다. 이는 `validateDataDir`의 unsafe 목록과 일관된다(우리는 `/var/lib/iop` 같은 우리 소유 부모만 관리한다).
- 보정은 `0o011`만 OR 한다. `mode | 0o755`를 사용하면 read bit까지 강제로 켜져 운영자가 의도한 보호를 약화시킬 수 있다.
- 테스트 `TestRunFixesExistingNonTraversableParent`는 `0o750`로 초기화된 parent가 setup 재실행 후 traverse bit를 가지는지 단언한다. leaf data dir의 `0o750`과 ownership 검증은 기존 `TestRunCreatesTraversableParentForNestedDataDir`/`TestRunWritesMissingConfigAndUnit`가 그대로 커버한다.

## 리뷰어를 위한 체크포인트

- 기존 `/var/lib/iop` parent가 non-traversable이어도 setup 재실행으로 교정되는지 확인한다.
- leaf data dir ownership, config ownership, unsafe data dir rejection, no-overwrite, dry-run 동작이 유지되는지 확인한다.

## 검증 결과

### 중간 검증
```
$ gofmt -w packages/hostsetup
$ go test -count=1 ./packages/hostsetup
ok  	iop/packages/hostsetup	0.003s
```

### 최종 검증
```
$ go test -count=1 ./packages/config ./packages/hostsetup
ok  	iop/packages/config	0.007s
ok  	iop/packages/hostsetup	0.004s
```

## 코드리뷰 결과

- 종합 판정: PASS
- 확인 사항:
  - `packages/hostsetup/setup.go`의 `ensureTraversable`이 기존 shared parent의 missing traverse bit를 교정한다.
  - `TestRunFixesExistingNonTraversableParent`가 기존 `0750` parent 재실행 케이스를 검증한다.
  - leaf data dir ownership, config ownership, unsafe absolute data dir rejection, no-overwrite, dry-run 동작이 기존 테스트로 유지된다.
- 검증 재현:
  - `gofmt -l packages/hostsetup` → 출력 없음
  - `go test -count=1 ./packages/hostsetup` → `ok  	iop/packages/hostsetup	0.003s`
  - `go test -count=1 ./packages/config ./packages/hostsetup` → `ok  	iop/packages/config	0.006s`, `ok  	iop/packages/hostsetup	0.003s`
- 잔여 주의: 후속 CLI에서 `--data-dir` 같은 사용자 입력 옵션을 추가한다면 relative path나 `/var/lib/iop` shared root 입력을 별도로 제한할지 재검토한다. 현재 active follow-up 범위와 계획된 setup CLI 기본 경로에는 해당하지 않아 PASS를 막지 않는다.
- 다음 단계: active plan/review를 `.log`로 아카이브하고 `complete.log` 작성 후 task 디렉터리를 archive로 이동한다.
