Skip to content

fix: PR #38 follow-ups — upgrade path, DeleteRun race, runquery lift - #39

Merged
myzie merged 2 commits into
mainfrom
pr38-followups
Apr 13, 2026
Merged

fix: PR #38 follow-ups — upgrade path, DeleteRun race, runquery lift#39
myzie merged 2 commits into
mainfrom
pr38-followups

Conversation

@myzie

@myzie myzie commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses the critical review on #38. Stacked on top of feat/worker-pgstore-v2 so it merges once the base PR ships.

Must-fix

  • schema.sql upgrade path: add UPDATE workflow_runs SET org_id/initiated_by = NULL WHERE = '' so pre-existing v0.0.3 single-tenant rows stay visible to the new read API after Migrate(). Also drop NOT NULL on initiated_by (was only dropped on org_id), and rewrite the upgrade comment to name the v0.0.3 → v0.0.4 boundary explicitly.
  • DeleteRun TOCTOU: collapse into a single DELETE … WHERE status <> 'running' RETURNING status. The destructive path is now one atomic statement; a follow-up probe only fires to pick between ErrRunNotFound and ErrCannotDeleteRunning for the error message, never to gate the delete.

Should-fix

  • Lift run types to a neutral package: new experimental/worker/runquery (stdlib-only) holds Run, RunFilter, RunCursor, ErrRunNotFound, ErrCannotDeleteRunning, and the Store interface. Postgres aliases the types and satisfies runquery.Store at compile time. Dashboards can now depend on runquery instead of importing experimental/store/postgres.
  • sqlite ReclaimStale parity: also reset started_at = NULL, matching Postgres.
  • HandlerStores / HandlerContext godoc: make it explicit that only Checkpointer is lease-fenced. ProgressStore / ActivityLogger accept *Claim for API symmetry and typically ignore it; SignalStore is shared across claims and intentionally lives on Config, not the factory.

Nice-to-have

  • Rename QueueStore.ListFailedWithCreditsListRefundPending across worker, memstore, postgres, sqlite, and the integration test.
  • sqlite coexistence doc: spell out that SQLite has no schema namespacing escape hatch; consumers who need coexistence should hand the library a dedicated *sql.DB.

New tests

  • TestStore_UpgradeFromV003 — seeds a v0.0.3-shaped workflow_runs table (org_id NOT NULL DEFAULT '', no project_id / parent_run_id / metadata), inserts a populated row, runs Migrate, and asserts the row is still findable via GetRun / ListRuns. This catches the exact regression the review flagged.
  • TestStore_ClaimQueuedConcurrent — 8 goroutines race to claim a 20-run queue; every run must be claimed exactly once across all workers.

Test plan

  • make test-all at repo root — all modules build, vet clean, existing tests pass
  • go vet cleanly across root, experimental/worker, experimental/store/postgres, experimental/store/sqlite
  • Run postgres integration tests against a real DB: WORKFLOW_PG_DSN=… go test ./experimental/store/postgres/… — new TestStore_UpgradeFromV003 and TestStore_ClaimQueuedConcurrent in particular

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new workflow run query package providing a unified, backend-neutral API for accessing run data across different storage implementations.
  • Improvements

    • Enhanced refund handling with improved method naming and consistency across storage backends.
    • Improved schema migration support for multi-tenant compatibility.
    • Added clarification on SQLite table coexistence strategies and handler store lease-fencing behavior.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR introduces a backend-neutral read API package (runquery) with types for querying workflow runs, refactors PostgreSQL stores to use these types via aliasing, renames the ListFailedWithCredits method to ListRefundPending across all store implementations, updates the v0.0.3 → v0.0.4 schema migration to handle initiated_by field nullification, and adds integration tests for concurrent claiming and schema upgrades.

Changes

Cohort / File(s) Summary
New runquery package
experimental/worker/runquery/runquery.go
Introduces backend-neutral read API: exported sentinel errors (ErrRunNotFound, ErrCannotDeleteRunning), data types (Run, RunFilter, RunCursor), and Store interface with GetRun, ListRuns, CountRuns, DeleteRun methods.
Queue method renaming
experimental/store/postgres/queue.go, experimental/store/sqlite/queue.go, experimental/worker/memstore/memstore.go, experimental/worker/queue_store.go
Renamed ListFailedWithCredits to ListRefundPending with updated documentation; memstore implementation changed sorting/limiting logic to sort by completedAt ascending before applying limit.
Postgres runs refactoring
experimental/store/postgres/runs.go
Converted local Run, RunFilter, RunCursor types and ErrRunNotFound, ErrCannotDeleteRunning errors to aliases of runquery types; refactored DeleteRun to use atomic DELETE ... RETURNING pattern; extracted common SQL column projection into runProjection constant.
Postgres integration tests
experimental/store/postgres/integration_test.go
Renamed TestStore_ListFailedWithCredits to TestStore_ListRefundPending; added TestStore_UpgradeFromV003 to verify schema migration preserves legacy single-tenant runs; added TestStore_ClaimQueuedConcurrent to assert concurrent claim exclusivity.
Schema migration
experimental/store/postgres/schema.sql
Updated v0.0.3 → v0.0.4 migration to drop NOT NULL constraint and default for initiated_by, with UPDATE statements converting empty string rows to NULL.
Worker subsystems
experimental/worker/subsystems.go, experimental/worker/credits.go
Updated reconcileCredits to call ListRefundPending instead of ListFailedWithCredits; updated CreditStore documentation comment.
Documentation and configuration
experimental/worker/handler.go, experimental/store/sqlite/store.go
Clarified lease-fencing behavior for handler stores; documented SQLite coexistence strategy with consumer tables and ATTACH DATABASE guidance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through schemas neat,
Refactoring runs, a tidy feat!
From credits lost to pending refunds bright,
New queries shine with runquery light. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the PR's main changes: addressing follow-ups from PR #38 with three key areas (upgrade path, DeleteRun race condition fix, and runquery abstraction).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr38-followups

Comment @coderabbitai help to get the list of available commands and usage tips.

Base automatically changed from feat/worker-pgstore-v2 to main April 13, 2026 01:53
myzie and others added 2 commits April 12, 2026 21:54
Addresses the critical review on #38:

- schema.sql: add UPDATE workflow_runs SET org_id/initiated_by = NULL
  WHERE = '' so single-tenant rows migrated from v0.0.3 stay visible
  to the new read API (GetRun, ListRuns), and drop NOT NULL on
  initiated_by alongside org_id. Expand the upgrade-path comment to
  name v0.0.3 -> v0.0.4 explicitly.
- DeleteRun: collapse into one atomic DELETE ... WHERE status <>
  'running' RETURNING status; a post-delete probe only runs to pick
  between ErrRunNotFound and ErrCannotDeleteRunning for the error
  message, not to gate the destructive path.
- experimental/worker/runquery: new stdlib-only package holding Run,
  RunFilter, RunCursor, ErrRunNotFound, ErrCannotDeleteRunning, and
  the Store interface. Postgres now aliases the runquery types and
  satisfies runquery.Store at compile time so dashboards can import
  the neutral package instead of experimental/store/postgres.
- sqlite ReclaimStale: also clear started_at, matching Postgres.
- sqlite store.go: document that SQLite has no schema namespacing;
  consumers who need coexistence should hand the library a dedicated
  *sql.DB.
- HandlerContext/HandlerStores: doc comment clarifying only
  Checkpointer is lease-fenced. ProgressStore and ActivityLogger
  accept *Claim for symmetry and typically ignore it; SignalStore is
  shared across claims and intentionally lives on Config, not the
  factory.
- Rename QueueStore.ListFailedWithCredits -> ListRefundPending in
  worker, memstore, postgres, sqlite, and the integration test.
- Add TestStore_UpgradeFromV003: seeds a v0.0.3-shaped table with a
  populated row, runs Migrate, and asserts the row is still findable
  via the new read API. Catches the very regression the review
  flagged.
- Add TestStore_ClaimQueuedConcurrent: 8 goroutines race to claim a
  20-run queue; every run must be claimed exactly once.

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

- memstore.ListRefundPending: collect every match, sort by completed_at
  ASC NULLS LAST then ID, then apply the limit. The previous loop
  applied the limit while iterating an unordered map, then sorted the
  truncated set, so the returned page was nondeterministic.
- postgres/runs.go: extract the GetRun/ListRuns column projection into
  a single runProjection constant kept in lock-step with scanRun. Adding
  or reordering a column now touches one place, not two.
- postgres/integration_test.go: replace the for-range-with-blanks queue
  drain with an explicit "claim until empty" loop. Same effect, the
  intent is on the page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@myzie
myzie merged commit 8d39f63 into main Apr 13, 2026
1 of 2 checks passed
@myzie
myzie deleted the pr38-followups branch April 13, 2026 01:58
myzie added a commit that referenced this pull request Apr 13, 2026
Reflect the changes from PRs #37-#39: richer Claim fields, HandlerContext
with the only-Checkpointer-is-fenced caveat, the runquery subpackage,
configurable Postgres schema via WithSchema, atomic DeleteRun, the
v0.0.3 single-tenant upgrade carry-forward, and the SQLite coexistence
note. Lighter tone throughout; replaced the "Going to production"
section with a friendlier "Bring your own storage" framing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
myzie added a commit that referenced this pull request Apr 13, 2026
…40)

Reflect the changes from PRs #37-#39: richer Claim fields, HandlerContext
with the only-Checkpointer-is-fenced caveat, the runquery subpackage,
configurable Postgres schema via WithSchema, atomic DeleteRun, the
v0.0.3 single-tenant upgrade carry-forward, and the SQLite coexistence
note. Lighter tone throughout; replaced the "Going to production"
section with a friendlier "Bring your own storage" framing.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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