HA: make command recovery restart-safe - #874
Conversation
🔐 Codex Security Review
Review SummaryOverall Risk: HIGH The restart recovery path can release per-device queue serialization while the prior device operation may still be running. Recovery also bypasses durable command-completion finalizers, leaving audit records incomplete after failover. Findings[HIGH] Restart reaping permits overlapping commands with unknown device operations
[MEDIUM] Recovery marks batches finished without running completion finalizers
NotesThe authoritative diff matched the stated commit range. No concrete authentication bypass, SQL injection, credential exposure, protobuf incompatibility, or pool-address substitution was found in the changed hunks. The added tests do not cover continued device-side execution during takeover or restoration of completion audit events after restart. Generated by Codex Security Review | |
6af7d4b to
2195fa0
Compare
d2d6861 to
9151ca5
Compare
- renew the Fleet lease immediately before activation cleanup - reopen rig-config reconciliation after takeover failures - keep takeover tests focused on durable state outcomes
- bulk insert command queue messages within the transaction bound - finish command batches after every enqueue failure - build the outstanding queue index concurrently
- block automatic reboot when queue ownership cannot be verified - preserve firmware status for reaped commands that were still pending
e2082a7 to
f7157d5
Compare
80e8ee4 to
9d32e0f
Compare
a7b9f6f to
ff3e92f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff3e92f1f8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
mcharles-square
left a comment
There was a problem hiding this comment.
Tradeoffs seems reasonable
1365c3a to
0911bf9
Compare
Reviewable diff: +649/-505 across 18 files (excludes generated, test, and story files).
Summary
Fleet now recovers command processing safely after an active-instance restart. Commands that never left the queue remain eligible for execution, while commands interrupted after dispatch began are failed because their device outcome is unknown. Ownership loss ends the active runtime instead of attempting an in-process demotion and reacquisition.
Standalone Fleet keeps its existing admission and graceful-shutdown behavior.
Stack
#875 is stacked directly on this PR and adds the production configuration that selects HA or standalone mode. This PR defines command recovery and active-runtime lifecycle behavior without changing how deployments enable HA.
How it works
Patroni selects the writable PostgreSQL server and publishes it through etcd. Fleet separately verifies that writer and acquires its database lease before it starts active-only work.
On startup or failover:
PENDINGrows because no device attempt has started. It changes interruptedPROCESSINGrows toFAILEDwith an unknown-outcome reason, then finishes affected terminal batches in bounded pages.PROCESSINGwork.On ownership loss, the coordinator cancels the active lifetime and returns a fatal error. The runtime closes admission, aborts active jobs, and returns that error to
fleetd. The deployment supervisor can then restart Fleet as a fresh passive candidate.Command writes use expected-state updates such as
PROCESSINGtoSUCCESS. A late result therefore affects zero rows after recovery has already failed that command and is treated as a benign lost race.Command enqueue uses one bounded transaction and a bulk insert. If the client sees an ambiguous enqueue error, Fleet checks whether the transaction committed and only fails the batch when it can prove no queue rows exist.
Recovered
DownloadLogsbatches no longer depend on the callback from the old process. Once the caller's organization-scoped batch is finished, the download endpoint lazily creates the missing log bundle from persisted results. First-time creation is serialized so concurrent requests reuse one complete bundle.flowchart LR DCS["etcd publishes Patroni primary"] --> OBS["Fleet verifies writer"] PG["Connected PostgreSQL identity"] --> OBS OBS --> LEASE["Acquire and renew Fleet lease"] LEASE --> REAP["Run startup reaper"] REAP --> PENDING["Keep PENDING queued"] REAP --> PROCESSING["Fail interrupted PROCESSING"] PENDING --> JOBS["Start active jobs"] PROCESSING --> JOBS JOBS --> GATE["Open request gate"]sequenceDiagram participant Old as Former active Fleet participant Lease as Fleet lease participant New as Replacement Fleet participant DB as Command tables participant Supervisor as Service supervisor Old->>Lease: Renewal fails Old->>Old: Close admission and abort active jobs Old->>Supervisor: Return fatal ownership error New->>Lease: Acquire ownership New->>DB: Fail PROCESSING and preserve PENDING New->>New: Start jobs and open admission Supervisor->>Old: Restart as passive candidateAreas of the code involved
server/internal/ha/server/internal/domain/command/PENDINGversusPROCESSINGpolicy and result consistencyserver/internal/infrastructure/queue/server/internal/infrastructure/files/, command service and handlerserver/sqlc/queries/server/internal/infrastructure/db/,runtimepolicy/server/generated/sqlc/and generated mocksKey technical decisions & trade-offs
PENDINGwork instead of failing all queued work, because no device attempt has started.PROCESSINGwork instead of replaying it, because Fleet cannot prove whether the device already acted.DownloadLogstype before accessing local files.PROCESSINGrow can release a later command before an already-issued request from the former process is provably quiesced. Device-side fencing or a quarantine and reconciliation workflow is out of scope for this PR.Testing & validation
fleetdtests passed.Post-Deploy Monitoring & Validation
active Fleet ownership ended,Interrupted by Fleet restart, command-service startup failures, and repeated Fleet process exits.PENDINGcounts fall after failover; interruptedPROCESSINGrows fail once; replacement workers resume dispatch.Follow-ups