Files
multi-agent-mux/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-1b40c4ee.md
T

372 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📐 구현 계획서 Rev.2 — `.env` → `.mam.env` 마이그레이션 최종화 (Finalize)
- **Job ID**: `fe4e0e6f`
- **Role**: Planner
- **목표**: `CURRENT_JOB.md` 기반 마이그레이션 최종화 · 원자적 커밋 · 리뷰어 검증 통과
- **선행 산출물**: `78e83796`(Rev.1) → `6dc9d528`(Rev.2) → `4e8b4839`(리뷰 `[VERDICT: NOT PASS]`) → `1b40c4ee`(최종화 계획 Rev.1) → **본 문서 (Rev.2)**
- **반영 피드백**: Creator `agy` Challenge Report — Job `c6c43df9`
---
## 0. 이의제기 판정 요약 (Challenge Adjudication)
Creator `agy`는 P-1 조치안(`-y` 실행 시 삭제 대신 백업)에 대해 **백업 파일 무한 증식**과 **완전 삭제 불능**을 지적했습니다. 실제 시나리오를 3주기 재현하여 검증했습니다.
| 항목 | 판정 | 근거 |
| :--- | :---: | :--- |
| **진단** — 반복 주기마다 백업 누적 | ✅ **채택** | 3주기 → 백업 3개 생성. 실측 확인 |
| 진단 — 백업 잔재를 타 도구가 오참조 | ❌ **기각** | `*.mam-backup`**읽는 코드는 전무**. `remove.sh`가 쓰기만 함. 전부 `.gitignore` 적용됨 |
| 진단 — `-y`로 완전 삭제 불가 | ⚠️ **부분 채택** | 사실이나 `--purge-env`가 이미 그 역할. 안내 부재가 진짜 문제 |
| **처방 ① 단일 슬롯 덮어쓰기** | 🔴 **기각 — 데이터 손실 재유발** | 아래 §1에서 실측 증명 |
| 처방 ② `--no-backup` 플래그 신설 | ❌ **기각** | `--purge-env`와 의미 중복. 플래그 2개가 같은 일을 하면 P-1의 "권한 붕괴"가 재발 |
| 처방 ② 안내 문구 강화 | ✅ **채택** | stdout 가이드 추가 |
**결론**: `agy`**문제 제기는 타당하나 처방은 위험합니다.** 진단을 채택하되 처방은 교체합니다. 대안으로 **내용 기반 중복 제거(content dedup) + 최초 백업 불변(immutable slot 1)**을 제시합니다.
---
## 1. 🔴 `agy` 처방 ①(단일 슬롯 덮어쓰기)을 기각하는 이유 — 실측
`remove.sh -y``install.sh` 주기를 3회 반복하며 각 백업의 **내용**을 측정했습니다. (P-1 패치를 적용한 사본으로 실행. 리포지토리 코드는 미수정.)
```
CYCLE 0: 사용자 실제 설정 저장 → .mam.env = MQTT_PASSWORD=REAL_USER_SECRET
CYCLE 1: remove.sh -y → backups: .mam.env.mam-backup
reinstall → .mam.env 재생성됨 (설치기 기본값)
CYCLE 2: remove.sh -y → backups: .mam.env.mam-backup .mam.env.mam-backup.20260804173654
CYCLE 3: remove.sh -y → backups: … + .mam.env.mam-backup.20260804173658
```
**핵심 측정 — 각 백업의 내용:**
```
[.mam.env.mam-backup] -> REAL_USER_SECRET 1건 ← 사용자 실제 설정
[.mam.env.mam-backup.20260804173654] -> REAL_USER_SECRET 0건 ← 설치기 생성 기본값
[.mam.env.mam-backup.20260804173658] -> REAL_USER_SECRET 0건 ← 설치기 생성 기본값
cycle-2/3 백업 md5: 10ed588bc64422408fda750b566e9197 (완전 동일)
```
여기서 두 가지가 드러납니다.
**(1) 증식의 실체는 "무가치한 사본의 반복"입니다.**
사용자의 진짜 설정은 **오직 슬롯 1**에만 있습니다. 2주기 이후 백업은 `install.sh`가 방금 만든 기본 설정을 되받아 적은 것이며, 서로 **바이트 단위로 동일**합니다. 즉 증식은 "정보가 늘어나는 것"이 아니라 **같은 쓰레기가 늘어나는 것**입니다. → 내용 기반 중복 제거로 완전히 해결 가능합니다.
**(2) 단일 슬롯 덮어쓰기는 그 유일한 진짜 설정을 파괴합니다.**
`agy`의 처방 ①을 실제로 적용해 보았습니다:
```
BEFORE — 슬롯 1의 REAL_USER_SECRET 보유: 1건
현재 live .mam.env 의 보유: 0건 (설치기 기본값)
$ mv -f .mam.env .mam.env.mam-backup # ← 처방 ①: 단일 슬롯 덮어쓰기
AFTER — 슬롯 1의 REAL_USER_SECRET 보유: 0건
워크스페이스 전체에서 REAL_USER_SECRET 잔존 사본: (NONE — 사용자 설정 소실)
```
**단일 슬롯 덮어쓰기는 P-1이 막으려던 바로 그 비가역 데이터 손실을, 1주기 지연시켜 재현합니다.** 원래 P-1은 "즉시 삭제"였고 처방 ①은 "다음 주기에 삭제"입니다. 손실 시점만 다를 뿐 결과는 동일하며, 오히려 **"백업했다"는 로그가 남아 있어 더 탐지하기 어렵습니다.**
역설적으로, 현재 코드의 타임스탬프 폴백(`remove.sh:182-184`)은 **바로 이 사고를 막고 있던 안전장치**였습니다. 이것을 제거해서는 안 됩니다.
**기각 사유 요약**: 디스크 정리(위생 문제)를 위해 데이터 보존(정확성 문제)을 희생하는 교환입니다. 우선순위가 역전되어 있습니다.
---
## 2. ✅ P-1 조치안 개정 (Revised Remedy)
### 2-1. 삭제 권한 분리 — Rev.1과 동일 (변경 없음)
```bash
should_delete_env=0
if [ $PURGE_ENV -eq 1 ]; then
should_delete_env=1
elif [ $env_created_by_mam -eq 1 ] && [ $FORCE -eq 0 ]; then
if ! read -p "❓ MAM-created '$env_name' found. Delete it? (Saying No preserves it) [y/N]: " -r env_response; then
env_response="n"
fi
if [[ "$env_response" =~ ^[yY](es)?$ ]]; then
should_delete_env=1
fi
fi
```
### 2-2. 🆕 백업 정책 개정 — 내용 기반 중복 제거 + 슬롯 1 불변
`agy`가 제기한 증식 문제를 **데이터 손실 없이** 해소합니다.
```bash
# 원칙: 기존 백업은 절대 덮어쓰지 않는다.
# 동일 내용이 이미 보존돼 있으면 새 사본을 만들지 않는다.
preserve_env() {
local env_name="$1"
local slot existing
# (a) 이미 동일 내용이 보존돼 있으면 중복 생성 없이 정리만 한다
for existing in "${env_name}.mam-backup" "${env_name}".mam-backup.*; do
[ -f "$existing" ] || continue
if cmp -s "$env_name" "$existing"; then
rm -f "$env_name"
echo "️ '$env_name' is already preserved in $existing (no duplicate created)."
return 0
fi
done
# (b) 내용이 다르면 새 슬롯에 보존한다. 슬롯 1은 영구 불변.
slot="${env_name}.mam-backup"
if [ -e "$slot" ]; then
slot="${env_name}.mam-backup.$(date +%Y%m%d%H%M%S)"
# 동일 초 내 재실행 충돌 방지
local n=1
while [ -e "$slot" ]; do
slot="${env_name}.mam-backup.$(date +%Y%m%d%H%M%S)-$n"
n=$((n + 1))
done
fi
mv "$env_name" "$slot"
echo "💾 Backed up $env_name -> $slot"
echo " To remove the configuration entirely, re-run with --purge-env."
}
```
**효과 (측정 기반 예측)**:
| 시나리오 | Rev.1 계획 | **Rev.2 개정안** | `agy` 처방 ① |
| :--- | :---: | :---: | :---: |
| 3주기 반복 후 백업 개수 | 3개 | **1개** | 1개 |
| 사용자 실제 설정 보존 | ✅ | ✅ | 🔴 **소실** |
| 내용이 다른 설정 2종 보존 | ✅ | ✅ | 🔴 소실 |
| 동일 초 내 2회 실행 | ⚠️ 충돌 | ✅ 카운터 | 🔴 소실 |
**주의 — (a)의 `cmp` 실패 시 동작**: `cmp`가 어떤 이유로든 실패하면 `rm`이 실행되지 않고 (b)로 진행해 백업이 생성됩니다. 즉 **판단 불능 시 보존 쪽으로 실패(fail-safe)** 합니다. 이 방향성을 반드시 유지해야 합니다.
### 2-3. 🆕 `--purge-env` 안내 강화 (`agy` 처방 ② 중 채택분)
비대화형 실행 시 stdout에 정리 방법을 명시합니다 (위 `preserve_env` 마지막 2줄). `--no-backup`**신설하지 않습니다**`--purge-env`와 기능이 동일하며, 같은 의미의 플래그를 2개 두는 것이 애초 P-1(`-y``--purge-env`의 권한 붕괴)의 원인이었습니다.
### 2-4. 📌 근본 해법은 별건 (범위 외 · 후속 과제로 등재)
증식의 **진짜 원인**은 백업 정책이 아니라, **백업이 바로 옆에 있는데도 `install.sh`가 기본 설정을 새로 생성한다**는 점입니다(M-1 가드가 `*.mam-backup`을 고려하지 않음). `install.sh`가 백업을 감지해 복원하도록 하면 증식은 발생 자체가 사라지고 재설치 UX도 개선됩니다.
다만 이는 **설치기 동작 변경**으로 별도 설계·검증이 필요하므로 본 마이그레이션 범위에서 제외하고 **후속 과제(FU-1)** 로 등재합니다. 2-2의 dedup만으로 `agy`가 제기한 증식은 실측상 해소됩니다.
---
## 3. 선행 리뷰 7개 항목 — 검증 결과 (변경 없음)
실제 명령 실행으로 확인한 현재 워킹 트리 상태 기준입니다.
| # | 리뷰(`4e8b4839`) 지적 | 상태 | 근거 |
| :--- | :--- | :---: | :--- |
| 1 | `remove.sh:192` 고아 `fi` | ✅ 해결 | `deploy/*.sh` 5개 전부 `bash -n` 통과 |
| 2 | T-8/T-10/T-11/T-12 미구현 | ⚠️ 부분 | T-8·10·12·13 추가. **T-11·14·15 없음** |
| 3 | T-4 무력 테스트 | ✅ 해결 | `patch.object(__file__)` 후 인자 없이 호출 — 실제 경계 탐색 진입 |
| 4 | 문서 13개소 | ⚠️ 거의 | **BOOTSTRAP 2개 `.gitignore` 예시만 잔존** (P-6) |
| 5 | 매니페스트 소유권 재기록 | ✅ 해결 | `install.sh:288-305` |
| 6 | 래퍼 cwd 폴백 | ✅ 해결 | `REPO_ROOT` 우선 + 단계별 경고 |
| 7 | `.tmp` 잔여물 | ✅ 해결 | 없음 |
**`CURRENT_JOB.md`의 파급 범위 오기**: `:23``lib.sh`에 ".env 로딩 로직"이 있다고 기술하나 **사실이 아닙니다.** `lib.sh``.env` 매칭 27건은 전부 `os.environ` 부분 문자열, dotenv 참조는 **0건**. `lib.sh`는 **범위 제외**이며 이 오기를 근거로 수정하면 불필요한 회귀 위험만 발생합니다.
---
## 4. 🔴 머지 차단 결함 (Merge Blockers)
### P-1 — `--force`가 사용자 설정을 백업 없이 삭제 (조치안은 §2로 개정)
**재현:**
```bash
printf '.env\nremove.sh\n' > .mam/install_manifest.txt
printf 'SECRET_KEY=user_secret_data\n' > .env
bash remove.sh --force
# EXITCODE=0 / 남은 파일: (없음) / .env.mam-backup 미생성 → 비가역 소실
```
**근본 원인**: 인자 파서(`remove.sh:17-20`)가 `-y|--yes|--force`를 하나의 `FORCE`로 묶고, 섹션 5가 `FORCE=1`을 삭제 권한으로 해석합니다. 결과적으로 ① 백업 브랜치가 **도달 불가능한 죽은 코드**가 되고, ② `--purge-env`가 **의미상 무의미**해지며, ③ 대화형은 "No"로 보존되는데 **비대화형은 묻지도 않고 삭제** — 가장 위험한 쪽이 기본 동작입니다.
**영향 범위**: `update.sh` 경로는 **안전**합니다(`:86,91`이 `remove.sh --force` 호출 `:151` 이전에 `*.update-tmp`로 이동 → 섹션 5의 `[ -f "$env_name" ] || continue`에 걸림). 피해자는 **`remove.sh -y`를 직접 실행하는 사용자/CI**로 한정됩니다. 한정되지만 비가역입니다.
### P-2 — T-8 단언문이 데이터 손실을 통과 판정
`tests/test_env_migration.py:134`:
```python
self.assertTrue(os.path.exists(".env.mam-backup") or not os.path.exists(".env"))
```
`or not os.path.exists(".env")` 때문에 **`.env`가 삭제되기만 하면 무조건 통과**합니다. 막아야 할 실패 양상이 곧 통과 조건이 되는 논리 역전이며, P-1이 지금까지 발견되지 않은 직접적 원인입니다.
**조치** — 보존 검증과 삭제 검증을 분리하고, §2-2 개정에 맞춰 케이스를 확장합니다.
```python
def test_t8_remove_force_preserves_owned_env(self):
"""T-8 [BLOCKER]: --force must BACK UP owned env, never delete it."""
res = subprocess.run(["bash", "remove.sh", "--force"], capture_output=True, text=True)
self.assertEqual(res.returncode, 0, f"remove.sh failed: {res.stderr}")
self.assertTrue(os.path.exists(".env.mam-backup"),
"MAM-owned .env MUST be backed up under --force, never deleted")
with open(".env.mam-backup") as f:
self.assertIn("user_secret_data", f.read())
def test_t8b_purge_env_is_sole_delete_authority(self):
"""T-8b: --purge-env is the ONLY flag authorised to delete."""
subprocess.run(["bash", "remove.sh", "--force", "--purge-env"], check=True)
self.assertFalse(os.path.exists(".env"))
self.assertFalse(os.path.exists(".env.mam-backup"))
```
**핵심 원칙**: 보존 계열 단언에 `or`를 쓰지 않습니다. `or` 대안지는 실패 양상을 흡수합니다.
---
## 5. 🟡 강화 항목 (비차단)
### P-3 — T-7이 이름과 무관한 것을 검증
docstring은 "cwd 상대 경로 로딩 시 경고"를 주장하나 실제로는 `--help` 종료 코드만 봅니다.
**조치**: 임시 디렉터리에 `.env`를 두고 그곳을 cwd로 래퍼 실행 → stderr에 `WARNING`/`deprecated` 포함 단언. 불가하면 docstring을 실제 검증 내용(`smoke: wrapper executes`)으로 정정해 **거짓 안전감을 제거**.
### P-4 — T-10/T-12의 공허한 통과 위험
T-10은 `subprocess.run(...)` 결과를 **어디에도 단언하지 않습니다**. `install.sh`가 초기 실패해도 통과합니다.
**단, 현재는 진짜로 통과합니다** (재현 확인: `EXITCODE=0`, `Preserved without shadowing`, `.mam.env` 미생성). 문제는 미래 회귀를 못 잡는다는 점입니다.
**조치**: `assertEqual(res.returncode, 0)` + **섹션 5 도달 표지 문자열** 단언.
```python
self.assertEqual(res.returncode, 0, f"install.sh failed: {res.stderr}")
self.assertIn("Preserved without shadowing", res.stdout)
self.assertFalse(os.path.exists(".mam.env"))
```
### P-5 — 미구현 테스트 T-11 / T-14 / T-15 (+ 신규 T-16 / T-17)
| ID | 검증 내용 | 방어 대상 |
| :--- | :--- | :--- |
| **T-11** | 구 `update.sh` 전체 시퀀스 E2E → 종료 후 사용자 설정값이 **실제로 로드됨** | E12 섀도잉 |
| T-14 | `update.sh` 중도 실패 → `restore_on_failure`가 **원래 이름**으로 복원 | M-4 트랩 대칭 |
| T-15 | `.mam.env.pre-migrate.bak``.mam.env.update-tmp` 상호 미간섭 | E15 슬롯 충돌 |
| **T-16** 🆕 | `remove.sh -y``install.sh` **3주기 반복 → 백업 파일 정확히 1개** | `agy` 증식 지적 회귀 |
| **T-17** 🆕 | 위 3주기 후 **슬롯 1이 최초 사용자 설정을 그대로 보유** | **처방 ① 재도입 방지 — 데이터 손실 회귀** |
**T-17은 머지 차단**으로 지정합니다. §1에서 실측으로 재현된 비가역 데이터 손실의 회귀 가드이기 때문입니다. (재현된 결함에만 차단을 부여한다는 본 계획서의 일관된 기준에 부합합니다.)
T-11은 Rev.2 명세상 차단이었으나, 코드 검토상 `update.sh:171-176` 복원 분기가 대칭이고 **실동작 결함이 재현되지 않아** 최우선 강화 항목으로 유지합니다. 리뷰어가 이견을 제시하면 원안(차단)으로 복귀합니다.
### P-6 — BOOTSTRAP `.gitignore` 예시의 유령 파일 참조
`BOOTSTRAP.md:126-130` / `BOOTSTRAP.ko.md:126-130``!.env.example`은 rename으로 **더 이상 존재하지 않는 파일**의 예외 규칙이며 실제 `.gitignore`(`:17-22`)와도 불일치합니다. 레거시 2줄(`.env`, `.env.*`)은 구 사용자 보호를 위해 유지가 타당합니다.
**조치**: `!.env.example` 줄 제거 또는 `# legacy — 구 설치 호환용` 주석 병기.
### P-7 — `CURRENT_JOB.md` 처리
세션 UUID·에이전트 상태 등 휘발성 런타임 정보를 담은 untracked 문서이며 §3의 `lib.sh` 오기를 포함합니다.
**권고**: **커밋하지 않고** `.gitignore`에 등재.
---
## 6. 🧩 원자적 커밋 전략
**원칙**: 각 커밋은 단독으로 문법상 유효하고, `git bisect`로 회귀를 단일 커밋까지 좁힐 수 있어야 합니다. 파일이 아니라 **관심사** 기준으로 자릅니다.
| # | 커밋 | 대상 | 메시지(안) |
| :---: | :--- | :--- | :--- |
| **C1** | 템플릿 rename + ignore 규칙 (+P-7) | `.mam.env.example`(staged rename), `.gitignore` | `refactor(config): rename .env.example to .mam.env.example and isolate .mam.env in gitignore` |
| **C2** | dotenv 로더 경계 수정 | `…/scripts/mqtt_common.py` | `fix(config): resolve dotenv via workspace marker and prefer .mam.env over legacy .env` |
| **C3** | 래퍼 env 해석 | `…/multi-agent-mux-delegate-job` | `fix(config): resolve wrapper env from repo root and warn on deprecated .env` |
| **C4** | 생성 스크립트 | `deploy/generate-env.sh` | `feat(deploy): target .mam.env and add --migrate-legacy flag` |
| **C5** | 설치기 (M-1 + M-2) | `deploy/install.sh`, `deploy/install_mam.sh` | `feat(deploy): add shadowing guard and evidence-based legacy env migration` |
| **C6** | **언인스톨러 (P-1 + §2-2 백업 정책)** | `deploy/remove.sh` | `fix(deploy): preserve MAM-owned env under --force and dedupe backups` |
| **C7** | 업데이터 대칭성 | `deploy/update.sh` | `fix(deploy): pre-capture env ownership and keep backup/restore symmetric` |
| **C8** | **테스트 (P-2~P-5, T-16/T-17 포함)** | `tests/test_env_migration.py` | `test: cover .mam.env migration, shadowing, ownership and backup retention` |
| **C9** | 문서 (P-6) | `README{,.ko}.md`, `BOOTSTRAP{,.ko}.md`, `MULTI_AGENT_RULES{,.ko}.md`, `deploy/README.md` | `docs: document .mam.env config file and legacy migration path` |
**순서 제약 (2건, 필수)**
- **C1 → C5**: `install.sh``.mam.env.example`을 참조하므로 rename이 선행해야 합니다.
- **C6 → C8**: C8의 T-8/T-16/T-17은 C6의 수정이 있어야 통과합니다. 역순이면 중간 커밋이 red가 되어 bisect가 오염됩니다.
**커밋 주체**: `MULTI_AGENT_RULES.md` §4에 따라 구현·커밋은 **Creator/GM 권한**입니다. Planner는 설계 자산만 산출하며 코드를 수정하지 않습니다.
---
## 7. ✅ 완료 정의 (DoD) — 리뷰어 검증 게이트
**게이트 A — 정적**
1. `for f in deploy/*.sh; do bash -n "$f"; done` 무오류
2. `git check-ignore -v .mam.env .mam.env.bak .mam.env.update-tmp .mam.env.mam-backup` 전부 매칭
3. `git check-ignore .mam.env.example` **비매칭** — 템플릿은 추적 대상
4. `*.tmp` 잔여물 없음
**게이트 B — 데이터 보존 (P-1 회귀 · 차단)**
5. 매니페스트 `.env` 기재 + `remove.sh -y``.env.mam-backup` 존재 + **원본 내용 보존**
6. `remove.sh --purge-env` → 삭제됨 (의도적 삭제 경로 정상)
7. 매니페스트 **없는** 사용자 소유 `.env` → 어떤 플래그로도 원본 보존
**게이트 B′ — 백업 위생 (`agy` 지적 반영 · 신규)**
8. `remove.sh -y``install.sh` **3주기 반복 → 백업 파일 정확히 1개** (증식 없음)
9. 위 3주기 후 **슬롯 1(`*.mam-backup`)이 최초 사용자 설정을 그대로 보유****차단**
10. 내용이 다른 설정 2종을 연속 보존 시 **둘 다 살아 있음** (dedup이 과잉 삭제하지 않음)
11. `remove.sh -y` stdout에 `--purge-env` 안내 문구 포함
**게이트 C — 섀도잉 방지 (M-1 회귀 · 차단)**
12. `.env.update-tmp`만 있는 상태로 `install.sh``.mam.env` **미생성**, 종료코드 0, 섹션 5 도달 표지 포함
13. 매니페스트 없는 `MQTT_BROKER` 포함 `.env` → 이관 안 됨 (휴리스틱 탈취 방지)
14. 매니페스트 있는 `.env` → 이관 + `chmod 0600` + 매니페스트 항목 치환
**게이트 D — 테스트 품질 (P-2 회귀 · 차단)**
15. `tests/test_env_migration.py` 전량 통과
16. **보존 계열 단언에 `or` 대안지 없음** — 정적 검토. `assertTrue(A or not B)` 금지
17. 각 subprocess 호출 테스트가 `returncode`를 단언
**게이트 E — 회귀**
18. `pytest tests/test_tier1_unit.py tests/test_tier2_component.py tests/test_env_migration.py` 통과
- 기준선 **62 passed / 461s(7분41초)**. 느릴 뿐 회귀 아님. **타임아웃 300초 이상 필요**
19. tier3/tier4는 P-1/P-2 수정 후 최소 1회 완주
**게이트 F — 문서**
20. 잔존 `.env` 참조가 전부 (a) 레거시 호환 로직, (b) 마이그레이션 안내, (c) 명시적 deprecated 표기 중 하나에 해당
---
## 8. ⚠️ 리스크 및 완화
| 리스크 | 심각도 | 완화 |
| :--- | :---: | :--- |
| **`--force`로 사용자 설정 비가역 소실** | **치명 · 비가역** | P-1 권한 분리 + P-2 T-8 재작성. **차단** |
| **단일 슬롯 덮어쓰기로 최초 백업 파괴** | **치명 · 비가역** | §2-2 슬롯 1 불변 + **T-17 차단 가드**. 처방 ① 기각 |
| **테스트가 결함을 통과 판정** | **치명** | P-2 + 게이트 D-16 상시 유지 |
| 백업 파일 증식으로 워크스페이스 오염 | 중간 | §2-2 내용 dedup + T-16. 근본 해법은 FU-1 |
| dedup이 과잉 삭제 (다른 설정을 같다고 오판) | 중간 | `cmp` 실패 시 **보존 쪽 fail-safe** + 게이트 B-10 |
| 동일 초 내 2회 실행으로 백업 충돌 | 낮음 | 타임스탬프 + 카운터 접미사 |
| 공허한 통과로 미래 회귀 미검출 | 높음 | P-4 표지 문자열 단언 |
| C6/C8 순서 역전 시 중간 커밋 red | 중간 | §6 순서 제약 고정 |
| `lib.sh` 오기 근거의 불필요한 수정 | 중간 | §3 명시 — dotenv 참조 0건, **범위 제외** |
---
## 9. 실행 순서 요약
1. **P-1** `remove.sh` 삭제 권한 분리 + **§2-2 백업 dedup/불변 정책** → **C6**
2. **P-2** T-8 재작성 + T-8b 신설 → **C8**
3. **P-3/P-4** T-7 정정, T-10/T-12 단언 보강 → **C8**
4. **P-5** T-11/T-14/T-15 + **T-16/T-17 신설****C8**
5. **P-6** BOOTSTRAP 예시 정리 → **C9**
6. **P-7** `CURRENT_JOB.md` `.gitignore` 등재 → **C1**
7. 게이트 A~F 전량 확인 (특히 **B-9는 차단**)
8. C1 → C9 순서로 원자적 커밋 (C1→C5, C6→C8 제약 준수)
9. 리뷰어 재검증 요청
**후속 과제 (범위 외)**
- **FU-1**: `install.sh`의 M-1 가드가 `*.mam-backup`을 인지하여 기본값 생성 대신 **복원**하도록 개선. 증식의 근본 해소 + 재설치 UX 개선. 별도 설계·검증 필요.
---
## 10. 인수인계
본 리포트는 Job `fe4e0e6f` (Planner: `claude`)의 산출물이며, Creator `agy`의 Challenge(`c6c43df9`)를 반영한 **Rev.2**입니다.
- **`agy`의 증식 진단은 채택했고 실측으로 확인했습니다**(3주기 → 백업 3개). 지적해 준 덕분에 Rev.1에는 없던 백업 위생 게이트(B′)가 추가되었습니다.
- **다만 처방 ①(단일 슬롯 덮어쓰기)은 기각합니다.** 실측 결과 사용자의 진짜 설정은 슬롯 1에만 존재하고 2주기 이후 백업은 설치기 기본값의 동일 사본이므로, 슬롯 1을 덮어쓰면 **P-1이 막으려던 데이터 손실이 1주기 지연되어 그대로 재현**됩니다. 대신 **내용 기반 dedup + 슬롯 1 불변**으로 동일한 위생 효과(백업 1개)를 데이터 손실 없이 달성합니다.
- 처방 ② 중 `--no-backup` 신설은 기각(`--purge-env`와 중복 — 권한 붕괴 재발 위험), **안내 문구 강화는 채택**했습니다.
- **차단 항목은 P-1, P-2, T-17** 3건입니다. 구현 및 커밋은 Creator/GM 소관이며 Planner는 코드를 수정하지 않았습니다.
[AGREEMENT: REACHED]