Add implementation plan for debug-mode hotfix
Bite-sized TDD-style plan: failing helper test, two implementation tasks (web then api), compose plumbing, doc updates, integration verification. Uses unittest stdlib so no new deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
97dbb79977
commit
40c3a76c13
621
docs/superpowers/plans/2026-05-02-debug-mode-hotfix.md
Normal file
621
docs/superpowers/plans/2026-05-02-debug-mode-hotfix.md
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
# Debug-Mode Hotfix Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Replace hardcoded `debug=True` in both Flask entrypoints with an env-driven `CM_DEBUG` toggle so the Werkzeug debugger is off by default in rex/siong containers and only enabled when an operator opts in.
|
||||||
|
|
||||||
|
**Architecture:** Add a small `_debug_enabled()` helper to `app/cm_api.py` and `app/cm_web_view.py` that reads `CM_DEBUG` from the environment and accepts `1`/`true`/`yes` (case-insensitive) as truthy. Wire `CM_DEBUG: ${CM_DEBUG:-false}` into the Flask service `environment:` blocks in `docker-compose.yml`. Document the variable in `.env.example` and `AGENTS.md`. Gate against regressions with a `unittest`-based test that exercises both copies of the helper.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.9 (containers), Python 3.12 (local venv), Flask 2.3.3, Docker Compose v2, `unittest` (stdlib — no new dependency).
|
||||||
|
|
||||||
|
**Spec:** [docs/superpowers/specs/2026-05-02-debug-mode-hotfix-design.md](../specs/2026-05-02-debug-mode-hotfix-design.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Map
|
||||||
|
|
||||||
|
| File | Operation | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `tests/__init__.py` | Create | Make `tests/` a package so the test runner can find it. |
|
||||||
|
| `tests/test_debug_enabled.py` | Create | unittest-based regression tests for the `_debug_enabled` helper in both Flask modules; parametrized so the two copies can't drift. |
|
||||||
|
| `app/cm_web_view.py` | Modify | Add `_debug_enabled()`; replace `debug=True` in `app.run(...)` at line 748. |
|
||||||
|
| `app/cm_api.py` | Modify | Add `import os`; add `_debug_enabled()`; change `run()` default to `debug=None` with env resolution. |
|
||||||
|
| `docker-compose.yml` | Modify | Plumb `CM_DEBUG: ${CM_DEBUG:-false}` into `api-server` and `web-view` env blocks. |
|
||||||
|
| `.env.example` | Modify | New `Runtime` section documenting `CM_DEBUG`. |
|
||||||
|
| `AGENTS.md` | Modify | One-line note about `CM_DEBUG` under Security & Configuration Tips. |
|
||||||
|
|
||||||
|
The `_debug_enabled` helper is intentionally duplicated rather than placed in a shared module — only two call sites, the parser is six lines, and `app/__init__.py` is just a package marker. The test file enforces parity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Test infrastructure + first failing test for `cm_web_view._debug_enabled`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tests/__init__.py`
|
||||||
|
- Create: `tests/test_debug_enabled.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the test package marker**
|
||||||
|
|
||||||
|
Create `tests/__init__.py` empty:
|
||||||
|
|
||||||
|
```python
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write the failing test**
|
||||||
|
|
||||||
|
Create `tests/test_debug_enabled.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""Regression tests for the _debug_enabled helper.
|
||||||
|
|
||||||
|
Both app.cm_api and app.cm_web_view define a private _debug_enabled()
|
||||||
|
function that parses the CM_DEBUG environment variable. They are
|
||||||
|
intentionally duplicated (only two call sites; no shared utility module
|
||||||
|
exists). This test runs the same parametrized cases against every copy
|
||||||
|
to catch drift if one is updated without the other.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
# Import the modules at top-level (before any mock.patch.dict with
|
||||||
|
# clear=True), so module-load-time os.getenv() reads see the real
|
||||||
|
# environment. The patches inside individual tests then only affect the
|
||||||
|
# helper's runtime read of CM_DEBUG.
|
||||||
|
import app.cm_api
|
||||||
|
import app.cm_web_view
|
||||||
|
|
||||||
|
|
||||||
|
# Modules expected to expose a private _debug_enabled() helper.
|
||||||
|
# Add new entries here if more Flask entrypoints adopt the same toggle.
|
||||||
|
HELPER_MODULES = (
|
||||||
|
app.cm_web_view,
|
||||||
|
app.cm_api,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# (env_value, expected_result). env_value=None means CM_DEBUG is unset.
|
||||||
|
CASES = (
|
||||||
|
(None, False),
|
||||||
|
("", False),
|
||||||
|
("false", False),
|
||||||
|
("False", False),
|
||||||
|
("FALSE", False),
|
||||||
|
("0", False),
|
||||||
|
("no", False),
|
||||||
|
("anything-else", False),
|
||||||
|
("true", True),
|
||||||
|
("True", True),
|
||||||
|
("TRUE", True),
|
||||||
|
("1", True),
|
||||||
|
("yes", True),
|
||||||
|
("YES", True),
|
||||||
|
(" true ", True), # whitespace tolerated
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DebugEnabledTests(unittest.TestCase):
|
||||||
|
def _resolve(self, module):
|
||||||
|
return getattr(module, "_debug_enabled", None)
|
||||||
|
|
||||||
|
def test_helper_exists_on_every_module(self):
|
||||||
|
for module in HELPER_MODULES:
|
||||||
|
with self.subTest(module=module.__name__):
|
||||||
|
helper = self._resolve(module)
|
||||||
|
self.assertTrue(
|
||||||
|
callable(helper),
|
||||||
|
f"{module.__name__}._debug_enabled must be callable",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parses_cm_debug_consistently(self):
|
||||||
|
for module in HELPER_MODULES:
|
||||||
|
helper = self._resolve(module)
|
||||||
|
if helper is None:
|
||||||
|
self.fail(
|
||||||
|
f"{module.__name__}._debug_enabled is missing — "
|
||||||
|
"make test_helper_exists_on_every_module pass first"
|
||||||
|
)
|
||||||
|
for env_value, expected in CASES:
|
||||||
|
with self.subTest(module=module.__name__, env=env_value):
|
||||||
|
env = {} if env_value is None else {"CM_DEBUG": env_value}
|
||||||
|
with mock.patch.dict(os.environ, env, clear=True):
|
||||||
|
self.assertEqual(
|
||||||
|
helper(),
|
||||||
|
expected,
|
||||||
|
f"{module.__name__}._debug_enabled() should be "
|
||||||
|
f"{expected!r} for CM_DEBUG={env_value!r}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run the test to verify it fails**
|
||||||
|
|
||||||
|
Run from repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
.venv/bin/python -m unittest tests.test_debug_enabled -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: import error or `AttributeError: module 'app.cm_web_view' has no attribute '_debug_enabled'`. The failure mode may be that importing `app.cm_web_view` itself fails because Flask is needed — that is fine, see Task 2 for the venv prerequisite.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Install runtime deps into the venv if Flask import fails**
|
||||||
|
|
||||||
|
Only if the previous step failed at import time with `ModuleNotFoundError: No module named 'flask'`, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/yiekheng/projects/cm_bot_v2/.venv/bin/pip install -r /home/yiekheng/projects/cm_bot_v2/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Then re-run Step 3. Expected after install: the test fails with the missing `_debug_enabled` attribute, not an import error.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit the failing test**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
git add tests/__init__.py tests/test_debug_enabled.py && \
|
||||||
|
git -c user.name='yiekheng' -c user.email='yiekheng@04080616.xyz' \
|
||||||
|
commit -m "test: add CM_DEBUG helper parity test (failing)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Implement `_debug_enabled` in `cm_web_view.py` and flip `app.run`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/cm_web_view.py` (top of file — verify `import os` already present at line 10; bottom of file — `__main__` block currently at lines 744-748)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Verify `os` is already imported**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -n "^import os" /home/yiekheng/projects/cm_bot_v2/app/cm_web_view.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `10:import os`. If absent, add `import os` to the imports section. Do not add `from os import getenv` — `os.getenv` is what the helper uses.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add the helper near the top of the module**
|
||||||
|
|
||||||
|
Insert after the imports section (before `app = Flask(...)`). Find the first blank line after the imports and insert:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _debug_enabled() -> bool:
|
||||||
|
"""Return True iff CM_DEBUG env var is set to a truthy value.
|
||||||
|
|
||||||
|
Truthy: '1', 'true', 'yes' (case-insensitive, whitespace-trimmed).
|
||||||
|
Anything else, including unset, is False. Default-off so the
|
||||||
|
Werkzeug debugger is never reachable in production containers.
|
||||||
|
"""
|
||||||
|
return os.getenv("CM_DEBUG", "false").strip().lower() in ("1", "true", "yes")
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Replace `debug=True` in the `__main__` block**
|
||||||
|
|
||||||
|
Find:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("Starting CM Web View...")
|
||||||
|
print("Web interface will be available at: http://localhost:8000")
|
||||||
|
print("Make sure the API server is running on port 3000")
|
||||||
|
app.run(host='0.0.0.0', port=8000, debug=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the last line with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
app.run(host='0.0.0.0', port=8000, debug=_debug_enabled())
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run the test — `cm_web_view` cases should now pass, `cm_api` cases should still fail**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
.venv/bin/python -m unittest tests.test_debug_enabled -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `test_helper_exists_on_every_module` and `test_parses_cm_debug_consistently` still fail because `app.cm_api._debug_enabled` doesn't exist yet. The `app.cm_web_view` subTest entries should pass. Confirm by reading the verbose subtest output.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
git add app/cm_web_view.py && \
|
||||||
|
git -c user.name='yiekheng' -c user.email='yiekheng@04080616.xyz' \
|
||||||
|
commit -m "feat(web): make Werkzeug debug opt-in via CM_DEBUG"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Implement `_debug_enabled` in `cm_api.py` and switch `run()` to env-resolved default
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/cm_api.py` (top of file — currently no `import os`; class `CM_API.run` method around line 160)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add `import os` at the top**
|
||||||
|
|
||||||
|
Find the existing import block (currently lines 1-4):
|
||||||
|
|
||||||
|
```python
|
||||||
|
import threading
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
from flask_cors import CORS
|
||||||
|
from .db import DB
|
||||||
|
```
|
||||||
|
|
||||||
|
Insert `import os` as the very first line so the imports remain stdlib-then-third-party:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
from flask_cors import CORS
|
||||||
|
from .db import DB
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add the helper above the `CM_API` class**
|
||||||
|
|
||||||
|
Insert between the imports and `class CM_API:` (around line 6):
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _debug_enabled() -> bool:
|
||||||
|
"""Return True iff CM_DEBUG env var is set to a truthy value.
|
||||||
|
|
||||||
|
Truthy: '1', 'true', 'yes' (case-insensitive, whitespace-trimmed).
|
||||||
|
Anything else, including unset, is False. Default-off so the
|
||||||
|
Werkzeug debugger is never reachable in production containers.
|
||||||
|
"""
|
||||||
|
return os.getenv("CM_DEBUG", "false").strip().lower() in ("1", "true", "yes")
|
||||||
|
```
|
||||||
|
|
||||||
|
This text is identical to the helper in `cm_web_view.py` — the parametrized test enforces it stays that way.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Change the `run` method signature and resolve when unset**
|
||||||
|
|
||||||
|
Find the existing method (currently around line 160):
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run(self, port=3000, debug=True):
|
||||||
|
# Test database connection before starting server
|
||||||
|
test_db = self._get_database_connection()
|
||||||
|
if test_db is None:
|
||||||
|
print("Cannot start server: Database not available")
|
||||||
|
exit(1)
|
||||||
|
self._close_database_connection(test_db)
|
||||||
|
|
||||||
|
print(f'CM Bot DB API Listening at Port : {port}')
|
||||||
|
self.app.run(host='0.0.0.0', port=port, debug=debug)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def run(self, port=3000, debug=None):
|
||||||
|
if debug is None:
|
||||||
|
debug = _debug_enabled()
|
||||||
|
# Test database connection before starting server
|
||||||
|
test_db = self._get_database_connection()
|
||||||
|
if test_db is None:
|
||||||
|
print("Cannot start server: Database not available")
|
||||||
|
exit(1)
|
||||||
|
self._close_database_connection(test_db)
|
||||||
|
|
||||||
|
print(f'CM Bot DB API Listening at Port : {port}')
|
||||||
|
self.app.run(host='0.0.0.0', port=port, debug=debug)
|
||||||
|
```
|
||||||
|
|
||||||
|
Do **not** touch `run_in_thread` — its `debug=False` default is already safe and used internally.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run the test — both modules should now pass**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
.venv/bin/python -m unittest tests.test_debug_enabled -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `OK` with all subTests passing for both `app.cm_web_view` and `app.cm_api`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Confirm no caller passes `debug` positionally to `run()`**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn "\.run(" /home/yiekheng/projects/cm_bot_v2/app/ | grep -v "self\.app\.run\|app\.run("
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: only `api.run(port = 3000)` in `cm_api.py:191`. If anything else appears that passes a positional second arg to `CM_API.run`, change it to a keyword argument before continuing — the signature change went from `debug=True` to `debug=None`, so a caller passing `True` positionally would now incorrectly enable debug.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
git add app/cm_api.py && \
|
||||||
|
git -c user.name='yiekheng' -c user.email='yiekheng@04080616.xyz' \
|
||||||
|
commit -m "feat(api): make Werkzeug debug opt-in via CM_DEBUG"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Plumb `CM_DEBUG` into `docker-compose.yml`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docker-compose.yml` (the `api-server` `environment:` block at lines 40-49 and the `web-view` `environment:` block at lines 63-66)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add `CM_DEBUG` to the `api-server` environment**
|
||||||
|
|
||||||
|
Find:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
PYTHONUNBUFFERED: "1"
|
||||||
|
DB_HOST: ${DB_HOST}
|
||||||
|
```
|
||||||
|
|
||||||
|
(under `api-server:`, currently lines 40-42). Insert `CM_DEBUG` directly after `PYTHONUNBUFFERED`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
PYTHONUNBUFFERED: "1"
|
||||||
|
CM_DEBUG: ${CM_DEBUG:-false}
|
||||||
|
DB_HOST: ${DB_HOST}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `${CM_DEBUG:-false}` form guarantees the variable is defined inside the container even if the operator did not set it in their `.env`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add `CM_DEBUG` to the `web-view` environment**
|
||||||
|
|
||||||
|
Find (under `web-view:`, currently lines 63-66):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
PYTHONUNBUFFERED: "1"
|
||||||
|
API_BASE_URL: http://api-server:3000
|
||||||
|
CM_PREFIX_PATTERN: ${CM_PREFIX_PATTERN}
|
||||||
|
```
|
||||||
|
|
||||||
|
Insert `CM_DEBUG` after `PYTHONUNBUFFERED`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
PYTHONUNBUFFERED: "1"
|
||||||
|
CM_DEBUG: ${CM_DEBUG:-false}
|
||||||
|
API_BASE_URL: http://api-server:3000
|
||||||
|
CM_PREFIX_PATTERN: ${CM_PREFIX_PATTERN}
|
||||||
|
```
|
||||||
|
|
||||||
|
Do **not** add it to `telegram-bot` or `transfer-bot` — neither runs a Flask server.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Validate the compose file parses**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
docker compose -f docker-compose.yml config >/dev/null && echo OK
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `OK`. If the command errors with a YAML or interpolation problem, fix the indentation around the new lines.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Confirm the variable reaches both services in the rendered config**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
docker compose -f docker-compose.yml config | grep -E "CM_DEBUG"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: two matching lines, one each under `api-server` and `web-view`, value rendered as `false` (or whatever `CM_DEBUG` resolves to in the current shell env).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
git add docker-compose.yml && \
|
||||||
|
git -c user.name='yiekheng' -c user.email='yiekheng@04080616.xyz' \
|
||||||
|
commit -m "chore(compose): pass CM_DEBUG into api-server and web-view"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: Document `CM_DEBUG` in `.env.example`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `.env.example` (currently 32 lines; the existing `=== Deployment Identity ===` section is at the top)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Insert a new `Runtime` section near the top**
|
||||||
|
|
||||||
|
Find the first three lines of `.env.example`:
|
||||||
|
|
||||||
|
```
|
||||||
|
# === Deployment Identity ===
|
||||||
|
# Unique name prefix for containers and network (avoid conflicts on same host)
|
||||||
|
CM_DEPLOY_NAME=rex-cm
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a new section above them:
|
||||||
|
|
||||||
|
```
|
||||||
|
# === Runtime ===
|
||||||
|
# Set to true ONLY in local dev. Werkzeug debugger = RCE if exposed.
|
||||||
|
CM_DEBUG=false
|
||||||
|
|
||||||
|
# === Deployment Identity ===
|
||||||
|
# Unique name prefix for containers and network (avoid conflicts on same host)
|
||||||
|
CM_DEPLOY_NAME=rex-cm
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Confirm the file still parses as a valid env file**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
grep -E "^[A-Z_]+=" .env.example | wc -l
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: one more line than before the change (run before/after to verify if curious; the absolute count is implementation-dependent).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
git add .env.example && \
|
||||||
|
git -c user.name='yiekheng' -c user.email='yiekheng@04080616.xyz' \
|
||||||
|
commit -m "docs(env): document CM_DEBUG in .env.example"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: Add a one-line note to `AGENTS.md`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `AGENTS.md` (the `## Security & Configuration Tips` section at the bottom)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Append a bullet to the Security section**
|
||||||
|
|
||||||
|
Find the existing section (currently the last block in the file):
|
||||||
|
|
||||||
|
```
|
||||||
|
## Security & Configuration Tips
|
||||||
|
- Never commit real secrets in `.env`.
|
||||||
|
- `app/cm_bot_hal.py` currently contains hardcoded agent credentials/pin; move these to env vars before production use.
|
||||||
|
- Keep container clocks mounted (`/etc/timezone`, `/etc/localtime`) as compose currently defines to avoid schedule drift.
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a new bullet above the `cm_bot_hal.py` line:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Security & Configuration Tips
|
||||||
|
- Never commit real secrets in `.env`.
|
||||||
|
- `CM_DEBUG` defaults to `false` for both Flask services. Set it to `true` only in local development; rex/siong production env files must leave it unset (the Werkzeug debugger is RCE if reachable).
|
||||||
|
- `app/cm_bot_hal.py` currently contains hardcoded agent credentials/pin; move these to env vars before production use.
|
||||||
|
- Keep container clocks mounted (`/etc/timezone`, `/etc/localtime`) as compose currently defines to avoid schedule drift.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
git add AGENTS.md && \
|
||||||
|
git -c user.name='yiekheng' -c user.email='yiekheng@04080616.xyz' \
|
||||||
|
commit -m "docs(agents): note CM_DEBUG default and intent"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7: Integration verification (manual run)
|
||||||
|
|
||||||
|
This task corresponds to the four verification scenarios in the spec. No commit — these are smoke checks. If any fails, do not declare done; debug and fix.
|
||||||
|
|
||||||
|
**Files:** none modified.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Local dev with `CM_DEBUG=true` — debug ON path**
|
||||||
|
|
||||||
|
In a scratch repo-root `.env` (or temporarily setting in shell), set:
|
||||||
|
|
||||||
|
```
|
||||||
|
CM_DEBUG=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Then bring up the local stack:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.override.yml up --build -d api-server web-view
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs --no-color api-server web-view | grep -E "Debug mode|Debugger PIN"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: at least one `* Debug mode: on` line and at least one `Debugger PIN:` line.
|
||||||
|
|
||||||
|
Tear down:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Local dev with `CM_DEBUG=false` (default) — debug OFF path**
|
||||||
|
|
||||||
|
Unset or set `CM_DEBUG=false` in `.env`, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.override.yml up --build -d api-server web-view
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs --no-color api-server web-view | grep -E "Debug mode|Debugger PIN" || echo "no debug lines (expected)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: prints `no debug lines (expected)` because neither pattern matches.
|
||||||
|
|
||||||
|
Sanity-check both endpoints still serve:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -o /dev/null -w "api %{http_code}\n" http://localhost:3000/acc/ ; \
|
||||||
|
curl -s -o /dev/null -w "web %{http_code}\n" "http://localhost:${CM_WEB_HOST_PORT:-8001}/api/acc/"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `api 200` and `web 200` (assuming the database is reachable; if the api shows 5xx because of DB, that is unrelated to this change — the absence of debug lines above is the success criterion for this task).
|
||||||
|
|
||||||
|
Tear down:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Override path regression check**
|
||||||
|
|
||||||
|
From the repo root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
.venv/bin/python -c "
|
||||||
|
import os
|
||||||
|
os.environ['CM_DEBUG'] = 'false'
|
||||||
|
from app.cm_api import _debug_enabled
|
||||||
|
print('helper says:', _debug_enabled())
|
||||||
|
import inspect
|
||||||
|
sig = inspect.signature(__import__('app.cm_api', fromlist=['CM_API']).CM_API.run)
|
||||||
|
print('run() signature:', sig)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
```
|
||||||
|
helper says: False
|
||||||
|
run() signature: (self, port=3000, debug=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Re-run the full unit test suite**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yiekheng/projects/cm_bot_v2 && \
|
||||||
|
.venv/bin/python -m unittest tests.test_debug_enabled -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `OK` with all subTests passing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spec Coverage Check (self-review)
|
||||||
|
|
||||||
|
| Spec requirement | Task |
|
||||||
|
|---|---|
|
||||||
|
| `_debug_enabled()` helper definition | Task 2 (web), Task 3 (api) |
|
||||||
|
| `cm_web_view.py:748` debug flag swap | Task 2 |
|
||||||
|
| `cm_api.py:160` signature change to `debug=None` | Task 3 |
|
||||||
|
| `import os` added to `cm_api.py` | Task 3, Step 1 |
|
||||||
|
| `run_in_thread` left untouched | Task 3, Step 3 (explicit "Do not touch") |
|
||||||
|
| `docker-compose.yml` plumbing for both Flask services | Task 4 |
|
||||||
|
| `.env.example` Runtime section | Task 5 |
|
||||||
|
| `AGENTS.md` one-line note | Task 6 |
|
||||||
|
| Verification: debug-on local | Task 7, Step 1 |
|
||||||
|
| Verification: debug-off local + endpoints serve | Task 7, Step 2 |
|
||||||
|
| Verification: explicit override path | Task 7, Step 3 |
|
||||||
|
| Regression test for helper parity | Task 1 (write), Tasks 2 & 3 (make pass), Task 7 Step 4 (re-run) |
|
||||||
Loading…
x
Reference in New Issue
Block a user