Skip to content

feat(api): GET /graphs/{slug}/migrations endpoint (Phase 7 #59)#257

Merged
charlie83Gs merged 2 commits intomainfrom
feat/migrations-list-endpoint
Apr 20, 2026
Merged

feat(api): GET /graphs/{slug}/migrations endpoint (Phase 7 #59)#257
charlie83Gs merged 2 commits intomainfrom
feat/migrations-list-endpoint

Conversation

@charlie83Gs
Copy link
Copy Markdown
Contributor

Summary

Ships the read-only half of the migration-audit API. Returns rows from `graph_migration_runs` for the graph, newest-first, as `GraphMigrationRunResponse`. Backs the Phase 7 frontend history table (#61) and gives operators a debug path without needing Hatchet UI access.

Split from the companion `POST /graphs/{slug}/re-migrate` (also part of #59) because re-migrate needs `graph_migration_wf` to exist (#57) to dispatch against; this list endpoint only depends on the audit table (#6, already shipped).

Contract

  • `GET /api/v1/graphs/{slug}/migrations`
  • Requires `GRAPH_READ` — audit rows can imply when data transformations touched the graph.
  • Empty list when graph has never migrated (brand-new graphs at v1, any graph on a plugin still at `current_version=1`).
  • Ordered by `created_at` desc so the UI can render "most recent first" without client-side sorting.
  • Read-only banner on the UI continues to rely on `Graph.read_only_reason='migrating'` — this endpoint is for history + debug only, not the banner's source of truth.

Schema design

`status` is `str`, not `Literal`. The DB column is `VARCHAR(16)` with a CHECK constraint at the model layer; validation lives there. New states added later (e.g. `'cancelled'`) won't require a coordinated API + frontend ship.

Test plan

  • 3 new tests in `test_graph_schemas.py::TestGraphMigrationRunResponse`: minimum-field construction, full round-trip including error/workflow_run_id, status stays open-ended
  • `services/api`: 83 unit tests pass
  • `ruff format` + pre-push hooks clean

🤖 Generated with Claude Code

Ships the read-only half of the migration-audit API. Reads the
graph_migration_runs table for this graph, returns rows newest-first
as GraphMigrationRunResponse. Backs the frontend history table (#61)
and gives operators a debug path without needing Hatchet UI access.

Split from the re-migrate endpoint (also #59) because the re-migrate
needs graph_migration_wf to exist (Phase 7 #57) to dispatch against;
the list endpoint only needs the audit table (#6, already shipped).

- Requires GRAPH_READ permission — history can leak when data
  transformations touched the graph. Read banner on UI relies on
  Graph.read_only_reason='migrating' separately; this endpoint is
  for history + debugging only.
- Status is str, not Literal — new states added later (e.g.
  'cancelled') should not require a coordinated API + frontend ship.
  DB-layer CHECK constraint stays the source of truth.
- Empty list when graph has never migrated (brand-new graphs at v1,
  any graph on a plugin still at current_version=1).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… tests

- order_by adds GraphMigrationRun.id.desc() as secondary sort so rows
  inserted in the same clock tick (batch migration enqueueing multiple
  pending rows) sort deterministically. Load-bearing for future cursor
  pagination.
- ?limit query param clamps response size to keep long-lived graphs
  on many-version plugins from returning unbounded audit history.
  Default 100, server-side max 500 — beyond max returns 422.
- 8 new HTTP-level integration tests in
  test_migrations_list_endpoint.py: 404 unknown slug, empty list for
  never-migrated graph, newest-first ordering, id-desc tie-break,
  cross-graph scoping (rows don't leak), limit clamps, limit=0 → 422,
  limit>max → 422. Mirror the fixture pattern from
  test_graph_read_only_endpoint.py.
- Status comment dropped; docstring now points at
  kt_db.models.GraphMigrationRun for the authoritative status set +
  DB CHECK constraint so the enum doesn't drift between sites.
- Schema tests: datetime import moved to module level (no more inline
  from datetime imports + __import__ hack).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@charlie83Gs
Copy link
Copy Markdown
Contributor Author

Review fixes applied

  • Tie-break on `id.desc()` — batch migrations enqueueing multiple pending rows in the same tick now sort deterministically. Load-bearing for future cursor pagination.
  • `?limit` query param — clamps response size. Default 100, server-side max 500 (values outside return 422). Keeps long-lived graphs on many-version plugins from returning unbounded audit history.
  • 8 new HTTP-level integration tests in `test_migrations_list_endpoint.py`: 404 on unknown slug, empty list for never-migrated graph, newest-first ordering, id-desc tie-break, cross-graph scoping (rows don't leak), limit clamps, limit=0 → 422, limit>max → 422. Mirrors fixture pattern from `test_graph_read_only_endpoint.py`.
  • Status comment dropped — docstring now points at `kt_db.models.GraphMigrationRun` for the authoritative status set + DB CHECK, so the enum definition doesn't drift between sites.
  • datetime imports — moved to module level; the `import("datetime").datetime.now()` hack is gone.

Kept `graph_id` in the response: dropping it is technically payload-smaller but some frontend list consumers key by it when rendering multiple graphs' history side-by-side. Low-value drop; parking until a consumer actually needs the savings.

Error-field scrub (re: internal paths/traceback leaks) remains a worker (#57) concern per your note.

@charlie83Gs charlie83Gs merged commit c02ffc9 into main Apr 20, 2026
8 checks passed
@charlie83Gs charlie83Gs deleted the feat/migrations-list-endpoint branch April 20, 2026 19:32
@github-actions
Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant