Skip to content

The Trial is judged against the newest App Store date seen, not only the iPhone's clock (#936) - #965

Merged
Skeyelab merged 1 commit into
mainfrom
fix/936-trial-clock
Oct 27, 2026
Merged

Skeyelab merged 1 commit into
mainfrom
fix/936-trial-clock

Conversation

@Skeyelab

Copy link
Copy Markdown
Member

Fixes #936 (code part; the device check stays open).

What

ProStore keeps the newest signedDate from a verified StoreKit entitlement or transaction update, saves it in UserDefaults (so it survives a relaunch), and passes max(Date(), newest) as now to the license. A clock held still or set back can no longer keep the Trial alive once the App Store has signed a later date. Entitlement.tier is unchanged, so the #935 rules hold (a now more than a day before the start reads Free; days left are capped at 14).

New SignedClock in Recording (so it's testable without StoreKit): observe (an older date never replaces a newer one), now(clock:), and save/load. Only a verified result's date counts, since an unverified one could be forged. ProStore(status:) (tests) starts with an empty SignedClock, so a test doesn't depend on the device's saved date.

Does signedDate advance on cached entitlements offline?

Found, from Apple's docs (not measured on a device): signedDate is "the date that the App Store signed the JWS transaction". A cached entitlement is the same signed value, so it keeps its old date while the phone is offline; it moves only when the App Store hands back a fresh one. I could not run StoreKit offline here.
So the fix helps once the phone has been online after the clock was held. A phone kept offline the whole time with the date held is not caught, and I did not try to work around that (no network time call, per the brief).

Tests (red first)

SignedClockTests. Red run against a stub that ignored the signed date: 3 of 5 failed (held clock at day 2 with a signed day 20 still read as a Trial; an older date replaced nothing, a newer one was not kept; the saved date was not read back). The other 2 (no signed date seen; clock ahead of the signed date) passed on the first run, since they describe today's behavior. After the change all 5 pass.

Full ./scripts/verify.sh passes (App/ changed).

Checked only by tests / reading

The ProStore wiring (where the date is read and written) is checked by reading and by the build, not by a test: it needs a StoreKit seam I did not add. Device check still open for Eric: with automatic time off and the date held, the Trial still ends 14 days after it started, after the phone has been online once.

…the iPhone's clock (#936)

Assisted-by: Claude Code:claude-sonnet-5-5
@Skeyelab
Skeyelab enabled auto-merge (squash) October 27, 2026 02:42
@codecov

codecov Bot commented Oct 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Skeyelab
Skeyelab merged commit 0a20879 into main Oct 27, 2026
11 checks passed
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.

The Trial never ends if the iPhone's clock is held still inside it

1 participant