Files
the-custodian/workplans/CUST-WP-0018-api-hardening-code-quality.md
tegwick 9aa54f8133 feat(api): CUST-WP-0018 — API hardening & code quality
T01: Fix datetime.utcnow() → datetime.now(tz=timezone.utc) in MCP server
T02: Wrap _get/_post/_patch/_delete with try/except; return error dicts
T03: Log warnings when write_log skips missing project path
T04: Add priority + due_date_before filters to GET /tasks/
T05: Add owner + slug filters to GET /workstreams/
T06: Add offset param to GET /progress/ for proper pagination
T07: Low-severity bundle:
  - CORS origins from CORS_ORIGINS env var (TD-017)
  - seed.py upsert domains+topics on re-run (TD-011)
  - normalise filter bar CSS → filter-text-input everywhere (TD-016)
  - add 30.5 avg-days-per-month comment in decisions.md (TD-019)
  - TD-009, TD-018 already resolved by existing code

Closes CUST-WP-0018.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 02:17:04 +01:00

172 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: CUST-WP-0018
type: workplan
title: "State Hub — API Hardening & Code Quality"
domain: custodian
status: done
owner: custodian
topic_slug: custodian
created: "2026-03-18"
updated: "2026-03-18"
state_hub_workstream_id: "c7777d8a-a796-4f72-b444-64cc14f77a58"
---
# State Hub — API Hardening & Code Quality
## Summary
Resolve all open medium/high technical debt items in the API and MCP server.
These are small, targeted fixes: datetime correctness, missing filter params,
MCP error handling, and a bundle of low-severity cleanup items. No schema
migrations required.
## Resolves
TD-CUST-006, TD-CUST-007, TD-CUST-008, TD-CUST-009, TD-CUST-010,
TD-CUST-011, TD-CUST-012, TD-CUST-013, TD-CUST-015, TD-CUST-016,
TD-CUST-017, TD-CUST-018, TD-CUST-019
TD-CUST-005 (N+1 selectin) deferred — not pressing at current scale.
---
## Tasks
### T01 — Fix deprecated datetime.utcnow() in MCP server
```task
id: CUST-WP-0018-T01
status: done
priority: high
state_hub_task_id: "5045749c-22a5-4f37-81b1-6fc87ae7c580"
```
Replace `datetime.utcnow()` with `datetime.now(tz=timezone.utc)` throughout
`state-hub/mcp_server/server.py`. Resolves TD-CUST-006 and TD-CUST-013
(mixed naive/aware datetime handling). One-line change with broad correctness
impact.
**Location:** `state-hub/mcp_server/server.py:343` (and any other occurrences)
---
### T02 — MCP HTTP helpers: catch exceptions and return user-friendly errors
```task
id: CUST-WP-0018-T02
status: done
priority: high
state_hub_task_id: "8aadbaf8-d898-436e-8df0-7f095c916613"
```
Wrap `_get()`, `_post()`, `_patch()`, `_delete()` in a try/except that
catches `httpx.HTTPError` and `Exception`, returning a structured error dict
instead of letting the MCP tool crash with a stack trace. Resolves TD-CUST-015.
**Location:** `state-hub/mcp_server/server.py:3469`
Pattern:
```python
try:
r = await client.get(url, ...)
r.raise_for_status()
return r.json()
except httpx.HTTPStatusError as e:
return {"error": f"API {e.response.status_code}: {e.response.text[:200]}"}
except Exception as e:
return {"error": f"Request failed: {e}"}
```
---
### T03 — Decision write_log: warn when project path not found
```task
id: CUST-WP-0018-T03
status: done
priority: medium
state_hub_task_id: "26f8d132-b2f4-4939-9497-a9ad64e0a73e"
```
`_write_project_log()` in `decisions.py` silently returns when the project
path directory does not exist. Add a log warning so the caller knows the write
was skipped. Resolves TD-CUST-012.
**Location:** `state-hub/api/routers/decisions.py:147196`
---
### T04 — Add priority and due_date filters to task list endpoint
```task
id: CUST-WP-0018-T04
status: done
priority: medium
state_hub_task_id: "18da9d84-54a4-4028-8b8e-014d2b2f6ed6"
```
Add `priority: str | None` and `due_date_before: date | None` query params to
`list_tasks()`. Resolves TD-CUST-007.
**Location:** `state-hub/api/routers/tasks.py:1430`
---
### T05 — Add owner and slug filters to workstream list endpoint
```task
id: CUST-WP-0018-T05
status: done
priority: medium
state_hub_task_id: "488f448f-396d-4924-98a5-a2e84d4b1b95"
```
Add `owner: str | None` and `slug: str | None` query params to
`list_workstreams()`. Resolves TD-CUST-008.
**Location:** `state-hub/api/routers/workstreams.py:1427`
---
### T06 — Add offset pagination to progress event list endpoint
```task
id: CUST-WP-0018-T06
status: done
priority: medium
state_hub_task_id: "dd7e9da8-19fb-4b02-a100-972c582dbaa9"
```
Add `offset: int = 0` query param to `list_progress()` alongside existing
`limit`. Resolves TD-CUST-010.
**Location:** `state-hub/api/routers/progress.py:1538`
---
### T07 — Low-severity cleanup bundle
```task
id: CUST-WP-0018-T07
status: done
priority: low
state_hub_task_id: "b949805b-dd3e-43d6-89cc-631e3183f67c"
```
Bundle of small fixes (each a few lines):
- **TD-CUST-009**: Document `decision_type` filter in OpenAPI schema for
`list_decisions()` consistently with other filter params.
- **TD-CUST-011**: Make `seed.py` upsert-based — update existing rows when
field values change (use `ON CONFLICT ... DO UPDATE`).
- **TD-CUST-016**: Normalise filter bar CSS class names across dashboard pages
(`filter-search` vs `filter-owner` → use `filter-text-input` everywhere).
- **TD-CUST-017**: Read CORS origins from `CORS_ORIGINS` env var (comma-separated),
defaulting to `localhost:3000`; remove hard-coded list from `main.py`.
- **TD-CUST-018**: Add a `ProgressEventCreate.detail` validator that at minimum
rejects non-dict values (dict-or-None is sufficient).
- **TD-CUST-019**: Add comment explaining the 30.5 avg-days-per-month constant
in `decisions.md:99`.
**Locations:** Various — see TD item descriptions for exact file:line refs.