Skip to content

feat(scheduler): lease-free Job domain model with verified state machine - #14

Merged
JeancarloBarrios merged 11 commits into
mainfrom
feat/local-job
Jun 2, 2026
Merged

feat(scheduler): lease-free Job domain model with verified state machine#14
JeancarloBarrios merged 11 commits into
mainfrom
feat/local-job

Conversation

@JeancarloBarrios

Copy link
Copy Markdown
Contributor

Slice 1 of the local scheduler: the lease-free Job aggregate and its value objects.

  • Value objects: Priority/Deadline/ReadyAt, JobId/TargetRef/AttemptNumber.
  • Job entity + JobStatus six-state machine with guarded transitions and atomic retry.
  • Tested four ways: exhaustive transition table, proptest invariants, a Kani proof of retry-overflow atomicity, and compiler-enforced match exhaustiveness.
  • Hermetic Kani via nix + just verify / just kani-setup recipes.

- Add JobId and TargetRef: non-empty newtypes with fallible constructors
  (reject empty -> EmptyJobId / EmptyTargetRef). TargetRef stays opaque
  (no Ord); JobId keeps Ord as the identity type.
- Harden Priority/Deadline/ReadyAt: const fn constructors and accessors,
  #[must_use] getters.
- Re-export JobId/TargetRef from the domain module.
- Unit tests for every invariant: empty rejection, value round-trips,
  priority MIN/MAX/DEFAULT and ordering direction, JobId ordering (13 tests).
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8915c628-1e96-41d5-9e3a-8dd63c79f2bf

📥 Commits

Reviewing files that changed from the base of the PR and between af65106 and 54340f1.

📒 Files selected for processing (1)
  • crates/invariant-scheduler/src/domain/job.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added verify and kani-setup commands to run Kani proofs and initialize the Kani toolchain from the dev shell.
  • Testing

    • Expanded unit, property, and Kani proof harnesses to validate job state transitions, retry semantics, and overflow behavior.
  • Chores

    • Hardened job domain model (typed identifiers, attempt tracking, strict transitions, new illegal-transition error) and added hermetic Kani provisioning plus cfg whitelist for proofs.
  • Documentation

    • Improved time-related API docs and crate-level documentation.

Walkthrough

Adds typed job domain types (ids, priority/deadline/readiness, attempt counting, status, Job with six transitions), exhaustive unit/property tests and a Kani proof for retry atomicity, Cargo test/tooling config, hermetic Kani provisioning in Nix, and Just recipes to run/setup Kani inside the dev shell.

Changes

Job domain model and formal verification

Layer / File(s) Summary
Priority, Deadline, ReadyAt and SchedulerTime docs
crates/invariant-scheduler/src/domain/priority.rs, crates/invariant-scheduler/src/domain/time.rs, crates/invariant-scheduler/src/lib.rs
Adds Priority(u8) with MIN/MAX/DEFAULT, Deadline and ReadyAt wrappers around SchedulerTime, unit tests, and expanded SchedulerTime and crate doc comments.
Illegal transition error
crates/invariant-scheduler/src/domain/error.rs
Adds DomainError::IllegalTransition, a Display arm rendering "illegal job state transition", and updates the display-stability test.
Job types and state machine
crates/invariant-scheduler/src/domain/job.rs, crates/invariant-scheduler/src/domain/mod.rs
Adds JobId/TargetRef (non-empty validated), AttemptNumber with checked_next() mapping overflow to AttemptOverflow, JobStatus with is_terminal(), and Job with transitions start, complete, fail, cancel, exhaust, retry; module re-exports updated to expose new types.
Tests, proptests, and Kani proof
crates/invariant-scheduler/src/domain/job.rs (tests/proptest/kani), crates/invariant-scheduler/Cargo.toml
Adds exhaustive unit tests including transition table checks, proptest property tests, a Kani proof module verifying retry atomicity, and Cargo config registering cfg(kani) and proptest dev-dep.
Hermetic Kani toolchain and Just recipes
flake.nix, Justfile
flake.nix adds kaniVerifier and kaniHome derivations, patches bundled Kani binaries on Linux, includes the verifier in devShells.default, and sets KANI_HOME; Justfile adds kani-setup and verify recipes that run Kani commands inside nix develop.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Job
  participant AttemptNumber
  participant Verification
  Caller->>Job: retry()
  Job->>AttemptNumber: checked_next()
  AttemptNumber-->>Job: Ok(next) or Err(AttemptOverflow)
  alt increment succeeds
    Job->>Job: set attempt to next
    Job->>Job: set status = Queued
    Job-->>Caller: Ok(())
  else overflow
    Job-->>Caller: Err(AttemptOverflow)
  end
  Caller->>Verification: run tests and Kani proof
  Verification->>Job: assert retry atomicity
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • ybird-labs/invariant#12: Earlier PR touching job-domain primitives and lifecycle behavior that overlaps conceptually with the new Job types and transitions.

Poem

🐰
I stitched the Job with id and try,
States march and counters climb high,
Kani peers through symbolic night,
Ensuring retry stays atomic and right,
In Nixy shells we proof by light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(scheduler): lease-free Job domain model with verified state machine' accurately reflects the main changes: introduction of a Job domain model with state machine verification, which is the primary focus across multiple files.
Description check ✅ Passed The description is directly related to the changeset, mentioning value objects, Job entity with state machine, testing approaches, and Kani setup—all present in the actual changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/local-job

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

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/invariant-scheduler/src/domain/priority.rs`:
- Around line 55-70: Remove ordering derives from ReadyAt to enforce the "not an
ordering/sort key" invariant: edit the ReadyAt definition to drop PartialOrd and
Ord from the #[derive(...)] list so it only implements Debug, Clone, Copy,
PartialEq, Eq, Hash; then search for any code comparing ReadyAt values and
replace those comparisons to compare ReadyAt.time() (SchedulerTime) instead.
Ensure ReadyAt::new and ReadyAt::time() remain unchanged.

In `@flake.nix`:
- Around line 144-149: The kaniBundleHash map currently contains x86_64-darwin =
null which will cause kaniBundleHash.${system} to evaluate to null and make
downstream fetchurl fail; update the kaniBundleHash handling in the flake to
either (A) provide the real prefetched sha256 for x86_64-darwin if that platform
should be supported, or (B) replace the null entry with an explicit runtime
error for that key (e.g., throw an informative error when system ==
"x86_64-darwin") so evaluation fails with a clear message; locate the
kaniBundleHash definition and adjust the x86_64-darwin branch or the code that
uses kaniBundleHash.${system} accordingly.

In `@Justfile`:
- Around line 90-95: The comment above the kani-setup recipe is misleading
because with KANI_HOME set by the Nix dev shell the cargo kani setup step is
skipped; update the Justfile to either (A) change the comment for
_kani-setup/kani-setup to state that setup is a no-op under the hermetic
NIX/KANI_HOME dev shell (mention KANI_HOME and Nix dev shell behavior), or (B)
remove the kani-setup wrapper entirely if you want to avoid a no-op target,
leaving _kani-setup only for non-Nix usage; ensure references to the target
names _kani-setup and kani-setup remain accurate in any docs or CI that call
them.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 61485cb8-ed85-4ee6-a779-2806387f9990

📥 Commits

Reviewing files that changed from the base of the PR and between 8ffe247 and 23defb3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Justfile
  • crates/invariant-scheduler/Cargo.toml
  • crates/invariant-scheduler/src/domain/error.rs
  • crates/invariant-scheduler/src/domain/job.rs
  • crates/invariant-scheduler/src/domain/mod.rs
  • crates/invariant-scheduler/src/domain/priority.rs
  • flake.nix

Comment thread crates/invariant-scheduler/src/domain/priority.rs Outdated
Comment thread flake.nix
Comment thread Justfile Outdated

@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)
crates/invariant-scheduler/src/domain/mod.rs (1)

12-17: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep time public or treat this as a breaking API change.

Making time private removes the invariant_scheduler::domain::time::SchedulerTime path. The re-export keeps the type available, but downstream code using the old module path will stop compiling.

♻️ Minimal compatibility fix
-mod time;
+pub mod time;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/invariant-scheduler/src/domain/mod.rs` around lines 12 - 17, The
change made `mod time;` private which breaks the downstream path
invariant_scheduler::domain::time::SchedulerTime; restore compatibility by
making the module public (i.e., export the time module) so existing code can
still reference domain::time::SchedulerTime while keeping the existing pub use
time::SchedulerTime in place; update the declaration for the time module
(related symbol: time and the re-export SchedulerTime) to be exported rather
than private.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/invariant-scheduler/src/domain/job.rs`:
- Around line 65-76: The docstring for AttemptNumber is misleading: it describes
"how many times" a job has been attempted while AttemptNumber is a zero-based
ordinal that starts at AttemptNumber::zero() and is incremented on retry(); this
causes off-by-one confusion for callers. Update the docs on the AttemptNumber
type and its accessors (e.g., AttemptNumber::zero(), any raw/count accessor, and
the retry() method) to state that it is a zero-based attempt index/ordinal
(first attempt = 0) or provide a proper count accessor (e.g., attempts_count()
that returns ordinal + 1) and ensure any documentation at the other occurrence
(the methods referenced around the second mention) is corrected to match the
chosen semantics.

---

Outside diff comments:
In `@crates/invariant-scheduler/src/domain/mod.rs`:
- Around line 12-17: The change made `mod time;` private which breaks the
downstream path invariant_scheduler::domain::time::SchedulerTime; restore
compatibility by making the module public (i.e., export the time module) so
existing code can still reference domain::time::SchedulerTime while keeping the
existing pub use time::SchedulerTime in place; update the declaration for the
time module (related symbol: time and the re-export SchedulerTime) to be
exported rather than private.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3a544691-6fa6-43f8-be9d-07233218b226

📥 Commits

Reviewing files that changed from the base of the PR and between e345dbf and af65106.

📒 Files selected for processing (6)
  • crates/invariant-scheduler/src/domain/error.rs
  • crates/invariant-scheduler/src/domain/job.rs
  • crates/invariant-scheduler/src/domain/mod.rs
  • crates/invariant-scheduler/src/domain/priority.rs
  • crates/invariant-scheduler/src/domain/time.rs
  • crates/invariant-scheduler/src/lib.rs

Comment thread crates/invariant-scheduler/src/domain/job.rs Outdated
@JeancarloBarrios
JeancarloBarrios merged commit fb60261 into main Jun 2, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant