feat(workplans): CUST-WP-0018/0019/0020 — API hardening, dashboard UX polish, test suite
Consolidates all open technical debt into three workplans: - CUST-WP-0018: API hardening & code quality (TD-006–019 medium/high items) - CUST-WP-0019: Dashboard UX polish (Repos nav restructure, config.js cleanup, todo filter fix for new suggestion workflow statuses) - CUST-WP-0020: pytest test suite with real DB (TD-014) Also fixes todo.md Suggestions filter: was checking status===open but new suggestions enter with status=submitted; now excludes terminal statuses only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,8 @@ const todoState = (async function*() {
|
||||
domain: wsMap[t.workstream_id]?.domain ?? "unknown",
|
||||
}));
|
||||
contribs = contribList;
|
||||
improvements = ri.ok ? (await ri.json()).filter(t => t.debt_type === "dashboard-improvement" && t.status === "open") : [];
|
||||
const CLOSED = new Set(["finished", "wont_fix", "resolved", "deferred"]);
|
||||
improvements = ri.ok ? (await ri.json()).filter(t => t.debt_type === "dashboard-improvement" && !CLOSED.has(t.status)) : [];
|
||||
}
|
||||
} catch {}
|
||||
yield {tasks, contribs, improvements, ok, ts: new Date()};
|
||||
|
||||
163
workplans/CUST-WP-0018-api-hardening-code-quality.md
Normal file
163
workplans/CUST-WP-0018-api-hardening-code-quality.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
id: CUST-WP-0018
|
||||
type: workplan
|
||||
title: "State Hub — API Hardening & Code Quality"
|
||||
domain: custodian
|
||||
status: active
|
||||
owner: custodian
|
||||
topic_slug: custodian
|
||||
created: "2026-03-18"
|
||||
updated: "2026-03-18"
|
||||
---
|
||||
|
||||
# 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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
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:34–69`
|
||||
|
||||
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: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`_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:147–196`
|
||||
|
||||
---
|
||||
|
||||
### T04 — Add priority and due_date filters to task list endpoint
|
||||
|
||||
```task
|
||||
id: CUST-WP-0018-T04
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
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:14–30`
|
||||
|
||||
---
|
||||
|
||||
### T05 — Add owner and slug filters to workstream list endpoint
|
||||
|
||||
```task
|
||||
id: CUST-WP-0018-T05
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Add `owner: str | None` and `slug: str | None` query params to
|
||||
`list_workstreams()`. Resolves TD-CUST-008.
|
||||
|
||||
**Location:** `state-hub/api/routers/workstreams.py:14–27`
|
||||
|
||||
---
|
||||
|
||||
### T06 — Add offset pagination to progress event list endpoint
|
||||
|
||||
```task
|
||||
id: CUST-WP-0018-T06
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Add `offset: int = 0` query param to `list_progress()` alongside existing
|
||||
`limit`. Resolves TD-CUST-010.
|
||||
|
||||
**Location:** `state-hub/api/routers/progress.py:15–38`
|
||||
|
||||
---
|
||||
|
||||
### T07 — Low-severity cleanup bundle
|
||||
|
||||
```task
|
||||
id: CUST-WP-0018-T07
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
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.
|
||||
92
workplans/CUST-WP-0019-dashboard-ux-polish.md
Normal file
92
workplans/CUST-WP-0019-dashboard-ux-polish.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
id: CUST-WP-0019
|
||||
type: workplan
|
||||
title: "State Hub Dashboard — UX Polish"
|
||||
domain: custodian
|
||||
status: active
|
||||
owner: custodian
|
||||
topic_slug: custodian
|
||||
created: "2026-03-18"
|
||||
updated: "2026-03-18"
|
||||
---
|
||||
|
||||
# State Hub Dashboard — UX Polish
|
||||
|
||||
## Summary
|
||||
|
||||
Targeted UX improvements to the Observable Framework dashboard. These are
|
||||
all small, self-contained changes with high visible impact. Tasks are ordered
|
||||
by priority; each is independently deliverable.
|
||||
|
||||
## Sources
|
||||
|
||||
- Submitted UI suggestion `c2fc284a` (Repos nav restructure)
|
||||
- TD-CUST-002 residual (remaining inline API/POLL in non-main pages)
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### T01 — Repos nav: rename to "Repository" section with sub-pages
|
||||
|
||||
```task
|
||||
id: CUST-WP-0019-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
User suggestion: rename the "Repos" nav link to "Repository" and restructure
|
||||
it as a collapsible section containing the three repo-related pages as
|
||||
sub-items.
|
||||
|
||||
**Location:** `state-hub/dashboard/observablehq.config.js` (pages array)
|
||||
|
||||
Target nav structure:
|
||||
```
|
||||
Repository
|
||||
├── Repo Sync (/repos)
|
||||
├── SBOM (/sbom)
|
||||
└── Tech Debt (/techdept)
|
||||
```
|
||||
|
||||
Implementation: in `observablehq.config.js` replace the flat `Repos` page
|
||||
entry with a section group containing three children. Update any hardcoded
|
||||
`/repos` or `/sbom` or `/techdept` links in other pages if they break.
|
||||
|
||||
---
|
||||
|
||||
### T02 — Centralize remaining inline API/POLL constants
|
||||
|
||||
```task
|
||||
id: CUST-WP-0019-T02
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
TD-CUST-002 residual: `policy/workstream-dod.md` still has a hard-coded
|
||||
`const API = "http://127.0.0.1:8000"`. Migrate to import from `config.js`.
|
||||
(Note: `contributions.md` and `goals.md` use intentionally different POLL
|
||||
values — leave as-is or add named exports like `POLL_SLOW = 30_000` to
|
||||
`config.js` if preferred.)
|
||||
|
||||
**Location:** `state-hub/dashboard/src/policy/workstream-dod.md:6`
|
||||
|
||||
---
|
||||
|
||||
### T03 — UI Feedback page: mark suggestion as submitted in todo filter
|
||||
|
||||
```task
|
||||
id: CUST-WP-0019-T03
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
`todo.md` Suggestions section filters with `t.status === "open"` but new
|
||||
suggestions now enter with `status: "submitted"` (workflow redesign from
|
||||
CUST-WP session). Update filter to include `submitted`, `analyse`, `plan`,
|
||||
`implement`, `test`, `review` — i.e. any non-terminal status.
|
||||
|
||||
**Location:** `state-hub/dashboard/src/todo.md:41`
|
||||
|
||||
Current: `.filter(t => t.debt_type === "dashboard-improvement" && t.status === "open")`
|
||||
Target: `.filter(t => t.debt_type === "dashboard-improvement" && !["finished","wont_fix","resolved","deferred"].includes(t.status))`
|
||||
109
workplans/CUST-WP-0020-test-suite.md
Normal file
109
workplans/CUST-WP-0020-test-suite.md
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
id: CUST-WP-0020
|
||||
type: workplan
|
||||
title: "State Hub — pytest Test Suite"
|
||||
domain: custodian
|
||||
status: active
|
||||
owner: custodian
|
||||
topic_slug: custodian
|
||||
created: "2026-03-18"
|
||||
updated: "2026-03-18"
|
||||
---
|
||||
|
||||
# State Hub — pytest Test Suite
|
||||
|
||||
## Summary
|
||||
|
||||
The API, MCP server, seed script, and migration scripts currently have zero
|
||||
automated test coverage (TD-CUST-014). Regressions are caught at runtime.
|
||||
This workplan introduces a pytest-asyncio test suite using HTTPX's async test
|
||||
client against a real test database (no mocking — see ADR below).
|
||||
|
||||
## ADR
|
||||
|
||||
Do NOT mock the database in these tests. The state-hub has had past incidents
|
||||
where mocked tests passed but the real DB diverged (see memory: feedback on
|
||||
not mocking the database). Use a real PostgreSQL test database via
|
||||
`TEST_DATABASE_URL` env var (docker-compose already provides postgres).
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### T01 — Test infrastructure: pytest-asyncio + async HTTP client + fixtures
|
||||
|
||||
```task
|
||||
id: CUST-WP-0020-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
- Add `pytest`, `pytest-asyncio`, `httpx` to `state-hub/pyproject.toml` dev deps
|
||||
- Create `state-hub/tests/conftest.py`:
|
||||
- `engine` fixture: creates a fresh async engine against `TEST_DATABASE_URL`
|
||||
- `db` fixture: runs `alembic upgrade head` then `alembic downgrade base`
|
||||
around each test session (or truncates tables between tests)
|
||||
- `client` fixture: `httpx.AsyncClient` pointed at the test app instance
|
||||
- Create `state-hub/tests/__init__.py`
|
||||
- Add `make test` target in `state-hub/Makefile`
|
||||
|
||||
---
|
||||
|
||||
### T02 — Core router tests: topics, workstreams, tasks, decisions
|
||||
|
||||
```task
|
||||
id: CUST-WP-0020-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Coverage targets (happy path + key error cases):
|
||||
- `POST /topics/` → 201, duplicate slug → 409
|
||||
- `POST /workstreams/` + `GET /workstreams/?topic_id=` + status transitions
|
||||
- `POST /tasks/` + `GET /tasks/?workstream_id=` + `needs_human` flag
|
||||
- `POST /decisions/` + `PATCH /{id}/resolve/`
|
||||
- `GET /state/summary` → returns expected shape with counts
|
||||
|
||||
---
|
||||
|
||||
### T03 — TD, EP, contributions router tests
|
||||
|
||||
```task
|
||||
id: CUST-WP-0020-T03
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
- TD CRUD + workflow status transitions + notes endpoints
|
||||
- EP CRUD + status transitions
|
||||
- Contribution lifecycle guard (invalid transitions → 422)
|
||||
- SBOM ingest + `/sbom/snapshots/` aggregation
|
||||
|
||||
---
|
||||
|
||||
### T04 — MCP server smoke tests
|
||||
|
||||
```task
|
||||
id: CUST-WP-0020-T04
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
For each MCP tool, call the underlying HTTP helper against the test client
|
||||
and assert the result shape. No need to test the MCP stdio protocol itself —
|
||||
just the HTTP-level correctness. Focus on: `get_state_summary`,
|
||||
`create_task`, `update_task_status`, `add_progress_event`, `flag_for_human`.
|
||||
|
||||
---
|
||||
|
||||
### T05 — CI: add pytest to pre-commit or Makefile gate
|
||||
|
||||
```task
|
||||
id: CUST-WP-0020-T05
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
Add `make test` as a step in any existing CI pipeline, or document the
|
||||
manual pre-push gate in CLAUDE.md. Ensure `TEST_DATABASE_URL` is set to the
|
||||
docker-compose postgres instance in the local dev workflow.
|
||||
Reference in New Issue
Block a user