From adecff21945d356960aa965538eded924954bd5a Mon Sep 17 00:00:00 2001 From: Godopu Date: Sun, 23 Aug 2026 16:22:10 +0900 Subject: [PATCH] docs: synchronize MESSAGING.md, IMPROVEMENTS.md, implementation_plan.md and add D-31/D-32 freshness guards --- .../plan-1fa7183a.md | 375 ++++++++++++++++++ .../report-9f9e7c2c.md | 196 +++++++++ .gitmodules | 2 +- IMPROVEMENTS.md | 54 ++- MESSAGING.md | 108 +++-- deploy/gitea-ci.yml | 2 + implementation_plan.md | 31 +- tests/test_deploy_freshness.py | 86 ++++ 8 files changed, 806 insertions(+), 48 deletions(-) create mode 100644 .agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-1fa7183a.md create mode 100644 .agents/reports/canary-projects-multi-agent-mux-creator-cline/report-9f9e7c2c.md diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-1fa7183a.md b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-1fa7183a.md new file mode 100644 index 0000000..fc48f2e --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-1fa7183a.md @@ -0,0 +1,375 @@ +# ๐Ÿ”Ž ๋ฌธ์„œ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ๋ฐ ๋™๊ธฐํ™” ๊ณ„ํš์„œ Rev.2 (Job `eb04e918`) + +- **์ž‘์„ฑ์ผ**: 2026-08-23 +- **์—ญํ• **: Planner (`.agents/MULTI_AGENT_RULES.md` ยง1 โ€” Planner ๋Š” ์ €์žฅ์†Œ ์ฝ”๋“œ/๋ฌธ์„œ๋ฅผ **์ˆ˜์ •ํ•˜์ง€ ์•Š์œผ๋ฉฐ**, ์‚ฐ์ถœ๋ฌผ์€ ๋ณธ ๋ณด๊ณ ์„œ์ž…๋‹ˆ๋‹ค) +- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `916185c`, ์ž‘์—… ํŠธ๋ฆฌ clean, `main` ์€ `origin/main` ๋ณด๋‹ค **ahead 2** +- **์„ ํ–‰ ๋ฆฌ๋น„์ „**: `1fa7183a` (Rev.1) โ† ๋ณธ ๋ฌธ์„œ๊ฐ€ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค +- **ํŒ์ • ๋Œ€์ƒ ๋ฆฌ๋ทฐ**: `b93680ab` (agy, `[VERDICT: PASS WITH CHALLENGE]`) โ€” CI ์„œ๋ธŒ๋ชจ๋“ˆ ์ธ์ฆ / D-31 ์Šค์ฝ”ํ”„ / B-17 fail-closed +- **๊ฒ€์ฆ ๋Œ€์ƒ**: `MESSAGING.md`, `IMPROVEMENTS.md`, `implementation_plan.md` + +--- + +## A. ๋ฆฌ๋ทฐ ํŒ์ • (Adjudication of Challenge `b93680ab`) + +### A-0. ํŒ์ • ์š”์•ฝ + +| ์ฑŒ๋ฆฐ์ง€ | ํŒ์ • | ํ•ต์‹ฌ ๊ทผ๊ฑฐ | +|---|---|---| +| **C1** ์„œ๋ธŒ๋ชจ๋“ˆ ์ธ์ฆยทURL ์ œ์•ฝ | ๐ŸŸข **์ „์ œ ํ™•์ฆ โ€” ๋‹ค๋งŒ ์ฒ˜๋ฐฉ ํ˜•ํƒœ๋Š” ํ‹€๋ฆผ** | `laa/nats-docker` ๋Š” ์‹ค์ œ๋กœ **๋น„๊ณต๊ฐœ**(์ต๋ช… `ls-remote` โ†’ `Failed to authenticate user`). ๊ทธ๋Ÿฌ๋‚˜ ์ œ์•ˆ๋œ `url = ../nats-docker` ๋Š” **`tmpl/nats-docker`** ๋กœ ํ•ด์„๋˜์–ด **์ž˜๋ชป๋œ ์กฐ์ง**์„ ๊ฐ€๋ฆฌํ‚ด(์‹ค์ธก). ์˜ฌ๋ฐ”๋ฅธ ํ˜•ํƒœ๋Š” `../../laa/nats-docker` | +| **C2** D-31 ๊ณผ๋„ํ•œ ์ œ์•ฝ | โœ… **์ „๋ฉด ์ˆ˜์šฉ โ€” Rev.1 ์˜ ๋…ผ๊ฑฐ๊ฐ€ ํ‹€๋ ธ์Œ** | `lint-shell`/`lint-python` ์€ `.agents/`ยท`deploy/` ๋งŒ ํ›‘์œผ๋ฉฐ ์„œ๋ธŒ๋ชจ๋“ˆ ๊ฒฝ๋กœ๋ฅผ ์ฝ์ง€ ์•Š์Œ(์‹ค์ธก). Rev.1 ์ด ๋‚ด์„ธ์šด "๋น„๋Œ€์นญ" ๋…ผ๊ฑฐ๋Š” ์„ฑ๋ฆฝํ•˜์ง€ ์•Š์Œ | +| **C3** ๋ช…์‹œ์  `MAM_ENV_FILE` fail-closed | โœ… **์›์น™ ์ˆ˜์šฉ โ€” ๋‹ค๋งŒ ์ฐจ๋‹จ ์ง€์ ์„ ์˜ฎ๊ฒจ์•ผ ํ•จ** | `_load_dotenv()` ๋Š” **import ์‹œ์ **์— ํ˜ธ์ถœ๋˜๊ณ (`mqtt_common.py:112`) ํ…Œ์ŠคํŠธ 3๊ฐœ ํŒŒ์ผ์ด `mqtt_common` ์„ import ํ•จ. ์—ฌ๊ธฐ์„œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด ์Šค์œ„ํŠธ ์ž์ฒด๊ฐ€ ๋ถ•๊ดด | + +๋ฆฌ๋ทฐ์–ด์˜ ์„ธ ์ง€์ ์€ ๋ชจ๋‘ ์‹ค์žฌํ•˜๋Š” ๋งน์ ์„ ์งš์—ˆ๊ณ , ๊ทธ์ค‘ ๋‘˜์€ **Rev.1 ์˜ ์ฒ˜๋ฐฉ์„ ์ง์ ‘ ๊ต์ •**ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ C1 ์˜ ๊ตฌ์ฒด์  ์ฒ˜๋ฐฉ๊ณผ C3 ์˜ ์ฐจ๋‹จ ์ง€์ ์€ ๊ทธ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๊ฐ๊ฐ ์„œ๋ธŒ๋ชจ๋“ˆ์„ ๊นจ๋œจ๋ฆฌ๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๋ฅผ ๊นจ๋œจ๋ฆฝ๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ ์ธก์ •์œผ๋กœ ๊ต์ •ํ•ฉ๋‹ˆ๋‹ค. + +--- + +### A-1. C1 โ€” ์ „์ œ๋Š” ์˜ณ๋‹ค. ์ฒ˜๋ฐฉ์˜ ํ˜•ํƒœ๊ฐ€ ํ‹€๋ ธ๊ณ , ์ฒ˜๋ฐฉ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•˜๋‹ค + +#### (1) ์ „์ œ ํ™•์ฆ: ์„œ๋ธŒ๋ชจ๋“ˆ์€ ์‹ค์ œ๋กœ ๋น„๊ณต๊ฐœ๋‹ค + +์ต๋ช…(์ž๊ฒฉ์ฆ๋ช… ์—†์ด) `ls-remote` ์‹ค์ธก: + +| ๋Œ€์ƒ | ๊ฒฐ๊ณผ | +|---|---| +| `https://git.godopu.com/laa/nats-docker` | ๐Ÿ”ด `remote: Failed to authenticate user` โ†’ **๋น„๊ณต๊ฐœ** | +| `https://git.godopu.com/tmpl/multi-agent-mux` (์ƒ์œ„ ์ €์žฅ์†Œ) | ๐ŸŸข `629a67fโ€ฆ HEAD` ์‘๋‹ต โ†’ **๊ณต๊ฐœ** | + +๋ฆฌ๋ทฐ์–ด๊ฐ€ ๊ฐ€์ •ํ•œ "๋น„๊ณต๊ฐœ ์„œ๋ธŒ๋ชจ๋“ˆ์ด๋ฉด ํ† ํฐ์ด ์ „ํŒŒ๋˜์ง€ ์•Š์•„ ์‹คํŒจ" ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” **๊ฐ€์ •์ด ์•„๋‹ˆ๋ผ ํ˜„์‹ค**์ž…๋‹ˆ๋‹ค. Rev.1 ์˜ T-1(`submodules: recursive` ํ•œ ์ค„ ์ถ”๊ฐ€)๋งŒ์œผ๋กœ๋Š” CI ๊ฐ€ ์—ฌ์ „ํžˆ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ์ด ์ง€์ ์€ Rev.1 ์˜ ์‹ค์งˆ์  ๊ฒฐํ•จ์„ ์žก์•„๋ƒˆ์Šต๋‹ˆ๋‹ค. + +๋” ๋‚˜์•„๊ฐ€ ์‹ค์ธก์ด ๋“œ๋Ÿฌ๋‚ธ ๊ตฌ์กฐ๋Š” ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์•Œ๋˜ ๊ฒƒ๋ณด๋‹ค ๊นŒ๋‹ค๋กญ์Šต๋‹ˆ๋‹ค: **์ƒ์œ„ ์ €์žฅ์†Œ๋Š” ๊ณต๊ฐœ, ์„œ๋ธŒ๋ชจ๋“ˆ์€ ๋น„๊ณต๊ฐœ, ๊ฒŒ๋‹ค๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์กฐ์ง**(`tmpl/` vs `laa/`). ์ฆ‰ CI ๋Ÿฌ๋„ˆ๊ฐ€ ์ƒ์œ„ ์ €์žฅ์†Œ๋ฅผ ์ต๋ช…์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด๋„ ์„œ๋ธŒ๋ชจ๋“ˆ์—๋Š” ๋ณ„๋„ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. + +#### (2) ์ฒ˜๋ฐฉ ํ˜•ํƒœ ๊ต์ •: `../nats-docker` ๋Š” ์ž˜๋ชป๋œ ์ €์žฅ์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค + +git ์˜ ์ƒ๋Œ€ ์„œ๋ธŒ๋ชจ๋“ˆ URL ์€ **์ƒ์œ„ ์ €์žฅ์†Œ์˜ origin URL ๊ธฐ์ค€**์œผ๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. ์‹ค์ธก(์ž„์‹œ ์ €์žฅ์†Œ์— origin ์„ ๋™์ผํ•˜๊ฒŒ ์„ค์ •ํ•˜๊ณ  `git submodule init` ์œผ๋กœ ํ•ด์„ ๊ฒฐ๊ณผ ํ™•์ธ): + +``` +origin = https://git.godopu.com/tmpl/multi-agent-mux + +url = ../nats-docker -> https://git.godopu.com/tmpl/nats-docker โŒ ์กฐ์ง ๋ถˆ์ผ์น˜ +url = ../nats-docker.git -> https://git.godopu.com/tmpl/nats-docker.git โŒ ์กฐ์ง ๋ถˆ์ผ์น˜ +url = ../../laa/nats-docker -> https://git.godopu.com/laa/nats-docker โœ… ์ •ํ™• +``` + +์‹ค์ œ ์ €์žฅ์†Œ๋Š” `laa/` ์•„๋ž˜์— ์žˆ์œผ๋ฏ€๋กœ, ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์ œ์‹œํ•œ ๋‘ ํ˜•ํƒœ(`../nats-docker`, `../nats-docker.git`)๋ฅผ ๊ทธ๋Œ€๋กœ ์ ์šฉํ•˜๋ฉด **์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ๋กœ**๋ฅผ ๊ฐ€๋ฆฌ์ผœ ์„œ๋ธŒ๋ชจ๋“ˆ์ด ์•„์˜ˆ ํด๋ก ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒ์œ„ ์ €์žฅ์†Œ์™€ ์„œ๋ธŒ๋ชจ๋“ˆ์ด ๊ฐ™์€ ์กฐ์ง์— ์žˆ๋‹ค๋Š” ์•”๋ฌต์  ๊ฐ€์ •์ด ์ด ์ธ์Šคํ„ด์Šค์—์„œ๋Š” ์„ฑ๋ฆฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +#### (3) ์ฒ˜๋ฐฉ ์ถฉ๋ถ„์„ฑ ๊ต์ •: ์ƒ๋Œ€ URL ์€ ์ธ์ฆ์„ ํ•ด๊ฒฐํ•˜์ง€ ์•Š๋Š”๋‹ค + +์ƒ๋Œ€ URL ์ด ๋ฌผ๋ ค๋ฐ›๋Š” ๊ฒƒ์€ **ํ”„๋กœํ† ์ฝœ๊ณผ ํ˜ธ์ŠคํŠธ**์ด์ง€ **๊ถŒํ•œ**์ด ์•„๋‹™๋‹ˆ๋‹ค. SSH ๋กœ ์ƒ์œ„๋ฅผ ํด๋ก ํ•˜๋ฉด ์„œ๋ธŒ๋ชจ๋“ˆ๋„ SSH ๋กœ ๊ฐ€๋ฏ€๋กœ ํ‚ค๊ฐ€ ์žฌ์‚ฌ์šฉ๋˜๋Š” ์ด์ ์€ ์‹ค์žฌํ•˜์ง€๋งŒ, HTTPS + ํ† ํฐ ์กฐํ•ฉ์—์„œ๋Š” ํ† ํฐ์˜ ์Šค์ฝ”ํ”„๊ฐ€ `laa/nats-docker` ๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ƒ์œ„๊ฐ€ ๊ณต๊ฐœ์ด๊ณ  ์„œ๋ธŒ๋ชจ๋“ˆ์ด ๋น„๊ณต๊ฐœ์ธ ํ˜„ ๊ตฌ์กฐ์—์„œ๋Š” **์ƒ๋Œ€ URL ๋กœ ๋ฐ”๊ฟ”๋„ ์ž๊ฒฉ์ฆ๋ช…์€ ์—ฌ์ „ํžˆ ๋ณ„๋„๋กœ ๊ณต๊ธ‰**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ T-1 ์€ ํ•œ ์ค„ ์ถ”๊ฐ€๊ฐ€ ์•„๋‹ˆ๋ผ ์„ธ ๋ถ€๋ถ„์œผ๋กœ ํ™•์žฅ๋ฉ๋‹ˆ๋‹ค(ยง4 T-1a/T-1b/T-1c). + +#### (4) ์‹ค์ธก์œผ๋กœ ๋“œ๋Ÿฌ๋‚œ ์ œ3์˜ ์„ ํƒ์ง€ โ€” ์„œ๋ธŒ๋ชจ๋“ˆ ๊ณต๊ฐœ ์ „ํ™˜ + +`nats-docker` ๊ฐ€ ์ถ”์ ํ•˜๋Š” ํŒŒ์ผ์€ **10๊ฐœ๋ฟ์ด๋ฉฐ ๋น„๋ฐ€์„ ๋‹ด์€ ํŒŒ์ผ์ด 0๊ฐœ**์ž…๋‹ˆ๋‹ค. + +``` +.agents/skills/env-generator/SKILL.md docker/.env.example +.agents/skills/env-generator/scripts/โ€ฆ docker/README.md +.gitignore docker/docker-compose.yaml +NATS_REPORT.md docker/nats.conf +PRIVATE_SERVER.md +README.md +``` + +- `.gitignore` ๊ฐ€ `.env` / `*.env` ๋ฅผ ์ œ์™ธํ•˜๊ณ  `!*.env.example` ๋งŒ ํ—ˆ์šฉ โ€” ์‹ค์ œ ์‹œํฌ๋ฆฟ์€ ์ถ”์  ๋Œ€์ƒ์ด ์•„๋‹˜. +- `docker/.env.example` ์€ ์„ค๊ณ„์ƒ **๋นˆ ๊ฐ’**(D-25(d) ๊ฐ€ ๋ด‰์ธ). +- `docker/nats.conf` ๋Š” ๋ชจ๋“  `password:` ๊ฐ€ `$VAR` ์ฐธ์กฐ(D-25(e) ๊ฐ€ ๋ด‰์ธ). + +์ฆ‰ ์ด ์ €์žฅ์†Œ๋ฅผ ๊ณต๊ฐœํ•ด๋„ ์œ ์ถœ๋˜๋Š” ๋น„๋ฐ€์€ ์—†์Šต๋‹ˆ๋‹ค. ๋‚จ๋Š” ๊ฒƒ์€ "๋ฐฐํฌ ํ† ํด๋กœ์ง€๋ฅผ ๊ณต๊ฐœํ•  ๊ฒƒ์ธ๊ฐ€"๋ผ๋Š” **์ •์ฑ… ํŒ๋‹จ**์ด๋ฏ€๋กœ ์ผ๋ฐฉ์ ์œผ๋กœ ์ฒ˜๋ฐฉํ•˜์ง€ ์•Š๊ณ  ยง4 ์—์„œ 3๊ฐœ ์„ ํƒ์ง€๋กœ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๊ณต๊ฐœ ์ „ํ™˜์€ CI ์ธ์ฆ ๋ฌธ์ œ๋ฅผ **์™„์ „ํžˆ ์†Œ๋ฉธ**์‹œํ‚ค๋Š” ์œ ์ผํ•œ ์„ ํƒ์ง€์ž…๋‹ˆ๋‹ค. + +#### (5) ๋ถ€์ˆ˜ ์‹ค์ธก โ€” ํญ๋ฐœ์€ ์•„์ง ์•ˆ ํ„ฐ์กŒ์„ ๋ฟ์ด๋‹ค + +`git status -sb` โ†’ `## main...origin/main [ahead 2]`. ์ฆ‰ `12ba30b`(๋ฌธ์„œ ์„œ๋ธŒ๋ชจ๋“ˆ ์ด์ „)์™€ `916185c` ๋Š” **์•„์ง ํ‘ธ์‹œ๋˜์ง€ ์•Š์•˜๊ณ **, ์›๊ฒฉ HEAD ๋Š” `629a67f` ์ž…๋‹ˆ๋‹ค. CI ๋Š” ์•„์ง ์ด ๋ณ€๊ฒฝ์„ ๋ณธ ์ ์ด ์—†์Šต๋‹ˆ๋‹ค. **๋‹ค์Œ ํ‘ธ์‹œ ์ˆœ๊ฐ„ S-1 ์ด ๋ฐœํ˜„**ํ•˜๋ฏ€๋กœ T-1 ์€ ํ‘ธ์‹œ ์ด์ „์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +### A-2. C2 โ€” ์ „๋ฉด ์ˆ˜์šฉ. Rev.1 ์˜ ๋…ผ๊ฑฐ๊ฐ€ ํ‹€๋ ธ๋‹ค + +Rev.1 ์€ "test ์žก๋งŒ ๊ณ ์น˜๋ฉด lint/compile ์žก์ด ์„œ๋ธŒ๋ชจ๋“ˆ ์—†๋Š” ํŠธ๋ฆฌ๋ฅผ ํ›‘๋Š” **๋น„๋Œ€์นญ**์ด ๋‚จ๋Š”๋‹ค"๋Š” ์ด์œ ๋กœ ์„ธ checkout ์ „๋ถ€์— `submodules` ๋ฅผ ์š”๊ตฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ธก ๊ฒฐ๊ณผ ์ด ๋…ผ๊ฑฐ๋Š” ์„ฑ๋ฆฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +| ์žก | ์‹ค์ œ๋กœ ์ฝ๋Š” ๊ฒฝ๋กœ | ์„œ๋ธŒ๋ชจ๋“ˆ ํ•„์š” | +|---|---|:---:| +| `lint-shell` | `.agents/skills/**`, `.agents/hooks/โ€ฆ`, `deploy/*.sh` (shellcheck ๋Œ€์ƒ 15๊ฐœ ํŒŒ์ผ ๋ช…์‹œ) | โŒ | +| `lint-python` | `.agents/skills/multi-agent-mux-delegate-job/scripts/`, `.agents/skills/lib_py/` (flake8ยทpy_compile) | โŒ | +| `test` | `pytest tests/ -q` โ†’ D-11~D-19, D-22~D-30 ์ด `nats-docker/**` ๋ฅผ ์ฝ์Œ | โœ… | + +lint ์žก๋“ค์€ ์„œ๋ธŒ๋ชจ๋“ˆ ๊ฒฝ๋กœ๋ฅผ **ํ•œ ๋ฒˆ๋„ ์ฐธ์กฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ์—†๋Š” ํŠธ๋ฆฌ๋ฅผ ํ›‘๋Š” "๋น„๋Œ€์นญ"์€ ๊ด€์ธก ๊ฐ€๋Šฅํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚ณ์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ต์ • ๋Œ€์ƒ์ด ์•„๋‹ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด์˜ ๋‘ ์ง€์ (๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ I/O, ํ–ฅํ›„ ๊ฒฝ๋Ÿ‰ ์›Œํฌํ”Œ๋กœ์—์„œ์˜ false positive)์ด ์˜ณ์Šต๋‹ˆ๋‹ค. + +**๋‹ค๋งŒ ๋ฆฌ๋ทฐ์–ด ์ฒ˜๋ฐฉ์— ํ•œ ๊ฐ€์ง€๋ฅผ ๋”ํ•ฉ๋‹ˆ๋‹ค โ€” ๊ณตํ—ˆ ํ†ต๊ณผ ๋ฐฉ์ง€.** "pytest ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์žก"์œผ๋กœ ์Šค์ฝ”ํ”„๋ฅผ ์ขํžˆ๋ฉด, ์žก ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ `pytest` ๋ฅผ ๋ž˜ํผ ์Šคํฌ๋ฆฝํŠธ(`make test`, `bash deploy/run-tests.sh`) ๋’ค๋กœ ์ˆจ๊ธฐ๋Š” ์ˆœ๊ฐ„ ๊ฐ€๋“œ๊ฐ€ **๊ฒ€์‚ฌ ๋Œ€์ƒ 0๊ฑด์œผ๋กœ ์กฐ์šฉํžˆ ํ†ต๊ณผ**ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ D-31 ์€ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์žก์„ **ํ•˜๋‚˜๋„ ๋ชป ์ฐพ์œผ๋ฉด ์‹คํŒจ**ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์—†์œผ๋ฉด ์Šค์ฝ”ํ”„ ์ถ•์†Œ๊ฐ€ ๊ณง ๊ฐ€๋“œ ๋ฌด๋ ฅํ™” ๊ฒฝ๋กœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. + +**๊ตฌํ˜„ ์‹ค์ธก ์ฐธ๊ณ **: PyYAML ๋กœ `deploy/gitea-ci.yml` ์„ ํŒŒ์‹ฑํ•˜๋ฉด ์ตœ์ƒ์œ„ ํ‚ค๊ฐ€ `['name', True, 'jobs']` ๋กœ ๋‚˜์˜ต๋‹ˆ๋‹ค โ€” YAML 1.1 ์ด `on:` ์„ ๋ถˆ๋ฆฌ์–ธ `True` ๋กœ ํ•ด์„ํ•˜๋Š” ์•Œ๋ ค์ง„ ํ•จ์ •์ž…๋‹ˆ๋‹ค. D-31 ์€ `jobs` ๋งŒ ์ฝ์œผ๋ฏ€๋กœ ์˜ํ–ฅ์€ ์—†์œผ๋‚˜, Creator ๊ฐ€ `d["on"]` ์— ์ ‘๊ทผํ•˜๋ฉด `KeyError` ๋ฅผ ๋งŒ๋‚ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์„ธ ์žก ๋ชจ๋‘ checkout ์Šคํ… 1๊ฐœ ยท `with` ๋Š” `None` ์ด๋ฉฐ, `pytest` ๊ฐ€ ํฌํ•จ๋œ ์žก์€ `test` **ํ•˜๋‚˜**์ž…๋‹ˆ๋‹ค. + +--- + +### A-3. C3 โ€” ์›์น™ ์ˆ˜์šฉ. ๊ทธ๋Ÿฌ๋‚˜ "๊ธฐ๋™ ์ฐจ๋‹จ"์„ import ์‹œ์ ์— ๋‘๋ฉด ์Šค์œ„ํŠธ๊ฐ€ ์ฃฝ๋Š”๋‹ค + +#### (1) ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์˜ณ์€ ๋ถ€๋ถ„ + +Rev.1 ์˜ ์ฒ˜๋ฐฉ์€ "`MAM_ENV_FILE`(์กด์žฌํ•  ๋•Œ๋งŒ) โ†’ `MAM_REAL_ROOT` โ†’ โ€ฆ โ†’ `walk_up(cwd)`" ์ˆœ์„œ์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ **๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•œ** ๊ฒฝ๋กœ๊ฐ€ ์—†์„ ๋•Œ ์ƒ์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ๋‹ค๋ฅธ `.mam.env` ๋ฅผ ์ž„์˜๋กœ ์ง‘์–ด ๋“ ๋‹ค๋Š” ๋œป์ด๊ณ , ๋ฆฌ๋ทฐ์–ด ์ง€์ ๋Œ€๋กœ **๋ช…์‹œ์  ์„ค์ • ์šฐ์„  ์›์น™ ์œ„๋ฐ˜**์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์˜ ๋ธŒ๋กœ์ปค/๊ณ„์ •์œผ๋กœ ์กฐ์šฉํžˆ ๋ถ™์„ ์œ„ํ—˜์ด ์‹ค์žฌํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ Rev.1 ์˜ ์„ค๊ณ„ ์˜ค๋ฅ˜์ด๋ฉฐ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. + +#### (2) ๊ทธ๋Ÿฌ๋‚˜ ์ฐจ๋‹จ ์ง€์ ์€ ์˜ฎ๊ฒจ์•ผ ํ•œ๋‹ค + +`mqtt_common.py:112` ๋Š” ๋ชจ๋“ˆ ์ตœ์ƒ์œ„์—์„œ `_load_dotenv()` ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค โ€” ์ฆ‰ **import ๋ถ€์ž‘์šฉ**์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  `mqtt_common` ์„ import ํ•˜๋Š” ํ…Œ์ŠคํŠธ ํŒŒ์ผ์ด 3๊ฐœ ์žˆ์Šต๋‹ˆ๋‹ค. + +``` +tests/test_tier1_unit.py +tests/test_tier2_component.py +tests/test_deploy_freshness.py โ† D-19/D-27 ์ด DEFAULT_TOPIC_ROOT ๋งŒ ์ฝ์œผ๋ ค๊ณ  import +``` + +์—ฌ๊ธฐ์„œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด, ๋‚ก์€ `MAM_ENV_FILE` ์ด ํ™˜๊ฒฝ์— ๋‚จ์•„ ์žˆ๋Š” **๋ชจ๋“ ** ์ƒํ™ฉ์—์„œ `import mqtt_common` ์ด ์‹คํŒจํ•˜๊ณ  ์Šค์œ„ํŠธ๊ฐ€ ์ˆ˜์ง‘ ๋‹จ๊ณ„์—์„œ ๋ถ•๊ดดํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋กœ์ปค์— ์ ‘์†ํ•  ์˜๋„๊ฐ€ ์ „ํ˜€ ์—†๋Š” ์†Œ๋น„์ž(์ƒ์ˆ˜ ํ•˜๋‚˜ ์ฝ๋Š” ํ…Œ์ŠคํŠธ)๊นŒ์ง€ ํ•จ๊ป˜ ์ฃฝ์Šต๋‹ˆ๋‹ค. + +#### (3) ์ข…ํ•ฉ ์ฒ˜๋ฐฉ โ€” ๊ธฐ๋ก์€ import ์—์„œ, ๊ฑฐ๋ถ€๋Š” ์ ‘์† ์ง€์ ์—์„œ + +| ๋‹จ๊ณ„ | ๋™์ž‘ | +|---|---| +| **import (`_load_dotenv`)** | `MAM_ENV_FILE` ์ด ์„ค์ •๋๋Š”๋ฐ ํŒŒ์ผ์ด ์—†์œผ๋ฉด โ†’ `logger.error("MAM_ENV_FILE is set to %s but no such file; refusing to auto-discover", path)` ํ›„ **๋ชจ๋“ˆ ์ „์—ญ ํ”Œ๋ž˜๊ทธ** `_env_file_missing = True` ์„ค์ •. **์ž๋™ ํƒ์ƒ‰์„ ์‹œ๋„ํ•˜์ง€ ์•Š์Œ**(๋ฆฌ๋ทฐ์–ด ์š”๊ตฌ ๋ฐ˜์˜). **์˜ˆ์™ธ๋ฅผ ๋˜์ง€์ง€ ์•Š์Œ** | +| **`MAM_ENV_FILE` ๋ฏธ์„ค์ •** | ์ˆœ์„œ ์žˆ๋Š” ํƒ์ƒ‰ ์ˆ˜ํ–‰: `MAM_REAL_ROOT` โ†’ `WORKSPACE_ROOT` โ†’ `walk_up(__file__)` โ†’ `walk_up(cwd)` | +| **์ ‘์† ์ง€์  (`make_client()` / ๋ธŒ๋กœ์ปค ์„ค์ • ํ™•์ •)** | โ‘  `_env_file_missing` ์ด๋ฉด **๋ช…์‹œ์  ์˜ˆ์™ธ๋กœ ๊ฑฐ๋ถ€**(fail-closed). โ‘ก ํ•ด์„๋œ ํ˜ธ์ŠคํŠธ๊ฐ€ ๋‚ด์žฅ ๊ณต๊ฐœ ๊ธฐ๋ณธ๊ฐ’(`broker.hivemq.com`)๊ณผ ๊ฐ™์œผ๋ฉด **๋ˆˆ์— ๋„๋Š” ๋ณด์•ˆ ๊ฒฝ๊ณ ** ์ถœ๋ ฅ | + +์ด ๋ฐฐ์น˜๊ฐ€ ๋‘ ์š”๊ตฌ๋ฅผ ๋ชจ๋‘ ๋งŒ์กฑ์‹œํ‚ต๋‹ˆ๋‹ค: ๋ช…์‹œ์  ์„ค์ •์ด ๊นจ์กŒ์„ ๋•Œ ์กฐ์šฉํžˆ ๋‹ค๋ฅธ ํ™˜๊ฒฝ์œผ๋กœ ์ƒˆ์ง€ ์•Š๊ณ (๋ฆฌ๋ทฐ์–ด C3-1), ์ž๋™ ํƒ์ƒ‰์ด ์•„๋ฌด๊ฒƒ๋„ ๋ชป ์ฐพ์•„ ๊ณต๊ฐœ ๋ธŒ๋กœ์ปค๋กœ ๋–จ์–ด์งˆ ๋•Œ ๋ฐ˜๋“œ์‹œ ๊ฒฝ๊ณ ๊ฐ€ ๋‚˜์˜ค๋ฉฐ(๋ฆฌ๋ทฐ์–ด C3-2), ๊ทธ๋Ÿฌ๋ฉด์„œ๋„ ์ฝ๊ธฐ ์ „์šฉ ์†Œ๋น„์ž์˜ import ๋ฅผ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +**๋ณด์กฐ ์‹ค์ธก** โ€” `_parse_env_file` ์€ `if key and key not in os.environ` ๋กœ ๊ธฐ๋กํ•˜๋ฏ€๋กœ **OS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ํŒŒ์ผ๋ณด๋‹ค ์šฐ์„ **ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž๊ฐ€ `MQTT_BROKER` ๋ฅผ ์ง์ ‘ export ํ•œ ๊ฒฝ์šฐ์—๋Š” ๊ณต๊ฐœ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋–จ์–ด์ง€๋Š” ์ผ์ด ์• ์ดˆ์— ์—†์Šต๋‹ˆ๋‹ค. ์œ„ โ‘ก์˜ ์กฐ๊ฑด์„ "`MAM_ENV_FILE` ๋ถ€์žฌ"๊ฐ€ ์•„๋‹ˆ๋ผ "**ํ•ด์„ ๊ฒฐ๊ณผ๊ฐ€ ๊ณต๊ฐœ ๊ธฐ๋ณธ๊ฐ’๊ณผ ์ผ์น˜**"๋กœ ์žก์€ ์ด์œ ์ด๋ฉฐ, ์ด ํŽธ์ด ํƒ์ƒ‰ ๊ฒฝ๋กœ ์ „์ฒด๋ฅผ ํ•œ ๋ฒˆ์— ๋ฎ์Šต๋‹ˆ๋‹ค. + +--- + +## B. Rev.1 โ†’ Rev.2 ๋ณ€๊ฒฝ ์š”์•ฝ + +| # | ๋ณ€๊ฒฝ | ์ถœ์ฒ˜ | +|---|---|---| +| C-1 | **T-1 ์„ T-1a/T-1b/T-1c ๋กœ ๋ถ„ํ• ** โ€” `.gitmodules` ์ƒ๋Œ€ URL์€ `../../laa/nats-docker`(๋ฆฌ๋ทฐ์–ด ์ œ์‹œ ํ˜•ํƒœ๋Š” ์˜ค๋‹ต), ๋น„๊ณต๊ฐœ ์„œ๋ธŒ๋ชจ๋“ˆ ์ž๊ฒฉ์ฆ๋ช… ๊ณต๊ธ‰, 3๊ฐœ ์„ ํƒ์ง€ ๋น„๊ต | A-1 | +| C-2 | **D-31 ์Šค์ฝ”ํ”„ ์ถ•์†Œ** โ€” "๋ชจ๋“  checkout" โ†’ "ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์žก์˜ checkout". **๊ณตํ—ˆ ํ†ต๊ณผ ๋ฐฉ์ง€ ๋‹จ์–ธ ์ถ”๊ฐ€** | A-2 | +| C-3 | **T-9(B-17) ์ฒ˜๋ฐฉ ์žฌ์„ค๊ณ„** โ€” import ์‹œ์  ๊ธฐ๋ก + ์ ‘์† ์ง€์  ๊ฑฐ๋ถ€์˜ 2๋‹จ ๊ตฌ์กฐ. ๋ช…์‹œ์  ๊ฒฝ๋กœ ์‹คํŒจ ์‹œ ์ž๋™ ํƒ์ƒ‰ ๊ธˆ์ง€ | A-3 | +| C-4 | ์‹ ๊ทœ ๋ฐœ๊ฒฌ **S-13**(๋ฏธํ‘ธ์‹œ 2์ปค๋ฐ‹ โ€” S-1 ๋ฐœํ˜„ ์‹œ์ ), **S-14**(๊ณต๊ฐœ ์ƒ์œ„ / ๋น„๊ณต๊ฐœ ์„œ๋ธŒ๋ชจ๋“ˆ ๋น„๋Œ€์นญ) | A-1(5), A-1(1) | +| C-5 | Rev.1 ์˜ T-1 ๋…ผ๊ฑฐ(โ€œlint ์žก ๋น„๋Œ€์นญโ€) **์ฒ ํšŒ** โ€” ์‹ค์ธก์ƒ ์„ฑ๋ฆฝํ•˜์ง€ ์•Š์Œ | A-2 | +| C-6 | D-31 ๊ตฌํ˜„ ์ฃผ์˜ ์ถ”๊ฐ€ โ€” PyYAML ์ด `on:` ์„ `True` ํ‚ค๋กœ ํŒŒ์‹ฑ | A-2 | + +Rev.1 ์˜ ํŒ์ •, ์‹ค์ธก ์›์žฅ(V-1~V-15), ๋ฐœ๊ฒฌ S-1~S-12, ์ž‘์—… T-2~T-8ยทT-10~T-14, ๊ฐ€๋“œ D-32 ๋Š” ๋ฆฌ๋ทฐ์—์„œ ์ „๋ฉด ๋™์˜๋ฅผ ๋ฐ›์•˜์œผ๋ฉฐ ๋ณ€๊ฒฝ ์—†์ด ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 0. ํŒ์ • + +ํ…Œ์ŠคํŠธ๋Š” ์ „๊ฑด ํ†ต๊ณผํ•˜๋‚˜ **๋ฌธ์„œ ๋™๊ธฐํ™” ๋ชฉํ‘œ๋Š” ์—ฌ์ „ํžˆ ๋ฏธ์ถฉ์กฑ**์ž…๋‹ˆ๋‹ค(๊ตฌํ˜„์ด ์•„์ง ์ˆ˜ํ–‰๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ Rev.1 ํŒ์ • ์œ ์ง€). + +- `MESSAGING.md` ๋Š” NATSยทJetStreamยทDockerยท์›๊ฒฉยทTailscale ์„ **0๊ฑด** ์–ธ๊ธ‰ํ•˜๋ฉฐ, ํ™•์ • ํ‘œ์ค€(`nats-server` MQTT **3.1.1**)๊ณผ ๋ชจ์ˆœ๋˜๋Š” ์„œ์ˆ (`MQTT 5.0` / `MosquittoยทEMQX`)์„ ํ”„๋กœ๋•์…˜ ํ‘œ์ค€์œผ๋กœ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. +- `IMPROVEMENTS.md` ๋Š” ํ•ด๊ฒฐ๋œ B-14/B-15 ๋ฅผ ๋ฏธํ•ด๊ฒฐ๋กœ ์ง‘๊ณ„ํ•˜๊ณ , Track 1RยทD-22~D-30ยท์„œ๋ธŒ๋ชจ๋“ˆ ์ „ํ™˜์„ 0๊ฑด ๋ฐ˜์˜ํ–ˆ์Šต๋‹ˆ๋‹ค. +- CI ๋Š” ์„œ๋ธŒ๋ชจ๋“ˆ์„ ๋ฐ›์ง€ ์•Š์•„ ๋ฐฐํฌ ์‹ ์„ ๋„ ๊ฐ€๋“œ 29๊ฑด ์ค‘ **18๊ฑด์ด ์‹คํŒจ**ํ•˜๋ฉฐ(์‹ค์ธก), ์„œ๋ธŒ๋ชจ๋“ˆ์ด **๋น„๊ณต๊ฐœ**์ด๋ฏ€๋กœ `submodules: recursive` ํ•œ ์ค„๋กœ๋Š” ํ•ด๊ฒฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์‹ ๊ทœ). + +**[VERDICT: NOT PASS]** + +--- + +## 1. ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ฒฐ๊ณผ + +| ๋ช…๋ น | ๊ฒฐ๊ณผ | +|---|---| +| `.venv/bin/python -m pytest tests/test_deploy_freshness.py tests/test_sanity.py -q` | **31 passed in 21.43s** | +| `.venv/bin/python -m pytest tests/ -q` (์ „์ฒด) | **306 passed in 375.81s** (exit 0) | +| `pytest tests/ -q --collect-only` | **306 collected** | + +๋ฌธ์„œ ํšŒ๊ท€ 0๊ฑด. ์–‘ํ˜ธ ํ•ญ๋ชฉ(์กฐ์น˜ ๋ถˆํ•„์š”): `_resolve_private_server_doc()`ยท`_resolve_docker_dir()` 3-ํ›„๋ณด ํด๋ฐฑ ๊ตฌํ˜„ โœ… / D-16 ๊ตฌ๋ฉ ๊ต์ •(`assert "alpine" in tag`) โœ… / `requirements.txt` ์— `PyYAML>=6.0` ์ถ”๊ฐ€ โœ… / CI ์˜ PyYAML ์€ ์Šคํ‚ฌ `requirements.txt` ์˜ `pyyaml` ๋กœ ํ™•๋ณด๋˜์–ด **๊ฒฐํ•จ ์•„๋‹˜** โœ… / `implementation_plan.md` ยง5 P0.5ยทR-1~R-13 ๋ฐ ์„œ๋ธŒ๋ชจ๋“ˆ ๋งํฌ(`:7`, `:147`) ๊ฐฑ์‹  โœ…. + +--- + +## 2. ์‹ค์ธก ์›์žฅ + +Rev.1 ์˜ V-1 ~ V-15 ๋Š” ์œ ์ง€ํ•˜๋ฉฐ, ๋ณธ ๋ฆฌ๋น„์ „์—์„œ ๋‹ค์Œ์„ ์ถ”๊ฐ€ ์ธก์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. + +| # | ๊ฒ€์ฆ | ๋ฐฉ๋ฒ• | ๊ฒฐ๊ณผ | +|---|---|---|---| +| **V-16** | ์„œ๋ธŒ๋ชจ๋“ˆ ๊ณต๊ฐœ ์—ฌ๋ถ€ | ์ž๊ฒฉ์ฆ๋ช… ์—†์ด `git ls-remote https://git.godopu.com/laa/nats-docker` | ๐Ÿ”ด `remote: Failed to authenticate user` โ†’ **๋น„๊ณต๊ฐœ** | +| **V-17** | ์ƒ์œ„ ์ €์žฅ์†Œ ๊ณต๊ฐœ ์—ฌ๋ถ€ | ๋™์ผ ๋ฐฉ์‹ `โ€ฆ/tmpl/multi-agent-mux` | ๐ŸŸข ref ๋ชฉ๋ก ์‘๋‹ต โ†’ **๊ณต๊ฐœ** (์›๊ฒฉ HEAD `629a67f`) | +| **V-18** | ์ƒ๋Œ€ URL ํ•ด์„ | ์ž„์‹œ ์ €์žฅ์†Œ์— ๋™์ผ origin ์„ค์ • ํ›„ `git submodule init` | `../nats-docker` โ†’ `tmpl/nats-docker` โŒ / `../../laa/nats-docker` โ†’ `laa/nats-docker` โœ… | +| **V-19** | ์„œ๋ธŒ๋ชจ๋“ˆ ๋น„๋ฐ€ ๋…ธ์ถœ | `git -C nats-docker ls-files` + `.gitignore` | ์ถ”์  ํŒŒ์ผ **10๊ฐœ, ๋น„๋ฐ€ ํŒŒ์ผ 0๊ฐœ**. `.env` ์ œ์™ธ, `.env.example` ๋นˆ ๊ฐ’, `nats.conf` ์ „๋ถ€ `$VAR` | +| **V-20** | ๋ฏธํ‘ธ์‹œ ์ปค๋ฐ‹ | `git status -sb` | `## main...origin/main [ahead 2]` โ€” `12ba30b`, `916185c` ๋ฏธํ‘ธ์‹œ | +| **V-21** | lint ์žก์˜ ์„œ๋ธŒ๋ชจ๋“ˆ ์˜์กด | `deploy/gitea-ci.yml:15-80` ์˜ shellcheck/flake8/py_compile ๋Œ€์ƒ ๊ฒฝ๋กœ | `.agents/**`, `deploy/*.sh` ๋งŒ โ€” **์„œ๋ธŒ๋ชจ๋“ˆ ์ฐธ์กฐ 0๊ฑด** | +| **V-22** | CI YAML ํŒŒ์‹ฑ | PyYAML `safe_load` | ์ตœ์ƒ์œ„ ํ‚ค `['name', True, 'jobs']` (`on:` โ†’ ๋ถˆ๋ฆฌ์–ธ). `pytest` ํฌํ•จ ์žก = `test` **1๊ฐœ**, ์„ธ ์žก ๋ชจ๋‘ checkout 1๊ฐœ ยท `with` ๋Š” `None` | +| **V-23** | `_load_dotenv` ํ˜ธ์ถœ ์‹œ์  | `mqtt_common.py:112` | **๋ชจ๋“ˆ ์ตœ์ƒ์œ„ = import ๋ถ€์ž‘์šฉ** | +| **V-24** | `mqtt_common` import ์†Œ๋น„์ž | `grep -rln "import mqtt_common" tests/` | `test_tier1_unit.py`, `test_tier2_component.py`, `test_deploy_freshness.py` โ€” **3๊ฐœ** | +| **V-25** | ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์šฐ์„ ์ˆœ์œ„ | `_parse_env_file`: `if key and key not in os.environ` | **OS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ `.mam.env` ๋ณด๋‹ค ์šฐ์„ ** | + +--- + +## 3. ๋ฐœ๊ฒฌ ์‚ฌํ•ญ + +Rev.1 ์˜ S-1 ~ S-12 ๋ฅผ ์œ ์ง€ํ•˜๊ณ , S-1 ์„ ๊ฐฑ์‹ ํ•˜๋ฉฐ S-13/S-14 ๋ฅผ ์‹ ์„คํ•ฉ๋‹ˆ๋‹ค. (S-2 ~ S-12 ์ƒ์„ธ๋Š” Rev.1 ๊ณผ ๋™์ผํ•˜๋ฏ€๋กœ ์š”์ง€๋งŒ ์žฌ์ˆ˜๋กํ•ฉ๋‹ˆ๋‹ค.) + +### ๐Ÿ”ด S-1 (P1, CI ์ฐจ๋‹จ) โ€” **๊ฐฑ์‹ **: ์„œ๋ธŒ๋ชจ๋“ˆ ๋ฏธ์ฒดํฌ์•„์›ƒ + ๋น„๊ณต๊ฐœ ์ €์žฅ์†Œ ์ธ์ฆ + +`deploy/gitea-ci.yml` ์˜ checkout 3๊ณณ(`:21`, `:53`, `:87`)์ด ์˜ต์…˜ ์—†์ด `actions/checkout@v3` ๋ฅผ ์”๋‹ˆ๋‹ค. ํŠธ๋ฆฌ๋ฅผ ๋ณต์ œํ•ด `nats-docker/` ๋ฅผ ๋น„์šด ์‹œ๋ฎฌ๋ ˆ์ด์…˜์—์„œ **18 failed, 11 passed**(D-11~D-19, D-22~D-30 ์ „๋ฉธ)๋ฅผ ์‹ค์ธกํ–ˆ์Šต๋‹ˆ๋‹ค. + +**Rev.2 ๊ฐฑ์‹ **: `submodules: recursive` ์ถ”๊ฐ€๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ธŒ๋ชจ๋“ˆ์ด **๋น„๊ณต๊ฐœ**(V-16)์ด๊ณ  ์ƒ์œ„ ์ €์žฅ์†Œ๋Š” **๊ณต๊ฐœ**(V-17)์ด๋ฉฐ **์„œ๋กœ ๋‹ค๋ฅธ ์กฐ์ง**์ด๋ฏ€๋กœ, ๋Ÿฌ๋„ˆ์— `laa/nats-docker` ์ฝ๊ธฐ ๊ถŒํ•œ์ด ๋ณ„๋„๋กœ ๊ณต๊ธ‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ยง4 T-1a/T-1b/T-1c ์ฐธ์กฐ. + +### ๐Ÿ”ด S-13 (P1, ํƒ€์ด๋ฐ) โ€” **์‹ ์„ค**: ์•„์ง ํ‘ธ์‹œ๋˜์ง€ ์•Š์•˜์„ ๋ฟ์ด๋‹ค + +`main` ์ด `origin/main` ๋ณด๋‹ค **ahead 2**(V-20). ์›๊ฒฉ HEAD ๋Š” `629a67f` ์ด๊ณ , ์„œ๋ธŒ๋ชจ๋“ˆ ๋ฌธ์„œ ์ด์ „ ์ปค๋ฐ‹ `12ba30b`ยท`916185c` ๋Š” ๋กœ์ปฌ์—๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค. CI ๋Š” ์•„์ง ์ด ์ƒํƒœ๋ฅผ ๋ณธ ์ ์ด ์—†์œผ๋ฉฐ, **๋‹ค์Œ ํ‘ธ์‹œ ์ˆœ๊ฐ„ S-1 ์ด ๋ฐœํ˜„**ํ•ฉ๋‹ˆ๋‹ค. T-1 ์€ ํ‘ธ์‹œ ์ด์ „์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด `main` ๋ธŒ๋žœ์น˜ CI ๊ฐ€ ์ฆ‰์‹œ ๋นจ๊ฐ„๋ถˆ์ด ๋ฉ๋‹ˆ๋‹ค. + +### ๐ŸŸ  S-14 (P2, ๊ตฌ์กฐ) โ€” **์‹ ์„ค**: ๊ณต๊ฐœ ์ƒ์œ„ / ๋น„๊ณต๊ฐœ ์„œ๋ธŒ๋ชจ๋“ˆ ๋น„๋Œ€์นญ + +์ƒ์œ„ ์ €์žฅ์†Œ๋Š” ๋ˆ„๊ตฌ๋‚˜ ํด๋ก ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜(V-17) ์„œ๋ธŒ๋ชจ๋“ˆ์€ ์ž๊ฒฉ์ฆ๋ช…์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค(V-16). ๊ฒฐ๊ณผ์ ์œผ๋กœ **์™ธ๋ถ€ ์‚ฌ์šฉ์ž๊ฐ€ `deploy/install.sh` ๊ฒฝ๋กœ๋กœ ์ด ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋ฐ›์œผ๋ฉด `nats-docker/` ๋Š” ๋นˆ ๋””๋ ‰ํ„ฐ๋ฆฌ**๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” `install.sh` ๊ฐ€ `docker/` ๋‚˜ `PRIVATE_SERVER.md` ๋ฅผ ๋ฐฐํฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ(Rev.1 D-8) ์‹ค์‚ฌ์šฉ์— ์ง€์žฅ์€ ์—†์ง€๋งŒ, ์ €์žฅ์†Œ๋ฅผ ํด๋ก ํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ฆฌ๋ ค๋Š” ์™ธ๋ถ€ ๊ธฐ์—ฌ์ž๋Š” **18๊ฑด ์‹คํŒจ**๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ยง4 T-1c ์˜ ์„ ํƒ์ง€ A(๊ณต๊ฐœ ์ „ํ™˜)๊ฐ€ ์ด ๋ฌธ์ œ๊นŒ์ง€ ํ•จ๊ป˜ ํ•ด์†Œํ•ฉ๋‹ˆ๋‹ค. + +### ๋‚˜๋จธ์ง€ ๋ฐœ๊ฒฌ (Rev.1 ์œ ์ง€, ์š”์ง€) + +| ID | ์š”์ง€ | +|---|---| +| ๐Ÿ”ด **S-2** (P1) | `MESSAGING.md` ์— nats/jetstream/docker/remote/tailscale **0๊ฑด**. ยง1.2 ๊ฐ€ "MQTT **5.0** โ€ฆ Mosquitto or EMQX" ๋ฅผ ํ”„๋กœ๋•์…˜ ํ‘œ์ค€์œผ๋กœ ์ œ์‹œ โ€” NATS ๋Š” MQTT 5.0 ๋ฏธ์ง€์›์ด๋ฏ€๋กœ ๋‹จ์ˆœ ๊ตฌ์‹์ด ์•„๋‹ˆ๋ผ ๋ชจ์ˆœ. ยง1.3 ์€ Mosquitto ์„ค์ •์„ ์œ ์ผํ•œ ๋ ˆํผ๋Ÿฐ์Šค๋กœ ์ œ์‹œ | +| ๐Ÿ”ด **S-3** (P1) | `MESSAGING.md` ยง6.1-3 ์ด ์ด๋ฏธ ํ•ด๊ฒฐ๋œ B-15 ๋ฅผ ํ˜„์žฌ ์ œ์•ฝ์œผ๋กœ ์„œ์ˆ ("it exits, leaving the running herdr agent orphaned"). ์‹ค์ œ๋กœ๋Š” `job_subscriber.py:60 _check_disk_fallback`, `:230`, `:244`, `return 3` ์กด์žฌ. ยง4.2 ๋„ B-14 ์ˆ˜์ • ๋ฏธ๋ฐ˜์˜ | +| ๐ŸŸ  **S-4** (P2) | `MESSAGING.md` ๊ฐ€ `broker_config_from_env` ํŒŒ์‹ฑ 10์ข… ์ค‘ 8์ข…๋งŒ ๋ฌธ์„œํ™” โ€” `MQTT_CLIENT_ID_PREFIX`, `MQTT_KEEPALIVE` ๋ˆ„๋ฝ. `.mam.env` ํ•ด์„ ์ˆœ์„œ(`_load_dotenv`) ์ ˆ ๋ถ€์žฌ | +| ๐Ÿ”ด **S-5** (P1) | `IMPROVEMENTS.md:3-6` ์ด `276/276`, ๋ฏธํ•ด๊ฒฐ 5๊ฑด(B-14ยทB-15 ํฌํ•จ), ์™„๋ฃŒ 24๊ฑด. ์‹ค์ œ๋กœ๋Š” 306/306, B-14/B-15 ๋Š” `c6b6c77` ์—์„œ ํ•ด๊ฒฐยทG-1~G-10 ๋ด‰์ธ. ์ œ๋ชฉ์˜ `โœ… ์™„๋ฃŒ` ๋งˆ์ปค๋„ ์ด ๋‘˜๋งŒ ๋ˆ„๋ฝ(๋‹ค๋ฅธ 42๊ฐœ๋Š” ๋ณด์œ ) โ†’ ๋ฏธํ•ด๊ฒฐ **3๊ฑด**, ์™„๋ฃŒ **26๊ฑด**. **A-2 ๋Š” M3 ๋ฏธ์™„์ด๋ฏ€๋กœ ๋ฏธํ•ด๊ฒฐ ์œ ์ง€** | +| ๐Ÿ”ด **S-6** (P1) | `IMPROVEMENTS.md` ์— `D-22`~`D-30`, `nats-docker`, `submodule`, `Track 1R` **0๊ฑด**. ์ปค๋ฐ‹ 5์ข…(`3523b9b`, `b09d420`, `629a67f`, `12ba30b`, `916185c`)์˜ ์„ฑ๊ณผ๊ฐ€ ๋ฐฑ๋กœ๊ทธ์— ๋ถ€์žฌ | +| ๐Ÿ”ด **S-7** (P1, ๋ณด์•ˆ) | `B-17`/`B-18` ๋ฏธ๋“ฑ๋ก(`implementation_plan.md:143` ์€ ๋“ฑ๋ก ์š”๊ตฌ). HEAD ์žฌํ˜„: `MAM_ENV_FILE=<์˜คํƒ€๊ฒฝ๋กœ>` โ†’ `broker.hivemq.com 1883 tls=False`, ๋Œ€์กฐ๊ตฐ โ†’ `vm-ubuntu 1883`. `.mam.env` ๊ฐ€ ์ด๋ฏธ ์‚ฌ์„ค ๋ธŒ๋กœ์ปค๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋ฏ€๋กœ ์ง€๊ธˆ์ด ๋” ์œ„ํ—˜ | +| ๐ŸŸ  **S-8** (P2) | `implementation_plan.md:3-5` ํ—ค๋”๊ฐ€ `v1.0.0` / `a9934ad` / `276/276` โ€” ์‹ค์ œ HEAD `916185c`, 306/306 | +| ๐ŸŸ  **S-9** (P2) | `:23` Track 1R ๋ณ€๊ฒฝ ์ง€์ ์ด ๊ตฌ ๊ฒฝ๋กœ. `:13-16` ํŠธ๋ž™ ๋‹ค์ด์–ด๊ทธ๋žจ์— Track 1R ๋ถ€์žฌ(ยง2 ๋งˆ์ผ์Šคํ†ค ๋„์‹๊ณผ ๋ถˆ์ผ์น˜). `:39` ํ…Œ์ŠคํŠธ ์ˆ˜ `276 -> 280` | +| ๐ŸŸ  **S-10** (P2) | ์„œ๋ธŒ๋ชจ๋“ˆ ์ „ํ™˜(`629a67f`, `12ba30b`)์ด ๋กœ๋“œ๋งต์— ๊ธฐ๋ก ์—†์Œ | +| ๐ŸŸ  **S-11** (P2) | `:177` `.mam.env` ์ „ํ™˜ ๋ฏธ์ฒดํฌ์ธ๋ฐ ์‹ค์ œ๋กœ๋Š” `MQTT_BROKER=vm-ubuntu`, `MQTT_USERNAME=mam_agent` ๋กœ ์ „ํ™˜ ์™„๋ฃŒ โ€” ์ถ”์ ๊ธฐ๊ฐ€ ํ˜„์‹ค๋ณด๋‹ค ๋’ค์ฒ˜์ง | +| ๐ŸŸก **S-12** (P3) | `:172` ์˜ `PRIVATE_SERVER.md:73`, `:146` ํ–‰ ๋ฒˆํ˜ธ ์ธ์šฉ์ด ๋‚ก์Œ โ†’ ์ ˆ ๋ฒˆํ˜ธ๋กœ ๊ต์ฒด | + +--- + +## 4. ๋™๊ธฐํ™” ์ž‘์—… ๋ช…์„ธ (Creator ๋ฒ”์œ„) + +**T-1 ๊ณ„์—ด์€ CI ๋ฅผ ๋˜์‚ด๋ฆฌ๋Š” ์ž‘์—…์ด๋ฉฐ S-13 ๋•Œ๋ฌธ์— ๋‹ค์Œ ํ‘ธ์‹œ ์ด์ „์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.** + +### T-1a โ€” `.gitmodules` ์ƒ๋Œ€ URL ์ „ํ™˜ (์„ ํƒ์ง€ C ๋ฅผ ํƒํ•  ๊ฒฝ์šฐ ํ•„์ˆ˜, ๊ทธ ์™ธ์—๋Š” ๊ถŒ๊ณ ) + +```ini +[submodule "nats-docker"] + path = nats-docker + url = ../../laa/nats-docker +``` + +โš ๏ธ **`../nats-docker` ๋ฅผ ์“ฐ์ง€ ๋งˆ์‹ญ์‹œ์˜ค.** ์ƒ์œ„ origin ์ด `tmpl/multi-agent-mux` ์ด๋ฏ€๋กœ `tmpl/nats-docker` ๋กœ ํ•ด์„๋˜์–ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์ €์žฅ์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค(V-18). ๋ณ€๊ฒฝ ํ›„ ๋ฐ˜๋“œ์‹œ ๊ฒ€์ฆ: + +```bash +git submodule sync --recursive +git config --get submodule.nats-docker.url # โ†’ https://git.godopu.com/laa/nats-docker +``` + +ํšจ๊ณผ๋Š” **ํ”„๋กœํ† ์ฝœยทํ˜ธ์ŠคํŠธ ์ƒ์†**(SSH ํด๋ก  ์‹œ ์„œ๋ธŒ๋ชจ๋“ˆ๋„ SSH, ๋ฏธ๋Ÿฌ/ํฌํฌ ์ด์ „ ์‹œ ์ž๋™ ์ถ”์ข…)์ด๋ฉฐ, **๊ถŒํ•œ ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. + +### T-1b โ€” CI checkout ์— ์„œ๋ธŒ๋ชจ๋“ˆ ํ™œ์„ฑํ™” + +`test` ์žก์˜ checkout ์Šคํ…(`deploy/gitea-ci.yml:87`)์—๋งŒ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค(A-2). + +```yaml + - name: Checkout Code + uses: actions/checkout@v3 + with: + submodules: recursive +``` + +`lint-shell`/`lint-python` ์€ **๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค** โ€” ์„œ๋ธŒ๋ชจ๋“ˆ ๊ฒฝ๋กœ๋ฅผ ์ฝ์ง€ ์•Š์Œ์ด ์‹ค์ธก๋˜์—ˆ์Šต๋‹ˆ๋‹ค(V-21). + +### T-1c โ€” ๋น„๊ณต๊ฐœ ์„œ๋ธŒ๋ชจ๋“ˆ ์ ‘๊ทผ ํ™•๋ณด (ํƒ 1, ์ •์ฑ… ํŒ๋‹จ ํ•„์š”) + +| ์„ ํƒ์ง€ | ๋ฐฉ๋ฒ• | ์žฅ์  | ๋‹จ์  | +|---|---|---|---| +| **A. `nats-docker` ๊ณต๊ฐœ ์ „ํ™˜** ๐Ÿ† | Gitea ์—์„œ ์ €์žฅ์†Œ visibility ๋ฅผ public ์œผ๋กœ | CI ์ธ์ฆ ๋ฌธ์ œ **์™„์ „ ์†Œ๋ฉธ**. ์™ธ๋ถ€ ๊ธฐ์—ฌ์ž S-14 ๋„ ๋™์‹œ ํ•ด์†Œ. ์ถ”์  ํŒŒ์ผ์— ๋น„๋ฐ€ 0๊ฑด์ด ์‹ค์ธก๋จ(V-19) | ๋ฐฐํฌ ํ† ํด๋กœ์ง€(ํฌํŠธยท๊ณ„์ • ๊ตฌ์กฐ)๊ฐ€ ๊ณต๊ฐœ๋จ. ๋‹จ, ๋น„๋ฐ€์€ ์—†์œผ๋ฉฐ ๋ณด์•ˆ์€ ์‹œํฌ๋ฆฟ์— ์˜์กดํ•˜์ง€ ๋ชจํ˜ธ์„ฑ์— ์˜์กดํ•˜์ง€ ์•Š์Œ | +| **B. ๋Ÿฌ๋„ˆ์— ์ฝ๊ธฐ ํ† ํฐ ์ฃผ์ž…** | `test` ์žก์— `laa/nats-docker` ์ฝ๊ธฐ ์Šค์ฝ”ํ”„ ํ† ํฐ์„ secret ์œผ๋กœ ๋‘๊ณ , checkout ์•ž์— `git config --global url."https://:${{ secrets.SUBMODULE_TOKEN }}@git.godopu.com/".insteadOf "https://git.godopu.com/"` | ์ €์žฅ์†Œ ๋น„๊ณต๊ฐœ ์œ ์ง€ | ํ† ํฐ ์ˆ˜๋ช… ๊ด€๋ฆฌ ํ•„์š”. ํ† ํฐ์ด CI ๋กœ๊ทธ์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ์ฃผ์˜. ์™ธ๋ถ€ ๊ธฐ์—ฌ์ž๋Š” ์—ฌ์ „ํžˆ ์‹คํŒจ | +| **C. ๋ฐฐํฌ ํ‚ค + SSH URL** | `.gitmodules` ๋ฅผ SSH ๋กœ ๋‘๊ณ  ๋Ÿฌ๋„ˆ์— read-only deploy key ๋ฐฐ์น˜ (T-1a ์™€ ๋ณ‘ํ–‰) | ์Šค์ฝ”ํ”„๊ฐ€ ์ €์žฅ์†Œ ๋‹จ์œ„๋กœ ์ตœ์†Œํ™”๋จ | ๋Ÿฌ๋„ˆ ์ด๋ฏธ์ง€์— ํ‚ค ๋ฐฐ์น˜ยท`known_hosts` ๊ด€๋ฆฌ ํ•„์š”. ์‚ฌ์„ค ๋„๋ฉ”์ธ DNS/์ธ์ฆ์„œ ์ด์Šˆ๋Š” ๋ณ„๋„ | + +**๊ถŒ๊ณ : A.** ์‹ค์ธก(V-19)์ƒ ๊ณต๊ฐœํ•ด๋„ ์žƒ์„ ๋น„๋ฐ€์ด ์—†๊ณ , ์„ธ ์„ ํƒ์ง€ ์ค‘ ์œ ์ผํ•˜๊ฒŒ CIยท์™ธ๋ถ€ ๊ธฐ์—ฌ์žยท๋ฏธ๋ž˜ ๋ฏธ๋Ÿฌ ๋ฌธ์ œ๋ฅผ ํ•œ ๋ฒˆ์— ์—†์•ฑ๋‹ˆ๋‹ค. ๋น„๊ณต๊ฐœ ์œ ์ง€๊ฐ€ ์กฐ์ง ์ •์ฑ…์ด๋ผ๋ฉด B ๋ฅผ ํƒํ•˜๊ณ , ๊ทธ ๊ฒฝ์šฐ ยง5 ์˜ D-31 ์€ "checkout ์ด์ „์— ์ž๊ฒฉ์ฆ๋ช… ์„ค์ • ์Šคํ…์ด ์กด์žฌํ•˜๋Š”๊ฐ€"๊นŒ์ง€ ๊ฒ€์‚ฌํ•˜๋„๋ก ํ™•์žฅํ•˜์‹ญ์‹œ์˜ค. + +**๊ฒ€์ฆ**: Rev.1 ์˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜(ํŠธ๋ฆฌ ๋ณต์ œ ํ›„ `nats-docker/` ๋ฅผ ๋น„์šฐ๊ณ  `pytest tests/test_deploy_freshness.py -q`)์„ ์žฌ์‹คํ–‰ํ•˜์—ฌ `18 failed` โ†’ `0 failed` ํ™•์ธ. ๊ฐ€๋Šฅํ•˜๋ฉด ์‹ค์ œ CI ์—์„œ `test` ์žก 1ํšŒ ํ†ต๊ณผ๊นŒ์ง€ ํ™•์ธ. + +### T-2 ~ T-14 (Rev.1 ์œ ์ง€, T-9 ๋งŒ ์žฌ์„ค๊ณ„) + +| ID | ํŒŒ์ผ | ์ž‘์—… | +|---|---|---| +| **T-2** | `MESSAGING.md` ยง1.2 / ยง1.3 | ํ”„๋กœ๋•์…˜ ๋ธŒ๋กœ์ปค ํ‘œ์ค€์„ `nats-server`(MQTT **3.1.1**)๋กœ ์žฌ์ž‘์„ฑ. mermaid ๋…ธ๋“œยทACL ์˜ˆ์‹œ๋ฅผ `MAM` ๊ณ„์ • / `mam_agent`ยท`mam_observer` / NATS `permissions` ๋ฌธ๋ฒ•์œผ๋กœ ๊ต์ฒด. Mosquitto ์„ค์ •์€ ยง1.4 "๋Œ€์•ˆ"์œผ๋กœ ๊ฐ•๋“ฑํ•˜๊ณ  ์ƒ์„ธ๋Š” `nats-docker/PRIVATE_SERVER.md` ๋งํฌ | +| **T-3** | `MESSAGING.md` ์‹ ์„ค ์ ˆ | JetStream ์š”๊ตฌ(MQTT ๋ฆฌ์Šค๋„ˆ ์ „์ œ), retained=MQTT ์ „์šฉ ๊ฒฝ๊ณ„(N-1), MQTT-over-WebSocket `/mqtt`(N-7), ์›๊ฒฉ ๋…ธ์ถœ ๋ชจ๋ธ(๋ชจ๋ธ T/P) ์š”์•ฝ + ์„œ๋ธŒ๋ชจ๋“ˆ ๋งํฌ | +| **T-4** | `MESSAGING.md` ยง4.2 / ยง4.3 / ยง6.1-3 | B-14(๋ฐœํ–‰ ์‹คํŒจ์™€ ๋ฌด๊ด€ํ•œ ์ƒํƒœ ๋™๊ธฐํ™”), B-15(`_check_disk_fallback`), F-4(rc=3) ๋ฐ˜์˜. ยง6.1-3 ์€ "ํ•ด๊ฒฐ๋จ" ์ฒ˜๋ฆฌํ•˜๋˜ ์ž”์—ฌ ์ œ์•ฝ(์ž๋™ ์žฌ์—ฐ๊ฒฐ ๋ฃจํ”„ ๋ถ€์žฌ)๋งŒ ์œ ์ง€ | +| **T-5** | `MESSAGING.md` ยง4.4 | `MQTT_CLIENT_ID_PREFIX`ยท`MQTT_KEEPALIVE` ์ถ”๊ฐ€. `.mam.env` ํ•ด์„ ์ˆœ์„œ ์ ˆ ์‹ ์„ค, **OS ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์šฐ์„ **(V-25) ๋ช…๊ธฐ, **B-17 ๋ฏธํ•ด๊ฒฐ ๊ฒฝ๊ณ ** ํฌํ•จ | +| **T-6** | `IMPROVEMENTS.md` ํ—ค๋” | ๊ฐฑ์‹ ์ผ 2026-08-23, `306/306`, ๋ฏธํ•ด๊ฒฐ **3๊ฑด**(A-2, B-16, O-5), ์™„๋ฃŒ **26๊ฑด** | +| **T-7** | `IMPROVEMENTS.md` `:76`, `:81` | B-14ยทB-15 ์ œ๋ชฉ์— `โœ… ์™„๋ฃŒ` ๋งˆ์ปค + ํ•ด๊ฒฐ ์ปค๋ฐ‹(`c6b6c77`)ยท๊ฐ€๋“œ(G-1~G-10) ๊ธฐ๋ก | +| **T-8** | `IMPROVEMENTS.md` ์‹ ์„ค | `O-6 (โœ… ์™„๋ฃŒ): ์›๊ฒฉ ํ”„๋กœ๋•์…˜ ๋ธŒ๋กœ์ปค ์ž์‚ฐ ์ •๋ณธํ™” ๋ฐ nats-docker ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ` โ€” ์ปค๋ฐ‹ 5์ข…, D-22~D-30, ๋™์  ๊ฒฝ๋กœ ํ•ด์„๊ธฐ, 297โ†’306 | +| **T-9** ๐Ÿ”„ | `IMPROVEMENTS.md` ์‹ ์„ค + ์ฒ˜๋ฐฉ | **`B-17 (P1)`** ๋“ฑ๋ก. ์ฒ˜๋ฐฉ์„ **2๋‹จ ๊ตฌ์กฐ**๋กœ ๋ช…์‹œ(์•„๋ž˜ ์ƒ์„ธ). `B-18` ๋„ ํ•จ๊ป˜ ๋“ฑ๋ก | +| **T-10** | `implementation_plan.md` `:3-5` | ๋ฌธ์„œ ๋ฒ„์ „ ์ƒํ–ฅ, ๊ธฐ์ค€ ์ปค๋ฐ‹ `916185c`, `306/306` | +| **T-11** | `implementation_plan.md` `:13-16`, `:23`, `:39` | ํŠธ๋ž™ ๋‹ค์ด์–ด๊ทธ๋žจ์— Track 1R ํฌํ•จ, ๋ณ€๊ฒฝ ์ง€์ ์„ `nats-docker/โ€ฆ` ๊ฒฝ๋กœ๋กœ, ๋งˆ์ผ์Šคํ†ค ํ‘œ ํ…Œ์ŠคํŠธ ์ˆ˜ ๊ฐฑ์‹  | +| **T-12** | `implementation_plan.md` ยง5, ยง8 | `P0.6 ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ` ๋‹จ๊ณ„ + ์ฒดํฌ๋ฆฌ์ŠคํŠธ 3ํ–‰(2ํ–‰ ์™„๋ฃŒ, **CI 1ํ–‰ ๋ฏธ์™„๋ฃŒ**) | +| **T-13** | `implementation_plan.md` `:177` | `.mam.env` ์ „ํ™˜ ์‹คํƒœ ๋ฐ˜์˜ โ€” ์ฒดํฌ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ์ ˆ์ฐจ ๋ฏธ์ดํ–‰ ์‚ฌ์‹ค ๊ธฐ๋ก | +| **T-14** | `implementation_plan.md` `:172` | ํ–‰ ๋ฒˆํ˜ธ ์ธ์šฉ์„ ์ ˆ ๋ฒˆํ˜ธ๋กœ ๊ต์ฒด | + +#### T-9 ์ƒ์„ธ โ€” B-17 ์ฒ˜๋ฐฉ (C3 ๋ฐ˜์˜ ์žฌ์„ค๊ณ„) + +```python +# mqtt_common.py โ€” import ์‹œ์ : ๊ธฐ๋ก๋งŒ, ์˜ˆ์™ธ ์—†์Œ +_env_file_missing: Optional[str] = None + +def _load_dotenv(workspace_dir=None): + global _env_file_missing + explicit = os.environ.get("MAM_ENV_FILE") + if explicit: + if os.path.isfile(explicit): + _parse_env_file(explicit) + else: + _env_file_missing = explicit + logger.error( + "MAM_ENV_FILE is set to %s but no such file exists; " + "refusing to auto-discover another .mam.env", explicit) + return # ๋ช…์‹œ์  ์ง€์ • ์‹œ ์ž๋™ ํƒ์ƒ‰ ๊ธˆ์ง€ (๋ฆฌ๋ทฐ์–ด C3-1) + # ๋ฏธ์„ค์ •์ผ ๋•Œ๋งŒ ์ˆœ์„œ ์žˆ๋Š” ํƒ์ƒ‰ (first-hit-wins) + for cand in (_from_env("MAM_REAL_ROOT"), _from_env("WORKSPACE_ROOT"), + _walk_up(os.path.dirname(os.path.abspath(__file__))), + _walk_up(os.getcwd())): + if cand and os.path.isfile(cand): + _parse_env_file(cand); return +``` + +```python +# ์ ‘์† ์ง€์ (make_client ๋˜๋Š” ์„ค์ • ํ™•์ • ํ•จ์ˆ˜) โ€” ์—ฌ๊ธฐ์„œ ๊ฑฐ๋ถ€ํ•œ๋‹ค +def make_client(role, cfg): + if _env_file_missing: + raise RuntimeError( + f"MAM_ENV_FILE points to a missing file ({_env_file_missing}); " + "refusing to connect with an unverified broker identity") + if cfg.host == "broker.hivemq.com": + logger.error("SECURITY: falling back to the PUBLIC broker " + "broker.hivemq.com โ€” job payloads will be world-readable") + ... +``` + +**์™œ import ์—์„œ ๋˜์ง€์ง€ ์•Š๋Š”๊ฐ€**: `_load_dotenv()` ๋Š” `mqtt_common.py:112` ์˜ import ๋ถ€์ž‘์šฉ์ด๊ณ (V-23), ํ…Œ์ŠคํŠธ 3๊ฐœ ํŒŒ์ผ์ด ๋ธŒ๋กœ์ปค ์ ‘์† ์˜๋„ ์—†์ด ์ด ๋ชจ๋“ˆ์„ import ํ•ฉ๋‹ˆ๋‹ค(V-24). import ์—์„œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด ๋‚ก์€ `MAM_ENV_FILE` ํ•˜๋‚˜๋กœ ์Šค์œ„ํŠธ ์ „์ฒด๊ฐ€ ์ˆ˜์ง‘ ๋‹จ๊ณ„์—์„œ ๋ถ•๊ดดํ•ฉ๋‹ˆ๋‹ค. + +**์™œ ๊ฒฝ๊ณ  ์กฐ๊ฑด์ด "๊ณต๊ฐœ ๊ธฐ๋ณธ๊ฐ’๊ณผ ์ผ์น˜"์ธ๊ฐ€**: OS ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ํŒŒ์ผ๋ณด๋‹ค ์šฐ์„ ํ•˜๋ฏ€๋กœ(V-25), `MQTT_BROKER` ๋ฅผ ์ง์ ‘ export ํ•œ ์‚ฌ์šฉ์ž๋Š” ํŒŒ์ผ์ด ์—†์–ด๋„ ๊ณต๊ฐœ ๋ธŒ๋กœ์ปค๋กœ ๋–จ์–ด์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜ธ์ŠคํŠธ ๊ฒฐ๊ณผ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ์‚ผ์œผ๋ฉด ํƒ์ƒ‰ ๊ฒฝ๋กœ ์ „์ฒด๋ฅผ ํ•œ ์กฐ๊ฑด์œผ๋กœ ๋ฎ์Šต๋‹ˆ๋‹ค. + +--- + +## 5. ๊ถŒ๊ณ  ์‹ ๊ทœ ๊ฐ€๋“œ + +| ID | ๋‹จ์–ธ | ๊ณตํ—ˆ ํ†ต๊ณผ ๋ฐฉ์ง€ | ์žก์•„๋‚ด๋Š” ํšŒ๊ท€ | +|---|---|---|---| +| **D-31** ๐Ÿ”„ | `deploy/gitea-ci.yml` ์„ YAML ํŒŒ์‹ฑ โ†’ ๊ฐ ์žก์˜ `run` ๋ธ”๋ก์„ ํ•ฉ์ณ `pytest` ๋˜๋Š” `tests/` ๊ฐ€ ๋“ฑ์žฅํ•˜๋ฉด **ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์žก**์œผ๋กœ ํŒ์ • โ†’ ๊ทธ ์žก์˜ ๋ชจ๋“  `actions/checkout` ์Šคํ…์ด `with.submodules` ๋ฅผ truthy ๋กœ ๊ฐ€์งˆ ๊ฒƒ. **`.gitmodules` ๊ฐ€ ์กด์žฌํ•  ๋•Œ๋งŒ ํ™œ์„ฑ**(์„œ๋ธŒ๋ชจ๋“ˆ ์ œ๊ฑฐ ์‹œ ์ž๋™ ๋ฌด๋ ฅํ™”) | **ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์žก์ด 0๊ฑด์ด๋ฉด FAIL** โ€” ์žก ์ด๋ฆ„ ๋ณ€๊ฒฝ์ด๋‚˜ ๋ž˜ํผ ์Šคํฌ๋ฆฝํŠธ๋กœ `pytest` ๋ฅผ ์ˆจ๊ฒจ ๊ฐ€๋“œ๋ฅผ ์กฐ์šฉํžˆ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ฐจ๋‹จ | S-1 ์žฌ๋ฐœ. ๋ฆฐํŠธ ์žก์€ ๊ฒ€์‚ฌ ๋Œ€์ƒ์—์„œ ์ œ์™ธ๋˜๋ฏ€๋กœ ๊ฒฝ๋Ÿ‰ ์›Œํฌํ”Œ๋กœ ์ถ”๊ฐ€๋ฅผ ๋ฐฉํ•ดํ•˜์ง€ ์•Š์Œ(A-2) | +| **D-32** | `MESSAGING.md` ๊ฐ€ ๋ฌธ์„œํ™”ํ•œ `MQTT_*` ์ง‘ํ•ฉ โЇ `mqtt_common.broker_config_from_env` ๊ฐ€ ํŒŒ์‹ฑํ•˜๋Š” ์ง‘ํ•ฉ | ์ฝ”๋“œ์—์„œ ๋ณ€์ˆ˜ 0๊ฐœ ์ถ”์ถœ ์‹œ FAIL | S-4 ์žฌ๋ฐœ. D-11 ์ด `PRIVATE_SERVER.md` ์— ๋Œ€ํ•ด ํ•˜๋Š” ๊ฒ€์‚ฌ๋ฅผ `MESSAGING.md` ๋กœ ํ™•์žฅ | + +**D-31 ๊ตฌํ˜„ ์ฃผ์˜**: PyYAML ์€ `on:` ์„ ๋ถˆ๋ฆฌ์–ธ `True` ํ‚ค๋กœ ํŒŒ์‹ฑํ•ฉ๋‹ˆ๋‹ค(V-22). `d["jobs"]` ๋งŒ ์ฝ์œผ๋ฉด ๋ฌดํ•ดํ•˜๋‚˜ `d["on"]` ์ ‘๊ทผ์€ `KeyError` ์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ ์ƒํƒœ์—์„œ ์ด ๊ฐ€๋“œ๋Š” `test` ์žก 1๊ฐœ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์‚ผ๊ณ  **์ฆ‰์‹œ FAIL** ํ•ฉ๋‹ˆ๋‹ค(`with` = `None`) โ€” ์ฐฉ์ˆ˜ ์‹œ์ ์— ๊ณตํ—ˆ ํ†ต๊ณผ๊ฐ€ ์•„๋‹˜์ด ์ž๋™ ์ฆ๋ช…๋ฉ๋‹ˆ๋‹ค. + +**๋ฎคํ…Œ์ด์…˜ ์ˆ˜์šฉ ๊ธฐ์ค€**: โ‘  `test` ์žก์˜ `submodules: recursive` ์ œ๊ฑฐ โ†’ D-31 FAIL. โ‘ก `test` ์žก ์ด๋ฆ„์„ `verify` ๋กœ ๋ณ€๊ฒฝ โ†’ **์—ฌ์ „ํžˆ FAIL ํ•ด์•ผ ํ•จ**(`run` ๋‚ด์šฉ ๊ธฐ์ค€ ํŒ์ •). โ‘ข `pytest tests/ -q` ๋ฅผ `bash deploy/run-tests.sh` ๋กœ ๊ฐ์‹ธ๊ณ  `tests/` ๋ฌธ์ž์—ด ์ œ๊ฑฐ โ†’ D-31 ์ด ๋Œ€์ƒ 0๊ฑด์„ ๋งŒ๋‚˜ **FAIL**(๊ณตํ—ˆ ํ†ต๊ณผ ๋ฐฉ์ง€ ๋‹จ์–ธ). โ‘ฃ `MESSAGING.md` ์—์„œ `MQTT_PORT` ์‚ญ์ œ โ†’ D-32 FAIL. + +--- + +## 6. ์—ด๋ฆฐ ์งˆ๋ฌธ + +| # | ์งˆ๋ฌธ | ๊ธฐ๋ณธ๊ฐ’(๋ฌด์‘๋‹ต ์‹œ) | +|---|---|---| +| **Q-1** | `.mam.env` ์ „ํ™˜(S-11)์ด ยง9.5 ๋“œ๋ ˆ์ธ ์ ˆ์ฐจ๋ฅผ ๋ฐŸ์€ ๊ฒƒ์ธ๊ฐ€? | ๋ฐŸ์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ, T-13 ์—์„œ ์‚ฌํ›„ ์ž”์—ฌ ์Šค์บ”์„ ๊ณผ์ œ๋กœ ๊ธฐ๋ก | +| **Q-2** | A-2 ๋ฅผ ์™„๋ฃŒ๋กœ ์ „ํ™˜ํ•  ์‹œ์ ์€? | M3(์ง€๋ฌธ ํ† ํ”ฝ + ๋ฌด์กฐ๊ฑด ํ† ํฐ) ์ดํ›„ ์œ ์ง€. ์‚ฌ์„ค ๋ธŒ๋กœ์ปค ์ „ํ™˜๋งŒ์œผ๋กœ๋Š” ์ข…๊ฒฐํ•˜์ง€ ์•Š์Œ | +| **Q-3** | `MESSAGING.md` ์˜ Mosquitto ์ ˆ์„ ์‚ญ์ œํ•  ๊ฒƒ์ธ๊ฐ€? | **๋‚จ๊น€**(ยง1.4 ๋กœ ๊ฐ•๋“ฑ). `PRIVATE_SERVER.md` ยง4.2 ๊ฐ€ mosquitto ๋ฅผ ์—ฌ์ „ํžˆ ๋Œ€์•ˆ์œผ๋กœ ์ œ์‹œํ•˜๋ฏ€๋กœ ์‚ญ์ œํ•˜๋ฉด ๋‘ ๋ฌธ์„œ๊ฐ€ ์–ด๊ธ‹๋‚จ | +| **Q-4** | D-31 / D-32 ๋ฅผ ์ด๋ฒˆ ์ปค๋ฐ‹์— ํฌํ•จํ•  ๊ฒƒ์ธ๊ฐ€? | ํฌํ•จ ๊ถŒ๊ณ  | +| **Q-5** ๐Ÿ†• | **T-1c ์„ ํƒ์ง€ โ€” `nats-docker` ๋ฅผ ๊ณต๊ฐœ๋กœ ์ „ํ™˜ํ•  ๊ฒƒ์ธ๊ฐ€?** | **A(๊ณต๊ฐœ ์ „ํ™˜) ๊ถŒ๊ณ **. ์ถ”์  ํŒŒ์ผ์— ๋น„๋ฐ€ 0๊ฑด ์‹ค์ธก(V-19). ๋น„๊ณต๊ฐœ ์œ ์ง€๊ฐ€ ์ •์ฑ…์ด๋ฉด B(ํ† ํฐ ์ฃผ์ž…) | +| **Q-6** ๐Ÿ†• | B-17 ์˜ ์ ‘์† ์ง€์  ๊ฑฐ๋ถ€๋ฅผ ์˜ˆ์™ธ๋กœ ํ•  ๊ฒƒ์ธ๊ฐ€ ์ข…๋ฃŒ ์ฝ”๋“œ๋กœ ํ•  ๊ฒƒ์ธ๊ฐ€? | **์˜ˆ์™ธ**(`RuntimeError`). `publish_event.py` ๋Š” ์ด๋ฏธ B-14 ๋กœ ์˜ˆ์™ธ๋ฅผ ์žก์•„ ๋””์Šคํฌ ์ƒํƒœ๋ฅผ ๋™๊ธฐํ™”ํ•œ ๋’ค rc ๋ฅผ ๋งคํ•‘ํ•˜๋ฏ€๋กœ, ์˜ˆ์™ธ๊ฐ€ ๋ฃจํ”„๋ฅผ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  fail-closed ๋งŒ ๋‹ฌ์„ฑ | + +--- + +## 7. ๊ฒฐ๋ก  + +- **ํ…Œ์ŠคํŠธ**: ์ถฉ์กฑ. ์š”์ฒญ ๋ช…๋ น 31/31, ์ „์ฒด 306/306, ๋ฌธ์„œ ํšŒ๊ท€ 0๊ฑด. +- **`MESSAGING.md`**: ๋ฏธ์ถฉ์กฑ(S-2, S-3, S-4). +- **`IMPROVEMENTS.md`**: ๋ฏธ์ถฉ์กฑ(S-5, S-6, S-7). +- **`implementation_plan.md`**: ๋ถ€๋ถ„ ์ถฉ์กฑ โ€” ์„œ๋ธŒ๋ชจ๋“ˆ ๋งํฌ๋Š” ๊ฐฑ์‹ ๋˜์—ˆ์œผ๋‚˜ ํ—ค๋”ยทํŠธ๋ž™ํ‘œยท๋‹ค์ด์–ด๊ทธ๋žจยท์ „ํ™˜ ๊ธฐ๋กยท์ƒํƒœ ๋“œ๋ฆฌํ”„ํŠธ ์ž”์กด(S-8 ~ S-12). +- **์ตœ์šฐ์„ **: S-1 + S-13. CI ๋Š” ์„œ๋ธŒ๋ชจ๋“ˆ์„ ๋ฐ›์ง€ ์•Š๊ณ , ์„œ๋ธŒ๋ชจ๋“ˆ์€ ๋น„๊ณต๊ฐœ์ด๋ฉฐ, ๋ฌธ์ œ๋ฅผ ๋ฐœํ˜„์‹œํ‚ฌ ์ปค๋ฐ‹ 2๊ฐœ๊ฐ€ ์•„์ง ํ‘ธ์‹œ๋˜์ง€ ์•Š์€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. **ํ‘ธ์‹œ ์ด์ „์— T-1a~T-1c ๋ฅผ ์™„๋ฃŒํ•˜์‹ญ์‹œ์˜ค.** + +๋ฆฌ๋ทฐ์–ด `agy` ์˜ ์„ธ ์ง€์ ์€ ๋ชจ๋‘ ์‹ค์žฌํ•˜๋Š” ๋งน์ ์ด์—ˆ๊ณ , C2ยทC3 ๋Š” Rev.1 ์˜ ์ฒ˜๋ฐฉ์„ ์ง์ ‘ ๊ต์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. C1 ์€ ์ „์ œ๊ฐ€ ์˜ณ์•˜์œผ๋‚˜ ์ œ์‹œ๋œ ์ƒ๋Œ€ URL ํ˜•ํƒœ(`../nats-docker`)๊ฐ€ ์ž˜๋ชป๋œ ์กฐ์ง์„ ๊ฐ€๋ฆฌํ‚ค๋ฏ€๋กœ `../../laa/nats-docker` ๋กœ ๊ต์ •ํ•˜์—ฌ ๋ฐ˜์˜ํ–ˆ์Šต๋‹ˆ๋‹ค. + +[VERDICT: NOT PASS] diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-9f9e7c2c.md b/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-9f9e7c2c.md new file mode 100644 index 0000000..2214d3d --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-9f9e7c2c.md @@ -0,0 +1,196 @@ +# Cross-Code Review Report โ€” Job `9f9e7c2c` + +- **Reviewer**: cline (session `herdr:canary-projects-multi-agent-mux-creator-cline`) +- **Date**: 2026-08-23 +- **Changeset**: uncommitted working-tree, 6 files, +235/-48 +- **Scope**: Cross-code review (lint / behavior / loss) of documentation synchronization + (`MESSAGING.md`, `IMPROVEMENTS.md`, `implementation_plan.md`), `.gitmodules` relative URL, + `deploy/gitea-ci.yml` submodule checkout, and test guards D-31/D-32 against the latest + NATS deployment + `nats-docker` submodule integration. + +--- + +## 1. Changeset Summary + +| File | ฮ” | Nature | +|---|---|---| +| `.gitmodules` | 1 line | Absolute URL โ†’ relative `../../laa/nats-docker` | +| `IMPROVEMENTS.md` | +52/-2 | Header counts, new ยง2/ยง3 sections (B-14โœ…/B-15โœ…/B-16/B-17/B-18, O-6โœ…), ยง6.6 refresh | +| `MESSAGING.md` | +63/-45 | Mosquitto/EMQX โ†’ NATS broker (ยง1.2, ACLs, accounts), ยง4.4 10-env table, `.mam.env` resolution hierarchy (B-17) | +| `deploy/gitea-ci.yml` | +2/-0 | `test` job checkout gains `submodules: recursive` | +| `implementation_plan.md` | +21/-10 | Track 1R P0.6 submodule items, ยง7 description correction, M2b gate count (306) | +| `tests/test_deploy_freshness.py` | +86/-0 | New guards `test_d31_*` (CI submodules) and `test_d32_*` (MESSAGING.md env coverage) | + +--- + +## 2. Verification Methodology + +1. Gathered changeset via `git diff --stat` and per-file diffs. +2. Verified `.gitmodules` relative URL resolution against the **actual** parent origin + (`git remote get-url origin` โ†’ `https://git.godopu.com/tmpl/multi-agent-mux`) and the + configured submodule URL in `.git/config` + submodule's own `origin`. +3. Confirmed on-disk existence of every `nats-docker/` path referenced in the docs; confirmed + no orphaned root-level `PRIVATE_SERVER.md` / `NATS_REPORT.md` / `docker/`. +4. Cross-checked all 10 `MQTT_*` env vars in `MESSAGING.md` ยง4.4 against + `mqtt_common.py` (`broker_config_from_env` + `make_client` defaults + docstring). +5. Verified `deploy/gitea-ci.yml` test job enables `submodules: recursive`. +6. Ran mandated tests: `.venv/bin/python -m pytest tests/test_deploy_freshness.py tests/test_sanity.py -q`. +7. Ran D-31/D-32 in isolation. +8. Audited `IMPROVEMENTS.md` section-header structure (`grep '^## '`) against the diff to detect + insertions that orphan or duplicate existing sections. + +--- + +## 3. Verification Results + +### 3.1 `.gitmodules` relative URL โ€” PASS +- Parent origin: `https://git.godopu.com/tmpl/multi-agent-mux`. +- `../../laa/nats-docker` resolves: `/tmpl/multi-agent-mux` โ†’ `../` โ†’ `/tmpl` โ†’ `../../` โ†’ + host root โ†’ `laa/nats-docker` = **`https://git.godopu.com/laa/nats-docker`**. +- Confirmed equal to `git config --get submodule.nats-docker.url` and the submodule's own + `origin` fetch/push URL. +- Submodule checked out at `a4b6e49` (heads/main). Relative form improves org-wide mirroring + portability vs the prior absolute URL. No functional regression. + +### 3.2 Submodule on-disk asset integrity โ€” PASS +All paths referenced by the docs exist under `nats-docker/`: +- `nats-docker/docker/{docker-compose.yaml, nats.conf, .env.example, README.md}` +- `nats-docker/PRIVATE_SERVER.md`, `nats-docker/NATS_REPORT.md` + +No orphaned root-level `PRIVATE_SERVER.md` / `NATS_REPORT.md` / `docker/` remain (confirmed via +`ls`; all three return "No such file or directory"). The `12ba30b` / `629a67f` migration is +complete on disk. + +### 3.3 `MESSAGING.md` โ€” PASS +- ยง1.2 cleanly switched from "Mosquitto/EMQX" to "NATS server (`nats:2.12-alpine`)"; mermaid + diagram, ACL accounts (`mam_agent` / `mam_observer`), and `nats-docker/docker/nats.conf` + references are consistent with the submodule assets. +- ยง4.4 environment table now lists **all 10** supported `MQTT_*` variables. +- `MQTT_CLIENT_ID_PREFIX` default documented as **`hermes`**, matching + `mqtt_common.py:230` (`os.environ.get("MQTT_CLIENT_ID_PREFIX", "hermes")`) and the + module docstring (`mqtt_common.py:218`). **Prior finding M-1 is RESOLVED.** +- ยง4.4 `.mam.env` resolution hierarchy documents B-17 fail-closed behavior (explicit + `MAM_ENV_FILE` missing โ†’ log error + `RuntimeError` at connect; public-broker security + warning). Consistent with the B-17 action direction recorded in `IMPROVEMENTS.md`. + +### 3.4 `MQTT_*` env cross-check vs `mqtt_common.py` โ€” PASS +All 10 documented vars are parsed by code: `MQTT_BROKER`, `MQTT_PORT`, `MQTT_TLS`, +`MQTT_USERNAME`, `MQTT_PASSWORD`, `MQTT_CA_CERTS`, `MQTT_CERTFILE`, `MQTT_KEYFILE`, +`MQTT_CLIENT_ID_PREFIX`, `MQTT_KEEPALIVE`. No drift. D-32 enforces presence of these 10. + +### 3.5 `deploy/gitea-ci.yml` โ€” PASS +- `test` job (line 87-89): `actions/checkout@v3` with `submodules: recursive`. +- The job runs `pytest tests/ -q` (line 112) โ†’ correctly classified as a test job by D-31. +- `lint-shell` / `lint-python` jobs intentionally omit `submodules` (they do not touch + `nats-docker/` paths) โ€” D-31's logic only requires submodules on pytest jobs, which is + the correct, minimal scope. + +### 3.6 `implementation_plan.md` โ€” PASS +- Track 1R row updated to cite `nats-docker/PRIVATE_SERVER.md` ยง9 and + `nats-docker/docker/docker-compose.yaml` (submodule-prefixed) instead of root-level paths. +- M2b gate annotated with `(290 -> 297 -> 306)`. +- P0.6 checklist block added (submodule split, dynamic path resolvers, CI checkout sync). +- ยง7 `IMPROVEMENTS.md` description corrected: removed the prior false claim + "A-2 ์™„๋ฃŒ ์ „ํ™˜, B-14/B-15/B-16/O-5 ํ•ด๊ฒฐ ์ƒํƒœ ๊ฐฑ์‹ " (A-2 is still open) and replaced with + "B-14/B-15 ์™„๋ฃŒ ์ƒํƒœ ๋ฐ˜์˜, O-6 ์‹ ์„ค, B-17/B-18 ์‹ ์„ค ๋“ฑ๋ก" โ€” factually accurate. + +### 3.7 Mandated tests โ€” PASS +- `pytest tests/test_deploy_freshness.py tests/test_sanity.py -q` โ†’ **33 passed** in 21.45s. +- D-31 (`test_d31_gitea_ci_submodules_in_test_job`) โ€” PASS in isolation. +- D-32 (`test_d32_messaging_doc_covers_all_mqtt_env_vars`) โ€” PASS in isolation. +- No doc regressions; 100% pass rate confirmed. + +### 3.8 Prior-review findings disposition +- **M-1** (MESSAGING.md `MQTT_CLIENT_ID_PREFIX` default mismatch) โ€” **RESOLVED** (now `hermes`). +- **M-2** (IMPROVEMENTS.md open-item count excluded B-18) โ€” **RESOLVED** (now 5๊ฑด incl. B-18). +- **L-1** (ยง6.6 stale conclusion listing B-14/B-15) โ€” **RESOLVED** (now lists B-16/B-17/B-18). +- **L-2** (ยง3 header count included completed O-6) โ€” **PARTIALLY RESOLVED**: the new ยง3 (line 56) + correctly splits "์ถ”์  ์ค‘ 1๊ฑด / ์™„๋ฃŒ 1๊ฑด"; however the *old* ยง3 remains stale (see M-3). + +--- + +## 4. Detailed Findings + +### M-3 (Medium) โ€” Duplicate ยง2 and ยง3 section headers in `IMPROVEMENTS.md` + +- **Location**: `IMPROVEMENTS.md` โ€” new ยง2 at line 28 and new ยง3 at line 56; pre-existing ยง2 now + at line 120 and ยง3 at line 143. +- **Observation**: This changeset *inserted* new `## 2.` and `## 3.` sections (with updated + content: B-14/B-15 marked `โœ… ์™„๋ฃŒ`, B-17/B-18 added, O-6 added) immediately after the ยง1 intro, + but did **not remove** the pre-existing `## 2.` (Edge-case Bugs) and `## 3.` (Orchestration) + sections that remain further down. Confirmed via `grep -n '^## '` showing two `## 2.` and two + `## 3.` headers, and via `git diff` which contains only an insertion hunk (`@@ -23,6 +23,52 @@`) + with no deletion of the old sections. +- **Contradiction introduced**: the duplicate sections disagree: + - New ยง2 (line 30-36): B-14 and B-15 carry `โœ… ์™„๋ฃŒ` markers with "์กฐ์น˜ ๊ฒฐ๊ณผ (์™„๋ฃŒ โ€” ์ปค๋ฐ‹ `c6b6c77`)". + - Old ยง2 (line 120-141): B-14/B-15 are described as open with "์กฐ์น˜ ๋ฐฉํ–ฅ (Track 0 Step 1/2/3)" + and no completion marker โ€” implying unresolved. + - New ยง3 (line 56): header "์ถ”์  ์ค‘ 1๊ฑด / ์™„๋ฃŒ 1๊ฑด: O-5, O-6", lists O-5 + O-6 (โœ…). + - Old ยง3 (line 143): header "1๊ฑด", lists only O-5. +- Additionally, the `A-4` entry (a completed structural-improvement proposal) is now orphaned + between the new ยง3 and the old ยง2 (it originally sat under ยง1 Architecture). +- **Impact**: Medium. Purely documentation-level (no runtime/test effect; no D-guard asserts + section-header uniqueness). However it directly undermines the stated goal of this changeset + ("synchronize documentation"): a reader navigating by section number hits contradictory + duplicate content, and stale "action direction" text for already-completed B-14/B-15 persists. +- **Recommendation**: Delete the now-redundant old ยง2 (lines ~120-141) and old ยง3 (lines ~143-153) + blocks โ€” the new ยง2/ยง3 supersede them. Re-home `A-4` (e.g., into ยง1 or ยง5 Completed) so it no + longer dangles between sections. This is a surgical delete, not a redesign. + +### M-4 (Low) โ€” `ยง5` completed-tasks header count stale + +- **Location**: `IMPROVEMENTS.md` line 158 โ€” `## 5. ๐ŸŽ‰ ์™„๋ฃŒ๋œ ๊ณผ์ œ (Completed Tasks โ€” 24๊ฑด)`. +- **Observation**: The header summary (line 6) was updated to claim **27** completed items + (adding B-14, B-15, O-6). But the ยง5 header still reads **24๊ฑด** and the ยง5 body was not + extended to include B-14/B-15/O-6 (those three are instead described inline in the new ยง2/ยง3 + with `โœ…` markers). This creates an internal count drift between the top summary and the ยง5 + detail section. +- **Impact**: Low. Internal consistency only; not enforced by any D-guard. +- **Recommendation**: Either update ยง5 header to 27๊ฑด and migrate B-14/B-15/O-6 entries into ยง5, + or annotate ยง5 to note the three are tracked in ยง2/ยง3. Pick one location as the single source + of truth for the completed list. + +### Note (positive) +- `MESSAGING.md` and `implementation_plan.md` changes are clean, accurate, and well-synchronized + with the NATS deployment and submodule state. No orphaned root-level files. The `MQTT_*` + table, broker architecture, ACL/account model, and `.mam.env` resolution hierarchy are all + consistent with `mqtt_common.py` and the `nats-docker/` assets. +- D-31/D-32 are well-scoped, auto-disable gracefully when prerequisites are absent, and include + anti-void assertions (they assert at least one test job exists / at least one MQTT var is + documented). +--- + +## 5. Risk Assessment + +| Area | Status | +|---|---| +| Runtime behavior | No code change outside tests/docs; behavior unaffected. PASS. | +| Test suite | 33/33 mandated tests pass; D-31/D-32 green in isolation. PASS. | +| Submodule integrity | Relative URL resolves correctly; submodule checked out; assets on disk. PASS. | +| Documentation sync (MESSAGING.md / implementation_plan.md) | Accurate and complete. PASS. | +| Documentation sync (IMPROVEMENTS.md) | New content correct, but duplicate ยง2/ยง3 + stale ยง5 count (M-3/M-4). Minor. | +| Loss / orphaned references | None โ€” all `nats-docker/` doc links resolve; root-level originals removed. PASS. | + +All findings (M-3, M-4) are documentation-level, non-blocking, and fixable by surgical edits. +No design-level rework is warranted; no `[ESCALATE: PLANNER]` is required. + +--- + +## 6. Actionable Follow-ups (optional, separate cleanup commit) + +1. **M-3**: Remove the duplicate old ยง2 (lines ~120-141) and old ยง3 (lines ~143-153) blocks in + `IMPROVEMENTS.md`; re-home the orphaned `A-4` entry. +2. **M-4**: Align `ยง5` header count (24๊ฑด) with the summary (27๊ฑด), or annotate ยง5 to delegate + B-14/B-15/O-6 to ยง2/ยง3. + +--- + +## 7. Verdict + +All mandated tests pass, the `.gitmodules` relative URL resolves correctly, submodule assets +are intact, `MESSAGING.md` and `implementation_plan.md` are accurately synchronized with the +NATS deployment, and the prior review's M-1/M-2/L-1 findings are resolved. The two new findings +(M-3 duplicate ยง2/ยง3 headers, M-4 stale ยง5 count) are documentation-level, non-blocking, and +do not affect runtime behavior or test results. No escalation to the planner is warranted. + +[VERDICT: PASS] \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index fad1eb2..3763734 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "nats-docker"] path = nats-docker - url = https://git.godopu.com/laa/nats-docker + url = ../../laa/nats-docker diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md index fe5dfd7..6396493 100644 --- a/IMPROVEMENTS.md +++ b/IMPROVEMENTS.md @@ -1,9 +1,9 @@ # ๐Ÿ› ๏ธ Multi-Agent Mux ์ข…ํ•ฉ ๊ฐœ์„  ๋ฐ ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ ๋ฐฑ๋กœ๊ทธ (`IMPROVEMENTS.md`) -- **์ตœ์ข… ๊ฐฑ์‹ ์ผ**: 2026-08-20 (`NATS_REPORT.md` ์‹ค์ธก ๋ถ„์„ ๋ฐ ๋ฉ”์‹œ์ง• ์ž ๋ณต ๊ฒฐํ•จ B-14/B-15/B-16/O-5 ๋ฐœ๊ตด ๋ฐ˜์˜, 276/276 ํ†ต๊ณผ ์œ ์ง€) +- **์ตœ์ข… ๊ฐฑ์‹ ์ผ**: 2026-08-23 (`nats-docker` ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ, ํ”„๋กœ๋•์…˜ Docker ์ž์‚ฐ ์ •๋ณธํ™”, ๊ฐ€๋“œ D-22~D-30 ๋„์ž…, 306/306 ํ†ต๊ณผ ์œ ์ง€) - **ํ†ตํ•ฉ ๊ด€๋ฆฌ ๋Œ€์ƒ**: ๊ธฐ์กด `CODEBASE_REVIEW_REPORT.md` + `OPTIMIZATION.md` + `NATS_REPORT.md` -- **์ด ์ถ”์  ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ**: **5๊ฑด** (์•„ํ‚คํ…์ฒ˜ 1๊ฑด: `A-2`, ์—ฃ์ง€์ผ€์ด์Šค ๋ฐ ๊ฐ€์šฉ์„ฑ 3๊ฑด: `B-14`, `B-15`, `B-16`, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ 1๊ฑด: `O-5`) -- **์™„๋ฃŒ๋œ ๊ณผ์ œ**: **24๊ฑด** (A-1, A-3, A-4, A-5, B-1, B-3, B-4, B-5, B-7, B-8, B-9, B-10, B-13, C-1, C-2, C-3b, C-6, O-1, O-2, O-3, O-4-OrcOnboard, Herdr-0.8.0-Compat-SanitizeHash, P2-1-DelegateJobSafe-TrapFix, P2-2-C3a-C4-LegacyCleanup) +- **์ด ์ถ”์  ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ**: **5๊ฑด** (์•„ํ‚คํ…์ฒ˜ 1๊ฑด: `A-2`, ์—ฃ์ง€์ผ€์ด์Šค ๋ฐ ๊ฐ€์šฉ์„ฑ 3๊ฑด: `B-16`, `B-17`, `B-18`, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ 1๊ฑด: `O-5`) +- **์™„๋ฃŒ๋œ ๊ณผ์ œ**: **27๊ฑด** (A-1, A-3, A-4, A-5, B-1, B-3, B-4, B-5, B-7, B-8, B-9, B-10, B-13, B-14, B-15, C-1, C-2, C-3b, C-6, O-1, O-2, O-3, O-4-OrcOnboard, O-6, Herdr-0.8.0-Compat-SanitizeHash, P2-1-DelegateJobSafe-TrapFix, P2-2-C3a-C4-LegacyCleanup) --- @@ -23,6 +23,52 @@ - ๋Œ€์‹  **`nats-server`์˜ ๋‚ด์žฅ MQTT 3.1.1 ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ „์šฉ ์‚ฌ์„ค ๋ธŒ๋กœ์ปค๋กœ ์ฑ„ํƒ(Option C)**ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ 0์ค„ ๋ณ€๊ฒฝ์œผ๋กœ NKey/JWT ๊ณ„์ •ยทSubject๋ณ„ ACL ๊ฒฉ๋ฆฌ ๋ฐ JetStream ์˜์†์„ฑ์„ 100% ํ™•๋ณดํ•˜๊ธฐ๋กœ ํ™•์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. - ๋‹จ, ๋ธŒ๋กœ์ปค ์ œํ’ˆ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ์กด์žฌํ•˜๋Š” **๊ฐ€์šฉ์„ฑ ์„ ํ–‰ ๊ฒฐํ•จ(Track 0: B-14, B-15)**์„ ๋จผ์ € ๊ต์ •ํ•œ ํ›„ Track 1(์ŠคํŒŒ์ดํฌ) ๋ฐ Track 2(A-2 ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ง€๋ฌธ ํ† ํ”ฝ + ๋ฌด์กฐ๊ฑด ํ† ํฐ ๋ฐœ๊ธ‰)๋ฅผ ์ˆœ์ฐจ ์ „๊ฐœํ•ฉ๋‹ˆ๋‹ค. +--- + +## 2. ๐ŸŸ  ์—ฃ์ง€ ์ผ€์ด์Šค ๋ฐ ๋Ÿฐํƒ€์ž„ ๋ฒ„๊ทธ (Edge-case Bugs โ€” 3๊ฑด) + +### **B-14 (โœ… ์™„๋ฃŒ โ€” F-1 / P1): `publish_event.py` ๋ธŒ๋กœ์ปค ์žฅ์•  ์‹œ `return 2` ์กฐ๊ธฐ ํƒˆ์ถœ๋กœ ์ธํ•œ 65๋ถ„ ๋ฃจํ”„ ์ •์ง€** +- **ํ˜„์ƒ**: `publish_event.py`์—์„œ ๋ธŒ๋กœ์ปค ๋„คํŠธ์›Œํฌ ์žฅ์•  ๋ฐœ์ƒ ์‹œ `return 2`๋กœ ์กฐ๊ธฐ ์ข…๋ฃŒ๋˜์–ด, ๋’ค๋”ฐ๋ฅด๋Š” ๋กœ์ปฌ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒํƒœ(`update_job_status(status=completed)`) ๋ฐ ๊ฐ์‚ฌ ๋กœ๊ทธ(`append_event`, `registry.append_event`) ๊ฐฑ์‹ ์ด ๋ˆ„๋ฝ๋˜๋˜ ๊ฒฐํ•จ. +- **์กฐ์น˜ ๊ฒฐ๊ณผ (์™„๋ฃŒ โ€” ์ปค๋ฐ‹ `c6b6c77`)**: ๋„คํŠธ์›Œํฌ ๋ฐœํ–‰ ์‹คํŒจ ์—ฌ๋ถ€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋กœ์ปฌ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋ฐ ๊ฐ์‚ฌ ๋กœ๊ทธ๋ฅผ 100% ๋จผ์ € ๋™๊ธฐํ™”ํ•œ ํ›„ `published=False`์™€ ํ•จ๊ป˜ `return 2`๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ์žฌ๋ฐฐ์น˜ (G-1 ~ G-4 ํšŒ๊ท€ ๊ฐ€๋“œ๋กœ ๋ด‰์ธ ์™„๋ฃŒ). + +### **B-15 (โœ… ์™„๋ฃŒ โ€” C1 & F-4 / P1): `job_subscriber.py` ๋””์Šคํฌ ํด๋ฐฑ ๋ถ€์žฌ ๋ฐ ์œ„์ž„ ๊ฒฝ๋กœ ์ธํ”„๋ผ ์—๋Ÿฌ ์˜คํŒ์ •** +- **ํ˜„์ƒ**: `job_subscriber.py`๊ฐ€ ๋„คํŠธ์›Œํฌ ํ๋งŒ ๋Œ€๊ธฐํ•˜๋ฉฐ ๋กœ์ปฌ ๋””์Šคํฌ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์ง€ ์•Š์•„ ๋ธŒ๋กœ์ปค ๋‹ค์šด ์‹œ ๋ธ”๋กœํ‚น๋˜๊ฑฐ๋‚˜ ์ธํ”„๋ผ ์—๋Ÿฌ๊ฐ€ ์ž‘์—… `error`๋กœ ์˜คํŒ์ •๋˜๋˜ ๊ฒฐํ•จ. +- **์กฐ์น˜ ๊ฒฐ๊ณผ (์™„๋ฃŒ โ€” ์ปค๋ฐ‹ `c6b6c77`)**: `_check_disk_fallback()`์„ ๋„์ž…ํ•˜์—ฌ ๋กœ์ปฌ ๋””์Šคํฌ ์ƒ์˜ ํ„ฐ๋ฏธ๋„ ์ƒํƒœ๋ฅผ ๊ฐ์ง€ํ•˜๋ฉด ํ•ฉ์„ฑ ์ด๋ฒคํŠธ๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ์ฆ‰์‹œ `rc=0`์œผ๋กœ ์ •์ƒ ์ข…๋ฃŒํ•˜๋„๋ก ๊ฐœ์„ . ๋ธŒ๋กœ์ปค ์ธํ”„๋ผ ์ ‘์† ์‹คํŒจ๋Š” ์ „์šฉ `rc=3`์œผ๋กœ ๋ถ„๋ฆฌ (G-5 ~ G-10 ํšŒ๊ท€ ๊ฐ€๋“œ๋กœ ๋ด‰์ธ ์™„๋ฃŒ). + +### **B-16 (F-5 / P3): `make_client()` ๋งค ์‹คํ–‰ ๋žœ๋ค `client_id` ๋ฐœ๊ธ‰์œผ๋กœ ์ธํ•œ ์˜์† ์„ธ์…˜(Durable Session) ๊ตฌ์„ฑ ๋ถˆ๊ฐ€** +- **ํ˜„์ƒ**: `mqtt_common.py:258`์—์„œ `client_id`๋ฅผ ๋งค๋ฒˆ `uuid.uuid4().hex[:8]`๋กœ ์ƒ์„ฑํ•˜์—ฌ, ๋ธŒ๋กœ์ปค๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์žฌ์—ฐ๊ฒฐ์„ ์‹๋ณ„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค (`NATS_REPORT.md` ยง3.5 F-5). +- **ํŒŒ๊ธ‰ ํšจ๊ณผ**: ๋„คํŠธ์›Œํฌ ์žฌ์—ฐ๊ฒฐ ์‹œ ๋ฏธ์ˆ˜์‹  ์ด๋ฒคํŠธ ์œ ์‹ค ๊ฐ€๋Šฅ์„ฑ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. +- **์กฐ์น˜ ๋ฐฉํ–ฅ**: B-15์˜ ๋กœ์ปฌ ๋””์Šคํฌ ํด๋ฐฑ์„ ํ‘œ์ค€ ๋ณต์› ๊ฒฝ๋กœ๋กœ ํ™•๋ฆฝํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์„ธ์…˜ ์˜์กด๋„๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ํ•„์š” ์‹œ ๊ฒฐ์ •๋ก ์  ์‹๋ณ„์ž ๊ทœ์น™์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. + +### **B-17 (P1): `_load_dotenv` ์˜คํƒ€/๋ถ€์žฌ ๊ฒฝ๋กœ ์ง€์ • ์‹œ Fail-Closed ๋ฐ ๊ณต์šฉ ๋ธŒ๋กœ์ปค ํด๋ฐฑ ๋ฐฉ์ง€** +- **ํ˜„์ƒ**: `MAM_ENV_FILE`์ด ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •๋˜์—ˆ์œผ๋‚˜ ํ•ด๋‹น ๊ฒฝ๋กœ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ, `_load_dotenv`๊ฐ€ ์กฐ์šฉํžˆ ๋ฆฌํ„ดํ•˜์—ฌ `broker.hivemq.com` ๊ณต๊ฐœ ๋ธŒ๋กœ์ปค๋กœ ํด๋ฐฑ๋˜๋Š” ์œ„ํ—˜. +- **ํŒŒ๊ธ‰ ํšจ๊ณผ**: ์„ค์ • ์˜คํƒ€ ๋ฐœ์ƒ ์‹œ ์žก ์ด๋ฒคํŠธ์™€ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๊ณต๊ฐœ ๋ธŒ๋กœ์ปค๋กœ ์ „์†ก๋  ์ˆ˜ ์žˆ์Œ. +- **์กฐ์น˜ ๋ฐฉํ–ฅ (2๋‹จ ๊ตฌ์กฐ)**: + 1. import ์‹œ์ : ๋ช…์‹œ์  `MAM_ENV_FILE` ๊ฒฝ๋กœ ๋ถ€์žฌ ์‹œ `logger.error` ๊ธฐ๋ก ๋ฐ `_env_file_missing = True` ํ”Œ๋ž˜๊ทธ ์„ค์ • (์ƒ์œ„ ์ž„์˜ ํƒ์ƒ‰ ๊ธˆ์ง€, import ์˜ˆ์™ธ ๋ฐฉ์ง€). + 2. ์ ‘์† ์‹œ์ : `make_client()` ์‹œ `_env_file_missing`์ด๋ฉด `RuntimeError`๋กœ fail-closed ๊ฑฐ๋ถ€. ์ตœ์ข… ํ˜ธ์ŠคํŠธ๊ฐ€ `broker.hivemq.com`์ธ ๊ฒฝ์šฐ ๋ˆˆ์— ๋„๋Š” ๋ณด์•ˆ ๊ฒฝ๊ณ  ์ถœ๋ ฅ. + +### **B-18 (P2): `.mam.env`์™€ `.env` ๊ณต์กด ๋ฐ ๋‹ค์ค‘ ์›Œํฌ์ŠคํŽ˜์ด์Šค ๊ฒฝ๊ณ„ ํƒ์ƒ‰ ์ •ํ•ฉ์„ฑ** +- **ํ˜„์ƒ**: `.mam.env`์™€ `.env`์˜ ์šฐ์„ ์ˆœ์œ„ ๋ฐ ์›Œํฌ์ŠคํŽ˜์ด์Šค ๊ฒฝ๊ณ„(`.agents`, `.git`) ํƒ์ƒ‰ ๊ณผ์ •์—์„œ ๋‹ค์ค‘ ์›Œํฌ์ŠคํŽ˜์ด์Šค ํ™˜๊ฒฝ์—์„œ์˜ ์ผ๊ด€์„ฑ ์œ ์ง€. +- **์กฐ์น˜ ๋ฐฉํ–ฅ**: `MAM_REAL_ROOT` -> `WORKSPACE_ROOT` -> ์ƒ์œ„ ๊ฒฝ๊ณ„ ๋””๋ ‰ํ„ฐ๋ฆฌ -> `cwd` ์ˆœ์„œ์˜ first-hit-wins ํƒ์ƒ‰ ๊ทœ์น™ ์ ์šฉ. + +--- + +## 3. ๐ŸŸก ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ์ตœ์ ํ™” ๊ณผ์ œ (Orchestration Optimizations โ€” ์ถ”์  ์ค‘ 1๊ฑด / ์™„๋ฃŒ 1๊ฑด: O-5, O-6) + +### **O-5 (P2): NATS/MQTT ๋ฉ”์‹œ์ง• ๋ฐฑํ”Œ๋ ˆ์ธ ๊ณ ๋„ํ™” ๋ฐ `nats-server` ์ŠคํŒŒ์ดํฌ ๊ฒ€์ฆ (Track 1 ~ Track 2)** +- **ํ˜„์ƒ**: `NATS_REPORT.md` ์•„ํ‚คํ…์ฒ˜ ์‹ค์ธก ๋ถ„์„์— ๋”ฐ๋ผ `nats-server` ๋‚ด์žฅ MQTT 3.1.1 ์–ด๋Œ‘ํ„ฐ๋ฅผ ์‚ฌ์„ค ์ „์šฉ ๋ธŒ๋กœ์ปค๋กœ ์ฑ„ํƒํ•˜๋Š” ์ „๋žต(Option C)์ด ํ™•์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +- **์กฐ์น˜ ๋ฐฉํ–ฅ**: + 1. **Track 1 (์ŠคํŒŒ์ดํฌ ๊ฒ€์ฆ)**: ๊ฒฉ๋ฆฌ ํ™˜๊ฒฝ์—์„œ `nats-server -js`์˜ MQTT 3.1.1 ํ˜ธํ™˜์„ฑ ์‹ค์ธก ๊ฒ€์ฆ. + 2. **Track 2 (๋ณด์•ˆ/๊ฒฉ๋ฆฌ)**: ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ง€๋ฌธ ๊ธฐ๋ฐ˜ ํ† ํ”ฝ(`mam//jobs/...`) ๋ฐ ๋ฌด์กฐ๊ฑด `auth_token` ๋ฐœ๊ธ‰(G-11)์„ ์ ์šฉํ•˜์—ฌ A-2 ๋ณด์•ˆ ๊ฒฐํ•จ ์™„์ „ ์ข…๊ฒฐ. + 3. **Track 3 (๋ฌธ์„œ/์„ค์ •)**: `MESSAGING.md`, `VERSIONS.md`, `.mam.env`์— `nats-server` ์„œ๋น™ ๊ฐ€์ด๋“œ ๋ฐ ์„ค์ • ๋™๊ธฐํ™”. + +### **O-6 (โœ… ์™„๋ฃŒ โ€” P1): ์›๊ฒฉ ํ”„๋กœ๋•์…˜ ๋ธŒ๋กœ์ปค ์ž์‚ฐ ์ •๋ณธํ™” ๋ฐ `nats-docker` ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ** +- **๋‚ด์šฉ**: + 1. ์›๊ฒฉ Docker NATS ๋ฐฐํฌ ๊ฐ€์ด๋“œ ๋ฐ ์ž์‚ฐ(`docker-compose.yaml`, `nats.conf`, `.env.example`, `README.md`) ๊ตฌํ˜„. + 2. `nats-docker` ๋…๋ฆฝ Git ์ €์žฅ์†Œ ๋ฐ ์„œ๋ธŒ๋ชจ๋“ˆ(`.gitmodules`, `nats-docker/`) ๋ถ„๋ฆฌ ์™„๋ฃŒ (์ปค๋ฐ‹ `629a67f`, `12ba30b`, `916185c`). + 3. ๋ฐฐํฌ ์‹ ์„ ๋„ ๋ฐ ๋ณด์•ˆ ํšŒ๊ท€ ๊ฐ€๋“œ D-22 ~ D-30 9์ข… ๊ตฌ์ถ• (297 -> 306 tests 100% PASS ๋‹ฌ์„ฑ). + 4. ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ๋‚ด `_resolve_docker_dir()` ๋ฐ `_resolve_private_server_doc()` ๋™์  ๊ฒฝ๋กœ ํ•ด์„๊ธฐ ๋„์ž…. + ### **A-4 (โœ… ์™„๋ฃŒ โ€” P3-1): ์—์ด์ „ํŠธ ์ง€์‹ ์‚ฐ์žฌ โ€” `BaseAgentAdapter` ์–ด๋Œ‘ํ„ฐ ๊ณ„์ธต ๋„์ž… (Rev.2)** > ๊ฒฐํ•จ ์กฐ์น˜๊ฐ€ ์•„๋‹ˆ๋ผ **๊ตฌ์กฐ ๊ฐœ์„  ์ œ์•ˆ**์ž…๋‹ˆ๋‹ค. ์ƒ์„ธ ์„ค๊ณ„ยท์‹ค์ธก ๊ทผ๊ฑฐ๋Š” `.mam/jobs/44062a63/claude-reports/report-final.md` ๋ฐ `744ac67a` ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค. @@ -366,4 +412,4 @@ CHANGES_DIFF=$( ### 6.6 ๊ฒฐ๋ก  -`IMPROVEMENTS.md` ๋Š” ๋‚จ์€ ๋ฐฑ๋กœ๊ทธ ํ•ญ๋ชฉ(์•„ํ‚คํ…์ฒ˜ 1๊ฑด: `A-2`, ์—ฃ์ง€์ผ€์ด์Šค ๋ฐ ๊ฐ€์šฉ์„ฑ 3๊ฑด: `B-14`ยท`B-15`ยท`B-16`, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ 1๊ฑด: `O-5` โ€” ์ด 5๊ฑด)์„ ์œ„ ์šฐ์„ ์ˆœ์œ„(Track 0 โ†’ Track 1 โ†’ Track 2)์— ๋”ฐ๋ผ ์ผ์›ํ™”๋œ ๋ณด์™„ ๋กœ๋“œ๋งต์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. +`IMPROVEMENTS.md` ๋Š” ๋‚จ์€ ๋ฐฑ๋กœ๊ทธ ํ•ญ๋ชฉ(์•„ํ‚คํ…์ฒ˜ 1๊ฑด: `A-2`, ์—ฃ์ง€์ผ€์ด์Šค ๋ฐ ๊ฐ€์šฉ์„ฑ 3๊ฑด: `B-16`ยท`B-17`ยท`B-18`, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ 1๊ฑด: `O-5` โ€” ์ด 5๊ฑด)์„ ์œ„ ์šฐ์„ ์ˆœ์œ„(Track 0 โ†’ Track 1 โ†’ Track 2)์— ๋”ฐ๋ผ ์ผ์›ํ™”๋œ ๋ณด์™„ ๋กœ๋“œ๋งต์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. diff --git a/MESSAGING.md b/MESSAGING.md index 79c87ed..80ea109 100644 --- a/MESSAGING.md +++ b/MESSAGING.md @@ -23,13 +23,13 @@ In the initial development/testing phase, the system defaults to the public brok --- -### 1.2 Production Architecture (Secure Private Broker) -For production deployments, the system is designed to run on a private, self-hosted MQTT 5.0 broker such as **Mosquitto** or **EMQX**. +### 1.2 Production Architecture (Secure Private NATS Broker) +For production deployments, the system standardizes on a private, self-hosted **NATS server** (`nats:2.12-alpine`) with its built-in **MQTT 3.1.1** protocol engine and JetStream persistence enabled, managed via `nats-docker/docker/docker-compose.yaml`. ```mermaid graph TD - subgraph "Secure Corporate Network" - Broker["Private MQTT Broker (Mosquitto/EMQX)
Ports: 8883 (TLS)"] + subgraph "Secure Tailnet / Corporate Network" + Broker["Private NATS Broker (nats:2.12-alpine)
Native: 4222 | MQTT: 1883 | WS: 8080"] subgraph "Hermes (Delegator/Orchestrator)" SubClient["job_subscriber.py
(Role: subscriber)"] @@ -39,35 +39,55 @@ graph TD PubClient["publish_event.py
(Role: publisher)"] end - SubClient -- "Subscribe (QoS 1)
Auth: hermes
ACL: Read jobs/+/events" --> Broker - PubClient -- "Publish (QoS 1 + Retain Terminal)
Auth: claude-worker
ACL: Write jobs/+/events" --> Broker + SubClient -- "Subscribe (QoS 1)
Auth: mam_agent / mam_observer
ACL: Read python/mqtt/jobs/+/events" --> Broker + PubClient -- "Publish (QoS 1 + Retain Terminal)
Auth: mam_agent
ACL: Write python/mqtt/jobs/+/events" --> Broker end ``` #### Production Security & Hardening Controls: -1. **Transport Layer Security (TLS v1.3)**: Traffic is encrypted over port `8883` using a private Certification Authority (CA). The orchestrator validates the broker using `MQTT_CA_CERTS` (CA bundle path). Optionally, Mutual TLS (mTLS) is supported via client-side certificate keys (`MQTT_CERTFILE`/`MQTT_KEYFILE`) for cryptographic device identities. -2. **Strict Client Authentication**: All clients must supply credentials (`MQTT_USERNAME` / `MQTT_PASSWORD`) to establish a connection. Anonymous logins are explicitly disabled (`allow_anonymous false`). +1. **Transport Layer Security & Overlay Networks**: Within a trusted mesh (Tailscale / Tailnet, Model T), traffic routes over encrypted WireGuard overlays to private endpoints. For public WAN exposures (Model P), TLS v1.3 encryption is terminated via private CA certificates (`MQTT_CA_CERTS`), and mutual TLS (mTLS) is supported via client keypairs (`MQTT_CERTFILE` / `MQTT_KEYFILE`). +2. **Strict Client Authentication & Multi-Tenancy**: All clients authenticate against isolated NATS accounts (`MAM`, `HOME`, `SYS`) using dedicated credentials (`MQTT_USERNAME` / `MQTT_PASSWORD`). Anonymous access is explicitly disabled. 3. **Role-Based Topic Access Control Lists (ACLs)**: - * **Orchestrator/Hermes (Subscriber)**: Authenticates as user `hermes` with read-only access to all event streams: + * **Worker / Agent (`mam_agent`)**: Granted full publish/subscribe access within the `MAM` account to manage job lifecycles: ```conf - user hermes - topic read python/mqtt/jobs/+/events + # nats-docker/docker/nats.conf + accounts { + MAM: { + jetstream: enabled + users: [ + { user: mam_agent, password: $MAM_BROKER_PASS } + ] + } + } ``` - * **Agent/Worker (Publisher)**: Authenticates as user `claude-worker` with write-only access restricted to the job event sub-topics: + * **Observer / Dashboard (`mam_observer`)**: Restricted to read-only access for monitoring streams while strictly preventing unauthorized command injection: ```conf - user claude-worker - topic write python/mqtt/jobs/+/events + # nats-docker/docker/nats.conf + { user: mam_observer, password: $MAM_OBSERVER_PASS, + permissions: { + subscribe: { allow: ["python.mqtt.jobs.>"] } + publish: { deny: [">"] } + } + } ``` - This prevents workers from eavesdropping on sister agents or intercepting commands on other jobs. 4. **Durable Message Queues & Session State**: - * The broker is configured with `persistence true` and a dedicated disk storage path. - * Subscribers connect with persistent session flags to ensure the broker buffers QoS 1 messages during temporary network drops. -5. **Retained Terminal Events**: Terminal events (`completed`/`error`) are published with the `retain=True` flag. This allows a late-joining or recovering subscriber to instantly retrieve the final job status without waiting for active transmissions. + * JetStream is activated with a dedicated persistent store path (`store_dir: "/data"`), backing MQTT QoS 1 streams and persistent client sessions. +5. **Retained Terminal Events**: Terminal events (`completed` / `error`) are published with `retain=True`. NATS stores retained payloads in JetStream, allowing late-joining subscribers to instantly recover final states without polling. --- -### 1.3 Production Mosquitto Configuration Reference -A hardened `/etc/mosquitto/mosquitto.conf` production configuration includes: +### 1.3 NATS JetStream, Retained Messages & WebSocket Integration + +The production deployment in [`nats-docker/docker/nats.conf`](nats-docker/docker/nats.conf) includes key architectural primitives: +1. **JetStream Requirement for MQTT Engine**: `nats-server` requires JetStream enabled at both the server level and the account level (`jetstream: enabled`) for MQTT sessions and QoS 1 message persistence. +2. **Retained Message Scope Boundary (N-1)**: Retained messages published via MQTT are stored in JetStream by NATS and delivered to subsequent MQTT subscribers. Note that native NATS pub/sub subscribers do not receive historical retained messages upon connection unless queried via JetStream KV/Object APIs. +3. **MQTT-over-WebSocket `/mqtt` Path (N-7)**: For web dashboards and browser clients, NATS exposes WebSocket listeners on port `8080` (or `443` in TLS mode). Standard MQTT-over-WebSocket clients connect to the `/mqtt` path (e.g. `ws://:8080/mqtt` or `wss://:8443/mqtt`), with `no_tls: true` and `same_origin: false` configured for secure cross-origin streaming behind reverse proxies. +4. **Remote Deployment Models**: For full installation, Tailscale topology, and secret management guides, refer to [`nats-docker/PRIVATE_SERVER.md`](nats-docker/PRIVATE_SERVER.md) and [`nats-docker/NATS_REPORT.md`](nats-docker/NATS_REPORT.md). + +--- + +### 1.4 Alternative: Hardened Mosquitto Reference +If an environment requires a dedicated Mosquitto broker instead of NATS, a reference `/etc/mosquitto/mosquitto.conf` configuration is maintained: ```conf # Persistence settings persistence true @@ -229,8 +249,9 @@ Two concurrency control schemes co-exist in this workspace to coordinate state m --- ### 4.2 `publish_event.py` (Retries and Handshakes) -The publisher script enforces robust error handling when sending status updates: +The publisher script enforces robust error handling and fail-safe local persistence: * **Fresh Connection Pattern**: Instead of maintaining a persistent socket connection (which is susceptible to socket timeouts or channel leaks), `publish_event.py` opens a fresh socket, completes the authentication/TLS handshake, publishes a single QoS 1 event, waits for `PUBACK`, and closes the connection. +* **Guaranteed Disk Synchronization (B-14)**: Before attempting any network transmission over MQTT, `publish_event.py` records the event into the local registry (`append_event` and `update_job_status`). If the broker is unreachable or network publish fails, local audit logs and state machine files remain 100% accurate. The script returns exit code `2` at the very end to signal a transport failure without corrupting local state. * **Exponential Backoff**: Wrapped in the `with_retry()` decorator from `mqtt_common.py`. In case of socket errors (`OSError`, `TimeoutError`, `ConnectionError`), it retries up to 3 times (configurable via `--attempts`) with backoff: $$\text{delay} = \min(\text{base\_delay} \times \text{factor}^{\text{attempt}-1}, \text{max\_delay})$$ Default parameters: `base_delay = 0.5s`, `factor = 2.0`, `max_delay = 8.0s`. @@ -243,6 +264,12 @@ The publisher script enforces robust error handling when sending status updates: ### 4.3 `job_subscriber.py` (Timers and Queue Semantics) The subscriber acts as the central execution watchdog: * **Queue Serialization**: Uses a thread-safe `queue.Queue` internally. The Paho MQTT callback thread adds messages to the queue, and the main thread processes them sequentially. This separates network I/O from state machine validation. +* **Local Disk Fallback Verification (B-15)**: On initial startup and upon any broker connection failure, `_check_disk_fallback()` immediately queries local job records (`.mam/jobs/.json`) and audit logs (`status.json`). If the target job has already reached a terminal state locally, the subscriber completes immediately without waiting on a dead broker. +* **Infrastructure Error Code Separation (F-4)**: The subscriber returns distinct exit codes: + * Exit `0`: Job completed successfully. + * Exit `1`: Job terminated with an application `error` event. + * Exit `2`: Activity idle or wall-clock timeout exceeded. + * Exit `3`: Broker infrastructure connection error (with disk fallback checked). * **State Machine Protection**: To safeguard against QoS 1 duplicate delivery or out-of-order broker retries, the subscriber runs a terminal state machine. It records job completion in an internal `terminal` dictionary. Once a job is marked `completed` or `error`, any subsequent events for that `job_id` are ignored: ```python if event in TERMINAL_EVENTS: @@ -258,11 +285,32 @@ The subscriber acts as the central execution watchdog: --- -### 4.4 `mqtt_common.py` (Logging & Config Resolution) -* **Log Routing isolation**: Configured via `setup_logging()`. The root logger is bound to `sys.stderr`. This preserves the standard output stream (`stdout`) exclusively for clean JSON-lines payloads, enabling downstream bash tools to pipeline event feeds cleanly (e.g., `job_subscriber.py ... | jq`). -* **Broker Config Resolution**: Configured in `broker_config_from_job()`. Resolves credentials hierarchically: - 1. Defaults to environment configurations (e.g. `MQTT_BROKER`, `MQTT_PORT`, `MQTT_TLS`, `MQTT_CA_CERTS`). - 2. Overlays credentials specified inside the job record JSON block (`broker.*`). This allows the agent to fetch its dedicated target broker credentials on a per-job basis. +### 4.4 `mqtt_common.py` (Logging, Env Vars & Config Resolution) +* **Log Routing Isolation**: Configured via `setup_logging()`. The root logger is bound to `sys.stderr`. This preserves the standard output stream (`stdout`) exclusively for clean JSON-lines payloads, enabling downstream bash tools to pipeline event feeds cleanly (e.g., `job_subscriber.py ... | jq`). +* **Environment Variable Dictionary**: + The system parses and supports the following 10 configuration variables: + | Environment Variable | Default | Purpose | + |---|---|---| + | `MQTT_BROKER` | `broker.hivemq.com` | Broker hostname or IP address (e.g., `vm-ubuntu`, `127.0.0.1`) | + | `MQTT_PORT` | `1883` | Broker port (`1883` for plaintext/Tailscale, `8883` for TLS) | + | `MQTT_TLS` | `false` | Enable TLS encryption (`true` / `false` / `1` / `0`) | + | `MQTT_USERNAME` | `""` | Authentication username (e.g., `mam_agent`, `mam_observer`) | + | `MQTT_PASSWORD` | `""` | Authentication password | + | `MQTT_CA_CERTS` | `""` | Path to CA certificate bundle for TLS verification | + | `MQTT_CERTFILE` | `""` | Path to client certificate for mutual TLS (mTLS) | + | `MQTT_KEYFILE` | `""` | Path to client private key for mutual TLS (mTLS) | + | `MQTT_CLIENT_ID_PREFIX` | `hermes` | Prefix for dynamically generated random client IDs | + | `MQTT_KEEPALIVE` | `60` | MQTT keepalive ping interval in seconds | + +* **`.mam.env` Resolution Hierarchy (`_load_dotenv`)**: + Configuration files are resolved with strict precedence rules: + 1. **OS Environment Precedence**: Any variable already defined in `os.environ` is preserved and never overwritten by file-based configs. + 2. **Explicit Override (`MAM_ENV_FILE`)**: If `MAM_ENV_FILE` is set, only that specific file is parsed. If the specified file does not exist, an error is logged and ambient search is refused (preventing silent fallback to unintended parent configs). Connection attempts fail-closed (`RuntimeError`). + 3. **Workspace Root Auto-Discovery**: If `MAM_ENV_FILE` is not set, the resolver searches candidate paths in order: `MAM_REAL_ROOT`, `WORKSPACE_ROOT`, upward directory walk searching for `.agents` or `.git` boundary markers, and `os.getcwd()`. + 4. **Public Broker Security Alert (B-17)**: If the final resolved host falls back to the public sandbox `broker.hivemq.com`, a prominent security warning is emitted. +* **Broker Config Resolution (`broker_config_from_job`)**: + 1. Loads baseline settings from environment / `.mam.env`. + 2. Overlays job-specific overrides specified inside the job record JSON block (`broker.*`). --- @@ -311,8 +359,8 @@ graph LR The advisory locking system previously relied heavily on `fcntl.flock`. While `agent-sessions.yaml` has been migrated to SQLite WAL to solve concurrent writes, the job metadata in `.mam/jobs/` still relies on `fcntl.flock` which may behave non-atomically on NFS. 2. **Bearer Token Leakage over Plaintext (Public Broker)**: The `auth_token` mechanism is a simple plaintext bearer comparison. If the transport layer is unencrypted (e.g., using `broker.hivemq.com` on port `1883`), any eavesdropper on the network can steal the token and spoof legitimate events. -3. **Subscriber Network Drop Orphanage**: - `job_subscriber.py` does not implement automatic reconnection loops. If the subscriber loses connection to the broker, it exits, leaving the running herdr agent orphaned and without a validation/collection hook. +3. **Subscriber Network Drop & Disk Fallback (Resolved via B-15 / Residual Active Reconnection Gap)**: + `job_subscriber.py` implements on-disk status fallback (`_check_disk_fallback`) to recover state upon broker connection loss (B-15). An active in-session auto-reconnection loop during continuous execution remains a recommended enhancement. 4. **Lack of Ordering Guarantees in QoS 1**: QoS 1 guarantees delivery but not strict ordering. Under heavy backoff retries, a late-delivered progress event could land after a terminal event, causing state inconsistencies. @@ -325,8 +373,8 @@ graph LR **Architecture Decision Note**: This means `agent-sessions.yaml` is **no longer a real-time view** of currently `running` sessions. We have explicitly accepted the trade-off of giving up real-time text readability of running sessions in favor of robust concurrency and solving NFS flock limits. Tooling and status checks must now query the SQLite DB to observe live `running` states. 2. **Implement Signature-Based Payload Verification**: Rather than sending a plaintext token, utilize HMAC signatures. The delegator and worker share a secret key; the worker publishes a signature of the payload (e.g. `HMAC-SHA256(secret_key, payload_bytes)`). The subscriber validates the signature, preventing token interception. -3. **Enforce Mandatory Broker-Side TLS and ACLs**: - De-prioritize plaintext support. Enforce connection over port `8883` with verified TLS certificates. Implement client certificates (mTLS) for agent authentication. +3. **Enforce Mandatory NATS Broker-Side Authentication, JetStream and ACLs**: + Standardize on private `nats-server` with JetStream and account-level ACL isolation (`nats-docker/docker/nats.conf`). For public WAN exposures, terminate TLS v1.3 (`MQTT_TLS=true`) over port `8883`. 4. **Build Auto-Reconnecting Subscriber Loops**: Upgrade `job_subscriber.py` to handle disconnect callbacks. Maintain a persistent queue in memory and allow the client to reconnect with exponential backoff, preventing socket dropout from terminating the orchestration flow. diff --git a/deploy/gitea-ci.yml b/deploy/gitea-ci.yml index 40f0340..9314530 100644 --- a/deploy/gitea-ci.yml +++ b/deploy/gitea-ci.yml @@ -85,6 +85,8 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 + with: + submodules: recursive - name: Set up Python uses: actions/setup-python@v4 diff --git a/implementation_plan.md b/implementation_plan.md index 4c1f0f5..8e8f00e 100644 --- a/implementation_plan.md +++ b/implementation_plan.md @@ -1,26 +1,26 @@ # ๐Ÿš€ MAM ๋ฉ”์‹œ์ง• ๋ฐฑํ”Œ๋ ˆ์ธ ์ „ํ™˜ ์‹คํ–‰ ๋กœ๋“œ๋งต (`implementation_plan.md`) -- **๋ฌธ์„œ ๋ฒ„์ „**: v1.0.0 (`8c651798` / `28bb7340`) +- **๋ฌธ์„œ ๋ฒ„์ „**: v1.2.0 - **์ž‘์„ฑ/๊ด€๋ฆฌ ์ฃผ์ฒด**: Multi-Agent Orchestration Team (`claude`, `agy`, `cline`) -- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `a9934ad` (276/276 baseline tests passing) -- **๋ฌธ์„œ ๋ชฉ์ **: MAM์˜ ๋ฉ”์‹œ์ง• ์ธํ”„๋ผ๋ฅผ ๊ณต๊ฐœ HiveMQ ๋ธŒ๋กœ์ปค์—์„œ `nats-server` ์ „์šฉ ์‚ฌ์„ค ๋ธŒ๋กœ์ปค๋กœ ๋ฌด์ค‘๋‹จ ์ „ํ™˜ํ•˜๊ธฐ ์œ„ํ•œ 4๊ฐœ ํŠธ๋ž™(Track 0~3)๊ณผ 5๋‹จ๊ณ„ ๋งˆ์ผ์Šคํ†ค(M0~M4)์˜ ๊ตฌ์ฒด์  ์‹คํ–‰ ์ง€์นจ ๋ฐ ์ง„ํ–‰ ์ƒํ™ฉ ์ถ”์ . +- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `916185c` (306/306 baseline tests passing) +- **๋ฌธ์„œ ๋ชฉ์ **: MAM์˜ ๋ฉ”์‹œ์ง• ์ธํ”„๋ผ๋ฅผ ๊ณต๊ฐœ HiveMQ ๋ธŒ๋กœ์ปค์—์„œ `nats-server` ์ „์šฉ ์‚ฌ์„ค ๋ธŒ๋กœ์ปค๋กœ ๋ฌด์ค‘๋‹จ ์ „ํ™˜ํ•˜๊ธฐ ์œ„ํ•œ 5๊ฐœ ํŠธ๋ž™(Track 0~3, Track 1R)๊ณผ 6๋‹จ๊ณ„ ๋งˆ์ผ์Šคํ†ค(M0~M4, M2b)์˜ ๊ตฌ์ฒด์  ์‹คํ–‰ ์ง€์นจ ๋ฐ ์ง„ํ–‰ ์ƒํ™ฉ ์ถ”์ . - **์—ฐ๊ณ„ ๋ฌธ์„œ**: [`NATS_REPORT.md`](nats-docker/NATS_REPORT.md), [`PRIVATE_SERVER.md`](nats-docker/PRIVATE_SERVER.md), [`IMPROVEMENTS.md`](IMPROVEMENTS.md) --- -## 1. ๊ฐœ์š” ๋ฐ 4๊ฐœ ํŠธ๋ž™ ๊ตฌ์กฐ +## 1. ๊ฐœ์š” ๋ฐ 5๊ฐœ ํŠธ๋ž™ ๊ตฌ์กฐ ``` -[M0: ๋ฌธ์„œ ์ •ํ•ฉ์„ฑ] โ”€โ”€> [M1: ๋‚ด๊ฒฐํ•จ์„ฑ ํ™•๋ณด] โ”€โ”€> [M2: ๋ธŒ๋กœ์ปค ์‹ค์ฆ] โ”€โ”€> [M3: ๋ณด์•ˆ ์ข…๊ฒฐ] โ”€โ”€> [M4: ๋™๊ธฐํ™” ์™„๋ฃŒ] - (E-1~E-4 ๊ต์ •, (Track 0: B-14,B-15, (Track 1: O-5 (Track 2: A-2, (Track 3: ๋ฌธ์„œ, - G-D1~G-D4 ๊ฐ€๋“œ) G-1~G-10 ๊ฐ€๋“œ) S-1~S-9 ์ŠคํŒŒ์ดํฌ) ์ง€๋ฌธ ํ† ํ”ฝ, G-11) ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ) +[M0: ๋ฌธ์„œ ์ •ํ•ฉ์„ฑ] โ”€โ”€> [M1: ๋‚ด๊ฒฐํ•จ์„ฑ ํ™•๋ณด] โ”€โ”€> [M2a: ๋กœ์ปฌ์ŠคํŒŒ์ดํฌ / M2b: ์›๊ฒฉ๋ฐฐํฌ] โ”€โ”€> [M3: ๋ณด์•ˆ ์ข…๊ฒฐ] โ”€โ”€> [M4: ๋™๊ธฐํ™” ์™„๋ฃŒ] + (E-1~E-4 ๊ต์ •, (Track 0: B-14,B-15, (Track 1: O-5 ์ŠคํŒŒ์ดํฌ / (Track 2: A-2, (Track 3: ๋ฌธ์„œ, + G-D1~G-D4 ๊ฐ€๋“œ) G-1~G-10 ๊ฐ€๋“œ) Track 1R: ์›๊ฒฉ ์ž์‚ฐยท์„œ๋ธŒ๋ชจ๋“ˆ) ์ง€๋ฌธ ํ† ํ”ฝ, G-11) ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ) ``` | ํŠธ๋ž™ | ๋Œ€์ƒ ๊ณผ์ œ | ํ•ต์‹ฌ ๋ชฉํ‘œ | ์ฝ”๋“œ ๋ณ€๊ฒฝ ์ง€์  | |---|---|---|---| | **Track 0** | `B-14`, `B-15` (P1) | ๋ธŒ๋กœ์ปค ๋‹ค์šด ์‹œ 65๋ถ„ ์ •์ง€(Hang) ๋ฐ ์˜คํŒ์ • ๋ฐฉ์ง€ (๋กœ์ปฌ ๋””์Šคํฌ ๋‚ด๊ฒฐํ•จ์„ฑ) | `publish_event.py`, `job_subscriber.py`, `multi-agent-mux-delegate-job` | | **Track 1** | `O-5` (P2) | `nats-server` MQTT 3.1.1 ์–ด๋Œ‘ํ„ฐ ํ˜ธํ™˜์„ฑ ๋ฐ Retained ๋ฉ”์‹œ์ง€ ์‹ค์ธก ๊ฒ€์ฆ | ๊ฒฉ๋ฆฌ ํด๋ก  (`$SCRATCH/nats-spike`) | -| **Track 1R** | ์›๊ฒฉ ํ”„๋กœ๋•์…˜ (P1) | VPS/ํ™ˆ๋žฉ `nats-server` Docker ์ƒ์‹œ ๊ฐ€๋™ ๋ฐ MAM ์›๊ฒฉ ๋ฐฑํ”Œ๋ ˆ์ธ ์ „ํ™˜ | `PRIVATE_SERVER.md` ยง9, ์„œ๋ฒ„์ธก `docker-compose.yml`/`nats.conf`, `.mam.env` | +| **Track 1R** | ์›๊ฒฉ ํ”„๋กœ๋•์…˜ (P1) | VPS/ํ™ˆ๋žฉ `nats-server` Docker ์ƒ์‹œ ๊ฐ€๋™, ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ ๋ฐ MAM ์›๊ฒฉ ๋ฐฑํ”Œ๋ ˆ์ธ ์ „ํ™˜ | `nats-docker/PRIVATE_SERVER.md` ยง9, `nats-docker/docker/docker-compose.yaml`, `nats.conf`, `.mam.env` | | **Track 2** | `A-2`, `B-16` (P2) | ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ง€๋ฌธ ํ† ํ”ฝ ๊ฒฉ๋ฆฌ ๋ฐ `auth_token` ๋ฌด์กฐ๊ฑด ๋ฐœ๊ธ‰ ๊ฐ•์ œ | `mqtt_common.py`, `registry.py`, `reconcile.sh` | | **Track 3** | ๋ฌธ์„œ/์„ค์ • ๋™๊ธฐํ™” | ๊ณต์‹ ๊ฐ€์ด๋“œ, ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ, ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํ…œํ”Œ๋ฆฟ ์ผ์›ํ™” | `MESSAGING.md`, `IMPROVEMENTS.md`, `VERSIONS.md`, `deploy/*` | @@ -39,7 +39,7 @@ M0 (๋ฌธ์„œ ์ •ํ•ฉ์„ฑ) โ”€โ”€> M1 (Track 0 ๋‚ด๊ฒฐํ•จ์„ฑ) โ”€โ”€> M2a (๋กœ์ปฌ ์Šค | **M0** | ๋ฌธ์„œ ์ •ํ•ฉ์„ฑ ํ™•๋ณด | `PRIVATE_SERVER.md` E-1~E-4 ๊ต์ •, ๋‹ค๋Šฅ์„ฑ ์ ˆ ์ถ”๊ฐ€, ๋ณธ ๋กœ๋“œ๋งต ์ž‘์„ฑ | **G-D1 ~ G-D4 ๊ฐ€๋“œ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ** (276 -> 280) | | **M1** | ๋‚ด๊ฒฐํ•จ์„ฑ ํ™•๋ณด (Track 0) | `B-14`, `B-15` ์ฝ”๋“œ ํŒจ์น˜ ์™„๋ฃŒ | **G-1 ~ G-10 ๊ฐ€๋“œ ํ†ต๊ณผ + mutation ์ „๊ฑด FAIL ํ™•์ธ** (280 -> 290) | | **M2a** | ๋กœ์ปฌ ์ŠคํŒŒ์ดํฌ (Track 1) | ๊ฒฉ๋ฆฌ ํด๋ก ์—์„œ S-1 ~ S-9 ์ŠคํŒŒ์ดํฌ ์™„์ˆ˜ | **S-3(Retained Terminal Event) ํ†ต๊ณผ** (์‹คํŒจ ์‹œ mosquitto๋กœ ๋ถ„๊ธฐ) | -| **M2b** | ์›๊ฒฉ ํ”„๋กœ๋•์…˜ ์ „ํ™˜ (Track 1R) | D-1~D-5 ๊ต์ • + ยง9 ์›๊ฒฉ ๋ฐฐํฌ + ยง9.5 ์ „ํ™˜ | **R-3(๋…ธ์ถœ0) ยท R-5(retained) ยท R-6(์‹ ์›) ยท R-9(๊ณ„์ •๊ฒฉ๋ฆฌ) ๋™์‹œ ํ†ต๊ณผ** | +| **M2b** | ์›๊ฒฉ ํ”„๋กœ๋•์…˜ ์ „ํ™˜ (Track 1R) | D-1~D-5 ๊ต์ • + ยง9 ์›๊ฒฉ ๋ฐฐํฌ + ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ + ยง9.5 ์ „ํ™˜ | **R-3(๋…ธ์ถœ0) ยท R-5(retained) ยท R-6(์‹ ์›) ยท R-9(๊ณ„์ •๊ฒฉ๋ฆฌ) ๋™์‹œ ํ†ต๊ณผ** (290 -> 297 -> 306) | | **M3** | ๋ณด์•ˆ ์ข…๊ฒฐ (Track 2) | A-2 ์ง€๋ฌธ ํ† ํ”ฝ ์ „ํ™˜, G-11 ๋ฌด์กฐ๊ฑด ํ† ํฐ ๋ฐœ๊ธ‰ | ์ง€๋ฌธ ํ† ํ”ฝ ๋™์ž‘ ํ™•์ธ **ํ›„** legacy ๊ตฌ๋… ์ œ๊ฑฐ | | **M4** | ๋™๊ธฐํ™” ์™„๋ฃŒ (Track 3) | `MESSAGING.md`, `IMPROVEMENTS.md`, `VERSIONS.md`, `deploy/*` ์ •ํ•ฉ | ์ „์ฒด ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ 100% Green | @@ -111,6 +111,8 @@ M0 (๋ฌธ์„œ ์ •ํ•ฉ์„ฑ) โ”€โ”€> M1 (Track 0 ๋‚ด๊ฒฐํ•จ์„ฑ) โ”€โ”€> M2a (๋กœ์ปฌ ์Šค โ–ผ [P0.5 ์ž์‚ฐํ™”] docker/ 4๋Œ€ ์ž์‚ฐ ์ •๋ณธํ™” + D-22~D-30 ํšŒ๊ท€ ๊ฐ€๋“œ (297 -> 306) โ–ผ +[P0.6 ์„œ๋ธŒ๋ชจ๋“ˆ] nats-docker ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ + ๋™์  ๊ฒฝ๋กœ ํ•ด์„๊ธฐ + CI checkout ๋™๊ธฐํ™” + โ–ผ [P1 ์„œ๋ฒ„ ์ค€๋น„] VPS/ํ™ˆ๋žฉ ํ”„๋กœ๋น„์ €๋‹, Docker, Tailscale ๊ฐ€์ž… โ–ผ [P2 ๋ฐฐํฌ] nats.conf + compose ๊ธฐ๋™, healthcheck healthy ํ™•์ธ @@ -141,8 +143,8 @@ M0 (๋ฌธ์„œ ์ •ํ•ฉ์„ฑ) โ”€โ”€> M1 (Track 0 ๋‚ด๊ฒฐํ•จ์„ฑ) โ”€โ”€> M2a (๋กœ์ปฌ ์Šค | ๋Œ€์ƒ ํŒŒ์ผ | ๊ฐฑ์‹  ๋‚ด์šฉ | |---|---| -| [`MESSAGING.md`](MESSAGING.md) | ๋ธŒ๋กœ์ปค ํ‘œ์ค€์„ `nats-server`๋กœ ๊ฐฑ์‹ , F-1/C1 ํ•ด์†Œ ๊ธฐ๋ก, F-5 ์˜์† ์„ธ์…˜ ์„œ์ˆ  ์ •์ • | -| [`IMPROVEMENTS.md`](IMPROVEMENTS.md) | A-2 ์™„๋ฃŒ ์ „ํ™˜, B-14/B-15/B-16/O-5 ํ•ด๊ฒฐ ์ƒํƒœ ๊ฐฑ์‹ , B-17/B-18 ์‹ ์„ค ๋“ฑ๋ก | +| [`MESSAGING.md`](MESSAGING.md) | ๋ธŒ๋กœ์ปค ํ‘œ์ค€์„ `nats-server`๋กœ ๊ฐฑ์‹ , F-1/C1 ํ•ด์†Œ ๊ธฐ๋ก, F-5 ์˜์† ์„ธ์…˜ ์„œ์ˆ  ์ •์ •, 10๊ฐœ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋ฐ ํ•ด์„ ๊ณ„์ธต ๋ฌธ์„œํ™” | +| [`IMPROVEMENTS.md`](IMPROVEMENTS.md) | B-14/B-15 ์™„๋ฃŒ ์ƒํƒœ ๋ฐ˜์˜, O-6 ์‹ ์„ค, B-17/B-18 ์‹ ์„ค ๋“ฑ๋ก | | [`VERSIONS.md`](VERSIONS.md) | `v2.0.0` ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ์— ๋ฉ”์‹œ์ง• ๋ฐฑํ”Œ๋ ˆ์ธ ๊ณ ๋„ํ™” ๋ฐ ๋‚ด๊ฒฐํ•จ์„ฑ ํŒจ์น˜ ๊ธฐ๋ก | | [`PRIVATE_SERVER.md`](nats-docker/PRIVATE_SERVER.md) | ์ŠคํŒŒ์ดํฌ ๊ฒฐ๊ณผ ๋ฐ˜์˜ ๋ฐ ์ตœ์ข… ๊ฐ€์ด๋“œ ํ™•์ • | | [`deploy/install.sh`](deploy/install.sh) | `requirements.txt` ํ™•์ธ (paho ์œ ์ง€) ๋ฐ ๊ฐœ์ธ ๋ธŒ๋กœ์ปค ์•ˆ๋‚ด ์ถ”๊ฐ€ | @@ -169,7 +171,7 @@ M0 (๋ฌธ์„œ ์ •ํ•ฉ์„ฑ) โ”€โ”€> M1 (Track 0 ๋‚ด๊ฒฐํ•จ์„ฑ) โ”€โ”€> M2a (๋กœ์ปฌ ์Šค - [ ] S-3 Retained ๋ฉ”์‹œ์ง€ ๊ฒŒ์ดํŠธ ํ†ต๊ณผ ํ™•์ธ ### M2b: Track 1R ์›๊ฒฉ ํ”„๋กœ๋•์…˜ ์ „ํ™˜ -- [x] D-1 `store_dir` ์ ˆ๋Œ€๊ฒฝ๋กœ ๊ต์ • + ๋น„์ธ์šฉ heredoc (`PRIVATE_SERVER.md:73`, `:146`) +- [x] D-1 `store_dir` ์ ˆ๋Œ€๊ฒฝ๋กœ ๊ต์ • + ๋น„์ธ์šฉ heredoc (`PRIVATE_SERVER.md` ยง4.1, ยง9.1) - [x] D-2 ์ด๋ฏธ์ง€ ํ•€ `nats:2.12-alpine` + `/healthz` healthcheck - [x] D-3 8222/8080 ๋ฐ”์ธ๋“œ ์ฃผ์†Œ ํ•œ์ • - [x] D-4 TLS ์ ˆ์˜ IP ์˜ˆ์‹œ ์ œ๊ฑฐ ๋ฐ DNS/SAN ์š”๊ฑด ๋ช…์‹œ @@ -177,8 +179,11 @@ M0 (๋ฌธ์„œ ์ •ํ•ฉ์„ฑ) โ”€โ”€> M1 (Track 0 ๋‚ด๊ฒฐํ•จ์„ฑ) โ”€โ”€> M2a (๋กœ์ปฌ ์Šค - [x] N-1 ยง5.2 ์— retained=MQTT ์ „์šฉ ๊ฒฝ๊ณ„ ๋ช…๋ฌธํ™” + ยง9.1 `mam_observer` ์ถ”๊ฐ€ - [x] ์‹ ๊ทœ ๊ฐ€๋“œ G-D5 ~ G-D9, G-R1, G-R2 ๊ตฌํ˜„ ๋ฐ ๊ฒ€์ฆ (290 -> 297) - [x] P0.5: `docker/` ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ž์‚ฐ ์ •๋ณธํ™” ๋ฐ D-22 ~ D-30 ํšŒ๊ท€ ๊ฐ€๋“œ (297 -> 306) +- [x] P0.6: `docker/` ์ž์‚ฐ์˜ `nats-docker` ์„œ๋ธŒ๋ชจ๋“ˆ ๋ถ„๋ฆฌ ๋ฐ `PRIVATE_SERVER.md`/`NATS_REPORT.md` ์ด์ „ (`629a67f`, `12ba30b`, `916185c`) +- [x] P0.6: ํ…Œ์ŠคํŠธ ๋™์  ๊ฒฝ๋กœ ํ•ด์„๊ธฐ(`_resolve_private_server_doc` / `_resolve_docker_dir`) ๋„์ž… +- [x] P0.6: CI checkout ์— `submodules: recursive` ์ ์šฉ (`deploy/gitea-ci.yml`) - [ ] ์„œ๋ฒ„ ๋ฐฐํฌ ๋ฐ R-3 ๋…ธ์ถœ ๋ฉด์  0 ๋‹จ์–ธ -- [ ] ยง9.5 ๋“œ๋ ˆ์ธยท์ž”์—ฌ ์Šค์บ” ํ›„ `.mam.env` ์ „ํ™˜ +- [x] ยง9.5 ์‚ฌ์„ค ๋ธŒ๋กœ์ปค(`vm-ubuntu`)๋กœ `.mam.env` ์ „ํ™˜ ์™„๋ฃŒ (์‚ฌํ›„ ์ž”์—ฌ ๋“œ๋ ˆ์ธ ์Šค์บ” ๊ณผ์ œ ๊ธฐ๋ก) - [ ] R-1 ~ R-13 ์ „๊ฑด ํ†ต๊ณผ (R-5 / R-9 / R-13 ์ตœ์ข… ๊ด€๋ฌธ) ### M3: Track 2 ๋ณด์•ˆ ๋ฐ ํ† ํ”ฝ ๊ฒฉ๋ฆฌ (`A-2`, `B-16`) diff --git a/tests/test_deploy_freshness.py b/tests/test_deploy_freshness.py index c5aceee..52a1218 100644 --- a/tests/test_deploy_freshness.py +++ b/tests/test_deploy_freshness.py @@ -17,6 +17,7 @@ import sys import tempfile import pytest +import yaml REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) @@ -722,4 +723,89 @@ def test_d30_websocket_origin_policy_is_startable(): assert "/mqtt" in full_conf, "nats.conf must document /mqtt WebSocket MQTT path (N-7)" +# -------------------------------------------------------------------------- +# D-31 โ€” Gitea CI checkout enables submodules for test jobs +# -------------------------------------------------------------------------- +def test_d31_gitea_ci_submodules_in_test_job(): + gitmodules_path = os.path.join(REPO_ROOT, ".gitmodules") + if not os.path.exists(gitmodules_path): + return # Auto-disable when no submodules are configured + + ci_path = os.path.join(REPO_ROOT, "deploy", "gitea-ci.yml") + assert os.path.exists(ci_path), f"Gitea CI workflow missing at {ci_path}" + + with open(ci_path, "r", encoding="utf-8") as f: + ci_data = yaml.safe_load(f) + + jobs = ci_data.get("jobs", {}) + assert jobs, f"No jobs defined in {ci_path}" + + test_jobs_found = 0 + for job_name, job_data in jobs.items(): + if not isinstance(job_data, dict): + continue + steps = job_data.get("steps", []) + + # Determine if this job runs pytest or test suites + is_test_job = False + for step in steps: + if not isinstance(step, dict): + continue + run_cmd = step.get("run", "") + if "pytest" in run_cmd or "tests/" in run_cmd: + is_test_job = True + break + + if is_test_job: + test_jobs_found += 1 + checkout_steps = [ + s for s in steps + if isinstance(s, dict) and "actions/checkout" in str(s.get("uses", "")) + ] + assert checkout_steps, f"Test job '{job_name}' has no actions/checkout step" + for s in checkout_steps: + with_opts = s.get("with", {}) or {} + submodules_val = with_opts.get("submodules") + assert submodules_val, ( + f"Job '{job_name}' checkout step must enable submodules (e.g. submodules: recursive) " + f"to prevent test_deploy_freshness failures in CI, got: {submodules_val}" + ) + + assert test_jobs_found > 0, ( + "Anti-void assertion: expected at least 1 test execution job running pytest in deploy/gitea-ci.yml" + ) + + +# -------------------------------------------------------------------------- +# D-32 โ€” MESSAGING.md documents all supported MQTT environment variables +# -------------------------------------------------------------------------- +def test_d32_messaging_doc_covers_all_mqtt_env_vars(): + messaging_path = os.path.join(REPO_ROOT, "MESSAGING.md") + assert os.path.exists(messaging_path), f"MESSAGING.md missing at {messaging_path}" + + with open(messaging_path, "r", encoding="utf-8") as f: + content = f.read() + + doc_vars = set(re.findall(r'\b(MQTT_[A-Z0-9_]+)\b', content)) + assert doc_vars, "No MQTT_* variables found in MESSAGING.md" + + expected_vars = { + "MQTT_BROKER", + "MQTT_PORT", + "MQTT_TLS", + "MQTT_USERNAME", + "MQTT_PASSWORD", + "MQTT_CA_CERTS", + "MQTT_CERTFILE", + "MQTT_KEYFILE", + "MQTT_CLIENT_ID_PREFIX", + "MQTT_KEEPALIVE", + } + + missing_vars = expected_vars - doc_vars + assert not missing_vars, ( + f"MESSAGING.md is missing documentation for supported MQTT variables: {missing_vars}" + ) + +