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

323 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.
# P2-2 (C-3a + C-4) 레거시 코드 정리 — 구현 계획서 **Rev.2**
- **Job**: `84300b64` · **Role**: Planner (`MULTI_AGENT_RULES.md` §1 — 저장소 코드 미수정)
- **선행 계획**: `07740cc7` (Rev.1) · **반영 챌린지**: `e63843ca` (`agy`, `[VERDICT: PASS WITH CHALLENGE]`)
- **기준 커밋**: `5e519e2` (Rev.1 과 **동일** — HEAD 이동 없음, §3.3 참조) · **작성 시각**: 2026-08-16
- **산출물**: 본 계획서 + `proposed/p2-2_c3a_c4.rev2.patch` + `proposed/test_legacy_isolate_flags_noop.py` + `proposed/pytest_rev2.log`
- **검증 방식**: `git clone --local --no-hardlinks` 로 만든 스크래치패드 사본에 패치를 적용해 전체 스위트 + 변이 검사(mutation check)를 실행했습니다. 본 저장소 워킹 트리는 계획 수립 전후 모두 clean 입니다.
---
## 0. 챌린지 판정 요약
| # | 챌린지 | 판정 | 근거 |
|---|---|---|---|
| **1** | `--isolate`/`--no-isolate` 자동화 회귀 테스트 부재 | **✅ 수용 + 강화** | 제시된 테스트를 그대로 실행 → 통과(0.09s). 변이 4종 중 3종 검출. 나머지 1종(usage 문서 줄 삭제)을 잡도록 **assert 1줄 추가** |
| **2** | `test_tier1_unit.py:31` 섹션 헤더 `(7 Test Cases)` 동기화 | **✅ 수용** | 현재 5개 헤더 **전부 정확**(7/6/5/5/6 = 29 = 실측)함을 확인. 방치하면 이 파일 최초의 불일치가 됨. `(5 Test Cases)` 로 갱신 |
| **3** | `IMPROVEMENTS.md` 라인 번호를 최신 HEAD 로 동기화 | **⚖️ 사실관계는 반박, 우려는 수용** | HEAD 는 `5e519e2`**이동하지 않았고** Rev.1 의 20개 인용 라인은 **전부 현행 일치**. 챌린지의 "문두 완료 **15건**" 은 실측 **16건**. 다만 §6.1 편집들이 **서로의 오프셋을 밀어내는** 문제는 실재하므로 **편집 순서 명세를 신설**(§4.3) |
**Rev.1 대비 순증분**: 테스트 1건 추가(순감 4 → 순감 3), 섹션 헤더 1줄, 편집 순서 명세 1개 절. 수집 개수 **259 → 256**.
---
## 1. Challenge 1 검증 — 수용, 그리고 한 줄 강화
### 1.1 제안된 테스트를 그대로 실행
챌린저가 제시한 코드를 **한 글자도 고치지 않고** 패치된 사본에 넣어 실행했습니다.
```
1 passed in 0.13s
0.09s call test_create_session_legacy_isolate_flags_noop
0.02s setup
```
동작합니다. 다만 **실측 0.09s** 로, 챌린지가 적은 `<0.05s` 보다 약 2배입니다. 원인은 `create_session.sh:25` 가 인자 파싱 **이전에** `source "$_lib_sh"` 를 하기 때문이며(플래그 2개 × 서브프로세스 2회), 절대값이 미미하므로 채택에는 영향이 없습니다. 계획에는 실측값으로 적습니다.
### 1.2 변이 검사 — 이 테스트가 실제로 무엇을 잡는가
"통과한다" 는 것만으로는 가드가 되지 못하므로, 이 테스트가 막으려는 회귀를 직접 주입해 **실패하는지** 확인했습니다.
| 변이 | 내용 | 챌린지 원안 | 강화안 |
|---|---|---|---|
| **A** | `--isolate` · `--no-isolate` 분기 **둘 다 삭제** | ✅ FAIL (`rc=2`, `ERROR: unknown arg: --isolate`) | ✅ FAIL |
| **B** | `--no-isolate` **한쪽만** 삭제 | ✅ FAIL (`ERROR: unknown arg: --no-isolate`) | ✅ FAIL |
| **C** | 분기는 두되 `echo` 를 지워 **조용한 no-op** 으로 | ✅ FAIL (stderr assert) | ✅ FAIL |
| **D** | 분기는 두되 `usage()` 의 문서 줄(`:42-43`) 삭제 | ❌ **PASS (놓침)** | ✅ FAIL |
| **E** | 무변이 대조군 | ✅ PASS | ✅ PASS |
변이 A/B/C 를 잡는다는 점에서 챌린지의 지적은 **정확하고 실효적**입니다. 특히 B(한쪽만 삭제)를 잡는 것은 `for flag in [...]` 루프 덕분이며, 원안 설계가 이미 이 경우를 고려했음을 보여줍니다.
**D 만 빠져나갑니다.** `--isolate`/`--no-isolate``create_session.sh:42-43` 에서 **usage 에 정식 문서화되어 있는** 옵션입니다. 챌린지가 지목한 "누군가 미사용으로 오판하여 삭제" 시나리오에서, 가장 먼저 지워질 후보는 실행 분기가 아니라 **도움말 줄**입니다(C-6 이 정확히 "도움말과 실제 파서의 불일치" 과제인 점을 상기하십시오). 그리고 `-h` 를 이미 실행하고 있으므로 그 출력은 **이미 `res.stdout` 에 잡혀 있습니다** — 서브프로세스 추가 없이 assert 한 줄이면 닫힙니다.
### 1.3 채택 최종본
```python
def test_create_session_legacy_isolate_flags_noop(mam_sandbox):
"""Legacy --isolate/--no-isolate must stay a documented no-op, not an arg-parser error."""
create_script = mam_sandbox / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh"
for flag in ["--isolate", "--no-isolate"]:
res = subprocess.run(["bash", str(create_script), flag, "-h"], capture_output=True, text=True)
assert res.returncode == 0, f"{flag} rejected by arg parser: {res.stderr}"
assert "NOTE: --isolate/--no-isolate is a no-op" in res.stderr
assert flag in res.stdout, f"{flag} missing from usage() help text"
```
원안 대비 변경은 **3줄**입니다.
1. `assert flag in res.stdout` **신설** — 변이 D 를 닫습니다. 부분 문자열 오탐 우려가 있어 확인했으나 **`"--isolate" in "--no-isolate"``False`** 입니다(`--no-isolate``--no` 다음에 하이픈이 하나뿐이므로 `--isolate` 를 부분 문자열로 포함하지 않음). 따라서 단순 `in` 으로 두 플래그가 모호함 없이 구분됩니다.
2. `assert res.returncode == 0`**실패 메시지 추가** — 실패 시 `assert 2 == 0` 대신 어느 플래그가 왜 거부됐는지 즉시 보이게 합니다(루프라서 어느 회차인지 모호해집니다).
3. docstring 을 계약 문장으로 교체 — "documented no-op" 이 assert 3개의 의도를 그대로 서술합니다.
### 1.4 배치 결정 — `test_tier1_unit.py` FEATURE 1
챌린지의 제안대로 tier1 에 둡니다. 스크립트를 실행하는 테스트라 tier2 도 후보였으나, **동일 파일에 정확한 선례가 있습니다**:
```python
def test_resume_script_invalid_args(mam_sandbox): # tier1:114 (현행)
script_path = mam_sandbox / "skills" / "multi-agent-mux-resume" / "scripts" / "resolve_session_id.sh"
res = subprocess.run(["bash", str(script_path), ...], capture_output=True, text=True)
assert res.returncode == 2
assert "ERROR: --agent required" in res.stderr
```
`mam_sandbox / "skills" / ...` 경로 관례, `subprocess.run`, rc + stderr assert — 신규 테스트가 이 관용구를 그대로 따릅니다. tier1 은 이미 **인자 파서 단위 테스트의 자리**입니다. `subprocess``tests/test_tier1_unit.py:2` 에서 이미 임포트되어 있어 추가 임포트도 없습니다.
**삭제되는 3건이 있던 바로 그 자리**(`test_create_derive_session_name_weird_characters``test_create_validate_env_key` 사이)에 넣습니다.
### 1.5 격리 검증 — 신규 테스트는 저장소를 오염시키지 않는가
이 테스트는 `create_session.sh` 를 실행하고, 그 스크립트는 `:25` 에서 `lib.sh` 를 source 하며, `lib.sh``_init_herdr_isolation` 으로 `$WORKSPACE_ROOT/.mam/shim/herdr`**씁니다**. 실제로 쓰기가 일어나는 테스트이므로 확인했습니다.
```
rm -rf <clone>/.mam
pytest ...::test_create_session_legacy_isolate_flags_noop → 1 passed
after run, .mam exists? NO
```
`conftest.py:44``monkeypatch.setenv("WORKSPACE_ROOT", str(tmp_path))` 가 서브프로세스까지 상속되어 쓰기가 `tmp_path` 안에 갇힙니다. **저장소 트리에 흔적 0건.**
(참고: 전체 스위트를 돌리면 사본에 `.mam/shim/` 이 생깁니다. 이는 **다른 기존 테스트**들이 만드는 것으로 P2-2 이전부터의 성질이며 `.gitignore:14` 대상입니다. 신규 테스트가 원인이 아님을 위 실험이 분리해 보여 줍니다.)
### 1.6 이 테스트가 여전히 잡지 못하는 것 (명시)
- `create_session.sh` **본문**의 동작(세션 생성 자체)은 검증하지 않습니다. `-h` 로 조기 종료하므로 파서 진입 지점까지만 봅니다. 이는 의도된 범위입니다 — 챌린지가 요구한 것은 "인자 파서 게이트" 입니다.
- 다른 레거시 no-op 플래그가 생기면 이 테스트는 자동으로 커버하지 않습니다. `for flag in [...]` 목록에 추가해야 합니다.
---
## 2. Challenge 2 검증 — 수용, 범위 명확화
`tests/test_tier1_unit.py:31``# FEATURE 1: Create Session (7 Test Cases)` 를 갱신하라는 지적입니다. 파일 전체의 헤더 정합성을 실측했습니다.
| 헤더 라인 | 섹션 | 선언 | 실측 |
|---|---|---|---|
| 31 | FEATURE 1: Create Session | 7 | **7** ✅ |
| 106 | FEATURE 2: Resume Session | 6 | **6** ✅ |
| 152 | FEATURE 3: Stop Session | 5 | **5** ✅ |
| 197 | FEATURE 4: Status Query | 5 | **5** ✅ |
| 283 | FEATURE 5: Monitor/Reconcile | 6 | **6** ✅ |
| | 합계 | 29 | **29** (`grep -c "^def test_"` = 29) ✅ |
**5개 헤더 전부 현재 정확합니다.** 이 파일은 메타데이터를 성실하게 유지해 온 파일이고, 따라서 `(7 Test Cases)` 를 방치하면 그것이 **이 파일 최초의 불일치**가 됩니다. 챌린지 판단이 옳습니다.
**갱신값은 `(5 Test Cases)`** 입니다 — 7 3(삭제) + 1(신규) = 5. 다른 4개 헤더는 손대지 않습니다(변동 없음).
패치 적용 후 재실측:
```
31 FEATURE 1: Create Session claimed=5 actual=5 OK
77 FEATURE 2: Resume Session claimed=6 actual=6 OK
123 FEATURE 3: Stop Session claimed=5 actual=5 OK
168 FEATURE 4: Status Query claimed=5 actual=5 OK
254 FEATURE 5: Monitor/Reconcile claimed=6 actual=6 OK
file total: 27
```
`tests/test_tier2_component.py` 에는 이런 개수 선언 헤더가 없으므로 해당 파일은 추가 조치 불필요합니다.
---
## 3. Challenge 3 판정 — 사실관계 반박, 우려는 §4.3 으로 수용
### 3.1 HEAD 는 이동하지 않았습니다
```
$ git rev-parse --short HEAD
5e519e2
$ git log --oneline -1
5e519e2 docs(improvements): synchronize header counts and roadmap table with completed P2-1 task
```
Rev.1 의 기준 커밋이 `5e519e2` 이고 현재 HEAD 도 `5e519e2` 입니다. 챌린지가 지목한 `b490713`(P2-1 수정)은 **4 커밋 이전**이며, 그 이후의 `af3dc16` → `a875b13` → `5e519e2` 가 전부 문서 커밋입니다. 그중 `5e519e2` 는 커밋 제목 그대로 **"헤더 개수와 로드맵 표를 P2-1 완료와 동기화"** 한 커밋 — 즉 챌린지가 요구하는 동기화는 **Rev.1 작성 시점에 이미 반영된 상태**였습니다.
### 3.2 Rev.1 의 인용 라인 20개 전수 재검증
챌린지를 계기로 §6.1·§6.3 이 인용한 모든 라인을 다시 대조했습니다.
| 인용 | 현행 내용 | 판정 |
|---|---|---|
| `:5` | `총 추적 미해결 과제: 9건 (아키텍처 2, 엣지케이스 4, 오케스트레이션 0, 레거시 잔재 3)` | ✅ |
| `:6` | `완료된 과제: **16건** (A-1 … P2-1-DelegateJobSafe-TrapFix)` | ✅ |
| `:70` | `## 2. 엣지 케이스 및 런타임 버그 (Edge-case Bugs — 5건)` | ✅ |
| `:107` | `## 4. 레거시 잔재 및 죽은 코드 (Legacy Remnants — 3건)` | ✅ |
| `:109-111` | C-3 제목 / C-3a / C-3b | ✅ |
| `:113-116` | C-4 제목 / 실제 대상 3종 / 목록 제외 / provision_isolation 중복 | ✅ |
| `:123` | `## 5. 완료된 과제 (Completed Tasks — 13건)` | ✅ |
| `:249` | 로드맵 P2-2 행 ("공허한 테스트 5건") | ✅ |
| `:260` | "정리(C 계열)를 P2 에 두는 이유" | ✅ |
| `:317` `:319-322` | §6.5-1 / §6.5-2 | ✅ |
| `:328` | §6.6 결론 ("총 12건") | ✅ |
**20/20 일치.** 오프셋 충돌은 발생하지 않습니다.
### 3.3 챌린지의 수치 주장은 사실과 다릅니다
챌린지 §Challenge 3 은 *"완료 과제 개수도 13건(문두 완료 **15건**)으로 갱신되었습니다"* 라고 적었습니다. 실측:
```
:6 - **완료된 과제**: **16건** (A-1, A-3, A-5, B-1, B-3, B-4, B-7, B-8, C-1, C-2,
O-1, O-2, O-3, O-4-OrcOnboard,
Herdr-0.8.0-Compat-SanitizeHash, P2-1-DelegateJobSafe-TrapFix)
```
쉼표 구분 항목 수 = **16개**, 선언값 = **16건**. 문두는 15가 아니라 **16**이며 목록과 자체 정합합니다. Rev.1 §6.1 의 "16건 → 17건" 이 맞습니다.
한편 챌린지가 같은 문장에서 언급한 *"C-3/C-4 섹션의 시작 위치가 `IMPROVEMENTS.md:107`"* 은 Rev.1 §6.1 이 이미 `:107` 로 적고 있는 값과 동일합니다 — 이 대목은 정정이 아니라 **Rev.1 의 확인**입니다.
### 3.4 그럼에도 수용하는 부분 — 편집 상호 간섭
챌린지가 우려한 "오프셋 충돌" 은 **HEAD 대비**로는 존재하지 않지만, **편집 도중**에는 실재합니다. §6.1 의 지시 11개가 **전부 같은 파일**을 대상으로 하고, 그중 3개가 줄 수를 바꿉니다:
- `:113-116` C-4 블록 **삭제** (−4줄) → 이후 모든 라인 상향 이동
- `:123` 직후 P2-2 완료 항목 **삽입** (+16줄) → 이후 모든 라인 하향 이동
- `:109-111` C-3 축소 (줄 수 변동 가능)
따라서 구현자가 `:5``:328` 순으로 위에서 아래로 편집하면 **`:249` 이후의 라인 번호가 전부 어긋납니다.** 이것이 챌린지가 감지한 실제 위험이며, 해법은 "HEAD 동기화" 가 아니라 **편집 순서 규정**입니다. §4.3 에 신설했습니다.
---
## 4. Rev.1 대비 변경 명세
> Rev.1(`07740cc7`)의 §1~§4(실측·경계·위험), §7.1 게이트, §8 비용·효과 정정, §9 예상 지적은 **전부 유효하며 변경 없습니다.** 아래는 델타만 기술합니다.
### 4.1 S5 개정 — 테스트 4건 제거 → **4건 제거 + 1건 추가 + 헤더 1줄**
```
tests/test_tier1_unit.py
:31 "(7 Test Cases)" → "(5 Test Cases)" [Challenge 2]
:52-88 test_create_isolation_lever
test_create_isolation_env_prefix 삭제
test_create_isolation_cmd_args
같은 자리 test_create_session_legacy_isolate_flags_noop 신설 [Challenge 1]
tests/test_tier2_component.py
:99-107 test_comp_create_isolation_folder_setup 삭제
```
패치 전체(`proposed/p2-2_c3a_c4.rev2.patch`): **5 files, +14 / 72**. Rev.1 은 +5/72 였습니다.
### 4.2 §7.2 개정 — 수동 스모크 항목 정리
Rev.1 §7.2 의 3개 요구 중 **3번(`--isolate`/`--no-isolate` 각 1회 수동 실행)은 자동화되었으므로 삭제**합니다. 이것이 Challenge 1 의 핵심 성과입니다 — 수동 절차가 CI 게이트로 승격되었습니다.
구현자가 여전히 직접 해야 할 것:
1. **`pytest tests/ -q` 재실행** — 사본에는 `.mam/`(gitignore)이 없습니다. **256 passed** 재현 확인.
2. **`create_session.sh` 실경로 스모크 1회** (`--dry-run` 가능) — `ISOLATE` 제거가 파서 본류에 영향 없음을 실행으로 확인. (신규 테스트는 `-h` 조기 종료 경로까지만 봅니다 — §1.6)
### 4.3 §6.1 신설 — 편집 순서 (Challenge 3 수용)
`IMPROVEMENTS.md` 의 11개 지시는 **반드시 아래 순서(= 라인 번호 내림차순)로** 적용하십시오. 그러면 앞선 편집이 뒤이을 편집의 라인 번호를 바꾸지 않습니다.
| 순 | 대상 | 작업 | 줄 수 변화 |
|---|---|---|---|
| 1 | `:319-322` §6.5-2 | C-4 완료 표기. **`:320``lib.sh:57``:79``lib.sh:83``:105` 로 정정** | ±0 |
| 2 | `:317` §6.5-1 | C-3a 완료 표기. 총계 표현 있으면 "4건" | ±0 |
| 3 | `:260` | 근거 문장 교체 (Rev.1 §8) | ±0 |
| 4 | `:249` 로드맵 행 | "5건"→"4건", `(✅ 완료 — 256/256 PASS)` | ±0 |
| 5 | `:123` 직후 | §5 최상단에 P2-2 완료 항목 삽입 (§4.4) | **+16** |
| 6 | `:123` §5 제목 | 항목 수 갱신 | ±0 |
| 7 | `:113-116` C-4 블록 | §4 에서 **삭제** (내용은 5번에서 이미 §5 로 이관) | **4** |
| 8 | `:109-111` C-3 | 제목을 `C-3b: isolation.root 소비자 처분 (보류 — A-4 M2)` 으로 축소, C-3a 줄 제거 | −1 내외 |
| 9 | `:107` §4 제목 | `Legacy Remnants — 3건`**2건** | ±0 |
| 10 | `:6` | 완료 `16건`**17건**, 목록에 `P2-2-C3a-C4-LegacyCleanup` 추가 | ±0 |
| 11 | `:5` | 미해결 `9건`**8건**, `레거시 잔재 3건`**2건** | ±0 |
**대안 (권장)**: 라인 번호 대신 **고유 문자열 앵커**로 편집하면 순서 제약이 사라집니다. 위 11개 지시는 모두 유일 문자열을 갖고 있습니다(예: `Legacy Remnants — 3건`, `공허한 테스트 5건`, `Completed Tasks — 13건`). 도구가 문자열 치환을 지원한다면 그쪽이 안전합니다.
> ⚠️ Rev.1 §6.3 은 "`:115`/`:320` 의 라인 번호를 정정" 하라고 했으나, **`:115` 는 7번에서 삭제되는 C-4 블록 안에 있습니다.** 따라서 정정 대상은 `:320` **하나**이며, `:115` 의 내용은 §5 로 이관될 때(§4.4 마지막 항목) 이미 올바른 `lib.sh:83-84 → :105` 로 적혀 나갑니다. Rev.2 에서 정정합니다.
### 4.4 §6.2 개정 — §5 완료 항목 (테스트 문구 수정)
Rev.1 초안에서 **두 번째 불릿만** 교체합니다.
```markdown
- 위 스텁의 빈 출력만 재확인하던 공허한 테스트 4건(`tests/test_tier1_unit.py` 3,
`tests/test_tier2_component.py` 1)을 제거하고, 그 자리에 `--isolate`/`--no-isolate`
레거시 no-op 플래그의 인자 파서 계약을 고정하는
`test_create_session_legacy_isolate_flags_noop` 1건을 신설했습니다. 신규 테스트는
분기 삭제·한쪽만 삭제·조용한 no-op 화·usage 문서 줄 삭제 4종 변이를 모두 검출함을
변이 검사로 입증했습니다. `test_tier1_unit.py:31` 섹션 헤더도 `(5 Test Cases)`
동기화했습니다.
```
마지막 불릿의 수치도 갱신합니다: **`전체 회귀 256/256 PASS (100%)` (259 → 256, 순감 3 = 제거 4 신설 1)**.
### 4.5 §6.4 개정 — `LOG.md`
주요 구현 목록의 테스트 줄을 교체하고 검증 수치를 갱신합니다.
```markdown
- `tests/test_tier1_unit.py` / `tests/test_tier2_component.py`: 공허한 테스트 4건 제거 및
`--isolate`/`--no-isolate` no-op 회귀 가드 1건 신설(변이 4종 검출 입증), 섹션 헤더 동기화.
- **검증**: `pytest tests/ -q` **256 passed (100%)**.
```
### 4.6 §3 미접촉 경계 — 한 줄 보강
Rev.1 §3 표의 `--isolate`/`--no-isolate` 행 사유를 다음으로 대체합니다.
> 레거시 호환 경고이자 **`create_session.sh:42-43` 에 정식 문서화된 옵션**. 제거하면 기존 호출자가 `unknown arg` 로 `exit 2`. **P2-2 이후로는 `test_create_session_legacy_isolate_flags_noop` 이 CI 게이트로 이를 고정한다.**
---
## 5. Rev.2 검증 결과
| # | 검증 | 기대 | 실측 |
|---|---|---|---|
| V1 | `bash -n lib.sh` / `create_session.sh` | rc=0 | ✅ (Rev.1 에서 확인, 해당 hunk 무변경) |
| V2 | `ast.parse(registry.py)` | rc=0 | ✅ (동상) |
| V3 | 신규 테스트 단독 실행 | pass | ✅ **1 passed, 0.09s call** |
| V4 | 변이 A (분기 2개 삭제) | FAIL | ✅ FAIL |
| V5 | 변이 B (한쪽만 삭제) | FAIL | ✅ FAIL |
| V6 | 변이 C (조용한 no-op) | FAIL | ✅ FAIL |
| V7 | 변이 D (usage 문서 줄 삭제) | FAIL | ✅ FAIL *(강화 후. 원안은 PASS)* |
| V8 | 변이 E (무변이 대조군) | PASS | ✅ PASS |
| V9 | 신규 테스트의 저장소 오염 | 0건 | ✅ `.mam` 미생성 |
| V10 | tier1 섹션 헤더 5개 정합 | 전부 일치 | ✅ 5/5 |
| V11 | 미사용화되는 헬퍼·임포트 | 없음 | ✅ `run_lib_func` 15회, `get_mqtt_common` 7회, `subprocess`/`shlex`/`hmac`/`hashlib` 전부 잔존 사용 |
| V12 | 수집 개수 | 259 → 256 | ✅ **256 collected** |
| V13 | `pytest tests/ -q` 전체 | 256 passed | ✅ **256 passed in 392.29s** |
### 5.1 전체 회귀 (Rev.2 사본)
```
256 passed in 392.29s (0:06:32)
```
원본 로그는 `proposed/pytest_rev2.log` 입니다. 참고로 Rev.1(255건) 은 376.08s 였습니다 — 차이 16s 는 신규 테스트 1건(0.09s)으로 설명되지 않는 **실행 간 편차**이며, Rev.1 §8 에서 이미 밝혔듯 이 스위트의 총 실행 시간은 P2-2 의 판단 근거가 아닙니다.
---
## 6. 검증 한계 (Rev.1 §10 갱신)
1. **실측은 `5e519e2` 로컬 클론에서 수행**. 실제 트리에서의 256 passed 는 **미확인** — §4.2-1 이 요구합니다.
2. **`create_session.sh` 본류 실행 스모크 미수행.** 신규 테스트는 `-h` 조기 종료 경로까지만 검증합니다(§1.6). §4.2-2 가 요구합니다.
3. **변이 검사는 `create_session.sh` 4종에 한정.** `lib.sh` 스텁 제거·`registry.py`·`_REAL_HERDR_PATH` 에는 변이 검사를 적용하지 않았습니다(제거 대상이라 고정할 계약이 없음 — Rev.1 §4.3).
4. **`_REAL_HERDR_PATH` 의 저장소 외부 소비자 미검색.** 확인 범위는 저장소 트리, 생성된 `.mam/shim/herdr`, `.agents/hooks/`, `~/.claude/settings.json` (Rev.1 §10-4 유지).
5. **`shellcheck` 미설치** — 정적 분석은 `bash -n` 까지.
6. **macOS · 직렬 실행**. Linux · `pytest-xdist` 병렬 미검증(xdist 미설치). 신규 테스트는 `mam_sandbox`(`tmp_path`) 안에서만 쓰기하므로 병렬 안전할 것으로 **판단**하나 실측은 아닙니다.
7. **챌린지 §Challenge 3 의 "15건" 반박은 `IMPROVEMENTS.md` 현행 파일 대조에 근거**합니다. 챌린저가 다른 시점의 파일을 봤을 가능성은 배제하지 못하나, HEAD 가 `5e519e2` 로 고정되어 있고 워킹 트리가 clean 이므로 두 에이전트가 본 파일은 동일해야 합니다.
8. 본 계획은 Planner 산출물이므로 **`IMPROVEMENTS.md` / `LOG.md` / 소스를 직접 수정하지 않았습니다.** §4 는 구현자가 적용할 명세입니다.