Skip to main content
Back to blog

A FinOps Recommendation Queue Needs One Non-Terminal State

Bableen Kaur
Bableen Kaur Engineer · Zop.Dev
8 min read
A FinOps Recommendation Queue Needs One Non-Terminal State

A FinOps Recommendation Queue Needs One Non-Terminal State

A recommendations engine with 450+ rules produces more findings than any one person will resolve in a sitting. That is not a defect. It is the point: the engine looks at 100% of your resources every day, and a human looks at some of them, sometimes.

The defect is what happens to the findings a reader picks up and cannot finish, which on a busy estate is most of the dozen they open in an hour. ZopNight now lets a recommendation be bookmarked, which sounds like a convenience and is actually the missing state in the queue.

Every Status in a Recommendation Queue Is Terminal Except the One You Need

Look at what a reader can do with a finding they have just opened and do not yet understand.

ActionWhat it assertsCost of being wrong
ApplyThis change is safe to make nowAn unreviewed change lands in production
DismissThis finding is not worth acting onA real saving is written off permanently
CloseThis is resolvedThe queue reports work that did not happen
Leave itNothingIt scrolls away and is never seen again

Every one of those except the last is terminal, and the last one is not a decision at all. A reader who needs to check with the service owner before stopping a database has no option that means “I am on this, ask me Thursday”. So they do the cheapest thing available in the moment, which is to leave it, and the finding falls back into a list of hundreds sorted by something other than their intent.

A triage state is a marker a reader applies to a finding that records attention without recording a decision, so the item can be found again without being resolved, dismissed, or silently abandoned in a queue. Bookmarking is that state, and adding it costs the queue nothing: it does not change savings-driven severity, it does not change what the engine believes, and it does not touch the savings figure.

FinOps is the practice of giving engineering, finance and product teams shared, current visibility into cloud spend, so cost decisions happen where the usage is created. A queue of findings nobody can hold onto is where that practice quietly stalls. The bookmarks are per-user. That is the right scope for a state that means “I am working on this”, because two engineers looking at the same estate are working on different things, and a shared flag would turn a personal working set into a small piece of unowned shared configuration nobody maintains.

A Personal Filter Has to Be Resolved Server-Side or It Is Not a Filter

The easy implementation of bookmarks is a local list of ids and a filter applied in the browser. It works in a demo with forty findings and breaks the moment the list is paginated.

Architecture diagram

Filtering the page is not filtering the list. A reader with twelve bookmarks across an estate sees the three that happen to fall on the current page, concludes they have three, and works from a set that is quietly wrong. Worse, the count changes as they page around, which reads as a bug in the bookmarks rather than in the filter.

So the filter is a server-side bookmarked=true, and the parsing is done once in the shared query parser rather than per endpoint. That detail is what makes it hold: all ten list handlers honour it, including the ones nobody was thinking about when the feature was designed. A filter implemented per handler is a filter that works on the two surfaces that were tested and silently returns unfiltered results on the other eight, which is the same class of bug as a filter that is accepted and ignored.

The History Panel Opens on the Rule, Not the Recommendation Id

Bookmarks give a reader a way back to a finding. The recommendation history panel is what they see when they get there, and it now opens narrowed to the clicked recommendation’s rule rather than its id.

The distinction matters because findings are not stable objects. An idle instance is flagged, somebody stops it, the finding closes. Three weeks later it is started again and the same rule raises a new recommendation with a new id. Narrowing by id shows one third of a story and presents it as the whole story.

Architecture diagram

There is a matching guard for the case where the clicked rule is not among the filter’s options. The panel leaves the filter empty and shows the whole history, rather than applying a filter that matches nothing. An empty panel and a panel filtered to zero look identical to a reader, and only one of them is recoverable without knowing how the filter works.

A Filter That Removes Nothing Is Worse Than No Filter

Three surfaces in the history panel were making claims they could not support, and the repair was to remove two of them outright.

SurfaceWhat it claimedWhat it did
Date-range filterNarrowing the history to a periodNothing; the list was unchanged
Event-type filterNarrowing to a kind of eventNothing; the list was unchanged
Rule filterNamed an internal code such as RC-109Filtered correctly, but nobody could tell which finding a code referred to
Panel footnoteThree statements about dates and statusesAll three were wrong

Removing a filter is a harder call to make than fixing one, and it is usually the right one when the filter has been shipped in a state where it lies. A control that reports narrowing while removing nothing does more damage than its absence: the reader believes they are looking at a subset, reasons about what is missing from that subset, and draws a conclusion about their estate from a list that was never filtered.

The rule filter was kept and made legible: it names the finding rather than the code. And every card now carries its own activity feed, so the panel does not depend on a single merged timeline to explain what happened to any individual recommendation.

The footnote is the smallest item on the list and the most instructive. It was three sentences of explanatory text about how dates and statuses behaved, all three of which were untrue. Explanatory copy ages faster than the code it explains, because nothing fails when it drifts, and a reader trusts it more than the interface itself precisely because it looks like somebody wrote it on purpose.

Select All and Select None Render the Same Cards, Deliberately

The rule filter is multi-select, with a select-all row that flips to clear-selection once everything is ticked. Ticking every rule and ticking no rule produce the same cards, and that equivalence is a choice rather than an accident.

The alternative is to treat an empty selection as “hide everything”, which is defensible in the abstract and hostile in practice. A reader who unticks the last box lands on a blank panel with no obvious way to tell whether the filter is working or the data is gone. Making empty mean everything costs one edge case and removes the only state the panel could get stuck in.

The pager follows the same rule and scopes to the filtered list, so a next or previous arrow can never step onto a card the filter is hiding. A pager over the unfiltered set is the version of this bug that survives review, because it only shows up when somebody filters and then pages, which is exactly what a reader does with a large history and never what anyone does while testing a filter.

This works when a bookmark means “I am working on this” and stays personal. It breaks the moment bookmarks are treated as a shared backlog, because a per-user flag has no owner, no age and no review. The panel header also went from three lines to one: resource type and identifier, with the identifier a click-to-copy token. That is not a design flourish. A reader who arrived here from a bookmark is holding an identifier they need to paste into a terminal or a ticket, and the fastest path between the two is worth more than the two lines of context that were removed to make room.

Tagged
Bableen Kaur

Bableen Kaur

Engineer · Zop.Dev

Bableen works on the Kubernetes side of Zop.Dev, focused on cluster ops, autoscaling, and the long tail of pod-level reliability work. She writes about MTTR, OOMKill diagnosis, and what runbooks actually need to do.

Stop watching the waste.
Start cutting it.

See. Find. Fix. Automatic.

Connect your first cloud account in under 5 minutes. See your first remediation in under 7. No credit card required.

CDCR connect detect classify remediate
full audit every action traceable
read-only default access
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·