feat: Persist and serve fleet roster order - #3200
Conversation
Add an id-based, hub-scoped fleet reorder API (PUT /api/fleet/order) backed by a lock-protected persisted roster order in the supervisor. status()/list reads apply the saved order while reconciling every known member (host/local/remote) so additions/removals never lose members or create duplicates. The submitted order is validated as a complete permutation of current member ids under the state lock; duplicate, unknown, missing, or malformed lists are rejected without mutating saved order. Immutable ids, member config, remote credentials, and the hub-only routing contract are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 5 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
QA panel review — PASS
code-review-structural · head 2abddee9944a · formal
No defects were found by the panel, and the verifier confirmed the empty result. However, confidence is reduced on two angles: the cross-file review report was truncated (incomplete coverage of inter-module interactions), and the protoPatch structural pass failed to complete (no automated structural analysis was available). These are process gaps, not findings — they mean the panel's "clean" verdict carries less weight than it would with full coverage. No prior requests to disposition. If this PR touches non-trivial cross-module logic or structural invariants, consider a targeted re-review of those areas before merging.
No findings — the review came back clean.
findings JSON (machine-readable)
[]Review finding: _load_roster_order() called p.read_text() but caught only OSError / json.JSONDecodeError. Invalid UTF-8 bytes in roster.json raise UnicodeDecodeError from read_text() (before json.loads runs), which escaped the loader — 500-ing status() and GET /api/fleet instead of falling back to unsaved (discovery) order. Catch UnicodeDecodeError alongside OSError/JSONDecodeError, matching the _load_archetype_catalog posture already used elsewhere in this file. Adds a regression test writing raw invalid-UTF-8 bytes and asserting neither the loader nor status() raises, plus broader corrupt/wrong-shape tolerance coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
QA panel review — PASS
code-review-structural · head 2b5def151836 · formal
⚠️ PR advanced 1 commit(s) during this round (2b5def151836→53cdfa81d04f); 0 finding(s) in the delta were demoted to possibly addressed.
Low-risk change: the PR reorders status() output fields, which is a no-op when no order is saved. All five finders returned zero findings, and the verifier confirmed the empty array with no claims to ground. No panel disagreement, no verification changes, and no coverage gaps — the structural pass ran clean. Nothing to fix; the PR is clear to merge.
No findings — the review came back clean.
findings JSON (machine-readable)
[]There was a problem hiding this comment.
QA panel review — PASS
code-review-structural · head 53cdfa81d04f · formal
The panel produced no findings to verify — the verifier explicitly reports that no findings array was delivered to it, so the structural verification pass was skipped entirely. There is no dedup, no re-ranking, and no disposition work to do. The practical effect is a clean pass with zero coverage: no finder surfaced a defect, and the verifier could not confirm or refute anything. If this PR is non-trivial, treat the absence of findings as a coverage gap rather than a signal of correctness.
No findings — the review came back clean.
findings JSON (machine-readable)
[]Add manual roster reordering to FleetManagerPanel (Settings ▸ Agents) atop the PR #3200 backend: native HTML5 drag-and-drop plus keyboard-accessible move-up/move-down controls, submitting the complete immutable-id permutation to the hub-scoped PUT /api/fleet/order. Optimistic update with reconcile through queryKeys.fleet on both success and failure. Refs #3197 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Render manual roster reordering in the Settings → Agents fleet panel: a per-row drag handle (native HTML5 DnD) plus explicit move-up / move-down controls as the accessible, non-pointer equivalent. Reorders submit the COMPLETE immutable-FleetAgent.id permutation to the hub-scoped PUT /api/fleet/order (backend from #3200), update React Query optimistically, and invalidate queryKeys.fleet on both success and failure so the polling query stays authoritative. The pinned host ("this instance") carries no reorder controls — it stays a zero-button row (fixing the fleet.spec.ts:45 E2E regression) but remains a valid drop slot. No name, URL, token, process state, or immutable id is changed. Ordering math, boundary/busy disabling, accessible labels, and failure-safe reconciliation are exported as pure helpers and unit-tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…3227) * feat(console): drag-and-drop + accessible fleet roster reordering (#3197) Render manual roster reordering in the Settings → Agents fleet panel: a per-row drag handle (native HTML5 DnD) plus explicit move-up / move-down controls as the accessible, non-pointer equivalent. Reorders submit the COMPLETE immutable-FleetAgent.id permutation to the hub-scoped PUT /api/fleet/order (backend from #3200), update React Query optimistically, and invalidate queryKeys.fleet on both success and failure so the polling query stays authoritative. The pinned host ("this instance") carries no reorder controls — it stays a zero-button row (fixing the fleet.spec.ts:45 E2E regression) but remains a valid drop slot. No name, URL, token, process state, or immutable id is changed. Ordering math, boundary/busy disabling, accessible labels, and failure-safe reconciliation are exported as pure helpers and unit-tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(console): guard the fleet drag path while a reorder save is pending (#3197) The move-up/move-down controls already disabled via moveDisabled(..., reorder.isPending), but the drag handle stayed live during a pending save, so a second drag's drop could fire a concurrent full-order PUT — whose out-of-order completion, or an earlier failure's optimistic roll-back, could overwrite a later drag's intended order. Route both reorder paths through a single pure choke point, shouldSubmitOrder (order actually changed AND no save in flight), and disable the drag handle itself while pending (draggable={false}, dimmed, guarded onDragStart) to match the move buttons. Unit-test the new guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a persisted, id-based, hub-scoped fleet roster display order to the fleet control plane (ADR 0042).
graph/fleet/supervisor.py— newset_roster_order()/get_roster_order()plus aroster.jsonstore (sibling offleet.json, not mixed into its{id: record}map).set_roster_order()validates a submission as a complete permutation of the current member ids (host + local + remote) under the existing_state_lock(), rejecting non-list / non-string / blank / duplicate / unknown / missing ids without ever mutating saved order.status()now applies the saved order via_apply_roster_order(), which reconciles live membership: ordered members first, any member added since (or never ordered) keeps discovery order and follows, and a removed member simply drops out — every current member appears exactly once.operator_api/fleet_routes.py—PUT /api/fleet/orderaccepting{order: [id, …]}, offloaded to a thread, mappingFleetError→ HTTP 400; subsequentGET /api/fleetreturns members in the saved order.changelog.d/3197.added.md— release note.Ordering is presentation metadata only: immutable ids, member configuration, remote credentials, the state-lock discipline, and the hub-only routing contract are all unchanged.
Fixes #3197