18 KiB
📐 구현 계획서 Rev.2 — PRIVATE_SERVER.md 확장 및 implementation_plan.md 신설
- Job ID:
8c651798(Rev.1 =d42004ee) - Planner: claude (session:
herdr:canary-projects-multi-agent-mux-creator-claude) - Role: Planner (
MULTI_AGENT_RULES.md§1 — 본 작업에서 저장소 코드 0건 수정) - 반영 대상 Challenge:
019495f4(agy, Worker / Plan Reviewer) —[VERDICT: PASS WITH CHALLENGE] - 기준 커밋:
a9934ad— 테스트 베이스라인 276
0. Challenge 판정 요약
Challenge 3건을 실측으로 판정했습니다. 3건 모두 지적은 타당하나, 그중 1건은 제시된 해법 자체가 동작하지 않고, 1건은 지적보다 심각하며, 1건은 Rev.1 에 이미 있던 조항의 구체화입니다.
| # | 지적 | 판정 | 실측 근거 |
|---|---|---|---|
| C1-a | E-3 수정에 registry.py register 정확한 인자가 필요 |
✅ 채택 — --prompt 는 실제로 required |
registry.py:240 p_reg.add_argument("--prompt", required=True) |
| C1-b | "--job-id <id> 를 쓴다 (not --job)" + 복사·붙여넣기 명령 제시 |
❌ 실측 반증 — 해법이 동작하지 않음 | --job-id 는 register 서브파서에 존재하지 않음. 실행 시 error: unrecognized arguments: --job-id test-ping-01 |
| C1-c | (제시 명령의 나머지 부분) | ⚠️ 추가 결함 2건 발견 | ① --registry-dir 는 부모 파서 인자라 서브커맨드 앞에 와야 함(실측 오류) ② 테스트 잡이 pending 으로 영구 잔존 → --wait-any 가 수집 |
| C2 | /etc/nats/nats.conf · /data 는 비루트 환경에서 Permission denied |
✅ 채택 — 심각도 상향 | macOS 는 Permission denied 가 아니라 Read-only file system. / 가 sealed APFS 라 sudo 로도 생성 불가 |
| C3 | G-D2 를 코드 펜스 범위로 한정할 것 | ✅ 채택 — 단, Rev.1 §5.5 에 이미 명시된 조항 | Rev.1 원문: "정규식이 코드 블록 밖의 산문까지 잡으면 오탐이 납니다. 펜스(```) 안 블록으로 스코프를 한정하고…" — 다만 구체적 충돌 사례를 특정한 것은 유효한 기여 |
메타 관찰: C1-b 는 이 리뷰가 교정하려는 결함(E-1·E-2·E-3 = 검증되지 않은 복사·붙여넣기 명령)과 정확히 같은 유형을 재생산했습니다. 이는 §5.5 문서 드리프트 가드의 필요성을 역설적으로 입증하므로, Rev.2 는 가드 범위를 문서 내 실행 명령 전반으로 확대합니다(G-D4 신설).
1. C1 정밀 판정 — E-3 수정의 정확한 명령
1.1 반증 — --job-id 는 존재하지 않습니다
Challenge 가 "copy-pasteable" 로 제시한 명령을 그대로 실행한 결과:
$ registry.py --registry-dir <dir> register --job-id test-ping-01 \
--prompt "Private broker connectivity test" --agent-session "herdr:test"
registry.py: error: unrecognized arguments: --job-id test-ping-01
register 서브파서(registry.py:239-252)의 인자는 다음이 전부입니다:
--prompt (required) --agent --agent-session --role --timeout --idle-timeout
--bits --artifact --auth-token --job-type --reviewer --reviewer-session --max-iterations
--job-id 도 --job 도 없습니다. 혼동의 원인은 함수 시그니처입니다 — register_job() 함수에는 job_id 파라미터가 있고(registry.py:72 job_id = job_id or generate_job_id(bits)), CLI 의 main() 은 이를 전달하지 않습니다(:304-318 의 register_job(...) 호출에 job_id= 인자 부재). 즉 CLI 로는 잡 ID 를 지정할 수 없고, 항상 새로 채번됩니다.
1.2 추가 결함 — --registry-dir 위치
$ registry.py register --registry-dir <dir> --prompt "x"
registry.py: error: unrecognized arguments: --registry-dir <dir>
--registry-dir 은 registry.py:236 에서 부모 파서에 등록되므로 서브커맨드 앞에 와야 합니다. 문서에 실릴 명령이라면 이 순서를 틀리게 적을 여지를 없애야 합니다.
1.3 추가 결함 — 테스트 잡의 영구 잔존
register_job() 은 status: "pending"(registry.py:84)으로 레코드를 만듭니다. 그리고 job_subscriber.py::_collect_jobs() 의 --wait-any 는 status in ("pending","running") 인 모든 잡을 수집합니다. 따라서 정리하지 않은 연결 테스트 잡은:
job_subscriber.py --wait-any가 영원히 기다리는 유령 잡이 되고,pick_pending의 후보로 남습니다(agent_session일치 시).
registry.py 에는 delete/remove 서브커맨드가 없습니다(register/list/get/status/update/get-feedback/pick/logs 가 전부). 따라서 정리는 status 서브커맨드로 종결 처리하는 것이 정석입니다.
1.4 채택 — PRIVATE_SERVER.md §6 에 실릴 최종 명령
# 1) 임시 잡 등록 — ID 는 지정할 수 없고 자동 채번되므로 stdout 을 반드시 캡처한다
JID=$(.venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/registry.py \
--registry-dir .mam/jobs \
register \
--prompt "Private broker connectivity test" \
--agent-session "herdr:test")
echo "registered job: $JID"
# 2) 이벤트 발행 (rc=0 단언)
.venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py \
--registry-dir .mam/jobs \
--job "$JID" \
--event progress \
--detail "Private broker connection verified" -v
# 3) 접속 대상 단언 — 개인 서버 IP 가 보이고 broker.hivemq.com 이 없어야 한다
# (-v 로그 또는 감사 로그에서 확인)
# 4) 정리 — 미정리 시 --wait-any 가 수집하는 유령 잡으로 남는다
.venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/registry.py \
--registry-dir .mam/jobs status --job "$JID" --set completed
주의 3가지를 문서에 각주로 명시: ①
--registry-dir은 서브커맨드 앞 ② 잡 ID 는 지정 불가, 캡처 필수 ③ 4)번 정리 생략 금지.
2. C2 판정 — 심각도 상향 (Permission denied 가 아니라 생성 불가)
Challenge 는 비루트 환경의 Permission denied 를 지적했습니다. 실측 결과 macOS 에서는 그보다 강한 제약입니다:
$ mkdir -p /data
mkdir: /data: Read-only file system
$ mount | grep 'on / '
/dev/disk3s1s1 on / (apfs, sealed, local, read-only, journaled)
macOS 의 루트 볼륨은 sealed read-only APFS 이므로 store_dir: "/data" 는 sudo 로도 생성할 수 없습니다(/etc/synthetic.conf 편집 + 재부팅이 필요). 그리고 본 프로젝트의 개발 플랫폼이 darwin 이므로, Rev.1 §3 A-2 의 네이티브 스니펫은 주 사용 환경에서 곧바로 실패합니다.
따라서 C2 는 "실용성 개선"이 아니라 E-2 교정안 자체의 결함으로 분류하고, 기본값을 사용자 공간으로 전환합니다.
2.1 채택 — 사용자 공간 기본값
네이티브 (기본 경로 — sudo 불필요)
# ~/.config/nats/nats.conf
server_name: mam-hub
jetstream {
store_dir: "~/.local/share/nats/data" # 홈 디렉터리. 루트 볼륨 접근 없음
max_file: 10G
}
http_port: 8222
mqtt { port: 1883 }
websocket { port: 8080, no_tls: true } # 내부망 한정
mkdir -p ~/.config/nats ~/.local/share/nats/data
nats-server -c ~/.config/nats/nats.conf
Docker Compose (상대 경로 + 네임드 볼륨)
services:
nats:
image: nats:latest
container_name: mam-nats
restart: unless-stopped
command: ["-c", "/etc/nats/nats.conf"]
volumes:
- ./nats.conf:/etc/nats/nats.conf:ro # 호스트 상대 경로
- nats-data:/data # 네임드 볼륨
ports:
- "1883:1883" # MQTT 3.1.1 (평면 A: MAM)
- "4222:4222" # NATS
- "8222:8222" # HTTP 모니터링
- "8080:8080" # WebSocket (평면 B)
volumes:
nats-data:
컨테이너 내부 nats.conf 는 store_dir: "/data" 를 씁니다(컨테이너 안에서는 유효 — 호스트 루트와 무관).
⚠️ 문서에 명시할 검증 포인트: 네임드 볼륨의 소유권이 컨테이너 실행 사용자와 맞지 않으면 JetStream 이 기동에 실패할 수 있습니다. 기동 직후
curl -s localhost:8222/jsz로 JetStream 활성 여부를 반드시 확인하도록 절차에 넣습니다. (이 확인은 §3 A-3 Step 1 과 자연스럽게 합쳐집니다.)
3. C3 판정 — 기존 조항의 구체화 (채택)
Rev.1 §5.5 는 이미 다음을 명시했습니다:
가드 구현 주의: 정규식이 코드 블록 밖의 산문까지 잡으면 오탐이 납니다. 펜스(```) 안 블록으로 스코프를 한정하고, G-D1 은
mqtt_common을 import 해 실제 집합과 대조해야 합니다.
따라서 C3 은 신규 발견이 아니라 동일 조항의 재확인입니다. 다만 Challenge 가 특정한 구체적 충돌 사례는 유효한 기여입니다 — Rev.1 §6 은 -m 1883 에 대해 "기존 안내는 오류였다"는 정정 각주를 권고했고, Creator 가 MAM_MQTT_* 에 대해서도 같은 각주를 쓰면 G-D2 가 자기 문서의 정정 설명에 걸립니다. 이 상호작용을 Rev.1 은 짚지 않았습니다.
3.1 채택 — G-D2 스펙 확정
- 판정 대상:
```bash,```conf,```yaml및.mam.env블록 안쪽만. - 판정 제외: 산문,
> [!NOTE]인용, 표, 각주 — 즉 정정 각주는 자유롭게 작성 가능. - 구현: 파일 전체
re.search금지. 펜스 파싱 후 블록 본문에 대해서만MAM_MQTT_부재를 단언. - 자기검증: 가드 자체가 스코핑을 지키는지 확인하기 위해, 테스트가 "산문에
MAM_MQTT_를 포함한 임시 문서"를 만들어 통과함을 함께 단언합니다(오탐 방지 회귀).
4. 신설 — G-D4 (C1-b 가 드러낸 구조적 결함)
E-1·E-2·E-3 와 C1-b 는 모두 "문서에 실린 명령이 실행되지 않는다" 는 단일 원인을 공유합니다. G-D1~G-D3 는 특정 문자열을 감시할 뿐 이 원인을 막지 못합니다.
| ID | 가드 | 검증 방식 |
|---|---|---|
| G-D4 | PRIVATE_SERVER.md §6 의 검증 절차에 등장하는 registry.py / publish_event.py 호출의 인자 이름이 실제 argparse 파서에 존재할 것 |
문서에서 명령을 추출 → 해당 스크립트의 _build_parser() 를 import → 각 플래그가 파서에 등록되어 있는지 대조. --job-id 같은 유령 인자를 즉시 검출 |
Mutation: 문서의 --job 을 --job-id 로 되돌리면 FAIL 해야 합니다.
구현 주의: 실제로 명령을 실행하지 않습니다(브로커·네트워크 의존). 파서 대조만으로 C1-b 유형은 전부 잡힙니다.
테스트 증분 전망 갱신: 276 → 286(Track 0 G-1G-10) → 290(G-D1G-D4) → 291(Track 2 G-11).
5. Phase A — PRIVATE_SERVER.md 교정 (Rev.2 확정본)
Rev.1 에서 발견한 E-1~E-4 는 판정 변경 없이 유지되며, C1·C2 를 반영해 A-2·A-3 을 갱신합니다.
| 항목 | 내용 | Rev.2 변경 |
|---|---|---|
| A-1 (E-1) | §5 의 MAM_MQTT_* → MQTT_BROKER/MQTT_PORT/MQTT_TLS/MQTT_USERNAME/MQTT_PASSWORD + MQTT_CA_CERTS/MQTT_CERTFILE/MQTT_KEYFILE 추가. .mam.env:39-64 템플릿과 1:1 정렬. OS 환경변수 우선순위 1줄 명시 |
불변 |
| A-2 (E-2) | -m 1883 3개소 전량 제거(§4.1 방법 A·B, §7 Phase 2), mqtt { port: 1883 } 설정 블록 + -c 도입, 8080 노출, Compose 포트 주석 정정, max_file 상한 |
🔄 경로를 사용자 공간으로 전환(§2.1). 네이티브 ~/.config/nats/nats.conf + ~/.local/share/nats/data, Docker ./nats.conf + 네임드 볼륨 |
| A-3 (E-3·E-4) | §6 을 4단계 검증으로 재작성 | 🔄 Step 2 명령을 §1.4 확정본으로 교체(ID 캡처·--registry-dir 위치·정리 단계). Step 1 에 /jsz JetStream 확인 추가(§2.1 단서) |
| A-4 | §6 Step 2 의 "개인 브로커 환경에서도 100% 통과" → "브로커와 무관하게 통과, 연동 검증은 Step 1~3 담당". 테스트 건수 고정 표기 회피 | 불변 |
§6 최종 4단계
| Step | 내용 | 통과 기준 | 검출 대상 |
|---|---|---|---|
| 1 | curl -s http://<host>:8222/varz (MQTT 리스너) + /jsz (JetStream) |
둘 다 활성 보고 | E-2, 볼륨 소유권 문제 |
| 2 | §1.4 의 잡 등록 → 발행 | rc=0 | E-3, C1 |
| 3 | 접속 대상 단언 — 로그에 개인 서버 IP, broker.hivemq.com 부재 |
단언 성립 | E-1 |
| 4 | pytest tests/ -q + "브로커 무관 검증" 명시 |
베이스라인 통과 | (E-4 오해 방지) |
6. Phase B — 다능성 절 (Rev.1 대비 불변)
§4 와 §5 사이에 신설. 설계 결정 "하나의 서버, 두 개의 소비 평면"(Rev.1 §2)은 Challenge 가 전면 승인했으므로 그대로 유지합니다.
| 소절 | 내용 | 필수 제약 |
|---|---|---|
| 5.1 두 소비 평면 | 평면 A(MAM/MQTT, 변경 없음) vs 평면 B(NATS·WS·KV·Object). "다능성은 이관할 이유가 아니라 이관하지 않고도 얻는 이득" 을 첫 문장으로 | NATS_REPORT.md 정합성 자기선언 |
| 5.2 교차 프로토콜 브리징 | MQTT python/mqtt/jobs/<id>/events ↔ NATS python.mqtt.jobs.<id>.events. MAM 코드 0줄로 대시보드 부착 |
① 동일 계정 내에서만 ② 토픽 레벨에 . 금지(MAM은 hex라 안전) |
| 5.3 JetStream 리플레이 | python.mqtt.jobs.> 캡처 스트림으로 사후 재생 |
① 옵트인 ② $MQTT_* 내부 스트림과 별개 ③ max_age/max_bytes 필수 |
| 5.4 KV / Object Store | 홈랩 설정·피처플래그·산출물 저장 | MAM 레지스트리를 KV로 대체 금지(wait_for_job 폴링 계약) |
| 5.5 멀티테넌트 계정 | MAM/HOME 계정 분리, 계정별 쿼터·subject 권한 → A-2 ACL 충족 |
① MQTT 접속 계정은 JetStream 활성 필수 ② 격리↔관측 상충과 권고 배치(Rev.1 §2.1) |
| 5.6 운영 이점 | 단일 정적 바이너리, /varz·/jsz, 컨테이너 1개 |
— |
서술 원칙 3가지 유지: ① 기능마다 "MAM에 쓰는가" 명시 ② Track 1 이전이므로 미검증 항목은 확정형 금지(특히 S-3 retained) ③ 제약을 장점과 같은 비중으로 기술.
7. Phase C — implementation_plan.md (Rev.1 구조 유지 + 갱신)
파일명: 브리핑대로 implementation_plan.md 로 진행하되, 저장소 대문자 규약(README.md·NATS_REPORT.md·PRIVATE_SERVER.md 등)과의 불일치를 Creator 가 1줄 확인받습니다. Challenge 도 이 항목은 이의 없이 통과했습니다.
마일스톤 (M0 게이트만 갱신)
| M | 이름 | DoD | 게이트 |
|---|---|---|---|
| M0 | 문서 정합성 | E-1~E-4 교정 + 다능성 절 + 로드맵 | 🔄 G-D1~G-D4 green (G-D4 신설) |
| M1 | 내결함성 (Track 0) | B-14·B-15, 286 passed | G-1~G-10 + mutation 전건 FAIL 확인 |
| M2 | 브로커 실증 (Track 1) | 격리 클론 S-1~S-9 | S-3(retained) 통과 ← 미통과 시 mosquitto 분기 |
| M3 | 보안 종결 (Track 2) | A-2 해소, B-16 완결 | 지문 토픽 전환 확인 후 legacy 구독 제거 |
| M4 | 동기화 (Track 3) | 문서·.mam.env·deploy/* 정합 |
전체 스위트 green |
의존성: M0 → M1 → M2 → M3 → M4 (직렬). M0 의 A-1 은 M2 의 선행조건이기도 합니다 — 환경변수 이름이 틀린 채 스파이크를 돌리면 공개 브로커에 붙은 결과를 개인 브로커 성공으로 오독합니다. 이 함정을 로드맵에 경고로 명시.
본문 구성 (Rev.1 §5.2 유지): 개요 / 마일스톤 / Track 0(3-Step 순서 의존성 + G-1G-10 + 통합 검증) / Track 1(S-1S-9, 격리 클론 원칙) / Track 2(무조건 토큰 발급 G-11, 지문 토픽 3단계 순서) / Track 3(문서 동기화표 — PRIVATE_SERVER.md 자신도 대상) / 의존성·롤백 / 진행 추적표.
역할 분리 명시: IMPROVEMENTS.md = 과제 백로그(무엇을/왜), implementation_plan.md = 실행 로드맵(언제/어떤 순서로/완료 판정). 상호 링크하되 사실을 복제하지 않습니다.
8. 위험 · 비-목표 (Rev.2 갱신분)
| 위험 | 완화 | 비고 |
|---|---|---|
| 문서에 실린 명령이 또 검증 없이 들어감 | G-D4 가 파서 대조로 차단 | 🆕 C1-b 대응 |
| macOS 사용자가 §4.1 를 따라가다 실패 | 사용자 공간 기본값 + /jsz 확인 절차 |
🆕 C2 대응 |
| 정정 각주가 G-D2 에 걸림 | 펜스 스코핑 확정 + 오탐 방지 회귀 단언 | 🆕 C3 대응 |
다능성 절이 NATS_REPORT.md 와 모순되게 읽힘 |
평면 분리를 절 도입부 첫 문장으로 고정 | 불변 |
| Track 1 이전 확정형 서술 | 미검증 "검증 대상" 표기, 특히 S-3 | 불변 |
테스트 잡 잔존으로 --wait-any 오염 |
§1.4 Step 4 정리 명령 필수화 | 🆕 C1-c |
비-목표 (불변): 저장소 코드 수정 / Track 03 실제 구현 / S-9 실행.nats-py 도입 / 레지스트리 KV 대체 / client_id 안정화 / 실제 브로커 기동 및 S-1
9. 산출물 및 Reviewer 확인 요청
Creator 산출물 2종
PRIVATE_SERVER.md— Phase A 교정(§1.4 명령·§2.1 경로 포함) + Phase B 신설 §5 + §7 Phase 2 명령 동시 교정implementation_plan.md— M0~M4, 4트랙 본문, 의존성/롤백, 진행 추적표- (M0 게이트)
tests/test_deploy_freshness.py에 G-D1~G-D4 — 단, 이는 Creator 의 구현 범위이며 본 계획서는 스펙만 제공합니다
Reviewer 재현 검증 요청 4건
- C1-b 반증:
registry.py … register --job-id X --prompt Y→error: unrecognized arguments: --job-id X인가 - C1-c:
--registry-dir을register뒤에 두면 오류인가 /register가status:"pending"을 만들고--wait-any가 이를 수집하는가 - C2:
mkdir -p /data→Read-only file system이며/가sealed … read-only인가 - C3: Rev.1 §5.5 에 펜스 스코핑 조항이 이미 있었는가 (기여의 범위 확인)
미해결 확인 요청 1건: implementation_plan.md vs IMPLEMENTATION_PLAN.md 파일명 — 기본은 브리핑대로 소문자.