Skip to content

api: replace dict[str, Any] returns from admin_service.py with EtlRunOut Pydantic model #65

Description

@countercheck

Source: codebase audit, 2026-06-11 (audit finding F11, Tier 3)
Severity: Low
Category: Type safety / API contract

Problem

`list_runs` and `get_run` return `dict[str, Any]` from psycopg's `dict_row` factory. The shape comes straight out of the SQL.

Consequences:

  • A column rename in `ops.etl_runs` (or a future view change) silently alters the JSON shape the admin UI consumes.
  • No type-checked contract between the service layer and the router that exposes `/admin/etl/runs`.
  • The frontend's `EtlRun` TypeScript type is the only thing pinning the shape, and it's hand-maintained.

Suggested approach

Add a Pydantic model in `api/etl/admin_service.py` (or a colocated `schemas.py`):

```python
class EtlRunOut(BaseModel):
run_id: UUID
started_at: datetime
completed_at: datetime | None
status: Literal['running', 'success', 'failed']
source_row_counts: dict[str, int]
mart_row_counts: dict[str, int] | None
dbt_version: str | None
git_sha: str | None
```

Convert the `dict_row` rows via `EtlRunOut.model_validate(row)` in `list_runs` / `get_run`. Update the router to declare `response_model=list[EtlRunOut]`.

Verification

`uv run mypy api/` and `uv run pytest` clean; the admin UI continues to render the runs page.

Priority

Defensive — no current bug. Worth bundling into another touch of `admin_service.py`.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions