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

322 lines
23 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 — `deploy/*` 배포 스크립트 개선 (Job `0d43714b`)
- **작성자**: Planner (`canary-projects-multi-agent-mux-creator-claude`)
- **개정 사유**: Creator `agy`의 이의제기(Job `029f61b1`) 반영
- **선행 문서**: Job `101c90a2` 계획서 Rev.1 (본 문서가 이를 대체함 — 구현 시 **본 문서만** 참조)
- **기준 커밋**: `2ff8b2c` (branch `main`)
---
## 0. 이의제기 판정 (Challenge Adjudication)
`agy`의 지적은 **실재하는 데이터 소실 위험을 정확히 짚었고, 채택합니다.** 다만 원인 귀속과 처방 두 가지에는 실측으로 반박되는 부분이 있어 수정 채택합니다.
| # | `agy`의 주장 / 처방 | 판정 | 근거 |
| :-- | :--- | :---: | :--- |
| ① | 로컬 커스텀 스킬 코드가 무단 덮어쓰기로 소실될 수 있다 | **채택** | E-6/E-7에서 실제 소실 재현. 잔존 사본 0건 |
| ② | "기존 `install.sh`는 파일이 있으면 건너뛰어 안전하게 **보호되었다**" | **반박** | 프레임워크 소유 파일은 이미 `cp -f`**무조건 덮어쓰기**(`install.sh:166`). 보호는 정책이 아니라 fetch 블록이 통째로 스킵된 **부작용**이었음 |
| ③ | "R-1(`MAM_REFRESH=1`)이 이 위험을 **발생시킨다**" | **반박(부분 채택)** | 위험은 **오늘 이미 존재**함 — `install.sh -f`(문서화된 플래그)와 `update.sh` 두 경로에서 재현됨. R-1은 원인이 아니라 **노출 빈도를 넓히는 요인**. 따라서 가드는 R-1의 전제조건으로 **필수**이되, R-1만 고쳐서는 부족 |
| ④ | 처방 A: 덮어쓰기 전 `.agents/skills/.../*.user-bak` 자동 백업 | **반려 → 대체** | `.agents/`는 §5.1에서 **의도적으로 gitignore하지 않기로** 결정한 경로. 백업이 사용자 저장소에 추적 파일로 쌓이고, manifest에 없어 `remove.sh`가 절대 청소하지 못함 → `agy` 본인이 직전 라운드(`c6c43df9`)에서 지적한 **백업 무한 증식**을 재현. `.mam/skill-backups/<ts>/`로 이전 |
| ⑤ | 처방 B: 로컬 변경 감지를 **diff/hash/mtime**로 | **반려 → 대체** | **치명적 오설계.** 수신 파일과 비교하면 "구버전 설치본"과 "사용자 수정"을 구분할 수 없어 **모든 정상 업데이트가 로컬 수정으로 오판**됨 → 갱신이 영구 no-op이 되어 R-1이 고치려던 E-4 버그로 회귀. mtime은 `cp -f`가 매 설치마다 갱신하므로 출처 정보가 아예 없음. **설치기가 마지막에 기록한 해시**와 비교해야 함(§2.2) |
| ⑥ | 처방 C: 기본 보존 + `--overwrite-custom` 플래그 + 안내 문구 | **채택** | 안전한 기본값. 다만 "조용히 건너뛰기"는 버전 불일치를 유발하므로 **항목별 경고 + 상태 기록**을 의무화(§2.4) |
| ⑦ | (미지적) 처방이 `install.sh` 복사 루프에만 적용됨 | **보완 추가** | `update.sh``remove.sh --force`**스킬을 먼저 전부 삭제한 뒤** 재설치한다. install.sh에 가드를 넣어도 이 경로에서는 이미 파일이 없어 아무 효과가 없음(E-7). 3개 경로 전부를 덮어야 함(§3) |
**추가 자기수정(Planner 귀책)**: Rev.1 §5.2가 `gitignore_created``.mam/install_state`에 기록하도록 했으나, `update.sh``.mam`에서 **명시적 allowlist 4종만** 스테이징하므로 업데이트 시 이 상태가 소실됩니다(§4). `agy`가 지적하지 않았지만 같은 계열의 결함이므로 함께 수정합니다.
---
## 1. 신규 실측 근거 (Evidence, Rev.2)
임시 워크스페이스에 정상 설치 후 프레임워크 소유 파일 2개(`scripts/mqtt_common.py`, delegate-job 래퍼)에 로컬 커스터마이즈를 가하고 각 경로를 실행했습니다.
### E-6. 오늘의 `install.sh -f`가 이미 무경고로 파괴함
```
$ bash install.sh -f "$WORK"
exit=0
mqtt_common.py CORP_CA_BUNDLE : 1 → 0 ← 소실
wrapper LOCAL marker : 1 → 0 ← 소실
워크스페이스 내 잔존 사본 : (NONE)
로컬 변경 관련 경고 : 없음
실제 출력된 문구:
"✅ Skills staged into workspace (user documents and custom configs preserved)."
```
마지막 줄이 핵심입니다. 설치기는 **커스텀 설정을 보존했다고 명시적으로 안심시키는 문구를 출력하면서 같은 실행에서 커스텀 코드를 파괴**합니다. 문구의 원래 의도는 `.mam.env`·사용자 문서를 가리키지만, 스킬 수정본을 잃은 사용자에게는 경고가 아니라 **역방향의 오신호**입니다. 즉 `agy`가 지적한 위험은 "R-1이 도입할 미래의 위험"이 아니라 **이미 출시되어 문서화된 플래그에 존재하는 현재의 버그**입니다.
### E-7. 업데이트 경로는 install.sh 가드로 막을 수 없음
`update.sh:151`이 실행하는 명령을 그대로 재현했습니다.
```
$ bash remove.sh --force
exit=0
.agents/skills/.../mqtt_common.py 존재 : NO — deleted
잔존 사본 : (NONE)
```
`remove.sh`가 manifest에 따라 `.agents/skills/**` 28개 파일을 삭제한 **뒤에**`install.sh`가 실행됩니다. 그 시점에 로컬 수정본은 이미 존재하지 않으므로, **복사 루프에 어떤 감지 로직을 넣어도 감지할 대상이 없습니다.** `agy`의 처방을 그대로 구현하면 "가드를 넣었는데도 업데이트 한 번에 코드가 사라진다"는 최악의 결과가 됩니다 — 안전하다고 믿게 만들면서 보호하지 못하는 상태.
### E-8. 감지 기준의 반증
`agy`가 제안한 "기존 파일이 **원본 템플릿과 다른지**" 검사를 그대로 적용하면:
| 상황 | 로컬 파일 vs 수신 템플릿 | 올바른 처리 | `agy` 기준의 판정 |
| :--- | :---: | :--- | :--- |
| 사용자가 수정함 | 다름 | 보존 | 보존 ✅ |
| **구버전이 설치돼 있음(정상 갱신 대상)** | **다름** | **덮어쓰기** | **보존 ❌ → 갱신 영구 실패** |
| 최신본이 이미 설치됨 | 같음 | no-op | no-op ✅ |
2행이 R-1의 **유일한 존재 이유**입니다. 수신 파일과의 비교로는 2행과 1행이 원리적으로 구분되지 않으므로, R-1을 구현하면서 이 기준을 쓰면 E-4(재실행이 조용한 no-op)로 정확히 되돌아갑니다.
---
## 2. 설계 R-1 — 안전 갱신 (Safe Refresh)
### 2.1 원칙
> 갱신은 **설치기가 스스로 쓴 것만** 덮어쓴다. 그 외 모든 것은 사용자 것으로 간주한다.
`.env` 마이그레이션에서 확립한 **증거 기반 소유 판정** 원칙과 동일합니다. 소유 증거는 manifest(경로)만으로 부족하며 **내용 지문**이 필요합니다.
### 2.2 소유 지문 대장 — `.mam/asset_hashes.txt`
설치기가 파일을 쓸 때마다 그 시점의 내용 해시를 기록합니다.
```
<sha256> .agents/skills/lib.sh
<sha256> .agents/skills/multi-agent-mux-delegate-job/scripts/mqtt_common.py
...
```
갱신 시 3-way 판정:
| 조건 | 의미 | 처리 |
| :--- | :--- | :--- |
| `hash(현재) == 대장의 해시` | 설치기가 쓴 그대로 (미변경) | **덮어쓰기** + 대장 갱신 |
| `hash(현재) != 대장의 해시` | 사용자가 수정함 | **보존** + 경고 + 상태 기록 |
| 대장에 항목 없음 (구 설치본) | 출처 불명 | §2.5 부트스트랩 규칙 |
| `hash(현재) == hash(수신)` | 이미 최신 | no-op (백업·경고 불필요) |
대상은 `.agents/skills/**` **28개 파일**(432 KB)뿐이므로 해시 비용은 무시할 수준입니다.
> ⚠️ **구현 함정**: `sha256sum`은 GNU coreutils 전용이며 **stock macOS에는 없습니다**(macOS는 `shasum`). 본 개발 머신에는 coreutils가 설치돼 있어 로컬 테스트는 통과하고 실사용자만 깨지는 전형적 분기가 발생합니다. §2.3의 `python3 hashlib` 인라인을 사용하십시오 — `install.sh`가 이미 python3를 하드 의존성으로 검증합니다(Rev.1 §2.3과 동일한 근거).
### 2.3 백업 위치 — `.mam/skill-backups/<UTC타임스탬프>/<원경로>`
`.user-bak` 인플레이스 방식을 쓰지 않는 이유:
1. `.agents/`**의도적으로 gitignore 대상이 아님**(Rev.1 §5.1) → 백업이 사용자 저장소에 추적 파일로 유입.
2. manifest에 등재되지 않으므로 `remove.sh`**영구히 청소하지 못함** → 언인스톨 후에도 잔재.
3. 갱신할 때마다 누적 → `agy`가 직전 라운드에서 정확히 지적한 **백업 증식** 재현.
`.mam/skill-backups/`는 ① gitignore 관리 블록의 `/.mam/`으로 이미 커버 ② `remove.sh``delete_asset ".mam"`으로 자동 정리 ③ 사용자 트리 무오염을 모두 만족합니다.
**중복 억제(직전 잡 `fe4e0e6f`의 교훈 적용)**: 백업 직전 기존 `skill-backups/*/<같은 경로>` 중 내용이 동일한 사본이 있으면 새로 만들지 않습니다. `cmp` 실패 시에는 **보존 쪽으로 실패**(백업 생성)합니다.
### 2.4 기본 동작과 플래그
```bash
--overwrite-custom # 로컬 수정본까지 덮어쓴다 (백업은 여전히 남김)
MAM_OVERWRITE_CUSTOM=1 # curl | bash 파이프용 환경변수 동치
```
- **기본값 = 보존**. `agy`의 처방 C를 채택합니다.
- 보존 시 **파일 목록을 항목별로 출력**해야 합니다. 총계만 찍으면 사용자는 무엇이 낡았는지 알 수 없습니다.
```
️ Local modifications detected — these files were NOT updated:
.agents/skills/multi-agent-mux-delegate-job/scripts/mqtt_common.py
.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job
Your copies are preserved as-is. Backups: .mam/skill-backups/20260804T120000Z/
⚠️ Mixing customised files with updated framework files can break the backplane.
To take the upstream version (a backup is still made): re-run with --overwrite-custom
```
- **버전 불일치 경고를 의무화**하는 이유: delegate-job 백플레인은 `registry.py`·`mqtt_common.py`·`publish_event.py`·래퍼가 한 벌로 동작합니다. 한 파일만 구버전으로 남으면 "업데이트 성공"이라 표시된 채 이벤트가 실패하는, 진단이 어려운 상태가 됩니다. 조용한 skip은 금지합니다.
- `.mam/version.txt``preserved_local=<n>`을 기록해 사후 진단 가능하게 합니다.
### 2.5 부트스트랩(대장이 없는 기존 설치본)
기존 설치본에는 `asset_hashes.txt`가 없습니다. 여기서 "출처 불명 = 보존"을 택하면 **설치 기반 전체가 첫 갱신에서 no-op**이 되어 R-1이 무력화됩니다. 따라서:
> 대장 없음 + 내용이 수신본과 다름 → **백업 후 덮어쓰기**, 그리고 그 사실을 출력.
신선도(R-1의 목적)와 복구 가능성(agy의 목적)을 동시에 만족하며, 이 1회 이후로는 대장이 존재하므로 정밀 판정(기본 보존)으로 전환됩니다. 내용이 수신본과 같으면 백업도 경고도 만들지 않습니다.
```
️ No asset fingerprints found (installed by an older version).
Backing up current skills to .mam/skill-backups/<ts>/ before refresh.
```
---
## 3. 3개 경로 전수 적용 (`agy` 처방의 결정적 확장)
| # | 경로 | 현재 위험 | Rev.2 처리 |
| :-- | :--- | :--- | :--- |
| P-A | `install.sh` 갱신(`-f` 및 R-1 기본 fetch) | E-6: 무경고 파괴 | §2 안전 갱신 적용 |
| P-B | `update.sh` (문서화된 **주 업데이트 수단**) | E-7: `remove.sh`가 선삭제 → 가드 무효 | **remove.sh 호출 전 스냅샷**(§3.1) |
| P-C | `remove.sh` 단독 실행(언인스톨) | 수정본이 조용히 삭제됨 | 항목별 경고 + 조건부 보존(§3.2) |
### 3.1 P-B — `update.sh` 선스냅샷
`update.sh`는 이미 `.mam.update-tmp` 스테이징 구조를 갖고 있으므로 여기에 얹습니다.
```
1) (remove.sh 호출 전) asset_hashes.txt 기준으로 수정된 프레임워크 파일 산출
2) .mam.update-tmp/skill-backups/<ts>/ 로 복사 ← remove.sh가 지우지 못하는 위치
3) bash "$REMOVER" --force "$TARGET_DIR"
4) 새 install.sh 실행
5) .mam.update-tmp/skill-backups → .mam/skill-backups 로 복원 + 목록 출력
```
**주의**: 이 스냅샷은 "수정본을 자동으로 되살리지 않습니다." 업데이트 후 트리에는 최신 프레임워크가 들어가고, 사용자 수정본은 백업으로만 남습니다. 자동 병합은 3-way merge가 필요해 셸 설치기의 책임 범위를 넘습니다. **출력에서 이 점을 명확히 말해야 합니다** — "백업했다"가 "복원했다"로 오해되면 안 됩니다.
```
💾 3 locally-modified skill file(s) backed up to .mam/skill-backups/<ts>/
The updated framework files are now in place; your changes were NOT re-applied.
Diff and re-apply manually if you still need them.
```
### 3.2 P-C — `remove.sh` 언인스톨
- 삭제 대상 중 로컬 수정 파일이 **있을 때만** 동작합니다(없으면 출력·디렉토리 생성 모두 없음 → 일반 사용자에게 잔재 0).
- 대화형: 확인 프롬프트에 수정 파일 목록을 포함해 사용자가 중단할 수 있게 합니다.
- `-y/--force`: 목록을 출력하고 `<workspace>/.mam-skill-backup.<ts>/`로 보존합니다. `.mam` 내부는 언인스톨 시 삭제되므로 쓸 수 없습니다.
- `--purge-skills` 지정 시에만 보존 없이 삭제합니다.
> 이는 직전 잡(P-1)에서 확립한 "`-y`(비대화 의도)는 삭제 권한이 아니다" 원칙의 직접 적용이며, 루트에 백업을 남기는 것은 `.mam.env.mam-backup`이 이미 따르는 기존 전례와 일관됩니다. 관리 블록에 `/.mam-skill-backup.*/`를 추가합니다(§5).
---
## 4. 자기수정 — `.mam` 잔존 allowlist 결함
`update.sh:94-120``.mam`에서 **다음 4종만** 스테이징합니다: `agent-sessions.*`, `jobs/`, `delegate_job_logs/`, `install_manifest.txt`. `remove.sh``.mam`을 통째로 지우므로 **여기 없는 것은 업데이트 때마다 소멸**합니다.
영향:
| 파일 | 도입 위치 | 소실 시 결과 |
| :--- | :--- | :--- |
| `.mam/install_state` (`gitignore_created`) | Rev.1 §5.2 | 언인스톨 시 `.gitignore` 소유 판정 불가 → 빈 파일 잔존(안전측 실패, 경미) |
| `.mam/asset_hashes.txt` | Rev.2 §2.2 | **업데이트마다 대장 소실 → 매번 부트스트랩 경로 → 정밀 판정이 영구히 동작하지 않음** |
| `.mam/version.txt` | Rev.1 §6.2 | 버전 이력 단절 |
| `.mam/skill-backups/` | Rev.2 §2.3 | 백업이 업데이트로 삭제 |
**조치**: `update.sh`의 스테이징/복원 목록에 위 4개를 추가하고, 나아가 **개별 열거 대신 `.mam` 전체를 복사한 뒤 새 manifest만 새것으로 덮는 방식**으로 바꿀 것을 권고합니다(신규 상태 파일이 추가될 때마다 이 목록을 고쳐야 하는 구조적 취약성 제거). 후자를 택할 경우 `.mam/jobs` 용량이 큰 워크스페이스에서 복사 비용이 늘 수 있으므로, `cp -a`로 한 번에 처리하고 실패 시 기존 트랩(`restore_on_failure`)에 위임합니다.
---
## 5. Rev.1에서 변경되는 항목 요약
| 절 | 변경 |
| :--- | :--- |
| §5.1 관리 블록 | `/.mam-skill-backup.*/` 1행 추가 |
| §5.2 소유권 기록 | `.mam/install_state` 유지, 단 §4에 따라 `update.sh` 잔존 목록에 반드시 포함 |
| §6.1 fetch 기본화 | **§2 안전 갱신 가드 구현이 선행 조건**. 가드 없이 기본값만 바꾸는 커밋은 금지 |
| §6.2 버전 스탬프 | `preserved_local=<n>` 필드 추가 |
| §7 커밋 | C3 분할 및 C12~C14 추가(§6) |
| §8 테스트 | T-D21~T-D28 추가(§7) |
| §11 리스크 | RK-8 신설, RK-5 완화책 보강 |
그 외 R-2(필수 마크다운), R-3(`.mam_deploy`), R-4(`.gitignore`) 설계와 B-1/B-2/B-3 차단 항목은 **Rev.1 그대로 유효**합니다. `agy`도 해당 부분에는 이의를 제기하지 않았습니다.
---
## 6. 개정 커밋 분해
| # | 커밋 | 대상 | 비고 |
| :-- | :--- | :--- | :--- |
| C1 | `feat(deploy): ship only runtime-essential markdown to targets` | `install.sh` | Rev.1 §3.2 |
| C2 | `fix(deploy): align install_mam.sh asset excludes with install.sh` | `install_mam.sh` | Rev.1 §3.3 |
| **C3a** | `feat(deploy): record asset fingerprints on install` | `install.sh` | §2.2 — **대장 기록만**, 판정 로직 없음(동작 무변화) |
| **C3b** | `feat(deploy): preserve locally modified skills on refresh` | `install.sh` | §2.3-2.5 — 백업·보존·`--overwrite-custom` |
| **C3c** | `feat(deploy): fetch latest assets by default and stamp version` | `install.sh` | Rev.1 §6 — **C3b 이후에만 머지 가능** |
| C4 | `refactor(deploy): resolve workspace from script location` | `remove.sh`, `update.sh` | Rev.1 §4.2 |
| C5 | `feat(deploy): support .mam_deploy layout in uninstaller/updater` | `remove.sh`, `update.sh` | Rev.1 §4.3/4.4 |
| C6 | `feat(deploy): install remove.sh/update.sh under .mam_deploy/` | `install.sh` | **C5 이후** |
| C7 | `feat(deploy): manage a .gitignore block for installed artifacts` | `install.sh` | Rev.1 §5.1-5.3 + §5 |
| C8 | `feat(deploy): strip the managed .gitignore block on uninstall` | `remove.sh` | **C7 이후** |
| C9 | `feat(deploy): unify install_mam.sh gitignore and deploy scripts` | `install_mam.sh` | Rev.1 §5.4 |
| **C12** | `fix(deploy): preserve .mam state files across the update cycle` | `update.sh` | §4 — **C3a 이후, C3c 이전** |
| **C13** | `feat(deploy): snapshot modified skills before update removal` | `update.sh` | §3.1 |
| **C14** | `feat(deploy): warn and preserve modified skills on uninstall` | `remove.sh` | §3.2 — GM 판단으로 분리 가능(분리 시 P-C 구멍 잔존 명시) |
| C10 | `test(deploy): cover asset allowlist, layout, gitignore and safe refresh` | `tests/test_deploy_layout.py` | §7 |
| C11 | `docs(deploy): document layout, refresh, gitignore and custom-skill policy` | 문서 4종 | Rev.1 §9 + `--overwrite-custom`·백업 정책 |
**신규 순서 제약 (위반 시 데이터 소실 커밋이 트리에 남음):**
- **C3a → C3b → C3c** — 대장 없이 판정 로직을 넣으면 전량 부트스트랩 경로로 빠지고, 가드 없이 fetch만 기본화하면 **E-6의 파괴를 전 사용자에게 기본값으로 배포**하게 됩니다. C3c를 먼저 머지하는 것은 **금지**입니다.
- **C3a → C12** — 대장을 만들자마자 업데이트가 그것을 지우면 §2.2가 영구히 부트스트랩 모드로 동작합니다.
- **C13은 C3a 이후** — 수정 파일 산출에 대장이 필요합니다.
---
## 7. 추가 테스트 (Rev.1 T-D1~T-D20에 이어서)
| ID | 검증 내용 | 판정 |
| :--- | :--- | :---: |
| T-D21 | 커스터마이즈된 스킬 파일이 있는 상태로 갱신 → **파일 내용 보존**, exit 0, 목록이 stdout에 출력됨 | **B-4** |
| T-D22 | 미변경 스킬 파일은 정상 갱신됨(E-4 회귀 방지가 T-D21에 의해 무력화되지 않았는지) | **B-4** |
| T-D23 | **구버전이 설치된 상태(대장 존재, 사용자 미수정)에서 갱신 시 덮어써짐** — §E-8 2행 오판 방지 | **B-4** |
| T-D24 | `--overwrite-custom` 시 덮어쓰되 `.mam/skill-backups/<ts>/`에 사본 존재 | |
| T-D25 | 백업이 `.agents/` 하위에 **생성되지 않음**(`find .agents -name '*.user-bak'` → 0건) | |
| T-D26 | 동일 내용 2회 갱신 시 백업 디렉토리 **증식하지 않음**(중복 억제) | |
| T-D27 | `update.sh` 1회 실행 후 `.mam/asset_hashes.txt`·`install_state`·`version.txt`·`skill-backups/`**모두 잔존** | **B-5** |
| T-D28 | 수정본이 있는 상태로 `update.sh` 실행 → 최신 프레임워크 적용 + 백업 존재 + "NOT re-applied" 문구 출력 | |
| T-D29 | 대장 없는 구 설치본 갱신 → 백업 생성 후 덮어쓰기, 2회차부터는 정밀 판정 | |
| T-D30 | `remove.sh -y` (수정본 존재) → `.mam-skill-backup.<ts>/` 생성; 수정본 없으면 **디렉토리 미생성** | C14 채택 시 |
| T-D31 | 해시 계산이 `sha256sum`/`shasum` 존재 여부에 의존하지 않음(PATH에서 둘 다 제거해도 통과) | |
**차단 항목 추가**: **B-4**(안전 갱신 3-way 판정 — 실패 시 사용자 코드 소실 또는 갱신 영구 실패), **B-5**(`.mam` 상태 파일 잔존 — 실패 시 B-4가 구조적으로 동작 불능).
**차단 항목은 총 5건: B-1, B-2, B-3, B-4, B-5.**
---
## 8. DoD 게이트 (추가분)
| 게이트 | 조건 |
| :--- | :--- |
| **I. 커스텀 보존** | 수정된 스킬 파일이 `install.sh` 갱신·`install.sh -f`·`update.sh` **3경로 모두**에서 소실되지 않음(원본 보존 또는 백업 존재) |
| **J. 갱신 유효성** | 미수정 파일은 3경로 모두에서 최신본으로 갱신됨 — 보존 로직이 R-1을 무력화하지 않았음을 증명 |
| **K. 무오염** | 갱신·업데이트·언인스톨 후 `.agents/` 하위에 백업/잔재 파일 0건 |
| **L. 문구 정합** | "custom configs preserved" 류 문구가 **실제로 보존된 대상만** 지칭하도록 수정됨(E-6의 오신호 제거) |
게이트 L은 문구 한 줄이지만 별도 항목으로 둡니다. E-6에서 확인했듯 **틀린 안심 문구는 경고가 없는 것보다 나쁩니다.**
---
## 9. 리스크 (개정)
| ID | 리스크 | 영향 | 완화 |
| :-- | :--- | :---: | :--- |
| RK-1~RK-7 | Rev.1과 동일 | — | Rev.1 §11 |
| **RK-8** | 보존 로직이 과도하게 동작해 갱신이 사실상 no-op화(E-4 회귀) | **높음** | 수신본이 아닌 **대장**과 비교(§2.2), T-D22/T-D23이 차단 |
| **RK-9** | 백업했다는 문구를 사용자가 "복원됐다"로 오해 | 중 | §3.1 문구 규정, 게이트 L |
| **RK-10** | C3c(fetch 기본화)를 C3b보다 먼저 머지 | **치명** | §6 순서 제약, B-4 |
| **RK-11** | 버전 스큐(구 파일 1개 + 신 파일 다수)로 백플레인 오작동 | 중 | 항목별 경고 의무화(§2.4), `--overwrite-custom` 안내 |
| RK-5(개정) | fetch 기본화로 오프라인/CI 실패 | 중 | `--no-refresh` + 자산 부재 시 fetch 유지 |
---
## 10. 결론
`agy`의 이의제기는 **채택**합니다. 다만 세 가지를 수정합니다.
1. 이 위험은 R-1이 만드는 것이 아니라 **`install.sh -f``update.sh`에 이미 존재하는 버그**입니다(E-6/E-7). 따라서 가드는 R-1의 부속이 아니라 **선행 조건**이며, R-1을 채택하지 않더라도 독립적으로 고쳐야 합니다.
2. 감지 기준을 **수신 템플릿과의 비교에서 설치 시점 해시 대장과의 비교로** 바꿉니다. 원안대로면 정상 갱신과 사용자 수정이 구분되지 않아 R-1이 무력화됩니다(E-8).
3. 백업 위치를 `.agents/**/*.user-bak`에서 **`.mam/skill-backups/<ts>/`**로 옮기고, 보호 범위를 `install.sh` 복사 루프에서 **install/update/remove 3경로 전체**로 확장합니다. 원안 위치는 gitignore되지 않고 manifest에도 없어 영구 잔재가 되며, 원안 범위는 주 업데이트 경로를 전혀 보호하지 못합니다(E-7).
부수적으로, `agy`가 지적하지 않았으나 같은 계열인 Planner 귀책 결함 1건(`.mam` 상태 파일이 업데이트 시 소멸, §4)을 함께 수정합니다.
---
## 11. 역할 경계
본 문서는 설계 산출물이며 **Planner는 저장소 코드를 일절 수정하지 않았습니다**(`MULTI_AGENT_RULES.md` §4). E-6~E-8 실험은 임시 디렉토리(`/tmp/mam_cust_*`)에서 수행 후 정리했으며, 저장소 워킹트리는 클린 상태입니다. 구현은 Creator, 커밋은 GM 소관입니다.
**차단 항목은 B-1, B-2, B-3, B-4, B-5 5건입니다.**
[AGREEMENT: REACHED]