fix: clear all addressable technical debt (DEBT-005 through DEBT-025)

Security:
- DEBT-008: remove query-string token auth; header-only Bearer now enforced
- DEBT-013: add regex constraint ^[a-z0-9\-]{1,64}$ on decky_name path param
- DEBT-015: stop leaking raw exception detail to API clients; log server-side
- DEBT-016: validate search (max_length=512) and datetime params with regex

Reliability:
- DEBT-014: wrap SSE event_generator in try/except; yield error frame on failure
- DEBT-017: emit log.warning/error on DB init retry; silent failures now visible

Observability / Docs:
- DEBT-020: add 401/422 response declarations to all route decorators

Infrastructure:
- DEBT-018: add HEALTHCHECK to all 24 template Dockerfiles
- DEBT-019: add USER decnet + setcap cap_net_bind_service to all 24 Dockerfiles
- DEBT-024: bump Redis template version 7.0.12 → 7.2.7

Config:
- DEBT-012: validate DECNET_API_PORT and DECNET_WEB_PORT range (1-65535)

Code quality:
- DEBT-010: delete 22 duplicate decnet_logging.py copies; deployer injects canonical
- DEBT-022: closed as false positive (print only in module docstring)
- DEBT-009: closed as false positive (templates already use structured syslog_line)

Build:
- DEBT-025: generate requirements.lock via pip freeze

Testing:
- DEBT-005/006/007: comprehensive test suite added across tests/api/
- conftest: in-memory SQLite + StaticPool + monkeypatched session_factory
- fuzz mark added; default run excludes fuzz; -n logical parallelism

DEBT.md updated: 23/25 items closed; DEBT-011 (Alembic) and DEBT-023 (digest pinning) remain
This commit is contained in:
2026-04-09 19:02:51 -04:00
parent 0166d0d559
commit 016115a523
78 changed files with 527 additions and 5579 deletions

View File

@@ -74,7 +74,7 @@ async def api_deploy_deckies(req: DeployIniRequest, current_user: str = Depends(
try:
_deploy(config)
except Exception as e:
logging.getLogger("decnet.web.api").error(f"Deployment failed: {e}")
raise HTTPException(status_code=500, detail=f"Deployment failed: {e}")
logging.getLogger("decnet.web.api").exception("Deployment failed: %s", e)
raise HTTPException(status_code=500, detail="Deployment failed. Check server logs for details.")
return {"message": "Deckies deployed successfully"}

View File

@@ -7,6 +7,7 @@ from decnet.web.dependencies import get_current_user, repo
router = APIRouter()
@router.get("/deckies", tags=["Fleet Management"])
@router.get("/deckies", tags=["Fleet Management"],
responses={401: {"description": "Not authenticated"}, 422: {"description": "Validation error"}},)
async def get_deckies(current_user: str = Depends(get_current_user)) -> list[dict[str, Any]]:
return await repo.get_deckies()

View File

@@ -1,4 +1,4 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Path
from decnet.mutator import mutate_decky
from decnet.web.dependencies import get_current_user
@@ -7,7 +7,10 @@ router = APIRouter()
@router.post("/deckies/{decky_name}/mutate", tags=["Fleet Management"])
async def api_mutate_decky(decky_name: str, current_user: str = Depends(get_current_user)) -> dict[str, str]:
async def api_mutate_decky(
decky_name: str = Path(..., pattern=r"^[a-z0-9\-]{1,64}$"),
current_user: str = Depends(get_current_user),
) -> dict[str, str]:
success = mutate_decky(decky_name)
if success:
return {"message": f"Successfully mutated {decky_name}"}

View File

@@ -7,7 +7,8 @@ from decnet.web.db.models import MutateIntervalRequest
router = APIRouter()
@router.put("/deckies/{decky_name}/mutate-interval", tags=["Fleet Management"])
@router.put("/deckies/{decky_name}/mutate-interval", tags=["Fleet Management"],
responses={401: {"description": "Not authenticated"}, 422: {"description": "Validation error"}},)
async def api_update_mutate_interval(decky_name: str, req: MutateIntervalRequest, current_user: str = Depends(get_current_user)) -> dict[str, str]:
state = load_state()
if not state: