Skip to content

002 phase 1.1+1.2: Claim carries WorkerID; fencing calls take *Claim - #37

Merged
myzie merged 4 commits into
mainfrom
feat/worker-claim-fencing
Apr 12, 2026
Merged

002 phase 1.1+1.2: Claim carries WorkerID; fencing calls take *Claim#37
myzie merged 4 commits into
mainfrom
feat/worker-claim-fencing

Conversation

@myzie

@myzie myzie commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Phase 1.1 + 1.2 of planning/specs/002-worker-store-improvements.md. Also lands the spec itself so subsequent PRs in this chain can reference it on main.

  • Claim gains a WorkerID field populated by ClaimQueued.
  • QueueStore.Heartbeat and QueueStore.Complete take *Claim instead of a separate Lease. The Lease type is deleted.
  • postgres.Store.NewCheckpointer now takes *Claim.

Fencing is still (WorkerID, Attempt) — the change is purely about giving stores full access to run metadata (OrgID, WorkflowType, CreditCost, CallbackURL) without an out-of-band cache. This eliminates a common adapter pattern in consumer code.

Open question decisions

  • Q2 (Lease type fate): delete. Went with the spec's lean recommendation. Nothing references Lease internally anymore.

Drive-by fix

TestStore_* in the postgres submodule were passing worker.NewRun{ID: "..."} with a nil Spec, which violates the NOT NULL spec column. The tests were latent-broken because CI skips postgres tests without a DSN. Fixed by supplying an empty JSON spec blob in the affected tests.

Test plan

  • make test-all (root module)
  • go test ./... in experimental/worker
  • WORKFLOW_PG_DSN=... go test ./... in experimental/store/postgres against a real Postgres

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Switched queue/lease handling to a richer claim object (includes worker identity and attempt) and updated lifecycle and checkpointing flows to use it.
  • Tests
    • Updated store and worker tests to exercise the claim-based flow and fencing behaviors.
  • Chore
    • Added experimental modules to CI/test workflow.
  • Documentation
    • Added a spec outlining phased improvements to worker/store design and APIs.

myzie and others added 2 commits April 12, 2026 15:47
002-worker-store-improvements.md captures the 7-phase plan to reduce
the consumer glue-code tax on experimental/worker and
experimental/store/postgres. Tracks the work through tagging at v0.1.0.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…es 1.1+1.2)

Claim now records the worker that holds the lease, and QueueStore
fencing calls (Heartbeat, Complete) take *Claim directly instead of
a separate Lease value. Deletes the Lease type entirely, since it
added nothing once *Claim became the unit of currency.

This lets store implementations access the full run metadata
(OrgID, WorkflowType, CreditCost, CallbackURL) during fencing
operations without maintaining a separate runID -> metadata cache.
Postgres NewCheckpointer also takes *Claim now.

Pre-existing: TestStore_* in the postgres submodule were passing
NewRun with nil Spec, which violates the NOT NULL spec column. Those
tests were latent-broken because CI skips postgres tests without a
DSN. Fix them here by supplying an empty JSON spec blob.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 12, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d215e9e-4524-4854-a9a9-44b3ed1d05e5

📥 Commits

Reviewing files that changed from the base of the PR and between 5097714 and 2deef18.

📒 Files selected for processing (3)
  • Makefile
  • experimental/store/sqlite/checkpointer.go
  • experimental/store/sqlite/store.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • Makefile
  • experimental/store/sqlite/checkpointer.go

📝 Walkthrough

Walkthrough

Replaced use of worker.Lease with *worker.Claim across worker and store code paths, added WorkerID to Claim, removed Lease type, and updated checkpointers, queue heartbeat/complete, store implementations (Postgres/SQLite/memstore), tests, Makefile, and added a planning spec.

Changes

Cohort / File(s) Summary
Store Interface & Types
experimental/worker/queue_store.go
Removed Lease type; added WorkerID to Claim; updated QueueStore interface signatures to accept *Claim for Heartbeat and Complete.
Postgres Store Implementation
experimental/store/postgres/checkpointer.go, experimental/store/postgres/queue.go, experimental/store/postgres/store_test.go
NewCheckpointer, Heartbeat, Complete now accept/use *worker.Claim; SQL fencing/UPDATE parameters switched to claim.ID, claim.WorkerID, claim.Attempt; tests updated to use returned claim and assert WorkerID.
SQLite Store Implementation
experimental/store/sqlite/checkpointer.go, experimental/store/sqlite/queue.go, experimental/store/sqlite/store.go
Parallel changes to Postgres: checkpointer and queue methods now take *worker.Claim; ClaimQueued sets WorkerID; SQL parameter binding & error messages updated to use claim fields.
Memory Store Implementation
experimental/worker/memstore/memstore.go
Heartbeat and Complete signatures changed to accept *worker.Claim; in-memory fencing/ownership checks now use claim.ID, claim.WorkerID, claim.Attempt.
Worker Core
experimental/worker/worker.go
Worker passes the existing *Claim through heartbeat and completion paths instead of constructing a separate Lease; logging updated to use claim.ID/claim.Attempt.
Tests & Build
experimental/store/postgres/store_test.go, Makefile
Tests updated to use claims instead of constructed leases; Makefile adds EXPERIMENTAL_MODULES and test-experimental target and wires it into test-all.
Planning Documentation
planning/specs/002-worker-store-improvements.md
New spec describing phased roadmap for worker/store API consolidation, fencing semantics, handler execution redesign, and read-side run management.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,200,255,0.5)
    participant Worker
    end
    rect rgba(200,255,200,0.5)
    participant QueueStore
    end
    rect rgba(255,200,200,0.5)
    participant Database
    end

    Worker->>QueueStore: Heartbeat(ctx, claim *Claim)
    QueueStore->>Database: UPDATE runs SET claimed_by=claim.ID, attempt=claim.Attempt WHERE id=claim.ID AND claimed_by=claim.WorkerID AND attempt=claim.Attempt
    Database-->>QueueStore: RowsAffected (0 => ErrLeaseLost / >0 => OK)
    QueueStore-->>Worker: return nil or ErrLeaseLost

    Worker->>QueueStore: Complete(ctx, claim *Claim, outcome)
    QueueStore->>Database: UPDATE runs SET status=..., claimed_by=claim.ID, attempt=claim.Attempt WHERE id=claim.ID AND claimed_by=claim.WorkerID AND attempt=claim.Attempt
    Database-->>QueueStore: RowsAffected (0 => ErrLeaseLost / >0 => OK)
    QueueStore-->>Worker: return nil or ErrLeaseLost
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibbled at Leases, then swapped them for Claim,
Pawed WorkerID in place, now fence checks speak the same.
Heartbeats and checkpoints hop in tidy line,
One less struct to stash — carrot cake time! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately describes the main changes: adding WorkerID to Claim and updating fencing calls to use *Claim instead of Lease.

✏️ 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 feat/worker-claim-fencing

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

…tal submodules

The sqlite submodule (experimental/store/sqlite) was still referencing
worker.Lease, which is now deleted. Port its QueueStore impl and
NewCheckpointer to the *Claim shape so the module builds again.

Also extends make test-all to build/vet/test each experimental
submodule. The previous target only covered the root module, so
cross-submodule build breaks like this one were only visible once
you cd'd into the affected submodule. The postgres target auto-skips
its integration tests when WORKFLOW_PG_DSN is unset, so running
test-all in an unconfigured environment stays clean.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
planning/specs/002-worker-store-improvements.md (1)

542-546: Add language specifier to fenced code block.

The code block for git tag commands is missing a language specifier. Consider adding bash or shell for consistency with other code blocks in the document.

📝 Suggested fix
-```
+```bash
 git tag experimental/worker/v0.1.0
 git tag experimental/store/postgres/v0.1.0
 git push --tags
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @planning/specs/002-worker-store-improvements.md around lines 542 - 546, The
fenced code block containing the git commands (lines with "git tag
experimental/worker/v0.1.0", "git tag experimental/store/postgres/v0.1.0", and
"git push --tags") lacks a language specifier; update the opening fence to
include a shell language (e.g., add "bash" or "shell" after the backticks) so
the block becomes a labeled fenced code block for consistent syntax highlighting
with other snippets in the document.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @planning/specs/002-worker-store-improvements.md:

  • Around line 542-546: The fenced code block containing the git commands (lines
    with "git tag experimental/worker/v0.1.0", "git tag
    experimental/store/postgres/v0.1.0", and "git push --tags") lacks a language
    specifier; update the opening fence to include a shell language (e.g., add
    "bash" or "shell" after the backticks) so the block becomes a labeled fenced
    code block for consistent syntax highlighting with other snippets in the
    document.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `0b6d4a17-ed85-407c-8c20-8d9505088a1e`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b0300aa3bd49cac2b34e6a850292daadf7988172 and 8aa128429b274ce735762b4119f694de122c88b6.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `experimental/store/postgres/checkpointer.go`
* `experimental/store/postgres/queue.go`
* `experimental/store/postgres/store_test.go`
* `experimental/worker/memstore/memstore.go`
* `experimental/worker/queue_store.go`
* `experimental/worker/worker.go`
* `planning/specs/002-worker-store-improvements.md`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
experimental/store/sqlite/checkpointer.go (1)

77-86: ⚠️ Potential issue | 🟠 Major

Use executionID in DeleteCheckpoint (or reject mismatch).

DeleteCheckpoint currently ignores its executionID argument and always operates on c.claim.ID, which can hide caller mistakes.

Proposed fix
 func (c *leasedCheckpointer) DeleteCheckpoint(ctx context.Context, executionID string) error {
+	if executionID != c.claim.ID {
+		return fmt.Errorf("sqlite: checkpoint execution ID %q does not match claim run ID %q",
+			executionID, c.claim.ID)
+	}
 	result, err := c.store.db.ExecContext(ctx, `
 		UPDATE workflow_runs
 		SET checkpoint = NULL
 		WHERE id         = ?
 		  AND claimed_by = ?
 		  AND attempt    = ?
-	`, c.claim.ID, c.claim.WorkerID, c.claim.Attempt)
+	`, executionID, c.claim.WorkerID, c.claim.Attempt)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@experimental/store/sqlite/checkpointer.go` around lines 77 - 86,
DeleteCheckpoint ignores its executionID parameter and always uses c.claim.ID;
change DeleteCheckpoint (method on leasedCheckpointer) to use the provided
executionID in the SQL WHERE clause (or at minimum validate that executionID ==
c.claim.ID and return an error if they differ) and update associated error
messages to reference the executionID for clarity; locate the method
DeleteCheckpoint and adjust the ExecContext parameter and fmt.Errorf call to use
executionID (or add an explicit mismatch check at the start of DeleteCheckpoint
that returns a clear error when executionID != c.claim.ID).
🧹 Nitpick comments (1)
Makefile (1)

23-25: Consider adding all and clean phony targets if checkmake is CI-gated.

Static analysis reports minphony warnings for missing required phony targets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 23 - 25, The Makefile is missing required phony
targets causing checkmake minphony warnings; add explicit .PHONY entries for the
missing targets (e.g., add "all" and "clean") and implement minimal "all" and
"clean" targets (for example, make "all" depend on "test-all" or desired default
and "clean" remove build artifacts) and update the existing .PHONY line to
include all phony names (include test-all, test, test-experimental, all, clean)
so checkmake no longer reports minphony issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@experimental/store/sqlite/store.go`:
- Around line 62-63: NewCheckpointer currently returns a leasedCheckpointer with
a possibly nil claim, causing panics when SaveCheckpoint or DeleteCheckpoint
dereference c.claim; add a nil guard in NewCheckpointer: if claim == nil then
panic (matching New()'s invalid-input behavior) with a clear message, otherwise
return &leasedCheckpointer{store: s, claim: claim}; reference NewCheckpointer
and leasedCheckpointer (and the callers SaveCheckpoint/DeleteCheckpoint) when
applying the change.

---

Outside diff comments:
In `@experimental/store/sqlite/checkpointer.go`:
- Around line 77-86: DeleteCheckpoint ignores its executionID parameter and
always uses c.claim.ID; change DeleteCheckpoint (method on leasedCheckpointer)
to use the provided executionID in the SQL WHERE clause (or at minimum validate
that executionID == c.claim.ID and return an error if they differ) and update
associated error messages to reference the executionID for clarity; locate the
method DeleteCheckpoint and adjust the ExecContext parameter and fmt.Errorf call
to use executionID (or add an explicit mismatch check at the start of
DeleteCheckpoint that returns a clear error when executionID != c.claim.ID).

---

Nitpick comments:
In `@Makefile`:
- Around line 23-25: The Makefile is missing required phony targets causing
checkmake minphony warnings; add explicit .PHONY entries for the missing targets
(e.g., add "all" and "clean") and implement minimal "all" and "clean" targets
(for example, make "all" depend on "test-all" or desired default and "clean"
remove build artifacts) and update the existing .PHONY line to include all phony
names (include test-all, test, test-experimental, all, clean) so checkmake no
longer reports minphony issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0236b2a9-834e-4d84-8e08-dbca8bb3975e

📥 Commits

Reviewing files that changed from the base of the PR and between 8aa1284 and 5097714.

📒 Files selected for processing (4)
  • Makefile
  • experimental/store/sqlite/checkpointer.go
  • experimental/store/sqlite/queue.go
  • experimental/store/sqlite/store.go

Comment thread experimental/store/sqlite/store.go
NewCheckpointer now panics on a nil claim instead of deferring the
nil-deref to the first Save/DeleteCheckpoint call. DeleteCheckpoint
validates that its executionID argument matches the claim's run ID
and uses it in both the SQL binding and error messages. Makefile
gains conventional all/clean targets with a consolidated .PHONY list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@myzie
myzie merged commit 7a86601 into main Apr 12, 2026
2 checks passed
@myzie
myzie deleted the feat/worker-claim-fencing branch April 12, 2026 20:14
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