Planned Effort
9 story points (one PR) — combines sprint items #3 + #5 + #6:
| Sprint item |
Points |
Topic |
| #3 |
3 |
Programmatic guard: refuse --debug with non-loopback --host |
| #5 |
3 |
__version__, CHANGELOG.md, deprecation policy |
| #6 |
3 |
API response shape contract (stable vs experimental fields) |
Out of scope this issue: removing export_count (Thursday #4 — requires this policy first).
Problem
- Security gap: Week 4 made
--debug opt-in (default=False) and documented the 0.0.0.0 + debug risk in the README, but the process still allows that dangerous combination.
- No release contract: There is no
__version__, no CHANGELOG.md, and no documented deprecation process — so API fields (e.g. duplicate export_count on /api/export/state) cannot evolve safely.
- Hyrum's Law: The SPA reads every JSON field it sees; without documented stable vs experimental fields, accidental coupling blocks refactors.
Goal
One merged PR that:
- Fails fast when
--debug is used with a non-loopback --host.
- Establishes versioning + deprecation policy for future API changes.
- Documents response field stability for key endpoints (especially export state before Thursday's alias cleanup).
Scope
A — Debug + non-loopback host guard (3 pt)
Touch points: app.py, tests/test_cli_args.py, README.md (one-line cross-link)
-
Add is_loopback_host(host: str) -> bool (module-level in app.py or utils/cli_hosts.py if you prefer zero test duplication):
- Accept:
127.0.0.1, localhost, ::1 (normalize case for hostnames).
- Reject:
0.0.0.0, empty string, public/LAN IPs.
-
In if __name__ == "__main__": after parse_args():
if args.debug and not is_loopback_host(args.host):
print(
"error: --debug is only allowed with a loopback --host "
"(127.0.0.1, localhost, or ::1). "
"Binding to a non-loopback address with debug exposes the Werkzeug "
"interactive debugger on the network.",
file=sys.stderr,
)
raise SystemExit(1)
-
Tests (tests/test_cli_args.py or tests/test_cli_host_guard.py):
127.0.0.1 + --debug → guard does not exit (smoke via calling is_loopback_host + guard helper, or subprocess if already used elsewhere).
0.0.0.0 + --debug → SystemExit(1).
0.0.0.0 without --debug → allowed.
-
README: add one sentence under existing security warning that the server refuses the bad combo at startup.
B — Versioning + CHANGELOG + deprecation policy (3 pt)
Touch points: app.py (or new version.py), CHANGELOG.md, CONTRIBUTING.md or docs/deprecation-policy.md, README.md
- Add canonical
__version__ = "0.1.0" (importable from app for future /api/version if desired — not required this issue).
- Create
CHANGELOG.md (Keep a Changelog format):
## [0.1.0] - 2026-06-02 (or merge date)
- Sections: Added (version file, policy, guard), Changed (docs), Deprecated (none yet — note
export_count planned).
- Document deprecation policy (minimum):
- Deprecated fields stay for at least one release with docs + CHANGELOG notice.
- SPA (
static/js/*.js) must migrate before field removal.
- Prefer
last_export_session_count over export_count for new code.
- Link policy from
CONTRIBUTING.md and README.
C — API response shape contract (3 pt)
Touch points: docs/api-reference.md, optional docs/api-stability.md
Extend documentation for at least these responses:
| Endpoint |
Why |
GET /api/export/state |
export_count alias + Thursday cleanup |
GET /api/projects (list) |
SPA projects page |
GET /api/projects/<name>/sessions/<id> (session payload) |
Drives sessions.js |
For each documented response table, add a Stability column:
| Value |
Meaning |
stable |
Will not rename/remove without deprecation period |
experimental |
May change without major version bump |
deprecated |
Still present; removal scheduled (link CHANGELOG) |
Document one migration approach (pick one, document clearly):
- Additive default: new field → deprecate old → remove after policy period, or
- Future
/api/v2/... for breaking changes (no implementation required this week).
Do not remove export_count in this PR — only mark it deprecated in docs.
Acceptance Criteria
Planned Effort
9 story points (one PR) — combines sprint items #3 + #5 + #6:
--debugwith non-loopback--host__version__,CHANGELOG.md, deprecation policyOut of scope this issue: removing
export_count(Thursday #4 — requires this policy first).Problem
--debugopt-in (default=False) and documented the0.0.0.0+ debug risk in the README, but the process still allows that dangerous combination.__version__, noCHANGELOG.md, and no documented deprecation process — so API fields (e.g. duplicateexport_counton/api/export/state) cannot evolve safely.Goal
One merged PR that:
--debugis used with a non-loopback--host.Scope
A — Debug + non-loopback host guard (3 pt)
Touch points:
app.py,tests/test_cli_args.py,README.md(one-line cross-link)Add
is_loopback_host(host: str) -> bool(module-level inapp.pyorutils/cli_hosts.pyif you prefer zero test duplication):127.0.0.1,localhost,::1(normalize case for hostnames).0.0.0.0, empty string, public/LAN IPs.In
if __name__ == "__main__":afterparse_args():Tests (
tests/test_cli_args.pyortests/test_cli_host_guard.py):127.0.0.1+--debug→ guard does not exit (smoke via callingis_loopback_host+ guard helper, or subprocess if already used elsewhere).0.0.0.0+--debug→SystemExit(1).0.0.0.0without--debug→ allowed.README: add one sentence under existing security warning that the server refuses the bad combo at startup.
B — Versioning + CHANGELOG + deprecation policy (3 pt)
Touch points:
app.py(or newversion.py),CHANGELOG.md,CONTRIBUTING.mdordocs/deprecation-policy.md,README.md__version__ = "0.1.0"(importable fromappfor future/api/versionif desired — not required this issue).CHANGELOG.md(Keep a Changelog format):## [0.1.0] - 2026-06-02(or merge date)export_countplanned).static/js/*.js) must migrate before field removal.last_export_session_countoverexport_countfor new code.CONTRIBUTING.mdand README.C — API response shape contract (3 pt)
Touch points:
docs/api-reference.md, optionaldocs/api-stability.mdExtend documentation for at least these responses:
GET /api/export/stateexport_countalias + Thursday cleanupGET /api/projects(list)GET /api/projects/<name>/sessions/<id>(session payload)sessions.jsFor each documented response table, add a Stability column:
stableexperimentaldeprecatedDocument one migration approach (pick one, document clearly):
/api/v2/...for breaking changes (no implementation required this week).Do not remove
export_countin this PR — only mark itdeprecatedin docs.Acceptance Criteria
app.pyexits with clear error on--host 0.0.0.0 --debug(and similar non-loopback hosts)__version__exists;CHANGELOG.mdhas initial release entrydocs/api-reference.mdmarks stable / experimental / deprecated fields for export state + at least two other key endpointspytest -qandmypypass;npm testunchanged/green