Fix the time-coupled failures: main was red with no code change - #7
Merged
Conversation
$periodStart/$periodEnd were literals (2026-07-01 → 2026-07-31), which gave the seeded dataset a hard expiry. Once the wall clock passed that month every seeded subscription's current period had lapsed, so nothing served: entitlements resolved null, the upgrade gate picked the wrong plan, and a developer running `composer setup:local` got a console full of customers with no working subscriptions. It also meant the test suite started failing on a day nobody changed any code — six tests went red between 27 July and 3 August with an identical tree. Anchoring the period to the current month, and the trial to a window genuinely spanning today, fixes three of them. The remaining three are a DIFFERENT instance of the same class and are not fixed here — see the branch notes. They are pre-existing on main, not introduced by this change.
…he wall clock WalletIncludedAllowanceResolver defaults its clock to `microtime()`, and it uses that instant to decide which wallet lots have EXPIRED. The app never injected one, so the included allowance was always computed against real time. That is a production defect, not a test detail: this app has virtual test clocks. A sandbox whose clock is advanced past a period boundary asked the wallet what was expired ACCORDING TO THE CALENDAR, not according to the virtual now the rest of the plane runs on — which is exactly what a test clock exists to control. Now injected with BillingClock, the seam that already returns real now in live mode and virtual now while a test clock advances. It surfaced as a suite that went red on a day nobody changed any code: the e2e test freezes Carbon mid-July, grants a lot expiring 1 August, and once the real date passed that the resolver reported the allowance as gone. The lot was present and in window the whole time — the read was asking a different clock. Also freezes the clock in the two proration tests. They assert a prorated charge is below the full delta, which is only meaningful at a known point in the period; run on the 3rd of a month the remaining fraction is ~90% and the assertion is simply false. Mid-period now, so the result is the same every day of the year. Together with the seeder anchoring in the previous commit, the suite is green again and no longer depends on the date it runs.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Six tests went from green to failing between 27 July and 3 August on an identical tree. Three distinct causes, all wall-clock coupling.
1. The demo dataset had a hard expiry
OrganizationSeederused literals (2026-07-01→2026-07-31). Past that month every seeded subscription's period had lapsed, so nothing served and entitlements resolved null — a developer runningcomposer setup:localgot a console full of customers with no working subscriptions. Anchored to the current month.2. A production bug: included allowance read the wall clock
WalletIncludedAllowanceResolverdefaults its clock tomicrotime()and uses that instant to decide which wallet lots have expired. The app never injected one.That matters beyond tests: this app has virtual test clocks. A sandbox advanced past a period boundary asked the wallet what was expired according to the calendar, not according to the virtual now the rest of the plane runs on — which is precisely what a test clock exists to control. Now injected with
BillingClock.It surfaced as the e2e test freezing Carbon mid-July, granting a lot that expires 1 August, and reporting the allowance gone once the real date passed. The lot was present and in window the whole time; the read was asking a different clock.
3. Proration tests used the real clock
Both assert a prorated charge is below the full delta — only meaningful at a known point in the period. On the 3rd of a month the remaining fraction is ~90% and the assertion is false. Frozen mid-period.
Gate
pint · PHPStan level max (0 errors) · 1032 tests — green, and no longer dependent on the date it runs.