2026-07-27
Sprint 14 Closes: A Second Opinion Pauses the Badge Work to Harden the Session Layer First
Coming off Sprint 12, the next thing queued was Sprint 13: milestone badges at 60 and 120 confirmations. Its core piece was already built and the sprint was paused mid-flight. The obvious move was to finish it. Instead I stopped and had the whole codebase reviewed with fresh eyes first, as a standalone pass with no feature to ship. The review came back with a written plan of what it thought was wrong and how to fix it. That plan is why this sprint exists, and why it's numbered 14 while 13 is still sitting half-done.
Checking the review's homework before trusting it
The first thing I did with the review was not act on it. I checked it. A review that hands you a confident list of problems is only useful if the problems are real, and the fastest way to burn a sprint is to go fix something the reviewer misread. So every claim in it got traced back to the actual code before anything moved. The findings held up, which was worth knowing for certain rather than assuming.
That verification became its own short document, split into what I agreed with, what I pushed back on, and what was worth digging into further. The pushback mattered as much as the agreement. One finding the review filed as a security problem turned out to be a code-cleanliness issue wearing a security costume: the endpoint it flagged was already locked down to admins, so the "hole" could only ever be opened by someone already fully trusted. That one got reclassified and pushed down the priority list instead of being rushed in alongside the genuine fixes. Sorting the real from the merely-plausible is the whole point of reviewing the review.
The decision: a hardening sprint, out of numeric order
The verified findings didn't fit into the badge work, so they became their own sprint, and that sprint got slotted in ahead of the rest of the badges rather than after. Two reasons. The first is plain: several of the findings were live session and login weaknesses, and there's no good argument for shipping more features on top of a login layer you already know has holes. The second is a sequencing one. One of the remaining badge stories needed to touch the exact same chunk of confirmation code that one of the hardening fixes was going to pull apart and restructure. Doing the restructure first meant the badge story would land on the clean version. Doing it second meant a feature change and a refactor fighting over the same file. Cheaper to reorder than to untangle.
What actually got fixed
Four pieces, each shipped as its own reviewed change.
The biggest was that account deactivation wasn't really enforced. Deactivating someone only stopped them at the login screen. Anyone already holding a login token kept full access until it expired, which could be a week out, and could even sign back in through Google or Facebook to mint a fresh one. So deactivation was cosmetic for up to seven days. The fix routes every token through one place that refuses to issue one to a deactivated account, and re-checks the account against the database on every single request rather than trusting the week-old token in hand. While in there, the review had spotted that account roles had the identical problem: demote an admin to a regular customer and they kept admin powers until their token aged out. Same shape of bug, same fix, so it got closed in the same pass. Now a demotion takes effect on the person's very next click.
The second: the login token was being passed back from Google and Facebook sign-in by putting it directly in the page's web address. Web addresses end up in browser history, in server logs, in the referrer header sent to the next site. A login credential should not be in any of those. The fix hands back a single-use code instead, which the app immediately trades for the real token out of view. Nothing sensitive in the address bar anymore.
The third was a rate limit. Only one feature in the whole app had any network-level cap on how often it could be hit, and it wasn't the login screen or the PIN pad, which are exactly the two places someone would sit and guess. Those got a cap. The PIN pad already had its own lockout logic; this just adds the outer fence that stops a machine from hammering the door in the first place.
The fourth wasn't a security fix at all, and that's the point of including it here. The confirmation logic, the beating heart of the whole app, had grown into a single five-hundred-line file that mixed the web plumbing with the actual rules. The next badge feature was going to make it worse. So it got pulled apart into a proper service with the rules on one side and the web plumbing on the other, with zero change to how anything behaves. The proof that nothing changed is that the entire existing test suite passed untouched. A refactor like that has no visible payoff on its own, which is exactly why it's easy to keep deferring, and exactly why the right moment to do it is right before the next feature that would otherwise deepen the mess.
Back to the badges
With the foundation solid, the badge work resumed where it paused, and the first piece was a cleanup the live-stamp logic couldn't do on its own. Badges only get awarded at the moment a confirmation crosses a threshold, going forward. Anyone who'd already blown past 60 or 120 before the feature existed would have none, which means the app could show someone sitting at 160 confirmations with an empty trophy case. The fix is a routine that runs on startup, finds those people, and grants the badges they'd already earned, dated to the confirmation that actually crossed the line rather than to today. It's safe to run on every boot because it only ever fills in what's missing, and it leaves anything already awarded exactly as it was. It builds directly on the service that the hardening sprint had just cleanly carved out, which is the payoff from doing that refactor first showing up one story later.
The PM angle
The interesting decision this sprint wasn't any single fix. It was letting an outside look reorder the plan. It's tempting to treat a roadmap as a commitment and a review as a distraction from it, and to file whatever the review finds as "later." The opposite move, pausing the feature you were about to start because a fresh pass found something more important, is uncomfortable in the moment and almost always right when the finding is real.
Two smaller habits sat underneath it. Verifying the review instead of obeying it, so the one finding that was overstated got demoted rather than rushed in next to the genuine ones. And spending a sprint's worth of effort on work with no demo at the end of it, the deactivation enforcement and the refactor especially, because "nothing visibly changed" and "nothing was worth doing" are not the same statement. The badge backfill landing cleanly the very next story is what a well-sequenced refactor looks like from the outside: quiet when it happens, and then obvious in hindsight.
Where the project stands
Sprint 14 was four issues (#146 to #149) across four pull requests (#151 to #154), all merged onto master in order, milestone closed. Backend suite at close: 456 passing. Every piece was checked against a real database, not only the fast in-memory test double: a still-valid token going dead the instant its account is deactivated, a demoted account losing its powers on the next request, and a burst of rapid logins tripping the new limit exactly where it should. The one thing still marked manual is the same class as always, a real phone completing a live sign-in through Google or Facebook end to end.
Then Sprint 13 picked back up. The badge backfill shipped as #139 (pull request #156), bringing the backend suite to 462 passing, verified against a live database by planting a customer who'd earned a badge the old way, restarting, and watching the backfill grant it with the right original date and then leave it alone on the next restart. What's left of the badge sprint is the earned-moment push, the read endpoints, and the two screens that show it all off. The two cleanup items the review turned up but didn't rate as urgent are written down and waiting their turn, not lost.
Comments
Loading comments...