You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #423. Withdrawing a plan hard-deleted the row, so "we were going to 2.61 in March, then dropped it" left no trace of ever having been decided.
Everything else in UPG is retained: a replaced plan keeps superseded_at, a met plan keeps met_at, and that history is what makes "how long do our upgrades really take" answerable. Withdrawal was the one hole in it.
Now a topical soft delete, withdrawn_at / withdrawn_by, with every open-plan query filtering on withdrawn_at IS NULL.
Two things worth arguing with:
The partial unique index had to move.upgrade_plans_one_open_per_group was WHERE met_at IS NULL AND superseded_at IS NULL. Left as it was, a withdrawn plan keeps holding the group's one open slot and blocks recording a replacement, which is the opposite of what withdrawing is for. The migration drops and recreates it with withdrawn_at IS NULL in the predicate.
The down migration deletes withdrawn plans rather than reopening them, and is marked DESTRUCTIVE. Reopening them would fail the restored narrower index for any group that withdrew one plan and then recorded another.
withdraw() returns Option<Self> and filters on the plan still being open, so withdrawing twice is a no-op rather than restamping who did it.
Nothing changes for the operator. The fleet view keys off all_open, so a withdrawn plan drops to the unplanned list exactly as before, and the existing Playwright test for that passes untouched.
UI. There wasn't any: for_group existed but nothing rendered it. The upgrades page now has a Past plans section, most recently closed first, showing where each deployment was going, the date it was planned for, and how it ended. Withdrawn reads differently from met, and the chip's tooltip carries who withdrew it. Fed by a new fleet-wide /api/upgrade_plans/history.
MCP. There was no upgrade-plan surface at all, so it's two read-only tools rather than a withdrawal-shaped patch. list_upgrade_plans gives every group's open plan with the version it runs, the version it's going to, the planned date and late, plus the groups with nothing recorded. get_upgrade_plan_history takes a group and gives every plan it has had, each with its outcome (open, met, replaced, withdrawn) and who recorded, amended or withdrew it. The server instructions now say a late plan is normal operational reality, so an agent doesn't file one as an incident.
The outcome derivation (PlanOutcome, outcome(), ended_at()) sits in the database crate, so the web view and MCP can't drift on what withdrawn means. UPG and MCP specs updated, endpoint and tool tests added, plus two more Playwright cases.
Screenshots are seeded e2e data, on assets/upgrade-plan-history so they render here; happy to drop that branch if you'd rather not have it hanging around.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #423. Withdrawing a plan hard-deleted the row, so "we were going to 2.61 in March, then dropped it" left no trace of ever having been decided.
Everything else in UPG is retained: a replaced plan keeps
superseded_at, a met plan keepsmet_at, and that history is what makes "how long do our upgrades really take" answerable. Withdrawal was the one hole in it.Now a topical soft delete,
withdrawn_at/withdrawn_by, with every open-plan query filtering onwithdrawn_at IS NULL.Two things worth arguing with:
The partial unique index had to move.
upgrade_plans_one_open_per_groupwasWHERE met_at IS NULL AND superseded_at IS NULL. Left as it was, a withdrawn plan keeps holding the group's one open slot and blocks recording a replacement, which is the opposite of what withdrawing is for. The migration drops and recreates it withwithdrawn_at IS NULLin the predicate.The down migration deletes withdrawn plans rather than reopening them, and is marked DESTRUCTIVE. Reopening them would fail the restored narrower index for any group that withdrew one plan and then recorded another.
withdraw()returnsOption<Self>and filters on the plan still being open, so withdrawing twice is a no-op rather than restamping who did it.Nothing changes for the operator. The fleet view keys off
all_open, so a withdrawn plan drops to the unplanned list exactly as before, and the existing Playwright test for that passes untouched.