Skip to content

TASK 1: Formalize run lifecycle as an explicit state machine - #322

Merged
sox8502 merged 5 commits into
alphaXiv:mainfrom
Trapa-Eureka:task-01-run-state-machine
Sep 14, 2026
Merged

sox8502 merged 5 commits into
alphaXiv:mainfrom
Trapa-Eureka:task-01-run-state-machine

Conversation

@Trapa-Eureka

@Trapa-Eureka Trapa-Eureka commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Run outcomes can no longer regress or be overwritten by stale callbacks. RunStatus defines the lifecycle once: Starting → Running → Done/Failed/Cancelled, with direct Starting → terminal transitions and terminal outcomes remaining fixed. Cancellation intent stays a separate flag.

Both update_status and conflicting upsert_run writes enforce the same transition rule. Late provider handles are retained, while rejected transitions preserve the winning outcome and failure explanation. Supervisors advance their cached status only after an accepted write and still deliver cancellation when a backward transition is rejected.

Part 1 of the split from #317. Current main is merged into this branch. Includes the approved, behavior-equivalent src/updates.rs boolean cleanup needed for Clippy.

Verification:

  • Rust formatting, Clippy, locked debug/release builds, source-build telemetry checks, and locked tests (830 passed, 2 ignored).
  • UI typecheck, localization/style checks, 162 UI tests, and 9 dev-slot tests.
  • Four independent read-only Claude reviews: no blockers.
  • Browser: successful completion (exit 0), deliberate failure (exit 7), and cancellation via Stop; checked logs and final states.
  • Browser: retry creates a new run; both cancellations persist across reload and both processes exit.
  • Regression coverage: every state pair through both writers, terminal outcome preservation, cancellation races, duplicate callbacks, and concurrent finalizers.
  • Windows CI build/test.
  • Live remote-provider smoke tests (browser scenarios used the isolated local backend).

* Formalize run lifecycle as an explicit state machine (TASK 1)

Runs previously carried status as a bare `String`, and `Store::update_status`
was an unconditional `UPDATE ... WHERE id = ?1` with no transition checks —
any caller could overwrite any status with any other status, and three
independent copies of `is_terminal` had to be kept in sync by hand.

Add `RunStatus` (store.rs): Starting -> Running -> {Done, Failed, Cancelled},
with Starting also able to jump straight to a terminal state. Terminal states
are absorbing and `RunStatus::can_transition_to` is the single source of
truth for legality.

`Store::update_status` now takes a typed `RunStatus` and derives its SQL
guard from `can_transition_to` (`WHERE status IN (<legal sources>)`) so the
check-and-write is atomic and can never drift from the documented table. It
returns `Result<bool>`: `false` means the transition was illegal (almost
always because the run was already terminal), which is an expected, benign
outcome rather than an error — a duplicate completion callback, or a
completion racing a cancellation, is now a no-op instead of a corruption.

Threaded the typed status through `jobs::stage_to_run_status`,
`supervise::run_status_for_stage`, and all 8 backend poll loops (HF, k8s,
Modal, ssh, Slurm, Ray, local, OpenResearch), replacing stringly-typed
statuses with compile-time-checked ones. Consolidated the three duplicate
`is_terminal` functions (local/mod.rs, commands/serve.rs, commands/up.rs)
into one canonical `store::is_terminal_status`.

Added deterministic tests covering the doc's listed scenarios: the full
transition matrix, duplicate terminal writes, completion arriving after
cancellation, invalid backward transitions (running -> starting), cancel
while starting/running, retry-as-a-new-row, and concurrent writers (separate
SQLite connections racing to finalize the same run) settling on exactly one
terminal outcome.

No wire/schema changes — RunStatus::as_str() matches the existing stored
vocabulary exactly, so the UI and API are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FESDZN8TW7HzGCLnP6Y5vB

* chore: trigger CI now that Actions is enabled on the fork

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FESDZN8TW7HzGCLnP6Y5vB

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@Trapa-Eureka

Copy link
Copy Markdown
Contributor Author

Stack (review/merge in this order)

  1. TASK 1: Formalize run lifecycle as an explicit state machine #322 -- TASK 1: run lifecycle state machine
  2. TASK 2: Add crash recovery and run reconciliation #323 -- TASK 2: crash recovery and reconciliation
  3. TASK 3: Add SQLite concurrency/stress tests and fix transaction gaps #324 -- TASK 3: SQLite concurrency/stress tests
  4. TASK 4: Standardize compute backend contracts #325 -- TASK 4: compute backend contracts
  5. TASK 5: Add error classification for run failures #326 -- TASK 5: error classification
  6. TASK 6: Strengthen experiment provenance metadata #327 -- TASK 6: experiment provenance metadata
  7. TASK 7: Decompose App.tsx: extract self-contained hooks and pure helpers #328 -- TASK 7: App.tsx decomposition
  8. TASK 8: Split CLI schema/argument definitions by domain out of main.rs #329 -- TASK 8: CLI schema split
  9. TASK 9: Reduce generated frontend artifact review noise #330 -- TASK 9: generated artifact review noise
  10. TASK 10: Clarify and harden the remote security model #331 -- TASK 10: remote security hardening

Superseded #317 (the original single combined PR) -- closed in favor of this stack.

sox8502 and others added 4 commits September 14, 2026 13:25
Windows installs could never update themselves: the PowerShell installer's
receipt lives under %LOCALAPPDATA%, which orx did not look in, and the
installer cannot overwrite a running exe. orx now stages the release via the
installer beside orx.exe and swaps it in with two renames, parks the old
binary under a unique .old name, and rewrites the receipt version itself.

A zip-extracted orx.exe outside package-manager and cargo target paths is a
new "portable" channel that updates in place with no receipt. `orx up`
relaunches on Windows by spawning the new binary, which waits on the parent's
process handle before binding the port.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@sox8502
sox8502 merged commit 3a8b028 into alphaXiv:main Sep 14, 2026
11 checks passed
@myles332 myles332 mentioned this pull request Sep 14, 2026
4 tasks
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.

3 participants