Upgrade Decision Matrix: Criticality and Type, Not Date
An EOL date gives the upgrade ceiling, not the order. A 3×3 matrix crosses system criticality with trigger type and gives each cell a response window.
TL;DR
The upgrade order is set not by the end-of-support date but by the system's criticality together with the trigger's type (mandatory, locking, opportunity); the same CVE is an opportunity in an internal tool and mandatory in a payment flow. A 3×3 matrix gives each cell a rank and a response window, and the EOL date is only the ceiling. Because the type drifts over time, the matrix is re-read in every planning round.
An upgrade queue sorted by date can push the mandatory signal of your most critical system to the bottom.
Picture a queue like this. You have an inventory and you have an EOL list; you merge them and sort by date. At the top sits the row whose EOL is nearest: an internal reporting tool used only by the team. At the bottom sits the row with no date, or the most distant one: a CVE that affects you in the payment flow.
The sorting worked correctly. What was wrong was what it sorted.
Part 2 drew the line from the start: the inventory doesn’t make decisions, it produces input for the decision matrix. The date is one column of that input, nothing more. Without knowing what a system stops when it goes down, and what type of signal you are holding, a date can’t put the queue in order.
The date is only an upper bound; the order is set by the system’s criticality and the trigger’s type together.
The Same CVE, Two Different Cells
Suppose two systems use the same package, and a CVE is published for one component of that package. The two questions from Part 1 are the tiebreaker here: am I using the affected component, and can the affected code path be reached from outside?
- Internal reporting tool. The component is there too, but only the team reaches the tool, and only from the company network. The second answer is “no”. The signal is an opportunity; a “not affected” record is filed.
- Payment flow. The same component sits behind an endpoint reachable from the internet. Both answers are “yes”. The signal is mandatory.
Same package, same CVE, same severity score. Different order. Severity measures how bad the component is; it doesn’t measure what your system would lose if it went down. That’s why severity alone doesn’t set the order.
Type is a property of signal × system: criticality labels the system, and type is the value the signal takes on when it lands on that system. The matrix crosses the two.
The “opportunity” label on the internal tool isn’t a permanent verdict either. If the tool is opened to the internet tomorrow, or an exploit is published before a fix arrives, both answers become “yes” and the signal moves to mandatory. That’s why the record should carry the reasoning, not the result: the sentence “can’t be reached from outside” also tells you under what condition to look again.
Three Classes, Three Types, Nine Cells
First the axes. Criticality is defined by what the system stops when it goes down. I use three classes:
- Critical: stops revenue or the customer flow (payment, login, orders).
- Medium: stops the work of a team or a customer segment, while the main flow continues (admin panel, reporting, integration).
- Low: nobody notices for days when it stops (internal tool, one-off batch).
The type axis is familiar from Part 1: mandatory, locking, opportunity. The order comes out in two steps: type first, then criticality within the type. The windows in the cells are my recommendation, not an industry standard. Shift them to your team’s capacity, but write a window down.
| Mandatory | Locking | Opportunity | |
|---|---|---|---|
| Critical | 1 · triage the same business day, patch within 7 days; on EOL, at least 3 months before the end date | 4 · into this quarter’s plan | 7 · into the quarter’s plan if there is a measured gain |
| Medium | 2 · triage within 1 business day, patch within 14 days; on EOL, at least 2 months before the end date | 5 · into next quarter’s plan | 8 · backlog |
| Low | 3 · triage within 1 week, patch within 30 days; on EOL, at least 1 month before the end date, or shut the system down | 6 · before the next major | 9 · no date |
Placing a signal in a cell takes three steps: determine the system’s class, take the signal’s type from the table in Part 1, and read the rank and window at the intersection. If several signals land in the same cell, the one with the nearer EOL date goes first. Between cells, the date doesn’t set the order; it can only narrow the window.
I won’t walk through all nine cells one by one; two corners are enough.
Critical × mandatory is rank one: the most expensive system when it goes down, and a signal that can’t be made to wait. This is exactly where the row that sank to the bottom of the opening queue belongs. The date column couldn’t give it a rank, because a CVE doesn’t fit a calendar.
Low × opportunity is rank nine. That doesn’t mean “never do it”, it means “don’t give it a date”: it gets done when you’re already in that repo for some other job. The function of rank nine is to keep this work from swelling the queue and slowing down the rank-one job.
The middle cells sit between the two corners, and this is where most of the arguments start: Medium × mandatory versus Critical × locking. My recommendation is to put the mandatory one first. A mandatory signal’s date or exploitation risk moves independently of you; a lock may break nothing today, but it has a budget on a quarterly scale. If the lock’s budget is about to run out, breaking this rule is a decision that belongs to you, and the matrix doesn’t forbid it.
If a window and an EOL collide, the EOL wins. The duration in the cell is the rule itself, the EOL date is the ceiling; whichever comes earlier applies. The 30 days of the Low × mandatory cell drops to 20 days if 20 days remain until the end date.
EOL also has a gap between “upstream is finished” and “no fix will come”. Debian 12’s EOL date is 12 July 2026; according to the same page, LTS continues until 30 June 2028. If you rely on LTS or on another extended support (ELS) provider, your ceiling is that date. But reading which packages are covered, and writing the note “relies on LTS” into the cell, is your job.
Cells Don’t Stay Put
The matrix is a photograph; signals move. Type drifts over time: an opportunity turns into a locking one, a locking one into a mandatory one. The mechanism is simple: a deferred opportunity narrows the options, and a narrowing option runs into a date.
Picture a medium-criticality system running on PHP 8.2. When a new major version of a package you use comes out, moving to it is an opportunity; the old major still works, nothing breaks. When the package stops shipping fixes to the old major, the same job turns into a locking one. If a security fix arrives only in the new major, the lock turns into a mandatory one.
The runtime row doesn’t walk this path, because on the day the EOL date is published the signal is already mandatory. php.net says 8.2’s security support ends on 31 December 2026; that is 53 days after this post’s publication date. The Medium × mandatory cell wants the work finished at least 2 months before the end date; that window closed before this post was published.
Two clocks run at once in the same system. The Laravel 12.x release notes say Laravel 12’s security fixes continue until 24 February 2027. The runtime row and the framework row hit their ceilings 55 days apart. A single “PHP 8.2” row squeezes these two ceilings into one date.
The second axis drifts too, but for a different reason: criticality changes not with time but with the work. An internal tool that opens an endpoint into the payment flow moves from Low to Critical that day. The code stays the same, its class changes.
The direction of drift is one-way: an opportunity walks toward mandatory on its own and doesn’t come back. So a “we’ll look at it later” decision isn’t free; every round moves the cell one step closer to mandatory. A deferred job doesn’t stay cheap, it moves toward an expensive date.
That’s why you shouldn’t fill in the matrix once and bury it in the queue. In every planning round, re-read each signal’s cell. Keep the cell next to the signal (an issue label, a PR description); then drift isn’t a surprise, it’s the first item of the round.
The Limit of This Framework
The matrix gives the order. It doesn’t give the target version: whether it’s 8.3, 8.4 or 8.5 is decided in Part 4. It doesn’t tell you whether you’re ready either: your tests, your way back and your dependency map are in Part 5.
Being first in line doesn’t mean being ready to start. The matrix says “this first”; it doesn’t say “this today”. If the system at rank one has no tests, the first job may not be the upgrade but writing those tests; Part 5 will draw that distinction.
The matrix also doesn’t tell you, in your place, which class a system falls into. “Is this system Critical or Medium?” is the first argument in most teams. If you keep the definition question as “what stops when it goes down?”, the argument gets shorter: the answer should point to a workflow, not to a product feature.
The three classes are deliberately coarse. There is no risk score or formula here; a team that needs a formula can split the classes into four, and the logic of the matrix doesn’t change.
Reorder the Queue
Back to the start. The internal reporting tool was at the top, the CVE in the payment flow at the bottom. With the matrix the order flips: payment × mandatory comes first, and the internal tool, even though its EOL is near, drops to third, into its own window. The date didn’t take the internal tool out of the queue; it only gave up deciding its position.
A queue that moves in date order makes the work look calm: every row has a day, none of them is wrong. The matrix breaks that calm, because it pulls some rows forward without looking at their date. It’s uncomfortable, but it’s correct.
The cost of getting the order wrong is written in Part 0. Part 1 sorted the signals into types, and Part 2 sorted the system into layers. This part turned the two into an order.
A date can be a reason to enter the queue; it isn’t enough to set the order.
Sort the systems in your inventory into three criticality classes, place the upgrade signals in your queue into the matrix, and take the one in the top cell into this sprint.