13 KiB
Cross-Code Review Report — Job 93a74271
Job ID: 93a74271
Reviewer: cline
Date: 2026-08-23
Scope: Track 1R — Docker deployment assets for nats-server on a remote server
Changeset: 5 new files (docker/ directory + requirements.txt) + 3 modified files (PRIVATE_SERVER.md, implementation_plan.md, tests/test_deploy_freshness.py)
1. Executive Summary
This review covers the creation of production-ready Docker deployment assets in the docker/ directory (docker-compose.yaml, nats.conf, .env.example, README.md) and the addition of 9 regression guards (D-22 ~ D-30) in tests/test_deploy_freshness.py, along with documentation updates to PRIVATE_SERVER.md and implementation_plan.md.
Verdict: PASS. All 5 task objectives are met. The Docker assets are correct, internally consistent, and match the canonical documentation. All 306 tests collect; the 29 deploy-freshness guards (including 9 new) pass, and the fast subset (sanity + tier1_unit, 47 tests) shows no regressions. Findings are limited to documentation consistency issues that do not affect the functionality or security of the deployment assets.
2. Task Objective Verification
2.1 docker/docker-compose.yaml — PASS
| Requirement | Status | Evidence |
|---|---|---|
nats:2.12-alpine service |
PASS | Line 9: image: nats:2.12-alpine |
| MQTT 1883 | PASS | Line 22: "${MQTT_BIND:-127.0.0.1}:1883:1883" |
| NATS 4222 | PASS | Line 23: "${NATS_BIND:-127.0.0.1}:4222:4222" |
| WS 8080 | PASS | Line 25: "${WS_BIND:-127.0.0.1}:8080:8080" |
| HTTP monitor 8222 (loopback only) | PASS | Line 24: "127.0.0.1:8222:8222" (hardcoded) |
JetStream volume /data |
PASS | Line 28: nats-data:/data |
| Healthcheck | PASS | Lines 29-34: wget to /healthz on 127.0.0.1:8222 |
| Fail-closed secrets | PASS | Lines 15-18: ${VAR:?error} syntax for all 4 secrets |
| Log rotation | PASS | Lines 35-37: json-file, 10m max-size, 3 max-file |
Note: The previous review (job e1c4e9c3) flagged M-1 (Medium): compose omitted NATS 4222 port. This is now fixed — ${NATS_BIND:-127.0.0.1}:4222:4222 is present in both docker/docker-compose.yaml and the PRIVATE_SERVER.md code fence.
2.2 docker/nats.conf — PASS
| Requirement | Status | Evidence |
|---|---|---|
| MQTT block | PASS | Lines 23-27: port: 1883, ack_wait: 60s, max_ack_pending: 1024 |
| JetStream block | PASS | Lines 15-19: store_dir: "/data", max_file: 10G, max_mem: 256M |
| Multi-tenant accounts (MAM with mam/observer) | PASS | Lines 55-70: MAM account with mam_agent + mam_observer (sub-only, pub denied) |
| HOME account | PASS | Line 72: HOME: { jetstream: enabled, users: [...] } |
| SYS account | PASS | Line 73: SYS: { users: [...] } |
system_account: SYS |
PASS | Line 75 |
| WebSocket block | PASS | Lines 29-52: port: 8080, no_tls: true, origin policy, /mqtt path docs |
| No hardcoded secrets | PASS | All passwords are $VAR references; D-25 guard verifies |
2.3 docker/.env.example — PASS
| Requirement | Status | Evidence |
|---|---|---|
| Fail-closed security | PASS | All 4 secrets have empty values (lines 22, 25, 28, 31) |
| Variable definitions | PASS | Each secret has a comment explaining purpose and generation method |
| Bind address variables | PASS | Lines 37-47: MQTT_BIND, NATS_BIND, WS_BIND (commented, default 127.0.0.1) |
| 8222 not variable-ized | PASS | Line 47: explicit note that HTTP monitor is loopback-fixed |
| Git tracking | PASS | .gitignore line 23 !.env.example exempts it; D-29 guard verifies |
2.4 docker/README.md — PASS (with findings — see section 3)
| Requirement | Status | Evidence |
|---|---|---|
| Step-by-step deployment | PASS | Section 3: 5-step quick deploy guide |
| Verification instructions | PASS | Section 5: R-3 port scan; Section 7: R-1~R-10 playbook |
| Network/firewall guidance | PASS | Section 4: UFW rules with Docker bypass warning |
| Client connection guide | PASS | Section 6: MAM .mam.env config + MQTT.js dashboard recipe |
| Operations/maintenance | PASS | Section 8: logs, backup, upgrade, monitoring |
| Troubleshooting | PASS | Section 9: 10-row troubleshooting table |
2.5 tests/test_deploy_freshness.py — PASS
9 new guards added (D-22 ~ D-30), all passing:
| Guard | What it checks | Result |
|---|---|---|
| D-22 | docker/ assets exist and are populated | PASS |
| D-23 | compose image matches doc and is alpine | PASS |
| D-24 | compose port exposure contract (4 ports, 8222 loopback) | PASS |
| D-25 | secrets are fail-closed (${VAR:?} syntax, empty .env.example, $VAR in nats.conf) |
PASS |
| D-26 | nats.conf jetstream/mqtt contract (store_dir /data, max_file/max_mem, mqtt 1883, MAM jetstream) | PASS |
| D-27 | observer permissions match mqtt_common.DEFAULT_TOPIC_ROOT (python.mqtt.jobs) |
PASS |
| D-28 | healthcheck contract (wget, /healthz, 127.0.0.1:8222) and alpine coupling | PASS |
| D-29 | env secrets never tracked (.env ignored, .env.example not ignored) |
PASS |
| D-30 | websocket origin policy startable (no_tls: true, no *, /mqtt documented) |
PASS |
D-16 guard change: Assertion tightened from "-alpine" in tag or tag.startswith("2.") to "alpine" in tag. Correct — healthcheck requires wget only in alpine. All nats: references in PRIVATE_SERVER.md use nats:2.12-alpine; no breakage.
Test count: 306 collected (was 297), matching implementation_plan.md claim "297 to 306".
3. Findings
M-1 (Medium) — R-1R-10 ID collision between PRIVATE_SERVER.md section 9.4 and docker/README.md section 7; R-11R-13 undefined
Category: Documentation consistency / Loss
The R-1~R-10 verification playbook IDs have different meanings in PRIVATE_SERVER.md section 9.4 and docker/README.md section 7. Key collisions:
| R-ID | PRIVATE_SERVER.md section 9.4 | docker/README.md section 7 |
|---|---|---|
| R-2 | Listener + TLS identity | External monitoring blocked |
| R-4 | Round-trip pub/sub + JetStream | WAN latency |
| R-5 | Retained terminal event (MQTT) | Auth rejection (unauthorized) |
| R-6 | Broker identity assertion | Auth success (normal) |
| R-7 | Freeze regression (H-1/H-4) | Retained event delivery |
| R-8 | Full regression suite | Broker identity verification |
Only R-1 (health), R-3 (port exposure), R-9 (tenant isolation), and R-10 (retained boundary) share the same concept.
Additionally, implementation_plan.md (lines 122, 182) references "R-1 ~ R-13" with "R-5(retained) / R-9(account boundary) / R-13(MQTT-over-WS)" as final gates. However:
- R-11, R-12, R-13 are never defined in either
PRIVATE_SERVER.mdsection 9.4 ordocker/README.mdsection 7. - The "R-5(retained)" reference matches
PRIVATE_SERVER.md's R-5, but notdocker/README.md's R-5 (auth rejection). PRIVATE_SERVER.mdsection 9.5 cutover procedure still says "R-1 ~ R-10" (not R-1~R-13).
Impact: An operator following docker/README.md who is told to verify "R-5(retained)" would check auth rejection instead of retained event delivery. The undefined R-11~R-13 create ambiguity about what constitutes the final acceptance gate.
Fix: Either (a) align the README's R-IDs with PRIVATE_SERVER.md section 9.4 (use different ID ranges like RR-1RR-10 for the README's deployment-focused checks), or (b) define R-11R-13 in both documents and update section 9.5 to reference R-1~R-13.
L-1 (Low) — docker/README.md section 7 R-4 references non-existent latency_check.py
Category: Operability / Loss
docker/README.md section 7 R-4 states: python latency_check.py with expected result "RTT P95 < 150ms". No such file exists in the repository. PRIVATE_SERVER.md section 9.4 defines the latency probe as an inline Python heredoc (not a standalone script). An operator following the README would get a "file not found" error.
Fix: Either (a) replace python latency_check.py with the inline heredoc from PRIVATE_SERVER.md section 9.4, or (b) create docker/latency_check.py as a standalone script, or (c) reference the PRIVATE_SERVER.md section 9.4 latency probe section.
L-2 (Low) — docker/README.md section 4 UFW rules more permissive than PRIVATE_SERVER.md section 9.3
Category: Documentation consistency
docker/README.md section 4 uses sudo ufw allow in on tailscale0 to any (allows all ports on the tailnet interface), while PRIVATE_SERVER.md section 9.3 uses granular per-port rules (port 1883, port 4222, port 8080). Both are valid for a trusted tailnet, but the README's approach is less defense-in-depth. The README also omits the 4222 UFW rule that PRIVATE_SERVER.md section 9.3 includes.
Fix: Align the README's UFW rules with PRIVATE_SERVER.md section 9.3's per-port approach, or add a note explaining the intentional difference.
V-1 (Very Low) — implementation_plan.md P0.5 step indentation
Category: Cosmetic
The new P0.5 step in the roadmap diagram uses slightly different indentation alignment than the surrounding steps. Purely cosmetic; does not affect readability of the plan.
4. Cross-Reference Verification
4.1 docker/ files vs PRIVATE_SERVER.md code fences
| File | Method | Result |
|---|---|---|
docker/nats.conf vs PRIVATE_SERVER.md section 9.1 code fence |
Programmatic byte-level comparison | EXACT MATCH |
docker/docker-compose.yaml vs PRIVATE_SERVER.md section 9.2 code fence |
Programmatic byte-level comparison | EXACT MATCH |
The D-22~D-30 guards provide structural verification but not a full byte-level diff. The manual programmatic comparison confirms zero drift between the canonical files and the documentation code fences.
4.2 Topic root consistency
mqtt_common.DEFAULT_TOPIC_ROOT = "python/mqtt/jobs" -> dotted form = "python.mqtt.jobs".
docker/nats.conf observer subscribe: { allow: ["python.mqtt.jobs.>"] } — matches. D-27 guard verifies this programmatically.
4.3 .gitignore verification
docker/.env-> ignored by.gitignoreline 21 (.envpattern). D-29 guard passes.docker/.env.example-> not ignored (.gitignoreline 23!.env.exampleoverrides line 22.env.*). D-29 guard passes..env.examplelines 4-5 reference.gitignore:23and.gitignore:21— line numbers verified correct.
4.4 Previous review findings (job e1c4e9c3) — resolution status
| Previous Finding | Status | Evidence |
|---|---|---|
| M-1: section 9.2 compose omits NATS 4222 port | FIXED | Both docker/docker-compose.yaml and PRIVATE_SERVER.md section 9.2 include ${NATS_BIND:-127.0.0.1}:4222:4222 |
| V-2: Unchecked M2b guard checkbox | FIXED | implementation_plan.md line 180: - [x] for guards G-D5~G-D9, G-R1, G-R2 (290 to 297) |
| L-1/L-2 (lib.sh latency, handle_startup_dialogs timeout) | Out of scope | Not part of this changeset |
| L-3 (D-19 regex scans full markdown) | Still present | D-19 unchanged; not part of this changeset |
5. Test Results
| Suite | Tests | Result |
|---|---|---|
tests/test_deploy_freshness.py (full) |
29 | 29 passed (12.16s) |
tests/test_sanity.py + tests/test_tier1_unit.py |
47 | 47 passed (16.99s) |
| Full collection | 306 | 306 collected (0.04s) — matches implementation_plan.md "297 to 306" |
Full suite (tests/) |
306 | Not completed (integration/e2e tests with MQTT exceed 30s timeout; not affected by this changeset) |
No regressions detected in the fast subset. The D-16 guard tightening is validated by all 29 deploy-freshness tests passing.
6. Security Review
| Check | Status |
|---|---|
| No hardcoded secrets in any file | PASS — All passwords are $VAR references; D-25 guard verifies |
| Fail-closed on missing secrets | PASS — ${VAR:?error} compose syntax; empty .env.example values |
| 8222 (HTTP monitor) loopback-only | PASS — Hardcoded 127.0.0.1:8222:8222, not variable-ized |
| Default bind addresses are loopback | PASS — ${MQTT_BIND:-127.0.0.1}, ${NATS_BIND:-127.0.0.1}, ${WS_BIND:-127.0.0.1} |
.env never tracked in git |
PASS — .gitignore + D-29 guard |
WebSocket no_tls: true explicit |
PASS — Prevents startup failure from implicit TLS requirement |
No * in allowed_origins |
PASS — D-30 guard verifies; comment explains NATS rejects * |
| Observer publish denied | PASS — publish: { deny: [">"] } in nats.conf; D-27 guard verifies |
7. Conclusion
The implementation fully satisfies all 5 task objectives. The Docker deployment assets are production-ready, internally consistent, and match the canonical documentation byte-for-byte. The 9 new regression guards (D-22~D-30) provide comprehensive structural verification of the deployment contract. The previous review's M-1 finding (missing 4222 port) is resolved.
The findings (1 Medium, 2 Low, 1 Very Low) are all documentation consistency issues that do not affect the functionality or security of the deployment assets. They can be addressed with minor documentation edits without rework.
[VERDICT: PASS]