Skip to content

Fix the time-coupled failures: main was red with no code change - #7

Merged
sylvesterdamgaard merged 2 commits into
mainfrom
fix/paywall-opaque-id
Aug 3, 2026
Merged

Fix the time-coupled failures: main was red with no code change#7
sylvesterdamgaard merged 2 commits into
mainfrom
fix/paywall-opaque-id

Conversation

@sylvesterdamgaard

Copy link
Copy Markdown
Contributor

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

OrganizationSeeder used literals (2026-07-012026-07-31). Past that month every seeded subscription's period had lapsed, so nothing served and entitlements resolved null — a developer running composer setup:local got a console full of customers with no working subscriptions. Anchored to the current month.

2. A production bug: included allowance read the wall clock

WalletIncludedAllowanceResolver defaults its clock to microtime() 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.

$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.
@sylvesterdamgaard
sylvesterdamgaard merged commit b422dff into main Aug 3, 2026
2 checks passed
@sylvesterdamgaard
sylvesterdamgaard deleted the fix/paywall-opaque-id branch August 3, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant