6 plans across 4 waves covering structlog/Loki observability, Locust load testing, multi-stage Dockerfile hardening, trusted-proxy rate limiting, and RUNBOOK.md. Verification passed (0 blockers). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
18 KiB
18 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-performance-production-hardening | 01 | execute | 0 |
|
false |
|
|
Purpose: Lock down behavior expectations before implementation; surface the slowapi key_func ordering assumption (RESEARCH.md A1) as an explicit test that downstream plans must turn green; gate package installs behind a blocking human verification per package-legitimacy protocol.
Output: Four scaffolded files plus a verified human-approved package list ready for 06-02 / 06-03 to install.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-performance-production-hardening/06-CONTEXT.md @.planning/phases/06-performance-production-hardening/06-RESEARCH.md @.planning/phases/06-performance-production-hardening/06-PATTERNS.md @.planning/phases/06-performance-production-hardening/06-VALIDATION.md @CLAUDE.md @backend/tests/conftest.py @backend/deps/utils.py @backend/pytest.ini Task 1: Package legitimacy verification — structlog and locust - .planning/phases/06-performance-production-hardening/06-RESEARCH.md (sections: Package Legitimacy Audit, Standard Stack) Two PyPI packages are flagged `[ASSUMED]` by the researcher because slopcheck was unavailable at research time. No code change has been made yet — this checkpoint exists so the user can confirm legitimacy before any requirements file is modified. 1. Open https://pypi.org/project/structlog/ — confirm version 25.5.0 is published; confirm the homepage points to https://github.com/hynek/structlog; confirm author is Hynek Schlawack (a CPython core contributor). 2. Open https://pypi.org/project/locust/ — confirm version 2.34.0 is published; confirm the homepage points to https://github.com/locustio/locust; confirm the project has >10 years of release history and multi-million monthly downloads. 3. Confirm neither package name is a typosquat of a similar published name (e.g. `struct-log`, `locusts`). - User responds with "approved" or "approved with notes" — execution may proceed. - User responds with anything else — execution halts; planner reconsiders alternatives or aborts the phase. Type "approved" to allow 06-02 to add structlog and 06-03 to add locust; otherwise describe the concern. Task 2: Create test_logging.py with xfail stubs (D-01/D-02) backend/tests/test_logging.py - backend/tests/conftest.py (fixture conventions: async_client, auth_user, pytest_asyncio.fixture, asyncio_mode=auto) - backend/pytest.ini (asyncio_mode setting; verify xfail is a recognised marker) - .planning/phases/06-performance-production-hardening/06-RESEARCH.md (Pattern 1, Pattern 2, Pitfall 2) Create backend/tests/test_logging.py with single-line-body xfail stubs (strict=False) for these behaviours, one test function each — body is exactly `pytest.xfail("not implemented yet")`: (1) test_setup_logging_emits_json_when_LOG_JSON_true — D-01 contract that setup_logging(json_logs=True) installs JSONRenderer through ProcessorFormatter and root logger emits JSON to stdout. (2) test_correlation_id_middleware_binds_contextvar — CorrelationIDMiddleware binds correlation_id, path, method into structlog.contextvars per request. (3) test_correlation_id_response_header_present — every HTTP response includes X-Correlation-ID header set to the bound correlation_id. (4) test_contextvars_cleared_between_requests — Pitfall 2 — clear_contextvars() runs first in the middleware so user_id from a prior request never bleeds into the next. (5) test_uvicorn_access_log_suppressed — uvicorn.access propagate=False after setup_logging() so the middleware owns request logging. Use the same async_client fixture pattern from conftest.py for tests that need ASGI requests. Mark all five with `@pytest.mark.xfail(strict=False, reason="implementation in 06-02")`. Single-line body only — no assertion code that could xpass accidentally. cd backend && pytest tests/test_logging.py -v --no-header 2>&1 | grep -v '^#' | grep -cE 'XFAIL|xfailed' - File backend/tests/test_logging.py exists and is importable: `cd backend && python -c "import tests.test_logging"` exits 0. - `cd backend && pytest tests/test_logging.py -v --no-header 2>&1` shows exactly 5 collected tests, all XFAIL, zero PASSED, zero FAILED, zero ERROR. - Every test body is the single line `pytest.xfail("not implemented yet")` — `grep -c "pytest.xfail" backend/tests/test_logging.py` returns 5. - All five tests are decorated `@pytest.mark.xfail(strict=False, reason=...)` — `grep -c "xfail(strict=False" backend/tests/test_logging.py` returns 5. Five xfail stubs collected, all show XFAIL status, full suite still passes the same count as before this plan. Task 3: Create test_rate_limiting.py with xfail stubs (D-11/D-12 + A1) backend/tests/test_rate_limiting.py - backend/deps/utils.py (current get_client_ip body; the function that 06-04 will replace) - backend/tests/conftest.py (async_client, auth_user, second_auth_user fixtures) - .planning/phases/06-performance-production-hardening/06-RESEARCH.md (Pattern 3, Pattern 4, Pitfall 3, Assumption A1) - .planning/phases/06-performance-production-hardening/06-PATTERNS.md (deps/utils.py section, account_limiter section) Create backend/tests/test_rate_limiting.py with single-line-body xfail stubs (strict=False), one test function each — body is exactly `pytest.xfail("not implemented yet")`: (1) test_get_client_ip_untrusted_returns_direct_peer — D-11 — when request.client.host is 8.8.8.8 (not in trusted CIDRs), ignore X-Forwarded-For and return "8.8.8.8". (2) test_get_client_ip_trusted_proxy_reads_xff_leftmost — D-11 — when request.client.host is 127.0.0.1 (trusted), return the leftmost IP of X-Forwarded-For: "1.2.3.4, 5.6.7.8" → "1.2.3.4". (3) test_get_client_ip_trusted_proxy_no_xff_falls_back — D-11 — trusted peer with no XFF header returns the direct peer IP. (4) test_get_client_ip_invalid_peer_returns_none_or_string — D-11 — request.client is None returns None without raising. (5) test_account_limiter_key_uses_user_id — D-12 — _account_key(request) where request.state.current_user has id=UUID(...) returns str(user.id), not request.client.host. (6) test_account_limiter_key_falls_back_to_ip_when_no_user — D-12 — when request.state.current_user is missing, key function returns the direct peer IP (Pitfall 3 — must not crash). (7) test_account_limiter_key_ordering_assumption — A1 verification — construct a FastAPI app with one endpoint that sets `request.state.current_user = current_user` as its first line, decorated with `@account_limiter.limit("100/minute")`; call it 101 times with the same user; assert the 101st response is 429 AND the limiter recorded the key as str(user.id), not as the IP. This test is the gate that turns A1 from assumption to fact before 06-04 wires the decorator on all endpoints. (8) test_authenticated_endpoint_429_after_100_per_minute — D-12 — full integration: GET /api/documents/ 101 times with the same auth_user; 101st returns 429. Mark all eight with `@pytest.mark.xfail(strict=False, reason="implementation in 06-04")`. Single-line body only. cd backend && pytest tests/test_rate_limiting.py -v --no-header 2>&1 | grep -v '^#' | grep -cE 'XFAIL|xfailed' - File backend/tests/test_rate_limiting.py exists and is importable: `cd backend && python -c "import tests.test_rate_limiting"` exits 0. - `cd backend && pytest tests/test_rate_limiting.py -v --no-header` shows exactly 8 collected tests, all XFAIL, zero PASSED, zero FAILED. - `grep -c "pytest.xfail" backend/tests/test_rate_limiting.py` returns 8. - `grep -c "xfail(strict=False" backend/tests/test_rate_limiting.py` returns 8. - At least one test name contains the substring "ordering" — `grep -c "def test.*ordering" backend/tests/test_rate_limiting.py` returns 1. Eight xfail stubs collected, A1 verification test present, no regressions in existing suite. Task 4: Create backend/load_tests/ package skeleton with Locust stub backend/load_tests/__init__.py, backend/load_tests/locustfile.py - .planning/phases/06-performance-production-hardening/06-RESEARCH.md (Pattern 7) - .planning/phases/06-performance-production-hardening/06-PATTERNS.md (locustfile.py section) - backend/pytest.ini (testpaths setting — confirm pytest does not include load_tests) Create two files: (1) backend/load_tests/__init__.py — empty file (zero bytes). This serves a single purpose: turn load_tests/ into a Python package so locust can import it cleanly, while pytest's default test discovery (which looks for test_*.py files) will not collect locustfile.py because its filename does not match the test_*.py pattern. Add a one-line comment `# Locust load-test package — not a pytest test target` if any content is needed. (2) backend/load_tests/locustfile.py — skeleton with the import block (`from locust import HttpUser, task, between, events`), TEST_EMAIL / TEST_PASSWORD env-var pickup with safe defaults, a class `DocuVaultUser(HttpUser)` containing wait_time = between(0.5, 2.0), an empty access_token attribute, and method stubs `on_start`, `_auth_headers`, `list_documents`, `upload_document`, `refresh_token`, plus an `@events.quitting.add_listener def check_sla(...)` registered listener. Every method body is exactly `raise NotImplementedError("implementation in 06-03")`. Top of file: docstring referencing D-04/D-05/D-06 and the run command from RESEARCH.md Pattern 7. Do NOT import anything from the application code (no `from backend...` or `from api...` lines). test -f backend/load_tests/__init__.py && test -f backend/load_tests/locustfile.py && cd backend && python -c "import ast; ast.parse(open('load_tests/locustfile.py').read())" && cd backend && pytest tests/ -v --collect-only --no-header 2>&1 | grep -c "load_tests" - File backend/load_tests/__init__.py exists; `wc -c backend/load_tests/__init__.py` returns ≤ 70 bytes (empty or one comment line). - File backend/load_tests/locustfile.py exists and is syntactically valid Python: `python -c "import ast; ast.parse(open('backend/load_tests/locustfile.py').read())"` exits 0. - locustfile.py defines exactly one class containing the string `class DocuVaultUser` — `grep -c "class DocuVaultUser" backend/load_tests/locustfile.py` returns 1. - locustfile.py contains the SLA listener marker — `grep -c "events.quitting.add_listener" backend/load_tests/locustfile.py` returns 1. - locustfile.py contains no application imports — `grep -cE "^from (backend|api|services|db|deps)" backend/load_tests/locustfile.py` returns 0. - `cd backend && pytest tests/ --collect-only --no-header 2>&1 | grep -c "load_tests"` returns 0 (pytest must NOT discover anything in load_tests). - Each method body raises NotImplementedError — `grep -c "raise NotImplementedError" backend/load_tests/locustfile.py` returns ≥ 5. Package present, locustfile importable by `python -c "import sys; sys.path.insert(0,'backend/load_tests'); import locustfile"`, pytest does not discover the directory, no application imports. Task 5: Full-suite regression check - backend/pytest.ini - backend/tests/test_logging.py (just created) - backend/tests/test_rate_limiting.py (just created) Run the full backend pytest suite to confirm: (a) the 13 new xfail stubs (5 + 8) all collect and report XFAIL, (b) zero pre-existing tests changed status, (c) no collection errors from the new files or the new load_tests/ package. Capture the XFAIL count and the PASSED count for the SUMMARY.md. cd backend && pytest tests/ -v --no-header --tb=short 2>&1 | tail -5 | grep -vE '^#' | grep -E 'passed|xfailed' - `cd backend && pytest tests/ --no-header --tb=line 2>&1 | tail -1` shows a green summary line containing "passed" with no "failed" or "error". - Total xfailed count increased by exactly 13 versus the baseline recorded in STATE.md (Phase 6.2: 344 passed, 1 pre-existing failure). - Zero NEW failures: the pre-existing test_extract_docx failure remains the only failure, no other tests turn red. - Plan SUMMARY records the exact passed/xfailed numbers from the run. Full suite green-with-known-pre-existing-failure, 13 new XFAIL items present, ready for implementation plans 06-02/06-03/06-04 to promote them to PASS.<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Untrusted client → reverse proxy | External HTTP clients can spoof headers; proxy must overwrite or strip them |
| Reverse proxy → FastAPI | Internal-network requests; XFF is trusted only when peer matches CIDR |
| Application code → PyPI registry | Third-party packages may be malicious typosquats; legitimacy gate required |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-06-01-01 | Tampering | structlog/locust packages from PyPI | mitigate | Blocking human checkpoint (Task 1) — verify PyPI URL, author, age, downloads before any requirements file modification |
| T-06-01-02 | Tampering | A1 assumption (slowapi key_func ordering) ships untested | mitigate | Dedicated xfail test (Task 3, test 7) — 06-04 must turn it green before applying the decorator to all routes; un-promoted test blocks phase gate |
| T-06-01-03 | Information Disclosure | Locust credentials hardcoded in version-controlled file | mitigate | Skeleton reads from env vars; .env file already in .gitignore; documented in RUNBOOK.md (06-06) |
| T-06-01-SC | Tampering | npm/pip/cargo installs | mitigate | Package legitimacy gate (Task 1) is gate="blocking-human"; cannot auto-advance |
| </threat_model> |
<success_criteria>
- All 13 xfail stubs collected and shown as XFAIL.
- backend/load_tests/ package exists, pytest skips it, locustfile.py imports cleanly under Python's stdlib only.
- Package legitimacy human-verify checkpoint passed and recorded.
- Zero regressions: the only red test in the suite is the pre-existing test_extract_docx (carried over from 06.2). </success_criteria>